Files

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;
}