Graphics: Rework shadowing (add cascaded shadow mapping)

- Add support for per-viewer shadows
- Add cascaded shadow mapping for directional lights (wip)
- Rework the way lights are sent to the shaders (they are now selected once per viewer)
- Fixes PointLight shadow mapping (using a dedicated pass)
- Lights out of frustum for every viewers are no longer processed (wip)
This commit is contained in:
SirLynix 2023-09-06 13:20:52 +02:00 committed by Jérôme Leclercq
parent a08850946a
commit 9aebb4f745
41 changed files with 1320 additions and 576 deletions

View File

@ -61,7 +61,6 @@ namespace Nz
std::size_t m_lastVisibilityHash;
std::string m_passName;
std::vector<std::unique_ptr<ElementRendererData>> m_elementRendererData;
std::vector<ElementRenderer::RenderStates> m_renderStates;
std::vector<RenderElementOwner> m_renderElements;
std::unordered_map<const MaterialInstance*, MaterialPassEntry> m_materialInstances;
RenderQueue<const RenderElement*> m_renderQueue;

View File

@ -24,9 +24,9 @@ namespace Nz
DirectionalLight(DirectionalLight&&) noexcept = default;
~DirectionalLight() = default;
float ComputeContributionScore(const BoundingVolumef& boundingVolume) const override;
float ComputeContributionScore(const Frustumf& viewerFrustum) const override;
void FillLightData(void* data) const override;
bool FrustumCull(const Frustumf& viewerFrustum) const override;
std::unique_ptr<LightShadowData> InstanciateShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry) const override;

View File

@ -0,0 +1,94 @@
// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Graphics module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#pragma once
#ifndef NAZARA_GRAPHICS_DIRECTIONALLIGHTSHADOWDATA_HPP
#define NAZARA_GRAPHICS_DIRECTIONALLIGHTSHADOWDATA_HPP
#include <NazaraUtils/Prerequisites.hpp>
#include <Nazara/Graphics/Config.hpp>
#include <Nazara/Graphics/DepthPipelinePass.hpp>
#include <Nazara/Graphics/Light.hpp>
#include <Nazara/Graphics/LightShadowData.hpp>
#include <Nazara/Graphics/ShadowViewer.hpp>
#include <NazaraUtils/FixedVector.hpp>
#include <NazaraUtils/SparsePtr.hpp>
#include <unordered_map>
namespace Nz
{
class FramePipeline;
class DirectionalLight;
class NAZARA_GRAPHICS_API DirectionalLightShadowData : public LightShadowData
{
public:
DirectionalLightShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry, const DirectionalLight& light, std::size_t cascadeCount);
DirectionalLightShadowData(const DirectionalLightShadowData&) = delete;
DirectionalLightShadowData(DirectionalLightShadowData&&) = delete;
~DirectionalLightShadowData() = default;
inline void EnableShadowStabilization(bool enable);
inline std::size_t GetCascadeCount() const;
inline void GetCascadeData(const AbstractViewer* viewer, SparsePtr<float> distance, SparsePtr<Matrix4f> viewProjMatrix) const;
inline bool IsShadowStabilization() const;
void PrepareRendering(RenderFrame& renderFrame, const AbstractViewer* viewer) override;
void RegisterMaterialInstance(const MaterialInstance& matInstance) override;
void RegisterPassInputs(FramePass& pass, const AbstractViewer* viewer) override;
void RegisterToFrameGraph(FrameGraph& frameGraph, const AbstractViewer* viewer) override;
void RegisterViewer(const AbstractViewer* viewer) override;
const Texture* RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph, const AbstractViewer* viewer) const override;
void UnregisterMaterialInstance(const MaterialInstance& matInstance) override;
void UnregisterViewer(const AbstractViewer* viewer) override;
DirectionalLightShadowData& operator=(const DirectionalLightShadowData&) = delete;
DirectionalLightShadowData& operator=(DirectionalLightShadowData&&) = delete;
private:
struct CascadeData;
template<typename F> void ForEachCascade(F&& callback);
void ComputeLightView(CascadeData& cascade, const Frustumf& cascadeFrustum, float cascadeDist);
void StabilizeShadows(CascadeData& cascade);
NazaraSlot(Light, OnLightShadowMapSettingChange, m_onLightShadowMapSettingChange);
NazaraSlot(Light, OnLightTransformInvalided, m_onLightTransformInvalidated);
struct CascadeData
{
std::optional<DepthPipelinePass> depthPass;
std::size_t attachmentIndex;
Matrix4f viewProjMatrix;
ShadowViewer viewer;
float distance;
};
struct PerViewerData
{
FixedVector<CascadeData, 8> cascades;
std::size_t textureArrayAttachmentIndex;
};
std::size_t m_cascadeCount;
std::unordered_map<const AbstractViewer*, std::unique_ptr<PerViewerData>> m_viewerData;
ElementRendererRegistry& m_elementRegistry;
FramePipeline& m_pipeline;
const DirectionalLight& m_light;
bool m_isShadowStabilizationEnabled;
float m_invTexelScale;
float m_texelScale;
};
}
#include <Nazara/Graphics/DirectionalLightShadowData.inl>
#endif // NAZARA_GRAPHICS_DIRECTIONALLIGHTSHADOWDATA_HPP

View File

@ -0,0 +1,42 @@
// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Graphics module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <cassert>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
inline void DirectionalLightShadowData::EnableShadowStabilization(bool enable)
{
m_isShadowStabilizationEnabled = enable;
}
inline std::size_t DirectionalLightShadowData::GetCascadeCount() const
{
return m_cascadeCount;
}
inline void DirectionalLightShadowData::GetCascadeData(const AbstractViewer* viewer, SparsePtr<float> distance, SparsePtr<Matrix4f> viewProjMatrix) const
{
assert(viewer);
PerViewerData& viewerData = *Retrieve(m_viewerData, viewer);
for (const auto& cascadeData : viewerData.cascades)
{
if (distance)
*distance++ = cascadeData.distance;
if (viewProjMatrix)
*viewProjMatrix++ = cascadeData.viewProjMatrix;
}
}
inline bool DirectionalLightShadowData::IsShadowStabilization() const
{
return m_isShadowStabilizationEnabled;
}
}
#include <Nazara/Graphics/DebugOff.hpp>

View File

@ -14,6 +14,7 @@
#include <Nazara/Graphics/PredefinedShaderStructs.hpp>
#include <Nazara/Graphics/RenderElementPool.hpp>
#include <Nazara/Renderer/RenderBufferView.hpp>
#include <NazaraUtils/SparsePtr.hpp>
#include <array>
#include <memory>
#include <optional>
@ -24,6 +25,7 @@ namespace Nz
class CommandBufferBuilder;
class RenderElement;
class RenderFrame;
class Texture;
class ViewerInstance;
struct ElementRendererData;
@ -38,7 +40,7 @@ namespace Nz
virtual RenderElementPoolBase& GetPool() = 0;
virtual std::unique_ptr<ElementRendererData> InstanciateData() = 0;
virtual void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer<const RenderElement>* elements, const RenderStates* renderStates);
virtual void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer<const RenderElement>* elements, SparsePtr<const RenderStates> renderStates);
virtual void PrepareEnd(RenderFrame& currentFrame, ElementRendererData& rendererData);
virtual void Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, std::size_t elementCount, const Pointer<const RenderElement>* elements) = 0;
virtual void Reset(ElementRendererData& rendererData, RenderFrame& currentFrame);
@ -47,12 +49,14 @@ namespace Nz
{
RenderStates()
{
shadowMaps2D.fill(nullptr);
shadowMapsCube.fill(nullptr);
shadowMapsDirectional.fill(nullptr);
shadowMapsPoint.fill(nullptr);
shadowMapsSpot.fill(nullptr);
}
std::array<const Texture*, PredefinedLightData::MaxLightCount> shadowMaps2D;
std::array<const Texture*, PredefinedLightData::MaxLightCount> shadowMapsCube;
std::array<const Texture*, PredefinedLightData::MaxLightCount> shadowMapsDirectional;
std::array<const Texture*, PredefinedLightData::MaxLightCount> shadowMapsPoint;
std::array<const Texture*, PredefinedLightData::MaxLightCount> shadowMapsSpot;
RenderBufferView lightData;
};
};

View File

@ -125,8 +125,9 @@ namespace Nz
InstanceDataUbo,
LightDataUbo,
OverlayTexture,
Shadowmap2D,
ShadowmapCube,
ShadowmapDirectional,
ShadowmapPoint,
ShadowmapSpot,
SkeletalDataUbo,
ViewerDataUbo,

View File

