L3DT documentation
Large 3D terrain generator

Light mapping algorithms


The L3DT lighting routine includes the following features:

  • Different RGB spectra for direct and ambient lighting, allowing afternoon sun or full moon effects.
  • Anisotropic ambient lighting (discussed below).
  • Physically realistic light absorption by water.
  • Light refraction in water (shadows, lighting directions, etc).
  • Fast shadow casting.
  • Bump mapping.

Calculating light intensity

The RGB intensity of a pixel in the light map is given by:

Irgb = Sunrgb × N·v + Ambrgb × (Nz + 1) ÷ 2


  • Sun = the RGB intensity (spectrum) of the sun / moon / whatever.
  • Amb = the spectrum of ambient light.
  • I = the resulting RGB intensity on the light map pixel.
  • N = the normal vector from the surface (unit length).
  • Nz = is the vertical component of the normal vector (from the normals map).
  • v = the light vector to the sun (unit length).
  • · = the scalar product operator for two vectors.

Effect of shadows, anisotropic ambient lighting

If the pixel has been shadowed, then the direct sunlight term (Sunrgb × N·v) is ignored and only the ambient light term (Ambrgb × (Nz + 1) ÷ 2) is considered.

Note that the Nz component in the ambient light term implies ambient lighting is anisotropic. Thus, steep terrain does not receive light from the whole sky hemisphere, and in the limiting case that the land is tilted at 90 degrees (thus Nz = 0) the terrain will see only half of the sky dome, in which case the ambient term becomes Ambrgb ÷ 2. This hack that assumes the sky dome is a uniformly bright Lambertian shell, which it most certainly is not, but the effect is still better than not doing it.

Light absorption by water

If the heightfield pixel is under water, then both Sunrgb and Ambrgb are modified by Beers' / Lambert's law [link: Wolfram Research] to give physically accurate light maps for the seafloor / lake bottom. The equation goes a little something like this:

I / I0 = Txe-λx

where the ratio I / I0 is the intensity of the light at a depth of x as a fraction of the intensity that was incident on the surface, for a medium with an attenuation coefficient of λ, and a surface transmission coefficient of Tx (discussed below). This equation is applied to each colour individually, as red, green and blue light are absorbed differently by water. In the 'Light/water effects' wizard you set the depths at which these colours are attenuated to 1/2 of the original transmitted light.

The relationship between half-depth (x1/2) and absorption coefficient (λ) is:

x1/2 = - loge(2) / λ

The transmission coefficient Tx is calculated using the Fresnel relations [link: Georgia State University]. Second-year university optics at its very best.

Anyhoo, I'm sure you get the idea.

A typical light-map.
The same light map, with absorption and refraction by water.

The heightfield is this case was 'wrapping', so shadows were cast across map edges.

Shadow ray-casting

The basic approach of shadow casting is described by Mircea Marghidanu in this GameDev article.

The method described in that article involves a raycast from the sun to every pixel in the heightfield (or vice versa) to test if each pixel is shadowed. The L3DT routine is a little different insofar as it casts a 'shadow ray'1) from each heightfield pixel in the direction away from the sun, shadowing all heightfield pixels that lie beneath the ray. The effect is the same, but with less raycasts through empty space. The raycasting terminates when the ray intersects the terrain, which is immediate for any non-shadowing pixel (which is to say, almost all of them). Furthermore, we do not cast shadows from pixels that have been shadowed. This routine is rather fast because it minimises the number of ray-casts.

Shadows in water

In L3DT, the 'shadow rays' in the casting routine are refracted when they enter water, in accordance with Snell's Law [link: Wolfram Research]. The effect is subtle, but I sleep better knowing it's there.

High-resolution light mapping

This option is only available in L3DT Professional.

For pretty terrain rendering, it is common to use a 'high-resolution' terrain texture that has a finer pixel size than the terrain heightmap (see high-resolution textures). To get the best out of this practice, you need to also generate a high-resolution light map, so that features like shadows don't get washed out. This is exactly what L3DT Professional does:

4× high-res light map, L3DT Professional (click for larger image).
Regular 1× light map, L3DT Standard (click for larger image).

The process is a simple extension of the lightmapping algorithm, whereby the heightfield is interpolated for the shadow-casting and the normals map is interpolated for the bilinear lighting. Additionally, the shadow edges are cleaned using a 3×3 anti-aliasing filter to remove pixellation. This filter only applies to the shadow state, and thus other lighting data retains its normal sharpness.

Bump mapping

This option is only available in L3DT Professional.

Bump mapping, as described by Wikipedia, is a process by which the normals map of a surface is perturbed during light rendering, typically by an image, to produce more detailed and realistic surface relief. In L3DT, each material may have its own bump map(s) that are overlayed during the generation of the light map. Below you can see the texture and corresponding bump map for a rock land type:

rock1.jpg rock1_bump.jpg
Rock texture Rock bump map

The cliffs in the left image below shows the effect of bump mapping, where the apparent surface relief is enhanced when compared to the non-bump-mapped image (click for larger view):

bumpshot3.jpg bumpshot2.jpg
With bump mapping Without bump mapping

Note that the effect of bump mapping is most noticeable when the light is at glancing angles to the surface. When the light direction is perpendicular to the surface, bump mapping has a lessened effect.

Here is another example, using bump mapping to apply the appearance of sand dunes:

With bump mapping Without bump mapping
desert_bumped_2d.jpg desert_nobump_2d.jpg
desert_bumped.jpg desert_nobump.jpg

There are even prettier bump mapping screenshots in the development blog.

1) Yes, I do understand that no such thing exists. It's simply a convenient geometric construct.
l3dt/algorithms/lm.txt · Last modified: 2017/08/31 05:17 (external edit)
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki