Skip to content

Commit

Permalink
feat(VR): enable volumetric lighting (#565)
Browse files Browse the repository at this point in the history
* feat(VR): enable volumetric lighting

* fix: fix stereo access to VLTex

VLTex is still mono and causes artifacts, but this is a correct step
once it is stereo.
#565 (comment)

* fix: fix stereouv access

* fix: fix WindInput

* feat: add initial RE for ISFullScreenVR

* fix: fix previous depth calculation in dynres

The depth stencil from ISDepthBufferCopy is actually full size and not
scaled with dynres enabled.

* feat: add dynres status to state

* fix: fix dynres for volumetric lighting

* chore: fix typo

* fix: require vr address library 0.154.0

* fix: fix crash of shizof mods at dataloaded

* revert: "feat: add dynres status to state"

This reverts commit c3658ad.

* fix: use Util::IsDynamicResolution

* fix: fix improper depth size when downsampled

* fix: fix bad rendering with bDepthBufferCulling

With bDepthBufferCulling enabled, culling is incorrect if diskcache is
on until ShaderCache is cleared. This is a stopgap until root cause is
identified.

* fix(VR): fix VR desync

closes #601

* revert: "fix(VR): fix VR desync"

This reverts commit 958bc9a.

* style: 🎨 apply clang-format changes

* style: fix doxygen spacing

* refactor: use new namespaces

* fix: fix VR namespace define conflict

* chore: remove unused vr code

* refactor: fix flat compile errors
  • Loading branch information
alandtse authored Oct 10, 2024
1 parent 8b0b2d8 commit 2fb12d0
Show file tree
Hide file tree
Showing 11 changed files with 371 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[Info]
Version = 1-0-0
62 changes: 41 additions & 21 deletions package/Shaders/Common/VR.hlsli
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,24 @@ namespace Stereo
return 0;
}

/**
* @brief Converts UV coordinates from the range [0, 1] to normalized screen space [-1, 1].
*
* This function takes texture coordinates and transforms them into a normalized
* coordinate system centered at the origin. This is useful for various graphical
* calculations, especially in shaders that require symmetry around the center.
*
* @param uv The input UV coordinates in the range [0, 1].
* @return float2 Normalized screen space coordinates in the range [-1, 1].
*/
float2 ConvertUVToNormalizedScreenSpace(float2 uv)
{
float2 normalizedCoord;
normalizedCoord.x = 2.0 * (-0.5 + abs(2.0 * (uv.x - 0.5))); // Convert UV.x
normalizedCoord.y = 2.0 * uv.y - 1.0; // Convert UV.y
return normalizedCoord;
}

#ifdef PSHADER
/**
Gets the eyeIndex for PSHADER
Expand All @@ -147,6 +165,25 @@ namespace Stereo
return eyeIndex;
}

/**
* @brief Checks if the color is non zero by testing if the color is greater than 0 by epsilon.
*
* This function check is a color is non black. It uses a small epsilon value to allow for
* floating point imprecision.
*
* For screen-space reflection (SSR), this acts as a mask and checks for an invalid reflection by
* checking if the reflection color is essentially black (close to zero).
*
* @param[in] ssrColor The color to check.
* @param[in] epsilon Small tolerance value used to determine if the color is close to zero.
* @return True if color is non zero, otherwise false.
*/
bool IsNonZeroColor(float4 ssrColor, float epsilon = 0.001)
{
return dot(ssrColor.xyz, ssrColor.xyz) > epsilon * epsilon;
}

# ifdef VR
/**
* @brief Converts mono UV coordinates from one eye to the corresponding mono UV coordinates of the other eye.
*
Expand Down Expand Up @@ -210,7 +247,7 @@ namespace Stereo
{
fromOtherEye = false;
float3 resultUV = monoUV;
# ifdef VR
# ifdef VR
// Check if the UV coordinates are outside the frame
if (FrameBuffer::isOutsideFrame(resultUV.xy, false)) {
// Transition to the other eye
Expand All @@ -224,7 +261,7 @@ namespace Stereo
} else {
resultUV = ConvertToStereoUV(resultUV, eyeIndex);
}
# endif
# endif
return resultUV;
}

Expand Down Expand Up @@ -258,24 +295,6 @@ namespace Stereo
return stereoUV;
}

/**
* @brief Checks if the color is non zero by testing if the color is greater than 0 by epsilon.
*
* This function check is a color is non black. It uses a small epsilon value to allow for
* floating point imprecision.
*
* For screen-space reflection (SSR), this acts as a mask and checks for an invalid reflection by
* checking if the reflection color is essentially black (close to zero).
*
* @param[in] ssrColor The color to check.
* @param[in] epsilon Small tolerance value used to determine if the color is close to zero.
* @return True if color is non zero, otherwise false.
*/
bool IsNonZeroColor(float4 ssrColor, float epsilon = 0.001)
{
return dot(ssrColor.xyz, ssrColor.xyz) > epsilon * epsilon;
}

