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:
parent
b95c50a72f
commit
67bb9372d7
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
38
src/main.cpp
38
src/main.cpp
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue