Math fixes, SSR enhancements, improved precision and image sharpness, moved envmap into its own shader to improve SSR realism. Various improvements.

This commit is contained in:
TriVoxel
2025-04-28 06:12:27 -06:00
parent ddc3a071f4
commit 0cdb90bdb8
5 changed files with 247 additions and 93 deletions

View File

@ -2,12 +2,10 @@
#include "compiled.inc"
#include "std/gbuffer.glsl"
#ifdef _Clusters
#include "std/clusters.glsl"
#endif
#ifdef _Irr
#include "std/shirr.glsl"
#endif
#ifdef _SSS
#include "std/sss.glsl"
#endif
@ -15,6 +13,15 @@
#include "std/ssrs.glsl"
#endif
// 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 gbufferD;
uniform sampler2D gbuffer0;
uniform sampler2D gbuffer1;
@ -39,10 +46,6 @@ uniform sampler3D voxelsSDF;
uniform float clipmaps[10 * voxelgiClipmapCount];
#endif
uniform float envmapStrength;
#ifdef _Irr
uniform vec4 shirr[7];
#endif
#ifdef _Brdf
uniform sampler2D senvmapBrdf;
#endif
@ -217,6 +220,7 @@ void main() {
vec2 occspec = unpackFloat2(g1.a);
vec3 albedo = surfaceAlbedo(g1.rgb, metallic); // g1.rgb - basecolor
vec3 f0 = surfaceF0(g1.rgb, metallic);
vec3 envl = vec3(0.0);
float depth = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
vec3 p = getPos(eye, eyeLook, normalize(viewRay), depth, cameraProj);
@ -235,24 +239,23 @@ void main() {
vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(dotNV, 1.0 - roughness) * 256.0), 0).xy;
#endif
// Envmap
#ifdef _Irr
// Sample ambient diffuse lighting
vec3 ambient = sampleDiffuseEnvironment(n);
vec3 envl = shIrradiance(n, shirr);
#ifdef _gbuffer2
if (g2.b >= 0.5) {
ambient = vec3(0.0); // Mask it if g2 says this surface wants no ambient lighting
}
#endif
#ifdef _gbuffer2
if (g2.b < 0.5) {
envl = envl;
} else {
envl = vec3(0.0);
}
#endif
fragColor.rgb += ambient * ambientIntensity;
#endif
#ifdef _EnvTex
envl /= PI;
#endif
#else
vec3 envl = vec3(0.0);
#ifndef _SSR
// Only apply specular environment lighting if SSR is NOT active
vec3 envSpecular = sampleSpecularEnvironment(reflect(viewDir, normal), roughness);
finalColor.rgb += envSpecular * environmentIntensity;
#endif
#ifdef _Rad

View File

