Add RenoDX DLSS5 Addon for OpenGL Reshade
This commit is contained in:
@@ -0,0 +1,817 @@
|
||||
/*
|
||||
DLSS5_Feed.fx - companion effect for the "DLSS 5 Feed" ReShade add-on (dlss5-feed.addon64/32).
|
||||
|
||||
It turns what ReShade already has into the guide textures DLSS needs, in the exact layout
|
||||
the add-on expects:
|
||||
|
||||
DLSS5_MV RG16F motion vectors in PIXELS, pointing from the current pixel to where it was
|
||||
in the previous frame (DLSS convention). Vectors that fail validation
|
||||
(below) are zeroed.
|
||||
DLSS5_Depth R32F the game's raw hardware depth (not linearised), sampled at backbuffer size,
|
||||
with ReShade's RESHADE_DEPTH_INPUT_* orientation fixes applied.
|
||||
DLSS5_Mask R8 "bias current colour" mask for DLSS: 1 where the motion vector could not
|
||||
be trusted, so DLSS leans on the current frame there instead of warping
|
||||
history in. Optional -- an add-on that does not know it ignores it.
|
||||
|
||||
MOTION VECTOR PROVIDER -- set the DLSS5_MV_PROVIDER preprocessor definition (ReShade overlay:
|
||||
this effect's "Preprocessor definitions", or the global list) and enable that provider's
|
||||
technique ABOVE this one in the effect list:
|
||||
|
||||
0 texMotionVectors the community-standard shared texture: qUINT_motionvectors,
|
||||
dh_uber_motion, ReshadeMotionEstimation (DRME -- NOTE: DRME does not
|
||||
compile on ReShade 6.8, "cannot sample from texture that is also used
|
||||
as render target"; it then silently writes nothing) [default]
|
||||
1 Launchpad iMMERSE Launchpad (MartysMods_LAUNCHPAD.fx): Deferred::MotionVectorsTex.
|
||||
Launchpad only runs its optical flow when asked to, so this mode also
|
||||
files that per-frame request (Launchpad's IPC buffer, see below).
|
||||
2 VORT vort_Motion.fx (MIT): MotVectTexVort -- the recommended provider
|
||||
3 LumeniteFX Kernel lumenite_Kernel.fx ("LUMENITE: Kernel"): Kernel::tFlow -- pyramidal
|
||||
optical flow with per-level median + a-trous filtering and previous-
|
||||
frame seeding. 1/8 resolution, upsampled here. Needs no depth buffer.
|
||||
4 LumeniteFX QuantMotion
|
||||
lumenite_QuantMotion.fx: QuantMotion::tFlow -- the light cut of 3.
|
||||
|
||||
This is the same mechanism dh_uber_rt (USE_MARTY_LAUNCHPAD_MOTION / USE_VORT_MOTION) and
|
||||
vort (V_MV_MODE) use: the selected provider's OUTPUT texture is declared here exactly as the
|
||||
provider declares it, so ReShade binds the same resource, and only that one is allocated.
|
||||
Every provider above hands out delta UV with prev_uv = uv + mv. Nothing of any provider is
|
||||
included or bundled: this file contains no third-party code and includes no third-party
|
||||
files beyond ReShade's own headers.
|
||||
|
||||
VALIDATION -- why it exists. A game's motion vectors are geometric: a static wall under a
|
||||
flickering light has vectors of exactly zero. Every provider above is OPTICAL FLOW: it
|
||||
matches pixels, so a lighting change (flicker, flames, particles) is answered with a vector
|
||||
that points at whatever happened to match -- confidently wrong, and DLSS then warps its
|
||||
history in from there. That is the "warping around flames" and the "bad dither when the
|
||||
light flickers". The fix is the one every production TAA uses: reproject and CHECK.
|
||||
For each pixel, three tests against the previous frame at uv + mv:
|
||||
- luma: the previous luma must fall inside the current 3x3 neighbourhood's range
|
||||
(flicker moves the whole range, so a stale match falls outside);
|
||||
- depth: the previous linear depth must match the current one (disocclusions);
|
||||
- consistency: the previous frame's vector at that spot must resemble this one
|
||||
(real motion is smooth frame to frame; flow on fire is erratic).
|
||||
A vector failing any test is zeroed (the surface is treated as static -- the right answer
|
||||
for a lit wall) and the pixel is flagged in DLSS5_Mask so DLSS trusts the current frame there.
|
||||
|
||||
The add-on runs DLSS + DLSS 5 neural rendering right after the "DLSS5_Feed" technique has
|
||||
rendered, so anything placed below it in the list is applied on top of the neural output.
|
||||
*/
|
||||
|
||||
#include "ReShade.fxh"
|
||||
|
||||
// Expose ReShade's completed frame to the add-on as an SRV. The 64-bit D3D11 path
|
||||
// uses this only when its work-resolution control is below 100%; no extra pass or
|
||||
// copy is introduced by this declaration.
|
||||
texture DLSS5_ColorInput : COLOR;
|
||||
sampler sDLSS5_ColorInput { Texture = DLSS5_ColorInput; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Point; MagFilter = Point; };
|
||||
|
||||
#ifndef DLSS5_MV_PROVIDER
|
||||
#define DLSS5_MV_PROVIDER 0
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// The selected provider's output, declared byte for byte like the provider itself does.
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
#if DLSS5_MV_PROVIDER == 1
|
||||
// iMMERSE Launchpad (MartysMods/mmx_deferred.fxh)
|
||||
namespace Deferred {
|
||||
texture MotionVectorsTex { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RG16F; };
|
||||
// Launchpad's request buffer. Launchpad only computes optical flow when a consumer asked
|
||||
// for it during the previous frame (it reads this 1x1 RGBA8 at the top of its technique
|
||||
// and clears it at the bottom; bit 4 = optical flow, written through the render-target
|
||||
// write mask). Being below Launchpad in the list, our request lands for the next frame.
|
||||
// Declared like Launchpad declares it; the two shaders that write it below are ours.
|
||||
namespace IPC {
|
||||
texture2D PredicationBuffer { Format = RGBA8; };
|
||||
}
|
||||
}
|
||||
sampler sDLSS5_ProviderMV { Texture = Deferred::MotionVectorsTex; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Point; MagFilter = Point; };
|
||||
float4 DLSS5_IpcRequestVS(in uint id : SV_VertexID) : SV_Position { return float4(0.0, 0.0, 0.0, 1.0); }
|
||||
float4 DLSS5_IpcRequestPS(in float4 vpos : SV_Position) : SV_Target0 { return 1.0; }
|
||||
#define DLSS5_MV_PROVIDER_NAME "Launchpad (Deferred::MotionVectorsTex)"
|
||||
#define DLSS5_MV_REQUEST_PASS pass IpcRequestOpticalFlow { PrimitiveTopology = POINTLIST; VertexCount = 1; VertexShader = DLSS5_IpcRequestVS; PixelShader = DLSS5_IpcRequestPS; RenderTarget = Deferred::IPC::PredicationBuffer; RenderTargetWriteMask = 4; }
|
||||
#elif DLSS5_MV_PROVIDER == 2
|
||||
// VORT (Includes/vort_MotionUtils.fxh, V_MV_MODE 1)
|
||||
texture2D MotVectTexVort { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RG16F; };
|
||||
sampler sDLSS5_ProviderMV { Texture = MotVectTexVort; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Point; MagFilter = Point; };
|
||||
#define DLSS5_MV_PROVIDER_NAME "VORT (MotVectTexVort)"
|
||||
#elif DLSS5_MV_PROVIDER == 3
|
||||
// LumeniteFX Kernel (lumenite_Kernel.fx), as lumenite_RTAO/TRAA re-declare it. 1/8 resolution.
|
||||
namespace Kernel {
|
||||
texture2D tFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
|
||||
texture2D tConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
|
||||
}
|
||||
sampler sDLSS5_ProviderMV { Texture = Kernel::tFlow; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Linear; MagFilter = Linear; };
|
||||
sampler sDLSS5_ProviderMVPoint { Texture = Kernel::tFlow; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Point; MagFilter = Point; };
|
||||
sampler sDLSS5_ProviderConfidence{ Texture = Kernel::tConfidence; AddressU = Clamp; AddressV = Clamp; };
|
||||
#define DLSS5_MV_PROVIDER_NAME "LumeniteFX Kernel (Kernel::tFlow, 1/8 res)"
|
||||
#define DLSS5_MV_LOWRES 1
|
||||
#elif DLSS5_MV_PROVIDER == 4
|
||||
// LumeniteFX QuantMotion (lumenite_QuantMotion.fx), as lumenite_QuantAO re-declares it. 1/8 resolution.
|
||||
namespace QuantMotion {
|
||||
texture2D tFlow { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RG16F; };
|
||||
texture2D tConfidence { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
|
||||
}
|
||||
sampler sDLSS5_ProviderMV { Texture = QuantMotion::tFlow; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Linear; MagFilter = Linear; };
|
||||
sampler sDLSS5_ProviderMVPoint { Texture = QuantMotion::tFlow; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Point; MagFilter = Point; };
|
||||
sampler sDLSS5_ProviderConfidence{ Texture = QuantMotion::tConfidence; AddressU = Clamp; AddressV = Clamp; };
|
||||
#define DLSS5_MV_PROVIDER_NAME "LumeniteFX QuantMotion (QuantMotion::tFlow, 1/8 res)"
|
||||
#define DLSS5_MV_LOWRES 1
|
||||
#else
|
||||
// The community-standard shared texture (ReshadeMotionEstimation, qUINT, dh_uber_motion, ...)
|
||||
texture texMotionVectors < pooled = false; > { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RG16F; };
|
||||
sampler sDLSS5_ProviderMV { Texture = texMotionVectors; AddressU = Clamp; AddressV = Clamp; MipFilter = Point; MinFilter = Point; MagFilter = Point; };
|
||||
#define DLSS5_MV_PROVIDER_NAME "texMotionVectors (DRME, qUINT, dh_uber_motion, ...)"
|
||||
#endif
|
||||
|
||||
#ifndef DLSS5_MV_LOWRES
|
||||
#define DLSS5_MV_LOWRES 0
|
||||
#endif
|
||||
#ifndef DLSS5_MV_REQUEST_PASS
|
||||
#define DLSS5_MV_REQUEST_PASS
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
uniform int MV_PROVIDER_INFO <
|
||||
ui_type = "radio";
|
||||
ui_label = " ";
|
||||
ui_text = "Motion vector provider: " DLSS5_MV_PROVIDER_NAME "\n"
|
||||
"Change it with the DLSS5_MV_PROVIDER preprocessor definition:\n"
|
||||
" 0 texMotionVectors (DRME, qUINT, dh_uber_motion) 1 Launchpad 2 VORT\n"
|
||||
" 3 LumeniteFX Kernel 4 LumeniteFX QuantMotion\n"
|
||||
"Enable that provider's technique ABOVE DLSS 5 Feed.";
|
||||
>;
|
||||
|
||||
#if DLSS5_MV_LOWRES
|
||||
uniform int MV_LOWRES_FILTER <
|
||||
ui_type = "combo";
|
||||
ui_items = "Bilinear\0Point (nearest)\0";
|
||||
ui_label = "Low-res provider filter";
|
||||
ui_tooltip = "How the provider's 1/8-resolution flow is brought up to full resolution.\n"
|
||||
"Bilinear smooths across flow cells; point keeps each 8x8 cell's vector as-is.";
|
||||
> = 0;
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Geometry vectors. A game's motion vectors for static geometry come from camera motion and
|
||||
// depth, not from pixels. We have depth; the camera motion is fitted each frame from the
|
||||
// provider's flow over a sparse grid (robust two-pass least squares on a 9-term screen-space
|
||||
// model: affine + quadratic rotation terms + inverse-depth parallax terms), and every pixel
|
||||
// then gets the vector that model predicts from its depth -- correct under flicker, correct
|
||||
// while moving. The provider's flow is only used where it disagrees with the model AND wins a
|
||||
// structure test: a genuinely moving object. Flames and flicker lose that test and keep the
|
||||
// geometric vector, so nothing warps.
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
uniform bool GEOM_ENABLE <
|
||||
ui_category = "Geometry vectors (camera model + depth) -- EXPERIMENTAL";
|
||||
ui_label = "Use geometry vectors (experimental, off by default)";
|
||||
ui_tooltip = "Fit the camera motion from the provider's flow + depth each frame and derive every static\n"
|
||||
"pixel's vector from it. The provider is then only consulted for moving objects.\n"
|
||||
"EXPERIMENTAL: the per-frame fit is still noisy, and anything not part of the 3D world\n"
|
||||
"(the HUD) gets camera vectors it should not have -- expect jitter there.\n"
|
||||
"Off = the per-pixel validation below is applied to the provider's flow directly.";
|
||||
> = false;
|
||||
|
||||
uniform float GEOM_PARALLAX <
|
||||
ui_category = "Geometry vectors (camera model + depth)";
|
||||
ui_type = "drag"; ui_min = 0.001; ui_max = 0.5; ui_step = 0.001;
|
||||
ui_label = "Parallax depth scale";
|
||||
ui_tooltip = "The model's inverse-depth term is s / (depth + s) with linear depth in 0..1. Smaller = more\n"
|
||||
"parallax resolution near the camera. Usually fine as is.";
|
||||
> = 0.02;
|
||||
|
||||
uniform float GEOM_OUTLIER_PX <
|
||||
ui_category = "Geometry vectors (camera model + depth)";
|
||||
ui_type = "drag"; ui_min = 0.5; ui_max = 32.0; ui_step = 0.5;
|
||||
ui_label = "Fit: outlier rejection (px)";
|
||||
ui_tooltip = "Second fitting pass ignores samples whose flow is further than this from the first pass's\n"
|
||||
"prediction -- moving objects, flames, the first-person weapon.";
|
||||
> = 4.0;
|
||||
|
||||
uniform float GEOM_AGREE_PX <
|
||||
ui_category = "Geometry vectors (camera model + depth)";
|
||||
ui_type = "drag"; ui_min = 0.0; ui_max = 16.0; ui_step = 0.1;
|
||||
ui_label = "Agreement (px)";
|
||||
ui_tooltip = "If the provider's flow is within this many pixels (+10% of the vector) of the model, the\n"
|
||||
"model's vector is used as-is. Beyond it, the structure test decides moving object vs junk.";
|
||||
> = 1.5;
|
||||
|
||||
uniform float GEOM_DYNAMIC_MARGIN <
|
||||
ui_category = "Geometry vectors (camera model + depth)";
|
||||
ui_type = "drag"; ui_min = 0.0; ui_max = 0.9; ui_step = 0.01;
|
||||
ui_label = "Moving-object margin";
|
||||
ui_tooltip = "For the provider's flow to override the model on a disagreeing pixel, its reprojection must\n"
|
||||
"explain the pixel's structure at least this much (relative) better than the model's does.\n"
|
||||
"Higher = more conservative (fewer things count as moving objects).";
|
||||
> = 0.25;
|
||||
|
||||
uniform float GEOM_MASK_REJECTED <
|
||||
ui_category = "Geometry vectors (camera model + depth)";
|
||||
ui_type = "drag"; ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||
ui_label = "Mask strength on rejected flow";
|
||||
ui_tooltip = "Where the provider disagreed with the model but did not win the structure test (fire, smoke,\n"
|
||||
"flicker), the geometric vector is used; this is how strongly DLSS is additionally asked to\n"
|
||||
"favour the current frame there. 0 = pure history (smoothest), 1 = mostly current frame.";
|
||||
> = 0.35;
|
||||
|
||||
uniform bool MV_VALIDATE <
|
||||
ui_category = "Validation (flicker / flames / disocclusion)";
|
||||
ui_label = "Validate motion vectors against the previous frame";
|
||||
ui_tooltip = "Optical-flow providers answer a lighting change (flicker, flames) with a vector that\n"
|
||||
"points at whatever happened to match. Reprojecting and checking catches those:\n"
|
||||
"the vector is zeroed and DLSS is told to trust the current frame there (DLSS5_Mask).";
|
||||
> = true;
|
||||
|
||||
uniform bool VALIDATE_STATIC <
|
||||
ui_category = "Validation (flicker / flames / disocclusion)";
|
||||
ui_label = "Static-hypothesis test (zeroes the vector, keeps history)";
|
||||
ui_tooltip = "For each pixel, asks which explains it better: 'did not move' or the provider's vector.\n"
|
||||
"Both are scored on illumination-normalised 3x3 structure (local mean removed), so a\n"
|
||||
"flickering light does not count as motion. When 'did not move' wins, the vector is zeroed\n"
|
||||
"and the pixel is NOT masked -- a static wall wants its full history, which is what smooths\n"
|
||||
"the flicker. This is the test for the flickering-wall case.";
|
||||
> = true;
|
||||
|
||||
uniform float STATIC_BIAS <
|
||||
ui_category = "Validation (flicker / flames / disocclusion)";
|
||||
ui_type = "drag"; ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
||||
ui_label = "Static bias";
|
||||
ui_tooltip = "How much worse (relative) the static explanation may score than the vector's and still win.\n"
|
||||
"0 = the vector must strictly beat 'did not move'. Higher favours zero vectors.";
|
||||
> = 0.15;
|
||||
|
||||
uniform float STATIC_MIN_CONTRAST <
|
||||
ui_category = "Validation (flicker / flames / disocclusion)";
|
||||
ui_type = "drag"; ui_min = 0.0; ui_max = 0.1; ui_step = 0.001;
|
||||
ui_label = "Static test: minimum patch contrast";
|
||||
ui_tooltip = "Below this 3x3 contrast (mean absolute deviation of luma) a patch has no structure to judge\n"
|
||||
"motion by, and the test abstains -- the provider's vector stands. Raise it if flat surfaces\n"
|
||||
"trail while moving (yellow on plain motion in the debug view); lower it if the\n"
|
||||
"flickering wall stops being caught.";
|
||||
> = 0.012;
|
||||
|
||||
uniform bool VALIDATE_LUMA <
|
||||
ui_category = "Validation (flicker / flames / disocclusion)";
|
||||
ui_label = "Luma test (mask only)";
|
||||
ui_tooltip = "The reprojected previous luma must fall inside the current 3x3 neighbourhood's luma range.\n"
|
||||
"A failure only raises the mask (DLSS leans on the current frame); it never zeroes the vector,\n"
|
||||
"because a lighting change does not prove the surface did not move. Off by default: on a\n"
|
||||
"flickering surface it asks DLSS to drop exactly the history that would smooth the flicker.";
|
||||
> = false;
|
||||
|
||||
uniform float LUMA_TOLERANCE <
|
||||
ui_category = "Validation (flicker / flames / disocclusion)";
|
||||
ui_type = "drag"; ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
||||
ui_label = "Luma tolerance";
|
||||
ui_tooltip = "How far outside the current 3x3 neighbourhood's luma range the reprojected previous luma\n"
|
||||
"may fall (relative to that range's maximum). Lower = stricter.";
|
||||
> = 0.25;
|
||||
|
||||
uniform bool VALIDATE_DEPTH <
|
||||
ui_category = "Validation (flicker / flames / disocclusion)";
|
||||
ui_label = "Depth test (zeroes the vector)";
|
||||
ui_tooltip = "The reprojected previous linear depth must match the current one: a mismatch means the vector\n"
|
||||
"points at a different surface (disocclusion), so it is zeroed and masked. Sky is exempt.";
|
||||
> = true;
|
||||
|
||||
uniform float DEPTH_TOLERANCE <
|
||||
ui_category = "Validation (flicker / flames / disocclusion)";
|
||||
ui_type = "drag"; ui_min = 0.0; ui_max = 0.5; ui_step = 0.005;
|
||||
ui_label = "Depth tolerance";
|
||||
ui_tooltip = "Allowed relative difference between the reprojected previous linear depth and the current one.";
|
||||
> = 0.10;
|
||||
|
||||
uniform bool VALIDATE_MV <
|
||||
ui_category = "Validation (flicker / flames / disocclusion)";
|
||||
ui_label = "Consistency test (zeroes the vector)";
|
||||
ui_tooltip = "This frame's vector must resemble the previous frame's vector at the spot it points to.\n"
|
||||
"Real motion is smooth frame to frame; optical flow on fire, smoke or a flickering wall is not.\n"
|
||||
"A failure zeroes the vector and masks the pixel.";
|
||||
> = true;
|
||||
|
||||
uniform float MV_CONSISTENCY <
|
||||
ui_category = "Validation (flicker / flames / disocclusion)";
|
||||
ui_type = "drag"; ui_min = 0.0; ui_max = 16.0; ui_step = 0.1;
|
||||
ui_label = "Vector consistency (px)";
|
||||
ui_tooltip = "Allowed change, in pixels, between this frame's vector and the previous frame's vector at\n"
|
||||
"the reprojected spot, plus 50% of the vector length. Raise it if plain camera motion\n"
|
||||
"shows blue in the 'Validation tests' debug view.";
|
||||
> = 1.4;
|
||||
|
||||
uniform float MASK_STRENGTH <
|
||||
ui_category = "Validation (flicker / flames / disocclusion)";
|
||||
ui_type = "drag"; ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||
ui_label = "Bias-current-colour mask strength";
|
||||
ui_tooltip = "How strongly a distrusted pixel asks DLSS to favour the current frame (DLSS5_Mask).\n"
|
||||
"1 = fully; 0 = only zero the vector, do not mask.";
|
||||
> = 1.0;
|
||||
|
||||
uniform float2 MV_SIGN <
|
||||
ui_type = "drag";
|
||||
ui_min = -1.0; ui_max = 1.0; ui_step = 2.0;
|
||||
ui_label = "Motion vector sign (x, y)";
|
||||
ui_tooltip = "Flip a component if the DLAA output doubles/smears in that direction while moving.\n"
|
||||
"Default (1, 1) matches the convention every supported provider uses (prev_uv = uv + mv).";
|
||||
> = float2(1.0, 1.0);
|
||||
|
||||
uniform float MV_SCALE <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 4.0; ui_step = 0.01;
|
||||
ui_label = "Motion vector scale";
|
||||
ui_tooltip = "1.0 = the provider's estimate as-is. Diagnostic only.";
|
||||
> = 1.0;
|
||||
|
||||
uniform int DEBUG_VIEW <
|
||||
ui_type = "combo";
|
||||
ui_items = "Motion vectors (colour = direction, brightness = speed)\0"
|
||||
"Raw depth\0"
|
||||
"Provider confidence (LumeniteFX only; white = confident)\0"
|
||||
"Validation mask (white = vector distrusted, DLSS uses current frame)\0"
|
||||
"Validation mask over the image\0"
|
||||
"Validation tests over the image (red = luma, green = depth, blue = consistency, yellow = static wins)\0"
|
||||
"Geometry model vectors (colour = direction, brightness = speed)\0"
|
||||
"Geometry decision over the image (green = model, red = provider won as moving object, blue = provider rejected)\0"
|
||||
"Geometry fit quality (grey = inlier share; top strip = fit error, black 0 px .. white 8 px)\0";
|
||||
ui_label = "Debug view (DLSS5_Feed_Debug technique)";
|
||||
> = 0;
|
||||
|
||||
// Outputs for the add-on
|
||||
texture DLSS5_MV { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RG16F; };
|
||||
texture DLSS5_Depth { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R32F; };
|
||||
texture DLSS5_Mask { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R8; };
|
||||
sampler sDLSS5_MV { Texture = DLSS5_MV; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
sampler sDLSS5_Depth { Texture = DLSS5_Depth; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
sampler sDLSS5_Mask { Texture = DLSS5_Mask; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
|
||||
// Previous-frame history for validation (written at the end of the technique)
|
||||
texture DLSS5_PrevLuma { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; };
|
||||
texture DLSS5_PrevDepth { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; };
|
||||
texture DLSS5_PrevMV { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RG16F; };
|
||||
// Luma may be interpolated (a smooth quantity); depth and vectors must NOT be -- bilinear
|
||||
// across an object edge mixes two surfaces' values and fails the test on every edge in motion.
|
||||
sampler sDLSS5_PrevLuma { Texture = DLSS5_PrevLuma; AddressU = Clamp; AddressV = Clamp; MinFilter = LINEAR; MagFilter = LINEAR; MipFilter = POINT; };
|
||||
sampler sDLSS5_PrevDepth { Texture = DLSS5_PrevDepth; AddressU = Clamp; AddressV = Clamp; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
sampler sDLSS5_PrevMV { Texture = DLSS5_PrevMV; AddressU = Clamp; AddressV = Clamp; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
|
||||
// Camera-model fit: a sparse sample grid of (x, y, w, valid | u, v), and the solved model as
|
||||
// six 1x1 RGBA32F texels (18 parameters + fit statistics).
|
||||
#define DLSS5_FIT_W 40
|
||||
#define DLSS5_FIT_H 23
|
||||
texture DLSS5_FitA { Width = DLSS5_FIT_W; Height = DLSS5_FIT_H; Format = RGBA32F; };
|
||||
texture DLSS5_FitB { Width = DLSS5_FIT_W; Height = DLSS5_FIT_H; Format = RGBA32F; };
|
||||
sampler sDLSS5_FitA { Texture = DLSS5_FitA; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
sampler sDLSS5_FitB { Texture = DLSS5_FitB; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
texture DLSS5_Cam0 { Width = 1; Height = 1; Format = RGBA32F; };
|
||||
texture DLSS5_Cam1 { Width = 1; Height = 1; Format = RGBA32F; };
|
||||
texture DLSS5_Cam2 { Width = 1; Height = 1; Format = RGBA32F; };
|
||||
texture DLSS5_Cam3 { Width = 1; Height = 1; Format = RGBA32F; };
|
||||
texture DLSS5_Cam4 { Width = 1; Height = 1; Format = RGBA32F; };
|
||||
texture DLSS5_Cam5 { Width = 1; Height = 1; Format = RGBA32F; };
|
||||
sampler sDLSS5_Cam0 { Texture = DLSS5_Cam0; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
sampler sDLSS5_Cam1 { Texture = DLSS5_Cam1; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
sampler sDLSS5_Cam2 { Texture = DLSS5_Cam2; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
sampler sDLSS5_Cam3 { Texture = DLSS5_Cam3; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
sampler sDLSS5_Cam4 { Texture = DLSS5_Cam4; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
sampler sDLSS5_Cam5 { Texture = DLSS5_Cam5; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; };
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
// The selected provider's vector at uv, as delta UV (prev_uv = uv + mv).
|
||||
float2 ProviderMV(float2 uv)
|
||||
{
|
||||
float4 c = float4(uv, 0.0, 0.0);
|
||||
#if DLSS5_MV_LOWRES
|
||||
return MV_LOWRES_FILTER == 0 ? tex2Dlod(sDLSS5_ProviderMV, c).xy : tex2Dlod(sDLSS5_ProviderMVPoint, c).xy;
|
||||
#else
|
||||
return tex2Dlod(sDLSS5_ProviderMV, c).xy;
|
||||
#endif
|
||||
}
|
||||
|
||||
float Luma(float2 uv)
|
||||
{
|
||||
return dot(tex2Dlod(sDLSS5_ColorInput, float4(uv, 0.0, 0.0)).rgb, float3(0.299, 0.587, 0.114));
|
||||
}
|
||||
|
||||
// Illumination-normalised 3x3 structure difference between the current frame at uv_cur and
|
||||
// the previous frame at uv_prev: each patch has its own mean removed first, so a brightness
|
||||
// change (flicker) contributes nothing and only the pattern is compared.
|
||||
// Also returns the current patch's contrast (mean absolute deviation): a patch with no
|
||||
// structure cannot decide anything, and the caller must not pretend it can.
|
||||
float PatchError(float2 uv_cur, float2 uv_prev, out float contrast)
|
||||
{
|
||||
const float2 px = BUFFER_PIXEL_SIZE;
|
||||
float c[9], p[9];
|
||||
float mc = 0.0, mp = 0.0;
|
||||
[unroll] for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
const float2 o = float2(i % 3 - 1, i / 3 - 1) * px;
|
||||
c[i] = Luma(uv_cur + o);
|
||||
p[i] = tex2Dlod(sDLSS5_PrevLuma, float4(uv_prev + o, 0.0, 0.0)).x;
|
||||
mc += c[i]; mp += p[i];
|
||||
}
|
||||
mc /= 9.0; mp /= 9.0;
|
||||
float err = 0.0;
|
||||
contrast = 0.0;
|
||||
[unroll] for (int j = 0; j < 9; ++j)
|
||||
{
|
||||
err += abs((c[j] - mc) - (p[j] - mp));
|
||||
contrast += abs(c[j] - mc);
|
||||
}
|
||||
contrast /= 9.0;
|
||||
return err / 9.0;
|
||||
}
|
||||
|
||||
// Per-test failure (0 = fine, 1 = failed, soft in between): x = luma, y = depth, z = consistency,
|
||||
// w = the static hypothesis won. Luma failing says "this pixel's appearance changed"; depth or
|
||||
// consistency failing says "this vector points at the wrong thing"; static winning says "no
|
||||
// vector explains this pixel better than zero". Only y, z and w justify zeroing the vector, and
|
||||
// only x, y, z justify asking DLSS to distrust history.
|
||||
float4 ValidateTests(float2 uv, float2 mv)
|
||||
{
|
||||
const float2 puv = uv + mv;
|
||||
float4 bad = 0.0;
|
||||
// Reprojecting off-screen: nothing to compare against. Keep the vector (DLSS handles
|
||||
// it) and let the mask lean on the current frame.
|
||||
if (any(puv < 0.0) || any(puv > 1.0)) return float4(1.0, 0.0, 0.0, 0.0);
|
||||
|
||||
// 0. Static hypothesis: does "did not move" explain this pixel at least as well as the
|
||||
// vector does? Scored on mean-removed structure, so flicker is not motion. Skipped for
|
||||
// vectors under half a pixel (nothing to decide).
|
||||
if (VALIDATE_STATIC && length(mv * BUFFER_SCREEN_SIZE) > 0.5)
|
||||
{
|
||||
float sc, unused;
|
||||
const float es = PatchError(uv, uv, sc);
|
||||
const float ef = PatchError(uv, puv, unused);
|
||||
// Only a patch with structure can tell the two apart. Below the contrast floor the
|
||||
// scores tie for lack of evidence, and a tie must go to the provider (its flow is
|
||||
// propagated from textured neighbours -- the right guess for a moving flat wall).
|
||||
// With structure, static wins only if it beats the vector by a share of that contrast.
|
||||
if (sc >= STATIC_MIN_CONTRAST)
|
||||
bad.w = es + 0.25 * sc <= ef * (1.0 + STATIC_BIAS) ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
// 1. Luma: current 3x3 range vs the previous luma at the reprojected spot.
|
||||
if (VALIDATE_LUMA)
|
||||
{
|
||||
const float2 px = BUFFER_PIXEL_SIZE;
|
||||
float lc = Luma(uv), lmin = lc, lmax = lc;
|
||||
[unroll] for (int y = -1; y <= 1; ++y)
|
||||
[unroll] for (int x = -1; x <= 1; ++x)
|
||||
{
|
||||
const float l = Luma(uv + float2(x, y) * px);
|
||||
lmin = min(lmin, l); lmax = max(lmax, l);
|
||||
}
|
||||
const float lp = tex2Dlod(sDLSS5_PrevLuma, float4(puv, 0.0, 0.0)).x;
|
||||
const float margin = LUMA_TOLERANCE * max(lmax, 0.05) + 2.0 / 255.0;
|
||||
bad.x = saturate(max(lmin - lp, lp - lmax) / margin);
|
||||
}
|
||||
|
||||
// 2. Depth: previous linear depth at the reprojected spot vs the current one (sky exempt).
|
||||
const float dc = ReShade::GetLinearizedDepth(uv);
|
||||
if (VALIDATE_DEPTH && dc < 0.999)
|
||||
{
|
||||
const float dp = tex2Dlod(sDLSS5_PrevDepth, float4(puv, 0.0, 0.0)).x;
|
||||
const float tol = DEPTH_TOLERANCE * max(dc, 1e-3);
|
||||
bad.y = saturate((abs(dp - dc) - tol) / (tol + 1e-5));
|
||||
}
|
||||
|
||||
// 3. Consistency: the previous frame's vector where this pixel came from vs this one.
|
||||
if (VALIDATE_MV && MV_CONSISTENCY > 0.0)
|
||||
{
|
||||
const float2 pmv = tex2Dlod(sDLSS5_PrevMV, float4(puv, 0.0, 0.0)).xy;
|
||||
const float diff = length((mv - pmv) * BUFFER_SCREEN_SIZE);
|
||||
const float allow = MV_CONSISTENCY + 0.5 * length(mv * BUFFER_SCREEN_SIZE);
|
||||
bad.z = saturate((diff - allow) / allow);
|
||||
}
|
||||
return bad;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Camera model. Screen position x, y in -0.5..0.5, inverse-depth term w = s / (depth + s).
|
||||
// Basis (9 terms): 1, x, y, x^2, xy, y^2, w, xw, yw -- the small-rotation flow field of a
|
||||
// pinhole camera is quadratic in the image position, and translation adds terms in 1/Z.
|
||||
// Both flow components share the basis; the fit solves them together (two right-hand sides).
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
#define DLSS5_BASIS(B, x, y, w) \
|
||||
B[0] = 1.0; B[1] = x; B[2] = y; B[3] = x * x; B[4] = x * y; B[5] = y * y; B[6] = w; B[7] = x * w; B[8] = y * w;
|
||||
|
||||
float ParallaxW(float d) { return GEOM_PARALLAX / (d + GEOM_PARALLAX); }
|
||||
|
||||
// The model's predicted delta-UV at uv for linear depth d.
|
||||
float2 PredictMV(float2 uv, float d)
|
||||
{
|
||||
const float4 c = float4(0.5, 0.5, 0.0, 0.0);
|
||||
const float4 p0 = tex2Dlod(sDLSS5_Cam0, c), p1 = tex2Dlod(sDLSS5_Cam1, c), p2 = tex2Dlod(sDLSS5_Cam2, c);
|
||||
const float4 p3 = tex2Dlod(sDLSS5_Cam3, c), p4 = tex2Dlod(sDLSS5_Cam4, c);
|
||||
const float x = uv.x - 0.5, y = uv.y - 0.5, w = ParallaxW(d);
|
||||
float B[9]; DLSS5_BASIS(B, x, y, w)
|
||||
// u: p0.xyzw p1.xyzw p2.x v: p2.yzw p3.xyzw p4.xy
|
||||
const float u = p0.x * B[0] + p0.y * B[1] + p0.z * B[2] + p0.w * B[3] + p1.x * B[4] + p1.y * B[5] + p1.z * B[6] + p1.w * B[7] + p2.x * B[8];
|
||||
const float v = p2.y * B[0] + p2.z * B[1] + p2.w * B[2] + p3.x * B[3] + p3.y * B[4] + p3.z * B[5] + p3.w * B[6] + p4.x * B[7] + p4.y * B[8];
|
||||
return float2(u, v);
|
||||
}
|
||||
|
||||
bool FitIsUsable()
|
||||
{
|
||||
const float4 s = tex2Dlod(sDLSS5_Cam5, float4(0.5, 0.5, 0.0, 0.0)); // x = inlier share, y = rms px, z = samples used
|
||||
return s.z >= 40.0 && s.x >= 0.25;
|
||||
}
|
||||
|
||||
// Pass 1: sample the provider's flow and the depth on a sparse grid.
|
||||
void PS_FitSamples(float4 vpos : SV_Position, float2 uv : TEXCOORD, out float4 A : SV_Target0, out float4 B : SV_Target1)
|
||||
{
|
||||
const float2 suv = (floor(vpos.xy) + 0.5) / float2(DLSS5_FIT_W, DLSS5_FIT_H);
|
||||
const float d = ReShade::GetLinearizedDepth(suv);
|
||||
const float2 mv = ProviderMV(suv);
|
||||
const bool valid = d > 0.001 && all(abs(mv * BUFFER_SCREEN_SIZE) < 512.0);
|
||||
A = float4(suv.x - 0.5, suv.y - 0.5, ParallaxW(d), valid ? 1.0 : 0.0);
|
||||
B = float4(mv, 0.0, 0.0);
|
||||
}
|
||||
|
||||
// Pass 2 (one pixel): robust least squares. Pass one fits everything; pass two refits on the
|
||||
// samples the first fit explains to within GEOM_OUTLIER_PX, which drops moving objects,
|
||||
// flames and the weapon from the camera estimate.
|
||||
void PS_FitSolve(float4 vpos : SV_Position, float2 uv : TEXCOORD,
|
||||
out float4 P0 : SV_Target0, out float4 P1 : SV_Target1, out float4 P2 : SV_Target2,
|
||||
out float4 P3 : SV_Target3, out float4 P4 : SV_Target4, out float4 P5 : SV_Target5)
|
||||
{
|
||||
float p[18];
|
||||
[unroll] for (int z = 0; z < 18; ++z) p[z] = 0.0;
|
||||
float inlier = 0.0, rms = 0.0, used = 0.0;
|
||||
const int total = DLSS5_FIT_W * DLSS5_FIT_H;
|
||||
|
||||
[loop] for (int it = 0; it < 2; ++it)
|
||||
{
|
||||
float M[45]; // upper triangle of the 9x9 normal matrix
|
||||
float ru[9], rv[9];
|
||||
[unroll] for (int z0 = 0; z0 < 45; ++z0) M[z0] = 0.0;
|
||||
[unroll] for (int z1 = 0; z1 < 9; ++z1) { ru[z1] = 0.0; rv[z1] = 0.0; }
|
||||
int n = 0;
|
||||
float se = 0.0;
|
||||
|
||||
[loop] for (int s = 0; s < total; ++s)
|
||||
{
|
||||
const int2 cell = int2(s % DLSS5_FIT_W, s / DLSS5_FIT_W);
|
||||
const float4 a = tex2Dfetch(sDLSS5_FitA, cell);
|
||||
const float4 b = tex2Dfetch(sDLSS5_FitB, cell);
|
||||
if (a.w < 0.5) continue;
|
||||
float B[9]; DLSS5_BASIS(B, a.x, a.y, a.z)
|
||||
if (it > 0)
|
||||
{
|
||||
float pu = 0.0, pv = 0.0;
|
||||
[unroll] for (int i0 = 0; i0 < 9; ++i0) { pu += p[i0] * B[i0]; pv += p[9 + i0] * B[i0]; }
|
||||
const float r = length((float2(pu, pv) - b.xy) * BUFFER_SCREEN_SIZE);
|
||||
if (r > GEOM_OUTLIER_PX) continue;
|
||||
se += r * r;
|
||||
}
|
||||
++n;
|
||||
int k = 0;
|
||||
[unroll] for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
ru[i] += B[i] * b.x;
|
||||
rv[i] += B[i] * b.y;
|
||||
[unroll] for (int j = i; j < 9; ++j) { M[k] += B[i] * B[j]; ++k; }
|
||||
}
|
||||
}
|
||||
if (n < 40) break; // not enough evidence: keep whatever the previous pass produced
|
||||
|
||||
// Augmented 9 x (9 + 2) system, Gauss-Jordan with partial pivoting, tiny ridge for
|
||||
// the degenerate cases (flat depth makes w collinear with 1; a still camera makes
|
||||
// everything zero).
|
||||
float G[99];
|
||||
{
|
||||
int k2 = 0;
|
||||
[unroll] for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
[unroll] for (int j = i; j < 9; ++j) { G[i * 11 + j] = M[k2]; G[j * 11 + i] = M[k2]; ++k2; }
|
||||
G[i * 11 + i] += 1e-5 * n;
|
||||
G[i * 11 + 9] = ru[i];
|
||||
G[i * 11 + 10] = rv[i];
|
||||
}
|
||||
}
|
||||
bool singular = false;
|
||||
[loop] for (int col = 0; col < 9; ++col)
|
||||
{
|
||||
int piv = col;
|
||||
float best = abs(G[col * 11 + col]);
|
||||
[loop] for (int r0 = col + 1; r0 < 9; ++r0)
|
||||
{
|
||||
const float v0 = abs(G[r0 * 11 + col]);
|
||||
if (v0 > best) { best = v0; piv = r0; }
|
||||
}
|
||||
if (best < 1e-12) { singular = true; break; }
|
||||
if (piv != col)
|
||||
[unroll] for (int c0 = 0; c0 < 11; ++c0) { const float t = G[col * 11 + c0]; G[col * 11 + c0] = G[piv * 11 + c0]; G[piv * 11 + c0] = t; }
|
||||
const float inv = 1.0 / G[col * 11 + col];
|
||||
[unroll] for (int c1 = 0; c1 < 11; ++c1) G[col * 11 + c1] *= inv;
|
||||
[loop] for (int r1 = 0; r1 < 9; ++r1)
|
||||
{
|
||||
if (r1 == col) continue;
|
||||
const float f = G[r1 * 11 + col];
|
||||
if (f == 0.0) continue;
|
||||
[unroll] for (int c2 = 0; c2 < 11; ++c2) G[r1 * 11 + c2] -= f * G[col * 11 + c2];
|
||||
}
|
||||
}
|
||||
if (singular) break;
|
||||
[unroll] for (int i2 = 0; i2 < 9; ++i2) { p[i2] = G[i2 * 11 + 9]; p[9 + i2] = G[i2 * 11 + 10]; }
|
||||
used = n;
|
||||
inlier = float(n) / float(total);
|
||||
if (it > 0) rms = sqrt(se / max(n, 1));
|
||||
}
|
||||
|
||||
P0 = float4(p[0], p[1], p[2], p[3]);
|
||||
P1 = float4(p[4], p[5], p[6], p[7]);
|
||||
P2 = float4(p[8], p[9], p[10], p[11]);
|
||||
P3 = float4(p[12], p[13], p[14], p[15]);
|
||||
P4 = float4(p[16], p[17], 0.0, 0.0);
|
||||
P5 = float4(inlier, rms, used, 0.0);
|
||||
}
|
||||
|
||||
// Per-pixel decision: x = final delta-UV vector, .z = 0 model / 1 provider (moving object) /
|
||||
// 2 provider rejected, .w = mask contribution from that decision.
|
||||
float4 GeometryDecide(float2 uv, float d, float2 flow)
|
||||
{
|
||||
const float2 pred = PredictMV(uv, d);
|
||||
const float r = length((flow - pred) * BUFFER_SCREEN_SIZE);
|
||||
const float agree = GEOM_AGREE_PX + 0.1 * length(pred * BUFFER_SCREEN_SIZE);
|
||||
if (r <= agree) return float4(pred, 0.0, 0.0);
|
||||
|
||||
float cp, cf;
|
||||
const float ep = PatchError(uv, uv + pred, cp);
|
||||
const float ef = PatchError(uv, uv + flow, cf);
|
||||
const bool dynamic = cp >= STATIC_MIN_CONTRAST && ef <= ep * (1.0 - GEOM_DYNAMIC_MARGIN) - 1.0 / 255.0;
|
||||
if (dynamic) return float4(flow, 1.0, 0.0);
|
||||
return float4(pred, 2.0, GEOM_MASK_REJECTED * saturate((r - agree) / (4.0 * agree)));
|
||||
}
|
||||
|
||||
float RawDepth(float2 uv)
|
||||
{
|
||||
// Raw hardware depth, exactly as the game wrote it -- the same orientation/offset
|
||||
// corrections ReShade.fxh applies in GetLinearizedDepth(), minus the linearisation
|
||||
// (DLSS must receive the raw values; the add-on tells it whether the range is reversed).
|
||||
float2 t = uv;
|
||||
#if RESHADE_DEPTH_INPUT_IS_UPSIDE_DOWN
|
||||
t.y = 1.0 - t.y;
|
||||
#endif
|
||||
t.x /= RESHADE_DEPTH_INPUT_X_SCALE;
|
||||
t.y /= RESHADE_DEPTH_INPUT_Y_SCALE;
|
||||
#if RESHADE_DEPTH_INPUT_X_PIXEL_OFFSET
|
||||
t.x -= RESHADE_DEPTH_INPUT_X_PIXEL_OFFSET * BUFFER_RCP_WIDTH;
|
||||
#else
|
||||
t.x -= RESHADE_DEPTH_INPUT_X_OFFSET / 2.000000001;
|
||||
#endif
|
||||
#if RESHADE_DEPTH_INPUT_Y_PIXEL_OFFSET
|
||||
t.y += RESHADE_DEPTH_INPUT_Y_PIXEL_OFFSET * BUFFER_RCP_HEIGHT;
|
||||
#else
|
||||
t.y += RESHADE_DEPTH_INPUT_Y_OFFSET / 2.000000001;
|
||||
#endif
|
||||
return tex2Dlod(ReShade::DepthBuffer, float4(t, 0.0, 0.0)).x;
|
||||
}
|
||||
|
||||
void PS_MotionVectors(float4 vpos : SV_Position, float2 uv : TEXCOORD,
|
||||
out float2 mv_out : SV_Target0, out float mask : SV_Target1,
|
||||
out float depth : SV_Target2)
|
||||
{
|
||||
// Providers hand out "delta UV": previous position = uv + mv. DLSS wants the same
|
||||
// direction, in pixels.
|
||||
const float2 flow = ProviderMV(uv);
|
||||
float2 mv = flow;
|
||||
float distrust = 0.0;
|
||||
|
||||
if (GEOM_ENABLE && FitIsUsable())
|
||||
{
|
||||
const float d = ReShade::GetLinearizedDepth(uv);
|
||||
const float4 g = GeometryDecide(uv, d, flow);
|
||||
mv = g.xy;
|
||||
distrust = g.w;
|
||||
// Disocclusion: the geometric vector on a newly revealed pixel points into the
|
||||
// occluder's old position; the depth test catches that and asks for the current frame.
|
||||
if (VALIDATE_DEPTH && d < 0.999)
|
||||
{
|
||||
const float2 puv = uv + mv;
|
||||
if (all(puv >= 0.0) && all(puv <= 1.0))
|
||||
{
|
||||
const float dp = tex2Dlod(sDLSS5_PrevDepth, float4(puv, 0.0, 0.0)).x;
|
||||
const float tol = DEPTH_TOLERANCE * max(d, 1e-3);
|
||||
distrust = max(distrust, saturate((abs(dp - d) - tol) / (tol + 1e-5)));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (MV_VALIDATE)
|
||||
{
|
||||
const float4 bad = ValidateTests(uv, flow);
|
||||
const float zero_vector = max(bad.y, max(bad.z, bad.w)); // wrong target, or static explains it: treat as static
|
||||
distrust = max(bad.x, max(bad.y, bad.z)); // appearance changed / wrong target: favour the current frame
|
||||
mv = flow * (1.0 - zero_vector);
|
||||
}
|
||||
|
||||
mv_out = mv * float2(BUFFER_WIDTH, BUFFER_HEIGHT) * MV_SIGN * MV_SCALE;
|
||||
mask = distrust * MASK_STRENGTH;
|
||||
depth = RawDepth(uv);
|
||||
}
|
||||
|
||||
// End of the technique: this frame becomes next frame's history. The raw provider vector is
|
||||
// stored (not the validated one), so one distrusted frame does not poison the next test.
|
||||
void PS_StoreHistory(float4 vpos : SV_Position, float2 uv : TEXCOORD,
|
||||
out float luma : SV_Target0, out float depth : SV_Target1, out float2 mv : SV_Target2)
|
||||
{
|
||||
luma = Luma(uv);
|
||||
depth = ReShade::GetLinearizedDepth(uv);
|
||||
mv = ProviderMV(uv);
|
||||
}
|
||||
|
||||
float3 PS_Debug(float4 vpos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
if (DEBUG_VIEW == 1)
|
||||
{
|
||||
const float raw_depth = tex2Dlod(sDLSS5_Depth, float4(uv, 0.0, 0.0)).x;
|
||||
#if RESHADE_DEPTH_INPUT_IS_REVERSED
|
||||
const float proximity = raw_depth;
|
||||
#else
|
||||
const float proximity = 1.0 - raw_depth;
|
||||
#endif
|
||||
// Display-only contrast curve: DLSS5_Depth itself remains raw and untouched.
|
||||
return pow(saturate(proximity), 0.125).xxx;
|
||||
}
|
||||
if (DEBUG_VIEW == 2)
|
||||
{
|
||||
#if DLSS5_MV_LOWRES
|
||||
return saturate(tex2Dlod(sDLSS5_ProviderConfidence, float4(uv, 0.0, 0.0)).x).xxx;
|
||||
#else
|
||||
return (0.25).xxx; // this provider publishes no confidence map
|
||||
#endif
|
||||
}
|
||||
if (DEBUG_VIEW == 3)
|
||||
return tex2Dlod(sDLSS5_Mask, float4(uv, 0.0, 0.0)).xxx;
|
||||
if (DEBUG_VIEW == 4)
|
||||
{
|
||||
const float m = tex2Dlod(sDLSS5_Mask, float4(uv, 0.0, 0.0)).x;
|
||||
const float3 img = tex2Dlod(sDLSS5_ColorInput, float4(uv, 0.0, 0.0)).rgb;
|
||||
return lerp(img, float3(1.0, 0.2, 0.1), m * 0.75);
|
||||
}
|
||||
if (DEBUG_VIEW == 5)
|
||||
{
|
||||
// Recomputed here against the same history the feed pass used this frame.
|
||||
const float4 bad = ValidateTests(uv, ProviderMV(uv));
|
||||
const float3 img = tex2Dlod(sDLSS5_ColorInput, float4(uv, 0.0, 0.0)).rgb * 0.5;
|
||||
return saturate(img + bad.xyz * 0.9 + bad.w * float3(0.6, 0.6, 0.0));
|
||||
}
|
||||
if (DEBUG_VIEW == 6)
|
||||
{
|
||||
const float2 pv = PredictMV(uv, ReShade::GetLinearizedDepth(uv)) * BUFFER_SCREEN_SIZE;
|
||||
const float angle = atan2(pv.y, pv.x), speed = length(pv);
|
||||
const float3 rgb = saturate(3.0 * abs(2.0 * frac(angle / 6.283185 + float3(0.0, -1.0 / 3.0, 1.0 / 3.0)) - 1.0) - 1.0);
|
||||
return lerp(0.5, rgb, saturate(speed / 16.0));
|
||||
}
|
||||
if (DEBUG_VIEW == 7)
|
||||
{
|
||||
const float3 img = tex2Dlod(sDLSS5_ColorInput, float4(uv, 0.0, 0.0)).rgb * 0.5;
|
||||
if (!FitIsUsable()) return img; // no usable fit this frame: nothing to show
|
||||
const float4 g = GeometryDecide(uv, ReShade::GetLinearizedDepth(uv), ProviderMV(uv));
|
||||
const float3 tint = g.z < 0.5 ? float3(0.0, 0.5, 0.0) : g.z < 1.5 ? float3(0.9, 0.0, 0.0) : float3(0.0, 0.2, 0.9);
|
||||
return saturate(img + tint);
|
||||
}
|
||||
if (DEBUG_VIEW == 8)
|
||||
{
|
||||
const float4 s = tex2Dlod(sDLSS5_Cam5, float4(0.5, 0.5, 0.0, 0.0));
|
||||
if (uv.y < 0.05) return saturate(s.y / 8.0).xxx; // fit error strip
|
||||
return s.x.xxx; // inlier share
|
||||
}
|
||||
float2 mv = tex2Dlod(sDLSS5_MV, float4(uv, 0.0, 0.0)).xy; // pixels
|
||||
float angle = atan2(mv.y, mv.x);
|
||||
float speed = length(mv);
|
||||
float3 rgb = saturate(3.0 * abs(2.0 * frac(angle / 6.283185 + float3(0.0, -1.0 / 3.0, 1.0 / 3.0)) - 1.0) - 1.0);
|
||||
return lerp(0.5, rgb, saturate(speed / 16.0)); // 16 px/frame saturates the colour
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
technique DLSS5_Feed
|
||||
<
|
||||
ui_label = "DLSS 5 Feed (place below your motion-vector provider)";
|
||||
ui_tooltip = "Prepares motion vectors + depth (+ a trust mask) for the DLSS 5 Feed add-on.\n\n"
|
||||
"Provider: " DLSS5_MV_PROVIDER_NAME "\n"
|
||||
"Change it with the DLSS5_MV_PROVIDER preprocessor definition (0 texMotionVectors,\n"
|
||||
"1 Launchpad, 2 VORT, 3 LumeniteFX Kernel, 4 LumeniteFX QuantMotion) and enable\n"
|
||||
"that provider's technique ABOVE this one.";
|
||||
>
|
||||
{
|
||||
pass FitSamples { VertexShader = PostProcessVS; PixelShader = PS_FitSamples; RenderTarget0 = DLSS5_FitA; RenderTarget1 = DLSS5_FitB; }
|
||||
pass FitSolve { VertexShader = PostProcessVS; PixelShader = PS_FitSolve; RenderTarget0 = DLSS5_Cam0; RenderTarget1 = DLSS5_Cam1; RenderTarget2 = DLSS5_Cam2; RenderTarget3 = DLSS5_Cam3; RenderTarget4 = DLSS5_Cam4; RenderTarget5 = DLSS5_Cam5; }
|
||||
pass Guides { VertexShader = PostProcessVS; PixelShader = PS_MotionVectors; RenderTarget0 = DLSS5_MV; RenderTarget1 = DLSS5_Mask; RenderTarget2 = DLSS5_Depth; }
|
||||
pass History { VertexShader = PostProcessVS; PixelShader = PS_StoreHistory; RenderTarget0 = DLSS5_PrevLuma; RenderTarget1 = DLSS5_PrevDepth; RenderTarget2 = DLSS5_PrevMV; }
|
||||
DLSS5_MV_REQUEST_PASS // Launchpad only: ask it to compute optical flow again next frame
|
||||
}
|
||||
|
||||
technique DLSS5_Feed_Debug
|
||||
<
|
||||
ui_label = "DLSS 5 Feed - debug view";
|
||||
ui_tooltip = "Shows the motion vectors / depth / mask the add-on will send to DLSS. Enable only for checking.";
|
||||
>
|
||||
{
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_Debug; }
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
========================================================================
|
||||
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_ColorManagement.fxh
|
||||
Version : 2026.05.05
|
||||
Author : Afzaal (Kaidō)
|
||||
Description: Provides color management including color space detection,
|
||||
color space transfers and tonemapping.
|
||||
Supported colorbuffers:
|
||||
- SDR (sRGB)
|
||||
- HDR (scRGB / Linear)
|
||||
- HDR (PQ / ST.2084)
|
||||
- HDR (HLG)
|
||||
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*-------------------.
|
||||
| :: PREPROCESSOR :: |
|
||||
'-------------------*/
|
||||
#ifndef HDR_WHITELEVEL
|
||||
#define HDR_WHITELEVEL 203
|
||||
#endif
|
||||
|
||||
#if BUFFER_COLOR_SPACE > 0
|
||||
//already defined by ReShade
|
||||
#else
|
||||
#if BUFFER_COLOR_BIT_DEPTH == 8
|
||||
#undef BUFFER_COLOR_SPACE
|
||||
#define BUFFER_COLOR_SPACE 1 //sRGB
|
||||
#elif BUFFER_COLOR_BIT_DEPTH == 16
|
||||
#undef BUFFER_COLOR_SPACE
|
||||
#define BUFFER_COLOR_SPACE 2 //scRGB
|
||||
#elif __RENDERER__ < 0xb000
|
||||
#undef BUFFER_COLOR_SPACE
|
||||
#define BUFFER_COLOR_SPACE 1 //D3D9/10 usually SDR
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*------------------.
|
||||
| :: UI UNIFORMS :: |
|
||||
'------------------*/
|
||||
// uniform int SHOW_COLOR_SPACE <
|
||||
// ui_category = "Color Management";
|
||||
// ui_type = "combo";
|
||||
// ui_label = "Colorspace";
|
||||
// ui_tooltip = "Shows the detected color space.\n1=sRGB, 2=scRGB, 3=PQ, 4=HLG";
|
||||
// hidden = true;
|
||||
// #if BUFFER_COLOR_SPACE == 1
|
||||
// ui_items = "sRGB (Detected)\0";
|
||||
// #elif BUFFER_COLOR_SPACE == 2
|
||||
// ui_items = "scRGB (Detected)\0";
|
||||
// #elif BUFFER_COLOR_SPACE == 3
|
||||
// ui_items = "PQ / ST.2084 (Detected)\0";
|
||||
// #elif BUFFER_COLOR_SPACE == 4
|
||||
// ui_items = "HLG (Detected)\0";
|
||||
// #else
|
||||
// ui_items = "Unknown (Defaulting to sRGB)\0";
|
||||
// #endif
|
||||
// > = 0;
|
||||
|
||||
#if BUFFER_COLOR_BIT_DEPTH > 8 || BUFFER_COLOR_SPACE > 1
|
||||
#define COLORSPACE_CONVERSION 1 //use approx. transfer function; 0 for accurate
|
||||
#else
|
||||
#define COLORSPACE_CONVERSION 2 //N/A for 8-bit
|
||||
#endif
|
||||
|
||||
#if BUFFER_COLOR_SPACE == 1
|
||||
#define TONEMAPPER 1 //reinhard tonemapper workflow for SDR (sRGB) colorbuffer; 0 for None
|
||||
#else
|
||||
#define TONEMAPPER 0
|
||||
#endif
|
||||
|
||||
/*-------------------------.
|
||||
| :: TRANSFER FUNCTIONS :: |
|
||||
'-------------------------*/
|
||||
//sRGB
|
||||
float3 sRGBtoLinearAccurate(float3 r) {
|
||||
return (r <= 0.04045) ? (r / 12.92) : pow(abs(r + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
float3 sRGBtoLinearFast(float3 r) {
|
||||
return max(r / 12.92, r * r); //gamma 2.0 approx
|
||||
}
|
||||
|
||||
float3 sRGBtoLinear(float3 r) {
|
||||
if (COLORSPACE_CONVERSION == 1) return sRGBtoLinearFast(r);
|
||||
else return sRGBtoLinearAccurate(r);
|
||||
}
|
||||
|
||||
float3 linearToSRGBAccurate(float3 r) {
|
||||
return (r <= 0.0031308) ? (r * 12.92) : (1.055 * pow(abs(r), 1.0 / 2.4) - 0.055);
|
||||
}
|
||||
|
||||
float3 linearToSRGBFast(float3 r) {
|
||||
return min(r * 12.92, sqrt(r)); //gamma 2.0 approx
|
||||
}
|
||||
|
||||
float3 linearToSRGB(float3 r) {
|
||||
if (COLORSPACE_CONVERSION == 1) return linearToSRGBFast(r);
|
||||
else return linearToSRGBAccurate(r);
|
||||
}
|
||||
|
||||
//PQ (ST.2084)
|
||||
float3 PQtoLinearAccurate(float3 r) {
|
||||
const float m1 = 1305.0/8192.0;
|
||||
const float m2 = 2523.0/32.0;
|
||||
const float c1 = 107.0/128.0;
|
||||
const float c2 = 2413.0/128.0;
|
||||
const float c3 = 2392.0/128.0;
|
||||
float3 powr = pow(max(r, 0), 1.0/m2);
|
||||
r = pow(max(max(powr - c1, 0) / (c2 - c3 * powr), 0), 1.0/m1);
|
||||
//scale 10,000 nits down so Paper White (HDR_WHITELEVEL) maps to 1.0
|
||||
return r * 10000.0 / HDR_WHITELEVEL;
|
||||
}
|
||||
|
||||
float3 PQtoLinearFast(float3 r) {
|
||||
float3 square = r * r;
|
||||
float3 quad = square * square;
|
||||
float3 oct = quad * quad;
|
||||
r = max(max(square / 340.0, quad / 6.0), oct);
|
||||
return r * 10000.0 / HDR_WHITELEVEL;
|
||||
}
|
||||
|
||||
float3 PQtoLinear(float3 r) {
|
||||
if (COLORSPACE_CONVERSION == 1) return PQtoLinearFast(r);
|
||||
else return PQtoLinearAccurate(r);
|
||||
}
|
||||
|
||||
float3 linearToPQAccurate(float3 r) {
|
||||
const float m1 = 1305.0/8192.0;
|
||||
const float m2 = 2523.0/32.0;
|
||||
const float c1 = 107.0/128.0;
|
||||
const float c2 = 2413.0/128.0;
|
||||
const float c3 = 2392.0/128.0;
|
||||
|
||||
r = r * (HDR_WHITELEVEL / 10000.0); //rescale 1.0 back to nits
|
||||
float3 powr = pow(max(r, 0), m1);
|
||||
r = pow(max((c1 + c2 * powr) / (1 + c3 * powr), 0), m2);
|
||||
return r;
|
||||
}
|
||||
|
||||
float3 linearToPQFast(float3 r) {
|
||||
r = r * (HDR_WHITELEVEL / 10000.0);
|
||||
float3 squareroot = sqrt(r);
|
||||
float3 quadroot = sqrt(squareroot);
|
||||
float3 octroot = sqrt(quadroot);
|
||||
r = min(octroot, min(sqrt(sqrt(6.0))*quadroot, sqrt(340.0)*squareroot));
|
||||
return r;
|
||||
}
|
||||
|
||||
float3 linearToPQ(float3 r) {
|
||||
if (COLORSPACE_CONVERSION == 1) return linearToPQFast(r);
|
||||
else return linearToPQAccurate(r);
|
||||
}
|
||||
|
||||
//HLG (Hybrid Log Gamma)
|
||||
float3 linearToHLG(float3 r) {
|
||||
r = r * HDR_WHITELEVEL / 1000.0;
|
||||
const float a = 0.17883277;
|
||||
const float b = 0.28466892;
|
||||
const float c = 0.55991073;
|
||||
float3 s = sqrt(3 * r);
|
||||
return (s < 0.5) ? s : (log(12 * r - b) * a + c);
|
||||
}
|
||||
|
||||
float3 HLGtoLinear(float3 r) {
|
||||
const float a = 0.17883277;
|
||||
const float b = 0.28466892;
|
||||
const float c = 0.55991073;
|
||||
r = (r < 0.5) ? (r * r / 3.0) : ((exp((r - c) / a) + b) / 12.0);
|
||||
return r * 1000.0 / HDR_WHITELEVEL;
|
||||
}
|
||||
|
||||
//YCoCg
|
||||
float3 linearToYCoCg(float3 r) {
|
||||
float y = (r.r + 2.0 * r.g + r.b) * 0.25;
|
||||
float co = (r.r - r.b) * 0.5;
|
||||
float cg = (r.g - (r.r + r.b) * 0.5) * 0.5;
|
||||
return float3(y, co, cg);
|
||||
}
|
||||
|
||||
float3 YCoCgToLinear(float3 r) {
|
||||
float y = r.x;
|
||||
float co = r.y;
|
||||
float cg = r.z;
|
||||
float g = y + cg;
|
||||
float rOut = y + co - cg;
|
||||
float b = y - co - cg;
|
||||
return float3(rOut, g, b);
|
||||
}
|
||||
|
||||
/*--------------.
|
||||
| :: HELPERS :: |
|
||||
'--------------*/
|
||||
float3 ToLinearColorspace(float3 r, bool tonemap) {
|
||||
if (BUFFER_COLOR_SPACE == 2) r = r * (80.0 / HDR_WHITELEVEL); //scRGB
|
||||
else if (BUFFER_COLOR_SPACE == 3) r = PQtoLinear(r);
|
||||
else if (BUFFER_COLOR_SPACE == 4) r = HLGtoLinear(r);
|
||||
else {
|
||||
r = sRGBtoLinear(r);
|
||||
if (TONEMAPPER == 1 && tonemap) r = r / max(1.0 - r, 0.001); //inverse reinhard
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
float3 ToOutputColorspace(float3 r, bool tonemap) {
|
||||
if (BUFFER_COLOR_SPACE == 2) r = r * (HDR_WHITELEVEL / 80.0); //scRGB
|
||||
else if (BUFFER_COLOR_SPACE == 3) r = linearToPQ(r);
|
||||
else if (BUFFER_COLOR_SPACE == 4) r = linearToHLG(r);
|
||||
else {
|
||||
if (TONEMAPPER == 1 && tonemap) r = r / (1.0 + r); //forward reinhard
|
||||
r = linearToSRGB(r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
//read the theoretical max value of the buffer (in linear scale)
|
||||
float GetMaxColorValue() {
|
||||
if (BUFFER_COLOR_SPACE == 4) return 1000.0 / HDR_WHITELEVEL;
|
||||
if (BUFFER_COLOR_SPACE >= 2) return 10000.0 / HDR_WHITELEVEL;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float GetLuminance(float3 color)
|
||||
{
|
||||
return dot(color, float3(0.2126, 0.7152, 0.0722));
|
||||
}
|
||||
|
||||
float3 GetLinearColor(float2 uv, bool tonemap)
|
||||
{
|
||||
float3 color = tex2Dlod(ReShade::BackBuffer, float4(uv, 0, 0)).rgb;
|
||||
return ToLinearColorspace(color, tonemap);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
========================================================================
|
||||
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_Compute.fxh
|
||||
Version : 2026.05.09
|
||||
Author : Afzaal (Kaidō)
|
||||
Description: Header file for supporting compute enabled platforms.
|
||||
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ReShade.fxh"
|
||||
|
||||
/*------------------.
|
||||
| :: DEFINITIONS :: |
|
||||
'------------------*/
|
||||
#define D3D9 0x9000
|
||||
#define D3D10 0xa000
|
||||
#define D3D11 0xb000
|
||||
#define D3D12 0xc000
|
||||
#define OPENGL 0x10000
|
||||
#define VULKAN 0x20000
|
||||
|
||||
#if __RENDERER__ >= D3D11
|
||||
#define _COMPUTE_ENABLED_ 1
|
||||
#else
|
||||
#define _COMPUTE_ENABLED_ 0
|
||||
#endif
|
||||
|
||||
struct CSInput
|
||||
{
|
||||
uint3 dispatchID : SV_DispatchThreadID; //global pixel coord (x, y, 0)
|
||||
uint3 groupID : SV_GroupID; //which tile/group in grid
|
||||
uint3 localID : SV_GroupThreadID; //thread inside group [0..CS_W-1]
|
||||
uint flatIndex : SV_GroupIndex; //localID flattened: y*CS_W + x
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
========================================================================
|
||||
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_Helpers.fxh
|
||||
Version : 2026.05.30
|
||||
Author : Afzaal (Kaidō)
|
||||
Description: Helper functions for Lumenite shaders.
|
||||
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ReShade.fxh"
|
||||
|
||||
/*------------------.
|
||||
| :: DEFINITIONS :: |
|
||||
'------------------*/
|
||||
#define PI 3.14159265359
|
||||
#define EPSILON 1e-6
|
||||
//R2 sequence constants
|
||||
static const float PHI_2 = 1.324717957244746;
|
||||
static const float2 R2_CONSTANT = float2(1.0/PHI_2, 1.0/(PHI_2*PHI_2));
|
||||
|
||||
/*--------------.
|
||||
| :: UNIFORMS ::|
|
||||
'--------------*/
|
||||
uniform float TIMER < source = "timer"; >; //ms since launch
|
||||
uniform float FRAME_TIME < source = "frametime"; >; //ms last frame
|
||||
uniform uint FRAME_COUNT < source = "framecount"; >;
|
||||
uniform float2 MOUSE_POS < source = "mousepoint"; >; //in screen px
|
||||
uniform bool MOUSE_DOWN < source = "mousebutton"; min = 0; max = 0; >;
|
||||
|
||||
/*--------------.
|
||||
| :: HELPERS :: |
|
||||
'--------------*/
|
||||
bool CheckerboardSkip(uint2 currentPos, float scale)
|
||||
{
|
||||
//map current buffer pixel to full screen pixel.
|
||||
//floor() to ensure we snap to the integer grid of the full screen
|
||||
uint2 fullScreenPos = uint2(floor(currentPos.x * scale), floor(currentPos.y * scale));
|
||||
return (((fullScreenPos.x + fullScreenPos.y + (FRAME_COUNT & 1)) & 1) == 1);
|
||||
}
|
||||
|
||||
float GetDepth(float2 uv)
|
||||
{
|
||||
return ReShade::GetLinearizedDepth(uv);
|
||||
}
|
||||
|
||||
bool IsOOB(float2 uv) {
|
||||
return any(uv < 0.0) || any(uv > 1.0);
|
||||
}
|
||||
|
||||
//QUASI-MONTE CARLO SEQUENCE
|
||||
//fast Hilbert curve math (a 1D index from 2D coords)
|
||||
uint HilbertIndex(uint x, uint y) {
|
||||
uint index = 0;
|
||||
[unroll] for (uint s = 64 / 2; s > 0; s /= 2) {
|
||||
uint rx = (x & s) > 0;
|
||||
uint ry = (y & s) > 0;
|
||||
index += s * s * ((3 * rx) ^ ry);
|
||||
if (ry == 0) {
|
||||
if (rx == 1) {
|
||||
x = 64 - 1 - x;
|
||||
y = 64 - 1 - y;
|
||||
}
|
||||
uint t = x; x = y; y = t;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
float2 GetStratifiedNoise(float2 vpos) {
|
||||
uint2 screenPos = uint2(vpos.xy) % 64; //64x64 tiled pixel coords
|
||||
uint hIndex = HilbertIndex(screenPos.x, screenPos.y); //Hilbert index (spatial)
|
||||
uint totalIndex = hIndex + (uint(FRAME_COUNT % 64) * 288); //temporal offset: 288 (same as Intel XeGTAO implementation)
|
||||
return frac(float(totalIndex) * R2_CONSTANT);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
========================================================================
|
||||
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_Projections.fxh
|
||||
Version : 2026.04.11
|
||||
Author : Afzaal (Kaidō)
|
||||
Description: Camera projection functions for Lumenite shaders.
|
||||
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ReShade.fxh"
|
||||
|
||||
/*--------------.
|
||||
| :: HELPERS :: |
|
||||
'--------------*/
|
||||
//VERTEX SHADER
|
||||
struct VSOUT
|
||||
{
|
||||
float4 vpos : SV_Position;
|
||||
float2 uv : TEXCOORD0;
|
||||
float tan_half_fov_x : TEXCOORD1;
|
||||
float tan_half_fov_y : TEXCOORD2;
|
||||
float inv_tan_half_fov_x : TEXCOORD3;
|
||||
float inv_tan_half_fov_y : TEXCOORD4;
|
||||
float near_ratio : TEXCOORD5;
|
||||
float diff_ratio : TEXCOORD6;
|
||||
};
|
||||
|
||||
#define TAN_HALF_FOV_Y tan(radians(FOV * 0.5))
|
||||
#define ASPECT_RATIO_X_OVER_Y ((float)BUFFER_WIDTH / (float)BUFFER_HEIGHT)
|
||||
#define TAN_HALF_FOV_X TAN_HALF_FOV_Y * ASPECT_RATIO_X_OVER_Y
|
||||
#define INV_TAN_HALF_FOV_X rcp(TAN_HALF_FOV_X)
|
||||
#define INV_TAN_HALF_FOV_Y rcp(TAN_HALF_FOV_Y)
|
||||
|
||||
VSOUT VS(uint id : SV_VertexID)
|
||||
{
|
||||
VSOUT o;
|
||||
o.uv.x = (id == 2) ? 2.0 : 0.0;
|
||||
o.uv.y = (id == 1) ? 2.0 : 0.0;
|
||||
o.vpos = float4(mad(o.uv.x, 2.0, -1.0), mad(o.uv.y, -2.0, 1.0), 0.0, 1.0);
|
||||
o.tan_half_fov_x = TAN_HALF_FOV_X;
|
||||
o.tan_half_fov_y = TAN_HALF_FOV_Y;
|
||||
o.inv_tan_half_fov_x = INV_TAN_HALF_FOV_X;
|
||||
o.inv_tan_half_fov_y = INV_TAN_HALF_FOV_Y;
|
||||
o.near_ratio = NEAR_PLANE / RESHADE_DEPTH_LINEARIZATION_FAR_PLANE;
|
||||
o.diff_ratio = 1.0 - o.near_ratio; //lerp(a,b,t) = (a+t * (b-a)), precompute (b-a) or (1.0-near_ratio) here
|
||||
return o;
|
||||
}
|
||||
|
||||
//PROJECTION FUNCTIONS
|
||||
//normalized frustum
|
||||
//left-handed viewspace
|
||||
//normals point outwards
|
||||
//Z+ goes into the screen
|
||||
float3 UVToViewSpace(float2 uv, float linear_depth_vs, VSOUT ps_input)
|
||||
{
|
||||
float projection_scale = mad(linear_depth_vs, ps_input.diff_ratio, ps_input.near_ratio); //faster lerp: a+t * diff
|
||||
float3 view_pos;
|
||||
float ndc_x = mad(uv.x, 2.0, -1.0);
|
||||
float ndc_y = mad(uv.y, -2.0, 1.0);
|
||||
view_pos.x = ndc_x * ps_input.tan_half_fov_x * projection_scale;
|
||||
view_pos.y = ndc_y * ps_input.tan_half_fov_y * projection_scale;
|
||||
view_pos.z = linear_depth_vs;
|
||||
return view_pos;
|
||||
}
|
||||
|
||||
float2 ViewSpaceToUV(float3 view_pos, VSOUT ps_input)
|
||||
{
|
||||
float inv_projection_scale = rcp(mad(view_pos.z, ps_input.diff_ratio, ps_input.near_ratio));
|
||||
float2 ndc;
|
||||
ndc.x = view_pos.x * ps_input.inv_tan_half_fov_x * inv_projection_scale;
|
||||
ndc.y = view_pos.y * ps_input.inv_tan_half_fov_y * inv_projection_scale;
|
||||
float2 uv;
|
||||
uv.x = mad(ndc.x, 0.5, 0.5);
|
||||
uv.y = mad(ndc.y, -0.5, 0.5);
|
||||
return uv;
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
/*
|
||||
========================================================================
|
||||
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_AnamorphicBloom.fx
|
||||
Version : 2026.06.09
|
||||
Author : Afzaal (Kaidō)
|
||||
Description: Artistic bloom approximating the Anamorphic lens aesthetic.
|
||||
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
/*------------------.
|
||||
| :: DEFINITIONS :: |
|
||||
'------------------*/
|
||||
#ifndef ANAMORPHIC_BLOOM
|
||||
#define ANAMORPHIC_BLOOM 1
|
||||
#endif
|
||||
|
||||
#ifndef ANAMORPHIC_STREAKS
|
||||
#define ANAMORPHIC_STREAKS 0
|
||||
#endif
|
||||
|
||||
#ifndef COLOR_FRINGING
|
||||
#define COLOR_FRINGING 0
|
||||
#endif
|
||||
|
||||
#define BLOOM_THRESHOLD_SCALER 10.0
|
||||
|
||||
/*--------------.
|
||||
| :: HEADERS :: |
|
||||
'--------------*/
|
||||
#include "ReShade.fxh"
|
||||
#include "./include/lumenite_ColorManagement.fxh"
|
||||
#include "./include/lumenite_Helpers.fxh"
|
||||
|
||||
/*---------------.
|
||||
| :: UNIFORMS :: |
|
||||
'---------------*/
|
||||
#if ANAMORPHIC_BLOOM
|
||||
uniform bool BLOOM_SKIP_SKYBOX <
|
||||
ui_type = "radio";
|
||||
ui_label = "Exclude Skybox (Bloom)";
|
||||
ui_tooltip = "Prevents sky pixels from contributing to bloom.";
|
||||
ui_category = "Anamorphic Bloom";
|
||||
> = false;
|
||||
|
||||
uniform bool BLOOM_SHARP <
|
||||
ui_type = "radio";
|
||||
ui_label = "Add More Definition to Bloom Shape (Experimental)";
|
||||
ui_tooltip = "Enables a sharper 1D horizontal kernel. May flicker with camera movement.";
|
||||
ui_category = "Anamorphic Bloom";
|
||||
> = false;
|
||||
|
||||
uniform float BLOOM_INTENSITY <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
||||
ui_label = "Bloom Intensity";
|
||||
ui_tooltip = "Scales the intensity of the Bloom effect.";
|
||||
ui_category = "Anamorphic Bloom";
|
||||
> = 1.0;
|
||||
|
||||
uniform float BLOOM_THRESHOLD <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
||||
ui_label = "Bloom Threshold";
|
||||
ui_tooltip = "Higher values bloom more of the scene.";
|
||||
ui_category = "Anamorphic Bloom";
|
||||
> = 0.7;
|
||||
|
||||
uniform float BLOOM_STRETCH <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 7.5; ui_step = 0.01;
|
||||
ui_label = "Bloom Stretch";
|
||||
ui_tooltip = "Adjusts the horizontal elongation of the Bloom effect.";
|
||||
ui_category = "Anamorphic Bloom";
|
||||
> = 7.5;
|
||||
|
||||
#if COLOR_FRINGING
|
||||
uniform float BLOOM_CA <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 10.0; ui_step = 0.01;
|
||||
ui_label = "Bloom Chromatic Shift";
|
||||
ui_tooltip = "Shifts R/B channels within the bloom passes.";
|
||||
ui_category = "Anamorphic Bloom";
|
||||
> = 10.0;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if ANAMORPHIC_STREAKS
|
||||
uniform bool STREAK_SKIP_SKYBOX <
|
||||
ui_type = "radio";
|
||||
ui_label = "Exclude Skybox (Streaks)";
|
||||
ui_tooltip = "Prevents sky pixels from contributing to light streaks.";
|
||||
ui_category = "Anamorphic Streaks";
|
||||
> = false;
|
||||
|
||||
uniform float STREAK_INTENSITY <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
||||
ui_label = "Streak Intensity";
|
||||
ui_tooltip = "Scales the intensity of the light streaks.";
|
||||
ui_category = "Anamorphic Streaks";
|
||||
> = 1.0;
|
||||
|
||||
uniform float STREAK_THRESHOLD <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
||||
ui_label = "Streak Threshold";
|
||||
ui_tooltip = "Higher values considers more of the scene.";
|
||||
ui_category = "Anamorphic Streaks";
|
||||
> = 0.5;
|
||||
|
||||
uniform float STREAK_STRETCH <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 10.0; ui_step = 0.01;
|
||||
ui_label = "Streak Stretch";
|
||||
ui_tooltip = "Adjusts the horizontal elongation of the light streaks.";
|
||||
ui_category = "Anamorphic Streaks";
|
||||
> = 10.0;
|
||||
|
||||
uniform float3 STREAK_TINT <
|
||||
ui_type = "color";
|
||||
ui_label = "Tint";
|
||||
ui_tooltip = "Tints the light streaks with chosen color. Set to white (1, 1, 1) for pass-through.";
|
||||
ui_category = "Anamorphic Streaks";
|
||||
> = float3(0.55, 0.55, 1.0);
|
||||
|
||||
#if COLOR_FRINGING
|
||||
uniform float STREAK_CA <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 10.0; ui_step = 0.01;
|
||||
ui_label = "Streak Chromatic Shift";
|
||||
ui_tooltip = "Shifts R/B channels of the light streaks.";
|
||||
ui_category = "Anamorphic Streaks";
|
||||
> = 10.0;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uniform int USER_GUIDE <
|
||||
ui_type = "radio";
|
||||
ui_category = "";
|
||||
ui_label = " ";
|
||||
ui_text = "Exclude Skybox: Requires access to properly configured depth buffer.";
|
||||
>;
|
||||
|
||||
namespace LumeniteAnamorphicBloom {
|
||||
|
||||
/*-------------.
|
||||
| :: MACROS :: |
|
||||
'-------------*/
|
||||
#if ANAMORPHIC_BLOOM
|
||||
#define BLOOM_SHIFT (float2(BLOOM_CA * BUFFER_PIXEL_SIZE.x, 0.0)) //once per pass
|
||||
|
||||
#if COLOR_FRINGING
|
||||
#define SAMPLE_BLOOM_TEX(s, uv) float3( \
|
||||
tex2D(s, (uv) - BLOOM_SHIFT).r, \
|
||||
tex2D(s, (uv)).g, \
|
||||
tex2D(s, (uv) + BLOOM_SHIFT).b \
|
||||
)
|
||||
#else
|
||||
#define SAMPLE_BLOOM_TEX(s, uv) tex2D(s, uv).rgb
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if ANAMORPHIC_STREAKS
|
||||
#define STREAK_SHIFT (STREAK_CA * BUFFER_PIXEL_SIZE.x)
|
||||
|
||||
#if COLOR_FRINGING
|
||||
#define SAMPLE_STREAK_TEX(s, uv, o) float3( \
|
||||
tex2D(s, uv + float2(o - STREAK_SHIFT, 0.0)).r, \
|
||||
tex2D(s, uv + float2(o, 0.0)).g, \
|
||||
tex2D(s, uv + float2(o + STREAK_SHIFT, 0.0)).b \
|
||||
)
|
||||
#else
|
||||
#define SAMPLE_STREAK_TEX(s, uv, o) tex2D(s, uv + float2(o, 0.0)).rgb
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*---------------------.
|
||||
| :: RENDER TARGETS :: |
|
||||
'---------------------*/
|
||||
texture2D tUnpackedColor { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
|
||||
sampler2D sUnpackedColor { Texture = tUnpackedColor; };
|
||||
|
||||
#if ANAMORPHIC_BLOOM
|
||||
texture2D tBloomDown0 { Width = BUFFER_WIDTH/2; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
|
||||
sampler2D sBloomDown0 { Texture = tBloomDown0; };
|
||||
|
||||
texture2D tBloomDown1 { Width = BUFFER_WIDTH/4; Height = BUFFER_HEIGHT/4; Format = RGBA16F; };
|
||||
sampler2D sBloomDown1 { Texture = tBloomDown1; };
|
||||
|
||||
texture2D tBloomDown2 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RGBA16F; };
|
||||
sampler2D sBloomDown2 { Texture = tBloomDown2; };
|
||||
|
||||
texture2D tBloomDown3 { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = RGBA16F; };
|
||||
sampler2D sBloomDown3 { Texture = tBloomDown3; };
|
||||
|
||||
texture2D tBloomDown4 { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/32; Format = RGBA16F; };
|
||||
sampler2D sBloomDown4 { Texture = tBloomDown4; };
|
||||
|
||||
texture2D tBloomUp3 { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = RGBA16F; };
|
||||
sampler2D sBloomUp3 { Texture = tBloomUp3; };
|
||||
|
||||
texture2D tBloomUp2 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = RGBA16F; };
|
||||
sampler2D sBloomUp2 { Texture = tBloomUp2; };
|
||||
|
||||
texture2D tBloomUp1 { Width = BUFFER_WIDTH/4; Height = BUFFER_HEIGHT/4; Format = RGBA16F; };
|
||||
sampler2D sBloomUp1 { Texture = tBloomUp1; };
|
||||
|
||||
texture2D tBloomUp0 { Width = BUFFER_WIDTH/2; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
|
||||
sampler2D sBloomUp0 { Texture = tBloomUp0; };
|
||||
|
||||
texture2D tBloomUp4 { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
|
||||
sampler2D sBloomUp4 { Texture = tBloomUp4; };
|
||||
#endif
|
||||
|
||||
#if ANAMORPHIC_STREAKS
|
||||
texture2D tStreakDown0 { Width = BUFFER_WIDTH/2; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
|
||||
sampler2D sStreakDown0 { Texture = tStreakDown0; };
|
||||
|
||||
texture2D tStreakDown1 { Width = BUFFER_WIDTH/4; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
|
||||
sampler2D sStreakDown1 { Texture = tStreakDown1; };
|
||||
|
||||
texture2D tStreakDown2 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
|
||||
sampler2D sStreakDown2 { Texture = tStreakDown2; };
|
||||
|
||||
texture2D tStreakDown3 { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
|
||||
sampler2D sStreakDown3 { Texture = tStreakDown3; };
|
||||
|
||||
texture2D tStreakDown4 { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
|
||||
sampler2D sStreakDown4 { Texture = tStreakDown4; };
|
||||
|
||||
texture2D tStreakUp3 { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
|
||||
sampler2D sStreakUp3 { Texture = tStreakUp3; };
|
||||
|
||||
texture2D tStreakUp2 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
|
||||
sampler2D sStreakUp2 { Texture = tStreakUp2; };
|
||||
|
||||
texture2D tStreakUp1 { Width = BUFFER_WIDTH/4; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
|
||||
sampler2D sStreakUp1 { Texture = tStreakUp1; };
|
||||
|
||||
texture2D tStreakUp0 { Width = BUFFER_WIDTH/2; Height = BUFFER_HEIGHT/2; Format = RGBA16F; };
|
||||
sampler2D sStreakUp0 { Texture = tStreakUp0; };
|
||||
#endif
|
||||
|
||||
/*--------------.
|
||||
| :: HELPERS :: |
|
||||
'--------------*/
|
||||
#if ANAMORPHIC_BLOOM
|
||||
float3 TentFilter13Anisotropic(sampler2D src, float2 uv, float2 radius)
|
||||
{
|
||||
float dx = radius.x;
|
||||
float dy = radius.y;
|
||||
|
||||
[branch] if (BLOOM_SHARP)
|
||||
{
|
||||
float3 center = SAMPLE_BLOOM_TEX(src, uv);
|
||||
float3 innerLeft = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, 0));
|
||||
float3 innerRight = SAMPLE_BLOOM_TEX(src, uv + float2( dx, 0));
|
||||
float3 outerLeft = SAMPLE_BLOOM_TEX(src, uv + float2(-2*dx, 0));
|
||||
float3 outerRight = SAMPLE_BLOOM_TEX(src, uv + float2( 2*dx, 0));
|
||||
return center * 0.25 + (innerLeft + innerRight) * 0.25 + (outerLeft + outerRight) * 0.125;
|
||||
}
|
||||
|
||||
float3 a = SAMPLE_BLOOM_TEX(src, uv + float2(-2*dx, 2*dy)).rgb;
|
||||
float3 b = SAMPLE_BLOOM_TEX(src, uv + float2( 0, 2*dy)).rgb;
|
||||
float3 c = SAMPLE_BLOOM_TEX(src, uv + float2( 2*dx, 2*dy)).rgb;
|
||||
float3 d = SAMPLE_BLOOM_TEX(src, uv + float2(-2*dx, 0)).rgb;
|
||||
float3 e = SAMPLE_BLOOM_TEX(src, uv + float2( 0, 0)).rgb;
|
||||
float3 f = SAMPLE_BLOOM_TEX(src, uv + float2( 2*dx, 0)).rgb;
|
||||
float3 g = SAMPLE_BLOOM_TEX(src, uv + float2(-2*dx, -2*dy)).rgb;
|
||||
float3 h = SAMPLE_BLOOM_TEX(src, uv + float2( 0, -2*dy)).rgb;
|
||||
float3 i = SAMPLE_BLOOM_TEX(src, uv + float2( 2*dx, -2*dy)).rgb;
|
||||
float3 j = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, dy)).rgb;
|
||||
float3 k = SAMPLE_BLOOM_TEX(src, uv + float2( dx, dy)).rgb;
|
||||
float3 l = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, -dy)).rgb;
|
||||
float3 m = SAMPLE_BLOOM_TEX(src, uv + float2( dx, -dy)).rgb;
|
||||
|
||||
return e * 0.125 + (a + c + g + i) * 0.03125 + (b + d + f + h) * 0.0625 + (j + k + l + m) * 0.125;
|
||||
}
|
||||
|
||||
float3 TentFilter9Anisotropic(sampler2D src, float2 uv, float2 radius)
|
||||
{
|
||||
float dx = radius.x;
|
||||
float dy = radius.y;
|
||||
|
||||
[branch] if (BLOOM_SHARP)
|
||||
{
|
||||
float3 center = SAMPLE_BLOOM_TEX(src, uv);
|
||||
float3 left = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, 0));
|
||||
float3 right = SAMPLE_BLOOM_TEX(src, uv + float2( dx, 0));
|
||||
return center * 0.5 + (left + right) * 0.25;
|
||||
}
|
||||
|
||||
float3 a = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, dy)).rgb;
|
||||
float3 b = SAMPLE_BLOOM_TEX(src, uv + float2( 0, dy)).rgb;
|
||||
float3 c = SAMPLE_BLOOM_TEX(src, uv + float2( dx, dy)).rgb;
|
||||
float3 d = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, 0)).rgb;
|
||||
float3 e = SAMPLE_BLOOM_TEX(src, uv + float2( 0, 0)).rgb;
|
||||
float3 f = SAMPLE_BLOOM_TEX(src, uv + float2( dx, 0)).rgb;
|
||||
float3 g = SAMPLE_BLOOM_TEX(src, uv + float2(-dx, -dy)).rgb;
|
||||
float3 h = SAMPLE_BLOOM_TEX(src, uv + float2( 0, -dy)).rgb;
|
||||
float3 i = SAMPLE_BLOOM_TEX(src, uv + float2( dx, -dy)).rgb;
|
||||
|
||||
return (e * 4.0 + (b + d + f + h) * 2.0 + (a + c + g + i)) * 0.0625;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ANAMORPHIC_STREAKS
|
||||
float3 StreakFilter(sampler2D src, float2 uv, float radius)
|
||||
{
|
||||
float dx = BUFFER_PIXEL_SIZE.x * radius;
|
||||
return SAMPLE_STREAK_TEX(src, uv, -dx * 2.0) * 0.1 +
|
||||
SAMPLE_STREAK_TEX(src, uv, -dx) * 0.25 +
|
||||
SAMPLE_STREAK_TEX(src, uv, 0.0) * 0.3 +
|
||||
SAMPLE_STREAK_TEX(src, uv, dx) * 0.25 +
|
||||
SAMPLE_STREAK_TEX(src, uv, dx * 2.0) * 0.1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*--------------.
|
||||
| :: SHADERS :: |
|
||||
'--------------*/
|
||||
float4 PS_StoreUnpackedColor(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
return float4(GetLinearColor(uv, false), 1);
|
||||
}
|
||||
|
||||
#if ANAMORPHIC_BLOOM
|
||||
//downsample with anisotropic blur (13-tap)
|
||||
float4 PS_BloomDownsample0(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float2 radius = float2(BLOOM_STRETCH, 1.0) * BUFFER_PIXEL_SIZE;
|
||||
float3 downsample = TentFilter13Anisotropic(sUnpackedColor, uv, radius);
|
||||
if (BLOOM_SKIP_SKYBOX) downsample *= (GetDepth(uv) < 1.0);
|
||||
downsample = downsample * smoothstep(0.0, max(1.0 - BLOOM_THRESHOLD, 0.07)*BLOOM_THRESHOLD_SCALER, GetLuminance(downsample));
|
||||
return float4(downsample, 1);
|
||||
}
|
||||
|
||||
float4 PS_BloomDownsample1(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float2 radius = float2(BLOOM_STRETCH, 1.0) * BUFFER_PIXEL_SIZE * 2.0;
|
||||
return float4(TentFilter13Anisotropic(sBloomDown0, uv, radius), 1);
|
||||
}
|
||||
|
||||
float4 PS_BloomDownsample2(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float2 radius = float2(BLOOM_STRETCH, 1.0) * BUFFER_PIXEL_SIZE * 4.0;
|
||||
return float4(TentFilter13Anisotropic(sBloomDown1, uv, radius), 1);
|
||||
}
|
||||
|
||||
float4 PS_BloomDownsample3(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float2 radius = float2(BLOOM_STRETCH, 1.0) * BUFFER_PIXEL_SIZE * 8.0;
|
||||
return float4(TentFilter13Anisotropic(sBloomDown2, uv, radius), 1);
|
||||
}
|
||||
|
||||
float4 PS_BloomDownsample4(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float2 radius = float2(BLOOM_STRETCH, 1.0) * BUFFER_PIXEL_SIZE * 16.0;
|
||||
return float4(TentFilter13Anisotropic(sBloomDown3, uv, radius), 1);
|
||||
}
|
||||
|
||||
//upsample with anisotropic blur (9-tap)
|
||||
float4 PS_BloomUpsample0(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float2 radius = float2(BLOOM_STRETCH, 0.0) * BUFFER_PIXEL_SIZE * 32.0 * float2(1.0, rcp(BUFFER_ASPECT_RATIO));
|
||||
float3 upsample = TentFilter9Anisotropic(sBloomDown4, uv, radius);
|
||||
float3 previous = tex2D(sBloomDown3, uv).rgb;
|
||||
return float4(upsample + previous, 1);
|
||||
}
|
||||
|
||||
float4 PS_BloomUpsample1(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float2 radius = float2(BLOOM_STRETCH, 0.0) * BUFFER_PIXEL_SIZE * 16.0 * float2(1.0, rcp(BUFFER_ASPECT_RATIO));
|
||||
float3 upsample = TentFilter9Anisotropic(sBloomUp3, uv, radius);
|
||||
float3 previous = tex2D(sBloomDown2, uv).rgb;
|
||||
return float4(upsample + previous, 1);
|
||||
}
|
||||
|
||||
float4 PS_BloomUpsample2(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float2 radius = float2(BLOOM_STRETCH, 0.0) * BUFFER_PIXEL_SIZE * 8.0 * float2(1.0, rcp(BUFFER_ASPECT_RATIO));
|
||||
float3 upsample = TentFilter9Anisotropic(sBloomUp2, uv, radius);
|
||||
float3 previous = tex2D(sBloomDown1, uv).rgb;
|
||||
return float4(upsample + previous, 1);
|
||||
}
|
||||
|
||||
float4 PS_BloomUpsample3(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float2 radius = float2(BLOOM_STRETCH, 0.0) * BUFFER_PIXEL_SIZE * 4.0 * float2(1.0, rcp(BUFFER_ASPECT_RATIO));
|
||||
float3 upsample = TentFilter9Anisotropic(sBloomUp1, uv, radius);
|
||||
float3 previous = tex2D(sBloomDown0, uv).rgb;
|
||||
return float4(upsample + previous, 1);
|
||||
}
|
||||
|
||||
float4 PS_BloomUpsample4(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float2 radius = float2(BLOOM_STRETCH, 0.0) * BUFFER_PIXEL_SIZE * 2.0 * float2(1.0, rcp(BUFFER_ASPECT_RATIO));
|
||||
float3 upsample = TentFilter9Anisotropic(sBloomUp0, uv, radius);
|
||||
float3 previous = tex2D(sBloomDown0, uv).rgb;
|
||||
return float4(upsample + previous, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ANAMORPHIC_STREAKS
|
||||
//thresholding pass
|
||||
float4 PS_Prefilter(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float3 color = tex2D(sUnpackedColor, uv).rgb;
|
||||
if (STREAK_SKIP_SKYBOX) color *= (GetDepth(uv) < 1.0);
|
||||
float br = max(color.r, max(color.g, color.b));
|
||||
float nm = max(0.0, br - (1.0 - STREAK_THRESHOLD));
|
||||
return float4(color * (nm / max(br, 0.0001)), 1.0);
|
||||
}
|
||||
|
||||
float4 PS_StreakDownsample0(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakDown0, uv, 1.0 * STREAK_STRETCH), 1); }
|
||||
float4 PS_StreakDownsample1(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakDown1, uv, 2.0 * STREAK_STRETCH), 1); }
|
||||
float4 PS_StreakDownsample2(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakDown2, uv, 4.0 * STREAK_STRETCH), 1); }
|
||||
float4 PS_StreakDownsample3(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakDown3, uv, 8.0 * STREAK_STRETCH), 1); }
|
||||
|
||||
float4 PS_StreakUpsample0(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakUp3, uv, 8.0 * STREAK_STRETCH) + tex2D(sStreakDown2, uv).rgb, 1); }
|
||||
float4 PS_StreakUpsample1(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakUp2, uv, 4.0 * STREAK_STRETCH) + tex2D(sStreakDown1, uv).rgb, 1); }
|
||||
float4 PS_StreakUpsample2(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakUp1, uv, 2.0 * STREAK_STRETCH) + tex2D(sStreakDown0, uv).rgb, 1); }
|
||||
float4 PS_StreakUpsample3(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target { return float4(StreakFilter(sStreakDown4, uv, 16.0 * STREAK_STRETCH) + tex2D(sStreakDown3, uv).rgb, 1); }
|
||||
#endif
|
||||
|
||||
float4 PS_ToDisplay(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float3 unpackedColor = tex2D(sUnpackedColor, uv).rgb;
|
||||
float3 light = 0;
|
||||
|
||||
#if ANAMORPHIC_BLOOM
|
||||
light = tex2D(sBloomUp4, uv).rgb * BLOOM_INTENSITY;
|
||||
#endif
|
||||
|
||||
#if ANAMORPHIC_STREAKS
|
||||
light = max(light, tex2D(sStreakUp0, uv).rgb * STREAK_TINT * STREAK_INTENSITY);
|
||||
#endif
|
||||
|
||||
float3 toDisplay;
|
||||
#if (BUFFER_COLOR_SPACE == 1)
|
||||
//sRGB colorspace
|
||||
toDisplay = 1.0 - (1.0 - unpackedColor) * (1.0 - light);
|
||||
#else
|
||||
toDisplay = unpackedColor + light;
|
||||
#endif
|
||||
|
||||
toDisplay = ToOutputColorspace(toDisplay, false);
|
||||
return float4(toDisplay, 1);
|
||||
}
|
||||
|
||||
/*----------------.
|
||||
| :: TECHNIQUE :: |
|
||||
'----------------*/
|
||||
technique Lumenite_AnamorphicBloom <
|
||||
ui_label = "LUMENITE: AnamorphicBloom";
|
||||
ui_tooltip = "Artistic bloom & Lens Flare approximating the Anamorphic lens aesthetic.";
|
||||
>
|
||||
{
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_StoreUnpackedColor; RenderTarget = tUnpackedColor; }
|
||||
|
||||
//bloom pyramid
|
||||
#if ANAMORPHIC_BLOOM
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomDownsample0; RenderTarget = tBloomDown0; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomDownsample1; RenderTarget = tBloomDown1; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomDownsample2; RenderTarget = tBloomDown2; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomDownsample3; RenderTarget = tBloomDown3; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomDownsample4; RenderTarget = tBloomDown4; }
|
||||
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomUpsample0; RenderTarget = tBloomUp3; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomUpsample1; RenderTarget = tBloomUp2; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomUpsample2; RenderTarget = tBloomUp1; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomUpsample3; RenderTarget = tBloomUp0; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_BloomUpsample4; RenderTarget = tBloomUp4; }
|
||||
#endif
|
||||
|
||||
//streak pyramid
|
||||
#if ANAMORPHIC_STREAKS
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_Prefilter; RenderTarget = tStreakDown0; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakDownsample0; RenderTarget = tStreakDown1; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakDownsample1; RenderTarget = tStreakDown2; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakDownsample2; RenderTarget = tStreakDown3; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakDownsample3; RenderTarget = tStreakDown4; }
|
||||
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakUpsample3; RenderTarget = tStreakUp3; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakUpsample0; RenderTarget = tStreakUp2; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakUpsample1; RenderTarget = tStreakUp1; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_StreakUpsample2; RenderTarget = tStreakUp0; }
|
||||
#endif
|
||||
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_ToDisplay; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,753 @@
|
||||
/*
|
||||
========================================================================
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
========================================================================
|
||||
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_LSAO.fx
|
||||
Version : 2026.06.09
|
||||
Author : Afzaal (Kaidō)
|
||||
Description: Large-Scale Ray Traced Ambient Occlusion (Screen Space).
|
||||
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
/*------------------.
|
||||
| :: DEFINITIONS :: |
|
||||
'------------------*/
|
||||
#define FOV 60.0
|
||||
#define NEAR_PLANE 0.01
|
||||
#define AO_MAX_MARCH_STEPS 100
|
||||
|
||||
/*--------------.
|
||||
| :: HEADERS :: |
|
||||
'--------------*/
|
||||
#include "ReShade.fxh"
|
||||
#include "./include/lumenite_Projections.fxh"
|
||||
#include "./include/lumenite_Helpers.fxh"
|
||||
#include "./include/lumenite_ColorManagement.fxh"
|
||||
|
||||
/*---------------.
|
||||
| :: UNIFORMS :: |
|
||||
'---------------*/
|
||||
uniform bool DEBUG_VIEW <
|
||||
ui_label = "Show AO Mask";
|
||||
ui_tooltip = "Debug view for the AO. Shows raw AO.";
|
||||
ui_category = "Ambient Occlusion";
|
||||
> = 0;
|
||||
|
||||
uniform float DEPTH_BOUNDARY <
|
||||
ui_type = "slider";
|
||||
ui_min = 0.001; ui_max = 0.999; ui_step = 0.001;
|
||||
ui_label = "AO Range";
|
||||
ui_tooltip = "The Z+ range/depth in which the effect is applied.";
|
||||
ui_category = "Ambient Occlusion";
|
||||
hidden = false;
|
||||
> = 0.6;
|
||||
|
||||
uniform float DEPTH_FADE_START <
|
||||
ui_type = "slider";
|
||||
ui_min = 0.1; ui_max = 1.0; ui_step = 0.01;
|
||||
ui_label = "Z+ Fade Start (%)";
|
||||
ui_tooltip = "Z+ fraction where effect starts fading out (relative to AO Range)";
|
||||
ui_category = "Ambient Occlusion";
|
||||
hidden = true;
|
||||
> = 0.75;
|
||||
|
||||
uniform float AO_INTENSITY <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 1.0;
|
||||
ui_label = "AO Strength";
|
||||
ui_tooltip = "Controls the intensity of the ambient occlusion effect.";
|
||||
ui_category = "Ambient Occlusion";
|
||||
> = 1.0;
|
||||
|
||||
/*--------------.
|
||||
| :: 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 LumeniteLSAO {
|
||||
|
||||
/*---------------------.
|
||||
| :: RENDER TARGETS :: |
|
||||
'---------------------*/
|
||||
texture tAOTrace { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = R16F; };
|
||||
sampler sAOTrace { Texture = tAOTrace; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
|
||||
|
||||
texture tAO1 { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
|
||||
sampler sAO1 { Texture = tAO1; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
|
||||
|
||||
texture tAO2 { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
|
||||
sampler sAO2 { Texture = tAO2; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
|
||||
sampler sAO2Linear { Texture = tAO2; AddressU = CLAMP; AddressV = CLAMP; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
|
||||
|
||||
texture tPrevAO { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
|
||||
sampler sPrevAO { Texture = tPrevAO; AddressU = CLAMP; AddressV = CLAMP; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
|
||||
|
||||
//HiZ mipchain
|
||||
texture tHiZMip0 { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; };
|
||||
texture tHiZMip1 { Width = BUFFER_WIDTH/2; Height = BUFFER_HEIGHT/2; Format = R16F; };
|
||||
texture tHiZMip2 { Width = BUFFER_WIDTH/4; Height = BUFFER_HEIGHT/4; Format = R16F; };
|
||||
texture tHiZMip3 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
|
||||
texture tHiZMip4 { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = R16F; };
|
||||
texture tHiZMip5 { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/32; Format = R16F; };
|
||||
|
||||
sampler sHiZMip0 { Texture = tHiZMip0; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
sampler sHiZMip1 { Texture = tHiZMip1; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
sampler sHiZMip2 { Texture = tHiZMip2; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
sampler sHiZMip3 { Texture = tHiZMip3; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
sampler sHiZMip4 { Texture = tHiZMip4; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
sampler sHiZMip5 { Texture = tHiZMip5; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
|
||||
/*--------------.
|
||||
| :: HELPERS :: |
|
||||
'--------------*/
|
||||
void BuildOrthonormalBasis(float3 n, out float3 b1, out float3 b2)
|
||||
{
|
||||
if (n.z < -0.9999999) {
|
||||
b1 = float3(0.0, -1.0, 0.0);
|
||||
b2 = float3(-1.0, 0.0, 0.0);
|
||||
} else {
|
||||
float a = rcp(1.0 + n.z);
|
||||
float b = -n.x * n.y * a;
|
||||
b1 = float3(mad(-n.x * n.x, a, 1.0), b, -n.x);
|
||||
b2 = float3(b, mad(-n.y * n.y, a, 1.0), -n.y);
|
||||
}
|
||||
}
|
||||
|
||||
float3 GenerateHemisphereDirection(float3 normal, float2 rand, float3 tangent, float3 bitangent)
|
||||
{
|
||||
float phi = rand.x * 6.28318530718; //2.0*PI as constant
|
||||
float sinPhi, cosPhi;
|
||||
sincos(phi, sinPhi, cosPhi);
|
||||
float cosTheta = sqrt(1.0 - rand.y);
|
||||
float sinTheta = sqrt(rand.y);
|
||||
float3 result = normal * cosTheta;
|
||||
result = mad(bitangent, sinTheta * sinPhi, result);
|
||||
result = mad(tangent, sinTheta * cosPhi, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
float CalculateDepthFade(float depth)
|
||||
{
|
||||
float fadeStartDepth = DEPTH_BOUNDARY * DEPTH_FADE_START;
|
||||
float fadeRange = DEPTH_BOUNDARY - fadeStartDepth;
|
||||
return 1.0 - saturate((depth - fadeStartDepth) / fadeRange);
|
||||
}
|
||||
|
||||
float2 ATrousFilter(sampler SourceSampler, float2 uv, uint dilation, bool adaptiveDilation)
|
||||
{
|
||||
float4 gbuffer = tex2D(Kernel::sNormals, uv);
|
||||
if (gbuffer.a == 0 || gbuffer.a >= DEPTH_BOUNDARY) return float2(1.0, 0.0);
|
||||
|
||||
[branch] if (adaptiveDilation) {
|
||||
float confidence = tex2Dlod(Kernel::sConfidence, float4(uv, 0, 0)).r;
|
||||
dilation += uint(round((1.0 - confidence) * 2.0)); //scale filter kernel w. motion by up to a factor of 2
|
||||
}
|
||||
|
||||
float2 centerData = tex2Dlod(SourceSampler, float4(uv, 0, 0)).rg;
|
||||
float variance = max(0.0, centerData.g - (centerData.r * centerData.r)); //Moment - AO^2
|
||||
variance = max(variance, 0.0001);
|
||||
float2 sum = centerData;
|
||||
float totalWeight = 1.0;
|
||||
for (int y = -1; y <= 1; y++) for (int x = -1; x <= 1; x++) {
|
||||
if (x == 0 && y == 0) continue;
|
||||
float2 sampleUV = uv + float2(x, y) * dilation * (BUFFER_PIXEL_SIZE * 2.0); //don't forget the x2.0 to properly step half-res grid!
|
||||
float2 sampleData = tex2Dlod(SourceSampler, float4(sampleUV, 0, 0)).rg;
|
||||
float4 sampleGeo = tex2Dlod(Kernel::sNormals, float4(sampleUV, 0, 0));
|
||||
float depthWeight = exp(-abs(gbuffer.a - sampleGeo.a) / (gbuffer.a * 0.02 + 0.001));
|
||||
float normalWeight = pow(saturate(dot(gbuffer.rgb, sampleGeo.rgb)), 50.0);
|
||||
float aoDiff = centerData.r - sampleData.r;
|
||||
float aoWeight = exp(-(aoDiff * aoDiff) / (variance + 0.0001));
|
||||
float weight = depthWeight * normalWeight * aoWeight;
|
||||
sum += sampleData * weight;
|
||||
totalWeight += weight;
|
||||
}
|
||||
return sum / (totalWeight + EPSILON);
|
||||
}
|
||||
|
||||
float SamplePrevHiZ(float2 centerUV, sampler srcSampler, int srcMipLvl) {
|
||||
float2 srcTexelSize = BUFFER_PIXEL_SIZE * pow(2, srcMipLvl);
|
||||
float2 off[4] = { float2(-0.5, -0.5), float2(0.5, -0.5), float2(-0.5, 0.5), float2(0.5, 0.5) };
|
||||
float minDepth = 1.0;
|
||||
[unroll] for(int i=0; i<4; i++)
|
||||
minDepth = min(minDepth, tex2D(srcSampler, centerUV + off[i] * srcTexelSize).r);
|
||||
return minDepth;
|
||||
}
|
||||
|
||||
/*--------------.
|
||||
| :: SHADERS :: |
|
||||
'--------------*/
|
||||
float PS_GenerateMip0(VSOUT input) : SV_Target
|
||||
{
|
||||
float2 blockOriginUV = floor(input.uv / (BUFFER_PIXEL_SIZE * 2.0)) * (BUFFER_PIXEL_SIZE * 2.0);
|
||||
float2 uvs[4] = { blockOriginUV + BUFFER_PIXEL_SIZE * float2(0.5, 0.5),
|
||||
blockOriginUV + BUFFER_PIXEL_SIZE * float2(1.5, 0.5),
|
||||
blockOriginUV + BUFFER_PIXEL_SIZE * float2(0.5, 1.5),
|
||||
blockOriginUV + BUFFER_PIXEL_SIZE * float2(1.5, 1.5) };
|
||||
float d0 = tex2D(Kernel::sDepth, uvs[0]).r;
|
||||
float d1 = tex2D(Kernel::sDepth, uvs[1]).r;
|
||||
float d2 = tex2D(Kernel::sDepth, uvs[2]).r;
|
||||
float d3 = tex2D(Kernel::sDepth, uvs[3]).r;
|
||||
return min(min(d0, d1), min(d2, d3));
|
||||
}
|
||||
|
||||
float PS_ReduceMip1 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip0, 0); }
|
||||
float PS_ReduceMip2 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip1, 1); }
|
||||
float PS_ReduceMip3 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip2, 2); }
|
||||
float PS_ReduceMip4 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip3, 3); }
|
||||
float PS_ReduceMip5 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip4, 4); }
|
||||
|
||||
float PS_TraceAO(VSOUT input) : SV_Target
|
||||
{
|
||||
float4 gbuffer = tex2D(Kernel::sNormals, input.uv);
|
||||
float3 normal = gbuffer.rgb;
|
||||
float depth = gbuffer.a;
|
||||
if (depth == 0 || depth >= DEPTH_BOUNDARY) discard;
|
||||
float3 startPos = UVToViewSpace(input.uv, depth, input);
|
||||
float3 tangent, bitangent;
|
||||
BuildOrthonormalBasis(normal, tangent, bitangent);
|
||||
float2 noise = GetStratifiedNoise(input.vpos.xy);
|
||||
float3 rayDir = GenerateHemisphereDirection(normal, noise, tangent, bitangent);
|
||||
float totalRayLength = 0.7 * depth;
|
||||
float baseStepSize = totalRayLength / (float)AO_MAX_MARCH_STEPS;
|
||||
float stepSize = baseStepSize;
|
||||
float3 currentPos = startPos + rayDir * stepSize;
|
||||
float occlusion = 0.0;
|
||||
float t = stepSize;
|
||||
[loop]
|
||||
for (int step = 0; step < AO_MAX_MARCH_STEPS; step++) {
|
||||
if (t >= totalRayLength) break;
|
||||
|
||||
float2 hitPos = ViewSpaceToUV(currentPos, input);
|
||||
if (IsOOB(hitPos)) break;
|
||||
|
||||
//select the appropriate Mip Level
|
||||
float2 ray_screen_velocity = abs(rayDir.xy / currentPos.z) * float2(BUFFER_WIDTH, BUFFER_HEIGHT);
|
||||
float footprint = max(ray_screen_velocity.x, ray_screen_velocity.y) * max(stepSize / currentPos.z, 1.0);
|
||||
int mip = clamp(int(log2(max(footprint, 1.0))), 0, 5);
|
||||
float HiZDepth;
|
||||
if (mip==5) HiZDepth = tex2Dlod(sHiZMip5, float4(hitPos,0,0)).r;
|
||||
else if (mip==4) HiZDepth = tex2Dlod(sHiZMip4, float4(hitPos,0,0)).r;
|
||||
else if (mip==3) HiZDepth = tex2Dlod(sHiZMip3, float4(hitPos,0,0)).r;
|
||||
else if (mip==2) HiZDepth = tex2Dlod(sHiZMip2, float4(hitPos,0,0)).r;
|
||||
else if (mip==1) HiZDepth = tex2Dlod(sHiZMip1, float4(hitPos,0,0)).r;
|
||||
else HiZDepth = tex2Dlod(sHiZMip0, float4(hitPos,0,0)).r;
|
||||
|
||||
//skip some empty space
|
||||
float currentStepSize = stepSize * max(1.0, float(mip) * 0.5); //stepsize mip scaling
|
||||
if (currentPos.z < HiZDepth && (HiZDepth - currentPos.z) > currentStepSize) {
|
||||
float leap = max(currentStepSize, (HiZDepth - currentPos.z) * 0.065);
|
||||
currentPos += rayDir * leap;
|
||||
t += leap;
|
||||
continue;
|
||||
}
|
||||
|
||||
//hit test
|
||||
float sceneDepth = tex2Dlod(Kernel::sDepth, float4(hitPos, 0, 0)).r;
|
||||
float depthDiff = currentPos.z - sceneDepth;
|
||||
float maxThickness = currentPos.z * 0.6;
|
||||
if (depthDiff > (currentPos.z * 0.0001) && depthDiff < maxThickness) {
|
||||
float3 scenePos = UVToViewSpace(hitPos, sceneDepth, input);
|
||||
float hitDistance = length(scenePos - startPos);
|
||||
float normalizedDist = hitDistance / totalRayLength;
|
||||
occlusion = 1.0 - saturate(depthDiff / maxThickness);
|
||||
occlusion = occlusion * occlusion;
|
||||
occlusion = saturate(pow(saturate(1.0 - normalizedDist), 1.2) * occlusion * 1.4);
|
||||
break;
|
||||
}
|
||||
|
||||
currentPos += rayDir * stepSize;
|
||||
t += stepSize;
|
||||
}
|
||||
|
||||
float aoFactor = 1.0 - saturate(occlusion * AO_INTENSITY);
|
||||
return aoFactor;
|
||||
}
|
||||
|
||||
float2 PS_TemporalFilter(VSOUT input) : SV_Target
|
||||
{
|
||||
float depth = tex2D(Kernel::sDepth, input.uv).r;
|
||||
//overwrite noise at boundary with clean White, prevents gaps
|
||||
if (depth >= DEPTH_BOUNDARY) return float2(1.0, 1.0); //1.0 AO, 1.0 Moment
|
||||
if (depth == 0) discard;
|
||||
float ao = tex2D(sAOTrace, input.uv).r;
|
||||
ao = lerp(1.0, ao, CalculateDepthFade(depth));
|
||||
float moment = ao * ao;
|
||||
float2 flow = tex2D(Kernel::sFlow, input.uv).xy;
|
||||
float confidence = tex2D(Kernel::sConfidence, input.uv).x;
|
||||
confidence = saturate(confidence + log2(2.0 - confidence) * 0.6); //boost confidence
|
||||
float2 rawHistory = tex2D(sPrevAO, input.uv + flow).rg; //history stores "1.0 - AO". 0.0 (Black Texture) -> Reads as 1.0 (White)
|
||||
float prevAO = 1.0 - rawHistory.r;
|
||||
float prevMoment = 1.0 - rawHistory.g;
|
||||
float alpha = confidence * 0.98;
|
||||
ao = lerp(ao, prevAO, alpha);
|
||||
moment = lerp(moment, prevMoment, alpha);
|
||||
//max(..., 0.001) to ensure we NEVER write exactly 0.0 again
|
||||
//this tells the next frame "I contain data"
|
||||
return float2(max(ao, 0.001), max(moment, 0.001));
|
||||
}
|
||||
|
||||
float2 PS_StoreAO(VSOUT input) : SV_Target
|
||||
{
|
||||
//must prevent history collision here
|
||||
//if we store exactly 0.0 (means White), the next frame's blend pass thinks
|
||||
//history is empty and resets it, causing shimmer
|
||||
//so clamp to 0.0001 so the system knows "This is valid history data"
|
||||
float2 data = tex2D(sAO1, input.uv).rg;
|
||||
return float2(max(1.0 - data.r, 0.0001), max(1.0 - data.g, 0.0001)); //store inverted
|
||||
}
|
||||
|
||||
float2 PS_ATrousPass1(VSOUT input) : SV_Target { return ATrousFilter(sAO1, input.uv, 2, false); }
|
||||
|
||||
float4 PS_ToDisplay(VSOUT input) : SV_Target
|
||||
{
|
||||
float depth = tex2D(Kernel::sDepth, input.uv).r;
|
||||
float ao = ATrousFilter(sAO2Linear, input.uv, 4, true).r; //stable AO mask (fades to 1.0)
|
||||
if (DEBUG_VIEW) {
|
||||
#if BUFFER_COLOR_SPACE > 1
|
||||
return float4(ToOutputColorspace(ao.xxx, true), 1.0);
|
||||
#else
|
||||
return float4(ao.xxx, 1.0);
|
||||
#endif
|
||||
}
|
||||
if (depth == 0 || depth >= DEPTH_BOUNDARY) discard;
|
||||
float3 base = GetLinearColor(input.uv, true);
|
||||
base *= ao;
|
||||
return float4(ToOutputColorspace(base, true), 1.0);
|
||||
}
|
||||
|
||||
/*----------------.
|
||||
| :: TECHNIQUE :: |
|
||||
'----------------*/
|
||||
technique Lumenite_LSAO <
|
||||
ui_label = "LUMENITE: LSAO";
|
||||
ui_tooltip = "Large-Scale Ray Traced Ambient Occlusion (Screen Space).";
|
||||
>
|
||||
{
|
||||
pass { VertexShader = VS; PixelShader = PS_GenerateMip0; RenderTarget = tHiZMip0; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ReduceMip1; RenderTarget = tHiZMip1; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ReduceMip2; RenderTarget = tHiZMip2; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ReduceMip3; RenderTarget = tHiZMip3; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ReduceMip4; RenderTarget = tHiZMip4; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ReduceMip5; RenderTarget = tHiZMip5; }
|
||||
|
||||
pass { VertexShader = VS; PixelShader = PS_TraceAO; RenderTarget = tAOTrace; }
|
||||
pass { VertexShader = VS; PixelShader = PS_TemporalFilter; RenderTarget = tAO1; }
|
||||
pass { VertexShader = VS; PixelShader = PS_StoreAO; RenderTarget = tPrevAO; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ATrousPass1; RenderTarget = tAO2; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ToDisplay; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
========================================================================
|
||||
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_QuantAO.fx
|
||||
Version : 2026.06.09
|
||||
Author : Afzaal (Kaidō)
|
||||
Description: Fast Ambient Occlusion (Screen Space).
|
||||
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
/*------------------.
|
||||
| :: DEFINITIONS :: |
|
||||
'------------------*/
|
||||
#define FOV 60.0
|
||||
#define NEAR_PLANE 0.01
|
||||
#define AO_MAX_MARCH_STEPS 48
|
||||
|
||||
/*--------------.
|
||||
| :: HEADERS :: |
|
||||
'--------------*/
|
||||
#include "ReShade.fxh"
|
||||
#include "./include/lumenite_Projections.fxh"
|
||||
#include "./include/lumenite_Helpers.fxh"
|
||||
#include "./include/lumenite_ColorManagement.fxh"
|
||||
|
||||
/*---------------.
|
||||
| :: UNIFORMS :: |
|
||||
'---------------*/
|
||||
uniform bool DEBUG_VIEW <
|
||||
ui_label = "Show AO Mask";
|
||||
ui_tooltip = "Debug view for the AO. Shows raw AO.";
|
||||
ui_category = "Ambient Occlusion";
|
||||
> = 0;
|
||||
|
||||
uniform float DEPTH_BOUNDARY <
|
||||
ui_type = "slider";
|
||||
ui_min = 0.001; ui_max = 0.999; ui_step = 0.001;
|
||||
ui_label = "AO Range";
|
||||
ui_tooltip = "The Z+ range/depth in which the effect is applied.";
|
||||
ui_category = "Ambient Occlusion";
|
||||
hidden = false;
|
||||
> = 0.6;
|
||||
|
||||
uniform float DEPTH_FADE_START <
|
||||
ui_type = "slider";
|
||||
ui_min = 0.1; ui_max = 1.0; ui_step = 0.01;
|
||||
ui_label = "Z+ Fade Start (%)";
|
||||
ui_tooltip = "Z+ fraction where effect starts fading out (relative to AO Range)";
|
||||
ui_category = "Ambient Occlusion";
|
||||
hidden = true;
|
||||
> = 0.75;
|
||||
|
||||
uniform float AO_INTENSITY <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 1.0;
|
||||
ui_label = "AO Strength";
|
||||
ui_tooltip = "Controls the intensity of the ambient occlusion effect.";
|
||||
ui_category = "Ambient Occlusion";
|
||||
> = 1.0;
|
||||
|
||||
/*--------------.
|
||||
| :: IMPORTS :: |
|
||||
'--------------*/
|
||||
namespace QuantMotion {
|
||||
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; };
|
||||
}
|
||||
|
||||
namespace LumeniteQuantAO {
|
||||
|
||||
/*---------------------.
|
||||
| :: RENDER TARGETS :: |
|
||||
'---------------------*/
|
||||
texture tNormals { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RGBA16F; };
|
||||
sampler sNormals { Texture = tNormals; };
|
||||
|
||||
texture2D tDepth { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = R16F; };
|
||||
sampler2D sDepth { Texture = tDepth; };
|
||||
|
||||
texture tAOTrace { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = R16F; };
|
||||
sampler sAOTrace { Texture = tAOTrace; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
|
||||
|
||||
texture tAO1 { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
|
||||
sampler sAO1 { Texture = tAO1; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
|
||||
|
||||
texture tAO2 { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
|
||||
sampler sAO2 { Texture = tAO2; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
|
||||
|
||||
texture tAO3 { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
|
||||
sampler sAO3Linear { Texture = tAO3; AddressU = CLAMP; AddressV = CLAMP; MagFilter = LINEAR; MinFilter = LINEAR; };
|
||||
|
||||
texture tPrevAO { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
|
||||
sampler sPrevAO { Texture = tPrevAO; AddressU = CLAMP; AddressV = CLAMP; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
|
||||
|
||||
//HiZ mipchain
|
||||
texture tHiZMip0 { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R16F; };
|
||||
texture tHiZMip1 { Width = BUFFER_WIDTH/2; Height = BUFFER_HEIGHT/2; Format = R16F; };
|
||||
texture tHiZMip2 { Width = BUFFER_WIDTH/4; Height = BUFFER_HEIGHT/4; Format = R16F; };
|
||||
texture tHiZMip3 { Width = BUFFER_WIDTH/8; Height = BUFFER_HEIGHT/8; Format = R16F; };
|
||||
texture tHiZMip4 { Width = BUFFER_WIDTH/16; Height = BUFFER_HEIGHT/16; Format = R16F; };
|
||||
texture tHiZMip5 { Width = BUFFER_WIDTH/32; Height = BUFFER_HEIGHT/32; Format = R16F; };
|
||||
|
||||
sampler sHiZMip0 { Texture = tHiZMip0; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
sampler sHiZMip1 { Texture = tHiZMip1; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
sampler sHiZMip2 { Texture = tHiZMip2; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
sampler sHiZMip3 { Texture = tHiZMip3; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
sampler sHiZMip4 { Texture = tHiZMip4; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
sampler sHiZMip5 { Texture = tHiZMip5; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
|
||||
/*--------------.
|
||||
| :: HELPERS :: |
|
||||
'--------------*/
|
||||
void BuildOrthonormalBasis(float3 n, out float3 b1, out float3 b2)
|
||||
{
|
||||
if (n.z < -0.9999999) {
|
||||
b1 = float3(0.0, -1.0, 0.0);
|
||||
b2 = float3(-1.0, 0.0, 0.0);
|
||||
} else {
|
||||
float a = rcp(1.0 + n.z);
|
||||
float b = -n.x * n.y * a;
|
||||
b1 = float3(mad(-n.x * n.x, a, 1.0), b, -n.x);
|
||||
b2 = float3(b, mad(-n.y * n.y, a, 1.0), -n.y);
|
||||
}
|
||||
}
|
||||
|
||||
float3 GenerateHemisphereDirection(float3 normal, float2 rand, float3 tangent, float3 bitangent)
|
||||
{
|
||||
float phi = rand.x * 6.28318530718; //2.0*PI as constant
|
||||
float sinPhi, cosPhi;
|
||||
sincos(phi, sinPhi, cosPhi);
|
||||
float cosTheta = sqrt(1.0 - rand.y);
|
||||
float sinTheta = sqrt(rand.y);
|
||||
float3 result = normal * cosTheta;
|
||||
result = mad(bitangent, sinTheta * sinPhi, result);
|
||||
result = mad(tangent, sinTheta * cosPhi, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
float CalculateDepthFade(float depth)
|
||||
{
|
||||
float fadeStartDepth = DEPTH_BOUNDARY * DEPTH_FADE_START;
|
||||
float fadeRange = DEPTH_BOUNDARY - fadeStartDepth;
|
||||
return 1.0 - saturate((depth - fadeStartDepth) / fadeRange);
|
||||
}
|
||||
|
||||
float2 ATrousFilter(sampler SourceSampler, float2 uv, uint dilation, bool adaptiveDilation)
|
||||
{
|
||||
float4 gbuffer = tex2D(sNormals, uv);
|
||||
if (gbuffer.a == 0 || gbuffer.a >= DEPTH_BOUNDARY) return float2(1.0, 0.0);
|
||||
|
||||
[branch] if (adaptiveDilation) {
|
||||
float confidence = tex2Dlod(QuantMotion::sConfidence, float4(uv, 0, 0)).r;
|
||||
dilation += uint(round((1.0 - confidence) * 2.0)); //scale filter kernel w. motion by up to a factor of 2
|
||||
}
|
||||
|
||||
float2 centerData = tex2Dlod(SourceSampler, float4(uv, 0, 0)).rg;
|
||||
float variance = max(0.0, centerData.g - (centerData.r * centerData.r)); //Moment - AO^2
|
||||
variance = max(variance, 0.0001);
|
||||
float2 sum = centerData;
|
||||
float totalWeight = 1.0;
|
||||
for (int y = -1; y <= 1; y++) for (int x = -1; x <= 1; x++) {
|
||||
if (x == 0 && y == 0) continue;
|
||||
float2 sampleUV = uv + float2(x, y) * dilation * (BUFFER_PIXEL_SIZE * 2.0); //don't forget the x2.0 to properly step half-res grid!
|
||||
float2 sampleData = tex2Dlod(SourceSampler, float4(sampleUV, 0, 0)).rg;
|
||||
float4 sampleGeo = tex2Dlod(sNormals, float4(sampleUV, 0, 0));
|
||||
float depthWeight = exp(-abs(gbuffer.a - sampleGeo.a) / (gbuffer.a * 0.02 + 0.001));
|
||||
float normalWeight = pow(saturate(dot(gbuffer.rgb, sampleGeo.rgb)), 50.0);
|
||||
float aoDiff = centerData.r - sampleData.r;
|
||||
float aoWeight = exp(-(aoDiff * aoDiff) / (variance + 0.0001));
|
||||
float weight = depthWeight * normalWeight * aoWeight;
|
||||
sum += sampleData * weight;
|
||||
totalWeight += weight;
|
||||
}
|
||||
return sum / (totalWeight + EPSILON);
|
||||
}
|
||||
|
||||
float SamplePrevHiZ(float2 centerUV, sampler srcSampler, int srcMipLvl) {
|
||||
float2 srcTexelSize = BUFFER_PIXEL_SIZE * pow(2, srcMipLvl);
|
||||
float2 off[4] = { float2(-0.5, -0.5), float2(0.5, -0.5), float2(-0.5, 0.5), float2(0.5, 0.5) };
|
||||
float minDepth = 1.0;
|
||||
[unroll] for(int i=0; i<4; i++)
|
||||
minDepth = min(minDepth, tex2D(srcSampler, centerUV + off[i] * srcTexelSize).r);
|
||||
return minDepth;
|
||||
}
|
||||
|
||||
/*--------------.
|
||||
| :: 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_GenerateMip0(VSOUT input) : SV_Target
|
||||
{
|
||||
float2 blockOriginUV = floor(input.uv / (BUFFER_PIXEL_SIZE * 2.0)) * (BUFFER_PIXEL_SIZE * 2.0);
|
||||
float2 uvs[4] = { blockOriginUV + BUFFER_PIXEL_SIZE * float2(0.5, 0.5),
|
||||
blockOriginUV + BUFFER_PIXEL_SIZE * float2(1.5, 0.5),
|
||||
blockOriginUV + BUFFER_PIXEL_SIZE * float2(0.5, 1.5),
|
||||
blockOriginUV + BUFFER_PIXEL_SIZE * float2(1.5, 1.5) };
|
||||
float d0 = tex2D(sDepth, uvs[0]).r;
|
||||
float d1 = tex2D(sDepth, uvs[1]).r;
|
||||
float d2 = tex2D(sDepth, uvs[2]).r;
|
||||
float d3 = tex2D(sDepth, uvs[3]).r;
|
||||
return min(min(d0, d1), min(d2, d3));
|
||||
}
|
||||
|
||||
float PS_ReduceMip1 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip0, 0); }
|
||||
float PS_ReduceMip2 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip1, 1); }
|
||||
float PS_ReduceMip3 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip2, 2); }
|
||||
float PS_ReduceMip4 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip3, 3); }
|
||||
float PS_ReduceMip5 (VSOUT input) : SV_Target { return SamplePrevHiZ(input.uv, sHiZMip4, 4); }
|
||||
|
||||
float PS_TraceAO(VSOUT input) : SV_Target
|
||||
{
|
||||
float4 gbuffer = tex2D(sNormals, input.uv);
|
||||
float3 normal = gbuffer.rgb;
|
||||
float depth = gbuffer.a;
|
||||
if (depth == 0 || depth >= DEPTH_BOUNDARY) discard;
|
||||
float3 startPos = UVToViewSpace(input.uv, depth, input);
|
||||
float3 tangent, bitangent;
|
||||
BuildOrthonormalBasis(normal, tangent, bitangent);
|
||||
float2 noise = GetStratifiedNoise(input.vpos.xy);
|
||||
float3 rayDir = GenerateHemisphereDirection(normal, noise, tangent, bitangent);
|
||||
float totalRayLength = 0.7 * depth;
|
||||
float baseStepSize = totalRayLength / (float)AO_MAX_MARCH_STEPS;
|
||||
float stepSize = baseStepSize;
|
||||
float3 currentPos = startPos + rayDir * stepSize;
|
||||
float occlusion = 0.0;
|
||||
float t = stepSize;
|
||||
[loop]
|
||||
for (int step = 0; step < AO_MAX_MARCH_STEPS; step++) {
|
||||
if (t >= totalRayLength) break;
|
||||
|
||||
float2 hitPos = ViewSpaceToUV(currentPos, input);
|
||||
if (IsOOB(hitPos)) break;
|
||||
|
||||
//select the appropriate Mip Level
|
||||
float2 ray_screen_velocity = abs(rayDir.xy / currentPos.z) * float2(BUFFER_WIDTH, BUFFER_HEIGHT);
|
||||
float footprint = max(ray_screen_velocity.x, ray_screen_velocity.y) * max(stepSize / currentPos.z, 1.0);
|
||||
int mip = clamp(int(log2(max(footprint, 1.0))), 0, 5);
|
||||
float HiZDepth;
|
||||
if (mip==5) HiZDepth = tex2Dlod(sHiZMip5, float4(hitPos,0,0)).r;
|
||||
else if (mip==4) HiZDepth = tex2Dlod(sHiZMip4, float4(hitPos,0,0)).r;
|
||||
else if (mip==3) HiZDepth = tex2Dlod(sHiZMip3, float4(hitPos,0,0)).r;
|
||||
else if (mip==2) HiZDepth = tex2Dlod(sHiZMip2, float4(hitPos,0,0)).r;
|
||||
else if (mip==1) HiZDepth = tex2Dlod(sHiZMip1, float4(hitPos,0,0)).r;
|
||||
else HiZDepth = tex2Dlod(sHiZMip0, float4(hitPos,0,0)).r;
|
||||
|
||||
//skip some empty space
|
||||
float currentStepSize = stepSize * max(1.0, float(mip) * 0.5); //stepsize mip scaling
|
||||
if (currentPos.z < HiZDepth && (HiZDepth - currentPos.z) > currentStepSize) {
|
||||
float leap = max(currentStepSize, (HiZDepth - currentPos.z) * 0.065);
|
||||
currentPos += rayDir * leap;
|
||||
t += leap;
|
||||
continue;
|
||||
}
|
||||
|
||||
//hit test
|
||||
float sceneDepth = tex2Dlod(sDepth, float4(hitPos, 0, 0)).r;
|
||||
float depthDiff = currentPos.z - sceneDepth;
|
||||
float maxThickness = currentPos.z * 0.6;
|
||||
if (depthDiff > (currentPos.z * 0.0001) && depthDiff < maxThickness) {
|
||||
float3 scenePos = UVToViewSpace(hitPos, sceneDepth, input);
|
||||
float hitDistance = length(scenePos - startPos);
|
||||
float normalizedDist = hitDistance / totalRayLength;
|
||||
occlusion = 1.0 - saturate(depthDiff / maxThickness);
|
||||
occlusion = occlusion * occlusion;
|
||||
occlusion = saturate(pow(saturate(1.0 - normalizedDist), 1.2) * occlusion * 1.4);
|
||||
break;
|
||||
}
|
||||
|
||||
currentPos += rayDir * stepSize;
|
||||
t += stepSize;
|
||||
}
|
||||
|
||||
float aoFactor = 1.0 - saturate(occlusion * AO_INTENSITY);
|
||||
return aoFactor;
|
||||
}
|
||||
|
||||
float2 PS_TemporalFilter(VSOUT input) : SV_Target
|
||||
{
|
||||
float depth = tex2D(sDepth, input.uv).r;
|
||||
//overwrite noise at boundary with clean White, prevents gaps
|
||||
if (depth >= DEPTH_BOUNDARY) return float2(1.0, 1.0); //1.0 AO, 1.0 Moment
|
||||
if (depth == 0) discard;
|
||||
float ao = tex2D(sAOTrace, input.uv).r;
|
||||
ao = lerp(1.0, ao, CalculateDepthFade(depth));
|
||||
float moment = ao * ao;
|
||||
float2 flow = tex2D(QuantMotion::sFlow, input.uv).xy;
|
||||
float confidence = tex2D(QuantMotion::sConfidence, input.uv).x;
|
||||
confidence = saturate(confidence + log2(2.0 - confidence) * 0.55); //boost confidence
|
||||
float2 rawHistory = tex2D(sPrevAO, input.uv + flow).rg; //history stores "1.0 - AO". 0.0 (Black Texture) -> Reads as 1.0 (White)
|
||||
float prevAO = 1.0 - rawHistory.r;
|
||||
float prevMoment = 1.0 - rawHistory.g;
|
||||
float alpha = confidence * 0.98;
|
||||
ao = lerp(ao, prevAO, alpha);
|
||||
moment = lerp(moment, prevMoment, alpha);
|
||||
//max(..., 0.001) to ensure we NEVER write exactly 0.0 again
|
||||
//this tells the next frame "I contain data"
|
||||
return float2(max(ao, 0.001), max(moment, 0.001));
|
||||
}
|
||||
|
||||
float2 PS_StoreAO(VSOUT input) : SV_Target
|
||||
{
|
||||
//must prevent history collision here
|
||||
//if we store exactly 0.0 (means White), the next frame's blend pass thinks
|
||||
//history is empty and resets it, causing shimmer
|
||||
//so clamp to 0.0001 so the system knows "This is valid history data"
|
||||
float2 data = tex2D(sAO1, input.uv).rg;
|
||||
return float2(max(1.0 - data.r, 0.0001), max(1.0 - data.g, 0.0001)); //store inverted
|
||||
}
|
||||
|
||||
float2 PS_ATrousPass1(VSOUT input) : SV_Target { return ATrousFilter(sAO1, input.uv, 2, false); }
|
||||
|
||||
float2 PS_ATrousPass2(VSOUT input) : SV_Target { return ATrousFilter(sAO2, input.uv, 4, true); }
|
||||
|
||||
float4 PS_ToDisplay(VSOUT input) : SV_Target
|
||||
{
|
||||
float depth = tex2D(sDepth, input.uv).r;
|
||||
float ao = tex2D(sAO3Linear, input.uv).r;
|
||||
if (DEBUG_VIEW) {
|
||||
#if BUFFER_COLOR_SPACE > 1
|
||||
return float4(ToOutputColorspace(ao.xxx, true), 1.0);
|
||||
#else
|
||||
return float4(ao.xxx, 1.0);
|
||||
#endif
|
||||
}
|
||||
if (depth == 0 || depth >= DEPTH_BOUNDARY) discard;
|
||||
float3 base = GetLinearColor(input.uv, true);
|
||||
base *= ao;
|
||||
return float4(ToOutputColorspace(base, true), 1.0);
|
||||
}
|
||||
|
||||
/*----------------.
|
||||
| :: TECHNIQUE :: |
|
||||
'----------------*/
|
||||
technique Lumenite_QuantAO <
|
||||
ui_label = "LUMENITE: QuantAO";
|
||||
ui_tooltip = "Fast Ambient Occlusion (Screen Space).";
|
||||
>
|
||||
{
|
||||
pass { VertexShader = VS; PixelShader = PS_ReconstructNormals; RenderTarget0 = tNormals; RenderTarget1 = tDepth; }
|
||||
pass { VertexShader = VS; PixelShader = PS_GenerateMip0; RenderTarget = tHiZMip0; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ReduceMip1; RenderTarget = tHiZMip1; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ReduceMip2; RenderTarget = tHiZMip2; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ReduceMip3; RenderTarget = tHiZMip3; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ReduceMip4; RenderTarget = tHiZMip4; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ReduceMip5; RenderTarget = tHiZMip5; }
|
||||
|
||||
pass { VertexShader = VS; PixelShader = PS_TraceAO; RenderTarget = tAOTrace; }
|
||||
pass { VertexShader = VS; PixelShader = PS_TemporalFilter; RenderTarget = tAO1; }
|
||||
pass { VertexShader = VS; PixelShader = PS_StoreAO; RenderTarget = tPrevAO; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ATrousPass1; RenderTarget = tAO2; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ATrousPass2; RenderTarget = tAO3; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ToDisplay; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
========================================================================
|
||||
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 : QuantMotion.fx
|
||||
Version : 2026.06.16
|
||||
Author : Afzaal (Kaidō)
|
||||
Description: Superfast motion vectors for low-end hardware.
|
||||
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
/*------------------.
|
||||
| :: DEFINITIONS :: |
|
||||
'------------------*/
|
||||
#define EPSILON 1e-6
|
||||
|
||||
#ifndef DEBUG_FLOW
|
||||
#define DEBUG_FLOW 0
|
||||
#endif
|
||||
|
||||
/*--------------.
|
||||
| :: HEADERS :: |
|
||||
'--------------*/
|
||||
#include "ReShade.fxh"
|
||||
|
||||
/*---------------.
|
||||
| :: UNIFORMS :: |
|
||||
'---------------*/
|
||||
uniform uint FRAME_COUNT < source = "framecount"; >;
|
||||
|
||||
namespace QuantMotion {
|
||||
|
||||
/*---------------------.
|
||||
| :: 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; };
|
||||
|
||||
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 :: |
|
||||
'--------------*/
|
||||
bool IsOOB(float2 uv) {
|
||||
return any(uv < 0.0) || any(uv > 1.0);
|
||||
}
|
||||
|
||||
float3 GetColor(float2 uv)
|
||||
{
|
||||
return tex2Dlod(ReShade::BackBuffer, float4(uv, 0, 0)).rgb;
|
||||
}
|
||||
|
||||
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 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, 0)).x;
|
||||
float lumaW = tex2Dlod(sCurrLuma, float4(uv + float2(-1.0, 0.0) * texelSize, 0, 0)).x;
|
||||
float lumaE = tex2Dlod(sCurrLuma, float4(uv + float2( 1.0, 0.0) * texelSize, 0, 0)).x;
|
||||
float lumaN = tex2Dlod(sCurrLuma, float4(uv + float2( 0.0,-1.0) * texelSize, 0, 0)).x;
|
||||
float lumaS = tex2Dlod(sCurrLuma, float4(uv + float2( 0.0, 1.0) * texelSize, 0, 0)).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, mip)).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) };
|
||||
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;
|
||||
float2 flowDelta = (sampleFlow - centerFlow) / BUFFER_PIXEL_SIZE * 8.0;
|
||||
float flowWeight = exp(-dot(flowDelta, flowDelta) * 0.125);
|
||||
float weight = flowWeight;
|
||||
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[6];
|
||||
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(sPrevFrameFlow, uv).xy;
|
||||
|
||||
float minCost = 1e6;
|
||||
float2 prediction = candidates[0];
|
||||
[loop] for (int i = 0; i < 6; 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 :: |
|
||||
'--------------*/
|
||||
float PS_PackFeatures(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float luma = dot(GetColor(uv), 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_MedianPass8(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;
|
||||
float currLuma = tex2Dlod(sCurrLuma, float4(uv, 0, 3)).r;
|
||||
float prevLuma = tex2Dlod(sPrevLuma, float4(prevUV, 0, 3)).r;
|
||||
float lumaError = abs(currLuma - prevLuma);
|
||||
if(lumaError > 0.1) return 0.0; //no confidence
|
||||
float subpixelThreshold = length(BUFFER_PIXEL_SIZE);
|
||||
float flowMagnitude = length(flow);
|
||||
if (flowMagnitude <= subpixelThreshold) return 0.9; //if flow is subpixel, high confidence
|
||||
float motionPenalty = flowMagnitude / subpixelThreshold;
|
||||
float lengthConfidence = rcp(motionPenalty * 0.07 + 1.0);
|
||||
float photometricConfidence = exp(-lumaError * 8.0 * lengthConfidence);
|
||||
//current frame final confidence
|
||||
float currentConf = lengthConfidence * photometricConfidence;
|
||||
//temporal filter
|
||||
float historyConf = tex2D(sPrevConfidence, prevUV).r;
|
||||
float alpha = (currentConf < historyConf - 0.05) ? 0.5 : 0.1; //drop fast (kill speckles promptly), regain slowly (stay stable)
|
||||
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_FLOW
|
||||
float4 PS_Debug(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
return float4(MotionToColor(tex2D(sFlow, uv).xy), 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*----------------.
|
||||
| :: TECHNIQUE :: |
|
||||
'----------------*/
|
||||
technique Lumenite_QuantMotion <
|
||||
ui_label = "LUMENITE: QuantMotion";
|
||||
ui_tooltip = "Superfast motion vectors for ReShade.";
|
||||
>
|
||||
{
|
||||
//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 = tFlow8; }
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_MedianPass8; 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_FLOW
|
||||
pass { VertexShader = PostProcessVS; PixelShader = PS_Debug; }
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
========================================================================
|
||||
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_RTAO.fx
|
||||
Version : 2026.05.30
|
||||
Author : Afzaal (Kaidō)
|
||||
Description: Ray Traced Ambient Occlusion (Screen Space).
|
||||
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
/*------------------.
|
||||
| :: DEFINITIONS :: |
|
||||
'------------------*/
|
||||
#define FOV 60.0
|
||||
#define NEAR_PLANE 0.01
|
||||
#define INITIAL_STEP_SCALE 0.9
|
||||
#define STEP_GROWTH_FACTOR 1.2
|
||||
#define AO_MAX_MARCH_STEPS 15
|
||||
|
||||
/*--------------.
|
||||
| :: HEADERS :: |
|
||||
'--------------*/
|
||||
#include "ReShade.fxh"
|
||||
#include "./include/lumenite_Projections.fxh"
|
||||
#include "./include/lumenite_Helpers.fxh"
|
||||
#include "./include/lumenite_ColorManagement.fxh"
|
||||
|
||||
/*---------------.
|
||||
| :: UNIFORMS :: |
|
||||
'---------------*/
|
||||
uniform bool DEBUG_VIEW <
|
||||
ui_label = "Show AO Mask";
|
||||
ui_tooltip = "Debug view for the AO. Shows raw AO.";
|
||||
ui_category = "Ambient Occlusion";
|
||||
> = 0;
|
||||
|
||||
uniform float DEPTH_BOUNDARY <
|
||||
ui_type = "slider";
|
||||
ui_min = 0.001; ui_max = 0.999; ui_step = 0.001;
|
||||
ui_label = "AO Range";
|
||||
ui_tooltip = "The Z+ range/depth in which the effect is applied.";
|
||||
ui_category = "Ambient Occlusion";
|
||||
hidden = false;
|
||||
> = 0.6;
|
||||
|
||||
uniform float DEPTH_FADE_START <
|
||||
ui_type = "slider";
|
||||
ui_min = 0.1; ui_max = 1.0; ui_step = 0.01;
|
||||
ui_label = "Z+ Fade Start (%)";
|
||||
ui_tooltip = "Z+ fraction where effect starts fading out (relative to AO Range)";
|
||||
ui_category = "Ambient Occlusion";
|
||||
hidden = true;
|
||||
> = 0.75;
|
||||
|
||||
uniform float AO_INTENSITY <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 1.0;
|
||||
ui_label = "AO Strength";
|
||||
ui_tooltip = "Controls the intensity of the ambient occlusion effect.";
|
||||
ui_category = "Ambient Occlusion";
|
||||
> = 1.0;
|
||||
|
||||
//deprecated
|
||||
// uniform int USER_GUIDE <
|
||||
// ui_type = "radio";
|
||||
// ui_category = "";
|
||||
// ui_label = " ";
|
||||
// ui_text = "RESOLUTION_SCALING:\n0: Renders AO at full-resolution.\n1: Renders AO at half-resolution.";
|
||||
// >;
|
||||
|
||||
/*--------------.
|
||||
| :: 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 LumeniteRTAO {
|
||||
|
||||
/*---------------------.
|
||||
| :: RENDER TARGETS :: |
|
||||
'---------------------*/
|
||||
texture tAOTrace { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = R16F; };
|
||||
sampler sAOTrace { Texture = tAOTrace; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
|
||||
|
||||
texture tAO1 { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
|
||||
sampler sAO1 { Texture = tAO1; AddressU = CLAMP; AddressV = CLAMP; MagFilter = POINT; MinFilter = POINT; MipFilter = POINT; };
|
||||
sampler sAO1Linear { Texture = tAO1; AddressU = CLAMP; AddressV = CLAMP; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
|
||||
|
||||
texture tPrevAO { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RG16F; };
|
||||
sampler sPrevAO { Texture = tPrevAO; AddressU = CLAMP; AddressV = CLAMP; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; };
|
||||
|
||||
/*--------------.
|
||||
| :: HELPERS :: |
|
||||
'--------------*/
|
||||
void BuildOrthonormalBasis(float3 n, out float3 b1, out float3 b2)
|
||||
{
|
||||
if (n.z < -0.9999999) {
|
||||
b1 = float3(0.0, -1.0, 0.0);
|
||||
b2 = float3(-1.0, 0.0, 0.0);
|
||||
} else {
|
||||
float a = rcp(1.0 + n.z);
|
||||
float b = -n.x * n.y * a;
|
||||
b1 = float3(mad(-n.x * n.x, a, 1.0), b, -n.x);
|
||||
b2 = float3(b, mad(-n.y * n.y, a, 1.0), -n.y);
|
||||
}
|
||||
}
|
||||
|
||||
float3 GenerateHemisphereDirection(float3 normal, float2 rand, float3 tangent, float3 bitangent)
|
||||
{
|
||||
float phi = rand.x * 6.28318530718; //2.0*PI as constant
|
||||
float sinPhi, cosPhi;
|
||||
sincos(phi, sinPhi, cosPhi);
|
||||
float cosTheta = sqrt(1.0 - rand.y);
|
||||
float sinTheta = sqrt(rand.y);
|
||||
float3 result = normal * cosTheta;
|
||||
result = mad(bitangent, sinTheta * sinPhi, result);
|
||||
result = mad(tangent, sinTheta * cosPhi, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
float CalculateDepthFade(float depth)
|
||||
{
|
||||
float fadeStartDepth = DEPTH_BOUNDARY * DEPTH_FADE_START;
|
||||
float fadeRange = DEPTH_BOUNDARY - fadeStartDepth;
|
||||
return 1.0 - saturate((depth - fadeStartDepth) / fadeRange);
|
||||
}
|
||||
|
||||
float2 ATrousFilter(sampler SourceSampler, float2 uv, uint dilation)
|
||||
{
|
||||
float4 gbuffer = tex2D(Kernel::sNormals, uv);
|
||||
if (gbuffer.a == 0 || gbuffer.a >= DEPTH_BOUNDARY) return float2(1.0, 0.0);
|
||||
float confidence = tex2Dlod(Kernel::sConfidence, float4(uv, 0, 0)).r;
|
||||
dilation += uint(round((1.0 - confidence))); //scale filter radius with motion
|
||||
float2 centerData = tex2Dlod(SourceSampler, float4(uv, 0, 0)).rg;
|
||||
float variance = max(0.0, centerData.g - (centerData.r * centerData.r)); //Moment - AO^2
|
||||
variance = max(variance, 0.0001);
|
||||
float2 sum = centerData;
|
||||
float totalWeight = 1.0;
|
||||
for (int y = -1; y <= 1; y++) for (int x = -1; x <= 1; x++) {
|
||||
if (x == 0 && y == 0) continue;
|
||||
float2 sampleUV = uv + float2(x, y) * dilation * (BUFFER_PIXEL_SIZE * 2.0); //don't forget the x2.0 to properly step half-res grid!
|
||||
float2 sampleData = tex2Dlod(SourceSampler, float4(sampleUV, 0, 0)).rg;
|
||||
float4 sampleGeo = tex2Dlod(Kernel::sNormals, float4(sampleUV, 0, 0));
|
||||
float depthWeight = exp(-abs(gbuffer.a - sampleGeo.a) / (gbuffer.a * 0.02 + 0.001));
|
||||
float normalWeight = pow(saturate(dot(gbuffer.rgb, sampleGeo.rgb)), 50.0);
|
||||
float aoDiff = centerData.r - sampleData.r;
|
||||
float aoWeight = exp(-(aoDiff * aoDiff) / (variance + 0.0001));
|
||||
float weight = depthWeight * normalWeight * aoWeight;
|
||||
sum += sampleData * weight;
|
||||
totalWeight += weight;
|
||||
}
|
||||
return sum / (totalWeight + EPSILON);
|
||||
}
|
||||
|
||||
/*--------------.
|
||||
| :: SHADERS :: |
|
||||
'--------------*/
|
||||
float PS_TraceAO(VSOUT input) : SV_Target
|
||||
{
|
||||
//deprecated
|
||||
// if (CHECKERBOARD_RENDERING) {
|
||||
// #if RESOLUTION_SCALING
|
||||
// if(CheckerboardSkip(uint2(input.vpos.xy), 2.0)) discard;
|
||||
// #else
|
||||
// if(CheckerboardSkip(uint2(input.vpos.xy), 1.0)) discard;
|
||||
// #endif
|
||||
// }
|
||||
|
||||
float4 gbuffer = tex2D(Kernel::sNormals, input.uv);
|
||||
float3 normal = gbuffer.rgb;
|
||||
float depth = gbuffer.a;
|
||||
if (depth == 0 || depth >= DEPTH_BOUNDARY) discard;
|
||||
float3 startPos = UVToViewSpace(input.uv, depth, input);
|
||||
float3 tangent, bitangent;
|
||||
BuildOrthonormalBasis(normal, tangent, bitangent);
|
||||
float2 noise = GetStratifiedNoise(input.vpos.xy);
|
||||
float3 rayDir = GenerateHemisphereDirection(normal, noise, tangent, bitangent);
|
||||
float invDepth = rcp(depth);
|
||||
float totalRayLength = 0.02 * depth;
|
||||
float initialStepScale = INITIAL_STEP_SCALE * rcp((float)AO_MAX_MARCH_STEPS);
|
||||
float stepSize = totalRayLength * initialStepScale;
|
||||
float3 rayPos = mad(rayDir, stepSize * 0.5, startPos);
|
||||
rayPos += normal * depth * 0.0005; //push ray slightly OUTWARD along the normal; clears staircase artifacts
|
||||
float occlusion = 0.0;
|
||||
[loop]
|
||||
for (int step = 0; step < AO_MAX_MARCH_STEPS; step++) {
|
||||
float2 sampleUV = ViewSpaceToUV(rayPos, input);
|
||||
float sceneDepth = GetDepth(sampleUV);
|
||||
float depthDiff = rayPos.z - sceneDepth;
|
||||
[branch]
|
||||
if (depthDiff > 0.0 && depthDiff < rayPos.z) {
|
||||
float3 scenePos = UVToViewSpace(sampleUV, sceneDepth, input);
|
||||
float hitDistance = length(scenePos - startPos);
|
||||
float normalizedDistance = hitDistance * invDepth;
|
||||
occlusion = exp(-normalizedDistance * 15.0);
|
||||
break;
|
||||
}
|
||||
stepSize *= STEP_GROWTH_FACTOR;
|
||||
rayPos = mad(rayDir, stepSize, rayPos);
|
||||
}
|
||||
|
||||
float aoFactor = 1.0 - saturate(occlusion * AO_INTENSITY);
|
||||
return aoFactor;
|
||||
}
|
||||
|
||||
float2 PS_TemporalFilter(VSOUT input) : SV_Target
|
||||
{
|
||||
float depth = tex2D(Kernel::sDepth, input.uv).r;
|
||||
//overwrite noise at boundary with clean White, prevents gaps
|
||||
if (depth >= DEPTH_BOUNDARY) return float2(1.0, 1.0); //1.0 AO, 1.0 Moment
|
||||
if (depth == 0) discard;
|
||||
float ao = tex2D(sAOTrace, input.uv).r;
|
||||
ao = lerp(1.0, ao, CalculateDepthFade(depth));
|
||||
float moment = ao * ao;
|
||||
float2 flow = tex2D(Kernel::sFlow, input.uv).xy;
|
||||
float confidence = tex2D(Kernel::sConfidence, input.uv).x;
|
||||
confidence = saturate(confidence + log2(2.0 - confidence) * 0.6); //boost confidence
|
||||
float2 rawHistory = tex2D(sPrevAO, input.uv + flow).rg; //history stores "1.0 - AO". 0.0 (Black Texture) -> Reads as 1.0 (White)
|
||||
float prevAO = 1.0 - rawHistory.r;
|
||||
float prevMoment = 1.0 - rawHistory.g;
|
||||
float alpha = confidence * 0.98;
|
||||
ao = lerp(ao, prevAO, alpha);
|
||||
moment = lerp(moment, prevMoment, alpha);
|
||||
//max(..., 0.001) to ensure we NEVER write exactly 0.0 again
|
||||
//this tells the next frame "I contain data"
|
||||
return float2(max(ao, 0.001), max(moment, 0.001));
|
||||
}
|
||||
|
||||
float2 PS_StoreAO(VSOUT input) : SV_Target
|
||||
{
|
||||
//must prevent history collision here
|
||||
//if we store exactly 0.0 (means White), the next frame's blend pass thinks
|
||||
//history is empty and resets it, causing shimmer
|
||||
//so clamp to 0.0001 so the system knows "This is valid history data"
|
||||
float2 data = tex2D(sAO1, input.uv).rg;
|
||||
return float2(max(1.0 - data.r, 0.0001), max(1.0 - data.g, 0.0001)); //store inverted
|
||||
}
|
||||
|
||||
float4 PS_ToDisplay(VSOUT input) : SV_Target
|
||||
{
|
||||
float depth = tex2D(Kernel::sDepth, input.uv).r;
|
||||
float ao = ATrousFilter(sAO1Linear, input.uv, 2).r; //stable AO mask (fades to 1.0)
|
||||
if (DEBUG_VIEW) {
|
||||
#if BUFFER_COLOR_SPACE > 1
|
||||
return float4(ToOutputColorspace(ao.xxx, true), 1.0);
|
||||
#else
|
||||
return float4(ao.xxx, 1.0);
|
||||
#endif
|
||||
}
|
||||
if (depth == 0 || depth >= DEPTH_BOUNDARY) discard;
|
||||
float3 base = GetLinearColor(input.uv, true);
|
||||
base *= ao;
|
||||
return float4(ToOutputColorspace(base, true), 1.0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*----------------.
|
||||
| :: TECHNIQUE :: |
|
||||
'----------------*/
|
||||
technique Lumenite_RTAO <
|
||||
ui_label = "LUMENITE: RTAO";
|
||||
ui_tooltip = "Ray Traced Ambient Occlusion (Screen Space).";
|
||||
>
|
||||
{
|
||||
pass { VertexShader = VS; PixelShader = PS_TraceAO; RenderTarget = tAOTrace; }
|
||||
pass { VertexShader = VS; PixelShader = PS_TemporalFilter; RenderTarget = tAO1; }
|
||||
pass { VertexShader = VS; PixelShader = PS_StoreAO; RenderTarget = tPrevAO; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ToDisplay; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
========================================================================
|
||||
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_SSSR.fx
|
||||
Version : 2026.05.30
|
||||
Author : Afzaal (Kaidō)
|
||||
Description: Stochastic Screen Space Reflections.
|
||||
License : AGNYA License (https://github.com/nvb-uy/AGNYA-License)
|
||||
|
||||
========================================================================
|
||||
*/
|
||||
|
||||
/*------------------.
|
||||
| :: DEFINITIONS :: |
|
||||
'------------------*/
|
||||
#define FOV 70.0
|
||||
#define NEAR_PLANE 0.5
|
||||
#define RAY_LENGTH_SCALE 9.0
|
||||
#define RAY_ORIGIN_BIAS -0.0004
|
||||
|
||||
/*--------------.
|
||||
| :: HEADERS :: |
|
||||
'--------------*/
|
||||
#include "ReShade.fxh"
|
||||
#include "./include/lumenite_Projections.fxh"
|
||||
#include "./include/lumenite_Helpers.fxh"
|
||||
#include "./include/lumenite_ColorManagement.fxh"
|
||||
|
||||
/*---------------.
|
||||
| :: UNIFORMS :: |
|
||||
'---------------*/
|
||||
uniform float DEPTH_BOUNDARY <
|
||||
ui_type = "slider";
|
||||
ui_min = 0.001; ui_max = 0.999; ui_step = 0.001;
|
||||
ui_label = "SSSR Range";
|
||||
ui_tooltip = "The Z+ range/depth in which the effect is applied.";
|
||||
ui_category = "";
|
||||
hidden = false;
|
||||
> = 0.85;
|
||||
|
||||
uniform float DEPTH_FADE_START <
|
||||
ui_type = "slider";
|
||||
ui_min = 0.1; ui_max = 1.0; ui_step = 0.01;
|
||||
ui_label = "Z+ Fade Start (%)";
|
||||
ui_tooltip = "Z+ fraction where effect starts fading out (relative to Z+ boundary)";
|
||||
ui_category = "";
|
||||
hidden = true;
|
||||
> = 0.75;
|
||||
|
||||
uniform int MAX_STEPS <
|
||||
ui_type = "drag";
|
||||
ui_min = 1; ui_max = 32; ui_step = 1;
|
||||
ui_label = "Ray Resolution";
|
||||
ui_category = "";
|
||||
ui_tooltip = "";
|
||||
> = 32;
|
||||
|
||||
uniform int BINARY_SEARCH_STEPS <
|
||||
ui_type = "drag";
|
||||
ui_min = 1; ui_max = 8; ui_step = 1;
|
||||
ui_label = "Hit Refinement";
|
||||
ui_category = "";
|
||||
ui_tooltip = "";
|
||||
> = 4;
|
||||
|
||||
uniform float F0 <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.001;
|
||||
ui_label = "Base Reflectivity (F0)";
|
||||
ui_category = "";
|
||||
ui_tooltip = "";
|
||||
> = 1.0;
|
||||
|
||||
uniform float ROUGHNESS <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 0.3; ui_step = 0.001;
|
||||
ui_label = "Roughness";
|
||||
ui_category = "";
|
||||
ui_tooltip = "";
|
||||
> = 0.1;
|
||||
|
||||
uniform float BUMP_SCALE <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.001;
|
||||
ui_label = "Bump Detail";
|
||||
ui_tooltip = "Scale of the extracted bump details. Lower = finer bumps.";
|
||||
> = 0.5;
|
||||
|
||||
uniform float TAIL_FEATHERING <
|
||||
ui_type = "drag";
|
||||
ui_min = 0.0; ui_max = 5.0; ui_step = 0.001;
|
||||
ui_label = "Tail Feathering";
|
||||
ui_category = "";
|
||||
ui_tooltip = "";
|
||||
> = 0.0;
|
||||
|
||||
|
||||
|
||||
/*--------------.
|
||||
| :: 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 LumeniteSSSR {
|
||||
|
||||
/*---------------------.
|
||||
| :: RENDER TARGETS :: |
|
||||
'---------------------*/
|
||||
texture tSpec1 { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
|
||||
sampler sSpec1 { Texture = tSpec1; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
|
||||
texture tSpec2 { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
|
||||
sampler sSpec2 { Texture = tSpec2; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
|
||||
texture tPrevSpec { Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = RGBA16F; };
|
||||
sampler sPrevSpec { Texture = tPrevSpec; AddressU = CLAMP; AddressV = CLAMP; };
|
||||
|
||||
/*--------------.
|
||||
| :: HELPERS :: |
|
||||
'--------------*/
|
||||
float CalculateDepthFade(float depth)
|
||||
{
|
||||
float fadeStartDepth = DEPTH_BOUNDARY * DEPTH_FADE_START;
|
||||
float fadeRange = DEPTH_BOUNDARY - fadeStartDepth;
|
||||
return 1.0 - saturate((depth - fadeStartDepth) / fadeRange);
|
||||
}
|
||||
|
||||
float3 CalculateSmoothNormal(float2 uv, float4 gbuffer, int dilation, sampler SrcSampler)
|
||||
{
|
||||
float3 normal = gbuffer.rgb;
|
||||
float depth = gbuffer.a;
|
||||
float3 normalSum = normal;
|
||||
float weightSum = 1.0;
|
||||
[unroll] for(int dy = -4; dy <= 4; dy++) for (int dx = -4; dx <= 4; dx++) {
|
||||
float2 sampleUV = uv + float2(dx, dy) * ReShade::PixelSize * dilation;
|
||||
float4 neighborData = tex2Dlod(SrcSampler, float4(sampleUV, 0, 0));
|
||||
float depthDiff = abs(neighborData.a - depth);
|
||||
float normalDot = max(dot(normal, neighborData.rgb), 0.0);
|
||||
float weight = exp(-depthDiff * 300.0) * pow(normalDot, 20.0);
|
||||
normalSum += neighborData.rgb * weight;
|
||||
weightSum += weight;
|
||||
}
|
||||
return normalize(normalSum / weightSum);
|
||||
}
|
||||
|
||||
float3 GetBackBuffer(float2 uv)
|
||||
{
|
||||
return tex2Dlod(ReShade::BackBuffer, float4(uv,0,0)).rgb;
|
||||
}
|
||||
|
||||
float3 CalculateBumpyNormal(float2 uv, float3 geoNormal)
|
||||
{
|
||||
float2 texelSize = BUFFER_PIXEL_SIZE * BUMP_SCALE;
|
||||
float3 lumaWeights = float3(0.299, 0.587, 0.114);
|
||||
//use gamma space intentionally
|
||||
float lumaCenter = dot(GetBackBuffer(uv), lumaWeights);
|
||||
float lumaRight = dot(GetBackBuffer(uv + float2(texelSize.x, 0.0)), lumaWeights);
|
||||
float lumaBottom = dot(GetBackBuffer(uv + float2(0.0, texelSize.y)), lumaWeights);
|
||||
//luma gradients
|
||||
float dx = (lumaRight - lumaCenter) * 2.5;
|
||||
float dy = (lumaBottom - lumaCenter) * 2.5;
|
||||
//orthogonal tangent basis around macro geometry normal
|
||||
float3 up = abs(geoNormal.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0);
|
||||
float3 tangent = normalize(cross(up, geoNormal));
|
||||
float3 bitangent = cross(geoNormal, tangent);
|
||||
//2D bump gradient to 3D tangent-space normal
|
||||
float3 bumpVec = normalize(float3(-dx, -dy, 1.0));
|
||||
return normalize(tangent * bumpVec.x + bitangent * bumpVec.y + geoNormal * bumpVec.z);
|
||||
}
|
||||
|
||||
/*--------------.
|
||||
| :: SHADERS :: |
|
||||
'--------------*/
|
||||
float4 PS_TraceSpecular(VSOUT input) : SV_Target
|
||||
{
|
||||
float4 gbuffer = tex2D(Kernel::sNormals, input.uv);
|
||||
float3 normal = gbuffer.rgb;
|
||||
float depth = gbuffer.a;
|
||||
if (depth <= 0.0 || depth > DEPTH_BOUNDARY) return float4(0, 0, 0, 1);
|
||||
|
||||
//process normals
|
||||
normal = CalculateSmoothNormal(input.uv, gbuffer, 3, Kernel::sNormals);
|
||||
if (BUMP_SCALE > 0.0)
|
||||
normal = CalculateBumpyNormal(input.uv, normal);
|
||||
|
||||
float3 StartPos = UVToViewSpace(input.uv, depth, input);
|
||||
float3 viewDir = normalize(-StartPos);
|
||||
float dynamicRayLengthNormalized = min(RAY_LENGTH_SCALE * depth, 1.0-depth);
|
||||
float stepSize = dynamicRayLengthNormalized / float(MAX_STEPS);
|
||||
float3 mirrorDir = reflect(-viewDir, normal);
|
||||
if (dot(mirrorDir, mirrorDir) < 0.001) { //mirror reflection validation chck
|
||||
return float4(0, 0, 0, 1);
|
||||
}
|
||||
float2 noise = GetStratifiedNoise(input.vpos.xy);
|
||||
float3 jitterN = normalize(normal + float3((noise * 2.0 - 1.0) * ROUGHNESS * 0.2, 0.0));
|
||||
float3 rayDir = reflect(-viewDir, jitterN);
|
||||
if (dot(rayDir, normal) < 0.0) rayDir = mirrorDir; //prevent jitter from pushing ray inside the geometry
|
||||
float biasedOffset = RAY_ORIGIN_BIAS + (depth * RAY_ORIGIN_BIAS * 0.01); //intentional -ve origin bias
|
||||
float3 biasedStartPos = StartPos - (normal * biasedOffset); //deliberately pushes the ray slightly into the floor, makes it immediately collide with the floor's depth, killing "joined reflections"
|
||||
float t = stepSize * noise.x;
|
||||
float3 spec = float3(0.0, 0.0, 0.0);
|
||||
bool hitFound = false;
|
||||
float distanceRatio = 0.0;
|
||||
float2 finalUV = 0.0;
|
||||
|
||||
for (int i = 0; i < MAX_STEPS; i++)
|
||||
{
|
||||
if (t >= dynamicRayLengthNormalized)
|
||||
break;
|
||||
|
||||
float3 currentPos = biasedStartPos + rayDir * t;
|
||||
float2 hitUV = ViewSpaceToUV(currentPos, input);
|
||||
|
||||
if (IsOOB(hitUV))
|
||||
break;
|
||||
|
||||
float sceneDepth = tex2Dlod(Kernel::sDepth, float4(hitUV, 0, 0)).r;
|
||||
if (sceneDepth > DEPTH_BOUNDARY) {
|
||||
t += stepSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
float3 scenePos = UVToViewSpace(hitUV, sceneDepth, input);
|
||||
float depthDiff = currentPos.z - scenePos.z;
|
||||
|
||||
if (depthDiff > 0.0) //passed behind surface
|
||||
{
|
||||
float gateThreshold = dynamicRayLengthNormalized * 0.1; //initially, a broad thickness check
|
||||
float extraThickness = (t > gateThreshold) ? 0.01 : 0.0; //if t is past the threshold, add extra thickness
|
||||
float dynamicThickness = (currentPos.z * 0.05) + extraThickness;
|
||||
|
||||
if (depthDiff < dynamicThickness)
|
||||
{
|
||||
//binary search refinement
|
||||
float binarySearchT = t;
|
||||
float binarySearchStep = stepSize;
|
||||
float3 binarySearchCurrentPos = currentPos;
|
||||
float2 binarySearchUV = hitUV;
|
||||
float3 binarySearchScenePos = scenePos;
|
||||
|
||||
for (int j = 0; j < BINARY_SEARCH_STEPS; j++) {
|
||||
binarySearchStep *= 0.5;
|
||||
binarySearchT += (binarySearchCurrentPos.z > binarySearchScenePos.z) ? -binarySearchStep : binarySearchStep; //move backwards if behind the surface, otherwise forwards
|
||||
binarySearchCurrentPos = biasedStartPos + rayDir * binarySearchT;
|
||||
binarySearchUV = ViewSpaceToUV(binarySearchCurrentPos, input);
|
||||
float binarySearchSceneDepth = tex2Dlod(Kernel::sDepth, float4(binarySearchUV, 0, 0)).r;
|
||||
binarySearchScenePos = UVToViewSpace(binarySearchUV, binarySearchSceneDepth, input);
|
||||
}
|
||||
|
||||
float finalDepthDiff = binarySearchCurrentPos.z - binarySearchScenePos.z;
|
||||
|
||||
if (abs(finalDepthDiff) < (binarySearchCurrentPos.z * 0.01 + 0.01)) { //tighter thickness tolerance on the final refined hit to discard empty space behind thin grass
|
||||
hitFound = true;
|
||||
distanceRatio = binarySearchT / dynamicRayLengthNormalized;
|
||||
finalUV = binarySearchUV;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
t += stepSize * noise.y;
|
||||
}
|
||||
|
||||
if (hitFound) {
|
||||
float3 hitColor = GetLinearColor(finalUV, false);
|
||||
float2 edgeFadeUV = abs(finalUV * 2.0 - 1.0);
|
||||
float edgeFade = saturate(1.0 - max(edgeFadeUV.x, edgeFadeUV.y));
|
||||
edgeFade = smoothstep(0.0, 0.05, edgeFade);
|
||||
float maxDistFade = pow(saturate(1.0 - distanceRatio), TAIL_FEATHERING + EPSILON);
|
||||
spec = hitColor * maxDistFade * edgeFade;
|
||||
}
|
||||
|
||||
return float4(spec, 1.0);
|
||||
}
|
||||
|
||||
float4 PS_TemporalBlend(VSOUT input) : SV_Target
|
||||
{
|
||||
float depth = tex2D(Kernel::sDepth, input.uv).r;
|
||||
if (depth >= DEPTH_BOUNDARY) return float4(0, 0, 0, 0);
|
||||
float3 spec = tex2D(sSpec1, input.uv).rgb;
|
||||
float2 flow = tex2D(Kernel::sFlow, input.uv).xy;
|
||||
float confidence = tex2D(Kernel::sConfidence, input.uv).x;
|
||||
confidence = saturate(confidence + log2(2.0 - confidence) * 0.5);
|
||||
float3 prevSpec = tex2D(sPrevSpec, input.uv + flow).rgb;
|
||||
float historyMax = max(prevSpec.r, max(prevSpec.g, prevSpec.b));
|
||||
float blendWeight = (historyMax < 0.00001) ? 0.0 : (confidence * 0.98);
|
||||
float3 blended = lerp(spec, prevSpec, blendWeight);
|
||||
return float4(blended, 1.0);
|
||||
}
|
||||
|
||||
float4 PS_StoreHistory(VSOUT input) : SV_Target
|
||||
{
|
||||
float depth = tex2D(Kernel::sDepth, input.uv).r;
|
||||
if (depth >= DEPTH_BOUNDARY) return float4(0, 0, 0, 0); //if past boundary, store 0.0 to 'clear' history for next frame
|
||||
return float4(max(tex2D(sSpec2, input.uv).rgb, 0.0001), 1.0); //clamp to 0.0001 so it knows 'valid hist data', prevents shimmer at depth boundary edges
|
||||
}
|
||||
|
||||
float4 PS_ToDisplay(VSOUT input) : SV_Target
|
||||
{
|
||||
float3 base = GetLinearColor(input.uv, false);
|
||||
float4 gbuffer = tex2D(Kernel::sNormals, input.uv);
|
||||
float3 normal = gbuffer.rgb;
|
||||
float depth = gbuffer.a;
|
||||
float3 surfacePos = UVToViewSpace(input.uv, depth, input);
|
||||
float depthFade = CalculateDepthFade(depth);
|
||||
float3 viewDir = normalize(-surfacePos);
|
||||
float NdotV = saturate(dot(normal, viewDir));
|
||||
float fresnel = F0 + (1.0 - F0) * pow(1.0 - NdotV, 5.0); //schlick's approximation
|
||||
float3 spec = tex2D(sSpec2, input.uv).rgb;
|
||||
spec *= depthFade;
|
||||
spec *= fresnel;
|
||||
float reflectionMask = saturate(length(spec) + fresnel * 0.5);
|
||||
float3 conservationBase = base * (1.0 - reflectionMask * 0.7 * depthFade);
|
||||
return float4(ToOutputColorspace(conservationBase + spec, false), 1.0);
|
||||
}
|
||||
|
||||
/*----------------.
|
||||
| :: TECHNIQUE :: |
|
||||
'----------------*/
|
||||
technique LUMENITE_SSSR <
|
||||
ui_label = "LUMENITE: SSSR";
|
||||
ui_tooltip = "Stochastic Screen Space Reflections.";
|
||||
>
|
||||
{
|
||||
pass { VertexShader = VS; PixelShader = PS_TraceSpecular; RenderTarget = tSpec1; }
|
||||
pass { VertexShader = VS; PixelShader = PS_TemporalBlend; RenderTarget = tSpec2; }
|
||||
pass { VertexShader = VS; PixelShader = PS_ToDisplay; }
|
||||
pass { VertexShader = VS; PixelShader = PS_StoreHistory; RenderTarget = tPrevSpec; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,525 @@
|
||||
/*
|
||||
========================================================================
|
||||
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; }
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 253 KiB |
Reference in New Issue
Block a user