diff --git a/features/Stochastic Screen Space Reflections/Shaders/Features/StochasticScreenSpaceReflections.ini b/features/Stochastic Screen Space Reflections/Shaders/Features/StochasticScreenSpaceReflections.ini new file mode 100644 index 0000000000..19f01444dc --- /dev/null +++ b/features/Stochastic Screen Space Reflections/Shaders/Features/StochasticScreenSpaceReflections.ini @@ -0,0 +1,2 @@ +[Info] +Version = 1-0-0 \ No newline at end of file diff --git a/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/noise.dds b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/noise.dds new file mode 100644 index 0000000000..94cf667514 Binary files /dev/null and b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/noise.dds differ diff --git a/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_common.hlsli b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_common.hlsli new file mode 100644 index 0000000000..ba78b8d053 --- /dev/null +++ b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_common.hlsli @@ -0,0 +1,312 @@ +#include "Common/BRDF.hlsli" +#include "Common/Color.hlsli" +#include "Common/FrameBuffer.hlsli" +#include "Common/Game.hlsli" +#include "Common/GBuffer.hlsli" +#include "Common/SharedData.hlsli" +#include "Common/Math.hlsli" +#include "Common/Random.hlsli" + +#define Pow2(x) ((x) * (x)) +#define SSSR_FLOAT_MAX 3.402823466e+38 + +Texture2D NormalRoughnessTexture : register(t2); + +SamplerState LinearSampler : register(s0); + +// Brian Karis, Epic Games "Real Shading in Unreal Engine 4" +float4 ImportanceSampleGGX(float2 E, float a2) +{ + float Phi = 2 * Math::PI * E.x; + float CosTheta = sqrt( (1 - E.y) / ( 1 + (a2 - 1) * E.y ) ); + float SinTheta = sqrt( 1 - CosTheta * CosTheta ); + + float3 H; + H.x = SinTheta * cos( Phi ); + H.y = SinTheta * sin( Phi ); + H.z = CosTheta; + + float d = ( CosTheta * a2 - CosTheta ) * CosTheta + 1; + float D = a2 / ( Math::PI*d*d ); + float PDF = D * CosTheta; + + return float4( H, PDF ); +} + +float VisibleGGXPDF_aniso(float3 V, float3 H, float2 Alpha, bool bLimitVDNFToReflection = true) +{ + float NoV = V.z; + float NoH = H.z; + float VoH = dot(V, H); + float a2 = Alpha.x * Alpha.y; + float3 Hs = float3(Alpha.y * H.x, Alpha.x * H.y, a2 * NoH); + float S = dot(Hs, Hs); + float D = (1.0f / Math::PI) * a2 * Pow2(a2 / S); + float LenV = length(float3(V.x * Alpha.x, V.y * Alpha.y, NoV)); + float k = 1.0; + if (bLimitVDNFToReflection) + { + float a = saturate(min(Alpha.x, Alpha.y)); + float s = 1.0f + length(V.xy); + float ka2 = a * a, s2 = s * s; + k = (s2 - ka2 * s2) / (s2 + ka2 * V.z * V.z); // Eq. 5 + } + float Pdf = (2 * D * VoH) / (k * NoV + LenV); + return Pdf; +} + +// PDF = G_SmithV * VoH * D / NoV / (4 * VoH) +// PDF = G_SmithV * D / (4 * NoV) +float4 ImportanceSampleVisibleGGX(float2 E, float2 Alpha, float3 V, bool bLimitVDNFToReflection = true) +{ + // stretch + float3 Vh = normalize(float3(Alpha * V.xy, V.z)); + + // "Sampling Visible GGX Normals with Spherical Caps" + // Jonathan Dupuy & Anis Benyoub - High Performance Graphics 2023 + float Phi = (2 * Math::PI) * E.x; + float k = 1.0; + if (bLimitVDNFToReflection) + { + // If we know we will be reflecting the view vector around the sampled micronormal, we can + // tweak the range a bit more to eliminate some of the vectors that will point below the horizon + float a = saturate(min(Alpha.x, Alpha.y)); + float s = 1.0 + length(V.xy); + float a2 = a * a, s2 = s * s; + k = (s2 - a2 * s2) / (s2 + a2 * V.z * V.z); + } + float Z = lerp(1.0, -k * Vh.z, E.y); + float SinTheta = sqrt(saturate(1 - Z * Z)); + float X = SinTheta * cos(Phi); + float Y = SinTheta * sin(Phi); + float3 H = float3(X, Y, Z) + Vh; + + // unstretch + H = normalize(float3(Alpha * H.xy, max(0.0, H.z))); + + return float4(H, VisibleGGXPDF_aniso(V, H, Alpha)); +} + +float3 ConcentricDiskSamplingHelper(float2 E) +{ + // Rescale input from [0,1) to (-1,1). This ensures the output radius is in [0,1) + float2 p = 2 * E - 0.99999994; + float2 a = abs(p); + float Lo = min(a.x, a.y); + float Hi = max(a.x, a.y); + float Epsilon = 5.42101086243e-20; // 2^-64 (this avoids 0/0 without changing the rest of the mapping) + float Phi = (Math::PI / 4) * (Lo / (Hi + Epsilon) + 2 * float(a.y >= a.x)); + float Radius = Hi; + // copy sign bits from p + const uint SignMask = 0x80000000; + float2 Disk = asfloat((asuint(float2(cos(Phi), sin(Phi))) & ~SignMask) | (asuint(p) & SignMask)); + // return point on the circle as well as the radius + return float3(Disk, Radius); +} + +float4 CosineSampleHemisphere( float2 E ) +{ + float Phi = 2 * Math::PI * E.x; + float CosTheta = sqrt(E.y); + float SinTheta = sqrt(1 - CosTheta * CosTheta); + + float3 H; + H.x = SinTheta * cos(Phi); + H.y = SinTheta * sin(Phi); + H.z = CosTheta; + + float PDF = CosTheta * (1.0 / Math::PI); + + return float4(H, PDF); +} + +float4 CosineSampleHemisphereConcentric(float2 E) +{ + float3 Result = ConcentricDiskSamplingHelper(E); + float SinTheta = Result.z; + float CosTheta = sqrt(1 - SinTheta * SinTheta); + return float4(Result.xy * SinTheta, CosTheta, CosTheta * (1.0 / Math::PI)); +} + +void GetNormalRoughness(uint2 dtid, out float3 normal, out float roughness) +{ + float3 normalGlossiness = NormalRoughnessTexture[dtid]; + // Normal is in view space + normal = GBuffer::DecodeNormal(normalGlossiness.xy); + roughness = 1.0f - normalGlossiness.z; +} + +void GetNormalRoughness(Texture2D NormalRoughness, uint2 dtid, out float3 normal, out float roughness) +{ + float3 normalGlossiness = NormalRoughness[dtid].xyz; + // Normal is in view space + normal = GBuffer::DecodeNormal(normalGlossiness.xy); + roughness = 1.0f - normalGlossiness.z; +} + +void GetNormalRoughnessUV(float2 uv, out float3 normal, out float roughness) +{ + float3 normalGlossiness = NormalRoughnessTexture.SampleLevel(LinearSampler, uv, 0); + // Normal is in view space + normal = GBuffer::DecodeNormal(normalGlossiness.xy); + roughness = 1.0f - normalGlossiness.z; +} + +// [ Duff et al. 2017, "Building an Orthonormal Basis, Revisited" ] +float3x3 GetTangentBasis( float3 TangentZ ) +{ + const float Sign = TangentZ.z >= 0 ? 1 : -1; + const float a = -rcp( Sign + TangentZ.z ); + const float b = TangentZ.x * TangentZ.y * a; + + float3 TangentX = { 1 + Sign * a * Pow2( TangentZ.x ), Sign * b, -Sign * TangentZ.x }; + float3 TangentY = { b, Sign + a * Pow2( TangentZ.y ), -TangentZ.y }; + + return float3x3( TangentX, TangentY, TangentZ ); +} + +float2 Hammersley16( uint Index, uint NumSamples, uint2 Random ) +{ + float E1 = frac( (float)Index / NumSamples + float( Random.x ) * (1.0 / 65536.0) ); + float E2 = float( ( reversebits(Index) >> 16 ) ^ Random.y ) * (1.0 / 65536.0); + return float2( E1, E2 ); +} + +static const int2 kStackowiakSampleSet4[15] = { int2(0, 1), int2(-2, 1), int2(2, -3), int2(-3, 0), int2(1, 2), int2(-1, -2), int2(3, 0), int2(-3, 3), int2(0, -3), int2(-1, -1), int2(2, 1), int2(-2, -2), int2(1, 0), int2(0, 2), int2(3, -1) }; + +float2 GetMotionVector(float sceneDepth, float2 screenUV, float4x4 matrix_LastViewProj, float4x4 matrix_ViewProj) +{ + float4 positionWS = float4(2 * float2(screenUV.x, -screenUV.y + 1) - 1, sceneDepth, 1); + + float4 curClipPos = mul(matrix_ViewProj, positionWS); + float4 lastClipPos = mul(matrix_LastViewProj, positionWS); + + float2 CurNDC = curClipPos.xy / curClipPos.w; + float2 LastNDC = lastClipPos.xy / lastClipPos.w; + + float2 CurUV = CurNDC.xy * float2(0.5f, -0.5f) + 0.5; + float2 LastUV = LastNDC.xy * float2(0.5f, -0.5f) + 0.5; + + return CurUV - LastUV; +} + +uint3 Rand3DPCG16(int3 p) +{ + // taking a signed int then reinterpreting as unsigned gives good behavior for negatives + uint3 v = uint3(p); + + // Linear congruential step. These LCG constants are from Numerical Recipies + // For additional #'s, PCG would do multiple LCG steps and scramble each on output + // So v here is the RNG state + v = v * 1664525u + 1013904223u; + + // PCG uses xorshift for the final shuffle, but it is expensive (and cheap + // versions of xorshift have visible artifacts). Instead, use simple MAD Feistel steps + // + // Feistel ciphers divide the state into separate parts (usually by bits) + // then apply a series of permutation steps one part at a time. The permutations + // use a reversible operation (usually ^) to part being updated with the result of + // a permutation function on the other parts and the key. + // + // In this case, I'm using v.x, v.y and v.z as the parts, using + instead of ^ for + // the combination function, and just multiplying the other two parts (no key) for + // the permutation function. + // + // That gives a simple mad per round. + v.x += v.y*v.z; + v.y += v.z*v.x; + v.z += v.x*v.y; + v.x += v.y*v.z; + v.y += v.z*v.x; + v.z += v.x*v.y; + + // only top 16 bits are well shuffled + return v >> 16u; +} + +float3 SampleGGXVNDF(float3 Ve, float alpha_x, float alpha_y, float U1, float U2) { + // Input Ve: view direction + // Input alpha_x, alpha_y: roughness parameters + // Input U1, U2: uniform random numbers + // Output Ne: normal sampled with PDF D_Ve(Ne) = G1(Ve) * max(0, dot(Ve, Ne)) * D(Ne) / Ve.z + // + // + // Section 3.2: transforming the view direction to the hemisphere configuration + float3 Vh = normalize(float3(alpha_x * Ve.x, alpha_y * Ve.y, Ve.z)); + // Section 4.1: orthonormal basis (with special case if cross product is zero) + float lensq = Vh.x * Vh.x + Vh.y * Vh.y; + float3 T1 = lensq > 0 ? float3(-Vh.y, Vh.x, 0) * rsqrt(lensq) : float3(1, 0, 0); + float3 T2 = cross(Vh, T1); + // Section 4.2: parameterization of the projected area + float r = sqrt(U1); + const float M_PI = 3.14159265358979f; + float phi = 2.0 * M_PI * U2; + float t1 = r * cos(phi); + float t2 = r * sin(phi); + float s = 0.5 * (1.0 + Vh.z); + t2 = (1.0 - s) * sqrt(1.0 - t1 * t1) + s * t2; + // Section 4.3: reprojection onto hemisphere + float3 Nh = t1 * T1 + t2 * T2 + sqrt(max(0.0, 1.0 - t1 * t1 - t2 * t2)) * Vh; + // Section 3.4: transforming the normal back to the ellipsoid configuration + float3 Ne = normalize(float3(alpha_x * Nh.x, alpha_y * Nh.y, max(0.0, Nh.z))); + return Ne; +} + +void ReprojectHit(Texture2D MotionTexture, SamplerState s, float3 hitUVz, uint eyeIndex, out float2 outPrevUV) +{ + // Camera motion for pixel (in ScreenPos space). + float2 thisScreen = (hitUVz.xy - 0.5f) * float2(2.0f, -2.0f); + float4 thisClip = float4(thisScreen, hitUVz.z, 1); + float4 thisView = mul(FrameBuffer::CameraProjUnjitteredInverse[eyeIndex], thisClip); + thisView.xyz = thisView.xyz / thisView.w; + float4 thisWorld = mul(FrameBuffer::CameraViewInverse[eyeIndex], float4(thisView.xyz, 1.0f)); + thisWorld.xyz = thisWorld.xyz / thisWorld.w; + float4 prevClip = mul(FrameBuffer::CameraPreviousViewProjUnjittered[eyeIndex], float4(thisWorld.xyz, 1.0f)); + float2 prevScreen = prevClip.xy / prevClip.w; + + float2 velocity = MotionTexture.SampleLevel(s, hitUVz.xy * FrameBuffer::DynamicResolutionParams1.xy, 0).xy; + + prevScreen = thisClip.xy + velocity * float2(2.f, -2.f); + + float2 prevUV = prevScreen.xy * float2(0.5f, -0.5f) + 0.5f; + + outPrevUV = prevUV; +} + +float GetSpecularOcclusionFromAmbientOcclusion(float NdotV, float ao, float roughness) { + return saturate(pow(abs(NdotV + ao), exp2(-16.0 * roughness - 1.0)) - 1.0 + ao); +} + +// by Profjack +#define ISNAN(x) (!(x < 0.f || x > 0.f || x == 0.f)) +float filterNaN(float v) +{ + return ISNAN(v) ? 0 : v; +} +float2 filterNaN(float2 v) { return float2(filterNaN(v.x), filterNaN(v.y)); } +float3 filterNaN(float3 v) { return float3(filterNaN(v.x), filterNaN(v.y), filterNaN(v.z)); } +float4 filterNaN(float4 v) { return float4(filterNaN(v.x), filterNaN(v.y), filterNaN(v.z), filterNaN(v.w)); } + +float filterInf(float v) { return isinf(v) ? 0 : v; } +float2 filterInf(float2 v) { return float2(filterInf(v.x), filterInf(v.y)); } +float3 filterInf(float3 v) { return float3(filterInf(v.x), filterInf(v.y), filterInf(v.z)); } +float4 filterInf(float4 v) { return float4(filterInf(v.x), filterInf(v.y), filterInf(v.z), filterInf(v.w)); } + +float CalculateWeight(float depthCenter, float depthP, float phiD, float3 normalCenter, float3 normalP, float phiN, + float luminanceCenter, float luminanceP, float phiL) +{ + float epsilon = 0.0000001; + + // Depth weight + float difference = abs(depthCenter - depthP); + float weightDepth = (phiD == 0) ? 0.f : difference / max(phiD, epsilon); + + // Normal weight + float weightNormal = pow(max(0.f, dot(normalCenter, normalP)), phiN); + + // Luminance weight + float weightLuminance = abs(luminanceCenter - luminanceP) / phiL; + + float weight = exp(-weightDepth - weightLuminance) * weightNormal; + return weight; +} \ No newline at end of file diff --git a/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_depth_downsample.hlsl b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_depth_downsample.hlsl new file mode 100644 index 0000000000..5185d27518 --- /dev/null +++ b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_depth_downsample.hlsl @@ -0,0 +1,50 @@ +Texture2D depth : register(t0); +RWTexture2D outDepth : register(u0); + +[numthreads(8, 8, 1)] void main(uint3 DTid : SV_DispatchThreadID) +{ + uint depth_width, depth_height; + depth.GetDimensions(depth_width, depth_height); + float2 depth_dim = float2(depth_width, depth_height); + + uint out_depth_width, out_depth_height; + outDepth.GetDimensions(out_depth_width, out_depth_height); + float2 out_depth_dim = float2(out_depth_width, out_depth_height); + + float2 ratio = depth_dim / out_depth_dim; + + uint2 vReadCoord = DTid << 1; + uint2 vWriteCoord = DTid; + + if (vWriteCoord.x >= out_depth_width || vWriteCoord.y >= out_depth_height) + return; + + float4 depth_samples = float4( + depth[vReadCoord].x, + depth[vReadCoord + uint2(1, 0)].x, + depth[vReadCoord + uint2(0, 1)].x, + depth[vReadCoord + uint2(1, 1)].x + ); + + float min_depth = min(depth_samples.x, min(depth_samples.y, min(depth_samples.z, depth_samples.w))); + + bool needExtraSampleX = ratio.x > 2; + bool needExtraSampleY = ratio.y > 2; + + if (needExtraSampleX) + { + min_depth = min(min_depth, min(depth[vReadCoord + uint2(2, 0)].x, depth[vReadCoord + uint2(2, 1)].x)); + } + + if (needExtraSampleY) + { + min_depth = min(min_depth, min(depth[vReadCoord + uint2(0, 2)].x, depth[vReadCoord + uint2(1, 2)].x)); + } + + if (needExtraSampleX && needExtraSampleY) + { + min_depth = min(min_depth, depth[vReadCoord + uint2(2, 2)].x); + } + + outDepth[vWriteCoord] = min_depth; +} diff --git a/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_prepare_color.hlsl b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_prepare_color.hlsl new file mode 100644 index 0000000000..37c0f1cfac --- /dev/null +++ b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_prepare_color.hlsl @@ -0,0 +1,12 @@ +#include "StochasticScreenSpaceReflections/sssr_common.hlsli" + +Texture2D DiffuseTexture : register(t0); +Texture2D SpecularTexture : register(t1); + +RWTexture2D ColorCombinedOutput : register(u0); + +[numthreads(8, 8, 1)] void main(uint3 DTid : SV_DispatchThreadID) +{ + float4 color = float4(Color::IrradianceToGamma(Color::IrradianceToLinear(DiffuseTexture[DTid.xy].xyz) + SpecularTexture[DTid.xy].xyz), 1.0f); + ColorCombinedOutput[DTid.xy] = color; +} \ No newline at end of file diff --git a/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_preprocess_depth.hlsl b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_preprocess_depth.hlsl new file mode 100644 index 0000000000..4d208cb505 --- /dev/null +++ b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_preprocess_depth.hlsl @@ -0,0 +1,10 @@ +#include "StochasticScreenSpaceReflections/sssr_common.hlsli" + +RWTexture2D DepthOutput : register(u0); + +Texture2D DepthTexture : register(t4); + +[numthreads(8, 8, 1)] void main(uint3 DTid : SV_DispatchThreadID) +{ + DepthOutput[DTid.xy] = DepthTexture[DTid.xy]; +} \ No newline at end of file diff --git a/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_raymarch.hlsl b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_raymarch.hlsl new file mode 100644 index 0000000000..bfa42a6424 --- /dev/null +++ b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_raymarch.hlsl @@ -0,0 +1,593 @@ +// This file is rewritten from AMD's FidelityFX SDK. +// +// Copyright (C) 2024 Advanced Micro Devices, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "StochasticScreenSpaceReflections/sssr_common.hlsli" + +Texture2D HistoryTexture : register(t0); +Texture2D MotionVectorTexture : register(t1); +Texture2D ScreenColorTextureMips : register(t3); +Texture2D DepthTexture : register(t4); +Texture2D DepthTextureMips : register(t5); +Texture2DArray NoiseTexture : register(t6); +#if defined(DYNAMIC_CUBEMAPS) +TextureCube EnvTexture : register(t7); +TextureCube EnvReflectionsTexture : register(t8); +# if defined(SSGI) +Texture2D SsgiAoTexture : register(t9); +# endif +# if defined(SKYLIGHTING) +# include "Skylighting/Skylighting.hlsli" +Texture3D SkylightingProbeArray : register(t10); +Texture2DArray stbn_vec3_2Dx1D_128x128x64 : register(t11); +# endif +#endif + +RWTexture2D SSRColorOutput : register(u0); +RWTexture2D SSRPDFOutput : register(u1); + +RWStructuredBuffer u_SharcHashEntriesBuffer : register(u2); +RWStructuredBuffer u_HashCopyOffsetBuffer : register(u3); +RWStructuredBuffer u_SharcVoxelDataBuffer : register(u4); +RWStructuredBuffer u_SharcVoxelDataBufferPrev : register(u5); + +cbuffer SSSRCB : register(b1) +{ + uint MaxSteps; + uint MaxMips; + uint UseDynamicCubemapsAsFallback; + float Thickness; + float NormalBias; + float BRDFBias; + float OcclusionStrength; + float CubemapNormalization; +}; + +#define HIZ_MAX_ITERATIONS MaxSteps +#define HIZ_MIN_MIP 0 +#define SSSR_FLOAT_MAX 3.402823466e+38 +#define SSSR_DEPTH_HIERARCHY_MAX_MIP MaxMips +#define SAMPLES_PER_PIXEL 1 + +float3 ProjectPosition(float3 origin, float4x4 mat) +{ + float4 projected = mul(mat, float4(origin, 1)); + projected.xyz /= projected.w; + projected.xy = 0.5 * projected.xy + 0.5; + projected.y = (1 - projected.y); + return projected.xyz; +} + +// Origin and direction must be in the same space and mat must be able to transform from that space into clip space. +float3 ProjectDirection(float3 origin, float3 direction, float3 screen_space_origin, float4x4 mat) +{ + float3 offsetted = ProjectPosition(origin + direction, mat); + return offsetted - screen_space_origin; +} + +float3 InvProjectPosition(float3 coord, float4x4 mat) +{ + coord.y = (1 - coord.y); + coord.xy = 2 * coord.xy - 1; + float4 projected = mul(mat, float4(coord, 1)); + projected.xyz /= projected.w; + return projected.xyz; +} + +float2 SSSR_GetMipResolution(float2 screen_dimensions, int mip_level) +{ + return screen_dimensions * pow(0.5, mip_level); + // uint2 dimensions; + // uint levels; + // DepthTextureMips.GetDimensions(mip_level, dimensions.x, dimensions.y, levels); + // return float2(dimensions.x, dimensions.y); +} + +float SSSR_LoadDepth(int2 pixel_coordinate, int mip) +{ + return DepthTextureMips.Load(int3(pixel_coordinate, mip /* + pc.depth_mip_bias*/)).x; +} + +float3 SSSR_ScreenSpaceToViewSpace(float3 screen_space_position, uint eyeIndex) +{ + return InvProjectPosition(screen_space_position, FrameBuffer::CameraProjInverse[eyeIndex]); +} + +void SSSR_InitialAdvanceRay(float3 origin, + float3 direction, + float3 inv_direction, + float2 current_mip_resolution, + float2 current_mip_resolution_inv, + float2 floor_offset, + float2 uv_offset, + out float3 position, + out float current_t) +{ + float2 current_mip_position = current_mip_resolution * origin.xy; + + // Intersect ray with the half box that is pointing away from the ray origin. + float2 xy_plane = floor(current_mip_position) + floor_offset; + xy_plane = xy_plane * current_mip_resolution_inv + uv_offset; + + // o + d * t = p' => t = (p' - o) / d + float2 t = xy_plane * inv_direction.xy - origin.xy * inv_direction.xy; + current_t = min(t.x, t.y); + position = origin + current_t * direction; +} + +bool SSSR_AdvanceRay(float3 origin, + float3 direction, + float3 inv_direction, + float2 current_mip_position, + float2 current_mip_resolution_inv, + uint current_mip_level, + float2 floor_offset, + float2 uv_offset, + float surface_z, + float thickness, + inout float3 position, + inout float current_t) +{ + // Create boundary planes + float2 xy_plane = floor(current_mip_position) + floor_offset; + xy_plane = xy_plane * current_mip_resolution_inv + uv_offset; + float3 boundary_planes = float3(xy_plane, surface_z); + + // Intersect ray with the half box that is pointing away from the ray origin. + // o + d * t = p' => t = (p' - o) / d + float3 t = boundary_planes * inv_direction - origin * inv_direction; + + // Prevent using z plane when shooting out of the depth buffer. +#if SSSR_OPTION_INVERTED_DEPTH + t.z = direction.z < 0 ? t.z : SSSR_FLOAT_MAX; +#else + t.z = direction.z > 0 ? t.z : SSSR_FLOAT_MAX; +#endif + + // Choose nearest intersection with a boundary. + float t_min = min(min(t.x, t.y), t.z); + +#if SSSR_OPTION_INVERTED_DEPTH + // Larger z means closer to the camera. + bool above_surface = surface_z < position.z; +#else + // Smaller z means closer to the camera. + bool above_surface = surface_z > position.z; +#endif + + // Decide whether we are able to advance the ray until we hit the xy boundaries or if we had to clamp it at the surface. + // We use the asuint comparison to avoid NaN / Inf logic, also we actually care about bitwise equality here to see if t_min is the t.z we fed into the min3 above. + bool skipped_tile = asuint(t_min) != asuint(t.z) && above_surface; + + // Make sure to only advance the ray if we're still above the surface. + current_t = above_surface ? t_min : current_t; + + // Advance ray + position = origin + current_t * direction; + + return skipped_tile; +} + +// Requires origin and direction of the ray to be in screen space [0, 1] x [0, 1] +float3 SSSR_HierarchicalRaymarch(float3 origin, float3 direction, bool is_mirror, float2 screen_size, int most_detailed_mip, float roughness, float thickness, + uint max_traversal_intersections, out bool valid_hit, out uint _num_iters) { + const float3 inv_direction = abs(direction) > float(1.0e-12) ? float(1.0) / direction : SSSR_FLOAT_MAX; + + // Start on mip with highest detail. + int current_mip = most_detailed_mip; + + // Could recompute these every iteration, but it's faster to hoist them out and update them. + float2 current_mip_resolution = SSSR_GetMipResolution(screen_size, current_mip); + float2 current_mip_resolution_inv = rcp(current_mip_resolution); + + // Offset to the bounding boxes uv space to intersect the ray with the center of the next pixel. + // This means we ever so slightly over shoot into the next region. + float2 uv_offset = 0.005 * exp2(most_detailed_mip) / screen_size; + uv_offset = direction.xy < 0 ? -uv_offset : uv_offset; + + // Offset applied depending on current mip resolution to move the boundary to the left/right upper/lower border depending on ray direction. + float2 floor_offset = direction.xy < 0 ? 0 : 1; + + // valid_hit = false; + // if (direction.z < f32(1.0e-6)) return f32x3(0.0, 0.0, 0.0); + + // Initially advance ray to avoid immediate self intersections. + float current_t; + float3 position; + SSSR_InitialAdvanceRay(origin, direction, inv_direction, current_mip_resolution, current_mip_resolution_inv, floor_offset, uv_offset, position, current_t); + + _num_iters = uint(0); + while (_num_iters < max_traversal_intersections && current_mip >= most_detailed_mip) { + if (any(position.xy > float2(1.0, 1.0)) || any(position.xy < float2(0.0, 0.0))) break; +#ifdef SSSR_INVERTED_DEPTH_RANGE + if (position.z < f32(1.0e-6)) break; +#else + if (position.z > float(1.0) - float(1.0e-6)) break; +#endif + + float2 current_mip_position = current_mip_resolution * position.xy; + float surface_z = SSSR_LoadDepth(current_mip_position * FrameBuffer::DynamicResolutionParams1.xy, current_mip); + bool skipped_tile = + SSSR_AdvanceRay(origin, direction, inv_direction, current_mip_position, current_mip_resolution_inv, current_mip, floor_offset, uv_offset, surface_z, thickness, position, current_t); + bool nextMipIsOutOfRange = skipped_tile && (current_mip >= SSSR_DEPTH_HIERARCHY_MAX_MIP); + if (!nextMipIsOutOfRange) + { + current_mip += skipped_tile ? 1 : -1; + current_mip_resolution *= skipped_tile ? 0.5 : 2; + current_mip_resolution_inv *= skipped_tile ? 2 : 0.5; + } + ++_num_iters; + } + + valid_hit = (_num_iters <= max_traversal_intersections); + + return position; +} + +float SSSR_ValidateHit(float3 hit, float2 uv, float3 world_space_ray_direction, float2 screen_size, float depth_buffer_thickness, uint eyeIndex, out float occlusion) +{ + occlusion = 1.f; + + // Reject hits outside the view frustum + if ((hit.x < 0.0f) || (hit.y < 0.0f) || (hit.x > 1.0f) || (hit.y > 1.0f)) + { + return 0.0f; + } + + // Don't lookup radiance from the background. + int2 texel_coords = int2(screen_size * hit.xy * FrameBuffer::DynamicResolutionParams1.xy); + float surface_z = SSSR_LoadDepth(texel_coords / 2, 1); +#if SSSR_OPTION_INVERTED_DEPTH + if (surface_z == 0.0) + { +#else + if (surface_z == 1.0) + { +#endif + return 0; + } + + float3 view_space_surface = SSSR_ScreenSpaceToViewSpace(float3(hit.xy, surface_z), eyeIndex); + float3 view_space_hit = SSSR_ScreenSpaceToViewSpace(hit, eyeIndex); + float distance = length(view_space_surface - view_space_hit); + + // We accept all hits that are within a reasonable minimum distance below the surface. + // Add constant in linear space to avoid growing of the reflections toward the reflected objects. + float confidence = 1.0f - smoothstep(0.0f, depth_buffer_thickness, distance); + confidence *= confidence; + + // We check if we hit the surface from the back, these should be rejected. + float3 hit_normalVS; + float hit_roughness; + GetNormalRoughness(texel_coords, hit_normalVS, hit_roughness); + float3 hit_normal = normalize(mul(FrameBuffer::CameraViewInverse[eyeIndex], float4(hit_normalVS, 0)).xyz); + if (dot(hit_normal, world_space_ray_direction) > 0) + { + occlusion = 1 - confidence; + return 0; + } + + // Reject the hit if we didnt advance the ray significantly to avoid immediate self reflection + float2 manhattan_dist = abs(hit.xy - uv); + if ((manhattan_dist.x < (2.f / screen_size.x)) && (manhattan_dist.y < (2.f / screen_size.y))) + { + occlusion = 1 - confidence; + return 0; + } + + // Fade out hits near the screen borders + float2 fov = 0.01 * float2(screen_size.y / screen_size.x, 1); + float2 border = smoothstep(float2(0.0f, 0.0f), fov, hit.xy) * (1 - smoothstep(float2(1.0f, 1.0f) - fov, float2(1.0f, 1.0f), hit.xy)); + float vignette = border.x * border.y; + + return vignette * confidence; +} + +bool IsMirrorReflection(float roughness) +{ + return roughness < 0.1; +} + +float3 Sample_GGX_VNDF_Ellipsoid(float3 Ve, float alpha_x, float alpha_y, float U1, float U2) { return SampleGGXVNDF(Ve, alpha_x, alpha_y, U1, U2); } + +float3 Sample_GGX_VNDF_Hemisphere(float3 Ve, float alpha, float U1, float U2) { return Sample_GGX_VNDF_Ellipsoid(Ve, alpha, alpha, U1, U2); } + +float3x3 CreateTBN(float3 N) { + float3 U; + if (abs(N.z) > 0.0) { + float k = sqrt(N.y * N.y + N.z * N.z); + U.x = 0.0; + U.y = -N.z / k; + U.z = N.y / k; + } else { + float k = sqrt(N.x * N.x + N.y * N.y); + U.x = N.y / k; + U.y = -N.x / k; + U.z = 0.0; + } + + float3x3 TBN; + TBN[0] = U; + TBN[1] = cross(N, U); + TBN[2] = N; + return transpose(TBN); +} + +#define GOLDEN_RATIO 1.61803398875f + +float2 SampleRandomVector2DBaked(uint2 pixel, uint index, uint numSamples) { + // int2 coord = int2(pixel.x & 127u, pixel.y & 127u); + // float2 xi = float2(NoiseTexture[uint3(coord, 0)].x, NoiseTexture[uint3(coord, 64)].x); + // float2 u = float2(fmod(xi.x + (((int)(pixel.x / 128)) & 0xFFu) * GOLDEN_RATIO, 1.0f), fmod(xi.y + (((int)(pixel.y / 128)) & 0xFFu) * GOLDEN_RATIO, 1.0f)); + // return u; + int3 seed = int3(pixel.xy, 0); + seed.z = Random::pcg3d(int3(seed.xy, SharedData::FrameCount)).x; + uint2 xi = Random::pcg3d(seed).xy / 0x10000; + float2 E = Hammersley16(index, numSamples, xi); +#if defined(SSSR_SPECULAR) + E.y = lerp(E.y, 0, BRDFBias); +#endif + return E; +} + +float3 SampleReflectionVector(float3 view_direction, float3 normal, float roughness, int2 dispatch_thread_id, uint index, uint numSamples, out float pdf) { + if (roughness < 0.001f) { + pdf = 1.0f; + return reflect(view_direction, normal); + } + float3x3 tbn_transform = CreateTBN(normal); + float3 view_direction_tbn = mul(-view_direction, tbn_transform); + float2 u = SampleRandomVector2DBaked(dispatch_thread_id, index, numSamples); + // float3 sampled_normal_tbn = Sample_GGX_VNDF_Hemisphere(view_direction_tbn, roughness, u.x, u.y); +#if defined(SSSR_SPECULAR) + float4 sampled_normal_tbn = ImportanceSampleGGX(u, roughness * roughness * roughness * roughness); +#else + float4 sampled_normal_tbn = CosineSampleHemisphereConcentric(u); +#endif +#ifdef PERFECT_REFLECTIONS + sampled_normal_tbn.xyz = float3(0, 0, 1); // Overwrite normal sample to produce perfect reflection. +#endif +#if defined(SSSR_SPECULAR) + float3 reflected_direction_tbn = reflect(-view_direction_tbn, sampled_normal_tbn.xyz); +#else + float3 reflected_direction_tbn = sampled_normal_tbn.xyz; +#endif + // Transform reflected_direction back to the initial space. + float3x3 inv_tbn_transform = transpose(tbn_transform); + pdf = sampled_normal_tbn.w; + return mul(reflected_direction_tbn, inv_tbn_transform); +} + +float3 ScreenSpaceToWorldSpace(float3 screen_space_position, float4x4 invViewProj) +{ + return InvProjectPosition(screen_space_position, invViewProj); +} + +float3 ScreenSpaceToViewSpace(float3 screen_uv_coord, float4x4 invProj) +{ + return InvProjectPosition(screen_uv_coord, invProj); +} + +groupshared float4 samples[64][SAMPLES_PER_PIXEL]; +groupshared float4 weights[64][SAMPLES_PER_PIXEL]; + +bool IsInGroup(int2 groupThreadID) +{ + return groupThreadID.x < 8 && groupThreadID.y < 8 && groupThreadID.x >= 0 && groupThreadID.y >= 0; +} + +static const int2 offset[4] = { + int2(-1, 0), int2(1, 0), int2(0, 1), int2(0, -1) +}; + +float LocalBRDF(float3 V, float3 L, float3 N, float roughness) { +#if defined(SSSR_SPECULAR) // D_GGX only + float3 H = normalize(V + L); + float NdotL = saturate(dot(N, L)); + float NdotV = saturate(dot(N, V)); + float NdotH = saturate(dot(N, H)); + float D = BRDF::D_GGX(roughness, NdotH); + float G = BRDF::Vis_SmithJointApprox(roughness, NdotV, NdotL); + return D * G; +#else // Lambert + float NdotL = saturate(dot(N, L)); + return NdotL * BRDF::Diffuse_Lambert(); +#endif +} + +[numthreads(8, 8, SAMPLES_PER_PIXEL)] void main(uint3 groupID : SV_GroupID, + uint3 groupThreadID : SV_GroupThreadID, + uint3 DTid : SV_DispatchThreadID) +{ + uint2 screen_size = SharedData::BufferDim.xy; + uint2 coords = DTid.xy; + uint sample_id = 0; + float3 debug; + + float4 outColor = float4(0, 0, 0, 0); + float4 outPDF = float4(0, 0, 0, 0); + + float2 uv = float2(coords.xy + 0.5) * SharedData::BufferDim.zw * FrameBuffer::DynamicResolutionParams2.xy; + uint eyeIndex = Stereo::GetEyeIndexFromTexCoord(uv); + + float3 normalVS; + float roughness; + GetNormalRoughness(coords.xy, normalVS, roughness); + roughness = clamp(roughness, 0.02f, 1.0f); + + + bool is_mirror = IsMirrorReflection(roughness); + int most_detailed_mip = HIZ_MIN_MIP; + float2 mip_resolution = SSSR_GetMipResolution(screen_size, most_detailed_mip); + float z = SSSR_LoadDepth(uv * mip_resolution * FrameBuffer::DynamicResolutionParams1.xy, most_detailed_mip); + float3 screen_uv_space_ray_origin = float3(uv, z); + float3 view_space_ray = ScreenSpaceToViewSpace(screen_uv_space_ray_origin, FrameBuffer::CameraProjInverse[eyeIndex]); + float3 world_space_normal = normalize(mul(FrameBuffer::CameraViewInverse[eyeIndex], float4(normalVS, 0)).xyz); + float3 view_space_surface_normal = normalVS; + float3 view_space_ray_direction = normalize(view_space_ray); + view_space_ray += view_space_surface_normal * NormalBias * view_space_ray.z * GAME_UNIT_TO_M; + float pdf; + float3 view_space_reflected_direction = SampleReflectionVector(view_space_ray_direction, view_space_surface_normal, roughness, coords, sample_id, SAMPLES_PER_PIXEL, pdf); + screen_uv_space_ray_origin = ProjectPosition(view_space_ray, FrameBuffer::CameraProj[eyeIndex]); + float3 screen_space_ray_direction = ProjectDirection(view_space_ray, view_space_reflected_direction, screen_uv_space_ray_origin, FrameBuffer::CameraProj[eyeIndex]); + float3 world_space_reflected_direction = mul(FrameBuffer::CameraViewInverse[eyeIndex], float4(view_space_reflected_direction, 0)).xyz; + float3 world_space_origin = mul(FrameBuffer::CameraViewInverse[eyeIndex], float4(view_space_ray, 1)).xyz; + float world_ray_length = 0.0; + bool valid_ray = all(coords < int2(screen_size * FrameBuffer::DynamicResolutionParams1.xy)) && all(coords >= int2(0, 0)); + uint hit_counter = 0; + float3 hit = float3(0.0, 0.0, 0.0); + float confidence = 0.0; + float3 world_space_hit = float3(0.0, 0.0, 0.0); + float3 world_space_ray = float3(0.0, 0.0, 0.0); + + float depth = DepthTexture[coords.xy].x; + float4 positionWS = float4(2 * float2(uv.x, -uv.y + 1) - 1, depth, 1); + positionWS = mul(FrameBuffer::CameraViewProjInverse[eyeIndex], positionWS); + positionWS.xyz = positionWS.xyz / positionWS.w; + + samples[groupThreadID.x * 8 + groupThreadID.y][sample_id] = 0.f; + float localWeight = pdf == 0 ? 0 : LocalBRDF(-view_space_ray_direction, view_space_surface_normal, view_space_reflected_direction, roughness) / max(pdf, 1e-4); + weights[groupThreadID.x * 8 + groupThreadID.y][sample_id] = float4(view_space_surface_normal, localWeight); + + if (valid_ray) + { + bool valid_hit; + bool go_through_thin = false; + uint numIterations; + float thickness = Thickness + roughness * 10.0; + hit = SSSR_HierarchicalRaymarch(screen_uv_space_ray_origin, + screen_space_ray_direction, + is_mirror, + screen_size, + most_detailed_mip, + roughness, + thickness, + HIZ_MAX_ITERATIONS, + valid_hit, numIterations); + + world_space_hit = ScreenSpaceToWorldSpace(hit, FrameBuffer::CameraViewProjInverse[eyeIndex]); + world_space_ray = world_space_hit - world_space_origin.xyz; + world_ray_length = length(world_space_ray); + float occlusion = 1.0f; + confidence = valid_hit ? SSSR_ValidateHit(hit, + uv, + world_space_ray, + screen_size, + thickness, + eyeIndex, + occlusion + ) + : 0; + float3 sampleColor = 0; + if (confidence > 0.0f) + { + // float2 projUV; + // ReprojectHit(MotionVectorTexture, LinearSampler, hit, eyeIndex, projUV); + + sampleColor = ScreenColorTextureMips.SampleLevel(LinearSampler, hit.xy * FrameBuffer::DynamicResolutionParams1.xy, 0).xyz; + sampleColor = Color::IrradianceToLinear(sampleColor); + sampleColor *= SharedData::sssrSettings.SpecularMult; + + outPDF.xyz += hit * confidence; + outPDF.w += pdf * confidence; + } + const float NdotV = saturate(dot(normalize(view_space_ray), view_space_surface_normal)); +#if defined(DYNAMIC_CUBEMAPS) + if (UseDynamicCubemapsAsFallback != 0 && (confidence < 0.999f)) + { +# if defined(SSSR_SPECULAR) + const uint sampleMip = 0; +# else + const uint sampleMip = 2; +# endif + float directionalAmbientLuminance = Color::RGBToLuminance(Color::Ambient(max(0.0, mul(SharedData::DirectionalAmbient, float4(world_space_reflected_direction, 1.0))))) * Color::ReflectionNormalisationScale; + float envLuminance; + // Fallback to dynamic cubemaps + float3 envColor = EnvReflectionsTexture.SampleLevel(LinearSampler, world_space_reflected_direction, sampleMip); +# if defined(SKYLIGHTING) + if (!SharedData::InInterior) + { + float3 positionMS = positionWS.xyz; + + sh2 skylighting = Skylighting::sample(SharedData::skylightingSettings, SkylightingProbeArray, stbn_vec3_2Dx1D_128x128x64, coords.xy, positionMS.xyz, world_space_reflected_direction); + float3 skylightingNormal = normalize(float3(world_space_normal.xy, max(0, world_space_normal.z))); + float skylightingDiffuse = SphericalHarmonics::FuncProductIntegral(skylighting, SphericalHarmonics::EvaluateCosineLobe(skylightingNormal)) / Math::PI; + skylightingDiffuse = saturate(skylightingDiffuse); + + skylightingDiffuse = lerp(1.0, skylightingDiffuse, Skylighting::getFadeOutFactor(positionMS.xyz)); + + skylightingDiffuse *= 1.0 + saturate(world_space_normal.z) * (1.0 - SharedData::skylightingSettings.MinDiffuseVisibility); + + skylightingDiffuse = Skylighting::mixDiffuse(SharedData::skylightingSettings, skylightingDiffuse); +# if defined(SSSR_SPECULAR) + skylightingDiffuse = GetSpecularOcclusionFromAmbientOcclusion(NdotV, skylightingDiffuse, roughness); +# endif + float3 envNoSkyColor = EnvTexture.SampleLevel(LinearSampler, world_space_reflected_direction, sampleMip); + float3 envSkyColor = envColor; + float3 skyColor = max(envSkyColor - envNoSkyColor, 0); + envLuminance = Color::RGBToLuminance(EnvTexture.SampleLevel(LinearSampler, world_space_reflected_direction, 15)); + envColor = lerp(envNoSkyColor, envNoSkyColor * (directionalAmbientLuminance / max(envLuminance, 1e-4)), CubemapNormalization); + envColor += skyColor * skylightingDiffuse; + } else { + envLuminance = Color::RGBToLuminance(EnvReflectionsTexture.SampleLevel(LinearSampler, world_space_reflected_direction, 15)); + envColor = lerp(envColor, envColor * (directionalAmbientLuminance / max(envLuminance, 1e-4)), CubemapNormalization); + } +# else + envLuminance = Color::RGBToLuminance(EnvReflectionsTexture.SampleLevel(LinearSampler, world_space_reflected_direction, 15).xyz); + envColor = lerp(envColor, envColor * (directionalAmbientLuminance / max(envLuminance, 1e-4)), CubemapNormalization); +# endif + envColor = Color::IrradianceToLinear(envColor); + float ao = lerp(1.0, occlusion, OcclusionStrength); +# if defined(SSGI) + ao *= 1 - saturate(SsgiAoTexture[coords.xy].x); +# endif +# if defined(SSSR_SPECULAR) + ao = GetSpecularOcclusionFromAmbientOcclusion(NdotV, ao, roughness); + envColor *= ao; +# endif + sampleColor.xyz = lerp(envColor, sampleColor.xyz, confidence); + confidence = 1; + } +#endif + samples[groupThreadID.x * 8 + groupThreadID.y][sample_id] = float4(sampleColor, confidence); + } + GroupMemoryBarrierWithGroupSync(); + +#if defined(SSSR_SPECULAR) + outColor.xyz = samples[groupThreadID.x * 8 + groupThreadID.y][0].xyz; + outColor.w = samples[groupThreadID.x * 8 + groupThreadID.y][0].w; + SSRColorOutput[coords.xy] = outColor; + SSRPDFOutput[coords.xy] = outPDF; +#else + + if (sample_id == 0) { + outColor = 0.f; + for (int i = 0; i < SAMPLES_PER_PIXEL; ++i) { + outColor.xyz += samples[groupThreadID.x * 8 + groupThreadID.y][i].xyz; + outColor.w += samples[groupThreadID.x * 8 + groupThreadID.y][i].w; + } + outColor.xyz /= SAMPLES_PER_PIXEL; + outColor.w = saturate(outColor.w / SAMPLES_PER_PIXEL); + SSRColorOutput[coords.xy] = outColor; + SSRPDFOutput[coords.xy] = outPDF; + } +#endif +} \ No newline at end of file diff --git a/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_spatial.hlsl b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_spatial.hlsl new file mode 100644 index 0000000000..b07a843649 --- /dev/null +++ b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_spatial.hlsl @@ -0,0 +1,123 @@ +#include "StochasticScreenSpaceReflections/sssr_common.hlsli" + +Texture2D HistoryTexture : register(t0); +Texture2D SSRColorTexture : register(t3); +Texture2D DepthTexture : register(t4); + +RWTexture2D FilteredOutput : register(u0); + +cbuffer DenoiserCB : register(b2) +{ + float invMaxAccumulatedFrames; + uint atrousIterations; + float colorPhi; + float normalPhi; +}; + +float GaussianBlur(uint2 id) +{ + float sum = 0.f; + float kernelSum = 0.f; + const float kernel[2][2] = + { + { 1.0 / 4.0, 1.0 / 8.0 }, + { 1.0 / 8.0, 1.0 / 16.0 } + }; + + const int radius = 1; + + for (int y = -radius; y <= radius; y++) + { + for (int x = -radius; x <= radius; x++) + { + const int2 p = id + int2(x, y); + const bool inside = (p.x >= 0 && p.y >= 0) && (p.x < SharedData::BufferDim.x * FrameBuffer::DynamicResolutionParams1.x && p.y < SharedData::BufferDim.y * FrameBuffer::DynamicResolutionParams1.y); + + if (inside) + { + const float k = kernel[abs(x)][abs(y)]; + kernelSum += k; + sum += SSRColorTexture[p].w * k; + } + } + } + + return sum / kernelSum; +} + +static const float kernelWeights[3] = { 1.0, 2.0 / 3.0, 1.0 / 6.0 }; + +#define VAR_EPSILON 0.00001f + +// Spatiotemporal Variance-Guided Filter +[numthreads(8, 8, 1)] void main(uint3 DTid : SV_DispatchThreadID) +{ + uint2 screen_size = SharedData::BufferDim.xy * FrameBuffer::DynamicResolutionParams1.xy; + if (DTid.x >= screen_size.x || DTid.y >= screen_size.y) + return; + + float2 uv = float2(DTid.xy + 0.5) * SharedData::BufferDim.zw * FrameBuffer::DynamicResolutionParams2.xy; + + float3 blendedColor = 0; + float4 historyColor = HistoryTexture[DTid.xy]; + float4 ssrColor = SSRColorTexture[DTid.xy]; + float depthCenter = DepthTexture[DTid.xy]; + + float3 normalVS; + float roughness; + GetNormalRoughness(DTid.xy, normalVS, roughness); + roughness = clamp(roughness, 0.001f, 1.0f); + + float luminanceCenter = Color::RGBToLuminance(ssrColor.rgb); + float variance = GaussianBlur(DTid.xy); + + if (depthCenter > 0) + { + float phiLuminance = max(colorPhi * sqrt(abs(variance) + VAR_EPSILON), VAR_EPSILON); + float phiNormal = normalPhi; +#if defined(SSSR_SPECULAR) + // Trying to reduce blurriness on glossy surfaces + phiLuminance *= roughness; + phiNormal /= roughness; +#endif + float phiDepth = (atrousIterations + 1); + float weightSum = 0.f; + + for (int ky = -2; ky <= 2; ky++) + { + for (int kx = -2; kx <= 2; kx++) + { + // A-Trous sampling + int2 samplePos = int2(DTid.xy) + int2(kx, ky) * (atrousIterations + 1); + bool inside = (samplePos.x >= 0 && samplePos.y >= 0) && (samplePos.x < screen_size.x && samplePos.y < screen_size.y); + if (inside) + { + float4 sampleSSRColor = SSRColorTexture[samplePos]; + float sampleDepth = DepthTexture[samplePos]; + if (sampleDepth > 0) + { + float3 sampleNormalVS; + float sampleRoughness; + GetNormalRoughness(samplePos, sampleNormalVS, sampleRoughness); + + float luminanceP = Color::RGBToLuminance(sampleSSRColor.rgb); + float weight = CalculateWeight(depthCenter, sampleDepth, phiDepth, normalVS, sampleNormalVS, phiNormal, luminanceCenter, luminanceP, phiLuminance) * kernelWeights[abs(kx)] * kernelWeights[abs(ky)]; + + blendedColor += sampleSSRColor.rgb * weight; + weightSum += weight; + } + } + } + } + if (weightSum > 0.f) + { + blendedColor /= weightSum; + } + else + { + blendedColor = ssrColor.rgb; + } + } + + FilteredOutput[DTid.xy] = float4(blendedColor, 1.0); +} \ No newline at end of file diff --git a/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_temporal.hlsl b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_temporal.hlsl new file mode 100644 index 0000000000..94ade60898 --- /dev/null +++ b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_temporal.hlsl @@ -0,0 +1,169 @@ +#include "StochasticScreenSpaceReflections/sssr_common.hlsli" + +Texture2D HistoryTexture : register(t0); +Texture2D MotionVectorTexture : register(t1); +Texture2D SSRColorTexture : register(t3); +Texture2D DepthTexture : register(t4); +Texture2D HistoryMomentsTexture : register(t5); // moments in RG, frame count in B +Texture2D HistoryNormalsTexture : register(t6); + +RWTexture2D FilteredOutput : register(u0); +RWTexture2D MomentsOutput : register(u1); + +cbuffer DenoiserCB : register(b2) +{ + float invMaxAccumulatedFrames; + uint atrousIterations; + float colorPhi; + float normalPhi; +}; + +bool IsValidHistory(uint2 pixel, float2 uv, float3 currNormalVS) +{ + uint2 screen_size = SharedData::BufferDim.xy * FrameBuffer::DynamicResolutionParams1.xy; + if (uv.x < 0 || uv.x > 1 || uv.y < 0 || uv.y > 1) + return false; + + if (pixel.x >= screen_size.x || pixel.y >= screen_size.y) + return false; + + float3 prevNormalVS; + float roughness; + GetNormalRoughness(HistoryNormalsTexture, pixel, prevNormalVS, roughness); + float normalDiff = dot(currNormalVS, prevNormalVS); + if (normalDiff < 0.866f) // cos 30 + return false; + + return true; +} + +[numthreads(8, 8, 1)] void main(uint3 DTid : SV_DispatchThreadID) +{ + uint2 screen_size = SharedData::BufferDim.xy * FrameBuffer::DynamicResolutionParams1.xy; + if (DTid.x >= screen_size.x || DTid.y >= screen_size.y) + return; + + float2 uv = float2(DTid.xy + 0.5) * SharedData::BufferDim.zw * FrameBuffer::DynamicResolutionParams2.xy; + uint eyeIndex = Stereo::GetEyeIndexFromTexCoord(uv); + + float3 blendedColor = 0; + float4 ssrColor = SSRColorTexture[DTid.xy]; + float depthCenter = DepthTexture[DTid.xy]; + + float3 normalVS; + float roughness; + GetNormalRoughness(DTid.xy, normalVS, roughness); + + float luminance = Color::RGBToLuminance(ssrColor.rgb); + float2 curMoment = float2(luminance, luminance * luminance) * 0.5; + + // Reproject UVs using motion vectors + float2 prevUV = uv; + ReprojectHit(MotionVectorTexture, LinearSampler, float3(uv, depthCenter), eyeIndex, prevUV); + + float4 prevColor = 0.f; + float prevAccumFrames = 0.f; + float2 prevMoments = float2(0.f, 0.f); + uint2 prevPixel = uint2(prevUV * screen_size); + bool valid = false; + + if (IsValidHistory(prevPixel, prevUV, normalVS)) + { + prevColor = HistoryTexture[prevPixel]; + prevAccumFrames = HistoryMomentsTexture[prevPixel].z; + prevMoments = HistoryMomentsTexture[prevPixel].xy; + valid = true; + } + + if (!valid) + { + int2 bilinOffset[4] = { int2(-1, 0), int2(1, 0), int2(0, -1), int2(0, 1) }; + float weightSum = 0.f; + [unroll(4)] + for (int i = 0; i < 4; i++) + { + int2 neighborPixel = int2(prevPixel) + bilinOffset[i]; + if (IsValidHistory(uint2(neighborPixel), prevUV, normalVS)) + { + float4 neighborColor = HistoryTexture[uint2(neighborPixel)]; + float neighborAccumFrames = HistoryMomentsTexture[uint2(neighborPixel)].z; + if (neighborAccumFrames > 0.f) + { + prevColor += neighborColor; + prevAccumFrames += neighborAccumFrames; + prevMoments += HistoryMomentsTexture[uint2(neighborPixel)].xy; + weightSum += 1.f; + } + } + } + + if (weightSum > 0.f) + { + prevColor /= weightSum; + prevAccumFrames /= weightSum; + prevMoments /= weightSum; + valid = true; + } + } + + if (!valid) + { + float weightSum = 0.f; + + int2 offsets[8] = + { + int2(0, 2), + int2(0, -2), + int2(1, 1), + int2(1, -1), + int2(-1, 1), + int2(-1, -1), + int2(2, 0), + int2(-2, 0) + }; + + [unroll(8)] + for (int i = 0; i < 8; i++) + { + int2 neighborPixel = int2(prevPixel) + offsets[i]; + if (IsValidHistory(uint2(neighborPixel), prevUV, normalVS)) + { + float4 neighborColor = HistoryTexture[uint2(neighborPixel)]; + float neighborAccumFrames = HistoryMomentsTexture[uint2(neighborPixel)].z; + if (neighborAccumFrames > 0.f) + { + prevColor += neighborColor; + prevAccumFrames += neighborAccumFrames; + prevMoments += HistoryMomentsTexture[uint2(neighborPixel)].xy; + weightSum += 1.f; + } + } + } + if (weightSum > 0.f) + { + prevColor /= weightSum; + prevAccumFrames /= weightSum; + prevMoments /= weightSum; + valid = true; + } + } + + if (valid) + { + float alpha = max(1.0f / (prevAccumFrames + 1.0f), invMaxAccumulatedFrames); + blendedColor = lerp(prevColor.rgb, ssrColor.rgb, alpha); + + float prevLuminance = Color::RGBToLuminance(prevColor.rgb); + float2 prevMoment = float2(prevLuminance, prevLuminance * prevLuminance); + + float momentAlpha = max(1.0f / (prevAccumFrames + 1.0f), invMaxAccumulatedFrames); + float2 moment = lerp(prevMoment, curMoment, momentAlpha); + float variance = moment.y - (moment.x * moment.x); + variance = max(variance, 0.f); + FilteredOutput[DTid.xy] = float4(blendedColor, variance); + MomentsOutput[DTid.xy] = float4(moment, prevAccumFrames + 1.0f, 0.f); + return; + } + MomentsOutput[DTid.xy] = float4(curMoment, 1.0f, 0.f); + FilteredOutput[DTid.xy] = float4(ssrColor.rgb, abs(curMoment.y - (curMoment.x * curMoment.x))); +} \ No newline at end of file diff --git a/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_variance.hlsl b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_variance.hlsl new file mode 100644 index 0000000000..7a717dbb5c --- /dev/null +++ b/features/Stochastic Screen Space Reflections/Shaders/StochasticScreenSpaceReflections/sssr_variance.hlsl @@ -0,0 +1,80 @@ +#include "StochasticScreenSpaceReflections/sssr_common.hlsli" + +Texture2D HistoryTexture : register(t0); +Texture2D MomentsTexture : register(t1); +Texture2D SSRColorTexture : register(t3); +Texture2D DepthTexture : register(t4); + +RWTexture2D VarianceOutput : register(u0); + +cbuffer DenoiserCB : register(b2) +{ + float invMaxAccumulatedFrames; + uint atrousIterations; + float colorPhi; + float normalPhi; +}; + +[numthreads(8, 8, 1)] void main(uint3 DTid : SV_DispatchThreadID) +{ + uint2 screen_size = SharedData::BufferDim.xy * FrameBuffer::DynamicResolutionParams1.xy; + if (DTid.x >= screen_size.x || DTid.y >= screen_size.y) + return; + + float2 uv = float2(DTid.xy + 0.5) * SharedData::BufferDim.zw * FrameBuffer::DynamicResolutionParams2.xy; + + float4 ssrColor = SSRColorTexture[DTid.xy]; + float3 blendedColor = ssrColor.xyz; + float depthCenter = DepthTexture[DTid.xy]; + VarianceOutput[DTid.xy] = ssrColor; + + float history = MomentsTexture[DTid.xy].z; + + if (history <= 2) { + float3 normalVS; + float roughness; + GetNormalRoughness(DTid.xy, normalVS, roughness); + + float luminanceCenter = Color::RGBToLuminance(ssrColor.xyz); + float weightedColor = 1.f; + float3 colorSum = ssrColor.xyz; + float2 momentsSum = MomentsTexture[DTid.xy].xy; + + const int radius = 3; + for (int y = -radius; y <= radius; y++) + { + for (int x = -radius; x <= radius; x++) + { + if (x == 0 && y == 0) + continue; + + const int2 p = int2(DTid.xy) + int2(x, y); + const bool inside = (p.x >= 0 && p.y >= 0) && (p.x < screen_size.x && p.y < screen_size.y); + + if (inside) + { + float4 neighborSSRColor = SSRColorTexture[p]; + float3 neighborNormalVS; + float neighborRoughness; + GetNormalRoughness(p, neighborNormalVS, neighborRoughness); + float neighborLuminance = Color::RGBToLuminance(neighborSSRColor.xyz); + float depthNeighbor = DepthTexture[p]; + + float weight = CalculateWeight(depthCenter, depthNeighbor, length(float2(x, y)), normalVS, neighborNormalVS, normalPhi, luminanceCenter, neighborLuminance, colorPhi); + + weightedColor += weight; + colorSum += neighborSSRColor.xyz * weight; + momentsSum += MomentsTexture[p].xy * weight; + } + } + } + + weightedColor = max(weightedColor, 1e-5f); + blendedColor = colorSum / weightedColor; + momentsSum /= weightedColor; + + float variance = momentsSum.y - (momentsSum.x * momentsSum.x); + variance *= 2 / max(history, 1.f); + VarianceOutput[DTid.xy] = float4(blendedColor, variance); + } +} \ No newline at end of file diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index 8ba6d4c5dc..cbb84e8de5 100644 --- a/package/Shaders/Common/SharedData.hlsli +++ b/package/Shaders/Common/SharedData.hlsli @@ -238,6 +238,13 @@ namespace SharedData uint3 _padding; }; + struct SSSRSettings + { + uint EnableSpecular; + float SpecularMult; + float2 _padding; + }; + cbuffer FeatureData : register(b6) { GrassLightingSettings grassLightingSettings; @@ -255,6 +262,7 @@ namespace SharedData ExtendedTranslucencySettings extendedTranslucencySettings; LinearLightingSettings linearLightingSettings; TerrainBlendingSettings terrainBlendingSettings; + SSSRSettings sssrSettings; }; Texture2D DepthTexture : register(t17); diff --git a/package/Shaders/DeferredCompositeCS.hlsl b/package/Shaders/DeferredCompositeCS.hlsl index 84f2030ad3..2e660e8483 100644 --- a/package/Shaders/DeferredCompositeCS.hlsl +++ b/package/Shaders/DeferredCompositeCS.hlsl @@ -55,7 +55,12 @@ void SampleSSGISpecular(uint2 pixCoord, sh2 lobe, out float ao, out float3 il, i ao = 1 - SsgiAoTexture[pixCoord].x; float NdotV = dot(normal, view); ao = Color::SpecularAOLagarde(saturate(NdotV), ao, roughness); - +# if defined(SSSR) + if (SharedData::sssrSettings.EnableSpecular) { + il = 0; + return; + } +# else float4 ssgiIlYSh = SsgiYTexture[pixCoord]; float ssgiIlY = SphericalHarmonics::FuncProductIntegral(ssgiIlYSh, lobe); float2 ssgiIlCoCg = SsgiCoCgTexture[pixCoord].xy; @@ -69,6 +74,7 @@ void SampleSSGISpecular(uint2 pixCoord, sh2 lobe, out float ao, out float3 il, i float4 hq_spec = SsgiSpecularTexture[pixCoord]; ao *= 1 - hq_spec.a; il += hq_spec.rgb; +# endif } #endif @@ -81,6 +87,10 @@ void SampleSSGISpecular(uint2 pixCoord, sh2 lobe, out float ao, out float3 il, i # endif #endif +#if defined(SSSR) +Texture2D SSSRTexture : register(t16); +#endif + [numthreads(8, 8, 1)] void main(uint3 dispatchID : SV_DispatchThreadID) { // Early exit if dispatch thread is outside screen bounds if (any(dispatchID.xy >= uint2(SharedData::BufferDim.xy))) @@ -275,6 +285,13 @@ void SampleSSGISpecular(uint2 pixCoord, sh2 lobe, out float ao, out float3 il, i finalIrradiance += ssgiIlSpecular; # endif +# if defined(SSSR) + if (SharedData::sssrSettings.EnableSpecular) { + float4 ssrIrradiance = SSSRTexture[dispatchID.xy]; + finalIrradiance = any(ssrIrradiance.rgb > 0) ? ssrIrradiance.rgb : finalIrradiance; + } +# endif + color += reflectance * finalIrradiance; } diff --git a/src/Deferred.cpp b/src/Deferred.cpp index 896d7b4882..7aa740e0aa 100644 --- a/src/Deferred.cpp +++ b/src/Deferred.cpp @@ -10,6 +10,7 @@ #include "Features/IBL.h" #include "Features/ScreenSpaceGI.h" #include "Features/Skylighting.h" +#include "Features/StochasticScreenSpaceReflections.h" #include "Features/SubsurfaceScattering.h" #include "Features/TerrainBlending.h" #include "Features/Upscaling.h" @@ -397,6 +398,8 @@ void Deferred::DeferredPasses() auto [ssgi_ao, ssgi_y, ssgi_cocg, ssgi_gi_spec] = ssgi.GetOutputTextures(); bool ssgi_hq_spec = ssgi.settings.EnableExperimentalSpecularGI; + auto& sssr = globals::features::screenSpaceRayTracing; + auto dispatchCount = Util::GetScreenDispatchCount(true); auto& sss = globals::features::subsurfaceScattering; @@ -410,11 +413,14 @@ void Deferred::DeferredPasses() auto& terrainBlending = globals::features::terrainBlending; auto& ibl = globals::features::ibl; + if (sssr.loaded && sssr.settings.EnableSpecular) + sssr.DrawSSSRSpecular(); + // Deferred Composite { TracyD3D11Zone(globals::state->tracyCtx, "Deferred Composite"); - ID3D11ShaderResourceView* srvs[16]{ + ID3D11ShaderResourceView* srvs[17]{ specular.SRV, albedo.SRV, normalRoughness.SRV, @@ -431,6 +437,7 @@ void Deferred::DeferredPasses() ssgi_hq_spec ? ssgi_gi_spec : nullptr, ibl.loaded ? ibl.diffuseIBLTexture->srv.get() : nullptr, ibl.loaded ? ibl.diffuseSkyIBLTexture->srv.get() : nullptr, + (sssr.loaded && sssr.settings.EnableSpecular) ? sssr.texOutput->srv.get() : nullptr, }; if (dynamicCubemaps.loaded) @@ -449,7 +456,7 @@ void Deferred::DeferredPasses() // Clear { - ID3D11ShaderResourceView* views[16]{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; + ID3D11ShaderResourceView* views[17]{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; context->CSSetShaderResources(0, ARRAYSIZE(views), views); ID3D11UnorderedAccessView* uavs[3]{ nullptr, nullptr, nullptr }; @@ -617,6 +624,9 @@ ID3D11ComputeShader* Deferred::GetComputeMainComposite() if (globals::features::ibl.loaded) defines.push_back({ "IBL", nullptr }); + if (globals::features::screenSpaceRayTracing.loaded) + defines.push_back({ "SSSR", nullptr }); + if (REL::Module::IsVR()) defines.push_back({ "FRAMEBUFFER", nullptr }); @@ -642,6 +652,9 @@ ID3D11ComputeShader* Deferred::GetComputeMainCompositeInterior() if (globals::features::ibl.loaded) defines.push_back({ "IBL", nullptr }); + if (globals::features::screenSpaceRayTracing.loaded) + defines.push_back({ "SSSR", nullptr }); + if (REL::Module::IsVR()) defines.push_back({ "FRAMEBUFFER", nullptr }); diff --git a/src/Feature.cpp b/src/Feature.cpp index 923981a161..4ac467815a 100644 --- a/src/Feature.cpp +++ b/src/Feature.cpp @@ -21,6 +21,7 @@ #include "Features/ScreenSpaceShadows.h" #include "Features/SkySync.h" #include "Features/Skylighting.h" +#include "Features/StochasticScreenSpaceReflections.h" #include "Features/SubsurfaceScattering.h" #include "Features/TerrainBlending.h" #include "Features/TerrainHelper.h" @@ -219,6 +220,7 @@ const std::vector& Feature::GetFeatureList() &globals::features::subsurfaceScattering, &globals::features::terrainShadows, &globals::features::screenSpaceGI, + &globals::features::screenSpaceRayTracing, &globals::features::skylighting, &globals::features::skySync, &globals::features::terrainBlending, diff --git a/src/FeatureBuffer.cpp b/src/FeatureBuffer.cpp index 5bf884053d..114c288f3d 100644 --- a/src/FeatureBuffer.cpp +++ b/src/FeatureBuffer.cpp @@ -11,6 +11,7 @@ #include "Features/LightLimitFix.h" #include "Features/LinearLighting.h" #include "Features/Skylighting.h" +#include "Features/StochasticScreenSpaceReflections.h" #include "Features/TerrainBlending.h" #include "Features/TerrainShadows.h" #include "Features/TerrainVariation.h" @@ -51,5 +52,6 @@ std::pair GetFeatureBufferData(bool a_inWorld) globals::features::ibl.settings, globals::features::extendedTranslucency.GetCommonBufferData(), globals::features::linearLighting.GetCommonBufferData(), - globals::features::terrainBlending.settings); + globals::features::terrainBlending.settings, + globals::features::screenSpaceRayTracing.GetCommonBufferData()); } \ No newline at end of file diff --git a/src/Features/StochasticScreenSpaceReflections.cpp b/src/Features/StochasticScreenSpaceReflections.cpp new file mode 100644 index 0000000000..66745eaf5e --- /dev/null +++ b/src/Features/StochasticScreenSpaceReflections.cpp @@ -0,0 +1,558 @@ +#include "StochasticScreenSpaceReflections.h" + +#include + +#include "Deferred.h" +#include "Menu.h" +#include "ShaderCache.h" +#include "State.h" + +#include "DynamicCubemaps.h" +#include "ScreenSpaceGI.h" +#include "Skylighting.h" + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( + StochasticScreenSpaceReflections::Settings, + EnableSpecular, + MaxSteps, + MaxMips, + Thickness, + NormalBias, + BRDFBias, + UseDynamicCubemapsAsFallbackSpecular, + SpecularMult, + OcclusionStrength, + CubemapNormalization, + EnableSVGF, + MaxAccumulatedFrames, + AtrousIterations, + ColorPhi, + NormalPhi) + +void StochasticScreenSpaceReflections::DrawSettings() +{ + ImGui::Checkbox("Enable Specular", &settings.EnableSpecular); + ImGui::SliderInt("Max Steps", (int*)&settings.MaxSteps, 1, 256); + ImGui::SliderInt("Max Mip Level", (int*)&settings.MaxMips, 1, maxMips, "%d", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat("Specular Multiplier", &settings.SpecularMult, 0.0f, 5.0f, "%.2f"); + ImGui::SliderFloat("Occlusion Strength", &settings.OcclusionStrength, 0.0f, 1.0f, "%.2f"); + + ImGui::Separator(); + + ImGui::SliderFloat("Thickness", &settings.Thickness, 0.0f, 50.0f, "%.2f"); + ImGui::SliderFloat("Normal Bias", &settings.NormalBias, 0.0f, 1.0f, "%.2f"); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("To avoid false hits from nearby geometry, increase this value to push the ray origin along the normal."); + ImGui::SliderFloat("BRDF Bias", &settings.BRDFBias, 0.0f, 1.0f, "%.2f"); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Specular only. Higher BRDF bias reduces noise but makes reflections more glossy."); + ImGui::Checkbox("Use Dynamic Cubemaps as Fallback", &settings.UseDynamicCubemapsAsFallbackSpecular); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("When ray marching misses, use dynamic cubemaps for reflections. Recommended for specular."); + ImGui::SliderFloat("Cubemap Normalization", &settings.CubemapNormalization, 0.0f, 1.0f, "%.2f"); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Matches cubemap luminance with ambient color."); + + ImGui::Separator(); + + ImGui::Checkbox("Enable Spatiotemporal Variance-Guided Filtering", &settings.EnableSVGF); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("SVGF denoiser. This may introduce some blurriness and temporal artifacts but significantly reduces noise."); + if (settings.EnableSVGF) { + ImGui::SliderInt("Max Accumulated Frames", (int*)&settings.MaxAccumulatedFrames, 1, 64, "%d", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderInt("À Trous Iterations", (int*)&settings.AtrousIterations, 1, 5, "%d", ImGuiSliderFlags_AlwaysClamp); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Number of À Trous wavelet filter iterations. More iterations yield smoother results but may blur details and have a higher computational cost."); + ImGui::SliderFloat("Color Phi", &settings.ColorPhi, 0.01f, 32.0f, "%.2f"); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Controls sensitivity to color differences in the À Trous filter. Lower values preserve more detail but may retain noise."); + ImGui::SliderFloat("Normal Phi", &settings.NormalPhi, 1.0f, 1024.0f, "%.2f"); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Controls sensitivity to normal differences in the À Trous filter. Higher values preserve more detail but may retain noise."); + } + + ImGui::SeparatorText("Debug"); + + if (ImGui::TreeNode("Buffer Viewer")) { + static float debugRescale = .3f; + ImGui::SliderFloat("View Resize", &debugRescale, 0.f, 1.f); + + BUFFER_VIEWER_NODE(texDepth, debugRescale) + BUFFER_VIEWER_NODE(texColor, debugRescale) + BUFFER_VIEWER_NODE(texSSRColor, debugRescale) + BUFFER_VIEWER_NODE(texHistory, debugRescale) + BUFFER_VIEWER_NODE(texHitPDF, debugRescale) + BUFFER_VIEWER_NODE(texTemporal, debugRescale) + BUFFER_VIEWER_NODE(texMoments, debugRescale) + BUFFER_VIEWER_NODE(texHistoryMoments, debugRescale) + BUFFER_VIEWER_NODE(texVariance, debugRescale) + BUFFER_VIEWER_NODE(texOutput, debugRescale) + + ImGui::TreePop(); + } +} + +void StochasticScreenSpaceReflections::RestoreDefaultSettings() +{ + settings = {}; +} + +void StochasticScreenSpaceReflections::LoadSettings(json& o_json) +{ + settings = o_json; +} + +void StochasticScreenSpaceReflections::SaveSettings(json& o_json) +{ + o_json = settings; +} + +void StochasticScreenSpaceReflections::SetupResources() +{ + auto renderer = globals::game::renderer; + auto device = globals::d3d::device; + + logger::debug("Creating buffers..."); + { + sssrCB = eastl::make_unique(ConstantBufferDesc()); + denoiserCB = eastl::make_unique(ConstantBufferDesc()); + } + + logger::debug("Creating textures..."); + { + auto mainTex = renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kMAIN]; + D3D11_TEXTURE2D_DESC texDesc = {}; + mainTex.texture->GetDesc(&texDesc); + texDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; + texDesc.MipLevels = 1; + texDesc.MiscFlags |= D3D11_RESOURCE_MISC_GENERATE_MIPS; + texDesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; + + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = { + .Format = texDesc.Format, + .ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D, + .Texture2D = { + .MostDetailedMip = 0, + .MipLevels = texDesc.MipLevels } + }; + D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc = { + .Format = texDesc.Format, + .ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D, + .Texture2D = { .MipSlice = 0 } + }; + + texColor = eastl::make_unique(texDesc); + texColor->CreateSRV(srvDesc); + texColor->CreateUAV(uavDesc); + texSSRColor = eastl::make_unique(texDesc); + texSSRColor->CreateSRV(srvDesc); + texSSRColor->CreateUAV(uavDesc); + texHitPDF = eastl::make_unique(texDesc); + texHitPDF->CreateSRV(srvDesc); + texHitPDF->CreateUAV(uavDesc); + texHistory = eastl::make_unique(texDesc); + texHistory->CreateSRV(srvDesc); + texHistory->CreateUAV(uavDesc); + texTemporal = eastl::make_unique(texDesc); + texTemporal->CreateSRV(srvDesc); + texTemporal->CreateUAV(uavDesc); + texVariance = eastl::make_unique(texDesc); + texVariance->CreateSRV(srvDesc); + texVariance->CreateUAV(uavDesc); + texOutput = eastl::make_unique(texDesc); + texOutput->CreateSRV(srvDesc); + texOutput->CreateUAV(uavDesc); + + texDesc.Format = srvDesc.Format = uavDesc.Format = DXGI_FORMAT_R11G11B10_FLOAT; + + texMoments = eastl::make_unique(texDesc); + texMoments->CreateSRV(srvDesc); + texMoments->CreateUAV(uavDesc); + texHistoryMoments = eastl::make_unique(texDesc); + texHistoryMoments->CreateSRV(srvDesc); + texHistoryMoments->CreateUAV(uavDesc); + + texDesc.Format = srvDesc.Format = uavDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM; + texHistoryNormals = eastl::make_unique(texDesc); + texHistoryNormals->CreateSRV(srvDesc); + texHistoryNormals->CreateUAV(uavDesc); + + texDesc.Format = srvDesc.Format = uavDesc.Format = DXGI_FORMAT_R32_FLOAT; + + texDesc.MipLevels = maxMips; + srvDesc.Texture2D.MipLevels = texDesc.MipLevels; + texDepth = eastl::make_unique(texDesc); + texDepth->CreateSRV(srvDesc); + texDepth->CreateUAV(uavDesc); + + for (uint i = 0; i < maxMips; i++) { + D3D11_SHADER_RESOURCE_VIEW_DESC mipSrvDesc = { + .Format = texDesc.Format, + .ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D, + .Texture2D = { .MostDetailedMip = i, .MipLevels = 1 } + }; + DX::ThrowIfFailed(device->CreateShaderResourceView(texDepth->resource.get(), &mipSrvDesc, depthSRVs[i].put())); + + D3D11_UNORDERED_ACCESS_VIEW_DESC mipUavDesc = { + .Format = texDesc.Format, + .ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D, + .Texture2D = { .MipSlice = i } + }; + DX::ThrowIfFailed(device->CreateUnorderedAccessView(texDepth->resource.get(), &mipUavDesc, depthUAVs[i].put())); + } + } + + logger::debug("Creating samplers..."); + { + D3D11_SAMPLER_DESC samplerDesc = { + .Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR, + .AddressU = D3D11_TEXTURE_ADDRESS_CLAMP, + .AddressV = D3D11_TEXTURE_ADDRESS_CLAMP, + .AddressW = D3D11_TEXTURE_ADDRESS_CLAMP, + .MaxAnisotropy = 1, + .MinLOD = 0, + .MaxLOD = D3D11_FLOAT32_MAX + }; + DX::ThrowIfFailed(device->CreateSamplerState(&samplerDesc, linearSampler.put())); + } + + logger::debug("Loading noise texture..."); + { + DirectX::CreateDDSTextureFromFile(device, globals::d3d::context, L"Data\\Shaders\\StochasticScreenSpaceReflections\\noise.dds", + nullptr, noiseSRV.put()); + } + + CompileComputeShaders(); +} + +void StochasticScreenSpaceReflections::ClearShaderCache() +{ + static const std::vector*> shaderPtrs = { + &raymarchSpecularCS, + &prepareColorCS, + &preprocessDepthCS, + &depthDownsampleCS, + &temporalCS, + &varianceCS, + &spatialCS, + &spatialSpecularCS, + }; + + for (auto shader : shaderPtrs) + *shader = nullptr; + + CompileComputeShaders(); +} + +void StochasticScreenSpaceReflections::CompileComputeShaders() +{ + struct ShaderCompileInfo + { + winrt::com_ptr* programPtr; + std::string_view filename; + std::vector> defines; + }; + + std::vector> defines; + + if (globals::features::dynamicCubemaps.loaded) + defines.push_back({ "DYNAMIC_CUBEMAPS", nullptr }); + + if (globals::features::screenSpaceGI.loaded) + defines.push_back({ "SSGI", nullptr }); + + if (globals::features::skylighting.loaded) + defines.push_back({ "SKYLIGHTING", nullptr }); + + auto definesSpecular = defines; + definesSpecular.push_back({ "SSSR_SPECULAR", nullptr }); + + std::vector + shaderInfos = { + { &raymarchSpecularCS, "sssr_raymarch.hlsl", definesSpecular }, + { &prepareColorCS, "sssr_prepare_color.hlsl", {} }, + { &preprocessDepthCS, "sssr_preprocess_depth.hlsl", {} }, + { &depthDownsampleCS, "sssr_depth_downsample.hlsl", {} }, + { &temporalCS, "sssr_temporal.hlsl", {} }, + { &varianceCS, "sssr_variance.hlsl", {} }, + { &spatialCS, "sssr_spatial.hlsl", {} }, + { &spatialSpecularCS, "sssr_spatial.hlsl", definesSpecular }, + }; + + for (auto& info : shaderInfos) { + auto path = std::filesystem::path("Data\\Shaders\\StochasticScreenSpaceReflections") / info.filename; + if (auto rawPtr = reinterpret_cast(Util::CompileShader(path.c_str(), info.defines, "cs_5_0"))) + info.programPtr->attach(rawPtr); + } +} + +void StochasticScreenSpaceReflections::Prepass() +{ + auto renderer = globals::game::renderer; + auto context = globals::d3d::context; + auto state = globals::state; + + auto depth = renderer->GetDepthStencilData().depthStencils[RE::RENDER_TARGETS_DEPTHSTENCIL::kPOST_ZPREPASS_COPY]; + + float2 size = Util::ConvertToDynamic(state->screenSize); + float2 dispatchCount = { (size.x + 7) / 8, (size.y + 7) / 8 }; + + std::array srvs = { nullptr }; + std::array uavs = { nullptr }; + + auto resetViews = [&]() { + srvs.fill(nullptr); + uavs.fill(nullptr); + + context->CSSetShaderResources(0, (uint)srvs.size(), srvs.data()); + context->CSSetUnorderedAccessViews(0, (uint)uavs.size(), uavs.data(), nullptr); + }; + + std::array samplers = { linearSampler.get() }; + context->CSSetSamplers(0, 1, samplers.data()); + + state->BeginPerfEvent("SSSR Prepass"); + + // preprocess depth + { + uavs.at(0) = texDepth->uav.get(); + srvs.at(4) = depth.depthSRV; + + context->CSSetShaderResources(0, (uint)srvs.size(), srvs.data()); + context->CSSetUnorderedAccessViews(0, (uint)uavs.size(), uavs.data(), nullptr); + context->CSSetShader(preprocessDepthCS.get(), nullptr, 0); + + context->Dispatch((uint)dispatchCount.x, (uint)dispatchCount.y, 1); + + // context->GenerateMips(texDepth->srv.get()); + + resetViews(); + } + + // downsample depth + { + state->BeginPerfEvent("Downsample Depth - HiZ Buffer"); + for (int i = 0; i < maxMips - 1; ++i) { + uavs.at(0) = depthUAVs[i + 1].get(); + srvs.at(0) = depthSRVs[i].get(); + + context->CSSetShaderResources(0, (uint)srvs.size(), srvs.data()); + context->CSSetUnorderedAccessViews(0, (uint)uavs.size(), uavs.data(), nullptr); + context->CSSetShader(depthDownsampleCS.get(), nullptr, 0); + + context->Dispatch((uint)dispatchCount.x >> i, (uint)dispatchCount.y >> i, 1); + resetViews(); + } + state->EndPerfEvent(); + } + + state->EndPerfEvent(); + + auto view = texDepth->srv.get(); + context->PSSetShaderResources(99, 1, &view); +} + +void StochasticScreenSpaceReflections::DrawSSSRSpecular() +{ + if (!settings.EnableSpecular) + return; + + auto renderer = globals::game::renderer; + auto context = globals::d3d::context; + auto state = globals::state; + + state->BeginPerfEvent("SSSR Compute"); + + auto main = renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kMAIN]; + auto depth = renderer->GetDepthStencilData().depthStencils[RE::RENDER_TARGETS_DEPTHSTENCIL::kPOST_ZPREPASS_COPY]; + auto specular = renderer->GetRuntimeData().renderTargets[SPECULAR]; + auto normal = renderer->GetRuntimeData().renderTargets[NORMALROUGHNESS]; + auto motion = renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kMOTION_VECTOR]; + + auto& dynamicCubemaps = globals::features::dynamicCubemaps; + auto& ssgi = globals::features::screenSpaceGI; + auto& skylighting = globals::features::skylighting; + + float2 size = Util::ConvertToDynamic(state->screenSize); + float2 dispatchCount = { (size.x + 7) / 8, (size.y + 7) / 8 }; + + SSSRCB ssrCBData; + { + ssrCBData.MaxSteps = settings.MaxSteps; + ssrCBData.MaxMips = settings.MaxMips; + ssrCBData.Thickness = settings.Thickness; + ssrCBData.NormalBias = settings.NormalBias; + ssrCBData.BRDFBias = settings.BRDFBias; + ssrCBData.UseDynamicCubemapsAsFallback = (uint)settings.UseDynamicCubemapsAsFallbackSpecular && dynamicCubemaps.loaded; + ssrCBData.OcclusionStrength = settings.OcclusionStrength; + ssrCBData.CubemapNormalization = settings.CubemapNormalization; + } + sssrCB->Update(ssrCBData); + auto buffer = sssrCB->CB(); + context->CSSetConstantBuffers(1, 1, &buffer); + + std::array srvs = { nullptr }; + std::array uavs = { nullptr }; + + auto resetViews = [&]() { + srvs.fill(nullptr); + uavs.fill(nullptr); + + context->CSSetShaderResources(0, (uint)srvs.size(), srvs.data()); + context->CSSetUnorderedAccessViews(0, (uint)uavs.size(), uavs.data(), nullptr); + }; + + std::array samplers = { linearSampler.get() }; + + context->CSSetSamplers(0, 1, samplers.data()); + + // prepare color + srvs.at(0) = main.SRV; + srvs.at(1) = specular.SRV; + srvs.at(2) = normal.SRV; + uavs.at(0) = texColor->uav.get(); + + context->CSSetShaderResources(0, (uint)srvs.size(), srvs.data()); + context->CSSetUnorderedAccessViews(0, (uint)uavs.size(), uavs.data(), nullptr); + context->CSSetShader(prepareColorCS.get(), nullptr, 0); + + context->Dispatch((uint)dispatchCount.x, (uint)dispatchCount.y, 1); + + resetViews(); + + const auto envTexture = dynamicCubemaps.loaded ? dynamicCubemaps.envTexture->srv.get() : nullptr; + const auto envReflectionsTexture = dynamicCubemaps.loaded ? dynamicCubemaps.envReflectionsTexture->srv.get() : nullptr; + + auto [ssgi_ao, ssgi_y, ssgi_cocg, ssgi_gi_spec] = ssgi.GetOutputTextures(); + + bool inInterior = true; + + if (auto player = RE::PlayerCharacter::GetSingleton()) { + if (auto parentCell = player->GetParentCell()) { + inInterior = parentCell->IsInteriorCell(); + } + } + + // raymarch + state->BeginPerfEvent("Raymarch"); + + uavs.at(0) = texSSRColor->uav.get(); + uavs.at(1) = texHitPDF->uav.get(); + + srvs.at(0) = texHistory->srv.get(); + srvs.at(1) = motion.SRV; + srvs.at(2) = normal.SRV; + srvs.at(3) = texColor->srv.get(); + srvs.at(4) = depth.depthSRV; + srvs.at(5) = texDepth->srv.get(); + srvs.at(6) = noiseSRV.get(); + srvs.at(7) = envTexture; + srvs.at(8) = inInterior ? envTexture : envReflectionsTexture; + srvs.at(9) = ssgi_ao; + srvs.at(10) = dynamicCubemaps.loaded && skylighting.loaded ? skylighting.texProbeArray->srv.get() : nullptr; + srvs.at(11) = dynamicCubemaps.loaded && skylighting.loaded ? skylighting.stbn_vec3_2Dx1D_128x128x64.get() : nullptr; + + context->CSSetShaderResources(0, (uint)srvs.size(), srvs.data()); + context->CSSetUnorderedAccessViews(0, (uint)uavs.size(), uavs.data(), nullptr); + context->CSSetShader(raymarchSpecularCS.get(), nullptr, 0); + context->CSSetConstantBuffers(1, 1, &buffer); + + context->Dispatch((uint)dispatchCount.x, (uint)dispatchCount.y, 1); + + state->EndPerfEvent(); + + resetViews(); + + if (settings.EnableSVGF) { + DenoiserCB denoiserCBData; + { + denoiserCBData.invMaxAccumulatedFrames = 1.0f / (settings.MaxAccumulatedFrames + 1.0f); + denoiserCBData.atrousIterations = settings.AtrousIterations; + denoiserCBData.colorPhi = settings.ColorPhi; + denoiserCBData.normalPhi = settings.NormalPhi; + } + denoiserCB->Update(denoiserCBData); + auto denoiserBuffer = denoiserCB->CB(); + context->CSSetConstantBuffers(2, 1, &denoiserBuffer); + + // temporal filter + uavs.at(0) = texTemporal->uav.get(); + uavs.at(1) = texMoments->uav.get(); + srvs.at(0) = texHistory->srv.get(); + srvs.at(1) = motion.SRV; + srvs.at(2) = normal.SRV; + srvs.at(3) = texSSRColor->srv.get(); + srvs.at(4) = depth.depthSRV; + srvs.at(5) = texHistoryMoments->srv.get(); + srvs.at(6) = texHistoryNormals->srv.get(); + + context->CSSetShaderResources(0, 7, srvs.data()); + context->CSSetUnorderedAccessViews(0, 2, uavs.data(), nullptr); + context->CSSetShader(temporalCS.get(), nullptr, 0); + + context->Dispatch((uint)dispatchCount.x, (uint)dispatchCount.y, 1); + resetViews(); + + context->CopyResource(texHistoryMoments->resource.get(), texMoments->resource.get()); + + // variance filter + uavs.at(0) = texVariance->uav.get(); + srvs.at(0) = texHistory->srv.get(); + srvs.at(1) = texMoments->srv.get(); + srvs.at(2) = normal.SRV; + srvs.at(3) = texTemporal->srv.get(); + srvs.at(4) = depth.depthSRV; + + context->CSSetShaderResources(0, 5, srvs.data()); + context->CSSetUnorderedAccessViews(0, 1, uavs.data(), nullptr); + context->CSSetShader(varianceCS.get(), nullptr, 0); + + context->Dispatch((uint)dispatchCount.x, (uint)dispatchCount.y, 1); + resetViews(); + + // spatial filter + for (int i = 0; i < (int)settings.AtrousIterations; ++i) { + denoiserCBData.atrousIterations = i; + denoiserCB->Update(denoiserCBData); + denoiserBuffer = denoiserCB->CB(); + context->CSSetConstantBuffers(2, 1, &denoiserBuffer); + uavs.at(0) = (i % 2 == 0) ? texSSRColor->uav.get() : texVariance->uav.get(); + srvs.at(0) = texHistory->srv.get(); + srvs.at(1) = motion.SRV; + srvs.at(2) = normal.SRV; + srvs.at(3) = (i % 2 == 0) ? texVariance->srv.get() : texSSRColor->srv.get(); + srvs.at(4) = depth.depthSRV; + + context->CSSetShaderResources(0, 5, srvs.data()); + context->CSSetUnorderedAccessViews(0, 1, uavs.data(), nullptr); + context->CSSetShader(spatialSpecularCS.get(), nullptr, 0); + + context->Dispatch((uint)dispatchCount.x, (uint)dispatchCount.y, 1); + + resetViews(); + } + + if (settings.AtrousIterations % 2 == 0) { + context->CopyResource(texSSRColor->resource.get(), texVariance->resource.get()); + } + } + + // output + context->CopyResource(texHistoryNormals->resource.get(), normal.texture); + context->CopyResource(texOutput->resource.get(), texSSRColor->resource.get()); + context->CopyResource(texHistory->resource.get(), texSSRColor->resource.get()); + + context->CSSetShader(nullptr, nullptr, 0); + + state->EndPerfEvent(); +} + +StochasticScreenSpaceReflections::SharedData StochasticScreenSpaceReflections::GetCommonBufferData() +{ + SharedData data; + data.EnableSpecular = settings.EnableSpecular; + data.SpecularMult = settings.SpecularMult; + data._padding[0] = 0.0f; + data._padding[1] = 0.0f; + return data; +} \ No newline at end of file diff --git a/src/Features/StochasticScreenSpaceReflections.h b/src/Features/StochasticScreenSpaceReflections.h new file mode 100644 index 0000000000..1e77df73cd --- /dev/null +++ b/src/Features/StochasticScreenSpaceReflections.h @@ -0,0 +1,122 @@ +#pragma once + +struct StochasticScreenSpaceReflections : Feature +{ + static StochasticScreenSpaceReflections* GetSingleton() + { + static StochasticScreenSpaceReflections singleton; + return &singleton; + } + + virtual inline std::string GetName() override { return "Stochastic Screen Space Reflections"; } + virtual inline std::string GetShortName() override { return "StochasticScreenSpaceReflections"; } + virtual inline std::string_view GetShaderDefineName() override { return "SSSR"; } + virtual std::string_view GetCategory() const override { return "Lighting"; } + virtual std::pair> GetFeatureSummary() override + { + return { + "Stochastic Screen Space Reflections provides high-quality specular reflections using screen-space data.", + { "Glossy reflections driven by surface roughness", + "Efficient ray marching with Hi-Z buffer", + "Uses dynamic cubemaps as fallback for missing information", + "Spatiotemporal Variance-Guided Filtering (SVGF) denoiser" } + }; + } + + virtual void RestoreDefaultSettings() override; + virtual void DrawSettings() override; + + virtual void LoadSettings(json& o_json) override; + virtual void SaveSettings(json& o_json) override; + + virtual void SetupResources() override; + virtual void ClearShaderCache() override; + void CompileComputeShaders(); + + bool HasShaderDefine(RE::BSShader::Type) override { return true; }; + virtual bool SupportsVR() override { return true; }; + + struct Settings + { + bool EnableSpecular = true; + uint MaxSteps = 128; + uint MaxMips = 6; + float Thickness = 5.f; + float NormalBias = 0.1f; + float BRDFBias = 0.25f; + bool UseDynamicCubemapsAsFallbackSpecular = true; + float SpecularMult = 1.0f; + float OcclusionStrength = 1.0f; + float CubemapNormalization = 0.0f; + bool EnableSVGF = false; + uint MaxAccumulatedFrames = 16; + uint AtrousIterations = 3; + float ColorPhi = 0.5f; + float NormalPhi = 512.0f; + } settings; + + struct alignas(16) SharedData + { + uint EnableSpecular; + float SpecularMult; + float _padding[2]; + }; + + struct alignas(16) SSSRCB + { + uint MaxSteps; + uint MaxMips; + uint UseDynamicCubemapsAsFallback; + float Thickness; + float NormalBias; + float BRDFBias; + float OcclusionStrength; + float CubemapNormalization; + }; + + struct alignas(16) DenoiserCB + { + float invMaxAccumulatedFrames; + uint atrousIterations; + float colorPhi; + float normalPhi; + }; + + eastl::unique_ptr sssrCB; + eastl::unique_ptr denoiserCB; + + void DrawSSSRSpecular(); + virtual void Prepass() override; + + SharedData GetCommonBufferData(); + + eastl::unique_ptr texDepth = nullptr; + eastl::unique_ptr texColor = nullptr; + eastl::unique_ptr texSSRColor = nullptr; + eastl::unique_ptr texHitPDF = nullptr; + eastl::unique_ptr texHistory = nullptr; + eastl::unique_ptr texTemporal = nullptr; + eastl::unique_ptr texMoments = nullptr; + eastl::unique_ptr texHistoryMoments = nullptr; + eastl::unique_ptr texHistoryNormals = nullptr; + eastl::unique_ptr texVariance = nullptr; + eastl::unique_ptr texOutput = nullptr; + + winrt::com_ptr noiseSRV = nullptr; + + static const uint maxMips = 9; + + std::array, maxMips> depthSRVs = { nullptr }; + std::array, maxMips> depthUAVs = { nullptr }; + + winrt::com_ptr linearSampler = nullptr; + + winrt::com_ptr preprocessDepthCS = nullptr; + winrt::com_ptr raymarchSpecularCS = nullptr; + winrt::com_ptr prepareColorCS = nullptr; + winrt::com_ptr depthDownsampleCS = nullptr; + winrt::com_ptr temporalCS = nullptr; + winrt::com_ptr varianceCS = nullptr; + winrt::com_ptr spatialCS = nullptr; + winrt::com_ptr spatialSpecularCS = nullptr; +}; diff --git a/src/Globals.cpp b/src/Globals.cpp index c5883bfc24..8a7196883c 100644 --- a/src/Globals.cpp +++ b/src/Globals.cpp @@ -20,6 +20,7 @@ #include "Features/ScreenSpaceShadows.h" #include "Features/SkySync.h" #include "Features/Skylighting.h" +#include "Features/StochasticScreenSpaceReflections.h" #include "Features/SubsurfaceScattering.h" #include "Features/TerrainBlending.h" #include "Features/TerrainHelper.h" @@ -63,6 +64,7 @@ namespace globals InteriorSun interiorSun{}; InverseSquareLighting inverseSquareLighting{}; ScreenSpaceGI screenSpaceGI{}; + StochasticScreenSpaceReflections screenSpaceRayTracing{}; ScreenSpaceShadows screenSpaceShadows{}; Skylighting skylighting{}; TerrainVariation terrainVariation{}; diff --git a/src/Globals.h b/src/Globals.h index e481b9f56e..ce2484be6c 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -13,6 +13,7 @@ struct LODBlending; struct InteriorSun; struct InverseSquareLighting; struct ScreenSpaceGI; +struct StochasticScreenSpaceReflections; struct ScreenSpaceShadows; struct Skylighting; struct TerrainVariation; @@ -68,6 +69,7 @@ namespace globals extern InteriorSun interiorSun; extern InverseSquareLighting inverseSquareLighting; extern ScreenSpaceGI screenSpaceGI; + extern StochasticScreenSpaceReflections screenSpaceRayTracing; extern ScreenSpaceShadows screenSpaceShadows; extern Skylighting skylighting; extern TerrainVariation terrainVariation;