Tonemapping¶
Summary: Project Eternal uses a custom AgX tonemapper replacing UE5's default ACES. This provides better color preservation in saturated highlights and supports the game's painterly chiaroscuro art direction with built-in color grading controls.
Table of Contents¶
- Architecture Overview
- Why AgX Over ACES?
- Signal Flow
- Color Grading Controls
- Post Process Setup
- User Brightness
- Scene Color Buffer
- Source References
- Related Systems
- Recent Changes
Architecture Overview¶
┌──────────────────┐ ┌───────────────────┐ ┌──────────────┐
│ HDR Scene Color │────▶│ AgX Tonemapper │────▶│ Final Output│
│ (PostProcess │ │ (Replacing │ │ (sRGB) │
│ Input0) │ │ Tonemapper) │ │ │
└──────────────────┘ │ │ └──────────────┘
│ • Inset Matrix │
│ • Log2 Encoding │
│ • Sigmoid Curve │
│ • Outset Matrix │
│ • Color Grading │
│ • Dithering │
└───────────────────┘
| Design Decision | Rationale |
|---|---|
| Replacing Tonemapper | Full control over tone curve; UE5 color grading bypassed |
| Built-in color grading | UE5 Global color grading only works with built-in tonemapper |
| Triangular-PDF dither | Eliminates banding in dark scenes at negligible cost |
| RGBA16F scene buffer | Prevents precision loss in low-light environments |
Why AgX Over ACES?¶
| Aspect | ACES | AgX |
|---|---|---|
| Saturated highlights | Hue shifts toward yellow/magenta | Preserves hue |
| Shadow detail | Crushed aggressively | Graceful rolloff |
| Overall character | Contrasty, filmic | Neutral, art-directable |
| Painterly suitability | Fights muted palettes | Complements earth tones |
AgX's neutral base allows the color grading controls to define the mood entirely, aligning with the GDD's "dramatic chiaroscuro with muted earth tones" directive.
Signal Flow¶
HDR Linear Input
│
▼
┌─────────────────┐
│ Inset Matrix │ sRGB linear → AgX working space
└────────┬────────┘
▼
┌─────────────────┐
│ Log2 Encoding │ Compress to [-12.47, 4.03] EV range → normalize to 0..1
└────────┬────────┘
▼
┌─────────────────┐
│ Sigmoid Curve │ 6th-order polynomial approximation of AgX S-curve
└────────┬────────┘
▼
┌─────────────────┐
│ Outset Matrix │ AgX working space → sRGB linear
└────────┬────────┘
▼
┌─────────────────┐
│ Color Grading │ Saturation → Contrast → Gamma → Gain → Shadow Crush
└────────┬────────┘
▼
┌─────────────────┐
│ Dithering │ Triangular-PDF noise, 1/255 amplitude, per-frame animated
└────────┬────────┘
▼
Linear Output
(engine applies sRGB)
Color Grading Controls¶
Since BL_ReplacingTonemapper bypasses UE5's built-in color grading, all grading is handled inside the shader via material parameters.
| Parameter | Default | Range | Purpose |
|---|---|---|---|
Gain |
0.90 | 0.5–1.5 | Overall brightness multiplier |
Gamma |
0.95 | 0.5–2.0 | Midtone curve (lower = darker mids) |
Saturation |
0.90 | 0.0–2.0 | Color intensity (0 = grayscale) |
ShadowMax |
0.02 | 0.0–0.15 | Shadow crush threshold (higher = more black) |
Contrast |
1.05 | 0.5–2.0 | S-curve strength around 0.5 midpoint |
Grading Order¶
The order matters — changing it produces different results:
- Saturation — lerp toward luminance
- Contrast — pivot around 0.5
- Gamma — power curve on midtones
- Gain — linear multiply
- Shadow Crush — remap below threshold to black
Art Direction Targets¶
| Look | Gain | Gamma | Saturation | ShadowMax | Contrast |
|---|---|---|---|---|---|
| Chiaroscuro (default) | 0.90 | 0.95 | 0.90 | 0.02 | 1.05 |
| Deep dungeon | 0.75 | 0.85 | 0.80 | 0.06 | 1.15 |
| Overworld daylight | 1.00 | 1.00 | 0.95 | 0.01 | 1.00 |
Post Process Setup¶
| Setting | Value | Reason |
|---|---|---|
| Material Domain | Post Process | — |
| Blendable Location | Replacing Tonemapper | Replaces ACES entirely |
| Metering Mode | Manual | Consistent artistic exposure |
| Exposure Compensation | 0.0 | Neutral; darken via Gain instead |
| Apply Physical Camera Exposure | Off | Non-physical light values for artistic control |
The material is applied via an Unbound Post Process Volume with Infinite Extent to affect the entire scene.
User Brightness¶
UEternalDisplaySettingsSubsystem (Source/ProjectEternal/Public/Settings/EternalDisplaySettingsSubsystem.h)
owns the player-facing brightness slider. It stores brightness normalized 0..1 (0.5 = neutral 2.2 gamma) in
GameUserSettings.ini and feeds it as a bounded gamma into MPC_Display.UserBrightness, which the AgX material
applies inside its custom node after its own grading. It has to ride inside the tonemapper material because
the material replaces the engine tonemapper: engine color grading (ColorGamma) and GEngine->DisplayGamma
either dead-end or restyle the whole editor window. MPC values are per-world instances, so PIE only ever shifts
its own viewport. This is a slider escape hatch for uncalibrated displays, not a calibration flow.
The same subsystem exposes the two curated look-identical quality profiles (0 = Low, 1 = Medium) that boot
auto-detect picks between; a user choice persists as an override boot honors ahead of the benchmark, and the
explicit -ScalabilityLevel= command line still wins over everything. In the editor the choice only persists —
applying Low would leak its sticky cvar overrides into the review session.
Scene Color Buffer¶
| Setting | Value | Purpose |
|---|---|---|
r.SceneColorFormat |
2 (RGBA16F) | 16-bit precision prevents banding |
Set in Config/DefaultEngine.ini under [/Script/Engine.RendererSettings]. The default R11G11B10 format (4 bytes/pixel) lacks precision for dark scenes. RGBA16F (8 bytes/pixel) eliminates banding at a minor VRAM cost (~16MB at 4K).
Requires editor restart to take effect.
Source References¶
| Component | Location |
|---|---|
| AgX HLSL reference | Shaders/AgXTonemapper.ush |
| Post-process material | Content/MaterialLibrary/Postprocessing/M_PP_AgX.uasset |
| Material instance | Content/MaterialLibrary/Postprocessing/MI_PP_AgX.uasset |
| Scene color format | Config/DefaultEngine.ini → r.SceneColorFormat=2 |
| User brightness / quality profile | Source/ProjectEternal/Public/Settings/EternalDisplaySettingsSubsystem.h → MPC_Display.UserBrightness |
Related Systems¶
- Post Process Volumes in level maps control per-area overrides
- GDD Art Direction (Section 4.4) defines the painterly chiaroscuro target
Recent Changes¶
| Date | Change | Impact |
|---|---|---|
| 2026-08-20 | Documented user brightness (UEternalDisplaySettingsSubsystem → MPC_Display.UserBrightness inside the AgX node) and the Low/Medium quality profile override |
Brightness must live in the custom tonemapper because the engine gamma paths dead-end once the tonemapper is replaced |
| 2026-03-09 | Added AgX tonemapper replacing ACES | All scenes use new tone curve |
| 2026-03-09 | Scene color buffer → RGBA16F | Eliminates color banding |