Skip to content

feat(llf): restore contact shadows with VR-aware noise#36

Merged
alandtse merged 1 commit into
devfrom
feat/llf-contact-shadows-restore
May 24, 2026
Merged

feat(llf): restore contact shadows with VR-aware noise#36
alandtse merged 1 commit into
devfrom
feat/llf-contact-shadows-restore

Conversation

@alandtse
Copy link
Copy Markdown
Owner

@alandtse alandtse commented May 23, 2026

Summary

Restores Light Limit Fix contact shadows for all point lights (strict and clustered, excluding simple emissive markers), reversing the maintainability-only removal in 347cb55. Adds a stereo-aware noise sample so VR doesn't flicker.

Why now

PR community-shaders#1495 (the upstream removal) cited no defect — no human discussion, no linked issue, no rationale beyond CodeRabbit's auto-summary. The shader call site in Lighting.hlsl was already // -commented out at the time of deletion, so this is reviving a dormant feature, not introducing new behaviour.

The ParticleTroned fork (cs-1.5-PL-VR-unleashed) has been carrying a restored implementation for months. This PR pulls just the contact-shadow piece — the fork's elaborate budgeted/cluster-aware variant requires Particle Lights to be meaningful and is deferred to a follow-up.

Changes

  • LightLimitFix.hlsli — restore IsSaturated() and ContactShadows() raymarch helper verbatim. Add GetContactShadowNoiseCoord() for stereo-aware noise sampling (one #if defined(VR) branch, leans on FrameBuffer::ViewToUV's per-eye return value).
  • Lighting.hlsl — hoist contact-shadow noise out of the per-light loop, compute once per pixel. Uncomment + activate the EnableContactShadows call site (gated to DEFERRED). Multiply contactShadow into pointLightShadow so it flows through the new DirectContext/EvaluateLighting path uniformly for both PBR and legacy.
  • SharedData.hlsli — re-add uint EnableContactShadows to LightLimitFixSettings cbuffer (replaces one float2 pad0 slot with uint EnableContactShadows; float pad0).
  • LightLimitFix.h — re-add bool EnableContactShadows = false to Settings.
  • LightLimitFix.cpp — restore Shadows section in DrawSettings() using current upstream's /////// separator style. Did not restore NLOHMANN persistence — current LLF is session-only per chore(llf): remove LightsVisualisationMode settings loading community-shaders/skyrim-community-shaders#1834, kept that stance.
  • LightLimitFix.ini — bump feature version 3-0-3 → 3-1-0 (minor: new persisted cbuffer field + user toggle).

VR jitter fix

Concept lifted from ParticleTroned/skyrim-community-shaders@8e615ea fix(contact-shadows): stabilize VR eye jitter, simplified to one helper / one ifdef.

In VR, using raw input.Position.xy for the noise sample makes each eye hash a different value at the same world position. The helper switches to screenUV * BufferDim.xy in VR — screenUV from FrameBuffer::ViewToUV is already per-eye via CameraProj[eye], so both eyes hash the same input and the noise stays stereo-stable. Flat keeps input.Position.xy to match the pre-removal implementation byte-for-byte.

What's not in this PR

Test plan

  • Builds clean on ALL preset (verified locally; CommunityShaders.dll produced)
  • CI shader validation (full hlslkit suite) passes — skipped locally per feedback_shader_validation_scope.md
  • In-game flat SE: enable in LLF settings, confirm point lights cast short screen-space shadows on receiver geometry
  • In-game VR: same as above; confirm no per-eye flicker on contact-shadow recipients
  • Verify no double-darkening with EMAT shadow path (SharedData::extendedMaterialSettings.EnableShadows)
  • Toggle on/off at runtime, no shader recompile required

Attribution

Both are credited in the commit trailers.

Summary by CodeRabbit

Release Notes

  • New Features

    • Contact shadows feature now available, enhancing shadow depth perception and overall visual rendering quality for more realistic lighting effects
    • User-configurable setting introduced to enable or disable contact shadows based on visual preferences and performance requirements
  • Chores

    • Version updated to 3.1.0

Review Change Stack

Copilot AI review requested due to automatic review settings May 23, 2026 20:39
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

📝 Walkthrough

Walkthrough

This PR adds contact-shadow rendering to enhance deferred lighting in the Light Limit Fix feature. It introduces shader helper functions for ray-marched contact shadows, extends CPU and GPU data structures to carry an EnableContactShadows flag, integrates shadow computation into the per-light loop, and exposes a UI toggle for artists to control the feature.

Changes

Contact Shadows Feature

Layer / File(s) Summary
Settings and data structure layout
src/Features/LightLimitFix.h, package/Shaders/Common/SharedData.hlsli, features/Light Limit Fix/Shaders/Features/LightLimitFix.ini
PerFrame and Settings structs add EnableContactShadows fields with adjusted padding; LightLimitFixSettings in shared GPU data gains the same flag; version bumped to 3-1-0.
Contact shadow shader utilities
features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli
IsSaturated overloads test clamped float/vector values; GetContactShadowNoiseCoord computes VR-aware noise sampling coordinates; ContactShadows implements ray-march accumulation of contact shadows from depth comparisons.
Lighting pass integration
package/Shaders/Lighting.hlsl
Per-pixel contactShadowSteps and stereo-stable noise are precomputed outside the per-light loop; per-light contactShadow is computed via ContactShadows for deferred eligible lights; parallax-shadow gating and final pointLightShadow multiplication incorporate the new contact shadow factor.
UI and runtime wiring
src/Features/LightLimitFix.cpp
Include order adjusted; settings UI adds a "Shadows" section with checkbox for EnableContactShadows; per-frame buffer population writes the setting to downstream shaders.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A fluffy hop through shadow's light,
Contact rays now march so right,
Depth and noise blend in the glow,
Clustered lights now steal the show!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: restoring contact shadows with VR-aware noise handling for Light Limit Fix, which is clearly reflected in the code changes across shader and configuration files.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/llf-contact-shadows-restore

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 and usage tips.

@github-actions
Copy link
Copy Markdown

No actionable suggestions for changed features.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Restores Light Limit Fix contact shadows for clustered/strict point lights and adds a VR-aware noise coordinate helper to reduce stereo flicker, wiring the result into the deferred lighting evaluation path via the shared feature constant buffer.

Changes:

  • Reintroduces an EnableContactShadows toggle (C++ settings + shared feature cbuffer field) and UI control.
  • Restores the ContactShadows() helper in LLF shader code and activates the call site in Lighting.hlsl (deferred-only), including hoisted per-pixel noise.
  • Bumps LLF feature version to reflect the updated feature cbuffer layout.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Features/LightLimitFix.h Adds EnableContactShadows to per-frame feature data and settings.
src/Features/LightLimitFix.cpp Adds UI checkbox + writes EnableContactShadows into the feature constant buffer payload.
package/Shaders/Lighting.hlsl Computes per-pixel contact-shadow noise (deferred) and multiplies contact shadow into point light shadowing.
package/Shaders/Common/SharedData.hlsli Extends LightLimitFixSettings with EnableContactShadows while keeping packing/alignment.
features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli Restores ContactShadows() and adds GetContactShadowNoiseCoord() for VR noise coordination.
features/Light Limit Fix/Shaders/Features/LightLimitFix.ini Version bump to 3-1-0.

Comment thread features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 23, 2026

✅ A pre-release build is available for this PR:
Download

@alandtse alandtse force-pushed the feat/llf-contact-shadows-restore branch from 5e9d661 to 380a568 Compare May 23, 2026 22:21
Copilot AI review requested due to automatic review settings May 24, 2026 04:10
@alandtse alandtse force-pushed the feat/llf-contact-shadows-restore branch from 380a568 to 58d4fcb Compare May 24, 2026 04:10
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli (1)

50-70: Add targeted shader validation for LLF contact-shadow permutations.

Given this helper is shared through includes, run targeted hlslkit-compile permutations for LIGHT_LIMIT_FIX + DEFERRED with VR on/off, plus hlslkit buffer scanning to catch register overlap early.

As per coding guidelines, "Highlight GPU register conflicts and recommend hlslkit buffer scanning for shader development" and "Use targeted hlslkit-compile shader validation for specific features during development rather than full validation".

🤖 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 `@features/Light` Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli around
lines 50 - 70, The shared helper GetContactShadowNoiseCoord can introduce GPU
register/buffer conflicts when compiled across permutations; run targeted
hlslkit-compile validation for the LIGHT_LIMIT_FIX + DEFERRED permutations with
VR defined and VR undefined to ensure the two branches compile correctly, and
run hlslkit's buffer/register scanner on the generated HLSL for these
permutations to catch register overlaps early; update CI/dev docs or the shader
build step to run these targeted checks rather than full-suite validation so LLF
contact-shadow permutations (GetContactShadowNoiseCoord, VR macro paths) are
validated during development.
package/Shaders/Lighting.hlsl (1)

2746-2748: ⚡ Quick win

Avoid full per-light position transform for VS contact-shadow direction.

At Line 2746-Line 2747, you can reuse lightDirection (already computed at Line 2704) and transform it as a direction (w=0) to reduce ALU in this hot loop.

♻️ Suggested micro-optimization
-			float3 lightPositionVS = mul(FrameBuffer::CameraView[eyeIndex], float4(light.positionWS[eyeIndex].xyz, 1)).xyz;
-			float3 normalizedLightDirectionVS = normalize(lightPositionVS - viewPosition.xyz);
+			float3 lightDirectionVS = mul((float3x3)FrameBuffer::CameraView[eyeIndex], lightDirection);
+			float3 normalizedLightDirectionVS = normalize(lightDirectionVS);
As per coding guidelines, "Always consider GPU workload and user experience when implementing graphics features".
🤖 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 `@package/Shaders/Lighting.hlsl` around lines 2746 - 2748, The code currently
transforms the light world-position to view space then subtracts viewPosition to
get a per-light direction (lightPositionVS - viewPosition), which is ALU-heavy;
instead reuse the already-computed world-space light direction symbol
lightDirection (from earlier around Line 2704) and transform it as a direction
by using mul(FrameBuffer::CameraView[eyeIndex], float4(lightDirection, 0)).xyz,
then normalize that result and pass it into LightLimitFix::ContactShadows
(replace lightPositionVS/viewPosition subtraction with normalized transformed
direction), keeping contactShadowNoise, contactShadowSteps and eyeIndex
unchanged.
🤖 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 `@features/Light` Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli:
- Around line 50-70: The shared helper GetContactShadowNoiseCoord can introduce
GPU register/buffer conflicts when compiled across permutations; run targeted
hlslkit-compile validation for the LIGHT_LIMIT_FIX + DEFERRED permutations with
VR defined and VR undefined to ensure the two branches compile correctly, and
run hlslkit's buffer/register scanner on the generated HLSL for these
permutations to catch register overlaps early; update CI/dev docs or the shader
build step to run these targeted checks rather than full-suite validation so LLF
contact-shadow permutations (GetContactShadowNoiseCoord, VR macro paths) are
validated during development.

In `@package/Shaders/Lighting.hlsl`:
- Around line 2746-2748: The code currently transforms the light world-position
to view space then subtracts viewPosition to get a per-light direction
(lightPositionVS - viewPosition), which is ALU-heavy; instead reuse the
already-computed world-space light direction symbol lightDirection (from earlier
around Line 2704) and transform it as a direction by using
mul(FrameBuffer::CameraView[eyeIndex], float4(lightDirection, 0)).xyz, then
normalize that result and pass it into LightLimitFix::ContactShadows (replace
lightPositionVS/viewPosition subtraction with normalized transformed direction),
keeping contactShadowNoise, contactShadowSteps and eyeIndex unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9f03a640-ee29-4b45-aadd-a4e164109b78

📥 Commits

Reviewing files that changed from the base of the PR and between 37dfdee and 58d4fcb.

📒 Files selected for processing (6)
  • features/Light Limit Fix/Shaders/Features/LightLimitFix.ini
  • features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli
  • package/Shaders/Common/SharedData.hlsli
  • package/Shaders/Lighting.hlsl
  • src/Features/LightLimitFix.cpp
  • src/Features/LightLimitFix.h

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread package/Shaders/Lighting.hlsl Outdated
Comment thread package/Shaders/Lighting.hlsl Outdated
Comment thread features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli Outdated
@alandtse alandtse force-pushed the feat/llf-contact-shadows-restore branch from 58d4fcb to 8d0e81d Compare May 24, 2026 04:38
Copilot AI review requested due to automatic review settings May 24, 2026 04:41
@alandtse alandtse force-pushed the feat/llf-contact-shadows-restore branch from 8d0e81d to 58d4fcb Compare May 24, 2026 04:41
@alandtse alandtse force-pushed the feat/llf-contact-shadows-restore branch from 58d4fcb to 6ac678a Compare May 24, 2026 04:42
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread features/Light Limit Fix/Shaders/LightLimitFix/LightLimitFix.hlsli
Comment thread package/Shaders/Lighting.hlsl
Comment thread package/Shaders/Lighting.hlsl
Reintroduce Light Limit Fix contact shadows for clustered point lights,
reversing the maintainability-driven removal in 347cb55. That removal
cited no defect, and the shader call site was already commented out at
the time of deletion, so this is reviving a dormant feature rather than
introducing new behaviour.

Adapted from the original implementation:
- LightLimitFix::ContactShadows() raymarch helper restored verbatim
- EnableContactShadows toggle re-added to LightLimitFix Settings and
  the SharedData::LightLimitFixSettings cbuffer
- Shadows section restored in the LLF settings UI (session-only,
  matching the current LLF settings-persistence stance)
- Wired into Lighting.hlsl as a multiplier on pointLightShadow, which
  the upstream DirectContext/EvaluateLighting refactor funnels into
  both the PBR and legacy paths uniformly

VR jitter stabilization (concept from ParticleTroned's 8e615ea):
- Hoist the contact-shadow noise sample out of the per-light loop and
  compute it once per pixel from (uv * eye-buffer dim) in VR or raw
  pixel position in flat. Using raw pixel coords in VR makes each eye
  sample a different noise pattern at the same world position, which
  reads as flicker on contact-shadow recipients.
- New helpers LightLimitFix::GetContactShadowScreenDim() and
  GetContactShadowNoiseCoord() centralise the stereo-aware sampling.

Bumps Light Limit Fix feature version to 3-1-0 (minor: new persisted
cbuffer field, new user-facing toggle).

Original contact-shadow implementation by jiayev (merged via #9 prior
to the 347cb55 removal). VR jitter analysis and fix from
ParticleTroned's fork.

Co-Authored-By: jiayev <jiayev1@gmail.com>
Co-Authored-By: ParticleTroned <248299730+ParticleTroned@users.noreply.github.com>
@alandtse alandtse force-pushed the feat/llf-contact-shadows-restore branch from 6ac678a to 6a78cef Compare May 24, 2026 05:35
@alandtse alandtse merged commit b84222b into dev May 24, 2026
21 checks passed
@alandtse alandtse deleted the feat/llf-contact-shadows-restore branch May 24, 2026 06:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants