/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "FFilamentAsset.h" #include "GltfEnums.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CGLTF_IMPLEMENTATION #include #include "math.h" #include "upcast.h" using namespace filament; using namespace filament::math; using namespace utils; namespace gltfio { static const auto FREE_CALLBACK = [](void* mem, size_t, void*) { free(mem); }; // MeshCache // --------- // If a given glTF mesh is referenced by multiple glTF nodes, then it generates a separate Filament // renderable for each of those nodes. All renderables generated by a given mesh share a common set // of VertexBuffer and IndexBuffer objects. To achieve the sharing behavior, the loader maintains a // small cache. The cache keys are glTF mesh definitions and the cache entries are lists of // primitives, where a "primitive" is a reference to a Filament VertexBuffer and IndexBuffer. struct Primitive { VertexBuffer* vertices = nullptr; IndexBuffer* indices = nullptr; Aabb aabb; // object-space bounding box }; using MeshCache = tsl::robin_map>; // MatInstanceCache // ---------------- // Each glTF material definition corresponds to a single filament::MaterialInstance, which are // cached here in the loader. The filament::Material objects that are used to create instances are // cached in MaterialProvider. If a given glTF material is referenced by multiple glTF meshes, then // their corresponding filament primitives will share the same Filament MaterialInstance and UvMap. // The UvMap is a mapping from each texcoord slot in glTF to one of Filament's 2 texcoord sets. struct MaterialEntry { MaterialInstance* instance; UvMap uvmap; }; using MatInstanceCache = tsl::robin_map; // 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. 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); } uint32_t computeBindingOffset(const cgltf_accessor* accessor) { return uint32_t(accessor->offset + accessor->buffer_view->offset); } static const char* getNodeName(const cgltf_node* node, const char* defaultNodeName) { if (node->name) return node->name; if (node->mesh && node->mesh->name) return node->mesh->name; if (node->light && node->light->name) return node->light->name; return defaultNodeName; } struct FAssetLoader : public AssetLoader { FAssetLoader(const AssetConfiguration& config) : mEntityManager(config.entities ? *config.entities : EntityManager::get()), mRenderableManager(config.engine->getRenderableManager()), mNameManager(config.names), mTransformManager(config.engine->getTransformManager()), mMaterials(config.materials), mEngine(config.engine), mDefaultNodeName(config.defaultNodeName) {} FFilamentAsset* createAssetFromJson(const uint8_t* bytes, uint32_t nbytes); FFilamentAsset* createAssetFromBinary(const uint8_t* bytes, uint32_t nbytes); FFilamentAsset* createInstancedAsset(const uint8_t* bytes, uint32_t numBytes, FilamentInstance** instances, size_t numInstances); bool createAssets(const uint8_t* bytes, uint32_t numBytes, FilamentAsset** assets, size_t numAssets); ~FAssetLoader() { delete mMaterials; } void destroyAsset(const FFilamentAsset* asset) { delete asset; } size_t getMaterialsCount() const noexcept { return mMaterials->getMaterialsCount(); } NameComponentManager* getNames() const noexcept { return mNameManager; } const Material* const* getMaterials() const noexcept { return mMaterials->getMaterials(); } void createAsset(const cgltf_data* srcAsset, size_t numInstances); void createEntity(const cgltf_node* node, Entity parent, bool enableLight, FFilamentInstance* instance); void createRenderable(const cgltf_node* node, Entity entity, const char* name); bool createPrimitive(const cgltf_primitive* inPrim, Primitive* outPrim, const UvMap& uvmap, const char* name); void createLight(const cgltf_node* node, Entity entity); MaterialInstance* createMaterialInstance(const cgltf_material* inputMat, UvMap* uvmap, bool vertexColor); void addTextureBinding(MaterialInstance* materialInstance, const char* parameterName, const cgltf_texture* srcTexture, bool srgb); bool primitiveHasVertexColor(const cgltf_primitive* inPrim) const; static LightManager::Type getLightType(const cgltf_light_type type); EntityManager& mEntityManager; RenderableManager& mRenderableManager; NameComponentManager* mNameManager; TransformManager& mTransformManager; MaterialProvider* mMaterials; Engine* mEngine; // The loader owns a few transient mappings used only for the current asset being loaded. FFilamentAsset* mResult; MatInstanceCache mMatInstanceCache; MeshCache mMeshCache; const char* mDefaultNodeName; bool mError = false; bool mDiagnosticsEnabled = false; }; FILAMENT_UPCAST(AssetLoader) FFilamentAsset* FAssetLoader::createAssetFromJson(const uint8_t* bytes, uint32_t nbytes) { cgltf_options options { cgltf_file_type_invalid }; cgltf_data* sourceAsset; cgltf_result result = cgltf_parse(&options, bytes, nbytes, &sourceAsset); if (result != cgltf_result_success) { slog.e << "Unable to parse JSON file." << io::endl; return nullptr; } createAsset(sourceAsset, 0); return mResult; } FFilamentAsset* FAssetLoader::createAssetFromBinary(const uint8_t* bytes, uint32_t nbytes) { // The cgltf library handles GLB efficiently by pointing all buffer views into the source data. // However, we wish our API to be simple and safe, allowing clients to free up their source blob // immediately, without worrying about when all the data has finished uploading asynchronously // to the GPU. To achieve this we create a copy of the source blob and stash it inside the // asset, asking cgltf to parse the copy. This allows us to free it at the correct time (i.e. // after all GPU uploads have completed). Although it incurs a copy, the added safety of this // API seems worthwhile. std::vector glbdata(bytes, bytes + nbytes); cgltf_options options { cgltf_file_type_glb }; cgltf_data* sourceAsset; cgltf_result result = cgltf_parse(&options, glbdata.data(), nbytes, &sourceAsset); if (result != cgltf_result_success) { slog.e << "Unable to parse glb file." << io::endl; return nullptr; } createAsset(sourceAsset, 0); if (mResult) { glbdata.swap(mResult->mGlbData); } return mResult; } FFilamentAsset* FAssetLoader::createInstancedAsset(const uint8_t* bytes, uint32_t numBytes, FilamentInstance** instances, size_t numInstances) { ASSERT_PRECONDITION(numInstances > 0, "Instance count must be 1 or more."); // This method can be used to load JSON or GLB. By using a default options struct, we are asking // cgltf to examine the magic identifier to determine which type of file is being loaded. cgltf_options options {}; // Clients can free up their source blob immediately, but cgltf has pointers into the data that // need to stay valid. Therefore we create a copy of the source blob and stash it inside the // asset. std::vector glbdata(bytes, bytes + numBytes); cgltf_data* sourceAsset; cgltf_result result = cgltf_parse(&options, glbdata.data(), numBytes, &sourceAsset); if (result != cgltf_result_success) { slog.e << "Unable to parse glTF file." << io::endl; return nullptr; } createAsset(sourceAsset, numInstances); if (mResult) { glbdata.swap(mResult->mGlbData); std::copy_n(mResult->mInstances.data(), numInstances, instances); } return mResult; } void FAssetLoader::createAsset(const cgltf_data* srcAsset, size_t numInstances) { SYSTRACE_CALL(); #if !GLTFIO_DRACO_SUPPORTED for (cgltf_size i = 0; i < srcAsset->extensions_required_count; i++) { if (!strcmp(srcAsset->extensions_required[i], "KHR_draco_mesh_compression")) { slog.e << "KHR_draco_mesh_compression is not supported." << io::endl; mResult = nullptr; return; } } #endif mResult = new FFilamentAsset(mEngine, mNameManager); mResult->mSourceAsset = srcAsset; mResult->acquireSourceAsset(); // If there is no default scene specified, then the default is the first one. // It is not an error for a glTF file to have zero scenes. const cgltf_scene* scene = srcAsset->scene ? srcAsset->scene : srcAsset->scenes; if (!scene) { return; } // Create a single root node with an identity transform as a convenience to the client. mResult->mRoot = mEntityManager.create(); mTransformManager.create(mResult->mRoot); if (numInstances == 0) { // For each scene root, recursively create all entities. for (cgltf_size i = 0, len = scene->nodes_count; i < len; ++i) { cgltf_node** nodes = scene->nodes; createEntity(nodes[i], mResult->mRoot, true, nullptr); } } else { // Create a separate entity hierarchy for each instance. Note that mMeshCache (vertex // buffers and index buffers) and mMatInstanceCache (materials and textures) help avoid // needless duplication of resources. for (size_t index = 0; index < numInstances; ++index) { // Create a root node within each instance that is a child of the master root. auto rootTransform = mTransformManager.getInstance(mResult->mRoot); Entity instanceRoot = mEntityManager.create(); mTransformManager.create(instanceRoot, rootTransform); // Create an instance object, which is a just a lightweight wrapper around a vector of // entities and a lazily created animator. FFilamentInstance* instance = new FFilamentInstance; instance->root = instanceRoot; instance->animator = nullptr; instance->owner = mResult; mResult->mInstances.push_back(instance); // For each scene root, recursively create all entities. for (cgltf_size i = 0, len = scene->nodes_count; i < len; ++i) { cgltf_node** nodes = scene->nodes; createEntity(nodes[i], instanceRoot, index == 0, instance); } } } if (mError) { delete mResult; mResult = nullptr; } // Find every unique resource URI and store a pointer to any of the cgltf-owned cstrings // that match the URI. These strings get freed during releaseSourceData(). tsl::robin_map resourceUris; auto addResourceUri = [&resourceUris](const char* uri) { if (uri) { resourceUris[uri] = uri; } }; for (cgltf_size i = 0, len = srcAsset->buffers_count; i < len; ++i) { addResourceUri(srcAsset->buffers[i].uri); } for (cgltf_size i = 0, len = srcAsset->images_count; i < len; ++i) { addResourceUri(srcAsset->images[i].uri); } mResult->mResourceUris.reserve(resourceUris.size()); for (auto pair : resourceUris) { mResult->mResourceUris.push_back(pair.second); } // We're done with the import, so free up transient bookkeeping resources. mMatInstanceCache.clear(); mMeshCache.clear(); mError = false; } void FAssetLoader::createEntity(const cgltf_node* node, Entity parent, bool enableLight, FFilamentInstance* instance) { Entity entity = mEntityManager.create(); // Always create a transform component to reflect the original hierarchy. mat4f localTransform; if (node->has_matrix) { memcpy(&localTransform[0][0], &node->matrix[0], 16 * sizeof(float)); } else { quatf* rotation = (quatf*) &node->rotation[0]; float3* scale = (float3*) &node->scale[0]; float3* translation = (float3*) &node->translation[0]; localTransform = composeMatrix(*translation, *rotation, *scale); } auto parentTransform = mTransformManager.getInstance(parent); mTransformManager.create(entity, parentTransform, localTransform); // Update the asset's entity list and private node mapping. mResult->mEntities.push_back(entity); if (instance) { instance->entities.push_back(entity); instance->nodeMap[node] = entity; } else { mResult->mNodeMap[node] = entity; } const char* name = getNodeName(node, mDefaultNodeName); if (mNameManager && name) { mNameManager->addComponent(entity); mNameManager->setName(mNameManager->getInstance(entity), name); } // If no name is provided in the glTF or AssetConfiguration, use "node" for error messages. name = name ? name : "node"; // If the node has a mesh, then create a renderable component. if (node->mesh) { createRenderable(node, entity, name); } if (node->light && enableLight) { createLight(node, entity); } for (cgltf_size i = 0, len = node->children_count; i < len; ++i) { createEntity(node->children[i], entity, enableLight, instance); } } void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity, const char* name) { const cgltf_mesh* mesh = node->mesh; // Compute the transform relative to the root. auto thisTransform = mTransformManager.getInstance(entity); mat4f worldTransform = mTransformManager.getWorldTransform(thisTransform); cgltf_size nprims = mesh->primitives_count; RenderableManager::Builder builder(nprims); // If the mesh is already loaded, obtain the list of Filament VertexBuffer / IndexBuffer objects // that were already generated (one for each primitive), otherwise allocate a new list of // pointers for the primitives. auto iter = mMeshCache.find(mesh); if (iter == mMeshCache.end()) { mMeshCache[mesh].resize(nprims); } Primitive* outputPrim = mMeshCache[mesh].data(); const cgltf_primitive* inputPrim = &mesh->primitives[0]; Aabb aabb; cgltf_size numMorphTargets = 0; // For each prim, create a Filament VertexBuffer, IndexBuffer, and MaterialInstance. for (cgltf_size index = 0; index < nprims; ++index, ++outputPrim, ++inputPrim) { RenderableManager::PrimitiveType primType; if (!getPrimitiveType(inputPrim->type, &primType)) { slog.e << "Unsupported primitive type in " << name << io::endl; } if (inputPrim->targets_count > 0) { if (numMorphTargets > 0 && inputPrim->targets_count != numMorphTargets) { slog.e << "Sister primitives must all have the same number of morph targets." << io::endl; } numMorphTargets = inputPrim->targets_count; } // Create a material instance for this primitive or fetch one from the cache. UvMap uvmap {}; bool hasVertexColor = primitiveHasVertexColor(inputPrim); MaterialInstance* mi = createMaterialInstance(inputPrim->material, &uvmap, hasVertexColor); if (!mi) { mError = true; continue; } mResult->mDependencyGraph.addEdge(entity, mi); builder.material(index, mi); // Create a Filament VertexBuffer and IndexBuffer for this prim if we haven't already. if (!outputPrim->vertices && !createPrimitive(inputPrim, outputPrim, uvmap, name)) { mError = true; continue; } // Expand the object-space bounding box. aabb.min = min(outputPrim->aabb.min, aabb.min); aabb.max = max(outputPrim->aabb.max, aabb.max); // We are not using the optional offset, minIndex, maxIndex, and count arguments when // calling geometry() on the builder. It appears that the glTF spec does not have // facilities for these parameters, which is not a huge loss since some of the buffer // view and accessor features already have this functionality. builder.geometry(index, primType, outputPrim->vertices, outputPrim->indices); } if (numMorphTargets > 0) { builder.morphing(true); } const Aabb transformed = aabb.transform(worldTransform); // Expand the world-space bounding box. mResult->mBoundingBox.min = min(mResult->mBoundingBox.min, transformed.min); mResult->mBoundingBox.max = max(mResult->mBoundingBox.max, transformed.max); if (node->skin) { builder.skinning(node->skin->joints_count); } // Per the spec, glTF models must have valid mix / max annotations for position attributes. // However in practice these can be missing and we should be as robust as other glTF viewers. // If desired, clients can enable the "recomputeBoundingBoxes" feature in ResourceLoader. Box box = Box().set(aabb.min, aabb.max); if (box.isEmpty()) { slog.w << "Missing bounding box in " << name << io::endl; box = Box().set(std::numeric_limits::lowest(), std::numeric_limits::max()); } builder .boundingBox(box) .culling(true) .castShadows(true) .receiveShadows(true) .build(*mEngine, entity); // According to the spec, the mesh may or may not specify default weights, regardless of whether // it actually has morph targets. If it has morphing enabled then the default weights are 0. If // node weights are provided, they override the ones specified on the mesh. if (numMorphTargets > 0) { RenderableManager::Instance renderable = mRenderableManager.getInstance(entity); float4 weights(0, 0, 0, 0); for (cgltf_size i = 0; i < std::min(MAX_MORPH_TARGETS, mesh->weights_count); ++i) { weights[i] = mesh->weights[i]; } for (cgltf_size i = 0; i < std::min(MAX_MORPH_TARGETS, node->weights_count); ++i) { weights[i] = node->weights[i]; } mRenderableManager.setMorphWeights(renderable, weights); } } bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* outPrim, const UvMap& uvmap, const char* name) { // Create a little lambda that appends to the asset's vertex buffer slots. auto slots = &mResult->mBufferSlots; auto addBufferSlot = [slots](BufferSlot entry) { slots->push_back(entry); }; // In glTF, each primitive may or may not have an index buffer. IndexBuffer* indices; const cgltf_accessor* accessor = inPrim->indices; if (accessor) { IndexBuffer::IndexType indexType; if (!getIndexType(accessor->component_type, &indexType)) { utils::slog.e << "Unrecognized index type in " << name << utils::io::endl; return false; } indices = IndexBuffer::Builder() .indexCount(accessor->count) .bufferType(indexType) .build(*mEngine); BufferSlot slot = { accessor }; slot.indexBuffer = indices; addBufferSlot(slot); } else { // If a primitive does not have an index buffer, generate a trivial one now. const uint32_t vertexCount = inPrim->attributes[0].data->count; indices = IndexBuffer::Builder() .indexCount(vertexCount) .bufferType(IndexBuffer::IndexType::UINT) .build(*mEngine); const size_t indexDataSize = vertexCount * sizeof(uint32_t); uint32_t* indexData = (uint32_t*) malloc(indexDataSize); for (size_t i = 0; i < vertexCount; ++i) { indexData[i] = i; } IndexBuffer::BufferDescriptor bd(indexData, indexDataSize, FREE_CALLBACK); indices->setBuffer(*mEngine, std::move(bd)); } mResult->mIndexBuffers.push_back(indices); VertexBuffer::Builder vbb; bool hasUv0 = false, hasUv1 = false, hasVertexColor = false, hasNormals = false; uint32_t vertexCount = 0; const size_t firstSlot = mResult->mBufferSlots.size(); int slot = 0; for (cgltf_size aindex = 0; aindex < inPrim->attributes_count; aindex++) { const cgltf_attribute& attribute = inPrim->attributes[aindex]; const int index = attribute.index; const cgltf_attribute_type atype = attribute.type; const cgltf_accessor* accessor = attribute.data; // The glTF tangent data is ignored here, but honored in ResourceLoader. if (atype == cgltf_attribute_type_tangent) { continue; } // At a minimum, surface orientation requires normals to be present in the source data. // Here we re-purpose the normals slot to point to the quats that get computed later. if (atype == cgltf_attribute_type_normal) { vbb.attribute(VertexAttribute::TANGENTS, slot, VertexBuffer::AttributeType::SHORT4); vbb.normalized(VertexAttribute::TANGENTS); hasNormals = true; addBufferSlot({&mResult->mGenerateTangents, atype, slot++}); continue; } if (atype == cgltf_attribute_type_color) { hasVertexColor = true; } // Translate the cgltf attribute enum into a Filament enum and ignore all uv sets // that do not have entries in the mapping table. VertexAttribute semantic; if (!getVertexAttrType(atype, &semantic)) { utils::slog.e << "Unrecognized vertex semantic in " << name << utils::io::endl; return false; } if (atype == cgltf_attribute_type_texcoord) { if (index >= UvMapSize) { utils::slog.e << "Too many texture coordinate sets in " << name << utils::io::endl; continue; } UvSet uvset = uvmap[index]; switch (uvset) { case UV0: semantic = VertexAttribute::UV0; hasUv0 = true; break; case UV1: semantic = VertexAttribute::UV1; hasUv1 = true; break; case UNUSED: // It is perfectly acceptable to drop unused texture coordinate sets. In fact // this can occur quite frequently, e.g. if the material has attached textures. continue; } } vertexCount = accessor->count; // The positions accessor is required to have min/max properties, use them to expand // the bounding box for this primitive. if (atype == cgltf_attribute_type_position) { const float* minp = &accessor->min[0]; const float* maxp = &accessor->max[0]; outPrim->aabb.min = min(outPrim->aabb.min, float3(minp[0], minp[1], minp[2])); outPrim->aabb.max = max(outPrim->aabb.max, float3(maxp[0], maxp[1], maxp[2])); } VertexBuffer::AttributeType fatype; if (!getElementType(accessor->type, accessor->component_type, &fatype)) { slog.e << "Unsupported accessor type in " << name << io::endl; return false; } // The cgltf library provides a stride value for all accessors, even though they do not // exist in the glTF file. It is computed from the type and the stride of the buffer view. // As a convenience, cgltf also replaces zero (default) stride with the actual stride. vbb.attribute(semantic, slot, fatype, 0, accessor->stride); vbb.normalized(semantic, accessor->normalized); addBufferSlot({accessor, atype, slot++}); } // If the model is lit but does not have normals, we'll need to generate flat normals. if (inPrim->material && !inPrim->material->unlit && !hasNormals) { vbb.attribute(VertexAttribute::TANGENTS, slot, VertexBuffer::AttributeType::SHORT4); vbb.normalized(VertexAttribute::TANGENTS); cgltf_attribute_type atype = cgltf_attribute_type_normal; addBufferSlot({&mResult->mGenerateNormals, atype, slot++}); } cgltf_size targetsCount = inPrim->targets_count; if (targetsCount > MAX_MORPH_TARGETS) { slog.w << "Too many morph targets in " << name << io::endl; targetsCount = MAX_MORPH_TARGETS; } constexpr int baseTangentsAttr = (int) VertexAttribute::MORPH_TANGENTS_0; constexpr int basePositionAttr = (int) VertexAttribute::MORPH_POSITION_0; for (cgltf_size targetIndex = 0; targetIndex < targetsCount; targetIndex++) { const cgltf_morph_target& morphTarget = inPrim->targets[targetIndex]; for (cgltf_size aindex = 0; aindex < morphTarget.attributes_count; aindex++) { const cgltf_attribute& attribute = morphTarget.attributes[aindex]; const cgltf_accessor* accessor = attribute.data; const cgltf_attribute_type atype = attribute.type; const int morphId = targetIndex + 1; // The glTF tangent data is ignored here, but honored in ResourceLoader. if (atype == cgltf_attribute_type_tangent) { continue; } if (atype == cgltf_attribute_type_normal) { VertexAttribute attr = (VertexAttribute) (baseTangentsAttr + targetIndex); vbb.attribute(attr, slot, VertexBuffer::AttributeType::SHORT4); vbb.normalized(attr); addBufferSlot({&mResult->mGenerateTangents, atype, slot++, morphId}); continue; } if (atype != cgltf_attribute_type_position) { utils::slog.e << "Only positions, normals, and tangents can be morphed." << utils::io::endl; return false; } const float* minp = &accessor->min[0]; const float* maxp = &accessor->max[0]; outPrim->aabb.min = min(outPrim->aabb.min, float3(minp[0], minp[1], minp[2])); outPrim->aabb.max = max(outPrim->aabb.max, float3(maxp[0], maxp[1], maxp[2])); VertexBuffer::AttributeType fatype; if (!getElementType(accessor->type, accessor->component_type, &fatype)) { slog.e << "Unsupported accessor type in " << name << io::endl; return false; } VertexAttribute attr = (VertexAttribute) (basePositionAttr + targetIndex); vbb.attribute(attr, slot, fatype, 0, accessor->stride); vbb.normalized(attr, accessor->normalized); addBufferSlot({accessor, atype, slot++, morphId}); } } vbb.vertexCount(vertexCount); // If an ubershader is used, then we provide a single dummy buffer for all unfulfilled vertex // requirements. The color data should be a sequence of normalized UBYTE4, so dummy UVs are // USHORT2 to make the sizes match. bool needsDummyData = false; if (mMaterials->getSource() == LOAD_UBERSHADERS) { if (!hasUv0) { needsDummyData = true; vbb.attribute(VertexAttribute::UV0, slot, VertexBuffer::AttributeType::USHORT2); vbb.normalized(VertexAttribute::UV0); } if (!hasUv1) { needsDummyData = true; vbb.attribute(VertexAttribute::UV1, slot, VertexBuffer::AttributeType::USHORT2); vbb.normalized(VertexAttribute::UV1); } if (!hasVertexColor) { needsDummyData = true; vbb.attribute(VertexAttribute::COLOR, slot, VertexBuffer::AttributeType::UBYTE4); vbb.normalized(VertexAttribute::COLOR); } } else { int numUvSets = getNumUvSets(uvmap); if (!hasUv0 && numUvSets > 0) { needsDummyData = true; vbb.attribute(VertexAttribute::UV0, slot, VertexBuffer::AttributeType::USHORT2); vbb.normalized(VertexAttribute::UV0); slog.w << "Missing UV0 data in " << name << io::endl; } if (!hasUv1 && numUvSets > 1) { needsDummyData = true; vbb.attribute(VertexAttribute::UV1, slot, VertexBuffer::AttributeType::USHORT2); vbb.normalized(VertexAttribute::UV1); slog.w << "Missing UV1 data in " << name << io::endl; } } vbb.bufferCount(needsDummyData ? slot + 1 : slot); VertexBuffer* vertices = vbb.build(*mEngine); outPrim->indices = indices; outPrim->vertices = vertices; mResult->mPrimitives.push_back({inPrim, vertices}); mResult->mVertexBuffers.push_back(vertices); for (size_t i = firstSlot; i < mResult->mBufferSlots.size(); ++i) { mResult->mBufferSlots[i].vertexBuffer = vertices; } if (needsDummyData) { uint32_t size = sizeof(ubyte4) * vertexCount; uint32_t* dummyData = (uint32_t*) malloc(size); memset(dummyData, 0xff, size); VertexBuffer::BufferDescriptor bd(dummyData, size, FREE_CALLBACK); vertices->setBufferAt(*mEngine, slot, std::move(bd)); } return true; } void FAssetLoader::createLight(const cgltf_node* node, Entity entity) { const cgltf_light* light = node->light; LightManager::Type type = getLightType(light->type); LightManager::Builder builder(type); builder.direction({0.0f, 0.0f, -1.0f}); builder.color({light->color[0], light->color[1], light->color[2]}); switch (type) { case LightManager::Type::SUN: case LightManager::Type::DIRECTIONAL: builder.intensity(light->intensity); break; case LightManager::Type::POINT: builder.intensityCandela(light->intensity); break; case LightManager::Type::FOCUSED_SPOT: case LightManager::Type::SPOT: // glTF specifies half angles, so does Filament builder.spotLightCone( light->spot_inner_cone_angle, light->spot_outer_cone_angle); builder.intensityCandela(light->intensity); break; } if (light->range == 0.0f) { // Use 10.0f units as a resonable default falloff value. builder.falloff(10.0f); } else { builder.falloff(light->range); } builder.build(*mEngine, entity); mResult->mLightEntities.push_back(entity); } MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_material* inputMat, UvMap* uvmap, bool vertexColor) { intptr_t key = ((intptr_t) inputMat) ^ (vertexColor ? 1 : 0); auto iter = mMatInstanceCache.find(key); if (iter != mMatInstanceCache.end()) { *uvmap = iter->second.uvmap; return iter->second.instance; } // The default glTF material. static const cgltf_material kDefaultMat = { .name = (char*) "Default GLTF material", .has_pbr_metallic_roughness = true, .has_pbr_specular_glossiness = false, .has_clearcoat = false, .pbr_metallic_roughness = { .base_color_factor = {1.0, 1.0, 1.0, 1.0}, .metallic_factor = 1.0, .roughness_factor = 1.0, }, }; inputMat = inputMat ? inputMat : &kDefaultMat; auto mrConfig = inputMat->pbr_metallic_roughness; auto sgConfig = inputMat->pbr_specular_glossiness; auto ccConfig = inputMat->clearcoat; bool hasTextureTransforms = sgConfig.diffuse_texture.has_transform || sgConfig.specular_glossiness_texture.has_transform || mrConfig.base_color_texture.has_transform || mrConfig.metallic_roughness_texture.has_transform || inputMat->normal_texture.has_transform || inputMat->occlusion_texture.has_transform || inputMat->emissive_texture.has_transform || ccConfig.clearcoat_texture.has_transform || ccConfig.clearcoat_roughness_texture.has_transform || ccConfig.clearcoat_normal_texture.has_transform; cgltf_texture_view baseColorTexture = mrConfig.base_color_texture; cgltf_texture_view metallicRoughnessTexture = mrConfig.metallic_roughness_texture; MaterialKey matkey { .doubleSided = (bool) inputMat->double_sided, .unlit = (bool) inputMat->unlit, .hasVertexColors = vertexColor, .hasBaseColorTexture = !!baseColorTexture.texture, .hasNormalTexture = !!inputMat->normal_texture.texture, .hasOcclusionTexture = !!inputMat->occlusion_texture.texture, .hasEmissiveTexture = !!inputMat->emissive_texture.texture, .useSpecularGlossiness = false, .alphaMode = AlphaMode::OPAQUE, .enableDiagnostics = mDiagnosticsEnabled, .hasMetallicRoughnessTexture = !!metallicRoughnessTexture.texture, .metallicRoughnessUV = (uint8_t) metallicRoughnessTexture.texcoord, .baseColorUV = (uint8_t) baseColorTexture.texcoord, .hasClearCoatTexture = !!ccConfig.clearcoat_texture.texture, .clearCoatUV = (uint8_t) ccConfig.clearcoat_texture.texcoord, .hasClearCoatRoughnessTexture = !!ccConfig.clearcoat_roughness_texture.texture, .clearCoatRoughnessUV = (uint8_t) ccConfig.clearcoat_roughness_texture.texcoord, .hasClearCoatNormalTexture = !!ccConfig.clearcoat_normal_texture.texture, .clearCoatNormalUV = (uint8_t) ccConfig.clearcoat_normal_texture.texcoord, .hasClearCoat = (bool) inputMat->has_clearcoat, .hasTextureTransforms = hasTextureTransforms, .emissiveUV = (uint8_t) inputMat->emissive_texture.texcoord, .aoUV = (uint8_t) inputMat->occlusion_texture.texcoord, .normalUV = (uint8_t) inputMat->normal_texture.texcoord, }; if (inputMat->has_pbr_specular_glossiness) { matkey.useSpecularGlossiness = true; if (sgConfig.diffuse_texture.texture) { baseColorTexture = sgConfig.diffuse_texture; matkey.hasBaseColorTexture = true; matkey.baseColorUV = (uint8_t) baseColorTexture.texcoord; } if (sgConfig.specular_glossiness_texture.texture) { metallicRoughnessTexture = sgConfig.specular_glossiness_texture; matkey.hasSpecularGlossinessTexture = true; matkey.specularGlossinessUV = (uint8_t) metallicRoughnessTexture.texcoord; } } switch (inputMat->alpha_mode) { case cgltf_alpha_mode_opaque: matkey.alphaMode = AlphaMode::OPAQUE; break; case cgltf_alpha_mode_mask: matkey.alphaMode = AlphaMode::MASK; break; case cgltf_alpha_mode_blend: matkey.alphaMode = AlphaMode::BLEND; break; } // This not only creates a material instance, it modifies the material key according to our // rendering constraints. For example, Filament only supports 2 sets of texture coordinates. MaterialInstance* mi = mMaterials->createMaterialInstance(&matkey, uvmap, inputMat->name); if (!mi) { slog.e << "No material with the specified requirements exists." << io::endl; return nullptr; } mResult->mMaterialInstances.push_back(mi); if (inputMat->alpha_mode == cgltf_alpha_mode_mask) { mi->setMaskThreshold(inputMat->alpha_cutoff); } const float* e = inputMat->emissive_factor; mi->setParameter("emissiveFactor", float3(e[0], e[1], e[2])); const float* c = mrConfig.base_color_factor; mi->setParameter("baseColorFactor", float4(c[0], c[1], c[2], c[3])); mi->setParameter("metallicFactor", mrConfig.metallic_factor); mi->setParameter("roughnessFactor", mrConfig.roughness_factor); if (matkey.useSpecularGlossiness) { const float* df = sgConfig.diffuse_factor; const float* sf = sgConfig.specular_factor; mi->setParameter("baseColorFactor", float4(df[0], df[1], df[2], df[3])); mi->setParameter("specularFactor", float3(sf[0], sf[1], sf[2])); mi->setParameter("glossinessFactor", sgConfig.glossiness_factor); } if (matkey.hasBaseColorTexture) { addTextureBinding(mi, "baseColorMap", baseColorTexture.texture, true); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = baseColorTexture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); mi->setParameter("baseColorUvMatrix", uvmat); } } if (matkey.hasMetallicRoughnessTexture) { // The "metallicRoughnessMap" is actually a specular-glossiness map when the extension is // enabled. Note that KHR_materials_pbrSpecularGlossiness specifies that diffuseTexture and // specularGlossinessTexture are both sRGB, whereas the core glTF spec stipulates that // metallicRoughness is not sRGB. bool srgb = inputMat->has_pbr_specular_glossiness; addTextureBinding(mi, "metallicRoughnessMap", metallicRoughnessTexture.texture, srgb); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = metallicRoughnessTexture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); mi->setParameter("metallicRoughnessUvMatrix", uvmat); } } if (matkey.hasNormalTexture) { addTextureBinding(mi, "normalMap", inputMat->normal_texture.texture, false); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = inputMat->normal_texture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); mi->setParameter("normalUvMatrix", uvmat); } mi->setParameter("normalScale", inputMat->normal_texture.scale); } else { mi->setParameter("normalScale", 1.0f); } if (matkey.hasOcclusionTexture) { addTextureBinding(mi, "occlusionMap", inputMat->occlusion_texture.texture, false); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = inputMat->occlusion_texture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); mi->setParameter("occlusionUvMatrix", uvmat); } mi->setParameter("aoStrength", inputMat->occlusion_texture.scale); } else { mi->setParameter("aoStrength", 1.0f); } if (matkey.hasEmissiveTexture) { addTextureBinding(mi, "emissiveMap", inputMat->emissive_texture.texture, true); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = inputMat->emissive_texture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); mi->setParameter("emissiveUvMatrix", uvmat); } } if (matkey.hasClearCoat) { mi->setParameter("clearCoatFactor", ccConfig.clearcoat_factor); mi->setParameter("clearCoatRoughnessFactor", ccConfig.clearcoat_roughness_factor); if (matkey.hasClearCoatTexture) { addTextureBinding(mi, "clearCoatMap", ccConfig.clearcoat_texture.texture, false); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = ccConfig.clearcoat_texture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); mi->setParameter("clearCoatUvMatrix", uvmat); } } if (matkey.hasClearCoatRoughnessTexture) { addTextureBinding(mi, "clearCoatRoughnessMap", ccConfig.clearcoat_roughness_texture.texture, false); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = ccConfig.clearcoat_roughness_texture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); mi->setParameter("clearCoatRoughnessUvMatrix", uvmat); } } if (matkey.hasClearCoatNormalTexture) { addTextureBinding(mi, "clearCoatNormalMap", ccConfig.clearcoat_normal_texture.texture, false); if (matkey.hasTextureTransforms) { const cgltf_texture_transform& uvt = ccConfig.clearcoat_normal_texture.transform; auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale); mi->setParameter("clearCoatNormalUvMatrix", uvmat); } mi->setParameter("clearCoatNormalScale", ccConfig.clearcoat_normal_texture.scale); } } mMatInstanceCache[key] = {mi, *uvmap}; return mi; } void FAssetLoader::addTextureBinding(MaterialInstance* materialInstance, const char* parameterName, const cgltf_texture* srcTexture, bool srgb) { if (!srcTexture->image) { slog.w << "Texture is missing image (" << srcTexture->name << ")." << io::endl; return; } TextureSampler dstSampler; auto srcSampler = srcTexture->sampler; if (srcSampler) { dstSampler.setWrapModeS(getWrapMode(srcSampler->wrap_s)); dstSampler.setWrapModeT(getWrapMode(srcSampler->wrap_t)); dstSampler.setMagFilter(getMagFilter(srcSampler->mag_filter)); dstSampler.setMinFilter(getMinFilter(srcSampler->min_filter)); } else { // These defaults are stipulated by the spec: dstSampler.setWrapModeS(TextureSampler::WrapMode::REPEAT); dstSampler.setWrapModeT(TextureSampler::WrapMode::REPEAT); // These defaults are up the implementation but since we generate mipmaps unconditionally, // we might as well use them. In practice the conformance models look awful without // using mipmapping by default. dstSampler.setMagFilter(TextureSampler::MagFilter::LINEAR); dstSampler.setMinFilter(TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR); } mResult->mTextureSlots.push_back({ .texture = srcTexture, .materialInstance = materialInstance, .materialParameter = parameterName, .sampler = dstSampler, .srgb = srgb }); mResult->mDependencyGraph.addEdge(materialInstance, parameterName); } bool FAssetLoader::primitiveHasVertexColor(const cgltf_primitive* inPrim) const { for (int slot = 0; slot < inPrim->attributes_count; slot++) { const cgltf_attribute& inputAttribute = inPrim->attributes[slot]; if (inputAttribute.type == cgltf_attribute_type_color) { return true; } } return false; } LightManager::Type FAssetLoader::getLightType(const cgltf_light_type light) { switch (light) { case cgltf_light_type_invalid: case cgltf_light_type_directional: return LightManager::Type::DIRECTIONAL; case cgltf_light_type_point: return LightManager::Type::POINT; case cgltf_light_type_spot: return LightManager::Type::FOCUSED_SPOT; } } AssetLoader* AssetLoader::create(const AssetConfiguration& config) { return new FAssetLoader(config); } void AssetLoader::destroy(AssetLoader** loader) { delete *loader; *loader = nullptr; } FilamentAsset* AssetLoader::createAssetFromJson(uint8_t const* bytes, uint32_t nbytes) { return upcast(this)->createAssetFromJson(bytes, nbytes); } FilamentAsset* AssetLoader::createAssetFromBinary(uint8_t const* bytes, uint32_t nbytes) { return upcast(this)->createAssetFromBinary(bytes, nbytes); } FilamentAsset* AssetLoader::createInstancedAsset(const uint8_t* bytes, uint32_t numBytes, FilamentInstance** instances, size_t numInstances) { return upcast(this)->createInstancedAsset(bytes, numBytes, instances, numInstances); } FilamentAsset* AssetLoader::createAssetFromHandle(const void* handle) { const cgltf_data* sourceAsset = (const cgltf_data*) handle; upcast(this)->createAsset(sourceAsset, 0); upcast(this)->mResult->mSharedSourceAsset = true; return upcast(this)->mResult; } void AssetLoader::enableDiagnostics(bool enable) { upcast(this)->mDiagnosticsEnabled = enable; } void AssetLoader::destroyAsset(const FilamentAsset* asset) { upcast(this)->destroyAsset(upcast(asset)); } size_t AssetLoader::getMaterialsCount() const noexcept { return upcast(this)->getMaterialsCount(); } NameComponentManager* AssetLoader::getNames() const noexcept { return upcast(this)->getNames(); } const Material* const* AssetLoader::getMaterials() const noexcept { return upcast(this)->getMaterials(); } } // namespace gltfio