Some basic concepts:
- 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).
- Each chunk (16x16x16 blocks) has its own glRenderList which contains the instructions for drawing all the blocks in that chunk. The glRenderList for each chunk is maintained by a corresponding WorldRenderer object.
- Several RenderGlobal methods are responsible for creating and maintaining the array of WorldRenderer objects that you can see (see .loadRenderers, .markRenderersForNewPosition, .sortAndRender). The number of WorldRenderer objects depends on the ViewDistance setting on the options screen - i.e. if your view distance is 64 blocks (4 chunks) then the array of WorldRenderer objects is 8 wide (4 east + 4 west) by 8 long (4 north + 4 south) by 16 high (full height of 256 blocks).
- The actual rendering commences with EntityRenderer.renderWorld(), which sets the camera position and then renders the various things you can see, including the world, any entities (eg creepers) in your field of view, your hand, the item you are holding, and the items overlay.
The Display List for a chunk is created once and rendered many times. It doesn't need to be updated every frame; it is only recreated if the blocks in the chunk change. This is performed by WorldRenderer.updateRenderer(), which walks through every block in the chunk and calls renderBlockByRenderType for each one. As previously described, each face of each block is then added to the Render List using a Tessellator object. The sequence is typically
- WorldRenderer.updateRenderer() creates a new gLrenderList using GL11.glNewList()
- It then initialises the Tessellator by calling .startDrawingQuads(). This tells the Tessellator that you will be drawing quadrilaterals.
- It then uses Tessellator.setTranslation to set 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. (The coordinates in the resulting Display list are all relative to the corner of the chunk, i.e. 0 - 16).
- Each face of each block is drawn by calling addVertexWithUV(x, y, z, u, v) four times, once for each corner of the face.
- After all the blocks are added, it calls Tessellator.draw(), which converts the vertex information into a Display list.
- WorldRenderer then keeps a reference (index actually) to this Display list, for use by renderWorld() when drawing the chunk.
- gLrenderList + 0: the display list for pass 0
- gLrenderList + 1: the display list for pass 1
- gLrenderList + 2: an "Occlusion Query" display list which is used to cull any chunks which are not visible. This display list just draws the bounding box corresponding to the entire chunk (i.e. a featureless cube enclosing the 16x16x16 blocks of the chunk).
- The WorldRenderers are sorted into ascending order by distance from the camera (player)- closest first, furthest last. These are rendered in ascending order.
- Before rendering each chunk, RenderGlobal.sortAndRender() performs an "Occlusion Query" by rendering the Occlusion Query display list (i.e. "drawing" the bounding box for the chunk). If the bounding box is not visible at all (i.e. is behind the other objects that have been drawn already), then there is no point rendering pass 0 and pass 1 for that chunk. This is a quick way to eliminate a large number of chunks without having to check every block in those chunks.