Vulkan Schnee 0.0.1
High-performance rendering engine
Loading...
Searching...
No Matches
Spherical Harmonics Probe Baking

SH probes capture indirect diffuse lighting at discrete points in a scene. At runtime, each object samples the nearest probe to receive ambient illumination from surrounding surfaces, lights, and the sky.

The pipeline has three stages: bake in Blender → export via glTF → load in the engine.

Source files:

  • BlenderPlugins/plugin/SphericalHarmonicsBaking.py - Baking operator
  • BlenderPlugins/ExporterPlugin/__init__.py - glTF scene extension hook
  • Engine/include/Engine/Renderer/SHProbeGrid.h - Engine-side data structures
  • Engine/src/Engine/Renderer/SHProbeGrid.cpp - Binary file loading
  • Engine/src/Engine/Ecs/FrameProcessing.cpp - glTF extension parsing

Blender Setup

1. Create a ProbeVolumes collection

In the Blender Outliner, create a collection named exactly ProbeVolumes. Add one or more closed mesh objects to this collection. Each mesh defines a region where probes will be placed.

The volume meshes do not render in the final export. Any mesh topology works as long as the volume is closed (watertight normals pointing outward).

Multiple meshes in the collection are treated as separate volumes — probes are placed inside the union of all volumes.

2. Probe placement rules

The baking script places probes only at positions that satisfy both conditions:

  • The position is inside at least one volume mesh (ray-cast test)
  • The position is not inside any other scene mesh (walls, floors, solid geometry)

Probes inside solid geometry are pruned automatically. If a probe location falls inside a wall due to cell alignment, it is skipped.


Baking Settings

Click Bake SH in Volumes in the 3D Viewport > Sidebar (N) > Schnee Engine panel. A dialog appears with these parameters:

Parameter Default Description
Probe Volume Collection ProbeVolumes Name of the Blender collection containing volume meshes
Min Cell Size (m) 0.5 Smallest octree cell size. Cells this small are placed near scene geometry.
Max Cell Size (m) 16.0 Largest octree cell size. Open areas (no nearby geometry) get one probe per 16m cell.
Panorama Resolution 128 Width of each per-probe equirectangular render. Height = Width / 2.
Max SH Band (L) 2 Maximum SH band. L=2 produces 9 coefficients (L0 + L1 + L2).

Adaptive octree placement

The baking system builds a sparse adaptive octree over the probe volumes. Cells that overlap scene geometry are subdivided down to Min Cell Size. Cells with no nearby geometry remain at Max Cell Size. This concentrates probes where lighting varies (near surfaces, in corners) and uses fewer probes in open space.

Example for a 10m × 10m × 4m room:

  • Min=0.5m → 8 probes across 4m height near walls/floor/ceiling
  • Max=4m → 1 probe per 4m slab in the room center
  • Typical result: 200-400 probes vs. ~3200 for a uniform 0.5m grid

Render settings

The operator temporarily overrides Cycles render settings for each probe:

  • Render engine: Cycles (required for panorama camera)
  • 32 samples with adaptive sampling (min 8 samples, threshold 0.1)
  • Denoiser: disabled (not needed at 128×64)
  • Output format: 16-bit EXR (uncompressed, for speed)
  • GPU rendering: enabled if available

Original scene render settings are restored after baking completes.

Prerequisites

SH baking requires scipy. Install it from the Schnee Engine panel:

3D Viewport > Sidebar (N) > Schnee Engine > Dependencies > Install Dependencies

Or manually in Blender's Python environment:

pip install scipy

Running the Bake

  1. Save the .blend file (required — output path is relative to it)
  2. Click Bake SH in Volumes in the Schnee Engine sidebar panel
  3. Adjust parameters in the dialog
  4. Click OK

The operator renders one Cycles panorama per probe location and projects the result onto L0–L2 SH basis functions. Progress is printed to the system console (Window > Toggle System Console).

Output: //sh_coeffs/probes.shprobe (relative to the .blend file)

For a scene with multiple named volumes, only one probes.shprobe is written per bake run. To bake separate volumes independently, run the operator multiple times with different collection names and manually rename the output files between runs.

Debug baking

Debug: Bake SH at Cursor bakes a single probe at the 3D cursor position. Output goes to //sh_coeffs/debug_probe.txt (text) and //sh_coeffs/probes.shprobe (binary, single probe). Useful for verifying that the render pipeline and scipy integration work before running a full bake.


Exporting

Automatic .shprobe detection

The glTF exporter plugin automatically includes .shprobe files during export — no manual configuration is needed. In File > Export > glTF 2.0, ensure Export Engine Properties is enabled (it is by default).

