feat: add spot light shadows with a tilled buffer
This commit is contained in:
@@ -118,6 +118,10 @@ void LightComponent::ImGuiInspector() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_Type == LightType::Spot) {
|
||||
ImGui::Checkbox("Cast Shadows", &m_CastShadows);
|
||||
}
|
||||
|
||||
float range = m_Range;
|
||||
if (ImGui::DragFloat("Range", &range, 0.05f, 0.01f)) {
|
||||
SetRange(range);
|
||||
|
||||
@@ -55,6 +55,74 @@ Frustum edge::createFrustumFromCamera(const Camera& camera) {
|
||||
return frustum;
|
||||
}
|
||||
|
||||
namespace {
|
||||
glm::vec4 matrixRow(const glm::mat4& matrix, int row)
|
||||
{
|
||||
return {matrix[0][row], matrix[1][row], matrix[2][row], matrix[3][row]};
|
||||
}
|
||||
|
||||
Frustum::Plane planeFromCoefficients(const glm::vec4& coefficients)
|
||||
{
|
||||
const glm::vec3 normal{coefficients};
|
||||
const float length = glm::length(normal);
|
||||
|
||||
Frustum::Plane plane;
|
||||
plane.normal = normal / length;
|
||||
plane.distance = -coefficients.w / length;
|
||||
return plane;
|
||||
}
|
||||
}
|
||||
|
||||
Frustum edge::createFrustumFromViewProj(const glm::mat4& viewProj)
|
||||
{
|
||||
const glm::vec4 rowX = matrixRow(viewProj, 0);
|
||||
const glm::vec4 rowY = matrixRow(viewProj, 1);
|
||||
const glm::vec4 rowZ = matrixRow(viewProj, 2);
|
||||
const glm::vec4 rowW = matrixRow(viewProj, 3);
|
||||
|
||||
Frustum frustum;
|
||||
frustum.leftFace = planeFromCoefficients(rowW + rowX);
|
||||
frustum.rightFace = planeFromCoefficients(rowW - rowX);
|
||||
frustum.bottomFace = planeFromCoefficients(rowW + rowY);
|
||||
frustum.topFace = planeFromCoefficients(rowW - rowY);
|
||||
frustum.nearFace = planeFromCoefficients(rowZ);
|
||||
frustum.farFace = planeFromCoefficients(rowW - rowZ);
|
||||
return frustum;
|
||||
}
|
||||
|
||||
std::array<glm::vec3, 8> edge::calculateFrustumCorners(const glm::mat4& viewProj)
|
||||
{
|
||||
const glm::mat4 clipToWorld = glm::inverse(viewProj);
|
||||
|
||||
std::array<glm::vec3, 8> corners{};
|
||||
std::size_t cornerIndex = 0;
|
||||
for (const float x : {-1.0f, 1.0f}) {
|
||||
for (const float y : {-1.0f, 1.0f}) {
|
||||
for (const float z : {0.0f, 1.0f}) {
|
||||
const glm::vec4 corner = clipToWorld * glm::vec4{x, y, z, 1.0f};
|
||||
corners[cornerIndex++] = glm::vec3{corner} / corner.w;
|
||||
}
|
||||
}
|
||||
}
|
||||
return corners;
|
||||
}
|
||||
|
||||
bool edge::frustumsOverlap(const Frustum& frustum, const glm::mat4& otherViewProj)
|
||||
{
|
||||
const auto otherCorners = calculateFrustumCorners(otherViewProj);
|
||||
|
||||
for (int planeIndex = 0; planeIndex < 6; ++planeIndex) {
|
||||
const Frustum::Plane& plane = frustum.getPlane(planeIndex);
|
||||
const bool allCornersOutside = std::ranges::all_of(otherCorners, [&plane](const glm::vec3& corner) {
|
||||
return plane.getSignedDistanceToPlane(corner) < 0.0f;
|
||||
});
|
||||
if (allCornersOutside) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isOnOrForwardPlane(const Frustum::Plane& plane, const Sphere& sphere)
|
||||
{
|
||||
return plane.getSignedDistanceToPlane(sphere.center) > -sphere.radius;
|
||||
|
||||
@@ -131,7 +131,7 @@ void LightCullingPipeline::beginDrawing(std::size_t frameIndex)
|
||||
getCurrentFrameData().lightsBuffer.clear();
|
||||
}
|
||||
|
||||
void LightCullingPipeline::appendLight(const Light& light)
|
||||
void LightCullingPipeline::appendLight(const Light& light, std::uint32_t shadowIndex)
|
||||
{
|
||||
auto& lightsBuffer = getCurrentFrameData().lightsBuffer;
|
||||
if (lightsBuffer.size >= lightsBuffer.capacity)
|
||||
@@ -158,7 +158,7 @@ void LightCullingPipeline::appendLight(const Light& light)
|
||||
.type = static_cast<std::uint32_t>(light.type),
|
||||
.spotInnerCos = std::cos(light.innerConeAngle),
|
||||
.spotOuterCos = std::cos(light.outerConeAngle),
|
||||
.castShadows = light.castShadows ? 1u : 0u,
|
||||
.shadowIndex = shadowIndex,
|
||||
.padding = 0.0f,
|
||||
};
|
||||
lightsBuffer.append(gpuLight);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <destrum/Graphics/Pipelines/ShadowMapPipeline.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
@@ -11,11 +12,43 @@
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/RenderResources.h>
|
||||
#include <destrum/Graphics/Caches/MeshCache.h>
|
||||
#include <destrum/Graphics/Frustum.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include "volk.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr float MAX_SPOT_SHADOW_FIELD_OF_VIEW = glm::radians(170.0f);
|
||||
constexpr float SPOT_SHADOW_NEAR_PLANE = 0.05f;
|
||||
constexpr float NORMAL_OFFSET_IN_TEXELS = 1.5f;
|
||||
|
||||
glm::vec3 upVectorFor(const glm::vec3& direction)
|
||||
{
|
||||
return std::abs(direction.y) > 0.99f ? glm::vec3{0.0f, 0.0f, 1.0f} : glm::vec3{0.0f, 1.0f, 0.0f};
|
||||
}
|
||||
|
||||
float spotShadowFieldOfView(const Light& spotLight)
|
||||
{
|
||||
return std::min(spotLight.outerConeAngle * 2.0f, MAX_SPOT_SHADOW_FIELD_OF_VIEW);
|
||||
}
|
||||
|
||||
VkRect2D spotShadowTileRegion(std::uint32_t shadowIndex)
|
||||
{
|
||||
const std::uint32_t column = shadowIndex % ShadowMapPipeline::SPOT_SHADOW_TILES_PER_ROW;
|
||||
const std::uint32_t row = shadowIndex / ShadowMapPipeline::SPOT_SHADOW_TILES_PER_ROW;
|
||||
|
||||
return VkRect2D{
|
||||
.offset = {
|
||||
static_cast<std::int32_t>(column * ShadowMapPipeline::SPOT_SHADOW_TILE_SIZE),
|
||||
static_cast<std::int32_t>(row * ShadowMapPipeline::SPOT_SHADOW_TILE_SIZE),
|
||||
},
|
||||
.extent = {ShadowMapPipeline::SPOT_SHADOW_TILE_SIZE, ShadowMapPipeline::SPOT_SHADOW_TILE_SIZE},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void ShadowMapPipeline::init(GfxDevice& gfxDevice, RenderResources& resources)
|
||||
{
|
||||
try
|
||||
@@ -77,14 +110,32 @@ void ShadowMapPipeline::init(GfxDevice& gfxDevice, RenderResources& resources)
|
||||
pipelineConfig
|
||||
);
|
||||
|
||||
const auto createInfo = vkutil::CreateImageInfo{
|
||||
.format = shadowMapFormat,
|
||||
.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
.extent = VkExtent3D{SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 1},
|
||||
};
|
||||
const auto depthUsage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
|
||||
shadowMapId = resources.createImage(gfxDevice, createInfo, "shadow map", nullptr, shadowMapId);
|
||||
shadowMapId = resources.createImage(
|
||||
gfxDevice,
|
||||
{
|
||||
.format = shadowMapFormat,
|
||||
.usage = depthUsage,
|
||||
.extent = VkExtent3D{SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 1},
|
||||
},
|
||||
"shadow map",
|
||||
nullptr,
|
||||
shadowMapId);
|
||||
spdlog::info("Created {0}x{0} shadow map with id {1}", SHADOW_MAP_SIZE, shadowMapId);
|
||||
|
||||
constexpr std::uint32_t spotShadowAtlasSize = SPOT_SHADOW_TILE_SIZE * SPOT_SHADOW_TILES_PER_ROW;
|
||||
spotShadowAtlasId = resources.createImage(
|
||||
gfxDevice,
|
||||
{
|
||||
.format = shadowMapFormat,
|
||||
.usage = depthUsage,
|
||||
.extent = VkExtent3D{spotShadowAtlasSize, spotShadowAtlasSize, 1},
|
||||
},
|
||||
"spot shadow atlas",
|
||||
nullptr,
|
||||
spotShadowAtlasId);
|
||||
spdlog::info("Created {0}x{0} spot shadow atlas with id {1}", spotShadowAtlasSize, spotShadowAtlasId);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -104,14 +155,13 @@ void ShadowMapPipeline::cleanup(VkDevice device)
|
||||
m_pipelineLayout = VK_NULL_HANDLE;
|
||||
|
||||
shadowMapId = NULL_IMAGE_ID;
|
||||
spotShadowAtlasId = NULL_IMAGE_ID;
|
||||
}
|
||||
|
||||
glm::mat4 ShadowMapPipeline::calculateLightViewProj(const glm::vec3& lightDirection, const glm::vec3& center) const
|
||||
glm::mat4 ShadowMapPipeline::calculateDirectionalViewProj(const glm::vec3& lightDirection, const glm::vec3& center) const
|
||||
{
|
||||
const glm::vec3 direction = glm::normalize(lightDirection);
|
||||
const glm::vec3 up = std::abs(direction.y) > 0.99f ? glm::vec3{0.0f, 0.0f, 1.0f} : glm::vec3{0.0f, 1.0f, 0.0f};
|
||||
|
||||
const glm::mat4 lightView = glm::lookAt(glm::vec3{0.0f}, direction, up);
|
||||
const glm::mat4 lightView = glm::lookAt(glm::vec3{0.0f}, direction, upVectorFor(direction));
|
||||
|
||||
const float texelSize = (2.0f * shadowDistance) / static_cast<float>(SHADOW_MAP_SIZE);
|
||||
glm::vec3 lightSpaceCenter = glm::vec3(lightView * glm::vec4(center, 1.0f));
|
||||
@@ -129,18 +179,36 @@ glm::mat4 ShadowMapPipeline::calculateLightViewProj(const glm::vec3& lightDirect
|
||||
return lightProj * lightView;
|
||||
}
|
||||
|
||||
GPUSpotShadow ShadowMapPipeline::calculateSpotShadow(const Light& spotLight)
|
||||
{
|
||||
const glm::vec3 direction = glm::normalize(spotLight.direction);
|
||||
const float fieldOfView = spotShadowFieldOfView(spotLight);
|
||||
const float farPlane = std::max(spotLight.range, SPOT_SHADOW_NEAR_PLANE * 2.0f);
|
||||
|
||||
const glm::mat4 lightView = glm::lookAt(spotLight.position, spotLight.position + direction, upVectorFor(direction));
|
||||
const glm::mat4 lightProj = glm::perspectiveRH_ZO(fieldOfView, 1.0f, SPOT_SHADOW_NEAR_PLANE, farPlane);
|
||||
|
||||
const float texelSizeAtOneMeter =
|
||||
2.0f * std::tan(fieldOfView * 0.5f) / static_cast<float>(SPOT_SHADOW_TILE_SIZE);
|
||||
|
||||
return GPUSpotShadow{
|
||||
.lightViewProj = lightProj * lightView,
|
||||
.normalOffsetPerMeter = texelSizeAtOneMeter * NORMAL_OFFSET_IN_TEXELS,
|
||||
};
|
||||
}
|
||||
|
||||
float ShadowMapPipeline::getNormalOffset() const
|
||||
{
|
||||
const float texelSize = (2.0f * shadowDistance) / static_cast<float>(SHADOW_MAP_SIZE);
|
||||
return texelSize * 1.5f;
|
||||
return texelSize * NORMAL_OFFSET_IN_TEXELS;
|
||||
}
|
||||
|
||||
void ShadowMapPipeline::draw(
|
||||
void ShadowMapPipeline::drawDirectionalShadow(
|
||||
VkCommandBuffer cmd,
|
||||
const RenderResources& resources,
|
||||
const GPUBuffer& sceneDataBuffer,
|
||||
const std::vector<MeshDrawCommand>& drawCommands,
|
||||
const std::vector<std::size_t>& sortedDrawCommands)
|
||||
const std::vector<std::size_t>& sortedDrawCommands,
|
||||
const glm::mat4& lightViewProj)
|
||||
{
|
||||
if (!m_pipeline || shadowMapId == NULL_IMAGE_ID)
|
||||
{
|
||||
@@ -149,16 +217,55 @@ void ShadowMapPipeline::draw(
|
||||
|
||||
const auto& shadowMap = resources.getImage(shadowMapId);
|
||||
|
||||
beginDepthRendering(cmd, resources, shadowMap);
|
||||
setRenderRegion(cmd, VkRect2D{.offset = {}, .extent = shadowMap.getExtent2D()});
|
||||
drawShadowCasters(cmd, resources, drawCommands, sortedDrawCommands, lightViewProj);
|
||||
endDepthRendering(cmd, shadowMap);
|
||||
}
|
||||
|
||||
void ShadowMapPipeline::drawSpotShadows(
|
||||
VkCommandBuffer cmd,
|
||||
const RenderResources& resources,
|
||||
const std::vector<MeshDrawCommand>& drawCommands,
|
||||
const std::vector<std::size_t>& sortedDrawCommands,
|
||||
const std::vector<glm::mat4>& lightViewProjs)
|
||||
{
|
||||
if (!m_pipeline || spotShadowAtlasId == NULL_IMAGE_ID || lightViewProjs.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
assert(lightViewProjs.size() <= MAX_SPOT_SHADOWS);
|
||||
const auto& spotShadowAtlas = resources.getImage(spotShadowAtlasId);
|
||||
|
||||
beginDepthRendering(cmd, resources, spotShadowAtlas);
|
||||
for (std::uint32_t shadowIndex = 0; shadowIndex < lightViewProjs.size(); ++shadowIndex)
|
||||
{
|
||||
vkutil::cmdBeginLabelFormatted(cmd, vkutil::labelColors::shadow, "Spot shadow %u", shadowIndex);
|
||||
|
||||
setRenderRegion(cmd, spotShadowTileRegion(shadowIndex));
|
||||
drawShadowCasters(cmd, resources, drawCommands, sortedDrawCommands, lightViewProjs[shadowIndex]);
|
||||
|
||||
vkutil::cmdEndLabel(cmd);
|
||||
}
|
||||
endDepthRendering(cmd, spotShadowAtlas);
|
||||
}
|
||||
|
||||
void ShadowMapPipeline::beginDepthRendering(
|
||||
VkCommandBuffer cmd,
|
||||
const RenderResources& resources,
|
||||
const GPUImage& depthImage)
|
||||
{
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
shadowMap.image,
|
||||
shadowMap.layout,
|
||||
depthImage.image,
|
||||
depthImage.layout,
|
||||
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||
shadowMap.setLayout(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||
depthImage.setLayout(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||
|
||||
auto renderInfo = vkutil::createRenderingInfo({
|
||||
.renderExtent = shadowMap.getExtent2D(),
|
||||
.depthImageView = shadowMap.imageView,
|
||||
.renderExtent = depthImage.getExtent2D(),
|
||||
.depthImageView = depthImage.imageView,
|
||||
.depthImageClearValue = 1.f,
|
||||
});
|
||||
renderInfo.renderingInfo.pDepthAttachment = &renderInfo.depthAttachment;
|
||||
@@ -167,31 +274,49 @@ void ShadowMapPipeline::draw(
|
||||
|
||||
m_pipeline->bind(cmd);
|
||||
resources.bindBindlessDescSet(cmd, m_pipelineLayout);
|
||||
vkCmdSetPolygonModeEXT(cmd, VK_POLYGON_MODE_FILL);
|
||||
}
|
||||
|
||||
void ShadowMapPipeline::endDepthRendering(VkCommandBuffer cmd, const GPUImage& depthImage)
|
||||
{
|
||||
vkCmdEndRendering(cmd);
|
||||
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
depthImage.image,
|
||||
depthImage.layout,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
depthImage.setLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
}
|
||||
|
||||
void ShadowMapPipeline::setRenderRegion(VkCommandBuffer cmd, const VkRect2D& region)
|
||||
{
|
||||
const auto viewport = VkViewport{
|
||||
.x = 0.f,
|
||||
.y = 0.f,
|
||||
.width = static_cast<float>(SHADOW_MAP_SIZE),
|
||||
.height = static_cast<float>(SHADOW_MAP_SIZE),
|
||||
.x = static_cast<float>(region.offset.x),
|
||||
.y = static_cast<float>(region.offset.y),
|
||||
.width = static_cast<float>(region.extent.width),
|
||||
.height = static_cast<float>(region.extent.height),
|
||||
.minDepth = 0.f,
|
||||
.maxDepth = 1.f,
|
||||
};
|
||||
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
||||
vkCmdSetScissor(cmd, 0, 1, ®ion);
|
||||
}
|
||||
|
||||
const auto scissor = VkRect2D{
|
||||
.offset = {},
|
||||
.extent = shadowMap.getExtent2D(),
|
||||
};
|
||||
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||
|
||||
vkCmdSetPolygonModeEXT(cmd, VK_POLYGON_MODE_FILL);
|
||||
|
||||
void ShadowMapPipeline::drawShadowCasters(
|
||||
VkCommandBuffer cmd,
|
||||
const RenderResources& resources,
|
||||
const std::vector<MeshDrawCommand>& drawCommands,
|
||||
const std::vector<std::size_t>& sortedDrawCommands,
|
||||
const glm::mat4& lightViewProj)
|
||||
{
|
||||
const MeshCache& meshCache = resources.meshes();
|
||||
MeshID prevMeshId = NULL_MESH_ID;
|
||||
const Frustum lightFrustum = edge::createFrustumFromViewProj(lightViewProj);
|
||||
MeshID previousMeshId = NULL_MESH_ID;
|
||||
|
||||
const auto drawOne = [&](const MeshDrawCommand& dc)
|
||||
const auto drawCaster = [&](const MeshDrawCommand& dc)
|
||||
{
|
||||
if (!dc.castShadow)
|
||||
if (!dc.castShadow || !edge::isInFrustum(lightFrustum, dc.worldBoundingSphere))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -208,21 +333,18 @@ void ShadowMapPipeline::draw(
|
||||
dc.materialId,
|
||||
dc.skinnedMesh != nullptr ? ", skinned" : "");
|
||||
|
||||
if (dc.meshId != prevMeshId)
|
||||
if (dc.meshId != previousMeshId)
|
||||
{
|
||||
prevMeshId = dc.meshId;
|
||||
previousMeshId = dc.meshId;
|
||||
vkCmdBindIndexBuffer(cmd, mesh.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
|
||||
}
|
||||
|
||||
const auto pushConstants = PushConstants{
|
||||
.transform = dc.transformMatrix,
|
||||
.sceneDataBuffer = sceneDataBuffer.address,
|
||||
.modelToLightClip = lightViewProj * dc.transformMatrix,
|
||||
.vertexBuffer =
|
||||
dc.skinnedMesh != nullptr
|
||||
? dc.skinnedMesh->skinnedVertexBuffer.address
|
||||
: mesh.vertexBuffer.address,
|
||||
.materialId = dc.materialId,
|
||||
.padding = 0,
|
||||
};
|
||||
|
||||
vkCmdPushConstants(
|
||||
@@ -243,23 +365,14 @@ void ShadowMapPipeline::draw(
|
||||
for (std::size_t drawIndex : sortedDrawCommands)
|
||||
{
|
||||
assert(drawIndex < drawCommands.size());
|
||||
drawOne(drawCommands[drawIndex]);
|
||||
drawCaster(drawCommands[drawIndex]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const MeshDrawCommand& dc : drawCommands)
|
||||
{
|
||||
drawOne(dc);
|
||||
drawCaster(dc);
|
||||
}
|
||||
}
|
||||
|
||||
vkCmdEndRendering(cmd);
|
||||
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
shadowMap.image,
|
||||
shadowMap.layout,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
shadowMap.setLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user