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

357 lines
14 KiB
HLSL

/*
========================================================================
Copyright (c) Afzaal. All rights reserved.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================================================
GitHub : https://github.com/umar-afzaal/LumeniteFX
Discord : https://discord.gg/deXJrW2dx6
Filename : lumenite_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; }
}
}