diff --git a/include/Nazara/Graphics/DepthPipelinePass.hpp b/include/Nazara/Graphics/DepthPipelinePass.hpp index c20c3c3dd..5886f50f2 100644 --- a/include/Nazara/Graphics/DepthPipelinePass.hpp +++ b/include/Nazara/Graphics/DepthPipelinePass.hpp @@ -61,7 +61,6 @@ namespace Nz std::size_t m_lastVisibilityHash; std::string m_passName; std::vector> m_elementRendererData; - std::vector m_renderStates; std::vector m_renderElements; std::unordered_map m_materialInstances; RenderQueue m_renderQueue; diff --git a/include/Nazara/Graphics/DirectionalLight.hpp b/include/Nazara/Graphics/DirectionalLight.hpp index f648f5339..a4360047d 100644 --- a/include/Nazara/Graphics/DirectionalLight.hpp +++ b/include/Nazara/Graphics/DirectionalLight.hpp @@ -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 InstanciateShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry) const override; diff --git a/include/Nazara/Graphics/DirectionalLightShadowData.hpp b/include/Nazara/Graphics/DirectionalLightShadowData.hpp new file mode 100644 index 000000000..1bf0f49ef --- /dev/null +++ b/include/Nazara/Graphics/DirectionalLightShadowData.hpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 distance, SparsePtr 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 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 depthPass; + std::size_t attachmentIndex; + Matrix4f viewProjMatrix; + ShadowViewer viewer; + float distance; + }; + + struct PerViewerData + { + FixedVector cascades; + std::size_t textureArrayAttachmentIndex; + }; + + std::size_t m_cascadeCount; + std::unordered_map> m_viewerData; + ElementRendererRegistry& m_elementRegistry; + FramePipeline& m_pipeline; + const DirectionalLight& m_light; + bool m_isShadowStabilizationEnabled; + float m_invTexelScale; + float m_texelScale; + }; +} + +#include + +#endif // NAZARA_GRAPHICS_DIRECTIONALLIGHTSHADOWDATA_HPP diff --git a/include/Nazara/Graphics/DirectionalLightShadowData.inl b/include/Nazara/Graphics/DirectionalLightShadowData.inl new file mode 100644 index 000000000..2de7c99bc --- /dev/null +++ b/include/Nazara/Graphics/DirectionalLightShadowData.inl @@ -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 +#include + +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 distance, SparsePtr 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 + diff --git a/include/Nazara/Graphics/ElementRenderer.hpp b/include/Nazara/Graphics/ElementRenderer.hpp index ddd2d5b14..70469ab93 100644 --- a/include/Nazara/Graphics/ElementRenderer.hpp +++ b/include/Nazara/Graphics/ElementRenderer.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -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 InstanciateData() = 0; - virtual void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer* elements, const RenderStates* renderStates); + virtual void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer* elements, SparsePtr renderStates); virtual void PrepareEnd(RenderFrame& currentFrame, ElementRendererData& rendererData); virtual void Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, std::size_t elementCount, const Pointer* 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 shadowMaps2D; - std::array shadowMapsCube; + std::array shadowMapsDirectional; + std::array shadowMapsPoint; + std::array shadowMapsSpot; RenderBufferView lightData; }; }; diff --git a/include/Nazara/Graphics/Enums.hpp b/include/Nazara/Graphics/Enums.hpp index 357c09864..bf24ab50c 100644 --- a/include/Nazara/Graphics/Enums.hpp +++ b/include/Nazara/Graphics/Enums.hpp @@ -125,8 +125,9 @@ namespace Nz InstanceDataUbo, LightDataUbo, OverlayTexture, - Shadowmap2D, - ShadowmapCube, + ShadowmapDirectional, + ShadowmapPoint, + ShadowmapSpot, SkeletalDataUbo, ViewerDataUbo, diff --git a/include/Nazara/Graphics/ForwardFramePipeline.hpp b/include/Nazara/Graphics/ForwardFramePipeline.hpp index bc7c61ac3..3df743ace 100644 --- a/include/Nazara/Graphics/ForwardFramePipeline.hpp +++ b/include/Nazara/Graphics/ForwardFramePipeline.hpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -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 visibleLights; + Frustumf frustum; + }; + std::size_t finalColorAttachment; std::size_t forwardColorAttachment; std::size_t debugColorAttachment; @@ -145,6 +152,8 @@ namespace Nz RenderQueueRegistry forwardRegistry; RenderQueue forwardRenderQueue; ShaderBindingPtr blitShaderBinding; + FrameData frame; + bool pendingDestruction = false; NazaraSlot(TransferInterface, OnTransferRequired, onTransferRequired); }; @@ -158,17 +167,17 @@ namespace Nz std::unordered_map m_renderTargets; std::unordered_map m_materialInstances; - std::vector m_renderStates; mutable std::vector m_visibleRenderables; - std::vector m_visibleLights; robin_hood::unordered_set m_transferSet; BakedFrameGraph m_bakedFrameGraph; - Bitset m_shadowCastingLights; + Bitset m_activeLights; Bitset m_removedSkeletonInstances; Bitset m_removedViewerInstances; Bitset m_removedWorldInstances; + Bitset m_shadowCastingLights; + Bitset m_visibleShadowCastingLights; ElementRendererRegistry& m_elementRegistry; - mutable MemoryPool m_renderablePool; //< FIXME: has to be mutable because MemoryPool has no const_iterator + MemoryPool m_renderablePool; MemoryPool m_lightPool; MemoryPool m_skeletonInstances; MemoryPool m_viewerPool; diff --git a/include/Nazara/Graphics/ForwardPipelinePass.hpp b/include/Nazara/Graphics/ForwardPipelinePass.hpp index a30fdc56b..89e3e4730 100644 --- a/include/Nazara/Graphics/ForwardPipelinePass.hpp +++ b/include/Nazara/Graphics/ForwardPipelinePass.hpp @@ -11,9 +11,7 @@ #include #include #include -#include #include -#include #include #include #include @@ -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& visibleRenderables, const std::vector& visibleLights, std::size_t visibilityHash); + void Prepare(RenderFrame& renderFrame, const Frustumf& frustum, const std::vector& visibleRenderables, const Bitset& 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& visibleLights); + struct MaterialPassEntry { std::size_t usedCount = 1; @@ -62,55 +68,30 @@ namespace Nz NazaraSlot(MaterialInstance, OnMaterialInstanceShaderBindingInvalidated, onMaterialInstanceShaderBindingInvalidated); }; - using LightKey = std::array; - - struct LightKeyHasher - { - inline std::size_t operator()(const LightKey& lightKey) const; - }; - - struct LightDataUbo - { - std::shared_ptr renderBuffer; - std::size_t offset = 0; - UploadPool::Allocation* allocation = nullptr; - }; - - struct LightPerElementData - { - RenderBufferView lightUniformBuffer; - std::array shadowMaps; - std::size_t lightCount; - }; - - struct LightUboPool - { - std::vector> lightUboBuffers; - }; - + template 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 m_lightUboPool; + std::shared_ptr m_lightDataBuffer; std::vector> m_elementRendererData; - std::vector m_renderStates; std::vector m_renderElements; std::unordered_map m_materialInstances; - std::unordered_map m_lightPerRenderElement; - std::unordered_map m_lightBufferPerLights; - std::vector m_lightDataBuffers; - std::vector m_renderableLights; + std::vector> m_directionalLights; + std::vector> m_pointLights; + std::vector> m_spotLights; + ElementRenderer::RenderStates m_renderState; RenderQueue m_renderQueue; RenderQueueRegistry m_renderQueueRegistry; AbstractViewer* m_viewer; ElementRendererRegistry& m_elementRegistry; FramePipeline& m_pipeline; + UploadPool::Allocation* m_pendingLightUploadAllocation; bool m_rebuildCommandBuffer; bool m_rebuildElements; }; diff --git a/include/Nazara/Graphics/ForwardPipelinePass.inl b/include/Nazara/Graphics/ForwardPipelinePass.inl index 3ff0c3553..87ff95c0e 100644 --- a/include/Nazara/Graphics/ForwardPipelinePass.inl +++ b/include/Nazara/Graphics/ForwardPipelinePass.inl @@ -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 lightPtrHasher; - for (std::size_t i = 0; i < lightKey.size(); ++i) - lightHash = CombineHash(lightHash, lightPtrHasher(lightKey[i])); - - return lightHash; - } } #include diff --git a/include/Nazara/Graphics/Light.hpp b/include/Nazara/Graphics/Light.hpp index 9efcc66f1..186b99fd8 100644 --- a/include/Nazara/Graphics/Light.hpp +++ b/include/Nazara/Graphics/Light.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -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; diff --git a/include/Nazara/Graphics/LightShadowData.hpp b/include/Nazara/Graphics/LightShadowData.hpp index f8595ea15..246242c11 100644 --- a/include/Nazara/Graphics/LightShadowData.hpp +++ b/include/Nazara/Graphics/LightShadowData.hpp @@ -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; }; } diff --git a/include/Nazara/Graphics/LightShadowData.inl b/include/Nazara/Graphics/LightShadowData.inl index c172f3461..3d32891a2 100644 --- a/include/Nazara/Graphics/LightShadowData.inl +++ b/include/Nazara/Graphics/LightShadowData.inl @@ -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 diff --git a/include/Nazara/Graphics/PointLight.hpp b/include/Nazara/Graphics/PointLight.hpp index 5b09fde0e..28cc859d3 100644 --- a/include/Nazara/Graphics/PointLight.hpp +++ b/include/Nazara/Graphics/PointLight.hpp @@ -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 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); diff --git a/include/Nazara/Graphics/PointLight.inl b/include/Nazara/Graphics/PointLight.inl index 3d47445a6..3a9adb7c1 100644 --- a/include/Nazara/Graphics/PointLight.inl +++ b/include/Nazara/Graphics/PointLight.inl @@ -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 diff --git a/include/Nazara/Graphics/PointLightShadowData.hpp b/include/Nazara/Graphics/PointLightShadowData.hpp index f3faf80bf..e1b020b6e 100644 --- a/include/Nazara/Graphics/PointLightShadowData.hpp +++ b/include/Nazara/Graphics/PointLightShadowData.hpp @@ -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; diff --git a/include/Nazara/Graphics/SpotLight.hpp b/include/Nazara/Graphics/SpotLight.hpp index 78708cda3..1cf86d632 100644 --- a/include/Nazara/Graphics/SpotLight.hpp +++ b/include/Nazara/Graphics/SpotLight.hpp @@ -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 InstanciateShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry) const override; diff --git a/include/Nazara/Graphics/SpotLight.inl b/include/Nazara/Graphics/SpotLight.inl index f8004b0c9..b4ef1cd64 100644 --- a/include/Nazara/Graphics/SpotLight.inl +++ b/include/Nazara/Graphics/SpotLight.inl @@ -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); diff --git a/include/Nazara/Graphics/SpotLightShadowData.hpp b/include/Nazara/Graphics/SpotLightShadowData.hpp index 63d125cea..582714473 100644 --- a/include/Nazara/Graphics/SpotLightShadowData.hpp +++ b/include/Nazara/Graphics/SpotLightShadowData.hpp @@ -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; diff --git a/include/Nazara/Graphics/SpotLightShadowData.inl b/include/Nazara/Graphics/SpotLightShadowData.inl index c172f3461..a4741205c 100644 --- a/include/Nazara/Graphics/SpotLightShadowData.inl +++ b/include/Nazara/Graphics/SpotLightShadowData.inl @@ -6,6 +6,10 @@ namespace Nz { + inline const ViewerInstance& SpotLightShadowData::GetViewerInstance() const + { + return m_viewer.GetViewerInstance(); + } } #include diff --git a/include/Nazara/Graphics/SpriteChainRenderer.hpp b/include/Nazara/Graphics/SpriteChainRenderer.hpp index ee7872fd3..06d0af20c 100644 --- a/include/Nazara/Graphics/SpriteChainRenderer.hpp +++ b/include/Nazara/Graphics/SpriteChainRenderer.hpp @@ -59,7 +59,7 @@ namespace Nz RenderElementPool& GetPool() override; std::unique_ptr InstanciateData() override; - void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer* elements, const RenderStates* renderStates) override; + void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer* elements, SparsePtr renderStates) override; void PrepareEnd(RenderFrame& currentFrame, ElementRendererData& rendererData) override; void Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, std::size_t elementCount, const Pointer* elements) override; void Reset(ElementRendererData& rendererData, RenderFrame& currentFrame) override; diff --git a/include/Nazara/Graphics/SubmeshRenderer.hpp b/include/Nazara/Graphics/SubmeshRenderer.hpp index 49e21c558..d52538e41 100644 --- a/include/Nazara/Graphics/SubmeshRenderer.hpp +++ b/include/Nazara/Graphics/SubmeshRenderer.hpp @@ -27,7 +27,7 @@ namespace Nz RenderElementPool& GetPool() override; std::unique_ptr InstanciateData() override; - void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer* elements, const RenderStates* renderStates) override; + void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer* elements, SparsePtr renderStates) override; void Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, std::size_t elementCount, const Pointer* elements) override; void Reset(ElementRendererData& rendererData, RenderFrame& currentFrame) override; diff --git a/include/Nazara/Math/Frustum.hpp b/include/Nazara/Math/Frustum.hpp index 332a24ccc..d2e20ab1b 100644 --- a/include/Nazara/Math/Frustum.hpp +++ b/include/Nazara/Math/Frustum.hpp @@ -56,6 +56,11 @@ namespace Nz constexpr IntersectionSide Intersect(const Sphere& sphere) const; constexpr IntersectionSide Intersect(const Vector3* points, std::size_t pointCount) const; + constexpr Frustum Reduce(T nearFactor, T farFactor) const; + + template constexpr void Split(std::initializer_list splitFactors, F&& callback) const; + template constexpr void Split(const T* splitFactors, std::size_t factorCount, F&& callback) const; + std::string ToString() const; constexpr Frustum& operator=(const Frustum&) = default; diff --git a/include/Nazara/Math/Frustum.inl b/include/Nazara/Math/Frustum.inl index 7bcd2024c..88a060cc5 100644 --- a/include/Nazara/Math/Frustum.inl +++ b/include/Nazara/Math/Frustum.inl @@ -427,6 +427,38 @@ namespace Nz return side; } + template + constexpr Frustum Frustum::Reduce(T nearFactor, T farFactor) const + { + EnumArray> 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(planes); + } + + template + template + constexpr void Frustum::Split(std::initializer_list splitFactors, F&& callback) const + { + return Split(splitFactors.begin(), splitFactors.size(), std::forward(callback)); + } + + template + template + constexpr void Frustum::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 ...)" diff --git a/src/Nazara/Graphics/DepthPipelinePass.cpp b/src/Nazara/Graphics/DepthPipelinePass.cpp index 5fe3928c7..6ec77af8a 100644 --- a/src/Nazara/Graphics/DepthPipelinePass.cpp +++ b/src/Nazara/Graphics/DepthPipelinePass.cpp @@ -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* 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) diff --git a/src/Nazara/Graphics/DirectionalLight.cpp b/src/Nazara/Graphics/DirectionalLight.cpp index ea6ac6f94..9650a3e1c 100644 --- a/src/Nazara/Graphics/DirectionalLight.cpp +++ b/src/Nazara/Graphics/DirectionalLight.cpp @@ -3,36 +3,24 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include -#include -#include -#include -#include -#include -#include -#include +#include #include namespace Nz { - float DirectionalLight::ComputeContributionScore(const BoundingVolumef& /*boundingVolume*/) const + float DirectionalLight::ComputeContributionScore(const Frustumf& /*viewerFrustum*/) const { return -std::numeric_limits::infinity(); } - void DirectionalLight::FillLightData(void* data) const + bool DirectionalLight::FrustumCull(const Frustumf& /*viewerFrustum*/) const { - auto lightOffset = PredefinedLightData::GetOffsets(); - - AccessByOffset(data, lightOffset.lightMemberOffsets.type) = UnderlyingCast(BasicLightType::Directional); - AccessByOffset(data, lightOffset.lightMemberOffsets.color) = Vector4f(m_color.r, m_color.g, m_color.b, m_color.a); - AccessByOffset(data, lightOffset.lightMemberOffsets.factor) = Vector2f(m_ambientFactor, m_diffuseFactor); - AccessByOffset(data, lightOffset.lightMemberOffsets.parameter1) = Vector4f(m_direction.x, m_direction.y, m_direction.z, 0.f); - AccessByOffset(data, lightOffset.lightMemberOffsets.shadowMapSize) = Vector2f(-1.f, -1.f); + return true; //< always visible } std::unique_ptr DirectionalLight::InstanciateShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry) const { - return nullptr; //< TODO + return std::make_unique(pipeline, elementRegistry, *this, 4); } void DirectionalLight::UpdateTransform(const Vector3f& /*position*/, const Quaternionf& rotation, const Vector3f& /*scale*/) diff --git a/src/Nazara/Graphics/DirectionalLightShadowData.cpp b/src/Nazara/Graphics/DirectionalLightShadowData.cpp new file mode 100644 index 000000000..1e8e4985d --- /dev/null +++ b/src/Nazara/Graphics/DirectionalLightShadowData.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(newSize), SafeCast(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 frustums = NazaraStackVector(Frustumf, m_cascadeCount); + StackVector 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 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::infinity(); + float right = -std::numeric_limits::infinity(); + float top = std::numeric_limits::infinity(); + float bottom = -std::numeric_limits::infinity(); + float zNear = std::numeric_limits::infinity(); + float zFar = -std::numeric_limits::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 = std::make_unique(); + 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(shadowMapSize), SafeCast(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 + void DirectionalLightShadowData::ForEachCascade(F&& callback) + { + for (auto it = m_viewerData.begin(); it != m_viewerData.end(); ++it) + { + for (CascadeData& cascade : it->second->cascades) + callback(cascade); + } + } +} diff --git a/src/Nazara/Graphics/ElementRenderer.cpp b/src/Nazara/Graphics/ElementRenderer.cpp index 27cb47fab..eac864515 100644 --- a/src/Nazara/Graphics/ElementRenderer.cpp +++ b/src/Nazara/Graphics/ElementRenderer.cpp @@ -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* /*elements*/, const RenderStates* /*renderStates*/) + void ElementRenderer::Prepare(const ViewerInstance& /*viewerInstance*/, ElementRendererData& /*rendererData*/, RenderFrame& /*currentFrame*/, std::size_t /*elementCount*/, const Pointer* /*elements*/, SparsePtr /*renderStates*/) { } diff --git a/src/Nazara/Graphics/ForwardFramePipeline.cpp b/src/Nazara/Graphics/ForwardFramePipeline.cpp index 0f9a61f55..f01379699 100644 --- a/src/Nazara/Graphics/ForwardFramePipeline.cpp +++ b/src/Nazara/Graphics/ForwardFramePipeline.cpp @@ -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()(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& 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); } diff --git a/src/Nazara/Graphics/ForwardPipelinePass.cpp b/src/Nazara/Graphics/ForwardPipelinePass.cpp index 1719490ec..b47c608ef 100644 --- a/src/Nazara/Graphics/ForwardPipelinePass.cpp +++ b/src/Nazara/Graphics/ForwardPipelinePass.cpp @@ -4,13 +4,18 @@ #include #include +#include #include #include #include #include #include #include +#include +#include #include +#include +#include #include #include #include @@ -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(); + + std::size_t lightUboAlignedSize = AlignPow2(PredefinedLightOffsets.totalSize, SafeCast(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& visibleRenderables, const std::vector& visibleLights, std::size_t visibilityHash) + void ForwardPipelinePass::Prepare(RenderFrame& renderFrame, const Frustumf& frustum, const std::vector& visibleRenderables, const Bitset& 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(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(targetLightData->allocation->mappedPtr) + targetLightData->offset; - AccessByOffset(lightDataPtr, lightOffsets.lightCountOffset) = SafeCast(lightCount); - - UInt8* lightPtr = static_cast(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* 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(lightMemory, PredefinedLightOffsets.directionalLightCountOffset) = SafeCast(lightCount); + for (std::size_t i = 0; i < lightCount; ++i) + { + UInt8* basePtr = static_cast(lightMemory) + PredefinedLightOffsets.directionalLightsOffset + PredefinedDirectionalLightOffsets.totalSize * i; + + const DirectionalLight* light = m_directionalLights[i].light; + + const Color& lightColor = light->GetColor(); + + AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.colorOffset) = Vector3f(lightColor.r, lightColor.g, lightColor.b); + AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.directionOffset) = light->GetDirection(); + AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.ambientFactorOffset) = light->GetAmbientFactor(); + AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.diffuseFactorOffset) = light->GetDiffuseFactor(); + AccessByOffset(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(m_pipeline.RetrieveLightShadowData(m_directionalLights[i].lightIndex)); + + float* cascadeFarPlanes = AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.cascadeFarPlanesOffset); + Matrix4f* cascadeViewProj = AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.cascadeViewProjMatricesOffset); + + shadowData->GetCascadeData(m_viewer, SparsePtr(cascadeFarPlanes, 4*sizeof(float)), SparsePtr(cascadeViewProj)); + + AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.cascadeCountOffset) = SafeCast(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(lightMemory, PredefinedLightOffsets.pointLightCountOffset) = SafeCast(lightCount); + for (std::size_t i = 0; i < lightCount; ++i) + { + UInt8* basePtr = static_cast(lightMemory) + PredefinedLightOffsets.pointLightsOffset + PredefinedPointLightOffsets.totalSize * i; + + const PointLight* light = m_pointLights[i].light; + + const Color& lightColor = light->GetColor(); + + AccessByOffset(basePtr, PredefinedPointLightOffsets.colorOffset) = Vector3f(lightColor.r, lightColor.g, lightColor.b); + AccessByOffset(basePtr, PredefinedPointLightOffsets.positionOffset) = light->GetPosition(); + AccessByOffset(basePtr, PredefinedPointLightOffsets.invShadowMapSizeOffset) = (light->IsShadowCaster()) ? Vector2f(1.f / light->GetShadowMapSize()) : Vector2f(-1.f, -1.f); + AccessByOffset(basePtr, PredefinedPointLightOffsets.ambientFactorOffset) = light->GetAmbientFactor(); + AccessByOffset(basePtr, PredefinedPointLightOffsets.diffuseFactorOffset) = light->GetDiffuseFactor(); + AccessByOffset(basePtr, PredefinedPointLightOffsets.radiusOffset) = light->GetRadius(); + AccessByOffset(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(lightMemory, PredefinedLightOffsets.spotLightCountOffset) = SafeCast(lightCount); + for (std::size_t i = 0; i < lightCount; ++i) + { + UInt8* basePtr = static_cast(lightMemory) + PredefinedLightOffsets.spotLightsOffset + PredefinedSpotLightOffsets.totalSize * i; + + const SpotLight* light = m_spotLights[i].light; + + const Color& lightColor = light->GetColor(); + + AccessByOffset(basePtr, PredefinedSpotLightOffsets.colorOffset) = Vector3f(lightColor.r, lightColor.g, lightColor.b); + AccessByOffset(basePtr, PredefinedSpotLightOffsets.directionOffset) = light->GetDirection(); + AccessByOffset(basePtr, PredefinedSpotLightOffsets.positionOffset) = light->GetPosition(); + AccessByOffset(basePtr, PredefinedSpotLightOffsets.invShadowMapSizeOffset) = (light->IsShadowCaster()) ? Vector2f(1.f / light->GetShadowMapSize()) : Vector2f(-1.f, -1.f); + AccessByOffset(basePtr, PredefinedSpotLightOffsets.ambientFactorOffset) = light->GetAmbientFactor(); + AccessByOffset(basePtr, PredefinedSpotLightOffsets.diffuseFactorOffset) = light->GetDiffuseFactor(); + AccessByOffset(basePtr, PredefinedSpotLightOffsets.innerAngleOffset) = light->GetInnerAngleCos(); + AccessByOffset(basePtr, PredefinedSpotLightOffsets.outerAngleOffset) = light->GetOuterAngleCos(); + AccessByOffset(basePtr, PredefinedSpotLightOffsets.invRadiusOffset) = light->GetInvRadius(); + AccessByOffset(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& 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(light), lightIndex, 0.f }); + break; + + case UnderlyingCast(BasicLightType::Point): + m_pointLights.push_back({ SafeCast(light), lightIndex, light->ComputeContributionScore(frustum) }); + break; + + case UnderlyingCast(BasicLightType::Spot): + m_spotLights.push_back({ SafeCast(light), lightIndex, light->ComputeContributionScore(frustum) }); + break; + } + } + + // Sort lights + std::sort(m_pointLights.begin(), m_pointLights.end(), [&](const RenderableLight& lhs, const RenderableLight& rhs) + { + return lhs.contributionScore < rhs.contributionScore; + }); + + std::sort(m_spotLights.begin(), m_spotLights.end(), [&](const RenderableLight& lhs, const RenderableLight& 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); + } } diff --git a/src/Nazara/Graphics/Graphics.cpp b/src/Nazara/Graphics/Graphics.cpp index 214a9b73a..4a096ab02 100644 --- a/src/Nazara/Graphics/Graphics.cpp +++ b/src/Nazara/Graphics/Graphics.cpp @@ -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(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(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(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() diff --git a/src/Nazara/Graphics/LightShadowData.cpp b/src/Nazara/Graphics/LightShadowData.cpp index cb31e3999..3d9dfe7af 100644 --- a/src/Nazara/Graphics/LightShadowData.cpp +++ b/src/Nazara/Graphics/LightShadowData.cpp @@ -8,4 +8,12 @@ namespace Nz { LightShadowData::~LightShadowData() = default; + + void LightShadowData::RegisterViewer(const AbstractViewer* /*viewer*/) + { + } + + void LightShadowData::UnregisterViewer(const AbstractViewer* /*viewer*/) + { + } } diff --git a/src/Nazara/Graphics/Material.cpp b/src/Nazara/Graphics/Material.cpp index 339e38651..95add7405 100644 --- a/src/Nazara/Graphics/Material.cpp +++ b/src/Nazara/Graphics/Material.cpp @@ -37,6 +37,7 @@ namespace Nz options.partialSanitization = true; options.moduleResolver = graphics->GetShaderModuleResolver(); options.optionValues[CRC32("MaxLightCount")] = SafeCast(PredefinedLightData::MaxLightCount); + options.optionValues[CRC32("MaxLightCascadeCount")] = SafeCast(PredefinedDirectionalLightData::MaxLightCascadeCount); options.optionValues[CRC32("MaxJointCount")] = SafeCast(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; diff --git a/src/Nazara/Graphics/PointLight.cpp b/src/Nazara/Graphics/PointLight.cpp index 367d641b3..7e7fef50c 100644 --- a/src/Nazara/Graphics/PointLight.cpp +++ b/src/Nazara/Graphics/PointLight.cpp @@ -3,33 +3,20 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include -#include -#include #include -#include -#include -#include -#include #include 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(data, lightOffset.lightMemberOffsets.type) = UnderlyingCast(BasicLightType::Point); - AccessByOffset(data, lightOffset.lightMemberOffsets.color) = Vector4f(m_color.r, m_color.g, m_color.b, m_color.a); - AccessByOffset(data, lightOffset.lightMemberOffsets.factor) = Vector2f(m_ambientFactor, m_diffuseFactor); - AccessByOffset(data, lightOffset.lightMemberOffsets.parameter1) = Vector4f(m_position.x, m_position.y, m_position.z, 0.f); - AccessByOffset(data, lightOffset.lightMemberOffsets.parameter2) = Vector4f(m_radius, m_invRadius, 0.f, 0.f); - AccessByOffset(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 PointLight::InstanciateShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry) const diff --git a/src/Nazara/Graphics/PointLightShadowData.cpp b/src/Nazara/Graphics/PointLightShadowData.cpp index 5a1f7a7d9..b1f84ce30 100644 --- a/src/Nazara/Graphics/PointLightShadowData.cpp +++ b/src/Nazara/Graphics/PointLightShadowData.cpp @@ -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(); } diff --git a/src/Nazara/Graphics/Resources/Shaders/Modules/Engine/LightData.nzsl b/src/Nazara/Graphics/Resources/Shaders/Modules/Engine/LightData.nzsl index 938d275ca..60de5b458 100644 --- a/src/Nazara/Graphics/Resources/Shaders/Modules/Engine/LightData.nzsl +++ b/src/Nazara/Graphics/Resources/Shaders/Modules/Engine/LightData.nzsl @@ -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, } diff --git a/src/Nazara/Graphics/Resources/Shaders/PhongMaterial.nzsl b/src/Nazara/Graphics/Resources/Shaders/PhongMaterial.nzsl index 7d852e503..b4c012b12 100644 --- a/src/Nazara/Graphics/Resources/Shaders/PhongMaterial.nzsl +++ b/src/Nazara/Graphics/Resources/Shaders/PhongMaterial.nzsl @@ -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; } diff --git a/src/Nazara/Graphics/Resources/Shaders/PhysicallyBasedMaterial.nzsl b/src/Nazara/Graphics/Resources/Shaders/PhysicallyBasedMaterial.nzsl index e8afba1f3..a073edac6 100644 --- a/src/Nazara/Graphics/Resources/Shaders/PhysicallyBasedMaterial.nzsl +++ b/src/Nazara/Graphics/Resources/Shaders/PhysicallyBasedMaterial.nzsl @@ -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; diff --git a/src/Nazara/Graphics/SpotLight.cpp b/src/Nazara/Graphics/SpotLight.cpp index 3592be8f5..b3111cfcc 100644 --- a/src/Nazara/Graphics/SpotLight.cpp +++ b/src/Nazara/Graphics/SpotLight.cpp @@ -3,35 +3,37 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include -#include -#include -#include #include -#include -#include -#include #include 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(data, lightOffset.lightMemberOffsets.type) = UnderlyingCast(BasicLightType::Spot); - AccessByOffset(data, lightOffset.lightMemberOffsets.color) = Vector4f(m_color.r, m_color.g, m_color.b, m_color.a); - AccessByOffset(data, lightOffset.lightMemberOffsets.factor) = Vector2f(m_ambientFactor, m_diffuseFactor); - AccessByOffset(data, lightOffset.lightMemberOffsets.parameter1) = Vector4f(m_position.x, m_position.y, m_position.z, m_invRadius); - AccessByOffset(data, lightOffset.lightMemberOffsets.parameter2) = Vector4f(m_direction.x, m_direction.y, m_direction.z, 0.f); - AccessByOffset(data, lightOffset.lightMemberOffsets.parameter3) = Vector4f(m_innerAngleCos, m_outerAngleCos, 0.f, 0.f); - AccessByOffset(data, lightOffset.lightMemberOffsets.shadowMapSize) = (IsShadowCaster()) ? Vector2f(1.f / GetShadowMapSize()) : Vector2f(-1.f, -1.f); - AccessByOffset(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 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 SpotLight::InstanciateShadowData(FramePipeline& pipeline, ElementRendererRegistry& elementRegistry) const diff --git a/src/Nazara/Graphics/SpotLightShadowData.cpp b/src/Nazara/Graphics/SpotLightShadowData.cpp index 0332909b5..2d9c1a7f6 100644 --- a/src/Nazara/Graphics/SpotLightShadowData.cpp +++ b/src/Nazara/Graphics/SpotLightShadowData.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include 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(); } diff --git a/src/Nazara/Graphics/SpriteChainRenderer.cpp b/src/Nazara/Graphics/SpriteChainRenderer.cpp index feeab1fac..2d458f844 100644 --- a/src/Nazara/Graphics/SpriteChainRenderer.cpp +++ b/src/Nazara/Graphics/SpriteChainRenderer.cpp @@ -56,7 +56,7 @@ namespace Nz return std::make_unique(); } - void SpriteChainRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer* elements, const RenderStates* renderStates) + void SpriteChainRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer* elements, SparsePtr renderStates) { Graphics* graphics = Graphics::Instance(); diff --git a/src/Nazara/Graphics/SubmeshRenderer.cpp b/src/Nazara/Graphics/SubmeshRenderer.cpp index 1710325f0..763393ab8 100644 --- a/src/Nazara/Graphics/SubmeshRenderer.cpp +++ b/src/Nazara/Graphics/SubmeshRenderer.cpp @@ -24,7 +24,7 @@ namespace Nz return std::make_unique(); } - void SubmeshRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& /*currentFrame*/, std::size_t elementCount, const Pointer* elements, const RenderStates* renderStates) + void SubmeshRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& /*currentFrame*/, std::size_t elementCount, const Pointer* elements, SparsePtr 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(renderState.shadowMaps2D.size()), &m_textureBindingCache[textureBindingBaseIndex] + SafeCast(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(renderState.shadowMapsCube.size()), &m_textureBindingCache[textureBindingBaseIndex] + SafeCast(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(renderState.shadowMapsSpot.size()), &m_textureBindingCache[textureBindingBaseIndex] }; }