Files
QuaternionEngine/shaders/deferred_lighting.frag

183 lines
5.2 KiB
GLSL

#version 450
#extension GL_GOOGLE_include_directive : require
#include "input_structures.glsl"
layout(location=0) in vec2 inUV;
layout(location=0) out vec4 outColor;
layout(set=1, binding=0) uniform sampler2D posTex;
layout(set=1, binding=1) uniform sampler2D normalTex;
layout(set=1, binding=2) uniform sampler2D albedoTex;
layout(set=2, binding=0) uniform sampler2D shadowTex[4];
const float PI = 3.14159265359;
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);
}
const vec2 POISSON_16[16] = vec2[16](
vec2(0.2852, -0.1883), vec2(-0.1464, 0.2591),
vec2(-0.3651, -0.0974), vec2(0.0901, 0.3807),
vec2(0.4740, 0.0679), vec2(-0.0512, -0.4466),
vec2(-0.4497, 0.1673), vec2(0.3347, 0.3211),
vec2(0.1948, -0.4196), vec2(-0.2919, -0.3291),
vec2(-0.0763, 0.4661), vec2(0.4421, -0.2217),
vec2(0.0281, -0.2468), vec2(-0.2104, 0.0573),
vec2(0.1197, 0.0779), vec2(-0.0905, -0.1203)
);
// Clipmap selection: choose the smallest level whose light-space XY NDC contains the point.
uint selectCascadeIndex(vec3 worldPos)
{
for (uint i = 0u; i < 4u; ++i)
{
vec4 lclip = sceneData.lightViewProjCascades[i] * vec4(worldPos, 1.0);
vec3 ndc = lclip.xyz / max(lclip.w, 1e-6);
if (abs(ndc.x) <= 1.0 && abs(ndc.y) <= 1.0 && ndc.z >= 0.0 && ndc.z <= 1.0)
{
return i;
}
}
return 3u;
}
float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L)
{
uint ci = selectCascadeIndex(worldPos);
mat4 lightMat = sceneData.lightViewProjCascades[ci];
vec4 lclip = lightMat * vec4(worldPos, 1.0);
vec3 ndc = lclip.xyz / lclip.w;
vec2 suv = ndc.xy * 0.5 + 0.5;
if (any(lessThan(suv, vec2(0.0))) || any(greaterThan(suv, vec2(1.0))))
return 1.0;
float current = clamp(ndc.z, 0.0, 1.0);
float NoL = max(dot(N, L), 0.0);
float slopeBias = max(0.0006 * (1.0 - NoL), 0.0001);
float dzdx = dFdx(current);
float dzdy = dFdy(current);
float ddz = max(abs(dzdx), abs(dzdy));
float bias = slopeBias + ddz * 0.75;
ivec2 dim = textureSize(shadowTex[ci], 0);
vec2 texelSize = 1.0 / vec2(dim);
float baseRadius = 1.25;
float radius = mix(baseRadius, baseRadius * 3.0, float(ci) / 3.0);
float ang = hash12(suv * 4096.0) * 6.2831853;
vec2 r = vec2(cos(ang), sin(ang));
mat2 rot = mat2(r.x, -r.y, r.y, r.x);
const int TAP_COUNT = 16;
float visible = 0.0;
float wsum = 0.0;
for (int i = 0; i < TAP_COUNT; ++i)
{
vec2 pu = rot * POISSON_16[i];
vec2 off = pu * radius * texelSize;
float pr = length(pu);
float w = 1.0 - smoothstep(0.0, 0.65, pr);
float mapD = texture(shadowTex[ci], suv + off).r;
float vis = step(mapD, current + bias);
visible += vis * w;
wsum += w;
}
float visibility = (wsum > 0.0) ? (visible / wsum) : 1.0;
return visibility;
}
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
float DistributionGGX(vec3 N, vec3 H, float roughness)
{
float a = roughness * roughness;
float a2 = a * a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH * NdotH;
float num = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return num / max(denom, 0.001);
}
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r * r) / 8.0;
float denom = NdotV * (1.0 - k) + k;
return NdotV / max(denom, 0.001);
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
float ggx2 = GeometrySchlickGGX(max(dot(N, V), 0.0), roughness);
float ggx1 = GeometrySchlickGGX(max(dot(N, L), 0.0), roughness);
return ggx1 * ggx2;
}
void main(){
vec4 posSample = texture(posTex, inUV);
if (posSample.w == 0.0)
{
outColor = vec4(0.0);
return;
}
vec3 pos = posSample.xyz;
vec4 normalSample = texture(normalTex, inUV);
vec3 N = normalize(normalSample.xyz);
float roughness = clamp(normalSample.w, 0.04, 1.0);
vec4 albedoSample = texture(albedoTex, inUV);
vec3 albedo = albedoSample.rgb;
float metallic = clamp(albedoSample.a, 0.0, 1.0);
vec3 camPos = vec3(inverse(sceneData.view)[3]);
vec3 V = normalize(camPos - pos);
vec3 L = normalize(-sceneData.sunlightDirection.xyz);
vec3 H = normalize(V + L);
vec3 F0 = mix(vec3(0.04), albedo, metallic);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 numerator = NDF * G * F;
float denom = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
vec3 specular = numerator / max(denom, 0.001);
vec3 kS = F;
vec3 kD = (1.0 - kS) * (1.0 - metallic);
float NdotL = max(dot(N, L), 0.0);
// Shadowing (directional, forward-Z shadow map)
float visibility = calcShadowVisibility(pos, N, L);
vec3 irradiance = sceneData.sunlightColor.rgb * sceneData.sunlightColor.a * NdotL * visibility;
vec3 color = (kD * albedo / PI + specular) * irradiance;
color += albedo * sceneData.ambientColor.rgb;
outColor = vec4(color, 1.0);
}