diff --git a/CMakeLists.txt b/CMakeLists.txt index aa348c7..85ee793 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ endif() # DRACO ExternalProject_Add(Draco GIT_REPOSITORY https://github.com/google/draco - GIT_TAG 1.2.5 + GIT_TAG 1.3.1 PREFIX draco INSTALL_DIR CMAKE_ARGS @@ -130,28 +130,48 @@ if (APPLE) endif() set(SOURCE_FILES - src/utils/File_Utils.cpp - src/utils/Image_Utils.cpp - src/utils/String_Utils.cpp - src/main.cpp + src/FBX2glTF.h src/Fbx2Raw.cpp + src/Fbx2Raw.h src/Raw2Gltf.cpp + src/Raw2Gltf.h src/RawModel.cpp - src/glTF/BufferData.cpp - src/glTF/MaterialData.cpp - src/glTF/MeshData.cpp - src/glTF/NodeData.cpp - src/glTF/PrimitiveData.cpp - src/glTF/BufferViewData.cpp - src/glTF/BufferViewData.h + src/RawModel.h src/glTF/AccessorData.cpp src/glTF/AccessorData.h - src/glTF/ImageData.cpp - src/glTF/TextureData.cpp - src/glTF/SkinData.cpp src/glTF/AnimationData.cpp + src/glTF/AnimationData.h + src/glTF/BufferData.cpp + src/glTF/BufferData.h + src/glTF/BufferViewData.cpp + src/glTF/BufferViewData.h src/glTF/CameraData.cpp + src/glTF/CameraData.h + src/glTF/ImageData.cpp + src/glTF/ImageData.h + src/glTF/MaterialData.cpp + src/glTF/MaterialData.h + src/glTF/MeshData.cpp + src/glTF/MeshData.h + src/glTF/NodeData.cpp + src/glTF/NodeData.h + src/glTF/PrimitiveData.cpp + src/glTF/PrimitiveData.h + src/glTF/SamplerData.h src/glTF/SceneData.cpp + src/glTF/SceneData.h + src/glTF/SkinData.cpp + src/glTF/SkinData.h + src/glTF/TextureData.cpp + src/glTF/TextureData.h + src/main.cpp + src/mathfu.h + src/utils/File_Utils.cpp + src/utils/File_Utils.h + src/utils/Image_Utils.cpp + src/utils/Image_Utils.h + src/utils/String_Utils.cpp + src/utils/String_Utils.h ) add_executable(FBX2glTF ${SOURCE_FILES}) diff --git a/README.md b/README.md index d317cb6..affb2d0 100644 --- a/README.md +++ b/README.md @@ -236,12 +236,12 @@ ratification process** Given the command line flag --pbr-metallic-roughness, we throw ourselves into the warm embrace of glTF 2.0's PBR preference. -As mentioned above, there is lilttle consensus in the world on how PBR should be +As mentioned above, there is little 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 +is a feature that comes bundled with Maya, and any PBR model exported through that route should be digested propertly by FBX2glTF. -(A happy note: Allegorithmic's Susbstance Painter also exports Stingray PBS, +(A happy note: Allegorithmic's Substance Painter also exports Stingray PBS, when hooked up to Maya.) ## Draco Compression diff --git a/src/FBX2glTF.h b/src/FBX2glTF.h index 27278eb..53b57ce 100644 --- a/src/FBX2glTF.h +++ b/src/FBX2glTF.h @@ -11,16 +11,21 @@ #define __FBX2GLTF_H__ #if defined ( _WIN32 ) -// This can be a macro under Windows, confusing FMT -#undef isnan // Tell Windows not to define min() and max() macros #define NOMINMAX #include #endif +const std::string FBX2GLTF_VERSION = "0.9.5"; + #include #include +#if defined ( _WIN32 ) +// this is defined in fbxmath.h +#undef isnan +#endif + #include "mathfu.h" #endif // !__FBX2GLTF_H__ diff --git a/src/Fbx2Raw.cpp b/src/Fbx2Raw.cpp index 685a0ce..5d61b39 100644 --- a/src/Fbx2Raw.cpp +++ b/src/Fbx2Raw.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "FBX2glTF.h" #include "utils/File_Utils.h" @@ -401,12 +402,14 @@ public: for (int deformerIndex = 0; deformerIndex < pMesh->GetDeformerCount(); deformerIndex++) { FbxSkin *skin = reinterpret_cast< FbxSkin * >( pMesh->GetDeformer(deformerIndex, FbxDeformer::eSkin)); if (skin != nullptr) { + const int clusterCount = skin->GetClusterCount(); + if (clusterCount == 0) { + continue; + } int controlPointCount = pMesh->GetControlPointsCount(); - vertexJointIndices.resize(controlPointCount, Vec4i(0, 0, 0, 0)); vertexJointWeights.resize(controlPointCount, Vec4f(0.0f, 0.0f, 0.0f, 0.0f)); - const int clusterCount = skin->GetClusterCount(); for (int clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) { FbxCluster *cluster = skin->GetCluster(clusterIndex); const int indexCount = cluster->GetControlPointIndicesCount(); @@ -1262,7 +1265,7 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene) for (int targetIx = 0; targetIx < targetCount; targetIx++) { if (curve) { float result = findInInterval(influence, targetIx-1); - if (!isnan(result)) { + if (!std::isnan(result)) { // we're transitioning into targetIx channel.weights.push_back(result); hasMorphs = true; @@ -1270,7 +1273,7 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene) } if (targetIx != targetCount-1) { result = findInInterval(influence, targetIx); - if (!isnan(result)) { + if (!std::isnan(result)) { // we're transitioning AWAY from targetIx channel.weights.push_back(1.0f - result); hasMorphs = true; diff --git a/src/Raw2Gltf.cpp b/src/Raw2Gltf.cpp index cd6c2ed..d2508af 100644 --- a/src/Raw2Gltf.cpp +++ b/src/Raw2Gltf.cpp @@ -827,7 +827,7 @@ ModelData *Raw2Gltf( && surfaceModel.GetVertexCount() > 65535); std::shared_ptr primitive; - if (options.useDraco) { + if (options.draco.enabled) { int triangleCount = surfaceModel.GetTriangleCount(); // initialize Draco mesh with vertex index information @@ -943,17 +943,29 @@ ModelData *Raw2Gltf( primitive->AddTarget(pAcc.get(), nAcc.get(), tAcc.get(), channel.name); } } - if (options.useDraco) { + if (options.draco.enabled) { // Set up the encoder. draco::Encoder encoder; - // TODO: generalize / allow configuration - encoder.SetSpeedOptions(5, 5); - encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14); - encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 10); - encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10); - encoder.SetAttributeQuantization(draco::GeometryAttribute::COLOR, 8); - encoder.SetAttributeQuantization(draco::GeometryAttribute::GENERIC, 8); + if (options.draco.compressionLevel != -1) { + int dracoSpeed = 10 - options.draco.compressionLevel; + encoder.SetSpeedOptions(dracoSpeed, dracoSpeed); + } + if (options.draco.quantBitsPosition != -1) { + encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, options.draco.quantBitsPosition); + } + if (options.draco.quantBitsTexCoord != -1) { + encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, options.draco.quantBitsTexCoord); + } + if (options.draco.quantBitsNormal != -1) { + encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, options.draco.quantBitsNormal); + } + if (options.draco.quantBitsColor != -1) { + encoder.SetAttributeQuantization(draco::GeometryAttribute::COLOR, options.draco.quantBitsColor); + } + if (options.draco.quantBitsGeneric != -1) { + encoder.SetAttributeQuantization(draco::GeometryAttribute::GENERIC, options.draco.quantBitsGeneric); + } draco::EncoderBuffer dracoBuffer; draco::Status status = encoder.EncodeMeshToBuffer(*primitive->dracoMesh, &dracoBuffer); @@ -1070,14 +1082,14 @@ ModelData *Raw2Gltf( if (options.useKHRMatUnlit) { extensionsUsed.push_back(KHR_MATERIALS_CMN_UNLIT); } - if (options.useDraco) { + if (options.draco.enabled) { extensionsUsed.push_back(KHR_DRACO_MESH_COMPRESSION); extensionsRequired.push_back(KHR_DRACO_MESH_COMPRESSION); } json glTFJson { { "asset", { - { "generator", "FBX2glTF" }, + { "generator", "FBX2glTF v" + FBX2GLTF_VERSION }, { "version", "2.0" }}}, { "scene", rootScene.ix } }; diff --git a/src/RawModel.cpp b/src/RawModel.cpp index 230b180..148776d 100644 --- a/src/RawModel.cpp +++ b/src/RawModel.cpp @@ -269,11 +269,20 @@ void RawModel::Condense() surfaces.clear(); + std::set survivingSurfaceIds; for (auto &triangle : triangles) { + int oldSurfaceIndex = triangle.surfaceIndex; const RawSurface &surface = oldSurfaces[triangle.surfaceIndex]; const int surfaceIndex = AddSurface(surface.name.c_str(), surface.id); surfaces[surfaceIndex] = surface; triangle.surfaceIndex = surfaceIndex; + survivingSurfaceIds.emplace(surface.id); + } + // clear out references to meshes that no longer exist + for (auto &node : nodes) { + if (node.surfaceId != 0 && survivingSurfaceIds.find(node.surfaceId) == survivingSurfaceIds.end()) { + node.surfaceId = 0; + } } } @@ -341,7 +350,9 @@ void RawModel::TransformGeometry(ComputeNormalsOption normals) if (verboseOutput) { if (normals == ComputeNormalsOption::BROKEN) { - fmt::printf("Repaired %lu empty normals.\n", computedNormalsCount); + if (computedNormalsCount > 0) { + fmt::printf("Repaired %lu empty normals.\n", computedNormalsCount); + } } else { fmt::printf("Computed %lu normals.\n", computedNormalsCount); } diff --git a/src/RawModel.h b/src/RawModel.h index 118930b..2519075 100644 --- a/src/RawModel.h +++ b/src/RawModel.h @@ -46,8 +46,18 @@ struct GltfOptions bool outputBinary { false }; /** If non-binary, whether to inline all resources, for a single (large) .glTF file. */ bool embedResources { false }; - /** Whether to use KHR_draco_mesh_compression to minimize static geometry size. */ - bool useDraco { false }; + + /** Whether and how to use KHR_draco_mesh_compression to minimize static geometry size. */ + struct { + bool enabled = false; + int compressionLevel = -1; + int quantBitsPosition = -1; + int quantBitsTexCoord = -1; + int quantBitsNormal = -1; + int quantBitsColor = -1; + int quantBitsGeneric = -1; + } draco; + /** Whether to use KHR_materials_unlit to extend materials definitions. */ bool useKHRMatUnlit { false }; /** Whether to populate the pbrMetallicRoughness substruct in materials. */ diff --git a/src/main.cpp b/src/main.cpp index 595b184..d608dd7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,7 +34,8 @@ int main(int argc, char *argv[]) { cxxopts::Options options( "FBX2glTF", - "FBX2glTF 2.0: Generate a glTF 2.0 representation of an FBX model."); + fmt::sprintf("FBX2glTF %s: Generate a glTF 2.0 representation of an FBX model.", FBX2GLTF_VERSION) + ); std::string inputPath; std::string outputPath; @@ -62,7 +63,25 @@ int main(int argc, char *argv[]) cxxopts::value>()) ( "d,draco", "Apply Draco mesh compression to geometries.", - cxxopts::value(gltfOptions.useDraco)) + cxxopts::value(gltfOptions.draco.enabled)) + ( + "draco-compression-level", "The compression level to tune Draco to, from 0 to 10. (default: 7)", + cxxopts::value(gltfOptions.draco.compressionLevel)) + ( + "draco-bits-for-positions", "How many bits to quantize position to. (default: 14)", + cxxopts::value(gltfOptions.draco.quantBitsPosition)) + ( + "draco-bits-for-uv", "How many bits to quantize UV coordinates to. (default: 10)", + cxxopts::value(gltfOptions.draco.quantBitsTexCoord)) + ( + "draco-bits-for-normals", "How many bits to quantize normals to. (default: 10)", + cxxopts::value(gltfOptions.draco.quantBitsNormal)) + ( + "draco-bits-for-colors", "How many bits to quantize color to. (default: 8)", + cxxopts::value(gltfOptions.draco.quantBitsColor)) + ( + "draco-bits-for-other", "How many bits to quantize other vertex attributes to to. (default: 8)", + cxxopts::value(gltfOptions.draco.quantBitsGeneric)) ( "compute-normals", "When to compute normals for vertices (never|broken|missing|always).", cxxopts::value>()) @@ -98,11 +117,7 @@ int main(int argc, char *argv[]) } if (options.count("version")) { - fmt::printf( - R"( -FBX2glTF version 2.0 -Copyright (c) 2016-2017 Oculus VR, LLC. -)"); + fmt::printf("FBX2glTF version %s\nCopyright (c) 2016-2017 Oculus VR, LLC.\n", FBX2GLTF_VERSION); return 0; } @@ -128,6 +143,12 @@ Copyright (c) 2016-2017 Oculus VR, LLC. gltfOptions.usePBRMetRough = true; } + if (gltfOptions.draco.compressionLevel != -1 && + (gltfOptions.draco.compressionLevel < 1 || gltfOptions.draco.compressionLevel > 10)) { + fmt::printf("Draco compression level must lie in [1, 10].\n"); + return 0; + } + if (options.count("flip-u") > 0) { texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(1.0f - uv[0], uv[1]); }); }