## calculating normals

ghettotek
Unregistered

Post: #1
im trying to get the lighting of my terrain to be realistic. i have all the normals of all the polygons in my terrain set to Y=+1.0. it looks nice when the terrain is perfectly flat, but if i throw in some hills and whatnot, the lighting looks a little wierd. i dont know much about normals except that its supposed to be the line perpendicular to the face. i would like to know how to calculate the normal given 4 points. does anybody know how?..or am i way off. thx.
Luminary
Posts: 5,143
Joined: 2002.04
Post: #2
Four points aren't necessarily coplanar, so there's no way to do that.

For 3 points, search the forums. It was answered (again!) just a couple of days ago.
ghettotek
Unregistered

Post: #3
yes i know but i am still confused. you say:

If the vertices of your triangle are A, B, C, the face normal is something like (A - B) x (C - B). (That might be back-to-front, should be easy to fix by changing the order of the subtractions).

i think it should be (A-B)x(B-C), but im not sure. anyway, if A,B, and C are verticies, then does (A-B)x(B-C) mean: normalX = (X[A]-X[b])*(X[b]-X[C]); normalY = (Y[A]-Y[b])*(Y[b]-Y[C]); normalZ = (Z[A]-Z[b])*(Z[b]-Z[C]); ? and do i use glNormal3f() before each polygon, or before each vertex?
Member
Posts: 151
Joined: 2002.09
Post: #4
Quote:Originally posted by ghettotek
and do i use glNormal3f() before each polygon, or before each vertex?

well since you have the same normal for all vertices in the polygon you only have to call it once for the whole polygon.

but you could also calculate the normals for each vertex (like the x-product from the two adjacent edges of the vertex) instead.
Feanor
Unregistered

Post: #5
Option 1: all vertices in each polygon have same normal

Two things are required to get proper normal:
1. both vectors used in the cross product must be co-planar but not co-linear
2. Rotational direction from first vector to second vector must be counterclockwise, because we are using right-handed co-ordinate system.

If your triangle's vertices are labelled A,B,C in counter-clockwise order, you can use ABxAC, BCxBA, CAxCB, ABxBC, BCxCA or CAxAB -- note that you can switch order of multiplication if you switch direction of one of the vectors (ABxAC = CAxAB). These will all result in a normal pointing out from the front of the polygon. Reverse any one of those vectors, or the order (not both), to get the back facing normal.

Option 2: vertices have individual normals

This is usually used when your polygons are sharing vertices, and you want your surface to have a smooth appearance (option 1 leads to a faceted appearance). In other words, if triangle 1 and triangle 2 share an edge, then they share the two vertices which define that edge. Usually this means you are defining your triangles using an index into a vertex list, whereas in option 1 you are defining each triangle as its own three-vertex list.

In order to get the right normals for an indexed list of vertices, it is usual to calculate (in advance) using interpolation of the polygon normals (calculated above) for all polygons sharing that vertex, possibly using a weighting factor.

