Sphere Light-Probes¶
To improve performance radiance light in the scene can be recorded in a sphere probe. There are two kind of sphere probes.
- World: Records the light from the background only.
- Probe: Records light from geometry in the scene from a specific location.
flowchart LR
RenderCubemap --> RemapCubeToOctahedralProjection --> UpdateWorldIrradianceFromOctahedralProjection
Each sphere probe is first rendered in a cubemap. See CaptureView::render_world or CaptureView::render_probes.
The cubemap is remapped to octahedral projection. See SphereProbeModule::remap_to_octahedral_projection.
In case we are creating a sphere probe for a world the irradiance of the world should also be updated.
See SphereProbeModule::update_world_irradiance
World probe baking¶
sequenceDiagram
participant Instance as Instance
participant View as CaptureView
participant Module as SphereProbeModule
participant Pipeline as WorldProbePipeline
activate Instance
Instance->>View: render_world()
activate View
alt world needs update
loop for each cubemap side
View->>Pipeline: render()
activate Pipeline
deactivate Pipeline
end
View->>Module:remap_to_octrahedral_projection()
activate Module
deactivate Module
View->>Module:update_probes_texture_mipmaps()
activate Module
deactivate Module
end
alt update irradiance needed?
View->>Module:update_world_irradiance()
activate Module
deactivate Module
end
deactivate View
deactivate Instance
Reflection probe baking¶
When a change is detected for a sphere probe it is marked to be updated. Prerequisites to update the sphere probes are:
- All material shaders should have been compiled.
- The
DeferredProbePipelineshould be activated. To save overhead theDeferredProbePipelineand the (probe_prepass,probe_shading) material passes are only created and populated when there are probes that needs to be updated.
The previous state of the sphere probes will be used, until the sphere probes are updated.
sequenceDiagram
participant Instance as Instance
participant View as CaptureView
participant Module as SphereProbeModule
participant Pipeline as DeferredProbePipeline
activate Instance
Instance->>View: render_probes()
activate View
loop while sphere probes left to update
View->>Module: update_info_pop
activate Module
deactivate Module
loop for each cubemap side
View->>Pipeline: render()
activate Pipeline
deactivate Pipeline
end
View->>Module:remap_to_octrahedral_projection()
activate Module
deactivate Module
end
alt new probe rendered
View->>Module:update_probes_texture_mipmaps()
activate Module
deactivate Module
end
deactivate View
deactivate Instance
Octahedral mapping¶
To reduce memory needs and improve sampling performance the cubemap
is stored in octahedral mapping space. This is done in eevee_lightprobe_sphere_remap_comp.glsl.
The regular octahedral mapping has been extended to fix leakages at the edges of the texture and to be able to be used on an atlas texture and by sampling the texture once.
We follow the regular octahedral mapping process. The input is a cubemap what can be evaluated using directions. These directions can be represented as a sphere. The next images are used as a visual guidance how the algorithm is structured.

Project the directions of the sphere onto an octahedron.

Unwrap the octahedron. At this point we can find a uv coordinate based on an directional vector.

To reduce sampling cost and improve the quality we add an border around the octahedral map and extend the octahedral coordinates. This also allows us to generate lower resolution mipmaps of the atlas texture using 2x2 box filtering from a higher resolution.

The octahedral map has an additional of 8 pixels of border on each side to support 4 mipmap levels.
Update Irradiance Cache¶
Diffuse lighting in the scene requires an Irradiance Volume object. When a scene
doesn't have this object setup the world light would not be visible on diffuse objects.
We update the first brick of the irradiance atlas texture with the world lighting so it will
still be visible. This is done inside SphereProbeModule::update_world_irradiance.
eevee_reflection_probe_update_irradiance_comp.glsl calculates the spherical harmonics
by evaluating the octahedral mapped world probe and stores the calculated spherical
harmonics homogeneously in the irradiance atlas texture.
Every probe resolution always contains a subdivision level containing a map of 64x64. This map would be selected as it is faster to extract the data from this resolution than a higher one and has enough data to capture the probe.
| Probe resolution | layer_subdivision | layer containing 64x64 |
|---|---|---|
| 2048x2048 | 0 | 5 |
| 1024x1024 | 1 | 4 |
| 512x512 | 2 | 3 |
| 256x256 | 3 | 2 |
| 128x128 | 4 | 1 |
| 64x64 | 5 | 0 |
Storage¶
The recorded sphere probes are stored in a single texture array. Each probe can be of any resolution as long as it is a power of 2 and not larger than 2048 or smaller than 64. So valid options are (2048x2048, 1024x1024, 512x512, etc).
Each probe can be stored in their own resolution and can be set by the user.
Note
Eventually we would like to add automatic resolution selection. This could be done by the distance from the camera or view.
The probes are packed in an 2d texture array with the dimension of 2048*2048. The number of layers depends on the actual needed layers to store all the probes.
Subdivisions and areas¶
Probes data are stored besides the texture. The data is used to find out where the probe is stored in the texture. It is also used to find free space to store new probes.
For each probe the next data is stored:
- layer: on which layer of the texture contains the data of the probe
- layer_subdivision: what subdivision is used by the probe. With the subdivision the resolution of probe in the layer can be determined. 0 would use the resolution of the texture (2048x2048). 1 divides the texture in 4 even areas of (1024x1024). 2 divides the texture in 16 areas of (512x512).
- area_index: What area of the layer contains the probe. A layer contains out of
(2^layer_subdivision) * (2^layer_subdivision)areas.
This approach ensures that we can be flexible at storing probes with different resolutions on the same layer. Lets see an example how that works
Example¶
Lets assume we have a world probe with the resolution of 1024x1024 and 2 sphere probes with the resolution of 512x512. The resolution of the texture is 2048x2048. This could be stored in the texture as follow
| Probe | layer | layer_subdivision | area_index | Area in texture coordinates |
|---|---|---|---|---|
| 0 | 0 | 1 | 0 | 0,0,0 - 1023,1023,0 |
| 1 | 0 | 2 | 2 | 1024,0,0 - 1535,511,0 |
| 2 | 0 | 2 | 3 | 1536,0,0 - 2047,511,0 |
Visually:
Layer 0 viewed at subdivision level 2
+---+---+---+---+
| | | | |
| | | | |
+---+---+---+---+
| | | | |
| | | | |
+---+---+---+---+
|000|000| | |
|000|000| | |
+---+---+---+---+
|000|000|111|222|
|000|000|111|222|
+---+---+---+---+
Looking at the same data, but in subdivision level 1 would look like:
Layer 0 viewed at subdivision level 1
+-------+-------+
| | |
| | |
| | |
| | |
| | |
+-------+-------+
|0000000|1212121|
|0000000|2121212|
|0000000|1212121|
|0000000|2121212|
|0000000|1212121|
+-------+-------+
Code-wise this is implemented by ProbeLocationFinder. ProbeLocationFinder can view a texture in a
given subdivision level and mark areas that are covered by probes. When finding a free spot it returns
the first empty area.
Evaluation¶
Evaluation is implemented in eevee_reflection_probe_eval_lib.glsl.
- Select the closest sphere probe. This is done by calculating the distance between P and the position of the sphere probe.
- Get the pixel based of the closest sphere probe for the needed vector
- If pixel is transparent or no sphere probe is available. * get the world background based on the same vector * mix the background with the pixel from the sphere probe.
- Apply GGX BRDF