@ -13,7 +13,6 @@
#include <Nazara/Graphics/Config.hpp>
#include <Nazara/Graphics/DebugDrawPipelinePass.hpp>
#include <Nazara/Graphics/DepthPipelinePass.hpp>
#include <Nazara/Graphics/ElementRenderer.hpp>
#include <Nazara/Graphics/ForwardPipelinePass.hpp>
#include <Nazara/Graphics/FramePipeline.hpp>
#include <Nazara/Graphics/InstancedRenderable.hpp>
@ -35,6 +34,7 @@
namespace Nz
{
class LightShadowData;
class RenderFrame;
class RenderTarget;
@ -59,7 +59,8 @@ namespace Nz
std::size_t RegisterWorldInstance(WorldInstancePtr worldInstance) override;
const Light* RetrieveLight(std::size_t lightIndex) const override;
const Texture* RetrieveLightShadowmap(std::size_t lightIndex) const override;
const LightShadowData* RetrieveLightShadowData(std::size_t lightIndex) const override;
const Texture* RetrieveLightShadowmap(std::size_t lightIndex, const AbstractViewer* viewer) const override;
void Render(RenderFrame& renderFrame) override;
@ -132,6 +133,12 @@ namespace Nz
struct ViewerData
{
struct FrameData
{
Bitset<UInt64> visibleLights;
Frustumf frustum;
};
std::size_t finalColorAttachment;
std::size_t forwardColorAttachment;
std::size_t debugColorAttachment;
@ -145,6 +152,8 @@ namespace Nz
RenderQueueRegistry forwardRegistry;
RenderQueue<RenderElement*> forwardRenderQueue;
ShaderBindingPtr blitShaderBinding;
FrameData frame;
bool pendingDestruction = false;
NazaraSlot(TransferInterface, OnTransferRequired, onTransferRequired);
};
@ -158,17 +167,17 @@ namespace Nz
std::unordered_map<const RenderTarget*, RenderTargetData> m_renderTargets;
std::unordered_map<MaterialInstance*, MaterialInstanceData> m_materialInstances;
std::vector<ElementRenderer::RenderStates> m_renderStates;
mutable std::vector<FramePipelinePass::VisibleRenderable> m_visibleRenderables;
std::vector<std::size_t> m_visibleLights;
robin_hood::unordered_set<TransferInterface*> m_transferSet;
BakedFrameGraph m_bakedFrameGraph;
Bitset<UInt64> m_shadowCastingLights;
Bitset<UInt64> m_activeLights;
Bitset<UInt64> m_removedSkeletonInstances;
Bitset<UInt64> m_removedViewerInstances;
Bitset<UInt64> m_removedWorldInstances;
Bitset<UInt64> m_shadowCastingLights;
Bitset<UInt64> m_visibleShadowCastingLights;
ElementRendererRegistry& m_elementRegistry;
mutable MemoryPool<RenderableData> m_renderablePool; //< FIXME: has to be mutable because MemoryPool has no const_iterator
MemoryPool<RenderableData> m_renderablePool;
MemoryPool<LightData> m_lightPool;
MemoryPool<SkeletonInstanceData> m_skeletonInstances;
MemoryPool<ViewerData> m_viewerPool;

View File

@ -11,9 +11,7 @@
#include <Nazara/Graphics/Config.hpp>
#include <Nazara/Graphics/ElementRenderer.hpp>
#include <Nazara/Graphics/FramePipelinePass.hpp>
#include <Nazara/Graphics/Light.hpp>
#include <Nazara/Graphics/MaterialInstance.hpp>
#include <Nazara/Graphics/MaterialPass.hpp>
#include <Nazara/Graphics/RenderElement.hpp>
#include <Nazara/Graphics/RenderElementOwner.hpp>
#include <Nazara/Graphics/RenderQueue.hpp>
@ -24,13 +22,16 @@
namespace Nz
{
class AbstractViewer;
class DirectionalLight;
class ElementRendererRegistry;
class FrameGraph;
class FramePass;
class FramePipeline;
class Light;
class NAZARA_GRAPHICS_API ForwardPipelinePass : public FramePipelinePass
class PointLight;
class SpotLight;
class NAZARA_GRAPHICS_API ForwardPipelinePass : public FramePipelinePass, TransferInterface
{
public:
ForwardPipelinePass(FramePipeline& owner, ElementRendererRegistry& elementRegistry, AbstractViewer* viewer);
@ -41,7 +42,7 @@ namespace Nz
inline void InvalidateCommandBuffers();
inline void InvalidateElements();
void Prepare(RenderFrame& renderFrame, const Frustumf& frustum, const std::vector<FramePipelinePass::VisibleRenderable>& visibleRenderables, const std::vector<std::size_t>& visibleLights, std::size_t visibilityHash);
void Prepare(RenderFrame& renderFrame, const Frustumf& frustum, const std::vector<FramePipelinePass::VisibleRenderable>& visibleRenderables, const Bitset<UInt64>& visibleLights, std::size_t visibilityHash);
void RegisterMaterialInstance(const MaterialInstance& material);
FramePass& RegisterToFrameGraph(FrameGraph& frameGraph, std::size_t colorBufferIndex, std::size_t depthBufferIndex, bool hasDepthPrepass);
@ -51,9 +52,14 @@ namespace Nz
ForwardPipelinePass& operator=(const ForwardPipelinePass&) = delete;
ForwardPipelinePass& operator=(ForwardPipelinePass&&) = delete;
static constexpr std::size_t MaxLightCountPerDraw = 3;
private:
void OnTransfer(RenderFrame& renderFrame, CommandBufferBuilder& builder) override;
void PrepareDirectionalLights(void* lightMemory);
void PreparePointLights(void* lightMemory);
void PrepareSpotLights(void* lightMemory);
void PrepareLights(RenderFrame& renderFrame, const Frustumf& frustum, const Bitset<UInt64>& visibleLights);
struct MaterialPassEntry
{
std::size_t usedCount = 1;
@ -62,55 +68,30 @@ namespace Nz
NazaraSlot(MaterialInstance, OnMaterialInstanceShaderBindingInvalidated, onMaterialInstanceShaderBindingInvalidated);
};
using LightKey = std::array<const Light*, MaxLightCountPerDraw>;
struct LightKeyHasher
{
inline std::size_t operator()(const LightKey& lightKey) const;
};
struct LightDataUbo
{
std::shared_ptr<RenderBuffer> renderBuffer;
std::size_t offset = 0;
UploadPool::Allocation* allocation = nullptr;
};
struct LightPerElementData
{
RenderBufferView lightUniformBuffer;
std::array<const Texture*, MaxLightCountPerDraw> shadowMaps;
std::size_t lightCount;
};
struct LightUboPool
{
std::vector<std::shared_ptr<RenderBuffer>> lightUboBuffers;
};
template<typename T>
struct RenderableLight
{
const Light* light;
const T* light;
std::size_t lightIndex;
float contributionScore;
};
std::size_t m_forwardPassIndex;
std::size_t m_lastVisibilityHash;
std::shared_ptr<LightUboPool> m_lightUboPool;
std::shared_ptr<RenderBuffer> m_lightDataBuffer;
std::vector<std::unique_ptr<ElementRendererData>> m_elementRendererData;
std::vector<ElementRenderer::RenderStates> m_renderStates;
std::vector<RenderElementOwner> m_renderElements;
std::unordered_map<const MaterialInstance*, MaterialPassEntry> m_materialInstances;
std::unordered_map<const RenderElement*, LightPerElementData> m_lightPerRenderElement;
std::unordered_map<LightKey, RenderBufferView, LightKeyHasher> m_lightBufferPerLights;
std::vector<LightDataUbo> m_lightDataBuffers;
std::vector<RenderableLight> m_renderableLights;
std::vector<RenderableLight<DirectionalLight>> m_directionalLights;
std::vector<RenderableLight<PointLight>> m_pointLights;
std::vector<RenderableLight<SpotLight>> m_spotLights;
ElementRenderer::RenderStates m_renderState;
RenderQueue<const RenderElement*> m_renderQueue;
RenderQueueRegistry m_renderQueueRegistry;
AbstractViewer* m_viewer;
ElementRendererRegistry& m_elementRegistry;
FramePipeline& m_pipeline;
UploadPool::Allocation* m_pendingLightUploadAllocation;
bool m_rebuildCommandBuffer;
bool m_rebuildElements;
};

View File

@ -15,21 +15,6 @@ namespace Nz
{
m_rebuildElements = true;
}
inline std::size_t ForwardPipelinePass::LightKeyHasher::operator()(const LightKey& lightKey) const
{
std::size_t lightHash = 5;
auto CombineHash = [](std::size_t currentHash, std::size_t newHash)
{
return currentHash * 23 + newHash;
};
std::hash<const Light*> lightPtrHasher;
for (std::size_t i = 0; i < lightKey.size(); ++i)
lightHash = CombineHash(lightHash, lightPtrHasher(lightKey[i]));
return lightHash;
}
}
#include <Nazara/Graphics/DebugOff.hpp>

View File

@ -11,6 +11,7 @@
#include <Nazara/Graphics/Config.hpp>
#include <Nazara/Graphics/LightShadowData.hpp>
#include <Nazara/Math/BoundingVolume.hpp>
#include <Nazara/Math/Frustum.hpp>
#include <Nazara/Math/Quaternion.hpp>
#include <Nazara/Math/Vector3.hpp>
#include <Nazara/Utility/PixelFormat.hpp>
@ -19,12 +20,8 @@
namespace Nz
{
class CommandBufferBuilder;
class ElementRendererRegistry;
class FramePipeline;
class RenderBuffer;
class RenderFrame;
class Texture;
class NAZARA_GRAPHICS_API Light
{
@ -34,11 +31,11 @@ namespace Nz
Light(Light&&) noexcept = default;
virtual ~Light();
virtual float ComputeContributionScore(const BoundingVolumef& boundingVolume) const = 0;
virtual float ComputeContributionScore(const Frustumf& viewerFrustum) const = 0;
inline void EnableShadowCasting(bool castShadows);
virtual void FillLightData(void* data) const = 0;
virtual bool FrustumCull(const Frustumf& viewerFrustum) const = 0;
inline const BoundingVolumef& GetBoundingVolume() const;
inline UInt8 GetLightType() const;

View File

@ -12,6 +12,7 @@
namespace Nz
{
class AbstractViewer;
class BakedFrameGraph;
class FrameGraph;
class FramePass;
@ -22,25 +23,33 @@ namespace Nz
class NAZARA_GRAPHICS_API LightShadowData
{
public:
LightShadowData() = default;
inline LightShadowData();
LightShadowData(const LightShadowData&) = delete;
LightShadowData(LightShadowData&&) = delete;
virtual ~LightShadowData();
virtual void PrepareRendering(RenderFrame& renderFrame) = 0;
inline bool IsPerViewer() const;
virtual void PrepareRendering(RenderFrame& renderFrame, const AbstractViewer* viewer) = 0;
virtual void RegisterMaterialInstance(const MaterialInstance& matInstance) = 0;
virtual void RegisterPassInputs(FramePass& pass) = 0;
virtual void RegisterToFrameGraph(FrameGraph& frameGraph) = 0;
virtual void RegisterPassInputs(FramePass& pass, const AbstractViewer* viewer) = 0;
virtual void RegisterToFrameGraph(FrameGraph& frameGraph, const AbstractViewer* viewer) = 0;
virtual void RegisterViewer(const AbstractViewer* viewer);
virtual const Texture* RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph) const = 0;
virtual const Texture* RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph, const AbstractViewer* viewer) const = 0;
virtual void UnregisterMaterialInstance(const MaterialInstance& matInstance) = 0;
virtual void UnregisterViewer(const AbstractViewer* viewer);
LightShadowData& operator=(const LightShadowData&) = delete;
LightShadowData& operator=(LightShadowData&&) = delete;
protected:
inline void UpdatePerViewerStatus(bool isPerViewer);
private:
bool m_isPerViewer;
};
}

View File

@ -6,6 +6,20 @@
namespace Nz
{
inline LightShadowData::LightShadowData() :
m_isPerViewer(false)
{
}
inline void LightShadowData::UpdatePerViewerStatus(bool isPerViewer)
{
m_isPerViewer = isPerViewer;
}
inline bool LightShadowData::IsPerViewer() const
{
return m_isPerViewer;
}
}
#include <Nazara/Graphics/DebugOff.hpp>

View File

@ -23,9 +23,9 @@ namespace Nz
PointLight(PointLight&&) noexcept = default;
~PointLight() = default;
float ComputeContributionScore(const BoundingVolumef& boundingVolume) const override;
float ComputeContributionScore(const Frustumf& viewerFrustum) const override;
void FillLightData(void* data) const override;
bool FrustumCull(const Frustumf& viewerFrustum) const override;
std::unique_ptr<LightShadowData> InstanciateShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry) const override;
@ -33,6 +33,7 @@ namespace Nz
inline float GetDiffuseFactor() const;
inline Color GetColor() const;
inline const Vector3f& GetPosition() const;
inline float GetInvRadius() const;
inline float GetRadius() const;
inline void UpdateAmbientFactor(float factor);

View File

@ -28,14 +28,19 @@ namespace Nz
return m_color;
}
inline float PointLight::GetDiffuseFactor() const
{
return m_diffuseFactor;
}
inline const Vector3f& PointLight::GetPosition() const
{
return m_position;
}
inline float PointLight::GetDiffuseFactor() const
inline float PointLight::GetInvRadius() const
{
return m_diffuseFactor;
return m_invRadius;
}
inline float PointLight::GetRadius() const

View File

@ -28,13 +28,13 @@ namespace Nz
PointLightShadowData(PointLightShadowData&&) = delete;
~PointLightShadowData() = default;
void PrepareRendering(RenderFrame& renderFrame) override;
void PrepareRendering(RenderFrame& renderFrame, const AbstractViewer* viewer) override;
void RegisterMaterialInstance(const MaterialInstance& matInstance) override;
void RegisterPassInputs(FramePass& pass) override;
void RegisterToFrameGraph(FrameGraph& frameGraph) override;
void RegisterPassInputs(FramePass& pass, const AbstractViewer* viewer) override;
void RegisterToFrameGraph(FrameGraph& frameGraph, const AbstractViewer* viewer) override;
const Texture* RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph) const override;
const Texture* RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph, const AbstractViewer* viewer) const override;
void UnregisterMaterialInstance(const MaterialInstance& matInstance) override;

View File

@ -24,19 +24,24 @@ namespace Nz
SpotLight(SpotLight&&) noexcept = default;
~SpotLight() = default;
float ComputeContributionScore(const BoundingVolumef& boundingVolume) const override;
float ComputeContributionScore(const Frustumf& viewerFrustum) const override;
void FillLightData(void* data) const override;
bool FrustumCull(const Frustumf& viewerFrustum) const override;
inline float GetAmbientFactor() const;
inline float GetDiffuseFactor() const;
inline Color GetColor() const;
inline const Vector3f& GetDirection() const;
inline RadianAnglef GetInnerAngle() const;
inline float GetInnerAngleCos() const;
inline float GetInvRadius() const;
inline RadianAnglef GetOuterAngle() const;
inline float GetOuterAngleCos() const;
inline float GetOuterAngleTan() const;
inline const Vector3f& GetPosition() const;
inline const Quaternionf& GetRotation() const;
inline float GetRadius() const;
inline const Matrix4f& GetViewProjMatrix() const;
std::unique_ptr<LightShadowData> InstanciateShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry) const override;

View File

@ -31,6 +31,11 @@ namespace Nz
return m_color;
}
inline float SpotLight::GetDiffuseFactor() const
{
return m_diffuseFactor;
}
inline const Vector3f& SpotLight::GetDirection() const
{
return m_direction;
@ -41,11 +46,31 @@ namespace Nz
return m_innerAngle;
}
inline float SpotLight::GetInnerAngleCos() const
{
return m_innerAngleCos;
}
inline float SpotLight::GetInvRadius() const
{
return m_invRadius;
}
inline RadianAnglef SpotLight::GetOuterAngle() const
{
return m_outerAngle;
}
inline float SpotLight::GetOuterAngleCos() const
{
return m_outerAngleCos;
}
inline float SpotLight::GetOuterAngleTan() const
{
return m_outerAngleTan;
}
inline const Vector3f& SpotLight::GetPosition() const
{
return m_position;
@ -56,16 +81,16 @@ namespace Nz
return m_rotation;
}
inline float SpotLight::GetDiffuseFactor() const
{
return m_diffuseFactor;
}
inline float SpotLight::GetRadius() const
{
return m_radius;
}
inline const Matrix4f& SpotLight::GetViewProjMatrix() const
{
return m_viewProjMatrix;
}
inline void SpotLight::UpdateAmbientFactor(float factor)
{
m_ambientFactor = factor;
@ -164,10 +189,10 @@ namespace Nz
inline void SpotLight::UpdateViewProjMatrix()
{
Matrix4f biasMatrix(0.5f, 0.0f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f);
constexpr Matrix4f biasMatrix(0.5f, 0.0f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f);
Matrix4f projection = Matrix4f::Perspective(m_outerAngle * 2.f, 1.f, 0.01f, m_radius);
Matrix4f view = Matrix4f::TransformInverse(m_position, m_rotation);

View File

@ -26,13 +26,15 @@ namespace Nz
SpotLightShadowData(SpotLightShadowData&&) = delete;
~SpotLightShadowData() = default;
void PrepareRendering(RenderFrame& renderFrame) override;
inline const ViewerInstance& GetViewerInstance() const;
void PrepareRendering(RenderFrame& renderFrame, const AbstractViewer* viewer) override;
void RegisterMaterialInstance(const MaterialInstance& matInstance) override;
void RegisterPassInputs(FramePass& pass) override;
void RegisterToFrameGraph(FrameGraph& frameGraph) override;
void RegisterPassInputs(FramePass& pass, const AbstractViewer* viewer) override;
void RegisterToFrameGraph(FrameGraph& frameGraph, const AbstractViewer* viewer) override;
const Texture* RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph) const override;
const Texture* RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph, const AbstractViewer* viewer) const override;
void UnregisterMaterialInstance(const MaterialInstance& matInstance) override;

View File

@ -6,6 +6,10 @@
namespace Nz
{
inline const ViewerInstance& SpotLightShadowData::GetViewerInstance() const
{
return m_viewer.GetViewerInstance();
}
}
#include <Nazara/Graphics/DebugOff.hpp>

View File

@ -59,7 +59,7 @@ namespace Nz
RenderElementPool<RenderSpriteChain>& GetPool() override;
std::unique_ptr<ElementRendererData> InstanciateData() override;
void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer<const RenderElement>* elements, const RenderStates* renderStates) override;
void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer<const RenderElement>* elements, SparsePtr<const RenderStates> renderStates) override;
void PrepareEnd(RenderFrame& currentFrame, ElementRendererData& rendererData) override;
void Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, std::size_t elementCount, const Pointer<const RenderElement>* elements) override;
void Reset(ElementRendererData& rendererData, RenderFrame& currentFrame) override;

View File

@ -27,7 +27,7 @@ namespace Nz
RenderElementPool<RenderSubmesh>& GetPool() override;
std::unique_ptr<ElementRendererData> InstanciateData() override;
void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer<const RenderElement>* elements, const RenderStates* renderStates) override;
void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer<const RenderElement>* elements, SparsePtr<const RenderStates> renderStates) override;
void Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, std::size_t elementCount, const Pointer<const RenderElement>* elements) override;
void Reset(ElementRendererData& rendererData, RenderFrame& currentFrame) override;

View File

@ -56,6 +56,11 @@ namespace Nz
constexpr IntersectionSide Intersect(const Sphere<T>& sphere) const;
constexpr IntersectionSide Intersect(const Vector3<T>* points, std::size_t pointCount) const;
constexpr Frustum<T> Reduce(T nearFactor, T farFactor) const;
template<typename F> constexpr void Split(std::initializer_list<T> splitFactors, F&& callback) const;
template<typename F> constexpr void Split(const T* splitFactors, std::size_t factorCount, F&& callback) const;
std::string ToString() const;
constexpr Frustum& operator=(const Frustum&) = default;

View File

