Objectives:
Learn about normals and their importance in shading

Activity (1) - Learning about normals


Consider the unit cube with vertices (0,0,0), (1,0,0), (1,0,1), (0,0,1), (0,1,0), (1,1,0), (1,1,1), (0,1,1).

(a) What normal should be specified on the face containing the first 4 of these points if the cube is to be displayed with sharp edges?

These four points all have y-coordinate 0, so this is the bottom of the cube. The normal is <0,-1,0>.

(b) How would you calculate normals if you want the 6 faces of the cube to blend smoothly into one another? Give a specific normal as an example.

Give a different normal at each vertex, obtained by averaging the normals of three faces meeting at that vertex. For example, at (1,1,1) the normal vector would point in the direction <1,1,1>.

The idea is to understand the role of normal specification in drawing polyhedrons. If you specify a normal only once for a polygon, the polygon will be uniformly shaded. If you specify a normal for each vertex, obtained for example by averaging the normals of all polygons meeting at that vertex, the shading will blend along the edges. Here are images of a cube with (1) a single normal specified for each face, and (2) normals for each vertex, averaging the normals of the three faces meeting at the vertex:

c1
c2

Here are the programs which generated the two images. Note the use of the gluPerspective function -- it's a replacement for the glFrustum function which easier to use. You specify the camera's angle of vision rather than worrying about ratios of the near distance to the dimensions of the x-y rectangle being viewed. Try experimenting with the angle! Larger values give wide-angle lenses; smaller values give telephoto lenses.

Program 1:

#include <GL/glut.h>
typedef GLfloat point3[3];
point3 p[8]={
{0,0,0},
{0,0,1},
{1,0,1},
{1,0,0},
{0,1,0},
{0,1,1},
{1,1,1},
{1,1,0}
};

point3 n[6]={ // normals for each face
{0,-1,0},
{0,0,1},
{1,0,0},
{0,0,-1},
{0,1,0},
{-1,0,0}
};
void drawPoint(int i){
glVertex3fv(p[i]);
}
void drawFace(int i1,int i2,int i3,int i4,int k){
glBegin(GL_POLYGON);
glNormal3fv(n[k]); // use normal for this face
drawPoint(i1);
drawPoint(i2);
drawPoint(i3);
drawPoint(i4);
glEnd();
}

void drawCube(){
drawFace(0,3,2,1,0);
drawFace(1,2,6,5,1);
drawFace(2,3,7,6,2);
drawFace(3,0,4,7,3);
drawFace(4,5,6,7,4);
drawFace(5,4,0,1,5);
}

void display( void )
{
glClearColor(1.0, 1.0, 1.0, 1.0); /* white background */
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); /*clear the window */
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(1.5,2,4,.5,.5,.5,0,1,0);
drawCube();
glFlush(); /* clear buffers */
}

void main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH);
glMatrixMode(GL_MODELVIEW);
glEnable(GL_DEPTH_TEST);
glutInitWindowSize(500, 500);
glutCreateWindow("Cube");
glutDisplayFunc(display);

GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 50.0 };
GLfloat light_position[] = { 1.0, 2.0, 3.0};
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
GLfloat mat_amb_diff_red[] = { 1, 0, 0, 1.0 };
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE,mat_amb_diff_red);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST); /* enable z buffer */
glEnable(GL_NORMALIZE);
glMatrixMode(GL_PROJECTION);
// gluPerspective is an alternative to glFrustum in which
// the 'lens' is specified by angle and aspect ratio rather
// than frustum coordinates
// The near and far planes have the same meaning as before.
gluPerspective( /* field of view in degree */ 40.0,
/* aspect ratio */ 1.0,
/* Z near */ .1, /* Z far */ 100);
glMatrixMode(GL_MODELVIEW);
glutMainLoop();
}

Program 2:

#include <GL/glut.h>
typedef GLfloat point3[3];
point3 p[8]={
{0,0,0},
{0,0,1},
{1,0,1},
{1,0,0},
{0,1,0},
{0,1,1},
{1,1,1},
{1,1,0}
};


