Add SH shading to gaussian splat renderer

This commit is contained in:
2026-04-11 06:32:38 +08:00
parent 5200fca82f
commit 785377bc9b
6 changed files with 162 additions and 5711 deletions

View File

@@ -15,6 +15,72 @@ Shader "Builtin Gaussian Splat Utilities"
float4 colorOpacity;
};
struct GaussianSplatSHData
{
float coefficients[45];
};
static const float SH_C1 = 0.4886025;
static const float SH_C2[] = { 1.0925484, -1.0925484, 0.3153916, -1.0925484, 0.5462742 };
static const float SH_C3[] = { -0.5900436, 2.8906114, -0.4570458, 0.3731763, -0.4570458, 1.4453057, -0.5900436 };
float3 LoadSHCoefficientTriplet(GaussianSplatSHData data, uint coefficientIndex)
{
return float3(
data.coefficients[coefficientIndex + 0u],
data.coefficients[coefficientIndex + 15u],
data.coefficients[coefficientIndex + 30u]);
}
float3 ShadeGaussianSplatSH(float3 baseColor, GaussianSplatSHData data, float3 direction, uint shOrder)
{
direction *= -1.0;
const float x = direction.x;
const float y = direction.y;
const float z = direction.z;
float3 result = baseColor;
if (shOrder >= 1u)
{
result += SH_C1 * (
-LoadSHCoefficientTriplet(data, 0u) * y +
LoadSHCoefficientTriplet(data, 1u) * z -
LoadSHCoefficientTriplet(data, 2u) * x);
if (shOrder >= 2u)
{
const float xx = x * x;
const float yy = y * y;
const float zz = z * z;
const float xy = x * y;
const float yz = y * z;
const float xz = x * z;
result +=
(SH_C2[0] * xy) * LoadSHCoefficientTriplet(data, 3u) +
(SH_C2[1] * yz) * LoadSHCoefficientTriplet(data, 4u) +
(SH_C2[2] * (2.0 * zz - xx - yy)) * LoadSHCoefficientTriplet(data, 5u) +
(SH_C2[3] * xz) * LoadSHCoefficientTriplet(data, 6u) +
(SH_C2[4] * (xx - yy)) * LoadSHCoefficientTriplet(data, 7u);
if (shOrder >= 3u)
{
result +=
(SH_C3[0] * y * (3.0 * xx - yy)) * LoadSHCoefficientTriplet(data, 8u) +
(SH_C3[1] * xy * z) * LoadSHCoefficientTriplet(data, 9u) +
(SH_C3[2] * y * (4.0 * zz - xx - yy)) * LoadSHCoefficientTriplet(data, 10u) +
(SH_C3[3] * z * (2.0 * zz - 3.0 * xx - 3.0 * yy)) * LoadSHCoefficientTriplet(data, 11u) +
(SH_C3[4] * x * (4.0 * zz - xx - yy)) * LoadSHCoefficientTriplet(data, 12u) +
(SH_C3[5] * z * (xx - yy)) * LoadSHCoefficientTriplet(data, 13u) +
(SH_C3[6] * x * (xx - 3.0 * yy)) * LoadSHCoefficientTriplet(data, 14u);
}
}
}
return max(result, 0.0);
}
uint FloatToSortableUint(float value)
{
const uint rawValue = asuint(value);
@@ -133,8 +199,10 @@ Shader "Builtin Gaussian Splat Utilities"
float4x4 gProjectionMatrix;
float4x4 gViewMatrix;
float4x4 gModelMatrix;
float4x4 gWorldToObjectMatrix;
float4 gCameraRight;
float4 gCameraUp;
float4 gCameraWorldPos;
float4 gScreenParams;
float4 gSplatParams;
};
@@ -142,6 +210,7 @@ Shader "Builtin Gaussian Splat Utilities"
StructuredBuffer<float4> GaussianSplatPositions;
StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther;
StructuredBuffer<float4> GaussianSplatColor;
StructuredBuffer<GaussianSplatSHData> GaussianSplatSH;
RWStructuredBuffer<uint> GaussianSplatSortDistances;
RWStructuredBuffer<uint> GaussianSplatOrderBuffer;
RWStructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer;
@@ -170,6 +239,8 @@ Shader "Builtin Gaussian Splat Utilities"
const float3 localCenter = GaussianSplatPositions[index].xyz;
const GaussianSplatOtherData otherData = GaussianSplatOther[index];
const float4 colorOpacity = GaussianSplatColor[index];
const GaussianSplatSHData shData = GaussianSplatSH[index];
const uint shOrder = min((uint)gSplatParams.z, 3u);
const float3 worldCenter = mul(gModelMatrix, float4(localCenter, 1.0)).xyz;
const float3 viewCenter = mul(gViewMatrix, float4(worldCenter, 1.0)).xyz;
@@ -202,7 +273,16 @@ Shader "Builtin Gaussian Splat Utilities"
viewData.clipCenter = clipCenter;
viewData.ellipseAxisU = float4(axisU, 0.0, 0.0);
viewData.ellipseAxisV = float4(axisV, 0.0, 0.0);
viewData.colorOpacity = colorOpacity;
float3 shadedColor = colorOpacity.rgb;
if (shOrder > 0u)
{
const float3 worldViewDirection = gCameraWorldPos.xyz - worldCenter;
const float3 objectViewDirection = normalize(
mul((float3x3)gWorldToObjectMatrix, worldViewDirection));
shadedColor = ShadeGaussianSplatSH(colorOpacity.rgb, shData, objectViewDirection, shOrder);
}
viewData.colorOpacity = float4(shadedColor, colorOpacity.a);
}
GaussianSplatViewDataBuffer[index] = viewData;
@@ -222,8 +302,10 @@ Shader "Builtin Gaussian Splat Utilities"
float4x4 gProjectionMatrix;
float4x4 gViewMatrix;
float4x4 gModelMatrix;
float4x4 gWorldToObjectMatrix;
float4 gCameraRight;
float4 gCameraUp;
float4 gCameraWorldPos;
float4 gScreenParams;
float4 gSplatParams;
};

View File

@@ -11,8 +11,10 @@ Shader "Builtin Gaussian Splat"
float4x4 gProjectionMatrix;
float4x4 gViewMatrix;
float4x4 gModelMatrix;
float4x4 gWorldToObjectMatrix;
float4 gCameraRight;
float4 gCameraUp;
float4 gCameraWorldPos;
float4 gScreenParams;
float4 gSplatParams;
};

View File

@@ -57,8 +57,10 @@ private:
Math::Matrix4x4 projection = Math::Matrix4x4::Identity();
Math::Matrix4x4 view = Math::Matrix4x4::Identity();
Math::Matrix4x4 model = Math::Matrix4x4::Identity();
Math::Matrix4x4 worldToObject = Math::Matrix4x4::Identity();
Math::Vector4 cameraRight = Math::Vector4::Zero();
Math::Vector4 cameraUp = Math::Vector4::Zero();
Math::Vector4 cameraWorldPos = Math::Vector4::Zero();
Math::Vector4 screenParams = Math::Vector4::Zero();
Math::Vector4 splatParams = Math::Vector4::Zero();
};

View File

@@ -243,7 +243,8 @@ bool BuiltinGaussianSplatPass::PrepareGaussianSplatResources(
if (cachedGaussianSplat == nullptr ||
cachedGaussianSplat->positions.shaderResourceView == nullptr ||
cachedGaussianSplat->other.shaderResourceView == nullptr ||
cachedGaussianSplat->color.shaderResourceView == nullptr) {
cachedGaussianSplat->color.shaderResourceView == nullptr ||
cachedGaussianSplat->sh.shaderResourceView == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"BuiltinGaussianSplatPass::PrepareGaussianSplatResources failed: gaussian splat GPU cache incomplete");
@@ -599,9 +600,10 @@ BuiltinGaussianSplatPass::PassResourceLayout* BuiltinGaussianSplatPass::GetOrCre
!passLayout.gaussianSplatPositionBuffer.IsValid() ||
!passLayout.gaussianSplatOtherBuffer.IsValid() ||
!passLayout.gaussianSplatColorBuffer.IsValid() ||
!passLayout.gaussianSplatSHBuffer.IsValid() ||
!passLayout.gaussianSplatViewDataBuffer.IsValid()) {
return failLayout(
"BuiltinGaussianSplatPass prepare-order pass requires sort distance, order, position, other, color, and view-data gaussian splat buffer bindings");
"BuiltinGaussianSplatPass prepare-order pass requires sort distance, order, position, other, color, SH, and view-data gaussian splat buffer bindings");
}
} else if (usage == PassLayoutUsage::BitonicSort) {
if (!passLayout.gaussianSplatSortDistanceBuffer.IsValid() ||
@@ -1012,7 +1014,11 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
const RenderResourceCache::CachedGaussianSplat* cachedGaussianSplat =
m_resourceCache.GetOrCreateGaussianSplat(m_device, visibleGaussianSplat.gaussianSplat);
if (cachedGaussianSplat == nullptr || cachedGaussianSplat->positions.shaderResourceView == nullptr) {
if (cachedGaussianSplat == nullptr ||
cachedGaussianSplat->positions.shaderResourceView == nullptr ||
cachedGaussianSplat->other.shaderResourceView == nullptr ||
cachedGaussianSplat->color.shaderResourceView == nullptr ||
cachedGaussianSplat->sh.shaderResourceView == nullptr) {
return fail("BuiltinGaussianSplatPass prepare-order failed: gaussian splat GPU cache is incomplete");
}
@@ -1062,8 +1068,10 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
sceneData.cameraData.projection,
sceneData.cameraData.view,
visibleGaussianSplat.localToWorld.Transpose(),
visibleGaussianSplat.localToWorld.Inverse(),
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
Math::Vector4(sceneData.cameraData.worldPosition, 0.0f),
Math::Vector4(
static_cast<float>(sceneData.cameraData.viewportWidth),
static_cast<float>(sceneData.cameraData.viewportHeight),
@@ -1072,7 +1080,7 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
Math::Vector4(
static_cast<float>(cachedGaussianSplat->splatCount),
static_cast<float>(workingSet->sortCapacity),
0.0f,
3.0f,
0.0f)
};
@@ -1095,6 +1103,7 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
setLayout.usesGaussianSplatPositionBuffer ||
setLayout.usesGaussianSplatOtherBuffer ||
setLayout.usesGaussianSplatColorBuffer ||
setLayout.usesGaussianSplatSHBuffer ||
setLayout.usesGaussianSplatViewDataBuffer)) {
return fail("BuiltinGaussianSplatPass prepare-order failed: unexpected descriptor set layout");
}
@@ -1107,6 +1116,7 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
setLayout.usesGaussianSplatPositionBuffer ||
setLayout.usesGaussianSplatOtherBuffer ||
setLayout.usesGaussianSplatColorBuffer ||
setLayout.usesGaussianSplatSHBuffer ||
setLayout.usesGaussianSplatViewDataBuffer)
? visibleGaussianSplat.gaussianSplat
: nullptr;
@@ -1239,8 +1249,10 @@ bool BuiltinGaussianSplatPass::SortVisibleGaussianSplat(
sceneData.cameraData.projection,
sceneData.cameraData.view,
visibleGaussianSplat.localToWorld.Transpose(),
visibleGaussianSplat.localToWorld.Inverse(),
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
Math::Vector4(sceneData.cameraData.worldPosition, 0.0f),
Math::Vector4(
static_cast<float>(sceneData.cameraData.viewportWidth),
static_cast<float>(sceneData.cameraData.viewportHeight),
@@ -1409,8 +1421,10 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
sceneData.cameraData.projection,
sceneData.cameraData.view,
visibleGaussianSplat.localToWorld.Transpose(),
visibleGaussianSplat.localToWorld.Inverse(),
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
Math::Vector4(sceneData.cameraData.worldPosition, 0.0f),
Math::Vector4(
static_cast<float>(sceneData.cameraData.viewportWidth),
static_cast<float>(sceneData.cameraData.viewportHeight),