Severity
CRITICAL - Direct lighting has ~3.14x excess energy causing over-bright surfaces
Summary
The PBR lighting system has two energy conservation violations:
- Sun emission is not divided by π before entering Cook-Torrance BRDF, causing ~3.14x energy excess in direct lighting
- Environment map (IBL) is not pre-convolved for roughness, causing incorrect ambient energy for non-diffuse surfaces
Problem 1: Sun Emission Missing π Division (CRITICAL)
Location
- File:
assets/shaders/vulkan/terrain.frag
- Line: 452-453
Current Code
vec3 sunColor = global.sun_color.rgb * global.params.w * 4.0;
vec3 Lo = (kD * albedo / PI + specular) * sunColor * NdotL_final * (1.0 - totalShadow);
Issue
The BRDF diffuse term divides by π (albedo / PI), but the light source is not scaled accordingly. In physically-based rendering:
- Diffuse BRDF is
albedo / π
- Light should be
radiance * π (energy conserved)
OR:
- Light remains as radiance
- BRDF should NOT divide by π
Current implementation does both: light is radiance AND diffuse divides by π, resulting in ~3.14x too much energy.
Impact
- Direct lighting term is 3.14x brighter than physically correct
- Over-exposed surfaces in sunlit areas
- Inconsistent energy balance with ambient lighting
- Tone-mapping compensates but hides the root cause
Proposed Fix
Option A (Preferred - fix light source):
// Line 452: Divide sun color by π to match BRDF
vec3 sunColor = global.sun_color.rgb * global.params.w * 4.0 / PI;
vec3 Lo = (kD * albedo / PI + specular) * sunColor * NdotL_final * (1.0 - totalShadow);
Option B (Alternative - fix BRDF):
// Line 452: Keep light as radiance
vec3 sunColor = global.sun_color.rgb * global.params.w * 4.0;
// Line 453: Remove π from diffuse term (light is already irradiance)
vec3 Lo = (kD * albedo + specular) * sunColor * NdotL_final * (1.0 - totalShadow);
Recommendation: Option A - keeps consistent with standard PBR implementations where light sources are irradiance.
Sun Color Reference Values
From src/engine/atmosphere/sky_palette.zig:35-38 (after toLinear() conversion):
- day_sun: ≈ (1.0, 0.88, 0.77) × 4.0 = (4.0, 3.5, 3.1)
- dawn_sun: ≈ (1.0, 0.68, 0.30) × 4.0 = (4.0, 2.7, 1.2)
- night_sun: ≈ (0.0014, 0.0014, 0.01) × 4.0 = (0.006, 0.006, 0.04)
Problem 2: IBL Environment Map Not Pre-Filtered
Location
- File:
assets/shaders/vulkan/terrain.frag
- Line: 456-457, 572
Current Code
vec2 envUV = SampleSphericalMap(normalize(N));
vec3 envColor = textureLod(uEnvMap, envUV, 8.0).rgb;
Issue
The environment map is sampled at a fixed mip level (8.0) regardless of surface roughness. For energy-conserving IBL:
- Rough surfaces need pre-convolved radiance (blurrier, higher mip level)
- Smooth surfaces need sharp radiance (lower mip level)
Current fixed mip level causes:
- Incorrect ambient energy for glossy/metallic surfaces
- Loss of material detail on rough surfaces
- Inconsistent reflection blur matching roughness
Impact
- Secondary issue (less critical than sun emission)
- Ambient lighting quality degradation for PBR materials
- Especially noticeable on smooth surfaces that should have sharp reflections
Proposed Fix
-
Generate pre-filtered envmap (offline or at load time):
- For each mip level, convolve with importance sampling using roughness matching that mip
- Store as separate texture or texture array
-
Update shader to use roughness for mip selection:
float roughness = clamp(packedPBR.r, 0.05, 1.0);
float envMipLevel = roughness * MAX_ENV_MIPS;
vec3 envColor = textureLod(uEnvMap, envUV, envMipLevel).rgb;
Verification Steps
After implementing fixes:
- Render a white Lambertian sphere facing the sun
- Verify luminance ≈ π (in linear space) at surface facing the sun directly
- Check that shadows don't cause ambient > direct lighting
- Validate IBL reflections blur correctly with roughness
- Compare tone-mapped output with reference PBR renders
References
Severity
CRITICAL - Direct lighting has ~3.14x excess energy causing over-bright surfaces
Summary
The PBR lighting system has two energy conservation violations:
Problem 1: Sun Emission Missing π Division (CRITICAL)
Location
assets/shaders/vulkan/terrain.fragCurrent Code
Issue
The BRDF diffuse term divides by π (
albedo / PI), but the light source is not scaled accordingly. In physically-based rendering:albedo / πradiance * π(energy conserved)OR:
Current implementation does both: light is radiance AND diffuse divides by π, resulting in ~3.14x too much energy.
Impact
Proposed Fix
Option A (Preferred - fix light source):
Option B (Alternative - fix BRDF):
Recommendation: Option A - keeps consistent with standard PBR implementations where light sources are irradiance.
Sun Color Reference Values
From
src/engine/atmosphere/sky_palette.zig:35-38(after toLinear() conversion):Problem 2: IBL Environment Map Not Pre-Filtered
Location
assets/shaders/vulkan/terrain.fragCurrent Code
Issue
The environment map is sampled at a fixed mip level (8.0) regardless of surface roughness. For energy-conserving IBL:
Current fixed mip level causes:
Impact
Proposed Fix
Generate pre-filtered envmap (offline or at load time):
Update shader to use roughness for mip selection:
Verification Steps
After implementing fixes:
References