void drawPoint(int i){
glVertex3fv(p[i]);
}
void makeNormal(int i){
// make each vertex normal point from middle of cube to itself
double x,y,z;
x=p[i][0]-.5;
y=p[i][1]-.5;
z=p[i][2]-.5;
glNormal3f(x,y,z);
drawPoint(i);
}

void drawFace(int i1,int i2,int i3,int i4,int k){
glBegin(GL_POLYGON);
makeNormal(i1);
makeNormal(i2);
makeNormal(i3);
makeNormal(i4);
glEnd();
}

void drawCube(){
drawFace(0,3,2,1,0);
drawFace(1,2,6,5,1);
drawFace(2,3,7,6,2);
drawFace(3,0,4,7,3);
drawFace(4,5,6,7,4);
drawFace(5,4,0,1,5);
}

void display( void )
{
glClearColor(1.0, 1.0, 1.0, 1.0); /* white background */
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); /*clear the window */
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(1.5,2,4,.5,.5,.5,0,1,0);
drawCube();
glFlush(); /* clear buffers */
}

void main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH);
glMatrixMode(GL_MODELVIEW);
glEnable(GL_DEPTH_TEST);
glutInitWindowSize(500, 500);
glutCreateWindow("Cube");
glutDisplayFunc(display);

GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 50.0 };
GLfloat light_position[] = { 1.0, 2.0, 3.0};
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
GLfloat mat_amb_diff_red[] = { 1, 0, 0, 1.0 };
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE,mat_amb_diff_red);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST); /* enable z buffer */
glEnable(GL_NORMALIZE);
glMatrixMode(GL_PROJECTION);
// gluPerspective is an alternative to glFrustum in which
// the 'lens' is specified by angle and aspect ratio rather
// than frustum coordinates
// The near and far planes have the same meaning as before.
gluPerspective( /* field of view in degree */ 40.0,
/* aspect ratio */ 1.0,
/* Z near */ .1, /* Z far */ 100);
glMatrixMode(GL_MODELVIEW);
glutMainLoop();
}

Activity (2) - Truncated Cylinder

We will try to build a cylinder, complete with normals (the vector that makes 90 degree with the surface). We can then add the top face (so the cylinder is closed when viewed from the top -- we might want to add a bottom face as well). Finally, we make the top radius different from the bottom to create a truncated cone, and we create an animation in which the top radius varies from 0 (a cone) to twice the bottom radius.

A cylinder is a nice special case to generate out of polygons. We can use rectangular rather than triangular panels since we know the rectangles are coplanar. (Convince yourself of this!) If we want a cylinder with radius = 1 and height =2, we can use rectangles connecting these points:

(cos(t),-1,sin(t)), (cos(t+h),-1,sin(t+h)), (cos(t+h),1,sin(t+h)), (cos(t),1,sin(t))

where t is increased in increments of h from 0 to 2 pi. If we change the four points to

(cos(t),-1,sin(t)), (cos(t+h),-1,sin(t+h)),  (r*cos(t+h),1,r*sin(t+h)), (r*cos(t),1,r*sin(t))

then the base radius stays at 1 and the top radius is r.

To (start to) make a closed cylinder, we can fill in the top by using triangles from 0,1,0 to cos(t),1,sin(t) to cos(t+h),1,sin(t+h), again  letting t increase by h until a circle is covered.

Finally, we can make a nifty animation by varying the top radius in an idle function. Here's the program:


