I've got an OpenGL program running, and it displays geometry, but it's all "flat," one gray tone, with no diffuse shading or specular reflection:
Pictured are three tori, each made of quad strips. We should see shading, but we don't. What am I doing wrong?
Here is the code where I set the vertices and normals (draw_torus()
is called to build a display list):
/* WrapTorus, adapted from
http://www.math.ucsd.edu/~sbuss/MathCG/OpenGLsoft/WrapTorus/WrapTorus.html
by Sam Buss */
/*
* Issue vertex command for segment number j of wrap number i.
* Normal added by Lars Huttar.
* slices1 = numWraps; slices2 = numPerWrap.
*/
void putVert(float i, float j, float slices1, float slices2, float majR, float minR) {
float wrapFrac = j / slices2;
/* phi is rotation about the circle of revolution */
float phi = PI2 * wrapFrac;
/* theta is rotation about the origin, in the xz plane. */
float theta = PI2 * (i + wrapFrac) / slices1;
float y = minR * (float)sin(phi);
float r = majR + minR * (float)cos(phi);
float x = (float)sin(theta) * r;
float z = (float)cos(theta) * r;
/* normal vector points to (x,y,z) from: */
float xb = (float)sin(theta) * majR;
float zb = (float)cos(theta) * majR;
glNormal3f(x - xb, y, z - zb);
glVertex3f(x, y, z);
}
static void draw_torus(int numPerWrap, int numWraps, float majR, float minR) {
int i, j;
glBegin( GL_QUAD_STRIP );
for (i=0; i < numWraps; i++ ) {
for (j=0; j < numPerWrap; j++) {
putVert((float)i, (float)j, (float)numWraps, (float)numPerWrap, majR, minR);
putVert((float)(i + 1), (float)j, (float)numWraps, (float)numPerWrap, majR, minR);
}
}
putVert(0.0, 0.0, (float)numWraps, (float)numPerWrap, majR, minR);
putVert(1.0, 0.0, (float)numWraps, (float)numPerWrap, majR, minR);
glEnd();
}
Is there something wrong with the order of vertices?
Here is the part of the init function where the display list is built:
GLfloat white[4] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat color[4] = { 0.5, 0.6, 0.7, 1.0 };
...
glShadeModel(GL_SMOOTH);
torusDL = glGenLists (1);
glNewList(torusDL, GL_COMPILE);
setMaterial(color, white, 100);
draw_torus(8, 45, 1.0, 0.05);
glEndList();
where setMaterial() just does:
static void setMaterial(const GLfloat color[3], const GLfloat hlite[3], int shininess) {
glColor3fv(color);
glMaterialfv(GL_FRONT, GL_SPECULAR, hlite);
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
glMateriali(GL_FRONT, GL_SHININESS, shininess); /* [0,128] */
}
Here is lighting that's also done during initialization:
GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
GLfloat color[4] = {0.20, 0.20, 0.20, 1.00};
GLfloat spec[4] = {0.30, 0.30, 0.30, 1.00};
GLfloat shiny = 8.0;
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glLightfv(GL_LIGHT0, GL_POSITION, pos);
glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec);
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
Here is where the display list gets called, in the draw function:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glLoadIdentity();
glScalef(3.5, 3.5, 3.5);
for (i = 0; i < ac->nrings; i++) {
glScalef(0.8, 0.8, 0.8);
glRotatef(...);
glCallList(torusDL);
}
glFlush();
glPopMatrix();
glXSwapBuffers(dpy, window);
The full .c source file for this "glx hack" is here. In case it makes a difference, this code is in the context of xscreensaver.
As you see, glEnable(GL_NORMALIZE)
normalizes the normal vectors after the transformations used for lighting calculations (in fixed-function pipelines). These calculations rely on unit length normals for correct results.
It's worth pointing out that the transforms applied to normal vectors are not the same as the transforms applied to vertex geometry. The OpenGL 2.1 specification describes the transform, as do many other resources. As a vector, a normal has the homogeneous representation: [nx, ny, nz, 0]
- a point at 'infinity', and a mathematically elegant way to unify matrix and 4-vector operations in the GL pipeline.
Of course, you could perform this normalization yourself, and it may be more efficient to do so, as your torus geometry is only generated once for a pre-compiled display list:
nx = x - b, ny = y, nz = z - zb;
nl = 1.0f / sqrtf(nx * nx + ny * ny + nz * nz);
glNormal3f(nx * nl, ny * nl, nz * nl);
Be sure to check (nl
) for division by zero (or some epsilon), if that's a possibility.