// Struct definitions (unchanged) struct deferredMaterialData { vec3 baseColor; vec3 diffuse; vec3 specularReflectance; vec3 normal; vec3 vertex; float roughness; float metallic; float ambientOcclusion; float alpha; float clearcoat; float clearcoatRoughness; #if ANISOTROPY == 1 vec3 tangent; #endif #if TRANSLUCENT_MATERIAL == 1 float translucency; float translucencyDistortion; float translucencyPower; float translucencyScale; float translucencyAmbient; #endif float geometricRoughness; }; struct deferredLightData { vec3 direction; float length; float radius; float inverseFalloff; int lightType; vec3 color; }; // ————————— Optimized BRDF & utilities ————————— float D_GGX(float NoH, float a) { float a2 = a * a; float f = (NoH * a2 - NoH) * NoH + 1.0; return a2 / max(PI * f * f, 1e-5); } float D_GGX_Anisotropy(float NoH, vec3 h, vec3 x, vec3 y, float ax, float ay) { float XoH = dot(x, h), YoH = dot(y, h); float d = XoH * XoH * (ax*ax) + YoH * YoH * (ay*ay) + NoH * NoH; return (ax * ay) / max(PI * d * d, 1e-5); } vec3 F_Schlick(float VoH, vec3 F0, float f90) { float pow5 = pow(1.0 - VoH, 5.0); return F0 + (vec3(f90) - F0) * pow5; } float V_SmithGGXCorrelated(float NoV, float NoL, float a) { float a2 = a * a; float gv = sqrt(max((-NoV*a2 + NoV) * NoV + a2, 0.0)); float gl = sqrt(max((-NoL*a2 + NoL) * NoL + a2, 0.0)); return 0.5 / max(NoL * gv + NoV * gl, 1e-5); } float Fd_Lambert() { return 1.0 / PI; } float F_Schlick_Scalar(float VoH, float F0, float f90) { float pow5 = pow(1.0 - VoH, 5.0); return F0 + (f90 - F0) * pow5; } vec3 irradianceSH(vec3 n) { float nx = n.x, ny = n.y, nz = n.z; float nymnx = ny * nx, nynz = ny * nz, nz2 = nz * nz, nxx_nyy = nx*nx - ny*ny; return vec3(0.7545535, 0.7485417, 0.7909225) + vec3(-0.08385509, 0.09253634, 0.3227673) * ny + vec3(0.3081546, 0.3667994, 0.4667058) * nz + vec3(-0.1888876, -0.2774037, -0.3778448) * nx + vec3(-0.2527824, -0.3160516, -0.396141) * nymnx + vec3(0.07136245, 0.1597891, 0.2905936) * nynz + vec3(-0.03104042) * (3.0 * nz2 - 1.0) // all three components identical + vec3(-0.1610019, -0.2036495, -0.246641) * (nz * nx) + vec3(0.04571093, 0.04812178, 0.04632638) * nxx_nyy; } vec2 prefilteredDFGKaris(float NoV, float roughness) { const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022); const vec4 c1 = vec4( 1.0, 0.0425, 1.040, -0.040); vec4 r = roughness * c0 + c1; float expTerm = exp2(-9.28 * NoV); float a004 = min(r.x * r.x, expTerm) * r.x + r.y; return vec2(-1.04, 1.04) * a004 + r.zw; } vec3 decodeEnvironmentMap(vec4 c) { return c.rgb; // assume linear } float getSquareFalloffAttenuation(float dist2, float invFalloff) { float sf = max(1.0 - (dist2 * invFalloff) * (dist2 * invFalloff), 0.0); return sf * sf; } vec3 fixCubemapLookup(vec3 v, float lod) { vec3 a = abs(v); float M = max(max(a.x, a.y), a.z); float sc = 1.0 - exp2(lod) * (1.0/256.0); if (a.x != M) v.x *= sc; if (a.y != M) v.y *= sc; if (a.z != M) v.z *= sc; return v; } vec3 evaluateSpecularIBL(vec3 r, float roughness) { float lod = 5.0 * roughness; return decodeEnvironmentMap(textureLod(reflectionSampler, fixCubemapLookup(r, lod), lod)) * 1.8; } float computeSpecularao(float NoV, float ao, float roughness) { float powe = exp2(-16.0 * roughness - 1.0); return clamp(pow(NoV + ao, powe) - 1.0 + ao, 0.0, 1.0); } vec3 getSpecularDominantDirection(vec3 n, vec3 r, float roughness2) { float s = 1.0 - roughness2; return mix(n, r, s * (sqrt(s) + sqrt(roughness2))); } // ————————— Lighting functions ————————— vec3 indirectLight(in deferredMaterialData m) { float rough2 = m.roughness * m.roughness; float NoV = max(dot(m.normal, m.vertex), 0.0); vec3 r = reflect(-m.vertex, m.normal); r = getSpecularDominantDirection(m.normal, r, rough2); vec3 diff = m.diffuse * irradianceSH(m.normal) * (1.0 - m.metallic) * m.ambientOcclusion; vec3 spec = evaluateSpecularIBL(r, m.roughness) * computeSpecularao(NoV, m.ambientOcclusion, m.roughness); return diff + spec; } vec3 directLight(in deferredLightData light, in deferredMaterialData m) { vec3 n = m.normal; vec3 v = m.vertex; vec3 L = normalize(-light.direction); float att = 1.0; if (light.lightType == 1) { float Nl = max(dot(n, L), 0.0); att = smoothstep(0.9999, 1.0, Nl); } float NoL = max(dot(n, L), 0.0), NoV = max(dot(n, v), 0.0); vec3 diff = m.diffuse * NoL; vec3 H = normalize(L + v); float NoH = max(dot(n, H), 0.0), VoH = max(dot(v, H), 0.0); #if ANISOTROPY == 1 vec3 t = normalize(m.tangent); vec3 b = normalize(cross(t, n)); float ax = sqrt(m.roughness); float ay = ax; float D = D_GGX_Anisotropy(NoH, H, t, b, ax, ay); #else float D = D_GGX(NoH, m.roughness * m.roughness); #endif float V = V_SmithGGXCorrelated(NoV, NoL, m.roughness * m.roughness); vec3 F = F_Schlick(VoH, m.specularReflectance, 1.0); vec3 spec = (D * V) * F / max(4.0 * NoV * NoL, 1e-5); if (m.clearcoat > 0.0) { float Cr2 = m.clearcoatRoughness * m.clearcoatRoughness; float Dc = D_GGX(NoH, Cr2); float Vc = V_SmithGGXCorrelated(NoV, NoL, Cr2); float Fc = F_Schlick_Scalar(VoH, 0.04, 1.0); spec += m.clearcoat * (Dc * Vc * Fc / max(4.0 * NoV * NoL, 1e-5)); } #if TRANSLUCENT_MATERIAL == 1 vec3 dL = normalize(L + m.translucencyDistortion * vec3(0.1)); float Nt = max(dot(n, dL), 0.0); float trs = pow(Nt, m.translucencyPower) * m.translucencyScale; trs = clamp(trs + m.translucencyAmbient, 0.0, 1.0); diff += m.translucency * trs * light.color; #endif return att * light.color * (diff * (1.0 - m.metallic) + spec); } // ————————— Main shading entry ————————— vec4 physically_based_shading(deferredLightData light, deferredMaterialData m) { m.diffuse = m.baseColor * (1.0 - m.metallic); m.specularReflectance = mix(vec3(0.04), m.baseColor, m.metallic); m.geometricRoughness = m.roughness * m.roughness; m.vertex = normalize(cameraPosition - m.vertex); vec3 color = indirectLight(m) + directLight(light, m); return vec4(color, m.alpha); }