Generate gaussian splat chunks during PLY import

This commit is contained in:
2026-04-11 07:13:32 +08:00
parent 92d5cc61cf
commit ff4e3f639a
3 changed files with 208 additions and 16 deletions

View File

@@ -450,22 +450,185 @@ bool IsFinite(const Math::Quaternion& value) {
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z) && std::isfinite(value.w);
}
Math::Vector3 MinVector3(const Math::Vector3& lhs, const Math::Vector3& rhs) {
return Math::Vector3(
std::min(lhs.x, rhs.x),
std::min(lhs.y, rhs.y),
std::min(lhs.z, rhs.z));
}
Math::Vector3 MaxVector3(const Math::Vector3& lhs, const Math::Vector3& rhs) {
return Math::Vector3(
std::max(lhs.x, rhs.x),
std::max(lhs.y, rhs.y),
std::max(lhs.z, rhs.z));
}
Math::Vector4 MinVector4(const Math::Vector4& lhs, const Math::Vector4& rhs) {
return Math::Vector4(
std::min(lhs.x, rhs.x),
std::min(lhs.y, rhs.y),
std::min(lhs.z, rhs.z),
std::min(lhs.w, rhs.w));
}
Math::Vector4 MaxVector4(const Math::Vector4& lhs, const Math::Vector4& rhs) {
return Math::Vector4(
std::max(lhs.x, rhs.x),
std::max(lhs.y, rhs.y),
std::max(lhs.z, rhs.z),
std::max(lhs.w, rhs.w));
}
Core::uint16 FloatToHalfBits(float value) {
std::uint32_t bits = 0u;
std::memcpy(&bits, &value, sizeof(bits));
const std::uint32_t sign = (bits >> 16u) & 0x8000u;
std::uint32_t mantissa = bits & 0x007fffffu;
std::int32_t exponent = static_cast<std::int32_t>((bits >> 23u) & 0xffu) - 127 + 15;
if (exponent <= 0) {
if (exponent < -10) {
return static_cast<Core::uint16>(sign);
}
mantissa = (mantissa | 0x00800000u) >> static_cast<std::uint32_t>(1 - exponent);
if ((mantissa & 0x00001000u) != 0u) {
mantissa += 0x00002000u;
}
return static_cast<Core::uint16>(sign | (mantissa >> 13u));
}
if (exponent >= 31) {
return static_cast<Core::uint16>(sign | 0x7c00u);
}
if ((mantissa & 0x00001000u) != 0u) {
mantissa += 0x00002000u;
if ((mantissa & 0x00800000u) != 0u) {
mantissa = 0u;
++exponent;
if (exponent >= 31) {
return static_cast<Core::uint16>(sign | 0x7c00u);
}
}
}
return static_cast<Core::uint16>(
sign |
(static_cast<std::uint32_t>(exponent) << 10u) |
(mantissa >> 13u));
}
Core::uint32 PackHalfRange(float minValue, float maxValue) {
return static_cast<Core::uint32>(FloatToHalfBits(minValue)) |
(static_cast<Core::uint32>(FloatToHalfBits(maxValue)) << 16u);
}
Core::uint32 ComputeChunkCount(Core::uint32 splatCount) {
return splatCount == 0u
? 0u
: ((splatCount + kGaussianSplatChunkSize - 1u) / kGaussianSplatChunkSize);
}
void BuildChunkRecords(Core::uint32 vertexCount,
const GaussianSplatPositionRecord* positions,
const GaussianSplatOtherRecord* other,
const GaussianSplatColorRecord* colors,
const GaussianSplatSHRecord* shRecords,
GaussianSplatChunkRecord* chunks,
Core::uint32 chunkCount) {
constexpr Core::uint32 kSHCoefficientsPerChannel =
kGaussianSplatSHCoefficientCount / kGaussianSplatSHColorChannelCount;
for (Core::uint32 chunkIndex = 0u; chunkIndex < chunkCount; ++chunkIndex) {
const Core::uint32 startIndex = chunkIndex * kGaussianSplatChunkSize;
const Core::uint32 endIndex = std::min(startIndex + kGaussianSplatChunkSize, vertexCount);
Math::Vector3 minPosition(std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max());
Math::Vector3 maxPosition(-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max());
Math::Vector3 minScale(std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max());
Math::Vector3 maxScale(-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max());
Math::Vector4 minColor(std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max());
Math::Vector4 maxColor(-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max());
Math::Vector3 minSh(std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max());
Math::Vector3 maxSh(-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max());
for (Core::uint32 vertexIndex = startIndex; vertexIndex < endIndex; ++vertexIndex) {
minPosition = MinVector3(minPosition, positions[vertexIndex].position);
maxPosition = MaxVector3(maxPosition, positions[vertexIndex].position);
minScale = MinVector3(minScale, other[vertexIndex].scale);
maxScale = MaxVector3(maxScale, other[vertexIndex].scale);
minColor = MinVector4(minColor, colors[vertexIndex].colorOpacity);
maxColor = MaxVector4(maxColor, colors[vertexIndex].colorOpacity);
const GaussianSplatSHRecord& shRecord = shRecords[vertexIndex];
for (Core::uint32 coefficientIndex = 0u; coefficientIndex < kSHCoefficientsPerChannel; ++coefficientIndex) {
minSh.x = std::min(minSh.x, shRecord.coefficients[coefficientIndex]);
maxSh.x = std::max(maxSh.x, shRecord.coefficients[coefficientIndex]);
minSh.y = std::min(minSh.y, shRecord.coefficients[coefficientIndex + kSHCoefficientsPerChannel]);
maxSh.y = std::max(maxSh.y, shRecord.coefficients[coefficientIndex + kSHCoefficientsPerChannel]);
minSh.z = std::min(minSh.z, shRecord.coefficients[coefficientIndex + (kSHCoefficientsPerChannel * 2u)]);
maxSh.z = std::max(maxSh.z, shRecord.coefficients[coefficientIndex + (kSHCoefficientsPerChannel * 2u)]);
}
}
GaussianSplatChunkRecord chunk = {};
chunk.posX = Math::Vector2(minPosition.x, maxPosition.x);
chunk.posY = Math::Vector2(minPosition.y, maxPosition.y);
chunk.posZ = Math::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);
chunk.colR = PackHalfRange(minColor.x, maxColor.x);
chunk.colG = PackHalfRange(minColor.y, maxColor.y);
chunk.colB = PackHalfRange(minColor.z, maxColor.z);
chunk.colA = PackHalfRange(minColor.w, maxColor.w);
chunk.shR = PackHalfRange(minSh.x, maxSh.x);
chunk.shG = PackHalfRange(minSh.y, maxSh.y);
chunk.shB = PackHalfRange(minSh.z, maxSh.z);
chunks[chunkIndex] = chunk;
}
}
void BuildSections(Core::uint32 vertexCount,
Core::uint32 chunkCount,
Containers::Array<GaussianSplatSection>& outSections,
size_t& outPayloadSize) {
outSections.Clear();
outSections.Reserve(4u);
outSections.Reserve(chunkCount > 0u ? 5u : 4u);
size_t payloadOffset = 0u;
auto appendSection = [&](GaussianSplatSectionType type,
GaussianSplatSectionFormat format,
size_t elementStride) {
size_t elementStride,
Core::uint32 elementCount) {
GaussianSplatSection section;
section.type = type;
section.format = format;
section.dataOffset = payloadOffset;
section.dataSize = elementStride * static_cast<size_t>(vertexCount);
section.elementCount = vertexCount;
section.dataSize = elementStride * static_cast<size_t>(elementCount);
section.elementCount = elementCount;
section.elementStride = static_cast<Core::uint32>(elementStride);
outSections.PushBack(section);
payloadOffset += section.dataSize;
@@ -474,19 +637,30 @@ void BuildSections(Core::uint32 vertexCount,
appendSection(
GaussianSplatSectionType::Positions,
GaussianSplatSectionFormat::VectorFloat32,
sizeof(GaussianSplatPositionRecord));
sizeof(GaussianSplatPositionRecord),
vertexCount);
appendSection(
GaussianSplatSectionType::Other,
GaussianSplatSectionFormat::OtherFloat32,
sizeof(GaussianSplatOtherRecord));
sizeof(GaussianSplatOtherRecord),
vertexCount);
appendSection(
GaussianSplatSectionType::Color,
GaussianSplatSectionFormat::ColorRGBA32F,
sizeof(GaussianSplatColorRecord));
sizeof(GaussianSplatColorRecord),
vertexCount);
appendSection(
GaussianSplatSectionType::SH,
GaussianSplatSectionFormat::SHFloat32,
sizeof(GaussianSplatSHRecord));
sizeof(GaussianSplatSHRecord),
vertexCount);
if (chunkCount > 0u) {
appendSection(
GaussianSplatSectionType::Chunks,
GaussianSplatSectionFormat::ChunkFloat32,
sizeof(GaussianSplatChunkRecord),
chunkCount);
}
outPayloadSize = payloadOffset;
}
@@ -506,9 +680,10 @@ LoadResult ImportGaussianSplatPlyFile(const Containers::String& path) {
return LoadResult(Containers::String("Failed to parse GaussianSplat PLY header: ") + path + " - " + headerError);
}
const Core::uint32 chunkCount = ComputeChunkCount(header.vertexCount);
Containers::Array<GaussianSplatSection> sections;
size_t payloadSize = 0u;
BuildSections(header.vertexCount, sections, payloadSize);
BuildSections(header.vertexCount, chunkCount, sections, payloadSize);
Containers::Array<Core::uint8> payload;
payload.Resize(payloadSize);
@@ -521,6 +696,10 @@ LoadResult ImportGaussianSplatPlyFile(const Containers::String& path) {
payload.Data() + static_cast<size_t>(sections[2].dataOffset));
auto* shRecords = reinterpret_cast<GaussianSplatSHRecord*>(
payload.Data() + static_cast<size_t>(sections[3].dataOffset));
auto* chunks = chunkCount > 0u
? reinterpret_cast<GaussianSplatChunkRecord*>(
payload.Data() + static_cast<size_t>(sections[4].dataOffset))
: nullptr;
Math::Bounds bounds;
bool hasBounds = false;
@@ -600,17 +779,21 @@ LoadResult ImportGaussianSplatPlyFile(const Containers::String& path) {
}
}
if (chunkCount > 0u && chunks != nullptr) {
BuildChunkRecords(header.vertexCount, positions, other, colors, shRecords, chunks, chunkCount);
}
GaussianSplatMetadata metadata;
metadata.contentVersion = 1u;
metadata.splatCount = header.vertexCount;
metadata.chunkCount = 0u;
metadata.chunkCount = chunkCount;
metadata.cameraCount = 0u;
metadata.bounds = bounds;
metadata.positionFormat = GaussianSplatSectionFormat::VectorFloat32;
metadata.otherFormat = GaussianSplatSectionFormat::OtherFloat32;
metadata.colorFormat = GaussianSplatSectionFormat::ColorRGBA32F;
metadata.shFormat = GaussianSplatSectionFormat::SHFloat32;
metadata.chunkFormat = GaussianSplatSectionFormat::Unknown;
metadata.chunkFormat = chunkCount > 0u ? GaussianSplatSectionFormat::ChunkFloat32 : GaussianSplatSectionFormat::Unknown;
metadata.cameraFormat = GaussianSplatSectionFormat::Unknown;
auto* gaussianSplat = new GaussianSplat();