Skip to content

DoF bokeh shaping: polygonal aperture, anamorphic, rotation, and intensity#346

Open
taylnos wants to merge 5 commits into
AlchemyViewer:developfrom
taylnos:feature/dof-bokeh-shaping
Open

DoF bokeh shaping: polygonal aperture, anamorphic, rotation, and intensity#346
taylnos wants to merge 5 commits into
AlchemyViewer:developfrom
taylnos:feature/dof-bokeh-shaping

Conversation

@taylnos

@taylnos taylnos commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds camera-lens character to the Depth-of-Field bokeh — an aperture-blade polygon, an anamorphic (oval) squeeze, aperture rotation, and a highlight intensity control — all opt-in. Every control defaults to a neutral value, so the current circular bokeh is preserved exactly until a user opts in.

New settings (settings_alchemy.xml, editable via Debug Settings)

Setting Default Effect
RenderDoFBokehBlades (S32) 0 Aperture blade count; <3 = round (off). 5–9 emulates a partially-closed iris (pentagon…enneagon).
RenderDoFBokehRoundness (F32) 0 0 = crisp polygon … 1 = circle (only when blades ≥ 3).
RenderDoFBokehRotation (F32°) 0 Rotates the aperture (polygon and anamorphic oval).
RenderDoFAnamorphicRatio (F32) 1.0 1 = circular; >1 = vertical-oval anamorphic. Area-preserving.
RenderDoFBokehIntensity (F32) 1.0 Exponent on the highlight weight; higher = brighter, more defined discs; 0 = flat. 1.0 is the classic look.

How it works

The DoF gather (postDeferredF.glsl) placed its blur taps on a circle. This reshapes where each tap lands — into a rounded N-gon and/or an anamorphic oval, rigidly rotated — via a new bokehOffset() helper called at both gather sites (near + far). The tap count is unchanged, so the O(CoC²) gather cost is unaffected; the reshape adds only a few ALU ops, and a coherent early-out returns the identical circular offset when everything is neutral.

The N-gon trig and anamorphic scale are baked once per frame on the CPU (ALDoFBokeh::bakeKernel, mirroring the existing chromatic-aberration path) and handed to the shader as two vec4 uniforms plus one scalar, so the shader avoids per-tap trig.

Intensity is an exponent on the existing highlight weight (wg = 0.25 + pow(lum, intensity)), not a multiplier. The gather is a normalized weighted average, so a global scale cancels in the ratio and saturates past ~1; an exponent instead changes which samples dominate and stays useful across its whole range. 1.0 reproduces the classic weighting via a bit-exact fast path.

Performance

No new texture taps — the reshape is a handful of ALU ops per existing tap, and feature-off is bit-for-bit the current bokeh (a coherent, uniform-driven branch skips the reshape entirely). Intensity swaps a multiply for a pow per tap only when it is ≠ 1 (ALU on an otherwise texture-fetch-bound loop).

Tests

The CPU-side kernel baking is factored into a pure, dependency-light helper (ALDoFBokeh::bakeKernel in indra/newview/aldofbokeh.{h,cpp}) with a TUT unit test (indra/newview/tests/aldofbokeh_test.cpp, 7 cases): neutral→inactive, blade-count threshold, N-gon constants, deg→rad conversion, area-preserving anamorphic squeeze, input clamping, and intensity clamp/passthrough. ctest -R aldofbokeh passes. The shader and wiring were validated by a full Release build plus in-viewer runtime shader-compile smoke tests.

Notes

  • Lens/chromatic aberration was intentionally left out of scope — a screen-space chromatic aberration already exists in the color-correct pass.
  • A separate experiment that runs DoF on the linear HDR scene before tonemap (for physically brighter bokeh) lives on its own branch and is not part of this PR.

🤖 Generated with Claude Code

taylnos and others added 3 commits July 6, 2026 18:14
Give the DoF bokeh real lens character by reshaping the gather kernel into an
aperture-blade polygon and/or an anamorphic oval. Both are pure sample-
placement reshapes -- the tap count is unchanged, so the O(CoC^2) gather cost
is unaffected. All controls default to neutral values, so the existing
circular bokeh is preserved bit-for-bit until a user opts in.

- postDeferredF.glsl: new bokehOffset() reshapes tap placement at both the
  near (FRONT_BLUR) and far gather sites, with a coherent early-out that
  returns the identical circular offset when inactive. Two CPU-baked vec4
  uniforms (bokeh_shape, bokeh_lens) carry the kernel.
