feat: add spot light shadows with a tilled buffer
This commit is contained in:
@@ -7,6 +7,10 @@
|
||||
#define TYPE_POINT_LIGHT 1
|
||||
#define TYPE_SPOT_LIGHT 2
|
||||
|
||||
#define NO_SHADOW 0xFFFFFFFFu
|
||||
// Must match ShadowMapPipeline::MAX_SPOT_SHADOWS
|
||||
#define MAX_SPOT_SHADOWS 16
|
||||
|
||||
// Forward+ tiling, must match LightCullingPipeline::TILE_SIZE / MAX_LIGHTS_PER_TILE
|
||||
#define LIGHT_TILE_SIZE 16
|
||||
#define MAX_LIGHTS_PER_TILE 255
|
||||
@@ -27,10 +31,17 @@ struct Light {
|
||||
|
||||
float spotInnerCos;
|
||||
float spotOuterCos;
|
||||
uint castShadows;
|
||||
// NO_SHADOW, or the light's slot in the shadow data of its type
|
||||
uint shadowIndex;
|
||||
float padding;
|
||||
};
|
||||
|
||||
// Must match GPUSpotShadow in Light.h
|
||||
struct SpotShadow {
|
||||
mat4 lightViewProj;
|
||||
float normalOffsetPerMeter;
|
||||
};
|
||||
|
||||
layout (buffer_reference, std430) readonly buffer LightsBuffer {
|
||||
Light data[];
|
||||
};
|
||||
|
||||
@@ -34,6 +34,32 @@ vec3 calculateNormal(MaterialData material, uint samplerID)
|
||||
return normalize(mat3(t, b, n) * tangentNormal);
|
||||
}
|
||||
|
||||
float calculateShadow(Light light, vec3 fragPos, vec3 geometryNormal)
|
||||
{
|
||||
if (light.shadowIndex == NO_SHADOW) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
if (light.type == TYPE_DIRECTIONAL_LIGHT) {
|
||||
return calculateDirectionalShadow(
|
||||
fragPos, geometryNormal,
|
||||
pcs.sceneData.lightViewProj,
|
||||
pcs.sceneData.shadowMapId,
|
||||
pcs.sceneData.shadowNormalOffset);
|
||||
}
|
||||
|
||||
if (light.type == TYPE_SPOT_LIGHT) {
|
||||
return calculateSpotShadow(
|
||||
fragPos, geometryNormal, light.position,
|
||||
pcs.sceneData.spotShadows[light.shadowIndex],
|
||||
light.shadowIndex,
|
||||
pcs.sceneData.spotShadowAtlasId,
|
||||
pcs.sceneData.spotShadowTilesPerRow);
|
||||
}
|
||||
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
MaterialData material = pcs.sceneData.materials.data[pcs.materialID];
|
||||
@@ -73,19 +99,13 @@ void main()
|
||||
uint tileOffset = (tileID.y * pcs.sceneData.tileCountX + tileID.x) * LIGHT_TILE_STRIDE;
|
||||
uint tileLightCount = pcs.sceneData.tileLightIndices.data[tileOffset];
|
||||
|
||||
vec3 geometryNormal = normalize(inNormal);
|
||||
vec3 fragColor = vec3(0.0);
|
||||
for (uint i = 0; i < tileLightCount; i++) {
|
||||
uint lightIndex = pcs.sceneData.tileLightIndices.data[tileOffset + 1 + i];
|
||||
Light light = pcs.sceneData.lights.data[lightIndex];
|
||||
|
||||
float shadow = 1.0;
|
||||
if (light.castShadows != 0) {
|
||||
shadow = calculateDirectionalShadow(
|
||||
fragPos, normalize(inNormal),
|
||||
pcs.sceneData.lightViewProj,
|
||||
pcs.sceneData.shadowMapId,
|
||||
pcs.sceneData.shadowNormalOffset);
|
||||
}
|
||||
float shadow = calculateShadow(light, fragPos, geometryNormal);
|
||||
|
||||
fragColor += calculateLight(light, fragPos, n, v,
|
||||
diffuseColor, roughness, metallic, f0, shadow);
|
||||
|
||||
@@ -39,6 +39,11 @@ layout (buffer_reference, scalar) readonly buffer SceneDataBuffer {
|
||||
float shadowNormalOffset;
|
||||
uint padding;
|
||||
mat4 lightViewProj;
|
||||
|
||||
// spot shadows
|
||||
uint spotShadowAtlasId;
|
||||
uint spotShadowTilesPerRow;
|
||||
SpotShadow spotShadows[MAX_SPOT_SHADOWS];
|
||||
} sceneDataBuffer;
|
||||
|
||||
#endif // SCENE_DATA_GLSL
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
#version 460
|
||||
|
||||
#extension GL_GOOGLE_include_directive : require
|
||||
#extension GL_EXT_buffer_reference : require
|
||||
#extension GL_EXT_scalar_block_layout : require
|
||||
|
||||
#include "mesh_pcs.glsl"
|
||||
#include "vertex.glsl"
|
||||
|
||||
layout (push_constant, scalar) uniform constants
|
||||
{
|
||||
mat4 modelToLightClip;
|
||||
VertexBuffer vertexBuffer;
|
||||
} pcs;
|
||||
|
||||
void main()
|
||||
{
|
||||
Vertex v = pcs.vertexBuffer.vertices[gl_VertexIndex];
|
||||
|
||||
gl_Position = pcs.sceneData.lightViewProj * pcs.transform * vec4(v.position, 1.0f);
|
||||
gl_Position = pcs.modelToLightClip * vec4(v.position, 1.0f);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,30 @@
|
||||
#ifndef SHADOWS_GLSL
|
||||
#define SHADOWS_GLSL
|
||||
|
||||
// expects bindless.glsl to be included before this file (it has no include guard)
|
||||
// expects bindless.glsl and lights.glsl to be included before this file
|
||||
|
||||
// Returns how much of a 3x3 area around uv is lit, every tap stays inside [minUV, maxUV]
|
||||
float sampleShadowPCF(uint shadowMapID, vec2 uv, float depth, vec2 minUV, vec2 maxUV)
|
||||
{
|
||||
vec2 texelSize = 1.0 / vec2(textureSize(sampler2DShadow(textures[shadowMapID], samplers[SHADOW_SAMPLER_ID]), 0));
|
||||
|
||||
float shadowed = 0.0;
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
vec2 sampleUV = clamp(uv + vec2(x, y) * texelSize, minUV, maxUV);
|
||||
shadowed += sampleTexture2DShadow(shadowMapID, vec3(sampleUV, depth));
|
||||
}
|
||||
}
|
||||
|
||||
// the shadow sampler compares with GREATER_OR_EQUAL (it was set up for reversed Z),
|
||||
// with regular depth that returns 1 when the fragment is behind the occluder
|
||||
return 1.0 - shadowed / 9.0;
|
||||
}
|
||||
|
||||
bool isOutsideShadowMap(vec2 uv, float depth)
|
||||
{
|
||||
return any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))) || depth > 1.0;
|
||||
}
|
||||
|
||||
// Returns 1.0 when fully lit, 0.0 when fully in shadow
|
||||
float calculateDirectionalShadow(vec3 fragPos, vec3 n, mat4 lightViewProj, uint shadowMapID, float normalOffset)
|
||||
@@ -11,24 +34,37 @@ float calculateDirectionalShadow(vec3 fragPos, vec3 n, mat4 lightViewProj, uint
|
||||
vec3 lightNDC = lightClip.xyz / lightClip.w;
|
||||
vec2 uv = lightNDC.xy * 0.5 + 0.5;
|
||||
|
||||
// outside the shadow map counts as lit
|
||||
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))) || lightNDC.z > 1.0) {
|
||||
if (isOutsideShadowMap(uv, lightNDC.z)) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
vec2 texelSize = 1.0 / vec2(textureSize(sampler2DShadow(textures[shadowMapID], samplers[SHADOW_SAMPLER_ID]), 0));
|
||||
return sampleShadowPCF(shadowMapID, uv, lightNDC.z, vec2(0.0), vec2(1.0));
|
||||
}
|
||||
|
||||
// 3x3 PCF, every tap is already bilinearly filtered by the compare sampler
|
||||
float shadowed = 0.0;
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
shadowed += sampleTexture2DShadow(shadowMapID, vec3(uv + vec2(x, y) * texelSize, lightNDC.z));
|
||||
}
|
||||
float calculateSpotShadow(vec3 fragPos, vec3 n, vec3 lightPos, SpotShadow spotShadow,
|
||||
uint shadowIndex, uint atlasID, uint tilesPerRow)
|
||||
{
|
||||
float normalOffset = distance(fragPos, lightPos) * spotShadow.normalOffsetPerMeter;
|
||||
vec4 lightClip = spotShadow.lightViewProj * vec4(fragPos + n * normalOffset, 1.0);
|
||||
if (lightClip.w <= 0.0) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// the shadow sampler compares with GREATER_OR_EQUAL (it was set up for reversed Z),
|
||||
// with regular depth that returns 1 when the fragment is behind the occluder
|
||||
return 1.0 - shadowed / 9.0;
|
||||
vec3 lightNDC = lightClip.xyz / lightClip.w;
|
||||
vec2 tileUV = lightNDC.xy * 0.5 + 0.5;
|
||||
if (isOutsideShadowMap(tileUV, lightNDC.z)) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
vec2 tile = vec2(shadowIndex % tilesPerRow, shadowIndex / tilesPerRow);
|
||||
float tileScale = 1.0 / float(tilesPerRow);
|
||||
vec2 atlasTexelSize = 1.0 / vec2(textureSize(sampler2DShadow(textures[atlasID], samplers[SHADOW_SAMPLER_ID]), 0));
|
||||
|
||||
vec2 atlasUV = (tile + tileUV) * tileScale;
|
||||
vec2 tileMinUV = tile * tileScale + atlasTexelSize;
|
||||
vec2 tileMaxUV = (tile + 1.0) * tileScale - atlasTexelSize;
|
||||
|
||||
return sampleShadowPCF(atlasID, atlasUV, lightNDC.z, tileMinUV, tileMaxUV);
|
||||
}
|
||||
|
||||
#endif // SHADOWS_GLSL
|
||||
|
||||
Reference in New Issue
Block a user