@ -427,6 +427,38 @@ namespace Nz
return side;
}
template<typename T>
constexpr Frustum<T> Frustum<T>::Reduce(T nearFactor, T farFactor) const
{
EnumArray<FrustumPlane, Plane<T>> planes = m_planes;
planes[FrustumPlane::Near].distance = Lerp(m_planes[FrustumPlane::Near].distance, -m_planes[FrustumPlane::Far].distance, nearFactor);
planes[FrustumPlane::Far].distance = Lerp(-m_planes[FrustumPlane::Near].distance, m_planes[FrustumPlane::Far].distance, farFactor);
return Frustum<T>(planes);
}
template<typename T>
template<typename F>
constexpr void Frustum<T>::Split(std::initializer_list<T> splitFactors, F&& callback) const
{
return Split(splitFactors.begin(), splitFactors.size(), std::forward<F>(callback));
}
template<typename T>
template<typename F>
constexpr void Frustum<T>::Split(const T* splitFactors, std::size_t factorCount, F&& callback) const
{
T previousFar = T(0.0);
for (std::size_t i = 0; i < factorCount; ++i)
{
T farFactor = splitFactors[i];
callback(previousFar, farFactor);
previousFar = farFactor;
}
callback(previousFar, T(1.0));
}
/*!
* \brief Gives a string representation
* \return A string representation of the object: "Frustum(Plane ...)"

View File

@ -83,14 +83,13 @@ namespace Nz
const auto& viewerInstance = m_viewer->GetViewerInstance();
ElementRenderer::RenderStates defaultRenderStates{};
m_elementRegistry.ProcessRenderQueue(m_renderQueue, [&](std::size_t elementType, const Pointer<const RenderElement>* elements, std::size_t elementCount)
{
ElementRenderer& elementRenderer = m_elementRegistry.GetElementRenderer(elementType);
m_renderStates.clear();
m_renderStates.resize(elementCount);
elementRenderer.Prepare(viewerInstance, *m_elementRendererData[elementType], renderFrame, elementCount, elements, m_renderStates.data());
elementRenderer.Prepare(viewerInstance, *m_elementRendererData[elementType], renderFrame, elementCount, elements, SparsePtr(&defaultRenderStates, 0));
});
m_elementRegistry.ForEachElementRenderer([&](std::size_t elementType, ElementRenderer& elementRenderer)

View File

@ -3,36 +3,24 @@
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/DirectionalLight.hpp>
#include <Nazara/Graphics/Enums.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Graphics/PredefinedShaderStructs.hpp>
#include <Nazara/Math/Vector2.hpp>
#include <Nazara/Math/Vector3.hpp>
#include <Nazara/Math/Vector4.hpp>
#include <limits>
#include <Nazara/Graphics/DirectionalLightShadowData.hpp>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
float DirectionalLight::ComputeContributionScore(const BoundingVolumef& /*boundingVolume*/) const
float DirectionalLight::ComputeContributionScore(const Frustumf& /*viewerFrustum*/) const
{
return -std::numeric_limits<float>::infinity();
}
void DirectionalLight::FillLightData(void* data) const
bool DirectionalLight::FrustumCull(const Frustumf& /*viewerFrustum*/) const
{
auto lightOffset = PredefinedLightData::GetOffsets();
AccessByOffset<UInt32&>(data, lightOffset.lightMemberOffsets.type) = UnderlyingCast(BasicLightType::Directional);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.color) = Vector4f(m_color.r, m_color.g, m_color.b, m_color.a);
AccessByOffset<Vector2f&>(data, lightOffset.lightMemberOffsets.factor) = Vector2f(m_ambientFactor, m_diffuseFactor);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.parameter1) = Vector4f(m_direction.x, m_direction.y, m_direction.z, 0.f);
AccessByOffset<Vector2f&>(data, lightOffset.lightMemberOffsets.shadowMapSize) = Vector2f(-1.f, -1.f);
return true; //< always visible
}
std::unique_ptr<LightShadowData> DirectionalLight::InstanciateShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry) const
{
return nullptr; //< TODO
return std::make_unique<DirectionalLightShadowData>(pipeline, elementRegistry, *this, 4);
}
void DirectionalLight::UpdateTransform(const Vector3f& /*position*/, const Quaternionf& rotation, const Vector3f& /*scale*/)

View File

@ -0,0 +1,278 @@
// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Graphics module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/DirectionalLightShadowData.hpp>
#include <Nazara/Graphics/BakedFrameGraph.hpp>
#include <Nazara/Graphics/DirectionalLight.hpp>
#include <Nazara/Graphics/FrameGraph.hpp>
#include <Nazara/Graphics/FramePipeline.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Math/Quaternion.hpp>
#include <Nazara/Math/Sphere.hpp>
#include <NazaraUtils/Algorithm.hpp>
#include <NazaraUtils/StackVector.hpp>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
DirectionalLightShadowData::DirectionalLightShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry, const DirectionalLight& light, std::size_t cascadeCount) :
m_cascadeCount(cascadeCount),
m_elementRegistry(elementRegistry),
m_pipeline(pipeline),
m_light(light),
m_isShadowStabilizationEnabled(true)
{
m_onLightShadowMapSettingChange.Connect(m_light.OnLightShadowMapSettingChange, [this](Light* /*light*/, PixelFormat /*newPixelFormat*/, UInt32 newSize)
{
m_texelScale = 2.0f / newSize;
m_invTexelScale = 1.0f / m_texelScale;
ForEachCascade([newSize](CascadeData& cascade)
{
cascade.viewer.UpdateViewport(Recti(0, 0, SafeCast<int>(newSize), SafeCast<int>(newSize)));
});
});
m_texelScale = 2.0f / m_light.GetShadowMapSize();
m_invTexelScale = 1.0f / m_texelScale;
UpdatePerViewerStatus(true);
}
void DirectionalLightShadowData::PrepareRendering(RenderFrame& renderFrame, const AbstractViewer* viewer)
{
assert(viewer);
PerViewerData& viewerData = *Retrieve(m_viewerData, viewer);
// Extract frustum from main viewer
const Vector3f& eyePosition = viewer->GetViewerInstance().GetEyePosition();
const Matrix4f& viewProjMatrix = viewer->GetViewerInstance().GetViewProjMatrix();
//std::array planePct = { 0.1f, 0.3f, 0.6f }; // TODO: Generate the separations based on other settings
std::array planePct = { 0.03f, 0.1f, 0.2f, 0.5f }; // TODO: Generate the separations based on other settings
assert(m_cascadeCount <= planePct.size() + 1);
StackVector<Frustumf> frustums = NazaraStackVector(Frustumf, m_cascadeCount);
StackVector<float> frustumDists = NazaraStackVector(float, m_cascadeCount);
Frustumf frustum = Frustumf::Extract(viewProjMatrix);
frustum.Split(planePct.data(), m_cascadeCount - 1, [&](float zNearPct, float zFarPct)
{
frustums.push_back(frustum.Reduce(zNearPct, zFarPct));
frustumDists.push_back(frustums.back().GetPlane(FrustumPlane::Far).SignedDistance(eyePosition));
});
for (std::size_t cascadeIndex = 0; cascadeIndex < m_cascadeCount; ++cascadeIndex)
{
CascadeData& cascade = viewerData.cascades[cascadeIndex];
ComputeLightView(cascade, frustums[cascadeIndex], frustumDists[cascadeIndex]);
if (m_isShadowStabilizationEnabled)
StabilizeShadows(cascade);
constexpr Matrix4f biasMatrix(0.5f, 0.0f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f);
ViewerInstance& viewerInstance = cascade.viewer.GetViewerInstance();
cascade.viewProjMatrix = viewerInstance.GetViewProjMatrix() * biasMatrix;
m_pipeline.QueueTransfer(&viewerInstance);
// Prepare depth pass
Frustumf lightFrustum = Frustumf::Extract(viewerInstance.GetViewProjMatrix());
std::size_t visibilityHash = 5U;
const auto& visibleRenderables = m_pipeline.FrustumCull(lightFrustum, 0xFFFFFFFF, visibilityHash);
cascade.depthPass->Prepare(renderFrame, lightFrustum, visibleRenderables, visibilityHash);
}
}
void DirectionalLightShadowData::ComputeLightView(CascadeData& cascade, const Frustumf& cascadeFrustum, float cascadeDist)
{
ViewerInstance& viewerInstance = cascade.viewer.GetViewerInstance();
EnumArray<BoxCorner, Vector3f> frustumCorners = cascadeFrustum.ComputeCorners();
// Get frustum center
Vector3f frustumCenter = Vector3f::Zero();
for (const Vector3f& corner : frustumCorners)
frustumCenter += corner;
frustumCenter /= float(frustumCorners.size());
// Compute radius (= biggest distance to the center)
float radius = 0.f;
for (const Vector3f& corner : frustumCorners)
radius = std::max(radius, frustumCenter.SquaredDistance(corner));
radius = std::ceil(std::sqrt(radius));
Matrix4f lightView = Matrix4f::TransformInverse(frustumCenter, m_light.GetRotation());
// Compute light projection matrix
Boxf aabb = Boxf::FromExtends(frustumCenter - Vector3f(radius), frustumCenter + Vector3f(radius));
float left = std::numeric_limits<float>::infinity();
float right = -std::numeric_limits<float>::infinity();
float top = std::numeric_limits<float>::infinity();
float bottom = -std::numeric_limits<float>::infinity();
float zNear = std::numeric_limits<float>::infinity();
float zFar = -std::numeric_limits<float>::infinity();
for (const Vector3f& corner : aabb.GetCorners())
{
Vector3f viewCorner = lightView * corner;
left = std::min(left, viewCorner.x);
right = std::max(right, viewCorner.x);
top = std::min(top, viewCorner.y);
bottom = std::max(bottom, viewCorner.y);
zNear = std::min(zNear, viewCorner.z);
zFar = std::max(zFar, viewCorner.z);
}
// Tune this parameter according to the scene
constexpr float zMult = 2.0f;
if (zNear < 0)
zNear *= zMult;
else
zNear /= zMult;
if (zFar < 0)
zFar /= zMult;
else
zFar *= zMult;
cascade.distance = cascadeDist;
Matrix4f lightProj = Matrix4f::Ortho(left, right, top, bottom, zNear, zFar);
viewerInstance.UpdateProjViewMatrices(lightProj, lightView);
viewerInstance.UpdateEyePosition(frustumCenter);
viewerInstance.UpdateNearFarPlanes(zNear, zFar);
}
void DirectionalLightShadowData::StabilizeShadows(CascadeData& cascade)
{
ViewerInstance& viewerInstance = cascade.viewer.GetViewerInstance();
// Stabilize cascade shadows by keeping the center to a texel boundary of the previous frame
// https://www.junkship.net/News/2020/11/22/shadow-of-a-doubt-part-2
Vector4f shadowOrigin(0.0f, 0.0f, 0.0f, 1.0f);
shadowOrigin = viewerInstance.GetViewProjMatrix() * shadowOrigin;
shadowOrigin *= m_texelScale;
Vector2f roundedOrigin = { std::round(shadowOrigin.x), std::round(shadowOrigin.y) };
Vector2f roundOffset = roundedOrigin - Vector2f(shadowOrigin);
roundOffset *= m_invTexelScale;
Matrix4f lightProj = viewerInstance.GetProjectionMatrix();
lightProj.ApplyTranslation(Vector3f(roundOffset.x, roundOffset.y, 0.f));
viewerInstance.UpdateProjectionMatrix(lightProj);
}
void DirectionalLightShadowData::RegisterMaterialInstance(const MaterialInstance& matInstance)
{
ForEachCascade([&](CascadeData& cascade)
{
cascade.depthPass->RegisterMaterialInstance(matInstance);
});
}
void DirectionalLightShadowData::RegisterPassInputs(FramePass& pass, const AbstractViewer* viewer)
{
assert(viewer);
PerViewerData& viewerData = *Retrieve(m_viewerData, viewer);
std::size_t arrayInputIndex = pass.AddInput(viewerData.textureArrayAttachmentIndex);
pass.SetInputLayout(arrayInputIndex, TextureLayout::ColorInput);
for (CascadeData& cascade : viewerData.cascades)
pass.AddInput(cascade.attachmentIndex);
}
void DirectionalLightShadowData::RegisterToFrameGraph(FrameGraph& frameGraph, const AbstractViewer* viewer)
{
UInt32 shadowMapSize = m_light.GetShadowMapSize();
PerViewerData& viewerData = *Retrieve(m_viewerData, viewer);
viewerData.textureArrayAttachmentIndex = frameGraph.AddAttachmentArray({
"Directional-light cascade shadowmaps",
m_light.GetShadowMapFormat(),
FramePassAttachmentSize::Fixed,
shadowMapSize, shadowMapSize,
}, m_cascadeCount);
for (std::size_t i = 0; i < viewerData.cascades.size(); ++i)
{
CascadeData& cascade = viewerData.cascades[i];
cascade.attachmentIndex = frameGraph.AddAttachmentArrayLayer(viewerData.textureArrayAttachmentIndex, i);
cascade.depthPass->RegisterToFrameGraph(frameGraph, cascade.attachmentIndex);
}
}
void DirectionalLightShadowData::RegisterViewer(const AbstractViewer* viewer)
{
std::size_t shadowPassIndex = Graphics::Instance()->GetMaterialPassRegistry().GetPassIndex("ShadowPass");
std::unique_ptr<PerViewerData> perViewerData = std::make_unique<PerViewerData>();
perViewerData->cascades.resize(m_cascadeCount);
std::size_t cascadeIndex = 0;
UInt32 shadowMapSize = m_light.GetShadowMapSize();
for (CascadeData& cascade : perViewerData->cascades)
{
ShadowViewer& shadowViewer = cascade.viewer;
shadowViewer.UpdateRenderMask(0xFFFFFFFF);
shadowViewer.UpdateViewport(Recti(0, 0, SafeCast<int>(shadowMapSize), SafeCast<int>(shadowMapSize)));
cascade.depthPass.emplace(m_pipeline, m_elementRegistry, &shadowViewer, shadowPassIndex, Format("Cascade #{}", cascadeIndex++));
}
m_pipeline.ForEachRegisteredMaterialInstance([&](const MaterialInstance& matInstance)
{
for (CascadeData& cascade : perViewerData->cascades)
cascade.depthPass->RegisterMaterialInstance(matInstance);
});
assert(m_viewerData.find(viewer) == m_viewerData.end());
m_viewerData[viewer] = std::move(perViewerData);
}
const Texture* DirectionalLightShadowData::RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph, const AbstractViewer* viewer) const
{
assert(viewer);
const PerViewerData& viewerData = *Retrieve(m_viewerData, viewer);
return bakedGraph.GetAttachmentTexture(viewerData.textureArrayAttachmentIndex).get();
}
void DirectionalLightShadowData::UnregisterMaterialInstance(const MaterialInstance& matInstance)
{
ForEachCascade([&](CascadeData& cascade)
{
cascade.depthPass->UnregisterMaterialInstance(matInstance);
});
}
void DirectionalLightShadowData::UnregisterViewer(const AbstractViewer* viewer)
{
m_viewerData.erase(viewer);
}
template<typename F>
void DirectionalLightShadowData::ForEachCascade(F&& callback)
{
for (auto it = m_viewerData.begin(); it != m_viewerData.end(); ++it)
{
for (CascadeData& cascade : it->second->cascades)
callback(cascade);
}
}
}

View File