- ALDoFBokeh::bakeKernel (new aldofbokeh.{h,cpp}): pure helper that clamps the
  settings and pre-bakes the N-gon trig and area-preserving anamorphic scale
  on the CPU (mirroring the chromatic-aberration path). Covered by a TUT unit
  test (aldofbokeh_test.cpp).
- pipeline.cpp renderDoF(): reads the settings, bakes once per pass, pushes
  the two uniforms onto the active post program.
- settings_alchemy.xml: 4 opt-in settings (RenderDoFBokehBlades / Roundness /
  Rotation, RenderDoFAnamorphicRatio), all neutral by default.
- llshadermgr.{h,cpp}: register the two reserved DoF bokeh uniforms.

Lens/chromatic aberration is intentionally out of scope (a screen-space CA
already exists) and left for a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two refinements to the DoF bokeh shaping:

- Rotation now rigidly rotates the entire aperture. Previously rotation was
  folded into the sampling angle while the anamorphic scale was applied on the
  fixed screen axes, so the oval never tilted (only the polygon rotated). Build
  the aperture in its own frame, then rotate the whole offset -- polygon AND
  anamorphic oval spin together. cos/sin(rotation) is loop-invariant so it is
  hoisted out of the gather loop.

- Add RenderDoFBokehIntensity (default 1.0): scales the gather's existing
  highlight weighting (wg = 0.25 + intensity*(r+g+b)) so bright out-of-focus
  points form brighter, more defined bokeh discs -- which makes the aperture
  shape and its rotation legible. Independent of the offset reshape, so it also
  intensifies a plain round bokeh. 1.0 reproduces the classic weighting exactly;
  0 gives a flat soft blur. Baked/clamped in ALDoFBokeh::bakeKernel (unit test
  extended to 7 cases) and carried by a new bokeh_intensity reserved uniform.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The intensity control was a linear weight multiplier, but the gather is a
normalized weighted average -- a global weight scale cancels in the ratio, so
past ~1.0 the control saturated (0->1 was a strong change, 1->16 barely moved).

