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.
master
Jeremy Hu 2018-10-20 16:42:29 +08:00
parent 096c107775
commit 6f471e7678
9 changed files with 136 additions and 79 deletions

View File

@ -254,6 +254,9 @@ HEADERS += src/material.h
SOURCES += src/fbxfile.cpp SOURCES += src/fbxfile.cpp
HEADERS += src/fbxfile.h HEADERS += src/fbxfile.h
SOURCES += src/anglesmooth.cpp
HEADERS += src/anglesmooth.h
SOURCES += src/main.cpp SOURCES += src/main.cpp
HEADERS += src/version.h HEADERS += src/version.h

56
src/anglesmooth.cpp Normal file
View File

@ -0,0 +1,56 @@
#include <cmath>
#include <map>
#include "dust3dutil.h"
#include "anglesmooth.h"
void angleSmooth(const std::vector<QVector3D> &vertices,
const std::vector<std::tuple<size_t, size_t, size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
float thresholdAngleDegrees, std::vector<QVector3D> &triangleVertexNormals)
{
std::vector<std::vector<std::pair<size_t, size_t>>> triangleVertexNormalsMapByIndicies(vertices.size());
std::vector<QVector3D> 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<std::pair<size_t, size_t>, 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();
}

11
src/anglesmooth.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef ANGLE_SMOOTH_H
#define ANGLE_SMOOTH_H
#include <QVector3D>
#include <vector>
void angleSmooth(const std::vector<QVector3D> &vertices,
const std::vector<std::tuple<size_t, size_t, size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
float thresholdAngleDegrees, std::vector<QVector3D> &triangleVertexNormals);
#endif

View File

@ -74,3 +74,19 @@ QQuaternion quaternionOvershootSlerp(const QQuaternion &q0, const QQuaternion &q
return QQuaternion::slerp(q0, q1, t); 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();
};

View File

@ -19,5 +19,8 @@ float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D plan
QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal); QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal);
QString unifiedWindowTitle(const QString &text); QString unifiedWindowTitle(const QString &text);
QQuaternion quaternionOvershootSlerp(const QQuaternion &q0, const QQuaternion &q1, float t); 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 #endif

View File

