#1440: Alpha-blended layers lying on a surface take its shadowed lamp light #1442

Open
TheSlowly wants to merge 0 commits from TheSlowly/Slipstream:blend-lamp-shadows into main
Collaborator

Fixes #1440. Four commits on top of PR #1420 (its first commit is #1419). The lighting records each pixel's demodulated shadowed lamp light (8 B per render pixel with RenderVKDeferVisReuse); a blended layer that matches the surface under it skips the lamps the deferred walk owns and adds the record with its own colours. Plus: the rigged alpha-blend FS gets the visibility reuse (CLEAN_RIG_ALPHA), a blend caster within 5 mm of its receiver casts nothing on it, and the record is written on nothing-casts frames. Every shader variant and the C++ pass the checks; the in-world test from the Test-Plans is still pending.

Fixes #1440. Four commits on top of PR #1420 (its first commit is #1419). The lighting records each pixel's demodulated shadowed lamp light (8 B per render pixel with RenderVKDeferVisReuse); a blended layer that matches the surface under it skips the lamps the deferred walk owns and adds the record with its own colours. Plus: the rigged alpha-blend FS gets the visibility reuse (CLEAN_RIG_ALPHA), a blend caster within 5 mm of its receiver casts nothing on it, and the record is written on nothing-casts frames. Every shader variant and the C++ pass the checks; the in-world test from the Test-Plans is still pending.
The ray-traced shadow walks take alpha-blend casters into account: they
dim and tint the light that passes through them (#212). A blended layer
that lies on a surface, such as a blush or makeup shell a millimetre or
two over the skin, or a decal or grime layer on a wall, sits in the first
millimetres of every shadow ray that leaves that surface. The ray origin
is offset by float precision only, so the layer shadowed the surface it
is drawn over. The skin under a makeup layer came out dimmer and tinted
toward the layer's colour, and a near-solid core (at or above the parity
rule's solid alpha) blacked it out. A layer that takes the surface's
resolved light through the visibility reuse then carries that shadow
of itself as well.

The walk's candidate test now ignores an alpha-blend caster within 5 mm
along the ray (RT_LAYER_SKIP). The check runs right after the alpha
test, before any rule that could block or tint. 5 mm keeps a 2 mm shell
out up to about 65 degrees off the light; past that N.L is small anyway.
Blend casters further off (hair, a hat brim, glass) shadow as before.

Test-Plan:
    python3 indra/newview/tests/blend_lamp_reuse_checks.py --source-root . --compile --out <dir>
      (the skip comes before every blocking / tinting rule; the lit FS, the RT shadow mask and
       pass 15's shadow cores through glslc)
    In world (RT shadows on): the skin under a makeup or blush layer in the sun, or under a lamp
      with "Shadows from all lights", is as bright as the skin around it. A decal on a wall no
      longer darkens the wall under it. Hair, hats and glass still cast their shadows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pass 2's alpha-blend FS finds the opaque surface a blended fragment lies
on through the visibility reuse's per-pixel record: the surface's view
depth and geometric normal. Only the split lighting's SHADOW pass wrote
it, and a frame where nothing casts (#823: no sun shadows, no shadowed
projector records) skips that pass and turned the reuse off. That is the
dim scene with no sun, an interior or a night lit by lamps, which is
exactly where a layer on the skin has the most to gain from taking the
skin's light.

On such a frame BASE (feature split) / COMBINE (plain split) now write
the record themselves (DEFER_FLAG_VIS_FILL), with the depth stored
negative: no sun visibility was evaluated, so the layer keeps its own
cloud-only sun, as before. The record has one writer function, shared
with the SHADOW pass. All 32 bits of the split push word are taken; the
flag uses bit 17, which only the uber kernel reads (DEFER_FLAG_RT_IN)
and which the host never sets on a split dispatch.

On its own this changes nothing a layer reads on those frames; the
lamp light reuse that follows needs the record.

Test-Plan:
    python3 indra/newview/tests/blend_lamp_reuse_checks.py --source-root . --compile --out <dir>
      (the fill flag's bit against every other flag and field; the record's writers; the
       SHADOW / COMBINE / BASE / uber kernels through glslc)
    In world (deferred, sun shadows off, no shadowed projectors): lighting unchanged;
      RenderVKDeferVisReuse 0 / 1 shows no difference yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pass 2's static alpha-blend FS (LIT_ALPHA, -DCLEAN_BLEND_TOL) already
takes the sun and projector visibility the split lighting resolved for
the opaque surface a fragment lies on (the visibility reuse: view depth
within 4 mm + 0.1 %, normal within ~25 degrees). The rigged alpha-blend
pipeline (LIT_RIG_ALPHA) ran the plain lit FS without it, so an avatar
layer on the skin -- a blush or makeup shell on a mesh head, a tattoo,
a stocking -- looked up its light on its own and could disagree with
the skin it is drawn over.

LIT_RIG_ALPHA now has its own FS flavour, -DCLEAN_RIG_ALPHA: the lit FS
plus the visibility reuse. On the strict match it takes everything the
static flavour takes (the sun record, the projector slots). It keeps the
exact hardware depth test (#742) and carries neither the #731 depth
tolerance nor the sample mask that goes with it, so hair and other
layers that match nothing pay one image load and nothing more. The
flavour is a fragment kind of its own (LITF_RIG) through the debug,
rez-fx wire and spec-set twins, and pass 2's rigged draws get the same
push word as the static ones. The cluster LOD path's avatars (off by
default) keep the plain lit FS: their push word carries the cluster
view index where the reuse flags would go.

The layer's own shadow is not in the skin's visibility: the shadow walk
ignores a blend caster lying on its receiver (RT_LAYER_SKIP).

Test-Plan:
    python3 indra/newview/tests/blend_lamp_reuse_checks.py --source-root . --compile --out <dir>
      (the flavour through every table; the tolerance code stays CLEAN_BLEND_TOL's; every lit FS
       variant through glslc)
    python3 indra/newview/tests/blend_opaque_gbuffer_checks.py --source-root .
    python3 indra/vkclean/regen_world_spv.py --out-dir <dir> --cache-dir <dir>
    In world (deferred, sun shadows on): a blush or makeup layer on a mesh head under a hat
      brim or hair is shadowed exactly where the skin is; RenderVKDeferVisReuse 0 brings back
      the layer's own lookup. Hair, eyelashes, eyes behind lids: drawn as before.
      VKPipeStats: LIT_RIG_ALPHA's registers close to LIT_ALPHA's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With "Shadows from all lights" (RenderVKReSTIRDI) in radiance mode
(RenderVKReSTIRRadiance), pass 15's denoised image holds the shadowed
light of every light it owns: the ray-traced projector records, ReSTIR's
point lights and projectors inside the light grid, and the emitters.
The deferred light walk leaves those lights out and BASE / COMBINE add
the image once per pixel. Alpha-blended surfaces are shaded forward in
pass 2, and their light walk had no such hooks, so it lit every lamp
unshadowed. A blush or makeup layer on a mesh head, or a decal on a
wall, glowed in a lamp's light (a wood stove's fire, a ceiling light)
where the skin or wall under it lay in that lamp's shadow.

BASE / COMBINE now record, per pixel, the demodulated diffuse and
specular radiance they lit the surface with (RG32UI, two RGB9E5 words;
set 3 binding 29, draw set 0 binding 65; 8 B per render pixel while
RenderVKDeferVisReuse is on, its description updated). They write it on
every frame the visibility reuse's depth record is written, including
the frames where nothing casts. A blended fragment that matches the
surface under it (the existing strict match: depth within 4 mm + 0.1 %,
normal within ~25 degrees) on a frame whose push word says the record is
current (VISR_RAD) skips exactly the lights the deferred walk skips. Its
hooks are the deferred kernels' rules evaluated in the forward FS: the
same light set test, the same light grid test (draw set 0 binding 66,
the deferred set's buffer), the same light-takeover test. It then adds
the record, remodulated by its own diffuse and specular colours through
the same call site the deferred kernels use, so the layer gets the
surface's lamp light with its shadows and emitters. The rigged flavour
takes it too. Fragments that match nothing walk every light as before,
and so does everything on frames in ratio mode or with the reuse off.

Test-Plan:
    python3 indra/newview/tests/blend_lamp_reuse_checks.py --source-root . --compile --out <dir>
      (the push bit, the record's bindings on both sides, the forward hooks against the deferred
       rules, the write in BASE / COMBINE; every lit FS variant and the lighting kernel's passes
       through glslc)
    python3 indra/newview/tests/blend_opaque_gbuffer_checks.py --source-root .
    python3 indra/vkclean/regen_world_spv.py --out-dir <dir> --cache-dir <dir>
    In world (deferred, RenderVKReSTIRDI 1-3, RenderVKReSTIRRadiance on): in a room lit by a
      point light (a fireplace, a lamp), a blush or makeup layer on a mesh head is shadowed
      exactly where the skin is, also with sun shadows off at night; a decal on a wall takes the
      wall's lamp shadow. RenderVKDeferVisReuse 0 or RenderVKReSTIRRadiance 0 brings the
      unshadowed glow back live. Glass and hair away from surfaces: lit as before.
    Log: "VKDeferSplit: intermediates ..." lists "visibility reuse depth + lamp record".
    VKPipeStats: LIT_ALPHA / LIT_RIG_ALPHA registers close to before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TheSlowly force-pushed blend-lamp-shadows from 1e84f38d82 to 281d1d42dd 2026-11-04 08:53:40 +00:00 Compare
TheSlowly force-pushed blend-lamp-shadows from 281d1d42dd to cc38a97deb 2026-11-04 19:54:34 +00:00 Compare
This branch is already included in the target branch. There is nothing to merge.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u blend-lamp-shadows:TheSlowly-blend-lamp-shadows
git switch TheSlowly-blend-lamp-shadows
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sky_line/Slipstream!1442
No description provided.