Reparameterize intensity as an exponent on the highlight weight
(wg = 0.25 + pow(lum, intensity)): changing the exponent changes which samples
dominate rather than applying a scale that cancels, so every step keeps having
an effect. 1.0 stays a bit-exact linear fast path (existing DoF unchanged); the
base is clamped for overflow safety and the range tightened to [0, 8] (an
exponent of 8 is already extreme). Settings help text and the unit test updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • indra/newview/app_settings/shaders/class1/deferred/postDeferredF.glsl is excluded by !**/*.glsl

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ae8e164f-f6a5-419b-98f4-88f589eda3fd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds CPU-side depth-of-field bokeh kernel baking, new shader uniforms and settings, pipeline wiring to upload the baked kernel, and build/test integration for the new module.

Changes

DoF Bokeh Kernel Feature

Layer / File(s) Summary
Kernel data contract and header
indra/newview/aldofbokeh.h
Defines ALDoFBokeh::Kernel and declares bakeKernel.
bakeKernel implementation
indra/newview/aldofbokeh.cpp
Implements clamping, kernel parameter derivation, intensity handling, and activation logic.
Shader uniform registration
indra/llrender/llshadermgr.h, indra/llrender/llshadermgr.cpp
Adds new DOF bokeh reserved uniforms and registers their names.
Pipeline wiring and settings
indra/newview/app_settings/settings_alchemy.xml, indra/newview/pipeline.cpp
Adds bokeh settings and uploads baked kernel values during the DOF pass.
Build integration and unit tests
indra/newview/CMakeLists.txt, indra/newview/tests/aldofbokeh_test.cpp
Adds build targets for the new module and validates bakeKernel with unit tests.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Pipeline as pipeline.cpp
    participant Bokeh as ALDoFBokeh::bakeKernel
    participant Settings as gSavedSettings
    participant Shader as DOF shader uniforms

    Pipeline->>Settings: read blades, roundness, rotation, anamorphic ratio, intensity
    Pipeline->>Bokeh: bakeKernel(blades, roundness, rotationDeg, anamorphicRatio, intensity)
    Bokeh->>Bokeh: clamp inputs and compute Kernel fields
    Bokeh-->>Pipeline: return Kernel
    Pipeline->>Shader: upload DOF_BOKEH_SHAPE, DOF_BOKEH_LENS, DOF_BOKEH_INTENSITY
Loading

Poem

A rabbit twirls a lens so bright,
New bokeh blooms in shader light.
Three blades, or six, or rounded glow,
Through soft DOF, the sparkles flow.
Hop hop — the kernel’s set just right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main DoF bokeh shaping changes.
Description check ✅ Passed The description is detailed and covers the feature, settings, implementation, performance, tests, and notes, but omits the template's issue link and checklist sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
indra/llrender/llshadermgr.h (1)

223-225: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Inconsistent terminology: "scale" vs. "exponent" for bokeh_intensity.

This comment describes bokeh_intensity as a "highlight-weight scale", but ALDoFBokeh::Kernel::intensity (aldofbokeh.h) and the referenced GLSL comment both document it as an exponent (per the PR's commit history: "changing intensity from a linear multiplier to an exponent"). Align the wording here to avoid confusing future readers of the reserved-uniform table about how the value is applied in the shader.

📝 Suggested comment fix
-        DOF_BOKEH_INTENSITY,                //  "bokeh_intensity" (float: highlight-weight scale)
+        DOF_BOKEH_INTENSITY,                //  "bokeh_intensity" (float: highlight-weight exponent)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indra/llrender/llshadermgr.h` around lines 223 - 225, The reserved-uniform
table entry for DOF_BOKEH_INTENSITY uses wording that conflicts with the shader
and ALDoFBokeh::Kernel::intensity documentation. Update the inline comment for
bokeh_intensity in llshadermgr.h to match the exponent-based terminology used
elsewhere, so the description is consistent with the GLSL comment and the kernel
field name.
indra/newview/aldofbokeh.cpp (2)

66-66: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Exact float equality for the active flag.

anamorphicRatio != 1.f uses exact float comparison. Since anamorphicRatio comes from a user-editable debug setting parsed as text, values extremely close to but not exactly 1.0 (e.g. rounding artifacts from a UI slider) would flip active true and take the reshaping path unnecessarily, even though visually indistinguishable from circular. Low risk given the default is likely stored as exact 1.0, but worth a small epsilon tolerance for robustness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indra/newview/aldofbokeh.cpp` at line 66, The `active` flag in
`aldofbokeh.cpp` is using an exact float comparison on `anamorphicRatio`, which
can incorrectly enable the reshaping path for values that are visually
indistinguishable from 1.0 due to parsing or slider rounding. Update the logic
in the code that sets `k.active` to use a small epsilon/tolerance around the
`anamorphicRatio` check instead of `!= 1.f`, while keeping the existing
`clampedBlades` condition intact.

58-61: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale "scale" wording; field is now an exponent.

The Kernel::intensity field doc in aldofbokeh.h explicitly calls this a "highlight-weight EXPONENT", matching the PR's move from a linear multiplier to an exponent, but this comment still says "Highlight-weight scale". Likely left over from before the exponent change (per commit history). Update for consistency to avoid confusing future maintainers about how the shader consumes this value.

📝 Suggested comment fix
-        // Highlight-weight scale for the gather. Independent of the offset reshape
+        // Highlight-weight exponent for the gather. Independent of the offset reshape
         // (it also intensifies a plain round bokeh); 1.0 reproduces the classic
         // weighting exactly.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indra/newview/aldofbokeh.cpp` around lines 58 - 61, Update the stale comment
in the bokeh kernel setup so it matches the current meaning of Kernel::intensity
as an exponent rather than a linear scale. In aldofbokeh.cpp around the code
that assigns k.intensity, revise the wording to describe a highlight-weight
exponent and keep it consistent with the documentation in aldofbokeh.h and the
shader usage in the bokeh kernel path.
indra/newview/aldofbokeh.h (1)

52-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the blade upper-bound clamp.

The docstring lists clamped ranges for roundness, anamorphicRatio, and intensity, but omits that blade count is also capped at 12 (llmin(blades, 12) in aldofbokeh.cpp). Worth adding for completeness since this comment is the documented contract callers rely on.

📝 Suggested doc tweak
-    // Bake the shader kernel from raw setting values. Inputs are clamped to their
-    // documented, sane ranges here so an out-of-range debug setting can never feed
-    // garbage into the gather: blades below 3 disable the polygon (circular);
-    // roundness clamps to [0, 1]; anamorphicRatio clamps to [0.25, 4] (1 = off);
-    // intensity clamps to [0, 8] (1 = off; the shader applies it as an exponent).
+    // Bake the shader kernel from raw setting values. Inputs are clamped to their
+    // documented, sane ranges here so an out-of-range debug setting can never feed
+    // garbage into the gather: blades below 3 disable the polygon (circular),
+    // blades above 12 are clamped to 12; roundness clamps to [0, 1];
+    // anamorphicRatio clamps to [0.25, 4] (1 = off); intensity clamps to [0, 8]
+    // (1 = off; the shader applies it as an exponent).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indra/newview/aldofbokeh.h` around lines 52 - 57, The Kernel::bakeKernel
docstring currently describes the lower-bound behavior for blades but omits the
upper-bound clamp applied in the implementation. Update the comment near
bakeKernel in aldofbokeh.h to state that blades are also capped at 12, matching
the clamping logic in Kernel::bakeKernel so the documented contract is complete
and accurate.
indra/newview/tests/aldofbokeh_test.cpp (1)

50-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a boundary test for blades == 3.

test<2> covers blades 0-2 (inactive) and test<3> jumps straight to blades=6. The exact activation threshold (blades == 3) is never directly exercised, which is the most off-by-one-prone value.

✅ Suggested additional boundary test
     // A hexagonal aperture bakes exactly the documented N-gon constants.
     template<> template<>
     void dofbokeh_object::test<3>()
     {
         Kernel k = bakeKernel(6, 0.f, 0.f, 1.f, 1.f);
         ensure("hexagon is active", k.active);
         ensure_distance("seg = 2pi/6", k.seg, F_PI / 3.f, 1e-5f);
         ensure_distance("halfSeg = pi/6", k.halfSeg, F_PI / 6.f, 1e-5f);
         ensure_distance("edge = cos(pi/6)", k.edge, cosf(F_PI / 6.f), 1e-5f);
     }
+
+    // A triangle (minimum blade count) is the exact activation boundary.
+    template<> template<>
+    void dofbokeh_object::test<8>()
+    {
+        Kernel k = bakeKernel(3, 0.f, 0.f, 1.f, 1.f);
+        ensure("blades==3 is active", k.active);
+        ensure_distance("seg = 2pi/3", k.seg, 2.f * F_PI / 3.f, 1e-5f);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indra/newview/tests/aldofbokeh_test.cpp` around lines 50 - 71, Add a boundary
case test for the exact activation threshold in dofbokeh_object::test by
exercising bakeKernel with blades == 3, since test<2> only covers 0-2 and
test<3> skips directly to 6. Extend the existing aldofbokeh test coverage with a
focused assertion that blades=3 produces an active Kernel and nonzero polygon
segment values, keeping the style consistent with the current
ensure/ensure_distance checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@indra/llrender/llshadermgr.h`:
- Around line 223-225: The reserved-uniform table entry for DOF_BOKEH_INTENSITY
uses wording that conflicts with the shader and ALDoFBokeh::Kernel::intensity
documentation. Update the inline comment for bokeh_intensity in llshadermgr.h to
match the exponent-based terminology used elsewhere, so the description is
consistent with the GLSL comment and the kernel field name.

In `@indra/newview/aldofbokeh.cpp`:
- Line 66: The `active` flag in `aldofbokeh.cpp` is using an exact float
comparison on `anamorphicRatio`, which can incorrectly enable the reshaping path
for values that are visually indistinguishable from 1.0 due to parsing or slider
rounding. Update the logic in the code that sets `k.active` to use a small
epsilon/tolerance around the `anamorphicRatio` check instead of `!= 1.f`, while
keeping the existing `clampedBlades` condition intact.
- Around line 58-61: Update the stale comment in the bokeh kernel setup so it
matches the current meaning of Kernel::intensity as an exponent rather than a
linear scale. In aldofbokeh.cpp around the code that assigns k.intensity, revise
the wording to describe a highlight-weight exponent and keep it consistent with
the documentation in aldofbokeh.h and the shader usage in the bokeh kernel path.

In `@indra/newview/aldofbokeh.h`:
- Around line 52-57: The Kernel::bakeKernel docstring currently describes the
lower-bound behavior for blades but omits the upper-bound clamp applied in the
implementation. Update the comment near bakeKernel in aldofbokeh.h to state that
blades are also capped at 12, matching the clamping logic in Kernel::bakeKernel
so the documented contract is complete and accurate.

In `@indra/newview/tests/aldofbokeh_test.cpp`:
- Around line 50-71: Add a boundary case test for the exact activation threshold
in dofbokeh_object::test by exercising bakeKernel with blades == 3, since
test<2> only covers 0-2 and test<3> skips directly to 6. Extend the existing
aldofbokeh test coverage with a focused assertion that blades=3 produces an
active Kernel and nonzero polygon segment values, keeping the style consistent
with the current ensure/ensure_distance checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ba45b8f9-b774-4aae-8371-497909e92be5

📥 Commits

Reviewing files that changed from the base of the PR and between 036c7f1 and c0ad6dd.

⛔ Files ignored due to path filters (1)
  • indra/newview/app_settings/shaders/class1/deferred/postDeferredF.glsl is excluded by !**/*.glsl
