Warung Bebas

Kamis, 22 Agustus 2013

Rendering Third-Person-View Items

Rendering items in third person view is very similar to first person view.  The entry point is RenderPlayer.renderPlayer(), which calls RenderPlayer.renderSpecials().  After performing a number of translations depending on the item type and what actions the player is performing, it calls ItemRenderer.renderItem() as described for First Person View rendering.

The item renders as a 3D block:
  • if it's a block and RenderBlocks.renderItemIn3d returns true
  • if it checks MinecraftForgeClient.getItemRenderer(EQUIPPED) to see if there is a custom renderer for this item, and if so, checks customRenderer.shouldUseRenderHelper(EQUIPPED, BLOCK_3D) 
One other point of interest:
  • If  the item is 2D and Item.isFull3D() returns true, the item rotates so it lies in the plane of the player's arm, and also makes it a bit bigger.  Otherwise it lies in a plane normal to the arm.  See picture below.  Vanilla items which return .isFull3D() are eg tools, swords, fishing rod.
Item.isFull3D() affects the orientation of 2D items:
pickaxe.isFull3D() == true: icon is drawn in a plane parallel to his arm
potion.isFull3D() == false: icon is drawn in a plane normal to his arm

Rendering First-Person-View Items


There are several different ways to render First-Person-View Items.  The parts of the code that do this are summarised in the diagram below.

The top level is EntityRenderer.renderHand() which calls ItemRenderer.renderItemInFirstPerson().

Vanilla items:

  • Items which are also 3D Blocks (eg TNT, Dirt) are typically rendered in RenderBlocks.renderBlockAsItem. If the block's RenderType is 0 (default), then the block is drawn as a standard cube, using .getBlockTexture for each side. For unusual blocks (RenderType != 0), there is a massive switch() statement in .renderBlockAsItem() which selects the correct Tessellator commands.
  • There are some unusual 3D blocks (eg Bed) which render in 2D like an item. These Blocks override RenderBlocks.renderItemIn3D() to return false.
  • For non-block items (eg pickaxe), ItemRenderer.renderItemIn2D() renders the item's icon with thickness.

Custom items:

  • If your item is simple, you can just provide an appropriate Icon or BlockTexture and it will render the same as vanilla items.
  • If your item is a custom block, you will probably have given it a special Block renderType to render it correctly. In this case, you can add code to your custom Renderer which implements ISimpleBlockRenderingHandler.renderInventoryBlock(). See .renderBlockAsItem for plenty of vanilla code showing how to render them. Use [-0.5, -0.5, -0.5] to [0.5, 0.5, 0.5] as the extent of the block, it has already been scaled, rotated, and translated appropriately. If you tessellate directly, do not forget to .setNormal() to match each face otherwise the lighting will look strange. (See .renderBlockAsItem).
  • An alternative way to render custom items is to use a custom renderer implementing IItemRenderer. This same interface is used for all the different ways an item can render; it is quite complicated so I have covered it separately here.
Different ways of rendering First-Person-View equipped items.


Rendering Inventory Items


There are several different ways to render Inventory Items.  The parts of the code that do this are summarised in the diagram below.  If you aren't really interested in the details, you may as well jump straight to the sample code.

The top level is GuiContainer.drawScreen (and a couple of other methods in this class) which call
RenderItem.renderItemAndEffectIntoGUI().  This renders the item itself, and is followed by a call to renderItemOverlayIntoGUI, which adds the item count for multiple items (eg stack of 64 dirt) and the damage bar for damaged items (.isItemDamaged() and .getItemDamageForDisplay()).

Vanilla items:

  • Items which are also 3D Blocks (eg TNT, Dirt) are typically rendered in RenderBlocks.renderBlockAsItem. If the block's RenderType is 0 (default), then the block is drawn as a standard cube, using .getBlockTexture for each side. For unusual blocks (RenderType != 0), there is a massive switch() statement in .renderBlockAsItem() which selects the correct Tessellator commands.
  • There are some unusual 3D blocks (eg Bed) which render in 2D like an item. These Blocks override RenderBlocks.renderItemIn3D() to return false.
  • For non-block items (eg pickaxe), RenderBlocks.renderItemIntoGUI just draws the Item's getIcon() directly onto the screen.

Custom items:

  • If your item is simple, you can just provide an appropriate Icon or BlockTexture and it will render the same as vanilla items.
  • If your item is a custom block, you will probably have given it a special Block renderType to render it correctly. In this case, you can add code to your custom Renderer which implements ISimpleBlockRenderingHandler.renderInventoryBlock(). See .renderBlockAsItem for plenty of vanilla code showing how to render them. Use [0,0,0] to [1,1,1] as the extent of the block, it has already been scaled, rotated, and translated appropriately. If you tessellate directly, do not forget to .setNormal() to match each face otherwise the lighting will look strange. (See .renderBlockAsItem).
  • An alternative way to render custom items is to use a custom renderer implementing IItemRenderer. This same interface is used for all the different ways an item can render; it is quite complicated so I have covered it separately here.
Code tree showing the various places that different types of item renders are performed for Inventory items.

Senin, 19 Agustus 2013

Rendering Dropped Items

Dropped Items are stored in the game world as an Entity (EntityItem) which has an associated [x,y,z] position.  As described in the Rendering Items overview, there are a number of different ways that Dropped Items can be rendered.  These are summarised in the diagram below.

The render starts in RenderManager.renderEntityWithPosYaw(), which draws the Entity at a given position with a given yaw angle (you have probably noticed that most dropped items slowly rotate around the y axis, this rotation is called yaw).

Vanilla items:

  • Items which are also 3D Blocks (eg TNT, Dirt) are typically rendered in RenderBlocks.renderBlockAsItem.  If the block's RenderType is 0 (default), then the block is drawn as a standard cube, using .getBlockTexture for each side. For unusual blocks (RenderType != 0), there is a massive switch() statement in .renderBlockAsItem() which selects the correct Tessellator commands.
  • There are some unusual 3D blocks (eg Bed) which render in 2D like an item.  These Blocks override RenderBlocks.renderItemIn3D() to return false.
  • For non-block items: if fancy graphics are off (yellow pickaxe), RenderItem.renderDroppedItem() just draws the Item's getIcon() directly onto the screen.
  • If fancy graphics are on (purple pickaxe), ItemRenderer.renderItemIn2D() is used to render the Item's .getIcon() as a 2D sheet with "thickness".

Custom items:

  • If your item is simple, you can just provide an appropriate Icon or BlockTexture and it will render the same as vanilla items.
  • If your item is a custom block, you will probably have given it a special Block renderType to render it correctly.  In this case, you can add code to your custom Renderer which implements ISimpleBlockRenderingHandler.renderInventoryBlock().  See .renderBlockAsItem for plenty of vanilla code showing how to render them, also sample code here.
    Use [-0.5, -0.5, -0.5] to [0.5,0.5,0.5] as the extent of the block, it has already been scaled, rotated, and translated appropriately.
    If you tessellate directly, do not forget to .setNormal() to match each face otherwise the lighting will look strange.  (See .renderBlockAsItem).
  • An alternative way to render custom items is to use a custom renderer implementing IItemRenderer.  This same interface is used for all the different ways an item can render; it is quite complicated so I have covered it separately here.
Code tree showing the various places that different types of item renders are performed for dropped items.

Minggu, 11 Agustus 2013

Rendering Items


Rendering items is a bit complicated because there are a number of different places they can be seen:
  • In the inventory (also in the Speedbar overlay) � referred to in Vanilla code as a GUI item (graphical user interface) and in Forge code as InventoryItem
  • Equipped (held in the player�s hand) � in first person view or third person view � referred to as EquippedItem.
  • Dropped (on the ground)- referred to as EntityItem and DroppedItem
  • In a picture frame � EntityItemFrame � rendered as an EntityItem, with the RenderItem.renderInFrame flag set.

In addition to this,
  • Most Items which are also Blocks are rendered in 3D (eg TNT), which uses a rendering method provided by the Block.
  • If fancy graphics are enabled, Items and 2D Blocks have thickness and also slowly rotate.
  • There are several flags which affect the display of items such as bobbing, rotation.
  •  ItemStack (multiple items/blocks) render with a number in the GUI overlay and as a pile of minicubes when dropped.

The pictures below show these various different renders:

Item (Pickaxe) and 3D Block (TNT) rendered into the GUI (InventoryItem)

 Fancy graphics enabled.
GUI items (red): Item (pickaxe),  3D Block (TNT), 2D Block (Bed), multiple blocks (Dirt)
Equipped Item (pickaxe blue circle);
Dropped Item/EntityItems (purple):  Item (Pickaxe), 3D Block (TNT), 2D Block (Bed), multiple blocks rendered as minicubes (Dirt)

Fancy graphics disabled.
 Dropped Item/EntityItems (yellow):  Item (Pickaxe), 2D Block (Bed)

Equipped Item - first person view: 3D block (TNT)

Equipped item - third person view: 3D block (TNT)


Items rendered in frames - rendered as per EntityItem: Item (pickaxe), 2D Block (bed), 3D Block (TNT)


Sabtu, 03 Agustus 2013

Lighting

Sky Light and Block Light

The basic lighting model in Minecraft is made up of two components; Sky Light and Block Light.  Each block in the world has a value between 0 - 15 for Sky Light and also a value between 0 - 15 for Block Light.
  1. Sky Light is the light that this block is receiving from the sky, either directly or indirectly.
  2. Block Light is the light that this block is receiving from other light sources nearby (torches, glowstone, etc). 
See here for some more background information.

World.computeLightValue() is used to calculate the Sky Light and Block Light for each block.  The basic concepts are:
  1. Each block has an Opacity value ( .getLightOpacity() ), which determines how much light it receives from its six neighbours.  A low opacity (eg 1) means the light level decreases by 1 from its brightest neighbour, higher opacity (eg 3) leads to a correspondingly larger decrease.  This calculation is performed separately for both the Sky Light and the Block Light.
  2. Some blocks emit light (eg glowstone) - if this is more than the Block Light coming from its neighbours, the Block Light is set to this emitted value.
  3. If the block has only open sky or transparent blocks above it, its Sky Light is 15.
Two-dimensional example for propagation of Block Light, with opacity = 2.  Sky Light is propagated in the same way.
When the block is rendered, the Block Light and Sky Light values for the block are used to set the brightness.  This is done as follows:
  1. The Block Light and Sky Light values are combined into a single 32-bit integer, for example by Block.getMixedBrightnessForBlock().  The upper 16 bits contain the Sky Light multiplied by 16, and the lower 16 bits contain the Block Light multiplied by 16.
  2.  tessellator.setBrightness() is called with this combined "mixed brightness" value, which is interpreted by the Tessellator as two 16 bit coordinates (i.e. [BlockLight, SkyLight]) for a texture called the lightMap texture (GL_TEXTURE1) - see picture below.  
  3.  When OpenGL renders the block face, it first draws the icon texture (GL_TEXTURE0) as previously described then "modulates" it using the appropriate texel from the lightMap, i.e. as given by the [BlockLight, SkyLight] coordinate.  (This process is called Multitexturing).
The lightMap texture (GL_TEXTURE1), which has 16x16 texels .  Each texel corresponds to a combination of Block Light and Sky Light.
Why does Minecraft bother to do it this way?  There are at least two good reasons
  1. Treating Block Light and Sky Light separately allows more realistic lighting effects.  Looking at the lightMap texture you can see that Block light imparts a reddish/brownish colour whereas Sky Light does not.  This simulates the yellowy light produced by (eg) a torch.
  2. The lightMap can be updated very easily to change the lighting of the entire scene- for example depending on the time of day (eg full sun, sunset); to create the effect of a flickering torch; or when the player drinks a night vision potion.  This updating is performed by EntityRenderer.updateLightMap().

Face-dependent lighting effects

In addition to the per-block lighting described above, Minecraft can also perform some extra lighting calculations for each block face to increase the realism of the lighting.  These are performed in one of the following methods:

.renderStandardBlockWithColorMultiplier()

This method renders the six faces with a different lighting intensity depending on which direction the faces points:
  1. Top face (Ypos) is at full intensity
  2. Bottom face (Yneg) is 50% intensity
  3. Z-side faces (Zpos, Zneg = South, North) are 80% intensity
  4. X-side faces (Xpos, Xneg = East, West) are 60% intensity.
This lighting is applied to each face using Tessellator.setColorOpaque() ;  when the texture is rendered it is multiplied by the "colour" value (in this case a shade of grey).

In some cases an RGB colour multiplier is also passed in by the caller (for example underwater gives blocks a blue tint) which is multiplied by the % intensity described above.

The lighting of the face is also affected by the [BlockLight, SkyLight] mixed-brightness in the usual way.  The diagram below summarises these steps.

Schematic showing how Minecraft applies lighting effects:
(1a) face-dependent intensity and (1b) colour multiplier, then (2) [Blocklight,Skylight] lightmap

.renderBlockWithAmbientOcclusion(), .renderStandardBlockWithAmbientOcclusion()

Ambient Occlusion is a method for calculating the amount of light shining on a surface based on nearby blocks.  A brief introduction on the approach used by minecraft is here.   Briefly, for each vertex, an average light intensity is calculated from the three adjacent blocks plus the block which touches the face.  In the example below for the red circled vertex this is the three blocks in white plus the block resting on top of the face (not shown).  If all these four blocks are transparent, the "Ambient Occlusion" light intensity will be 1.0.  Each opaque block reduces light received by the vertex - if all four blocks are opaque the intensity will be 0.2.  This "Ambient Occlusion" value is then multiplied by the colour multiplier and used to .setColorOpaque() for that vertex.
The Mixed Brightness for the vertex is calculated as the average of these four adjacent blocks.
 (There are some extra details about whether the corner block is included or not, but you get the basic idea).
 OpenGL applies smoothing when drawing the face, so that the lighting effect is graded smoothly over the face between the vertices.

Ambient Occlusion algorithm.  Diagram adapted from 0fps.


Which method does minecraft choose?
If Smooth lighting is on and the block doesn't glow (i.e. Block.lightValue == 0):  Ambient Occlusion.
Otherwise:  .renderStandardBlockWithColorMultiplier()



Jumat, 02 Agustus 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.)

 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;
       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


 

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