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

301 lines
12 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_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; }
}
}