From 17d331afb1c418890d026632f1a033b0344172c4 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Thu, 9 Apr 2026 05:22:03 +0800 Subject: [PATCH] Connect volumetric lighting to main directional light --- .../assets/builtin/shaders/volumetric.shader | 20 +++++++- .../Rendering/Passes/BuiltinVolumetricPass.h | 9 ++++ .../Passes/BuiltinVolumetricPass.cpp | 46 +++++++++++++++++-- .../integration/volume_scene/main.cpp | 18 +++++++- .../unit/test_builtin_forward_pipeline.cpp | 46 ++++++++++++++----- 5 files changed, 120 insertions(+), 19 deletions(-) diff --git a/engine/assets/builtin/shaders/volumetric.shader b/engine/assets/builtin/shaders/volumetric.shader index f6b55848..ed4daf1b 100644 --- a/engine/assets/builtin/shaders/volumetric.shader +++ b/engine/assets/builtin/shaders/volumetric.shader @@ -26,6 +26,12 @@ Shader "Builtin Volumetric" float4 gVolumeBoundsMax; }; + cbuffer LightingConstants + { + float4 gMainLightDirectionAndIntensity; + float4 gMainLightColorAndFlags; + }; + cbuffer MaterialConstants { float4 gVolumeTint; @@ -220,8 +226,18 @@ Shader "Builtin Volumetric" float transmittance = 1.0f; float accumulatedDensity = 0.0f; + float3 resolvedLightDirectionWS = gLightDirection.xyz; + float3 resolvedLightRadiance = 1.0f.xxx; + if (gMainLightColorAndFlags.a >= 0.5f && + length(gMainLightDirectionAndIntensity.xyz) > 0.0001f) { + resolvedLightDirectionWS = gMainLightDirectionAndIntensity.xyz; + resolvedLightRadiance = + max(gMainLightColorAndFlags.rgb, 0.0f.xxx) * + max(gMainLightDirectionAndIntensity.w, 0.0f); + } + float3 localLightDirection = - mul((float3x3)gInverseModelMatrix, gLightDirection.xyz); + mul((float3x3)gInverseModelMatrix, resolvedLightDirectionWS); const float localLightLength = length(localLightDirection); if (localLightLength > 0.0001f) { localLightDirection /= localLightLength; @@ -267,7 +283,7 @@ Shader "Builtin Volumetric" localLightDirection, lightSamples, volume.accessor); - const float3 S = sigmaS * shadow.xxx; + const float3 S = sigmaS * shadow.xxx * resolvedLightRadiance; const float3 integratedSegment = (S - S * exp(-sigmaE * stepSize)) / sigmaE; integratedLight += transmittance * integratedSegment; transmittance *= exp(-sigmaE * stepSize); diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinVolumetricPass.h b/engine/include/XCEngine/Rendering/Passes/BuiltinVolumetricPass.h index d5f5979e..38a3702f 100644 --- a/engine/include/XCEngine/Rendering/Passes/BuiltinVolumetricPass.h +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinVolumetricPass.h @@ -29,6 +29,7 @@ class VolumeField; namespace Rendering { struct VisibleVolumeItem; +struct RenderLightingData; namespace Passes { @@ -54,6 +55,11 @@ private: Math::Vector4 localBoundsMax = Math::Vector4::Zero(); }; + struct LightingConstants { + Math::Vector4 mainLightDirectionAndIntensity = Math::Vector4::Zero(); + Math::Vector4 mainLightColorAndFlags = Math::Vector4::Zero(); + }; + struct OwnedDescriptorSet { RHI::RHIDescriptorPool* pool = nullptr; RHI::RHIDescriptorSet* set = nullptr; @@ -82,6 +88,7 @@ private: Core::uint32 descriptorSetCount = 0; std::vector setLayouts; PassResourceBindingLocation perObject = {}; + PassResourceBindingLocation lighting = {}; PassResourceBindingLocation material = {}; PassResourceBindingLocation volumeField = {}; }; @@ -161,6 +168,7 @@ private: bool EnsureInitialized(const RenderContext& context); bool CreateResources(const RenderContext& context); void DestroyResources(); + static LightingConstants BuildLightingConstants(const RenderLightingData& lightingData); ResolvedShaderPass ResolveVolumeShaderPass( const RenderSceneData& sceneData, @@ -185,6 +193,7 @@ private: const Resources::Material* material, const Resources::VolumeField* volumeField, const MaterialConstantPayloadView& materialConstants, + const LightingConstants& lightingConstants, RHI::RHIResourceView* volumeFieldView); void DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet); void DestroyPassResourceLayout(PassResourceLayout& passLayout); diff --git a/engine/src/Rendering/Passes/BuiltinVolumetricPass.cpp b/engine/src/Rendering/Passes/BuiltinVolumetricPass.cpp index be511ae9..8074c583 100644 --- a/engine/src/Rendering/Passes/BuiltinVolumetricPass.cpp +++ b/engine/src/Rendering/Passes/BuiltinVolumetricPass.cpp @@ -140,6 +140,26 @@ const char* BuiltinVolumetricPass::GetName() const { return "BuiltinVolumetricPass"; } +BuiltinVolumetricPass::LightingConstants BuiltinVolumetricPass::BuildLightingConstants( + const RenderLightingData& lightingData) { + LightingConstants lightingConstants = {}; + if (!lightingData.HasMainDirectionalLight()) { + return lightingConstants; + } + + lightingConstants.mainLightDirectionAndIntensity = Math::Vector4( + lightingData.mainDirectionalLight.direction.x, + lightingData.mainDirectionalLight.direction.y, + lightingData.mainDirectionalLight.direction.z, + lightingData.mainDirectionalLight.intensity); + lightingConstants.mainLightColorAndFlags = Math::Vector4( + lightingData.mainDirectionalLight.color.r, + lightingData.mainDirectionalLight.color.g, + lightingData.mainDirectionalLight.color.b, + 1.0f); + return lightingConstants; +} + RHI::InputLayoutDesc BuiltinVolumetricPass::BuildInputLayout() { RHI::InputLayoutDesc inputLayout = {}; @@ -357,6 +377,7 @@ BuiltinVolumetricPass::PassResourceLayout* BuiltinVolumetricPass::GetOrCreatePas passLayout.firstDescriptorSet = bindingPlan.firstDescriptorSet; passLayout.descriptorSetCount = bindingPlan.descriptorSetCount; passLayout.perObject = bindingPlan.perObject; + passLayout.lighting = bindingPlan.lighting; passLayout.material = bindingPlan.material; passLayout.volumeField = bindingPlan.volumeField; @@ -472,10 +493,11 @@ BuiltinVolumetricPass::CachedDescriptorSet* BuiltinVolumetricPass::GetOrCreateDy const BuiltinPassSetLayoutMetadata& setLayout, Core::uint32 setIndex, Core::uint64 objectId, - const Resources::Material* material, - const Resources::VolumeField* volumeField, - const MaterialConstantPayloadView& materialConstants, - RHI::RHIResourceView* volumeFieldView) { + const Resources::Material* material, + const Resources::VolumeField* volumeField, + const MaterialConstantPayloadView& materialConstants, + const LightingConstants& lightingConstants, + RHI::RHIResourceView* volumeFieldView) { DynamicDescriptorSetKey key = {}; key.passLayout = passLayoutKey; key.setIndex = setIndex; @@ -504,6 +526,17 @@ BuiltinVolumetricPass::CachedDescriptorSet* BuiltinVolumetricPass::GetOrCreateDy } } + if (setLayout.usesLighting) { + if (!passLayout.lighting.IsValid() || passLayout.lighting.set != setIndex) { + return nullptr; + } + + cachedDescriptorSet.descriptorSet.set->WriteConstant( + passLayout.lighting.binding, + &lightingConstants, + sizeof(lightingConstants)); + } + if (setLayout.usesVolumeField) { if (volumeFieldView == nullptr || !passLayout.volumeField.IsValid() || @@ -548,6 +581,7 @@ void BuiltinVolumetricPass::DestroyPassResourceLayout(PassResourceLayout& passLa passLayout.firstDescriptorSet = 0; passLayout.descriptorSetCount = 0; passLayout.perObject = {}; + passLayout.lighting = {}; passLayout.material = {}; passLayout.volumeField = {}; } @@ -595,6 +629,7 @@ bool BuiltinVolumetricPass::DrawVisibleVolume( const Resources::MaterialRenderState effectiveRenderState = ResolveEffectiveRenderState(resolvedShaderPass.pass, material); const MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material); + const LightingConstants lightingConstants = BuildLightingConstants(sceneData.lighting); if (passLayout->material.IsValid() && !materialConstants.IsValid()) { return false; } @@ -630,7 +665,7 @@ bool BuiltinVolumetricPass::DrawVisibleVolume( } const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex]; - if (!(setLayout.usesPerObject || setLayout.usesMaterial || setLayout.usesVolumeField)) { + if (!(setLayout.usesPerObject || setLayout.usesLighting || setLayout.usesMaterial || setLayout.usesVolumeField)) { return false; } @@ -651,6 +686,7 @@ bool BuiltinVolumetricPass::DrawVisibleVolume( materialKey, volumeFieldKey, materialConstants, + lightingConstants, cachedVolume->shaderResourceView); if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) { return false; diff --git a/tests/Rendering/integration/volume_scene/main.cpp b/tests/Rendering/integration/volume_scene/main.cpp index 2d11377f..3d879e21 100644 --- a/tests/Rendering/integration/volume_scene/main.cpp +++ b/tests/Rendering/integration/volume_scene/main.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -29,7 +30,14 @@ private: void VolumeSceneTest::BuildScene() { mVolumeMaterial = CreateVolumetricMaterial( "VolumeMaterial", - "Tests/Rendering/VolumeScene/Volume.material"); + "Tests/Rendering/VolumeScene/Volume.material", + Vector4(1.0f, 1.0f, 1.0f, 1.0f), + 0.2f, + 1.0f, + 2000.0f, + 0.005f, + Vector3(-0.72f, 0.16f, 0.67f), + 8.0f); ASSERT_NE(mVolumeMaterial, nullptr); mVolumeField = LoadCloudVolumeField(); @@ -45,6 +53,13 @@ void VolumeSceneTest::BuildScene() { cameraObject->GetTransform()->SetLocalPosition(Vector3(-10.0f, 300.0f, -1200.0f)); cameraObject->GetTransform()->LookAt(Vector3(-10.0f, 73.0f, 0.0f)); + GameObject* lightObject = mScene->CreateGameObject("MainDirectionalLight"); + auto* light = lightObject->AddComponent(); + light->SetLightType(LightType::Directional); + light->SetColor(XCEngine::Math::Color(1.0f, 1.0f, 1.0f, 1.0f)); + light->SetIntensity(1.0f); + lightObject->GetTransform()->LookAt(Vector3(-0.5f, -0.8f, -0.3f)); + GameObject* volumeObject = mScene->CreateGameObject("CloudVolume"); auto* volumeRenderer = volumeObject->AddComponent(); volumeRenderer->SetVolumeField(mVolumeField); @@ -56,6 +71,7 @@ void VolumeSceneTest::BuildScene() { const RenderSceneData sceneData = extractor.Extract(*mScene, nullptr, kFrameWidth, kFrameHeight); ASSERT_EQ(sceneData.visibleItems.size(), 0u); ASSERT_EQ(sceneData.visibleVolumes.size(), 1u); + ASSERT_TRUE(sceneData.lighting.HasMainDirectionalLight()); } void VolumeSceneTest::ReleaseSceneResources() { diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index 588f2f8e..a8cf23d9 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -687,19 +687,29 @@ TEST(BuiltinForwardPipeline_Test, BuiltinVolumetricShaderUsesAuthoringContract) const ShaderPass* pass = shader->FindPass("Volumetric"); ASSERT_NE(pass, nullptr); - EXPECT_EQ(pass->resources.Size(), 3u); + EXPECT_EQ(pass->resources.Size(), 4u); EXPECT_TRUE(pass->hasFixedFunctionState); EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None); EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable); EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual); EXPECT_TRUE(pass->fixedFunctionState.blendEnable); + const ShaderResourceBindingDesc* lighting = + shader->FindPassResourceBinding("Volumetric", "LightingConstants"); + ASSERT_NE(lighting, nullptr); + EXPECT_EQ(lighting->type, ShaderResourceType::ConstantBuffer); + EXPECT_EQ(lighting->set, 1u); + EXPECT_EQ(lighting->binding, 0u); + EXPECT_EQ( + ResolveBuiltinPassResourceSemantic(*lighting), + BuiltinPassResourceSemantic::Lighting); + const ShaderResourceBindingDesc* volumeData = shader->FindPassResourceBinding("Volumetric", "VolumeData"); ASSERT_NE(volumeData, nullptr); EXPECT_EQ(volumeData->type, ShaderResourceType::StructuredBuffer); EXPECT_EQ(volumeData->set, 2u); - EXPECT_EQ(volumeData->binding, 0u); + EXPECT_EQ(volumeData->binding, 1u); EXPECT_EQ( ResolveBuiltinPassResourceSemantic(*volumeData), BuiltinPassResourceSemantic::VolumeField); @@ -722,27 +732,33 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded BuiltinPassResourceBindingPlan plan = {}; String error; EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); - ASSERT_EQ(plan.bindings.Size(), 3u); + ASSERT_EQ(plan.bindings.Size(), 4u); EXPECT_TRUE(plan.perObject.IsValid()); + EXPECT_TRUE(plan.lighting.IsValid()); EXPECT_TRUE(plan.material.IsValid()); EXPECT_TRUE(plan.volumeField.IsValid()); EXPECT_EQ(plan.perObject.set, 0u); - EXPECT_EQ(plan.material.set, 1u); + EXPECT_EQ(plan.lighting.set, 1u); + EXPECT_EQ(plan.material.set, 2u); EXPECT_EQ(plan.volumeField.set, 2u); - EXPECT_EQ(plan.volumeField.binding, 0u); + EXPECT_EQ(plan.volumeField.binding, 1u); std::vector setLayouts; ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); ASSERT_EQ(setLayouts.size(), 3u); EXPECT_TRUE(setLayouts[0].usesPerObject); - EXPECT_TRUE(setLayouts[1].usesMaterial); + EXPECT_TRUE(setLayouts[1].usesLighting); + EXPECT_TRUE(setLayouts[2].usesMaterial); EXPECT_TRUE(setLayouts[2].usesVolumeField); - ASSERT_EQ(setLayouts[2].bindings.size(), 1u); + ASSERT_EQ(setLayouts[2].bindings.size(), 2u); EXPECT_EQ( static_cast(setLayouts[2].bindings[0].type), + DescriptorType::CBV); + EXPECT_EQ( + static_cast(setLayouts[2].bindings[1].type), DescriptorType::SRV); EXPECT_EQ( - setLayouts[2].bindings[0].resourceDimension, + setLayouts[2].bindings[1].resourceDimension, ResourceViewDimension::StructuredBuffer); delete shader; @@ -972,8 +988,12 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringVolum "register(b0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, - "cbuffer MaterialConstants", + "cbuffer LightingConstants", "register(b1)")); + EXPECT_TRUE(SourceContainsRegisterBinding( + d3d12Source, + "cbuffer MaterialConstants", + "register(b2)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "StructuredBuffer VolumeData", @@ -1000,12 +1020,16 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringVolum "register(b0, space0)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, - "cbuffer MaterialConstants", + "cbuffer LightingConstants", "register(b0, space1)")); + EXPECT_TRUE(SourceContainsRegisterBinding( + vulkanSource, + "cbuffer MaterialConstants", + "register(b0, space2)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "StructuredBuffer VolumeData", - "register(t0, space2)")); + "register(t1, space2)")); delete shader; }