From 608c6f1797f3bcccff64209b15226154ca30003d Mon Sep 17 00:00:00 2001 From: Par Winzell Date: Mon, 19 Feb 2018 13:54:27 -0800 Subject: [PATCH 1/3] Add support for 32-bit indices. This was way overdue. Breaking up large meshes into many 65535-vertex primitives can save a few bytes, but it's really a lot of complication for minor benefit. With this change the user can force short or long indices, and the default is to use shorts for smaller meshes and longs for longer. --- src/Raw2Gltf.cpp | 40 ++++++++++++++++------------------------ src/Raw2Gltf.h | 4 +++- src/RawModel.cpp | 12 ++++++------ src/RawModel.h | 12 ++++++++++-- src/main.cpp | 25 +++++++++++++++++++++---- 5 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/Raw2Gltf.cpp b/src/Raw2Gltf.cpp index 8f12426..1f9a382 100644 --- a/src/Raw2Gltf.cpp +++ b/src/Raw2Gltf.cpp @@ -39,7 +39,7 @@ #include "glTF/SkinData.h" #include "glTF/TextureData.h" -typedef unsigned short TriangleIndex; +typedef uint32_t TriangleIndex; extern bool verboseOutput; @@ -290,7 +290,11 @@ ModelData *Raw2Gltf( } std::vector materialModels; - raw.CreateMaterialModels(materialModels, (1 << (sizeof(TriangleIndex) * 8)), options.keepAttribs, true); + raw.CreateMaterialModels( + materialModels, + options.useLongIndices == UseLongIndicesOptions::NEVER, + options.keepAttribs, + true); if (verboseOutput) { fmt::printf("%7d vertices\n", raw.GetVertexCount()); @@ -646,20 +650,6 @@ ModelData *Raw2Gltf( 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); - }; - std::shared_ptr pbrMetRough; if (options.usePBRMetRough) { // albedo is a basic texture, no merging needed @@ -744,17 +734,14 @@ ModelData *Raw2Gltf( materialsByName[materialHash(material)] = mData; } - for (size_t surfaceIndex = 0; surfaceIndex < materialModels.size(); surfaceIndex++) { - const RawModel &surfaceModel = materialModels[surfaceIndex]; - + for (const auto &surfaceModel : materialModels) { assert(surfaceModel.GetSurfaceCount() == 1); - const RawSurface &rawSurface = surfaceModel.GetSurface(0); - const int surfaceId = rawSurface.id; + const RawSurface &rawSurface = surfaceModel.GetSurface(0); + const long surfaceId = rawSurface.id; const RawMaterial &rawMaterial = surfaceModel.GetMaterial(surfaceModel.GetTriangle(0).materialIndex); const MaterialData &mData = require(materialsByName, materialHash(rawMaterial)); - MeshData *mesh = nullptr; auto meshIter = meshBySurfaceId.find(surfaceId); if (meshIter != meshBySurfaceId.end()) { @@ -770,6 +757,11 @@ ModelData *Raw2Gltf( mesh = meshPtr.get(); } + bool useLongIndices = + (options.useLongIndices == UseLongIndicesOptions::ALWAYS) + || (options.useLongIndices == UseLongIndicesOptions::AUTO + && surfaceModel.GetVertexCount() > 65535); + std::shared_ptr primitive; if (options.useDraco) { int triangleCount = surfaceModel.GetTriangleCount(); @@ -786,13 +778,13 @@ ModelData *Raw2Gltf( dracoMesh->SetFace(draco::FaceIndex(ii), face); } - AccessorData &indexes = *gltf->accessors.hold(new AccessorData(GLT_USHORT)); + AccessorData &indexes = *gltf->accessors.hold(new AccessorData(useLongIndices ? GLT_UINT : GLT_USHORT)); indexes.count = 3 * triangleCount; primitive.reset(new PrimitiveData(indexes, mData, dracoMesh)); } else { const AccessorData &indexes = *gltf->AddAccessorWithView( *gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ELEMENT_ARRAY_BUFFER), - GLT_USHORT, getIndexArray(surfaceModel)); + useLongIndices ? GLT_UINT : GLT_USHORT, getIndexArray(surfaceModel)); primitive.reset(new PrimitiveData(indexes, mData)); }; diff --git a/src/Raw2Gltf.h b/src/Raw2Gltf.h index 9fdd75d..9127061 100644 --- a/src/Raw2Gltf.h +++ b/src/Raw2Gltf.h @@ -51,6 +51,7 @@ struct ComponentType { }; const ComponentType CT_USHORT = {ComponentType::GL_UNSIGNED_SHORT, 2}; +const ComponentType CT_UINT = {ComponentType::GL_UNSIGNED_INT, 4}; const ComponentType CT_FLOAT = {ComponentType::GL_FLOAT, 4}; // Map our low-level data types for glTF output @@ -64,7 +65,7 @@ struct GLType { unsigned int byteStride() const { return componentType.size * count; } void write(uint8_t *buf, const float scalar) const { *((float *) buf) = scalar; } - void write(uint8_t *buf, const uint16_t scalar) const { *((uint16_t *) buf) = scalar; } + void write(uint8_t *buf, const uint32_t scalar) const { *((uint32_t *) buf) = scalar; } template void write(uint8_t *buf, const mathfu::Vector &vector) const { @@ -101,6 +102,7 @@ struct GLType { const GLType GLT_FLOAT = {CT_FLOAT, 1, "SCALAR"}; const GLType GLT_USHORT = {CT_USHORT, 1, "SCALAR"}; +const GLType GLT_UINT = {CT_UINT, 1, "SCALAR"}; const GLType GLT_VEC2F = {CT_FLOAT, 2, "VEC2"}; const GLType GLT_VEC3F = {CT_FLOAT, 3, "VEC3"}; const GLType GLT_VEC4F = {CT_FLOAT, 4, "VEC4"}; diff --git a/src/RawModel.cpp b/src/RawModel.cpp index 9443608..e8b708a 100644 --- a/src/RawModel.cpp +++ b/src/RawModel.cpp @@ -327,15 +327,15 @@ void RawModel::Condense() void RawModel::TransformGeometry(ComputeNormalsOption normals) { switch(normals) { - case NEVER: + case ComputeNormalsOption::NEVER: break; - case MISSING: + case ComputeNormalsOption::MISSING: if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) { break; } // otherwise fall through - case BROKEN: - case ALWAYS: + case ComputeNormalsOption::BROKEN: + case ComputeNormalsOption::ALWAYS: size_t computedNormalsCount = this->CalculateNormals(normals == ComputeNormalsOption::BROKEN); vertexAttributes |= RAW_VERTEX_ATTRIBUTE_NORMAL; @@ -395,7 +395,7 @@ struct TriangleModelSortNeg }; void RawModel::CreateMaterialModels( - std::vector &materialModels, const int maxModelVertices, const int keepAttribs, const bool forceDiscrete) const + std::vector &materialModels, bool shortIndices, const int keepAttribs, const bool forceDiscrete) const { // Sort all triangles based on material first, then surface, then first vertex index. std::vector sortedTriangles; @@ -467,7 +467,7 @@ void RawModel::CreateMaterialModels( } if (i == 0 || - model->GetVertexCount() > maxModelVertices - 3 || + (shortIndices && model->GetVertexCount() >= 0xFFFE) || sortedTriangles[i].materialIndex != sortedTriangles[i - 1].materialIndex || (sortedTriangles[i].surfaceIndex != sortedTriangles[i - 1].surfaceIndex && (forceDiscrete || surfaces[sortedTriangles[i].surfaceIndex].discrete || diff --git a/src/RawModel.h b/src/RawModel.h index d0361ad..ce3ab67 100644 --- a/src/RawModel.h +++ b/src/RawModel.h @@ -17,13 +17,19 @@ /** * The variuos situations in which the user may wish for us to (re-)compute normals for our vertices. */ -enum ComputeNormalsOption { +enum class ComputeNormalsOption { NEVER, // do not ever compute any normals (results in broken glTF for some sources) BROKEN, // replace zero-length normals in any mesh that has a normal layer MISSING, // if a mesh lacks normals, compute them all ALWAYS // compute a new normal for every vertex, obliterating whatever may have been there before }; +enum class UseLongIndicesOptions { + NEVER, // only ever use 16-bit indices + AUTO, // use shorts or longs depending on vertex count + ALWAYS, // only ever use 32-bit indices +}; + /** * User-supplied options that dictate the nature of the glTF being generated. */ @@ -52,6 +58,8 @@ struct GltfOptions bool useBlendShapeTangents { false }; /** When to compute vertex normals from geometry. */ ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN; + /** When to use 32-bit indices. */ + UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO; }; enum RawVertexAttribute @@ -470,7 +478,7 @@ public: // Multiple surfaces with the same material will turn into a single model. // However, surfaces that are marked as 'discrete' will turn into separate models. void CreateMaterialModels( - std::vector &materialModels, const int maxModelVertices, const int keepAttribs, const bool forceDiscrete) const; + std::vector &materialModels, bool shortIndices, const int keepAttribs, const bool forceDiscrete) const; private: Vec3f getFaceNormal(int verts[3]) const; diff --git a/src/main.cpp b/src/main.cpp index 0f34e92..1f9ee6d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -75,6 +75,9 @@ int main(int argc, char *argv[]) ( "blend-shape-tangents", "Include blend shape tangents, if reported present by the FBX SDK.", cxxopts::value(gltfOptions.useBlendShapeTangents)) + ( + "long-indices", "Whether to use 32-bit indices (never|auto|always).", + cxxopts::value>()) ( "compute-normals", "When to compute normals for vertices (never|broken|missing|always).", cxxopts::value>()) @@ -137,16 +140,30 @@ Copyright (c) 2016-2017 Oculus VR, LLC. fmt::printf("Suppressing --flip-v transformation of texture coordinates.\n"); } + for (const std::string &choice : options["long-indices"].as>()) { + if (choice == "never") { + gltfOptions.useLongIndices = UseLongIndicesOptions::NEVER; + } else if (choice == "auto") { + gltfOptions.useLongIndices = UseLongIndicesOptions::AUTO; + } else if (choice == "always") { + gltfOptions.useLongIndices = UseLongIndicesOptions::ALWAYS; + } else { + fmt::printf("Unknown --long-indices: %s\n", choice); + fmt::printf(options.help()); + return 1; + } + } + if (options.count("compute-normals") > 0) { for (const std::string &choice : options["compute-normals"].as>()) { if (choice == "never") { - gltfOptions.computeNormals = NEVER; + gltfOptions.computeNormals = ComputeNormalsOption::NEVER; } else if (choice == "broken") { - gltfOptions.computeNormals = BROKEN; + gltfOptions.computeNormals = ComputeNormalsOption::BROKEN; } else if (choice == "missing") { - gltfOptions.computeNormals = MISSING; + gltfOptions.computeNormals = ComputeNormalsOption::MISSING; } else if (choice == "always") { - gltfOptions.computeNormals = ALWAYS; + gltfOptions.computeNormals = ComputeNormalsOption::ALWAYS; } else { fmt::printf("Unknown --compute-normals: %s\n", choice); fmt::printf(options.help()); From da5d606c93c0e6de6cab1286cea9c4671c03b1f0 Mon Sep 17 00:00:00 2001 From: Par Winzell Date: Mon, 19 Feb 2018 20:15:01 -0800 Subject: [PATCH 2/3] Documentation tweaks. --- README.md | 56 +++++++++++++++++++++------------------------------- src/main.cpp | 12 +++++------ 2 files changed, 28 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index baedf00..9975b32 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The tool can be invoked like so: Or perhaps, as part of a more complex pipeline: ``` - > FBX2glTF --binary --draco --khr-materials-common \ + > FBX2glTF --binary --draco --verbose \ --input ~/models/source/butterfly.fbx \ --output ~/models/target/butterfly.glb ``` @@ -41,18 +41,18 @@ Usage: behaviour!) --no-flip-v Suppress the default flipping of V texture coordinates - --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, or make a best effort - to convert from traditional shader models. - --pbr-specular-glossiness - (WIP) Very experimentally employ the - KHR_materials_pbrSpecularGlossiness extension. + --pbr-metallic-roughness Try to glean glTF 2.0 native PBR attributes + from the FBX. + --khr-materials-unlit Use KHR_materials_unlit extension to specify + Unlit shader. --blend-shape-normals Include blend shape normals, if reported present by the FBX SDK. --blend-shape-tangents Include blend shape tangents, if reported present by the FBX SDK. + --long-indices arg Whether to use 32-bit indices + (never|auto|always). + --compute-normals arg When to compute normals for vertices + (never|broken|missing|always). -k, --keep-attribute arg Used repeatedly to build a limiting set of vertex attributes to keep. -v, --verbose Enable verbose output. @@ -73,6 +73,12 @@ Some of these switches are not obvious: Your FBX is likely constructed with the assumption that `(0, 0)` is bottom left, whereas glTF has `(0, 0)` as top left. To produce spec-compliant glTF, we must flip the texcoords. To request unflipped coordinates: +- `--long-indices` lets you force the use of either 16-bit or 32-bit indices. + The default option is auto, which make the choice on a per-mesh-size basis. +- `--compute-mormals` controls when automatic vertex normals should be computed + from the mesh. By default, empty normals (which are forbidden by glTF) are + replaced. A choice of 'missing' implies 'broken', but additionally creates + normals for models that lack them completely. - `--no-flip-v` will actively disable v coordinat flipping. This can be useful if your textures are pre-flipped, or if for some other reason you were already in a glTF-centric texture coordinate system. @@ -216,19 +222,15 @@ and additionally, with Blinn/Phong: (All these can be either constants or textures.) -#### Exporting as Unlit/Lambert/Phong -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. +#### Exporting as Unlit +If you have a model was constructed using an unlit workflow, e.g. a photogrammetry +capture or a landscape with careful baked-in lighting, you may choose to export +it using the --khr-materials-common switch. This incurs a dependency on the glTF +extension 'KHR_materials_unlit; a client that accepts that extension is making +a promise it'll do its best to render pixel values without lighting calculations. **Note that at the time of writing, this glTF extension is still undergoing the -ratification process, and is furthermore likely to change names.** +ratification process** #### Exporting as Metallic-Roughness PBR Given the command line flag --pbr-metallic-roughness, we throw ourselves into @@ -242,20 +244,6 @@ that route should be digested propertly by FBX2glTF. (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) compression to the geometric data of each mesh (vertex indices, positions, diff --git a/src/main.cpp b/src/main.cpp index 1f9ee6d..ae86257 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,9 +57,15 @@ int main(int argc, char *argv[]) ( "b,binary", "Output a single binary format .glb file.", cxxopts::value(gltfOptions.outputBinary)) + ( + "long-indices", "Whether to use 32-bit indices (never|auto|always).", + cxxopts::value>()) ( "d,draco", "Apply Draco mesh compression to geometries.", cxxopts::value(gltfOptions.useDraco)) + ( + "compute-normals", "When to compute normals for vertices (never|broken|missing|always).", + cxxopts::value>()) ("flip-u", "Flip all U texture coordinates.") ("flip-v", "Flip all V texture coordinates (default behaviour!)") ("no-flip-v", "Suppress the default flipping of V texture coordinates") @@ -75,12 +81,6 @@ int main(int argc, char *argv[]) ( "blend-shape-tangents", "Include blend shape tangents, if reported present by the FBX SDK.", cxxopts::value(gltfOptions.useBlendShapeTangents)) - ( - "long-indices", "Whether to use 32-bit indices (never|auto|always).", - cxxopts::value>()) - ( - "compute-normals", "When to compute normals for vertices (never|broken|missing|always).", - cxxopts::value>()) ( "k,keep-attribute", "Used repeatedly to build a limiting set of vertex attributes to keep.", cxxopts::value>()) From f1982e6ca3fa00d62de5118f2d810619b9c7bdee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Winzell?= Date: Mon, 19 Feb 2018 20:59:25 -0800 Subject: [PATCH 3/3] Bump NPM version. --- npm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/package.json b/npm/package.json index f027a14..664d7ca 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "fbx2gltf", - "version": "0.9.3", + "version": "0.9.4", "description": "Node wrapper around FBX2glTF tools.", "main": "index.js", "repository": {