At export time, the gather_scene_hook scans sh_coeffs/ next to the .blend file. For each .shprobe file found, it:

  1. Reads the 48-byte binary header to extract metadata (probe count, grid dimensions, origin, spacing, default scale, default ambient)
  2. Adds an entry to the shProbeGrids array in the scene-level VULKAN_SCHNEE_engine_properties extension
  3. Copies the .shprobe file to sh_coeffs/<filename> relative to the glTF output directory

glTF extension JSON

The scene-level extension looks like this for a single volume:

{
"extensions": {
"VULKAN_SCHNEE_engine_properties": {
"shProbeGrids": [
{
"shProbePath": "sh_coeffs/probes.shprobe",
"volumeName": "probes",
"probeCount": 312,
"gridDimensions": [40, 8, 40],
"gridOrigin": [-10.0, 0.0, -10.0],
"gridSpacing": 0.5,
"defaultScale": 1.0,
"defaultAmbient": 0.1
}
]
}
}
}

gridOrigin and grid axes are in engine coordinate space (Y-up). The baking script converts from Blender's Z-up coordinate system automatically:

Multi-volume export

Place multiple .shprobe files in sh_coeffs/. The exporter adds one entry per file to shProbeGrids, sorted by filename. The engine loads all entries on scene load and queries them in order — the first grid containing the world position wins.

"shProbeGrids": [
{ "shProbePath": "sh_coeffs/interior.shprobe", "volumeName": "interior", ... },
{ "shProbePath": "sh_coeffs/exterior.shprobe", "volumeName": "exterior", ... }
]

Export directory layout

After export, the directory structure next to the .gltf file:

scene.gltf
scene.bin
sh_coeffs/
probes.shprobe

Engine Loading

When a glTF scene is loaded, FrameProcessing.cpp checks the scene-level VULKAN_SCHNEE_engine_properties extension for a shProbeGrids array. Each entry is parsed into an SHProbeGridInfo struct and the referenced .shprobe file is loaded via SHProbeGrid::loadFromFile().

Probe data is uploaded to a GPU storage buffer (SHProbeBuffer). Index 0 is always the neutral fallback probe (hardcoded SH coefficients matching the shader defaults). Objects outside all probe volumes receive this fallback.

Per-object probe assignment uses RenderingDataManager::queryProbeIndex(worldPos), which iterates loaded grids and calls SHProbeGrid::getProbeIndex() on each. The first grid returning a valid index (≥ 0) wins. Invalid occupancy grid cells (value -1) also fall back to index 0.


.shprobe Binary Format

Version 1 format, little-endian:

Offset Size Type Field
0 4 uint32 Magic: 0x52485053 ("SHPR")
4 4 uint32 Version: 1
8 4 uint32 Probe count
12 4 uint32 Grid dim X
16 4 uint32 Grid dim Y
20 4 uint32 Grid dim Z
24 4 float Grid origin X
28 4 float Grid origin Y
32 4 float Grid origin Z
36 4 float Grid spacing (meters)
40 4 float Default SH scale
44 4 float Default ambient term
48 probeCount × 160 - Probe data (9 × vec4 coefficients + scale + ambient + 8 bytes padding)
after probes dimX × dimY × dimZ × 4 int32[] Occupancy grid (-1 = empty cell)

Occupancy grid flat index: x + y * dimX + z * dimX * dimY


Material Parameters

The VS Movable Diffuse shader node in Blender has two parameters that control how SH data is applied at runtime. Set them per-material in the shader node graph:

Parameter Default Description
SH Scale 1.0 Multiplier for all SH coefficients. Lower it only for intentional artistic dimming.
Ambient Term 0.1 Constant added to diffuse after SH evaluation. Prevents fully dark areas in uncovered regions.

These values are exported with the material via the VULKAN_SCHNEE_materials extension and read by the engine shader pipeline.


Practical Settings

Interior room (living room, corridor):

Min Cell Size: 0.5m
Max Cell Size: 4.0m
Panorama Resolution: 128

Outdoor environment (courtyard, terrain):

Min Cell Size: 2.0m
Max Cell Size: 32.0m
Panorama Resolution: 128

Large interior with open areas (warehouse, hall):

Min Cell Size: 0.5m
Max Cell Size: 8.0m
Panorama Resolution: 128

Increasing Panorama Resolution beyond 128 has diminishing returns — SH order 2 captures only low-frequency lighting, so detail in the panorama averages out during projection. Use higher resolution only when L_MAX is set to 3 or 4.


Related Documentation