feat(render): Atmospheric post stack runs in linear light; neutral preset is numerically the pack-off image (Campaign VM VM3)
Closes review finding F4 (docs/research/2026-08-22-campaign-ar-review.md): retail's main-world colour, sun rays and volumetric shafts are all gamma-encoded display-space values (the 2013 client has no linear lighting pipeline), but bloom thresholding, ACES (Narkowicz fit), Rec.709 luma saturation, the contrast pivot and the vignette were all operating directly on those gamma values, then writing the result to the UNORM swapchain without re-encoding. - atmospheric_common.glsl gains acdreamDecodeDisplay/acdreamEncodeDisplay (pow(c, 2.2) / pow(c, 1/2.2)). 2.2 is the retail-era CRT/early-LCD display-gamma assumption, deliberately not the sRGB piecewise curve, which would claim a precision retail's authoring pipeline never had. uAtmosphereSunColor's comment is corrected from "authored linear rgb" to "authored display-space rgb (retail has no linear pipeline)". - atmospheric_bloom_downsample.frag, atmospheric_filmic.frag (both the fused-Low and non-fused paths) decode every world/ray/volumetric read before summing/thresholding; atmospheric_bloom_blur.frag is unchanged (it already reads the now-linear bloom buffer); atmospheric_sun_rays.frag and atmospheric_volumetric.frag are documented as writing display-space colour that the consumers decode. - The contrast pivot moves from 0.5 (a gamma-space midpoint) to 0.18 (linear mid-grey, the standard 18%-grey-card exposure convention). The final filmic output is clamped in linear, then re-encoded before the UNORM write. - Bloom threshold/knee are re-derived for linear light: the pre-VM3 gamma-space pair was threshold 1.0 / knee 0.45, i.e. a soft range of [0.55, 1.0] in gamma. Decoding both ends with the same 2.2 assumption gives decode(1.0) = 1.0 (threshold unchanged) and decode(0.55) = 0.55^2.2 ~= 0.27, so linear knee = 1.0 - 0.27 ~= 0.73. Replaced the inline 0.45f literals with named constants BloomThresholdLinear = 1f / BloomKneeLinear = 0.73f on AtmosphericPostProcessGraph. bloom-strength's 0.65 default is untouched. - Exposure stays at its accepted 0.80 default: in linear, encode(acesFitted(0.80 * decode(0.46))) ~= 0.50, reproducing the same accepted midtone the old gamma-space pipeline produced as 0.51 for the same 0.46 input (0.46 * 0.80 fed straight into acesFitted, no decode/encode). Highlights now retain more (gamma 0.9 input moves from ~0.74 to ~0.85 through the full pipeline) and blacks deepen slightly (gamma 0.1 moves from ~0.09 to ~0.05) — the owner's visual gate judges. - Added AtmosphericColorPipeline, a CPU mirror of the GLSL decode/encode/ ACES/grade/filmic math (line-for-line, with a header comment requiring it stay mirrored), and AtmosphericColorPipelineTests: neutral-preset identity within half an 8-bit step for a 0..255 grey sweep (proving the neutral preset is numerically the pack-off image), decode/encode round-trip within 1e-6, monotonic-in-exposure, the pinned midtone/ highlight/shadow numbers above, and the bloom-knee derivation. - Added a shader-source pinning test so a future edit cannot silently drop the colour-space conversions: atmospheric_filmic.frag must contain exactly one acdreamEncodeDisplay( call in main()'s output, atmospheric_bloom_downsample.frag must contain at least three acdreamDecodeDisplay( calls. - Regenerated SPIR-V (tools/compile-shaders.ps1, glslc from the installed Vulkan SDK). Only atmospheric_bloom_downsample.frag.spv and atmospheric_filmic.frag.spv changed in bytes; every other pack shader that includes atmospheric_common.glsl recompiled to a byte-identical binary (the new decode/encode helpers are unreferenced dead code for them). VulkanShaderManifestTests' retail-oracle SHA-256 set (mesh_modern, terrain_modern, mesh_detail, etc.) is untouched and still passes — the retail default path did not change. - Docs: noted the linear-light move in the AR plan's Slice 1 section, and added a "Colour space" section to the render-pack ABI doc (docs/render-packs/semantic-bindings-v1.md) naming which inputs are display-space and pointing at atmospheric_common.glsl as the reference implementation. No ABI version bump — the binding layout is unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
b1e9912535
commit
87677f9c4f
16 changed files with 410 additions and 24 deletions
|
|
@ -574,6 +574,11 @@ appraisal surfaces retain their accepted colours; 1080p/1440p/4K captures show
|
|||
no clipping, haloing at the world/UI edge, stale frame, or resource leak; the
|
||||
slice meets its preset GPU/VRAM budget.
|
||||
|
||||
Campaign VM VM3 (2026-08-22) moved the post stack to linear light: every
|
||||
world/ray/volumetric/bloom read is decoded with the 2.2 display assumption,
|
||||
tonemap/grade/vignette run in linear, and the result is re-encoded; the
|
||||
neutral preset is numerically the pack-off image.
|
||||
|
||||
### Slice 2 — Tier-1 screen-space sun rays
|
||||
|
||||
**Implementation:** complete. Authored sun projection, the screen-space
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue