Connect volumetric lighting to main directional light

This commit is contained in:
2026-04-09 05:22:03 +08:00
parent 2084412010
commit 17d331afb1
5 changed files with 120 additions and 19 deletions

View File

@@ -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);

View File

@@ -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<BuiltinPassSetLayoutMetadata> 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);

View File

@@ -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;

View File

@@ -4,6 +4,7 @@
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/LightComponent.h>
#include <XCEngine/Components/VolumeRendererComponent.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Vector3.h>
@@ -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<LightComponent>();
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<VolumeRendererComponent>();
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() {

View File

@@ -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<BuiltinPassSetLayoutMetadata> 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<DescriptorType>(setLayouts[2].bindings[0].type),
DescriptorType::CBV);
EXPECT_EQ(
static_cast<DescriptorType>(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<uint> 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<uint> VolumeData",
"register(t0, space2)"));
"register(t1, space2)"));
delete shader;
}