Landon Townsend
C++ / C# Programmer
Averaging Normals
The following is a code sample from my rendering program. It part of a class that reads in OBJ files and translates them into mesh objects. The method this code is a part of is
MeshLoader::loadMesh(Mesh *mesh, const char* path, bool smoothNormals)
where mesh is the pointer to the Mesh object that you want to store the information, path is the filename string, and smoothNormals is whether or not you want to average the normals when generating them, if the OBJ has no normal information.
If smoothNormals is true, and no normal information is stored in the OBJ, this code runs. Here are the variables used earlier in the code which this sample uses:
int numVerts - the total number of verts found in the OBJ file
int numFaces - the the total number of faces found in the OBJ file
vec3 *verts - an array of vertices (stored as 3D vectors) read in from the OBJ file.
vec3 *normals - an array of normals (stored as 3D vectors) read in from the OBJ file (or initialized as zero vectors in this case, as no normals were found). If there are no normals found, this will be an array of zero vectors which is the same length as verts.
int **faces - a double array of faces (tris). The first index is the face index that indicates which face it is, and the second index are 9 values that represent the indices of vertices, UVs, and normals. These vertices are read in order from the OBJ file, so [0], [1], and [2] are the vertex, UV and normal indices, respectively, from the first vertex in the triangle, [3], [4], and [5] are the vertex, UV, and normal indices from the second vertex, and [6], [7], and [8] are the vertex, UV, and normal indices from the third vertex. Indexes are read in directly from the OBJ file, where indices start at 1, while vertices are stored in an array (verts), which starts at index 0, so every time we retrieve an index from faces, we need to subtract 1 before using it.
{
// count the number of faces that has each vert, accounting for bad geometry where a face has multiple verts
//in the same spot
uint *vertAttachStartIndices = new uint[numVerts];
memset(vertAttachStartIndices, 0, numVerts*sizeof(uint));
for (int i = 0; i < numFaces; i++) {
vertAttachStartIndices[faces[i][0]-1]++;
vertAttachStartIndices[faces[i][3]-1]++;
vertAttachStartIndices[faces[i][6]-1]++;
}
// Generate face normals
vec3 *faceNormals = new vec3[numFaces];
for (int i = 0; i < numFaces; i++) {
vec3 point1 = verts[faces[i][0]-1];
vec3 point2 = verts[faces[i][3]-1];
vec3 point3 = verts[faces[i][6]-1];
faceNormals[i] = glm::normalize(cross(point3 - point2, point1 - point2));
}
// Generate array of indices for "faceNormals"; this will tell the vertexes which normals they're attached to
uint *vertNormalAttachments = new uint[numFaces*3];
memset(vertNormalAttachments, -1, numFaces*3*sizeof(uint)); // -1 tells us that no index has been entered yet
// Generate the array of indexes telling where each vertex's corrisponding list of normals start
int currentAttachIndexTotal = 0;
for (int i = 0; i < numVerts; i++) {
int currentIndex = vertAttachStartIndices[i];
vertAttachStartIndices[i] = currentAttachIndexTotal;
currentAttachIndexTotal += currentIndex;
}
// Copy that array, this will be used to mark the next empty attachment for each vertex
uint *vertAttachCurrentIndices = new uint[numVerts];
memcpy(vertAttachCurrentIndices, vertAttachStartIndices, numVerts*sizeof(uint));
// Populate the Vert Normal Attachment Array
for (int i = 0; i < numFaces; i++) {
for (int j = 0; j < 3; j++) {
uint vertIndex = faces[i][3*j] - 1;
uint VACI = vertAttachCurrentIndices[vertIndex];
vertNormalAttachments[VACI] = i;
++vertAttachCurrentIndices[vertIndex];
}
}
// Populate the Normal array with the averages of each vertice's attached normals
for (int i = 0; i < numVerts; i++) {
vec3 addedVectors = vec3(0,0,0);
uint jLoopStart = vertAttachStartIndices[i];
// If we're at the end of our list of start indices, set the loop to end at the length of the normal
// attachment array, which is numFaces * 3; otherwise, Set the loop to end at the next start index.
uint jLoopEnd = numFaces * 3;
if (i != numVerts - 1)
jLoopEnd = vertAttachStartIndices[i + 1];
for (int j = jLoopStart; j < jLoopEnd; j++) {
uint VNA = vertNormalAttachments[j];
addedVectors += faceNormals[VNA];
}
normals[i] = normalize(addedVectors);
}
// Add the normals to the faces; the normal index will be the same as the vert index
for (int i = 0; i < numFaces; i++) {
for (int j = 0; j < 3; j++) {
faces[i][j*3+2] = faces[i][j*3];
}
}
delete[] vertAttachStartIndices;
delete[] vertAttachCurrentIndices;
delete[] vertNormalAttachments;
delete[] faceNormals;
}