Advanced Modding - Vanilla Rendering

Goal

I want to show you which methods of vanilla Minecraft can be used to render some objects onto the screen.

Difficulty


4/10 - Moderately Easy

Prerequisites

  • A renderer class (e.g. TESR)

Forge Version

This Tutorial was created with Forge 11.14.3.1450 for Minecraft 1.8. If anything doesn't work with other versions, please contact me!


Modifying the viewport and render state

Before we can start rendering our objects, we need to know a bit about render states.

Minecraft's rendering is based on OpenGL which offers a great variety of settings we can play with.

There are some very important methods that need to be known when creating a renderer class.

pushMatrix() and popMatrix()

Those two methods are probably the most important ones in rendering because they store and restore the viewport's transformation.

If you want to translate, scale or rotate your model, you always need to place a pushMatrix() before and a popMatrix() after the rendering method. The methods store the transformation and restore it afterwards, so your code cannot mess up other renderers.

To call those methods simply use this syntax:

Rendering Code:
GlStateManager.pushMatrix();
//Rendering code goes here!
GlStateManager.popMatrix();

Remember that you always need both methods, otherwise you'll get a GLStackOverflow or GLStackUnderflow error.

translate(), rotate(), scale()

Translation, rotation and scale are the three transformations that can be applied to the rendering matrix.

Their function is pretty self-explanatory: Translate offsets the origin, rotate rotates the axis around the origin and scale scales the axis depending on the origin.

As an example, I'll show you rendering results of a TileEntity that has been transformed using those methods. In every example, the origin is located in the back left corner of the block. The x axis is pointing towards the player and the z axis is pointing to the right.

No transformation - Show
Translation - Show
Rotation - Show
Scale - Show

By combining those transformations, you can modify your model to many different positions.

Blending

If your textures are halfway transparent, you need to enable blending. If blending is disabled, half-transparent textures are displayed opaque or fully transparent (invisible), depending on each pixels alpha value. Pixels with an alpha value greater than 50% are visible, the rest is transparent. When blending is enabled, the half-transparent pixels are rendered as they are.

To en-/disable blending, call these methods:

Rendering Code:
GlStateManager.enableBlend();
GlStateManager.disableBlend();

The WorldRenderer

The WorldRenderer is the class used to render the objects onto the screen. The first thing we need to do in order to use it is to get an instance:

Rendering Code:
WorldRenderer wr = Tessellator.getInstance().getWorldRenderer();

With this instance we can render our objects.

To render a face, we first need to start the world renderer.

Rendering Code:
wr.startDrawingQuads();

Now, we can render quadrilateral faces. If we want the face to be textured, we need to bind a texture using this method.

Rendering Code:
Minecraft.getMinecraft().getTextureManager().bindTexture(new ResourceLocation("modid:path/to/image.png"));

Note that you can only define one texture per drawing call.


The corners of the face are defined as vertices. For a quadrilateral face, we need to add four vertices which contain position and UV coordinates for the position of the texture.

Additionally, we need to set the face's normal vactor before rendering it. The normal is the direction the face is pointing into. This is needed for lighting and shadow, so it improves the rendering result if we add it.

Rendering Code:
wr.setNormal(0, 0, -1); // Normal towards -z

When the normal is set, we can add the four vertices. They need to be ordered counter-clockwise, starting at the lower-left vertex.

These are the methods for a face from (1|0|0) to (0|0.5|0) pointing into -z direction.

Rendering Code:
                //  X    Y    Z    U    V
wr.addVertexWithUV(1.0, 0.0, 0.0, 0.0, 1.0); // Bottom left
wr.addVertexWithUV(0.0, 0.0, 0.0, 1.0, 1.0); // Bottom right
wr.addVertexWithUV(0.0, 0.5, 0.0, 1.0, 0.5); // Top right
wr.addVertexWithUV(1.0, 0.5, 0.0, 0.0, 0.5); // Top left

Once we rendered our faces (or if we want to change textures) we need to stop the renderer. This can be done using this method.

Rendering Code:
Tessellator.getInstance().draw();

Do not  use wr.finishDrawing() here!


With those methods we can assemble quite complex rendering methods. But there are also some more things the world renderer can do.

Color

It is possible to assign a color to the world renderer, for instance to tint a texture. This is done using one of these methods.

Rendering Code:
wr.setColorOpaque_F(float r, float g, float b); // Values btw 0-1
wr.setColorRGBA_F(float r, float g, float b, float a); // Values btw 0-1

Brightness

Normally, the world renderer uses a brightness value that fits the current environmental lighting. However, it is possible to set that value manually. The most common usage for this (probably the only one) is to render a face that has a constant brightness, no matter how dark it is. To make a face self-glowing, set the brightness to 240. I'm actually not really sure how the value is calculated, but as long as it works... If someone out there knows more than me, please tell me.

Rendering Code:
wr.setBrightness(240);

Sample code

Here I'll provide some sample code to show you how it would look like to render a TileEntity with vanilla code.

The code renders the TE you've seen above in the images about transformation.


Rendering Code:
@Override
public void renderTileEntityAt(TileEntity te, double x, double y, double z, float partialTick, int destroyStage) {
        GlStateManager.pushMatrix();

        GlStateManager.translate(x, y, z);

        WorldRenderer wr = Tessellator.getInstance().getWorldRenderer();
        wr.startDrawingQuads();

        // Base
        Minecraft.getMinecraft().getTextureManager().bindTexture(new ResourceLocation("tutorial:textures/blocks/tile_entity_tutorial_base.png"));
        // -Z
        wr.setNormal(0, 0, -1);
        wr.addVertexWithUV(1.0, 0.0, 0.0, 0.0, 1.0);
        wr.addVertexWithUV(0.0, 0.0, 0.0, 1.0, 1.0);
        wr.addVertexWithUV(0.0, 0.5, 0.0, 1.0, 0.5);
        wr.addVertexWithUV(1.0, 0.5, 0.0, 0.0, 0.5);
        // +Z
        wr.setNormal(0, 0, 1);
        wr.addVertexWithUV(0.0, 0.0, 1.0, 0.0, 1.0);
        wr.addVertexWithUV(1.0, 0.0, 1.0, 1.0, 1.0);
        wr.addVertexWithUV(1.0, 0.5, 1.0, 1.0, 0.5);
        wr.addVertexWithUV(0.0, 0.5, 1.0, 0.0, 0.5);
        // -X
        wr.setNormal(-1, 0, 0);
        wr.addVertexWithUV(0.0, 0.0, 0.0, 0.0, 1.0);
        wr.addVertexWithUV(0.0, 0.0, 1.0, 1.0, 1.0);
        wr.addVertexWithUV(0.0, 0.5, 1.0, 1.0, 0.5);
        wr.addVertexWithUV(0.0, 0.5, 0.0, 0.0, 0.5);
        // +X
        wr.setNormal(1, 0, 0);
        wr.addVertexWithUV(1.0, 0.0, 1.0, 0.0, 1.0);
        wr.addVertexWithUV(1.0, 0.0, 0.0, 1.0, 1.0);
        wr.addVertexWithUV(1.0, 0.5, 0.0, 1.0, 0.5);
        wr.addVertexWithUV(1.0, 0.5, 1.0, 0.0, 0.5);
        // -Y
        wr.setNormal(0, -1, 0);
        wr.addVertexWithUV(0.0, 0.0, 1.0, 0.0, 1.0);
        wr.addVertexWithUV(0.0, 0.0, 0.0, 1.0, 1.0);
        wr.addVertexWithUV(1.0, 0.0, 0.0, 1.0, 0.0);
        wr.addVertexWithUV(1.0, 0.0, 1.0, 0.0, 0.0);
        // +Y
        wr.setNormal(0, 1, 0);
        wr.addVertexWithUV(1.0, 0.5, 1.0, 0.0, 1.0);
        wr.addVertexWithUV(1.0, 0.5, 0.0, 1.0, 1.0);
        wr.addVertexWithUV(0.0, 0.5, 0.0, 1.0, 0.0);
        wr.addVertexWithUV(0.0, 0.5, 1.0, 0.0, 0.0);
        Tessellator.getInstance().draw();

        GlStateManager.translate(0.5, 0.5, 0.5);
        GlStateManager.scale(0.5, 0.5, 0.5);
        GlStateManager.rotate((te.getWorld().getTotalWorldTime() + partialTick) * 3, 0, 1, 0);
        GlStateManager.translate(-0.5, 0, -0.5);

        // Core
        wr.startDrawingQuads();
                
        Minecraft.getMinecraft().getTextureManager().bindTexture(new ResourceLocation("tutorial:textures/blocks/tile_entity_tutorial_core.png"));
        // -Z
        wr.setNormal(0, 0, -1);
        wr.addVertexWithUV(1.0, 0.0, 0.0, 0.0, 1.0);
        wr.addVertexWithUV(0.0, 0.0, 0.0, 1.0, 1.0);
        wr.addVertexWithUV(0.0, 1.0, 0.0, 1.0, 0.0);
        wr.addVertexWithUV(1.0, 1.0, 0.0, 0.0, 0.0);
        // +Z
        wr.setNormal(0, 0, 1);
        wr.addVertexWithUV(0.0, 0.0, 1.0, 0.0, 1.0);
        wr.addVertexWithUV(1.0, 0.0, 1.0, 1.0, 1.0);
        wr.addVertexWithUV(1.0, 1.0, 1.0, 1.0, 0.0);
        wr.addVertexWithUV(0.0, 1.0, 1.0, 0.0, 0.0);
        // -X
        wr.setNormal(-1, 0, 0);
        wr.addVertexWithUV(0.0, 0.0, 0.0, 0.0, 1.0);
        wr.addVertexWithUV(0.0, 0.0, 1.0, 1.0, 1.0);
        wr.addVertexWithUV(0.0, 1.0, 1.0, 1.0, 0.0);
        wr.addVertexWithUV(0.0, 1.0, 0.0, 0.0, 0.0);
        // +X
        wr.setNormal(1, 0, 0);
        wr.addVertexWithUV(1.0, 0.0, 1.0, 0.0, 1.0);
        wr.addVertexWithUV(1.0, 0.0, 0.0, 1.0, 1.0);
        wr.addVertexWithUV(1.0, 1.0, 0.0, 1.0, 0.0);
        wr.addVertexWithUV(1.0, 1.0, 1.0, 0.0, 0.0);
        // -Y
        wr.setNormal(0, -1, 0);
        wr.addVertexWithUV(0.0, 0.0, 1.0, 0.0, 1.0);
        wr.addVertexWithUV(0.0, 0.0, 0.0, 1.0, 1.0);
        wr.addVertexWithUV(1.0, 0.0, 0.0, 1.0, 0.0);
        wr.addVertexWithUV(1.0, 0.0, 1.0, 0.0, 0.0);
        // +Y
        wr.setNormal(0, 1, 0);
        wr.addVertexWithUV(1.0, 1.0, 1.0, 0.0, 1.0);
        wr.addVertexWithUV(1.0, 1.0, 0.0, 1.0, 1.0);
        wr.addVertexWithUV(0.0, 1.0, 0.0, 1.0, 0.0);
        wr.addVertexWithUV(0.0, 1.0, 1.0, 0.0, 0.0);

        Tessellator.getInstance().draw();
        GlStateManager.popMatrix();
}

You can download the code used in this tutorial as a .zip file from here.


Recommended tutorials to continue with

Advanced Modding:

  • Miner's Basic Rendering
  • Techne Models
  • In-code modeling
  • Rendering obj files

Comments and Questions:

If you want to report modding problems, please make sure to include the code in a pastebin link or something else! Don't just write "It doesn't work", otherwise your post will be deleted. For more complicated problems, please use the troubleshooter form.