Vulkan Schnee 0.0.1
High-performance rendering engine
Loading...
Searching...
No Matches
Lightmap Light Baking

Lightmaps capture baked direct and indirect illumination for static scene geometry. At runtime, the staticLightmap shader samples a pre-baked EXR texture via a dedicated UV channel (TEXCOORD_1), replacing dynamic lighting with zero-cost precomputed results.

The pipeline: configure objects in Blender → bake with Cycles → export via glTF → load in engine.

Source files:

  • BlenderPlugins/plugin/LightBaking.py - Core baking implementation
  • BlenderPlugins/plugin/AtlasBaking.py - Atlas batching for large scenes
  • BlenderPlugins/plugin/LightmapAutomation.py - Automated setup operator
  • BlenderPlugins/plugin/LightmapResolutionCalculator.py - Surface area-based resolution
  • BlenderPlugins/plugin/LightmapSettings.py - Scene settings panel
  • BlenderPlugins/plugin/LightmapVisualization.py - Viewport overlay
  • BlenderPlugins/plugin/GameEngineProperties.py - Per-object properties
  • BlenderPlugins/ExporterPlugin/VulkanSchneeExtension.py - glTF export
  • Engine/include/Engine/Mesh/GltfLoader.h - Lightmap data structures
  • Engine/src/Engine/Mesh/GltfLoader.cpp - Engine-side EXR loading
  • Engine/include/Engine/Mesh/Vertex.h - Vertex struct with lightmapUvCoordinate
  • Engine/include/Engine/Material/MaterialData.h - StaticLightmapMaterialData class
  • Engine/shaders/material/triangle_static_lightmap.frag - Lightmap fragment shader
  • Engine/shaders/material/triangle_static_lightmap_alpha_mask.frag - Alpha mask variant

Prerequisites

Only static mesh objects participate in lightmap baking. Objects with is_static = False are hidden during bake and skipped entirely by the bake script.

Materials must use the VS Static Lightmap shader node. Add it from Shader Editor > Sidebar (N) > Vulkan Schnee > Lightmap Shaders > Static Lightmap (UV2).


Complete Workflow

1. Mark Objects for Lightmapping

For each static mesh:

  1. Select the mesh object
  2. Go to Properties > Object Properties > Vulkan Schnee Engine
  3. Under Lightmaps, enable Has Lightmaps
  4. Set Lightmap Resolution (see Resolution Selection)
  5. Set Lightmap UV Index to the UV channel index of LightmapUV

Or use the automated operator to handle all three steps at once (see Automated Setup).

2. Set Up Materials

Add the VS Static Lightmap shader node to every material on lightmapped objects:

Shader Editor > Add > Vulkan Schnee Shaders > Static Lightmap (UV2)

Connect inputs:

  • Base Color — albedo texture
  • Lightmap Texture — baked lightmap (TEXCOORD_1 is set automatically by the engine)
  • Normal Map, Roughness, Metallic, Emissive — PBR maps as needed

Connect the node's BSDF output to Material Output.

3. Configure Bake Settings

Go to Properties > Scene Properties > Lightmap Settings:

Setting Description
Quality Preset Draft (32), Preview (128), Production (512), Ultra (1024), Custom
Bake Samples Sample count; only editable when preset is Custom
Adaptive Sampling Stops sampling converged pixels; reduces bake time
Noise Threshold Lower = cleaner (0.01 high quality, 0.1 fast draft)
Min Samples Minimum samples before adaptive kicks in
Use Denoiser Post-bake denoising (OptiX for NVIDIA, OIDN for any GPU)
UV Margin Pixels around UV islands to prevent seams (default 6)
Margin Type Adjacent Faces (recommended) or Extend
EXR Compression None (fastest), ZIP (smallest)

4. Run the Bake

  1. Save the .blend file (output paths are relative to it)
  2. Click Bake Lightmaps in 3D Viewport > Sidebar (N) > Schnee Engine > Lighting

The script bakes all objects that have has_lightmaps = True and a valid LightmapUV channel. Output files go to //lightmaps/ relative to the .blend file.