(Has it occurred to anyone that if no one is checking the FAQ, then the FAQ maybe needs to be made more user-friendly? Or that we need some kind of big, bold link and disclaimer on the main Forum page? You can get on people's cases for repeating questions all you want, it does not seem to make much difference overall.)
Member
Posts: 304
Joined: 2002.04
Post: #6
Quote:Originally posted by ghettotek
does (A-B)x(B-C) mean: normalX = (X[A]-X[b])*(X[b]-X[C]); normalY = (Y[A]-Y[b])*(Y[b]-Y[C]); normalZ = (Z[A]-Z[b])*(Z[b]-Z[C]);

no - here the 'x' means 'cross product'. Do some googling with terms "cross product", "vectors", "linear algebra". If you take the cross product of two unit vectors it returns a unit vector that is perpendicular to both of them. I use Eberly's Wild Magic library <http://wild-magic.com/> which defines cross product as:

[SOURCECODE]
class Vector3
{
public:
// construction
Vector3 (Real fX, Real fY, Real fZ);

// coordinates
Real x, y, z;

// vector operations
Vector3 Cross (const Vector3& rkVector) const;
}

//----------------
Vector3::Vector3 (Real fX, Real fY, Real fZ)
{
x = fX;
y = fY;
z = fZ;
}
//----------------
Vector3 Vector3::Cross (const Vector3& rkVector) const
{
return Vector3(y*rkVector.z-z*rkVector.y,z*rkVector.x-x*rkVector.z,
x*rkVector.y-y*rkVector.x);
}
//----------------

[/SOURCECODE]
Member
Posts: 196
Joined: 2002.04
Post: #7
Check out my earlier post. I think that may clear up your problems: http://www.idevgames.com/forum/showthrea...eadid=2332

Hope this helps,
Iceman
lpetrich
Unregistered

Post: #8
Calculating and using a normal for each face will result in a faceted appearance. If one wants a smooth appearance, one ought to calculate and use a normal for each vertex.

There is a simple way to calculate such vertex normals, however.
1. Set them all to zero.
2. Calculate all the face normals; add each face's normal to the normals for the face's vertices.
3. Normalize the vertex normals.

I had implemented that for normal-less models in Aleph One (Marathon-engine open-source project). I had also implemented a refinement that allows a mixture of faceted and smooth appearance -- if a vertex's face normals are too different, then split that vertex and assign a face normal to each vertex copy.

Also, if one generates variable heights, each grid square is likely to be nonplanar. OpenGL renders polygons by turning them into triangle fans: a polygon with vertices 0,1,2,3,4,5 will be turned into these triangles:

0.1,2
0,2,3
0,3,4
0,4,5

So a quad (4-sided polygon), will be split in two along a diagonal. If one always starts with (say) the lower left corner, then the polygons will be split

/ / / /
/ / / /

Starting with some alternation of starting corners can produce this pattern of splits:

/ \ / \
\ / \ /

One can even go further and define a grid of centers of one's original grid. One can then split each original-grid square in this fashon:

\ /
.*
/ \

(the . added to position the *)
lpetrich
Unregistered

Post: #9
The math for calculating terrain normals is rather easy to work out with a symbolic-algebra program like Mathematica.

For using the gridline-direction neighbors, the normal is

{(h[-1,0] - h[1,0])/2, (h[0,-1] - h[0,1])/2, 1}

-- unnormalized, but approximately normalized for low slopes. Here, the heights are h[x,y], where x and y are in grid units relative to the point (+1 for next, -1 for previous).

For using both gridline-direction and diagonal neighbors, the normal is
{
(2*(h[-1,0] - h[1,0]) + (h[-1,-1] - h[1,1]) + (h[-1,1] - h[1,-1]))/8,
(2*(h[0,-1] - h[0,1]) + (h[-1,-1] - h[1,1]) + (h[1,-1] - h[-1,1]))/8,
1
}
ghettotek
Unregistered

Post: #10
looks like itd work but i have no clue how to implement that.
lpetrich
Unregistered

Post: #11
Ghettotek, that's almost absurdly simple.

Define

inline int INDEX(iint X, int Y, int XSize) {return (XSize*Y + X);}
template<class T> inline SQR(T& X) {return X*X;}

Define an array Heights with size XSize*YSize and an array Normals with size 3*XSize*YSize -- a packed array of normal vectors.

Then for each X between 1 and XSize-2 and each Y between 1 and YSize-2 do

(FLOAT is either float or double)

FLOAT H_px = Heights(INDEX(X+1,Y,XSize));
FLOAT H_py = Heights(INDEX(X,Y+1,XSize));
FLOAT H_nx = Heights(INDEX(X-1,Y,XSize));
FLOAT H_ny = Heights(INDEX(X,Y-1,XSize));

FLOAT N0 = (H_nx - H_px)/2;
FLOAT N1 = (H_ny - H_py)/2;
FLOAT N2 = 1;

FLOAT NNorm = 1/sqrt(SQR(N0) + SQR(N1) + SQR(N2));
N0 *= NNorm;
N1 *= NNorm;
N2 *= NNorm;

Normals[3*INDEX(X,Y,XSize)] = N0;
Normals[3*INDEX(X,Y,XSize)+1] = N1;
Normals[3*INDEX(X,Y,XSize)+2] = N2;

You'll have to treat the edges as a special case. And you could easily optimize the array indexing.

Here's a good way of debugging your code. Paint each vertex a color corresponding to the normal components. Something like setting individual colors with

glColor3f((N0+1)/2,(N1+1)/2,(N2+1)/2);

Multiply the N's by something to enhance the color if you so desire; OpenGL will clip the color-channel value to [0,1]