Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ namespace ExtendedMaterials
float2 GetParallaxCoords(float distance, float2 coords, float mipLevel, float3 viewDir, float3x3 tbn, float noise, Texture2D<float4> tex, SamplerState texSampler, uint channel, DisplacementParams params, out float pixelOffset)
#endif
{
pixelOffset = 0.5;
float3 viewDirTS = normalize(mul(tbn, viewDir));
#if defined(LANDSCAPE)
viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params[0].FlattenAmount; // Fix for objects at extreme viewing angles
Expand Down Expand Up @@ -496,7 +497,7 @@ namespace ExtendedMaterials
#endif
nearBlendToFar *= nearBlendToFar;
float offset = (1.0 - parallaxAmount) * -maxHeight + minHeight;
pixelOffset = lerp(parallaxAmount * scale, 0, nearBlendToFar);
pixelOffset = saturate(lerp(parallaxAmount, 0.5, nearBlendToFar));
return lerp(viewDirTS.xy * offset + coords.xy, coords, nearBlendToFar);
}

Expand All @@ -509,7 +510,7 @@ namespace ExtendedMaterials
weights[5] = input.LandBlendWeights2.y;
#endif

pixelOffset = 0;
pixelOffset = 0.5;
return coords;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ cbuffer PerFrame : register(b1)

parameters.DynamicRes = DynamicRes;

parameters.UsePrecisionOffset = true;
// VR note: precision offset adds a depth bias that can cause subtle shadow
// shifting. Disabled to match the old (stable) SSS implementation.
// See: docs/development/Old code/RaymarchCS.hlsl
parameters.UsePrecisionOffset = false;

WriteScreenSpaceShadow(parameters, groupID, groupThreadID);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,82 @@
// Screen Space Shadows consumption helper.
// Non-VR: depth-weighted 4-sample Poisson blur for spatial denoising.
// VR: direct Load — the Poisson blur's per-pixel noise rotation is
// screen-position-dependent, causing shadows to shift on camera movement.
// Without TAA to average out the rotation noise, the instability hits
// the final output directly. Matches the stable v1.2 VR implementation.

#include "Common/Math.hlsli"

