From f61814f7c9239f8ee71a5b04b6a17e2c7a47f275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Winzell?= Date: Tue, 21 Nov 2017 19:10:45 -0800 Subject: [PATCH 01/11] Pin Draco at 1.2.0, for now As @robertlong discovered, there are compilation errors in newer Draco patch releases under Win/VS 2017. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b7d3518..87ccd41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ endif() # DRACO ExternalProject_Add(Draco GIT_REPOSITORY https://github.com/google/draco + GIT_TAG 1.2.0 PREFIX draco INSTALL_DIR CMAKE_ARGS From fada9e45ee514dbd03569e7e9bbda739d17f7338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20=C3=81vila?= Date: Tue, 28 Nov 2017 10:16:01 +0100 Subject: [PATCH 02/11] Modify FBX2glTF to allow that several nodes share the same mesh. (#46) --- src/Fbx2Raw.cpp | 21 ++++++++- src/Raw2Gltf.cpp | 113 ++++++++++++++++++++++++++++------------------- src/RawModel.cpp | 20 +++++++-- src/RawModel.h | 6 ++- 4 files changed, 106 insertions(+), 54 deletions(-) diff --git a/src/Fbx2Raw.cpp b/src/Fbx2Raw.cpp index 62463d8..d0c7243 100644 --- a/src/Fbx2Raw.cpp +++ b/src/Fbx2Raw.cpp @@ -607,8 +607,25 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: meshConverter.Triangulate(pNode->GetNodeAttribute(), true); FbxMesh *pMesh = pNode->GetMesh(); + // Obtains the surface Id + const long surfaceId = pMesh->GetUniqueID(); + + // Associate the node to this surface + int nodeId = raw.GetNodeByName(pNode->GetName()); + if (nodeId >= 0) + { + RawNode& node = raw.GetNode(nodeId); + node.surfaceId = surfaceId; + } + + if (raw.GetSurfaceById(surfaceId) >= 0) + { + // This surface is already loaded + return; + } + const char *meshName = (pNode->GetName()[0] != '\0') ? pNode->GetName() : pMesh->GetName(); - const int rawSurfaceIndex = raw.AddSurface(meshName, pNode->GetName()); + const int rawSurfaceIndex = raw.AddSurface(meshName, surfaceId); const FbxVector4 *controlPoints = pMesh->GetControlPoints(); const FbxLayerElementAccess normalLayer(pMesh->GetElementNormal(), pMesh->GetElementNormalCount()); @@ -688,7 +705,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: const std::shared_ptr fbxMaterial = materials.GetMaterial(polygonIndex); int textures[RAW_TEXTURE_USAGE_MAX]; - std::fill_n(textures, RAW_TEXTURE_USAGE_MAX, -1); + std::fill_n(textures, (int)RAW_TEXTURE_USAGE_MAX, -1); FbxString shadingModel, materialName; FbxVector4 ambient, specular, diffuse, emissive; diff --git a/src/Raw2Gltf.cpp b/src/Raw2Gltf.cpp index 45c98e6..76d94b1 100644 --- a/src/Raw2Gltf.cpp +++ b/src/Raw2Gltf.cpp @@ -231,6 +231,15 @@ T &require(std::map> map, std::string key) return result; } +template +T &require(std::map> map, long key) +{ + auto iter = map.find(key); + assert(iter != map.end()); + T &result = *iter->second; + return result; +} + static const std::vector getIndexArray(const RawModel &raw) { std::vector result; @@ -284,7 +293,7 @@ ModelData *Raw2Gltf( std::map> nodesByName; std::map> materialsByName; - std::map> meshByNodeName; + std::map> meshBySurfaceId; // for now, we only have one buffer; data->binary points to the same vector as that BufferData does. BufferData &buffer = *gltf->buffers.hold( @@ -461,17 +470,6 @@ ModelData *Raw2Gltf( materialsByName[materialHash(material)] = mData; } - // - // surfaces - // - - // in GLTF 2.0, the structural limitation is that a node can - // only belong to a single mesh. A mesh can however contain any - // number of primitives, which are essentially little meshes. - // - // so each RawSurface turns into a primitive, and we sort them - // by root node using this map; one mesh per node. - for (size_t surfaceIndex = 0; surfaceIndex < materialModels.size(); surfaceIndex++) { const RawModel &surfaceModel = materialModels[surfaceIndex]; @@ -481,50 +479,25 @@ ModelData *Raw2Gltf( const RawMaterial &rawMaterial = surfaceModel.GetMaterial(surfaceModel.GetTriangle(0).materialIndex); const MaterialData &mData = require(materialsByName, materialHash(rawMaterial)); - std::string nodeName = rawSurface.nodeName; - NodeData &meshNode = require(nodesByName, nodeName); + int surfaceId = rawSurface.id; + //NodeData &meshNode = require(nodesByName, nodeName); - MeshData *mesh = nullptr; - auto meshIter = meshByNodeName.find(nodeName); - if (meshIter != meshByNodeName.end()) { + MeshData *mesh = nullptr; + auto meshIter = meshBySurfaceId.find(surfaceId); + if (meshIter != meshBySurfaceId.end()) { mesh = meshIter->second.get(); - } else { + } + else { std::vector defaultDeforms; for (const auto &channel : rawSurface.blendChannels) { defaultDeforms.push_back(channel.defaultDeform); } auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name, defaultDeforms)); - meshByNodeName[nodeName] = meshPtr; - meshNode.SetMesh(meshPtr->ix); + meshBySurfaceId[surfaceId] = meshPtr; mesh = meshPtr.get(); } - // - // surface skin - // - if (!rawSurface.jointNames.empty()) { - if (meshNode.skin == -1) { - // glTF uses column-major matrices - std::vector inverseBindMatrices; - for (const auto &inverseBindMatrice : rawSurface.inverseBindMatrices) { - inverseBindMatrices.push_back(inverseBindMatrice.Transpose()); - } - - std::vector jointIndexes; - for (const auto &jointName : rawSurface.jointNames) { - jointIndexes.push_back(require(nodesByName, jointName).ix); - } - - // Write out inverseBindMatrices - auto accIBM = gltf->AddAccessorAndView(buffer, GLT_MAT4F, inverseBindMatrices); - - auto skeletonRoot = require(nodesByName, rawSurface.skeletonRootName); - auto skin = *gltf->skins.hold(new SkinData(jointIndexes, *accIBM, skeletonRoot)); - meshNode.SetSkin(skin.ix); - } - } - std::shared_ptr primitive; if (options.useDraco) { int triangleCount = surfaceModel.GetTriangleCount(); @@ -544,7 +517,8 @@ ModelData *Raw2Gltf( AccessorData &indexes = *gltf->accessors.hold(new AccessorData(GLT_USHORT)); indexes.count = 3 * triangleCount; primitive.reset(new PrimitiveData(indexes, mData, dracoMesh)); - } else { + } + else { const AccessorData &indexes = *gltf->AddAccessorWithView( *gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ELEMENT_ARRAY_BUFFER), GLT_USHORT, getIndexArray(surfaceModel)); @@ -665,6 +639,53 @@ ModelData *Raw2Gltf( mesh->AddPrimitive(primitive); } + // + // Assign meshes to node + // + + for (int i = 0; i < raw.GetNodeCount(); i++) { + + const RawNode &node = raw.GetNode(i); + auto nodeData = gltf->nodes.ptrs[i]; + + // + // Assign mesh to node + // + if (node.surfaceId > 0) + { + int surfaceIndex = raw.GetSurfaceById(node.surfaceId); + const RawSurface &rawSurface = raw.GetSurface(surfaceIndex); + + MeshData &meshData = require(meshBySurfaceId, rawSurface.id); + nodeData->SetMesh(meshData.ix); + + // + // surface skin + // + if (!rawSurface.jointNames.empty()) { + if (nodeData->skin == -1) { + // glTF uses column-major matrices + std::vector inverseBindMatrices; + for (const auto &inverseBindMatrice : rawSurface.inverseBindMatrices) { + inverseBindMatrices.push_back(inverseBindMatrice.Transpose()); + } + + std::vector jointIndexes; + for (const auto &jointName : rawSurface.jointNames) { + jointIndexes.push_back(require(nodesByName, jointName).ix); + } + + // Write out inverseBindMatrices + auto accIBM = gltf->AddAccessorAndView(buffer, GLT_MAT4F, inverseBindMatrices); + + auto skeletonRoot = require(nodesByName, rawSurface.skeletonRootName); + auto skin = *gltf->skins.hold(new SkinData(jointIndexes, *accIBM, skeletonRoot)); + nodeData->SetSkin(skin.ix); + } + } + } + } + // // cameras // diff --git a/src/RawModel.cpp b/src/RawModel.cpp index 22084c2..d6ea545 100644 --- a/src/RawModel.cpp +++ b/src/RawModel.cpp @@ -180,18 +180,18 @@ int RawModel::AddSurface(const RawSurface &surface) return (int) (surfaces.size() - 1); } -int RawModel::AddSurface(const char *name, const char *nodeName) +int RawModel::AddSurface(const char *name, const long surfaceId) { assert(name[0] != '\0'); for (size_t i = 0; i < surfaces.size(); i++) { - if (Gltf::StringUtils::CompareNoCase(surfaces[i].name, name) == 0) { + if (surfaces[i].id == surfaceId) { return (int) i; } } RawSurface surface; + surface.id = surfaceId; surface.name = name; - surface.nodeName = nodeName; surface.bounds.Clear(); surface.discrete = false; @@ -263,6 +263,7 @@ int RawModel::AddNode(const char *name, const char *parentName) joint.isJoint = false; joint.name = name; joint.parentName = parentName; + joint.surfaceId = 0; joint.translation = Vec3f(0, 0, 0); joint.rotation = Quatf(0, 0, 0, 1); joint.scale = Vec3f(1, 1, 1); @@ -281,7 +282,7 @@ void RawModel::Condense() for (auto &triangle : triangles) { const RawSurface &surface = oldSurfaces[triangle.surfaceIndex]; - const int surfaceIndex = AddSurface(surface.name.c_str(), surface.nodeName.c_str()); + const int surfaceIndex = AddSurface(surface.name.c_str(), surface.id); surfaces[surfaceIndex] = surface; triangle.surfaceIndex = surfaceIndex; } @@ -538,3 +539,14 @@ int RawModel::GetNodeByName(const char *name) const } return -1; } + +int RawModel::GetSurfaceById(const long surfaceId) const +{ + for (size_t i = 0; i < surfaces.size(); i++) { + if (surfaces[i].id == surfaceId) { + return (int)i; + } + } + return -1; +} + diff --git a/src/RawModel.h b/src/RawModel.h index 1d0961f..3077bbb 100644 --- a/src/RawModel.h +++ b/src/RawModel.h @@ -182,8 +182,8 @@ struct RawBlendChannel struct RawSurface { + long id; std::string name; // The name of this surface - std::string nodeName; // The node that links to this surface. std::string skeletonRootName; // The name of the root of the skeleton. Bounds bounds; std::vector jointNames; @@ -248,6 +248,7 @@ struct RawNode Vec3f translation; Quatf rotation; Vec3f scale; + long surfaceId; }; class RawModel @@ -267,7 +268,7 @@ public: Vec4f diffuseFactor, Vec3f specularFactor, Vec3f emissiveFactor, float shinineness); int AddSurface(const RawSurface &suface); - int AddSurface(const char *name, const char *nodeName); + int AddSurface(const char *name, long surfaceId); int AddAnimation(const RawAnimation &animation); int AddCameraPerspective( const char *name, const char *nodeName, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, @@ -307,6 +308,7 @@ public: int GetSurfaceCount() const { return (int) surfaces.size(); } const RawSurface &GetSurface(const int index) const { return surfaces[index]; } RawSurface &GetSurface(const int index) { return surfaces[index]; } + int GetSurfaceById(const long id) const; // Iterate over the animations. int GetAnimationCount() const { return (int) animations.size(); } From e2e0d741a22ae321f06ca640d51a982612967265 Mon Sep 17 00:00:00 2001 From: Par Winzell Date: Tue, 28 Nov 2017 01:22:43 -0800 Subject: [PATCH 03/11] Style tweaks. --- src/Fbx2Raw.cpp | 8 +++----- src/Raw2Gltf.cpp | 15 ++++++--------- src/RawModel.cpp | 1 - 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/Fbx2Raw.cpp b/src/Fbx2Raw.cpp index d0c7243..9355faa 100644 --- a/src/Fbx2Raw.cpp +++ b/src/Fbx2Raw.cpp @@ -612,14 +612,12 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: // Associate the node to this surface int nodeId = raw.GetNodeByName(pNode->GetName()); - if (nodeId >= 0) - { - RawNode& node = raw.GetNode(nodeId); + if (nodeId >= 0) { + RawNode &node = raw.GetNode(nodeId); node.surfaceId = surfaceId; } - if (raw.GetSurfaceById(surfaceId) >= 0) - { + if (raw.GetSurfaceById(surfaceId) >= 0) { // This surface is already loaded return; } diff --git a/src/Raw2Gltf.cpp b/src/Raw2Gltf.cpp index 76d94b1..19f2e96 100644 --- a/src/Raw2Gltf.cpp +++ b/src/Raw2Gltf.cpp @@ -293,7 +293,7 @@ ModelData *Raw2Gltf( std::map> nodesByName; std::map> materialsByName; - std::map> meshBySurfaceId; + std::map> meshBySurfaceId; // for now, we only have one buffer; data->binary points to the same vector as that BufferData does. BufferData &buffer = *gltf->buffers.hold( @@ -475,20 +475,18 @@ ModelData *Raw2Gltf( assert(surfaceModel.GetSurfaceCount() == 1); const RawSurface &rawSurface = surfaceModel.GetSurface(0); + const int surfaceId = rawSurface.id; const RawMaterial &rawMaterial = surfaceModel.GetMaterial(surfaceModel.GetTriangle(0).materialIndex); const MaterialData &mData = require(materialsByName, materialHash(rawMaterial)); - int surfaceId = rawSurface.id; - //NodeData &meshNode = require(nodesByName, nodeName); MeshData *mesh = nullptr; - auto meshIter = meshBySurfaceId.find(surfaceId); + auto meshIter = meshBySurfaceId.find(surfaceId); if (meshIter != meshBySurfaceId.end()) { mesh = meshIter->second.get(); - } - else { + } else { std::vector defaultDeforms; for (const auto &channel : rawSurface.blendChannels) { defaultDeforms.push_back(channel.defaultDeform); @@ -517,8 +515,7 @@ ModelData *Raw2Gltf( AccessorData &indexes = *gltf->accessors.hold(new AccessorData(GLT_USHORT)); indexes.count = 3 * triangleCount; primitive.reset(new PrimitiveData(indexes, mData, dracoMesh)); - } - else { + } else { const AccessorData &indexes = *gltf->AddAccessorWithView( *gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ELEMENT_ARRAY_BUFFER), GLT_USHORT, getIndexArray(surfaceModel)); @@ -650,7 +647,7 @@ ModelData *Raw2Gltf( // // Assign mesh to node - // + // if (node.surfaceId > 0) { int surfaceIndex = raw.GetSurfaceById(node.surfaceId); diff --git a/src/RawModel.cpp b/src/RawModel.cpp index d6ea545..3dcd051 100644 --- a/src/RawModel.cpp +++ b/src/RawModel.cpp @@ -549,4 +549,3 @@ int RawModel::GetSurfaceById(const long surfaceId) const } return -1; } - From fdf7bb3336c0b580b0dba73af6a3e7299580bf4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Winzell?= Date: Thu, 30 Nov 2017 22:22:31 -0800 Subject: [PATCH 04/11] Support for Stingray PBS material definitions (#47) This adds the first FBX PBR import path. Materials that have been exported via the Stingray PBS preset should be picked up as native metallic/roughness, and exported essentially 1:1 to the glTF output. In more detail, this commit: - (Re)introduces the STB header libraries as a dependency. We currently use it for reading and writing images. In time we may need a more dedicated PNG compression library. - Generalizes FbxMaterialAccess to return different subclasses of FbxMaterialInfo; currently FbxRoughMetMaterialInfo and FbxTraditionalMaterialInfo. - FbxTraditionalMaterialInfo is populated from the canonical FbxSurfaceMaterial classes. - FbxRoughMetMaterialInfo is currently populated through the Stingray PBS set of properties, further documented in the code. - RawMaterial was in turn generalized to feature a pluggable, type-specific RawMatProps struct; current implementations are, unsurprisingly, RawTraditionalMatProps and RawMetRoughMatProps. These are basically just lists of per-surface constants, e.g. diffuseFactor or roughness. - In the third phase, glTF generation, the bulk of the changes are concerned with creating packed textures of the type needed by e.g. the metallic-roughness struct, where one colour channel holds roughness and the other metallic. This is done with a somewhat pluggable "map source pixels to destination pixel" mechanism. More work will likely be needed here in the future to accomodate more demanding mappings. There's also a lot of code to convert from one representation to another. The most useful, but also the least well-supported conversion, is from old workflow (diffuse, specular, shininess) to metallic/roughness. Going from PBR spec/gloss to PBR met/rough is hard enough, but we go one step sillier and treat shininess as if it were glossiness, which it certainly isn't. More work is needed here! But it's still a fun proof of concept of sorts, and perhaps for some people it's useful to just get *something* into the PBR world. --- CMakeLists.txt | 12 + README.md | 65 +++-- src/Fbx2Raw.cpp | 367 +++++++++++++++++---------- src/Raw2Gltf.cpp | 504 ++++++++++++++++++++++++++++++++++---- src/RawModel.cpp | 33 +-- src/RawModel.h | 163 +++++++++--- src/glTF/MaterialData.cpp | 17 +- src/glTF/MaterialData.h | 5 +- 8 files changed, 903 insertions(+), 263 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 87ccd41..2bc327b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,16 @@ ExternalProject_Add(Json ) set(JSON_INCLUDE_DIR "${CMAKE_BINARY_DIR}/json/src/Json/src") +# stb +ExternalProject_Add(STB + PREFIX stb + GIT_REPOSITORY https://github.com/nothings/stb + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping STB configure step." + BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping STB build step." + INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping STB install step." +) +set(STB_INCLUDE_DIR "${CMAKE_BINARY_DIR}/stb/src/STB") + # cppcodec ExternalProject_Add(CPPCodec PREFIX cppcodec @@ -151,6 +161,7 @@ add_dependencies(FBX2glTF MathFu FiFoMap Json + STB CxxOpts CPPCodec Fmt @@ -182,6 +193,7 @@ target_include_directories(FBX2glTF PUBLIC ${FIFO_MAP_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${CXXOPTS_INCLUDE_DIR} + ${STB_INCLUDE_DIR} ${CPPCODEC_INCLUDE_DIR} ${FMT_INCLUDE_DIR} ) diff --git a/README.md b/README.md index aa2ec16..baedf00 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,10 @@ Usage: --khr-materials-common (WIP) Use KHR_materials_common extensions to specify Unlit/Lambert/Blinn/Phong shaders. --pbr-metallic-roughness (WIP) Try to glean glTF 2.0 native PBR - attributes from the FBX. + attributes from the FBX, or make a best effort + to convert from traditional shader models. --pbr-specular-glossiness - (WIP) Experimentally fill in the + (WIP) Very experimentally employ the KHR_materials_pbrSpecularGlossiness extension. --blend-shape-normals Include blend shape normals, if reported present by the FBX SDK. @@ -192,50 +193,68 @@ With glTF 2.0, we leaped headlong into physically-based rendering (BPR), where the canonical way of expressing what a mesh looks like is by describing its visible material in fundamental attributes like "how rough is this surface". -By contrast, FBX's material support remains in the older world of Lambert and -Phong, with simpler and more direct illumination and shading models. These modes -are largely incompatible — for example, textures in the old workflow often -contain baked lighting, which would arise naturally in a PBR environment. +By contrast, FBX's material support remains largely in the older world of +Lambert and Phong, with simpler and more direct illumination and shading +models. These modes are inherently incompatible — for example, textures in the +old workflow often contain baked lighting of the type that would arise naturally +in a PBR environment. Some material settings remain well supported and transfer automatically: - Emissive constants and textures - Occlusion maps - Normal maps -This leaves the other traditional settings of Lambert: +This leaves the other traditional settings, first of Lambert: - Ambient — this is anathema in the PBR world, where such effects should emerge naturally from the fundamental colour of the material and any ambient lighting present. - Diffuse — the material's direction-agnostic, non-specular reflection, and additionally, with Blinn/Phong: - Specular — a more polished material's direction-sensitive reflection, - - Shininess — just how polished the material is, + - Shininess — just how polished the material is; a higher value here yields a + more mirror-like surface. (All these can be either constants or textures.) #### Exporting as Unlit/Lambert/Phong -Increasingly with PBR materials, these properties are just left at zero or -default values in the FBX. But when they're there, and they're how you want the -glTF materials generated, one option is to use the --khr-materials-common -command line switch, with the awareness that this incurs a required dependency -on the glTF extension `KHR_materials_common`. +If you have a model was constructed using the traditional workflow, you may +choose to export it using the --khr-materials-common switch. This incurs a +dependency on the glTF extension 'KHR_materials_common'; a client that accepts +that extension is making a promise it'll do its best to render i.e. Lambert or +Phong. + +You can use this flag even for PBR models, but the conversion is imperfect to +say the least, and there is no reason why you would ever want to do such a +thing. **Note that at the time of writing, this glTF extension is still undergoing the ratification process, and is furthermore likely to change names.** #### Exporting as Metallic-Roughness PBR -Given the command line flag --pbr-metallic-roughness, we accept glTF 2.0's PBR -mode, but we do so very partially, filling in a couple of reasonable constants -for metalness and roughness and using the diffuse texture, if it exists, as the -`base colour` texture. +Given the command line flag --pbr-metallic-roughness, we throw ourselves into +the warm embrace of glTF 2.0's PBR preference. -More work is needed to harness the power of glTF's 2.0's materials. The biggest -issue here is the lack of any obviously emerging standards to complement FBX -itself. It's not clear what format an artist can export their PBR materials on, -and when they can, how to communicate this information well to `FBX2glTF`. +As mentioned above, there is lilttle consensus in the world on how PBR should be +represented in FBX. At present, we support only one format: Stingray PBS. This +is a featue that comes bundled with Maya, and any PBR model exported through +that route should be digested propertly by FBX2glTF. -(*Stingray PBS* support is -[high on the TODO list](https://github.com/facebookincubator/FBX2glTF/issues/12).) +(A happy note: Allegorithmic's Susbstance Painter also exports Stingray PBS, +when hooked up to Maya.) + +If your model is not a Stingray PBS one, but you still wish to export PBR +(perhaps you want to generate only core glTF wirhout reliance on extensions), +this converter will try its best to convert your old textures. It calculates, on +a pixel by pixel basis, reasonable values for base colour, metallicness and +roughness, using your model's diffuse, specular, and shinines textures. + +It should noted here that this process cannot ever be perfect; this is very much +an apples and oranges situation. + +A note of gratitude here to Gary Hsu who developed the formulae we use for this +process. They can be eyeballed +[here](https://github.com/KhronosGroup/glTF/blob/master/extensions/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows/js/three.pbrUtilities.js) +for the curious. ## Draco Compression The tool will optionally apply [Draco](https://github.com/google/draco) diff --git a/src/Fbx2Raw.cpp b/src/Fbx2Raw.cpp index 9355faa..08b1440 100644 --- a/src/Fbx2Raw.cpp +++ b/src/Fbx2Raw.cpp @@ -88,46 +88,181 @@ private: const FbxLayerElementArrayTemplate *indices; }; -class FbxMaterialAccess -{ - struct FbxMaterialProperties { - FbxFileTexture *texAmbient {}; - FbxVector4 colAmbient {}; - FbxFileTexture *texSpecular {}; - FbxVector4 colSpecular {}; - FbxFileTexture *texDiffuse {}; - FbxVector4 colDiffuse {}; - FbxFileTexture *texEmissive {}; - FbxVector4 colEmissive {}; - FbxFileTexture *texNormal {}; - FbxFileTexture *texShininess {}; - FbxDouble shininess {}; - }; - -private: - const FbxSurfaceMaterial *fbxMaterial; - const std::map &textureLocations; - -public: +struct FbxMaterialInfo { + FbxMaterialInfo(const FbxString &name, const FbxString &shadingModel) + : name(name), + shadingModel(shadingModel) + {} const FbxString name; const FbxString shadingModel; +}; - const struct FbxMaterialProperties props; +struct FbxRoughMetMaterialInfo : FbxMaterialInfo { + static constexpr const char *FBX_SHADER_METROUGH = "MetallicRoughness"; - explicit FbxMaterialAccess( - const FbxSurfaceMaterial *fbxMaterial, const std::map &textureNames) : - fbxMaterial(fbxMaterial), - name(fbxMaterial->GetName()), - shadingModel(fbxMaterial->ShadingModel), - textureLocations(textureNames), - props(extractTextures()) + FbxRoughMetMaterialInfo(const FbxString &name, const FbxString &shadingModel) + : FbxMaterialInfo(name, shadingModel) + {} + const FbxFileTexture *texColor {}; + FbxVector4 colBase {}; + const FbxFileTexture *texNormal {}; + const FbxFileTexture *texMetallic {}; + FbxDouble metallic {}; + const FbxFileTexture *texRoughness {}; + FbxDouble roughness {}; + const FbxFileTexture *texEmissive {}; + FbxVector4 colEmissive {}; + FbxDouble emissiveIntensity {}; + const FbxFileTexture *texAmbientOcclusion {}; + + static std::unique_ptr From( + FbxSurfaceMaterial *fbxMaterial, + const std::map &textureLocations) + { + std::unique_ptr res(new FbxRoughMetMaterialInfo(fbxMaterial->GetName(), FBX_SHADER_METROUGH)); + + const FbxProperty mayaProp = fbxMaterial->FindProperty("Maya"); + if (mayaProp.GetPropertyDataType() != FbxCompoundDT) { + return nullptr; + } + if (!fbxMaterial->ShadingModel.Get().IsEmpty()) { + fmt::printf("Warning: Material %s has surprising shading model: %s\n", + fbxMaterial->GetName(), fbxMaterial->ShadingModel.Get()); + } + + auto getTex = [&](std::string propName) { + const FbxFileTexture *ptr = nullptr; + + const FbxProperty useProp = mayaProp.FindHierarchical(("use_" + propName + "_map").c_str()); + if (useProp.IsValid() && useProp.Get()) { + const FbxProperty texProp = mayaProp.FindHierarchical(("TEX_" + propName + "_map").c_str()); + if (texProp.IsValid()) { + ptr = texProp.GetSrcObject(); + if (ptr != nullptr && textureLocations.find(ptr) == textureLocations.end()) { + ptr = nullptr; + } + } + } else if (verboseOutput && useProp.IsValid()) { + fmt::printf("Note: Property '%s' of material '%s' exists, but is flagged as 'do not use'.\n", + propName, fbxMaterial->GetName()); + } + return ptr; + }; + + auto getVec = [&](std::string propName) -> FbxDouble3 { + const FbxProperty vecProp = mayaProp.FindHierarchical(propName.c_str()); + return vecProp.IsValid() ? vecProp.Get() : FbxDouble3(1, 1, 1); + }; + + auto getVal = [&](std::string propName) -> FbxDouble { + const FbxProperty vecProp = mayaProp.FindHierarchical(propName .c_str()); + return vecProp.IsValid() ? vecProp.Get() : 0; + }; + + res->texNormal = getTex("normal"); + res->texColor = getTex("color"); + res->colBase = getVec("base_color"); + res->texAmbientOcclusion = getTex("ao"); + res->texEmissive = getTex("emissive"); + res->colEmissive = getVec("emissive"); + res->emissiveIntensity = getVal("emissive_intensity"); + res->texMetallic = getTex("metallic"); + res->metallic = getVal("metallic"); + res->texRoughness = getTex("roughness"); + res->roughness = getVal("roughness"); + + return res; + } +}; + +struct FbxTraditionalMaterialInfo : FbxMaterialInfo { + static constexpr const char *FBX_SHADER_LAMBERT = "Lambert"; + static constexpr const char *FBX_SHADER_BLINN = "Blinn"; + static constexpr const char *FBX_SHADER_PHONG = "Phong"; + + FbxTraditionalMaterialInfo(const FbxString &name, const FbxString &shadingModel) + : FbxMaterialInfo(name, shadingModel) {} - struct FbxMaterialProperties extractTextures() { - struct FbxMaterialProperties res; + FbxFileTexture *texAmbient {}; + FbxVector4 colAmbient {}; + FbxFileTexture *texSpecular {}; + FbxVector4 colSpecular {}; + FbxFileTexture *texDiffuse {}; + FbxVector4 colDiffuse {}; + FbxFileTexture *texEmissive {}; + FbxVector4 colEmissive {}; + FbxFileTexture *texNormal {}; + FbxFileTexture *texShininess {}; + FbxDouble shininess {}; + + static std::unique_ptr From( + FbxSurfaceMaterial *fbxMaterial, + const std::map &textureLocations) + { + auto getSurfaceScalar = [&](const char *propName) -> std::tuple { + const FbxProperty prop = fbxMaterial->FindProperty(propName); + + FbxDouble val(0); + FbxFileTexture *tex = prop.GetSrcObject(); + if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) { + tex = nullptr; + } + if (tex == nullptr && prop.IsValid()) { + val = prop.Get(); + } + return std::make_tuple(val, tex); + }; + + auto getSurfaceVector = [&](const char *propName) -> std::tuple { + const FbxProperty prop = fbxMaterial->FindProperty(propName); + + FbxDouble3 val(1, 1, 1); + FbxFileTexture *tex = prop.GetSrcObject(); + if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) { + tex = nullptr; + } + if (tex == nullptr && prop.IsValid()) { + val = prop.Get(); + } + return std::make_tuple(val, tex); + }; + + auto getSurfaceValues = [&](const char *colName, const char *facName) -> std::tuple { + const FbxProperty colProp = fbxMaterial->FindProperty(colName); + const FbxProperty facProp = fbxMaterial->FindProperty(facName); + + FbxDouble3 colorVal(1, 1, 1); + FbxDouble factorVal(1); + + FbxFileTexture *colTex = colProp.GetSrcObject(); + if (colTex != nullptr && textureLocations.find(colTex) == textureLocations.end()) { + colTex = nullptr; + } + if (colTex == nullptr && colProp.IsValid()) { + colorVal = colProp.Get(); + } + FbxFileTexture *facTex = facProp.GetSrcObject(); + if (facTex != nullptr && textureLocations.find(facTex) == textureLocations.end()) { + facTex = nullptr; + } + if (facTex == nullptr && facProp.IsValid()) { + factorVal = facProp.Get(); + } + + auto val = FbxVector4( + colorVal[0] * factorVal, + colorVal[1] * factorVal, + colorVal[2] * factorVal, + factorVal); + return std::make_tuple(val, colTex, facTex); + }; + + std::string name = fbxMaterial->GetName(); + std::unique_ptr res(new FbxTraditionalMaterialInfo(name.c_str(), fbxMaterial->ShadingModel.Get())); // four properties are on the same structure and follow the same rules - auto handleBasicProperty = [&](const char *colName, const char *facName) { + auto handleBasicProperty = [&](const char *colName, const char *facName) -> std::tuple{ FbxFileTexture *colTex, *facTex; FbxVector4 vec; @@ -141,20 +276,20 @@ public: return std::make_tuple(vec, facTex); }; - std::tie(res.colAmbient, res.texAmbient) = + std::tie(res->colAmbient, res->texAmbient) = handleBasicProperty(FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor); - std::tie(res.colSpecular, res.texSpecular) = + std::tie(res->colSpecular, res->texSpecular) = handleBasicProperty(FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor); - std::tie(res.colDiffuse, res.texDiffuse) = + std::tie(res->colDiffuse, res->texDiffuse) = handleBasicProperty(FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor); - std::tie(res.colEmissive, res.texEmissive) = + std::tie(res->colEmissive, res->texEmissive) = handleBasicProperty(FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor); // the normal map can only ever be a map, ignore everything else - std::tie(std::ignore, res.texNormal) = getSurfaceVector(FbxSurfaceMaterial::sNormalMap); + std::tie(std::ignore, res->texNormal) = getSurfaceVector(FbxSurfaceMaterial::sNormalMap); // shininess can be a map or a factor - std::tie(res.shininess, res.texShininess) = getSurfaceScalar(FbxSurfaceMaterial::sShininess); + std::tie(res->shininess, res->texShininess) = getSurfaceScalar(FbxSurfaceMaterial::sShininess); // for transparency we just want a constant vector value; FbxVector4 transparency; @@ -169,73 +304,24 @@ public: fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparencyFactor); } // FBX color is RGB, so we calculate the A channel as the average of the FBX transparency color vector - res.colDiffuse[3] = 1.0 - (transparency[0] + transparency[1] + transparency[2])/3.0; + res->colDiffuse[3] = 1.0 - (transparency[0] + transparency[1] + transparency[2])/3.0; return res; } - - std::tuple getSurfaceScalar(const char *propName) const - { - const FbxProperty prop = fbxMaterial->FindProperty(propName); - - FbxDouble val(0); - FbxFileTexture *tex = prop.GetSrcObject(); - if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) { - tex = nullptr; - } - if (tex == nullptr && prop.IsValid()) { - val = prop.Get(); - } - return std::make_tuple(val, tex); - } - - std::tuple getSurfaceVector(const char *propName) const - { - const FbxProperty prop = fbxMaterial->FindProperty(propName); - - FbxDouble3 val(1, 1, 1); - FbxFileTexture *tex = prop.GetSrcObject(); - if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) { - tex = nullptr; - } - if (tex == nullptr && prop.IsValid()) { - val = prop.Get(); - } - return std::make_tuple(val, tex); - } - - std::tuple getSurfaceValues(const char *colName, const char *facName) const - { - const FbxProperty colProp = fbxMaterial->FindProperty(colName); - const FbxProperty facProp = fbxMaterial->FindProperty(facName); - - FbxDouble3 colorVal(1, 1, 1); - FbxDouble factorVal(1); - - FbxFileTexture *colTex = colProp.GetSrcObject(); - if (colTex != nullptr && textureLocations.find(colTex) == textureLocations.end()) { - colTex = nullptr; - } - if (colTex == nullptr && colProp.IsValid()) { - colorVal = colProp.Get(); - } - FbxFileTexture *facTex = facProp.GetSrcObject(); - if (facTex != nullptr && textureLocations.find(facTex) == textureLocations.end()) { - facTex = nullptr; - } - if (facTex == nullptr && facProp.IsValid()) { - factorVal = facProp.Get(); - } - - auto val = FbxVector4( - colorVal[0] * factorVal, - colorVal[1] * factorVal, - colorVal[2] * factorVal, - factorVal); - return std::make_tuple(val, colTex, facTex); - }; }; + +std::unique_ptr +GetMaterialInfo(FbxSurfaceMaterial *material, const std::map &textureLocations) +{ + std::unique_ptr res; + res = FbxRoughMetMaterialInfo::From(material, textureLocations); + if (!res) { + res = FbxTraditionalMaterialInfo::From(material, textureLocations); + } + return res; +} + class FbxMaterialsAccess { public: @@ -273,14 +359,14 @@ public: } auto summary = summaries[materialNum]; if (summary == nullptr) { - summary = summaries[materialNum] = std::make_shared( + summary = summaries[materialNum] = GetMaterialInfo( mesh->GetNode()->GetSrcObject(materialNum), textureLocations); } } } - const std::shared_ptr GetMaterial(const int polygonIndex) const + const std::shared_ptr GetMaterial(const int polygonIndex) const { if (mappingMode != FbxGeometryElement::eNone) { const int materialNum = indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0); @@ -293,10 +379,10 @@ public: } private: - FbxGeometryElement::EMappingMode mappingMode; - std::vector> summaries {}; - const FbxMesh *mesh; - const FbxLayerElementArrayTemplate *indices; + FbxGeometryElement::EMappingMode mappingMode; + std::vector> summaries {}; + const FbxMesh *mesh; + const FbxLayerElementArrayTemplate *indices; }; class FbxSkinningAccess @@ -699,27 +785,23 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: for (int polygonIndex = 0; polygonIndex < pMesh->GetPolygonCount(); polygonIndex++) { FBX_ASSERT(pMesh->GetPolygonSize(polygonIndex) == 3); - - const std::shared_ptr fbxMaterial = materials.GetMaterial(polygonIndex); + const std::shared_ptr fbxMaterial = materials.GetMaterial(polygonIndex); int textures[RAW_TEXTURE_USAGE_MAX]; - std::fill_n(textures, (int)RAW_TEXTURE_USAGE_MAX, -1); + std::fill_n(textures, (int) RAW_TEXTURE_USAGE_MAX, -1); - FbxString shadingModel, materialName; - FbxVector4 ambient, specular, diffuse, emissive; - FbxDouble shininess; + std::shared_ptr rawMatProps; + FbxString materialName; if (fbxMaterial == nullptr) { materialName = "DefaultMaterial"; - shadingModel = "Lambert"; + rawMatProps.reset(new RawTraditionalMatProps(RAW_SHADING_MODEL_LAMBERT, + Vec3f(0, 0, 0), Vec4f(.5, .5, .5, 1), Vec3f(0, 0, 0), Vec3f(0, 0, 0), 0.5)); } else { materialName = fbxMaterial->name; - shadingModel = fbxMaterial->shadingModel; - const auto &matProps = fbxMaterial->props; - - const auto maybeAddTexture = [&](FbxFileTexture *tex, RawTextureUsage usage) { + const auto maybeAddTexture = [&](const FbxFileTexture *tex, RawTextureUsage usage) { if (tex != nullptr) { // dig out the inferred filename from the textureLocations map FbxString inferredPath = textureLocations.find(tex)->second; @@ -727,19 +809,44 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: } }; - ambient = matProps.colAmbient; - maybeAddTexture(matProps.texAmbient, RAW_TEXTURE_USAGE_AMBIENT); - specular = matProps.colSpecular; - maybeAddTexture(matProps.texSpecular, RAW_TEXTURE_USAGE_SPECULAR); - diffuse = matProps.colDiffuse; - maybeAddTexture(matProps.texDiffuse, RAW_TEXTURE_USAGE_DIFFUSE); - emissive = matProps.colEmissive; - maybeAddTexture(matProps.texEmissive, RAW_TEXTURE_USAGE_EMISSIVE); + std::shared_ptr matInfo; + if (fbxMaterial->shadingModel == FbxRoughMetMaterialInfo::FBX_SHADER_METROUGH) { + FbxRoughMetMaterialInfo *fbxMatInfo = static_cast(fbxMaterial.get()); - maybeAddTexture(matProps.texNormal, RAW_TEXTURE_USAGE_NORMAL); + maybeAddTexture(fbxMatInfo->texColor, RAW_TEXTURE_USAGE_ALBEDO); + maybeAddTexture(fbxMatInfo->texNormal, RAW_TEXTURE_USAGE_NORMAL); + maybeAddTexture(fbxMatInfo->texEmissive, RAW_TEXTURE_USAGE_EMISSIVE); + maybeAddTexture(fbxMatInfo->texRoughness, RAW_TEXTURE_USAGE_ROUGHNESS); + maybeAddTexture(fbxMatInfo->texMetallic, RAW_TEXTURE_USAGE_METALLIC); + maybeAddTexture(fbxMatInfo->texAmbientOcclusion, RAW_TEXTURE_USAGE_OCCLUSION); + rawMatProps.reset(new RawMetRoughMatProps( + RAW_SHADING_MODEL_PBR_MET_ROUGH, toVec4f(fbxMatInfo->colBase), toVec3f(fbxMatInfo->colEmissive), + fbxMatInfo->emissiveIntensity, fbxMatInfo->metallic, fbxMatInfo->roughness)); + } else { - shininess = matProps.shininess; - maybeAddTexture(matProps.texShininess, RAW_TEXTURE_USAGE_SHININESS); + FbxTraditionalMaterialInfo *fbxMatInfo = static_cast(fbxMaterial.get()); + RawShadingModel shadingModel; + if (fbxMaterial->shadingModel == "Lambert") { + shadingModel = RAW_SHADING_MODEL_LAMBERT; + } else if (fbxMaterial->shadingModel == "Blinn") { + shadingModel = RAW_SHADING_MODEL_BLINN; + } else if (fbxMaterial->shadingModel == "Phong") { + shadingModel = RAW_SHADING_MODEL_PHONG; + } else if (fbxMaterial->shadingModel == "Constant") { + shadingModel = RAW_SHADING_MODEL_PHONG; + } else { + shadingModel = RAW_SHADING_MODEL_UNKNOWN; + } + maybeAddTexture(fbxMatInfo->texDiffuse, RAW_TEXTURE_USAGE_DIFFUSE); + maybeAddTexture(fbxMatInfo->texNormal, RAW_TEXTURE_USAGE_NORMAL); + maybeAddTexture(fbxMatInfo->texEmissive, RAW_TEXTURE_USAGE_EMISSIVE); + maybeAddTexture(fbxMatInfo->texShininess, RAW_TEXTURE_USAGE_SHININESS); + maybeAddTexture(fbxMatInfo->texAmbient, RAW_TEXTURE_USAGE_AMBIENT); + maybeAddTexture(fbxMatInfo->texSpecular, RAW_TEXTURE_USAGE_SPECULAR); + rawMatProps.reset(new RawTraditionalMatProps(shadingModel, + toVec3f(fbxMatInfo->colAmbient), toVec4f(fbxMatInfo->colDiffuse), toVec3f(fbxMatInfo->colEmissive), + toVec3f(fbxMatInfo->colSpecular), fbxMatInfo->shininess)); + } } RawVertex rawVertices[3]; @@ -867,9 +974,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: } const RawMaterialType materialType = GetMaterialType(raw, textures, vertexTransparency, skinning.IsSkinned()); - const int rawMaterialIndex = raw.AddMaterial( - materialName, shadingModel, materialType, textures, - toVec3f(ambient), toVec4f(diffuse), toVec3f(specular), toVec3f(emissive), shininess); + const int rawMaterialIndex = raw.AddMaterial(materialName, materialType, textures, rawMatProps); raw.AddTriangle(rawVertexIndices[0], rawVertexIndices[1], rawVertexIndices[2], rawMaterialIndex, rawSurfaceIndex); } diff --git a/src/Raw2Gltf.cpp b/src/Raw2Gltf.cpp index 19f2e96..3860c95 100644 --- a/src/Raw2Gltf.cpp +++ b/src/Raw2Gltf.cpp @@ -12,6 +12,11 @@ #include #include +#define STB_IMAGE_IMPLEMENTATION +#include +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include + #include "FBX2glTF.h" #include "utils/String_Utils.h" #include "utils/Image_Utils.h" @@ -217,13 +222,21 @@ struct GLTFData Holder scenes; }; +template +static void WriteToVectorContext(void *context, void *data, int size) { + std::vector *vec = static_cast *>(context); + for (int ii = 0; ii < size; ii ++) { + vec->emplace_back(((T *) data)[ii]); + } +} + /** * This method sanity-checks existance and then returns a *reference* to the *Data instance * registered under that name. This is safe in the context of this tool, where all such data * classes are guaranteed to stick around for the duration of the process. */ template -T &require(std::map> map, std::string key) +T &require(std::map> map, const std::string &key) { auto iter = map.find(key); assert(iter != map.end()); @@ -269,7 +282,7 @@ ModelData *Raw2Gltf( for (int i = 0; i < raw.GetMaterialCount(); i++) { fmt::printf( "Material %d: %s [shading: %s]\n", i, raw.GetMaterial(i).name.c_str(), - raw.GetMaterial(i).shadingModel.c_str()); + Describe(raw.GetMaterial(i).info->shadingModel)); } if (raw.GetVertexCount() > 2 * raw.GetTriangleCount()) { fmt::printf( @@ -293,6 +306,7 @@ ModelData *Raw2Gltf( std::map> nodesByName; std::map> materialsByName; + std::map> textureByIndicesKey; std::map> meshBySurfaceId; // for now, we only have one buffer; data->binary points to the same vector as that BufferData does. @@ -378,24 +392,189 @@ ModelData *Raw2Gltf( // // textures // + typedef std::array pixel; // pixel components are floats in [0, 1] + typedef std::function)> pixel_merger; - for (int textureIndex = 0; textureIndex < raw.GetTextureCount(); textureIndex++) { - const RawTexture &texture = raw.GetTexture(textureIndex); - const std::string textureName = Gltf::StringUtils::GetFileBaseString(texture.name); - const std::string relativeFilename = Gltf::StringUtils::GetFileNameString(texture.fileLocation); + auto texIndicesKey = [&](std::vector ixVec, std::string tag) -> std::string { + std::string result = tag; + for (int ix : ixVec) { + result += "_"; + result += ix; + } + return result; + }; - ImageData *source = nullptr; + /** + * Create a new derived TextureData for the two given RawTexture indexes, or return a previously created one. + * Each pixel in the derived texture will be determined from its equivalent in each source pixels, as decided + * by the provided `combine` function. + */ + auto getDerivedTexture = [&]( + std::vector rawTexIndices, + const pixel_merger &combine, + const std::string &tag + ) -> std::shared_ptr + { + const std::string key = texIndicesKey(rawTexIndices, tag); + auto iter = textureByIndicesKey.find(key); + if (iter != textureByIndicesKey.end()) { + return iter->second; + } + + auto describeChannel = [&](int channels) -> std::string { + switch(channels) { + case 1: return "G"; + case 2: return "GA"; + case 3: return "RGB"; + case 4: return "RGBA"; + default: + return fmt::format("?%d?", channels); + } + }; + + // keep track of some texture data as we load them + struct TexInfo { + explicit TexInfo(int rawTexIx) : rawTexIx(rawTexIx) {} + + const int rawTexIx; + int width {}; + int height {}; + int channels {}; + uint8_t *pixels {}; + }; + + int width = -1, height = -1; + std::string mergedName = tag; + std::string mergedFilename = tag; + std::vector texes { }; + for (const int rawTexIx : rawTexIndices) { + TexInfo info(rawTexIx); + if (rawTexIx >= 0) { + const RawTexture &rawTex = raw.GetTexture(rawTexIx); + const std::string &fileLoc = rawTex.fileLocation; + const std::string &fileLocBase = Gltf::StringUtils::GetFileBaseString(Gltf::StringUtils::GetFileNameString(fileLoc)); + if (!fileLoc.empty()) { + info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 4); + if (!info.pixels) { + fmt::printf("Warning: merge texture [%d](%s) could not be loaded.\n", + rawTexIx, + Gltf::StringUtils::GetFileBaseString(Gltf::StringUtils::GetFileNameString(fileLoc))); + } else { + if (width < 0) { + width = info.width; + height = info.height; + } else if (width != info.width || height != info.height) { + fmt::printf("Warning: texture %s (%d, %d) can't be merged with previous texture(s) of dimension (%d, %d)\n", + Gltf::StringUtils::GetFileBaseString(Gltf::StringUtils::GetFileNameString(fileLoc)), + info.width, info.height, width, height); + // this is bad enough that we abort the whole merge + return nullptr; + } + mergedName += "_" + rawTex.fileName; + mergedFilename += "_" + fileLocBase; + } + } + } + texes.push_back(info); + } + + if (width < 0) { + // no textures to merge; bail + return nullptr; + } + // TODO: which channel combinations make sense in input files? + + std::vector mergedPixels(4 * width * height); + std::vector pixels(texes.size()); + std::vector pixelPointers(texes.size()); + for (int ii = 0; ii < mergedPixels.size(); ii += 4) { + pixels.clear(); + for (int jj = 0; jj < texes.size(); jj ++) { + const TexInfo &tex = texes[jj]; + if (tex.pixels != nullptr) { + pixels[jj] = { tex.pixels[ii+0]/255.0f, tex.pixels[ii+1]/255.0f, tex.pixels[ii+2]/255.0f, tex.pixels[ii+3]/255.0f }; + } else { + // absent textures are to be treated as all ones + pixels[jj] = { 1.0f, 1.0f, 1.0f, 1.0f }; + } + pixelPointers[jj] = &pixels[jj]; + } + const pixel &merged = combine(pixelPointers); + for (int jj = 0; jj < 4; jj ++) { + mergedPixels[ii + jj] = static_cast(fmax(0, fmin(255.0f, merged[jj] * 255.0f))); + } + } + + bool png = true; + std::vector imgBuffer; + int res; + if (png) { + res = stbi_write_png_to_func(WriteToVectorContext, &imgBuffer, width, height, 4, mergedPixels.data(), width * 4); + } else { + res = stbi_write_jpg_to_func(WriteToVectorContext, &imgBuffer, width, height, 4, mergedPixels.data(), 80); + } + if (!res) { + fmt::printf("Warning: failed to generate merge texture '%s'.\n", mergedFilename); + return nullptr; + } + + ImageData *image; if (options.outputBinary) { - auto bufferView = gltf->AddBufferViewForFile(buffer, texture.fileLocation); + const auto bufferView = gltf->AddRawBufferView(buffer, imgBuffer.data(), imgBuffer.size()); + image = new ImageData(mergedName, *bufferView, png ? "image/png" : "image/jpeg"); + + } else { + const std::string imageFilename = mergedFilename + (png ? ".png" : ".jpg"); + const std::string imagePath = outputFolder + imageFilename; + FILE *fp = fopen(imagePath.c_str(), "wb"); + if (fp == nullptr) { + fmt::printf("Warning:: Couldn't write file '%s' for writing.\n", imagePath); + return nullptr; + } + + if (fwrite(imgBuffer.data(), imgBuffer.size(), 1, fp) != 1) { + fmt::printf("Warning: Failed to write %lu bytes to file '%s'.\n", imgBuffer.size(), imagePath); + fclose(fp); + return nullptr; + } + fclose(fp); + if (verboseOutput) { + fmt::printf("Wrote %lu bytes to texture '%s'.\n", imgBuffer.size(), imagePath); + } + + image = new ImageData(mergedName, imageFilename); + } + + std::shared_ptr texDat = gltf->textures.hold( + new TextureData(mergedName, defaultSampler, *gltf->images.hold(image))); + textureByIndicesKey.insert(std::make_pair(key, texDat)); + return texDat; + }; + + /** Create a new TextureData for the given RawTexture index, or return a previously created one. */ + auto getSimpleTexture = [&](int rawTexIndex, const std::string &tag) { + const std::string key = texIndicesKey({ rawTexIndex }, tag); + auto iter = textureByIndicesKey.find(key); + if (iter != textureByIndicesKey.end()) { + return iter->second; + } + + const RawTexture &rawTexture = raw.GetTexture(rawTexIndex); + const std::string textureName = Gltf::StringUtils::GetFileBaseString(rawTexture.name); + const std::string relativeFilename = Gltf::StringUtils::GetFileNameString(rawTexture.fileLocation); + + ImageData *image = nullptr; + if (options.outputBinary) { + auto bufferView = gltf->AddBufferViewForFile(buffer, rawTexture.fileLocation); if (bufferView) { - std::string suffix = Gltf::StringUtils::GetFileSuffixString(texture.fileLocation); - source = new ImageData(relativeFilename, *bufferView, suffixToMimeType(suffix)); + std::string suffix = Gltf::StringUtils::GetFileSuffixString(rawTexture.fileLocation); + image = new ImageData(relativeFilename, *bufferView, suffixToMimeType(suffix)); } } else if (!relativeFilename.empty()) { - source = new ImageData(relativeFilename, relativeFilename); + image = new ImageData(relativeFilename, relativeFilename); std::string outputPath = outputFolder + relativeFilename; - if (FileUtils::CopyFile(texture.fileLocation, outputPath)) { + if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath)) { if (verboseOutput) { fmt::printf("Copied texture '%s' to output folder: %s\n", textureName, outputPath); } @@ -405,15 +584,16 @@ ModelData *Raw2Gltf( // reference, even if the copy failed. } } - if (!source) { + if (!image) { // fallback is tiny transparent gif - source = new ImageData(textureName, "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="); + image = new ImageData(textureName, "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="); } - const TextureData &texDat = *gltf->textures.hold( - new TextureData(textureName, defaultSampler, *gltf->images.hold(source))); - assert(texDat.ix == textureIndex); - } + std::shared_ptr texDat = gltf->textures.hold( + new TextureData(textureName, defaultSampler, *gltf->images.hold(image))); + textureByIndicesKey.insert(std::make_pair(key, texDat)); + return texDat; + }; // // materials @@ -425,47 +605,289 @@ ModelData *Raw2Gltf( material.type == RAW_MATERIAL_TYPE_TRANSPARENT || material.type == RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT; - // find a texture by usage and return it as a TextureData*, or nullptr if none exists. - auto getTex = [&](RawTextureUsage usage) - { - // note that we depend on TextureData.ix == rawTexture's index - return (material.textures[usage] >= 0) ? gltf->textures.ptrs[material.textures[usage]].get() : nullptr; + Vec3f emissiveFactor; + float emissiveIntensity; + + const Vec3f dielectric(0.04f, 0.04f, 0.04f); + const float epsilon = 1e-6f; + + // acquire the texture of a specific RawTextureUsage as *TextData, or nullptr if none exists + auto simpleTex = [&](RawTextureUsage usage) -> std::shared_ptr { + return (material.textures[usage] >= 0) ? getSimpleTexture(material.textures[usage], "simple") : nullptr; + }; + + // acquire derived texture of two RawTextureUsage as *TextData, or nullptr if neither exists + auto merge2Tex = [&]( + const std::string tag, + RawTextureUsage u1, + RawTextureUsage u2, + const pixel_merger &combine + ) -> std::shared_ptr { + return getDerivedTexture({ material.textures[u1], material.textures[u2] }, combine, tag); + }; + + // acquire derived texture of two RawTextureUsage as *TextData, or nullptr if neither exists + auto merge3Tex = [&]( + const std::string tag, + RawTextureUsage u1, + RawTextureUsage u2, + RawTextureUsage u3, + const pixel_merger &combine + ) -> std::shared_ptr { + return getDerivedTexture({ material.textures[u1], material.textures[u2], material.textures[u3] }, combine, tag); + }; + + auto getMaxComponent = [&](const Vec3f &color) { + return fmax(color.x, fmax(color.y, color.z)); + }; + auto getPerceivedBrightness = [&](const Vec3f &color) { + return sqrt(0.299 * color.x * color.x + 0.587 * color.y * color.y + 0.114 * color.z * color.z); + }; + auto toVec3f = [&](const pixel &pix) -> const Vec3f { + return Vec3f(pix[0], pix[1], pix[2]); + }; + auto toVec4f = [&](const pixel &pix) -> const Vec4f { + return Vec4f(pix[0], pix[1], pix[2], pix[3]); }; std::shared_ptr pbrMetRough; if (options.usePBRMetRough) { - pbrMetRough.reset(new PBRMetallicRoughness(getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor)); + // albedo is a basic texture, no merging needed + std::shared_ptr baseColorTex, metRoughTex; + + Vec4f diffuseFactor; + float metallic, roughness; + if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) { + /** + * PBR FBX Material -> PBR Met/Rough glTF. + * + * METALLIC and ROUGHNESS textures are packed in G and B channels of a rough/met texture. + * Other values translate directly. + */ + RawMetRoughMatProps *props = (RawMetRoughMatProps *) material.info.get(); + // merge metallic into the blue channel and roughness into the green, of a new combinatory texture + metRoughTex = merge2Tex("met_rough", + RAW_TEXTURE_USAGE_METALLIC, RAW_TEXTURE_USAGE_ROUGHNESS, + [&](const std::vector pixels) -> pixel { return { 0, (*pixels[1])[0], (*pixels[0])[0], 0 }; }); + baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO); + diffuseFactor = props->diffuseFactor; + metallic = props->metallic; + roughness = props->roughness; + emissiveFactor = props->emissiveFactor; + emissiveIntensity = props->emissiveIntensity; + } else { + /** + * Traditional FBX Material -> PBR Met/Rough glTF. + * + * Diffuse channel is used as base colour. No metallic/roughness texture is attempted. Constant + * metallic/roughness values are hard-coded. + * + * TODO: If we have specular & optional shininess map, we can generate a reasonable rough/met map. + */ + const RawTraditionalMatProps *props = ((RawTraditionalMatProps *) material.info.get()); + diffuseFactor = props->diffuseFactor; + // TODO: make configurable on the command line, or do a better job guessing from other, supplied params + if (material.info->shadingModel == RAW_SHADING_MODEL_LAMBERT) { + metallic = 0.6f; + roughness = 1.0f; + } else { + metallic = 0.2f; + roughness = 0.6f; + } + auto solveMetallic = [&](float pDiff, float pSpec, float oneMinusSpecularStrength) + { + if (pSpec < dielectric.x) { + return 0.0; + } + + float a = dielectric.x; + float b = pDiff * oneMinusSpecularStrength / (1 - dielectric.x) + pSpec - 2 * dielectric.x; + float c = dielectric.x - pSpec; + float D = fmax(b * b - 4 * a * c, 0); + return fmax(0.0, fmin(1.0, (-b + sqrt(D)) / (2 * a))); + }; + metRoughTex = merge3Tex("rough_met", + RAW_TEXTURE_USAGE_SPECULAR, RAW_TEXTURE_USAGE_SHININESS, RAW_TEXTURE_USAGE_DIFFUSE, + [&](const std::vector pixels) -> pixel { + const Vec3f specular = pixels[0] ? toVec3f(*pixels[0]) : props->specularFactor; + float shininess = pixels[1] ? (*pixels[1])[0] : props->shininess; + const Vec4f diffuse = pixels[2] ? toVec4f(*pixels[2]) : props->diffuseFactor; + + float pixelMet = solveMetallic( + getPerceivedBrightness(diffuse.xyz()), + getPerceivedBrightness(specular), + 1 - getMaxComponent(specular)); + float pixelRough = 1 - shininess; + + return { 0, pixelRough, pixelMet, 0 }; + }); + if (material.textures[RAW_TEXTURE_USAGE_DIFFUSE] >= 0) { + baseColorTex = merge2Tex("base_col", + RAW_TEXTURE_USAGE_DIFFUSE, RAW_TEXTURE_USAGE_SPECULAR, + [&](const std::vector pixels) -> pixel { + const Vec4f diffuse = pixels[0] ? toVec4f(*pixels[0]) : props->diffuseFactor; + const Vec3f specular = pixels[1] ? toVec3f(*pixels[1]) : props->specularFactor; + + float oneMinus = 1 - getMaxComponent(specular); + + float pixelMet = solveMetallic( + getPerceivedBrightness(diffuse.xyz()), + getPerceivedBrightness(specular), + oneMinus); + + Vec3f fromDiffuse = diffuse.xyz() * (oneMinus / (1.0 - dielectric.x) / fmax(1.0 - pixelMet, epsilon)); + Vec3f fromSpecular = specular - dielectric * (1.0 - pixelMet) * (1.0 / fmax(pixelMet, epsilon)); + Vec3f baseColor = Vec3f::Lerp(fromDiffuse, fromSpecular, pixelMet * pixelMet); + + return { baseColor[0], baseColor[1], baseColor[2], diffuse[3] }; + }); + } + emissiveFactor = props->emissiveFactor; + emissiveIntensity = 1.0f; + } + pbrMetRough.reset(new PBRMetallicRoughness(baseColorTex.get(), metRoughTex.get(), diffuseFactor, metallic, roughness)); } + std::shared_ptr pbrSpecGloss; if (options.usePBRSpecGloss) { + Vec4f diffuseFactor; + Vec3f specularFactor; + float glossiness; + std::shared_ptr specGlossTex; + std::shared_ptr diffuseTex; + + const Vec3f dielectric(0.04f, 0.04f, 0.04f); + const float epsilon = 1e-6f; + if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) { + /** + * PBR FBX Material -> PBR Spec/Gloss glTF. + * + * TODO: A complete mess. Low priority. + */ + RawMetRoughMatProps *props = (RawMetRoughMatProps *) material.info.get(); + // we can estimate spec/gloss from met/rough by between Vec4f(0.04, 0.04, 0.04) and baseColor + // according to metalness, and then taking gloss to be the inverse of roughness + specGlossTex = merge3Tex("specgloss", + RAW_TEXTURE_USAGE_ALBEDO, RAW_TEXTURE_USAGE_METALLIC, RAW_TEXTURE_USAGE_ROUGHNESS, + [&](const std::vector pixels) -> pixel { + Vec3f baseColor(1.0f); + if (pixels[0]) { + baseColor.x = (*pixels[0])[0]; + baseColor.y = (*pixels[0])[1]; + baseColor.z = (*pixels[0])[2]; + } + float metallic = pixels[1] ? (*pixels[1])[0] : 1.0f; + float roughness = pixels[2] ? (*pixels[2])[0] : 1.0f; + Vec3f spec = Vec3f::Lerp(dielectric, baseColor, metallic); + return { spec[0], spec[1], spec[2], 1.0f - roughness }; + }); + diffuseTex = merge2Tex("albedo", + RAW_TEXTURE_USAGE_ALBEDO, RAW_TEXTURE_USAGE_METALLIC, + [&](const std::vector pixels) -> pixel { + Vec3f baseColor(1.0f); + float alpha = 1.0f; + if (pixels[0]) { + baseColor[0] = (*pixels[0])[0]; + baseColor[1] = (*pixels[0])[1]; + baseColor[2] = (*pixels[0])[2]; + alpha = (*pixels[0])[3]; + } + float metallic = pixels[1] ? (*pixels[1])[0] : 1.0f; + Vec3f spec = Vec3f::Lerp(dielectric, baseColor, metallic); + float maxSpecComp = fmax(fmax(spec.x, spec.y), spec.z); + // attenuate baseColor to get specgloss-compliant diffuse; for details consult + // https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_pbrSpecularGlossiness + Vec3f diffuse = baseColor * (1 - dielectric[0]) * (1 - metallic) * fmax(1.0f - maxSpecComp, epsilon); + return { diffuse[0], diffuse[1], diffuse[2], alpha }; + }); + diffuseFactor = props->diffuseFactor; + specularFactor = Vec3f::Lerp(dielectric, props->diffuseFactor.xyz(), props->metallic); + glossiness = 1.0f - props->roughness; + + } else { + /** + * Traditional FBX Material -> PBR Spec/Gloss glTF. + * + * TODO: A complete mess. Low priority. + */ + // TODO: this section a ludicrous over-simplifictation; we can surely do better. + const RawTraditionalMatProps *props = ((RawTraditionalMatProps *) material.info.get()); + specGlossTex = merge2Tex("specgloss", + RAW_TEXTURE_USAGE_SPECULAR, RAW_TEXTURE_USAGE_SHININESS, + [&](const std::vector pixels) -> pixel { + const auto &spec = *(pixels[0]); + const auto &shine = *(pixels[1]); + return { spec[0], spec[1], spec[2], shine[0] }; + }); + diffuseTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE); + diffuseFactor = props->diffuseFactor; + specularFactor = props->specularFactor; + glossiness = props->shininess; + } + pbrSpecGloss.reset( new PBRSpecularGlossiness( - getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor, - getTex(RAW_TEXTURE_USAGE_SPECULAR), material.specularFactor, material.shininess)); + diffuseTex.get(), diffuseFactor, specGlossTex.get(), specularFactor, glossiness)); } std::shared_ptr khrComMat; if (options.useKHRMatCom) { - auto type = KHRCommonMats::MaterialType::Constant; - if (material.shadingModel == "Lambert") { - type = KHRCommonMats::MaterialType::Lambert; - } else if (material.shadingModel == "Blinn") { - type = KHRCommonMats::MaterialType::Blinn; - } else if (material.shadingModel == "Phong") { - type = KHRCommonMats::MaterialType::Phong; + float shininess; + Vec3f ambientFactor, specularFactor; + Vec4f diffuseFactor; + std::shared_ptr diffuseTex; + auto type = KHRCommonMats::MaterialType::Constant; + + if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) { + /** + * PBR FBX Material -> KHR Common Materials glTF. + * + * TODO: We can use the specularFactor calculation below to generate a reasonable specular map, too. + */ + const RawMetRoughMatProps *props = (RawMetRoughMatProps *) material.info.get(); + shininess = 1.0f - props->roughness; + ambientFactor = Vec3f(0.0f, 0.0f, 0.0f); + diffuseTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO); + diffuseFactor = props->diffuseFactor; + specularFactor = Vec3f::Lerp(Vec3f(0.04f, 0.04f, 0.04f), props->diffuseFactor.xyz(), props->metallic); + // render as Phong if there's any specularity, otherwise Lambert + type = (specularFactor.LengthSquared() > 1e-4) ? + KHRCommonMats::MaterialType::Phong : KHRCommonMats::MaterialType::Lambert; + // TODO: convert textures + + } else { + /** + * Traditional FBX Material -> KHR Common Materials glTF. + * + * Should be in good shape. Essentially pass-through. + */ + const RawTraditionalMatProps *props = (RawTraditionalMatProps *) material.info.get(); + shininess = props->shininess; + ambientFactor = props->ambientFactor; + diffuseTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE); + diffuseFactor = props->diffuseFactor; + specularFactor = props->specularFactor; + + if (material.info->shadingModel == RAW_SHADING_MODEL_LAMBERT) { + type = KHRCommonMats::MaterialType::Lambert; + } else if (material.info->shadingModel == RAW_SHADING_MODEL_BLINN) { + type = KHRCommonMats::MaterialType::Blinn; + } else if (material.info->shadingModel == RAW_SHADING_MODEL_PHONG) { + type = KHRCommonMats::MaterialType::Phong; + } } khrComMat.reset( - new KHRCommonMats( - type, - getTex(RAW_TEXTURE_USAGE_SHININESS), material.shininess, - getTex(RAW_TEXTURE_USAGE_AMBIENT), material.ambientFactor, - getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor, - getTex(RAW_TEXTURE_USAGE_SPECULAR), material.specularFactor)); + new KHRCommonMats(type, + simpleTex(RAW_TEXTURE_USAGE_SHININESS).get(), shininess, + simpleTex(RAW_TEXTURE_USAGE_AMBIENT).get(), ambientFactor, + diffuseTex.get(), diffuseFactor, + simpleTex(RAW_TEXTURE_USAGE_SPECULAR).get(), specularFactor)); } std::shared_ptr mData = gltf->materials.hold( new MaterialData( - material.name, isTransparent, getTex(RAW_TEXTURE_USAGE_NORMAL), - getTex(RAW_TEXTURE_USAGE_EMISSIVE), material.emissiveFactor, + material.name, isTransparent, + simpleTex(RAW_TEXTURE_USAGE_NORMAL).get(), simpleTex(RAW_TEXTURE_USAGE_EMISSIVE).get(), + emissiveFactor * emissiveIntensity, khrComMat, pbrMetRough, pbrSpecGloss)); materialsByName[materialHash(material)] = mData; } diff --git a/src/RawModel.cpp b/src/RawModel.cpp index 3dcd051..712c799 100644 --- a/src/RawModel.cpp +++ b/src/RawModel.cpp @@ -111,35 +111,25 @@ int RawModel::AddTexture(const std::string &name, const std::string &fileName, c int RawModel::AddMaterial(const RawMaterial &material) { - return AddMaterial( - material.name.c_str(), material.shadingModel.c_str(), material.type, material.textures, material.ambientFactor, - material.diffuseFactor, material.specularFactor, material.emissiveFactor, material.shininess); + return AddMaterial(material.name.c_str(), material.type, material.textures, material.info); } int RawModel::AddMaterial( - const char *name, const char *shadingModel, const RawMaterialType materialType, - const int textures[RAW_TEXTURE_USAGE_MAX], const Vec3f ambientFactor, - const Vec4f diffuseFactor, const Vec3f specularFactor, - const Vec3f emissiveFactor, float shinineness) + const char *name, + const RawMaterialType materialType, + const int textures[RAW_TEXTURE_USAGE_MAX], + std::shared_ptr materialInfo) { for (size_t i = 0; i < materials.size(); i++) { if (materials[i].name != name) { continue; } - if (materials[i].shadingModel != shadingModel) { - continue; - } if (materials[i].type != materialType) { continue; } - if (materials[i].ambientFactor != ambientFactor || - materials[i].diffuseFactor != diffuseFactor || - materials[i].specularFactor != specularFactor || - materials[i].emissiveFactor != emissiveFactor || - materials[i].shininess != shinineness) { + if (*(materials[i].info) != *materialInfo) { continue; } - bool match = true; for (int j = 0; match && j < RAW_TEXTURE_USAGE_MAX; j++) { match = match && (materials[i].textures[j] == textures[j]); @@ -150,14 +140,9 @@ int RawModel::AddMaterial( } RawMaterial material; - material.name = name; - material.shadingModel = shadingModel; - material.type = materialType; - material.ambientFactor = ambientFactor; - material.diffuseFactor = diffuseFactor; - material.specularFactor = specularFactor; - material.emissiveFactor = emissiveFactor; - material.shininess = shinineness; + material.name = name; + material.type = materialType; + material.info = materialInfo; for (int i = 0; i < RAW_TEXTURE_USAGE_MAX; i++) { material.textures[i] = textures[i]; diff --git a/src/RawModel.h b/src/RawModel.h index 3077bbb..f729b6f 100644 --- a/src/RawModel.h +++ b/src/RawModel.h @@ -95,8 +95,32 @@ struct RawTriangle int surfaceIndex; }; +enum RawShadingModel +{ + RAW_SHADING_MODEL_UNKNOWN = -1, + RAW_SHADING_MODEL_CONSTANT, + RAW_SHADING_MODEL_LAMBERT, + RAW_SHADING_MODEL_BLINN, + RAW_SHADING_MODEL_PHONG, + RAW_SHADING_MODEL_PBR_MET_ROUGH, + RAW_SHADING_MODEL_MAX +}; + +static inline std::string Describe(RawShadingModel model) { + switch(model) { + case RAW_SHADING_MODEL_UNKNOWN: return ""; + case RAW_SHADING_MODEL_CONSTANT: return "Constant"; + case RAW_SHADING_MODEL_LAMBERT: return "Lambert"; + case RAW_SHADING_MODEL_BLINN: return "Blinn"; + case RAW_SHADING_MODEL_PHONG: return "Phong"; + case RAW_SHADING_MODEL_PBR_MET_ROUGH: return "Metallic/Roughness"; + case RAW_SHADING_MODEL_MAX: default: return ""; + } +} + enum RawTextureUsage { + RAW_TEXTURE_USAGE_NONE = -1, RAW_TEXTURE_USAGE_AMBIENT, RAW_TEXTURE_USAGE_DIFFUSE, RAW_TEXTURE_USAGE_NORMAL, @@ -104,32 +128,28 @@ enum RawTextureUsage RAW_TEXTURE_USAGE_SHININESS, RAW_TEXTURE_USAGE_EMISSIVE, RAW_TEXTURE_USAGE_REFLECTION, + RAW_TEXTURE_USAGE_ALBEDO, + RAW_TEXTURE_USAGE_OCCLUSION, + RAW_TEXTURE_USAGE_ROUGHNESS, + RAW_TEXTURE_USAGE_METALLIC, RAW_TEXTURE_USAGE_MAX }; -inline std::string DescribeTextureUsage(int usage) +static inline std::string Describe(RawTextureUsage usage) { - if (usage < 0) { - return ""; - } - switch (static_cast(usage)) { - case RAW_TEXTURE_USAGE_AMBIENT: - return "ambient"; - case RAW_TEXTURE_USAGE_DIFFUSE: - return "diffuse"; - case RAW_TEXTURE_USAGE_NORMAL: - return "normal"; - case RAW_TEXTURE_USAGE_SPECULAR: - return "specuar"; - case RAW_TEXTURE_USAGE_SHININESS: - return "shininess"; - case RAW_TEXTURE_USAGE_EMISSIVE: - return "emissive"; - case RAW_TEXTURE_USAGE_REFLECTION: - return "reflection"; - case RAW_TEXTURE_USAGE_MAX: - default: - return "unknown"; + switch (usage) { + case RAW_TEXTURE_USAGE_NONE: return ""; + case RAW_TEXTURE_USAGE_AMBIENT: return "ambient"; + case RAW_TEXTURE_USAGE_DIFFUSE: return "diffuse"; + case RAW_TEXTURE_USAGE_NORMAL: return "normal"; + case RAW_TEXTURE_USAGE_SPECULAR: return "specuar"; + case RAW_TEXTURE_USAGE_SHININESS: return "shininess"; + case RAW_TEXTURE_USAGE_EMISSIVE: return "emissive"; + case RAW_TEXTURE_USAGE_REFLECTION: return "reflection"; + case RAW_TEXTURE_USAGE_OCCLUSION: return "occlusion"; + case RAW_TEXTURE_USAGE_ROUGHNESS: return "roughness"; + case RAW_TEXTURE_USAGE_METALLIC: return "metallic"; + case RAW_TEXTURE_USAGE_MAX:default: return "unknown"; } }; @@ -159,18 +179,91 @@ enum RawMaterialType RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT, }; +struct RawMatProps { + explicit RawMatProps(RawShadingModel shadingModel) + : shadingModel(shadingModel) + {} + const RawShadingModel shadingModel; + + virtual bool operator!=(const RawMatProps &other) const { return !(*this == other); } + virtual bool operator==(const RawMatProps &other) const { return shadingModel == other.shadingModel; }; +}; + +struct RawTraditionalMatProps : RawMatProps { + RawTraditionalMatProps( + RawShadingModel shadingModel, + const Vec3f &&ambientFactor, + const Vec4f &&diffuseFactor, + const Vec3f &&emissiveFactor, + const Vec3f &&specularFactor, + const float shininess + ) : RawMatProps(shadingModel), + ambientFactor(ambientFactor), + diffuseFactor(diffuseFactor), + emissiveFactor(emissiveFactor), + specularFactor(specularFactor), + shininess(shininess) + {} + + const Vec3f ambientFactor; + const Vec4f diffuseFactor; + const Vec3f emissiveFactor; + const Vec3f specularFactor; + const float shininess; + + bool operator==(const RawMatProps &other) const override { + if (RawMatProps::operator==(other)) { + const auto &typed = (RawTraditionalMatProps &) other; + return ambientFactor == typed.ambientFactor && + diffuseFactor == typed.diffuseFactor && + specularFactor == typed.specularFactor && + emissiveFactor == typed.emissiveFactor && + shininess == typed.shininess; + } + return false; + } +}; + +struct RawMetRoughMatProps : RawMatProps { + RawMetRoughMatProps( + RawShadingModel shadingModel, + const Vec4f &&diffuseFactor, + const Vec3f &&emissiveFactor, + float emissiveIntensity, + float metallic, + float roughness + ) : RawMatProps(shadingModel), + diffuseFactor(diffuseFactor), + emissiveFactor(emissiveFactor), + emissiveIntensity(emissiveIntensity), + metallic(metallic), + roughness(roughness) + {} + const Vec4f diffuseFactor; + const Vec3f emissiveFactor; + const float emissiveIntensity; + const float metallic; + const float roughness; + + bool operator==(const RawMatProps &other) const override { + if (RawMatProps::operator==(other)) { + const auto &typed = (RawMetRoughMatProps &) other; + return diffuseFactor == typed.diffuseFactor && + emissiveFactor == typed.emissiveFactor && + emissiveIntensity == typed.emissiveIntensity && + metallic == typed.metallic && + roughness == typed.roughness; + } + return false; + } +}; + struct RawMaterial { - - std::string name; - std::string shadingModel; // typically "Surface", "Anisotropic", "Blinn", "Lambert", "Phong", "Phone E" - RawMaterialType type; - Vec3f ambientFactor; - Vec4f diffuseFactor; - Vec3f specularFactor; - Vec3f emissiveFactor; - float shininess; - int textures[RAW_TEXTURE_USAGE_MAX]; + std::string name; + RawMaterialType type; + std::shared_ptr info; + int textures[RAW_TEXTURE_USAGE_MAX]; }; struct RawBlendChannel @@ -263,10 +356,8 @@ public: int AddTexture(const std::string &name, const std::string &fileName, const std::string &fileLocation, RawTextureUsage usage); int AddMaterial(const RawMaterial &material); int AddMaterial( - const char *name, const char *shadingModel, RawMaterialType materialType, - const int textures[RAW_TEXTURE_USAGE_MAX], Vec3f ambientFactor, - Vec4f diffuseFactor, Vec3f specularFactor, - Vec3f emissiveFactor, float shinineness); + const char *name, const RawMaterialType materialType, const int textures[RAW_TEXTURE_USAGE_MAX], + std::shared_ptr materialInfo); int AddSurface(const RawSurface &suface); int AddSurface(const char *name, long surfaceId); int AddAnimation(const RawAnimation &animation); diff --git a/src/glTF/MaterialData.cpp b/src/glTF/MaterialData.cpp index 60984cc..af76401 100644 --- a/src/glTF/MaterialData.cpp +++ b/src/glTF/MaterialData.cpp @@ -126,10 +126,11 @@ void to_json(json &j, const PBRSpecularGlossiness &d) } PBRMetallicRoughness::PBRMetallicRoughness( - const TextureData *baseColorTexture, const Vec4f &baseolorFactor, - float metallic, float roughness) + const TextureData *baseColorTexture, const TextureData *metRoughTexture, + const Vec4f &baseColorFactor, float metallic, float roughness) : baseColorTexture(Tex::ref(baseColorTexture)), - baseColorFactor(baseolorFactor), + metRoughTexture(Tex::ref(metRoughTexture)), + baseColorFactor(baseColorFactor), metallic(metallic), roughness(roughness) { @@ -144,10 +145,14 @@ void to_json(json &j, const PBRMetallicRoughness &d) if (d.baseColorFactor.LengthSquared() > 0) { j["baseColorFactor"] = toStdVec(d.baseColorFactor); } - if (d.metallic != 1.0f) { + if (d.metRoughTexture != nullptr) { + j["metallicRoughnessTexture"] = *d.metRoughTexture; + // if a texture is provided, throw away metallic/roughness values + j["roughnessFactor"] = 1.0f; + j["metallicFactor"] = 1.0f; + } else { + // without a texture, however, use metallic/roughness as constants j["metallicFactor"] = d.metallic; - } - if (d.roughness != 1.0f) { j["roughnessFactor"] = d.roughness; } } diff --git a/src/glTF/MaterialData.h b/src/glTF/MaterialData.h index 3c45ed1..afcfcd7 100644 --- a/src/glTF/MaterialData.h +++ b/src/glTF/MaterialData.h @@ -70,10 +70,11 @@ struct PBRSpecularGlossiness struct PBRMetallicRoughness { PBRMetallicRoughness( - const TextureData *baseColorTexture, const Vec4f &baseolorFactor, - float metallic = 0.1f, float roughness = 0.4f); + const TextureData *baseColorTexture, const TextureData *metRoughTexture, + const Vec4f &baseColorFactor, float metallic = 0.1f, float roughness = 0.6f); std::unique_ptr baseColorTexture; + std::unique_ptr metRoughTexture; const Vec4f baseColorFactor; const float metallic; const float roughness; From 21305baa9306aac2abc6329d0c8e750a877fbc17 Mon Sep 17 00:00:00 2001 From: Par Winzell Date: Thu, 30 Nov 2017 23:02:22 -0800 Subject: [PATCH 05/11] NPM-publish of patch release 0.9.3. --- npm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/package.json b/npm/package.json index 881e248..f027a14 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "fbx2gltf", - "version": "0.9.2", + "version": "0.9.3", "description": "Node wrapper around FBX2glTF tools.", "main": "index.js", "repository": { From a9c4fb1bd0fed1b09afcbc14c92575ffee540b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Winzell?= Date: Thu, 30 Nov 2017 23:15:13 -0800 Subject: [PATCH 06/11] Fix for Windows. (And generally a good idea.) --- src/Raw2Gltf.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Raw2Gltf.cpp b/src/Raw2Gltf.cpp index 3860c95..4cf656c 100644 --- a/src/Raw2Gltf.cpp +++ b/src/Raw2Gltf.cpp @@ -735,8 +735,8 @@ ModelData *Raw2Gltf( getPerceivedBrightness(specular), oneMinus); - Vec3f fromDiffuse = diffuse.xyz() * (oneMinus / (1.0 - dielectric.x) / fmax(1.0 - pixelMet, epsilon)); - Vec3f fromSpecular = specular - dielectric * (1.0 - pixelMet) * (1.0 / fmax(pixelMet, epsilon)); + Vec3f fromDiffuse = diffuse.xyz() * (oneMinus / (1.0f - dielectric.x) / fmax(1.0f - pixelMet, epsilon)); + Vec3f fromSpecular = specular - dielectric * (1.0f - pixelMet) * (1.0f / fmax(pixelMet, epsilon)); Vec3f baseColor = Vec3f::Lerp(fromDiffuse, fromSpecular, pixelMet * pixelMet); return { baseColor[0], baseColor[1], baseColor[2], diffuse[3] }; From 9c2e485cfc2f60b28e01c5b3ddd9f15ef351feba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20=C3=81vila=20Membrives?= Date: Sun, 3 Dec 2017 15:32:53 +0100 Subject: [PATCH 07/11] Apply the correct transform on normals (remove transform & scaling components) --- src/Fbx2Raw.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Fbx2Raw.cpp b/src/Fbx2Raw.cpp index 08b1440..35242ba 100644 --- a/src/Fbx2Raw.cpp +++ b/src/Fbx2Raw.cpp @@ -739,7 +739,10 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: const FbxVector4 meshScaling = pNode->GetGeometricScaling(FbxNode::eSourcePivot); const FbxAMatrix meshTransform(meshTranslation, meshRotation, meshScaling); const FbxMatrix transform = meshTransform; - const FbxMatrix inverseTransposeTransform = transform.Inverse().Transpose(); + + // Remove translation & scaling from transforms that will bi applied to normals, tangents & binormals + const FbxMatrix normalTransform(FbxVector4(), meshRotation, FbxVector4(1.0f, 1.0f, 1.0f, 1.0f)); + const FbxMatrix inverseTransposeTransform = normalTransform.Inverse().Transpose(); raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_POSITION); if (normalLayer.LayerPresent()) { raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_NORMAL); } From 1a46ccf7ff5f42b68d9a11abf0477b2ce775764d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20=C3=81vila=20Membrives?= Date: Sun, 3 Dec 2017 20:47:41 +0100 Subject: [PATCH 08/11] Include meshScaling in normal transform --- src/Fbx2Raw.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fbx2Raw.cpp b/src/Fbx2Raw.cpp index 35242ba..61b241f 100644 --- a/src/Fbx2Raw.cpp +++ b/src/Fbx2Raw.cpp @@ -741,7 +741,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: const FbxMatrix transform = meshTransform; // Remove translation & scaling from transforms that will bi applied to normals, tangents & binormals - const FbxMatrix normalTransform(FbxVector4(), meshRotation, FbxVector4(1.0f, 1.0f, 1.0f, 1.0f)); + const FbxMatrix normalTransform(FbxVector4(), meshRotation, meshScaling); const FbxMatrix inverseTransposeTransform = normalTransform.Inverse().Transpose(); raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_POSITION); From 9d1096f3fff1bdf9354189925438785a4059a21f Mon Sep 17 00:00:00 2001 From: Mike Feldstein Date: Mon, 4 Dec 2017 14:35:57 -0800 Subject: [PATCH 09/11] Properly pass camera data through --- src/Raw2Gltf.cpp | 2 +- src/glTF/NodeData.cpp | 10 +++++----- src/glTF/NodeData.h | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Raw2Gltf.cpp b/src/Raw2Gltf.cpp index 4cf656c..943cc88 100644 --- a/src/Raw2Gltf.cpp +++ b/src/Raw2Gltf.cpp @@ -1134,7 +1134,7 @@ ModelData *Raw2Gltf( fmt::printf("Warning: Camera node name %s does not exist.\n", cam.nodeName); continue; } - iter->second->AddCamera(cam.name); + iter->second->SetCamera(camera.ix); } } diff --git a/src/glTF/NodeData.cpp b/src/glTF/NodeData.cpp index f4a3efd..5e9cfb4 100644 --- a/src/glTF/NodeData.cpp +++ b/src/glTF/NodeData.cpp @@ -20,7 +20,7 @@ NodeData::NodeData( scale(scale), children(), mesh(-1), - cameraName(""), + camera(-1), skin(-1) { } @@ -44,10 +44,10 @@ void NodeData::SetSkin(uint32_t skinIx) skin = skinIx; } -void NodeData::AddCamera(std::string camera) +void NodeData::SetCamera(uint32_t cameraIndex) { assert(!isJoint); - cameraName = std::move(camera); + camera = cameraIndex; } json NodeData::serialize() const @@ -75,8 +75,8 @@ json NodeData::serialize() const if (skin >= 0) { result["skin"] = skin; } - if (!cameraName.empty()) { - result["camera"] = cameraName; + if (camera >= 0) { + result["camera"] = camera; } } return result; diff --git a/src/glTF/NodeData.h b/src/glTF/NodeData.h index cd78acb..6fdd2e7 100644 --- a/src/glTF/NodeData.h +++ b/src/glTF/NodeData.h @@ -19,7 +19,7 @@ struct NodeData : Holdable void AddChildNode(uint32_t childIx); void SetMesh(uint32_t meshIx); void SetSkin(uint32_t skinIx); - void AddCamera(std::string camera); + void SetCamera(uint32_t camera); json serialize() const override; @@ -30,7 +30,7 @@ struct NodeData : Holdable Vec3f scale; std::vector children; int32_t mesh; - std::string cameraName; + int32_t camera; int32_t skin; std::vector skeletons; }; From a2d5c7d87b1b61572d2ea40afe9575075276cbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20=C3=81vila?= Date: Wed, 6 Dec 2017 19:43:18 +0100 Subject: [PATCH 10/11] Fix duplicate name issues (#51) Fix the naming issues. Now the nodes are identified by pNode->GetUniqueID(), instead of its name. All dictionaries and references to nodes are replaced by its id, instead of its name. --- src/Fbx2Raw.cpp | 51 ++++++++++++++++++++++++------------------------ src/Raw2Gltf.cpp | 28 +++++++++++++------------- src/RawModel.cpp | 29 ++++++++++++++------------- src/RawModel.h | 25 ++++++++++++------------ 4 files changed, 68 insertions(+), 65 deletions(-) diff --git a/src/Fbx2Raw.cpp b/src/Fbx2Raw.cpp index 61b241f..d8fafc7 100644 --- a/src/Fbx2Raw.cpp +++ b/src/Fbx2Raw.cpp @@ -424,7 +424,7 @@ public: inverseBindMatrices.emplace_back(globalBindposeInverseMatrix); jointNodes.push_back(cluster->GetLink()); - jointNames.push_back(*cluster->GetLink()->GetName() != '\0' ? cluster->GetLink()->GetName() : cluster->GetName()); + jointIds.push_back(cluster->GetLink()->GetUniqueID()); const FbxAMatrix globalNodeTransform = cluster->GetLink()->EvaluateGlobalTransform(); jointSkinningTransforms.push_back(FbxMatrix(globalNodeTransform * globalBindposeInverseMatrix)); @@ -486,9 +486,9 @@ public: return jointNodes[jointIndex]; } - const char *GetJointName(const int jointIndex) const + const long GetJointId(const int jointIndex) const { - return jointNames[jointIndex].c_str(); + return jointIds[jointIndex]; } const FbxMatrix &GetJointSkinningTransform(const int jointIndex) const @@ -501,10 +501,10 @@ public: return jointInverseGlobalTransforms[jointIndex]; } - const char *GetRootNode() const + const long GetRootNode() const { assert(rootIndex != -1); - return jointNames[rootIndex].c_str(); + return jointIds[rootIndex]; } const FbxAMatrix &GetInverseBindMatrix(const int jointIndex) const @@ -526,7 +526,7 @@ public: private: int rootIndex; - std::vector jointNames; + std::vector jointIds; std::vector jointNodes; std::vector jointSkinningTransforms; std::vector jointInverseGlobalTransforms; @@ -697,7 +697,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: const long surfaceId = pMesh->GetUniqueID(); // Associate the node to this surface - int nodeId = raw.GetNodeByName(pNode->GetName()); + int nodeId = raw.GetNodeById(pNode->GetUniqueID()); if (nodeId >= 0) { RawNode &node = raw.GetNode(nodeId); node.surfaceId = surfaceId; @@ -725,7 +725,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: if (verboseOutput) { fmt::printf( "mesh %d: %s (skinned: %s)\n", rawSurfaceIndex, meshName, - skinning.IsSkinned() ? skinning.GetRootNode() : "NO"); + skinning.IsSkinned() ? raw.GetNode(raw.GetNodeById(skinning.GetRootNode())).name.c_str() : "NO"); } // The FbxNode geometric transformation describes how a FbxNodeAttribute is offset from @@ -758,12 +758,12 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: RawSurface &rawSurface = raw.GetSurface(rawSurfaceIndex); - rawSurface.skeletonRootName = (skinning.IsSkinned()) ? skinning.GetRootNode() : pNode->GetName(); + rawSurface.skeletonRootId = (skinning.IsSkinned()) ? skinning.GetRootNode() : pNode->GetUniqueID(); for (int jointIndex = 0; jointIndex < skinning.GetNodeCount(); jointIndex++) { - const char *jointName = skinning.GetJointName(jointIndex); - raw.GetNode(raw.GetNodeByName(jointName)).isJoint = true; + const long jointId = skinning.GetJointId(jointIndex); + raw.GetNode(raw.GetNodeById(jointId)).isJoint = true; - rawSurface.jointNames.emplace_back(jointName); + rawSurface.jointIds.emplace_back(jointId); rawSurface.inverseBindMatrices.push_back(toMat4f(skinning.GetInverseBindMatrix(jointIndex))); rawSurface.jointGeometryMins.emplace_back(FLT_MAX, FLT_MAX, FLT_MAX); rawSurface.jointGeometryMaxs.emplace_back(-FLT_MAX, -FLT_MAX, -FLT_MAX); @@ -988,12 +988,12 @@ static void ReadCamera(RawModel &raw, FbxScene *pScene, FbxNode *pNode) const FbxCamera *pCamera = pNode->GetCamera(); if (pCamera->ProjectionType.Get() == FbxCamera::EProjectionType::ePerspective) { raw.AddCameraPerspective( - "", pNode->GetName(), (float) pCamera->FilmAspectRatio, + "", pNode->GetUniqueID(), (float) pCamera->FilmAspectRatio, (float) pCamera->FieldOfViewX, (float) pCamera->FieldOfViewX, (float) pCamera->NearPlane, (float) pCamera->FarPlane); } else { raw.AddCameraOrthographic( - "", pNode->GetName(), + "", pNode->GetUniqueID(), (float) pCamera->OrthoZoom, (float) pCamera->OrthoZoom, (float) pCamera->FarPlane, (float) pCamera->NearPlane); } @@ -1069,10 +1069,11 @@ static FbxVector4 computeLocalScale(FbxNode *pNode, FbxTime pTime = FBXSDK_TIME_ static void ReadNodeHierarchy( RawModel &raw, FbxScene *pScene, FbxNode *pNode, - const std::string &parentName, const std::string &path) + const long parentId, const std::string &path) { + const FbxUInt64 nodeId = pNode->GetUniqueID(); const char *nodeName = pNode->GetName(); - const int nodeIndex = raw.AddNode(nodeName, parentName.c_str()); + const int nodeIndex = raw.AddNode(nodeId, nodeName, parentId); RawNode &node = raw.GetNode(nodeIndex); FbxTransform::EInheritType lInheritType; @@ -1085,7 +1086,7 @@ static void ReadNodeHierarchy( static int warnRrSsCount = 0; static int warnRrsCount = 0; - if (lInheritType == FbxTransform::eInheritRrSs && !parentName.empty()) { + if (lInheritType == FbxTransform::eInheritRrSs && parentId) { if (++warnRrSsCount == 1) { fmt::printf("Warning: node %s uses unsupported transform inheritance type 'eInheritRrSs'.\n", newPath); fmt::printf(" (Further warnings of this type squelched.)\n"); @@ -1112,19 +1113,19 @@ static void ReadNodeHierarchy( node.rotation = toQuatf(localRotation); node.scale = toVec3f(localScaling); - if (parentName.size() > 0) { - RawNode &parentNode = raw.GetNode(raw.GetNodeByName(parentName.c_str())); + if (parentId) { + RawNode &parentNode = raw.GetNode(raw.GetNodeById(parentId)); // Add unique child name to the parent node. - if (std::find(parentNode.childNames.begin(), parentNode.childNames.end(), nodeName) == parentNode.childNames.end()) { - parentNode.childNames.push_back(nodeName); + if (std::find(parentNode.childIds.begin(), parentNode.childIds.end(), nodeId) == parentNode.childIds.end()) { + parentNode.childIds.push_back(nodeId); } } else { // If there is no parent then this is the root node. - raw.SetRootNode(nodeName); + raw.SetRootNode(nodeId); } for (int child = 0; child < pNode->GetChildCount(); child++) { - ReadNodeHierarchy(raw, pScene, pNode->GetChild(child), nodeName, newPath); + ReadNodeHierarchy(raw, pScene, pNode->GetChild(child), nodeId, newPath); } } @@ -1180,7 +1181,7 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene) bool hasMorphs = false; RawChannel channel; - channel.nodeIndex = raw.GetNodeByName(pNode->GetName()); + channel.nodeIndex = raw.GetNodeById(pNode->GetUniqueID()); for (FbxLongLong frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; frameIndex++) { FbxTime pTime; @@ -1423,7 +1424,7 @@ bool LoadFBXFile(RawModel &raw, const char *fbxFileName, const char *textureExte FbxSystemUnit::m.ConvertScene(pScene); } - ReadNodeHierarchy(raw, pScene, pScene->GetRootNode(), "", ""); + ReadNodeHierarchy(raw, pScene, pScene->GetRootNode(), 0, ""); ReadNodeAttributes(raw, pScene, pScene->GetRootNode(), textureLocations); ReadAnimations(raw, pScene); diff --git a/src/Raw2Gltf.cpp b/src/Raw2Gltf.cpp index 943cc88..eaa034d 100644 --- a/src/Raw2Gltf.cpp +++ b/src/Raw2Gltf.cpp @@ -304,7 +304,7 @@ ModelData *Raw2Gltf( std::unique_ptr gltf(new GLTFData(options.outputBinary)); - std::map> nodesByName; + std::map> nodesById; std::map> materialsByName; std::map> textureByIndicesKey; std::map> meshBySurfaceId; @@ -326,13 +326,13 @@ ModelData *Raw2Gltf( auto nodeData = gltf->nodes.hold( new NodeData(node.name, node.translation, node.rotation, node.scale, node.isJoint)); - for (const auto &childName : node.childNames) { - int childIx = raw.GetNodeByName(childName.c_str()); + for (const auto &childId : node.childIds) { + int childIx = raw.GetNodeById(childId); assert(childIx >= 0); nodeData->AddChildNode(childIx); } - assert(nodesByName.find(nodeData->name) == nodesByName.end()); - nodesByName.insert(std::make_pair(nodeData->name, nodeData)); + + nodesById.insert(std::make_pair(node.id, nodeData)); } // @@ -367,7 +367,7 @@ ModelData *Raw2Gltf( channel.scales.size(), channel.weights.size()); } - NodeData &nDat = require(nodesByName, node.name); + NodeData &nDat = require(nodesById, node.id); if (!channel.translations.empty()) { aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.translations), "translation"); } @@ -1081,7 +1081,7 @@ ModelData *Raw2Gltf( // // surface skin // - if (!rawSurface.jointNames.empty()) { + if (!rawSurface.jointIds.empty()) { if (nodeData->skin == -1) { // glTF uses column-major matrices std::vector inverseBindMatrices; @@ -1090,14 +1090,14 @@ ModelData *Raw2Gltf( } std::vector jointIndexes; - for (const auto &jointName : rawSurface.jointNames) { - jointIndexes.push_back(require(nodesByName, jointName).ix); + for (const auto &jointId : rawSurface.jointIds) { + jointIndexes.push_back(require(nodesById, jointId).ix); } // Write out inverseBindMatrices auto accIBM = gltf->AddAccessorAndView(buffer, GLT_MAT4F, inverseBindMatrices); - auto skeletonRoot = require(nodesByName, rawSurface.skeletonRootName); + auto skeletonRoot = require(nodesById, rawSurface.skeletonRootId); auto skin = *gltf->skins.hold(new SkinData(jointIndexes, *accIBM, skeletonRoot)); nodeData->SetSkin(skin.ix); } @@ -1129,16 +1129,16 @@ ModelData *Raw2Gltf( } // Add the camera to the node hierarchy. - auto iter = nodesByName.find(cam.nodeName); - if (iter == nodesByName.end()) { - fmt::printf("Warning: Camera node name %s does not exist.\n", cam.nodeName); + auto iter = nodesById.find(cam.nodeId); + if (iter == nodesById.end()) { + fmt::printf("Warning: Camera node id %s does not exist.\n", cam.nodeId); continue; } iter->second->SetCamera(camera.ix); } } - NodeData &rootNode = require(nodesByName, "RootNode"); + NodeData &rootNode = require(nodesById, raw.GetRootNode()); const SceneData &rootScene = *gltf->scenes.hold(new SceneData(defaultSceneName, rootNode)); if (options.outputBinary) { diff --git a/src/RawModel.cpp b/src/RawModel.cpp index 712c799..98eb337 100644 --- a/src/RawModel.cpp +++ b/src/RawModel.cpp @@ -193,8 +193,8 @@ int RawModel::AddAnimation(const RawAnimation &animation) int RawModel::AddNode(const RawNode &node) { for (size_t i = 0; i < nodes.size(); i++) { - if (Gltf::StringUtils::CompareNoCase(nodes[i].name.c_str(), node.name) == 0) { - return (int) i; + if (nodes[i].id == node.id) { + return (int)i; } } @@ -203,12 +203,12 @@ int RawModel::AddNode(const RawNode &node) } int RawModel::AddCameraPerspective( - const char *name, const char *nodeName, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, const float nearZ, + const char *name, const long nodeId, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, const float nearZ, const float farZ) { RawCamera camera; camera.name = name; - camera.nodeName = nodeName; + camera.nodeId = nodeId; camera.mode = RawCamera::CAMERA_MODE_PERSPECTIVE; camera.perspective.aspectRatio = aspectRatio; camera.perspective.fovDegreesX = fovDegreesX; @@ -220,11 +220,11 @@ int RawModel::AddCameraPerspective( } int RawModel::AddCameraOrthographic( - const char *name, const char *nodeName, const float magX, const float magY, const float nearZ, const float farZ) + const char *name, const long nodeId, const float magX, const float magY, const float nearZ, const float farZ) { RawCamera camera; camera.name = name; - camera.nodeName = nodeName; + camera.nodeId = nodeId; camera.mode = RawCamera::CAMERA_MODE_ORTHOGRAPHIC; camera.orthographic.magX = magX; camera.orthographic.magY = magY; @@ -234,20 +234,21 @@ int RawModel::AddCameraOrthographic( return (int) cameras.size() - 1; } -int RawModel::AddNode(const char *name, const char *parentName) +int RawModel::AddNode(const long id, const char *name, const long parentId) { assert(name[0] != '\0'); for (size_t i = 0; i < nodes.size(); i++) { - if (Gltf::StringUtils::CompareNoCase(nodes[i].name, name) == 0) { + if (nodes[i].id == id ) { return (int) i; } } RawNode joint; joint.isJoint = false; + joint.id = id; joint.name = name; - joint.parentName = parentName; + joint.parentId = parentId; joint.surfaceId = 0; joint.translation = Vec3f(0, 0, 0); joint.rotation = Quatf(0, 0, 0, 1); @@ -455,9 +456,9 @@ void RawModel::CreateMaterialModels( RawSurface &rawSurface = model->GetSurface(surfaceIndex); if (model->GetSurfaceCount() > prevSurfaceCount) { - const std::vector &jointNames = surfaces[sortedTriangles[i].surfaceIndex].jointNames; - for (const auto &jointName : jointNames) { - const int nodeIndex = GetNodeByName(jointName.c_str()); + const std::vector &jointIds = surfaces[sortedTriangles[i].surfaceIndex].jointIds; + for (const auto &jointId : jointIds) { + const int nodeIndex = GetNodeById(jointId); assert(nodeIndex != -1); model->AddNode(GetNode(nodeIndex)); } @@ -515,10 +516,10 @@ void RawModel::CreateMaterialModels( } } -int RawModel::GetNodeByName(const char *name) const +int RawModel::GetNodeById(const long nodeId) const { for (size_t i = 0; i < nodes.size(); i++) { - if (nodes[i].name == name) { + if (nodes[i].id == nodeId) { return (int) i; } } diff --git a/src/RawModel.h b/src/RawModel.h index f729b6f..1a2c6c4 100644 --- a/src/RawModel.h +++ b/src/RawModel.h @@ -277,9 +277,9 @@ struct RawSurface { long id; std::string name; // The name of this surface - std::string skeletonRootName; // The name of the root of the skeleton. + long skeletonRootId; // The id of the root node of the skeleton. Bounds bounds; - std::vector jointNames; + std::vector jointIds; std::vector jointGeometryMins; std::vector jointGeometryMaxs; std::vector inverseBindMatrices; @@ -306,7 +306,7 @@ struct RawAnimation struct RawCamera { std::string name; - std::string nodeName; + long nodeId; enum { @@ -335,9 +335,10 @@ struct RawCamera struct RawNode { bool isJoint; + long id; std::string name; - std::string parentName; - std::vector childNames; + long parentId; + std::vector childIds; Vec3f translation; Quatf rotation; Vec3f scale; @@ -362,14 +363,14 @@ public: int AddSurface(const char *name, long surfaceId); int AddAnimation(const RawAnimation &animation); int AddCameraPerspective( - const char *name, const char *nodeName, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, + const char *name, const long nodeId, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, const float nearZ, const float farZ); int - AddCameraOrthographic(const char *name, const char *nodeName, const float magX, const float magY, const float nearZ, const float farZ); + AddCameraOrthographic(const char *name, const long nodeId, const float magX, const float magY, const float nearZ, const float farZ); int AddNode(const RawNode &node); - int AddNode(const char *name, const char *parentName); - void SetRootNode(const char *name) { rootNodeName = name; } - const char *GetRootNode() const { return rootNodeName.c_str(); } + int AddNode(const long id, const char *name, const long parentId); + void SetRootNode(const long nodeId) { rootNodeId = nodeId; } + const long GetRootNode() const { return rootNodeId; } // Remove unused vertices, textures or materials after removing vertex attributes, textures, materials or surfaces. void Condense(); @@ -413,7 +414,7 @@ public: int GetNodeCount() const { return (int) nodes.size(); } const RawNode &GetNode(const int index) const { return nodes[index]; } RawNode &GetNode(const int index) { return nodes[index]; } - int GetNodeByName(const char *name) const; + int GetNodeById(const long nodeId) const; // Create individual attribute arrays. // Returns true if the vertices store the particular attribute. @@ -427,7 +428,7 @@ public: std::vector &materialModels, const int maxModelVertices, const int keepAttribs, const bool forceDiscrete) const; private: - std::string rootNodeName; + long rootNodeId; int vertexAttributes; std::unordered_map vertexHash; std::vector vertices; From 41f8a5ef03be03f23160107858abc1d6bf15ad54 Mon Sep 17 00:00:00 2001 From: Par Winzell Date: Fri, 22 Dec 2017 16:02:48 -0800 Subject: [PATCH 11/11] Improvements in texture merging. Be more flexible about reading various input formats (most especially varying numbers of channels), and stop outputting RGBA PNGs for textures that don't need it. I'm not sure JPG generation ever worked right. But now it does. --- src/Raw2Gltf.cpp | 100 +++++++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 39 deletions(-) diff --git a/src/Raw2Gltf.cpp b/src/Raw2Gltf.cpp index eaa034d..65356de 100644 --- a/src/Raw2Gltf.cpp +++ b/src/Raw2Gltf.cpp @@ -222,11 +222,10 @@ struct GLTFData Holder scenes; }; -template static void WriteToVectorContext(void *context, void *data, int size) { - std::vector *vec = static_cast *>(context); + auto *vec = static_cast *>(context); for (int ii = 0; ii < size; ii ++) { - vec->emplace_back(((T *) data)[ii]); + vec->push_back(((char *) data)[ii]); } } @@ -392,14 +391,14 @@ ModelData *Raw2Gltf( // // textures // - typedef std::array pixel; // pixel components are floats in [0, 1] - typedef std::function)> pixel_merger; + + using pixel = std::array; // pixel components are floats in [0, 1] + using pixel_merger = std::function)>; auto texIndicesKey = [&](std::vector ixVec, std::string tag) -> std::string { std::string result = tag; for (int ix : ixVec) { - result += "_"; - result += ix; + result += "_" + std::to_string(ix); } return result; }; @@ -412,7 +411,8 @@ ModelData *Raw2Gltf( auto getDerivedTexture = [&]( std::vector rawTexIndices, const pixel_merger &combine, - const std::string &tag + const std::string &tag, + bool transparentOutput ) -> std::shared_ptr { const std::string key = texIndicesKey(rawTexIndices, tag); @@ -454,7 +454,7 @@ ModelData *Raw2Gltf( const std::string &fileLoc = rawTex.fileLocation; const std::string &fileLocBase = Gltf::StringUtils::GetFileBaseString(Gltf::StringUtils::GetFileNameString(fileLoc)); if (!fileLoc.empty()) { - info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 4); + info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 0); if (!info.pixels) { fmt::printf("Warning: merge texture [%d](%s) could not be loaded.\n", rawTexIx, @@ -484,34 +484,49 @@ ModelData *Raw2Gltf( } // TODO: which channel combinations make sense in input files? - std::vector mergedPixels(4 * width * height); + // write 3 or 4 channels depending on whether or not we need transparency + int channels = transparentOutput ? 4 : 3; + + std::vector mergedPixels(static_cast(channels * width * height)); std::vector pixels(texes.size()); std::vector pixelPointers(texes.size()); - for (int ii = 0; ii < mergedPixels.size(); ii += 4) { - pixels.clear(); - for (int jj = 0; jj < texes.size(); jj ++) { - const TexInfo &tex = texes[jj]; - if (tex.pixels != nullptr) { - pixels[jj] = { tex.pixels[ii+0]/255.0f, tex.pixels[ii+1]/255.0f, tex.pixels[ii+2]/255.0f, tex.pixels[ii+3]/255.0f }; - } else { - // absent textures are to be treated as all ones - pixels[jj] = { 1.0f, 1.0f, 1.0f, 1.0f }; + for (int xx = 0; xx < width; xx ++) { + for (int yy = 0; yy < height; yy ++) { + pixels.clear(); + for (int jj = 0; jj < texes.size(); jj ++) { + const TexInfo &tex = texes[jj]; + // each texture's structure will depend on its channel count + int ii = tex.channels * (xx + yy*width); + int kk = 0; + if (tex.pixels != nullptr) { + for (; kk < tex.channels; kk ++) { + pixels[jj][kk] = tex.pixels[ii++] / 255.0f; + } + } + for (; kk < pixels[jj].size(); kk ++) { + pixels[jj][kk] = 1.0f; + } + pixelPointers[jj] = &pixels[jj]; + } + const pixel merged = combine(pixelPointers); + int ii = channels * (xx + yy*width); + for (int jj = 0; jj < channels; jj ++) { + mergedPixels[ii + jj] = static_cast(fmax(0, fmin(255.0f, merged[jj] * 255.0f))); } - pixelPointers[jj] = &pixels[jj]; - } - const pixel &merged = combine(pixelPointers); - for (int jj = 0; jj < 4; jj ++) { - mergedPixels[ii + jj] = static_cast(fmax(0, fmin(255.0f, merged[jj] * 255.0f))); } } - bool png = true; + // write a .png iff we need transparency in the destination texture + bool png = transparentOutput; + std::vector imgBuffer; int res; if (png) { - res = stbi_write_png_to_func(WriteToVectorContext, &imgBuffer, width, height, 4, mergedPixels.data(), width * 4); + res = stbi_write_png_to_func(WriteToVectorContext, &imgBuffer, + width, height, channels, mergedPixels.data(), width * channels); } else { - res = stbi_write_jpg_to_func(WriteToVectorContext, &imgBuffer, width, height, 4, mergedPixels.data(), 80); + res = stbi_write_jpg_to_func(WriteToVectorContext, &imgBuffer, + width, height, channels, mergedPixels.data(), 80); } if (!res) { fmt::printf("Warning: failed to generate merge texture '%s'.\n", mergedFilename); @@ -621,9 +636,12 @@ ModelData *Raw2Gltf( const std::string tag, RawTextureUsage u1, RawTextureUsage u2, - const pixel_merger &combine + const pixel_merger &combine, + bool outputHasAlpha ) -> std::shared_ptr { - return getDerivedTexture({ material.textures[u1], material.textures[u2] }, combine, tag); + return getDerivedTexture( + { material.textures[u1], material.textures[u2] }, + combine, tag, outputHasAlpha); }; // acquire derived texture of two RawTextureUsage as *TextData, or nullptr if neither exists @@ -632,9 +650,12 @@ ModelData *Raw2Gltf( RawTextureUsage u1, RawTextureUsage u2, RawTextureUsage u3, - const pixel_merger &combine + const pixel_merger &combine, + bool outputHasAlpha ) -> std::shared_ptr { - return getDerivedTexture({ material.textures[u1], material.textures[u2], material.textures[u3] }, combine, tag); + return getDerivedTexture( + { material.textures[u1], material.textures[u2], material.textures[u3] }, + combine, tag, outputHasAlpha); }; auto getMaxComponent = [&](const Vec3f &color) { @@ -668,7 +689,8 @@ ModelData *Raw2Gltf( // merge metallic into the blue channel and roughness into the green, of a new combinatory texture metRoughTex = merge2Tex("met_rough", RAW_TEXTURE_USAGE_METALLIC, RAW_TEXTURE_USAGE_ROUGHNESS, - [&](const std::vector pixels) -> pixel { return { 0, (*pixels[1])[0], (*pixels[0])[0], 0 }; }); + [&](const std::vector pixels) -> pixel { return { 0, (*pixels[1])[0], (*pixels[0])[0], 0 }; }, + false); baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO); diffuseFactor = props->diffuseFactor; metallic = props->metallic; @@ -720,10 +742,10 @@ ModelData *Raw2Gltf( float pixelRough = 1 - shininess; return { 0, pixelRough, pixelMet, 0 }; - }); + }, false); if (material.textures[RAW_TEXTURE_USAGE_DIFFUSE] >= 0) { - baseColorTex = merge2Tex("base_col", - RAW_TEXTURE_USAGE_DIFFUSE, RAW_TEXTURE_USAGE_SPECULAR, + const RawTexture &diffuseTex = raw.GetTexture(material.textures[RAW_TEXTURE_USAGE_DIFFUSE]); + baseColorTex = merge2Tex("base_col", RAW_TEXTURE_USAGE_DIFFUSE, RAW_TEXTURE_USAGE_SPECULAR, [&](const std::vector pixels) -> pixel { const Vec4f diffuse = pixels[0] ? toVec4f(*pixels[0]) : props->diffuseFactor; const Vec3f specular = pixels[1] ? toVec3f(*pixels[1]) : props->specularFactor; @@ -740,7 +762,7 @@ ModelData *Raw2Gltf( Vec3f baseColor = Vec3f::Lerp(fromDiffuse, fromSpecular, pixelMet * pixelMet); return { baseColor[0], baseColor[1], baseColor[2], diffuse[3] }; - }); + }, diffuseTex.occlusion == RAW_TEXTURE_OCCLUSION_TRANSPARENT); } emissiveFactor = props->emissiveFactor; emissiveIntensity = 1.0f; @@ -780,7 +802,7 @@ ModelData *Raw2Gltf( float roughness = pixels[2] ? (*pixels[2])[0] : 1.0f; Vec3f spec = Vec3f::Lerp(dielectric, baseColor, metallic); return { spec[0], spec[1], spec[2], 1.0f - roughness }; - }); + }, false); diffuseTex = merge2Tex("albedo", RAW_TEXTURE_USAGE_ALBEDO, RAW_TEXTURE_USAGE_METALLIC, [&](const std::vector pixels) -> pixel { @@ -799,7 +821,7 @@ ModelData *Raw2Gltf( // https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_pbrSpecularGlossiness Vec3f diffuse = baseColor * (1 - dielectric[0]) * (1 - metallic) * fmax(1.0f - maxSpecComp, epsilon); return { diffuse[0], diffuse[1], diffuse[2], alpha }; - }); + }, true); diffuseFactor = props->diffuseFactor; specularFactor = Vec3f::Lerp(dielectric, props->diffuseFactor.xyz(), props->metallic); glossiness = 1.0f - props->roughness; @@ -818,7 +840,7 @@ ModelData *Raw2Gltf( const auto &spec = *(pixels[0]); const auto &shine = *(pixels[1]); return { spec[0], spec[1], spec[2], shine[0] }; - }); + }, false); diffuseTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE); diffuseFactor = props->diffuseFactor; specularFactor = props->specularFactor;