Cull invisible gaussian splat chunks in prepare pass

This commit is contained in:
2026-04-11 14:22:51 +08:00
parent c543ccf79c
commit 2fb6eca854
3 changed files with 177 additions and 2 deletions

View File

@@ -107,6 +107,102 @@ Shader "Builtin Gaussian Splat Utilities"
return rawValue ^ mask; return rawValue ^ mask;
} }
float2 UnpackHalfRange(uint packedValue)
{
return float2(
f16tof32(packedValue & 0xffffu),
f16tof32(packedValue >> 16u));
}
float4 TransformLocalPointToClip(
float3 localPosition,
float4x4 modelMatrix,
float4x4 viewMatrix,
float4x4 projectionMatrix)
{
const float3 worldPosition = mul(modelMatrix, float4(localPosition, 1.0)).xyz;
const float3 viewPosition = mul(viewMatrix, float4(worldPosition, 1.0)).xyz;
return mul(projectionMatrix, float4(viewPosition, 1.0));
}
bool IsChunkDefinitelyOutsideFrustum(
GaussianSplatChunkData chunkData,
uint chunkCount,
uint chunkIndex,
float4x4 modelMatrix,
float4x4 viewMatrix,
float4x4 projectionMatrix)
{
if (chunkCount == 0u || chunkIndex >= chunkCount)
{
return false;
}
float3 localMin = float3(chunkData.posX.x, chunkData.posY.x, chunkData.posZ.x);
float3 localMax = float3(chunkData.posX.y, chunkData.posY.y, chunkData.posZ.y);
if (localMin.x > localMax.x || localMin.y > localMax.y || localMin.z > localMax.z)
{
return true;
}
// Inflate by a conservative 3-sigma envelope derived from the chunk's maximum splat scale.
const float3 maxScale = float3(
UnpackHalfRange(chunkData.sclX).y,
UnpackHalfRange(chunkData.sclY).y,
UnpackHalfRange(chunkData.sclZ).y);
const float radius = max(maxScale.x, max(maxScale.y, maxScale.z)) * 3.0;
localMin -= radius.xxx;
localMax += radius.xxx;
bool allBehind = true;
bool anyBehind = false;
bool outsideLeft = true;
bool outsideRight = true;
bool outsideBottom = true;
bool outsideTop = true;
bool outsideNear = true;
bool outsideFar = true;
[unroll]
for (uint cornerIndex = 0u; cornerIndex < 8u; ++cornerIndex)
{
const float3 localCorner = float3(
(cornerIndex & 1u) != 0u ? localMax.x : localMin.x,
(cornerIndex & 2u) != 0u ? localMax.y : localMin.y,
(cornerIndex & 4u) != 0u ? localMax.z : localMin.z);
const float4 clipCorner = TransformLocalPointToClip(
localCorner,
modelMatrix,
viewMatrix,
projectionMatrix);
const bool behind = clipCorner.w <= 0.0;
allBehind = allBehind && behind;
anyBehind = anyBehind || behind;
if (!behind)
{
outsideLeft = outsideLeft && (clipCorner.x < -clipCorner.w);
outsideRight = outsideRight && (clipCorner.x > clipCorner.w);
outsideBottom = outsideBottom && (clipCorner.y < -clipCorner.w);
outsideTop = outsideTop && (clipCorner.y > clipCorner.w);
outsideNear = outsideNear && (clipCorner.z < 0.0);
outsideFar = outsideFar && (clipCorner.z > clipCorner.w);
}
}
if (allBehind)
{
return true;
}
if (anyBehind)
{
return false;
}
return outsideLeft || outsideRight || outsideBottom || outsideTop || outsideNear || outsideFar;
}
float3x3 CalcMatrixFromRotationScale(float4 rotation, float3 scale) float3x3 CalcMatrixFromRotationScale(float4 rotation, float3 scale)
{ {
const float x = rotation.x; const float x = rotation.x;
@@ -260,7 +356,9 @@ Shader "Builtin Gaussian Splat Utilities"
const GaussianSplatOtherData otherData = GaussianSplatOther[index]; const GaussianSplatOtherData otherData = GaussianSplatOther[index];
const float4 colorOpacity = GaussianSplatColor[index]; const float4 colorOpacity = GaussianSplatColor[index];
const GaussianSplatSHData shData = GaussianSplatSH[index]; const GaussianSplatSHData shData = GaussianSplatSH[index];
const GaussianSplatChunkData chunkData = GaussianSplatChunks[index / GAUSSIAN_SPLAT_CHUNK_SIZE]; const uint chunkIndex = index / GAUSSIAN_SPLAT_CHUNK_SIZE;
const uint chunkCount = (uint)gSplatParams.w;
const GaussianSplatChunkData chunkData = GaussianSplatChunks[chunkIndex];
const uint shOrder = min((uint)gSplatParams.z, 3u); const uint shOrder = min((uint)gSplatParams.z, 3u);
if (chunkData.posX.x > chunkData.posX.y || if (chunkData.posX.x > chunkData.posX.y ||
chunkData.posY.x > chunkData.posY.y || chunkData.posY.x > chunkData.posY.y ||
@@ -270,6 +368,18 @@ Shader "Builtin Gaussian Splat Utilities"
GaussianSplatViewDataBuffer[index] = viewData; GaussianSplatViewDataBuffer[index] = viewData;
return; return;
} }
if (IsChunkDefinitelyOutsideFrustum(
chunkData,
chunkCount,
chunkIndex,
gModelMatrix,
gViewMatrix,
gProjectionMatrix))
{
GaussianSplatSortDistances[index] = 0xffffffffu;
GaussianSplatViewDataBuffer[index] = viewData;
return;
}
const float3 worldCenter = mul(gModelMatrix, float4(localCenter, 1.0)).xyz; const float3 worldCenter = mul(gModelMatrix, float4(localCenter, 1.0)).xyz;
const float3 viewCenter = mul(gViewMatrix, float4(worldCenter, 1.0)).xyz; const float3 viewCenter = mul(gViewMatrix, float4(worldCenter, 1.0)).xyz;

View File

@@ -1106,7 +1106,7 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
static_cast<float>(cachedGaussianSplat->splatCount), static_cast<float>(cachedGaussianSplat->splatCount),
static_cast<float>(workingSet->sortCapacity), static_cast<float>(workingSet->sortCapacity),
shOrder, shOrder,
0.0f) static_cast<float>(cachedGaussianSplat->chunkCount))
}; };
if (passLayout->descriptorSetCount > 0u) { if (passLayout->descriptorSetCount > 0u) {

View File

@@ -50,6 +50,53 @@ constexpr uint32_t kFrameWidth = 1280;
constexpr uint32_t kFrameHeight = 720; constexpr uint32_t kFrameHeight = 720;
constexpr uint32_t kBaselineSubsetSplatCount = 65536u; constexpr uint32_t kBaselineSubsetSplatCount = 65536u;
XCEngine::Core::uint16 FloatToHalfBits(float value) {
uint32_t bits = 0u;
std::memcpy(&bits, &value, sizeof(bits));
const uint32_t sign = (bits >> 16u) & 0x8000u;
uint32_t mantissa = bits & 0x007fffffu;
int32_t exponent = static_cast<int32_t>((bits >> 23u) & 0xffu) - 127 + 15;
if (exponent <= 0) {
if (exponent < -10) {
return static_cast<XCEngine::Core::uint16>(sign);
}
mantissa = (mantissa | 0x00800000u) >> static_cast<uint32_t>(1 - exponent);
if ((mantissa & 0x00001000u) != 0u) {
mantissa += 0x00002000u;
}
return static_cast<XCEngine::Core::uint16>(sign | (mantissa >> 13u));
}
if (exponent >= 31) {
return static_cast<XCEngine::Core::uint16>(sign | 0x7c00u);
}
if ((mantissa & 0x00001000u) != 0u) {
mantissa += 0x00002000u;
if ((mantissa & 0x00800000u) != 0u) {
mantissa = 0u;
++exponent;
if (exponent >= 31) {
return static_cast<XCEngine::Core::uint16>(sign | 0x7c00u);
}
}
}
return static_cast<XCEngine::Core::uint16>(
sign |
(static_cast<uint32_t>(exponent) << 10u) |
(mantissa >> 13u));
}
uint32_t PackHalfRange(float minValue, float maxValue) {
return static_cast<uint32_t>(FloatToHalfBits(minValue)) |
(static_cast<uint32_t>(FloatToHalfBits(maxValue)) << 16u);
}
std::filesystem::path GetRoomPlyPath() { std::filesystem::path GetRoomPlyPath() {
return std::filesystem::path(XCENGINE_TEST_ROOM_PLY_PATH); return std::filesystem::path(XCENGINE_TEST_ROOM_PLY_PATH);
} }
@@ -153,21 +200,39 @@ GaussianSplat* CreateGaussianSplatSubset(
-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max()); -std::numeric_limits<float>::max());
Vector3 minScale(
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max());
Vector3 maxScale(
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max());
for (uint32_t subsetIndex = startIndex; subsetIndex < endIndex; ++subsetIndex) { for (uint32_t subsetIndex = startIndex; subsetIndex < endIndex; ++subsetIndex) {
const Vector3& position = subsetPositions[subsetIndex].position; const Vector3& position = subsetPositions[subsetIndex].position;
const Vector3& scale = subsetOther[subsetIndex].scale;
minPosition.x = std::min(minPosition.x, position.x); minPosition.x = std::min(minPosition.x, position.x);
minPosition.y = std::min(minPosition.y, position.y); minPosition.y = std::min(minPosition.y, position.y);
minPosition.z = std::min(minPosition.z, position.z); minPosition.z = std::min(minPosition.z, position.z);
maxPosition.x = std::max(maxPosition.x, position.x); maxPosition.x = std::max(maxPosition.x, position.x);
maxPosition.y = std::max(maxPosition.y, position.y); maxPosition.y = std::max(maxPosition.y, position.y);
maxPosition.z = std::max(maxPosition.z, position.z); maxPosition.z = std::max(maxPosition.z, position.z);
minScale.x = std::min(minScale.x, scale.x);
minScale.y = std::min(minScale.y, scale.y);
minScale.z = std::min(minScale.z, scale.z);
maxScale.x = std::max(maxScale.x, scale.x);
maxScale.y = std::max(maxScale.y, scale.y);
maxScale.z = std::max(maxScale.z, scale.z);
} }
GaussianSplatChunkRecord chunk = {}; GaussianSplatChunkRecord chunk = {};
chunk.posX = Vector2(minPosition.x, maxPosition.x); chunk.posX = Vector2(minPosition.x, maxPosition.x);
chunk.posY = Vector2(minPosition.y, maxPosition.y); chunk.posY = Vector2(minPosition.y, maxPosition.y);
chunk.posZ = Vector2(minPosition.z, maxPosition.z); chunk.posZ = Vector2(minPosition.z, maxPosition.z);
chunk.sclX = PackHalfRange(minScale.x, maxScale.x);
chunk.sclY = PackHalfRange(minScale.y, maxScale.y);
chunk.sclZ = PackHalfRange(minScale.z, maxScale.z);
subsetChunks[chunkIndex] = chunk; subsetChunks[chunkIndex] = chunk;
} }