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
HEADERS += src/fbxfile.h
SOURCES += src/anglesmooth.cpp
HEADERS += src/anglesmooth.h
SOURCES += src/main.cpp
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);
}
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);
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

View File

@ -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<double> 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());

View File

@ -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<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++) {
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<QVector3D> 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();

View File

@ -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<QUuid, ResultPart> &parts)
{
std::map<std::pair<QUuid, int>, 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<QUuid, ResultPart> &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<ResultTriangleUv>
}
}
void MeshResultContext::interpolateVertexNormals(std::vector<QVector3D> &resultNormals)
void MeshResultContext::interpolateTriangleVertexNormals(std::vector<QVector3D> &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<QVector3D> inputVerticies;
std::vector<std::tuple<size_t, size_t, size_t>> inputTriangles;
std::vector<QVector3D> 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<QVector3D> &MeshResultContext::interpolatedVertexNormals()
const std::vector<QVector3D> &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<QVector3D> &MeshResultContext::triangleTangents()

View File

@ -80,7 +80,7 @@ public:
const std::map<QUuid, ResultPart> &parts();
const std::vector<ResultTriangleUv> &triangleUvs();
const std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap();
const std::vector<QVector3D> &interpolatedVertexNormals();
const std::vector<QVector3D> &interpolatedTriangleVertexNormals();
const std::vector<QVector3D> &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<std::pair<QUuid, QUuid>> m_triangleSourceNodes;
@ -101,7 +101,7 @@ private:
std::set<int> m_seamVertices;
std::map<int, std::pair<QUuid, QUuid>> m_vertexSourceMap;
std::map<int, int> m_rearrangedVerticesToOldIndexMap;
std::vector<QVector3D> m_interpolatedVertexNormals;
std::vector<QVector3D> m_interpolatedTriangleVertexNormals;
std::vector<QVector3D> m_triangleTangents;
private:
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 calculateResultParts(std::map<QUuid, ResultPart> &parts);
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);
};