@ -9,7 +9,7 @@ namespace Nz
{
ElementRenderer::~ElementRenderer() = default;
void ElementRenderer::Prepare(const ViewerInstance& /*viewerInstance*/, ElementRendererData& /*rendererData*/, RenderFrame& /*currentFrame*/, std::size_t /*elementCount*/, const Pointer<const RenderElement>* /*elements*/, const RenderStates* /*renderStates*/)
void ElementRenderer::Prepare(const ViewerInstance& /*viewerInstance*/, ElementRendererData& /*rendererData*/, RenderFrame& /*currentFrame*/, std::size_t /*elementCount*/, const Pointer<const RenderElement>* /*elements*/, SparsePtr<const RenderStates> /*renderStates*/)
{
}

View File

@ -112,6 +112,9 @@ namespace Nz
//TODO: Switch lights to storage buffers so they can all be part of GPU memory
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
UInt32 viewerRenderMask = viewerData.viewer->GetRenderMask();
if (viewerRenderMask & renderMask)
@ -125,6 +128,17 @@ namespace Nz
{
m_shadowCastingLights.UnboundedSet(lightIndex);
lightData->shadowData = light->InstanciateShadowData(*this, m_elementRegistry);
if (lightData->shadowData->IsPerViewer())
{
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
if ((viewerData.viewer->GetRenderMask() & lightData->renderMask) != 0)
lightData->shadowData->RegisterViewer(viewerData.viewer);
}
}
}
else
{
@ -139,6 +153,18 @@ namespace Nz
{
m_shadowCastingLights.UnboundedSet(lightIndex);
lightData->shadowData = light->InstanciateShadowData(*this, m_elementRegistry);
if (lightData->shadowData->IsPerViewer())
{
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
if ((viewerData.viewer->GetRenderMask() & lightData->renderMask) != 0)
lightData->shadowData->RegisterViewer(viewerData.viewer);
}
}
m_rebuildFrameGraph = true;
}
@ -161,6 +187,9 @@ namespace Nz
// TODO: Invalidate only relevant viewers and passes
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
UInt32 viewerRenderMask = viewerData.viewer->GetRenderMask();
if (viewerRenderMask & renderMask)
@ -181,6 +210,9 @@ namespace Nz
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
if (viewerData.depthPrepass)
viewerData.depthPrepass->RegisterMaterialInstance(*newMaterial);
@ -195,6 +227,9 @@ namespace Nz
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
if (viewerData.depthPrepass)
viewerData.depthPrepass->UnregisterMaterialInstance(*prevMaterial);
@ -212,6 +247,9 @@ namespace Nz
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
if (viewerData.depthPrepass)
viewerData.depthPrepass->RegisterMaterialInstance(*mat);
@ -262,6 +300,14 @@ namespace Nz
m_transferSet.insert(&viewerInstance->GetViewerInstance());
UInt32 renderMask = viewerInstance->GetRenderMask();
for (std::size_t i = m_shadowCastingLights.FindFirst(); i != m_shadowCastingLights.npos; i = m_shadowCastingLights.FindNext(i))
{
LightData* lightData = m_lightPool.RetrieveFromIndex(i);
if (lightData->shadowData->IsPerViewer() && (renderMask & lightData->renderMask) != 0)
lightData->shadowData->RegisterViewer(viewerInstance);
}
m_rebuildFrameGraph = true;
return viewerIndex;
@ -287,12 +333,21 @@ namespace Nz
return m_lightPool.RetrieveFromIndex(lightIndex)->light;
}
const Texture* ForwardFramePipeline::RetrieveLightShadowmap(std::size_t lightIndex) const
const LightShadowData* ForwardFramePipeline::RetrieveLightShadowData(std::size_t lightIndex) const
{
if (!m_shadowCastingLights.UnboundedTest(lightIndex))
return nullptr;
return m_lightPool.RetrieveFromIndex(lightIndex)->shadowData->RetrieveLightShadowmap(m_bakedFrameGraph);
return m_lightPool.RetrieveFromIndex(lightIndex)->shadowData.get();
}
const Texture* ForwardFramePipeline::RetrieveLightShadowmap(std::size_t lightIndex, const AbstractViewer* viewer) const
{
const LightShadowData* lightShadowData = RetrieveLightShadowData(lightIndex);
if (!lightShadowData)
return nullptr;
return lightShadowData->RetrieveLightShadowmap(m_bakedFrameGraph, viewer);
}
void ForwardFramePipeline::Render(RenderFrame& renderFrame)
@ -334,75 +389,67 @@ namespace Nz
else
frameGraphInvalidated = m_bakedFrameGraph.Resize(renderFrame);
// Update UBOs and materials
renderFrame.Execute([&](CommandBufferBuilder& builder)
{
builder.BeginDebugRegion("CPU to GPU transfers", Color::Yellow());
{
builder.PreTransferBarrier();
for (TransferInterface* transferInterface : m_transferSet)
transferInterface->OnTransfer(renderFrame, builder);
m_transferSet.clear();
OnTransfer(this, renderFrame, builder);
builder.PostTransferBarrier();
}
builder.EndDebugRegion();
}, QueueType::Transfer);
// Shadow map handling
for (std::size_t i = m_shadowCastingLights.FindFirst(); i != m_shadowCastingLights.npos; i = m_shadowCastingLights.FindNext(i))
{
LightData* lightData = m_lightPool.RetrieveFromIndex(i);
lightData->shadowData->PrepareRendering(renderFrame);
}
// Render queues handling
// Find active lights (i.e. visible in any frustum)
m_activeLights.Clear();
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
UInt32 renderMask = viewerData.viewer->GetRenderMask();
// Frustum culling
// Extract frustum from viewproj matrix
const Matrix4f& viewProjMatrix = viewerData.viewer->GetViewerInstance().GetViewProjMatrix();
viewerData.frame.frustum = Frustumf::Extract(viewProjMatrix);
Frustumf frustum = Frustumf::Extract(viewProjMatrix);
std::size_t visibilityHash = 5;
const auto& visibleRenderables = FrustumCull(frustum, renderMask, visibilityHash);
// Lights update don't trigger a rebuild of the depth pre-pass
std::size_t depthVisibilityHash = visibilityHash;
m_visibleLights.clear();
viewerData.frame.visibleLights.Clear();
for (auto it = m_lightPool.begin(); it != m_lightPool.end(); ++it)
{
const LightData& lightData = *it;
std::size_t lightIndex = it.GetIndex();
const BoundingVolumef& boundingVolume = lightData.light->GetBoundingVolume();
if ((lightData.renderMask & renderMask) == 0)
continue;
// TODO: Use more precise tests for point lights (frustum/sphere is cheap)
if (renderMask & lightData.renderMask && frustum.Intersect(boundingVolume) != IntersectionSide::Outside)
{
m_visibleLights.push_back(lightIndex);
m_activeLights.UnboundedSet(lightIndex);
viewerData.frame.visibleLights.UnboundedSet(lightIndex);
}
}
auto CombineHash = [](std::size_t currentHash, std::size_t newHash)
{
return currentHash * 23 + newHash;
};
m_visibleShadowCastingLights.PerformsAND(m_activeLights, m_shadowCastingLights);
visibilityHash = CombineHash(visibilityHash, std::hash<const void*>()(lightData.light));
}
// Shadow map handling (for active lights)
for (std::size_t i = m_visibleShadowCastingLights.FindFirst(); i != m_visibleShadowCastingLights.npos; i = m_visibleShadowCastingLights.FindNext(i))
{
LightData* lightData = m_lightPool.RetrieveFromIndex(i);
if (!lightData->shadowData->IsPerViewer())
lightData->shadowData->PrepareRendering(renderFrame, nullptr);
}
// Viewer handling (second pass)
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
UInt32 renderMask = viewerData.viewer->GetRenderMask();
// Per-viewer shadow map handling
for (std::size_t lightIndex = viewerData.frame.visibleLights.FindFirst(); lightIndex != viewerData.frame.visibleLights.npos; lightIndex = viewerData.frame.visibleLights.FindNext(lightIndex))
{
LightData* lightData = m_lightPool.RetrieveFromIndex(lightIndex);
if (lightData->shadowData && lightData->shadowData->IsPerViewer() && (renderMask & lightData->renderMask) != 0)
lightData->shadowData->PrepareRendering(renderFrame, viewerData.viewer);
}
if (viewerData.depthPrepass)
viewerData.depthPrepass->Prepare(renderFrame, frustum, visibleRenderables, depthVisibilityHash);
// Frustum culling
std::size_t visibilityHash = 5;
const auto& visibleRenderables = FrustumCull(viewerData.frame.frustum, renderMask, visibilityHash);
if (viewerData.gammaCorrectionPass)
viewerData.gammaCorrectionPass->Prepare(renderFrame);
viewerData.forwardPass->Prepare(renderFrame, frustum, visibleRenderables, m_visibleLights, visibilityHash);
viewerData.forwardPass->Prepare(renderFrame, viewerData.frame.frustum, visibleRenderables, viewerData.frame.visibleLights, visibilityHash);
if (viewerData.debugDrawPass)
viewerData.debugDrawPass->Prepare(renderFrame);
@ -413,6 +460,9 @@ namespace Nz
const std::shared_ptr<TextureSampler>& sampler = graphics->GetSamplerCache().Get({});
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
if (viewerData.blitShaderBinding)
renderFrame.PushForRelease(std::move(viewerData.blitShaderBinding));
@ -446,6 +496,24 @@ namespace Nz
}
}
// Update UBOs and materials
renderFrame.Execute([&](CommandBufferBuilder& builder)
{
builder.BeginDebugRegion("CPU to GPU transfers", Color::Yellow());
{
builder.PreTransferBarrier();
for (TransferInterface* transferInterface : m_transferSet)
transferInterface->OnTransfer(renderFrame, builder);
m_transferSet.clear();
OnTransfer(this, renderFrame, builder);
builder.PostTransferBarrier();
}
builder.EndDebugRegion();
}, QueueType::Transfer);
m_bakedFrameGraph.Execute(renderFrame);
m_rebuildFrameGraph = false;
@ -494,7 +562,11 @@ namespace Nz
void ForwardFramePipeline::UnregisterLight(std::size_t lightIndex)
{
m_lightPool.Free(lightIndex);
m_shadowCastingLights.UnboundedReset(lightIndex);
if (m_shadowCastingLights.UnboundedTest(lightIndex))
{
m_shadowCastingLights.Reset(lightIndex);
m_rebuildFrameGraph = true;
}
}
void ForwardFramePipeline::UnregisterRenderable(std::size_t renderableIndex)
@ -509,6 +581,9 @@ namespace Nz
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
if (viewerData.depthPrepass)
viewerData.depthPrepass->UnregisterMaterialInstance(*material);
@ -521,20 +596,31 @@ namespace Nz
void ForwardFramePipeline::UnregisterSkeleton(std::size_t skeletonIndex)
{
// Defer world instance release
// Defer instance release
m_removedSkeletonInstances.UnboundedSet(skeletonIndex);
}
void ForwardFramePipeline::UnregisterViewer(std::size_t viewerIndex)
{
// Defer world instance release
auto& viewerData = *m_viewerPool.RetrieveFromIndex(viewerIndex);
viewerData.pendingDestruction = true;
UInt32 renderMask = viewerData.viewer->GetRenderMask();
for (std::size_t i = m_shadowCastingLights.FindFirst(); i != m_shadowCastingLights.npos; i = m_shadowCastingLights.FindNext(i))
{
LightData* lightData = m_lightPool.RetrieveFromIndex(i);
if (lightData->shadowData->IsPerViewer() && (renderMask & lightData->renderMask) != 0)
lightData->shadowData->UnregisterViewer(viewerData.viewer);
}
// Defer instance release
m_removedViewerInstances.UnboundedSet(viewerIndex);
m_rebuildFrameGraph = true;
}
void ForwardFramePipeline::UnregisterWorldInstance(std::size_t worldInstance)
{
// Defer world instance release
// Defer instance release
m_removedWorldInstances.UnboundedSet(worldInstance);
}
@ -558,6 +644,9 @@ namespace Nz
// TODO: Invalidate only relevant viewers and passes
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
UInt32 viewerRenderMask = viewerData.viewer->GetRenderMask();
if (viewerRenderMask & renderableData->renderMask)
@ -578,6 +667,9 @@ namespace Nz
// TODO: Invalidate only relevant viewers and passes
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
UInt32 viewerRenderMask = viewerData.viewer->GetRenderMask();
if (viewerRenderMask & renderableData->renderMask)
@ -593,6 +685,7 @@ namespace Nz
void ForwardFramePipeline::UpdateViewerRenderMask(std::size_t viewerIndex, Int32 renderOrder)
{
ViewerData* viewerData = m_viewerPool.RetrieveFromIndex(viewerIndex);
assert(!viewerData->pendingDestruction);
if (viewerData->renderOrder != renderOrder)
{
viewerData->renderOrder = renderOrder;
@ -607,11 +700,23 @@ namespace Nz
for (std::size_t i : m_shadowCastingLights.IterBits())
{
LightData* lightData = m_lightPool.RetrieveFromIndex(i);
lightData->shadowData->RegisterToFrameGraph(frameGraph);
if (!lightData->shadowData->IsPerViewer())
lightData->shadowData->RegisterToFrameGraph(frameGraph, nullptr);
}
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
UInt32 renderMask = viewerData.viewer->GetRenderMask();
for (std::size_t i = m_shadowCastingLights.FindFirst(); i != m_shadowCastingLights.npos; i = m_shadowCastingLights.FindNext(i))
{
LightData* lightData = m_lightPool.RetrieveFromIndex(i);
if (lightData->shadowData->IsPerViewer() && (renderMask & lightData->renderMask) != 0)
lightData->shadowData->RegisterToFrameGraph(frameGraph, viewerData.viewer);
}
viewerData.forwardColorAttachment = frameGraph.AddAttachment({
"Forward output",
PixelFormat::RGBA8
@ -629,7 +734,8 @@ namespace Nz
for (std::size_t i : m_shadowCastingLights.IterBits())
{
LightData* lightData = m_lightPool.RetrieveFromIndex(i);
lightData->shadowData->RegisterPassInputs(forwardPass);
if ((renderMask & lightData->renderMask) != 0)
lightData->shadowData->RegisterPassInputs(forwardPass, (lightData->shadowData->IsPerViewer()) ? viewerData.viewer : nullptr);
}
viewerData.finalColorAttachment = viewerData.forwardColorAttachment;
@ -660,6 +766,9 @@ namespace Nz
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
const RenderTarget& renderTarget = viewerData.viewer->GetRenderTarget();
*viewerIt++ = std::make_pair(&renderTarget, &viewerData);
}

View File

@ -4,13 +4,18 @@
#include <Nazara/Graphics/ForwardPipelinePass.hpp>
#include <Nazara/Graphics/AbstractViewer.hpp>
#include <Nazara/Graphics/DirectionalLight.hpp>
#include <Nazara/Graphics/ElementRendererRegistry.hpp>
#include <Nazara/Graphics/FrameGraph.hpp>
#include <Nazara/Graphics/FramePipeline.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Graphics/InstancedRenderable.hpp>
#include <Nazara/Graphics/Material.hpp>
#include <Nazara/Graphics/PointLight.hpp>
#include <Nazara/Graphics/SpotLight.hpp>
#include <Nazara/Graphics/PredefinedShaderStructs.hpp>
#include <Nazara/Graphics/DirectionalLightShadowData.hpp>
#include <Nazara/Graphics/SpotLightShadowData.hpp>
#include <Nazara/Graphics/ViewerInstance.hpp>
#include <Nazara/Renderer/CommandBufferBuilder.hpp>
#include <Nazara/Renderer/RenderFrame.hpp>
@ -23,155 +28,41 @@ namespace Nz
m_viewer(viewer),
m_elementRegistry(elementRegistry),
m_pipeline(owner),
m_pendingLightUploadAllocation(nullptr),
m_rebuildCommandBuffer(false),
m_rebuildElements(false)
{
Graphics* graphics = Graphics::Instance();
m_forwardPassIndex = graphics->GetMaterialPassRegistry().GetPassIndex("ForwardPass");
m_lightUboPool = std::make_shared<LightUboPool>();
std::size_t lightUboAlignedSize = AlignPow2(PredefinedLightOffsets.totalSize, SafeCast<std::size_t>(graphics->GetRenderDevice()->GetDeviceInfo().limits.minUniformBufferOffsetAlignment));
m_lightDataBuffer = graphics->GetRenderDevice()->InstantiateBuffer(BufferType::Uniform, lightUboAlignedSize, BufferUsage::DeviceLocal | BufferUsage::Dynamic | BufferUsage::Write);
m_lightDataBuffer->UpdateDebugName("Lights buffer");
m_renderState.lightData = RenderBufferView(m_lightDataBuffer.get());
}
void ForwardPipelinePass::Prepare(RenderFrame& renderFrame, const Frustumf& frustum, const std::vector<FramePipelinePass::VisibleRenderable>& visibleRenderables, const std::vector<std::size_t>& visibleLights, std::size_t visibilityHash)
void ForwardPipelinePass::Prepare(RenderFrame& renderFrame, const Frustumf& frustum, const std::vector<FramePipelinePass::VisibleRenderable>& visibleRenderables, const Bitset<UInt64>& visibleLights, std::size_t visibilityHash)
{
if (m_lastVisibilityHash != visibilityHash || m_rebuildElements) //< FIXME
{
renderFrame.PushForRelease(std::move(m_renderElements));
m_renderElements.clear();
m_renderQueueRegistry.Clear();
m_renderQueue.Clear();
m_lightBufferPerLights.clear();
m_lightPerRenderElement.clear();
for (auto& lightDataUbo : m_lightDataBuffers)
{
renderFrame.PushReleaseCallback([pool = m_lightUboPool, lightUbo = std::move(lightDataUbo.renderBuffer)]() mutable
{
pool->lightUboBuffers.push_back(std::move(lightUbo));
});
}
m_lightDataBuffers.clear();
Graphics* graphics = Graphics::Instance();
PredefinedLightData lightOffsets = PredefinedLightData::GetOffsets();
std::size_t lightUboAlignedSize = AlignPow2(lightOffsets.totalSize, SafeCast<std::size_t>(graphics->GetRenderDevice()->GetDeviceInfo().limits.minUniformBufferOffsetAlignment));
UploadPool& uploadPool = renderFrame.GetUploadPool();
for (const auto& renderableData : visibleRenderables)
{
BoundingVolumef renderableBoundingVolume(renderableData.instancedRenderable->GetAABB());
renderableBoundingVolume.Update(renderableData.worldInstance->GetWorldMatrix());
// Select lights
m_renderableLights.clear();
for (std::size_t lightIndex : visibleLights)
{
const Light* light = m_pipeline.RetrieveLight(lightIndex);
const BoundingVolumef& boundingVolume = light->GetBoundingVolume();
if (boundingVolume.Intersect(renderableBoundingVolume.aabb))
{
float contributionScore = light->ComputeContributionScore(renderableBoundingVolume);
m_renderableLights.push_back({ light, lightIndex, contributionScore });
}
}
// Sort lights
std::sort(m_renderableLights.begin(), m_renderableLights.end(), [&](const RenderableLight& lhs, const RenderableLight& rhs)
{
return lhs.contributionScore < rhs.contributionScore;
});
std::size_t lightCount = std::min(m_renderableLights.size(), MaxLightCountPerDraw);
LightKey lightKey;
lightKey.fill(nullptr);
for (std::size_t i = 0; i < lightCount; ++i)
lightKey[i] = m_renderableLights[i].light;
RenderBufferView lightUboView;
auto it = m_lightBufferPerLights.find(lightKey);
if (it == m_lightBufferPerLights.end())
{
// Prepare light ubo upload
// Find light ubo
LightDataUbo* targetLightData = nullptr;
for (auto& lightUboData : m_lightDataBuffers)
{
if (lightUboData.offset + lightUboAlignedSize <= lightUboData.renderBuffer->GetSize())
{
targetLightData = &lightUboData;
break;
}
}
if (!targetLightData)
{
// Make a new light UBO
auto& lightUboData = m_lightDataBuffers.emplace_back();
// Reuse from pool if possible
if (!m_lightUboPool->lightUboBuffers.empty())
{
lightUboData.renderBuffer = m_lightUboPool->lightUboBuffers.back();
m_lightUboPool->lightUboBuffers.pop_back();
}
else
lightUboData.renderBuffer = graphics->GetRenderDevice()->InstantiateBuffer(BufferType::Uniform, 256 * lightUboAlignedSize, BufferUsage::DeviceLocal | BufferUsage::Dynamic | BufferUsage::Write);
targetLightData = &lightUboData;
}
assert(targetLightData);
if (!targetLightData->allocation)
targetLightData->allocation = &uploadPool.Allocate(targetLightData->renderBuffer->GetSize());
void* lightDataPtr = static_cast<UInt8*>(targetLightData->allocation->mappedPtr) + targetLightData->offset;
AccessByOffset<UInt32&>(lightDataPtr, lightOffsets.lightCountOffset) = SafeCast<UInt32>(lightCount);
UInt8* lightPtr = static_cast<UInt8*>(lightDataPtr) + lightOffsets.lightsOffset;
for (std::size_t i = 0; i < lightCount; ++i)
{
m_renderableLights[i].light->FillLightData(lightPtr);
lightPtr += lightOffsets.lightSize;
}
// Associate render element with light ubo
lightUboView = RenderBufferView(targetLightData->renderBuffer.get(), targetLightData->offset, lightUboAlignedSize);
targetLightData->offset += lightUboAlignedSize;
m_lightBufferPerLights.emplace(lightKey, lightUboView);
}
else
lightUboView = it->second;
InstancedRenderable::ElementData elementData{
&renderableData.scissorBox,
renderableData.skeletonInstance,
renderableData.worldInstance
};
std::size_t previousCount = m_renderElements.size();
renderableData.instancedRenderable->BuildElement(m_elementRegistry, elementData, m_forwardPassIndex, m_renderElements);
for (std::size_t i = previousCount; i < m_renderElements.size(); ++i)
{
const RenderElement* element = m_renderElements[i].GetElement();
LightPerElementData perElementData;
perElementData.lightCount = lightCount;
perElementData.lightUniformBuffer = lightUboView;
for (std::size_t j = 0; j < lightCount; ++j)
perElementData.shadowMaps[j] = m_pipeline.RetrieveLightShadowmap(m_renderableLights[j].lightIndex);
m_lightPerRenderElement.emplace(element, perElementData);
}
}
m_renderQueueRegistry.Clear();
m_renderQueue.Clear();
for (const auto& renderElement : m_renderElements)
{
renderElement->Register(m_renderQueueRegistry);
@ -180,25 +71,8 @@ namespace Nz
m_renderQueueRegistry.Finalize();
renderFrame.Execute([&](CommandBufferBuilder& builder)
{
builder.BeginDebugRegion("Light UBO Update", Color::Yellow());
{
for (auto& lightUboData : m_lightDataBuffers)
{
if (!lightUboData.allocation)
continue;
builder.CopyBuffer(*lightUboData.allocation, RenderBufferView(lightUboData.renderBuffer.get(), 0, lightUboData.offset));
}
builder.PostTransferBarrier();
}
builder.EndDebugRegion();
}, QueueType::Transfer);
m_lastVisibilityHash = visibilityHash;
m_rebuildElements = true;
InvalidateElements();
}
// TODO: Don't sort every frame if no material pass requires distance sorting
@ -207,6 +81,8 @@ namespace Nz
return element->ComputeSortingScore(frustum, m_renderQueueRegistry);
});
PrepareLights(renderFrame, frustum, visibleLights);
if (m_rebuildElements)
{
m_elementRegistry.ForEachElementRenderer([&](std::size_t elementType, ElementRenderer& elementRenderer)
@ -222,41 +98,10 @@ namespace Nz
const auto& viewerInstance = m_viewer->GetViewerInstance();
auto& lightPerRenderElement = m_lightPerRenderElement;
m_elementRegistry.ProcessRenderQueue(m_renderQueue, [&](std::size_t elementType, const Pointer<const RenderElement>* elements, std::size_t elementCount)
{
ElementRenderer& elementRenderer = m_elementRegistry.GetElementRenderer(elementType);
m_renderStates.clear();
m_renderStates.reserve(elementCount);
for (std::size_t i = 0; i < elementCount; ++i)
{
auto it = lightPerRenderElement.find(elements[i]);
assert(it != lightPerRenderElement.end());
const LightPerElementData& lightData = it->second;
auto& renderStates = m_renderStates.emplace_back();
renderStates.lightData = lightData.lightUniformBuffer;
for (std::size_t j = 0; j < lightData.lightCount; ++j)
{
const Texture* texture = lightData.shadowMaps[j];
if (!texture)
continue;
if (texture->GetType() == ImageType::E2D)
renderStates.shadowMaps2D[j] = texture;
else
{
assert(texture->GetType() == ImageType::Cubemap);
renderStates.shadowMapsCube[j] = texture;
}
}
}
elementRenderer.Prepare(viewerInstance, *m_elementRendererData[elementType], renderFrame, elementCount, elements, m_renderStates.data());
elementRenderer.Prepare(viewerInstance, *m_elementRendererData[elementType], renderFrame, elementCount, elements, SparsePtr(&m_renderState, 0));
});
m_elementRegistry.ForEachElementRenderer([&](std::size_t elementType, ElementRenderer& elementRenderer)
@ -342,4 +187,165 @@ namespace Nz
m_materialInstances.erase(it);
}
}
void ForwardPipelinePass::OnTransfer(RenderFrame& renderFrame, CommandBufferBuilder& builder)
{
assert(m_pendingLightUploadAllocation);
builder.CopyBuffer(*m_pendingLightUploadAllocation, RenderBufferView(m_lightDataBuffer.get()));
m_pendingLightUploadAllocation = nullptr;
}
void ForwardPipelinePass::PrepareDirectionalLights(void* lightMemory)
{
std::size_t lightCount = std::min(m_directionalLights.size(), PredefinedLightData::MaxLightCount);
AccessByOffset<UInt32&>(lightMemory, PredefinedLightOffsets.directionalLightCountOffset) = SafeCast<UInt32>(lightCount);
for (std::size_t i = 0; i < lightCount; ++i)
{
UInt8* basePtr = static_cast<UInt8*>(lightMemory) + PredefinedLightOffsets.directionalLightsOffset + PredefinedDirectionalLightOffsets.totalSize * i;
const DirectionalLight* light = m_directionalLights[i].light;
const Color& lightColor = light->GetColor();
AccessByOffset<Vector3f&>(basePtr, PredefinedDirectionalLightOffsets.colorOffset) = Vector3f(lightColor.r, lightColor.g, lightColor.b);
AccessByOffset<Vector3f&>(basePtr, PredefinedDirectionalLightOffsets.directionOffset) = light->GetDirection();
AccessByOffset<float&>(basePtr, PredefinedDirectionalLightOffsets.ambientFactorOffset) = light->GetAmbientFactor();
AccessByOffset<float&>(basePtr, PredefinedDirectionalLightOffsets.diffuseFactorOffset) = light->GetDiffuseFactor();
AccessByOffset<Vector2f&>(basePtr, PredefinedDirectionalLightOffsets.invShadowMapSizeOffset) = (light->IsShadowCaster()) ? Vector2f(1.f / light->GetShadowMapSize()) : Vector2f(-1.f, -1.f);
// Shadowmap handling
const Texture* shadowmap = m_pipeline.RetrieveLightShadowmap(m_directionalLights[i].lightIndex, m_viewer);
if (shadowmap)
{
const DirectionalLightShadowData* shadowData = SafeCast<const DirectionalLightShadowData*>(m_pipeline.RetrieveLightShadowData(m_directionalLights[i].lightIndex));
float* cascadeFarPlanes = AccessByOffset<float*>(basePtr, PredefinedDirectionalLightOffsets.cascadeFarPlanesOffset);
Matrix4f* cascadeViewProj = AccessByOffset<Matrix4f*>(basePtr, PredefinedDirectionalLightOffsets.cascadeViewProjMatricesOffset);
shadowData->GetCascadeData(m_viewer, SparsePtr<float>(cascadeFarPlanes, 4*sizeof(float)), SparsePtr(cascadeViewProj));
AccessByOffset<UInt32&>(basePtr, PredefinedDirectionalLightOffsets.cascadeCountOffset) = SafeCast<UInt32>(shadowData->GetCascadeCount());
}
if (m_renderState.shadowMapsDirectional[i] != shadowmap)
{
m_renderState.shadowMapsDirectional[i] = shadowmap;
InvalidateElements();
}
}
}
void ForwardPipelinePass::PreparePointLights(void* lightMemory)
{
std::size_t lightCount = std::min(m_pointLights.size(), PredefinedLightData::MaxLightCount);
AccessByOffset<UInt32&>(lightMemory, PredefinedLightOffsets.pointLightCountOffset) = SafeCast<UInt32>(lightCount);
for (std::size_t i = 0; i < lightCount; ++i)
{
UInt8* basePtr = static_cast<UInt8*>(lightMemory) + PredefinedLightOffsets.pointLightsOffset + PredefinedPointLightOffsets.totalSize * i;
const PointLight* light = m_pointLights[i].light;
const Color& lightColor = light->GetColor();
AccessByOffset<Vector3f&>(basePtr, PredefinedPointLightOffsets.colorOffset) = Vector3f(lightColor.r, lightColor.g, lightColor.b);
AccessByOffset<Vector3f&>(basePtr, PredefinedPointLightOffsets.positionOffset) = light->GetPosition();
AccessByOffset<Vector2f&>(basePtr, PredefinedPointLightOffsets.invShadowMapSizeOffset) = (light->IsShadowCaster()) ? Vector2f(1.f / light->GetShadowMapSize()) : Vector2f(-1.f, -1.f);
AccessByOffset<float&>(basePtr, PredefinedPointLightOffsets.ambientFactorOffset) = light->GetAmbientFactor();
AccessByOffset<float&>(basePtr, PredefinedPointLightOffsets.diffuseFactorOffset) = light->GetDiffuseFactor();
AccessByOffset<float&>(basePtr, PredefinedPointLightOffsets.radiusOffset) = light->GetRadius();
AccessByOffset<float&>(basePtr, PredefinedPointLightOffsets.invRadiusOffset) = light->GetInvRadius();
// Shadowmap handling
const Texture* shadowmap = m_pipeline.RetrieveLightShadowmap(m_pointLights[i].lightIndex, m_viewer);
if (m_renderState.shadowMapsPoint[i] != shadowmap)
{
m_renderState.shadowMapsPoint[i] = shadowmap;
InvalidateElements();
}
}
}
void ForwardPipelinePass::PrepareSpotLights(void* lightMemory)
{
std::size_t lightCount = std::min(m_spotLights.size(), PredefinedLightData::MaxLightCount);
AccessByOffset<UInt32&>(lightMemory, PredefinedLightOffsets.spotLightCountOffset) = SafeCast<UInt32>(lightCount);
for (std::size_t i = 0; i < lightCount; ++i)
{
UInt8* basePtr = static_cast<UInt8*>(lightMemory) + PredefinedLightOffsets.spotLightsOffset + PredefinedSpotLightOffsets.totalSize * i;
const SpotLight* light = m_spotLights[i].light;
const Color& lightColor = light->GetColor();
AccessByOffset<Vector3f&>(basePtr, PredefinedSpotLightOffsets.colorOffset) = Vector3f(lightColor.r, lightColor.g, lightColor.b);
AccessByOffset<Vector3f&>(basePtr, PredefinedSpotLightOffsets.directionOffset) = light->GetDirection();
AccessByOffset<Vector3f&>(basePtr, PredefinedSpotLightOffsets.positionOffset) = light->GetPosition();
AccessByOffset<Vector2f&>(basePtr, PredefinedSpotLightOffsets.invShadowMapSizeOffset) = (light->IsShadowCaster()) ? Vector2f(1.f / light->GetShadowMapSize()) : Vector2f(-1.f, -1.f);
AccessByOffset<float&>(basePtr, PredefinedSpotLightOffsets.ambientFactorOffset) = light->GetAmbientFactor();
AccessByOffset<float&>(basePtr, PredefinedSpotLightOffsets.diffuseFactorOffset) = light->GetDiffuseFactor();
AccessByOffset<float&>(basePtr, PredefinedSpotLightOffsets.innerAngleOffset) = light->GetInnerAngleCos();
AccessByOffset<float&>(basePtr, PredefinedSpotLightOffsets.outerAngleOffset) = light->GetOuterAngleCos();
AccessByOffset<float&>(basePtr, PredefinedSpotLightOffsets.invRadiusOffset) = light->GetInvRadius();
AccessByOffset<Matrix4f&>(basePtr, PredefinedSpotLightOffsets.viewProjMatrixOffset) = light->GetViewProjMatrix();
// Shadowmap handling
const Texture* shadowmap = m_pipeline.RetrieveLightShadowmap(m_spotLights[i].lightIndex, m_viewer);
if (m_renderState.shadowMapsSpot[i] != shadowmap)
{
m_renderState.shadowMapsSpot[i] = shadowmap;
InvalidateElements();
}
}
}
void ForwardPipelinePass::PrepareLights(RenderFrame& renderFrame, const Frustumf& frustum, const Bitset<UInt64>& visibleLights)
{
// Select lights
m_directionalLights.clear();
m_pointLights.clear();
m_spotLights.clear();
for (std::size_t lightIndex = visibleLights.FindFirst(); lightIndex != visibleLights.npos; lightIndex = visibleLights.FindNext(lightIndex))
{
const Light* light = m_pipeline.RetrieveLight(lightIndex);
switch (light->GetLightType())
{
case UnderlyingCast(BasicLightType::Directional):
m_directionalLights.push_back({ SafeCast<const DirectionalLight*>(light), lightIndex, 0.f });
break;
case UnderlyingCast(BasicLightType::Point):
m_pointLights.push_back({ SafeCast<const PointLight*>(light), lightIndex, light->ComputeContributionScore(frustum) });
break;
case UnderlyingCast(BasicLightType::Spot):
m_spotLights.push_back({ SafeCast<const SpotLight*>(light), lightIndex, light->ComputeContributionScore(frustum) });
break;
}
}
// Sort lights
std::sort(m_pointLights.begin(), m_pointLights.end(), [&](const RenderableLight<PointLight>& lhs, const RenderableLight<PointLight>& rhs)
{
return lhs.contributionScore < rhs.contributionScore;
});
std::sort(m_spotLights.begin(), m_spotLights.end(), [&](const RenderableLight<SpotLight>& lhs, const RenderableLight<SpotLight>& rhs)
{
return lhs.contributionScore < rhs.contributionScore;
});
UploadPool& uploadPool = renderFrame.GetUploadPool();
auto& lightAllocation = uploadPool.Allocate(m_lightDataBuffer->GetSize());
PrepareDirectionalLights(lightAllocation.mappedPtr);
PreparePointLights(lightAllocation.mappedPtr);
PrepareSpotLights(lightAllocation.mappedPtr);
m_pendingLightUploadAllocation = &lightAllocation;
m_pipeline.QueueTransfer(this);
}
}

View File

@ -240,6 +240,7 @@ namespace Nz
{
std::size_t depthPassIndex = m_materialPassRegistry.GetPassIndex("DepthPass");
std::size_t shadowPassIndex = m_materialPassRegistry.GetPassIndex("ShadowPass");
std::size_t distanceShadowPassIndex = m_materialPassRegistry.GetPassIndex("DistanceShadowPass");
std::size_t forwardPassIndex = m_materialPassRegistry.GetPassIndex("ForwardPass");
// BasicMaterial
@ -257,9 +258,12 @@ namespace Nz
settings.AddPass(depthPassIndex, depthPass);
MaterialPass shadowPass = depthPass;
shadowPass.states.faceCulling = FaceCulling::Front;
settings.AddPass(shadowPassIndex, shadowPass);
MaterialPass distanceShadowPass = shadowPass;
distanceShadowPass.options[CRC32("DistanceDepth")] = true;
settings.AddPass(distanceShadowPassIndex, distanceShadowPass);
m_defaultMaterials.materials[MaterialType::Basic].material = std::make_shared<Material>(std::move(settings), "BasicMaterial");
}
@ -279,9 +283,12 @@ namespace Nz
settings.AddPass(depthPassIndex, depthPass);
MaterialPass shadowPass = depthPass;
shadowPass.states.faceCulling = FaceCulling::Front;
settings.AddPass(shadowPassIndex, shadowPass);
MaterialPass distanceShadowPass = shadowPass;
distanceShadowPass.options[CRC32("DistanceDepth")] = true;
settings.AddPass(distanceShadowPassIndex, distanceShadowPass);
m_defaultMaterials.materials[MaterialType::PhysicallyBased].material = std::make_shared<Material>(std::move(settings), "PhysicallyBasedMaterial");
}
@ -301,12 +308,12 @@ namespace Nz
settings.AddPass(depthPassIndex, depthPass);
MaterialPass shadowPass = depthPass;
shadowPass.states.faceCulling = FaceCulling::Front;
shadowPass.states.depthBias = true;
shadowPass.states.depthBiasConstantFactor = 0.005f;
shadowPass.states.depthBiasSlopeFactor = 0.05f;
settings.AddPass(shadowPassIndex, shadowPass);
MaterialPass distanceShadowPass = shadowPass;
distanceShadowPass.options[CRC32("DistanceDepth")] = true;
settings.AddPass(distanceShadowPassIndex, distanceShadowPass);
m_defaultMaterials.materials[MaterialType::Phong].material = std::make_shared<Material>(std::move(settings), "PhongMaterial");
}
@ -399,6 +406,7 @@ namespace Nz
m_materialPassRegistry.RegisterPass("ForwardPass");
m_materialPassRegistry.RegisterPass("DepthPass");
m_materialPassRegistry.RegisterPass("ShadowPass");
m_materialPassRegistry.RegisterPass("DistanceShadowPass");
}
void Graphics::RegisterShaderModules()

View File

@ -8,4 +8,12 @@
namespace Nz
{
LightShadowData::~LightShadowData() = default;
void LightShadowData::RegisterViewer(const AbstractViewer* /*viewer*/)
{
}
void LightShadowData::UnregisterViewer(const AbstractViewer* /*viewer*/)
{
}
}

View File

@ -37,6 +37,7 @@ namespace Nz
options.partialSanitization = true;
options.moduleResolver = graphics->GetShaderModuleResolver();
options.optionValues[CRC32("MaxLightCount")] = SafeCast<UInt32>(PredefinedLightData::MaxLightCount);
options.optionValues[CRC32("MaxLightCascadeCount")] = SafeCast<UInt32>(PredefinedDirectionalLightData::MaxLightCascadeCount);
options.optionValues[CRC32("MaxJointCount")] = SafeCast<UInt32>(PredefinedSkeletalData::MaxMatricesCount);
nzsl::Ast::ModulePtr sanitizedModule = nzsl::Ast::Sanitize(*referenceModule, options);
@ -94,11 +95,14 @@ namespace Nz
if (auto it = block->uniformBlocks.find("ViewerData"); it != block->uniformBlocks.end())
m_engineShaderBindings[EngineShaderBinding::ViewerDataUbo] = it->second.bindingIndex;
if (auto it = block->samplers.find("ShadowMaps2D"); it != block->samplers.end())
m_engineShaderBindings[EngineShaderBinding::Shadowmap2D] = it->second.bindingIndex;
if (auto it = block->samplers.find("ShadowMapsDirectional"); it != block->samplers.end())
m_engineShaderBindings[EngineShaderBinding::ShadowmapDirectional] = it->second.bindingIndex;
if (auto it = block->samplers.find("ShadowMapsCube"); it != block->samplers.end())
m_engineShaderBindings[EngineShaderBinding::ShadowmapCube] = it->second.bindingIndex;
if (auto it = block->samplers.find("ShadowMapsPoint"); it != block->samplers.end())
m_engineShaderBindings[EngineShaderBinding::ShadowmapPoint] = it->second.bindingIndex;
if (auto it = block->samplers.find("ShadowMapsSpot"); it != block->samplers.end())
m_engineShaderBindings[EngineShaderBinding::ShadowmapSpot] = it->second.bindingIndex;
if (auto it = block->uniformBlocks.find("SkeletalData"); it != block->uniformBlocks.end())
m_engineShaderBindings[EngineShaderBinding::SkeletalDataUbo] = it->second.bindingIndex;

