bevy icon indicating copy to clipboard operation
bevy copied to clipboard

Implement irradiance volumes.

Open pcwalton opened this issue 2 years ago • 15 comments

Objective

Bevy could benefit from irradiance volumes, also known as voxel global illumination or simply as light probes (though this term is not preferred, as multiple techniques can be called light probes). Irradiance volumes are a form of baked global illumination; they work by sampling the light at the centers of each voxel within a cuboid. At runtime, the voxels surrounding the fragment center are sampled and interpolated to produce indirect diffuse illumination.

Solution

This is divided into two sections. The first is copied and pasted from the irradiance volume module documentation and describes the technique. The second part consists of notes on the implementation.

Overview

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 as to 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.

The primary reason for choosing ambient cubes is to match Blender, so that its Eevee renderer can be used for baking. However, they also have some advantages over the common second-order spherical harmonics approach: ambient cubes don't suffer from ringing artifacts, they are smaller (6 colors for ambient cubes as opposed to 9 for spherical harmonics), and evaluation is faster. A smaller basis allows for a denser grid of voxels with the same storage requirements.

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.

Implementation notes

This patch generalizes light probes so as to reuse as much code as possible between irradiance volumes and the existing reflection probes. This approach was chosen because both techniques share numerous similarities:

  1. Both irradiance volumes and reflection probes are cuboid bounding regions.
  2. Both are responsible for providing baked indirect light.
  3. Both techniques involve presenting a variable number of textures to the shader from which indirect light is sampled. (In the current implementation, this uses binding arrays.)
  4. Both irradiance volumes and reflection probes require gathering and sorting probes by distance on CPU.
  5. Both techniques require the GPU to search through a list of bounding regions.
  6. Both will eventually want to have falloff so that we can smoothly blend as objects enter and exit the probes' influence ranges. (This is not implemented yet to keep this patch relatively small and reviewable.)

To do this, we generalize most of the methods in the reflection probes patch #11366 to be generic over a trait, LightProbeComponent. This trait is implemented by both EnvironmentMapLight (for reflection probes) and IrradianceVolume (for irradiance volumes). Using a trait will allow us to add more types of light probes in the future. In particular, I highly suspect we will want real-time reflection planes for mirrors in the future, which can be easily slotted into this framework.

Changelog

This section is optional. If this was a trivial fix, or has no externally-visible impact, you can delete this section.

Added

  • A new IrradianceVolume asset type is available for baked voxelized light probes. You can bake the global illumination using Blender or another tool of your choice and use it in Bevy to apply indirect illumination to dynamic objects.

pcwalton avatar Oct 26 '23 02:10 pcwalton

This needs a bit of cleaning up and documentation, but it works and is pretty close.

pcwalton avatar Oct 26 '23 02:10 pcwalton

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

github-actions[bot] avatar Oct 26 '23 03:10 github-actions[bot]

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

github-actions[bot] avatar Jan 21 '24 00:01 github-actions[bot]

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

github-actions[bot] avatar Jan 22 '24 08:01 github-actions[bot]

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

github-actions[bot] avatar Jan 22 '24 08:01 github-actions[bot]

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

github-actions[bot] avatar Jan 23 '24 03:01 github-actions[bot]

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

github-actions[bot] avatar Jan 24 '24 01:01 github-actions[bot]

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

github-actions[bot] avatar Jan 24 '24 08:01 github-actions[bot]

Leaving this in the milestone for now, but we might have to end up cutting it depending on time constraints. @pcwalton if this is ready to go, make sure to take it out of draft.

JMS55 avatar Jan 24 '24 18:01 JMS55

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

github-actions[bot] avatar Jan 25 '24 04:01 github-actions[bot]

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

github-actions[bot] avatar Jan 26 '24 20:01 github-actions[bot]

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

github-actions[bot] avatar Jan 26 '24 23:01 github-actions[bot]

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

github-actions[bot] avatar Jan 27 '24 00:01 github-actions[bot]

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

github-actions[bot] avatar Jan 27 '24 02:01 github-actions[bot]

This should be ready to go now. I've verified that this works on Windows, macOS, Android, and WebGL 2. The check-bans CI failure seems unrelated.

pcwalton avatar Jan 28 '24 04:01 pcwalton

@JMS55 So I added the toggle requested, but it doesn't show up that well in the example because the irradiance volume ends above the camera. I tried drawing a small circle at the center of every voxel but that was waaaaaay too slow in debug mode.

pcwalton avatar Jan 29 '24 04:01 pcwalton

I reduced the number of voxels in the irradiance volume to a more reasonable level, so now the gizmo shows the center of each voxel.

pcwalton avatar Jan 29 '24 04:01 pcwalton

@JMS55 Actually, you know what, I only count 12 textures that are enabled in the PBR shader, even with this PR. So I think we're under the 16 limit.

pcwalton avatar Jan 29 '24 23:01 pcwalton

I added a visualization mode to the example that shows the voxels.

pcwalton avatar Jan 30 '24 02:01 pcwalton

This should be ready for re-review. All comments have been addressed.

pcwalton avatar Jan 30 '24 02:01 pcwalton

Comments addressed and ready for re-review.

pcwalton avatar Feb 02 '24 01:02 pcwalton

Screenshot 2024-02-02 145444

pcwalton avatar Feb 02 '24 22:02 pcwalton

Don't merge yet. The CI failure is legit.

pcwalton avatar Feb 03 '24 19:02 pcwalton

This should be ready for review again.

pcwalton avatar Feb 04 '24 18:02 pcwalton

Merging tomorrow morning unless I hear otherwise from the rendering crew.

alice-i-cecile avatar Feb 05 '24 20:02 alice-i-cecile