📒 Files selected for processing (8)
  • indra/llrender/llshadermgr.cpp
  • indra/llrender/llshadermgr.h
  • indra/newview/CMakeLists.txt
  • indra/newview/aldofbokeh.cpp
  • indra/newview/aldofbokeh.h
  • indra/newview/app_settings/settings_alchemy.xml
  • indra/newview/pipeline.cpp
  • indra/newview/tests/aldofbokeh_test.cpp

…ilon, docs, test)

- llshadermgr.h / aldofbokeh.cpp: describe bokeh_intensity as a
  highlight-weight EXPONENT (not "scale"), matching aldofbokeh.h and the
  shader, which apply it via pow().
- aldofbokeh.cpp: decide `active` with a small epsilon
  (fabsf(anamorphicRatio - 1) > 1e-3) instead of an exact float inequality,
  so a ratio that rounds to ~1.0 (UI-slider noise) stays on the circular path.
- aldofbokeh.h: document that blades are also capped at 12 (upper bound).
- aldofbokeh_test.cpp: add a blades==3 boundary case (the exact activation
  threshold) asserting it is active and bakes the triangle N-gon constants.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@taylnos

taylnos commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review! Addressed the five nitpicks in 8074587 (these came through as a single consolidated review rather than inline threads, so replying here):

  • bokeh_intensity terminology (llshadermgr.h, aldofbokeh.cpp): now consistently documented as a highlight-weight exponent rather than "scale", matching aldofbokeh.h and the shader's pow().
  • active flag float comparison (aldofbokeh.cpp): replaced anamorphicRatio != 1.f with fabsf(anamorphicRatio - 1.f) > 1e-3f, so a ratio that rounds to ~1.0 (UI-slider noise) stays on the circular path.
  • Blade upper-bound docs (aldofbokeh.h): the bakeKernel docstring now notes blades are also capped at 12.
  • Boundary test (aldofbokeh_test.cpp): added a blades == 3 case (the exact activation threshold) asserting the kernel is active and bakes the triangle N-gon constants.