Bake output naming:

  • Combined mode: ObjectName_LM.exr
  • Light-only mode: ObjectName_LightOnly.exr

After baking, each object gets a duplicate in the Baked_Lightmaps collection (hidden from render). The original object's lightmap_path property is updated to the output file path.

Bake optimization: The script uses a single-BVH strategy: all objects are prepared first (images created, nodes set up), baked in one pass without scene modifications, then finalized. This avoids repeated BVH rebuilds that would otherwise dominate bake time for large scenes.

5. Export

Use File > Export > GLTF with Vulkan Schnee Data. The exporter writes lightmap metadata to the VULKAN_SCHNEE_engine_properties node-level extension:

{
"extensions": {
"VULKAN_SCHNEE_engine_properties": {
"lightingProperties": {
"hasLightmaps": true
},
"lightmapProperties": {
"lightmapPath": "//lightmaps/Chair_LM.exr",
"lightmapUvIndex": 1,
"lightmapResolution": 1024,
"lightOnly": false
}
}
}
}

Resolution Selection

Manual Presets

In Object Properties > Vulkan Schnee Engine > Lightmaps, preset buttons set resolution directly:

Preset Use
512 Small/background objects
1K (1024) Standard resolution
2K (2048) Large/hero objects
4K (4096) Maximum detail

Auto-Assignment by Surface Area

The resolution calculator assigns resolution based on world-space surface area × texel density:

ideal_resolution = sqrt(surface_area_m2) * texel_density

Result is clamped to the nearest power-of-2 in [256, 512, 1024, 2048, 4096].

Texel density presets (texels per linear meter):

Preset Density Application
Low 128 Mobile/background
Medium 256 Standard quality (default)
High 512 Hero objects
Ultra 1024 Extreme detail

Run auto-assignment from Scene Properties > Lightmap Settings > Resolution Calculator:

  • Auto-Assign to Selected — processes selected mesh objects
  • Auto-Assign to All Lightmap Objects — processes all objects with has_lightmaps = True

Automated Setup

Operator: object.vulkanschnee_automated_lightmap_setup

Location: 3D Viewport > Sidebar (N) > Schnee Engine > Automated Lightmap Setup

Processes selected mesh objects in one step: creates the LightmapUV UV channel, unwraps it, and sets the object properties.

Dialog options:

Option Description
Resolution Preset 512, 1024, 2048, 4096, or Custom
Enable Lightmaps Sets has_lightmaps = True
Lightmap Only Enables light-only baking (pure lighting, no albedo)
Use Existing UV Skips channel creation if LightmapUV already exists
Use xatlas Packing Cross-platform UV island packing using the bundled xatlas CLI
UV Padding (px) Pixel padding between UV islands
High Quality Packing Uses xatlas brute-force chart placement for better utilization
Angle Limit Smart UV unwrap angle limit (default ~66°)
Initial Island Margin Temporary Smart UV Project margin before xatlas packing

Processing order per object:

  1. Create LightmapUV UV channel (or reuse existing)
  2. Smart UV Project unwrap onto that channel when a new UV layout is needed
  3. Pack the LightmapUV islands with xatlas using the selected pixel margin
  4. Set has_lightmaps, lightmap_resolution, lightmap_uv_index on the object

The xatlas packer is a small native tool built on demand by CMake from https://github.com/jpcy/xatlas. Objects are grouped by lightmap resolution for the command call, but each mesh is packed into its own 0-1 UV space so existing per-object lightmap baking still works.


Light-Only Baking

When bake_light_only = True on an object, the bake uses DIFFUSE pass type with direct + indirect contributions and no albedo color. This produces pure lighting data without material colors — useful for scenes where albedo is applied separately or where tonemapping is not desired.

  • Bake type: DIFFUSE (direct + indirect only, no color)
  • Color space: Linear Rec.709
  • Output filename: ObjectName_LightOnly.exr

The engine applies this texture directly, without the albedo multiplication that the standard combined-mode path uses.


Atlas Batching

For scenes with many objects (100+), repeated Cycles BVH rebuilds dominate bake time. Atlas batching groups objects and bakes them together into a shared texture atlas, reducing BVH rebuilds from N to N/groupSize.

