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