View File

@ -3,33 +3,20 @@
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/PointLight.hpp>
#include <Nazara/Graphics/Enums.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Graphics/PointLightShadowData.hpp>
#include <Nazara/Graphics/PredefinedShaderStructs.hpp>
#include <Nazara/Math/Vector2.hpp>
#include <Nazara/Math/Vector3.hpp>
#include <Nazara/Math/Vector4.hpp>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
float PointLight::ComputeContributionScore(const BoundingVolumef& boundingVolume) const
float PointLight::ComputeContributionScore(const Frustumf& viewerFrustum) const
{
// TODO: take luminosity/radius into account
return Vector3f::SquaredDistance(m_position, boundingVolume.aabb.GetCenter());
return viewerFrustum.GetPlane(FrustumPlane::Near).SignedDistance(m_position);
}
void PointLight::FillLightData(void* data) const
bool PointLight::FrustumCull(const Frustumf& viewerFrustum) const
{
auto lightOffset = PredefinedLightData::GetOffsets();
AccessByOffset<UInt32&>(data, lightOffset.lightMemberOffsets.type) = UnderlyingCast(BasicLightType::Point);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.color) = Vector4f(m_color.r, m_color.g, m_color.b, m_color.a);
AccessByOffset<Vector2f&>(data, lightOffset.lightMemberOffsets.factor) = Vector2f(m_ambientFactor, m_diffuseFactor);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.parameter1) = Vector4f(m_position.x, m_position.y, m_position.z, 0.f);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.parameter2) = Vector4f(m_radius, m_invRadius, 0.f, 0.f);
AccessByOffset<Vector2f&>(data, lightOffset.lightMemberOffsets.shadowMapSize) = (IsShadowCaster()) ? Vector2f(1.f / GetShadowMapSize()) : Vector2f(-1.f, -1.f);
return viewerFrustum.Intersect(Spheref(m_position, m_radius)) != IntersectionSide::Outside;
}
std::unique_ptr<LightShadowData> PointLight::InstanciateShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry) const

View File

@ -61,7 +61,7 @@ namespace Nz
}
});
std::size_t shadowPassIndex = Graphics::Instance()->GetMaterialPassRegistry().GetPassIndex("ShadowPass");
std::size_t shadowPassIndex = Graphics::Instance()->GetMaterialPassRegistry().GetPassIndex("DistanceShadowPass");
constexpr float zNear = 0.01f;
@ -93,8 +93,10 @@ namespace Nz
});
}
void PointLightShadowData::PrepareRendering(RenderFrame& renderFrame)
void PointLightShadowData::PrepareRendering(RenderFrame& renderFrame, [[maybe_unused]] const AbstractViewer* viewer)
{
assert(viewer == nullptr);
for (DirectionData& direction : m_directions)
{
const Matrix4f& viewProjMatrix = direction.viewer.GetViewerInstance().GetViewProjMatrix();
@ -114,8 +116,10 @@ namespace Nz
direction.depthPass->RegisterMaterialInstance(matInstance);
}
void PointLightShadowData::RegisterPassInputs(FramePass& pass)
void PointLightShadowData::RegisterPassInputs(FramePass& pass, [[maybe_unused]] const AbstractViewer* viewer)
{
assert(viewer == nullptr);
std::size_t cubeInputIndex = pass.AddInput(m_cubeAttachmentIndex);
pass.SetInputLayout(cubeInputIndex, TextureLayout::ColorInput);
@ -123,8 +127,10 @@ namespace Nz
pass.AddInput(direction.attachmentIndex);
}
void PointLightShadowData::RegisterToFrameGraph(FrameGraph& frameGraph)
void PointLightShadowData::RegisterToFrameGraph(FrameGraph& frameGraph, [[maybe_unused]] const AbstractViewer* viewer)
{
assert(viewer == nullptr);
UInt32 shadowMapSize = m_light.GetShadowMapSize();
m_cubeAttachmentIndex = frameGraph.AddAttachmentCube({
@ -142,7 +148,7 @@ namespace Nz
}
}
const Texture* PointLightShadowData::RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph) const
const Texture* PointLightShadowData::RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph, const AbstractViewer* /*viewer*/) const
{
return bakedGraph.GetAttachmentTexture(m_cubeAttachmentIndex).get();
}

