Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

In Minecraft when you look at water the deeper you view the darker it gets. Does anyone know how to code something like that?

Minecraft with effect minecraft with effect

similar game without effect similar game without effect

share|improve this question
18  
Isn't this done automagically since the material of the water cube is semi-transparent? –  pek Aug 20 '11 at 22:01
    
I don't think so. I added a picture without the effect for comparison. –  Xavier Aug 20 '11 at 22:05
2  
Maybe it's an additive blend effect applied only on the water cubes? Again, this should be easy since the material is semi-transparent. –  pek Aug 20 '11 at 22:11
1  
you can also change the color of boxes according to the depth. –  Ali.S Aug 20 '11 at 22:29
add comment

3 Answers

There are essentially two different approaches for lighting water based on depth:

Voxel-Lighting

Minecraft uses voxel-based lighting, wich works by propagating light to adjacent cubes, lowering the brightness depending on the block type. Dark oceans are a side effect of this system.

Water blocks sunlight and reduces the light by 3 levels per block (instead of the default 1 level), wich means the brightness in an ocean for each distance from the surface is:

0 (surface): 15 (direct sunlight)
1:           12
2:            9
3:            6
4:            3
5 and below:  0 (darkness)

Source: Minecraft Wiki - Light

Distance-Based Shadowing

In games with a traditional lighting model, this effect can be created by measuring the amount of water that is between the light source and the ocean floor. The light is then faded based on this distance. There are a few methods for doing this:

Direct Calculation

If you have a flat surface, you can easily calculate the distance the light travels in the water if you pass the surface normal away from body of water \vec{n} and the dot product of this normal and a surface position s into the geometry shader.

The effective water distance is

\max(\left ( s - \vec{n}\cdot \vec{p}\right ),0) \cdot \left ( 1 + \tan(\alpha) \right )

where \vec{p} is the position of the vertex and alpha is the angle between the light direction beneath the surface and the water's surface normal towards the body of water.

At sunset, alpha only reaches a bit less than 50° because the light is refracted when entering the water.
Here's a blog post with a good explanation: The Digital Camera: Total Internal Reflection
Another post with more details: The Digital Camera: Snell’s Law of Refraction

If you're using a heightmap on a surface parallel to the water, \left (s - \vec{n}\cdot \vec{p}\right ) becomes \left ( s - h\right ). The right factor equals 1 if the sun is directly above the water surface.
With a point light, you have to calculate alpha for each vertex based on the relative position to the light source.

With a fixed water level or a fixed light direction, parts of the equation are constant and shouldn't be calculated in the shader for performance reasons.

Pros:

  • Fast and accurate

Cons:

  • Only works for flat water surfaces or only for light from directly above, as only one surface is normally taken into account. (The combination of a rough surface and tilted light could work to some extend with parallax mapping.)
  • No caustics

Shadow Mapping

If you render the water surface to a separate depth map (as seen from the light source), you can use that depth texture to calculate the distance the light travels in the water before hitting the surface.
To do this, you project each vertex into the light source's view projection in the vertex shader and do the texture lookup in the pixel shader.

If the surface is relatively flat, you should use a refracted light origin for better results.

Pros:

  • Works with relatively complex water geometry, as long as it doesn't occlude itself.*
  • Can be reused for almost any kind of partially transparent volume.

Cons:

  • Slower than the direct calculation.
  • Needs additional VRAM for the depth map.
  • Not 100% accurate.

*You can determine the amount of water in front of the nearest solid surface by counting the depth from the light's POV as follows:

  1. Render the solid geometry in your scene as normal. For each fragment, you add the depth value to the result texture.
  2. Render the water's front-faces without updating the depth buffer and subtract the fragments' depths from the result.
  3. Render the back-faces in the same way, but add the fragment depth to the result.

The result texture now contains the amount of water in front of the light in light-view-space, so the value must be transformed back before you use it. This method works to calculate the directional light (minus refraction), but will lead to incorrect ambient light if the surfaces are very irregular and there's a large amount of air between to bodies of water affecting the same fragments.
The pros and cons are the same as for normal shadow mapping, except that you need one more buffer while calculating depth and the performance is worse because you have to draw more geometry.

Ray-Tracing

Ray tracing is by far the most accurate but also the most expensive solution for rendering transparent volumes. There are two ways of doing this: 1. Tracing from the ocean floor towards the surface and 2. Tracing from the light source towards the water. Multiple rays are needed for each point on the floor to calculate the brightness.

Pros:

  • Works correctly with every geometry.
  • Correct caustics!

Cons:

  • Slow!

Additional effects

There are a few more things to take into account when rendering water:

Fog

Light in water is scattered again while travelling to the observer, so you should blend it towards a solid color.

If the observer is submerged, you can just render fog based on the final result of the depth buffer. The fog color, but not its density should change with the observer's distance from the surface! (Minecraft only uses this part of the effect.)

If the observer looks at the water from above, you need to calculate the fog based on the depth difference between the surface and the geometry under water. The fog colour should get slightly darker with larger depth differences, but should only change to the point where the fog is fully opaque.

The fog colour should also depend on the view direction for each pixel, so it's slightly darker when looking down in both cases.

Faking Caustics

If you use a seamless tiling 3D-Texture instead of a decal for fake caustics, you can avoid stretching on vertical surfaces. The strength of scattered light near the surface varies in three dimensions, so using a 2D-Texture usually produces stretching somewhere in the scene. You can model changing light angles by projecting the vertex positions of the floor into a different coordinate system.

Another possibility is to calculate the light density based on the surface position in the light's coordinate system, although that would most likely cost some performance.

The caustics should fade faster than the diffuse light with increasing depth.

Colour Gradient

Colours are scattered differently, so the light colour should change with increasing depth. This also prevents abrupt edges where, for example, a beach intersects the water surface.

Angle of Incidence

Because of refraction, the light hits the ocean floor much steeper than it normally would. The Wikipedia article about Snell's law has formulae for angles and vectors.

share|improve this answer
add comment

I believe that the sky lighting effect in Minecraft is straight down - things get shaded by what is above them no matter where the sun is. Then local lighting from torches, etc. is applied with a dropoff effect - the farther away from the light source, the less light a cube gets.

If done this way, each layer of water would cumulatively shadow the layer below it, so each becomes progressively darker. Tree foliage provides shade like this, however it is not cumulative. You get the same shade under a tree whether it's 1 or 100 foliage cubes.

One clue that this is the method being used is that water doesn't get darker when farther away from the viewer - only as you go down. Yes, the fog effect does kick in at distance, but not the water dark effect.

So the basic formula for calculating lighting would be something like this in pseudo-code...

light_on_cube = 1.0
for each cube above target cube, from lowest to highest {
   if cube being examined is tree foliage
      light_on_cube = 0.5
   else if cube being examined is water
      light_on_cube = light_on_cube - 0.1
   else if cube being examined is solid 
      light_on_cube = 0
}

This isn't perfect for calculating lighting under overhangs or in caves, as it would be pitch dark under an overhang using this method. But one could add in both local light sources (torches, fires, etc.) as well as treating sun lit blocks as light sources. Something like this might do it...

  1. Calculate light from sun from directly above (via above pseudo-code) for each cube.
  2. If a cube has a light source next to it, consider it fully lit (1.0)
  3. If a cube is receiving no sun from directly above, give it some light based on how far away it is from a fully lit cube. Closer means more light, farther away means less (until zero).

The idea here is that if a cube is lit by the sun or a torch, the cube next to it is going to also be lit in some way. And the farther away you are from that lit cube, the less light there will be. It's sort of a kludge way to estimate diffuse lighting but I think (?) it would work.

share|improve this answer
1  
Yeah, I'm pretty sure that's the ticket. I've done something similar in my game. –  Byte56 Aug 21 '11 at 0:44
    
By the way I just added your blog to my google reader list Byte56 - developer blogs FTW! –  Tim Holt Aug 21 '11 at 0:52
    
Oh, why thank you. Still off topic of this question, but I just read your blog about Professor Bailey's class. I was in that class last year! I'm fairly sure you gave that presentation last year too. I thought your name was familiar. Small world :) –  Byte56 Aug 21 '11 at 5:35
add comment

Perhaps I'm misunderstanding the question, but why can't you just change the colour of the blocks depending on their depth?

If you have the depth d (in blocks, starting at 0) then a reasonable equation for brightness would be:

L = (1-m) e-kd+ m

Code: L = (1.0 - m) * exp(-k * d) + m;

k controls how fast it gets darker (higher = faster). A reasonable value would be 0.5.
m is the minimum brightness you want.
L varies from 0 to 1.

If you don't know how to change the colour of the blocks in whatever graphics API you are using then please ask that as a separate question (stating what API you use, and whether or not you are using shaders).

share|improve this answer
    
I simply didn't think of doing that. Just out of curiosity where did you get that equation? –  Xavier Aug 21 '11 at 0:02
1  
@Xavier: I just made it up. The e^-kd bit is just an exponential decay, which is a standard function for things that gradually tend toward zero over some value (depth). The multiplication by (1-m) and addition of m are just to scale and offset the decay so that it ends up at a minimum of m but still starts at 1. en.wikipedia.org/wiki/Exponential_decay –  Peter Alexander Aug 21 '11 at 0:19
    
Thing is, the blocks of a deeper hue will only be seen if the blocks have alpha colours; in which case there is no need to change the block colour, the alpha will create the effect automagically. –  Jonathan Connell Aug 22 '11 at 7:42
    
@Jonathan: You don't render the water blocks, you render the blocks on the sea bed with the colour darkening and then just have a single alpha layer on the water surface. –  Peter Alexander Aug 22 '11 at 9:37
    
@Peter Alexander Ok, I assumed that in these block type games, even the water was made of blocks. –  Jonathan Connell Aug 22 '11 at 9:57
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.