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
parent
096c107775
commit
6f471e7678
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue