feat: add forward+ rendering

This commit is contained in:
2026-10-11 00:12:23 +02:00
parent c96b2282fb
commit 4bf87bd91b
26 changed files with 1346 additions and 191 deletions
@@ -0,0 +1,51 @@
#ifndef LIGHTCOMPONENT_H
#define LIGHTCOMPONENT_H
#include <destrum/ObjectModel/Component.h>
#include <destrum/Graphics/Light.h>
#include <glm/glm.hpp>
class LightComponent final : public Component {
public:
explicit LightComponent(GameObject& parent, LightType type = LightType::Point);
LightComponent(const LightComponent& other) = delete;
LightComponent(LightComponent&& other) noexcept = delete;
LightComponent& operator=(const LightComponent& other) = delete;
LightComponent& operator=(LightComponent&& other) noexcept = delete;
void Render(const RenderContext& ctx) override;
std::string GetTypeName() const override { return "LightComponent"; }
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void SetType(LightType type) { m_Type = type; }
[[nodiscard]] LightType GetType() const { return m_Type; }
void SetColor(const glm::vec3& color) { m_Color = color; }
[[nodiscard]] const glm::vec3& GetColor() const { return m_Color; }
void SetIntensity(float intensity);
[[nodiscard]] float GetIntensity() const { return m_Intensity; }
void SetRange(float range);
[[nodiscard]] float GetRange() const { return m_Range; }
void SetConeAngles(float innerConeAngle, float outerConeAngle);
[[nodiscard]] float GetInnerConeAngle() const { return m_InnerConeAngle; }
[[nodiscard]] float GetOuterConeAngle() const { return m_OuterConeAngle; }
private:
LightType m_Type{LightType::Point};
glm::vec3 m_Color{1.0f};
float m_Intensity{1.0f};
float m_Range{10.0f};
float m_InnerConeAngle{0.35f};
float m_OuterConeAngle{0.5f};
};
#endif // LIGHTCOMPONENT_H
+47
View File
@@ -0,0 +1,47 @@
#ifndef LIGHT_H
#define LIGHT_H
#include <cstdint>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
enum class LightType : std::uint32_t {
Directional = 0,
Point = 1,
Spot = 2,
};
struct Light {
LightType type{LightType::Point};
glm::vec3 position{0.0f};
glm::vec3 direction{0.0f, -1.0f, 0.0f};
glm::vec3 color{1.0f};
float intensity{1.0f};
float range{10.0f};
float innerConeAngle{0.35f};
float outerConeAngle{0.5f};
};
struct GPULight {
glm::vec3 position;
float range;
glm::vec3 color;
float intensity;
glm::vec3 direction;
std::uint32_t type;
float spotInnerCos;
float spotOuterCos;
glm::vec2 padding;
};
static_assert(sizeof(GPULight) == 64, "GPULight must match the struct in lights.glsl");
#endif //LIGHT_H
@@ -0,0 +1,78 @@
#ifndef LIGHTCULLINGPIPELINE_H
#define LIGHTCULLINGPIPELINE_H
#include <vulkan/vulkan.h>
#include <array>
#include <memory>
#include <glm/glm.hpp>
#include <destrum/Graphics/ComputePipeline.h>
#include <destrum/Graphics/Light.h>
#include <destrum/Graphics/ids.h>
#include <destrum/Graphics/Resources/AppendableBuffer.h>
#include <destrum/Graphics/Resources/Buffer.h>
class GfxDevice;
class RenderResources;
class LightCullingPipeline final {
public:
static constexpr std::uint32_t TILE_SIZE = 16;
static constexpr std::uint32_t MAX_LIGHTS_PER_TILE = 255;
static constexpr std::uint32_t TILE_STRIDE = MAX_LIGHTS_PER_TILE + 1;
static constexpr std::size_t MAX_LIGHTS = 1024;
void init(GfxDevice& gfxDevice, RenderResources& resources, glm::ivec2 drawImageSize);
void cleanup(GfxDevice& gfxDevice);
void resize(GfxDevice& gfxDevice, glm::ivec2 drawImageSize);
void beginDrawing(std::size_t frameIndex);
void appendLight(const Light& light);
void flushCurrentFrame(VkCommandBuffer cmd, GfxDevice& gfxDevice);
void cullLights(
VkCommandBuffer cmd,
const RenderResources& resources,
const GPUBuffer& sceneDataBuffer,
ImageID depthImageId,
VkExtent2D renderExtent);
[[nodiscard]] VkDeviceAddress getLightsBufferAddress() const;
[[nodiscard]] VkDeviceAddress getTileLightIndicesBufferAddress() const { return tileLightIndicesBuffer.address; }
[[nodiscard]] std::uint32_t getNumLights() const;
[[nodiscard]] glm::uvec2 getTileCount() const { return tileCount; }
private:
void createTileBuffer(GfxDevice& gfxDevice, glm::ivec2 drawImageSize);
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
std::unique_ptr<ComputePipeline> lightCullingPipeline;
struct PushConstants {
VkDeviceAddress sceneDataBuffer;
glm::uvec2 screenSize;
std::uint32_t depthTexId;
std::uint32_t padding;
};
struct PerFrameData {
AppendableBuffer<GPULight> lightsBuffer;
};
std::array<PerFrameData, FRAMES_IN_FLIGHT> framesData;
std::size_t currentFrameIndex{0};
bool warnedAboutLightLimit{false};
GPUBuffer tileLightIndicesBuffer;
glm::uvec2 tileCount{0u, 0u};
PerFrameData& getCurrentFrameData();
const PerFrameData& getCurrentFrameData() const;
};
#endif //LIGHTCULLINGPIPELINE_H
@@ -27,6 +27,15 @@ public:
VkFormat drawImageFormat,
VkFormat depthImageFormat);
void drawDepthPrepass(
VkCommandBuffer cmd,
VkExtent2D renderExtent,
const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands);
void draw(
VkCommandBuffer cmd,
VkExtent2D renderExtent,
@@ -55,9 +64,20 @@ private:
std::uint32_t padding;
};
void drawMeshes(
VkCommandBuffer cmd,
const Pipeline& pipeline,
VkExtent2D renderExtent,
const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands);
private:
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
std::unique_ptr<Pipeline> m_pipeline;
std::unique_ptr<Pipeline> m_depthPrepassPipeline;
bool m_renderWireframe{false};
};
@@ -35,7 +35,6 @@ private:
struct SkyboxPushConstants {
glm::mat4 invViewProj;
glm::vec4 skyboxRot[3];
glm::vec3 cameraPos;
std::uint32_t skyboxTextureId;
};
@@ -57,7 +57,10 @@ public:
BindlessSetManager& getBindlessSetManager();
VkDescriptorSetLayout getBindlessDescSetLayout() const;
const VkDescriptorSet& getBindlessDescSet() const;
void bindBindlessDescSet(VkCommandBuffer cmd, VkPipelineLayout layout) const;
void bindBindlessDescSet(
VkCommandBuffer cmd,
VkPipelineLayout layout,
VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS) const;
MeshCache& meshes() { return *meshCache; }
const MeshCache& meshes() const { return *meshCache; }
+41 -12
View File
@@ -5,6 +5,8 @@
#include <glm/vec3.hpp>
#include <destrum/Graphics/Camera.h>
#include <destrum/Graphics/Light.h>
#include <destrum/Graphics/Pipelines/LightCullingPipeline.h>
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
#include <destrum/Graphics/Pipelines/MeshPipeline.h>
#include <destrum/Graphics/Pipelines/QuadRendererPass.h>
@@ -20,9 +22,11 @@
#include "Pipelines/SkyboxPipeline.h"
class GameRenderer {
class GameRenderer
{
public:
struct SceneData {
struct SceneData
{
const Camera& camera;
glm::vec3 ambientColor;
float ambientIntensity;
@@ -40,11 +44,15 @@ public:
void cleanup(GfxDevice& gfxDevice);
void drawMesh(MeshID id, const glm::mat4& transform, MaterialID materialId);
void drawSkinnedMesh(MeshID id, const glm::mat4& transform, MaterialID materialId, SkinnedMesh* skinnedMesh, std::size_t jointMatricesStartIndex);
void drawSkinnedMesh(MeshID id, const glm::mat4& transform, MaterialID materialId, SkinnedMesh* skinnedMesh,
std::size_t jointMatricesStartIndex);
void submitLight(const Light& light);
const GPUImage& getDrawImage() const;
void resize(GfxDevice& gfxDevice, const glm::ivec2& newSize) {
void resize(GfxDevice& gfxDevice, const glm::ivec2& newSize)
{
createDrawImage(gfxDevice, newSize, false);
lightCullingPipeline->resize(gfxDevice, newSize);
}
Material& getMaterialMutable(MaterialID id);
@@ -54,18 +62,31 @@ public:
void flushMaterialUpdates(GfxDevice& gfxDevice);
[[nodiscard]] SkinningPipeline& getSkinningPipeline() const {
[[nodiscard]] SkinningPipeline& getSkinningPipeline() const
{
return *skinningPipeline;
}
void setRenderWireframe(bool wireframe) {
void setRenderWireframe(bool wireframe)
{
meshPipeline->setRenderWireframe(wireframe);
}
bool getRenderWireframe() {
bool getRenderWireframe()
{
return meshPipeline->getRenderWireframe();
}
void setRenderLightHeatmap(bool heatmap)
{
renderLightHeatmap = heatmap;
}
bool getRenderLightHeatmap() const
{
return renderLightHeatmap;
}
RenderResources* getResources()
{
return resources;
@@ -96,33 +117,41 @@ private:
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
struct GPUSceneData {
// camera
struct GPUSceneData
{
glm::mat4 view;
glm::mat4 proj;
glm::mat4 invProj;
glm::mat4 viewProj;
glm::vec4 cameraPos;
// ambient
glm::vec3 ambientColor;
float ambientIntensity;
// fog
glm::vec3 fogColor;
float fogDensity;
VkDeviceAddress materialsBuffer;
VkDeviceAddress lightsBuffer;
VkDeviceAddress tileLightIndicesBuffer;
std::uint32_t numLights;
std::uint32_t tileCountX;
std::uint32_t debugLightHeatmap;
std::uint32_t padding;
};
NBuffer sceneDataBuffer;
std::unique_ptr<MeshPipeline> meshPipeline;
std::unique_ptr<SkyboxPipeline> skyboxPipeline;
std::unique_ptr<LineRenderingPass> lineRenderingPass;
std::unique_ptr<QuadRendererPass> quadRendererPass;
std::unique_ptr<SkinningPipeline> skinningPipeline;
std::unique_ptr<LightCullingPipeline> lightCullingPipeline;
bool renderLightHeatmap{false};
bool initialized{false};
};
@@ -1,9 +1,8 @@
#ifndef DESTRUM_ENGINECOMPONENTLIST_H
#define DESTRUM_ENGINECOMPONENTLIST_H
// Keep the component manifest in one place. Consumers define X before
// expanding one of these lists to generate registration or other metadata.
#include <destrum/Components/Animator.h>
#include <destrum/Components/LightComponent.h>
#include <destrum/Components/MeshRendererComponent.h>
#include <destrum/Components/OrbitAndSpin.h>
#include <destrum/Components/Rotator.h>
@@ -15,6 +14,7 @@
#define DESTRUM_ENGINE_COMPONENTS(X) \
X(MeshRendererComponent) \
X(LightComponent) \
X(Rotator) \
X(Spinner) \
X(OrbitAndSpin) \
@@ -22,11 +22,8 @@ public:
static bool Load(Scene& scene, const std::filesystem::path& path,
std::function<void(float)> progressCallback = nullptr);
// Thread-safe: parse and validate a scene file on any thread.
// No engine state is touched during parsing.
static LoadResult LoadSceneFile(const std::filesystem::path& path);
// Main-thread-only: construct a scene from pre-validated JSON.
static bool ConstructScene(Scene& scene, LoadResult& result,
std::function<void(float)> progressCallback = nullptr);