View File

@ -1,26 +1,61 @@
[nzsl_version("1.0")]
module Engine.LightData;
option MaxLightCascadeCount: u32 = u32(4); //< FIXME: Fix integral value types
option MaxLightCount: u32 = u32(3); //< FIXME: Fix integral value types
[export]
[layout(std140)]
struct Light
struct DirectionalLight
{
type: i32,
color: vec4[f32],
factor: vec2[f32],
parameter1: vec4[f32],
parameter2: vec4[f32],
parameter3: vec4[f32],
invShadowMapSize: vec2[f32],
viewProjMatrix: mat4[f32]
color: vec3[f32],
direction: vec3[f32],
invShadowMapSize: vec2[f32],
ambientFactor: f32,
diffuseFactor: f32,
cascadeCount: u32,
cascadeDistances: array[f32, MaxLightCascadeCount],
viewProjMatrices: array[mat4[f32], MaxLightCascadeCount],
}
[export]
[layout(std140)]
struct PointLight
{
color: vec3[f32],
position: vec3[f32],
invShadowMapSize: vec2[f32],
ambientFactor: f32,
diffuseFactor: f32,
radius: f32,
invRadius: f32,
}
[export]
[layout(std140)]
struct SpotLight
{
color: vec3[f32],
direction: vec3[f32],
position: vec3[f32],
invShadowMapSize: vec2[f32],
ambientFactor: f32,
diffuseFactor: f32,
innerAngle: f32,
outerAngle: f32,
invRadius: f32,
viewProjMatrix: mat4[f32],
}
[export]
[layout(std140)]
struct LightData
{
lights: array[Light, MaxLightCount],
lightCount: u32,
directionalLights: array[DirectionalLight, MaxLightCount],
pointLights: array[PointLight, MaxLightCount],
spotLights: array[SpotLight, MaxLightCount],
directionalLightCount: u32,
pointLightCount: u32,
spotLightCount: u32,
}

View File

@ -10,6 +10,7 @@ import SkinLinearPosition, SkinLinearPositionNormal from Engine.SkinningLinear;
// Pass-specific options
option DepthPass: bool = false;
option DistanceDepth: bool = false;
// Basic material options
option HasBaseColorTexture: bool = false;
@ -39,6 +40,7 @@ option VertexUvLoc: i32 = -1;
option VertexJointIndicesLoc: i32 = -1;
option VertexJointWeightsLoc: i32 = -1;
option MaxLightCascadeCount: u32 = u32(4); //< FIXME: Fix integral value types
option MaxLightCount: u32 = u32(3); //< FIXME: Fix integral value types
const HasNormal = (VertexNormalLoc >= 0);
@ -98,8 +100,9 @@ external
[tag("ViewerData")] viewerData: uniform[ViewerData],
[tag("SkeletalData")] skeletalData: uniform[SkeletalData],
[tag("LightData")] lightData: uniform[LightData],
[tag("ShadowMaps2D")] shadowMaps2D: array[depth_sampler2D[f32], MaxLightCount],
[tag("ShadowMapsCube")] shadowMapsCube: array[sampler_cube[f32], MaxLightCount]
[tag("ShadowMapsDirectional")] shadowMapsDirectional: array[depth_sampler2D_array[f32], MaxLightCount],
[tag("ShadowMapsPoint")] shadowMapsPoint: array[sampler_cube[f32], MaxLightCount],
[tag("ShadowMapsSpot")] shadowMapsSpot: array[depth_sampler2D[f32], MaxLightCount],
}
struct VertToFrag
@ -109,14 +112,15 @@ struct VertToFrag
[location(2), cond(HasColor)] color: vec4[f32],
[location(3), cond(HasNormal)] normal: vec3[f32],
[location(4), cond(HasNormalMapping)] tangent: vec3[f32],
[location(5), cond(HasLighting)] lightProjPos: array[vec4[f32], MaxLightCount],
[builtin(position)] position: vec4[f32],
[builtin(frag_coord)] fragcoord: vec4[f32]
}
// Fragment stage
struct FragOut
{
[location(0)] RenderTarget0: vec4[f32]
[location(0)] RenderTarget0: vec4[f32],
[builtin(frag_depth), cond(DistanceDepth)] fragdepth: f32,
}
fn LinearizeDepth(depth: f32, zNear: f32, zFar: f32) -> f32
@ -124,8 +128,7 @@ fn LinearizeDepth(depth: f32, zNear: f32, zFar: f32) -> f32
return zNear * zFar / (zFar + depth * (zNear - zFar));
}
[entry(frag), cond(!DepthPass || AlphaTest)]
fn main(input: VertToFrag) -> FragOut
fn ComputeColor(input: VertToFrag) -> vec4[f32]
{
let color = settings.BaseColor;
@ -141,6 +144,13 @@ fn main(input: VertToFrag) -> FragOut
const if (HasAlphaTexture)
color.w *= MaterialAlphaMap.Sample(input.uv).x;
return color;
}
[entry(frag), cond(!DepthPass)]
fn main(input: VertToFrag) -> FragOut
{
let color = ComputeColor(input);
const if (AlphaTest)
{
if (color.w < settings.AlphaThreshold)
@ -168,139 +178,177 @@ fn main(input: VertToFrag) -> FragOut
else
normal = normalize(input.normal);
for i in u32(0) -> lightData.lightCount
for lightIndex in u32(0) -> lightData.directionalLightCount
{
let light = lightData.lights[i];
let light = lightData.directionalLights[lightIndex];
let lightAmbientFactor = light.factor.x;
let lightDiffuseFactor = light.factor.y;
let lambert = max(dot(normal, -light.direction), 0.0);
// TODO: Add switch instruction
if (light.type == DirectionalLight)
let reflection = reflect(light.direction, normal);
let specFactor = max(dot(reflection, eyeVec), 0.0);
specFactor = pow(specFactor, settings.Shininess);
let shadowFactor = 1.0;
const if (EnableShadowMapping)
{
let lightDir = light.parameter1.xyz;
lightAmbient += light.color.rgb * lightAmbientFactor * settings.AmbientColor.rgb;
let lambert = max(dot(normal, -lightDir), 0.0);
lightDiffuse += lambert * light.color.rgb * lightDiffuseFactor;
let reflection = reflect(lightDir, normal);
let specFactor = max(dot(reflection, eyeVec), 0.0);
specFactor = pow(specFactor, settings.Shininess);
lightSpecular += specFactor * light.color.rgb;
}
else if (light.type == PointLight)
{
let lightPos = light.parameter1.xyz;
let lightRadius = light.parameter2.x;
let lightInvRadius = light.parameter2.y;
let lightToPos = input.worldPos - lightPos;
let dist = length(lightToPos);
let lightToPosNorm = lightToPos / max(dist, 0.0001);
let attenuationFactor = max(1.0 - dist * lightInvRadius, 0.0);
let lambert = max(dot(normal, -lightToPosNorm), 0.0);
let reflection = reflect(lightToPosNorm, normal);
let specFactor = max(dot(reflection, eyeVec), 0.0);
specFactor = pow(specFactor, settings.Shininess);
let shadowFactor = 1.0;
const if (EnableShadowMapping)
if (light.invShadowMapSize.x > 0.0)
{
if (light.invShadowMapSize.x > 0.0)
let fragPosViewSpace = viewerData.viewMatrix * vec4[f32](input.worldPos, 1.0);
let depthValue = abs(fragPosViewSpace.z);
let cascadeIndex = MaxLightCascadeCount;
for index in u32(0) -> light.cascadeCount
{
shadowFactor = 0.0;
if (depthValue < light.cascadeDistances[index])
{
cascadeIndex = index;
break;
}
}
let sampleDir = vec3[f32](lightToPosNorm.x, lightToPosNorm.y, -lightToPosNorm.z);
if (cascadeIndex >= light.cascadeCount)
cascadeIndex = light.cascadeCount - u32(1);
const sampleCount = 4;
const offset = 0.005;
let lightProjPos = light.viewProjMatrices[cascadeIndex] * vec4[f32](input.worldPos, 1.0);
let shadowCoords = lightProjPos.xyz / lightProjPos.w;
const invSampleCount = 1.0 / f32(sampleCount);
const start = vec3[f32](offset * 0.5, offset * 0.5, offset * 0.5);
const shadowContribution = 1.0 / f32(sampleCount * sampleCount * sampleCount);
// calculate bias (based on depth map resolution and slope)
let bias = max(0.05 * (1.0 - lambert), 0.005);
bias *= 1.0 / (light.cascadeDistances[cascadeIndex] * 0.05);
//shadowFactor = shadowMapsDirectional[lightIndex].SampleDepthComp(vec3[f32](shadowCoords.xy, f32(cascadeIndex)), shadowCoords.z).r;
shadowFactor = 0.0;
[unroll]
for x in -1 -> 2
{
[unroll]
for x in 0 -> sampleCount
for y in -1 -> 2
{
let coords = shadowCoords.xy + vec2[f32](f32(x), f32(y)) * light.invShadowMapSize;
shadowFactor += shadowMapsDirectional[lightIndex].SampleDepthComp(vec3[f32](coords, f32(cascadeIndex)), shadowCoords.z - bias).r;
}
}
shadowFactor /= 9.0;
}
}
lightAmbient += shadowFactor * light.color.rgb * light.ambientFactor * settings.AmbientColor.rgb;
lightDiffuse += shadowFactor * lambert * light.color.rgb * light.diffuseFactor;
lightSpecular += shadowFactor * specFactor * light.color.rgb;
}
for lightIndex in u32(0) -> lightData.pointLightCount
{
let light = lightData.pointLights[lightIndex];
let lightToPos = input.worldPos - light.position;
let dist = length(lightToPos);
let lightToPosNorm = lightToPos / max(dist, 0.0001);
let attenuationFactor = max(1.0 - dist * light.invRadius, 0.0);
let lambert = clamp(dot(normal, -lightToPosNorm), 0.0, 1.0);
let reflection = reflect(lightToPosNorm, normal);
let specFactor = max(dot(reflection, eyeVec), 0.0);
specFactor = pow(specFactor, settings.Shininess);
let shadowFactor = 1.0;
const if (EnableShadowMapping)
{
if (light.invShadowMapSize.x > 0.0)
{
shadowFactor = 0.0;
let sampleDir = vec3[f32](lightToPosNorm.x, lightToPosNorm.y, -lightToPosNorm.z);
const sampleCount = 4;
const offset = 0.005;
const invSampleCount = 1.0 / f32(sampleCount);
const start = vec3[f32](offset * 0.5, offset * 0.5, offset * 0.5);
const shadowContribution = 1.0 / f32(sampleCount * sampleCount * sampleCount);
let bias = 0.05;
[unroll]
for x in 0 -> sampleCount
{
[unroll]
for y in 0 -> sampleCount
{
[unroll]
for y in 0 -> sampleCount
for z in 0 -> sampleCount
{
[unroll]
for z in 0 -> sampleCount
{
let dirOffset = vec3[f32](f32(x), f32(y), f32(z)) * invSampleCount * offset - start;
let sampleDir = sampleDir + dirOffset;
let dirOffset = vec3[f32](f32(x), f32(y), f32(z)) * invSampleCount * offset - start;
let sampleDir = sampleDir + dirOffset;
let depth = shadowMapsCube[i].Sample(sampleDir).r;
depth = LinearizeDepth(depth, 0.01, lightRadius);
if (depth > dist)
shadowFactor += shadowContribution;
}
let closestDepth = shadowMapsPoint[lightIndex].Sample(sampleDir).r;
closestDepth *= light.radius;
if (closestDepth > dist - bias)
shadowFactor += shadowContribution;
}
}
}
}
lightAmbient += attenuationFactor * light.color.rgb * lightAmbientFactor * settings.AmbientColor.rgb;
lightDiffuse += shadowFactor * attenuationFactor * lambert * light.color.rgb * lightDiffuseFactor;
lightSpecular += shadowFactor * attenuationFactor * specFactor * light.color.rgb;
}
else if (light.type == SpotLight)
lightAmbient += attenuationFactor * light.color.rgb * light.ambientFactor * settings.AmbientColor.rgb;
lightDiffuse += shadowFactor * attenuationFactor * lambert * light.color.rgb * light.diffuseFactor;
lightSpecular += shadowFactor * attenuationFactor * specFactor * light.color.rgb;
}
for lightIndex in u32(0) -> lightData.spotLightCount
{
let light = lightData.spotLights[lightIndex];
let lightToPos = input.worldPos - light.position;
let dist = length(lightToPos);
let lightToPosNorm = lightToPos / max(dist, 0.0001);
let curAngle = dot(light.direction, lightToPosNorm);
let innerMinusOuterAngle = light.innerAngle - light.outerAngle;
let attenuationFactor = max(1.0 - dist * light.invRadius, 0.0);
attenuationFactor *= max((curAngle - light.outerAngle) / innerMinusOuterAngle, 0.0);
let lambert = clamp(dot(normal, -lightToPosNorm), 0.0, 1.0);
let reflection = reflect(lightToPosNorm, normal);
let specFactor = max(dot(reflection, eyeVec), 0.0);
specFactor = pow(specFactor, settings.Shininess);
let shadowFactor = 1.0;
const if (EnableShadowMapping)
{
let lightPos = light.parameter1.xyz;
let lightDir = light.parameter2.xyz;
let lightInvRadius = light.parameter1.w;
let lightInnerAngle = light.parameter3.x;
let lightOuterAngle = light.parameter3.y;
let lightToPos = input.worldPos - lightPos;
let dist = length(lightToPos);
let lightToPosNorm = lightToPos / max(dist, 0.0001);
let curAngle = dot(lightDir, lightToPosNorm);
let innerMinusOuterAngle = lightInnerAngle - lightOuterAngle;
let attenuationFactor = max(1.0 - dist * lightInvRadius, 0.0);
attenuationFactor *= max((curAngle - lightOuterAngle) / innerMinusOuterAngle, 0.0);
let lambert = max(dot(normal, -lightToPosNorm), 0.0);
let reflection = reflect(lightToPosNorm, normal);
let specFactor = max(dot(reflection, eyeVec), 0.0);
specFactor = pow(specFactor, settings.Shininess);
let shadowFactor = 1.0;
const if (EnableShadowMapping)
if (light.invShadowMapSize.x > 0.0)
{
if (light.invShadowMapSize.x > 0.0)
{
let shadowCoords = input.lightProjPos[i].xyz / input.lightProjPos[i].w;
shadowFactor = 0.0;
[unroll]
for x in -1 -> 2
{
[unroll]
for y in -1 -> 2
{
let coords = shadowCoords.xy + vec2[f32](f32(x), f32(y)) * light.invShadowMapSize;
shadowFactor += shadowMaps2D[i].SampleDepthComp(coords, shadowCoords.z).r;
}
}
shadowFactor /= 9.0;
}
}
let bias = 0.0005 * tan(acos(lambert));
bias = clamp(bias, 0.0, 0.01);
lightAmbient += attenuationFactor * light.color.rgb * lightAmbientFactor * settings.AmbientColor.rgb;
lightDiffuse += shadowFactor * attenuationFactor * lambert * light.color.rgb * lightDiffuseFactor;
lightSpecular += shadowFactor * attenuationFactor * specFactor * light.color.rgb;
let lightProjPos = light.viewProjMatrix * vec4[f32](input.worldPos, 1.0);
let shadowCoords = lightProjPos.xyz / lightProjPos.w;
shadowFactor = 0.0;
[unroll]
for x in -1 -> 2
{
[unroll]
for y in -1 -> 2
{
let coords = shadowCoords.xy + vec2[f32](f32(x), f32(y)) * light.invShadowMapSize;
shadowFactor += shadowMapsSpot[lightIndex].SampleDepthComp(coords, shadowCoords.z - bias).r;
}
}
shadowFactor /= 9.0;
}
}
lightAmbient += attenuationFactor * light.color.rgb * light.ambientFactor * settings.AmbientColor.rgb;
lightDiffuse += shadowFactor * attenuationFactor * lambert * light.color.rgb * light.diffuseFactor;
lightSpecular += shadowFactor * attenuationFactor * specFactor * light.color.rgb;
}
lightSpecular *= settings.SpecularColor.rgb;
@ -310,22 +358,50 @@ fn main(input: VertToFrag) -> FragOut
let lightColor = lightAmbient + lightDiffuse + lightSpecular;
let output: FragOut;
output.RenderTarget0 = vec4[f32](lightColor, 1.0) * color;
return output;
}
else
{
let output: FragOut;
output.RenderTarget0 = color;
return output;
color.rgb *= lightColor;
}
let output: FragOut;
output.RenderTarget0 = color;
return output;
}
// Dummy fragment shader (TODO: Add a way to delete stage?)
[entry(frag), cond(DepthPass && !AlphaTest)]
fn main() {}
// Shadow passes entries
[entry(frag), cond(DepthPass && DistanceDepth)]
[depth_write(replace)]
fn main(input: VertToFrag) -> FragOut
{
let color = ComputeColor(input);
const if (AlphaTest)
{
if (color.w < settings.AlphaThreshold)
discard;
}
let output: FragOut;
output.RenderTarget0 = ComputeColor(input);
let dist = distance(viewerData.eyePosition, input.worldPos);
output.fragdepth = dist / viewerData.farPlane;
return output;
}
[entry(frag), cond(DepthPass && AlphaTest && !DistanceDepth)]
fn main(input: VertToFrag) -> FragOut
{
let color = ComputeColor(input);
if (color.w < settings.AlphaThreshold)
discard;
let output: FragOut;
output.RenderTarget0 = color;
return output;
}
[entry(frag), cond(DepthPass && !AlphaTest && !DistanceDepth)]
fn main() {} //< dummy
// Vertex stage
struct VertIn
@ -432,7 +508,7 @@ fn main(input: VertIn) -> VertToFrag
output.worldPos = worldPosition.xyz;
output.position = viewerData.viewProjMatrix * worldPosition;
let rotationMatrix = transpose(inverse(mat3[f32](instanceData.worldMatrix)));
const if (HasNormal || HasNormalMapping) let rotationMatrix = transpose(inverse(mat3[f32](instanceData.worldMatrix)));
const if (HasColor)
output.color = input.color;
@ -446,11 +522,5 @@ fn main(input: VertIn) -> VertToFrag
const if (HasNormalMapping)
output.tangent = rotationMatrix * input.tangent;
const if (HasLighting)
{
for i in u32(0) -> lightData.lightCount
output.lightProjPos[i] = lightData.lights[i].viewProjMatrix * worldPosition;
}
return output;
}

