This is part of a series about the different game systems I’ve created for Lamorna Engine.
Lamorna engine features dynamic lightmaps, which allow for the real time world painting effect. The light maps are low resolution textures of uniform texel density applied to static world surfaces, which can accumulate colour from light emitters. Each surface of the blocks that make up the game world are assigned a unique texture at setup, and the deltas necessary to map from world coordinate space to local lightmap space are stored. A base texture is applied to the lightmap, since this is the only texture applied to the surface some visual detail is necessary, and colour is accumulated over this base texture. Mip-maps for the lightmaps are also generated.
The process of updating the lightmaps each frame begins by determining which light emitters are casting light on which world blocks. This process is accelerated using the bounding volume hierarchy created for collision detection, where the static world blocks have been binned to a bounding grid at setup, partitioning up the world into nodes which can be checked and trivially rejected. Since both the nodes and the world blocks are axis aligned, a simple overlap test against the light volume suffices. We use SIMD to process firstly 4 grid nodes at a time, then 4 bounding boxes, and sparse array compression to output an indexed list of lit surfaces vs light sources.
The narrow phase processes each lit block with a list of valid light sources close enough to contribute colour, where each light source has a position, colour and intensity. The modus operandi of the algorithm is to compute a colour contribution from the light source for each lexel (lightmap pixel) in the lightmap based on the distance to the light source and its intensity. Instead of a radiosity calculation per lexel the process contributes colour to a surface by way of a projected texture light. This is a grayscale texture that describes light intensity fall-off in 2D :
If we imagine instances of this texture lying on the plane of each of the principal axes and centred at the light source, we can see a 3D position in world space can be mapped into this texture to obtain a light intensity value, where the z component is used to attenuate the u, v components. This is similar to the process Doom 3 used for it’s real time lighting, and is a neat way of avoiding complex radiosity calculations while giving artistic control over light properties, particularly fall-off. To avoid having to visit each lexel on the surface we compute a bounding rectangle for the light’s extent in lightmap space, clamp it to the lightmap dimensions and iterate within it. Lights that lie behind the lightmap plane are skipped. Lighting computations are performed on the highest mip level. Once all the lights affecting a surface have been processed the lower level mip-maps are re-computed.
The narrow phase is threaded, where each lit block and it’s attendant light sources are expressed as a job and submitted to the thread pool. The whole lightmap scheme is double buffered with read & write copies of each lightmap, so that the renderer can access the read copy while the write copy is being updated. To maintain continuity the write process must copy over the read lightmap before updating begins. At the end of the frame the read and write references for those lightmaps that have been updated are exchanged. The double buffering doubles the memory requirements but allows us to run lightmap update in parellel with rendering, albeit at the negligible cost of lagging one frame behind the world update.
The dynamic lightmaps are used to create a permanent ‘painted’ effect in the game world, but could just as easily be used for dynamic lighting by resetting the lightmaps each frame. The lexel to world space ratio can be adjusted, with higher ratios improving visual fidelity at the cost of performance.