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
@@ -1,6 +1,8 @@
#ifndef FRUSTUM_H
#define FRUSTUM_H
#include <array>
#include <glm/glm.hpp>
#include "Camera.h"
@@ -71,7 +73,12 @@ namespace edge
std::array<glm::vec3, 8> calculateFrustumCornersWorldSpace(const Camera& camera);
Frustum createFrustumFromCamera(const Camera& camera);
Frustum createFrustumFromViewProj(const glm::mat4& viewProj);
std::array<glm::vec3, 8> calculateFrustumCorners(const glm::mat4& viewProj);
bool isInFrustum(const Frustum& frustum, const Sphere& s);
// Conservative: frustums that only come close can still count as overlapping
bool frustumsOverlap(const Frustum& frustum, const glm::mat4& otherViewProj);
bool isInFrustum(const Frustum& frustum, const AABB& aabb);
Sphere calculateBoundingSphereWorld(const glm::mat4& transform, const Sphere& s, bool hasSkeleton);
}
+10 -2
View File
@@ -3,9 +3,12 @@
#include <cstdint>
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
inline constexpr std::uint32_t NO_SHADOW = 0xFFFFFFFF;
enum class LightType : std::uint32_t {
Directional = 0,
Point = 1,
@@ -26,7 +29,7 @@ struct Light {
float innerConeAngle{0.35f};
float outerConeAngle{0.5f};
// directional lights only, the first one submitted each frame gets the shadow map
// directional and spot lights, point lights don't have shadows yet
bool castShadows{false};
};
@@ -42,10 +45,15 @@ struct GPULight {
float spotInnerCos;
float spotOuterCos;
std::uint32_t castShadows;
std::uint32_t shadowIndex;
float padding;
};
struct GPUSpotShadow {
glm::mat4 lightViewProj;
float normalOffsetPerMeter;
};
static_assert(sizeof(GPULight) == 64, "GPULight must match the struct in lights.glsl");
#endif //LIGHT_H
@@ -30,7 +30,7 @@ public:
void resize(GfxDevice& gfxDevice, glm::ivec2 drawImageSize);
void beginDrawing(std::size_t frameIndex);
void appendLight(const Light& light);
void appendLight(const Light& light, std::uint32_t shadowIndex);
void flushCurrentFrame(VkCommandBuffer cmd, GfxDevice& gfxDevice);
@@ -8,33 +8,46 @@
#include <glm/glm.hpp>
#include <vulkan/vulkan.h>
#include <destrum/Graphics/Light.h>
#include <destrum/Graphics/Pipeline.h>
#include <destrum/Graphics/ids.h>
#include <destrum/Graphics/MeshDrawCommand.h>
#include <destrum/Graphics/Resources/Buffer.h>
class GfxDevice;
struct GPUImage;
class RenderResources;
// Renders the depth of the scene as seen from a directional light, centered around the camera
// Renders scene depth from the lights: one map for the directional light, one atlas tile per spot light
class ShadowMapPipeline final
{
public:
static constexpr std::uint32_t SHADOW_MAP_SIZE = 2048;
static constexpr std::uint32_t SPOT_SHADOW_TILE_SIZE = 1024;
static constexpr std::uint32_t SPOT_SHADOW_TILES_PER_ROW = 4;
static constexpr std::uint32_t MAX_SPOT_SHADOWS = SPOT_SHADOW_TILES_PER_ROW * SPOT_SHADOW_TILES_PER_ROW;
void init(GfxDevice& gfxDevice, RenderResources& resources);
void cleanup(VkDevice device);
void draw(
void 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);
[[nodiscard]] glm::mat4 calculateLightViewProj(const glm::vec3& lightDirection, const glm::vec3& center) const;
void drawSpotShadows(
VkCommandBuffer cmd,
const RenderResources& resources,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands,
const std::vector<glm::mat4>& lightViewProjs);
[[nodiscard]] glm::mat4 calculateDirectionalViewProj(const glm::vec3& lightDirection, const glm::vec3& center) const;
[[nodiscard]] static GPUSpotShadow calculateSpotShadow(const Light& spotLight);
[[nodiscard]] ImageID getShadowMapId() const { return shadowMapId; }
[[nodiscard]] ImageID getSpotShadowAtlasId() const { return spotShadowAtlasId; }
[[nodiscard]] float getNormalOffset() const;
// half the width of the area around the camera that receives shadows
@@ -44,18 +57,26 @@ public:
private:
struct PushConstants
{
glm::mat4 transform;
VkDeviceAddress sceneDataBuffer;
glm::mat4 modelToLightClip;
VkDeviceAddress vertexBuffer;
std::uint32_t materialId;
std::uint32_t padding;
};
void beginDepthRendering(VkCommandBuffer cmd, const RenderResources& resources, const GPUImage& depthImage);
void endDepthRendering(VkCommandBuffer cmd, const GPUImage& depthImage);
void setRenderRegion(VkCommandBuffer cmd, const VkRect2D& region);
void drawShadowCasters(
VkCommandBuffer cmd,
const RenderResources& resources,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands,
const glm::mat4& lightViewProj);
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
std::unique_ptr<Pipeline> m_pipeline;
VkFormat shadowMapFormat{VK_FORMAT_D32_SFLOAT};
ImageID shadowMapId{NULL_IMAGE_ID};
ImageID spotShadowAtlasId{NULL_IMAGE_ID};
float shadowDistance{25.0f};
// how far in front of / behind the camera (along the light) casters are still captured
@@ -17,8 +17,10 @@
#include <destrum/Graphics/RenderResources.h>
#include <array>
#include <memory>
#include <optional>
#include <vector>
#include <destrum/Graphics/Pipelines/SkinningPipeline.h>
@@ -112,6 +114,8 @@ public:
private:
void createDrawImage(GfxDevice& gfxDevice, const glm::ivec2& drawImageSize, bool firstCreate);
void uploadLights(const Camera& camera);
[[nodiscard]] std::uint32_t assignShadowIndex(const Light& light, const Frustum& cameraFrustum);
RenderResources* resources = nullptr;
@@ -156,6 +160,10 @@ private:
float shadowNormalOffset;
std::uint32_t padding;
glm::mat4 lightViewProj;
std::uint32_t spotShadowAtlasId;
std::uint32_t spotShadowTilesPerRow;
std::array<GPUSpotShadow, ShadowMapPipeline::MAX_SPOT_SHADOWS> spotShadows;
};
NBuffer sceneDataBuffer;
@@ -170,6 +178,8 @@ private:
std::unique_ptr<ShadowMapPipeline> shadowMapPipeline;
std::optional<glm::vec3> shadowLightDirection;
std::vector<Light> submittedLights;
std::vector<GPUSpotShadow> spotShadows;
bool renderLightHeatmap{false};
bool initialized{false};