264 lines
8.6 KiB
GLSL
264 lines
8.6 KiB
GLSL
// Copyright (c) Wojciech Figat. All rights reserved.
|
|
|
|
// Implementation based on:
|
|
// "Temporal Reprojection Anti-Aliasing in INSIDE", Lasse Jon Fuglsang Pedersen at GDC 2026
|
|
// Source: https://github.com/playdeadgames/temporal, MIT Licence, 2015 Playdead
|
|
|
|
// Configs
|
|
#define UNJITTER_INPUT 1
|
|
#define UNJITTER_NEIGHBORHOOD 0
|
|
#define UNJITTER_VELOCITY_DEPTH 0
|
|
#define MINMAX_3X3 1
|
|
//#define MINMAX_3X3_ROUNDED 1
|
|
//#define MINMAX_4TAP_VARYING 1
|
|
#define DEBUG_LUMINANCE_DIFF 0
|
|
#define DEBUG_MOTION 0
|
|
#define DEBUG_VELOCITY_REJECTION 0
|
|
|
|
#define NO_GBUFFER_SAMPLING
|
|
#define NEED_DEPTH_VELOCITY (MINMAX_4TAP_VARYING)
|
|
#if NEED_DEPTH_VELOCITY
|
|
#define VelocityDepth float3
|
|
#else
|
|
#define VelocityDepth float2
|
|
#endif
|
|
|
|
#include "./Flax/Common.hlsl"
|
|
#include "./Flax/GBuffer.hlsl"
|
|
#include "./Flax/Noise.hlsl"
|
|
#include "./Flax/Temporal.hlsl"
|
|
|
|
META_CB_BEGIN(0, Data)
|
|
float2 ScreenSizeInv;
|
|
float2 JitterInv;
|
|
float2 MotionScale;
|
|
float StationaryBlending;
|
|
float MotionBlending;
|
|
float3 QuantizationError;
|
|
float Sharpness;
|
|
GBufferData GBuffer;
|
|
META_CB_END
|
|
|
|
Texture2D Input : register(t0);
|
|
Texture2D InputHistory : register(t1);
|
|
Texture2D MotionVectors : register(t2);
|
|
Texture2D Depth : register(t3);
|
|
|
|
// Samples nearby pixels and returns (uv, raw depth) of the closest one to camera.
|
|
float3 FindClosestDepth3x3(float2 uv)
|
|
{
|
|
float2 du = float2(ScreenSizeInv.x, 0.0);
|
|
float2 dv = float2(0.0, ScreenSizeInv.y);
|
|
|
|
float3 dtl = float3(-1, -1, SAMPLE_RT_DEPTH(Depth, uv - dv - du));
|
|
float3 dtc = float3( 0, -1, SAMPLE_RT_DEPTH(Depth, uv - dv));
|
|
float3 dtr = float3( 1, -1, SAMPLE_RT_DEPTH(Depth, uv - dv + du));
|
|
|
|
float3 dml = float3(-1, 0, SAMPLE_RT_DEPTH(Depth, uv - du));
|
|
float3 dmc = float3( 0, 0, SAMPLE_RT_DEPTH(Depth, uv).x);
|
|
float3 dmr = float3( 1, 0, SAMPLE_RT_DEPTH(Depth, uv + du));
|
|
|
|
float3 dbl = float3(-1, 1, SAMPLE_RT_DEPTH(Depth, uv + dv - du));
|
|
float3 dbc = float3( 0, 1, SAMPLE_RT_DEPTH(Depth, uv + dv));
|
|
float3 dbr = float3( 1, 1, SAMPLE_RT_DEPTH(Depth, uv + dv + du));
|
|
|
|
float3 dmin = dtl;
|
|
#if REVERSE_Z
|
|
#define FIND_MIN(a, b) a < b
|
|
#else
|
|
#define FIND_MIN(a, b) a > b
|
|
#endif
|
|
if (FIND_MIN(dmin.z, dtc.z)) dmin = dtc;
|
|
if (FIND_MIN(dmin.z, dtr.z)) dmin = dtr;
|
|
|
|
if (FIND_MIN(dmin.z, dml.z)) dmin = dml;
|
|
if (FIND_MIN(dmin.z, dmc.z)) dmin = dmc;
|
|
if (FIND_MIN(dmin.z, dmr.z)) dmin = dmr;
|
|
|
|
if (FIND_MIN(dmin.z, dbl.z)) dmin = dbl;
|
|
if (FIND_MIN(dmin.z, dbc.z)) dmin = dbc;
|
|
if (FIND_MIN(dmin.z, dbr.z)) dmin = dbr;
|
|
#undef FIND_MIN
|
|
|
|
return float3(uv + ScreenSizeInv.xy * dmin.xy, dmin.z);
|
|
}
|
|
|
|
// Samples nearby pixels and returns (velocity, uv) of the pixel with largest velocity.
|
|
float4 FindLargestVelocity(float2 uv, int range)
|
|
{
|
|
float4 result = float4(0, 0, uv.x, uv.y);
|
|
float vLargest = 0.0f;
|
|
for (int y = -range; y <= range; y++)
|
|
{
|
|
for (int x = -range; x <= range; x++)
|
|
{
|
|
float2 vUV = uv + ScreenSizeInv * float2(x, y);
|
|
float2 v = SAMPLE_RT_LINEAR(MotionVectors, vUV).xy;
|
|
float vSize = dot(v, v);
|
|
if (vSize > vLargest)
|
|
{
|
|
result = float4(v, vUV);
|
|
vLargest = vSize;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
VelocityDepth SampleVelocityDepth(float2 uv)
|
|
{
|
|
#if UNJITTER_VELOCITY_DEPTH
|
|
uv -= JitterInv.xy;
|
|
#endif
|
|
VelocityDepth velocityDepth;
|
|
|
|
#if QUALITY >= 2
|
|
|
|
// 3x3 search for the largest velocity
|
|
float4 largestVelocity = FindLargestVelocity(uv, 3);
|
|
velocityDepth.xy = largestVelocity.xy;
|
|
#if NEED_DEPTH_VELOCITY
|
|
velocityDepth.z = SAMPLE_RT_DEPTH(Depth, largestVelocity.zw);
|
|
#endif
|
|
|
|
#elif QUALITY >= 1
|
|
|
|
// 3x3 search of the closest depth
|
|
float3 closestDepth = FindClosestDepth3x3(uv);
|
|
velocityDepth.xy = SAMPLE_RT_LINEAR(MotionVectors, closestDepth.xy).xy;
|
|
#if NEED_DEPTH_VELOCITY
|
|
velocityDepth.z = closestDepth.z;
|
|
#endif
|
|
|
|
#else // QUALITY == 0
|
|
|
|
// Current sample
|
|
velocityDepth.xy = SAMPLE_RT_LINEAR(MotionVectors, uv).xy;
|
|
#if NEED_DEPTH_VELOCITY
|
|
velocityDepth.z = SAMPLE_RT_DEPTH(Depth, uv);
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#if NEED_DEPTH_VELOCITY
|
|
// Linearize depth
|
|
velocityDepth.z = LinearizeZ(GBuffer, velocityDepth.z);
|
|
#endif
|
|
|
|
return velocityDepth;
|
|
}
|
|
|
|
// Pixel Shader for Temporal Anti-Aliasing
|
|
META_PS(true, FEATURE_LEVEL_ES2)
|
|
META_PERMUTATION_1(QUALITY=0)
|
|
META_PERMUTATION_1(QUALITY=1)
|
|
META_PERMUTATION_1(QUALITY=2)
|
|
float4 PS(Quad_VS2PS input) : SV_Target0
|
|
{
|
|
// Sample velocity and depth
|
|
VelocityDepth velocityDepth = SampleVelocityDepth(input.TexCoord);
|
|
|
|
// Sample image and history
|
|
#if UNJITTER_INPUT
|
|
float4 current = SAMPLE_RT(Input, input.TexCoord - JitterInv.xy);
|
|
#else
|
|
float4 current = SAMPLE_RT(Input, input.TexCoord);
|
|
#endif
|
|
float4 history = SAMPLE_RT_LINEAR(InputHistory, input.TexCoord - velocityDepth.xy);
|
|
|
|
// Calculate min-max of current pixel neighbourhood
|
|
#if UNJITTER_NEIGHBORHOOD
|
|
float2 uv = input.TexCoord - JitterInv.xy;
|
|
#else
|
|
float2 uv = input.TexCoord;
|
|
#endif
|
|
#if MINMAX_3X3 || MINMAX_3X3_ROUNDED
|
|
float2 du = float2(ScreenSizeInv.x, 0.0);
|
|
float2 dv = float2(0.0, ScreenSizeInv.y);
|
|
|
|
float4 ctl = SAMPLE_RT(Input, uv - dv - du);
|
|
float4 ctc = SAMPLE_RT(Input, uv - dv);
|
|
float4 ctr = SAMPLE_RT(Input, uv - dv + du);
|
|
float4 cml = SAMPLE_RT(Input, uv - du);
|
|
float4 cmc = SAMPLE_RT(Input, uv);
|
|
float4 cmr = SAMPLE_RT(Input, uv + du);
|
|
float4 cbl = SAMPLE_RT(Input, uv + dv - du);
|
|
float4 cbc = SAMPLE_RT(Input, uv + dv);
|
|
float4 cbr = SAMPLE_RT(Input, uv + dv + du);
|
|
|
|
float4 cMin = min(ctl, min(ctc, min(ctr, min(cml, min(cmc, min(cmr, min(cbl, min(cbc, cbr))))))));
|
|
float4 cMax = max(ctl, max(ctc, max(ctr, max(cml, max(cmc, max(cmr, max(cbl, max(cbc, cbr))))))));
|
|
float4 cAvg = (ctl + ctc + ctr + cml + cmc + cmr + cbl + cbc + cbr) / 9.0f;
|
|
float4 corners = 4.0f * (ctl + cbr) - 2.0f * current;
|
|
|
|
#if MINMAX_3X3_ROUNDED
|
|
float4 cMin5 = min(ctc, min(cml, min(cmc, min(cmr, cbc))));
|
|
float4 cMax5 = max(ctc, max(cml, max(cmc, max(cmr, cbc))));
|
|
float4 cAvg5 = (ctc + cml + cmc + cmr + cbc) / 5.0;
|
|
cMin = 0.5 * (cMin + cMin5);
|
|
cMax = 0.5 * (cMax + cMax5);
|
|
cAvg = 0.5 * (cAvg + cAvg5);
|
|
#endif
|
|
#elif MINMAX_4TAP_VARYING
|
|
const float SubpixelThreshold = 0.5;
|
|
const float GatherBase = 0.5;
|
|
const float GatherSubpixelMotion = 0.1666;
|
|
|
|
float velocityMagnitude = length(velocityDepth.xy / ScreenSizeInv.xy) * velocityDepth.z;
|
|
float subpixelMotion = saturate(SubpixelThreshold / (velocityMagnitude + TAA_EPSILON));
|
|
float minMaxSupport = GatherBase + GatherSubpixelMotion * subpixelMotion;
|
|
|
|
float2 ssOffset01 = minMaxSupport * float2(-ScreenSizeInv.x, ScreenSizeInv.y);
|
|
float2 ssOffset11 = minMaxSupport * float2(ScreenSizeInv.x, ScreenSizeInv.y);
|
|
float4 c00 = SAMPLE_RT_LINEAR(Input, uv - ssOffset11);
|
|
float4 c10 = SAMPLE_RT_LINEAR(Input, uv - ssOffset01);
|
|
float4 c01 = SAMPLE_RT_LINEAR(Input, uv + ssOffset01);
|
|
float4 c11 = SAMPLE_RT_LINEAR(Input, uv + ssOffset11);
|
|
float4 corners = 4.0f * (c00 + c11) - 2.0f * current;
|
|
|
|
float4 cMin = min(c00, min(c10, min(c01, c11)));
|
|
float4 cMax = max(c00, max(c10, max(c01, c11)));
|
|
float4 cAvg = (c00 + c10 + c01 + c11) / 4.0f;
|
|
#endif
|
|
|
|
// Apply sharpening
|
|
current += (current - (corners * 0.166667)) * Sharpness;
|
|
current = clamp(current, 0, HDR_CLAMP_MAX);
|
|
|
|
// Clamp current sample to neighbourhood pixels nearby colors to reduce ghosting artifacts
|
|
history = ClipAAB(cMin.xyz, cMax.xyz, clamp(cAvg, cMin, cMax), history);
|
|
//history = clamp(history, cMin, cMax);
|
|
|
|
// Calculate history weight from unbiased luminance diff
|
|
float historyBlend = TemporalHistoryWeight(current, history, MotionBlending);
|
|
#if DEBUG_LUMINANCE_DIFF
|
|
return historyBlend.xxxx;
|
|
#endif
|
|
|
|
// Higher history blend when there is no motion
|
|
float motion = saturate(length(velocityDepth.xy) * 1000.0f);
|
|
#if DEBUG_MOTION
|
|
return motion.xxxx;
|
|
#endif
|
|
historyBlend = lerp(StationaryBlending, historyBlend, motion);
|
|
|
|
// Lower history blend when motion goes outside the view
|
|
const float velocityMin = 2.0f;
|
|
const float velocityMax = 20.0f;
|
|
const float velocityRange = velocityMax - velocityMin;
|
|
float velocityRejection = clamp(length(velocityDepth.xy * MotionScale) - velocityMin, 0.0, velocityRange) / velocityRange;
|
|
#if DEBUG_VELOCITY_REJECTION
|
|
return velocityRejection.xxxx;
|
|
#endif
|
|
historyBlend = lerp(historyBlend, 0.0f, velocityRejection);
|
|
|
|
// Blend current frame with history
|
|
float4 color = lerp(current, history, historyBlend);
|
|
|
|
// Apply quantization error to reduce yellowish artifacts due to R11G11B10 format
|
|
float noise = rand2dTo1d(input.TexCoord);
|
|
color.rgb = QuantizeColor(color.rgb, noise, QuantizationError);
|
|
|
|
return color;
|
|
}
|