Enable in Scene Properties > Lightmap Settings > Atlas Batching:

Setting Description
Enable Atlas Batching Groups objects by resolution for batch baking
Max Atlas Size 4K (4096), 8K (8192, recommended), 16K (16384)

Objects per atlas at 8K:

Object Resolution Objects per Atlas
256px 1024
512px 256
1024px 64
2048px 16
4096px 4

Strategy: Objects are grouped by resolution. Within each group, duplicates are joined into one mesh, baked as a single atlas, then results are split back per-object. Original objects are never modified.

Expected speedup: 40–100x for BVH-bound scenarios with 100+ objects at similar resolutions.


Viewport Visualization

The resolution overlay color-codes objects by their assigned lightmap resolution. Toggle via Scene Properties > Lightmap Settings > Visualization > Show Resolution Overlay.

Color Resolution
Red 256px
Orange 512px
Yellow 1024px
Green 2048px
Blue 4096px

Objects without lightmaps are not drawn. The overlay caches per-object GPU batches and invalidates them when mesh geometry changes.


Engine Loading

The engine loads lightmap textures during glTF import in GltfLoader.cpp. For each mesh with lightingProperties.hasLightmaps = true and a valid lightmapProperties.lightmapPath:

  1. The EXR file at lightmapPath is loaded via ExrLoader
  2. The texture is stored as lightmapTextureData on the material asset
  3. UV coordinates use the accessor at TEXCOORD_<lightmapUvIndex> (TEXCOORD_1 by default)
  4. The descriptor index is resolved asynchronously; the material buffer updates when the texture becomes available

The GPU material struct (GpuPbrMaterial) has two lightmap fields:

uint lightmapTextureIndex; // 0xFFFFFFFF = not present
uint hasLightmap; // 1 if present

The fragment shader (triangle_static_lightmap.frag) multiplies albedo by the lightmap sample:

// Pseudocode of the final color computation
vec3 lightmapColor = texture(textureSamplers[lightmapTextureIndex], fragLightmapUV).rgb;
vec3 finalColor = applyToneMapping(albedo.rgb * lightmapColor);

If no lightmap is present (hasLightmap == 0), the shader falls back to ambient_term (defaults to 1.0 for static lightmap materials, so the object appears fully lit rather than black).

Debug Modes

The static lightmap shader supports debug visualization via the Debug Mode material parameter:

Mode Output
0 Normal rendering
1 Surface normals as RGB
2 Lightmap UV coordinates
3 Raw lightmap without albedo
4 Albedo only (no lighting)
5 Normal map texture (raw RGB)
6 Metallic-roughness texture (R=metallic, G=roughness)
7 Emissive texture
8 Texture indices as colors (R=baseColor, G=normal, B=lightmap)
9 Texture binding presence (binary: present=1, missing=0)
10 Meshlet ID (PCG hash coloring)
11 Meshlet ID (golden ratio hue separation)
12 Object ID visualization

Validation

Diagnose Lightmap Issues (3D Viewport > Sidebar > Schnee Engine > Diagnose Lightmap Issues):

  • Checks for missing LightmapUV channel
  • Reports UV map issues per object

Validate Lightmaps (object.vulkanschnee_validate_lightmaps):

  • Scans all objects with has_lightmaps = True
  • Reports UV map problems across the entire scene

Object Properties panel warnings:

  • UV Issue: ... — shown in red when LightmapUV channel is missing or invalid
  • Warning: Lightmap path is empty! — shown when no output path is set

glTF Extension Reference

The exporter writes two keys to VULKAN_SCHNEE_engine_properties per node:

lightingProperties (inside lightingProperties sub-object):

{
"castShadows": true,
"receiveShadows": true,
"hasLightmaps": true
}

lightmapProperties (sibling of lightingProperties):

{
"lightmapPath": "//lightmaps/Chair_LM.exr",
"lightmapUvIndex": 1,
"lightmapResolution": 1024,
"lightOnly": false
}

The engine parser (GltfLoader.cpp) reads both keys independently. lightmapProperties is only loaded if hasLightmaps = true and lightmapPath is non-empty.


Related Documentation