Global Exposure Management Analysis¶
Date: January 24, 2026
This analysis details how exposure is defined, managed, and applied in the application, covering both the CPU (management) and GPU (implementation and auto-exposure).
1. Definition and Data Structure¶
Exposure is managed via two distinct, non-overlapping modes: Manual and Automatic.
CPU Side (include/postprocess.h, src/postprocess.c)¶
Parameters are stored in the PostProcess structure:
-
Manual Exposure:
- Struct:
ExposureParams - Variable:
exposure(float). - Default:
1.0(Defined byDEFAULT_EXPOSURE). - Control:
postprocess_set_exposure().
- Struct:
-
Automatic Exposure (Eye Adaptation):
- Struct:
AutoExposureParams - Variables:
min_luminance/max_luminance: Clamping range for scene luminance.speed_up/speed_down: Adaptation speed (eye opens faster than it closes).key_value: The target middle grey value (Exposure anchor).
- Control:
postprocess_set_auto_exposure().
- Struct:
GPU Side (Uniforms & Textures)¶
- Manual: Transmitted via the
exposure.exposureuniform. - Automatic:
autoExposureTexture: 1x1GL_RGBA32Ftexture (Image Load/Store) storing the persistent exposure state.lumTexture: 64x64GL_R16Ftexture used as an intermediate step for average luminance calculation.
2. Runpaths and Usage Modes¶
The mode selection occurs in the render loop postprocess_end().
Mode A: Automatic Exposure (POSTFX_AUTO_EXPOSURE)¶
If this flag is enabled, a pre-calculation pass is executed before the final render.
-
Downsample (Luminance Reduction)
- File:
shaders/lum_downsample.frag - Action: Takes the HDR scene (
scene_color_tex) and reduces it to a 64x64 texture. - Logic:
- Samples 4x4 blocks.
- Calculates luminance:
dot(color, vec3(0.2126, 0.7152, 0.0722)). - Converts to log:
log2(lum). - Averages valid values (ignoring pure blacks/infinite).
- Goal: Prepare compact data for the Compute Shader.
- File:
-
Adaptation (Compute Shader)
- File:
shaders/lum_adapt.comp - Action: Calculates final exposure with temporal inertia.
- Logic (Single Thread 1,1,1):
- Reads the 64x64 texture and calculates global average Log Luminance.
- Converts to Linear Scene Luminance:
exp2(avgLogLum). - Clamps luminance between
minLuminanceandmaxLuminance. - Calculates target:
targetExposure = keyValue / sceneLum. - Interpolation: Reads the previous exposure from the 1x1 image and interpolates towards the target using
deltaTimeandspeedUp/speedDown. - Writes the new result to the 1x1 image.
- File:
-
Final Application
- File:
shaders/postprocess/exposure.glsl(included inpostprocess.frag). - The
getCombinedExposure()function detects auto-exposure is active. - It samples the 1x1 texture:
texture(autoExposureTexture, vec2(0.5)).r.
- File:
Mode B: Manual Exposure (POSTFX_EXPOSURE)¶
Used if auto-exposure is disabled.
- Downsample and Compute Shader passes are skipped.
- Final Application:
getCombinedExposure()directly uses theexposure.exposureuniform.
Mode C: No Exposure¶
If no effect is active, the value 1.0 is used.
3. Integration in the Pipeline (postprocess.frag)¶
Exposure is applied at a specific stage in the final fragment shader:
- Input (Scene Color).
- Chrom. Aberration / Motion Blur.
- Depth of Field.
- Bloom (Added to color).
- >>> EXPOSURE <<<:
color_val *= exposure_total. - Color Grading & White Balance.
- Tone Mapping (HDR to LDR).
- Gamma Correction.
Important Observation: Exposure is applied as a simple scalar multiplier on the linear HDR signal. This is physically consistent (simulation of camera aperture/ISO) and correctly occurs before Tone Mapping.
4. Pre-calculation and Optimizations¶
- Downsample 64x64: Rather than performing a complex parallel reduction of the full 1080p/4k image, the image is drastically reduced via a very fast fragment shader (
lum_downsample.frag) which performs an approximate average (4x4 Box Filter with stride). This massively reduces the load for the Compute Shader. - Single Compute Shader: Adaptation launches only a single WorkGroup (1,1,1) as it processes only 4096 pixels (64x64). It is extremely lightweight.
- Persistent Texture: Using a 1x1 texture as storage allows retaining exposure state from one frame to the next without CPU-GPU roundtrips (no
glGetTexImageneeded for logic, everything stays on GPU).
Synthetic Summary¶
| Aspect | Detail |
|---|---|
| State Storage | 1x1 Float Texture (GPU) |
| Adaptation Logic | Compute Shader (lum_adapt.comp) |
| Metric | Log Average Luminance |
| Input Analysis | 64x64 Downsampled Texture |
| Application | Scalar Multiplication in postprocess.frag |
| Conflict | Auto-Exposure overrides Manual Exposure |