Files
filament/samples/app/MeshAssimp.cpp
Philip Rideout 84eb9547f5 Tangent-related fixups in MeshAssimp.
Assimp's CalcTangentSpace deviates from de facto glTF 2.0 so we were
compensating for this with an unconditional fixup in MeshAssimp. However
the fixup should apply only when CalcTangentSpace is active, i.e. when
the model is missing tangents.

This makes it so that NormalTangentMirrorTest (has tangents) and
NormalTangentTest (needs tangents) both look reasonable.

I also noticed that MeshAssimp was inexplicably applying
aiProcess_CalcTangentSpace twice: once as a flag, and once as a
post-process. I removed the latter.

This will be fixed in the upcoming cgltf-based loader, which will
use our officially-sanctioned utility method in VertexBuffer.

See #528
2019-02-20 13:29:44 -08:00

1109 lines
45 KiB
C++

/*
* 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 <stdlib.h>
#include <string.h>
#include <array>
#include <filament/Color.h>
#include <filament/VertexBuffer.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/Material.h>
#include <filament/Renderer.h>
#include <filament/Scene.h>
#include <filament/RenderableManager.h>
#include <filament/TransformManager.h>
#include <math/norm.h>
#include <math/vec3.h>
#include <math/TVecHelpers.h>
#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>
#include <assimp/cimport.h>
#include <assimp/scene.h>
#include <assimp/pbrmaterial.h>
#include <stb_image.h>
#include <filament/driver/DriverEnums.h>
#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 = texture(materialParams_emissiveMap, emissiveUV);
material.emissive.rgb *= materialParams.emissiveFactor.rgb;
)SHADER";
}
shader += "}\n";
return shader;
}
Material* createMaterialFromConfig(Engine& engine, MaterialConfig config ) {
std::string shader = shaderFromConfig(config);
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(driver::TextureFormat::RGBA8)
.build(mEngine);
Texture::PixelBufferDescriptor defaultNormalBuffer(textureData,
size_t(1 * 1 * 4),
Texture::Format::RGBA,
Texture::Type::UBYTE,
(driver::BufferDescriptor::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<const float3*>(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<bool SNORMUVS>
static ushort2 convertUV(float2 uv) {
if (SNORMUVS) {
short2 uvshort(packSnorm16(uv));
return bit_cast<ushort2>(uvshort);
} else {
half2 uvhalf(uv);
return bit_cast<ushort2>(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<typename T>
struct State {
std::vector<T> state;
explicit State(std::vector<T>&& state) : state(state) { }
static void free(void* buffer, size_t size, void* user) {
auto* const that = static_cast<State<T>*>(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;
driver::TextureFormat inputFormat;
if (sRGB) {
inputFormat = hasAlpha ? driver::TextureFormat::SRGB8_A8 : driver::TextureFormat::SRGB8;
} else {
inputFormat = hasAlpha ? driver::TextureFormat::RGBA8 : driver::TextureFormat::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,
(driver::BufferDescriptor::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;
driver::TextureFormat inputFormat;
if (sRGB) {
inputFormat = hasAlpha ? driver::TextureFormat::SRGB8_A8 : driver::TextureFormat::SRGB8;
} else {
inputFormat = hasAlpha ? driver::TextureFormat::RGBA8 : driver::TextureFormat::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,
(driver::BufferDescriptor::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<filament::Texture*> textures, const aiString &textureFile,
const std::string &materialName, const std::string &textureDirectory,
aiTextureMapMode *mapMode, const char *parameterName,
std::map<std::string, MaterialInstance *> &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<typename VECTOR, typename INDEX>
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<float>::max());
filament::math::float3 bmax(std::numeric_limits<float>::lowest());
for (size_t i = 0; i < count; ++i) {
VECTOR const* p = reinterpret_cast<VECTOR const*>(
(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<std::string, MaterialInstance*>& 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<half4>(std::move(asset.positions));
auto ns = new State<short4>(std::move(asset.tangents));
auto t0s = new State<ushort2>(std::move(asset.texCoords0));
auto t1s = new State<ushort2>(std::move(asset.texCoords1));
auto is = new State<uint32_t>(std::move(asset.indices));
mVertexBuffer->setBufferAt(mEngine, 0,
VertexBuffer::BufferDescriptor(ps->data(), ps->size(), State<half4>::free, ps));
mVertexBuffer->setBufferAt(mEngine, 1,
VertexBuffer::BufferDescriptor(ns->data(), ns->size(), State<short4>::free, ns));
mVertexBuffer->setBufferAt(mEngine, 2,
VertexBuffer::BufferDescriptor(t0s->data(), t0s->size(), State<ushort2>::free, t0s));
mVertexBuffer->setBufferAt(mEngine, 3,
VertexBuffer::BufferDescriptor(t1s->data(), t1s->size(), State<ushort2>::free, t1s));
mIndexBuffer = IndexBuffer::Builder().indexCount(uint32_t(is->size())).build(mEngine);
mIndexBuffer->setBuffer(mEngine,
IndexBuffer::BufferDescriptor(is->data(), is->size(), State<uint32_t>::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<std::string, MaterialInstance*>& 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<void(aiNode const* node, size_t& totalVertexCount, size_t& totalIndexCount)>
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<float>::max());
float2 maxUV0 = float2(std::numeric_limits<float>::lowest());
getMinMaxUV(scene, node, minUV0, maxUV0, 0);
float2 minUV1 = float2(std::numeric_limits<float>::max());
float2 maxUV1 = float2(std::numeric_limits<float>::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<true, true>(asset, outMaterials,
scene, isGLTF, deep, matCount, node, -1, depth);
} else {
processNode<true, false>(asset, outMaterials,
scene, isGLTF, deep, matCount, node, -1, depth);
}
} else {
if (asset.snormUV1) {
processNode<false, true>(asset, outMaterials,
scene, isGLTF, deep, matCount, node, -1, depth);
} else {
processNode<false, false>(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<bool SNORMUV0, bool SNORMUV1>
void MeshAssimp::processNode(Asset& asset,
std::map<std::string,
MaterialInstance *> &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<mat4f const*>(&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<float3 const*>(mesh->mVertices);
float3 const* tangents = reinterpret_cast<float3 const*>(mesh->mTangents);
float3 const* bitangents = reinterpret_cast<float3 const*>(mesh->mBitangents);
float3 const* normals = reinterpret_cast<float3 const*>(mesh->mNormals);
float3 const* texCoords0 = reinterpret_cast<float3 const*>(mesh->mTextureCoords[0]);
float3 const* texCoords1 = reinterpret_cast<float3 const*>(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<float>::packTangentFrame({tangent, bitangent, normal});
asset.tangents.push_back(packSnorm16(q.xyzw));
asset.texCoords0.emplace_back(convertUV<SNORMUV0>(texCoord0));
asset.texCoords1.emplace_back(convertUV<SNORMUV1>(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<sRGBColor*>(&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<sRGBColor*>(&color);
} else {
if (baseColor.r == 0.0f && baseColor.g == 0.0f && baseColor.b == 0.0f) {
metallic = 1.0f;
baseColor = *reinterpret_cast<sRGBColor*>(&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<int>(asset.meshes.size()) - 1;
deep++;
depth = std::max(deep, depth);
for (size_t i = 0, c = node->mNumChildren; i < c; i++) {
processNode<SNORMUV0, SNORMUV1>(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<std::string, MaterialInstance*>& 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<sRGBColor*>(&emissiveFactor);
outMaterials[materialName]->setParameter("emissiveFactor", emissiveFactorCast);
}
if (material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, baseColorFactor) == AI_SUCCESS) {
sRGBColorA baseColorFactorCast = *reinterpret_cast<sRGBColorA*>(&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;
}
}
}