526 lines
21 KiB
HLSL
526 lines
21 KiB
HLSL
/*
|
|
========================================================================
|
|
Copyright (c) Afzaal. All rights reserved.
|
|
|
|
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.
|
|
|
|
========================================================================
|
|
|
|
GitHub : https://github.com/umar-afzaal/LumeniteFX
|
|
Discord : https://discord.gg/deXJrW2dx6
|
|
|
|
|
|
Filename : lumenite_TRAA.fx
|
|
Version : 2026.07.28
|
|
Author : Afzaal (Kaidō)
|
|
Description: Temporal Reprojection Anti-Aliasing
|
|
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
|
|
|
|
========================================================================
|
|
*/
|
|
|
|
/*------------------.
|
|
| :: DEFINITIONS :: |
|
|
'------------------*/
|
|
#ifndef ENABLE_DLAA
|
|
#define ENABLE_DLAA 1
|
|
#endif
|
|
|
|
/*--------------.
|
|
| :: HEADERS :: |
|
|
'--------------*/
|
|
#include "ReShade.fxh"
|
|
#include "./include/lumenite_ColorManagement.fxh"
|
|
#include "./include/lumenite_Helpers.fxh"
|
|
|
|
/*---------------.
|
|
| :: UNIFORMS :: |
|
|
'---------------*/
|
|
uniform int SHOW_STATUS <
|
|
ui_type = "radio";
|
|
ui_label = " ";
|
|
#if ENABLE_DLAA
|
|
ui_text = "DLAA Prepass: Enabled.";
|
|
#else
|
|
ui_text = "DLAA Prepass: Disabled.";
|
|
#endif
|
|
>;
|
|
|
|
#if ENABLE_DLAA
|
|
uniform bool DEBUG_EDGES <
|
|
ui_label = "Show Edge Mask";
|
|
ui_tooltip = "Paints the detected edge mask over black background.";
|
|
> = false;
|
|
|
|
uniform int EDGE_MODE <
|
|
ui_type = "combo";
|
|
ui_label = "Edge Detection";
|
|
ui_items = "Luma\0Geometric\0";
|
|
ui_tooltip = "Luma: shading and texture edges as well; the classic DLAA mask.\n"
|
|
"Geometric: silhouettes only, ignores flat UI.";
|
|
> = 0;
|
|
#endif
|
|
|
|
uniform float HISTORY_BLEND <
|
|
ui_type = "slider";
|
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
|
ui_label = "Temporal Blend";
|
|
hidden = false;
|
|
> = 0.9;
|
|
|
|
uniform float SHARP_STRENGTH <
|
|
ui_type = "drag";
|
|
ui_min = 0; ui_max = 2.0; ui_step = 0.05;
|
|
ui_label = "Adaptive Sharpen";
|
|
hidden = false;
|
|
> = 1.0;
|
|
|
|
uniform float MAX_SHARP_DIFF <
|
|
ui_type = "drag";
|
|
ui_min = 0.05; ui_max = 0.25; ui_step = 0.01;
|
|
ui_label = "Sharpen Guard";
|
|
ui_tooltip = "Higher = more aggressive sharpening allowed.\nLower = tighter anti-ringing clamp.";
|
|
hidden = true;
|
|
> = 0.1;
|
|
|
|
uniform float HFI_INTENSITY <
|
|
ui_type = "drag";
|
|
ui_min = 0.0; ui_max = 0.1; ui_step = 0.001;
|
|
ui_label = "High-Frequency Injection";
|
|
ui_tooltip = "Re-injects detail lost during Temporal blend.";
|
|
> = 0.01;
|
|
|
|
/*--------------.
|
|
| :: IMPORTS :: |
|
|
'--------------*/
|
|
namespace Kernel {
|
|
texture2D tFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
|
|
sampler2D sFlow { Texture = tFlow; MagFilter = POINT; MinFilter = POINT; };
|
|
|
|
texture2D tConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
|
|
sampler2D sConfidence { Texture = tConfidence; };
|
|
|
|
texture tNormals { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; MipLevels = 4; };
|
|
sampler sNormals { Texture = tNormals; };
|
|
|
|
texture2D tDepth { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; MipLevels = 4; };
|
|
sampler2D sDepth { Texture = tDepth; };
|
|
}
|
|
|
|
namespace LumeniteTRAA {
|
|
/*---------------------.
|
|
| :: RENDER TARGETS :: |
|
|
'---------------------*/
|
|
#if ENABLE_DLAA
|
|
texture tDLAAPreFilter { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
|
|
sampler sDLAAPreFilter { Texture = tDLAAPreFilter; MinFilter = LINEAR; MagFilter = LINEAR; MipFilter = LINEAR; };
|
|
|
|
texture tDLAAPrePass { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
|
|
sampler sDLAAPrePass { Texture = tDLAAPrePass; MagFilter = POINT; MinFilter = POINT; };
|
|
#endif
|
|
|
|
texture tCurrHistory { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
|
|
sampler sCurrHistory { Texture = tCurrHistory; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
|
|
|
|
texture tPrevHistory { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
|
|
sampler sPrevHistory { Texture = tPrevHistory; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
|
|
|
|
/*--------------.
|
|
| :: HELPERS :: |
|
|
'--------------*/
|
|
//5-Tap 2D Catmull-Rom Filter (Brian Karis / Unreal Engine)
|
|
float3 SampleCatmullRom5Tap(sampler tex, float2 uv) {
|
|
float2 pos = uv * float2(BUFFER_WIDTH, BUFFER_HEIGHT);
|
|
float2 centerPos = floor(pos - 0.5) + 0.5;
|
|
float2 f = pos - centerPos;
|
|
|
|
//1D Catmull-Rom weights
|
|
float2 f2 = f * f;
|
|
float2 f3 = f2 * f;
|
|
|
|
float2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
|
|
float2 w1 = 1.0 + f2 * (-2.5 + 1.5 * f);
|
|
float2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));
|
|
float2 w3 = f2 * (-0.5 + 0.5 * f);
|
|
|
|
//group the inner positive lobes (w1, w2) for bilinear hardware
|
|
float2 w12 = w1 + w2;
|
|
float2 offset12 = w2 / (w12 + 0.00001); // Prevent div by zero
|
|
|
|
//5-tap texture fetch in a cross pattern
|
|
float2 texCoord0 = (centerPos - float2(1.0, 0.0) + float2(0.0, offset12.y)) * BUFFER_PIXEL_SIZE; //left
|
|
float2 texCoord1 = (centerPos + float2(2.0, 0.0) + float2(0.0, offset12.y)) * BUFFER_PIXEL_SIZE; //right
|
|
float2 texCoord2 = (centerPos + float2(offset12.x, -1.0)) * BUFFER_PIXEL_SIZE; //top
|
|
float2 texCoord3 = (centerPos + float2(offset12.x, 2.0)) * BUFFER_PIXEL_SIZE; //bottom
|
|
float2 texCoord4 = (centerPos + offset12) * BUFFER_PIXEL_SIZE; //center
|
|
|
|
//final 2D weights for the 5 taps
|
|
float weight0 = w0.x * w12.y; //left
|
|
float weight1 = w3.x * w12.y; //right
|
|
float weight2 = w12.x * w0.y; //top
|
|
float weight3 = w12.x * w3.y; //bottom
|
|
float weight4 = w12.x * w12.y; //center
|
|
|
|
//normalize weights because we dropped the 4 corner taps (sum would be slightly < 1.0)
|
|
float weightSum = weight0 + weight1 + weight2 + weight3 + weight4;
|
|
weightSum = max(weightSum, 0.0001);
|
|
weight0 /= weightSum;
|
|
weight1 /= weightSum;
|
|
weight2 /= weightSum;
|
|
weight3 /= weightSum;
|
|
weight4 /= weightSum;
|
|
|
|
//sample w. hw bilinear filtering (offsets take care of the interpolation)
|
|
float3 color0 = tex2Dlod(tex, float4(texCoord0, 0, 0)).rgb;
|
|
float3 color1 = tex2Dlod(tex, float4(texCoord1, 0, 0)).rgb;
|
|
float3 color2 = tex2Dlod(tex, float4(texCoord2, 0, 0)).rgb;
|
|
float3 color3 = tex2Dlod(tex, float4(texCoord3, 0, 0)).rgb;
|
|
float3 color4 = tex2Dlod(tex, float4(texCoord4, 0, 0)).rgb;
|
|
|
|
float3 result = color0 * weight0 + color1 * weight1 + color2 * weight2 + color3 * weight3 + color4 * weight4;
|
|
//anti-ringing clamp
|
|
float3 minColor = min(min(min(color0, color1), min(color2, color3)), color4);
|
|
float3 maxColor = max(max(max(color0, color1), max(color2, color3)), color4);
|
|
return clamp(result, minColor, maxColor);
|
|
}
|
|
|
|
//if ray misses the bounding box (tEnter > tExit), returning t = 1.0 is the safe fallback
|
|
float3 YCoCgLineBoxClip(float3 historyYCoCg, float3 meanYCoCg, float3 colorMin, float3 colorMax) {
|
|
float3 rayDir = meanYCoCg - historyYCoCg;
|
|
|
|
rayDir = abs(rayDir) < 0.0001 ? float3(0.0001, 0.0001, 0.0001) : rayDir; //avoid div by zero
|
|
|
|
//compute t for intersection with min and max bounds per-channel
|
|
float3 tMin = (colorMin - historyYCoCg) / rayDir;
|
|
float3 tMax = (colorMax - historyYCoCg) / rayDir;
|
|
|
|
float3 t1 = min(tMin, tMax);
|
|
float3 t2 = max(tMin, tMax);
|
|
tMin = t1;
|
|
tMax = t2;
|
|
|
|
//entry and exit points for ray-box intersection
|
|
float tEnter = max(max(tMin.x, tMin.y), tMin.z);
|
|
float tExit = min(min(tMax.x, tMax.y), tMax.z);
|
|
|
|
//if ray misses the box; fallback to 1.0 (mean) to discard history
|
|
//else, clamp the entry point to [0, 1] to clip exactly at the box edge
|
|
float t = tEnter > tExit ? 1.0 : clamp(tEnter, 0.0, 1.0);
|
|
|
|
return historyYCoCg + rayDir * t;
|
|
}
|
|
|
|
/*--------------.
|
|
| :: SHADERS :: |
|
|
'--------------*/
|
|
#if ENABLE_DLAA
|
|
|
|
float4 PS_DLAAPreFilter(float4 vpos : SV_Position, float2 uv : TexCoord) : SV_Target {
|
|
float3 center = sqrt(max(GetLinearColor(uv, false), 0.0));
|
|
float edge;
|
|
if (!EDGE_MODE) {
|
|
//luma edge in perceptual space; the extra sqrt fattens the mask
|
|
float2 px = float2(BUFFER_PIXEL_SIZE.x, 0.0);
|
|
float2 py = float2(0.0, BUFFER_PIXEL_SIZE.y);
|
|
float3 left = sqrt(max(GetLinearColor(uv - px, false), 0.0));
|
|
float3 right = sqrt(max(GetLinearColor(uv + px, false), 0.0));
|
|
float3 top = sqrt(max(GetLinearColor(uv - py, false), 0.0));
|
|
float3 bottom = sqrt(max(GetLinearColor(uv + py, false), 0.0));
|
|
float3 edges = 4.0 * abs((left + right + top + bottom) - 4.0 * center);
|
|
edge = GetLuminance(sqrt(max(edges, 0.0))); //recursive gamma compression: do another sqrt(), fattens the edge mask
|
|
} else {
|
|
float4 s0 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2(-1,-1), 0, 0));
|
|
float4 s1 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2( 0,-1), 0, 0));
|
|
float4 s2 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2( 1,-1), 0, 0));
|
|
float4 s3 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2(-1, 0), 0, 0));
|
|
float4 s4 = tex2Dlod(Kernel::sNormals, float4(uv, 0, 0));
|
|
float4 s5 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2( 1, 0), 0, 0));
|
|
float4 s6 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2(-1, 1), 0, 0));
|
|
float4 s7 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2( 0, 1), 0, 0));
|
|
float4 s8 = tex2Dlod(Kernel::sNormals, float4(uv + BUFFER_PIXEL_SIZE * float2( 1, 1), 0, 0));
|
|
|
|
//3x3 depth Sobel
|
|
float dC = s4.a;
|
|
float sxD = -s0.a + s2.a - 2.0 * s3.a + 2.0 * s5.a - s6.a + s8.a;
|
|
float syD = -s0.a - 2.0 * s1.a - s2.a + s6.a + 2.0 * s7.a + s8.a;
|
|
float depthEdge = saturate(sqrt(sxD * sxD + syD * syD) / (dC + 1e-5));
|
|
|
|
//3x3 normal Sobel
|
|
float3 sxN = -s0.xyz + s2.xyz - 2.0 * s3.xyz + 2.0 * s5.xyz - s6.xyz + s8.xyz;
|
|
float3 syN = -s0.xyz - 2.0 * s1.xyz - s2.xyz + s6.xyz + 2.0 * s7.xyz + s8.xyz;
|
|
float normalEdge = saturate(length(sxN) + length(syN));
|
|
|
|
edge = max(depthEdge, normalEdge);
|
|
}
|
|
return float4(center, edge);
|
|
}
|
|
|
|
#define SAMPLE_G(uv, dx, dy) tex2Dlod(sDLAAPreFilter, float4((uv) + float2(dx, dy) * BUFFER_PIXEL_SIZE, 0, 0))
|
|
float4 PS_DLAA(float4 vpos : SV_Position, float2 uv : TexCoord) : SV_Target {
|
|
float4 center = SAMPLE_G(uv, 0.0, 0.0);
|
|
float4 left01 = SAMPLE_G(uv, -1.5, 0.0);
|
|
float4 right01 = SAMPLE_G(uv, 1.5, 0.0);
|
|
float4 top01 = SAMPLE_G(uv, 0.0, -1.5);
|
|
float4 bottom01 = SAMPLE_G(uv, 0.0, 1.5);
|
|
|
|
//flat-region early exit
|
|
float localEdges = max(center.a, max(max(left01.a, right01.a), max(top01.a, bottom01.a)));
|
|
if (localEdges < 0.05) return float4(center.xyz * center.xyz, 1.0);
|
|
|
|
float4 wH = 2.0 * (left01 + right01);
|
|
float4 wV = 2.0 * (top01 + bottom01);
|
|
|
|
float4 edgeH = abs(wH - 4.0 * center) / 4.0;
|
|
float4 edgeV = abs(wV - 4.0 * center) / 4.0;
|
|
|
|
float4 blurredH = (wH + 2.0 * center) / 6.0;
|
|
float4 blurredV = (wV + 2.0 * center) / 6.0;
|
|
|
|
float edgeHLum = GetLuminance(edgeH.xyz);
|
|
float edgeVLum = GetLuminance(edgeV.xyz);
|
|
float blurredHLum = GetLuminance(blurredH.xyz);
|
|
float blurredVLum = GetLuminance(blurredV.xyz);
|
|
|
|
const float kLambda = 3.0;
|
|
const float kEpsilon = 0.1;
|
|
float edgeMaskH = saturate((kLambda * edgeHLum - kEpsilon) / (blurredVLum + 1e-5));
|
|
float edgeMaskV = saturate((kLambda * edgeVLum - kEpsilon) / (blurredHLum + 1e-5));
|
|
|
|
float gate = (!EDGE_MODE) ? 1.0 : center.a;
|
|
edgeMaskH *= gate;
|
|
edgeMaskV *= gate;
|
|
|
|
float4 clr = center;
|
|
clr = lerp(clr, blurredH, edgeMaskV);
|
|
clr = lerp(clr, blurredV, edgeMaskH * 0.5);
|
|
|
|
//skip unnecessary work on long-edges
|
|
if (localEdges > 0.5) {
|
|
float4 h0 = right01;
|
|
float4 h1 = SAMPLE_G(uv, 3.5, 0.0);
|
|
float4 h2 = SAMPLE_G(uv, 5.5, 0.0);
|
|
float4 h3 = SAMPLE_G(uv, 7.5, 0.0);
|
|
float4 h4 = left01;
|
|
float4 h5 = SAMPLE_G(uv, -3.5, 0.0);
|
|
float4 h6 = SAMPLE_G(uv, -5.5, 0.0);
|
|
float4 h7 = SAMPLE_G(uv, -7.5, 0.0);
|
|
|
|
float4 v0 = bottom01;
|
|
float4 v1 = SAMPLE_G(uv, 0.0, 3.5);
|
|
float4 v2 = SAMPLE_G(uv, 0.0, 5.5);
|
|
float4 v3 = SAMPLE_G(uv, 0.0, 7.5);
|
|
float4 v4 = top01;
|
|
float4 v5 = SAMPLE_G(uv, 0.0, -3.5);
|
|
float4 v6 = SAMPLE_G(uv, 0.0, -5.5);
|
|
float4 v7 = SAMPLE_G(uv, 0.0, -7.5);
|
|
|
|
float longEdgeMaskH = (h0.a + h1.a + h2.a + h3.a + h4.a + h5.a + h6.a + h7.a) / 8.0;
|
|
float longEdgeMaskV = (v0.a + v1.a + v2.a + v3.a + v4.a + v5.a + v6.a + v7.a) / 8.0;
|
|
|
|
longEdgeMaskH = saturate(longEdgeMaskH * 2.0 - 1.0);
|
|
longEdgeMaskV = saturate(longEdgeMaskV * 2.0 - 1.0);
|
|
|
|
if (abs(longEdgeMaskH - longEdgeMaskV) > 0.2) {
|
|
float4 left = SAMPLE_G(uv, -1.0, 0.0);
|
|
float4 right = SAMPLE_G(uv, 1.0, 0.0);
|
|
float4 top = SAMPLE_G(uv, 0.0, -1.0);
|
|
float4 bottom = SAMPLE_G(uv, 0.0, 1.0);
|
|
|
|
float4 longBlurredH = (h0 + h1 + h2 + h3 + h4 + h5 + h6 + h7) / 8.0;
|
|
float4 longBlurredV = (v0 + v1 + v2 + v3 + v4 + v5 + v6 + v7) / 8.0;
|
|
|
|
float lbHLum = GetLuminance(longBlurredH.xyz);
|
|
float lbVLum = GetLuminance(longBlurredV.xyz);
|
|
|
|
float centerLum = GetLuminance(center.xyz);
|
|
float leftLum = GetLuminance(left.xyz);
|
|
float rightLum = GetLuminance(right.xyz);
|
|
float topLum = GetLuminance(top.xyz);
|
|
float bottomLum = GetLuminance(bottom.xyz);
|
|
|
|
float4 clrV = center;
|
|
float4 clrH = center;
|
|
|
|
float hx = saturate(0.0 + (lbHLum - topLum) / (centerLum - topLum + 1e-6));
|
|
float hy = saturate(1.0 + (lbHLum - centerLum) / (centerLum - bottomLum + 1e-6));
|
|
float vx = saturate(0.0 + (lbVLum - leftLum) / (centerLum - leftLum + 1e-6));
|
|
float vy = saturate(1.0 + (lbVLum - centerLum) / (centerLum - rightLum + 1e-6));
|
|
|
|
float4 vhxy = float4(vx, vy, hx, hy);
|
|
vhxy.x = (vhxy.x == 0.0) ? 1.0 : vhxy.x;
|
|
vhxy.y = (vhxy.y == 0.0) ? 1.0 : vhxy.y;
|
|
vhxy.z = (vhxy.z == 0.0) ? 1.0 : vhxy.z;
|
|
vhxy.w = (vhxy.w == 0.0) ? 1.0 : vhxy.w;
|
|
|
|
clrV = lerp(left, clrV, vhxy.x);
|
|
clrV = lerp(right, clrV, vhxy.y);
|
|
clrH = lerp(top, clrH, vhxy.z);
|
|
clrH = lerp(bottom, clrH, vhxy.w);
|
|
|
|
clr = lerp(clr, clrV, longEdgeMaskV);
|
|
clr = lerp(clr, clrH, longEdgeMaskH);
|
|
}
|
|
}
|
|
|
|
//highlight protection
|
|
float4 r0 = SAMPLE_G(uv, -1.5, -1.5);
|
|
float4 r1 = SAMPLE_G(uv, 1.5, -1.5);
|
|
float4 r2 = SAMPLE_G(uv, -1.5, 1.5);
|
|
float4 r3 = SAMPLE_G(uv, 1.5, 1.5);
|
|
float4 r = (4.0 * (r0 + r1 + r2 + r3) + center + top01 + bottom01 + left01 + right01) / 25.0;
|
|
|
|
float mask = saturate(r.a * 3.0 - 2.0);
|
|
clr = lerp(clr, center, mask);
|
|
|
|
return float4(clr.xyz * clr.xyz, 1.0); //store linear color here!
|
|
}
|
|
|
|
#endif
|
|
|
|
float4 PS_TRAA(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
|
|
//3x3 neighborhood from DLAA prepass
|
|
static const float2 offsets[9] = {
|
|
float2(-1, -1), float2(0, -1), float2(1, -1),
|
|
float2(-1, 0) , float2(0, 0) , float2(1, 0),
|
|
float2(-1, 1) , float2(0, 1) , float2(1, 1)
|
|
};
|
|
float3 samples[9];
|
|
float3 samplesYCoCg[9];
|
|
float3 meanYCoCg = float3(0, 0, 0);
|
|
|
|
for (int i = 0; i < 9; i++) {
|
|
float2 samplePos = texcoord + BUFFER_PIXEL_SIZE * offsets[i];
|
|
#if ENABLE_DLAA
|
|
samples[i] = tex2Dlod(sDLAAPrePass, float4(samplePos, 0, 0)).rgb;
|
|
#else
|
|
samples[i] = GetLinearColor(samplePos, false);
|
|
#endif
|
|
samplesYCoCg[i] = linearToYCoCg(samples[i]);
|
|
meanYCoCg += samplesYCoCg[i];
|
|
}
|
|
meanYCoCg /= 9.0;
|
|
|
|
//standard deviation per channel
|
|
float3 stddev = float3(0, 0, 0);
|
|
for (int i = 0; i < 9; i++) {
|
|
float3 diff = samplesYCoCg[i] - meanYCoCg;
|
|
stddev += diff * diff;
|
|
}
|
|
stddev = sqrt(stddev / 9.0);
|
|
|
|
//variance-scaled bounding box in YCoCg
|
|
float3 colorMin = meanYCoCg - stddev * 1.25;
|
|
float3 colorMax = meanYCoCg + stddev * 1.25;
|
|
|
|
float2 flow = tex2D(Kernel::sFlow, texcoord).xy;
|
|
float confidence = tex2D(Kernel::sConfidence, texcoord).x;
|
|
confidence = saturate(confidence + 0.11 * 4.0 * confidence * (1.0 - confidence));
|
|
|
|
float2 historyUV = texcoord + flow;
|
|
historyUV = clamp(historyUV, BUFFER_PIXEL_SIZE, 1.0 - BUFFER_PIXEL_SIZE);
|
|
float3 historyRGB = SampleCatmullRom5Tap(sPrevHistory, historyUV);
|
|
float3 historyYCoCg = linearToYCoCg(historyRGB);
|
|
|
|
//clip history to current neighborhood bounds via line-box intersection
|
|
float3 clippedHistoryYCoCg = YCoCgLineBoxClip(historyYCoCg, meanYCoCg, colorMin, colorMax);
|
|
|
|
//re-inject current pixel's detail into clipped history
|
|
float3 centerYCoCg = samplesYCoCg[4]; //blend against the center pixel (index 4 of the 3x3 grid)
|
|
float3 injectedHistory = clippedHistoryYCoCg + (centerYCoCg - meanYCoCg) * HFI_INTENSITY;
|
|
|
|
//blend clipped history with current in YCoCg space
|
|
float blendVal = min(0.98, HISTORY_BLEND);
|
|
float3 blendedYCoCg = lerp(centerYCoCg, injectedHistory, confidence * blendVal);
|
|
float3 output = YCoCgToLinear(blendedYCoCg);
|
|
return float4(output, 1.0);
|
|
}
|
|
|
|
float4 PS_ToDisplay(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
|
|
#if ENABLE_DLAA
|
|
|
|
if (DEBUG_EDGES) {
|
|
float edgeDbg = tex2D(sDLAAPreFilter, texcoord).a;
|
|
static const float3 edgeTint = float3(0.125, 0.698, 0.667) * float3(0.125, 0.698, 0.667); //target color squared so lands on the real hue
|
|
return float4(ToOutputColorspace(edgeTint * saturate(edgeDbg), false), 1.0);
|
|
}
|
|
|
|
#endif
|
|
|
|
float3 c = tex2D(sCurrHistory, texcoord).rgb;
|
|
float3 sharpened = c;
|
|
|
|
if (SHARP_STRENGTH > 0) {
|
|
float2 off = BUFFER_PIXEL_SIZE * 0.5;
|
|
|
|
float3 ne = tex2D(sCurrHistory, texcoord + float2( off.x, off.y)).rgb;
|
|
float3 sw = tex2D(sCurrHistory, texcoord + float2(-off.x, -off.y)).rgb;
|
|
float3 se = tex2D(sCurrHistory, texcoord + float2( off.x, -off.y)).rgb;
|
|
float3 nw = tex2D(sCurrHistory, texcoord + float2(-off.x, off.y)).rgb;
|
|
|
|
//bounds for the neighborhood
|
|
float3 local_min = min(min(min(ne, nw), min(se, sw)), c);
|
|
float3 local_max = max(max(max(ne, nw), max(se, sw)), c);
|
|
|
|
//high-pass
|
|
float3 diag_max = max(max(ne, nw), max(se, sw));
|
|
float3 diag_min = min(min(ne, nw), min(se, sw));
|
|
float3 diff_rgb = 2.0 * c + (ne + nw + se + sw) - 3.0 * (diag_max + diag_min);
|
|
|
|
static const float3 luma_weight = float3(0.2126, 0.7152, 0.0722);
|
|
float luma_c = dot(c, luma_weight);
|
|
float luma_diff = dot(diff_rgb, luma_weight);
|
|
|
|
//rational limit
|
|
float max_allowed = MAX_SHARP_DIFF * (luma_c + 0.1);
|
|
luma_diff = luma_diff / (rcp(SHARP_STRENGTH) + abs(luma_diff) / max(max_allowed, 0.001));
|
|
|
|
//lower epsilon (0.005) for more dark-area detail
|
|
float ratio = (luma_c + luma_diff) / max(luma_c, 0.005);
|
|
|
|
//allow up to 3x brightness for extreme highlights
|
|
ratio = clamp(ratio, 0.3, 3.0);
|
|
sharpened = c * ratio;
|
|
|
|
//anti-ringing
|
|
//instead of clamping strictly to min/max, we allow a 20% overshoot
|
|
//perceived "sharpness" while capping fireflies
|
|
float3 overshoot_min = local_min * 0.8;
|
|
float3 overshoot_max = local_max * 1.2;
|
|
|
|
sharpened = clamp(sharpened, overshoot_min, overshoot_max);
|
|
}
|
|
|
|
return float4(ToOutputColorspace(sharpened, false), 1.0);
|
|
}
|
|
|
|
float4 PS_StoreHistory(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target {
|
|
float3 taaResult = tex2D(sCurrHistory, texcoord).rgb;
|
|
return float4(taaResult, 1.0);
|
|
}
|
|
|
|
/*----------------.
|
|
| :: TECHNIQUE :: |
|
|
'----------------*/
|
|
|
|
technique Lumenite_TRAA <
|
|
ui_label = "LUMENITE: TRAA";
|
|
ui_tooltip = "Temporal Reprojection Anti-Aliasing.";
|
|
>
|
|
{
|
|
#if ENABLE_DLAA
|
|
pass { VertexShader = PostProcessVS; PixelShader = PS_DLAAPreFilter; RenderTarget = tDLAAPreFilter; }
|
|
pass { VertexShader = PostProcessVS; PixelShader = PS_DLAA; RenderTarget = tDLAAPrePass; } //spatial filter
|
|
#endif
|
|
pass { VertexShader = PostProcessVS; PixelShader = PS_TRAA; RenderTarget = tCurrHistory; } //temporal filter
|
|
pass { VertexShader = PostProcessVS; PixelShader = PS_ToDisplay; }
|
|
pass { VertexShader = PostProcessVS; PixelShader = PS_StoreHistory; RenderTarget = tPrevHistory; }
|
|
}
|
|
|
|
}
|