|
|
@@ -5,7 +5,7 @@
|
|
|
|
|
|
// Ground truth ambiend occlusion
|
|
|
|
|
|
-#pragma anki mutator SAMPLE_COUNT 3 5 7 9 11 13 15
|
|
|
+#pragma anki mutator SPATIAL_DENOISE_QUALITY 0 1
|
|
|
|
|
|
#include <AnKi/Shaders/Common.hlsl>
|
|
|
|
|
|
@@ -26,12 +26,8 @@
|
|
|
[[vk::binding(3)]] SamplerState g_trilinearRepeatSampler;
|
|
|
[[vk::binding(4)]] SamplerState g_linearAnyClampSampler;
|
|
|
|
|
|
-[[vk::binding(5)]] Texture2D<RVec4> g_historyTex;
|
|
|
-[[vk::binding(6)]] Texture2D<Vec4> g_motionVectorsTex;
|
|
|
-[[vk::binding(7)]] Texture2D<RVec4> g_historyLengthTex;
|
|
|
-
|
|
|
# if ANKI_COMPUTE_SHADER
|
|
|
-[[vk::binding(8)]] RWTexture2D<RVec4> g_outUav;
|
|
|
+[[vk::binding(5)]] RWTexture2D<Vec4> g_outUav;
|
|
|
# endif
|
|
|
|
|
|
[[vk::push_constant]] ConstantBuffer<SsaoConstants> g_consts;
|
|
|
@@ -56,7 +52,7 @@ RF32 computeFalloff(RF32 len)
|
|
|
# if ANKI_COMPUTE_SHADER
|
|
|
[numthreads(8, 8, 1)] void main(UVec2 svDispatchThreadId : SV_DISPATCHTHREADID)
|
|
|
# else
|
|
|
-RF32 main([[vk::location(0)]] Vec2 uv : TEXCOORD, Vec4 svPosition : SV_POSITION) : SV_TARGET0
|
|
|
+Vec2 main([[vk::location(0)]] Vec2 uv : TEXCOORD, Vec4 svPosition : SV_POSITION) : SV_TARGET0
|
|
|
# endif
|
|
|
{
|
|
|
# if ANKI_COMPUTE_SHADER
|
|
|
@@ -67,7 +63,8 @@ RF32 main([[vk::location(0)]] Vec2 uv : TEXCOORD, Vec4 svPosition : SV_POSITION)
|
|
|
# endif
|
|
|
|
|
|
const Vec2 ndc = uvToNdc(uv);
|
|
|
- const Vec3 Pc = unproject(ndc);
|
|
|
+ const F32 depth = g_depthTex.SampleLevel(g_linearAnyClampSampler, uv, 0.0).r;
|
|
|
+ const Vec3 Pc = cheapPerspectiveUnprojection(g_consts.m_unprojectionParameters, ndc, depth);
|
|
|
const RVec3 V = normalize(-Pc); // View vector
|
|
|
|
|
|
// Get noise
|
|
|
@@ -151,116 +148,174 @@ RF32 main([[vk::location(0)]] Vec2 uv : TEXCOORD, Vec4 svPosition : SV_POSITION)
|
|
|
// Apply power
|
|
|
Vd = pow(Vd, g_consts.m_ssaoPower);
|
|
|
|
|
|
- // Blend color with history
|
|
|
- {
|
|
|
- const Vec2 historyUv = uv + g_motionVectorsTex.SampleLevel(g_linearAnyClampSampler, uv, 0.0f).xy;
|
|
|
-
|
|
|
- // History length creates black trails so it doesn't work correctly
|
|
|
-# if 0
|
|
|
- const RVec4 historyLengths = g_historyLengthTex.GatherRed(g_linearAnyClampSampler, uv + g_consts.m_prevJitterUv);
|
|
|
- const RF32 historyLength = max4(historyLengths);
|
|
|
-# else
|
|
|
- const RF32 historyLength = (any(historyUv < 0.0f) || any(historyUv > 1.0f)) ? 0.0f : 1.0f;
|
|
|
-# endif
|
|
|
-
|
|
|
- const RF32 lowestBlendFactor = 0.1f;
|
|
|
- const RF32 maxHistoryLength = 16.0f;
|
|
|
- const RF32 stableFrames = 4.0f;
|
|
|
- const RF32 lerpVal = min(1.0f, (historyLength * maxHistoryLength - 1.0f) / stableFrames);
|
|
|
- const RF32 blendFactor = lerp(1.0f, lowestBlendFactor, lerpVal);
|
|
|
-
|
|
|
- // Blend with history
|
|
|
- if(blendFactor < 1.0)
|
|
|
- {
|
|
|
- const RF32 history = g_historyTex.SampleLevel(g_linearAnyClampSampler, historyUv, 0.0f).r;
|
|
|
- Vd = lerp(history, Vd, blendFactor);
|
|
|
- }
|
|
|
- }
|
|
|
+ const F32 linearDepth = linearizeDepthOptimal(depth, g_consts.m_linearizeDepthParams.x, g_consts.m_linearizeDepthParams.y);
|
|
|
|
|
|
# if ANKI_COMPUTE_SHADER
|
|
|
- g_outUav[svDispatchThreadId] = Vd;
|
|
|
+ g_outUav[svDispatchThreadId] = Vec4(Vd, linearDepth, 0.0f, 0.0f);
|
|
|
# else
|
|
|
- return Vd;
|
|
|
+ return Vec2(Vd, linearDepth);
|
|
|
# endif
|
|
|
}
|
|
|
#endif // ANKI_TECHNIQUE_Ssao && (ANKI_COMPUTE_SHADER || ANKI_FRAGMENT_SHADER)
|
|
|
|
|
|
// ===========================================================================
|
|
|
-// SSAO denoise =
|
|
|
+// SSAO spatial denoise =
|
|
|
// ===========================================================================
|
|
|
-#if(ANKI_TECHNIQUE_SsaoDenoiseVertical || ANKI_TECHNIQUE_SsaoDenoiseHorizontal) && (ANKI_COMPUTE_SHADER || ANKI_FRAGMENT_SHADER)
|
|
|
+#if ANKI_TECHNIQUE_SsaoSpatialDenoise && (ANKI_COMPUTE_SHADER || ANKI_FRAGMENT_SHADER)
|
|
|
# include <AnKi/Shaders/BilateralFilter.hlsl>
|
|
|
|
|
|
[[vk::binding(0)]] SamplerState g_linearAnyClampSampler;
|
|
|
-[[vk::binding(1)]] Texture2D<RVec4> g_inTex;
|
|
|
-[[vk::binding(2)]] Texture2D<Vec4> g_depthTex;
|
|
|
+[[vk::binding(1)]] Texture2D<Vec4> g_inTex;
|
|
|
|
|
|
# if ANKI_COMPUTE_SHADER
|
|
|
-[[vk::binding(3)]] RWTexture2D<RVec4> g_outImg;
|
|
|
+[[vk::binding(2)]] RWTexture2D<RVec4> g_outImg;
|
|
|
# endif
|
|
|
|
|
|
-F32 readDepth(Vec2 uv)
|
|
|
+F32 computeWeight(F32 depth, F32 refDepth)
|
|
|
{
|
|
|
- return g_depthTex.SampleLevel(g_linearAnyClampSampler, uv, 0.0).x;
|
|
|
+ const F32 diff = abs(depth - refDepth);
|
|
|
+ return sqrt(1.0 / (0.0003 + diff));
|
|
|
}
|
|
|
|
|
|
-void sampleTex(Vec2 uv, F32 refDepth, inout RF32 col, inout RF32 weight)
|
|
|
+void sampleTex(Vec2 uv, IVec2 offset, F32 refDepth, inout RF32 col, inout F32 weight)
|
|
|
{
|
|
|
- const RF32 color = g_inTex.SampleLevel(g_linearAnyClampSampler, uv, 0.0).x;
|
|
|
- const RF32 w = calculateBilateralWeightDepth(refDepth, readDepth(uv), 1.0f);
|
|
|
- col += color * w;
|
|
|
+ const Vec2 ssaoWithDepth = g_inTex.SampleLevel(g_linearAnyClampSampler, uv, 0.0, offset).xy;
|
|
|
+ const F32 w = computeWeight(refDepth, ssaoWithDepth.y);
|
|
|
+ col += ssaoWithDepth.x * w;
|
|
|
weight += w;
|
|
|
}
|
|
|
|
|
|
# if ANKI_COMPUTE_SHADER
|
|
|
[numthreads(8, 8, 1)] void main(UVec2 svDispatchThreadId : SV_DISPATCHTHREADID)
|
|
|
# else
|
|
|
-F32 main([[vk::location(0)]] Vec2 uv : TEXCOORD) : SV_TARGET0
|
|
|
+RF32 main([[vk::location(0)]] Vec2 uv : TEXCOORD) : SV_TARGET0
|
|
|
# endif
|
|
|
{
|
|
|
- UVec2 textureSize;
|
|
|
+// Set UVs
|
|
|
+# if ANKI_COMPUTE_SHADER
|
|
|
+ Vec2 textureSize;
|
|
|
U32 mipCount;
|
|
|
g_inTex.GetDimensions(0, textureSize.x, textureSize.y, mipCount);
|
|
|
+ const Vec2 uv = (Vec2(svDispatchThreadId) + 0.5f) / textureSize;
|
|
|
+# endif
|
|
|
+
|
|
|
+ // Sample ref
|
|
|
+ const Vec2 refSsaoAndDepth = g_inTex.SampleLevel(g_linearAnyClampSampler, uv, 0.0).xy;
|
|
|
+ RF32 color = refSsaoAndDepth.x;
|
|
|
+ const F32 refDepth = refSsaoAndDepth.y;
|
|
|
+ F32 weight = computeWeight(0.0f, 0.0f); // Highest weight that this function can give
|
|
|
+
|
|
|
+ // Sample taps
|
|
|
+ sampleTex(uv, IVec2(1, 1), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(0, 1), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(-1, 1), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(-1, 0), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(-1, -1), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(0, -1), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(1, -1), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(1, 0), refDepth, color, weight);
|
|
|
+
|
|
|
+# if SPATIAL_DENOISE_QUALITY == 1
|
|
|
+ sampleTex(uv, IVec2(2, 2), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(0, 2), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(-2, 2), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(-2, 0), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(-2, -2), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(0, -2), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(2, -2), refDepth, color, weight);
|
|
|
+ sampleTex(uv, IVec2(2, 0), refDepth, color, weight);
|
|
|
+# endif
|
|
|
|
|
|
- // Set UVs
|
|
|
+ color /= weight;
|
|
|
+
|
|
|
+ // Write value
|
|
|
# if ANKI_COMPUTE_SHADER
|
|
|
- const Vec2 uv = (Vec2(svDispatchThreadId) + 0.5) / Vec2(textureSize);
|
|
|
+ g_outImg[svDispatchThreadId] = color;
|
|
|
+# else
|
|
|
+ return color;
|
|
|
# endif
|
|
|
+}
|
|
|
+#endif
|
|
|
|
|
|
- const Vec2 texelSize = 1.0 / Vec2(textureSize);
|
|
|
+// ===========================================================================
|
|
|
+// SSAO temporal denoise =
|
|
|
+// ===========================================================================
|
|
|
+#if ANKI_TECHNIQUE_SsaoTemporalDenoise && (ANKI_COMPUTE_SHADER || ANKI_FRAGMENT_SHADER)
|
|
|
+# include <AnKi/Shaders/Functions.hlsl>
|
|
|
|
|
|
- // Sample
|
|
|
- RF32 color = g_inTex.SampleLevel(g_linearAnyClampSampler, uv, 0.0).r;
|
|
|
- const F32 refDepth = readDepth(uv);
|
|
|
- RF32 weight = 1.0;
|
|
|
+[[vk::binding(0)]] SamplerState g_linearAnyClampSampler;
|
|
|
+[[vk::binding(1)]] Texture2D<RVec4> g_ssaoTex;
|
|
|
+[[vk::binding(2)]] Texture2D<RVec4> g_historySsaoTex;
|
|
|
+[[vk::binding(3)]] Texture2D<Vec4> g_motionVectorsTex;
|
|
|
|
|
|
-# if ANKI_TECHNIQUE_SsaoDenoiseHorizontal
|
|
|
-# define X_OR_Y x
|
|
|
+# if ANKI_COMPUTE_SHADER
|
|
|
+[[vk::binding(4)]] RWTexture2D<RVec4> g_outImg;
|
|
|
+# endif
|
|
|
+
|
|
|
+# if ANKI_COMPUTE_SHADER
|
|
|
+[numthreads(8, 8, 1)] void main(UVec2 svDispatchThreadId : SV_DISPATCHTHREADID)
|
|
|
# else
|
|
|
-# define X_OR_Y y
|
|
|
+RF32 main([[vk::location(0)]] Vec2 uv : TEXCOORD) : SV_TARGET0
|
|
|
+# endif
|
|
|
+{
|
|
|
+# if ANKI_COMPUTE_SHADER
|
|
|
+ Vec2 textureSize;
|
|
|
+ U32 mipCount;
|
|
|
+ g_ssaoTex.GetDimensions(0, textureSize.x, textureSize.y, mipCount);
|
|
|
+ const Vec2 uv = (Vec2(svDispatchThreadId) + 0.5f) / textureSize;
|
|
|
# endif
|
|
|
|
|
|
- Vec2 uvOffset = 0.0f;
|
|
|
- uvOffset.X_OR_Y = 1.0f * texelSize.X_OR_Y;
|
|
|
+ const Vec2 historyUv = uv + g_motionVectorsTex.SampleLevel(g_linearAnyClampSampler, uv, 0.0f).xy;
|
|
|
|
|
|
- [unroll] for(U32 i = 0u; i < (SAMPLE_COUNT - 1u) / 2u; ++i)
|
|
|
- {
|
|
|
- sampleTex(uv + uvOffset, refDepth, color, weight);
|
|
|
- sampleTex(uv - uvOffset, refDepth, color, weight);
|
|
|
+ // Read textures
|
|
|
+ RF32 history = g_historySsaoTex.SampleLevel(g_linearAnyClampSampler, historyUv, 0.0f).x;
|
|
|
+ RF32 current = g_ssaoTex.SampleLevel(g_linearAnyClampSampler, uv, 0.0f).x;
|
|
|
|
|
|
- uvOffset.X_OR_Y += 1.0f * texelSize.X_OR_Y;
|
|
|
- }
|
|
|
+ // Remove ghosting by clamping the history color to neighbour's AABB
|
|
|
+ const RF32 near0 = g_ssaoTex.SampleLevel(g_linearAnyClampSampler, uv, 0, IVec2(1, 0)).x;
|
|
|
+ const RF32 near1 = g_ssaoTex.SampleLevel(g_linearAnyClampSampler, uv, 0, IVec2(0, 1)).x;
|
|
|
+ const RF32 near2 = g_ssaoTex.SampleLevel(g_linearAnyClampSampler, uv, 0, IVec2(-1, 0)).x;
|
|
|
+ const RF32 near3 = g_ssaoTex.SampleLevel(g_linearAnyClampSampler, uv, 0, IVec2(0, -1)).x;
|
|
|
|
|
|
- color /= weight;
|
|
|
+# if 0
|
|
|
+ const RF32 boxMin = min(current, min4(near0, near1, near2, near3));
|
|
|
+ const RF32 boxMax = max(current, max4(near0, near1, near2, near3));
|
|
|
+# else
|
|
|
+ const RF32 m1 = current + near0 + near1 + near2 + near3;
|
|
|
+ const RF32 m2 = current * current + near0 * near0 + near1 * near1 + near2 * near2 + near3 * near3;
|
|
|
+
|
|
|
+ const RF32 mu = m1 / 5.0;
|
|
|
+ const RF32 sigma = sqrt(m2 / 5.0 - mu * mu);
|
|
|
+
|
|
|
+ const F32 varianceClippingGamma = 1.2f;
|
|
|
+ const RF32 boxMin = mu - varianceClippingGamma * sigma;
|
|
|
+ const RF32 boxMax = mu + varianceClippingGamma * sigma;
|
|
|
+# endif
|
|
|
+
|
|
|
+ history = clamp(history, boxMin, boxMax);
|
|
|
+
|
|
|
+ // Final
|
|
|
+ const RF32 kBlendFactor = 0.1f;
|
|
|
+
|
|
|
+ const F32 lum0 = current;
|
|
|
+ const F32 lum1 = history;
|
|
|
+ const F32 maxLum = 1.0;
|
|
|
+
|
|
|
+ RF32 diff = abs(lum0 - lum1) / max(lum0, max(lum1, maxLum + kEpsilonF32));
|
|
|
+ diff = 1.0 - diff;
|
|
|
+ diff = diff * diff;
|
|
|
+ const RF32 feedback = lerp(0.0, kBlendFactor, diff);
|
|
|
+
|
|
|
+ const RF32 ssao = lerp(history, current, feedback);
|
|
|
|
|
|
// Write value
|
|
|
# if ANKI_COMPUTE_SHADER
|
|
|
- g_outImg[svDispatchThreadId] = color;
|
|
|
+ g_outImg[svDispatchThreadId] = ssao;
|
|
|
# else
|
|
|
- return color;
|
|
|
+ return ssao;
|
|
|
# endif
|
|
|
}
|
|
|
-#endif // (ANKI_TECHNIQUE_SsaoDenoiseVertical || ANKI_TECHNIQUE_SsaoDenoiseHorizontal) && (ANKI_COMPUTE_SHADER || ANKI_FRAGMENT_SHADER)
|
|
|
+#endif
|
|
|
|
|
|
// ===========================================================================
|
|
|
// Techniques =
|
|
|
@@ -275,22 +330,22 @@ F32 main([[vk::location(0)]] Vec2 uv : TEXCOORD) : SV_TARGET0
|
|
|
#pragma anki technique_start comp Ssao
|
|
|
#pragma anki technique_end comp Ssao
|
|
|
|
|
|
-#pragma anki technique_start vert SsaoDenoiseVertical
|
|
|
+#pragma anki technique_start vert SsaoSpatialDenoise
|
|
|
#include <AnKi/Shaders/QuadVert.hlsl>
|
|
|
-#pragma anki technique_end vert SsaoDenoiseVertical
|
|
|
+#pragma anki technique_end vert SsaoSpatialDenoise
|
|
|
|
|
|
-#pragma anki technique_start frag SsaoDenoiseVertical
|
|
|
-#pragma anki technique_end frag SsaoDenoiseVertical
|
|
|
+#pragma anki technique_start frag SsaoSpatialDenoise
|
|
|
+#pragma anki technique_end frag SsaoSpatialDenoise
|
|
|
|
|
|
-#pragma anki technique_start comp SsaoDenoiseVertical
|
|
|
-#pragma anki technique_end comp SsaoDenoiseVertical
|
|
|
+#pragma anki technique_start comp SsaoSpatialDenoise
|
|
|
+#pragma anki technique_end comp SsaoSpatialDenoise
|
|
|
|
|
|
-#pragma anki technique_start vert SsaoDenoiseHorizontal
|
|
|
+#pragma anki technique_start vert SsaoTemporalDenoise
|
|
|
#include <AnKi/Shaders/QuadVert.hlsl>
|
|
|
-#pragma anki technique_end vert SsaoDenoiseHorizontal
|
|
|
+#pragma anki technique_end vert SsaoTemporalDenoise
|
|
|
|
|
|
-#pragma anki technique_start frag SsaoDenoiseHorizontal
|
|
|
-#pragma anki technique_end frag SsaoDenoiseHorizontal
|
|
|
+#pragma anki technique_start frag SsaoTemporalDenoise
|
|
|
+#pragma anki technique_end frag SsaoTemporalDenoise
|
|
|
|
|
|
-#pragma anki technique_start comp SsaoDenoiseHorizontal
|
|
|
-#pragma anki technique_end comp SsaoDenoiseHorizontal
|
|
|
+#pragma anki technique_start comp SsaoTemporalDenoise
|
|
|
+#pragma anki technique_end comp SsaoTemporalDenoise
|