diff --git a/src/Raw2Gltf.h b/src/Raw2Gltf.h index 38890cf..15b1fa4 100644 --- a/src/Raw2Gltf.h +++ b/src/Raw2Gltf.h @@ -35,38 +35,6 @@ static const std::string KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS = "KHR_materials_ static const std::string extBufferFilename = "buffer.bin"; -/** - * User-supplied options that dictate the nature of the glTF being generated. - */ -struct GltfOptions -{ - /** - * If negative, disabled. Otherwise, a bitfield of RawVertexAttributes that - * specify the largest set of attributes that'll ever be kept for a vertex. - * The special bit RAW_VERTEX_ATTRIBUTE_AUTO triggers smart mode, where the - * attributes to keep are inferred from which textures are supplied. - */ - int keepAttribs; - /** Whether to output a .glb file, the binary format of glTF. */ - bool outputBinary; - /** If non-binary, whether to inline all resources, for a single (large) .glTF file. */ - bool embedResources; - /** Whether to use KHR_draco_mesh_compression to minimize static geometry size. */ - bool useDraco; - /** Whether to use KHR_materials_common to extend materials definitions. */ - bool useKHRMatCom; - /** Whether to use KHR_materials_unlit to extend materials definitions. */ - bool useKHRMatUnlit; - /** Whether to populate the pbrMetallicRoughness substruct in materials. */ - bool usePBRMetRough; - /** Whether to use KHR_materials_pbrSpecularGlossiness to extend material definitions. */ - bool usePBRSpecGloss; - /** Whether to include blend shape normals, if present according to the SDK. */ - bool useBlendShapeNormals; - /** Whether to include blend shape tangents, if present according to the SDK. */ - bool useBlendShapeTangents; -}; - struct ComponentType { // OpenGL Datatype enums enum GL_DataType diff --git a/src/RawModel.cpp b/src/RawModel.cpp index dcd7856..6f2bea5 100644 --- a/src/RawModel.cpp +++ b/src/RawModel.cpp @@ -261,16 +261,6 @@ int RawModel::AddNode(const long id, const char *name, const long parentId) return (int) nodes.size() - 1; } -void RawModel::Repair() -{ - const auto &brokenNormalVerts = this->CalculateBrokenNormals(); - if (verboseOutput) { - fmt::printf("Repaired %lu empty normals.\n", brokenNormalVerts.size()); - } - -} - - void RawModel::Condense() { // Only keep surfaces that are referenced by one or more triangles. @@ -334,6 +324,30 @@ void RawModel::Condense() } } +void RawModel::TransformGeometry(ComputeNormalsOption normals) +{ + switch(normals) { + case NEVER: + break; + case MISSING: + if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) { + break; + } + // otherwise fall through + case BROKEN: + case ALWAYS: + size_t computedNormalsCount = this->CalculateNormals(normals == ComputeNormalsOption::BROKEN); + if (verboseOutput) { + if (normals == ComputeNormalsOption::BROKEN) { + fmt::printf("Repaired %lu empty normals.\n", computedNormalsCount); + } else { + fmt::printf("Computed %lu normals.\n", computedNormalsCount); + } + } + break; + } +} + void RawModel::TransformTextures(const std::vector> &transforms) { for (auto &vertice : vertices) { @@ -567,14 +581,18 @@ Vec3f RawModel::getFaceNormal(int verts[3]) const return result; } -std::set RawModel::CalculateNormals() +size_t RawModel::CalculateNormals(bool onlyBroken) { Vec3f averagePos = Vec3f { 0.0f }; std::set brokenVerts; for (int vertIx = 0; vertIx < vertices.size(); vertIx ++) { - averagePos += (vertices[vertIx].position / vertices.size()); - if (vertices[vertIx].normal.LengthSquared() < FLT_MIN) { - vertices[vertIx].normal = Vec3f { 0.0f }; + RawVertex &vertex = vertices[vertIx]; + averagePos += (vertex.position / vertices.size()); + if (onlyBroken && (vertex.normal.LengthSquared() >= FLT_MIN)) { + continue; + } + vertex.normal = Vec3f { 0.0f }; + if (onlyBroken) { brokenVerts.emplace(vertIx); } } @@ -589,13 +607,16 @@ std::set RawModel::CalculateNormals() } Vec3f faceNormal = this->getFaceNormal(triangle.verts); for (int vertIx : triangle.verts) { - if (brokenVerts.count(vertIx) > 0) { + if (!onlyBroken || brokenVerts.count(vertIx) > 0) { vertices[vertIx].normal += faceNormal; } } } - for (int vertIx : brokenVerts) { + for (int vertIx = 0; vertIx < vertices.size(); vertIx ++) { + if (onlyBroken && brokenVerts.count(vertIx) == 0) { + continue; + } RawVertex &vertex = vertices[vertIx]; if (vertex.normal.LengthSquared() < FLT_MIN) { vertex.normal = vertex.position - averagePos; @@ -606,5 +627,5 @@ std::set RawModel::CalculateNormals() } vertex.normal.Normalize(); } - return brokenVerts; + return onlyBroken ? brokenVerts.size() : vertices.size(); } diff --git a/src/RawModel.h b/src/RawModel.h index 72411dd..071f51c 100644 --- a/src/RawModel.h +++ b/src/RawModel.h @@ -14,6 +14,50 @@ #include #include +/** + * The variuos situations in which the user may wish for us to (re-)compute normals for our vertices. + */ +enum 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 +}; + +/** + * User-supplied options that dictate the nature of the glTF being generated. + */ +struct GltfOptions +{ + /** + * If negative, disabled. Otherwise, a bitfield of RawVertexAttributes that + * specify the largest set of attributes that'll ever be kept for a vertex. + * The special bit RAW_VERTEX_ATTRIBUTE_AUTO triggers smart mode, where the + * attributes to keep are inferred from which textures are supplied. + */ + int keepAttribs { -1 }; + /** Whether to output a .glb file, the binary format of glTF. */ + 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 to use KHR_materials_common to extend materials definitions. */ + bool useKHRMatCom { false }; + /** Whether to use KHR_materials_unlit to extend materials definitions. */ + bool useKHRMatUnlit { false }; + /** Whether to populate the pbrMetallicRoughness substruct in materials. */ + bool usePBRMetRough { false }; + /** Whether to use KHR_materials_pbrSpecularGlossiness to extend material definitions. */ + bool usePBRSpecGloss { false }; + /** Whether to include blend shape normals, if present according to the SDK. */ + bool useBlendShapeNormals { false }; + /** Whether to include blend shape tangents, if present according to the SDK. */ + bool useBlendShapeTangents { false }; + /** When to compute vertex normals from geometry. */ + ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN; +}; + enum RawVertexAttribute { RAW_VERTEX_ATTRIBUTE_POSITION = 1 << 0, @@ -376,11 +420,11 @@ public: // Remove unused vertices, textures or materials after removing vertex attributes, textures, materials or surfaces. void Condense(); - void Repair(); + void TransformGeometry(ComputeNormalsOption); void TransformTextures(const std::vector> &transforms); - std::set CalculateBrokenNormals(); + size_t CalculateNormals(bool); // Get the attributes stored per vertex. int GetVertexAttributes() const { return vertexAttributes; } diff --git a/src/main.cpp b/src/main.cpp index 3858fde..37a2050 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,18 +42,7 @@ int main(int argc, char *argv[]) std::vector> texturesTransforms; - GltfOptions gltfOptions{ - -1, // keepAttribs - false, // outputBinary - false, // embedResources - false, // useDraco - false, // useKHRMatCom - false, // useKHRMatUnlit - false, // usePBRMetRough - false, // usePBRSpecGloss - false, // useBlendShapeNormals - false, // useBlendShapeTangents - }; + GltfOptions gltfOptions; options.positional_help("[]"); options.add_options() @@ -93,6 +82,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)) + ( + "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>()) @@ -152,9 +144,27 @@ Copyright (c) 2016-2017 Oculus VR, LLC. fmt::printf("Suppressing --flip-v transformation of texture coordinates.\n"); } + if (options.count("compute-normals") > 0) { + for (const std::string &choice : options["compute-normals"].as>()) { + if (choice == "never") { + gltfOptions.computeNormals = NEVER; + } else if (choice == "broken") { + gltfOptions.computeNormals = BROKEN; + } else if (choice == "missing") { + gltfOptions.computeNormals = MISSING; + } else if (choice == "always") { + gltfOptions.computeNormals = ALWAYS; + } else { + fmt::printf("Unknown --compute-normals: %s\n", choice); + fmt::printf(options.help()); + return 1; + } + } + } + if (options.count("keep-attribute") > 0) { gltfOptions.keepAttribs = RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; - for (const auto &attribute : options["keep-attribute"].as>()) { + for (std::string attribute : options["keep-attribute"].as>()) { if (attribute == "position") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION; } else if (attribute == "normal") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_NORMAL; } else if (attribute == "tangent") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_TANGENT; } @@ -210,7 +220,7 @@ Copyright (c) 2016-2017 Oculus VR, LLC. raw.TransformTextures(texturesTransforms); } raw.Condense(); - raw.Repair(); + raw.TransformGeometry(gltfOptions.computeNormals); std::ofstream outStream; // note: auto-flushes in destructor const auto streamStart = outStream.tellp();