diff --git a/docs/plan/Shader与Material系统下一阶段计划.md b/docs/plan/Shader与Material系统下一阶段计划.md index 3c708ca7..1a23041e 100644 --- a/docs/plan/Shader与Material系统下一阶段计划.md +++ b/docs/plan/Shader与Material系统下一阶段计划.md @@ -355,11 +355,14 @@ Unity-like Shader Authoring (.shader) - 新增 `BuiltinDepthOnlyPass / BuiltinShadowCasterPass`,作为独立 `RenderPass` 复用同一套 shared pass-layout skeleton - 两个 pass 当前先收敛到 `PerObject` 常量路径;opaque 物体走 builtin fallback,`ShadowCaster` 额外尊重 `MeshRenderer.castShadows` - 这一步解决的是“pass contract 与执行骨架缺失”,还没有把 shadow map / light-space request flow 一次性做完 -- 已验证:`rendering_unit_tests` 71/71 +- 已完成:`CameraRenderRequest + CameraRenderer` 正式接入 `DepthOnly / ShadowCaster` request 流程 + - 新增 `depthOnly / shadowCaster` request,支持独立 surface、clear flags / clear color,以及可选 `RenderCameraData` override + - `CameraRenderer` 现在按 `ShadowCaster -> DepthOnly -> Forward -> ObjectId` 的顺序执行这些请求,`BuiltinDepthStylePassBase` 也会按 request clear flags 清理目标 +- 已验证:`rendering_unit_tests` 73/73 - 已验证:`rendering_integration_textured_quad_scene` 3/3(D3D12 / OpenGL / Vulkan) - 已验证:`rendering_integration_unlit_scene` 3/3(D3D12 / OpenGL / Vulkan) - 已验证:`rendering_integration_object_id_scene` 3/3(D3D12 / OpenGL / Vulkan) -- 下一步:如果继续沿 renderer 主线收口,优先把 `DepthOnly / ShadowCaster` 真正接到 request / light-space 渲染流程,并补对应 integration coverage;如果先控制范围,当前 shader/material/pass contract 这一小阶段已经接近收口 +- 下一步:如果继续沿 renderer 主线收口,优先补 `DepthOnly / ShadowCaster` 的真实 cross-backend integration coverage,并决定是否把 shadow-map 结果继续接回 forward lighting 消费链;如果先控制范围,request 流程这一块已经收口 ### 阶段 D:扩展 AssetDatabase / Library Artifact 能力 diff --git a/engine/include/XCEngine/Rendering/CameraRenderRequest.h b/engine/include/XCEngine/Rendering/CameraRenderRequest.h index ff31df01..570146ee 100644 --- a/engine/include/XCEngine/Rendering/CameraRenderRequest.h +++ b/engine/include/XCEngine/Rendering/CameraRenderRequest.h @@ -13,6 +13,31 @@ class Scene; namespace Rendering { +struct ScenePassRenderRequest { + RenderSurface surface; + RenderClearFlags clearFlags = RenderClearFlags::Depth; + bool hasClearColorOverride = false; + Math::Color clearColorOverride = Math::Color::Black(); + bool hasCameraDataOverride = false; + RenderCameraData cameraDataOverride = {}; + + bool IsRequested() const { + return !surface.GetColorAttachments().empty(); + } + + bool IsValid() const { + const std::vector& colorAttachments = surface.GetColorAttachments(); + return !colorAttachments.empty() && + colorAttachments[0] != nullptr && + surface.GetDepthAttachment() != nullptr && + surface.GetRenderAreaWidth() > 0 && + surface.GetRenderAreaHeight() > 0; + } +}; + +using DepthOnlyRenderRequest = ScenePassRenderRequest; +using ShadowCasterRenderRequest = ScenePassRenderRequest; + struct ObjectIdRenderRequest { RenderSurface surface; @@ -35,6 +60,8 @@ struct CameraRenderRequest { Components::CameraComponent* camera = nullptr; RenderContext context; RenderSurface surface; + DepthOnlyRenderRequest depthOnly; + ShadowCasterRenderRequest shadowCaster; ObjectIdRenderRequest objectId; float cameraDepth = 0.0f; uint8_t cameraStackOrder = 0; diff --git a/engine/include/XCEngine/Rendering/CameraRenderer.h b/engine/include/XCEngine/Rendering/CameraRenderer.h index 4ecae49b..2d09c9dd 100644 --- a/engine/include/XCEngine/Rendering/CameraRenderer.h +++ b/engine/include/XCEngine/Rendering/CameraRenderer.h @@ -24,15 +24,21 @@ public: explicit CameraRenderer(std::shared_ptr pipelineAsset); CameraRenderer( std::unique_ptr pipeline, - std::unique_ptr objectIdPass); + std::unique_ptr objectIdPass, + std::unique_ptr depthOnlyPass = nullptr, + std::unique_ptr shadowCasterPass = nullptr); ~CameraRenderer(); void SetPipeline(std::unique_ptr pipeline); void SetPipelineAsset(std::shared_ptr pipelineAsset); void SetObjectIdPass(std::unique_ptr objectIdPass); + void SetDepthOnlyPass(std::unique_ptr depthOnlyPass); + void SetShadowCasterPass(std::unique_ptr shadowCasterPass); RenderPipeline* GetPipeline() const { return m_pipeline.get(); } const RenderPipelineAsset* GetPipelineAsset() const { return m_pipelineAsset.get(); } ObjectIdPass* GetObjectIdPass() const { return m_objectIdPass.get(); } + RenderPass* GetDepthOnlyPass() const { return m_depthOnlyPass.get(); } + RenderPass* GetShadowCasterPass() const { return m_shadowCasterPass.get(); } bool Render(const CameraRenderRequest& request); @@ -43,6 +49,8 @@ private: std::shared_ptr m_pipelineAsset; std::unique_ptr m_pipeline; std::unique_ptr m_objectIdPass; + std::unique_ptr m_depthOnlyPass; + std::unique_ptr m_shadowCasterPass; }; } // namespace Rendering diff --git a/engine/src/Rendering/CameraRenderer.cpp b/engine/src/Rendering/CameraRenderer.cpp index 93d792dd..0dfa96ec 100644 --- a/engine/src/Rendering/CameraRenderer.cpp +++ b/engine/src/Rendering/CameraRenderer.cpp @@ -1,6 +1,8 @@ #include "Rendering/CameraRenderer.h" +#include "Rendering/Passes/BuiltinDepthOnlyPass.h" #include "Rendering/Passes/BuiltinObjectIdPass.h" +#include "Rendering/Passes/BuiltinShadowCasterPass.h" #include "Rendering/Pipelines/BuiltinForwardPipeline.h" #include "Rendering/RenderPipelineAsset.h" #include "Rendering/RenderSurface.h" @@ -40,6 +42,14 @@ std::shared_ptr CreateDefaultPipelineAsset() { return s_defaultPipelineAsset; } +std::unique_ptr CreateDefaultDepthOnlyPass() { + return std::make_unique(); +} + +std::unique_ptr CreateDefaultShadowCasterPass() { + return std::make_unique(); +} + std::unique_ptr CreatePipelineFromAsset( const std::shared_ptr& pipelineAsset) { if (pipelineAsset != nullptr) { @@ -52,6 +62,54 @@ std::unique_ptr CreatePipelineFromAsset( return std::make_unique(); } +bool InitializeStandalonePass( + RenderPass* pass, + const RenderContext& context) { + if (pass == nullptr) { + return false; + } + + if (pass->Initialize(context)) { + return true; + } + + pass->Shutdown(); + return false; +} + +bool ExecuteScenePassRequest( + RenderPass* pass, + const ScenePassRenderRequest& request, + const RenderContext& context, + const RenderSceneData& baseSceneData) { + if (!request.IsRequested()) { + return true; + } + + if (!InitializeStandalonePass(pass, context)) { + return false; + } + + RenderSceneData sceneData = baseSceneData; + if (request.hasCameraDataOverride) { + sceneData.cameraData = request.cameraDataOverride; + } + + sceneData.cameraData.viewportWidth = request.surface.GetRenderAreaWidth(); + sceneData.cameraData.viewportHeight = request.surface.GetRenderAreaHeight(); + sceneData.cameraData.clearFlags = request.clearFlags; + if (request.hasClearColorOverride) { + sceneData.cameraData.clearColor = request.clearColorOverride; + } + + const RenderPassContext passContext = { + context, + request.surface, + sceneData + }; + return pass->Execute(passContext); +} + } // namespace CameraRenderer::CameraRenderer() @@ -59,23 +117,39 @@ CameraRenderer::CameraRenderer() } CameraRenderer::CameraRenderer(std::unique_ptr pipeline) - : CameraRenderer(std::move(pipeline), std::make_unique()) { + : CameraRenderer( + std::move(pipeline), + std::make_unique(), + CreateDefaultDepthOnlyPass(), + CreateDefaultShadowCasterPass()) { } CameraRenderer::CameraRenderer( std::unique_ptr pipeline, - std::unique_ptr objectIdPass) + std::unique_ptr objectIdPass, + std::unique_ptr depthOnlyPass, + std::unique_ptr shadowCasterPass) : m_pipelineAsset(nullptr) - , m_objectIdPass(std::move(objectIdPass)) { + , m_objectIdPass(std::move(objectIdPass)) + , m_depthOnlyPass(std::move(depthOnlyPass)) + , m_shadowCasterPass(std::move(shadowCasterPass)) { if (m_objectIdPass == nullptr) { m_objectIdPass = std::make_unique(); } + if (m_depthOnlyPass == nullptr) { + m_depthOnlyPass = CreateDefaultDepthOnlyPass(); + } + if (m_shadowCasterPass == nullptr) { + m_shadowCasterPass = CreateDefaultShadowCasterPass(); + } ResetPipeline(std::move(pipeline)); } CameraRenderer::CameraRenderer(std::shared_ptr pipelineAsset) : m_pipelineAsset(std::move(pipelineAsset)) - , m_objectIdPass(std::make_unique()) { + , m_objectIdPass(std::make_unique()) + , m_depthOnlyPass(CreateDefaultDepthOnlyPass()) + , m_shadowCasterPass(CreateDefaultShadowCasterPass()) { SetPipelineAsset(m_pipelineAsset); } @@ -86,6 +160,12 @@ CameraRenderer::~CameraRenderer() { if (m_objectIdPass != nullptr) { m_objectIdPass->Shutdown(); } + if (m_depthOnlyPass != nullptr) { + m_depthOnlyPass->Shutdown(); + } + if (m_shadowCasterPass != nullptr) { + m_shadowCasterPass->Shutdown(); + } } void CameraRenderer::SetPipeline(std::unique_ptr pipeline) { @@ -109,6 +189,28 @@ void CameraRenderer::SetObjectIdPass(std::unique_ptr objectIdPass) } } +void CameraRenderer::SetDepthOnlyPass(std::unique_ptr depthOnlyPass) { + if (m_depthOnlyPass != nullptr) { + m_depthOnlyPass->Shutdown(); + } + + m_depthOnlyPass = std::move(depthOnlyPass); + if (m_depthOnlyPass == nullptr) { + m_depthOnlyPass = CreateDefaultDepthOnlyPass(); + } +} + +void CameraRenderer::SetShadowCasterPass(std::unique_ptr shadowCasterPass) { + if (m_shadowCasterPass != nullptr) { + m_shadowCasterPass->Shutdown(); + } + + m_shadowCasterPass = std::move(shadowCasterPass); + if (m_shadowCasterPass == nullptr) { + m_shadowCasterPass = CreateDefaultShadowCasterPass(); + } +} + void CameraRenderer::ResetPipeline(std::unique_ptr pipeline) { if (m_pipeline != nullptr) { m_pipeline->Shutdown(); @@ -131,6 +233,14 @@ bool CameraRenderer::Render( request.surface.GetRenderAreaHeight() == 0) { return false; } + if (request.depthOnly.IsRequested() && + !request.depthOnly.IsValid()) { + return false; + } + if (request.shadowCaster.IsRequested() && + !request.shadowCaster.IsValid()) { + return false; + } if (request.objectId.IsRequested() && !request.objectId.IsValid()) { return false; @@ -167,6 +277,24 @@ bool CameraRenderer::Render( return false; } + if (!ExecuteScenePassRequest( + m_shadowCasterPass.get(), + request.shadowCaster, + request.context, + sceneData)) { + ShutdownPassSequence(request.preScenePasses, preScenePassesInitialized); + return false; + } + + if (!ExecuteScenePassRequest( + m_depthOnlyPass.get(), + request.depthOnly, + request.context, + sceneData)) { + ShutdownPassSequence(request.preScenePasses, preScenePassesInitialized); + return false; + } + if (!m_pipeline->Render(request.context, request.surface, sceneData)) { ShutdownPassSequence(request.preScenePasses, preScenePassesInitialized); return false; diff --git a/engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp b/engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp index e10d8fc8..8479ea13 100644 --- a/engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp +++ b/engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp @@ -179,8 +179,19 @@ bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) { renderArea.x + renderArea.width, renderArea.y + renderArea.height }; + const RHI::Rect clearRects[] = { scissorRect }; commandList->SetViewport(viewport); commandList->SetScissorRect(scissorRect); + + const Math::Color clearColor = context.sceneData.cameraData.clearColor; + const float clearValues[4] = { clearColor.r, clearColor.g, clearColor.b, clearColor.a }; + if (HasRenderClearFlag(context.sceneData.cameraData.clearFlags, RenderClearFlags::Color)) { + commandList->ClearRenderTarget(renderTarget, clearValues, 1, clearRects); + } + if (HasRenderClearFlag(context.sceneData.cameraData.clearFlags, RenderClearFlags::Depth)) { + commandList->ClearDepthStencil(context.surface.GetDepthAttachment(), 1.0f, 0, 1, clearRects); + } + commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); for (const VisibleRenderItem& visibleItem : context.sceneData.visibleItems) { diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index d81f8759..c43cd3d8 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -131,6 +131,59 @@ private: bool m_renderResult = true; }; +class MockScenePass final : public RenderPass { +public: + MockScenePass( + std::shared_ptr state, + const char* label, + bool initializeResult = true, + bool executeResult = true) + : m_state(std::move(state)) + , m_label(label) + , m_initializeResult(initializeResult) + , m_executeResult(executeResult) { + } + + const char* GetName() const override { + return m_label; + } + + bool Initialize(const RenderContext&) override { + m_state->eventLog.push_back(std::string("init:") + m_label); + return m_initializeResult; + } + + bool Execute(const RenderPassContext& context) override { + m_state->eventLog.push_back(m_label); + lastViewportWidth = context.sceneData.cameraData.viewportWidth; + lastViewportHeight = context.sceneData.cameraData.viewportHeight; + lastClearFlags = context.sceneData.cameraData.clearFlags; + lastClearColor = context.sceneData.cameraData.clearColor; + lastWorldPosition = context.sceneData.cameraData.worldPosition; + lastSurfaceWidth = context.surface.GetRenderAreaWidth(); + lastSurfaceHeight = context.surface.GetRenderAreaHeight(); + return m_executeResult; + } + + void Shutdown() override { + m_state->eventLog.push_back(std::string("shutdown:") + m_label); + } + + uint32_t lastViewportWidth = 0; + uint32_t lastViewportHeight = 0; + RenderClearFlags lastClearFlags = RenderClearFlags::All; + XCEngine::Math::Color lastClearColor = XCEngine::Math::Color::Black(); + XCEngine::Math::Vector3 lastWorldPosition = XCEngine::Math::Vector3::Zero(); + uint32_t lastSurfaceWidth = 0; + uint32_t lastSurfaceHeight = 0; + +private: + std::shared_ptr m_state; + const char* m_label = ""; + bool m_initializeResult = true; + bool m_executeResult = true; +}; + class TrackingPass final : public RenderPass { public: TrackingPass( @@ -334,6 +387,92 @@ TEST(CameraRenderer_Test, ExecutesObjectIdPassBetweenPipelineAndPostPassesWhenRe "shutdown:pre" })); } +TEST(CameraRenderer_Test, ExecutesShadowCasterAndDepthOnlyRequestsBeforeMainPipeline) { + Scene scene("CameraRendererDepthAndShadowScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetDepth(3.0f); + + auto state = std::make_shared(); + CameraRenderer renderer( + std::make_unique(state), + std::make_unique(state)); + + auto shadowPass = std::make_unique(state, "shadowCaster"); + MockScenePass* shadowPassRaw = shadowPass.get(); + renderer.SetShadowCasterPass(std::move(shadowPass)); + + auto depthPass = std::make_unique(state, "depthOnly"); + MockScenePass* depthPassRaw = depthPass.get(); + renderer.SetDepthOnlyPass(std::move(depthPass)); + + CameraRenderRequest request; + request.scene = &scene; + request.camera = camera; + request.context = CreateValidContext(); + request.surface = RenderSurface(320, 180); + request.cameraDepth = camera->GetDepth(); + + request.shadowCaster.surface = RenderSurface(128, 64); + request.shadowCaster.surface.SetColorAttachment(reinterpret_cast(1)); + request.shadowCaster.surface.SetDepthAttachment(reinterpret_cast(2)); + request.shadowCaster.hasCameraDataOverride = true; + request.shadowCaster.cameraDataOverride.worldPosition = XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f); + + request.depthOnly.surface = RenderSurface(96, 48); + request.depthOnly.surface.SetColorAttachment(reinterpret_cast(3)); + request.depthOnly.surface.SetDepthAttachment(reinterpret_cast(4)); + request.depthOnly.hasClearColorOverride = true; + request.depthOnly.clearColorOverride = XCEngine::Math::Color(0.3f, 0.2f, 0.1f, 1.0f); + + ASSERT_TRUE(renderer.Render(request)); + EXPECT_EQ( + state->eventLog, + (std::vector{ + "init:shadowCaster", + "shadowCaster", + "init:depthOnly", + "depthOnly", + "pipeline" })); + EXPECT_EQ(shadowPassRaw->lastViewportWidth, 128u); + EXPECT_EQ(shadowPassRaw->lastViewportHeight, 64u); + EXPECT_EQ(shadowPassRaw->lastSurfaceWidth, 128u); + EXPECT_EQ(shadowPassRaw->lastSurfaceHeight, 64u); + EXPECT_EQ(shadowPassRaw->lastClearFlags, RenderClearFlags::Depth); + EXPECT_EQ(shadowPassRaw->lastWorldPosition, XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f)); + EXPECT_EQ(depthPassRaw->lastViewportWidth, 96u); + EXPECT_EQ(depthPassRaw->lastViewportHeight, 48u); + EXPECT_EQ(depthPassRaw->lastClearFlags, RenderClearFlags::Depth); + EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.r, 0.3f); + EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.g, 0.2f); + EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.b, 0.1f); + EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.a, 1.0f); +} + +TEST(CameraRenderer_Test, StopsRenderingWhenShadowCasterRequestIsInvalid) { + Scene scene("CameraRendererInvalidShadowScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + + auto state = std::make_shared(); + CameraRenderer renderer(std::make_unique(state)); + + CameraRenderRequest request; + request.scene = &scene; + request.camera = camera; + request.context = CreateValidContext(); + request.surface = RenderSurface(320, 180); + request.shadowCaster.surface = RenderSurface(64, 64); + request.shadowCaster.surface.SetColorAttachment(reinterpret_cast(1)); + + EXPECT_FALSE(renderer.Render(request)); + EXPECT_TRUE(state->eventLog.empty()); +} + TEST(CameraRenderer_Test, ShutsDownInitializedPassesWhenPipelineRenderFails) { Scene scene("CameraRendererFailureScene");