Building a Reusable WebGPU & Rust Engine: Part 6 — HDRI Environment Lighting & Dynamic Shadows
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…
Image-Based Lighting, also known as HDRI, is a technique used to represent complex sky and ambient lighting in a texture. In contrast to uniform lighting, which relies on constant math parameters, HDRI uses spherical texture data to create a more realistic lighting environment. Our engine handles the entire HDRI generation pipeline in the environment.rs module, which includes three baked textures (Diffuse Irradiance, Prefiltered Specular, and BRDF LUT), a sampler, and camera/environment uniform information in a single wgpu::BindGroup bound at slot 4.
To efficiently sample the environment in WGSL shaders, the equirectangular source texture is decoded into an Equirect buffer. This buffer contains width, height, and raw linear float pixel values, which can exceed 1.0 because they represent real-world illumination. After decoding, the texture is projected onto a 6-faced cubemap, with each texel on every cube face $(u, v)$ converted into a normalized 3D direction vector.
This 3D direction is then transformed into spherical coordinates to find the corresponding UV in the equirectangular map, and the HDR pixel is sampled and written into the cubemap face.
Instead of performing costly Monte Carlo sampling during runtime, we pre-bake lighting data into two key cubemaps: the Diffuse Irradiance Map and the Prefiltered Specular Map. The Diffuse Irradiance Map is created by performing cosine-weighted hemisphere convolution across the cubemap, averaging incoming ambient light from all directions to illuminate the material's diffuse albedo component.
The Prefiltered Specular Map uses GGX importance sampling to generate a cubemap mip chain, with each mip level storing the environment blurred at a specific surface roughness. This allows the PBR shader to sample sharp or blurry specular reflections based on roughness.
Although image-based environment lighting does not utilize a shadow map, as it provides pre-convolved ambient illumination, we still add a dynamic shadow to the engine. This shadow is a simple 2D depth map that updates every frame when the light moves, ensuring that the shadow moves accordingly. The shadow map texture, which holds the depth values written during the shadow pass, is kept within the main GPU state Texture & View.
Additionally, a Comparison Sampler with depth comparison turned on is used for hardware PCF to smooth out shadow edges. The shadow pipeline, a bare-bones pipeline that only writes depth values and skips color output, is used in the frame update. The shadow pass runs first, requiring only a uniform buffer with the light matrix, avoiding any GPU read/write conflicts.
The shadow vertex shader transforms mesh vertices using the light's matrix instead of the camera matrix, and a depth bias is applied to prevent shadow acne. Finally, in the main scene fragment shader, everything is put together, transforming the pixel's world position into light space to sample the shadow map and kill direct diffuse and specular light if occluded, leaving only ambient HDRI lighting.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.