Friday 2 August 2013

The Tessellator

Minecraft uses OpenGL to draw what you see on the screen. But interacting directly with OpenGL is quite messy, so it uses the Tessellator class to make things easier.

What you see on the screen is created by a long list of rendering instructions that is executed by OpenGL and your graphics card. These instructions might include "draw a pixel at [3,4,5]", or "draw a line from [1,2,3] to [4,5,6]", or "draw a quadrilateral with vertices [1,2,3], [4,5,6], [7,8,9] and [10,11,12], filling it with bitmap texture abc." This list of rendering instructions is called a Display list (glRenderList in forge).

The Tessellator class is used to add rendering instructions to a Display list.   The Display List will already have been created, for example by WorldRenderer.updateRenderer - GL11.glNewList.  A typical way to use the Tessellator is:
  1. Tessellator.startDrawingQuads(), which tells the tessellator that you are drawing quadrilaterals.
  2. Tessellator.setTranslation() to set the origin to an appropriate location.
    Often these first two steps will already have been done for you - for example before rendering a block,  updateRenderer sets the origin so that the renderBlock and renderFace methods can draw the block using its world [x,y,z] coordinates without worrying about which chunk it is in.
  3. Set up the various flags (brightness, normals, ambient occlusion, color, alpha).
  4. Each face of each block is drawn by calling addVertexWithUV(x, y, z, u, v) four times, once for each corner of the face.  [x,y,z] are the coordinates of the vertex, and [u,v] is the coordinates of the texture pixel (texel) corresponding to that vertex.  See the picture and examples below.
    * The order of the vertices is important!  If you are looking at a face, the coordinates must be given in an anticlockwise order.  Otherwise, the face will be pointing in the wrong direction.
  5. Minecraft uses a single large texture for all block face textures.  As the icons are registered during startup, they are stitched together into a single large texture - (see glTexImage2D in TextureUtil).  When you render a face, you use the small portion of this large texture that corresponds to the appropriate icon.  The usual way to retrieve the [u,v] coordinates for your icon is
    icon.getMinU(), icon.getMaxU(), icon.getMinV(), icon.getMaxV().  For points that don't lie on the edge of the texture, use icon.getInterpolatedU(0 .. 16), icon.getInterpolatedV(0..16).  (For example: icon.getInterpolatedU(8), icon.getInterpolatedV(8) corresponds to the middle point of the icon's texture.)
  6. Tessellator.draw();

 Note that the direction of the v axis in the texture map is opposite to the y axis in world coordinates!   So for example - to draw the grass texture onto the ABCD face:

Some further examples:

Our test block: rendering an "up arrow" on the Zpos (south) face, a "5" on the east face, a "2" on the north face.
Red block points north, blue block points east.

Flipped left-right

Rotated

Weirdness, probably not very useful!

Added the world coordinates in clockwise order instead of anti-clockwise... where did the face go?

Looking south: clockwise world coordinates make the face point in the opposite direction.  (Pass 0 faces are not visible from their back).
The points don't have to be on the outer face, they can lie anywhere within the block.
Diagonal

Rotated on outer face

There are some restrictions on what you can do with quads:
  • Quads can be sheared - eg the top is pushed right relative to the bottom, or the left is pushed up relative to the right.  So long as the top edge has the same length as the bottom edge, and the left edge has the same length as the right edge, it will still render OK.
Left half of the texture, top is sheared 0.25 to the right

Right half of the texture, top is sheared 0.25 to the left

Top half of the texture, left is sheared down by 0.25

  • However, if you try to "pinch" the quad (eg the top edge is shorter than the bottom edge) it will render looking rather strange.
Top edge is not the same length as the bottom edge --> strange appearance

  • If your points aren't coplanar (eg wouldn't "lie flat" on a tabletop) then the Quad "folds" into two triangles (ABC, CDA) and may not render how you intended.
Non-coplanar points.  (Black line added to emphasise how the face has folded into two triangles.)

Some specific notes for using the Tessellator in .renderWorldBlock

  1. Generally you shouldn't use .startDrawingQuads() or .draw() because the caller does this for you.
  2. There are a couple of things to set up on the Tessellator first - in particular the brightness and the colour.  For example:
       Tessellator tessellator = Tessellator.instance;
       tessellator.startDrawingQuads();
       int lightValue = block.getMixedBrightnessForBlock(world, x, y, z);
       tessellator.setBrightness(lightValue);
       tessellator.setColorOpaque_F(1.0F, 1.0F, 1.0F);
     The calculations for "Ambient Occlusion" (if enabled) are significantly more complicated but are probably overkill for most custom blocks rendered using the Tessellator.  The vanilla code doesn't bother with them for non-standard blocks.   See here for more information on lighting.
  3. Some useful examples of how to use the Tessellator properly are in .drawCrossedSquares() and .renderBlockTorch()

Using the Tessellator for other shapes

Although mostly used for Quads, the Tessellator is also suitable for drawing other shapes as well - see the list of suitable drawMode settings below, taken from GL11.class.  The vanilla code uses several of these including GL_LINES, GL_LINE_STRIP, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN.  They are used in the same way as for Quads - i.e. .startDrawing(drawMode), setting appropriate flags, adding the appropriate number of vertices, then calling draw().

public static final int GL_POINTS = 0;
public static final int GL_LINES = 1;
public static final int GL_LINE_LOOP = 2;
public static final int GL_LINE_STRIP = 3;
public static final int GL_TRIANGLES = 4;
public static final int GL_TRIANGLE_STRIP = 5;
public static final int GL_TRIANGLE_FAN = 6;
public static final int GL_QUADS = 7;
public static final int GL_QUAD_STRIP = 8;
public static final int GL_POLYGON = 9;

A final note

If you're planning to use the Tessellator for anything other than simple shapes, I recommend that you don't!  Use Techne instead, it's much easier.

Techne home website
Techne Tutorial - Part 1 - Basics