109 lines
3.4 KiB
GLSL
109 lines
3.4 KiB
GLSL
#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
|