This commit is contained in:
2026-11-02 19:35:36 -05:00
parent a6103e453d
commit 7b32ad674e
52 changed files with 3285 additions and 1488 deletions
+150 -13
View File
@@ -12,7 +12,42 @@ struct DirLight {
float intensity;
};
uniform DirLight dirLight;
struct PointLight {
vec3 position;
vec3 color;
float intensity;
float radius;
int shadowTile;
};
struct SpotLight {
vec3 position;
vec3 direction;
vec3 color;
float intensity;
float radius;
float innerCos;
float outerCos;
int shadowTile;
};
// The array sizes have to match Light.h
#define MAX_DIRECTIONAL_LIGHTS 4
#define MAX_POINT_LIGHTS 16
#define MAX_SPOT_LIGHTS 8
#define SHADOW_ATLAS_COLUMNS 6
#define SHADOW_ATLAS_TILES (SHADOW_ATLAS_COLUMNS * SHADOW_ATLAS_COLUMNS)
uniform DirLight dirLight; // the sun, the only light with a shadow map
uniform DirLight directionalLights[MAX_DIRECTIONAL_LIGHTS];
uniform int directionalLightCount = 0;
uniform PointLight pointLights[MAX_POINT_LIGHTS];
uniform int pointLightCount = 0;
uniform SpotLight spotLights[MAX_SPOT_LIGHTS];
uniform int spotLightCount = 0;
uniform sampler2DShadow localShadowMap;
uniform mat4 shadowTileMatrices[SHADOW_ATLAS_TILES];
uniform vec3 viewPos;
uniform sampler2DShadow shadowMap;
@@ -23,6 +58,7 @@ uniform int useTexture = 0;
uniform float metallic = 0.0;
uniform float roughness = 0.7;
uniform float ao = 1.0;
uniform float emissive = 0.0;
uniform float envIntensity = 0.8;
const float PI = 3.14159265359;
@@ -72,6 +108,65 @@ vec3 EnvironmentBRDF(vec3 F0, float rough, float NdotV)
return F0 * AB.x + AB.y;
}
// Light from one direction, L points from the surface toward the light
vec3 ShadeLight(vec3 L, vec3 radiance, vec3 N, vec3 V, vec3 albedo, float metal, float rough, vec3 F0, float NdotV)
{
float NdotL = max(dot(N, L), 0.0);
if (NdotL <= 0.0)
return vec3(0.0);
vec3 H = normalize(V + L);
vec3 F = FresnelSchlick(max(dot(H, V), 0.0), F0);
float NDF = DistributionGGX(N, H, rough);
float G = GeometrySmith(N, V, L, rough);
vec3 specular = (NDF * G * F) / max(4.0 * NdotV * NdotL, 1e-4);
vec3 kD = (vec3(1.0) - F) * (1.0 - metal);
return (kD * albedo / PI + specular) * radiance * NdotL;
}
// Inverse square falloff, windowed so it reaches exactly zero at the radius
float PointAttenuation(float distanceSquared, float radius)
{
float ratio = distanceSquared / (radius * radius);
float window = clamp(1.0 - ratio * ratio, 0.0, 1.0);
return window * window / max(distanceSquared, 0.01);
}
// Which of a point light's 6 atlas tiles covers offset, the order is set in RenderLocalShadows
int PointShadowFace(vec3 offset)
{
vec3 size = abs(offset);
if (size.x >= size.y && size.x >= size.z) return offset.x > 0.0 ? 0 : 1;
if (size.y >= size.z) return offset.y > 0.0 ? 2 : 3;
return offset.z > 0.0 ? 4 : 5;
}
// 1 = lit, 0 = shadowed. Stays inside the tile so the filter can't pick up its neighbours.
float LocalShadow(int tile, vec3 worldPos, vec3 N)
{
vec4 clipPos = shadowTileMatrices[tile] * vec4(worldPos + N * 0.04, 1.0);
if (clipPos.w <= 0.0)
return 1.0;
vec3 projCoords = clipPos.xyz / clipPos.w * 0.5 + 0.5;
if (any(lessThan(projCoords, vec3(0.0))) || any(greaterThan(projCoords, vec3(1.0))))
return 1.0;
float tileSize = 1.0 / float(SHADOW_ATLAS_COLUMNS);
vec2 tileOrigin = vec2(float(tile % SHADOW_ATLAS_COLUMNS), float(tile / SHADOW_ATLAS_COLUMNS)) * tileSize;
vec2 texelSize = 1.0 / vec2(textureSize(localShadowMap, 0));
vec2 uv = tileOrigin + projCoords.xy * tileSize;
float shadow = 0.0;
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
vec2 sampleUV = clamp(uv + vec2(x, y) * texelSize, tileOrigin + texelSize, tileOrigin + tileSize - texelSize);
shadow += texture(localShadowMap, vec3(sampleUV, projCoords.z - 0.0002));
}
}
return shadow / 9.0;
}
float ShadowCalculation(vec4 fragPosLightSpace, vec3 N, vec3 L)
{
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
@@ -112,21 +207,63 @@ void main()
vec3 N = normalize(normal);
vec3 V = normalize(viewPos - FragPos);
vec3 L = normalize(-dirLight.direction);
vec3 H = normalize(V + L);
float NdotV = max(dot(N, V), 1e-4);
float NdotL = max(dot(N, L), 0.0);
vec3 F0 = mix(vec3(0.04), albedo, metal);
vec3 F = FresnelSchlick(max(dot(H, V), 0.0), F0);
float NDF = DistributionGGX(N, H, rough);
float G = GeometrySmith(N, V, L, rough);
vec3 specular = (NDF * G * F) / max(4.0 * NdotV * NdotL, 1e-4);
vec3 kD = (vec3(1.0) - F) * (1.0 - metal);
vec3 radiance = dirLight.color * dirLight.intensity;
vec3 direct = vec3(0.0);
float shadow = ShadowCalculation(fragposlightspace, N, L);
vec3 direct = (kD * albedo / PI + specular) * radiance * NdotL * shadow;
// Facing away from the sun has no direct light, so skip the BRDF and the 9 shadow samples
vec3 sunL = normalize(-dirLight.direction);
if (dot(N, sunL) > 0.0) {
float shadow = ShadowCalculation(fragposlightspace, N, sunL);
direct += ShadeLight(sunL, dirLight.color * dirLight.intensity, N, V, albedo, metal, rough, F0, NdotV) * shadow;
}
for (int i = 0; i < directionalLightCount; i++) {
vec3 L = normalize(-directionalLights[i].direction);
vec3 radiance = directionalLights[i].color * directionalLights[i].intensity;
direct += ShadeLight(L, radiance, N, V, albedo, metal, rough, F0, NdotV);
}
for (int i = 0; i < pointLightCount; i++) {
vec3 toLight = pointLights[i].position - FragPos;
float distanceSquared = dot(toLight, toLight);
float radius = pointLights[i].radius;
if (distanceSquared >= radius * radius)
continue;
vec3 radiance = pointLights[i].color * pointLights[i].intensity * PointAttenuation(distanceSquared, radius);
vec3 L = toLight / sqrt(distanceSquared);
if (dot(N, L) <= 0.0)
continue;
vec3 lit = ShadeLight(L, radiance, N, V, albedo, metal, rough, F0, NdotV);
if (pointLights[i].shadowTile >= 0)
lit *= LocalShadow(pointLights[i].shadowTile + PointShadowFace(-toLight), FragPos, N);
direct += lit;
}
for (int i = 0; i < spotLightCount; i++) {
vec3 toLight = spotLights[i].position - FragPos;
float distanceSquared = dot(toLight, toLight);
float radius = spotLights[i].radius;
if (distanceSquared >= radius * radius)
continue;
vec3 L = toLight / sqrt(distanceSquared);
float cone = smoothstep(spotLights[i].outerCos, spotLights[i].innerCos, dot(-L, spotLights[i].direction));
if (cone <= 0.0)
continue;
if (dot(N, L) <= 0.0)
continue;
vec3 radiance = spotLights[i].color * spotLights[i].intensity * PointAttenuation(distanceSquared, radius) * cone;
vec3 lit = ShadeLight(L, radiance, N, V, albedo, metal, rough, F0, NdotV);
if (spotLights[i].shadowTile >= 0)
lit *= LocalShadow(spotLights[i].shadowTile, FragPos, N);
direct += lit;
}
vec3 ambientF = FresnelSchlickRoughness(NdotV, F0, rough);
vec3 ambientKD = (vec3(1.0) - ambientF) * (1.0 - metal);
@@ -136,5 +273,5 @@ void main()
vec3 specularIBL = prefiltered * EnvironmentBRDF(F0, rough, NdotV);
vec3 ambient = (ambientKD * irradiance * albedo + specularIBL) * ao;
FragColor = vec4(direct + ambient, albedoSample.a);
FragColor = vec4(direct + ambient + albedo * emissive * 4.0, albedoSample.a);
}