Fix NanoVDB volume loading and rendering

This commit is contained in:
2026-04-09 01:11:59 +08:00
parent b839fd98af
commit fde99a4d34
13 changed files with 628024 additions and 55 deletions

View File

@@ -17,7 +17,10 @@ bool VolumeField::Create(VolumeStorageKind storageKind,
const void* payload,
size_t payloadSize,
const Math::Bounds& bounds,
const Math::Vector3& voxelSize) {
const Math::Vector3& voxelSize,
const VolumeIndexBounds& indexBounds,
Core::uint32 gridType,
Core::uint32 gridClass) {
if (payload == nullptr || payloadSize == 0) {
return false;
}
@@ -25,6 +28,9 @@ bool VolumeField::Create(VolumeStorageKind storageKind,
m_storageKind = storageKind;
m_bounds = bounds;
m_voxelSize = voxelSize;
m_indexBounds = indexBounds;
m_gridType = gridType;
m_gridClass = gridClass;
m_payload.Resize(payloadSize);
std::memcpy(m_payload.Data(), payload, payloadSize);
m_isValid = true;

View File

@@ -4,6 +4,13 @@
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Resources/Volume/VolumeField.h>
#if defined(XCENGINE_HAS_NANOVDB)
#include <nanovdb/GridHandle.h>
#include <nanovdb/HostBuffer.h>
#include <nanovdb/io/IO.h>
#endif
#include <cmath>
#include <cstring>
#include <filesystem>
#include <fstream>
@@ -26,6 +33,9 @@ LoadResult CreateVolumeFieldResource(const Containers::String& path,
VolumeStorageKind storageKind,
const Math::Bounds& bounds,
const Math::Vector3& voxelSize,
const VolumeIndexBounds& indexBounds,
Core::uint32 gridType,
Core::uint32 gridClass,
const void* payload,
size_t payloadSize) {
auto* volumeField = new VolumeField();
@@ -37,7 +47,15 @@ LoadResult CreateVolumeFieldResource(const Containers::String& path,
params.memorySize = payloadSize;
volumeField->Initialize(params);
if (!volumeField->Create(storageKind, payload, payloadSize, bounds, voxelSize)) {
if (!volumeField->Create(
storageKind,
payload,
payloadSize,
bounds,
voxelSize,
indexBounds,
gridType,
gridClass)) {
delete volumeField;
return LoadResult(Containers::String("Failed to create volume field resource: ") + path);
}
@@ -45,7 +63,7 @@ LoadResult CreateVolumeFieldResource(const Containers::String& path,
return LoadResult(volumeField);
}
LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
std::filesystem::path ResolveVolumeFieldPath(const Containers::String& path) {
std::filesystem::path resolvedPath(path.CStr());
if (!resolvedPath.is_absolute() && !std::filesystem::exists(resolvedPath)) {
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
@@ -54,6 +72,12 @@ LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
}
}
return resolvedPath.lexically_normal();
}
LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
const std::filesystem::path resolvedPath = ResolveVolumeFieldPath(path);
std::ifstream input(resolvedPath, std::ios::binary);
if (!input.is_open()) {
return LoadResult(Containers::String("Failed to read volume artifact: ") + path);
@@ -66,7 +90,7 @@ LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
}
const bool validHeader =
std::memcmp(header.magic, "XCVOL01", 7) == 0 &&
std::memcmp(header.magic, "XCVOL02", 7) == 0 &&
header.schemaVersion == kVolumeFieldArtifactSchemaVersion &&
header.payloadSize > 0;
if (!validHeader) {
@@ -83,14 +107,118 @@ LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
Math::Bounds bounds;
bounds.SetMinMax(header.boundsMin, header.boundsMax);
VolumeIndexBounds indexBounds = {};
indexBounds.minX = header.indexBoundsMin[0];
indexBounds.minY = header.indexBoundsMin[1];
indexBounds.minZ = header.indexBoundsMin[2];
indexBounds.maxX = header.indexBoundsMax[0];
indexBounds.maxY = header.indexBoundsMax[1];
indexBounds.maxZ = header.indexBoundsMax[2];
return CreateVolumeFieldResource(path,
static_cast<VolumeStorageKind>(header.storageKind),
bounds,
header.voxelSize,
indexBounds,
header.gridType,
header.gridClass,
payload.Data(),
payload.Size());
}
#if defined(XCENGINE_HAS_NANOVDB)
Math::Vector3 ToEngineVector3(const nanovdb::Vec3d& value) {
return Math::Vector3(
static_cast<float>(value[0]),
static_cast<float>(value[1]),
static_cast<float>(value[2]));
}
VolumeIndexBounds ToEngineIndexBounds(const nanovdb::CoordBBox& value) {
VolumeIndexBounds bounds = {};
bounds.minX = value.min()[0];
bounds.minY = value.min()[1];
bounds.minZ = value.min()[2];
bounds.maxX = value.max()[0];
bounds.maxY = value.max()[1];
bounds.maxZ = value.max()[2];
return bounds;
}
bool IsFiniteVector3(const Math::Vector3& value) {
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z);
}
Math::Bounds BuildNanoVDBBounds(const nanovdb::GridMetaData& metadata) {
const Math::Vector3 minPoint = ToEngineVector3(metadata.worldBBox().min());
const Math::Vector3 maxPoint = ToEngineVector3(metadata.worldBBox().max());
if (!IsFiniteVector3(minPoint) || !IsFiniteVector3(maxPoint)) {
return Math::Bounds();
}
Math::Bounds bounds;
bounds.SetMinMax(
Math::Vector3(
std::min(minPoint.x, maxPoint.x),
std::min(minPoint.y, maxPoint.y),
std::min(minPoint.z, maxPoint.z)),
Math::Vector3(
std::max(minPoint.x, maxPoint.x),
std::max(minPoint.y, maxPoint.y),
std::max(minPoint.z, maxPoint.z)));
return bounds;
}
LoadResult LoadNanoVDBSourceFile(const Containers::String& path) {
const std::filesystem::path resolvedPath = ResolveVolumeFieldPath(path);
if (!std::filesystem::exists(resolvedPath)) {
return LoadResult(Containers::String("Failed to read file: ") + path);
}
try {
nanovdb::GridHandle<nanovdb::HostBuffer> handle =
nanovdb::io::readGrid<nanovdb::HostBuffer>(resolvedPath.string());
if (!handle || handle.data() == nullptr || handle.bufferSize() == 0u) {
return LoadResult(Containers::String("Failed to parse NanoVDB grid payload: ") + path);
}
const nanovdb::GridMetaData* metadata = handle.gridMetaData();
Math::Bounds bounds;
Math::Vector3 voxelSize = Math::Vector3::Zero();
VolumeIndexBounds indexBounds = {};
Core::uint32 gridType = 0u;
Core::uint32 gridClass = 0u;
if (metadata != nullptr && metadata->isValid()) {
bounds = BuildNanoVDBBounds(*metadata);
voxelSize = ToEngineVector3(metadata->voxelSize());
indexBounds = ToEngineIndexBounds(metadata->indexBBox());
gridType = static_cast<Core::uint32>(metadata->gridType());
gridClass = static_cast<Core::uint32>(metadata->gridClass());
if (!IsFiniteVector3(voxelSize)) {
voxelSize = Math::Vector3::Zero();
}
}
return CreateVolumeFieldResource(
path,
VolumeStorageKind::NanoVDB,
bounds,
voxelSize,
indexBounds,
gridType,
gridClass,
handle.data(),
static_cast<size_t>(handle.bufferSize()));
} catch (const std::exception& e) {
return LoadResult(
Containers::String("Failed to parse NanoVDB file: ") +
path +
" - " +
e.what());
}
}
#endif
} // namespace
VolumeFieldLoader::VolumeFieldLoader() = default;
@@ -121,17 +249,12 @@ LoadResult VolumeFieldLoader::Load(const Containers::String& path, const ImportS
return LoadVolumeFieldArtifact(path);
}
Containers::Array<Core::uint8> payload = ReadFileData(path);
if (payload.Empty()) {
return LoadResult(Containers::String("Failed to read file: ") + path);
}
return CreateVolumeFieldResource(path,
VolumeStorageKind::NanoVDB,
Math::Bounds(),
Math::Vector3::Zero(),
payload.Data(),
payload.Size());
#if defined(XCENGINE_HAS_NANOVDB)
return LoadNanoVDBSourceFile(path);
#else
return LoadResult(
Containers::String("NanoVDB source-file support is unavailable in this build: ") + path);
#endif
}
ImportSettings* VolumeFieldLoader::GetDefaultSettings() const {