/**
* @brief Blends color data from two eyes based on their UV coordinates and validity.
*
Expand Down Expand Up @@ -322,7 +341,8 @@ namespace Stereo
{
return BlendEyeColors(float3(uv1, 0), color1, float3(uv2, 0), color2, dynamicres);
}
#endif // PSHADER
# endif // VR
#endif // PSHADER

#ifdef VSHADER
struct VR_OUTPUT
Expand Down
28 changes: 23 additions & 5 deletions package/Shaders/ISApplyVolumetricLighting.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ PS_OUTPUT main(PS_INPUT input)

float2 screenPosition = FrameBuffer::GetDynamicResolutionAdjustedScreenPosition(input.TexCoord);
float depth = DepthTex.Sample(DepthSampler, screenPosition).x;

# ifdef VR
if (depth < 0.0001) { // not a valid location
psout.VL = 0.0;
return psout;
}
# endif
float repartition = clamp(RepartitionTex.SampleLevel(RepartitionSampler, depth, 0).x, 0, 0.9999);
float vl = g_IntensityX_TemporalY.x * VLTex.SampleLevel(VLSampler, float3(input.TexCoord, repartition), 0).x;

Expand All @@ -48,14 +55,25 @@ PS_OUTPUT main(PS_INPUT input)
float2 previousTexCoord = input.TexCoord + motionVector;
float2 previousScreenPosition = FrameBuffer::GetPreviousDynamicResolutionAdjustedScreenPosition(previousTexCoord);
float previousVl = PreviousFrameTex.Sample(PreviousFrameSampler, previousScreenPosition).x;
float previousDepth = PreviousDepthTex.Sample(PreviousDepthSampler, previousScreenPosition).x;
float previousDepth = PreviousDepthTex.Sample(PreviousDepthSampler,
# ifndef VR
previousScreenPosition
# else
// In VR with dynamic resolution enabled, there's a bug with the depth stencil.
// The depth stencil from ISDepthBufferCopy is actually full size and not scaled.
// Thus there's never a need to scale it down.
previousTexCoord
# endif
)
.x;

float temporalContribution = g_IntensityX_TemporalY.y * (1 - smoothstep(0, 1, min(1, 100 * abs(depth - previousDepth))));

float isValid = 0;
if (previousTexCoord.x >= 0 && previousTexCoord.x < 1 && previousTexCoord.y >= 0 && previousTexCoord.y < 1) {
isValid = 1;
}
float isValid = (previousTexCoord.x >= 0 && previousTexCoord.x < 1 && previousTexCoord.y >= 0 && previousTexCoord.y < 1
# ifdef VR
&& abs(previousDepth) > 0.0001
# endif
);
psout.VL = lerp(adjustedVl, previousVl, temporalContribution * isValid);
} else {
psout.VL = adjustedVl;
Expand Down
2 changes: 1 addition & 1 deletion package/Shaders/ISCopy.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ PS_OUTPUT main(PS_INPUT input)
{
PS_OUTPUT psout;

# if !defined(DISABLE_DYNAMIC)
# if !defined(DISABLE_DYNAMIC) || (defined(DEPTHBUFFER_COPY) && defined(DEPTHBUFFER_4X_DOWNSAMPLE))
float2 screenPosition = FrameBuffer::GetDynamicResolutionAdjustedScreenPosition(input.TexCoord);
# else
float2 screenPosition = input.TexCoord;
Expand Down
69 changes: 69 additions & 0 deletions package/Shaders/ISFullScreenVR.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include "Common/DummyVSTexCoord.hlsl"
#include "Common/VR.hlsli"

typedef VS_OUTPUT PS_INPUT;

struct PS_OUTPUT
{
float4 Color : SV_Target0; // Final color output for the pixel shader.
};

#if defined(PSHADER)
SamplerState ImageSampler : register(s0); // Sampler state for texture sampling.
Texture2D<float4> ImageTex : register(t0); // Texture to sample colors from.

cbuffer PerGeometry : register(b2)
{
float4 FullScreenColor; // Color applied to the final output, used for tinting or blending effects.
float4 Params0; // General parameters; may include scaling or offset values.
float4 Params1; // Length parameters for scaling or thresholding; Params1.z represents `g_flTime`.
float4 UpsampleParams; // Dynamic resolution parameters:
// - UpsampleParams.x: fDynamicResolutionWidthRatio
// - UpsampleParams.y: fDynamicResolutionHeightRatio
// - UpsampleParams.z: fDynamicResolutionPreviousWidthRatio
// - UpsampleParams.w: fDynamicResolutionPreviousHeightRatio
};

// Function to generate noise using Valve's ScreenSpaceDither method.
// References:
// - https://blog.frost.kiwi/GLSL-noise-and-radial-gradient/
// - https://media.steampowered.com/apps/valve/2015/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf
float3 ScreenSpaceDither(float2 vScreenPos)
{
// Iestyn's RGB dither (7 asm instructions) from Portal 2 X360,
// slightly modified for VR applications.
float3 vDither = dot(float2(171.0, 231.0), vScreenPos.xy + Params1.zz).xxx;
vDither.rgb = frac(vDither.rgb / float3(103.0, 71.0, 97.0)) - float3(0.5, 0.5, 0.5);
return (vDither.rgb / 255.0) * 0.375; // Normalize dither values to a suitable range.
}

PS_OUTPUT main(PS_INPUT input)
{
PS_OUTPUT psout;

float2 uv = input.TexCoord; // Get the UV coordinates from input.

// Convert UV to normalized screen space [-1, 1].
float2 normalizedScreenCoord = Stereo::ConvertUVToNormalizedScreenSpace(uv);

// Calculate the length of the normalized screen coordinates.
float normalizedLength = saturate(Params1.x * (length(normalizedScreenCoord) - Params1.y) * Params0.x);

// Upsample and clamp texture coordinates based on dynamic resolution.
float2 uvScaled = min(UpsampleParams.zw, UpsampleParams.xy * uv.xy); // Clamp UVs to prevent overflow.
float3 sampledColor = ImageTex.Sample(ImageSampler, uvScaled).xyz; // Sample color from the texture.

// Manipulate the sampled color based on the normalized length.
float3 finalColor = sampledColor * (1.0 + normalizedLength); // Scale sampled color.

// Generate noise to apply to the final color.
float3 noise = ScreenSpaceDither(input.Position.xy);
finalColor += Params0.yyy * noise * Params1.www; // Adjust final color with noise.

// Final color manipulation: blend final color with FullScreenColor.
psout.Color.xyz = lerp(finalColor, FullScreenColor.xyz, FullScreenColor.www); // Blend based on the alpha component.
psout.Color.w = 1.0; // Set alpha to full opacity.

return psout; // Return the pixel shader output.
}
#endif
47 changes: 35 additions & 12 deletions package/Shaders/ISVolumetricLightingGenerateCS.hlsl
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include "Common/Constants.hlsli"
#include "Common/Random.hlsli"
#include "Common/VR.hlsli"

#if defined(CSHADER)
SamplerState ShadowmapSampler : register(s0);
Expand All @@ -16,21 +18,39 @@ RWTexture3D<float4> DensityCopyRW : register(u1);

cbuffer PerTechnique : register(b0)
{
float4x4 CameraViewProj : packoffset(c0);
float4x4 CameraViewProjInverse : packoffset(c4);
float4x3 ShadowMapProj[3] : packoffset(c8);
# ifndef VR
float4x4 CameraViewProj[1] : packoffset(c0);
float4x4 CameraViewProjInverse[1] : packoffset(c4);
float4x3 ShadowMapProj[1][3] : packoffset(c8);
float3 EndSplitDistances : packoffset(c17.x);
float ShadowMapCount : packoffset(c17.w);
float EnableShadowCasting : packoffset(c18);
float3 DirLightDirection : packoffset(c19);
float3 TextureDimensions : packoffset(c20);
float3 WindInput : packoffset(c21);
float3 WindInput[1] : packoffset(c21);
float InverseDensityScale : packoffset(c21.w);
float3 PosAdjust : packoffset(c22);
float3 PosAdjust[1] : packoffset(c22);
float IterationIndex : packoffset(c22.w);
float PhaseContribution : packoffset(c23.x);
float PhaseScattering : packoffset(c23.y);
float DensityContribution : packoffset(c23.z);
# else
float4x4 CameraViewProj[2] : packoffset(c0);
float4x4 CameraViewProjInverse[2] : packoffset(c8);
float4x3 ShadowMapProj[2][3] : packoffset(c16);
float3 EndSplitDistances : packoffset(c34.x);
float ShadowMapCount : packoffset(c34.w);
float EnableShadowCasting : packoffset(c35.x);
float3 DirLightDirection : packoffset(c36);
float3 TextureDimensions : packoffset(c37);
float3 WindInput[2] : packoffset(c38);
float InverseDensityScale : packoffset(c39.w);
float3 PosAdjust[2] : packoffset(c40);
float IterationIndex : packoffset(c41.w);
float PhaseContribution : packoffset(c42.x);
float PhaseScattering : packoffset(c42.y);
float DensityContribution : packoffset(c42.z);
# endif
}

[numthreads(32, 32, 1)] void main(uint3 dispatchID
Expand All @@ -44,29 +64,32 @@ cbuffer PerTechnique : register(b0)
{ 1.000000, 1.000000, 0 },
{ 1.000000, 1.000000, 1.000000 } };

float3 depthUv = dispatchID.xyz / TextureDimensions.xyz + 0.001 * StepCoefficients[IterationIndex].xyz;
float3 normalizedCoordinates = dispatchID.xyz / TextureDimensions.xyz;
float2 uv = normalizedCoordinates.xy;
uint eyeIndex = Stereo::GetEyeIndexFromTexCoord(uv);
float3 depthUv = Stereo::ConvertFromStereoUV(normalizedCoordinates, eyeIndex) + 0.001 * StepCoefficients[IterationIndex].xyz;
float depth = InverseRepartitionTex.SampleLevel(InverseRepartitionSampler, depthUv.z, 0).x;
float4 positionCS = float4(2 * depthUv.x - 1, 1 - 2 * depthUv.y, depth, 1);

float4 positionWS = mul(transpose(CameraViewProjInverse), positionCS);
float4 positionWS = mul(transpose(CameraViewProjInverse[eyeIndex]), positionCS);
positionWS /= positionWS.w;

float4 positionCSShifted = mul(transpose(CameraViewProj), positionWS);
float4 positionCSShifted = mul(transpose(CameraViewProj[eyeIndex]), positionWS);
positionCSShifted /= positionCSShifted.w;

float shadowMapDepth = positionCSShifted.z;

float shadowContribution = 1;
if (EndSplitDistances.z >= shadowMapDepth) {
float4x3 lightProjectionMatrix = ShadowMapProj[0];
float4x3 lightProjectionMatrix = ShadowMapProj[eyeIndex][0];
float shadowMapThreshold = 0.01;
float cascadeIndex = 0;
if (2.5 < ShadowMapCount && EndSplitDistances.y < shadowMapDepth) {
lightProjectionMatrix = ShadowMapProj[2];
lightProjectionMatrix = ShadowMapProj[eyeIndex][2];
shadowMapThreshold = 0;
cascadeIndex = 2;
} else if (EndSplitDistances.x < shadowMapDepth) {
lightProjectionMatrix = ShadowMapProj[1];
lightProjectionMatrix = ShadowMapProj[eyeIndex][1];
shadowMapThreshold = 0;
cascadeIndex = 1;
}
Expand Down Expand Up @@ -95,7 +118,7 @@ cbuffer PerTechnique : register(b0)
}
}

float3 noiseUv = 0.0125 * (InverseDensityScale * (positionWS.xyz + WindInput));
float3 noiseUv = 0.0125 * (InverseDensityScale * (positionWS.xyz + WindInput[eyeIndex]));
float noise = NoiseTex.SampleLevel(NoiseSampler, noiseUv, 0).x;
float densityFactor = noise * (1 - 0.75 * smoothstep(0, 1, saturate(2 * positionWS.z / 300)));
float densityContribution = lerp(1, densityFactor, DensityContribution);
Expand Down
4 changes: 3 additions & 1 deletion src/Feature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "Features/SubsurfaceScattering.h"
#include "Features/TerrainBlending.h"
#include "Features/TerrainShadows.h"
#include "Features/VolumetricLighting.h"
#include "Features/WaterLighting.h"
#include "Features/WetnessEffects.h"

Expand Down Expand Up @@ -124,7 +125,8 @@ const std::vector<Feature*>& Feature::GetFeatureList()
TerrainShadows::GetSingleton(),
ScreenSpaceGI::GetSingleton(),
Skylighting::GetSingleton(),
TerrainBlending::GetSingleton()
TerrainBlending::GetSingleton(),
VolumetricLighting::GetSingleton()
};

static std::vector<Feature*> featuresVR(features);
Expand Down
Loading

0 comments on commit 2fb12d0

Please sign in to comment.