Image-Based Lighting (HDRI) Architecture & Baking Pipeline
HDRI is a texture used to compute lighting, unlike uniform lighting. Uniform lighting relies on constant math parameters, while HDRI / IBL uses spherical texture data to represent complex sky and ambient lighting.
In our engine, environment.rs handles the complete generation pipeline and encapsulates all environment assets—the three baked textures (Diffuse Irradiance, Prefiltered Specular, and the BRDF LUT), a sampler, and the camera/environment uniform—into a single wgpu::BindGroup bound at slot 4.
1. Decoding the Equirectangular Source
We begin by decoding the .exr file into an Equirect buffer containing width, height, and raw linear float pixel values. Because it represents real-world illumination, these values are uncompressed and can freely exceed 1.0.
- Equirectangular-to-Cubemap Projection To make environment sampling efficient in WGSL shaders, we project the 2D panorama onto a 6-faced cubemap:
- iterate over each texel on every cube face $(u, v)$ and build a normalized 3D direction vector.
- convert that 3D direction into spherical coordinates to find the corresponding UV in the equirectangular map.
- sample the HDR pixel and write it into the cubemap face.
Visualizing spherical UV coordinates on an implicit ray-marched sphere to verify equirectangular projection alignment.

Next, we evaluate cubemap projection on the ray-marched sphere, visualizing the 6 cube face boundaries and their local UV gradients.

3. Pre-Baking the Lighting Data
Instead of costly Monte Carlo sampling during runtime, we pre-bake the light into two key cubemaps:
- Diffuse Irradiance Map: We perform cosine-weighted hemisphere convolution across the cubemap. This averages incoming ambient light from all directions to illuminate the material's diffuse albedo component.
- Prefiltered Specular Map: We use GGX importance sampling to generate a cubemap mip chain. Each mip level stores the environment blurred at a specific surface roughness, allowing the PBR shader to sample sharp or blurry specular reflections based on roughness.
Adding Directional Shadow Mapping
Next, we add a dynamic shadow, the idea here is simple: create a single 2D depth map for our direct light and redraw it every frame. When the light moves, the shadow moves with it.
Image-based environment lighting (HDRI) does not use this map, as HDRI provides pre-convolved ambient illumination rather than a single directional depth shadow.
1. Creating the Shadow Map Texture
First, we make a 1024x1024 texture just for shadow depth. This is basically the same thing as the scene depth texture we built earlier, but it looks at the scene from the light’s point of view instead of the player camera.
2. Storing It in GPU State
Just like everything else in the engine, we keep the shadow resources inside our main GPU state
- Texture & View: Holds the depth values we write during the shadow pass, which we then sample during the main draw pass.
- Comparison Sampler: A special sampler with depth comparison turned on so hardware PCF can smooth out the shadow edges.
- Shadow Pipeline: A bare-bones pipeline that only writes depth values and skips color output completely.
3. Execution Order and Pass Setup
In the frame update, we recalculate the light’s view-projection matrix from its current position.
When rendering starts, we run the shadow pass first. This pass only needs a uniform buffer with the light matrix. Because it only writes to the shadow map and never tries to read from it, we avoid any GPU read/write conflicts.
4. The Shadow Vertex Shader and Bias
In the shadow pass vertex shader, we transform our mesh vertices using the light's matrix instead of the camera matrix.
To fix shadow acne (those ugly dark stripes across flat surfaces), we set the pipeline's depth bias to constant 2 and slope 2. This pushes the depth check back just enough so surfaces don't accidentally shadow themselves.
5. Shading the Final Pixels
Finally, in the main scene fragment shader, we put everything together:
transform the pixel's world position into light space to sample the shadow map, killing direct diffuse and specular light if occluded so only ambient HDRI remains.
Thanks for reading! Check out my industrial Rust projects available on the Microsoft Store:
Download on the Microsoft Store
Download from Gumroad


