#ifndef LIGHTING_GLSL #define LIGHTING_GLSL #include "lights.glsl" #define PI 3.14159265359 // GGX / Trowbridge-Reitz normal distribution float distributionGGX(float NoH, float roughness) { float a = roughness * roughness; float a2 = a * a; float d = NoH * NoH * (a2 - 1.0) + 1.0; return a2 / (PI * d * d); } // Smith-Schlick GGX, k remapped for direct lighting float geometrySmithGGX(float NoV, float NoL, float roughness) { float r = roughness + 1.0; float k = (r * r) / 8.0; float ggxV = NoV / (NoV * (1.0 - k) + k); float ggxL = NoL / (NoL * (1.0 - k) + k); return ggxV * ggxL; } vec3 fresnelSchlick(float VoH, vec3 f0) { return f0 + (1.0 - f0) * pow(1.0 - VoH, 5.0); } // Inverse square falloff, windowed so it reaches exactly 0 at the light range. // The range is also the sphere the light culling uses, so no light gets cut off at a tile edge. float calculateDistanceAttenuation(float distance, float range) { float window = clamp(1.0 - pow(distance / range, 4.0), 0.0, 1.0); return (window * window) / max(distance * distance, 0.0001); } float calculateSpotAttenuation(Light light, vec3 l) { float cd = dot(normalize(light.direction), -l); return smoothstep(light.spotOuterCos, light.spotInnerCos, cd); } vec3 calculateLight(Light light, vec3 fragPos, vec3 n, vec3 v, vec3 diffuseColor, float roughness, float metallic, vec3 f0, float occlusion) { vec3 l; float attenuation = 1.0; if (light.type == TYPE_DIRECTIONAL_LIGHT) { l = normalize(-light.direction); } else { vec3 toLight = light.position - fragPos; float distance = length(toLight); l = toLight / max(distance, 0.0001); attenuation = calculateDistanceAttenuation(distance, light.range); if (light.type == TYPE_SPOT_LIGHT) { attenuation *= calculateSpotAttenuation(light, l); } } float NoL = clamp(dot(n, l), 0.0, 1.0); if (NoL <= 0.0 || attenuation <= 0.0) { return vec3(0.0); } vec3 h = normalize(v + l); float NoV = clamp(dot(n, v), 0.0001, 1.0); float NoH = clamp(dot(n, h), 0.0, 1.0); float VoH = clamp(dot(v, h), 0.0, 1.0); float D = distributionGGX(NoH, roughness); float G = geometrySmithGGX(NoV, NoL, roughness); vec3 F = fresnelSchlick(VoH, f0); vec3 specular = (D * G * F) / (4.0 * NoV * NoL + 0.0001); vec3 kd = (vec3(1.0) - F) * (1.0 - metallic); vec3 diffuse = kd * diffuseColor / PI; return (diffuse + specular) * light.color * light.intensity * attenuation * NoL * occlusion; } // Analytic approximation of the split sum environment BRDF (Karis, "Physically Based Shading on Mobile"). // Used to light the flat ambient term with the same specular response as the lights. vec3 envBRDFApprox(vec3 f0, float roughness, float NoV) { const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022); const vec4 c1 = vec4(1.0, 0.0425, 1.04, -0.04); vec4 r = roughness * c0 + c1; float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y; vec2 ab = vec2(-1.04, 1.04) * a004 + r.zw; return f0 * ab.x + ab.y; } // blue -> green -> red, used to visualise how many lights a tile has vec3 lightHeatmapColor(uint lightCount) { float t = clamp(float(lightCount) / 16.0, 0.0, 1.0); if (t < 0.5) { return mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0), t * 2.0); } return mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), (t - 0.5) * 2.0); } #endif // LIGHTING_GLSL