Warung Bebas

Senin, 29 Juli 2013

Rendering the world - more details

Minecraft uses OpenGL to draw what you see on the screen.

Some basic concepts:
  1. 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).
  2. 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.
  3. 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).
  4. 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.
Rendering blocks:
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
  1. WorldRenderer.updateRenderer() creates a new gLrenderList using GL11.glNewList()
  2. It then initialises the Tessellator by calling .startDrawingQuads().  This tells the Tessellator that you will be drawing quadrilaterals.  
  3. 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).
  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.
  5. After all the blocks are added, it calls Tessellator.draw(), which converts the vertex information into a Display list.
  6. WorldRenderer then keeps a reference (index actually) to this Display list, for use by renderWorld() when drawing the chunk.
Each WorldRenderer actually keeps track of three Display lists, these are
  • 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).
For interest' sake- the "Occlusion Query" works as follows:
  1. The WorldRenderers are sorted into ascending order by distance from the camera (player)- closest first, furthest last.  These are rendered in ascending order.
  2. 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.





Jumat, 26 Juli 2013

Rendering transparent blocks

Rendering a block with transparent sections (eg a block of glass) is relatively straightforward.  You need to create an image which has an alpha channel (a mask which indicates the transparent parts of the image).  This can be rendered as a standard block.  For example, if the numbered cube from the earlier block rendering tutorials is modified so that each face has an alpha channel where the white sections are made transparent:


Some important things to note:
  • You can only see faces which point in your direction.  Any faces which are pointing the other way (eg faces 2 and 0 in the pictures above) are not visible until you walk to the other side of the block (as per faces 4 and 5 above).
  • You need to override isOpaqueCube() to return false, otherwise the adjacent faces of adjacent blocks won't be rendered properly, for example see below.  For more information on the various effects of isOpaqueCube, see here.
isOpaqueCube returns true - grass under the block is not rendered
  • The transparency is either on or off - a pixel is either fully transparent or fully opaque.  For exampe - if your alpha channel has 50% transparency, it will be treated as opaque. 
In order to render partially transparent blocks such as ice, Minecraft uses a different rendering "pass".  During WorldRenderer.updateRenderer(), two passes are performed:
  • pass 0 (all pixels either "opaque" or "fully transparent"), back faces not rendered
  • pass 1 (0 - 100% transparency; back faces visible).
Each block is rendered on either pass 0 or pass 1, as determined by Block.getRenderBlockPass() and Block.canRenderInPass().  For example - Ice normally renders in pass 1.  The picture below shows the difference between ice rendered in pass 1 (left) and pass 0 (right).

Ice rendered in pass 1 (left) or pass 0 (right).

If our example cube is converted to have a striped alpha channel on the "2" face, i.e. from top to bottom 0%, 25%, 50%, 75%:
then it renders as follows in the two different passes:
Pass 0 render (left) vs pass 1 render (right)
The key differences are:
  • Pass 1 renders 25%, 50%, 75% transparency as partially transparent.
  • The back faces (eg 3) are visible in pass 1
It is possible to render a block in both passes, but this is not generally useful.

If multiple transparent blocks are placed next to each other, the internal faces are all visible (the two "4" faces in the picture below).  This is different from (for example) glass and ice, where these "internal faces" are not shown.
Minecraft achieves this using the shouldSideBeRendered() method.  For example - BlockGlass overrides shouldSideBeRendered(), and returns false if the adjacent block is also a glass block.  (see BlockBreakable.shouldSideBeRendered()).
BlockGlass.shouldSideBeRendered(): left = vanilla code; right = overridden to always return true







Sabtu, 20 Juli 2013

Rendering Non-standard Blocks

In the vanilla code, rendering a non-standard block is typically done by calling the same methods as standard blocks (i.e.  renderStandardBlock, or the various .renderFace#### methods), after changing one or more static member variables in RenderBlocks:
  •  .setRenderBounds(minX, minY, minZ, maxX, maxY, maxZ): These define the size and location of the block faces within the 1x1x1 cube and allow you to draw blocks which are smaller than the full 1x1x1 cube. See example pictures below.  For simple blocks you can just set the boundaries using  this.setBlockBounds() in the constructor, or setBlockBoundsBasedOnState if the boundaries vary depending on the metadata.  Note - if you set the render bounds smaller than the full cube, makes sure your MyBlock overrides isOpaqueCube() to return false, otherwise you will see blank patches of colour where adjacent block faces should be.  
  • .flipTexture (for X and Z renders only): flips the texture left-right. The flipTexture flag is not used by the Top and Bottom faces. See example pictures below. Note! You must set this flag back to false afterwards, otherwise other blocks in the scene will also be flipped.
  • .uvRotateSouth, uvRotateTop, etc : rotates the texture on the corresponding face through 90, 180, or 270 degrees. For example, uvRotateTop is used by renderFaceYPos. The face number, direction, renderFace method, and flag name are listed below. Unfortunately, most of these flags are currently incorrectly named. And some of the faces get flipped left-right before rotating by 90 or 270 degrees. Confused? So am I! The example pictures below may help. Note that flipTexture has no effect when using the uvRotate#### flags. 
                0 = bottom = YNeg = uvRotateBottom
                1 = top = YPos = uvRotateTop
                2 = north = ZNeg = uvRotateEast 
                3 = south = ZPos = uvRotateWest 
                4 = west = XNeg = uvRotateNorth
                5 = east = XPos = uvRotateSouth

 

Many of the non-standard blocks can be rendered in this way, with repeated calls to renderStandardBlock() or renderFace####() as necessary, changing the flags each time.  However if the render requires any faces which aren't aligned with the axes (eg a torch, which is at an angle to the wall), then Tesselator.addVertexWithUV is used instead.  More details here.

If you call the renderFace####() methods, you will need to set up the lighting information beforehand.   See here for more information on lighting.   The relatively easy way is to use Tessellator.setBrightness() and Tessellator. setColorOpaque_F().   More hardcore is the "smooth lighting" setting - if you set .enableAO to true, the renderFace####() methods will expect you to set up all the per-vertex lighting information as well (12 colour settings eg .colorRedBottomRight, plus 4 brightness settings eg .brightnessTopRight, etc).  This is an exercise in head damage.  Luckily I can't think of any situations where it would be required.

Rendering Standard Blocks (cubes)

When minecraft needs to draw a standard cube block, it starts with a call to RenderBlocks.renderStandardBlock(myBlock), where myBlock is the Block that it wants to draw.

It then renders each of the six faces in turn, after asking myBlock to supply the Icon corresponding to each face. The faces are numbered from 0 to 5 � see net.minecraft.util.Facing.


The coordinate system used by Minecraft is shown below, where each block is a 1x1x1 cube.


If you program myBlock.getIcon to return the six Icon textures below corresponding to the six directions (Show me how?), it renders the cube shown below (red block points north and the blue block points east).


Points to note:
  • The side faces always render with the same orientation as the Icon Texture.
  • The top face (up, YPos) points north- if you are facing north and looking down, the face will have the same orientation as the Icon texture.
  • The bottom face points north (the same as the top face), but is flipped left-right. 
  • RenderBlocks actually calls Block.getBlockTexture(World, x, y, z, side) before it calls Block.getIcon.  If you want to change your block's textures depending on where it is or what other blocks are nearby (for example - make it turn green if a flower is planted on top of it), then override getBlockTexture instead of getIcon.

Jumat, 19 Juli 2013

Blocks


Key concepts relating to blocks:

  • The minecraft world is composed of blocks.  The world is only 256 blocks high but extends indefinitely along the two horizontal axes.
  • The blocks are grouped into 'Chunks', which comprise a cube of 16x16x16 blocks.
  • Chunks are then grouped into regions; each region is comprised of 32 x 32x16 chunks.  Regions are stored on disk, one per file.
  • The point of all this grouping is firstly to reduce storage space (Sections which are completely empty are not saved to disk) and secondly to allow chunks to be created and loaded into memory individually as necessary, without having to store areas where the player hasn't explored.
  • The coordinate system used in minecraft is shown below:

 
At a minimum, each block location (x,y,z) contains the following information:
  1.  An integer BlockID, which specifies the type of block at that location.  These are generally unique but not always (for example: different types of wood share the same BlockID).  If your mod adds new blocks, it will need to assign them unique numbers.
  2. An integer "metadata" value from 0 to 15, which gives further information about the block.  Typically it is used to show different variants of the block - for example different types of wood, different colours of wool, different orientations of a piston.  Minecraft has custom code for each block which determines what the effect of metadata is (if any).  More Info
  3. An integer "SkyLight" value from 0 to 15, which specifies the amount of light this block is receiving from the sky.
  4. An integer "BlockLight" value from 0 to 15, which specifies the amount of light this block is receiving from nearby blocks.
  5. These data are not stored in the Block class.   They are stored in an array inside ExtendedBlockStorage objects, inside Chunk objects.
A number of other data structures can be used to store more information about a particular block location:
  • TileEntity, which stores custom information such as the text on signs, the list of items in a chest, etc.  Most blocks do not have a corresponding TileEntity, these are stored separately with an associated (x,y,z) coordinate.
  • Entity, used for some special "blocks" such falling blocks (sand, gravel), paintings, item frames.

Kamis, 18 Juli 2013

Block Rendering

Minecraft renders Blocks in different ways depending on the type of the block (see diagram below).
  • Standard blocks (full cubes) can all be drawn the same way -i.e. using a square texture for each of the six sides.  More Info
  • Unusual blocks in vanilla minecraft (eg doors, pistons, bed, etc) are rendered using the corresponding .renderBlock#### method in RenderBlocks, for example renderBlockBed().
  • Forge has added a hook for rendering custom blocks.  If you create a new type of Block which needs special rendering, you need to define a custom renderer class for it:
  1. Create a class MyRenderer which implements ISimpleBlockRenderingHandler.  
  2. Assign it a myRenderID using RenderingRegistry.getNextAvailableRenderID()
  3. Register myRenderer with RenderingRegistry.registerBlockHandler().
  4. Ensure your myBlock.getRenderType() returns myRenderID.
  5. Put your custom rendering code in MyRenderer.renderWorldBlock().
One of the trickier parts of rendering custom blocks can be figuring out the folder where you need to place your custom icons.  For block textures this is constructed as 
{base path}/assets/{mod name}/textures/blocks/{Icon name}.png

In my case, during development this was (eg)
C:\Documents and Settings\TheGreyGhost\My Documents\IDEAprojects\ForgeCurrent\out\production\testitemrendering\assets\testitemrendering\textures\blocks\myIcon.png

Some more details here.


A note about rendering animated Blocks:

When rendering Blocks, minecraft uses a 'cached renderlist'  that is only refreshed when the block changes.  So if you try to animate your Block using the Block rendering methods above, you won't see anything until the Block is changed or updated.
If you want your Block to be animated, you need to either use an animated texture for your Block, or use a TileEntity with TileEntitySpecialRender.

Introduction

The point of this blog is to help new modders understand how Minecraft works.

There are lots of code samples and individual tutorials out there on the web, but for me I had a lot of trouble understanding them because they tended to leap right into details and focus on showing code rather than explaining the concepts.

So as I figure stuff out the hard way, I'll post the concepts here.  No code samples, because once you know what keywords to look for, the minecraft code has lots of examples and Google will show you plenty more.   My explanations are based on Forge, since that's what I'm using.







 

My Blog Copyright © 2012 Fast Loading -- Powered by Blogger