From 1ccf2d6a08091a9515464b75c448310bbff503a2 Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Wed, 6 Mar 2019 19:08:33 -0800 Subject: [PATCH] gltfio: fix up and enhance AABB functionality. gltfio computes two types of bounding boxes: one for renderables (used for frustum culling) and one for the overall asset (used for positioning the camera or asset). Both of these are based on the min+max attributes in the glTF file, but the asset-level box was incorrect because only two corners of the transformed AABB were considered. This CL also adds optional computation of bounding boxes that crawls through the vertex positions. This is useful when diagnosing potential issues with the asset's min+max info. These enhancements are motivated by a culling issue seen with the voxel Cathedral on sketchfab. --- libs/gltfio/include/gltfio/ResourceLoader.h | 4 +- libs/gltfio/src/AssetLoader.cpp | 28 ++++++--- libs/gltfio/src/ResourceLoader.cpp | 66 ++++++++++++++++++++- 3 files changed, 86 insertions(+), 12 deletions(-) diff --git a/libs/gltfio/include/gltfio/ResourceLoader.h b/libs/gltfio/include/gltfio/ResourceLoader.h index 996eec10bc..13f855ab95 100644 --- a/libs/gltfio/include/gltfio/ResourceLoader.h +++ b/libs/gltfio/include/gltfio/ResourceLoader.h @@ -34,6 +34,7 @@ struct ResourceConfiguration { class filament::Engine* engine; utils::Path basePath; bool normalizeSkinningWeights; + bool recomputeBoundingBoxes; }; /** @@ -60,7 +61,8 @@ public: private: bool createTextures(details::FFilamentAsset* asset) const; void computeTangents(details::FFilamentAsset* asset) const; - void normalizeWeights(details::FFilamentAsset* asset) const; + void normalizeSkinningWeights(details::FFilamentAsset* asset) const; + void updateBoundingBoxes(details::FFilamentAsset* asset) const; details::AssetPool* mPool; const ResourceConfiguration mConfig; }; diff --git a/libs/gltfio/src/AssetLoader.cpp b/libs/gltfio/src/AssetLoader.cpp index 7b43fed4c7..aef103695e 100644 --- a/libs/gltfio/src/AssetLoader.cpp +++ b/libs/gltfio/src/AssetLoader.cpp @@ -73,11 +73,12 @@ using MeshCache = tsl::robin_map>; // Filament materials are cached by the MaterialGenerator, but material instances are cached here. using MatInstanceCache = tsl::robin_map; -// Filament automatically infers the size of driver-level vertex buffers from the attribute data -// (stride, count, offset) and clients are expected to avoid uploading data blobs that exceed this -// size. Since this information doesn't exist in the glTF we need to compute it manually. This is a -// bit of a cheat, cgltf_calc_size is private but its implementation file is available in this cpp -// file. +// Sometimes a glTF bufferview includes unused data at the end (e.g. in skinning.gltf) so we need to +// compute the correct size of the vertex buffer. Filament automatically infers the size of +// driver-level vertex buffers from the attribute data (stride, count, offset) and clients are +// expected to avoid uploading data blobs that exceed this size. Since this information doesn't +// exist in the glTF we need to compute it manually. This is a bit of a cheat, cgltf_calc_size is +// private but its implementation file is available in this cpp file. static uint32_t computeBindingSize(const cgltf_accessor* accessor){ cgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type); return uint32_t(accessor->stride * (accessor->count - 1) + element_size); @@ -319,10 +320,19 @@ void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity) { builder.geometry(index, primType, outputPrim->vertices, outputPrim->indices); } - // Expand the world-space bounding box. - float3 minpt = (worldTransform * float4(aabb.min, 1.0)).xyz; - float3 maxpt = (worldTransform * float4(aabb.max, 1.0)).xyz; + // Transform all eight corners of the bounding box and find the new AABB. + float3 a = (worldTransform * float4(aabb.min.x, aabb.min.y, aabb.min.z, 1.0)).xyz; + float3 b = (worldTransform * float4(aabb.min.x, aabb.min.y, aabb.max.z, 1.0)).xyz; + float3 c = (worldTransform * float4(aabb.min.x, aabb.max.y, aabb.min.z, 1.0)).xyz; + float3 d = (worldTransform * float4(aabb.min.x, aabb.max.y, aabb.max.z, 1.0)).xyz; + float3 e = (worldTransform * float4(aabb.max.x, aabb.min.y, aabb.min.z, 1.0)).xyz; + float3 f = (worldTransform * float4(aabb.max.x, aabb.min.y, aabb.max.z, 1.0)).xyz; + float3 g = (worldTransform * float4(aabb.max.x, aabb.max.y, aabb.min.z, 1.0)).xyz; + float3 h = (worldTransform * float4(aabb.max.x, aabb.max.y, aabb.max.z, 1.0)).xyz; + float3 minpt = min(min(min(min(min(min(min(a, b), c), d), e), f), g), h); + float3 maxpt = max(max(max(max(max(max(max(a, b), c), d), e), f), g), h); + // Expand the world-space bounding box. mResult->mBoundingBox.min = min(mResult->mBoundingBox.min, minpt); mResult->mBoundingBox.max = max(mResult->mBoundingBox.max, maxpt); @@ -331,7 +341,7 @@ void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity) { } builder - .boundingBox({aabb.min, aabb.max}) + .boundingBox(Box().set(aabb.min, aabb.max)) .culling(true) .castShadows(true) .receiveShadows(true) diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index bf82359b7b..7cecaf0eba 100644 --- a/libs/gltfio/src/ResourceLoader.cpp +++ b/libs/gltfio/src/ResourceLoader.cpp @@ -138,7 +138,11 @@ bool ResourceLoader::loadResources(FilamentAsset* asset) { // feature, and instead simply require correct models. See also: // https://github.com/KhronosGroup/glTF-Sample-Models/issues/215 if (mConfig.normalizeSkinningWeights) { - normalizeWeights(fasset); + normalizeSkinningWeights(fasset); + } + + if (mConfig.recomputeBoundingBoxes) { + updateBoundingBoxes(fasset); } // Upload data to the GPU. @@ -377,7 +381,7 @@ void ResourceLoader::computeTangents(FFilamentAsset* asset) const { } } -void ResourceLoader::normalizeWeights(details::FFilamentAsset* asset) const { +void ResourceLoader::normalizeSkinningWeights(details::FFilamentAsset* asset) const { auto normalize = [](cgltf_accessor* data) { if (data->type != cgltf_type_vec4 || data->component_type != cgltf_component_type_r_32f) { slog.w << "Cannot normalize weights, unsupported attribute type." << io::endl; @@ -409,4 +413,62 @@ void ResourceLoader::normalizeWeights(details::FFilamentAsset* asset) const { } } +void ResourceLoader::updateBoundingBoxes(details::FFilamentAsset* asset) const { + auto& rm = mConfig.engine->getRenderableManager(); + auto& tm = mConfig.engine->getTransformManager(); + + auto computeBoundingBox = [&](const cgltf_primitive& prim) { + Aabb aabb; + for (cgltf_size slot = 0; slot < prim.attributes_count; slot++) { + const cgltf_attribute& attr = prim.attributes[slot]; + if (attr.type == cgltf_attribute_type_position) { + const cgltf_accessor* accessor = attr.data; + float3 pt; + for (cgltf_size i = 0, n = accessor->count; i < n; ++i) { + cgltf_accessor_read_float(accessor, i, &pt.x, 3); + aabb.min = min(aabb.min, pt); + aabb.max = max(aabb.max, pt); + } + break; + } + } + return aabb; + }; + + Aabb assetBounds; + for (auto iter : asset->mNodeMap) { + const cgltf_mesh* mesh = iter.first->mesh; + if (mesh) { + // Find the object-space bounds for the renderable by unioning the bounds of each prim. + Aabb aabb; + for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) { + Aabb primBounds = computeBoundingBox(mesh->primitives[index]); + aabb.min = min(aabb.min, primBounds.min); + aabb.max = max(aabb.max, primBounds.max); + } + auto renderable = rm.getInstance(iter.second); + rm.setAxisAlignedBoundingBox(renderable, Box().set(aabb.min, aabb.max)); + + // Transform all eight corners of the bounding box to world space and find the new AABB. + // This is used for the asset-level bounding box. + auto transformable = tm.getInstance(iter.second); + mat4f worldTransform = tm.getWorldTransform(transformable); + float3 a = (worldTransform * float4(aabb.min.x, aabb.min.y, aabb.min.z, 1.0)).xyz; + float3 b = (worldTransform * float4(aabb.min.x, aabb.min.y, aabb.max.z, 1.0)).xyz; + float3 c = (worldTransform * float4(aabb.min.x, aabb.max.y, aabb.min.z, 1.0)).xyz; + float3 d = (worldTransform * float4(aabb.min.x, aabb.max.y, aabb.max.z, 1.0)).xyz; + float3 e = (worldTransform * float4(aabb.max.x, aabb.min.y, aabb.min.z, 1.0)).xyz; + float3 f = (worldTransform * float4(aabb.max.x, aabb.min.y, aabb.max.z, 1.0)).xyz; + float3 g = (worldTransform * float4(aabb.max.x, aabb.max.y, aabb.min.z, 1.0)).xyz; + float3 h = (worldTransform * float4(aabb.max.x, aabb.max.y, aabb.max.z, 1.0)).xyz; + float3 minpt = min(min(min(min(min(min(min(a, b), c), d), e), f), g), h); + float3 maxpt = max(max(max(max(max(max(max(a, b), c), d), e), f), g), h); + + assetBounds.min = min(assetBounds.min, minpt); + assetBounds.max = max(assetBounds.max, maxpt); + } + } + asset->mBoundingBox = assetBounds; +} + } // namespace gltfio