From d6e7ccbaf820ecbb7cba18c8d949284b6336929b Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Fri, 27 Feb 2026 00:34:01 -0800 Subject: [PATCH 1/5] fix(VR): screen space shadows desync --- .../ScreenSpaceShadows/RaymarchCS.hlsl | 7 -- .../ScreenSpaceShadows.hlsli | 43 +++++++++ .../ScreenSpaceShadows/StereoSyncCS.hlsl | 93 +++++++++++++++++++ .../ScreenSpaceShadows/bend_sss_gpu.hlsli | 19 +--- src/Features/ScreenSpaceShadows.cpp | 90 +++++++++++++++--- src/Features/ScreenSpaceShadows.h | 27 ++++-- 6 files changed, 235 insertions(+), 44 deletions(-) create mode 100644 features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl diff --git a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/RaymarchCS.hlsl b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/RaymarchCS.hlsl index b57213a038..132ad940b1 100644 --- a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/RaymarchCS.hlsl +++ b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/RaymarchCS.hlsl @@ -24,10 +24,6 @@ cbuffer PerFrame : register(b1) float2 DynamicRes; - uint DynamicSampleCount; - uint DynamicReadCount; - float2 pad0; - float SurfaceThickness; float BilinearThreshold; float ShadowContrast; @@ -54,9 +50,6 @@ cbuffer PerFrame : register(b1) parameters.DynamicRes = DynamicRes; - parameters.DynamicSampleCount = DynamicSampleCount; - parameters.DynamicReadCount = DynamicReadCount; - parameters.UsePrecisionOffset = true; WriteScreenSpaceShadow(parameters, groupID, groupThreadID); diff --git a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/ScreenSpaceShadows.hlsli b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/ScreenSpaceShadows.hlsli index 0d1f221726..f581b8b1f7 100644 --- a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/ScreenSpaceShadows.hlsli +++ b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/ScreenSpaceShadows.hlsli @@ -1,3 +1,4 @@ +#include "Common/Math.hlsli" namespace ScreenSpaceShadows { @@ -5,6 +6,48 @@ namespace ScreenSpaceShadows float GetScreenSpaceShadow(float3 screenPosition, float2 uv, float noise, uint eyeIndex) { +#if defined(VR) + // Depth-weighted rotated blur to reduce per-eye noise before stereo sync + noise *= Math::TAU; + + half2x2 rotationMatrix = half2x2(cos(noise), sin(noise), -sin(noise), cos(noise)); + + float weight = 0; + float shadow = 0; + + int3 centerCoord = SharedData::ConvertUVToSampleCoord(uv, eyeIndex); + float viewZ = SharedData::GetScreenDepth(SharedData::DepthTexture.Load(centerCoord).x); + + static const float2 BlurOffsets[4] = { + float2(0.381664f, 0.89172f), + float2(0.491409f, 0.216926f), + float2(0.937803f, 0.734825f), + float2(0.00921659f, 0.0562151f), + }; + + for (uint i = 0; i < 4; i++) { + float2 offset = mul(BlurOffsets[i], rotationMatrix) * 0.0025; + + float2 sampleUV = saturate(uv + offset); + int3 sampleCoord = SharedData::ConvertUVToSampleCoord(sampleUV, eyeIndex); + + float linearDepth = SharedData::GetScreenDepth(SharedData::DepthTexture.Load(sampleCoord).x); + + float attenuation = 1.0 - saturate(100.0 * abs(linearDepth - viewZ) / viewZ); + if (attenuation > 0.0) { + shadow += ScreenSpaceShadowsTexture.Load(sampleCoord).x * attenuation; + weight += attenuation; + } + } + + if (weight > 0.0) + shadow /= weight; + else + shadow = ScreenSpaceShadowsTexture.Load(centerCoord).x; + + return shadow; +#else return ScreenSpaceShadowsTexture.Load(int3(int2(screenPosition.xy + 0.5f), 0)).x; +#endif } } \ No newline at end of file diff --git a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl new file mode 100644 index 0000000000..469ce6c6f1 --- /dev/null +++ b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl @@ -0,0 +1,93 @@ +// Stereo Sync - Bilateral blend of SSS shadow buffer between eyes +// +// Reprojects each pixel to the other eye and blends shadow values based on +// depth agreement with back-check validation. Runs after the raymarch pass +// to reduce per-eye shadow disparities in VR. +// +// Based on: Shi, Billeter, Eisemann 2022, "Stereo-consistent screen-space +// ambient occlusion" https://eprints.whiterose.ac.uk/id/eprint/187713/ + +#include "Common/FrameBuffer.hlsli" +#include "Common/VR.hlsli" + +#ifdef VR + +Texture2D DepthTexture : register(t0); +Texture2D SrcShadowTexture : register(t1); + +RWTexture2D OutShadowTexture : register(u0); + +cbuffer StereoSyncCB : register(b1) +{ + float2 FrameDim; + float2 RcpFrameDim; +}; + +static const float kDepthSigma = 0.01; +static const float kMaxBlend = 1.0; +static const float kBackCheckThreshold = 8.0; + +[numthreads(8, 8, 1)] void main(uint2 dtid : SV_DispatchThreadID) { + if (any(dtid >= uint2(FrameDim))) + return; + + float2 uv = (dtid + 0.5) * RcpFrameDim; + + uint eyeIndex = Stereo::GetEyeIndexFromTexCoord(uv); + + float depth = DepthTexture[dtid]; + + // depth == 0: VR HMD mask; depth == 1: sky/far plane + if (depth < 1e-5 || depth >= 1.0) { + OutShadowTexture[dtid] = SrcShadowTexture[dtid]; + return; + } + + Stereo::StereoBilateralResult r = Stereo::ReprojectToOtherEye(uv, depth, eyeIndex, FrameDim); + + if (!r.valid) { + OutShadowTexture[dtid] = SrcShadowTexture[dtid]; + return; + } + + float otherDepth = DepthTexture[r.otherPx]; + + // Skip if other eye sees mask or sky + if (otherDepth < 1e-5 || otherDepth >= 1.0) { + OutShadowTexture[dtid] = SrcShadowTexture[dtid]; + return; + } + + // Reject if reprojected pixel is near the HMD mask boundary. + // The raymarch at edge pixels samples into the mask (depth=0 -> treated as + // far plane), producing incorrect shadow values that cause a visible seam. + static const int kEdgeMargin = 2; + [unroll] for (int dx = -kEdgeMargin; dx <= kEdgeMargin; dx += kEdgeMargin) + { + [unroll] for (int dy = -kEdgeMargin; dy <= kEdgeMargin; dy += kEdgeMargin) + { + float neighborDepth = DepthTexture[r.otherPx + int2(dx, dy)]; + if (neighborDepth < 1e-5) { + OutShadowTexture[dtid] = SrcShadowTexture[dtid]; + return; + } + } + } + + Stereo::FinalizeStereoBlend(r, uv, depth, otherDepth, eyeIndex, FrameDim, kDepthSigma, kMaxBlend, kBackCheckThreshold); + + float myShadow = SrcShadowTexture[dtid]; + float otherShadow = SrcShadowTexture[r.otherPx]; + + // For shadows, use min (darkest) when depths agree well. + // If either eye detected an occluder, that shadow should be visible. + // The bilateral blend weight gates this -- if depths disagree (different + // surfaces), we fall back toward our own eye's value. + // + // blend=0: keep myShadow unchanged + // blend>0: lerp toward min(myShadow, otherShadow) + float combined = min(myShadow, otherShadow); + OutShadowTexture[dtid] = lerp(myShadow, combined, r.blendWeight); +} + +#endif // VR diff --git a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/bend_sss_gpu.hlsli b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/bend_sss_gpu.hlsli index 000b36beee..5a569d732f 100644 --- a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/bend_sss_gpu.hlsli +++ b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/bend_sss_gpu.hlsli @@ -58,9 +58,6 @@ struct DispatchParameters float2 DynamicRes; - uint DynamicSampleCount; - uint DynamicReadCount; - bool IgnoreEdgePixels; // If an edge is detected, the edge pixel will not contribute to the shadow. // If a very flat surface is being lit and rendered at an grazing angles, the edge detect may incorrectly detect multiple 'edge' pixels along that flat surface. // In these cases, the grazing angle of the light may subsequently produce aliasing artefacts in the shadow where these incorrect edges were detected. @@ -223,11 +220,8 @@ void WriteScreenSpaceShadow(DispatchParameters inParameters, int3 inGroupID, int half2 write_xy = floor(pixel_xy); - [loop] for (i = 0; i < READ_COUNT; i++) + [unroll] for (i = 0; i < READ_COUNT; i++) { - if (i == inParameters.DynamicSampleCount) - break; - // We sample depth twice per pixel per sample, and interpolate with an edge detect filter // Interpolation should only occur on the minor axis of the ray - major axis coordinates should be at pixel centers half2 read_xy = floor(pixel_xy); @@ -308,11 +302,8 @@ void WriteScreenSpaceShadow(DispatchParameters inParameters, int3 inGroupID, int } // Write the shadow depths to LDS - [loop] for (i = 0; i < READ_COUNT; i++) + [unroll] for (i = 0; i < READ_COUNT; i++) { - if (i == inParameters.DynamicSampleCount) - break; - // Perspective correct the shadowing depth, in this space, all light rays are parallel half stored_depth = (shadowing_depth[i] - inParameters.LightCoordinate.z) / sample_distance[i]; @@ -370,10 +361,8 @@ void WriteScreenSpaceShadow(DispatchParameters inParameters, int3 inGroupID, int start_depth = start_depth * depth_scale - z_sign; - for (i = 0; i < SAMPLE_COUNT; i++) { - if (i == inParameters.DynamicSampleCount) - break; - + [unroll] for (i = 0; i < SAMPLE_COUNT; i++) + { half depth_delta = abs(start_depth - DepthData[sample_index + i] * depth_scale); // By using 4 values, the average shadow can be taken, which can help soften single-pixel shadows. diff --git a/src/Features/ScreenSpaceShadows.cpp b/src/Features/ScreenSpaceShadows.cpp index fbdb250233..57f5098f82 100644 --- a/src/Features/ScreenSpaceShadows.cpp +++ b/src/Features/ScreenSpaceShadows.cpp @@ -26,6 +26,9 @@ void ScreenSpaceShadows::DrawSettings() ImGui::SliderFloat("Bilinear Threshold", &bendSettings.BilinearThreshold, 0.02f, 1.0f); ImGui::SliderFloat("Shadow Contrast", &bendSettings.ShadowContrast, 0.0f, 4.0f); + if (REL::Module::IsVR()) + ImGui::Checkbox("VR Stereo Sync", &enableStereoSync); + ImGui::Spacing(); ImGui::Spacing(); ImGui::TreePop(); @@ -42,17 +45,17 @@ void ScreenSpaceShadows::ClearShaderCache() raymarchRightCS->Release(); raymarchRightCS = nullptr; } + if (stereoSyncCS) { + stereoSyncCS->Release(); + stereoSyncCS = nullptr; + } } -uint ScreenSpaceShadows::GetScaledSampleCount(bool a_dynamic) +uint ScreenSpaceShadows::GetScaledSampleCount() { auto screenSize = globals::state->screenSize; - if (a_dynamic) - screenSize = Util::ConvertToDynamic(globals::state->screenSize); - // Scale sample count based on both dimensions relative to 1920x1080 reference - float2 referenceRes = { 1920.0f, 1080.0f }; float referenceArea = referenceRes.x * referenceRes.y; float currentArea = screenSize.x * screenSize.y; @@ -75,7 +78,7 @@ ID3D11ComputeShader* ScreenSpaceShadows::GetComputeRaymarch() } if (!raymarchCS) { - uint scaledSampleCount = GetScaledSampleCount(false); + uint scaledSampleCount = GetScaledSampleCount(); raymarchCS = (ID3D11ComputeShader*)Util::CompileShader(L"Data\\Shaders\\ScreenSpaceShadows\\RaymarchCS.hlsl", { { "SAMPLE_COUNT", std::format("{}", scaledSampleCount).c_str() } }, "cs_5_0"); } return raymarchCS; @@ -94,7 +97,7 @@ ID3D11ComputeShader* ScreenSpaceShadows::GetComputeRaymarchRight() } if (!raymarchRightCS) { - uint scaledSampleCount = GetScaledSampleCount(false); + uint scaledSampleCount = GetScaledSampleCount(); raymarchRightCS = (ID3D11ComputeShader*)Util::CompileShader(L"Data\\Shaders\\ScreenSpaceShadows\\RaymarchCS.hlsl", { { "SAMPLE_COUNT", std::format("{}", scaledSampleCount).c_str() }, { "RIGHT", "" } }, "cs_5_0"); } return raymarchRightCS; @@ -151,9 +154,6 @@ void ScreenSpaceShadows::DrawShadows() float2 dynamicRes = { viewport->GetRuntimeData().dynamicResolutionWidthRatio, viewport->GetRuntimeData().dynamicResolutionHeightRatio }; - uint dynamicSampleCount = GetScaledSampleCount(true); - uint dynamicReadCount = (dynamicSampleCount / 64 + 2); - // Shared dispatch logic for both VR and non-VR auto DispatchEye = [&](const char* eyeName, ID3D11ComputeShader* shader, const float* lightProj, float invTexSizeX, float invTexSizeY) { @@ -187,9 +187,6 @@ void ScreenSpaceShadows::DrawShadows() data.DynamicRes = dynamicRes; - data.DynamicSampleCount = dynamicSampleCount; - data.DynamicReadCount = dynamicReadCount; - data.InvDepthTextureSize[0] = invTexSizeX; data.InvDepthTextureSize[1] = invTexSizeY; @@ -233,6 +230,59 @@ void ScreenSpaceShadows::DrawShadows() context->CSSetConstantBuffers(1, 1, &buffer); } +void ScreenSpaceShadows::DrawStereoSync() +{ + if (!globals::game::isVR || !enableStereoSync || !stereoSyncCS || !stereoSyncCopyTex || !stereoSyncCB) + return; + + ZoneScoped; + TracyD3D11Zone(globals::state->tracyCtx, "SSS - Stereo Sync"); + + if (globals::state->frameAnnotations) + globals::state->BeginPerfEvent("SSS - Stereo Sync"); + + auto context = globals::d3d::context; + + context->CopyResource(stereoSyncCopyTex->resource.get(), screenSpaceShadowsTexture->resource.get()); + + float2 resolution = Util::ConvertToDynamic(globals::state->screenSize); + + StereoSyncCB cbData{}; + cbData.FrameDim[0] = resolution.x; + cbData.FrameDim[1] = resolution.y; + cbData.RcpFrameDim[0] = 1.0f / resolution.x; + cbData.RcpFrameDim[1] = 1.0f / resolution.y; + + stereoSyncCB->Update(cbData); + auto cbPtr = stereoSyncCB->CB(); + + auto* depthSRV = Util::GetCurrentSceneDepthSRV(); + ID3D11ShaderResourceView* srvs[2]{ depthSRV, stereoSyncCopyTex->srv.get() }; + ID3D11UnorderedAccessView* uavs[1]{ screenSpaceShadowsTexture->uav.get() }; + + context->CSSetConstantBuffers(1, 1, &cbPtr); + auto* sharedDataBuf = globals::state->sharedDataCB->CB(); + context->CSSetConstantBuffers(5, 1, &sharedDataBuf); + context->CSSetShaderResources(0, 2, srvs); + context->CSSetUnorderedAccessViews(0, 1, uavs, nullptr); + context->CSSetShader(stereoSyncCS, nullptr, 0); + + auto dispatchCount = Util::GetScreenDispatchCount(true); + context->Dispatch(dispatchCount.x, dispatchCount.y, 1); + + srvs[0] = nullptr; + srvs[1] = nullptr; + uavs[0] = nullptr; + cbPtr = nullptr; + context->CSSetShaderResources(0, 2, srvs); + context->CSSetUnorderedAccessViews(0, 1, uavs, nullptr); + context->CSSetConstantBuffers(1, 1, &cbPtr); + context->CSSetShader(nullptr, nullptr, 0); + + if (globals::state->frameAnnotations) + globals::state->EndPerfEvent(); +} + void ScreenSpaceShadows::Prepass() { auto context = globals::d3d::context; @@ -241,8 +291,10 @@ void ScreenSpaceShadows::Prepass() context->ClearUnorderedAccessViewFloat(screenSpaceShadowsTexture->uav.get(), white); if (auto sky = globals::game::sky) - if (bendSettings.Enable && sky->mode.get() == RE::Sky::Mode::kFull) + if (bendSettings.Enable && sky->mode.get() == RE::Sky::Mode::kFull) { DrawShadows(); + DrawStereoSync(); + } auto view = screenSpaceShadowsTexture->srv.get(); context->PSSetShaderResources(45, 1, &view); @@ -272,6 +324,11 @@ void ScreenSpaceShadows::SetupResources() { raymarchCB = new ConstantBuffer(ConstantBufferDesc()); + if (REL::Module::IsVR()) { + stereoSyncCB = new ConstantBuffer(ConstantBufferDesc()); + stereoSyncCS = reinterpret_cast(Util::CompileShader(L"Data\\Shaders\\ScreenSpaceShadows\\StereoSyncCS.hlsl", { { "VR", "" }, { "FRAMEBUFFER", "" } }, "cs_5_0")); + } + { auto device = globals::d3d::device; @@ -313,5 +370,10 @@ void ScreenSpaceShadows::SetupResources() screenSpaceShadowsTexture = new Texture2D(texDesc); screenSpaceShadowsTexture->CreateSRV(srvDesc); screenSpaceShadowsTexture->CreateUAV(uavDesc); + + if (REL::Module::IsVR()) { + stereoSyncCopyTex = new Texture2D(texDesc); + stereoSyncCopyTex->CreateSRV(srvDesc); + } } } \ No newline at end of file diff --git a/src/Features/ScreenSpaceShadows.h b/src/Features/ScreenSpaceShadows.h index 189d0ae805..a256514b8c 100644 --- a/src/Features/ScreenSpaceShadows.h +++ b/src/Features/ScreenSpaceShadows.h @@ -31,9 +31,9 @@ struct ScreenSpaceShadows : Feature struct BendSettings { - float SurfaceThickness = 0.02f; + float SurfaceThickness = !globals::game::isVR ? 0.02f : 0.010f; float BilinearThreshold = 0.02f; - float ShadowContrast = 1.0f; + float ShadowContrast = !globals::game::isVR ? 1.0f : 4.0f; uint Enable = 1; uint SampleCount = 1; uint pad0[3]; @@ -58,14 +58,19 @@ struct ScreenSpaceShadows : Feature float2 DynamicRes; - uint DynamicSampleCount; - uint DynamicReadCount; - float pad0[2]; - BendSettings settings; }; STATIC_ASSERT_ALIGNAS_16(RaymarchCB); + bool enableStereoSync = true; + + struct alignas(16) StereoSyncCB + { + float FrameDim[2]; + float RcpFrameDim[2]; + }; + STATIC_ASSERT_ALIGNAS_16(StereoSyncCB); + ID3D11SamplerState* pointBorderSampler = nullptr; ConstantBuffer* raymarchCB = nullptr; @@ -74,12 +79,17 @@ struct ScreenSpaceShadows : Feature Texture2D* screenSpaceShadowsTexture = nullptr; + // VR stereo sync resources + Texture2D* stereoSyncCopyTex = nullptr; + ConstantBuffer* stereoSyncCB = nullptr; + ID3D11ComputeShader* stereoSyncCS = nullptr; + virtual void SetupResources() override; virtual void DrawSettings() override; virtual void ClearShaderCache() override; - uint GetScaledSampleCount(bool a_dynamic); + uint GetScaledSampleCount(); ID3D11ComputeShader* GetComputeRaymarch(); ID3D11ComputeShader* GetComputeRaymarchRight(); @@ -89,8 +99,9 @@ struct ScreenSpaceShadows : Feature virtual void SaveSettings(json& o_json) override; void DrawShadows(); + void DrawStereoSync(); virtual void RestoreDefaultSettings() override; - virtual bool SupportsVR() override { return false; }; + virtual bool SupportsVR() override { return true; }; }; From d6464a196456e4c44e6d4d94694ae702144afac7 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Fri, 27 Feb 2026 17:58:12 -0800 Subject: [PATCH 2/5] chore: address ai comments --- src/Features/ScreenSpaceShadows.cpp | 33 +++++++++++------------------ src/Features/ScreenSpaceShadows.h | 2 ++ 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/Features/ScreenSpaceShadows.cpp b/src/Features/ScreenSpaceShadows.cpp index 57f5098f82..0a02d98b1d 100644 --- a/src/Features/ScreenSpaceShadows.cpp +++ b/src/Features/ScreenSpaceShadows.cpp @@ -35,7 +35,7 @@ void ScreenSpaceShadows::DrawSettings() } } -void ScreenSpaceShadows::ClearShaderCache() +void ScreenSpaceShadows::InvalidateRaymarchShaders() { if (raymarchCS) { raymarchCS->Release(); @@ -45,6 +45,11 @@ void ScreenSpaceShadows::ClearShaderCache() raymarchRightCS->Release(); raymarchRightCS = nullptr; } +} + +void ScreenSpaceShadows::ClearShaderCache() +{ + InvalidateRaymarchShaders(); if (stereoSyncCS) { stereoSyncCS->Release(); stereoSyncCS = nullptr; @@ -53,12 +58,12 @@ void ScreenSpaceShadows::ClearShaderCache() uint ScreenSpaceShadows::GetScaledSampleCount() { - auto screenSize = globals::state->screenSize; + float2 renderSize = Util::ConvertToDynamic(globals::state->screenSize); // Scale sample count based on both dimensions relative to 1920x1080 reference float2 referenceRes = { 1920.0f, 1080.0f }; float referenceArea = referenceRes.x * referenceRes.y; - float currentArea = screenSize.x * screenSize.y; + float currentArea = renderSize.x * renderSize.y; float areaScale = std::sqrt(currentArea / referenceArea); uint scaledSampleCount = static_cast(std::round(bendSettings.SampleCount * 60 * areaScale)); @@ -67,18 +72,14 @@ uint ScreenSpaceShadows::GetScaledSampleCount() ID3D11ComputeShader* ScreenSpaceShadows::GetComputeRaymarch() { - static uint sampleCount = bendSettings.SampleCount; + uint scaledSampleCount = GetScaledSampleCount(); - if (sampleCount != bendSettings.SampleCount) { - sampleCount = bendSettings.SampleCount; - if (raymarchCS) { - raymarchCS->Release(); - raymarchCS = nullptr; - } + if (scaledSampleCount != lastCompiledSampleCount) { + lastCompiledSampleCount = scaledSampleCount; + InvalidateRaymarchShaders(); } if (!raymarchCS) { - uint scaledSampleCount = GetScaledSampleCount(); raymarchCS = (ID3D11ComputeShader*)Util::CompileShader(L"Data\\Shaders\\ScreenSpaceShadows\\RaymarchCS.hlsl", { { "SAMPLE_COUNT", std::format("{}", scaledSampleCount).c_str() } }, "cs_5_0"); } return raymarchCS; @@ -86,16 +87,6 @@ ID3D11ComputeShader* ScreenSpaceShadows::GetComputeRaymarch() ID3D11ComputeShader* ScreenSpaceShadows::GetComputeRaymarchRight() { - static uint sampleCount = bendSettings.SampleCount; - - if (sampleCount != bendSettings.SampleCount) { - sampleCount = bendSettings.SampleCount; - if (raymarchRightCS) { - raymarchRightCS->Release(); - raymarchRightCS = nullptr; - } - } - if (!raymarchRightCS) { uint scaledSampleCount = GetScaledSampleCount(); raymarchRightCS = (ID3D11ComputeShader*)Util::CompileShader(L"Data\\Shaders\\ScreenSpaceShadows\\RaymarchCS.hlsl", { { "SAMPLE_COUNT", std::format("{}", scaledSampleCount).c_str() }, { "RIGHT", "" } }, "cs_5_0"); diff --git a/src/Features/ScreenSpaceShadows.h b/src/Features/ScreenSpaceShadows.h index a256514b8c..de9b8e1bd4 100644 --- a/src/Features/ScreenSpaceShadows.h +++ b/src/Features/ScreenSpaceShadows.h @@ -89,7 +89,9 @@ struct ScreenSpaceShadows : Feature virtual void DrawSettings() override; virtual void ClearShaderCache() override; + void InvalidateRaymarchShaders(); uint GetScaledSampleCount(); + uint lastCompiledSampleCount = 0; ID3D11ComputeShader* GetComputeRaymarch(); ID3D11ComputeShader* GetComputeRaymarchRight(); From 6e7f06a726e73fad26304fecc2b12dfa38dadb37 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Fri, 27 Feb 2026 18:18:27 -0800 Subject: [PATCH 3/5] perf: combine blur with sync --- .../ScreenSpaceShadows.hlsli | 43 --------- .../ScreenSpaceShadows/StereoSyncCS.hlsl | 89 +++++++++++++------ src/Features/ScreenSpaceShadows.cpp | 22 ++++- 3 files changed, 84 insertions(+), 70 deletions(-) diff --git a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/ScreenSpaceShadows.hlsli b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/ScreenSpaceShadows.hlsli index f581b8b1f7..0d1f221726 100644 --- a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/ScreenSpaceShadows.hlsli +++ b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/ScreenSpaceShadows.hlsli @@ -1,4 +1,3 @@ -#include "Common/Math.hlsli" namespace ScreenSpaceShadows { @@ -6,48 +5,6 @@ namespace ScreenSpaceShadows float GetScreenSpaceShadow(float3 screenPosition, float2 uv, float noise, uint eyeIndex) { -#if defined(VR) - // Depth-weighted rotated blur to reduce per-eye noise before stereo sync - noise *= Math::TAU; - - half2x2 rotationMatrix = half2x2(cos(noise), sin(noise), -sin(noise), cos(noise)); - - float weight = 0; - float shadow = 0; - - int3 centerCoord = SharedData::ConvertUVToSampleCoord(uv, eyeIndex); - float viewZ = SharedData::GetScreenDepth(SharedData::DepthTexture.Load(centerCoord).x); - - static const float2 BlurOffsets[4] = { - float2(0.381664f, 0.89172f), - float2(0.491409f, 0.216926f), - float2(0.937803f, 0.734825f), - float2(0.00921659f, 0.0562151f), - }; - - for (uint i = 0; i < 4; i++) { - float2 offset = mul(BlurOffsets[i], rotationMatrix) * 0.0025; - - float2 sampleUV = saturate(uv + offset); - int3 sampleCoord = SharedData::ConvertUVToSampleCoord(sampleUV, eyeIndex); - - float linearDepth = SharedData::GetScreenDepth(SharedData::DepthTexture.Load(sampleCoord).x); - - float attenuation = 1.0 - saturate(100.0 * abs(linearDepth - viewZ) / viewZ); - if (attenuation > 0.0) { - shadow += ScreenSpaceShadowsTexture.Load(sampleCoord).x * attenuation; - weight += attenuation; - } - } - - if (weight > 0.0) - shadow /= weight; - else - shadow = ScreenSpaceShadowsTexture.Load(centerCoord).x; - - return shadow; -#else return ScreenSpaceShadowsTexture.Load(int3(int2(screenPosition.xy + 0.5f), 0)).x; -#endif } } \ No newline at end of file diff --git a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl index 469ce6c6f1..572224c59c 100644 --- a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl +++ b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl @@ -1,8 +1,7 @@ -// Stereo Sync - Bilateral blend of SSS shadow buffer between eyes -// -// Reprojects each pixel to the other eye and blends shadow values based on -// depth agreement with back-check validation. Runs after the raymarch pass -// to reduce per-eye shadow disparities in VR. +// Stereo Sync + Blur - Combined bilateral stereo blend and depth-weighted +// blur for VR screen-space shadows. Runs as a single compute pass after the +// raymarch to both synchronize shadow data between eyes and smooth per-pixel +// noise. // // Based on: Shi, Billeter, Eisemann 2022, "Stereo-consistent screen-space // ambient occlusion" https://eprints.whiterose.ac.uk/id/eprint/187713/ @@ -27,6 +26,49 @@ static const float kDepthSigma = 0.01; static const float kMaxBlend = 1.0; static const float kBackCheckThreshold = 8.0; +// Depth-weighted 4-sample blur using a rotated Poisson disk. +// Uses dtid hash for per-pixel rotation to break structured patterns. +float BlurShadow(int2 dtid, float centerDepth) +{ + // Per-pixel rotation from interleaved gradient noise + float noise = frac(52.9829189 * frac(0.06711056 * dtid.x + 0.00583715 * dtid.y)); + float angle = noise * 6.28318530718; + float sn, cs; + sincos(angle, sn, cs); + float2x2 rot = float2x2(cs, sn, -sn, cs); + + static const float2 kOffsets[4] = { + float2(0.382, 0.892), + float2(0.491, 0.217), + float2(0.938, 0.735), + float2(0.009, 0.056), + }; + + float weight = 0; + float shadow = 0; + + [unroll] for (uint i = 0; i < 4; i++) + { + float2 offset = mul(kOffsets[i], rot); + int2 samplePx = dtid + int2(offset * 2.5); + samplePx = clamp(samplePx, int2(0, 0), int2(FrameDim) - 1); + + float sampleDepth = DepthTexture[samplePx]; + + if (sampleDepth < 1e-5) + continue; + + float attenuation = 1.0 - saturate(100.0 * abs(sampleDepth - centerDepth) / max(centerDepth, 1e-5)); + + if (attenuation > 0.0) { + shadow += SrcShadowTexture[samplePx] * attenuation; + weight += attenuation; + } + } + + return weight > 0.0 ? shadow / weight : SrcShadowTexture[dtid]; +} + [numthreads(8, 8, 1)] void main(uint2 dtid : SV_DispatchThreadID) { if (any(dtid >= uint2(FrameDim))) return; @@ -43,10 +85,13 @@ static const float kBackCheckThreshold = 8.0; return; } + // Depth-weighted blur on this eye's shadow data + float myShadow = BlurShadow(dtid, depth); + Stereo::StereoBilateralResult r = Stereo::ReprojectToOtherEye(uv, depth, eyeIndex, FrameDim); if (!r.valid) { - OutShadowTexture[dtid] = SrcShadowTexture[dtid]; + OutShadowTexture[dtid] = myShadow; return; } @@ -54,38 +99,30 @@ static const float kBackCheckThreshold = 8.0; // Skip if other eye sees mask or sky if (otherDepth < 1e-5 || otherDepth >= 1.0) { - OutShadowTexture[dtid] = SrcShadowTexture[dtid]; + OutShadowTexture[dtid] = myShadow; return; } - // Reject if reprojected pixel is near the HMD mask boundary. - // The raymarch at edge pixels samples into the mask (depth=0 -> treated as - // far plane), producing incorrect shadow values that cause a visible seam. + // Reject if reprojected pixel is near the HMD mask boundary static const int kEdgeMargin = 2; - [unroll] for (int dx = -kEdgeMargin; dx <= kEdgeMargin; dx += kEdgeMargin) + static const int2 kNeighborOffsets[4] = { + int2(-kEdgeMargin, 0), int2(kEdgeMargin, 0), + int2(0, -kEdgeMargin), int2(0, kEdgeMargin) + }; + [unroll] for (int n = 0; n < 4; n++) { - [unroll] for (int dy = -kEdgeMargin; dy <= kEdgeMargin; dy += kEdgeMargin) - { - float neighborDepth = DepthTexture[r.otherPx + int2(dx, dy)]; - if (neighborDepth < 1e-5) { - OutShadowTexture[dtid] = SrcShadowTexture[dtid]; - return; - } + if (DepthTexture[r.otherPx + kNeighborOffsets[n]] < 1e-5) { + OutShadowTexture[dtid] = myShadow; + return; } } Stereo::FinalizeStereoBlend(r, uv, depth, otherDepth, eyeIndex, FrameDim, kDepthSigma, kMaxBlend, kBackCheckThreshold); - float myShadow = SrcShadowTexture[dtid]; float otherShadow = SrcShadowTexture[r.otherPx]; - // For shadows, use min (darkest) when depths agree well. - // If either eye detected an occluder, that shadow should be visible. - // The bilateral blend weight gates this -- if depths disagree (different - // surfaces), we fall back toward our own eye's value. - // - // blend=0: keep myShadow unchanged - // blend>0: lerp toward min(myShadow, otherShadow) + // Use min (darkest) when depths agree: if either eye detected an + // occluder, that shadow should be visible. float combined = min(myShadow, otherShadow); OutShadowTexture[dtid] = lerp(myShadow, combined, r.blendWeight); } diff --git a/src/Features/ScreenSpaceShadows.cpp b/src/Features/ScreenSpaceShadows.cpp index 0a02d98b1d..1e92c6bd7b 100644 --- a/src/Features/ScreenSpaceShadows.cpp +++ b/src/Features/ScreenSpaceShadows.cpp @@ -21,13 +21,33 @@ void ScreenSpaceShadows::DrawSettings() { if (ImGui::TreeNodeEx("General", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Checkbox("Enable", (bool*)&bendSettings.Enable); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Enable screen-space contact shadows from the sun/moon direction."); + ImGui::SliderInt("Sample Count Multiplier", (int*)&bendSettings.SampleCount, 1, 4); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Multiplier for shadow ray sample count. Higher values increase shadow reach at the cost of performance. Adapts to render resolution."); + ImGui::SliderFloat("Surface Thickness", &bendSettings.SurfaceThickness, 0.005f, 0.05f); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Assumed thickness of surfaces for shadow detection. Lower values produce thinner, more precise shadows."); + ImGui::SliderFloat("Bilinear Threshold", &bendSettings.BilinearThreshold, 0.02f, 1.0f); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Depth threshold for edge detection during bilinear interpolation. Higher values smooth more aggressively across edges."); + ImGui::SliderFloat("Shadow Contrast", &bendSettings.ShadowContrast, 0.0f, 4.0f); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Contrast boost for the shadow transition. Higher values produce harder shadow edges."); - if (REL::Module::IsVR()) + if (globals::game::isVR && globals::state->IsDeveloperMode()) { ImGui::Checkbox("VR Stereo Sync", &enableStereoSync); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text( + "Synchronizes shadow data between left and right eyes via bilateral reprojection " + "and applies a depth-weighted blur to reduce per-eye noise. " + "Uses min-blend so if either eye detects an occluder, the shadow is preserved. "); + } ImGui::Spacing(); ImGui::Spacing(); From 82416e6903be7a5417b006d5c8c132be0f850b37 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Fri, 27 Feb 2026 22:42:07 -0800 Subject: [PATCH 4/5] fix: avoid outlines due to occlusion --- .../ScreenSpaceShadows/StereoSyncCS.hlsl | 55 +++++++++++++++---- src/Features/ScreenSpaceShadows.cpp | 19 ++++++- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl index 572224c59c..3789ee35c5 100644 --- a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl +++ b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl @@ -7,11 +7,12 @@ // ambient occlusion" https://eprints.whiterose.ac.uk/id/eprint/187713/ #include "Common/FrameBuffer.hlsli" +#include "Common/SharedData.hlsli" #include "Common/VR.hlsli" #ifdef VR -Texture2D DepthTexture : register(t0); +Texture2D SrcDepthTexture : register(t0); Texture2D SrcShadowTexture : register(t1); RWTexture2D OutShadowTexture : register(u0); @@ -24,7 +25,7 @@ cbuffer StereoSyncCB : register(b1) static const float kDepthSigma = 0.01; static const float kMaxBlend = 1.0; -static const float kBackCheckThreshold = 8.0; +static const float kEdgeDepthThreshold = 0.05; // Depth-weighted 4-sample blur using a rotated Poisson disk. // Uses dtid hash for per-pixel rotation to break structured patterns. @@ -53,7 +54,7 @@ float BlurShadow(int2 dtid, float centerDepth) int2 samplePx = dtid + int2(offset * 2.5); samplePx = clamp(samplePx, int2(0, 0), int2(FrameDim) - 1); - float sampleDepth = DepthTexture[samplePx]; + float sampleDepth = SrcDepthTexture[samplePx]; if (sampleDepth < 1e-5) continue; @@ -77,7 +78,7 @@ float BlurShadow(int2 dtid, float centerDepth) uint eyeIndex = Stereo::GetEyeIndexFromTexCoord(uv); - float depth = DepthTexture[dtid]; + float depth = SrcDepthTexture[dtid]; // depth == 0: VR HMD mask; depth == 1: sky/far plane if (depth < 1e-5 || depth >= 1.0) { @@ -85,7 +86,32 @@ float BlurShadow(int2 dtid, float centerDepth) return; } - // Depth-weighted blur on this eye's shadow data + // Skip stereo sync for first-person geometry interior (hands/weapons). + // Placed before the blur: arm shadow is uniform so the bilateral blur + // would return SrcShadowTexture[dtid] unchanged anyway. + float linearDepth = SharedData::GetScreenDepth(depth); + if (linearDepth < VR_FP_Z) { + OutShadowTexture[dtid] = SrcShadowTexture[dtid]; + return; + } + + // Skip stereo sync at depth discontinuities (arm/world silhouettes, object edges). + // Placed before the blur: the bilateral depth weighting zeroes out cross-edge + // samples, so the blur collapses to SrcShadowTexture[dtid] at these pixels anyway. + float4 edgeDepths = float4( + SrcDepthTexture[dtid + int2(1, 0)], + SrcDepthTexture[dtid + int2(-1, 0)], + SrcDepthTexture[dtid + int2(0, 1)], + SrcDepthTexture[dtid + int2(0, -1)]); + float maxEdgeDiff = max(max(abs(depth - edgeDepths.x), abs(depth - edgeDepths.y)), + max(abs(depth - edgeDepths.z), abs(depth - edgeDepths.w))); + if (maxEdgeDiff > kEdgeDepthThreshold) { + OutShadowTexture[dtid] = SrcShadowTexture[dtid]; + return; + } + + // Depth-weighted blur on this eye's shadow data. + // Only reached by world pixels that will attempt stereo sync. float myShadow = BlurShadow(dtid, depth); Stereo::StereoBilateralResult r = Stereo::ReprojectToOtherEye(uv, depth, eyeIndex, FrameDim); @@ -95,15 +121,20 @@ float BlurShadow(int2 dtid, float centerDepth) return; } - float otherDepth = DepthTexture[r.otherPx]; + float otherDepth = SrcDepthTexture[r.otherPx]; - // Skip if other eye sees mask or sky - if (otherDepth < 1e-5 || otherDepth >= 1.0) { + // Skip if other eye sees mask, sky, or first-person geometry + if (otherDepth < 1e-5 || otherDepth >= 1.0 || SharedData::GetScreenDepth(otherDepth) < VR_FP_Z) { OutShadowTexture[dtid] = myShadow; return; } - // Reject if reprojected pixel is near the HMD mask boundary + // Reject if reprojected pixel is near the HMD mask boundary, or if it sits + // at a depth discontinuity in the other eye. The source-side edge check above + // only fires when *this* eye sees the boundary; due to VR parallax the arm + // silhouette appears at a different screen position in each eye, so the + // reprojection can cross a boundary invisible from this eye's perspective. + // Reusing the same four neighbor reads covers both purposes at no extra cost. static const int kEdgeMargin = 2; static const int2 kNeighborOffsets[4] = { int2(-kEdgeMargin, 0), int2(kEdgeMargin, 0), @@ -111,13 +142,15 @@ float BlurShadow(int2 dtid, float centerDepth) }; [unroll] for (int n = 0; n < 4; n++) { - if (DepthTexture[r.otherPx + kNeighborOffsets[n]] < 1e-5) { + float neighborDepth = SrcDepthTexture[r.otherPx + kNeighborOffsets[n]]; + if (neighborDepth < 1e-5 || abs(otherDepth - neighborDepth) > kEdgeDepthThreshold) { OutShadowTexture[dtid] = myShadow; return; } } - Stereo::FinalizeStereoBlend(r, uv, depth, otherDepth, eyeIndex, FrameDim, kDepthSigma, kMaxBlend, kBackCheckThreshold); + // Source + destination edge detection + Stereo::FinalizeStereoBlend(r, uv, depth, otherDepth, eyeIndex, FrameDim, kDepthSigma, kMaxBlend, 0.0); float otherShadow = SrcShadowTexture[r.otherPx]; diff --git a/src/Features/ScreenSpaceShadows.cpp b/src/Features/ScreenSpaceShadows.cpp index 1e92c6bd7b..de28a4a052 100644 --- a/src/Features/ScreenSpaceShadows.cpp +++ b/src/Features/ScreenSpaceShadows.cpp @@ -80,6 +80,10 @@ uint ScreenSpaceShadows::GetScaledSampleCount() { float2 renderSize = Util::ConvertToDynamic(globals::state->screenSize); + // In VR, renderSize covers both eyes side-by-side; raymarch dispatches per-eye + if (globals::game::isVR) + renderSize.x /= 2.0f; + // Scale sample count based on both dimensions relative to 1920x1080 reference float2 referenceRes = { 1920.0f, 1080.0f }; float referenceArea = referenceRes.x * referenceRes.y; @@ -87,6 +91,10 @@ uint ScreenSpaceShadows::GetScaledSampleCount() float areaScale = std::sqrt(currentArea / referenceArea); uint scaledSampleCount = static_cast(std::round(bendSettings.SampleCount * 60 * areaScale)); + // Quantize to steps of 8 to prevent frequent recompilation from small DRS oscillations + scaledSampleCount = ((scaledSampleCount + 7u) / 8u) * 8u; + scaledSampleCount = std::max(scaledSampleCount, 8u); + return scaledSampleCount; } @@ -243,7 +251,12 @@ void ScreenSpaceShadows::DrawShadows() void ScreenSpaceShadows::DrawStereoSync() { - if (!globals::game::isVR || !enableStereoSync || !stereoSyncCS || !stereoSyncCopyTex || !stereoSyncCB) + if (!globals::game::isVR || !enableStereoSync || !stereoSyncCopyTex || !stereoSyncCB) + return; + + if (!stereoSyncCS) + stereoSyncCS = reinterpret_cast(Util::CompileShader(L"Data\\Shaders\\ScreenSpaceShadows\\StereoSyncCS.hlsl", { { "VR", "" }, { "FRAMEBUFFER", "" } }, "cs_5_0")); + if (!stereoSyncCS) return; ZoneScoped; @@ -335,7 +348,7 @@ void ScreenSpaceShadows::SetupResources() { raymarchCB = new ConstantBuffer(ConstantBufferDesc()); - if (REL::Module::IsVR()) { + if (globals::game::isVR) { stereoSyncCB = new ConstantBuffer(ConstantBufferDesc()); stereoSyncCS = reinterpret_cast(Util::CompileShader(L"Data\\Shaders\\ScreenSpaceShadows\\StereoSyncCS.hlsl", { { "VR", "" }, { "FRAMEBUFFER", "" } }, "cs_5_0")); } @@ -382,7 +395,7 @@ void ScreenSpaceShadows::SetupResources() screenSpaceShadowsTexture->CreateSRV(srvDesc); screenSpaceShadowsTexture->CreateUAV(uavDesc); - if (REL::Module::IsVR()) { + if (globals::game::isVR) { stereoSyncCopyTex = new Texture2D(texDesc); stereoSyncCopyTex->CreateSRV(srvDesc); } From 459f354f2e8bca635b6ca3e8326d3551045d8a34 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Fri, 27 Feb 2026 23:58:25 -0800 Subject: [PATCH 5/5] refactor: extract MaxDepthDiff --- .../ScreenSpaceShadows/StereoSyncCS.hlsl | 35 ++++++++++--------- src/Features/ScreenSpaceShadows.cpp | 1 - 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl index 3789ee35c5..87b90d3e35 100644 --- a/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl +++ b/features/Screen-Space Shadows/Shaders/ScreenSpaceShadows/StereoSyncCS.hlsl @@ -23,9 +23,15 @@ cbuffer StereoSyncCB : register(b1) float2 RcpFrameDim; }; -static const float kDepthSigma = 0.01; -static const float kMaxBlend = 1.0; -static const float kEdgeDepthThreshold = 0.05; +static const float kDepthSigma = 0.01; // Bilateral depth tolerance (NDC): surfaces within this range are considered the same and blended +static const float kMaxBlend = 1.0; // Maximum stereo blend weight; reduce below 1.0 to soften the cross-eye contribution +static const float kEdgeDepthThreshold = 0.05; // NDC depth difference above which a pixel is considered a depth discontinuity and excluded from stereo sync + +float MaxDepthDiff(float center, float4 neighbors) +{ + return max(max(abs(center - neighbors.x), abs(center - neighbors.y)), + max(abs(center - neighbors.z), abs(center - neighbors.w))); +} // Depth-weighted 4-sample blur using a rotated Poisson disk. // Uses dtid hash for per-pixel rotation to break structured patterns. @@ -103,9 +109,7 @@ float BlurShadow(int2 dtid, float centerDepth) SrcDepthTexture[dtid + int2(-1, 0)], SrcDepthTexture[dtid + int2(0, 1)], SrcDepthTexture[dtid + int2(0, -1)]); - float maxEdgeDiff = max(max(abs(depth - edgeDepths.x), abs(depth - edgeDepths.y)), - max(abs(depth - edgeDepths.z), abs(depth - edgeDepths.w))); - if (maxEdgeDiff > kEdgeDepthThreshold) { + if (MaxDepthDiff(depth, edgeDepths) > kEdgeDepthThreshold) { OutShadowTexture[dtid] = SrcShadowTexture[dtid]; return; } @@ -136,17 +140,14 @@ float BlurShadow(int2 dtid, float centerDepth) // reprojection can cross a boundary invisible from this eye's perspective. // Reusing the same four neighbor reads covers both purposes at no extra cost. static const int kEdgeMargin = 2; - static const int2 kNeighborOffsets[4] = { - int2(-kEdgeMargin, 0), int2(kEdgeMargin, 0), - int2(0, -kEdgeMargin), int2(0, kEdgeMargin) - }; - [unroll] for (int n = 0; n < 4; n++) - { - float neighborDepth = SrcDepthTexture[r.otherPx + kNeighborOffsets[n]]; - if (neighborDepth < 1e-5 || abs(otherDepth - neighborDepth) > kEdgeDepthThreshold) { - OutShadowTexture[dtid] = myShadow; - return; - } + float4 otherNeighbors = float4( + SrcDepthTexture[r.otherPx + int2(-kEdgeMargin, 0)], + SrcDepthTexture[r.otherPx + int2(kEdgeMargin, 0)], + SrcDepthTexture[r.otherPx + int2(0, -kEdgeMargin)], + SrcDepthTexture[r.otherPx + int2(0, kEdgeMargin)]); + if (any(otherNeighbors < 1e-5) || MaxDepthDiff(otherDepth, otherNeighbors) > kEdgeDepthThreshold) { + OutShadowTexture[dtid] = myShadow; + return; } // Source + destination edge detection diff --git a/src/Features/ScreenSpaceShadows.cpp b/src/Features/ScreenSpaceShadows.cpp index de28a4a052..2d6893482b 100644 --- a/src/Features/ScreenSpaceShadows.cpp +++ b/src/Features/ScreenSpaceShadows.cpp @@ -350,7 +350,6 @@ void ScreenSpaceShadows::SetupResources() if (globals::game::isVR) { stereoSyncCB = new ConstantBuffer(ConstantBufferDesc()); - stereoSyncCS = reinterpret_cast(Util::CompileShader(L"Data\\Shaders\\ScreenSpaceShadows\\StereoSyncCS.hlsl", { { "VR", "" }, { "FRAMEBUFFER", "" } }, "cs_5_0")); } {