Files
REM-Mod/EXE/reshade-shaders/Shaders/lumenite_Kernel.fx
T
2026-09-02 22:07:05 +02:00

754 lines
35 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_Kernel.fx
Version : 2026.07.28
Author : Afzaal (Kaidō)
Description: Pre-effect for various LumeniteFX shaders.
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
========================================================================
*/
/*------------------.
| :: DEFINITIONS :: |
'------------------*/
#define FOV 60.0
#define NEAR_PLANE 0.01
#ifndef IMAGE_SPACE
#define IMAGE_SPACE 0
#endif
#ifndef DEBUG_KERNEL
#define DEBUG_KERNEL 0
#endif
/*--------------.
| :: HEADERS :: |
'--------------*/
#include "ReShade.fxh"
#if DEBUG_KERNEL
#include "DrawText.fxh"
#endif
#include "./include/lumenite_Projections.fxh"
#include "./include/lumenite_Helpers.fxh"
#include "./include/lumenite_Compute.fxh"
/*---------------.
| :: UNIFORMS :: |
'---------------*/
#if DEBUG_KERNEL
uniform int DEBUG_VIEW <
ui_type = "combo";
ui_items = "Split View\0"
"Normals/Depth\0"
"Optical Flow\0"
"Motion Vectors\0"
"Motion Confidence\0"
;
ui_label = "Debug View";
ui_category = "Kernel";
> = 0;
#endif
namespace Kernel {
/*---------------------.
| :: RENDER TARGETS :: |
'---------------------*/
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; };
texture2D tCurrLuma { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; MipLevels = 8; };
sampler2D sCurrLuma { Texture = tCurrLuma; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tPrevLuma { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; MipLevels = 8; };
sampler2D sPrevLuma { Texture = tPrevLuma; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow128 { Width = BUFFER_WIDTH/128; Height = BUFFER_HEIGHT/128; Format = RG16F; };
sampler2D sFlow128 { Texture = tFlow128; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow64A { Width = BUFFER_WIDTH/64; Height = BUFFER_HEIGHT/64; Format = RG16F; };
sampler2D sFlow64A { Texture = tFlow64A; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow64B { Width = BUFFER_WIDTH/64; Height = BUFFER_HEIGHT/64; Format = RG16F; };
sampler2D sFlow64B { Texture = tFlow64B; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow32A { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/32; Format = RG16F; };
sampler2D sFlow32A { Texture = tFlow32A; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow32B { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/32; Format = RG16F; };
sampler2D sFlow32B { Texture = tFlow32B; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow16A { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = RG16F; };
sampler2D sFlow16A { Texture = tFlow16A; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow16B { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = RG16F; };
sampler2D sFlow16B { Texture = tFlow16B; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tFlow8 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sFlow8 { Texture = tFlow8; MagFilter = POINT; MinFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; };
texture2D tPrevFrameFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
sampler2D sPrevFrameFlow { Texture = tPrevFrameFlow; MagFilter = POINT; MinFilter = POINT; };
texture2D tPrevConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
sampler2D sPrevConfidence { Texture = tPrevConfidence; };
/*--------------.
| :: HELPERS :: |
'--------------*/
float3 GetColor(float2 uv)
{
return tex2Dlod(ReShade::BackBuffer, float4(uv, 0, 0)).rgb;
}
float3 DepthGradient(float t, float2 uv)
{
//grayscale: close=dark, far=bright
float3 depth = saturate(t).xxx;
const float ditherBit = 8.0;
float gridPos = frac(dot(uv, (BUFFER_SCREEN_SIZE * float2(1.0 / 16.0, 10.0 / 36.0)) + 0.25));
float ditherShift = 0.25 * (1.0 / (pow(2.0, ditherBit) - 1.0));
float3 ditherShiftRGB = float3(ditherShift, -ditherShift, ditherShift); //subpixel dithering
ditherShiftRGB = lerp(2.0 * ditherShiftRGB, -2.0 * ditherShiftRGB, gridPos);
return depth + ditherShiftRGB;
}
float3 MotionToColor(float2 motion)
{
float angle = atan2(-motion.y, -motion.x) / 6.283 + 0.5;
float rawLength = length(motion) / (15.0 * BUFFER_PIXEL_SIZE.x);
float compressed = rawLength / (1.0 + rawLength * 1.4); //asymptotic squash
float boosted = pow(compressed, 0.5); //lift shadows
float magnitude = saturate(lerp(compressed, boosted, saturate(rawLength * 3.0)));
float3 hsv = float3(angle, 1, magnitude);
float4 K = float4(1, 2/3.0, 1/3.0, 3);
float3 p = abs(frac(hsv.xxx + K.xyz) * 6 - K.www);
return hsv.z * lerp(K.xxx, clamp(p - K.xxx, 0, 1), hsv.y) + 0.1;
}
float SegmentDist(float2 p, float2 a, float2 b) //anti-aliased distance from point p to segment a-b
{
float2 pa = p - a;
float2 ba = b - a;
float h = saturate(dot(pa, ba) / (dot(ba, ba) + EPSILON));
return length(pa - ba * h);
}
float4 DrawMotionVectors(float2 uv)
{
static const int GATHER = 2; //cell radius searched (5x5); always MAX_LENGTH <= GATHER*GRID_SPACING
static const float GRID_SPACING = 16.0; //px between grid nodes
static const float DOT_RADIUS = 2.0; //px radius of node dots
static const float GRID_OPACITY = 0.20; //0..1 lattice visibility
static const float3 GRID_TINT = float3(0.55, 0.55, 0.60);
static const float SHAFT_THICKNESS = 1.5; //px half-width of shaft (larger)
static const float HEAD_LENGTH = 6.0; //px length of arrowhead (larger)
static const float HEAD_HALF_WIDTH = 4.0; //px half-width of head base (larger)
static const float MIN_LENGTH = 7.0; //px shortest arrow
static const float MAX_LENGTH = 30.0; //px longest arrow (<= GATHER*GRID_SPACING)
static const float LENGTH_SCALE = 2.5; //arrow px per motion px (elongation gain)
static const float AA = 0.9; //px edge softness
float3 baseColor = GetColor(uv);
float2 pixelPos = uv * BUFFER_SCREEN_SIZE;
//dotted grid
float2 g = pixelPos / GRID_SPACING;
float2 nearest = round(g) * GRID_SPACING; //nearest node centre, px
float dDot = length(pixelPos - nearest); //px distance to that node
float gridCov = (1.0 - smoothstep(DOT_RADIUS - AA, DOT_RADIUS + AA, dDot)) * GRID_OPACITY;
float bestCov = 0.0;
float3 bestColor = float3(0.0, 0.0, 0.0);
//union of arrows from the (2*GATHER+1)^2 nearest nodes (roots on grid crossings)
float2 baseNode = round(g);
[unroll] for (int ny = -GATHER; ny <= GATHER; ny++)
[unroll] for (int nx = -GATHER; nx <= GATHER; nx++)
{
float2 rootPx = (baseNode + float2(nx, ny)) * GRID_SPACING; //node sits on a crossing
float2 rootUV = rootPx * BUFFER_PIXEL_SIZE;
float2 motion = tex2Dlod(sFlow, float4(rootUV, 0, 0)).xy;
float2 motionPx = motion * BUFFER_SCREEN_SIZE;
float magPx = length(motionPx);
bool valid = (magPx >= 0.4) && (tex2Dlod(sDepth, float4(rootUV, 0, 0)).r < 0.999);
float len = clamp(magPx * LENGTH_SCALE, MIN_LENGTH, MAX_LENGTH); //elongates with this node's motion
float2 fwd = -motionPx / (magPx + EPSILON); //negate for forward motion
float2 tip = rootPx + fwd * len;
float2 perp = float2(-fwd.y, fwd.x);
//shaft
float2 shaftEnd = rootPx + fwd * max(len - HEAD_LENGTH, 0.0);
float dShaft = SegmentDist(pixelPos, rootPx, shaftEnd);
float covShaft = 1.0 - smoothstep(SHAFT_THICKNESS - AA, SHAFT_THICKNESS + AA, dShaft);
//head
float2 toTip = pixelPos - tip;
float along = dot(toTip, -fwd);
float side = abs(dot(toTip, perp));
float halfW = HEAD_HALF_WIDTH * saturate(along / HEAD_LENGTH);
float covAlong = smoothstep(-AA, AA, along) * (1.0 - smoothstep(HEAD_LENGTH - AA, HEAD_LENGTH + AA, along));
float covHead = covAlong * (1.0 - smoothstep(halfW - AA, halfW + AA, side));
float cov = max(covShaft, covHead) * (valid ? 1.0 : 0.0);
if (cov > bestCov) { bestCov = cov; bestColor = MotionToColor(motion); }
}
float3 outColor = lerp(baseColor, GRID_TINT, gridCov); //lattice underneath
outColor = lerp(outColor, bestColor, bestCov); //arrows on top
return float4(outColor, 1.0);
}
float ZMSAD(sampler2D currLumaSrc, sampler2D prevLumaSrc, float2 posA, float2 posB, float2 texelSize, uint mip)
{
static const int2 offsets[9] = {
int2(0, 3),
int2(0, 1),
int2(-3,0), int2(-1,0), int2(0, 0), int2(1,0), int2(3,0),
int2(0,-1),
int2(0,-3)
};
//gather samples and calculate the mean for each patch
float samplesA[9], samplesB[9];
float meanA = 0.0, meanB = 0.0;
[unroll] for(int i = 0; i < 9; i++) {
float2 offset = float2(offsets[i]) * texelSize;
samplesA[i] = tex2Dlod(currLumaSrc, float4(posA + offset, 0, mip)).r;
samplesB[i] = tex2Dlod(prevLumaSrc, float4(posB + offset, 0, mip)).r;
meanA += samplesA[i];
meanB += samplesB[i];
}
meanA /= 9.0;
meanB /= 9.0;
//SAD on the normalized samples
float err = 0.0;
[unroll] for(int i = 0; i < 9; i++)
err += abs((samplesA[i] - meanA) - (samplesB[i] - meanB));
return ((err / 9.0) + EPSILON);
}
float2 Median9(sampler2D flowSrc, float2 uv, float2 texelSize, uint mip)
{
float2 v[9];
int idx = 0;
[unroll] for(int dy = -1; dy <= 1; dy++) for(int dx = -1; dx <= 1; dx++)
v[idx++] = tex2Dlod(flowSrc, float4(uv + float2(dx, dy) * texelSize, 0, mip)).xy;
//bubble sort ensures the Median lands in v[4], only needs 5 passes
//indices 4,5,6,7,8 contain the 5 largest items, so v[4] is the median
[unroll] for(int k = 0; k < 5; k++) for(int i = 0; i < 8 - k; i++) { //checks decrease as right side gets sorted
float2 a = v[i];
float2 b = v[i+1];
v[i] = min(a, b);
v[i+1] = max(a, b);
}
return v[4];
}
float2 BilateralMedian9(sampler2D flowSrc, float2 uv, float2 texelSize, uint mip)
{
static const int2 DENSE_3X3[9] = {
int2(-1,-1), int2(0,-1), int2(1,-1),
int2(-1, 0), int2(0, 0), int2(1, 0),
int2(-1, 1), int2(0, 1), int2(1, 1)
};
float lumaC = tex2Dlod(sCurrLuma, float4(uv, 0, mip)).x;
float lumaW = tex2Dlod(sCurrLuma, float4(uv + float2(-1.0, 0.0) * texelSize, 0, mip)).x;
float lumaE = tex2Dlod(sCurrLuma, float4(uv + float2( 1.0, 0.0) * texelSize, 0, mip)).x;
float lumaN = tex2Dlod(sCurrLuma, float4(uv + float2( 0.0,-1.0) * texelSize, 0, mip)).x;
float lumaS = tex2Dlod(sCurrLuma, float4(uv + float2( 0.0, 1.0) * texelSize, 0, mip)).x;
//central-difference gradient, wider baseline than quad ddx/ddy, derived from real samples
float dxLuma = (lumaE - lumaW) * 0.5;
float dyLuma = (lumaS - lumaN) * 0.5;
float2 v[9];
uint validCount = 0;
[unroll] for (int i = 0; i < 9; i++) {
int2 off = DENSE_3X3[i];
float2 sampleUV = uv + float2(off) * texelSize;
//cardinals + center use sampled luma; diagonals get linear prediction
float sampleLuma = lumaC; //covers (0,0)
if (off.x == -1 && off.y == 0) sampleLuma = lumaW;
else if (off.x == 1 && off.y == 0) sampleLuma = lumaE;
else if (off.x == 0 && off.y == -1) sampleLuma = lumaN;
else if (off.x == 0 && off.y == 1) sampleLuma = lumaS;
else if (off.x != 0 && off.y != 0) sampleLuma = lumaC + float(off.x) * dxLuma + float(off.y) * dyLuma;
bool isValid = abs(lumaC - sampleLuma) <= 0.05;
v[i] = isValid ? tex2Dlod(flowSrc, float4(sampleUV, 0, 0)).xy : float2(1e38, 1e38);
validCount += uint(isValid);
}
if(validCount < 3u) return v[4];
//right-to-left bubble: smallest reaches v[0] per pass; after 5 passes, v[0..4] sorted ascending
[unroll] for(int k = 0; k < 5; k++) for(int j = 7; j >= k; j--) {
float2 a = v[j];
float2 b = v[j+1];
v[j] = min(a, b);
v[j+1] = max(a, b);
}
uint medianIdx = validCount / 2u;
float2 result = v[1]; //fallback for validCount == 3 (medianIdx 1)
if (medianIdx == 2u) result = v[2];
if (medianIdx == 3u) result = v[3];
if (medianIdx == 4u) result = v[4];
return result;
}
float2 ATrousFilter(sampler2D motionSrc, float2 uv, uint dilation, uint mip)
{
static const int2 offsets[8] = { int2(-1,-1), int2(0,-1), int2(1,-1),
int2(-1, 0), int2(1, 0),
int2(-1, 1), int2(0, 1), int2(1, 1) };
float centerLuma = tex2Dlod(sCurrLuma, float4(uv, 0, mip)).r;
#if IMAGE_SPACE == 0
float centerDepth = tex2Dlod(sDepth, float4(uv, 0, mip)).r;
#endif
float2 centerFlow = tex2Dlod(motionSrc, float4(uv, 0, 0)).xy;
float centerConf = max(tex2Dlod(sConfidence, float4(uv, 0, 0)).r, 0.01); //0.01 floor prevents NaN if conf hits 0
float2 sum = centerFlow * centerConf;
float totalWeight = centerConf;
[unroll] for (int i = 0; i < 8; i++) {
float2 sampleUV = uv + float2(offsets[i]) * dilation * BUFFER_PIXEL_SIZE * 8.0; //*8 = stride of flow grid
float2 sampleFlow = tex2Dlod(motionSrc, float4(sampleUV, 0, 0)).xy;
float sampleConf = tex2Dlod(sConfidence, float4(sampleUV, 0, 0)).r;
float confWeight = pow(sampleConf, 3.0);
float discontinuityGate;
#if IMAGE_SPACE == 0
float sampleDepth = tex2Dlod(sDepth, float4(sampleUV, 0, mip)).r;
float absDepthDiff = abs(centerDepth - sampleDepth);
float depthWeight = (absDepthDiff < 0.003) ? 1.0 : 0.0;
discontinuityGate = depthWeight;
#else
float2 flowDeltaPx = (sampleFlow - centerFlow) * BUFFER_SCREEN_SIZE; //measure flow disagreement in full-res px
float rawMotionGate = exp2(-dot(flowDeltaPx, flowDeltaPx) / (0.01 + EPSILON));
float motionGate = lerp(1.0, rawMotionGate, saturate(centerConf)); //if center flow is unreliable; relax gate so confident neighbors repair it
discontinuityGate = motionGate;
#endif
float sampleLuma = tex2Dlod(sCurrLuma, float4(sampleUV, 0, mip)).r;
float absLumaDiff = abs(centerLuma - sampleLuma);
float lumaWeight = saturate(1.0 - absLumaDiff * 10.0); //10.0: scale, 4.0: sharpness
float weight = confWeight * lumaWeight * discontinuityGate;
sum += sampleFlow * weight;
totalWeight += weight;
}
return sum / (totalWeight + EPSILON);
}
float2 UpscaleFlow(sampler2D coarseSrc, sampler2D currLumaSrc, sampler2D prevLumaSrc, float2 uv, float2 texelSize, uint mip)
{
if(FRAME_COUNT == 0) return float2(0, 0);
float2 coarseTexelSize = rcp(float2(tex2Dsize(coarseSrc, 0)));
//pool candidates for tournament selection. order matters here
float2 candidates[10];
candidates[0] = tex2D(coarseSrc, uv).xy ;
candidates[1] = tex2D(coarseSrc, uv + float2(0, -coarseTexelSize.y)).xy ;
candidates[2] = tex2D(coarseSrc, uv + float2(0, coarseTexelSize.y)).xy ;
candidates[3] = tex2D(coarseSrc, uv - float2(coarseTexelSize.x, 0)).xy ;
candidates[4] = tex2D(coarseSrc, uv + float2(coarseTexelSize.x, 0)).xy ;
candidates[5] = tex2D(coarseSrc, uv + float2(-coarseTexelSize.x, -coarseTexelSize.y)).xy ;
candidates[6] = tex2D(coarseSrc, uv + float2( coarseTexelSize.x, -coarseTexelSize.y)).xy ;
candidates[7] = tex2D(coarseSrc, uv + float2(-coarseTexelSize.x, coarseTexelSize.y)).xy ;
candidates[8] = tex2D(coarseSrc, uv + float2(coarseTexelSize.x, coarseTexelSize.y)).xy ;
candidates[9] = tex2D(sPrevFrameFlow, uv).xy;
float minCost = 1e6;
float2 prediction = candidates[0];
[loop] for (int i = 0; i < 10; i++) {
float cost = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + candidates[i], texelSize, mip);
if (cost < minCost) {
minCost = cost;
prediction = candidates[i];
}
}
//refinement with parabolic fitting
float costLeft = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + prediction - float2(texelSize.x, 0), texelSize, mip);
float costRight = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + prediction + float2(texelSize.x, 0), texelSize, mip);
float costDown = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + prediction - float2(0, texelSize.y), texelSize, mip);
float costUp = ZMSAD(currLumaSrc, prevLumaSrc, uv, uv + prediction + float2(0, texelSize.y), texelSize, mip);
//sub-pixel offset (parabolic fitting)
float2 subpixelOffset;
subpixelOffset.x = (costLeft - costRight) / (4.0 * (costLeft + costRight - 2.0 * minCost) + EPSILON); //EPSILON for flat surface handling
subpixelOffset.y = (costDown - costUp) / (4.0 * (costDown + costUp - 2.0 * minCost) + EPSILON);
//clamp offset to a reasonable range
subpixelOffset = clamp(subpixelOffset, -0.5, 0.5);
return (prediction+subpixelOffset*texelSize);
}
/*--------------.
| :: SHADERS :: |
'--------------*/
void PS_ReconstructNormals(VSOUT input, out float4 gbuffer : SV_Target0, out float depthC : SV_Target1)
{
depthC = GetDepth(input.uv);
const float2 offsetX = float2(BUFFER_PIXEL_SIZE.x, 0);
const float2 offsetY = float2(0, BUFFER_PIXEL_SIZE.y);
float3 pC = UVToViewSpace(input.uv, depthC, input);
float3 pL = UVToViewSpace(input.uv - offsetX, GetDepth(input.uv - offsetX), input);
float3 pR = UVToViewSpace(input.uv + offsetX, GetDepth(input.uv + offsetX), input);
float3 pT = UVToViewSpace(input.uv - offsetY, GetDepth(input.uv - offsetY), input);
float3 pB = UVToViewSpace(input.uv + offsetY, GetDepth(input.uv + offsetY), input);
float3 diffX2 = pR - pC;
float3 diffX1 = pC - pL;
float3 diffY2 = pB - pC;
float3 diffY1 = pC - pT;
float lenSqX2 = dot(diffX2, diffX2);
float lenSqX1 = dot(diffX1, diffX1);
float lenSqY2 = dot(diffY2, diffY2);
float lenSqY1 = dot(diffY1, diffY1);
float3 ddx = lenSqX2 < lenSqX1 ? diffX2 : diffX1;
float3 ddy = lenSqY2 < lenSqY1 ? diffY2 : diffY1;
float3 geoNormal = normalize(cross(ddx, ddy));
gbuffer = float4(geoNormal, depthC);
}
float PS_PackFeatures(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float3 color = GetColor(uv);
float luma = dot(color, float3(0.2126, 0.7152, 0.0722));
return luma * rcp(1.0 + luma);
}
float2 PS_ComputeFlow128(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
if(FRAME_COUNT == 0) return float2(0, 0);
static const int SEARCH_RADIUS = 3;
static const uint mip = 5;
float2 texelSize = BUFFER_PIXEL_SIZE * exp2(mip);
//candidate seeds for the coarsest level for tournament selection
float2 prevSeed = tex2D(sPrevFrameFlow, uv).xy;
float2 zeroSeed = float2(0, 0);
float prevCost = ZMSAD(sCurrLuma, sPrevLuma, uv, uv + prevSeed, texelSize, mip);
float zeroCost = ZMSAD(sCurrLuma, sPrevLuma, uv, uv + zeroSeed, texelSize, mip);
float2 seed = (zeroCost < prevCost) ? zeroSeed : prevSeed; //pick better candidate as seed
float2 bestFlow = seed;
float minCost = ZMSAD(sCurrLuma, sPrevLuma, uv, uv+seed, texelSize, mip);
//search in a grid AROUND the seed
for (int y = -SEARCH_RADIUS; y <= SEARCH_RADIUS; ++y) for (int x = -SEARCH_RADIUS; x <= SEARCH_RADIUS; ++x) {
if (x == 0 && y == 0) continue;
float2 candidateFlow = seed + float2(x, y) * texelSize;
float cost = ZMSAD(sCurrLuma, sPrevLuma, uv, uv + candidateFlow, texelSize, mip);
if (cost < minCost) {
minCost = cost;
bestFlow = candidateFlow;
if (minCost < 0.01) //near-perfect match found
return bestFlow;
}
}
return bestFlow;
}
float2 PS_UpscaleFlow64(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return UpscaleFlow(sFlow128, sCurrLuma, sPrevLuma, uv, BUFFER_PIXEL_SIZE*16.0, 4);
}
float2 PS_MedianPass64(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return Median9(sFlow64A, uv, BUFFER_PIXEL_SIZE*64.0, 6);
}
float2 PS_UpscaleFlow32(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return UpscaleFlow(sFlow64B, sCurrLuma, sPrevLuma, uv, BUFFER_PIXEL_SIZE*8.0, 3);
}
float2 PS_MedianPass32(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return Median9(sFlow32A, uv, BUFFER_PIXEL_SIZE*32.0, 5);
}
float2 PS_UpscaleFlow16(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return UpscaleFlow(sFlow32B, sCurrLuma, sPrevLuma, uv, BUFFER_PIXEL_SIZE*4.0, 2);
}
float2 PS_MedianPass16(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return Median9(sFlow16A, uv, BUFFER_PIXEL_SIZE*16.0, 4);
}
float2 PS_UpscaleFlow8(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return UpscaleFlow(sFlow16B, sCurrLuma, sPrevLuma, uv, BUFFER_PIXEL_SIZE*2.0, 1);
}
float2 PS_MedianPass8A(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return BilateralMedian9(sFlow, uv, BUFFER_PIXEL_SIZE*8.0, 3);
}
float2 PS_MedianPass8B(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return BilateralMedian9(sFlow8, uv, BUFFER_PIXEL_SIZE*8.0, 3);
}
float2 PS_ATrousPassA(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target //stride 1
{
return ATrousFilter(sFlow, uv, 2, 3);
}
float2 PS_ATrousPassB(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target //stride 2
{
float2 flow = ATrousFilter(sFlow8, uv, 4, 1);
//kill sub-pixel noise
float flowPixelMag = length(flow / BUFFER_PIXEL_SIZE);
float gate = saturate(1.0 - pow(1.0 - saturate(saturate(flowPixelMag) - 0.2), 10.0)); //SNAP TO REALITY
return flow*gate;
}
float PS_Confidence(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
if(FRAME_COUNT == 0) return 0.0; //no confidence
float2 flow = tex2D(sFlow, uv).xy;
float2 prevUV = uv + flow; //warp prev frame forward
if(IsOOB(prevUV)) return 0.0;
//look at local contrast for pattern confidence
float sumX = 0, sumX2 = 0, sumY = 0, sumY2 = 0;
float2 lumaTexSize = BUFFER_PIXEL_SIZE * 4.0;
static const float2 offsets[5] = {
float2(0, 1),
float2(-1,0), float2(0, 0), float2(1,0),
float2(0,-1)
};
[unroll] for(int i = 0; i < 5; i++) {
float valCurr = tex2Dlod(sCurrLuma, float4(uv + offsets[i] * lumaTexSize, 0, 2)).r;
float valPrev = tex2Dlod(sPrevLuma, float4(prevUV + offsets[i] * lumaTexSize, 0, 2)).r;
sumX += valCurr; sumX2 += valCurr * valCurr;
sumY += valPrev; sumY2 += valPrev * valPrev;
}
float varCurr = max(0.0, (sumX2 / 5.0) - (sumX / 5.0 * sumX / 5.0));
float varPrev = max(0.0, (sumY2 / 5.0) - (sumY / 5.0 * sumY / 5.0));
float patternConf = 1.0 - saturate(abs(sqrt(varCurr) - sqrt(varPrev)) / (sqrt(varCurr) + 0.01));
//look at neighborhood for flow consistency
float flowMagnitude = length(flow);
float2 flowTexelSize = BUFFER_PIXEL_SIZE * 8.0;
float2 flowN = tex2Dlod(sFlow, float4(uv + float2(0, -flowTexelSize.y), 0, 0)).xy;
float2 flowS = tex2Dlod(sFlow, float4(uv + float2(0, flowTexelSize.y), 0, 0)).xy;
float2 flowE = tex2Dlod(sFlow, float4(uv + float2( flowTexelSize.x, 0), 0, 0)).xy;
float2 flowW = tex2Dlod(sFlow, float4(uv + float2(-flowTexelSize.x, 0), 0, 0)).xy;
float2 avgNeighborFlow = (flowN + flowS + flowE + flowW) * 0.25;
float spatialDiff = distance(flow, avgNeighborFlow);
float spatialThreshold = flowMagnitude * 0.5 + BUFFER_PIXEL_SIZE.x;
float spatialConfidence = saturate(1.0 - (spatialDiff / (spatialThreshold + EPSILON)));
//motion length penalty
float subpixelThreshold = length(BUFFER_PIXEL_SIZE);
float lengthConfidence = (flowMagnitude <= subpixelThreshold) ? 1.0 : rcp((flowMagnitude / subpixelThreshold) * 0.05 + 1.0);
//float panThreshold = BUFFER_PIXEL_SIZE.x * 30.0;
//float lengthConfidence = (flowMagnitude <= panThreshold) ? 1.0 : rcp(((flowMagnitude - panThreshold) / panThreshold) * 0.1 + 1.0);
//current frame final confidence
float currentConf = spatialConfidence * lengthConfidence * patternConf;
//temporal filter
float historyConf = tex2D(sPrevConfidence, prevUV).r;
//DEPRECATED: linear EMA (a=0.15) 15% new + 85% history every frame
//unbiased (settles at the true mean), very stable but distrusts a real drop only as slowly as it trusts a rise
//return lerp(historyConf, currentConf, 0.15); //higher makes it react to changes quickly
//Asymmetric EMA; a=0.5 only on a genuine drop (>0.05 below history) fast distrust, else a reasonable a=0.1
//0.05 deadband keeps calm-region jitter on 0.1; only true occlusion/disocclusion bleeds confidence fast
float alpha = (currentConf < historyConf - 0.05) ? 0.5 : 0.1;
return lerp(historyConf, currentConf, alpha);
}
void PS_StoreFlow(float4 pos : SV_Position, float2 uv : TEXCOORD, out float2 flow : SV_Target0, out float confidence : SV_Target1)
{
flow = tex2D(sFlow, uv).xy;
confidence = tex2D(sConfidence, uv).r;
}
float PS_StoreLuma(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
return tex2D(sCurrLuma, uv).r;
}
#if DEBUG_KERNEL
float4 PS_Debug(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float3 sceneColor = GetColor(uv);
switch(DEBUG_VIEW)
{
case 0: {
static const float LINE_PX = 1.5; //divider half-width, px
static const float3 LINE_TINT = float3(0.0, 0.0, 0.0);
static const float2 BOX_HALF = float2(0.16, 0.18); //centre inset half-extents, uv
float2 pixelPos = uv * BUFFER_SCREEN_SIZE;
float2 centrePx = BUFFER_SCREEN_SIZE * 0.5;
float2 boxHalfPx = BOX_HALF * BUFFER_SCREEN_SIZE;
//axis-aligned box distance
float2 dd = abs(pixelPos - centrePx) - boxHalfPx;
float boxSDF = length(max(dd, 0.0)) + min(max(dd.x, dd.y), 0.0);
float3 view;
if (boxSDF < 0.0)
{
float2 boxUV = (uv - (0.5 - BOX_HALF)) / (2.0 * BOX_HALF); //full frame mapped into inset
view = DrawMotionVectors(boxUV).rgb; //centre: motion vectors
}
else
{
float2 quadUV = frac(uv * 2.0); //flow/confidence remap to full [0,1] frame
if (uv.y < 0.5)
view = (uv.x < 0.5)
? tex2Dlod(sNormals, float4(uv, 0, 0)).rgb * 0.5 + 0.5 //TL: normals (spatial, raw uv)
: DepthGradient(tex2Dlod(sDepth, float4(uv, 0, 0)).r, uv); //TR: depth (spatial, raw uv)
else if (uv.x < 0.5)
view = MotionToColor(tex2Dlod(sFlow, float4(quadUV, 0, 0)).xy); //BL: optical flow field
else
{
float confidence = tex2Dlod(sConfidence, float4(quadUV, 0, 0)).x; //BR: motion confidence field
float3 confidenceColor = (confidence < 0.5)
? lerp(float3(1.0, 0.0, 0.0), float3(1.0, 1.0, 0.0), confidence * 2.0)
: lerp(float3(1.0, 1.0, 0.0), float3(0.0, 1.0, 0.0), (confidence - 0.5) * 2.0);
view = lerp(GetColor(quadUV), confidenceColor, 0.9);
}
//black dividers
float dCross = min(abs(pixelPos.x - centrePx.x), abs(pixelPos.y - centrePx.y));
view = lerp(view, LINE_TINT, 1.0 - smoothstep(LINE_PX - 0.9, LINE_PX + 0.9, dCross));
}
//centre inset border
view = lerp(view, LINE_TINT, 1.0 - smoothstep(LINE_PX - 0.9, LINE_PX + 0.9, abs(boxSDF)));
//window labels
float2 texcoord = uv; //alias: the DrawText macro declares its own internal 'uv'
float labelMask = 0.0;
float labelSize = max(BUFFER_HEIGHT * 0.025, 12.0); //label height, px
int lblNormals[21] = { __R, __e, __c, __o, __n, __s, __t, __r, __u, __c, __t, __e, __d, __Space, __N, __o, __r, __m, __a, __l, __s };
int lblDepth[16] = { __L, __i, __n, __e, __a, __r, __i, __z, __e, __d, __Space, __D, __e, __p, __t, __h };
int lblFlow[10] = { __F, __l, __o, __w, __Space, __F, __i, __e, __l, __d };
int lblConfidence[16] = { __C, __o, __n, __f, __i, __d, __e, __n, __c, __e, __Space, __F, __i, __e, __l, __d };
int lblVectors[14] = { __M, __o, __t, __i, __o, __n, __Space, __V, __e, __c, __t, __o, __r, __s };
labelMask = 0.0; DrawText_String(float2(BUFFER_WIDTH * 0.25 - 21.0 * labelSize * 0.25, BUFFER_HEIGHT * 0.03), labelSize, 1.0, texcoord, lblNormals, 21, labelMask); view = lerp(view, float3(1.00, 1.00, 1.00), saturate(labelMask)); //TL white
labelMask = 0.0; DrawText_String(float2(BUFFER_WIDTH * 0.75 - 16.0 * labelSize * 0.25, BUFFER_HEIGHT * 0.03), labelSize, 1.0, texcoord, lblDepth, 16, labelMask); view = lerp(view, float3(0.55, 0.85, 1.00), saturate(labelMask)); //TR blue
labelMask = 0.0; DrawText_String(float2(BUFFER_WIDTH * 0.25 - 10.0 * labelSize * 0.25, BUFFER_HEIGHT * 0.53), labelSize, 1.0, texcoord, lblFlow, 10, labelMask); view = lerp(view, float3(1.00, 1.00, 1.00), saturate(labelMask)); //BL white
labelMask = 0.0; DrawText_String(float2(BUFFER_WIDTH * 0.75 - 16.0 * labelSize * 0.25, BUFFER_HEIGHT * 0.53), labelSize, 1.0, texcoord, lblConfidence, 16, labelMask); view = lerp(view, float3(1.00, 1.00, 1.00), saturate(labelMask)); //BR white
labelMask = 0.0; DrawText_String(float2(BUFFER_WIDTH * 0.50 - 14.0 * labelSize * 0.25, BUFFER_HEIGHT * (0.5 - BOX_HALF.y) + 8.0), labelSize, 1.0, texcoord, lblVectors, 14, labelMask); view = lerp(view, float3(1.00, 1.00, 1.00), saturate(labelMask)); //centre white
view = lerp(view, float3(1.0, 1.0, 1.0), saturate(labelMask)); //white labels
return float4(view, 1.0);
}
case 1: {
float4 gbuffer = tex2D(sNormals, uv);
float3 normal = gbuffer.rgb;
float depth = gbuffer.a;
bool isLeftHalf = uv.x < 0.5;
float4 dbg;
if (isLeftHalf)
dbg = float4(normal * 0.5 + 0.5, 1.0); //left: normals
else
dbg = float4(DepthGradient(depth, uv), 1.0); //right: depth gradient
return dbg;
}
case 2: return float4(MotionToColor(tex2D(sFlow, uv).xy), 1);
case 3: return DrawMotionVectors(uv);
case 4:
{
float confidence = tex2D(sConfidence, uv).x;
float3 confidenceColor;
if (confidence < 0.5)
confidenceColor = lerp(float3(1.0, 0.0, 0.0), float3(1.0, 1.0, 0.0), confidence * 2.0);
else
confidenceColor = lerp(float3(1.0, 1.0, 0.0), float3(0.0, 1.0, 0.0), (confidence - 0.5) * 2.0);
return float4(lerp(sceneColor, confidenceColor, 0.9), 1.0);
}
default: return float4(sceneColor, 1.0);
}
}
#endif
/*----------------.
| :: TECHNIQUE :: |
'----------------*/
technique Lumenite_Kernel <
ui_label = "LUMENITE: Kernel 2.0";
ui_tooltip = "Pre-effect for LumeniteFX shaders.";
>
{
//normals
#if IMAGE_SPACE == 0
pass { VertexShader = VS; PixelShader = PS_ReconstructNormals; RenderTarget0 = tNormals; RenderTarget1 = tDepth; }
#endif
//optical flow
pass { VertexShader = PostProcessVS; PixelShader = PS_PackFeatures; RenderTarget = tCurrLuma; }
pass { VertexShader = PostProcessVS; PixelShader = PS_ComputeFlow128; RenderTarget = tFlow128; }
pass { VertexShader = PostProcessVS; PixelShader = PS_UpscaleFlow64; RenderTarget = tFlow64A; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass64; RenderTarget = tFlow64B; }
pass { VertexShader = PostProcessVS; PixelShader = PS_UpscaleFlow32; RenderTarget = tFlow32A; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass32; RenderTarget = tFlow32B; }
pass { VertexShader = PostProcessVS; PixelShader = PS_UpscaleFlow16; RenderTarget = tFlow16A; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass16; RenderTarget = tFlow16B; }
pass { VertexShader = PostProcessVS; PixelShader = PS_UpscaleFlow8; RenderTarget = tFlow; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass8A; RenderTarget = tFlow8; }
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass8B; RenderTarget = tFlow; }
pass { VertexShader = PostProcessVS; PixelShader = PS_Confidence; RenderTarget = tConfidence; }
pass { VertexShader = PostProcessVS; PixelShader = PS_ATrousPassA; RenderTarget = tFlow8; }
pass { VertexShader = PostProcessVS; PixelShader = PS_ATrousPassB; RenderTarget = tFlow; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StoreFlow; RenderTarget0 = tPrevFrameFlow; RenderTarget1 = tPrevConfidence; }
pass { VertexShader = PostProcessVS; PixelShader = PS_StoreLuma; RenderTarget = tPrevLuma; }
//debug views
#if DEBUG_KERNEL
pass { VertexShader = PostProcessVS; PixelShader = PS_Debug; }
#endif
}
}