From 6f471e767825d95b984d7eab04d14bef88d283dd Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Sat, 20 Oct 2018 16:42:29 +0800 Subject: [PATCH] Smooth normal based on angle Currently, if the angle of two faces are less than 60 degrees, the normal is not averaged. This feature greatly boost the realistic look of the model. --- dust3d.pro | 3 +++ src/anglesmooth.cpp | 56 +++++++++++++++++++++++++++++++++++++++ src/anglesmooth.h | 11 ++++++++ src/dust3dutil.cpp | 16 +++++++++++ src/dust3dutil.h | 3 +++ src/fbxfile.cpp | 7 ++--- src/meshloader.cpp | 56 +++++++++++++-------------------------- src/meshresultcontext.cpp | 55 +++++++++++++++----------------------- src/meshresultcontext.h | 8 +++--- 9 files changed, 136 insertions(+), 79 deletions(-) create mode 100644 src/anglesmooth.cpp create mode 100644 src/anglesmooth.h diff --git a/dust3d.pro b/dust3d.pro index 076e8dbd..8315e669 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -254,6 +254,9 @@ HEADERS += src/material.h SOURCES += src/fbxfile.cpp HEADERS += src/fbxfile.h +SOURCES += src/anglesmooth.cpp +HEADERS += src/anglesmooth.h + SOURCES += src/main.cpp HEADERS += src/version.h diff --git a/src/anglesmooth.cpp b/src/anglesmooth.cpp new file mode 100644 index 00000000..a5559639 --- /dev/null +++ b/src/anglesmooth.cpp @@ -0,0 +1,56 @@ +#include +#include +#include "dust3dutil.h" +#include "anglesmooth.h" + +void angleSmooth(const std::vector &vertices, + const std::vector> &triangles, + const std::vector &triangleNormals, + float thresholdAngleDegrees, std::vector &triangleVertexNormals) +{ + std::vector>> triangleVertexNormalsMapByIndicies(vertices.size()); + std::vector angleAreaWeightedNormals; + for (size_t triangleIndex = 0; triangleIndex < triangles.size(); ++triangleIndex) { + const auto &sourceTriangle = triangles[triangleIndex]; + size_t indicies[] = {std::get<0>(sourceTriangle), + std::get<1>(sourceTriangle), + std::get<2>(sourceTriangle)}; + const auto &v1 = vertices[indicies[0]]; + const auto &v2 = vertices[indicies[1]]; + const auto &v3 = vertices[indicies[2]]; + float area = areaOfTriangle(v1, v2, v3); + float angles[] = {radianBetweenVectors(v2-v1, v3-v1), + radianBetweenVectors(v1-v2, v3-v2), + radianBetweenVectors(v1-v3, v2-v3)}; + for (int i = 0; i < 3; ++i) { + triangleVertexNormalsMapByIndicies[indicies[i]].push_back({triangleIndex, angleAreaWeightedNormals.size()}); + angleAreaWeightedNormals.push_back(triangleNormals[triangleIndex] * area * angles[i]); + } + } + triangleVertexNormals = angleAreaWeightedNormals; + std::map, float> degreesBetweenFacesMap; + for (size_t vertexIndex = 0; vertexIndex < vertices.size(); ++vertexIndex) { + const auto &triangleVertices = triangleVertexNormalsMapByIndicies[vertexIndex]; + for (const auto &triangleVertex: triangleVertices) { + for (const auto &otherTriangleVertex: triangleVertices) { + if (triangleVertex.first == otherTriangleVertex.first) + continue; + float degrees = 0; + auto findDegreesResult = degreesBetweenFacesMap.find({triangleVertex.first, otherTriangleVertex.first}); + if (findDegreesResult == degreesBetweenFacesMap.end()) { + degrees = angleBetweenVectors(triangleNormals[triangleVertex.first], triangleNormals[otherTriangleVertex.first]); + degreesBetweenFacesMap.insert({{triangleVertex.first, otherTriangleVertex.first}, degrees}); + degreesBetweenFacesMap.insert({{otherTriangleVertex.first, triangleVertex.first}, degrees}); + } else { + degrees = findDegreesResult->second; + } + if (degrees > thresholdAngleDegrees) { + continue; + } + triangleVertexNormals[triangleVertex.second] += angleAreaWeightedNormals[otherTriangleVertex.second]; + } + } + } + for (auto &item: triangleVertexNormals) + item.normalize(); +} diff --git a/src/anglesmooth.h b/src/anglesmooth.h new file mode 100644 index 00000000..6dceaaea --- /dev/null +++ b/src/anglesmooth.h @@ -0,0 +1,11 @@ +#ifndef ANGLE_SMOOTH_H +#define ANGLE_SMOOTH_H +#include +#include + +void angleSmooth(const std::vector &vertices, + const std::vector> &triangles, + const std::vector &triangleNormals, + float thresholdAngleDegrees, std::vector &triangleVertexNormals); + +#endif diff --git a/src/dust3dutil.cpp b/src/dust3dutil.cpp index f745b1dc..9806dbf8 100644 --- a/src/dust3dutil.cpp +++ b/src/dust3dutil.cpp @@ -74,3 +74,19 @@ QQuaternion quaternionOvershootSlerp(const QQuaternion &q0, const QQuaternion &q return QQuaternion::slerp(q0, q1, t); } +float radianBetweenVectors(const QVector3D &first, const QVector3D &second) +{ + return std::acos(QVector3D::dotProduct(first.normalized(), second.normalized())); +}; + +float angleBetweenVectors(const QVector3D &first, const QVector3D &second) +{ + return radianBetweenVectors(first, second) * 180.0 / M_PI; +} + +float areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c) +{ + auto ab = b - a; + auto ac = c - a; + return 0.5 * QVector3D::crossProduct(ab, ac).length(); +}; diff --git a/src/dust3dutil.h b/src/dust3dutil.h index e4f069de..6c2a1436 100644 --- a/src/dust3dutil.h +++ b/src/dust3dutil.h @@ -19,5 +19,8 @@ float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D plan QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal); QString unifiedWindowTitle(const QString &text); QQuaternion quaternionOvershootSlerp(const QQuaternion &q0, const QQuaternion &q1, float t); +float radianBetweenVectors(const QVector3D &first, const QVector3D &second); +float angleBetweenVectors(const QVector3D &first, const QVector3D &second); +float areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c); #endif diff --git a/src/fbxfile.cpp b/src/fbxfile.cpp index a0abbc87..233f4dad 100644 --- a/src/fbxfile.cpp +++ b/src/fbxfile.cpp @@ -1476,11 +1476,12 @@ FbxFileWriter::FbxFileWriter(MeshResultContext &resultContext, layerElementNormal.addProperty((int32_t)0); layerElementNormal.addPropertyNode("Version", (int32_t)101); layerElementNormal.addPropertyNode("Name", ""); - layerElementNormal.addPropertyNode("MappingInformationType", "ByVertice"); + layerElementNormal.addPropertyNode("MappingInformationType", "ByPolygonVertex"); layerElementNormal.addPropertyNode("ReferenceInformationType", "Direct"); std::vector normals; - for (decltype(resultContext.vertices.size()) i = 0; i < resultContext.vertices.size(); ++i) { - const auto &n = resultContext.interpolatedVertexNormals()[i]; + const auto &triangleVertexNormals = resultContext.interpolatedTriangleVertexNormals(); + for (decltype(triangleVertexNormals.size()) i = 0; i < triangleVertexNormals.size(); ++i) { + const auto &n = triangleVertexNormals[i]; normals.push_back((double)n.x()); normals.push_back((double)n.y()); normals.push_back((double)n.z()); diff --git a/src/meshloader.cpp b/src/meshloader.cpp index 163d7bc0..47dbca59 100644 --- a/src/meshloader.cpp +++ b/src/meshloader.cpp @@ -7,6 +7,7 @@ #include "theme.h" #include "positionmap.h" #include "ds3file.h" +#include "anglesmooth.h" #define MAX_VERTICES_PER_FACE 100 @@ -104,21 +105,15 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo GLfloat *triangleNormals = new GLfloat[triangleCount * 3]; int loadedTriangleNormalItemCount = meshlite_get_triangle_normal_array(meshlite, triangleMesh, triangleNormals, triangleCount * 3); - auto angleBetweenVectors = [](const QVector3D &first, const QVector3D &second) { - return std::acos(QVector3D::dotProduct(first.normalized(), second.normalized())); - }; - - auto areaOfTriangle = [](const QVector3D &a, const QVector3D &b, const QVector3D &c) { - auto ab = b - a; - auto ac = c - a; - return 0.5 * QVector3D::crossProduct(ab, ac).length(); - }; - float modelR = defaultColor.redF(); float modelG = defaultColor.greenF(); float modelB = defaultColor.blueF(); m_triangleVertexCount = triangleCount * 3; m_triangleVertices = new Vertex[m_triangleVertexCount * 3]; + const std::vector &inputVerticesForSmoothAngle = m_triangulatedVertices; + std::vector> inputTrianglesForSmoothAngle; + std::vector inputNormalsForSmoothAngle; + float thresholdAngleDegrees = 60; for (int i = 0; i < triangleCount; i++) { int firstIndex = i * 3; float useColorR = modelR; @@ -140,21 +135,14 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo float area = 1.0; float angles[3] = {1.0, 1.0, 1.0}; if (smoothNormal) { - for (int j = 0; j < 3; j++) { - assert(firstIndex + j < loadedTriangleVertexIndexItemCount); - int posIndex = triangleIndices[firstIndex + j] * 3; - assert(posIndex < loadedTriangleVertexPositionItemCount); - positions[j] = QVector3D(triangleVertexPositions[posIndex + 0], - triangleVertexPositions[posIndex + 1], - triangleVertexPositions[posIndex + 2]); - } - const auto &v1 = positions[0]; - const auto &v2 = positions[1]; - const auto &v3 = positions[2]; - area = areaOfTriangle(v1, v2, v3); - angles[0] = angleBetweenVectors(v2-v1, v3-v1); - angles[1] = angleBetweenVectors(v1-v2, v3-v2); - angles[2] = angleBetweenVectors(v1-v3, v2-v3); + inputTrianglesForSmoothAngle.push_back({ + triangleIndices[firstIndex + 0], + triangleIndices[firstIndex + 1], + triangleIndices[firstIndex + 2] + }); + inputNormalsForSmoothAngle.push_back(QVector3D(triangleNormals[firstIndex + 0], + triangleNormals[firstIndex + 1], + triangleNormals[firstIndex + 2])); } for (int j = 0; j < 3; j++) { assert(firstIndex + j < loadedTriangleVertexIndexItemCount); @@ -169,34 +157,26 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo v->normX = triangleNormals[firstIndex + 0]; v->normY = triangleNormals[firstIndex + 1]; v->normZ = triangleNormals[firstIndex + 2]; - if (smoothNormal) { - auto factor = area * angles[j]; - triangleVertexSmoothNormals[posIndex + 0] += v->normX * factor; - triangleVertexSmoothNormals[posIndex + 1] += v->normY * factor; - triangleVertexSmoothNormals[posIndex + 2] += v->normZ * factor; - } v->colorR = useColorR; v->colorG = useColorG; v->colorB = useColorB; v->metalness = useMetalness; v->roughness = useRoughness; } - - m_triangulatedFaces.push_back(triangulatedFace); } if (smoothNormal) { + std::vector resultNormals; + angleSmooth(inputVerticesForSmoothAngle, inputTrianglesForSmoothAngle, inputNormalsForSmoothAngle, + thresholdAngleDegrees, resultNormals); + size_t normalIndex = 0; for (int i = 0; i < triangleCount; i++) { int firstIndex = i * 3; for (int j = 0; j < 3; j++) { assert(firstIndex + j < loadedTriangleVertexIndexItemCount); - int posIndex = triangleIndices[firstIndex + j] * 3; Vertex *v = &m_triangleVertices[firstIndex + j]; - QVector3D normal(triangleVertexSmoothNormals[posIndex + 0], - triangleVertexSmoothNormals[posIndex + 1], - triangleVertexSmoothNormals[posIndex + 2]); - normal.normalize(); + const auto &normal = resultNormals[normalIndex++]; v->normX = normal.x(); v->normY = normal.y(); v->normZ = normal.z(); diff --git a/src/meshresultcontext.cpp b/src/meshresultcontext.cpp index 20dbfa9b..f4922270 100644 --- a/src/meshresultcontext.cpp +++ b/src/meshresultcontext.cpp @@ -9,6 +9,7 @@ #include "theme.h" #include "meshresultcontext.h" #include "positionmap.h" +#include "anglesmooth.h" struct HalfColorEdge { @@ -32,7 +33,7 @@ MeshResultContext::MeshResultContext() : m_bmeshNodeMapResolved(false), m_resultPartsResolved(false), m_resultTriangleUvsResolved(false), - m_vertexNormalsInterpolated(false), + m_triangleVertexNormalsInterpolated(false), m_triangleTangentsResolved(false) { } @@ -318,6 +319,7 @@ void MeshResultContext::calculateResultParts(std::map &parts) { std::map, int> oldVertexToNewMap; for (auto x = 0u; x < triangles.size(); x++) { + size_t normalIndex = x * 3; const auto &triangle = triangles[x]; const auto &sourceNode = triangleSourceNodes()[x]; auto it = parts.find(sourceNode.first); @@ -330,13 +332,14 @@ void MeshResultContext::calculateResultParts(std::map &parts) ResultTriangle newTriangle; newTriangle.normal = triangle.normal; for (auto i = 0u; i < 3; i++) { + const auto &normal = interpolatedTriangleVertexNormals()[normalIndex++]; auto key = std::make_pair(sourceNode.first, triangle.indicies[i]); const auto &it = oldVertexToNewMap.find(key); bool isNewVertex = it == oldVertexToNewMap.end(); bool isSeamVertex = m_seamVertices.end() != m_seamVertices.find(triangle.indicies[i]); if (isNewVertex || isSeamVertex) { int newIndex = resultPart.vertices.size(); - resultPart.interpolatedVertexNormals.push_back(interpolatedVertexNormals()[triangle.indicies[i]]); + resultPart.interpolatedVertexNormals.push_back(normal); resultPart.verticesOldIndicies.push_back(triangle.indicies[i]); resultPart.vertices.push_back(vertices[triangle.indicies[i]]); ResultVertexUv vertexUv; @@ -417,44 +420,28 @@ void MeshResultContext::calculateResultTriangleUvs(std::vector } } -void MeshResultContext::interpolateVertexNormals(std::vector &resultNormals) +void MeshResultContext::interpolateTriangleVertexNormals(std::vector &resultNormals) { - resultNormals.resize(vertices.size()); - - auto angleBetweenVectors = [](const QVector3D &first, const QVector3D &second) { - return std::acos(QVector3D::dotProduct(first.normalized(), second.normalized())); - }; - - auto areaOfTriangle = [](const QVector3D &a, const QVector3D &b, const QVector3D &c) { - auto ab = b - a; - auto ac = c - a; - return 0.5 * QVector3D::crossProduct(ab, ac).length(); - }; - - for (size_t triangleIndex = 0; triangleIndex < triangles.size(); triangleIndex++) { - const auto &sourceTriangle = triangles[triangleIndex]; - const auto &v1 = vertices[sourceTriangle.indicies[0]].position; - const auto &v2 = vertices[sourceTriangle.indicies[1]].position; - const auto &v3 = vertices[sourceTriangle.indicies[2]].position; - float area = areaOfTriangle(v1, v2, v3); - float angles[] = {angleBetweenVectors(v2-v1, v3-v1), - angleBetweenVectors(v1-v2, v3-v2), - angleBetweenVectors(v1-v3, v2-v3)}; - for (int i = 0; i < 3; i++) - resultNormals[sourceTriangle.indicies[i]] += sourceTriangle.normal * area * angles[i]; + std::vector inputVerticies; + std::vector> inputTriangles; + std::vector inputNormals; + float thresholdAngleDegrees = 60; + for (const auto &vertex: vertices) + inputVerticies.push_back(vertex.position); + for (const auto &triangle: triangles) { + inputTriangles.push_back({triangle.indicies[0], triangle.indicies[1], triangle.indicies[2]}); + inputNormals.push_back(triangle.normal); } - - for (auto &item: resultNormals) - item.normalize(); + angleSmooth(inputVerticies, inputTriangles, inputNormals, thresholdAngleDegrees, resultNormals); } -const std::vector &MeshResultContext::interpolatedVertexNormals() +const std::vector &MeshResultContext::interpolatedTriangleVertexNormals() { - if (!m_vertexNormalsInterpolated) { - m_vertexNormalsInterpolated = true; - interpolateVertexNormals(m_interpolatedVertexNormals); + if (!m_triangleVertexNormalsInterpolated) { + m_triangleVertexNormalsInterpolated = true; + interpolateTriangleVertexNormals(m_interpolatedTriangleVertexNormals); } - return m_interpolatedVertexNormals; + return m_interpolatedTriangleVertexNormals; } const std::vector &MeshResultContext::triangleTangents() diff --git a/src/meshresultcontext.h b/src/meshresultcontext.h index 0cb9a5be..60fd9804 100644 --- a/src/meshresultcontext.h +++ b/src/meshresultcontext.h @@ -80,7 +80,7 @@ public: const std::map &parts(); const std::vector &triangleUvs(); const std::map> &vertexSourceMap(); - const std::vector &interpolatedVertexNormals(); + const std::vector &interpolatedTriangleVertexNormals(); const std::vector &triangleTangents(); private: bool m_triangleSourceResolved; @@ -89,7 +89,7 @@ private: bool m_bmeshNodeMapResolved; bool m_resultPartsResolved; bool m_resultTriangleUvsResolved; - bool m_vertexNormalsInterpolated; + bool m_triangleVertexNormalsInterpolated; bool m_triangleTangentsResolved; private: std::vector> m_triangleSourceNodes; @@ -101,7 +101,7 @@ private: std::set m_seamVertices; std::map> m_vertexSourceMap; std::map m_rearrangedVerticesToOldIndexMap; - std::vector m_interpolatedVertexNormals; + std::vector m_interpolatedTriangleVertexNormals; std::vector m_triangleTangents; private: void calculateTriangleSourceNodes(std::vector> &triangleSourceNodes, std::map> &vertexSourceMap); @@ -111,7 +111,7 @@ private: void calculateBmeshNodeMap(std::map, BmeshNode *> &bmeshNodeMap); void calculateResultParts(std::map &parts); void calculateResultTriangleUvs(std::vector &uvs, std::set &seamVertices); - void interpolateVertexNormals(std::vector &resultNormals); + void interpolateTriangleVertexNormals(std::vector &resultNormals); void calculateTriangleTangents(std::vector &tangents); };