diff --git a/leenkx/Shaders/deferred_light/deferred_light.frag.glsl b/leenkx/Shaders/deferred_light/deferred_light.frag.glsl index 61f94c12..976dd487 100644 --- a/leenkx/Shaders/deferred_light/deferred_light.frag.glsl +++ b/leenkx/Shaders/deferred_light/deferred_light.frag.glsl @@ -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 diff --git a/leenkx/Shaders/ssr_pass/ssr_pass.frag.glsl b/leenkx/Shaders/ssr_pass/ssr_pass.frag.glsl index e21a16a7..3313681a 100644 --- a/leenkx/Shaders/ssr_pass/ssr_pass.frag.glsl +++ b/leenkx/Shaders/ssr_pass/ssr_pass.frag.glsl @@ -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; +} \ No newline at end of file diff --git a/leenkx/Shaders/std/environment_sample.glsl b/leenkx/Shaders/std/environment_sample.glsl new file mode 100644 index 00000000..a44b296c --- /dev/null +++ b/leenkx/Shaders/std/environment_sample.glsl @@ -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 diff --git a/leenkx/Shaders/std/gbuffer.glsl b/leenkx/Shaders/std/gbuffer.glsl index 1ce3a4d2..ac10245d 100644 --- a/leenkx/Shaders/std/gbuffer.glsl +++ b/leenkx/Shaders/std/gbuffer.glsl @@ -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) { diff --git a/leenkx/Shaders/std/math.glsl b/leenkx/Shaders/std/math.glsl index a4a64138..d75a8e52 100644 --- a/leenkx/Shaders/std/math.glsl +++ b/leenkx/Shaders/std/math.glsl @@ -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) {