namespace ScreenSpaceShadows
{
Texture2D<unorm half> ScreenSpaceShadowsTexture : register(t45);

float4 GetBlurWeights(float4 depths, float centerDepth)
{
centerDepth += 1.0;
float depthSharpness = saturate((1024.0 * 1024.0) / (centerDepth * centerDepth));
float4 depthDifference = (depths - centerDepth) * depthSharpness;
return exp2(-depthDifference * depthDifference);
}

float GetScreenSpaceShadow(float3 screenPosition, float2 uv, float noise, uint eyeIndex)
{
return ScreenSpaceShadowsTexture.Load(int3(int2(screenPosition.xy + 0.5f), 0)).x;
#if defined(VR)
// VR: direct sample, no spatial blur. The Poisson blur's per-pixel noise
// rotation is screen-position-dependent — camera movement changes the
// rotation angle for the same world surface, causing shadows to visually
// shift. Without TAA to average out the rotation noise, the per-frame
// instability hits the final output directly. Direct Load avoids this.
// Matches the stable v1.2 VR implementation.
return ScreenSpaceShadowsTexture.Load(int3(screenPosition.xy, 0));
#else
// Flat: depth-weighted 4-sample Poisson blur for spatial denoising.
// Rotated per-pixel by screen-space noise to break structured patterns.
// TAA averages out the rotation noise across frames.
noise *= Math::TAU;

float2x2 rotationMatrix = float2x2(cos(noise), sin(noise), -sin(noise), cos(noise));

float4 shadowSamples = 0;
float4 depthSamples = 0;

# if defined(DEFERRED) && !defined(DO_ALPHA_TEST)
depthSamples[0] = screenPosition.z;
# else
depthSamples[0] = SharedData::DepthTexture.Load(int3(screenPosition.xy, 0)).x;
# endif

shadowSamples[0] = ScreenSpaceShadowsTexture.Load(int3(screenPosition.xy, 0));

static const float2 BlurOffsets[3] = {
float2(-0.6720635096678028f, 0.6601738628451107f),
float2(0.6110340335380645f, 0.5269905984201742f),
float2(0.20239029763403027f, -0.7841160574831084f),
};

[unroll] for (uint i = 1; i < 4; i++)
{
float2 offset = mul(BlurOffsets[i - 1], rotationMatrix) * 0.0025;

float2 sampleUV = uv + offset;
sampleUV = saturate(sampleUV);

int3 sampleCoord = SharedData::ConvertUVToSampleCoord(sampleUV, eyeIndex);

depthSamples[i] = SharedData::DepthTexture.Load(sampleCoord).x;
shadowSamples[i] = ScreenSpaceShadowsTexture.Load(sampleCoord);
}

depthSamples = SharedData::GetScreenDepths(depthSamples);

float4 blurWeights = GetBlurWeights(depthSamples, depthSamples[0]);
float shadow = dot(shadowSamples, blurWeights);

float blurWeightsTotal = dot(blurWeights, 1.0);
[flatten] if (blurWeightsTotal > 0.0)
shadow = shadow / blurWeightsTotal;

return shadow;
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
Texture2D<float> SrcDepthTexture : register(t0);
Texture2D<unorm half> SrcShadowTexture : register(t1);

# if defined(VR_STEREO_OPT)
Texture2D<uint> StereoOptModeTexture : register(t16);
# endif

RWTexture2D<unorm half> OutShadowTexture : register(u0);

cbuffer StereoSyncCB : register(b1)
Expand Down Expand Up @@ -90,6 +94,18 @@ float4 SampleCrossDepths(int2 center, int offset, uint eyeIndex)

uint eyeIndex = Stereo::GetEyeIndexFromTexCoord(uv);

# if defined(VR_STEREO_OPT)
// Eye 1 pixels with mode 1 (edge) or 2 (main) will be overwritten by StereoBlend
// reprojection, so skip the expensive stereo sync work and write neutral (unshadowed).
if (eyeIndex == 1) {
uint mode = StereoOptModeTexture[uint2(dtid.xy)] & 0x0F;
if (mode == 1 || mode == 2) {
OutShadowTexture[dtid] = 1.0; // 1.0 = no shadow (neutral)
return;
}
}
# endif

float depth = SrcDepthTexture[dtid];

// depth == 0: VR HMD mask; depth == 1: sky/far plane
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,17 +225,15 @@ void WriteScreenSpaceShadow(DispatchParameters inParameters, int3 inGroupID, int
// 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);

read_xy *= inParameters.DynamicRes;

#if defined(VR)
read_xy *= half2(0.5, 1.0);
#endif
// VR fix: do NOT pre-scale read_xy here. DynamicRes and VR 0.5x must be
// applied AFTER offset_xy addition so the bilinear neighbor is exactly
// 1 texel away. Pre-scaling causes the offset to sample ~3px away,
// breaking edge detection and causing shadow instability on camera movement.
// See: docs/development/Old code/bend_sss_gpu.hlsli for the correct ordering.

half minor_axis = x_axis_major ? pixel_xy.y : pixel_xy.x;

// If a pixel has been detected as an edge, then optionally (inParameters.IgnoreEdgePixels) don't include it in the shadow
const half edge_skip = 1e20; // if edge skipping is enabled, apply an extreme value/blend on edge samples to push the value out of range
const half edge_skip = 1e20;

half2 depths;
half bilinear = frac(minor_axis) - 0.5;
Expand All @@ -247,34 +245,47 @@ void WriteScreenSpaceShadow(DispatchParameters inParameters, int3 inGroupID, int
half bias = bilinear > 0 ? 1 : -1;
half2 offset_xy = half2(x_axis_major ? 0 : bias, x_axis_major ? bias : 0);

// HLSL enforces that a pixel offset is a compile-time constant, which isn't strictly required (and can sometimes be a bit faster)
// So this fallback will use a manual uv offset instead
half2 coord = read_xy * inParameters.InvDepthTextureSize;
half2 coord_with_offset = (read_xy + offset_xy) * inParameters.InvDepthTextureSize;
// VR fix: scale by DynamicRes AFTER offset_xy is incorporated, so the
// offset represents exactly 1 texel in the final UV space.
half2 coord = read_xy * inParameters.InvDepthTextureSize * inParameters.DynamicRes;
half2 coord_with_offset = (read_xy + offset_xy) * inParameters.InvDepthTextureSize * inParameters.DynamicRes;

#if defined(VR)
// VR side-by-side: halve x to map stereo pixel coords to texture UV
coord *= half2(0.5, 1.0);
coord_with_offset *= half2(0.5, 1.0);

# if defined(RIGHT)
// Right eye: valid UV range is [0.5, 1.0]
// Right eye: valid UV range is [0.5*DynRes.x, DynRes.x]
bool coord_out_of_eye = coord.x < 0.5 * inParameters.DynamicRes.x;
bool coord_offset_out_of_eye = coord_with_offset.x < 0.5 * inParameters.DynamicRes.x;
# else
// Left eye: valid UV range is [0.0, 0.5)
// Left eye: valid UV range is [0.0, 0.5*DynRes.x)
bool coord_out_of_eye = coord.x >= 0.5 * inParameters.DynamicRes.x;
bool coord_offset_out_of_eye = coord_with_offset.x >= 0.5 * inParameters.DynamicRes.x;
# endif

// Clamp cross-eye depth reads to FarDepthValue (1.0) so rays near the SBS
// center seam don't sample the other eye's depth. At distance, stereo parallax
// makes cross-eye depth noticeably different, causing shadow patterns to shift
// with camera movement. Clamping to 1.0 means the ray sees “no occluder” at
// the boundary — shadow weakens by ~1 pixel but stays temporally stable.
// The WRITE guard is intentionally removed (see below GroupMemoryBarrier section)
// so both dispatches write to the seam overlap, preventing a visible gap/line.
depths.x = coord_out_of_eye ? 1.0 : inParameters.DepthTexture.SampleLevel(inParameters.PointBorderSampler, coord, 0);
depths.y = coord_offset_out_of_eye ? 1.0 : inParameters.DepthTexture.SampleLevel(inParameters.PointBorderSampler, coord_with_offset, 0);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

depths.x = lerp(depths.x, 1.0, (float)(depths.x == 0)); // Stencil area
depths.y = lerp(depths.y, 1.0, (float)(depths.y == 0)); // Stencil area
// VR HMD mask: depth==0 is outside the visible lens area. Remap to
// FarDepthValue (1.0) so mask pixels don't cast false shadows.
depths.x = lerp(depths.x, 1.0, (float)(depths.x == 0));
depths.y = lerp(depths.y, 1.0, (float)(depths.y == 0));
#else
depths.x = inParameters.DepthTexture.SampleLevel(inParameters.PointBorderSampler, coord, 0);
depths.y = inParameters.DepthTexture.SampleLevel(inParameters.PointBorderSampler, coord_with_offset, 0);
#endif

// Depth thresholds (bilinear/shadow thickness) are based on a fractional ratio of the difference between sampled depth and the far clip depth
depth_thickness_scale[i] = abs(inParameters.FarDepthValue - depths.x);
depth_thickness_scale[i] = max(abs(inParameters.FarDepthValue - depths.x), 1e-4);

// If depth variance is more than a specific threshold, then just use point filtering
bool use_point_filter = abs(depths.x - depths.y) > depth_thickness_scale[i] * inParameters.BilinearThreshold;
Expand Down Expand Up @@ -321,19 +332,6 @@ void WriteScreenSpaceShadow(DispatchParameters inParameters, int3 inGroupID, int
// Sync wavefronts now groupshared DepthData is written
GroupMemoryBarrierWithGroupSync();

#if defined(VR)
// Check if the pixel we're writing to is on the correct eye side
half writeX = write_xy.x * inParameters.InvDepthTextureSize.x;

# if defined(RIGHT)
if (writeX < 0.0)
return;
# else
if (writeX > 1.0)
return;
# endif
#endif

half start_depth = sampling_depth[0];

if (start_depth == 0.0 || start_depth == 1.0)
Expand Down Expand Up @@ -381,5 +379,6 @@ void WriteScreenSpaceShadow(DispatchParameters inParameters, int3 inGroupID, int

// Asking the GPU to write scattered single-byte pixels isn't great,
// But thankfully the latency is hidden by all the work we're doing...

inParameters.OutputTexture[(int2)write_xy] = result;
}
4 changes: 2 additions & 2 deletions package/Shaders/Common/SharedData.hlsli
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace SharedData
bool InInterior; // If the area lacks a directional shadow light e.g. the sun or moon
bool InMapMenu; // If the world/local map is open (note that the renderer is still deferred here)
bool HideSky; // HideSky flag in WorldSpace, e.g. Blackreach
float MipBias; // Offset to mip level for TAA sharpness#
float MipBias; // Offset to mip level for TAA sharpness
float pad0;
float4 AmbientSHR;
float4 AmbientSHG;
Expand Down Expand Up @@ -52,7 +52,7 @@ namespace SharedData
bool EnableShadows;
bool ExtendShadows;
bool EnableParallaxWarpingFix;
float1 pad0;
bool pad0;
};

struct CubemapCreatorSettings
Expand Down
1 change: 1 addition & 0 deletions package/Shaders/Common/VR.hlsli
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ cbuffer VRValues : register(b13)
float2 EyeOffsetScale : packoffset(c0.z);
float4 EyeClipEdge[2] : packoffset(c1);
}

#endif

namespace Stereo
Expand Down
14 changes: 14 additions & 0 deletions package/Shaders/DeferredCompositeCS.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ RWTexture2D<float4> NormalTAAMaskSpecularMaskRW : register(u1);
RWTexture2D<float2> MotionVectorsRW : register(u2);
Texture2D<float> DepthTexture : register(t4);

#if defined(VR_STEREO_OPT)
Texture2D<uint> StereoOptModeTexture : register(t16);
#endif

#if defined(DYNAMIC_CUBEMAPS)
Texture2D<float3> ReflectanceTexture : register(t5);
TextureCube<float3> EnvTexture : register(t6);
Expand Down Expand Up @@ -92,6 +96,16 @@ void SampleSSGISpecular(uint2 pixCoord, sh2 lobe, inout float ao, out float3 il,
uv *= FrameBuffer::DynamicResolutionParams2.xy; // adjust for dynamic res

uint eyeIndex = Stereo::GetEyeIndexFromTexCoord(uv);

#if defined(VR_STEREO_OPT)
if (eyeIndex == 1) {
uint mode = StereoOptModeTexture[uint2(dispatchID.xy)] & 0x0F;
if (mode == 2 || mode == 1) { // MODE_MAIN or MODE_EDGE — stencil-culled, reprojected by StereoBlend
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
#endif

uv = Stereo::ConvertFromStereoUV(uv, eyeIndex);

float3 normalGlossiness = NormalRoughnessTexture[dispatchID.xy];
Expand Down
10 changes: 9 additions & 1 deletion package/Shaders/Lighting.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -3166,7 +3166,15 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace)
}
# endif

psout.Reflectance = float4(indirectLobeWeights.specular, psout.Diffuse.w);
# if defined(VR) && (defined(EMAT) || defined(TRUE_PBR)) && (defined(PARALLAX) || defined(LANDSCAPE) || defined(TRUE_PBR))
// VR: store POM parallax amount for stereo reprojection depth correction.
// Read by StereoBlendCS to adjust Eye 1 (right eye) reprojection depth
// at POM-displaced surfaces. Not consumed on flat (SE/AE).
psout.Reflectance = float4(indirectLobeWeights.specular,
(pixelOffset > 0.0) ? saturate(pixelOffset) : 0.0);
# else
psout.Reflectance = float4(indirectLobeWeights.specular, 0.0);
# endif
psout.NormalGlossiness = float4(GBuffer::EncodeNormal(screenSpaceNormal), saturate(1.0 - material.Roughness), psout.Diffuse.w);

# if defined(SNOW)
Expand Down
1 change: 0 additions & 1 deletion package/Shaders/RunGrass.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -850,7 +850,6 @@ PS_OUTPUT main(PS_INPUT input)

# if defined(RENDER_DEPTH)
float diffuseAlpha = input.VertexColor.w * baseColor.w;

if ((diffuseAlpha - AlphaTestRefRS) < 0) {
discard;
}
Expand Down
Loading
Loading