Skip to main content
gamedev.net
🔒 Locked

Normal calculation problem

Started by Starfox 4 replies 1.5k views
Original Post
Starfox
Starfox
I'm trying to calculate normals for an MD5Mesh and I'm facing discontinuities - see the back of the Marine's arms and shoes in this set: http://www.flickr.co...57626234014059/

Here's my code. It has 2 methods to calculate normals and whichever I use I get the same seam discontinuities:


void calculate_normals( const float3* Vertex, float3* Normal, const unsigned int VertexCount,
const uint16* Index, const unsigned int IndexCount)
{
#if 1
std::vector<float3>* NormalBuffer = new std::vector<float3>[VertexCount];
std::fill(Normal, Normal+VertexCount, float3(0.0f));
for( int i = 0; i < IndexCount; i += 3 )
{
// get the three vertices that make the faces
float3 p1 = Vertex[Index[i+0]];
float3 p2 = Vertex[Index[i+1]];
float3 p3 = Vertex[Index[i+2]];

float3 v1 = p2 - p1;
float3 v2 = p3 - p1;
float3 normal = cross(v2, v1);

normal.normalize();

// Store the face's normal for each of the vertices that make up the face.
NormalBuffer[Index[i+0]].push_back( normal );
NormalBuffer[Index[i+1]].push_back( normal );
NormalBuffer[Index[i+2]].push_back( normal );
}


// Now loop through each vertex vector, and avarage out all the normals stored.
for( int i = 0; i < VertexCount; ++i )
{
for( int j = 0; j < NormalBuffer.size(); ++j )
Normal += NormalBuffer[j];

//Normal /= float(NormalBuffer.size());
Normal = float3(Normal).normalize();
}
delete [] NormalBuffer;
#endif
#if 0
std::fill(Normal, Normal+VertexCount, float3(0,0,0));
for( int i = 0; i < IndexCount; i += 3 )
{
// get the three vertices that make the faces
float3 p1 = Vertex[Index[i+0]];
float3 p2 = Vertex[Index[i+1]];
float3 p3 = Vertex[Index[i+2]];

float3 v1 = p2 - p1;
float3 v2 = p3 - p1;
float3 normal = cross(v2, v1);
//assert(normal != float3(0));

normal.normalize();

// Store the face's normal for each of the vertices that make up the face.
Normal[Index[i+0]] += normal;
Normal[Index[i+1]] += normal;
Normal[Index[i+2]] += normal;
}
for( int i = 0; i < VertexCount; ++i )
{
Normal = float3(Normal).normalize();
}
#endif
}


Any ideas? This normal calculation is messing up tangents and bitangents too so the lighting is messed up along the seams..
Zakwayda
Zakwayda
I read through your code and didn't see any obvious errors. But, I could have missed something, and there could be an error in the support code somewhere, I suppose.

Could you add some debug graphics to show the triangles and normals?

Also, this is unrelated, but you shouldn't have to create the array of std::vector's using 'new'; you can just create a std::vector of std::vector's instead. Or, since you're just normalizing the result, you could probably just add the face normals directly to the vertex normals as they (the face normals) are computed.
Doug Rogers
Doug Rogers
You are indexing by the vertex, not the point location. There will be two vertices on the seam that are shared by one point. You need to have only one normal per point in space, not one normal per vertex.

This might not be exactly what you need, but it should get you closer.


void FBXMesh::ComputeNormals( )
{


std::map<FBXVector3, FBXVector3> newPositionMap;

for(uint pid = 0; pid < GetNumVertices(); pid++)
{
FBXVector3 & p = GetPosition(pid);

newPositionMap

= FBXVector3(0,0,0);
}

for(uint fid = 0; fid < GetNumFaces(); fid++)
{
uint i0 = fid * 3;
uint i1 = fid * 3 + 1;
uint i2 = fid * 3 + 2;
int pid0 = m_indexList[i0];
int pid1 = m_indexList[i1];
int pid2 = m_indexList[i2];


FBXVector3 & p0 = GetPosition(pid0);
FBXVector3 & p1 = GetPosition(pid1);
FBXVector3 & p2 = GetPosition(pid2);



FBXVector3 n = TriangleNormal(p0, p1, p2);

newPositionMap[p0] += n;
newPositionMap[p1] += n;
newPositionMap[p2] += n;

}

for(uint pid = 0; pid < GetNumVertices(); pid++)
{
FBXVector3 & normal = GetNormal(pid);

FBXVector3 & p = GetPosition(pid);

normal = newPositionMap

;

normal.normalize();

}


}


Starfox
Starfox
Doug, you beat me to it by a minute or two. jyk: I added debug output and that made it immediately obvious - take a look:
http://www.flickr.co...N06/5565793769/ (Normals in red, tangents in green and bitangents in blue)
There're TWO vertices, not one, along the seam, so averaging normals doesn't work properly. Now does anyone know WHY there would be two very-close vertices like that? It seems like an anomaly and not by design, I can't think of a valid reason for it so if someone can let me know.
Indexing by vertex would fix it in theory, but floating point is a harsh mistress - and I think this is an art flaw. Opinions welcome.
Doug Rogers
Doug Rogers

Doug, you beat me to it by a minute or two. jyk: I added debug output and that made it immediately obvious - take a look:
http://www.flickr.co...N06/5565793769/ (Normals in red, tangents in green and bitangents in blue)
There're TWO vertices, not one, along the seam, so averaging normals doesn't work properly. Now does anyone know WHY there would be two very-close vertices like that? It seems like an anomaly and not by design, I can't think of a valid reason for it so if someone can let me know.
Indexing by vertex would fix it in theory, but floating point is a harsh mistress - and I think this is an art flaw. Opinions welcome.


It's not a flaw, the verts have to be split because they do not have the same texture coordinates, but they do share the same point in space.

Consider a texture mapped cylinder and our vertices are defined by <p,uv>. p being the position and uv being the texture coordinate.

There will be a seam where the texture is wrapped around it. Those vertices will have the same point in space, but will have either 0 or 1 as the coordinate value.

So there will be two verts for the seam, for example
<p, (0,0)>
<p,(1,0)>

To calculate a single vertex normal for 'p', you need to use all the vertices that use 'p' as their position.
RobTheBloke
RobTheBloke
First it's worth pointing out that this 'flaw' was visible in Doom3 - but it's not as noticeable when you have texturing enabled....


The best approach I've found is to pre-compute a mapping for the wrap around vertices. A quick hunt for the cases where fmodf(uv1.v) == fmod(uv2.v) || fmodf(uv1.u) == fmod(uv2.u) [and the normal and vertex is the same]. There can be a variable number though! Ultimately you want to pre-process it so you can use it like so:


uint32_t numNormalFixes;
uint8_t counts[];
uint16_t indices[];

uint32_t numProcessed = 0;
for(uint32_t i=0; i!=numNormalFixes; ++i)
{
uint16_t firstIndex = indices[ numProcessed ];
for(uint8_t j=1; j<counts; ++j)
{
NormalBuffer[ firstIndex ] += NormalBuffer[ indices[ numProcessed+j ] ];
}

for(uint8_t j=1; j<counts; ++j)
{
NormalBuffer[ indices[ numProcessed+j ] ] = NormalBuffer[ firstIndex ];
}
numProcessed += counts;
}


Of course the best option is to not use file formats that don't explicitly provide the TBN matrix. Maya/Xsi/Max will give you access to the TBN values, which more or less solves the problem (since they can then just be rotated instead of computed).

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.