Files
LNXSDK/leenkx/Shaders/sss_pass/sss_pass.frag.glsl

151 lines
5.3 KiB
GLSL

//
// Copyright (C) 2012 Jorge Jimenez (jorge@iryoku.com)
// Copyright (C) 2012 Diego Gutierrez (diegog@unizar.es)
// Copyright (C) 2025 Onek8 (info@leenkx.com)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the following disclaimer
// in the documentation and/or other materials provided with the
// distribution:
//
// "Uses Separable SSS. Copyright (C) 2012 by Jorge Jimenez and Diego
// Gutierrez."
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
// IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation are
// those of the authors and should not be interpreted as representing official
// policies, either expressed or implied, of the copyright holders.
//
// TODO:
// Add real sss radius
// Add real sss scale
// Move temp hash, reorganize shader utility functions
// Add compiler flag for quality presets or with samples parameter
// Clean up + Document comment
#version 450
#include "compiled.inc"
uniform sampler2D gbufferD;
uniform sampler2D gbuffer0;
uniform sampler2D tex;
uniform vec2 dir;
uniform vec2 cameraProj;
in vec2 texCoord;
out vec4 fragColor;
const float SSSS_FOVY = 108.0;
// Temp hash func -
float hash13(vec3 p3) {
p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
vec4 SSSSBlur() {
const int SSSS_N_SAMPLES = 15;
vec4 kernel[SSSS_N_SAMPLES];
// color neutral kernel weights to prevent color shifting
kernel[0] = vec4(0.2, 0.2, 0.2, 0.0);
kernel[1] = vec4(0.12, 0.12, 0.12, 0.2);
kernel[2] = vec4(0.09, 0.09, 0.09, 0.4);
kernel[3] = vec4(0.06, 0.06, 0.06, 0.8);
kernel[4] = vec4(0.04, 0.04, 0.04, 1.2);
kernel[5] = vec4(0.025, 0.025, 0.025, 1.6);
kernel[6] = vec4(0.015, 0.015, 0.015, 2.0);
kernel[7] = vec4(0.005, 0.005, 0.005, 2.5);
kernel[8] = vec4(0.12, 0.12, 0.12, -0.2);
kernel[9] = vec4(0.09, 0.09, 0.09, -0.4);
kernel[10] = vec4(0.06, 0.06, 0.06, -0.8);
kernel[11] = vec4(0.04, 0.04, 0.04, -1.2);
kernel[12] = vec4(0.025, 0.025, 0.025, -1.6);
kernel[13] = vec4(0.015, 0.015, 0.015, -2.0);
kernel[14] = vec4(0.005, 0.005, 0.005, -2.5);
vec4 colorM = textureLod(tex, texCoord, 0.0);
float depth = textureLod(gbufferD, texCoord, 0.0).r;
float depthM = cameraProj.y / (depth - cameraProj.x);
float distanceToProjectionWindow = 1.0 / tan(0.5 * radians(SSSS_FOVY));
float scale = distanceToProjectionWindow / depthM;
vec2 finalStep = sssWidth * scale * dir;
vec3 jitterSeed = vec3(texCoord.xy * 1000.0, fract(cameraProj.x * 0.0001));
float jitterOffset = (hash13(jitterSeed) * 2.0 - 1.0) * 0.15; // 15% jitteR
finalStep *= (1.0 + jitterOffset);
finalStep *= 0.05;
vec3 colorBlurred = vec3(0.0);
vec3 weightSum = vec3(0.0);
colorBlurred += colorM.rgb * kernel[0].rgb;
weightSum += kernel[0].rgb;
// Accumulate the other samples with per-pixel jittering to reduce banding
for (int i = 1; i < SSSS_N_SAMPLES; i++) {
float sampleJitter = hash13(vec3(texCoord.xy * 720.0, float(i) * 37.45)) * 0.1 - 0.05;
vec2 offset = texCoord + (kernel[i].a + sampleJitter) * finalStep;
vec4 color = textureLod(tex, offset, 0.0);
// ADJUST FOR SURFACE FOLLOWING
// 0.0 = disabled (maximum SSS but with bleeding), 1.0 = fully enabled (prevents bleeding but might reduce SSS effect)
const float SURFACE_FOLLOWING_STRENGTH = 0.15; // Reduced to preserve more SSS effect
if (SURFACE_FOLLOWING_STRENGTH > 0.0) {
float sampleDepth = textureLod(gbufferD, offset, 0.0).r;
float depthScale = 5.0;
float depthDiff = abs(depth - sampleDepth) * depthScale;
if (depthDiff > 0.3) {
float blendFactor = clamp(depthDiff - 0.3, 0.0, 1.0) * SURFACE_FOLLOWING_STRENGTH;
color.rgb = mix(color.rgb, colorM.rgb, blendFactor);
}
}
colorBlurred += color.rgb * kernel[i].rgb;
weightSum += kernel[i].rgb;
}
vec3 normalizedColor = colorBlurred / max(weightSum, vec3(0.00001));
float dither = hash13(vec3(texCoord * 1333.0, 0.0)) * 0.003 - 0.0015;
return vec4(normalizedColor + vec3(dither), colorM.a);
}
void main() {
if (textureLod(gbuffer0, texCoord, 0.0).a == 8192.0) {
vec4 originalColor = textureLod(tex, texCoord, 0.0);
vec4 blurredColor = SSSSBlur();
vec4 finalColor = mix(blurredColor, originalColor, 0.15);
fragColor = clamp(finalColor, 0.0, 1.0);
} else {
fragColor = textureLod(tex, texCoord, 0.0);
}
}