/* * Copyright (C) 2015 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. */ #define GL_NEAREST 0x2600 #define GL_LINEAR 0x2601 #define GL_NEAREST_MIPMAP_NEAREST 0x2700 #define GL_LINEAR_MIPMAP_NEAREST 0x2701 #define GL_NEAREST_MIPMAP_LINEAR 0x2702 #define GL_LINEAR_MIPMAP_LINEAR 0x2703 #define GL_TEXTURE_MAG_FILTER 0x2800 #define GL_TEXTURE_MIN_FILTER 0x2801 #define GL_TEXTURE_WRAP_S 0x2802 #define GL_TEXTURE_WRAP_T 0x2803 #include "MeshAssimp.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "generated/resources/resources.h" using namespace filament; using namespace filamat; using namespace filament::math; using namespace utils; enum class AlphaMode : uint8_t { OPAQUE, MASKED, TRANSPARENT }; struct MaterialConfig { bool doubleSided = false; bool unlit = false; bool hasVertexColors = false; AlphaMode alphaMode = AlphaMode::OPAQUE; float maskThreshold = 0.5f; uint8_t baseColorUV = 0; uint8_t metallicRoughnessUV = 0; uint8_t emissiveUV = 0; uint8_t aoUV = 0; uint8_t normalUV = 0; uint8_t maxUVIndex() { return std::max({baseColorUV, metallicRoughnessUV, emissiveUV, aoUV, normalUV}); } }; void appendBooleanToBitMask(uint64_t &bitmask, bool b) { bitmask <<= 1; bitmask |= b; } uint64_t hashMaterialConfig(MaterialConfig config) { uint64_t bitmask = 0; memcpy(&bitmask, &config.maskThreshold, sizeof(config.maskThreshold)); appendBooleanToBitMask(bitmask, config.doubleSided); appendBooleanToBitMask(bitmask, config.unlit); appendBooleanToBitMask(bitmask, config.hasVertexColors); appendBooleanToBitMask(bitmask, config.alphaMode == AlphaMode::OPAQUE); appendBooleanToBitMask(bitmask, config.alphaMode == AlphaMode::MASKED); appendBooleanToBitMask(bitmask, config.alphaMode == AlphaMode::TRANSPARENT); appendBooleanToBitMask(bitmask, config.baseColorUV == 0); appendBooleanToBitMask(bitmask, config.metallicRoughnessUV == 0); appendBooleanToBitMask(bitmask, config.emissiveUV == 0); appendBooleanToBitMask(bitmask, config.aoUV == 0); appendBooleanToBitMask(bitmask, config.normalUV == 0); return bitmask; } std::string shaderFromConfig(MaterialConfig config) { std::string shader = R"SHADER( void material(inout MaterialInputs material) { )SHADER"; shader += "float2 normalUV = getUV" + std::to_string(config.normalUV) + "();\n"; shader += "float2 baseColorUV = getUV" + std::to_string(config.baseColorUV) + "();\n"; shader += "float2 metallicRoughnessUV = getUV" + std::to_string(config.metallicRoughnessUV) + "();\n"; shader += "float2 aoUV = getUV" + std::to_string(config.aoUV) + "();\n"; shader += "float2 emissiveUV = getUV" + std::to_string(config.emissiveUV) + "();\n"; if (!config.unlit) { shader += R"SHADER( material.normal = texture(materialParams_normalMap, normalUV).xyz * 2.0 - 1.0; material.normal.y = -material.normal.y; )SHADER"; } shader += R"SHADER( prepareMaterial(material); material.baseColor = texture(materialParams_baseColorMap, baseColorUV); material.baseColor *= materialParams.baseColorFactor; )SHADER"; if (config.alphaMode == AlphaMode::TRANSPARENT) { shader += R"SHADER( material.baseColor.rgb *= material.baseColor.a; )SHADER"; } if (!config.unlit) { shader += R"SHADER( vec4 metallicRoughness = texture(materialParams_metallicRoughnessMap, metallicRoughnessUV); material.roughness = materialParams.roughnessFactor * metallicRoughness.g; material.metallic = materialParams.metallicFactor * metallicRoughness.b; material.ambientOcclusion = texture(materialParams_aoMap, aoUV).r; material.emissive.rgb = texture(materialParams_emissiveMap, emissiveUV).rgb; material.emissive.rgb *= materialParams.emissiveFactor.rgb; // The opinionated lighting model specified by glTF does not account for energy // compensation, using this value basically disables it: material.emissive.a = 3.0; )SHADER"; } shader += "}\n"; return shader; } Material* createMaterialFromConfig(Engine& engine, MaterialConfig config ) { std::string shader = shaderFromConfig(config); MaterialBuilder::init(); MaterialBuilder builder = MaterialBuilder() .name("material") .material(shader.c_str()) .doubleSided(config.doubleSided) .require(VertexAttribute::UV0) .parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "baseColorMap") .parameter(MaterialBuilder::UniformType::FLOAT4, "baseColorFactor") .parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "metallicRoughnessMap") .parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "aoMap") .parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "emissiveMap") .parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "normalMap") .parameter(MaterialBuilder::UniformType::FLOAT, "metallicFactor") .parameter(MaterialBuilder::UniformType::FLOAT, "roughnessFactor") .parameter(MaterialBuilder::UniformType::FLOAT, "normalScale") .parameter(MaterialBuilder::UniformType::FLOAT, "aoStrength") .parameter(MaterialBuilder::UniformType::FLOAT3, "emissiveFactor"); if (config.maxUVIndex() > 0) { builder.require(VertexAttribute::UV1); } switch(config.alphaMode) { case AlphaMode::MASKED : builder.blending(MaterialBuilder::BlendingMode::MASKED); builder.maskThreshold(config.maskThreshold); break; case AlphaMode::TRANSPARENT : builder.blending(MaterialBuilder::BlendingMode::TRANSPARENT); break; default : builder.blending(MaterialBuilder::BlendingMode::OPAQUE); } builder.shading(config.unlit ? Shading::UNLIT : Shading::LIT); Package pkg = builder.build(); return Material::Builder().package(pkg.getData(), pkg.getSize()).build(engine); } Texture* MeshAssimp::createOneByOneTexture(uint32_t pixel) { uint32_t *textureData = (uint32_t *) malloc(sizeof(uint32_t)); *textureData = pixel; Texture *texturePtr = Texture::Builder() .width(uint32_t(1)) .height(uint32_t(1)) .levels(0xff) .format(Texture::InternalFormat::RGBA8) .build(mEngine); Texture::PixelBufferDescriptor defaultNormalBuffer(textureData, size_t(1 * 1 * 4), Texture::Format::RGBA, Texture::Type::UBYTE, (Texture::PixelBufferDescriptor::Callback) &free); texturePtr->setImage(mEngine, 0, std::move(defaultNormalBuffer)); texturePtr->generateMipmaps(mEngine); return texturePtr; } void getMinMaxUV(const aiScene *scene, const aiNode* node, float2 &minUV, float2 &maxUV, uint32_t uvIndex) { for (size_t i = 0; i < node->mNumMeshes; ++i) { const aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; if (!mesh->HasTextureCoords(uvIndex)) { continue; } const float3* uv = reinterpret_cast(mesh->mTextureCoords[uvIndex]); const size_t numVertices = mesh->mNumVertices; const size_t numFaces = mesh->mNumFaces; if (numVertices == 0 || numFaces == 0) { continue; } if (uv) { for (size_t j = 0; j < numVertices; j++) { minUV = min(uv[j].xy, minUV); maxUV = max(uv[j].xy, maxUV); } } } for (size_t i = 0 ; i < node->mNumChildren ; ++i) { getMinMaxUV(scene, node->mChildren[i], minUV, maxUV, uvIndex); } } template static ushort2 convertUV(float2 uv) { if (SNORMUVS) { short2 uvshort(packSnorm16(uv)); return bit_cast(uvshort); } else { half2 uvhalf(uv); return bit_cast(uvhalf); } } MeshAssimp::MeshAssimp(Engine& engine) : mEngine(engine) { mDefaultMap = createOneByOneTexture(0xffffffff); mDefaultNormalMap = createOneByOneTexture(0xffff8080); mDefaultColorMaterial = Material::Builder() .package(RESOURCES_AIDEFAULTMAT_DATA, RESOURCES_AIDEFAULTMAT_SIZE) .build(mEngine); mDefaultColorMaterial->setDefaultParameter("baseColor", RgbType::LINEAR, float3{0.8}); mDefaultColorMaterial->setDefaultParameter("metallic", 0.0f); mDefaultColorMaterial->setDefaultParameter("roughness", 0.4f); mDefaultColorMaterial->setDefaultParameter("reflectance", 0.5f); mDefaultTransparentColorMaterial = Material::Builder() .package(RESOURCES_AIDEFAULTTRANS_DATA, RESOURCES_AIDEFAULTTRANS_SIZE) .build(mEngine); mDefaultTransparentColorMaterial->setDefaultParameter("baseColor", RgbType::LINEAR, float3{0.8}); mDefaultTransparentColorMaterial->setDefaultParameter("metallic", 0.0f); mDefaultTransparentColorMaterial->setDefaultParameter("roughness", 0.4f); } MeshAssimp::~MeshAssimp() { mEngine.destroy(mVertexBuffer); mEngine.destroy(mIndexBuffer); mEngine.destroy(mDefaultColorMaterial); mEngine.destroy(mDefaultTransparentColorMaterial); mEngine.destroy(mDefaultNormalMap); mEngine.destroy(mDefaultMap); for (auto& item : mGltfMaterialCache) { auto material = item.second; mEngine.destroy(material); } for (Entity renderable : mRenderables) { mEngine.destroy(renderable); } for (Texture* texture : mTextures) { mEngine.destroy(texture); } // destroy the Entities itself EntityManager::get().destroy(mRenderables.size(), mRenderables.data()); } template struct State { std::vector state; explicit State(std::vector&& state) : state(std::move(state)) { } static void free(void* buffer, size_t size, void* user) { auto* const that = static_cast*>(user); delete that; } size_t size() const { return state.size() * sizeof(T); } T const * data() const { return state.data(); } }; //TODO: Remove redundant method from sample_full_pbr static void loadTexture(Engine *engine, const std::string &filePath, Texture **map, bool sRGB, bool hasAlpha) { if (!filePath.empty()) { Path path(filePath); if (path.exists()) { int w, h, n; int numChannels = hasAlpha ? 4 : 3; Texture::InternalFormat inputFormat; if (sRGB) { inputFormat = hasAlpha ? Texture::InternalFormat::SRGB8_A8 : Texture::InternalFormat::SRGB8; } else { inputFormat = hasAlpha ? Texture::InternalFormat::RGBA8 : Texture::InternalFormat::RGB8; } Texture::Format outputFormat = hasAlpha ? Texture::Format::RGBA : Texture::Format::RGB; uint8_t *data = stbi_load(path.getAbsolutePath().c_str(), &w, &h, &n, numChannels); if (data != nullptr) { *map = Texture::Builder() .width(uint32_t(w)) .height(uint32_t(h)) .levels(0xff) .format(inputFormat) .build(*engine); Texture::PixelBufferDescriptor buffer(data, size_t(w * h * numChannels), outputFormat, Texture::Type::UBYTE, (Texture::PixelBufferDescriptor::Callback) &stbi_image_free); (*map)->setImage(*engine, 0, std::move(buffer)); (*map)->generateMipmaps(*engine); } else { std::cout << "The texture " << path << " could not be loaded" << std::endl; } } else { std::cout << "The texture " << path << " does not exist" << std::endl; } } } void loadEmbeddedTexture(Engine *engine, aiTexture *embeddedTexture, Texture **map, bool sRGB, bool hasAlpha) { int w, h, n; int numChannels = hasAlpha ? 4 : 3; Texture::InternalFormat inputFormat; if (sRGB) { inputFormat = hasAlpha ? Texture::InternalFormat::SRGB8_A8 : Texture::InternalFormat::SRGB8; } else { inputFormat = hasAlpha ? Texture::InternalFormat::RGBA8 : Texture::InternalFormat::RGB8; } Texture::Format outputFormat = hasAlpha ? Texture::Format::RGBA : Texture::Format::RGB; uint8_t *data = stbi_load_from_memory((unsigned char *) embeddedTexture->pcData, embeddedTexture->mWidth, &w, &h, &n, numChannels); *map = Texture::Builder() .width(uint32_t(w)) .height(uint32_t(h)) .levels(0xff) .format(inputFormat) .build(*engine); Texture::PixelBufferDescriptor defaultBuffer(data, size_t(w * h * numChannels), outputFormat, Texture::Type::UBYTE, (Texture::PixelBufferDescriptor::Callback) &free); (*map)->setImage(*engine, 0, std::move(defaultBuffer)); (*map)->generateMipmaps(*engine); } // Takes a texture filename and returns the index of the embedded texture, // -1 if the texture is not embedded int32_t getEmbeddedTextureId(const aiString& path) { const char *pathStr = path.C_Str(); if (path.length >= 2 && pathStr[0] == '*') { for (int i = 1; i < path.length; i++) { if (!isdigit(pathStr[i])) { return -1; } } return std::atoi(pathStr + 1); // NOLINT } return -1; } TextureSampler::WrapMode aiToFilamentMapMode(aiTextureMapMode mapMode) { switch(mapMode) { case aiTextureMapMode_Clamp : return TextureSampler::WrapMode::CLAMP_TO_EDGE; case aiTextureMapMode_Mirror : return TextureSampler::WrapMode::MIRRORED_REPEAT; default: return TextureSampler::WrapMode::REPEAT; } } TextureSampler::MinFilter aiMinFilterToFilament(unsigned int aiMinFilter) { switch(aiMinFilter) { case GL_NEAREST: return TextureSampler::MinFilter::NEAREST; case GL_LINEAR: return TextureSampler::MinFilter::LINEAR; case GL_NEAREST_MIPMAP_NEAREST: return TextureSampler::MinFilter::NEAREST_MIPMAP_NEAREST; case GL_LINEAR_MIPMAP_NEAREST: return TextureSampler::MinFilter::LINEAR_MIPMAP_NEAREST; case GL_NEAREST_MIPMAP_LINEAR: return TextureSampler::MinFilter::NEAREST_MIPMAP_LINEAR; case GL_LINEAR_MIPMAP_LINEAR: return TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR; default: return TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR; } } TextureSampler::MagFilter aiMagFilterToFilament(unsigned int aiMagFilter) { switch(aiMagFilter) { case GL_NEAREST: return TextureSampler::MagFilter::NEAREST; default: return TextureSampler::MagFilter::LINEAR; } } // TODO: Change this to a member function (requires some alteration of cmakelsts.txt) void setTextureFromPath(const aiScene *scene, Engine *engine, std::vector textures, const aiString &textureFile, const std::string &materialName, const std::string &textureDirectory, aiTextureMapMode *mapMode, const char *parameterName, std::map &outMaterials, unsigned int aiMinFilterType=0, unsigned int aiMagFilterType=0) { TextureSampler::MinFilter minFilterType = aiMinFilterToFilament(aiMinFilterType); TextureSampler::MagFilter magFilterType = aiMagFilterToFilament(aiMagFilterType); TextureSampler sampler; if (mapMode) { sampler = TextureSampler( minFilterType, magFilterType, aiToFilamentMapMode(mapMode[0]), aiToFilamentMapMode(mapMode[1]), aiToFilamentMapMode(mapMode[2])); } else { sampler = TextureSampler( minFilterType, magFilterType, TextureSampler::WrapMode::REPEAT); } Texture* textureMap = nullptr; int32_t embeddedId = getEmbeddedTextureId(textureFile); // TODO: change this in refactor bool isSRGB = strcmp(parameterName, "baseColorMap") == 0 || strcmp(parameterName, "emissiveMap") == 0; bool hasAlpha = strcmp(parameterName, "baseColorMap") == 0; if (embeddedId != -1) { loadEmbeddedTexture(engine, scene->mTextures[embeddedId], &textureMap, isSRGB, hasAlpha); } else { loadTexture(engine, textureDirectory + textureFile.C_Str(), &textureMap, isSRGB, hasAlpha); } textures.push_back(textureMap); if (textureMap != nullptr) { outMaterials[materialName]->setParameter(parameterName, textureMap, sampler); } } template Box computeTransformedAABB(VECTOR const* vertices, INDEX const* indices, size_t count, const mat4f& transform) noexcept { size_t stride = sizeof(VECTOR); filament::math::float3 bmin(std::numeric_limits::max()); filament::math::float3 bmax(std::numeric_limits::lowest()); for (size_t i = 0; i < count; ++i) { VECTOR const* p = reinterpret_cast( (char const*) vertices + indices[i] * stride); const filament::math::float3 v(p->x, p->y, p->z); float3 tv = (transform * float4(v, 1.0f)).xyz; bmin = min(bmin, tv); bmax = max(bmax, tv); } return Box().set(bmin, bmax); } void MeshAssimp::addFromFile(const Path& path, std::map& materials, bool overrideMaterial) { Asset asset; asset.file = path; { // This scope to make sure we're not using std::move()'d objects later // TODO: if we had a way to allocate temporary buffers from the engine with a // "command buffer" lifetime, we wouldn't need to have to deal with freeing the // std::vectors here. //TODO: a lot of these method arguments should probably be class or global variables if (!setFromFile(asset, materials)) { return; } VertexBuffer::Builder vertexBufferBuilder = VertexBuffer::Builder() .vertexCount((uint32_t)asset.positions.size()) .bufferCount(4) .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::HALF4) .attribute(VertexAttribute::TANGENTS, 1, VertexBuffer::AttributeType::SHORT4) .normalized(VertexAttribute::TANGENTS); if (asset.snormUV0) { vertexBufferBuilder.attribute(VertexAttribute::UV0, 2, VertexBuffer::AttributeType::SHORT2) .normalized(VertexAttribute::UV0); } else { vertexBufferBuilder.attribute(VertexAttribute::UV0, 2, VertexBuffer::AttributeType::HALF2); } if (asset.snormUV1) { vertexBufferBuilder.attribute(VertexAttribute::UV1, 3, VertexBuffer::AttributeType::SHORT2) .normalized(VertexAttribute::UV1); } else { vertexBufferBuilder.attribute(VertexAttribute::UV1, 3, VertexBuffer::AttributeType::HALF2); } mVertexBuffer = vertexBufferBuilder.build(mEngine); auto ps = new State(std::move(asset.positions)); auto ns = new State(std::move(asset.tangents)); auto t0s = new State(std::move(asset.texCoords0)); auto t1s = new State(std::move(asset.texCoords1)); auto is = new State(std::move(asset.indices)); mVertexBuffer->setBufferAt(mEngine, 0, VertexBuffer::BufferDescriptor(ps->data(), ps->size(), State::free, ps)); mVertexBuffer->setBufferAt(mEngine, 1, VertexBuffer::BufferDescriptor(ns->data(), ns->size(), State::free, ns)); mVertexBuffer->setBufferAt(mEngine, 2, VertexBuffer::BufferDescriptor(t0s->data(), t0s->size(), State::free, t0s)); mVertexBuffer->setBufferAt(mEngine, 3, VertexBuffer::BufferDescriptor(t1s->data(), t1s->size(), State::free, t1s)); mIndexBuffer = IndexBuffer::Builder().indexCount(uint32_t(is->size())).build(mEngine); mIndexBuffer->setBuffer(mEngine, IndexBuffer::BufferDescriptor(is->data(), is->size(), State::free, is)); } // always add the DefaultMaterial (with its default parameters), so we don't pick-up // whatever defaults is used in mesh if (materials.find(AI_DEFAULT_MATERIAL_NAME) == materials.end()) { materials[AI_DEFAULT_MATERIAL_NAME] = mDefaultColorMaterial->createInstance(); } size_t startIndex = mRenderables.size(); mRenderables.resize(startIndex + asset.meshes.size()); EntityManager::get().create(asset.meshes.size(), mRenderables.data() + startIndex); EntityManager::get().create(1, &rootEntity); TransformManager& tcm = mEngine.getTransformManager(); //Add root instance tcm.create(rootEntity, TransformManager::Instance{}, mat4f()); for (auto& mesh : asset.meshes) { RenderableManager::Builder builder(mesh.parts.size()); builder.boundingBox(mesh.aabb); size_t partIndex = 0; for (auto& part : mesh.parts) { builder.geometry(partIndex, RenderableManager::PrimitiveType::TRIANGLES, mVertexBuffer, mIndexBuffer, part.offset, part.count); if (overrideMaterial) { builder.material(partIndex, materials[AI_DEFAULT_MATERIAL_NAME]); } else { auto pos = materials.find(part.material); if (pos != materials.end()) { builder.material(partIndex, pos->second); } else { MaterialInstance* colorMaterial; if (part.opacity < 1.0f) { colorMaterial = mDefaultTransparentColorMaterial->createInstance(); colorMaterial->setParameter("baseColor", RgbaType::sRGB, sRGBColorA { part.baseColor, part.opacity }); } else { colorMaterial = mDefaultColorMaterial->createInstance(); colorMaterial->setParameter("baseColor", RgbType::sRGB, part.baseColor); colorMaterial->setParameter("reflectance", part.reflectance); } colorMaterial->setParameter("metallic", part.metallic); colorMaterial->setParameter("roughness", part.roughness); builder.material(partIndex, colorMaterial); materials[part.material] = colorMaterial; } } partIndex++; } const size_t meshIndex = &mesh - asset.meshes.data(); Entity entity = mRenderables[startIndex + meshIndex]; if (!mesh.parts.empty()) { builder.build(mEngine, entity); } auto pindex = asset.parents[meshIndex]; TransformManager::Instance parent((pindex < 0) ? tcm.getInstance(rootEntity) : tcm.getInstance(mRenderables[pindex])); tcm.create(entity, parent, mesh.transform); } } using Assimp::Importer; bool MeshAssimp::setFromFile(Asset& asset, std::map& outMaterials) { Importer importer; importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT); importer.SetPropertyBool(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION, true); importer.SetPropertyBool(AI_CONFIG_PP_PTV_KEEP_HIERARCHY, true); aiScene const* scene = importer.ReadFile(asset.file, // normals and tangents aiProcess_GenSmoothNormals | aiProcess_CalcTangentSpace | // UV Coordinates aiProcess_GenUVCoords | // topology optimization aiProcess_FindInstances | aiProcess_OptimizeMeshes | aiProcess_JoinIdenticalVertices | // misc optimization aiProcess_ImproveCacheLocality | aiProcess_SortByPType | // we only support triangles aiProcess_Triangulate); size_t index = importer.GetImporterIndex(asset.file.getExtension().c_str()); const aiImporterDesc* importerDesc = importer.GetImporterInfo(index); bool isGLTF = importerDesc && (!strncmp("glTF Importer", importerDesc->mName, 13) || !strncmp("glTF2 Importer", importerDesc->mName, 14)); if (!scene) { std::cout << "No scene" << std::endl; } if (scene && !scene->mRootNode) { std::cout << "No root node" << std::endl; } // we could use those, but we want to keep the graph if any, for testing // aiProcess_OptimizeGraph // aiProcess_PreTransformVertices const std::function countVertices = [scene, &countVertices] (aiNode const* node, size_t& totalVertexCount, size_t& totalIndexCount) { for (size_t i = 0; i < node->mNumMeshes; i++) { aiMesh const *mesh = scene->mMeshes[node->mMeshes[i]]; totalVertexCount += mesh->mNumVertices; const aiFace *faces = mesh->mFaces; const size_t numFaces = mesh->mNumFaces; totalIndexCount += numFaces * faces[0].mNumIndices; } for (size_t i = 0; i < node->mNumChildren; i++) { countVertices(node->mChildren[i], totalVertexCount, totalIndexCount); } }; if (scene) { size_t deep = 0; size_t depth = 0; size_t matCount = 0; aiNode const* node = scene->mRootNode; size_t totalVertexCount = 0; size_t totalIndexCount = 0; countVertices(node, totalVertexCount, totalIndexCount); asset.positions.reserve(asset.positions.size() + totalVertexCount); asset.tangents.reserve(asset.tangents.size() + totalVertexCount); asset.texCoords0.reserve(asset.texCoords0.size() + totalVertexCount); asset.texCoords1.reserve(asset.texCoords1.size() + totalVertexCount); asset.indices.reserve(asset.indices.size() + totalIndexCount); float2 minUV0 = float2(std::numeric_limits::max()); float2 maxUV0 = float2(std::numeric_limits::lowest()); getMinMaxUV(scene, node, minUV0, maxUV0, 0); float2 minUV1 = float2(std::numeric_limits::max()); float2 maxUV1 = float2(std::numeric_limits::lowest()); getMinMaxUV(scene, node, minUV1, maxUV1, 1); asset.snormUV0 = minUV0.x >= -1.0f && minUV0.x <= 1.0f && maxUV0.x >= -1.0f && maxUV0.x <= 1.0f && minUV0.y >= -1.0f && minUV0.y <= 1.0f && maxUV0.y >= -1.0f && maxUV0.y <= 1.0f; asset.snormUV1 = minUV1.x >= -1.0f && minUV1.x <= 1.0f && maxUV1.x >= -1.0f && maxUV1.x <= 1.0f && minUV1.y >= -1.0f && minUV1.y <= 1.0f && maxUV1.y >= -1.0f && maxUV1.y <= 1.0f; if (asset.snormUV0) { if (asset.snormUV1) { processNode(asset, outMaterials, scene, isGLTF, deep, matCount, node, -1, depth); } else { processNode(asset, outMaterials, scene, isGLTF, deep, matCount, node, -1, depth); } } else { if (asset.snormUV1) { processNode(asset, outMaterials, scene, isGLTF, deep, matCount, node, -1, depth); } else { processNode(asset, outMaterials, scene, isGLTF, deep, matCount, node, -1, depth); } } // compute the aabb and find bounding box of entire model for (auto& mesh : asset.meshes) { mesh.aabb = RenderableManager::computeAABB( asset.positions.data(), asset.indices.data() + mesh.offset, mesh.count); Box transformedAabb = computeTransformedAABB( asset.positions.data(), asset.indices.data() + mesh.offset, mesh.count, mesh.accTransform); float3 aabbMin = transformedAabb.getMin(); float3 aabbMax = transformedAabb.getMax(); if (!isinf(aabbMin.x) && !isinf(aabbMax.x)) { if (minBound.x > maxBound.x) { minBound.x = aabbMin.x; maxBound.x = aabbMax.x; } else { minBound.x = fmin(minBound.x, aabbMin.x); maxBound.x = fmax(maxBound.x, aabbMax.x); } } if (!isinf(aabbMin.y) && !isinf(aabbMax.y)) { if (minBound.y > maxBound.y) { minBound.y = aabbMin.y; maxBound.y = aabbMax.y; } else { minBound.y = fmin(minBound.y, aabbMin.y); maxBound.y = fmax(maxBound.y, aabbMax.y); } } if (!isinf(aabbMin.z) && !isinf(aabbMax.z)) { if (minBound.z > maxBound.z) { minBound.z = aabbMin.z; maxBound.z = aabbMax.z; } else { minBound.z = fmin(minBound.z, aabbMin.z); maxBound.z = fmax(maxBound.z, aabbMax.z); } } } return true; } return false; } template void MeshAssimp::processNode(Asset& asset, std::map &outMaterials, const aiScene *scene, bool isGLTF, size_t deep, size_t matCount, const aiNode *node, int parentIndex, size_t &depth) const { mat4f const& current = transpose(*reinterpret_cast(&node->mTransformation)); size_t totalIndices = 0; asset.parents.push_back(parentIndex); asset.meshes.push_back(Mesh{}); asset.meshes.back().offset = asset.indices.size(); asset.meshes.back().transform = current; mat4f parentTransform = parentIndex >= 0 ? asset.meshes[parentIndex].accTransform : mat4f(); asset.meshes.back().accTransform = parentTransform * current; for (size_t i = 0; i < node->mNumMeshes; i++) { aiMesh const* mesh = scene->mMeshes[node->mMeshes[i]]; float3 const* positions = reinterpret_cast(mesh->mVertices); float3 const* tangents = reinterpret_cast(mesh->mTangents); float3 const* bitangents = reinterpret_cast(mesh->mBitangents); float3 const* normals = reinterpret_cast(mesh->mNormals); float3 const* texCoords0 = reinterpret_cast(mesh->mTextureCoords[0]); float3 const* texCoords1 = reinterpret_cast(mesh->mTextureCoords[1]); const size_t numVertices = mesh->mNumVertices; if (numVertices > 0) { const aiFace* faces = mesh->mFaces; const size_t numFaces = mesh->mNumFaces; if (numFaces > 0) { size_t indicesOffset = asset.positions.size(); for (size_t j = 0; j < numVertices; j++) { float3 normal = normals[j]; float3 tangent; float3 bitangent; // Assimp always returns 3D tex coords but we only support 2D tex coords. float2 texCoord0 = texCoords0 ? texCoords0[j].xy : float2{0.0}; float2 texCoord1 = texCoords1 ? texCoords1[j].xy : float2{0.0}; // If the tangent and bitangent don't exist, make arbitrary ones. This only // occurs when the mesh is missing texture coordinates, because assimp // computes tangents for us. (search up for aiProcess_CalcTangentSpace) if (!tangents) { bitangent = normalize(cross(normal, float3{1.0, 0.0, 0.0})); tangent = normalize(cross(normal, bitangent)); } else { tangent = tangents[j]; bitangent = bitangents[j]; } quatf q = filament::math::details::TMat33::packTangentFrame({tangent, bitangent, normal}); asset.tangents.push_back(packSnorm16(q.xyzw)); asset.texCoords0.emplace_back(convertUV(texCoord0)); asset.texCoords1.emplace_back(convertUV(texCoord1)); asset.positions.emplace_back(positions[j], 1.0_h); } // Populate the index buffer. All faces are triangles at this point because we // asked assimp to perform triangulation. size_t indicesCount = numFaces * faces[0].mNumIndices; size_t indexBufferOffset = asset.indices.size(); totalIndices += indicesCount; for (size_t j = 0; j < numFaces; ++j) { const aiFace& face = faces[j]; for (size_t k = 0; k < face.mNumIndices; ++k) { asset.indices.push_back(uint32_t(face.mIndices[k] + indicesOffset)); } } uint32_t materialId = mesh->mMaterialIndex; aiMaterial const* material = scene->mMaterials[materialId]; aiString name; std::string materialName; if (material->Get(AI_MATKEY_NAME, name) != AI_SUCCESS) { if (isGLTF) { while (outMaterials.find("_mat_" + std::to_string(matCount)) != outMaterials.end()) { matCount++; } materialName = "_mat_" + std::to_string(matCount); } else { materialName = AI_DEFAULT_MATERIAL_NAME; } } else { materialName = name.C_Str(); } if (isGLTF && outMaterials.find(materialName) == outMaterials.end()) { std::string dirName = asset.file.getParent(); processGLTFMaterial(scene, material, materialName, dirName, outMaterials); } aiColor3D color; sRGBColor baseColor{1.0f}; if (material->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) { baseColor = *reinterpret_cast(&color); } float opacity; if (material->Get(AI_MATKEY_OPACITY, opacity) != AI_SUCCESS) { opacity = 1.0f; } if (opacity <= 0.0f) opacity = 1.0f; float shininess; if (material->Get(AI_MATKEY_SHININESS, shininess) != AI_SUCCESS) { shininess = 0.0f; } // convert shininess to roughness float roughness = sqrt(2.0f / (shininess + 2.0f)); float metallic = 0.0f; float reflectance = 0.5f; if (material->Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) { // if there's a non-grey specular color, assume a metallic surface if (color.r != color.g && color.r != color.b) { metallic = 1.0f; baseColor = *reinterpret_cast(&color); } else { if (baseColor.r == 0.0f && baseColor.g == 0.0f && baseColor.b == 0.0f) { metallic = 1.0f; baseColor = *reinterpret_cast(&color); } else { // TODO: the conversion formula is correct // reflectance = sqrtf(color.r / 0.16f); } } } asset.meshes.back().parts.push_back({ indexBufferOffset, indicesCount, materialName, baseColor, opacity, metallic, roughness, reflectance }); } } } if (node->mNumMeshes > 0) { asset.meshes.back().count = totalIndices; } if (node->mNumChildren) { parentIndex = static_cast(asset.meshes.size()) - 1; deep++; depth = std::max(deep, depth); for (size_t i = 0, c = node->mNumChildren; i < c; i++) { processNode(asset, outMaterials, scene, isGLTF, deep, matCount, node->mChildren[i], parentIndex, depth); } deep--; } } void MeshAssimp::processGLTFMaterial(const aiScene* scene, const aiMaterial* material, const std::string& materialName, const std::string& dirName, std::map& outMaterials) const { aiString baseColorPath; aiString AOPath; aiString MRPath; aiString normalPath; aiString emissivePath; aiTextureMapMode mapMode[3]; MaterialConfig matConfig; material->Get(AI_MATKEY_TWOSIDED, matConfig.doubleSided); material->Get(AI_MATKEY_GLTF_UNLIT, matConfig.unlit); aiString alphaMode; material->Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode); if (strcmp(alphaMode.C_Str(), "BLEND") == 0) { matConfig.alphaMode = AlphaMode::TRANSPARENT; } else if (strcmp(alphaMode.C_Str(), "MASK") == 0) { matConfig.alphaMode = AlphaMode::MASKED; float maskThreshold = 0.5; material->Get(AI_MATKEY_GLTF_ALPHACUTOFF, maskThreshold); matConfig.maskThreshold = maskThreshold; } material->Get(_AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, matConfig.baseColorUV); material->Get(_AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, matConfig.metallicRoughnessUV); material->Get(_AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, aiTextureType_LIGHTMAP, 0, matConfig.aoUV); material->Get(_AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, aiTextureType_NORMALS, 0, matConfig.normalUV); material->Get(_AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, aiTextureType_EMISSIVE, 0, matConfig.emissiveUV); uint64_t configHash = hashMaterialConfig(matConfig); if (mGltfMaterialCache.find(configHash) == mGltfMaterialCache.end()) { mGltfMaterialCache[configHash] = createMaterialFromConfig(mEngine, matConfig); } outMaterials[materialName] = mGltfMaterialCache[configHash]->createInstance(); // TODO: is there a way to use the same material for multiple mask threshold values? // if (matConfig.alphaMode == masked) { // float maskThreshold = 0.5; // material->Get(AI_MATKEY_GLTF_ALPHACUTOFF, maskThreshold); // outMaterials[materialName]->setParameter("maskThreshold", maskThreshold); // } // Load property values for gltf files aiColor4D baseColorFactor; aiColor3D emissiveFactor; float metallicFactor = 1.0; float roughnessFactor = 1.0; // TODO: is occlusion strength available on Assimp now? // Load texture images for gltf files TextureSampler sampler( TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR, TextureSampler::MagFilter::LINEAR, TextureSampler::WrapMode::REPEAT); if (material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, &baseColorPath, nullptr, nullptr, nullptr, nullptr, mapMode) == AI_SUCCESS) { unsigned int minType = 0; unsigned int magType = 0; material->Get("$tex.mappingfiltermin", AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, minType); material->Get("$tex.mappingfiltermag", AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, magType); setTextureFromPath(scene, &mEngine, mTextures, baseColorPath, materialName, dirName, mapMode, "baseColorMap", outMaterials, minType, magType); } else { outMaterials[materialName]->setParameter("baseColorMap", mDefaultMap, sampler); } if (material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &MRPath, nullptr, nullptr, nullptr, nullptr, mapMode) == AI_SUCCESS) { unsigned int minType = 0; unsigned int magType = 0; material->Get("$tex.mappingfiltermin", AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, minType); material->Get("$tex.mappingfiltermag", AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, magType); setTextureFromPath(scene, &mEngine, mTextures, MRPath, materialName, dirName, mapMode, "metallicRoughnessMap", outMaterials, minType, magType); } else { outMaterials[materialName]->setParameter("metallicRoughnessMap", mDefaultMap, sampler); outMaterials[materialName]->setParameter("metallicFactor", mDefaultMetallic); outMaterials[materialName]->setParameter("roughnessFactor", mDefaultRoughness); } if (material->GetTexture(aiTextureType_LIGHTMAP, 0, &AOPath, nullptr, nullptr, nullptr, nullptr, mapMode) == AI_SUCCESS) { unsigned int minType = 0; unsigned int magType = 0; material->Get("$tex.mappingfiltermin", aiTextureType_LIGHTMAP, 0, minType); material->Get("$tex.mappingfiltermag", aiTextureType_LIGHTMAP, 0, magType); setTextureFromPath(scene, &mEngine, mTextures, AOPath, materialName, dirName, mapMode, "aoMap", outMaterials, minType, magType); } else { outMaterials[materialName]->setParameter("aoMap", mDefaultMap, sampler); } if (material->GetTexture(aiTextureType_NORMALS, 0, &normalPath, nullptr, nullptr, nullptr, nullptr, mapMode) == AI_SUCCESS) { unsigned int minType = 0; unsigned int magType = 0; material->Get("$tex.mappingfiltermin", aiTextureType_NORMALS, 0, minType); material->Get("$tex.mappingfiltermag", aiTextureType_NORMALS, 0, magType); setTextureFromPath(scene, &mEngine, mTextures, normalPath, materialName, dirName, mapMode, "normalMap", outMaterials, minType, magType); } else { outMaterials[materialName]->setParameter("normalMap", mDefaultNormalMap, sampler); } if (material->GetTexture(aiTextureType_EMISSIVE, 0, &emissivePath, nullptr, nullptr, nullptr, nullptr, mapMode) == AI_SUCCESS) { unsigned int minType = 0; unsigned int magType = 0; material->Get("$tex.mappingfiltermin", aiTextureType_EMISSIVE, 0, minType); material->Get("$tex.mappingfiltermag", aiTextureType_EMISSIVE, 0, magType); setTextureFromPath(scene, &mEngine, mTextures, emissivePath, materialName, dirName, mapMode, "emissiveMap", outMaterials, minType, magType); } else { outMaterials[materialName]->setParameter("emissiveMap", mDefaultMap, sampler); outMaterials[materialName]->setParameter("emissiveFactor", mDefaultEmissive); } //If the gltf has texture factors, override the default factor values if (material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, metallicFactor) == AI_SUCCESS) { outMaterials[materialName]->setParameter("metallicFactor", metallicFactor); } if (material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, roughnessFactor) == AI_SUCCESS) { outMaterials[materialName]->setParameter("roughnessFactor", roughnessFactor); } if (material->Get(AI_MATKEY_COLOR_EMISSIVE, emissiveFactor) == AI_SUCCESS) { sRGBColor emissiveFactorCast = *reinterpret_cast(&emissiveFactor); outMaterials[materialName]->setParameter("emissiveFactor", emissiveFactorCast); } if (material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, baseColorFactor) == AI_SUCCESS) { sRGBColorA baseColorFactorCast = *reinterpret_cast(&baseColorFactor); outMaterials[materialName]->setParameter("baseColorFactor", baseColorFactorCast); } aiBool isSpecularGlossiness = false; if (material->Get(AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS, isSpecularGlossiness) == AI_SUCCESS) { if (isSpecularGlossiness) { std::cout << "Warning: pbrSpecularGlossiness textures are not currently supported" << std::endl; } } }