You can download the following program by clicking HERE
or you can copy and paste the code below. /* A truncated cone animation */
#include <math.h>
#include
<GL/glut.h>
double t=0;
double PI2; // 2 PI
double
rad=1;
double inc=.05;
void cylinder(){
int n=4;
double
h=PI2/n;
double x,z,xn,zn;
double
t=0;
x=cos(t);
z=sin(t);
glBegin(GL_QUADS);
for(int
i=0;i<n;i++){
t=t+h;
xn=cos(t);
zn=sin(t);
glNormal3f(x,0,z);
glVertex3f(xn,-1,zn);
glVertex3f(xn,1,zn);
glVertex3f(x,1,z);
glVertex3f(x,-1,z);
x=xn;
z=zn;
}
glEnd();
glBegin(GL_TRIANGLES);
t=0;
x=cos(t);
z=sin(t);
glNormal3f(0,1,0);
for(i=0;i<n;i++){
t+=h;
xn=cos(t);
zn=sin(t);
glVertex3f(0,0,0);
glVertex3f(x,1,z);
glVertex3f(xn,1,zn);
x=xn;
z=zn;
}
glEnd();

}

void trunccylinder(double toprad,int
sides){
int n=sides;
double h=PI2/n;
double x,z,xn,zn;
double
t=0;
x=cos(t);
z=sin(t);
glBegin(GL_QUADS);
for(int
i=0;i<n;i++){
t=t+h;
xn=cos(t);
zn=sin(t);
glNormal3f(cos(t),0,sin(t));
glVertex3f(cos(t+h),-1,sin(t+h));
glVertex3f(toprad*cos(t+h),1,toprad*sin(t+h));
glVertex3f(toprad*cos(t),1,toprad*sin(t));
glVertex3f(cos(t),-1,sin(t));
x=xn;
z=zn;
}
glEnd();
glBegin(GL_TRIANGLES);
t=0;
x=toprad*cos(t);
z=toprad*sin(t);
glNormal3f(0,1,0);
for(i=0;i<n;i++){
t+=h;
xn=toprad*cos(t);
zn=toprad*sin(t);
glVertex3f(0,0,0);
glVertex3f(x,1,z);
glVertex3f(xn,1,zn);
x=xn;
z=zn;
}
glEnd();

}
void display()
{
//int
i;
glClearColor(1,1,1,0);
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0,2,4,0,0,0,0,1,0);
cylinder();
//trunccylinder(rad,36);
glFlush();
glutSwapBuffers();
}

void
myinit()
{
PI2=atan(1.0)*8;
GLfloat mat_specular[] = { 1.0, 1.0, 1.0,
1.0 };
GLfloat mat_shininess[] = { 50.0 };
GLfloat mat_amb_diff[] = { 0.1,
0.5, 0.8, 1.0 };
GLfloat light_position[] = { 1.0, 2.0,
3.0};
glLightfv(GL_LIGHT0, GL_POSITION,
light_position);
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE,

mat_amb_diff);
glMaterialfv(GL_FRONT, GL_SPECULAR,
mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS,
mat_shininess);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
/* enable z buffer
*/
glEnable(GL_NORMALIZE);
glMatrixMode(GL_PROJECTION);
//
gluPerspective is an alternative to glFrustum in which
// the 'lens' is
specified by angle and aspect ratio rather
// than frustum coordinates
//
The near and far planes have the same meaning as before.
gluPerspective( /*
field of view in degree */ 40.0,
/* aspect ratio */ 1.0,
/* Z near */ 1.0,
/* Z far */ 10);
glMatrixMode(GL_MODELVIEW);
}

void
idle(){
rad+=inc;
if(rad<0){
rad=0;
inc=-inc;
}else
if(rad>2){
rad=2;
inc=-inc;
}
glutPostRedisplay();
}

int
main(int argc,char**
argv)
{
glutInit(&argc,argv);
glutInitWindowSize(400,400);
glutInitDisplayMode
(GLUT_DOUBLE| GLUT_RGB|GLUT_DEPTH);

glutInitWindowPosition(100,100);
glutCreateWindow("Cylinders");
glutDisplayFunc(display);
glutIdleFunc(idle);
myinit();
glutMainLoop();
return
0;
}

Try the above program with n = 4.  What do you get.  What will you get with a larger n value?

Try to uncomment the call to the truncated cylinder, try to understand how they shapes are created.

Here are a couple of screen shots:

trn1

trn2