diff --git a/Core/Source/bee/Convert/SceneConverter.Animation.cpp b/Core/Source/bee/Convert/SceneConverter.Animation.cpp index 24eff93..662b52d 100644 --- a/Core/Source/bee/Convert/SceneConverter.Animation.cpp +++ b/Core/Source/bee/Convert/SceneConverter.Animation.cpp @@ -111,6 +111,9 @@ void SceneConverter::_convertAnimation(fbxsdk::FbxScene &fbx_scene_) { fx::gltf::Animation glTFAnimation; const auto animName = _convertName(animStack->GetName()); glTFAnimation.name = animName; + + _log(Logger::Level::verbose, fmt::format("Take {}: {}s", animName, timeSpan.GetDuration().GetSecondDouble())); + fbx_scene_.SetCurrentAnimationStack(animStack); for (std::remove_const_t iAnimLayer = 0; iAnimLayer < nAnimLayers; ++iAnimLayer) { diff --git a/Core/Source/bee/Convert/SceneConverter.Material.cpp b/Core/Source/bee/Convert/SceneConverter.Material.cpp index 3165e33..61e0642 100644 --- a/Core/Source/bee/Convert/SceneConverter.Material.cpp +++ b/Core/Source/bee/Convert/SceneConverter.Material.cpp @@ -2,8 +2,257 @@ #include #include #include +#include +#include namespace bee { + +namespace { +template +using ValueAndTexture = std::tuple; + +using FbxDoubleValueAndTexture = ValueAndTexture; + +using FbxDouble4ValueAndTexture = ValueAndTexture; + +template +void extract_property(const fbxsdk::FbxProperty parent_property_, + std::string_view property_name_, + T &value_) { + const auto prop = parent_property_.FindHierarchical(property_name_.data()); + if (prop.IsValid()) { + value_ = prop.Get(); + } +} + +void extract_texture(const fbxsdk::FbxProperty parent_property_, + std::string_view property_name_, + fbxsdk::FbxFileTexture *&value_) { + const auto textureProperty = + parent_property_.Find(property_name_.data(), false); + if (!textureProperty.IsValid()) { + return; + } + const auto isEnabled = parent_property_.Find( + (std::string{property_name_} + "_on").c_str(), false); + if (isEnabled.IsValid() && !isEnabled.Get()) { + return; + } + const auto fbxTexture = + textureProperty.GetSrcObject(); + value_ = fbxTexture; +} + +template +void extract_property(const fbxsdk::FbxProperty parent_property_, + std::string_view property_name_, + std::string_view texture_property_name_, + ValueAndTexture &value_tex_) { + auto &[value, map] = value_tex_; + extract_property(parent_property_, property_name_, value); + extract_texture(parent_property_, texture_property_name_, map); +} + +constexpr fbxsdk::FbxDouble fbx_double_0 = 0.0; +constexpr fbxsdk::FbxDouble fbx_double_1 = 1.0; + +template struct SpecularGlossiness { + glm::tvec3 diffuse; + T opacity; + glm::tvec3 specular; + T shininess_exponent; + + T specular_intensity() const { + return specular.x * 0.2125 + specular.y * 0.7154 + specular.z * 0.0721; + } + + T diffuse_brighness() const { + return diffuse.x * 0.299 + diffuse.y * 0.587 + diffuse.z * 0.114; + } + + T specular_brighness() const { + return specular.x * 0.299 + specular.y * 0.587 + specular.z * 0.114; + } + + T specular_strength() const { + return std::max({specular.x, specular.y, specular.z}); + } +}; + +template struct MetallicRoughness { + glm::tvec3 baseColor; + T opacity; + T metallic; + T roughness; +}; + +template constexpr static T dielectric_specular = 0.04; + +template constexpr static T epsilon = 1e-4; + +template +T solve_metallic(T diffuse_, T specular_, T one_minus_specular_strength_) { + if (specular_ < dielectric_specular) { + return 0.0; + } + + const auto a = dielectric_specular; + const auto b = + diffuse_ * one_minus_specular_strength_ / (1.0 - a) + specular_ - 2.0 * a; + const auto c = a - specular_; + const auto D = b * b - 4.0 * a * c; + const auto squareRoot = std::sqrt(std::max(0.0, D)); + return std::clamp((-b + squareRoot) / (2.0 * a), 0.0, 1.0); +} + +template +MetallicRoughness +sg_to_mr(const SpecularGlossiness &specular_glossiniess_) { + // https://docs.microsoft.com/en-us/azure/remote-rendering/reference/material-mapping + + const auto diffuse = specular_glossiniess_.diffuse; + const auto opacity = specular_glossiniess_.opacity; + const auto specular = specular_glossiniess_.specular; + + const auto oneMinusSpecularStrength = + 1.0 - specular_glossiniess_.specular_strength(); + + const auto roughness = + std::sqrt(2.0 / (specular_glossiniess_.shininess_exponent * + specular_glossiniess_.specular_intensity() + + 2.0)); + + const auto metallic = solve_metallic( + specular_glossiniess_.diffuse_brighness(), + specular_glossiniess_.specular_brighness(), oneMinusSpecularStrength); + + const auto baseColorFromDiffuse = + diffuse * (oneMinusSpecularStrength / (1.0 - dielectric_specular) / + std::max(1.0 - metallic, epsilon)); + const auto baseColroFromSpecular = + (specular - dielectric_specular * (1.0 - metallic)) * + (1.0 / std::max(metallic, epsilon)); + const auto baseColor = + glm::clamp(glm::lerp(baseColorFromDiffuse, baseColroFromSpecular, + metallic * metallic), + glm::zero>(), glm::one>()); + + MetallicRoughness metallicRoughness; + metallicRoughness.baseColor = baseColor; + metallicRoughness.opacity = opacity; + metallicRoughness.metallic = metallic; + metallicRoughness.roughness = roughness; + return metallicRoughness; +} + +struct FbxSurfaceMaterialStandardProperties { +public: + fbxsdk::FbxString shading_mode = "unknown"; + fbxsdk::FbxDouble4 ambient_color = { + static_cast(0.4), static_cast(0.4), + static_cast(0.4), fbx_double_1}; + fbxsdk::FbxDouble ambient_factor = 1.0; + fbxsdk::FbxDouble4 diffuse_color = { + static_cast(0.4), static_cast(0.4), + static_cast(0.4), fbx_double_1}; + fbxsdk::FbxDouble diffuse_factor = fbx_double_1; + fbxsdk::FbxDouble3 specular_color = {static_cast(0.5), + static_cast(0.5), + static_cast(0.5)}; + fbxsdk::FbxDouble specular_factor = fbx_double_1; + fbxsdk::FbxDouble shininess_exponent = + static_cast(6.31179141998291); + fbxsdk::FbxDouble4 transparency_color = {fbx_double_1, fbx_double_1, + fbx_double_1, fbx_double_1}; + fbxsdk::FbxDouble transparency_factor = fbx_double_0; + fbxsdk::FbxDouble4 emissive_color = {fbx_double_0, fbx_double_0, fbx_double_0, + fbx_double_1}; + fbxsdk::FbxDouble emissive_factor = fbx_double_0; + fbxsdk::FbxFileTexture *bump = nullptr; + fbxsdk::FbxFileTexture *normal_map = nullptr; + fbxsdk::FbxDouble bump_factor = fbx_double_1; + + static FbxSurfaceMaterialStandardProperties + read_from(const fbxsdk::FbxSurfaceMaterial &fbx_material_) { + FbxSurfaceMaterialStandardProperties properties; + + const auto &rootProperty = fbx_material_.RootProperty; + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sShadingModel, + properties.shading_mode); + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sAmbient, + properties.ambient_color); + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sAmbientFactor, + properties.ambient_factor); + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sDiffuse, + properties.diffuse_color); + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sDiffuseFactor, + properties.diffuse_factor); + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sSpecular, + properties.specular_color); + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sSpecularFactor, + properties.specular_factor); + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sShininess, + properties.shininess_exponent); + extract_property(rootProperty, + fbxsdk::FbxSurfaceMaterial::sTransparentColor, + properties.transparency_color); + extract_property(rootProperty, + fbxsdk::FbxSurfaceMaterial::sTransparencyFactor, + properties.transparency_factor); + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sEmissive, + properties.emissive_color); + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sEmissiveFactor, + properties.emissive_factor); + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sBump, + properties.bump); + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sNormalMap, + properties.normal_map); + extract_property(rootProperty, fbxsdk::FbxSurfaceMaterial::sBumpFactor, + properties.bump_factor); + // TODO: + // static const char* sReflection; + // static const char* sReflectionFactor; + // static const char* sDisplacementColor; + // static const char* sDisplacementFactor; + // static const char* sVectorDisplacementColor; + // static const char* sVectorDisplacementFactor; + + return properties; + } + + SpecularGlossiness get_specular_glossiness() const { + glm::tvec3 fbxTransparenyScaled = { + transparency_color[0] * transparency_factor, + transparency_color[1] * transparency_factor, + transparency_color[2] * transparency_factor}; + glm::tvec3 diffuseColor; + for (int i = 0; i < 3; ++i) { + diffuseColor[i] = + static_cast(diffuse_color[i] * diffuse_factor * + (fbx_double_1 - fbxTransparenyScaled[i])); + } + // FBX color is RGB, so we calculate the A channel as the average of the FBX + // transparency color + const auto opacity = + fbx_double_1 - (fbxTransparenyScaled.x + fbxTransparenyScaled.y + + fbxTransparenyScaled.z) / + static_cast(3.0); + + SpecularGlossiness specularGlossiness; + specularGlossiness.diffuse = diffuseColor; + specularGlossiness.opacity = opacity; + specularGlossiness.shininess_exponent = shininess_exponent; + specularGlossiness.specular = { + specular_color[0] * specular_factor, + specular_color[1] * specular_factor, + specular_color[2] * specular_factor, + }; + return specularGlossiness; + } +}; + +} // namespace + template class MaterialError : public ErrorBase { public: @@ -68,7 +317,7 @@ SceneConverter::_convertMaterial(fbxsdk::FbxSurfaceMaterial &fbx_material_, static_cast(fbx_material_), material_usage_); } else { - return {}; + return _convertUnknownMaterial(fbx_material_, material_usage_); } }(); r = _materialConvertCache.emplace(convertKey, glTFMaterialIndex).first; @@ -197,4 +446,162 @@ std::optional SceneConverter::_convertLambertMaterial( _glTFBuilder.add(&fx::gltf::Document::materials, std::move(glTFMaterial)); return glTFMaterailIndex; } + +std::optional +SceneConverter::_convertStanardMaterialProperties( + fbxsdk::FbxSurfaceMaterial &fbx_material_, + const MaterialUsage &material_usage_) { + const auto standardProperties = + FbxSurfaceMaterialStandardProperties::read_from(fbx_material_); + + const auto materialName = std::string{fbx_material_.GetName()}; + + fx::gltf::Material glTFMaterial; + glTFMaterial.name = materialName; + auto &glTFPbrMetallicRoughness = glTFMaterial.pbrMetallicRoughness; + + glTFMaterial.emissiveFactor = { + static_cast(standardProperties.emissive_color[0] * + standardProperties.emissive_factor), + static_cast(standardProperties.emissive_color[1] * + standardProperties.emissive_factor), + static_cast(standardProperties.emissive_color[2] * + standardProperties.emissive_factor)}; + + const auto specularGlossiness = standardProperties.get_specular_glossiness(); + + const auto metallicRoughness = sg_to_mr(specularGlossiness); + + glTFPbrMetallicRoughness.baseColorFactor = { + static_cast(metallicRoughness.baseColor.x), + static_cast(metallicRoughness.baseColor.y), + static_cast(metallicRoughness.baseColor.z), + static_cast(metallicRoughness.opacity), + }; + glTFPbrMetallicRoughness.metallicFactor = + static_cast(metallicRoughness.metallic); + glTFPbrMetallicRoughness.roughnessFactor = + static_cast(metallicRoughness.roughness); + + // Normal map + if (standardProperties.normal_map) { + if (const auto glTFNormalMapIndex = + _convertFileTextureShared(*standardProperties.normal_map)) { + glTFMaterial.normalTexture.index = *glTFNormalMapIndex; + } + } + + // Bump map + if (standardProperties.bump) { + if (const auto glTFBumpMapIndex = + _convertFileTextureShared(*standardProperties.bump)) { + glTFMaterial.normalTexture.index = *glTFBumpMapIndex; + } + } + + auto glTFMaterailIndex = + _glTFBuilder.add(&fx::gltf::Document::materials, std::move(glTFMaterial)); + return glTFMaterailIndex; +} // namespace bee + +std::optional SceneConverter::_convertUnknownMaterial( + fbxsdk::FbxSurfaceMaterial &fbx_material_, + const MaterialUsage &material_usage_) { + const auto standard = + _convertStanardMaterialProperties(fbx_material_, material_usage_); + if (!standard) { + return {}; + } + + std::function(const fbxsdk::FbxProperty &)> dumpProperty; + + const auto dumpCompoundProperty = + [&](const fbxsdk::FbxProperty &fbx_property_) -> std::optional { + Json json; + auto child = fbx_property_.GetChild(); + for (; child.IsValid(); child = child.GetSibling()) { + const auto childJson = dumpProperty(child); + if (childJson) { + json[static_cast(child.GetName())] = *childJson; + } + } + return json; + }; + + dumpProperty = + [&](const fbxsdk::FbxProperty &fbx_property_) -> std::optional { + const auto propertyDataType = fbx_property_.GetPropertyDataType(); + if (propertyDataType == fbxsdk::FbxDoubleDT) { + return fbx_property_.Get(); + } else if (propertyDataType == fbxsdk::FbxDouble2DT) { + const auto value = fbx_property_.Get(); + return Json::array({value[0], value[1]}); + } else if (propertyDataType == fbxsdk::FbxDouble3DT || + propertyDataType == fbxsdk::FbxColor3DT) { + const auto value = fbx_property_.Get(); + return Json::array({value[0], value[1], value[2]}); + } else if (propertyDataType == fbxsdk::FbxDouble4DT || + propertyDataType == fbxsdk::FbxColor4DT) { + const auto value = fbx_property_.Get(); + return Json::array({value[0], value[1], value[2], value[3]}); + } else if (propertyDataType == fbxsdk::FbxFloatDT) { + return fbx_property_.Get(); + } else if (propertyDataType == fbxsdk::FbxBoolDT) { + return fbx_property_.Get(); + } else if (propertyDataType == fbxsdk::FbxIntDT) { + return fbx_property_.Get(); + } else if (propertyDataType == fbxsdk::FbxUIntDT) { + return fbx_property_.Get(); + } else if (propertyDataType == fbxsdk::FbxBoolDT) { + return fbx_property_.Get(); + } else if (propertyDataType == fbxsdk::FbxStringDT) { + const auto value = fbx_property_.Get(); + // TODO: process NON-UTF8 strings + return static_cast(value); + } else if (propertyDataType == fbxsdk::FbxReferenceDT) { + const auto srcObjectCount = fbx_property_.GetSrcObjectCount(); + if (srcObjectCount == 0) { + return {}; // nullptr? + } else if (srcObjectCount == 1) { + const auto fbxFileTexture = + fbx_property_.GetSrcObject(); + if (fbxFileTexture) { + const auto glTFTextureIndex = + _convertFileTextureShared(*fbxFileTexture); + if (glTFTextureIndex) { + return *glTFTextureIndex; + } else { + return {}; // Bad texture. + } + } + } + _log(Logger::Level::verbose, + fmt::format( + "Material property {} is invalid: can only reference to texture", + fbx_property_.GetHierarchicalName())); + return {}; + } else if (propertyDataType == fbxsdk::FbxCompoundDT) { + return dumpCompoundProperty(fbx_property_); + } else { + const auto propertyDataTypeName = + fbxsdk::FbxGetDataTypeNameForIO(propertyDataType); + _log(Logger::Level::verbose, + fmt::format("Unknown property data type: {}", propertyDataTypeName)); + return {}; + } + }; + + auto &glTFMaterial = + _glTFBuilder.get(&fx::gltf::Document::materials)[*standard]; + auto &originalMaterial = + glTFMaterial + .extensionsAndExtras["extras"]["FBX-glTF-conv"]["originalMaterial"]; + + const auto properties = dumpCompoundProperty(fbx_material_.RootProperty); + if (properties) { + originalMaterial["properties"] = *properties; + } + + return standard; +} } // namespace bee \ No newline at end of file diff --git a/Core/Source/bee/Convert/SceneConverter.Mesh.cpp b/Core/Source/bee/Convert/SceneConverter.Mesh.cpp index 5e4d4a5..5f6972b 100644 --- a/Core/Source/bee/Convert/SceneConverter.Mesh.cpp +++ b/Core/Source/bee/Convert/SceneConverter.Mesh.cpp @@ -436,8 +436,7 @@ SceneConverter::_createPrimitive(std::list &bulks_, glTFBufferView.name = fmt::format("{}/Target-{}", primitive_name_, *bulk.morphTargetHint); } else { - glTFBufferView.name = - fmt::format("{}", primitive_name_, *bulk.morphTargetHint); + glTFBufferView.name = fmt::format("{}", primitive_name_); } if (bulk.vertexBuffer) { glTFBufferView.target = fx::gltf::BufferView::TargetType::ArrayBuffer; diff --git a/Core/Source/bee/Convert/SceneConverter.Texture.cpp b/Core/Source/bee/Convert/SceneConverter.Texture.cpp index 4d00e71..7ea8758 100644 --- a/Core/Source/bee/Convert/SceneConverter.Texture.cpp +++ b/Core/Source/bee/Convert/SceneConverter.Texture.cpp @@ -9,22 +9,27 @@ std::optional SceneConverter::_convertTextureProperty(fbxsdk::FbxProperty &fbx_property_) { const auto fbxFileTexture = fbx_property_.GetSrcObject(); - if (!fbxFileTexture) { + if (fbxFileTexture) { + return _convertFileTextureShared(*fbxFileTexture); + } else { const auto fbxTexture = fbx_property_.GetSrcObject(); if (fbxTexture) { _log(Logger::Level::verbose, u8"The property is texture but is not file texture. It's ignored."); } return {}; + } +} + +std::optional SceneConverter::_convertFileTextureShared( + fbxsdk::FbxFileTexture &fbx_file_texture_) { + auto fbxTextureId = fbx_file_texture_.GetUniqueID(); + if (auto r = _textureMap.find(fbxTextureId); r != _textureMap.end()) { + return r->second; } else { - auto fbxTextureId = fbxFileTexture->GetUniqueID(); - if (auto r = _textureMap.find(fbxTextureId); r != _textureMap.end()) { - return r->second; - } else { - auto glTFTextureIndex = _convertFileTexture(*fbxFileTexture); - _textureMap.emplace(fbxTextureId, glTFTextureIndex); - return glTFTextureIndex; - } + auto glTFTextureIndex = _convertFileTexture(fbx_file_texture_); + _textureMap.emplace(fbxTextureId, glTFTextureIndex); + return glTFTextureIndex; } } diff --git a/Core/Source/bee/Convert/SceneConverter.h b/Core/Source/bee/Convert/SceneConverter.h index fe224ae..2c00738 100644 --- a/Core/Source/bee/Convert/SceneConverter.h +++ b/Core/Source/bee/Convert/SceneConverter.h @@ -347,9 +347,20 @@ class SceneConverter { _convertLambertMaterial(fbxsdk::FbxSurfaceLambert &fbx_material_, const MaterialUsage &material_usage_); + std::optional + _convertUnknownMaterial(fbxsdk::FbxSurfaceMaterial &fbx_material_, + const MaterialUsage &material_usage_); + + std::optional + _convertStanardMaterialProperties(fbxsdk::FbxSurfaceMaterial &fbx_material_, + const MaterialUsage &material_usage_); + std::optional _convertTextureProperty(fbxsdk::FbxProperty &fbx_property_); + std::optional + _convertFileTextureShared(fbxsdk::FbxFileTexture &fbx_file_texture_); + std::optional _convertFileTexture(const fbxsdk::FbxFileTexture &fbx_texture_); diff --git a/vcpkg.json b/vcpkg.json index e1e515a..7051936 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,17 +1,18 @@ { - "name": "fbx-gltf-conv", - "version-string": "1.0.0-alpha.0", - "maintainers": ["Leslie Leigh "], - "description": "A FBX to glTF file format converter.", - "license": "MIT", - "supports": "windows & osx", - "dependencies": [ - "libxml2", - "zlib", - "nlohmann-json", - "fmt", - "cppcodec", - "range-v3", - "clipp" - ] + "name": "fbx-gltf-conv", + "version-string": "1.0.0-alpha.0", + "maintainers": [ "Leslie Leigh " ], + "description": "A FBX to glTF file format converter.", + "license": "MIT", + "supports": "windows & osx", + "dependencies": [ + "libxml2", + "zlib", + "nlohmann-json", + "fmt", + "cppcodec", + "range-v3", + "clipp", + "glm" + ] } \ No newline at end of file