View File

@ -170,7 +170,7 @@ fn main(input: VertToFrag) -> FragOut
let albedoFactor = albedo / Pi;
for i in u32(0) -> lightData.lightCount
/*for i in u32(0) -> lightData.lightCount
{
let light = lightData.lights[i];
@ -224,7 +224,7 @@ fn main(input: VertToFrag) -> FragOut
let NdotL = max(dot(normal, lightToPosNorm), 0.0);
lightRadiance += (diffuse * albedoFactor + specular) * radiance * NdotL;
}
}*/
let ambient = (0.03).rrr * albedo;

View File

@ -3,35 +3,37 @@
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/SpotLight.hpp>
#include <Nazara/Graphics/Enums.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Graphics/PredefinedShaderStructs.hpp>
#include <Nazara/Graphics/SpotLightShadowData.hpp>
#include <Nazara/Math/Vector2.hpp>
#include <Nazara/Math/Vector3.hpp>
#include <Nazara/Math/Vector4.hpp>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
float SpotLight::ComputeContributionScore(const BoundingVolumef& boundingVolume) const
float SpotLight::ComputeContributionScore(const Frustumf& viewerFrustum) const
{
// TODO: take luminosity/radius/direction into account
return Vector3f::SquaredDistance(m_position, boundingVolume.aabb.GetCenter());
// TODO: take luminosity/radius into account
return viewerFrustum.GetPlane(FrustumPlane::Near).SignedDistance(m_position);
}
void SpotLight::FillLightData(void* data) const
bool SpotLight::FrustumCull(const Frustumf& viewerFrustum) const
{
auto lightOffset = PredefinedLightData::GetOffsets();
// We need the radius of the projected circle depending on the distance
// Tangent = Opposite/Adjacent <=> Opposite = Adjacent * Tangent
float opposite = m_radius * m_outerAngleTan;
AccessByOffset<UInt32&>(data, lightOffset.lightMemberOffsets.type) = UnderlyingCast(BasicLightType::Spot);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.color) = Vector4f(m_color.r, m_color.g, m_color.b, m_color.a);
AccessByOffset<Vector2f&>(data, lightOffset.lightMemberOffsets.factor) = Vector2f(m_ambientFactor, m_diffuseFactor);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.parameter1) = Vector4f(m_position.x, m_position.y, m_position.z, m_invRadius);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.parameter2) = Vector4f(m_direction.x, m_direction.y, m_direction.z, 0.f);
AccessByOffset<Vector4f&>(data, lightOffset.lightMemberOffsets.parameter3) = Vector4f(m_innerAngleCos, m_outerAngleCos, 0.f, 0.f);
AccessByOffset<Vector2f&>(data, lightOffset.lightMemberOffsets.shadowMapSize) = (IsShadowCaster()) ? Vector2f(1.f / GetShadowMapSize()) : Vector2f(-1.f, -1.f);
AccessByOffset<Matrix4f&>(data, lightOffset.lightMemberOffsets.viewProjMatrix) = m_viewProjMatrix;
Vector3f base = Vector3f::Forward() * m_radius;
Vector3f lExtend = Vector3f::Left() * opposite;
Vector3f uExtend = Vector3f::Up() * opposite;
// Test five points against frustum
std::array<Vector3f, 5> points = {
m_position,
base + lExtend + uExtend,
base + lExtend - uExtend,
base - lExtend + uExtend,
base - lExtend - uExtend,
};
return viewerFrustum.Contains(points.data(), points.size());
}
std::unique_ptr<LightShadowData> SpotLight::InstanciateShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry) const

View File

@ -8,6 +8,7 @@
#include <Nazara/Graphics/FramePipeline.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Graphics/SpotLight.hpp>
#include <cassert>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
@ -54,8 +55,10 @@ namespace Nz
});
}
void SpotLightShadowData::PrepareRendering(RenderFrame& renderFrame)
{
void SpotLightShadowData::PrepareRendering(RenderFrame& renderFrame, [[maybe_unused]] const AbstractViewer* viewer)
{
assert(viewer == nullptr);
const Matrix4f& viewProjMatrix = m_viewer.GetViewerInstance().GetViewProjMatrix();
Frustumf frustum = Frustumf::Extract(viewProjMatrix);
@ -71,13 +74,17 @@ namespace Nz
m_depthPass->RegisterMaterialInstance(matInstance);
}
void SpotLightShadowData::RegisterPassInputs(FramePass& pass)
void SpotLightShadowData::RegisterPassInputs(FramePass& pass, [[maybe_unused]] const AbstractViewer* viewer)
{
assert(viewer == nullptr);
pass.AddInput(m_attachmentIndex);
}
void SpotLightShadowData::RegisterToFrameGraph(FrameGraph& frameGraph)
void SpotLightShadowData::RegisterToFrameGraph(FrameGraph& frameGraph, [[maybe_unused]] const AbstractViewer* viewer)
{
assert(viewer == nullptr);
UInt32 shadowMapSize = m_light.GetShadowMapSize();
m_attachmentIndex = frameGraph.AddAttachment({
@ -90,7 +97,7 @@ namespace Nz
m_depthPass->RegisterToFrameGraph(frameGraph, m_attachmentIndex);
}
const Nz::Texture* SpotLightShadowData::RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph) const
const Texture* SpotLightShadowData::RetrieveLightShadowmap(const BakedFrameGraph& bakedGraph, const AbstractViewer* /*viewer*/) const
{
return bakedGraph.GetAttachmentTexture(m_attachmentIndex).get();
}

View File

@ -56,7 +56,7 @@ namespace Nz
return std::make_unique<SpriteChainRendererData>();
}
void SpriteChainRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer<const RenderElement>* elements, const RenderStates* renderStates)
void SpriteChainRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer<const RenderElement>* elements, SparsePtr<const RenderStates> renderStates)
{
Graphics* graphics = Graphics::Instance();

View File

@ -24,7 +24,7 @@ namespace Nz
return std::make_unique<SubmeshRendererData>();
}
void SubmeshRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& /*currentFrame*/, std::size_t elementCount, const Pointer<const RenderElement>* elements, const RenderStates* renderStates)
void SubmeshRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& /*currentFrame*/, std::size_t elementCount, const Pointer<const RenderElement>* elements, SparsePtr<const RenderStates> renderStates)
{
Graphics* graphics = Graphics::Instance();
@ -55,6 +55,7 @@ namespace Nz
};
const auto& depthTexture2D = Graphics::Instance()->GetDefaultTextures().depthTextures[ImageType::E2D];
const auto& depthTexture2DArray = Graphics::Instance()->GetDefaultTextures().depthTextures[ImageType::E2D_Array];
const auto& depthTextureCube = Graphics::Instance()->GetDefaultTextures().depthTextures[ImageType::Cubemap];
const auto& whiteTexture2D = Graphics::Instance()->GetDefaultTextures().whiteTextures[ImageType::E2D];
const auto& defaultSampler = graphics->GetSamplerCache().Get({});
@ -129,7 +130,7 @@ namespace Nz
m_bindingCache.clear();
m_textureBindingCache.clear();
m_textureBindingCache.reserve(renderState.shadowMaps2D.size() + renderState.shadowMapsCube.size());
m_textureBindingCache.reserve(renderState.shadowMapsSpot.size() + renderState.shadowMapsDirectional.size() + renderState.shadowMapsPoint.size());
currentMaterialInstance->FillShaderBinding(m_bindingCache);
const Material& material = *currentMaterialInstance->GetParentMaterial();
@ -158,15 +159,15 @@ namespace Nz
};
}
if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::Shadowmap2D); bindingIndex != Material::InvalidBindingIndex)
if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::ShadowmapDirectional); bindingIndex != Material::InvalidBindingIndex)
{
std::size_t textureBindingBaseIndex = m_textureBindingCache.size();
for (std::size_t j = 0; j < renderState.shadowMaps2D.size(); ++j)
for (std::size_t j = 0; j < renderState.shadowMapsDirectional.size(); ++j)
{
const Texture* texture = renderState.shadowMaps2D[j];
const Texture* texture = renderState.shadowMapsDirectional[j];
if (!texture)
texture = depthTexture2D.get();
texture = depthTexture2DArray.get();
auto& textureEntry = m_textureBindingCache.emplace_back();
textureEntry.texture = texture;
@ -176,17 +177,17 @@ namespace Nz
auto& bindingEntry = m_bindingCache.emplace_back();
bindingEntry.bindingIndex = bindingIndex;
bindingEntry.content = ShaderBinding::SampledTextureBindings {
SafeCast<UInt32>(renderState.shadowMaps2D.size()), &m_textureBindingCache[textureBindingBaseIndex]
SafeCast<UInt32>(renderState.shadowMapsDirectional.size()), &m_textureBindingCache[textureBindingBaseIndex]
};
}
if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::ShadowmapCube); bindingIndex != Material::InvalidBindingIndex)
if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::ShadowmapPoint); bindingIndex != Material::InvalidBindingIndex)
{
std::size_t textureBindingBaseIndex = m_textureBindingCache.size();
for (std::size_t j = 0; j < renderState.shadowMapsCube.size(); ++j)
for (std::size_t j = 0; j < renderState.shadowMapsPoint.size(); ++j)
{
const Texture* texture = renderState.shadowMapsCube[j];
const Texture* texture = renderState.shadowMapsPoint[j];
if (!texture)
texture = depthTextureCube.get();
@ -198,7 +199,29 @@ namespace Nz
auto& bindingEntry = m_bindingCache.emplace_back();
bindingEntry.bindingIndex = bindingIndex;
bindingEntry.content = ShaderBinding::SampledTextureBindings {
SafeCast<UInt32>(renderState.shadowMapsCube.size()), &m_textureBindingCache[textureBindingBaseIndex]
SafeCast<UInt32>(renderState.shadowMapsPoint.size()), &m_textureBindingCache[textureBindingBaseIndex]
};
}
if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::ShadowmapSpot); bindingIndex != Material::InvalidBindingIndex)
{
std::size_t textureBindingBaseIndex = m_textureBindingCache.size();
for (std::size_t j = 0; j < renderState.shadowMapsSpot.size(); ++j)
{
const Texture* texture = renderState.shadowMapsSpot[j];
if (!texture)
texture = depthTexture2D.get();
auto& textureEntry = m_textureBindingCache.emplace_back();
textureEntry.texture = texture;
textureEntry.sampler = shadowSampler.get();
}
auto& bindingEntry = m_bindingCache.emplace_back();
bindingEntry.bindingIndex = bindingIndex;
bindingEntry.content = ShaderBinding::SampledTextureBindings {
SafeCast<UInt32>(renderState.shadowMapsSpot.size()), &m_textureBindingCache[textureBindingBaseIndex]
};
}