feat: add spot light shadows with a tilled buffer

This commit is contained in:
2026-10-18 12:56:52 +02:00
parent 4bd99b25d1
commit 65a7310b56
19 changed files with 519 additions and 122 deletions
+70 -11
View File
@@ -70,6 +70,8 @@ void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
skinningPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
lightCullingPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
shadowLightDirection.reset();
submittedLights.clear();
spotShadows.clear();
Renderer2D::GetInstance().Clear();
}
@@ -126,15 +128,26 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Lights Buffer Flush");
vkutil::ScopedCmdLabel label(cmd, "Lights Buffer Flush", vkutil::labelColors::transfer);
uploadLights(sceneData.camera);
lightCullingPipeline->flushCurrentFrame(cmd, gfxDevice);
}
const glm::mat4 lightViewProj = shadowLightDirection
? shadowMapPipeline->calculateLightViewProj(
? shadowMapPipeline->calculateDirectionalViewProj(
*shadowLightDirection,
sceneData.camera.GetPosition())
: glm::mat4{1.0f};
std::array<GPUSpotShadow, ShadowMapPipeline::MAX_SPOT_SHADOWS> gpuSpotShadows{};
std::ranges::copy(spotShadows, gpuSpotShadows.begin());
std::vector<glm::mat4> spotShadowViewProjs;
spotShadowViewProjs.reserve(spotShadows.size());
for (const GPUSpotShadow& spotShadow : spotShadows)
{
spotShadowViewProjs.push_back(spotShadow.lightViewProj);
}
// TODO: there has to be a better way to do this
const auto gpuSceneData = GPUSceneData{
.view = sceneData.camera.GetViewMatrix(),
@@ -156,6 +169,9 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
.shadowNormalOffset = shadowMapPipeline->getNormalOffset(),
.padding = 0,
.lightViewProj = lightViewProj,
.spotShadowAtlasId = static_cast<std::uint32_t>(shadowMapPipeline->getSpotShadowAtlasId()),
.spotShadowTilesPerRow = ShadowMapPipeline::SPOT_SHADOW_TILES_PER_ROW,
.spotShadows = gpuSpotShadows,
};
{
@@ -212,12 +228,25 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Shadow Map");
vkutil::ScopedCmdLabel label(cmd, "Shadow Map", vkutil::labelColors::shadow);
shadowMapPipeline->draw(
shadowMapPipeline->drawDirectionalShadow(
cmd,
*resources,
sceneDataBuffer.getBuffer(),
meshDrawCommands,
sortedMeshDrawCommands);
sortedMeshDrawCommands,
lightViewProj);
}
if (!spotShadowViewProjs.empty())
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Spot Shadows");
vkutil::ScopedCmdLabel label(cmd, "Spot Shadows", vkutil::labelColors::shadow);
shadowMapPipeline->drawSpotShadows(
cmd,
*resources,
meshDrawCommands,
sortedMeshDrawCommands,
spotShadowViewProjs);
}
{
@@ -400,6 +429,8 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
lightCullingPipeline.reset();
shadowMapPipeline.reset();
shadowLightDirection.reset();
submittedLights.clear();
spotShadows.clear();
pendingMaterialUploads.clear();
meshDrawCommands.clear();
sortedMeshDrawCommands.clear();
@@ -444,18 +475,46 @@ void GameRenderer::drawSkinnedMesh(MeshID id,
void GameRenderer::submitLight(const Light& light)
{
Light submittedLight = light;
submittedLights.push_back(light);
}
// there's a single shadow map, the first shadow casting directional light gets it
submittedLight.castShadows =
light.castShadows && light.type == LightType::Directional && !shadowLightDirection;
void GameRenderer::uploadLights(const Camera& camera)
{
const Frustum cameraFrustum = edge::createFrustumFromCamera(camera);
if (submittedLight.castShadows)
for (const Light& light : submittedLights)
{
shadowLightDirection = light.direction;
lightCullingPipeline->appendLight(light, assignShadowIndex(light, cameraFrustum));
}
}
std::uint32_t GameRenderer::assignShadowIndex(const Light& light, const Frustum& cameraFrustum)
{
if (!light.castShadows)
{
return NO_SHADOW;
}
lightCullingPipeline->appendLight(submittedLight);
// there's a single directional shadow map, the first shadow casting directional light gets it
if (light.type == LightType::Directional && !shadowLightDirection)
{
shadowLightDirection = light.direction;
return 0;
}
if (light.type == LightType::Spot && spotShadows.size() < ShadowMapPipeline::MAX_SPOT_SHADOWS)
{
const GPUSpotShadow spotShadow = ShadowMapPipeline::calculateSpotShadow(light);
if (!edge::frustumsOverlap(cameraFrustum, spotShadow.lightViewProj))
{
return NO_SHADOW;
}
spotShadows.push_back(spotShadow);
return static_cast<std::uint32_t>(spotShadows.size() - 1);
}
return NO_SHADOW;
}
const GPUImage& GameRenderer::getDrawImage() const