From b3acb5afc2fe9c4e5e941aada16e24913a0348ac Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 11 Apr 2026 06:57:47 +0800 Subject: [PATCH] Derive gaussian splat SH order from resource layout --- .../Resources/GaussianSplat/GaussianSplat.h | 6 +++ .../Passes/BuiltinGaussianSplatPass.cpp | 7 +++- .../Resources/GaussianSplat/GaussianSplat.cpp | 37 +++++++++++++++++++ .../integration/gaussian_splat_scene/main.cpp | 2 + .../GaussianSplat/test_gaussian_splat.cpp | 31 ++++++++++++++++ 5 files changed, 82 insertions(+), 1 deletion(-) diff --git a/engine/include/XCEngine/Resources/GaussianSplat/GaussianSplat.h b/engine/include/XCEngine/Resources/GaussianSplat/GaussianSplat.h index 957b99f6..cf5c504f 100644 --- a/engine/include/XCEngine/Resources/GaussianSplat/GaussianSplat.h +++ b/engine/include/XCEngine/Resources/GaussianSplat/GaussianSplat.h @@ -43,6 +43,11 @@ enum class GaussianSplatSectionFormat : Core::uint32 { }; constexpr Core::uint32 kGaussianSplatSHCoefficientCount = 45; +constexpr Core::uint32 kGaussianSplatSHColorChannelCount = 3; +constexpr Core::uint32 kGaussianSplatMaxSHOrder = 3; + +Core::uint32 ResolveGaussianSplatSHOrderFromCoefficientCount(Core::uint32 coefficientCount); +Core::uint32 ResolveGaussianSplatSHOrderFromSectionStride(Core::uint32 elementStride); struct GaussianSplatPositionRecord { Math::Vector3 position = Math::Vector3::Zero(); @@ -113,6 +118,7 @@ public: GaussianSplatSectionFormat GetOtherFormat() const { return m_metadata.otherFormat; } GaussianSplatSectionFormat GetColorFormat() const { return m_metadata.colorFormat; } GaussianSplatSectionFormat GetSHFormat() const { return m_metadata.shFormat; } + Core::uint32 GetSHOrder() const; GaussianSplatSectionFormat GetChunkFormat() const { return m_metadata.chunkFormat; } GaussianSplatSectionFormat GetCameraFormat() const { return m_metadata.cameraFormat; } const Containers::Array& GetSections() const { return m_sections; } diff --git a/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp b/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp index 9fa22a44..e92cc7ea 100644 --- a/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp +++ b/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp @@ -1064,6 +1064,11 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat( commandList->SetPipelineState(pipelineState); + const float shOrder = + visibleGaussianSplat.gaussianSplat != nullptr + ? static_cast(visibleGaussianSplat.gaussianSplat->GetSHOrder()) + : 0.0f; + const PerObjectConstants perObjectConstants = { sceneData.cameraData.projection, sceneData.cameraData.view, @@ -1080,7 +1085,7 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat( Math::Vector4( static_cast(cachedGaussianSplat->splatCount), static_cast(workingSet->sortCapacity), - 3.0f, + shOrder, 0.0f) }; diff --git a/engine/src/Resources/GaussianSplat/GaussianSplat.cpp b/engine/src/Resources/GaussianSplat/GaussianSplat.cpp index 1e5b8e3f..63844090 100644 --- a/engine/src/Resources/GaussianSplat/GaussianSplat.cpp +++ b/engine/src/Resources/GaussianSplat/GaussianSplat.cpp @@ -3,6 +3,38 @@ namespace XCEngine { namespace Resources { +Core::uint32 ResolveGaussianSplatSHOrderFromCoefficientCount(Core::uint32 coefficientCount) { + if (coefficientCount == 0u || (coefficientCount % kGaussianSplatSHColorChannelCount) != 0u) { + return 0u; + } + + Core::uint32 remainingTriplets = coefficientCount / kGaussianSplatSHColorChannelCount; + Core::uint32 resolvedOrder = 0u; + for (Core::uint32 order = 1u; order <= kGaussianSplatMaxSHOrder; ++order) { + const Core::uint32 bandTriplets = (2u * order) + 1u; + if (remainingTriplets < bandTriplets) { + break; + } + + remainingTriplets -= bandTriplets; + resolvedOrder = order; + } + + return resolvedOrder; +} + +Core::uint32 ResolveGaussianSplatSHOrderFromSectionStride(Core::uint32 elementStride) { + if (elementStride < sizeof(float)) { + return 0u; + } + + const Core::uint32 floatCount = elementStride / static_cast(sizeof(float)); + const Core::uint32 coefficientCount = floatCount > kGaussianSplatSHCoefficientCount + ? kGaussianSplatSHCoefficientCount + : floatCount; + return ResolveGaussianSplatSHOrderFromCoefficientCount(coefficientCount); +} + GaussianSplat::GaussianSplat() = default; GaussianSplat::~GaussianSplat() = default; @@ -69,6 +101,11 @@ const GaussianSplatSHRecord* GaussianSplat::GetSHRecords() const { return static_cast(GetSectionData(GaussianSplatSectionType::SH)); } +Core::uint32 GaussianSplat::GetSHOrder() const { + const GaussianSplatSection* shSection = FindSection(GaussianSplatSectionType::SH); + return shSection != nullptr ? ResolveGaussianSplatSHOrderFromSectionStride(shSection->elementStride) : 0u; +} + bool GaussianSplat::ValidateSections(const Containers::Array& sections, size_t payloadSize) const { for (size_t index = 0; index < sections.Size(); ++index) { diff --git a/tests/Rendering/integration/gaussian_splat_scene/main.cpp b/tests/Rendering/integration/gaussian_splat_scene/main.cpp index 4a1b220f..fa6c7e01 100644 --- a/tests/Rendering/integration/gaussian_splat_scene/main.cpp +++ b/tests/Rendering/integration/gaussian_splat_scene/main.cpp @@ -328,6 +328,7 @@ void GaussianSplatSceneTest::PrepareRuntimeProject() { ASSERT_NE(m_gaussianSplat.Get(), nullptr); ASSERT_TRUE(m_gaussianSplat->IsValid()); ASSERT_GT(m_gaussianSplat->GetSplatCount(), 0u); + ASSERT_EQ(m_gaussianSplat->GetSHOrder(), 3u); m_subsetGaussianSplat = CreateGaussianSplatSubset( *m_gaussianSplat.Get(), kBaselineSubsetSplatCount, @@ -335,6 +336,7 @@ void GaussianSplatSceneTest::PrepareRuntimeProject() { ASSERT_NE(m_subsetGaussianSplat, nullptr); ASSERT_TRUE(m_subsetGaussianSplat->IsValid()); ASSERT_GT(m_subsetGaussianSplat->GetSplatCount(), 0u); + ASSERT_EQ(m_subsetGaussianSplat->GetSHOrder(), 3u); } void GaussianSplatSceneTest::BuildScene() { diff --git a/tests/Resources/GaussianSplat/test_gaussian_splat.cpp b/tests/Resources/GaussianSplat/test_gaussian_splat.cpp index 8c66f4f2..5dd947e1 100644 --- a/tests/Resources/GaussianSplat/test_gaussian_splat.cpp +++ b/tests/Resources/GaussianSplat/test_gaussian_splat.cpp @@ -8,6 +8,14 @@ using namespace XCEngine::Math; namespace { +TEST(GaussianSplat, ResolvesSHOrderFromCoefficientCount) { + EXPECT_EQ(ResolveGaussianSplatSHOrderFromCoefficientCount(0u), 0u); + EXPECT_EQ(ResolveGaussianSplatSHOrderFromCoefficientCount(9u), 1u); + EXPECT_EQ(ResolveGaussianSplatSHOrderFromCoefficientCount(24u), 2u); + EXPECT_EQ(ResolveGaussianSplatSHOrderFromCoefficientCount(45u), 3u); + EXPECT_EQ(ResolveGaussianSplatSHOrderFromCoefficientCount(46u), 0u); +} + TEST(GaussianSplat, CreateOwnedStoresMetadataSectionsAndPayload) { GaussianSplat gaussianSplat; @@ -48,6 +56,29 @@ TEST(GaussianSplat, CreateOwnedStoresMetadataSectionsAndPayload) { EXPECT_NE(gaussianSplat.GetSectionData(GaussianSplatSectionType::Positions), nullptr); } +TEST(GaussianSplat, GetSHOrderUsesSHSectionStride) { + GaussianSplat gaussianSplat; + + GaussianSplatMetadata metadata; + metadata.splatCount = 1u; + + XCEngine::Containers::Array sections; + sections.PushBack(GaussianSplatSection{ + GaussianSplatSectionType::SH, + GaussianSplatSectionFormat::SHFloat32, + 0u, + 96u, + 1u, + 96u + }); + + XCEngine::Containers::Array payload; + payload.Resize(96u); + + ASSERT_TRUE(gaussianSplat.CreateOwned(metadata, std::move(sections), std::move(payload))); + EXPECT_EQ(gaussianSplat.GetSHOrder(), 2u); +} + TEST(GaussianSplat, RejectsInvalidSectionLayout) { GaussianSplat gaussianSplat;