Files
LNXSDK/leenkx/Shaders/ssr_pass/ssr_pass.frag.glsl
2025-04-29 14:33:13 -06:00

251 lines
8.3 KiB
GLSL

// TODO: Integrate with Blender UI
// TODO: Option to disable cone tracing
#version 450
#include "compiled.inc"
#include "std/math.glsl"
#include "std/gbuffer.glsl"
// Environment map
uniform float envmapStrength;
#ifdef _Irr
uniform vec4 shirr[7];
uniform float ambientIntensity;
#include "std/shirr.glsl"
#endif
#include "std/environment_sample.glsl"
uniform sampler2D tex; // Environment map
//uniform sampler2D depthtex; // Full Depth buffer
uniform sampler2D gbufferD; // Cheap Depth buffer
uniform sampler2D gbuffer0; // Normal, roughness
uniform sampler2D gbuffer1; // Base color, spec
uniform mat4 P; // Projection matrix
uniform mat3 V3; // View matrix
uniform vec2 cameraProj; // Camera projection params
uniform vec2 invScreenSize; // (1.0/width, 1.0/height)
const float ssrPrecision = 0.0; // 0.0 - 100.0 (user slider control)
//const float rayThickness = 0.1; // TODO: Adds some thickness to prevent gaps
uniform int ssrConetraceMode = 2; // 0 = no weighting, 1 = light, 2 = strong
const int ssrConetraceTaps = 18; // Number of taps (higher = more precision)
in vec3 viewRay;
in vec2 texCoord;
out vec4 fragColor;
vec3 hitCoord;
float depth;
const int baseBinarySearchSteps = 10; // Parameterize?
const int baseMaxSteps = int(ceil(1.0 / ssrRayStep) * ssrSearchDist);
int dynamicBinarySearchSteps() {
return int(mix(7.0, 20.0, clamp(ssrPrecision / 100.0, 0.0, 1.0)));
}
int dynamicMaxSteps() {
return int(mix(float(baseMaxSteps), 300.0, clamp(ssrPrecision / 100.0, 0.0, 1.0)));
}
vec2 getProjectedCoord(const vec3 hit) {
vec4 clip = P * vec4(hit, 1.0);
vec2 uv = clip.xy / clip.w;
uv = uv * 0.5 + 0.5;
#ifdef _InvY
uv.y = 1.0 - uv.y;
#endif
uv = clamp(uv, 0.0, 1.0);
uv += invScreenSize * 0.5; // half-pixel offset
return uv;
}
float getDeltaDepth(const vec3 hit) {
vec2 screenUV = getProjectedCoord(hit);
float raw = textureLod(gbufferD, screenUV, 0.0).r;
float sampledDepth = raw * 2.0 - 1.0;
float linearSampledDepth = linearize(sampledDepth, cameraProj);
return linearSampledDepth - hit.z;
}
vec4 binarySearch(vec3 dir) {
float ddepth;
for (int i = 0; i < dynamicBinarySearchSteps(); i++) {
dir *= 0.5;
hitCoord -= dir;
ddepth = getDeltaDepth(hitCoord);
if (ddepth < 0.0) hitCoord += dir;
}
vec2 projectedCoord = getProjectedCoord(hitCoord);
float pixelSize = length(fwidth(projectedCoord)) * 0.5;
float epsilon = max(pixelSize * 10.0, 0.01);
if (abs(ddepth) > epsilon) return vec4(0.0);
hitCoord.xy = clamp(projectedCoord, 0.0, 1.0);
return vec4(projectedCoord, 0.0, 1.0);
}
vec4 rayCast(vec3 dir, float roughness) {
vec3 stepDir = normalize(dir);
// Apply small jitter if high precision
if (ssrPrecision > 80.0 && roughness > 0.05) {
stepDir = normalize(stepDir + vec3(rand2(texCoord), 0.0) * 0.01);
}
float distance = length(hitCoord - viewRay);
float stepDivisor = mix(100.0, 300.0, clamp(ssrPrecision / 100.0, 0.0, 1.0));
float stepSize = max(0.01, distance / stepDivisor);
float maxStepSize = 0.1;
for (int i = 0; i < dynamicMaxSteps(); i++) {
hitCoord += stepDir * stepSize;
vec2 projCoord = getProjectedCoord(hitCoord);
float sampledDepth = textureLod(gbufferD, projCoord, 0.0).r;
float depthTolerance = fwidth(sampledDepth) * 2.0; // Dynamic tolerance
if (getDeltaDepth(hitCoord) > depthTolerance) {
return binarySearch(stepDir);
}
stepSize = min(stepSize * 1.1, maxStepSize);
}
return vec4(0.0);
}
vec3 coneTraceApprox(vec3 reflDir, float roughness) {
vec3 result = vec3(0.0);
float totalWeight = 0.0;
float randAngle = hash12(texCoord) * 6.2831853;
mat2 rot = mat2(cos(randAngle), -sin(randAngle), sin(randAngle), cos(randAngle));
float coneSpread = roughness * 0.5; // widen based on roughness
for (int i = 0; i < ssrConetraceTaps; ++i) {
float angle = float(i) / float(ssrConetraceTaps) * 6.2831853;
vec2 offset = rot * vec2(cos(angle), sin(angle)) * coneSpread;
vec3 sampleDir = normalize(reflDir + vec3(offset, 0.0));
float weight = 1.0;
if (ssrConetraceMode == 1) {
weight = pow(max(dot(reflDir, sampleDir), 0.0), 2.0); // Light cosine lobe
}
else if (ssrConetraceMode == 2) {
weight = pow(max(dot(reflDir, sampleDir), 0.0), 8.0); // Strong GGX lobe
}
// Approximate sampling by using tex at reflection direction
vec2 envUV = envMapEquirect(sampleDir);
vec3 sampleColor = textureLod(tex, envUV, 0.0).rgb;
result += sampleColor * weight;
totalWeight += weight;
}
return result / max(totalWeight, 0.0001);
}
vec3 tangentSpaceGGX(vec3 N, float roughness) {
float a = roughness * roughness;
float phi = rand(texCoord) * 6.2831853;
float cosTheta = sqrt((1.0 - rand(texCoord)) / (1.0 + (a * a - 1.0) * rand(texCoord)));
float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
vec3 T = normalize(cross(N, vec3(0.0, 1.0, 0.0)));
if (length(T) < 0.01) T = normalize(cross(N, vec3(1.0, 0.0, 0.0)));
vec3 B = cross(N, T);
return normalize(T * cos(phi) * sinTheta + B * sin(phi) * sinTheta + N * cosTheta);
}
void main() {
vec4 g0 = textureLod(gbuffer0, texCoord, 0.0);
float roughness = unpackFloat(g0.b).y;
if (roughness == 1.0) { fragColor.rgb = vec3(0.0); return; }
float spec = fract(textureLod(gbuffer1, texCoord, 0.0).a);
if (spec == 0.0) { fragColor.rgb = vec3(0.0); return; }
// sample raw depth, bail if empty
float dRaw = textureLod(gbufferD, texCoord, 0.0).r;
if (dRaw == 0.0) { fragColor.rgb = vec3(0.0); return; }
// convert to NDC z before reconstructing
float d = dRaw * 2.0 - 1.0;
vec3 n = decode_oct(g0.rg);
vec3 viewNormal = V3 * n;
viewNormal = normalize(mix(viewNormal, normalize(-viewRay), 0.05)); // slightly bias the normal toward the view direction at glancing angles
vec3 viewPos = getPosView(viewRay, d, cameraProj);
vec3 viewDir = normalize(-viewPos);
vec3 idealReflection = reflect(normalize(viewPos), viewNormal);
hitCoord = viewPos;
// Apply GGX importance sampling in tangent space
vec3 jitteredDir = tangentSpaceGGX(viewNormal, roughness);
// Blend based on roughness (0 = perfect mirror, 1 = fully scattered)
vec3 dir = normalize(mix(idealReflection, jitteredDir, roughness));
vec4 coords = rayCast(dir, roughness);
vec2 deltaCoords = abs(vec2(0.5, 0.5) - coords.xy);
float screenEdgeFactor = clamp(1.0 - (deltaCoords.x + deltaCoords.y), 0.0, 1.0);
float reflectivity = 1.0 - roughness;
#ifdef _CPostprocess
// Postprocess mode intensity calculation...
#else
float intensity = 0.0;
if (coords.w > 0.0) {
// Ray hit in screen-space
intensity = pow(reflectivity, ssrFalloffExp) * screenEdgeFactor *
clamp(-idealReflection.z, 0.0, 1.0) *
clamp((ssrSearchDist - length(viewPos - hitCoord)) * (1.0 / ssrSearchDist), 0.0, 1.0);
}
else if (roughness < 0.7) {
// Ray miss, roughness low enough: do cone trace approximation
vec3 coneColor = coneTraceApprox(idealReflection, roughness);
vec3 envCol = sampleSpecularEnvironment(idealReflection, roughness);
coneColor = clamp(coneColor, 0.0, 1.0);
envCol = clamp(envCol, 0.0, 1.0);
vec3 finalColor = mix(envCol, mix(envCol, coneColor, intensity), coords.w);
fragColor.rgb = finalColor * 0.5; // match previous intensity scaling
return;
}
else {
// Ray miss, roughness too high: fallback to environment map
vec3 fallbackEnvColor = sampleSpecularEnvironment(reflect(viewDir, n), roughness);
fragColor.rgb = fallbackEnvColor;
return;
}
#endif
vec3 reflCol = textureLod(tex, coords.xy, 0.0).rgb; // SSR reflection
vec3 envCol = textureLod(tex, texCoord, 0.0).rgb; // Background environment
intensity = clamp(intensity, 0.0, 1.0);
reflCol = clamp(reflCol, 0.0, 1.0);
envCol = clamp(envCol, 0.0, 1.0);
// Roughness-based fade (smoothstep)
float roughFade = smoothstep(0.2, 0.7, roughness);
float ssrVisibility = coords.w * (1.0 - roughFade);
// Blend SSR
vec3 finalColor = mix(envCol, reflCol, ssrVisibility);
fragColor.rgb = finalColor;
}