diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c621b4..a4fa2cc 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 GIT_TAG 1.2.0 INSTALL_DIR @@ -76,6 +77,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 +162,7 @@ add_dependencies(FBX2glTF MathFu FiFoMap Json + STB CxxOpts CPPCodec Fmt @@ -182,6 +194,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/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": { diff --git a/src/Fbx2Raw.cpp b/src/Fbx2Raw.cpp index 62463d8..d8fafc7 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 @@ -338,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)); @@ -400,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 @@ -415,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 @@ -440,7 +526,7 @@ public: private: int rootIndex; - std::vector jointNames; + std::vector jointIds; std::vector jointNodes; std::vector jointSkinningTransforms; std::vector jointInverseGlobalTransforms; @@ -607,8 +693,23 @@ 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.GetNodeById(pNode->GetUniqueID()); + 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()); @@ -624,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 @@ -638,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, meshScaling); + const FbxMatrix inverseTransposeTransform = normalTransform.Inverse().Transpose(); raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_POSITION); if (normalLayer.LayerPresent()) { raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_NORMAL); } @@ -654,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); @@ -684,27 +788,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, 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; @@ -712,19 +812,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]; @@ -852,9 +977,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); } @@ -865,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); } @@ -946,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; @@ -962,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"); @@ -989,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); } } @@ -1057,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; @@ -1300,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 05ada4c..ed787cb 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,29 @@ struct GLTFData Holder scenes; }; +static void WriteToVectorContext(void *context, void *data, int size) { + auto *vec = static_cast *>(context); + for (int ii = 0; ii < size; ii ++) { + vec->push_back(((char *) 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()); + T &result = *iter->second; + return result; +} + +template +T &require(std::map> map, long key) { auto iter = map.find(key); assert(iter != map.end()); @@ -260,7 +281,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( @@ -282,9 +303,10 @@ ModelData *Raw2Gltf( std::unique_ptr gltf(new GLTFData(options.outputBinary)); - std::map> nodesByName; + std::map> nodesById; std::map> materialsByName; - std::map> meshByNodeName; + std::map> textureByIndicesKey; + 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( @@ -303,13 +325,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)); } // @@ -344,7 +366,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"); } @@ -370,23 +392,204 @@ ModelData *Raw2Gltf( // textures // - 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); + using pixel = std::array; // pixel components are floats in [0, 1] + using pixel_merger = std::function)>; - ImageData *source = nullptr; + auto texIndicesKey = [&](std::vector ixVec, std::string tag) -> std::string { + std::string result = tag; + for (int ix : ixVec) { + result += "_" + std::to_string(ix); + } + return result; + }; + + /** + * 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, + bool transparentOutput + ) -> 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, 0); + 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? + + // 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 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))); + } + } + } + + // 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, channels, mergedPixels.data(), width * channels); + } else { + 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); + 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); } @@ -396,15 +599,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 @@ -416,42 +620,290 @@ 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, + bool outputHasAlpha + ) -> std::shared_ptr { + return getDerivedTexture( + { material.textures[u1], material.textures[u2] }, + combine, tag, outputHasAlpha); + }; + + // 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, + bool outputHasAlpha + ) -> std::shared_ptr { + return getDerivedTexture( + { material.textures[u1], material.textures[u2], material.textures[u3] }, + combine, tag, outputHasAlpha); + }; + + 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 }; }, + false); + 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 }; + }, false); + if (material.textures[RAW_TEXTURE_USAGE_DIFFUSE] >= 0) { + 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; + + float oneMinus = 1 - getMaxComponent(specular); + + float pixelMet = solveMetallic( + getPerceivedBrightness(diffuse.xyz()), + getPerceivedBrightness(specular), + oneMinus); + + 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] }; + }, diffuseTex.occlusion == RAW_TEXTURE_OCCLUSION_TRANSPARENT); + } + 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 }; + }, false); + 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 }; + }, true); + 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] }; + }, false); + 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 khrCmnConstantMat; @@ -464,38 +916,27 @@ ModelData *Raw2Gltf( 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, khrCmnConstantMat, pbrMetRough, pbrSpecGloss)); 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]; 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)); - std::string nodeName = rawSurface.nodeName; - 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 { @@ -504,36 +945,10 @@ ModelData *Raw2Gltf( 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(); @@ -674,6 +1089,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.jointIds.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 &jointId : rawSurface.jointIds) { + jointIndexes.push_back(require(nodesById, jointId).ix); + } + + // Write out inverseBindMatrices + auto accIBM = gltf->AddAccessorAndView(buffer, GLT_MAT4F, inverseBindMatrices); + + auto skeletonRoot = require(nodesById, rawSurface.skeletonRootId); + auto skin = *gltf->skins.hold(new SkinData(jointIndexes, *accIBM, skeletonRoot)); + nodeData->SetSkin(skin.ix); + } + } + } + } + // // cameras // @@ -698,16 +1160,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->AddCamera(cam.name); + 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 22084c2..98eb337 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]; @@ -180,18 +165,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; @@ -208,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; } } @@ -218,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; @@ -235,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; @@ -249,20 +234,22 @@ 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); joint.scale = Vec3f(1, 1, 1); @@ -281,7 +268,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; } @@ -469,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)); } @@ -529,12 +516,22 @@ 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; } } 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..1a2c6c4 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 @@ -182,11 +275,11 @@ 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. + 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; @@ -213,7 +306,7 @@ struct RawAnimation struct RawCamera { std::string name; - std::string nodeName; + long nodeId; enum { @@ -242,12 +335,14 @@ 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; + long surfaceId; }; class RawModel @@ -262,22 +357,20 @@ 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, 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, + 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(); @@ -307,6 +400,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(); } @@ -320,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. @@ -334,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; diff --git a/src/glTF/MaterialData.cpp b/src/glTF/MaterialData.cpp index 853ae70..01d489e 100644 --- a/src/glTF/MaterialData.cpp +++ b/src/glTF/MaterialData.cpp @@ -135,10 +135,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) { @@ -153,10 +154,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 b4bcbdb..448d94e 100644 --- a/src/glTF/MaterialData.h +++ b/src/glTF/MaterialData.h @@ -75,10 +75,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; 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; };