Achieving realism in a computer-rendered scene means approximating the physics of how light propagates. Accurate implementation makes a big difference: correct appearance of soft indirect light and darkened scene corners strongly makes up for lack of realism in other areas. This can enhance even many non-photorealistic graphics styles.
In an ideal world, one would directly simulate photons travelling from the light source, bouncing off object surfaces in the scene and ultimately ending up in the virtual “eye”. However, for real-time graphics, especially in the browser, this kind of lighting computation is too slow to do dynamically. Instead, static objects in the scene are accompanied by a precomputed lightmap that stores the amount of light received by every part of the object surface. Most popular 3D engines and tools can perform that computation, including Unity, Unreal Engine 4 and Blender. It is important to note that there already are near-real-time global illumination implementations that do not need the above (such as in Godot and UE5 and others), but lightmaps are still a trusty part of the toolbox.
I wanted to attempt a very simplistic implementation of such a lightmap baker, one that could run right in the browser and still use GPU acceleration. Built on top of ThreeJS and react-three-fiber, the algorithm computes every lightmap texel by rendering the scene in five cardinal directions (away from surface, up, down, left, right) – a very simple half-cubemap light probe. The probe pixels are then averaged into a single diffuse irradiance component. This process is repeated over several passes – this is what creates the soft indirect bounced light effect.
In addition, I still wanted this computed lighting to coexist with built-in shadowmapping features of ThreeJS, so the lightmap only stores the indirect component of the light bounces. Emissive textures on surfaces are also trivially included in the “baking” process.
Finally, the baker implementation supports separate lightmap layers per light source – so different lights can be turned on and off independently, or have dynamic intensity. Then, for each frame, the layers are modulated and composited at runtime into a single visible lightmap. Even basic scene animation can be supported the same way, too – for example, when window blinds open in a room, the scene can transition from being dimly lit to being flooded with sunlight.