Expand description

Irradiance volumes, also known as voxel global illumination.

An irradiance volume is a cuboid voxel region consisting of regularly-spaced precomputed samples of diffuse indirect light. They’re ideal if you have a dynamic object such as a character that can move about static non-moving geometry such as a level in a game, and you want that dynamic object to be affected by the light bouncing off that static geometry.

To use irradiance volumes, you need to precompute, or bake, the indirect light in your scene. Bevy doesn’t currently come with a way to do this. Fortunately, Blender provides a baking tool as part of the Eevee renderer, and its irradiance volumes are compatible with those used by Bevy. The bevy-baked-gi project provides a tool, export-blender-gi, that can extract the baked irradiance volumes from the Blender .blend file and package them up into a .ktx2 texture for use by the engine. See the documentation in the bevy-baked-gi project for more details on this workflow.

Like all light probes in Bevy, irradiance volumes are 1×1×1 cubes that can be arbitrarily scaled, rotated, and positioned in a scene with the bevy_transform::components::Transform component. The 3D voxel grid will be stretched to fill the interior of the cube, and the illumination from the irradiance volume will apply to all fragments within that bounding region.

Bevy’s irradiance volumes are based on Valve’s ambient cubes as used in Half-Life 2 (Mitchell 2006, slide 27). These encode a single color of light from the six 3D cardinal directions and blend the sides together according to the surface normal. For an explanation of why ambient cubes were chosen over spherical harmonics, see Why ambient cubes? below.

If you wish to use a tool other than export-blender-gi to produce the irradiance volumes, you’ll need to pack the irradiance volumes in the following format. The irradiance volume of resolution (Rx, Ry, Rz) is expected to be a 3D texture of dimensions (Rx, 2Ry, 3Rz). The unnormalized texture coordinate (s, t, p) of the voxel at coordinate (x, y, z) with side S{-X, +X, -Y, +Y, -Z, +Z} is as follows:

s = x

t = y + ⎰  0 if S ∈ {-X, -Y, -Z}
        ⎱ Ry if S ∈ {+X, +Y, +Z}

        ⎧   0 if S ∈ {-X, +X}
p = z + ⎨  Rz if S ∈ {-Y, +Y}
        ⎩ 2Rz if S ∈ {-Z, +Z}

Visually, in a left-handed coordinate system with Y up, viewed from the right, the 3D texture looks like a stacked series of voxel grids, one for each cube side, in this order:

+X+Y+Z
-X-Y-Z

A terminology note: Other engines may refer to irradiance volumes as voxel global illumination, VXGI, or simply as light probes. Sometimes light probe refers to what Bevy calls a reflection probe. In Bevy, light probe is a generic term that encompasses all cuboid bounding regions that capture indirect illumination, whether based on voxels or not.

Note that, if binding arrays aren’t supported (e.g. on WebGPU or WebGL 2), then only the closest irradiance volume to the view will be taken into account during rendering. The required wgpu features are bevy_render::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY and bevy_render::settings::WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING.

Why ambient cubes?

This section describes the motivation behind the decision to use ambient cubes in Bevy. It’s not needed to use the feature; feel free to skip it unless you’re interested in its internal design.

Bevy uses Half-Life 2-style ambient cubes (usually abbreviated as HL2) as the representation of irradiance for light probes instead of the more-popular spherical harmonics (SH). This might seem to be a surprising choice, but it turns out to work well for the specific case of voxel sampling on the GPU. Spherical harmonics have two problems that make them less ideal for this use case:

  1. The level 1 spherical harmonic coefficients can be negative. That prevents the use of the efficient RGB9E5 texture format, which only encodes unsigned floating point numbers, and forces the use of the less-efficient RGBA16F format if hardware interpolation is desired.

  2. As an alternative to RGBA16F, level 1 spherical harmonics can be normalized and scaled to the SH0 base color, as Frostbite does. This allows them to be packed in standard LDR RGBA8 textures. However, this prevents the use of hardware trilinear filtering, as the nonuniform scale factor means that hardware interpolation no longer produces correct results. The 8 texture fetches needed to interpolate between voxels can be upwards of twice as slow as the hardware interpolation.

The following chart summarizes the costs and benefits of ambient cubes, level 1 spherical harmonics, and level 2 spherical harmonics:

TechniqueHW-interpolated samplesTexel fetchesBytes per voxelQuality
Ambient cubes3024Medium
Level 1 SH, compressed03616Low
Level 1 SH, uncompressed4024Low
Level 2 SH, compressed07228High
Level 2 SH, uncompressed9054High

(Note that the number of bytes per voxel can be reduced using various texture compression methods, but the overall ratios remain similar.)

From these data, we can see that ambient cubes balance fast lookups (from leveraging hardware interpolation) with relatively-small storage requirements and acceptable quality. Hence, they were chosen for irradiance volumes in Bevy.

Structs

Constants