Optionally compute normals from geometry.

The user can now ask for normals to be computed NEVER (can easily cause
broken glTF if the source isn't perfect), MISSING (when the mesh simply
lacks normals), BROKEN (only emptuy normals are replaced), or
ALWAYS (perhaps if the normals in the source are junk).
This commit is contained in:
Par Winzell 2018-02-18 16:12:33 -08:00
parent b95c50a72f
commit 67bb9372d7
4 changed files with 108 additions and 65 deletions

View File

@ -35,38 +35,6 @@ static const std::string KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS = "KHR_materials_
static const std::string extBufferFilename = "buffer.bin"; 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 { struct ComponentType {
// OpenGL Datatype enums // OpenGL Datatype enums
enum GL_DataType enum GL_DataType

View File

@ -261,16 +261,6 @@ int RawModel::AddNode(const long id, const char *name, const long parentId)
return (int) nodes.size() - 1; 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() void RawModel::Condense()
{ {
// Only keep surfaces that are referenced by one or more triangles. // 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<std::function<Vec2f(Vec2f)>> &transforms) void RawModel::TransformTextures(const std::vector<std::function<Vec2f(Vec2f)>> &transforms)
{ {
for (auto &vertice : vertices) { for (auto &vertice : vertices) {
@ -567,14 +581,18 @@ Vec3f RawModel::getFaceNormal(int verts[3]) const
return result; return result;
} }
std::set<int> RawModel::CalculateNormals() size_t RawModel::CalculateNormals(bool onlyBroken)
{ {
Vec3f averagePos = Vec3f { 0.0f }; Vec3f averagePos = Vec3f { 0.0f };
std::set<int> brokenVerts; std::set<int> brokenVerts;
for (int vertIx = 0; vertIx < vertices.size(); vertIx ++) { for (int vertIx = 0; vertIx < vertices.size(); vertIx ++) {
averagePos += (vertices[vertIx].position / vertices.size()); RawVertex &vertex = vertices[vertIx];
if (vertices[vertIx].normal.LengthSquared() < FLT_MIN) { averagePos += (vertex.position / vertices.size());
vertices[vertIx].normal = Vec3f { 0.0f }; if (onlyBroken && (vertex.normal.LengthSquared() >= FLT_MIN)) {
continue;
}
vertex.normal = Vec3f { 0.0f };
if (onlyBroken) {
brokenVerts.emplace(vertIx); brokenVerts.emplace(vertIx);
} }
} }
@ -589,13 +607,16 @@ std::set<int> RawModel::CalculateNormals()
} }
Vec3f faceNormal = this->getFaceNormal(triangle.verts); Vec3f faceNormal = this->getFaceNormal(triangle.verts);
for (int vertIx : triangle.verts) { for (int vertIx : triangle.verts) {
if (brokenVerts.count(vertIx) > 0) { if (!onlyBroken || brokenVerts.count(vertIx) > 0) {
vertices[vertIx].normal += faceNormal; 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]; RawVertex &vertex = vertices[vertIx];
if (vertex.normal.LengthSquared() < FLT_MIN) { if (vertex.normal.LengthSquared() < FLT_MIN) {
vertex.normal = vertex.position - averagePos; vertex.normal = vertex.position - averagePos;
@ -606,5 +627,5 @@ std::set<int> RawModel::CalculateNormals()
} }
vertex.normal.Normalize(); vertex.normal.Normalize();
} }
return brokenVerts; return onlyBroken ? brokenVerts.size() : vertices.size();
} }

View File

@ -14,6 +14,50 @@
#include <functional> #include <functional>
#include <set> #include <set>
/**
* 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 enum RawVertexAttribute
{ {
RAW_VERTEX_ATTRIBUTE_POSITION = 1 << 0, 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. // Remove unused vertices, textures or materials after removing vertex attributes, textures, materials or surfaces.
void Condense(); void Condense();
void Repair(); void TransformGeometry(ComputeNormalsOption);
void TransformTextures(const std::vector<std::function<Vec2f(Vec2f)>> &transforms); void TransformTextures(const std::vector<std::function<Vec2f(Vec2f)>> &transforms);
std::set<int> CalculateBrokenNormals(); size_t CalculateNormals(bool);
// Get the attributes stored per vertex. // Get the attributes stored per vertex.
int GetVertexAttributes() const { return vertexAttributes; } int GetVertexAttributes() const { return vertexAttributes; }

View File

@ -42,18 +42,7 @@ int main(int argc, char *argv[])
std::vector<std::function<Vec2f(Vec2f)>> texturesTransforms; std::vector<std::function<Vec2f(Vec2f)>> texturesTransforms;
GltfOptions gltfOptions{ GltfOptions gltfOptions;
-1, // keepAttribs
false, // outputBinary
false, // embedResources
false, // useDraco
false, // useKHRMatCom
false, // useKHRMatUnlit
false, // usePBRMetRough
false, // usePBRSpecGloss
false, // useBlendShapeNormals
false, // useBlendShapeTangents
};
options.positional_help("[<FBX File>]"); options.positional_help("[<FBX File>]");
options.add_options() 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.", "blend-shape-tangents", "Include blend shape tangents, if reported present by the FBX SDK.",
cxxopts::value<bool>(gltfOptions.useBlendShapeTangents)) cxxopts::value<bool>(gltfOptions.useBlendShapeTangents))
(
"compute-normals", "When to compute normals for vertices (never|broken|missing|always).",
cxxopts::value<std::vector<std::string>>())
( (
"k,keep-attribute", "Used repeatedly to build a limiting set of vertex attributes to keep.", "k,keep-attribute", "Used repeatedly to build a limiting set of vertex attributes to keep.",
cxxopts::value<std::vector<std::string>>()) cxxopts::value<std::vector<std::string>>())
@ -152,9 +144,27 @@ Copyright (c) 2016-2017 Oculus VR, LLC.
fmt::printf("Suppressing --flip-v transformation of texture coordinates.\n"); 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<std::vector<std::string>>()) {
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) { if (options.count("keep-attribute") > 0) {
gltfOptions.keepAttribs = RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; gltfOptions.keepAttribs = RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
for (const auto &attribute : options["keep-attribute"].as<std::vector<std::string>>()) { for (std::string attribute : options["keep-attribute"].as<std::vector<std::string>>()) {
if (attribute == "position") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION; } if (attribute == "position") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION; }
else if (attribute == "normal") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_NORMAL; } else if (attribute == "normal") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_NORMAL; }
else if (attribute == "tangent") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_TANGENT; } 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.TransformTextures(texturesTransforms);
} }
raw.Condense(); raw.Condense();
raw.Repair(); raw.TransformGeometry(gltfOptions.computeNormals);
std::ofstream outStream; // note: auto-flushes in destructor std::ofstream outStream; // note: auto-flushes in destructor
const auto streamStart = outStream.tellp(); const auto streamStart = outStream.tellp();