🤖 Addressed by Claude Code

pow(lum, bokeh_intensity) with bokeh_intensity == 0 and a pure-black tap
(lum == 0) is pow(0,0) -- undefined in GLSL, and NaN on common drivers.
Intensity 0 is a legal, documented value ("0 = fully flat" in the
RenderDoFBokehIntensity setting text), and one NaN-weighted tap poisons the
normalized average for the whole disc, smearing a NaN blotch through the
blur. Floor the clamped luma to 1e-5: pow(x>0, 0) == 1 gives exactly the
intended flat weight, and at every other intensity the floor is
imperceptible under the 0.25 weight base. Both gather sites fixed.

Found by an adversarial shader-math review on the follow-on compute-DoF
branch, which shares this expression; fixed there in 8f86115.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@taylnos

taylnos commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed one post-review commit: f68a193.

What: the pop weight's pow(lum, bokeh_intensity) is pow(0, 0) when bokeh_intensity == 0 (a documented value — "0 = fully flat" in the setting text) and the tap is pure black. That's undefined in GLSL and evaluates to NaN on common drivers, and one NaN-weighted tap poisons the whole disc's normalized average — it shows as a spreading NaN blotch in the blur near dark geometry.

Fix: floor the already-clamped luma to 1e-5 at both gather sites (dofSample/dofSampleNear). pow(x>0, 0) == 1 yields exactly the intended flat weight at intensity 0, and at every other intensity the floor is invisible under the 0.25 weight base. The default (intensity 1.0) still takes the bit-exact linear fast path, so default rendering is unchanged.

Found by a shader-math audit on the follow-on compute-DoF branch, which shares this expression (fixed there too); verified in-world at intensity 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant