Friday 26 December 2014

Lighting [1.8]

Minecraft uses three different lighting models depending on what it is rendering:

  1. No lighting - typically for GUI screens.
  2. Item Lighting - uses OpenGL light source models, which are used when rendering items in GUI screens such as inventory slots
  3. World lighting,  which is used for objects which become brighter or darker depending on the amount of light in the world (light from the sky and/or nearby glowing objects).

Item Lighting

Item lighting is turned on and off using RenderHelper.disableStandardItemLighting() and .enableStandardItemLighting().  It models two light sources chosen to ensure the rendered items look three-dimensional.

World Lighting 

The basic world lighting model in Minecraft is made up of two components; Sky Light and Block Light.  Each grid location 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 (can see the sky) or indirectly (is near blocks that can directly see the sky).
  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.  worldRenderer.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 (OpenGlHelper.lightmapTexUnit) - see picture below.  
  3.  When OpenGL renders the block face, it first draws the icon texture (OpenGlHelper.defaultTexUnit) 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 (OpenGlHelper.lightmapTexUnit), 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:

1-Direction-dependent face brightness

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.
Direction-dependent face brightness: off (left) vs on (right)

This lighting is applied to each face using worldRenderer.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

2- Ambient Occlusion

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.


Some further extra details

The actual multitexturing used by minecraft is a bit more complicated than the explanation above.  It is set up in two functions in RendererLivingEntity - currently called func_177092_a and func_177091_f but this will likely change in the future.
The multitexturing is set up with three layers, and they are changed depending on what is being rendered.

For normal world rendering func_177091_f sets up like this:
layer 0 multiplies the model texture by the untextured colour and alpha.
layer 1 multiplies the lightmap texture by the layer 0 output, both colour and alpha
layer 2 is unused.

For rendering an entity func_177092_a is used:
Called before rendering parts of living entities, the purpose is to change the entity colour when it is damaged (i.e. it flashes the entity skin red).

Layer 0 multiplies the model texture by the source colour, untextured alpha is overwritten by model texture
Layer 1 – layer 0 is blended to a target colour using a target alpha (i.e. at 50% alpha with a target colour of red, the pixel is 50:50 mix of layer 0 and red)
Layer 2 – lightmap multiply by RGB texture, ignore lightmap alpha

The OpenGlHelper.GL_TEXTURE2 used by one of the layers is just a dummy to provide an extra layer for the multitexturing.

No comments:

Post a Comment