234 lines
8.0 KiB
GLSL
234 lines
8.0 KiB
GLSL
#version 330 core
|
|
out vec4 FragColor;
|
|
|
|
in vec3 normal;
|
|
in vec2 texCoord;
|
|
in vec3 FragPos;
|
|
in vec4 fragposlightspace;
|
|
|
|
struct DirLight {
|
|
vec3 direction;
|
|
vec3 color;
|
|
float intensity;
|
|
};
|
|
|
|
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;
|
|
uniform sampler2D tex;
|
|
uniform samplerCube environmentMap;
|
|
uniform vec4 colorOverride = vec4(0.0);
|
|
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;
|
|
|
|
// Plain Blinn-Phong: ambient, Lambert diffuse and a half vector highlight.
|
|
// Roughness sets how tight and strong the highlight is, metallic tints it and takes away the diffuse.
|
|
|
|
// Close enough to the 2.2 curve and much cheaper than pow
|
|
vec3 ToLinear(vec3 c)
|
|
{
|
|
return c * c;
|
|
}
|
|
|
|
// Light from one direction, L points from the surface toward the light.
|
|
// diffuse and specular are already scaled for this pixel, see main.
|
|
vec3 ShadeLight(vec3 L, vec3 radiance, vec3 N, vec3 V, vec3 diffuse, vec3 specular, float power)
|
|
{
|
|
float NdotL = max(dot(N, L), 0.0);
|
|
vec3 H = normalize(V + L);
|
|
float highlight = pow(max(dot(N, H), 0.0), power);
|
|
return (diffuse + specular * highlight) * 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;
|
|
projCoords = projCoords * 0.5 + 0.5;
|
|
|
|
if (projCoords.z > 1.0 || any(lessThan(projCoords, vec3(0.0))) ||
|
|
any(greaterThan(projCoords, vec3(1.0))))
|
|
return 1.0;
|
|
|
|
float bias = max(0.004 * (1.0 - dot(N, L)), 0.001);
|
|
vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
|
|
float shadow = 0.0;
|
|
|
|
for (int x = -1; x <= 1; x++) {
|
|
for (int y = -1; y <= 1; y++) {
|
|
vec2 offset = vec2(x, y) * texelSize;
|
|
shadow += texture(shadowMap, vec3(projCoords.xy + offset, projCoords.z - bias));
|
|
}
|
|
}
|
|
return shadow / 9.0;
|
|
}
|
|
|
|
void main()
|
|
{
|
|
vec4 albedoSample = vec4(0.8, 0.8, 0.8, 1.0);
|
|
if (useTexture != 0) {
|
|
albedoSample = texture(tex, texCoord);
|
|
if (albedoSample.a < 0.1)
|
|
discard;
|
|
albedoSample.rgb = ToLinear(albedoSample.rgb);
|
|
}
|
|
|
|
albedoSample.rgb = mix(albedoSample.rgb, colorOverride.rgb, colorOverride.w);
|
|
|
|
vec3 albedo = albedoSample.rgb;
|
|
float metal = clamp(metallic, 0.0, 1.0);
|
|
float rough = clamp(roughness, 0.04, 1.0);
|
|
|
|
vec3 N = normalize(normal);
|
|
vec3 V = normalize(viewPos - FragPos);
|
|
|
|
float smoothness = 1.0 - clamp(rough, 0.0, 1.0);
|
|
float power = exp2(10.0 * smoothness + 1.0); // 2 when fully rough, 2048 when mirror smooth
|
|
vec3 specularColor = mix(vec3(0.5 * smoothness * smoothness), albedo * (0.25 + 0.75 * smoothness), metal);
|
|
vec3 diffuseBRDF = albedo * (1.0 - metal) / PI; // the light intensities were tuned with the 1/PI in
|
|
vec3 specularBRDF = specularColor / PI;
|
|
vec3 direct = vec3(0.0);
|
|
|
|
// 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, diffuseBRDF, specularBRDF, power) * 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, diffuseBRDF, specularBRDF, power);
|
|
}
|
|
|
|
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, diffuseBRDF, specularBRDF, power);
|
|
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, diffuseBRDF, specularBRDF, power);
|
|
if (spotLights[i].shadowTile >= 0)
|
|
lit *= LocalShadow(spotLights[i].shadowTile, FragPos, N);
|
|
direct += lit;
|
|
}
|
|
|
|
// Ambient is the sky's blurred colour in the direction the surface faces
|
|
vec3 ambient = ToLinear(textureLod(environmentMap, N, 7.0).rgb) * envIntensity * albedo * ao;
|
|
|
|
FragColor = vec4(direct + ambient + albedo * emissive * 4.0, albedoSample.a);
|
|
}
|