@ -1476,11 +1476,12 @@ FbxFileWriter::FbxFileWriter(MeshResultContext &resultContext,
layerElementNormal.addProperty((int32_t)0); layerElementNormal.addProperty((int32_t)0);
layerElementNormal.addPropertyNode("Version", (int32_t)101); layerElementNormal.addPropertyNode("Version", (int32_t)101);
layerElementNormal.addPropertyNode("Name", ""); layerElementNormal.addPropertyNode("Name", "");
layerElementNormal.addPropertyNode("MappingInformationType", "ByVertice"); layerElementNormal.addPropertyNode("MappingInformationType", "ByPolygonVertex");
layerElementNormal.addPropertyNode("ReferenceInformationType", "Direct"); layerElementNormal.addPropertyNode("ReferenceInformationType", "Direct");
std::vector<double> normals; std::vector<double> normals;
for (decltype(resultContext.vertices.size()) i = 0; i < resultContext.vertices.size(); ++i) { const auto &triangleVertexNormals = resultContext.interpolatedTriangleVertexNormals();
const auto &n = resultContext.interpolatedVertexNormals()[i]; 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.x());
normals.push_back((double)n.y()); normals.push_back((double)n.y());
normals.push_back((double)n.z()); normals.push_back((double)n.z());

View File

@ -7,6 +7,7 @@
#include "theme.h" #include "theme.h"
#include "positionmap.h" #include "positionmap.h"
#include "ds3file.h" #include "ds3file.h"
#include "anglesmooth.h"
#define MAX_VERTICES_PER_FACE 100 #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]; GLfloat *triangleNormals = new GLfloat[triangleCount * 3];
int loadedTriangleNormalItemCount = meshlite_get_triangle_normal_array(meshlite, triangleMesh, triangleNormals, 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 modelR = defaultColor.redF();
float modelG = defaultColor.greenF(); float modelG = defaultColor.greenF();
float modelB = defaultColor.blueF(); float modelB = defaultColor.blueF();
m_triangleVertexCount = triangleCount * 3; m_triangleVertexCount = triangleCount * 3;
m_triangleVertices = new Vertex[m_triangleVertexCount * 3]; m_triangleVertices = new Vertex[m_triangleVertexCount * 3];
const std::vector<QVector3D> &inputVerticesForSmoothAngle = m_triangulatedVertices;
std::vector<std::tuple<size_t, size_t, size_t>> inputTrianglesForSmoothAngle;
std::vector<QVector3D> inputNormalsForSmoothAngle;
float thresholdAngleDegrees = 60;
for (int i = 0; i < triangleCount; i++) { for (int i = 0; i < triangleCount; i++) {
int firstIndex = i * 3; int firstIndex = i * 3;
float useColorR = modelR; float useColorR = modelR;
@ -140,21 +135,14 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo
float area = 1.0; float area = 1.0;
float angles[3] = {1.0, 1.0, 1.0}; float angles[3] = {1.0, 1.0, 1.0};
if (smoothNormal) { if (smoothNormal) {
for (int j = 0; j < 3; j++) { inputTrianglesForSmoothAngle.push_back({
assert(firstIndex + j < loadedTriangleVertexIndexItemCount); triangleIndices[firstIndex + 0],
int posIndex = triangleIndices[firstIndex + j] * 3; triangleIndices[firstIndex + 1],
assert(posIndex < loadedTriangleVertexPositionItemCount); triangleIndices[firstIndex + 2]
positions[j] = QVector3D(triangleVertexPositions[posIndex + 0], });
triangleVertexPositions[posIndex + 1], inputNormalsForSmoothAngle.push_back(QVector3D(triangleNormals[firstIndex + 0],
triangleVertexPositions[posIndex + 2]); triangleNormals[firstIndex + 1],
} triangleNormals[firstIndex + 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);
} }
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
assert(firstIndex + j < loadedTriangleVertexIndexItemCount); assert(firstIndex + j < loadedTriangleVertexIndexItemCount);
@ -169,34 +157,26 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo
v->normX = triangleNormals[firstIndex + 0]; v->normX = triangleNormals[firstIndex + 0];
v->normY = triangleNormals[firstIndex + 1]; v->normY = triangleNormals[firstIndex + 1];
v->normZ = triangleNormals[firstIndex + 2]; 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->colorR = useColorR;
v->colorG = useColorG; v->colorG = useColorG;
v->colorB = useColorB; v->colorB = useColorB;
v->metalness = useMetalness; v->metalness = useMetalness;
v->roughness = useRoughness; v->roughness = useRoughness;
} }
m_triangulatedFaces.push_back(triangulatedFace); m_triangulatedFaces.push_back(triangulatedFace);
} }
if (smoothNormal) { if (smoothNormal) {
std::vector<QVector3D> resultNormals;
angleSmooth(inputVerticesForSmoothAngle, inputTrianglesForSmoothAngle, inputNormalsForSmoothAngle,
thresholdAngleDegrees, resultNormals);
size_t normalIndex = 0;
for (int i = 0; i < triangleCount; i++) { for (int i = 0; i < triangleCount; i++) {
int firstIndex = i * 3; int firstIndex = i * 3;
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
assert(firstIndex + j < loadedTriangleVertexIndexItemCount); assert(firstIndex + j < loadedTriangleVertexIndexItemCount);
int posIndex = triangleIndices[firstIndex + j] * 3;
Vertex *v = &m_triangleVertices[firstIndex + j]; Vertex *v = &m_triangleVertices[firstIndex + j];
QVector3D normal(triangleVertexSmoothNormals[posIndex + 0], const auto &normal = resultNormals[normalIndex++];
triangleVertexSmoothNormals[posIndex + 1],
triangleVertexSmoothNormals[posIndex + 2]);
normal.normalize();
v->normX = normal.x(); v->normX = normal.x();
v->normY = normal.y(); v->normY = normal.y();
v->normZ = normal.z(); v->normZ = normal.z();

View File

@ -9,6 +9,7 @@
#include "theme.h" #include "theme.h"
#include "meshresultcontext.h" #include "meshresultcontext.h"
#include "positionmap.h" #include "positionmap.h"
#include "anglesmooth.h"
struct HalfColorEdge struct HalfColorEdge
{ {
@ -32,7 +33,7 @@ MeshResultContext::MeshResultContext() :
m_bmeshNodeMapResolved(false), m_bmeshNodeMapResolved(false),
m_resultPartsResolved(false), m_resultPartsResolved(false),
m_resultTriangleUvsResolved(false), m_resultTriangleUvsResolved(false),
m_vertexNormalsInterpolated(false), m_triangleVertexNormalsInterpolated(false),
m_triangleTangentsResolved(false) m_triangleTangentsResolved(false)
{ {
} }
@ -318,6 +319,7 @@ void MeshResultContext::calculateResultParts(std::map<QUuid, ResultPart> &parts)
{ {
std::map<std::pair<QUuid, int>, int> oldVertexToNewMap; std::map<std::pair<QUuid, int>, int> oldVertexToNewMap;
for (auto x = 0u; x < triangles.size(); x++) { for (auto x = 0u; x < triangles.size(); x++) {
size_t normalIndex = x * 3;
const auto &triangle = triangles[x]; const auto &triangle = triangles[x];
const auto &sourceNode = triangleSourceNodes()[x]; const auto &sourceNode = triangleSourceNodes()[x];
auto it = parts.find(sourceNode.first); auto it = parts.find(sourceNode.first);
@ -330,13 +332,14 @@ void MeshResultContext::calculateResultParts(std::map<QUuid, ResultPart> &parts)
ResultTriangle newTriangle; ResultTriangle newTriangle;
newTriangle.normal = triangle.normal; newTriangle.normal = triangle.normal;
for (auto i = 0u; i < 3; i++) { for (auto i = 0u; i < 3; i++) {
const auto &normal = interpolatedTriangleVertexNormals()[normalIndex++];
auto key = std::make_pair(sourceNode.first, triangle.indicies[i]); auto key = std::make_pair(sourceNode.first, triangle.indicies[i]);
const auto &it = oldVertexToNewMap.find(key); const auto &it = oldVertexToNewMap.find(key);
bool isNewVertex = it == oldVertexToNewMap.end(); bool isNewVertex = it == oldVertexToNewMap.end();
bool isSeamVertex = m_seamVertices.end() != m_seamVertices.find(triangle.indicies[i]); bool isSeamVertex = m_seamVertices.end() != m_seamVertices.find(triangle.indicies[i]);
if (isNewVertex || isSeamVertex) { if (isNewVertex || isSeamVertex) {
int newIndex = resultPart.vertices.size(); 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.verticesOldIndicies.push_back(triangle.indicies[i]);
resultPart.vertices.push_back(vertices[triangle.indicies[i]]); resultPart.vertices.push_back(vertices[triangle.indicies[i]]);
ResultVertexUv vertexUv; ResultVertexUv vertexUv;
@ -417,44 +420,28 @@ void MeshResultContext::calculateResultTriangleUvs(std::vector<ResultTriangleUv>
} }
} }
void MeshResultContext::interpolateVertexNormals(std::vector<QVector3D> &resultNormals) void MeshResultContext::interpolateTriangleVertexNormals(std::vector<QVector3D> &resultNormals)
{ {
resultNormals.resize(vertices.size()); std::vector<QVector3D> inputVerticies;
std::vector<std::tuple<size_t, size_t, size_t>> inputTriangles;
auto angleBetweenVectors = [](const QVector3D &first, const QVector3D &second) { std::vector<QVector3D> inputNormals;
return std::acos(QVector3D::dotProduct(first.normalized(), second.normalized())); float thresholdAngleDegrees = 60;
}; for (const auto &vertex: vertices)
inputVerticies.push_back(vertex.position);
auto areaOfTriangle = [](const QVector3D &a, const QVector3D &b, const QVector3D &c) { for (const auto &triangle: triangles) {
auto ab = b - a; inputTriangles.push_back({triangle.indicies[0], triangle.indicies[1], triangle.indicies[2]});
auto ac = c - a; inputNormals.push_back(triangle.normal);
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];
} }
angleSmooth(inputVerticies, inputTriangles, inputNormals, thresholdAngleDegrees, resultNormals);
for (auto &item: resultNormals)
item.normalize();
} }
const std::vector<QVector3D> &MeshResultContext::interpolatedVertexNormals() const std::vector<QVector3D> &MeshResultContext::interpolatedTriangleVertexNormals()
{ {
if (!m_vertexNormalsInterpolated) { if (!m_triangleVertexNormalsInterpolated) {
m_vertexNormalsInterpolated = true; m_triangleVertexNormalsInterpolated = true;
interpolateVertexNormals(m_interpolatedVertexNormals); interpolateTriangleVertexNormals(m_interpolatedTriangleVertexNormals);
} }
return m_interpolatedVertexNormals; return m_interpolatedTriangleVertexNormals;
} }
const std::vector<QVector3D> &MeshResultContext::triangleTangents() const std::vector<QVector3D> &MeshResultContext::triangleTangents()

View File

@ -80,7 +80,7 @@ public:
const std::map<QUuid, ResultPart> &parts(); const std::map<QUuid, ResultPart> &parts();
const std::vector<ResultTriangleUv> &triangleUvs(); const std::vector<ResultTriangleUv> &triangleUvs();
const std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap(); const std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap();
const std::vector<QVector3D> &interpolatedVertexNormals(); const std::vector<QVector3D> &interpolatedTriangleVertexNormals();
const std::vector<QVector3D> &triangleTangents(); const std::vector<QVector3D> &triangleTangents();
private: private:
bool m_triangleSourceResolved; bool m_triangleSourceResolved;
@ -89,7 +89,7 @@ private:
bool m_bmeshNodeMapResolved; bool m_bmeshNodeMapResolved;
bool m_resultPartsResolved; bool m_resultPartsResolved;
bool m_resultTriangleUvsResolved; bool m_resultTriangleUvsResolved;
bool m_vertexNormalsInterpolated; bool m_triangleVertexNormalsInterpolated;
bool m_triangleTangentsResolved; bool m_triangleTangentsResolved;
private: private:
std::vector<std::pair<QUuid, QUuid>> m_triangleSourceNodes; std::vector<std::pair<QUuid, QUuid>> m_triangleSourceNodes;
@ -101,7 +101,7 @@ private:
std::set<int> m_seamVertices; std::set<int> m_seamVertices;
std::map<int, std::pair<QUuid, QUuid>> m_vertexSourceMap; std::map<int, std::pair<QUuid, QUuid>> m_vertexSourceMap;
std::map<int, int> m_rearrangedVerticesToOldIndexMap; std::map<int, int> m_rearrangedVerticesToOldIndexMap;
std::vector<QVector3D> m_interpolatedVertexNormals; std::vector<QVector3D> m_interpolatedTriangleVertexNormals;
std::vector<QVector3D> m_triangleTangents; std::vector<QVector3D> m_triangleTangents;
private: private:
void calculateTriangleSourceNodes(std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes, std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap); void calculateTriangleSourceNodes(std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes, std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap);
@ -111,7 +111,7 @@ private:
void calculateBmeshNodeMap(std::map<std::pair<QUuid, QUuid>, BmeshNode *> &bmeshNodeMap); void calculateBmeshNodeMap(std::map<std::pair<QUuid, QUuid>, BmeshNode *> &bmeshNodeMap);
void calculateResultParts(std::map<QUuid, ResultPart> &parts); void calculateResultParts(std::map<QUuid, ResultPart> &parts);
void calculateResultTriangleUvs(std::vector<ResultTriangleUv> &uvs, std::set<int> &seamVertices); void calculateResultTriangleUvs(std::vector<ResultTriangleUv> &uvs, std::set<int> &seamVertices);
void interpolateVertexNormals(std::vector<QVector3D> &resultNormals); void interpolateTriangleVertexNormals(std::vector<QVector3D> &resultNormals);
void calculateTriangleTangents(std::vector<QVector3D> &tangents); void calculateTriangleTangents(std::vector<QVector3D> &tangents);
}; };