@ -1,17 +1,34 @@
// TODO: Integrate with Blender UI
// TODO: Option to disable cone tracing
#version 450
#include "compiled.inc"
#include "std/math.glsl"
#include "std/gbuffer.glsl"
uniform samplerCube probeTex;
uniform sampler2D tex;
uniform sampler2D gbufferD;
uniform sampler2D gbuffer0; // Normal, roughness
uniform sampler2D gbuffer1; // basecol, spec
uniform mat4 P;
uniform mat3 V3;
uniform vec2 cameraProj;
// 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 gbufferD; // 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 = 100.0; // 0.0 - 100.0 (user slider control)
//const float rayThickness = 0.1; // TODO: Adds some thickness to prevent gaps
uniform int coneTraceMode = 2; // 0 = no weighting, 1 = light, 2 = strong
const int coneTraceTapCount = 18; // Number of taps (higher = more precision)
#ifdef _CPostprocess
uniform vec3 PPComp9;
@ -25,62 +42,135 @@ out vec4 fragColor;
vec3 hitCoord;
float depth;
const int numBinarySearchSteps = 7;
const int maxSteps = int(ceil(1.0 / ssrRayStep) * ssrSearchDist);
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)));
}
// Project a view-space hit coordinate into screen UVs
vec2 getProjectedCoord(const vec3 hit) {
vec4 projectedCoord = P * vec4(hit, 1.0);
projectedCoord.xy /= projectedCoord.w;
projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
#ifdef _InvY
projectedCoord.y = 1.0 - projectedCoord.y;
#endif
projectedCoord.xy = clamp(projectedCoord.xy, 0.0, 1.0);
projectedCoord.xy += invScreenSize * 0.5; // half-pixel offset
return projectedCoord.xy;
}
// Compute depth difference between current ray hit and gbuffer depth
float getDeltaDepth(const vec3 hit) {
depth = textureLod(gbufferD, getProjectedCoord(hit), 0.0).r * 2.0 - 1.0;
vec3 viewPos = getPosView(viewRay, depth, cameraProj);
return viewPos.z - hit.z;
vec2 screenUV = getProjectedCoord(hit);
float sampledDepth = textureLod(gbufferD, screenUV, 0.0).r;
float linearSampledDepth = linearize(sampledDepth, cameraProj);
return linearSampledDepth - hit.z;
}
// Refine hit using binary search
vec4 binarySearch(vec3 dir) {
float ddepth;
for (int i = 0; i < numBinarySearchSteps; i++) {
for (int i = 0; i < dynamicBinarySearchSteps(); i++) {
dir *= 0.5;
hitCoord -= dir;
ddepth = getDeltaDepth(hitCoord);
if (ddepth < 0.0) hitCoord += dir;
}
#ifdef _CPostprocess
if (abs(ddepth) > PPComp9.z / 500) return vec4(0.0);
#else
if (abs(ddepth) > ssrSearchDist / 500) return vec4(0.0);
#endif
return vec4(getProjectedCoord(hitCoord), 0.0, 1.0);
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);
}
// Perform raymarching using view-space direction
vec4 rayCast(vec3 dir) {
#ifdef _CPostprocess
dir *= PPComp9.x;
#else
dir *= ssrRayStep;
#endif
for (int i = 0; i < maxSteps; i++) {
hitCoord += dir;
if (getDeltaDepth(hitCoord) > 0.0) return binarySearch(dir);
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 < coneTraceTapCount; ++i) {
float angle = float(i) / float(coneTraceTapCount) * 6.2831853;
vec2 offset = rot * vec2(cos(angle), sin(angle)) * coneSpread;
vec3 sampleDir = normalize(reflDir + vec3(offset, 0.0));
float weight = 1.0;
if (coneTraceMode == 1) {
weight = pow(max(dot(reflDir, sampleDir), 0.0), 2.0); // Light cosine lobe
}
else if (coneTraceMode == 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; }
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; }
@ -88,46 +178,71 @@ void main() {
float d = textureLod(gbufferD, texCoord, 0.0).r * 2.0 - 1.0;
if (d == 1.0) { fragColor.rgb = vec3(0.0); return; }
// Decode octahedral normal
vec2 enc = g0.rg;
vec3 n;
n.z = 1.0 - abs(enc.x) - abs(enc.y);
n.xy = n.z >= 0.0 ? enc.xy : octahedronWrap(enc.xy);
n = normalize(n);
vec3 viewNormal = normalize(V3 * n);
// View-space position and reflection
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 reflected = normalize(reflect(viewDir, viewNormal));
vec3 viewDir = normalize(-viewPos);
vec3 idealReflection = reflect(normalize(viewPos), viewNormal);
hitCoord = viewPos;
// Importance sampling jitter based on roughness
float jitterStrength = roughness * roughness;
vec3 randVec = normalize(vec3(rand(texCoord), rand(texCoord * 1.3), rand(texCoord * 2.7)) * 2.0 - 1.0);
vec3 dir = normalize(mix(reflected, randVec, jitterStrength));
// 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);
vec4 coords = rayCast(dir);
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
float distAtten = clamp((PPComp9.z - length(viewPos - hitCoord)) / PPComp9.z, 0.0, 1.0);
float intensity = pow(reflectivity, PPComp10.x) * screenEdgeFactor * clamp(-reflected.z, 0.0, 1.0) * distAtten * coords.w;
#else
float distAtten = clamp((ssrSearchDist - length(viewPos - hitCoord)) / ssrSearchDist, 0.0, 1.0);
float intensity = pow(reflectivity, ssrFalloffExp) * screenEdgeFactor * clamp(-reflected.z, 0.0, 1.0) * distAtten * coords.w;
#endif
// 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);
vec3 ssrColor = textureLod(tex, coords.xy, roughness * 4.0).rgb;
ssrColor = clamp(ssrColor, 0.0, 1.0);
reflCol = clamp(reflCol, 0.0, 1.0);
envCol = clamp(envCol, 0.0, 1.0);
// Cubemap fallback with roughness-aware LOD sampling
vec3 cubemapColor = textureLod(probeTex, reflected, roughness * 6.0).rgb;
// Roughness-based fade (smoothstep)
float roughFade = smoothstep(0.2, 0.7, roughness);
float ssrVisibility = coords.w * (1.0 - roughFade);
// Additively blend SSR with cubemap fallback
fragColor.rgb = mix(cubemapColor, ssrColor, intensity) * spec;
}
// Blend SSR
vec3 finalColor = mix(envCol, reflCol, ssrVisibility);
fragColor.rgb = finalColor;
}

View File

@ -0,0 +1,28 @@
#ifndef _ENVIRONMENT_SAMPLE_GLSL_
#define _ENVIRONMENT_SAMPLE_GLSL_
// Sample diffuse ambient environment lighting (irradiance)
vec3 sampleDiffuseEnvironment(vec3 normal) {
#ifdef _Irr
vec3 envl = shIrradiance(normal, shirr);
#ifdef _EnvTex
envl /= PI;
#endif
return envl;
#else
return vec3(0.0);
#endif
}
// Sample specular environment reflection (skybox or cubemap)
vec3 sampleSpecularEnvironment(vec3 viewDir, float roughness) {
#ifdef _EnvTex
return textureLod(texEnvironment, viewDir, roughness * 8.0).rgb;
#else
return vec3(0.0);
#endif
}
#endif

View File

@ -109,8 +109,10 @@ vec2 encode_oct(vec3 v) {
vec3 decode_oct(vec2 e) {
vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y));
if (v.z < 0) v.xy = (1.0 - abs(v.yx)) * signNotZero(v.xy);
return normalize(v);
if (v.z < 0.0) {
v.xy = (1.0 - abs(v.yx)) * (vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0));
}
return normalize(v);
}
uint encNor(vec3 n) {

View File

@ -7,6 +7,12 @@ float hash(const vec2 p) {
return fract(sin(h) * 43758.5453123);
}
float hash12(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
vec2 envMapEquirect(const vec3 normal) {
const float PI = 3.1415926535;
const float PI2 = PI * 2.0;
@ -27,9 +33,9 @@ vec2 rand2(const vec2 coord) {
return vec2(noiseX, noiseY);
}
float linearize(const float depth, vec2 cameraProj) {
// to viewz
return cameraProj.y / (depth - cameraProj.x);
float linearize(float depth, vec2 cameraProj) {
depth = depth * 2.0 - 1.0;
return cameraProj.y / (cameraProj.x - depth);
}
float attenuate(const float dist) {