openglfreeglutopengl-compatshading

OpenGL diffuse/specular light not rendering (only ambient) on polygons


I have setup the lighting to the best of my understanding but all that renders is Ambient lighting. It seems to work on built-in shapes like glutSolidSphere and glutSolidCube, but not my manually coded polygons.

I have spent hours reviewing tutorials and StackOverflow but haven't been able to resolve it on my own.

What am I missing? I am using Netbeans (Windows), C++, FreeGlut.

image: 3D Airplane

#include <GL/glut.h>
#include <GL/glext.h>
​
GLfloat pos0 [] = {0,1,0,0},
        white[] = {1,1,1};
​
void render(){
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glNormal3f(0,1,0);
  glScaled(4,4,4);
  glRotated(30,1,0,0);
  glRotated(60,0,1,0);
  glRotated(10,0,0,-1);
  glTranslated(0,0,-.2);
  glBegin(GL_QUADS);
    #define V glVertex3f
    // body
    glColor3f(.7,.5,.3);
    V(0,   0, 0); V(  1,.06,.05); V(  1,.10,.05); V(0, .15, 0);
    V(0,   0, 0); V(  1,.06,.05); V(  1,.05,.10); V(0,-.03,.1);
    V(0,-.03,.1); V(  1,.05,.10); V(  1,.06,.15); V(0,   0,.2);
    V(0,   0,.2); V(  1,.06,.15); V(  1,.10,.15); V(0, .15,.2);
    V(0, .15,.2); V(  1,.10,.15); V(  1,.11,.10); V(0, .17,.1);
    V(0, .17,.1); V(  1,.11,.10); V(  1,.10,.05); V(0, .15, 0);
    V(0,   0, 0); V(-.5,.03,.07); V(-.5,.06,.07); V(0, .15, 0);
    V(0,   0, 0); V(-.5,.03,.07); V(-.5,.02,.10); V(0,-.03,.1);
    V(0,-.03,.1); V(-.5,.02,.10); V(-.5,.03,.13); V(0,   0,.2);
    V(0,   0,.2); V(-.5,.03,.13); V(-.5,.06,.13); V(0, .15,.2);
    V(0, .15,.2); V(-.5,.06,.13); V(-.5,.07,.10); V(0, .17,.1);
    V(0, .17,.1); V(-.5,.07,.10); V(-.5,.06,.07); V(0, .15, 0);
    // wings
    glColor3f(.5,.5,.5);
    V(-.1,.06, .1); V(.3,.06, .1); V(.4,.10, .7); V( .2,.10, .7);
    V(-.1,.10, .1); V(.3,.10, .1); V(.4,.11, .7); V( .2,.11, .7);
    V(-.1,.06, .1); V(.2,.10, .7); V(.2,.11, .7); V(-.1,.10, .1);
    V( .3,.06, .1); V(.4,.10, .7); V(.3,.10, .1); V( .4,.11, .7);
    V( .4,.10, .7); V(.2,.10, .7); V(.4,.11, .7); V( .2,.11, .7);
    V(-.1,.06, .1); V(.3,.06, .1); V(.4,.10,-.5); V( .2,.10,-.5);
    V(-.1,.10, .1); V(.3,.10, .1); V(.4,.11,-.5); V( .2,.11,-.5);
    V(-.1,.06, .1); V(.2,.10,-.5); V(.2,.11,-.5); V(-.1,.10, .1);
    V( .3,.06, .1); V(.4,.10,-.5); V(.3,.10, .1); V( .4,.11,-.5);
    V( .4,.10,-.5); V(.2,.10,-.5); V(.4,.11,-.5); V( .2,.11,-.5);
    // tail
    V(.6,.06, .10); V(.8,.06, .10); V(.9,.10, .40); V(.8,.10, .40);
    V(.6,.10, .10); V(.8,.10, .10); V(.9,.11, .40); V(.8,.11, .40);
    V(.6,.06, .10); V(.8,.10, .40); V(.8,.11, .40); V(.6,.10, .10);
    V(.8,.06, .10); V(.9,.10, .40); V(.9,.11, .40); V(.8,.10, .10);
    V(.9,.10, .40); V(.8,.10, .40); V(.8,.11, .40); V(.9,.11, .40);
    V(.6,.06, .10); V(.8,.06, .10); V(.9,.10,-.20); V(.8,.10,-.20);
    V(.6,.10, .10); V(.8,.10, .10); V(.9,.11,-.20); V(.8,.11,-.20);
    V(.6,.06, .10); V(.8,.10,-.20); V(.8,.11,-.20); V(.6,.10, .10);
    V(.8,.06, .10); V(.9,.10,-.20); V(.9,.11,-.20); V(.8,.10, .10);
    V(.9,.10,-.20); V(.8,.10,-.20); V(.8,.11,-.20); V(.9,.11,-.20);
    V(.7,.10, .09); V(.9,.10, .09); V( 1,.30, .09); V(.9,.30, .09);
    V(.7,.10, .11); V(.9,.10, .11); V( 1,.30, .11); V(.9,.30, .11);
    V(.7,.10, .09); V(.9,.30, .09); V(.9,.10, .11); V(.7,.10, .11);
    V(.9,.10, .09); V( 1,.30, .09); V( 1,.30, .11); V(.9,.10, .11);
    V( 1,.30, .09); V(.9,.30, .09); V(.9,.30, .11); V( 1,.30, .11);
  glEnd();
  glFlush();
}
int main(int argc,char** argv){
  // setup window
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB|GLUT_DEPTH|GLUT_MULTISAMPLE);
  glutInitWindowSize(800,600);
  glutCreateWindow("3D Airplane");
  glClearColor(0,0,0,1);

  // setup perspective
  glMatrixMode(GL_PROJECTION);
  glFrustum(-2,2, -1.5,1.5, 3,25);
  glMatrixMode(GL_MODELVIEW);
  gluLookAt(0,0,4, 0,0,0, 0,1,0);
  glViewport(0,0,800,600);
  glEnable(GL_DEPTH_TEST);

  // setup lighting
  glLightfv(GL_LIGHT0,GL_POSITION,pos0 );
  glLightfv(GL_LIGHT0,GL_DIFFUSE ,white);
  glEnable (GL_LIGHT0);
  glEnable (GL_LIGHTING);
  glEnable (GL_COLOR_MATERIAL);

  glutDisplayFunc(render);
  glutMainLoop();
}

EDIT: I have written a normal calculation function called calcNormal, and converted my glVertex calls to a quadrilateral array of vertices...

GLfloat* calcNormal(GLfloat* a,GLfloat* b,GLfloat* c){
  // define target plane
  GLfloat u[] = {b[0]-a[0], b[1]-a[1], b[2]-a[2]};
  GLfloat v[] = {c[0]-a[0], c[1]-a[1], c[2]-a[2]};
  // calculate cross product
  static GLfloat cp[] = { u[1]*v[2] - u[2]*v[1], 
                          u[2]*v[0] - u[0]*v[2], 
                          u[0]*v[1] - u[1]*v[0] };
  // normalize
  GLfloat length = sqrt(cp[0]*cp[0] + cp[1]*cp[1] + cp[2]*cp[2]);
  for(int i=0;i<3;i++) cp[i] /= length;
  return cp;
}
render(){
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glColor3f(.7,.5,.3);
  glPushMatrix();
  glScaled(3,3,3);
  glRotated(30,1,0,0);
  glRotated(60,0,1,0);
  glRotated(10,0,0,-1);
  glTranslatef(x,y,z);

  for(GLint* quad:planeQuads){
    #define PV planeVertices
    glNormal3fv(calcNormal(PV[quad[0]],PV[quad[1]],PV[quad[2]]));
    glBegin(GL_QUADS);
      glVertex3fv(PV[quad[0]]);
      glVertex3fv(PV[quad[1]]);
      glVertex3fv(PV[quad[2]]);
      glVertex3fv(PV[quad[3]]);
    glEnd();
  }
  glPopMatrix();
  glFlush();
}

...but it still isn't rendering diffuse light, even though I am now setting glNormal for each poly.

My poly/vertex structure now looks like this:

// polygon arrays
GLint planeQuads[37][4] = {
  { 9,15,17,11}, { 9,15,14, 8}, { 8,14,16,10}, {10,16,18,12}, {12,18,19,13}, {13,19,17,11}, // rear body
  { 9, 1, 3,11}, { 9, 1, 0, 8}, { 8, 0, 2,10}, {10, 2, 4,12}, {12, 4, 5,13}, {13, 5, 3,11}, // front body
  { 6,17,20,14}, { 7,19,22,16}, { 6,14,16, 7}, {17,20,19,22}, {20,14,22,16}, // left wing
  { 6,17,23,18}, { 7,19,21,15}, { 6,18,17, 7}, {17,23,19,21}, {23,18,21,15}, // right wing
  {24,28,37,31}, {25,30,39,33}, {24,31,33,25}, {28,37,39,30}, {37,31,33,39}, // left fin
  {24,28,34,29}, {25,30,38,32}, {24,29,32,25}, {28,34,38,30}, {34,29,32,38}, // right fin
  {26,35,48,40}, {27,36,49,41}, {26,40,36,27}, {35,48,49,36}, {48,40,41,49}  // tail
};

// vertex arrays
GLfloat planeVertices[50][3] = {
  {-.5,0.02,0.10}, {-.5,0.03,0.07}, {-.5,0.03,0.13}, {-.5,0.06,0.07}, {-.5,0.06,0.13},
  {-.5,0.07,0.10}, {-.1,0.06,0.10}, {-.1,0.10,0.10}, {0.0,-.03,0.10}, {0.0,0.00,0.00},
  {0.0,0.00,0.20}, {0.0,0.15,0.00}, {0.0,0.15,0.20}, {0.0,0.17,0.10}, {0.2,0.10,-.50},
  {0.2,0.10,0.70}, {0.2,0.11,-.50}, {0.2,0.11,0.70}, {0.3,0.06,0.10}, {0.3,0.10,0.10},
  {0.4,0.10,-.50}, {0.4,0.10,0.70}, {0.4,0.11,-.50}, {0.4,0.11,0.70}, {0.6,0.06,0.10},
  {0.6,0.10,0.10}, {0.7,0.10,0.09}, {0.7,0.10,0.11}, {0.8,0.06,0.10}, {0.8,0.10,-.20},
  {0.8,0.10,0.10}, {0.8,0.10,0.40}, {0.8,0.11,-.20}, {0.8,0.11,0.40}, {0.9,0.10,-.20},
  {0.9,0.10,0.09}, {0.9,0.10,0.11}, {0.9,0.10,0.40}, {0.9,0.11,-.20}, {0.9,0.11,0.40},
  {0.9,0.30,0.09}, {0.9,0.30,0.11}, {1.0,0.05,0.10}, {1.0,0.06,0.05}, {1.0,0.06,0.15},
  {1.0,0.10,0.05}, {1.0,0.10,0.15}, {1.0,0.11,0.10}, {1.0,0.30,0.09}, {1.0,0.30,0.11}
};

Solution

  • You can see that your setup works in general, if you change the one and only normal vector form glNormal3f(0,1,0) to glNormal3f(0,-1,0), which makes the scene darker.

    But, you've to set normal vectors, which are perpendicular to the faces (or even vertices) of the geometry. So you've to set a normal vector for each face or vertex coordinate.

    The diffuse light is calculated by the the Dot product of the light vector and the normal vector attribute of the mesh. For a directional light, the light vector is the same for the entire scene. If the normal vector is constant for the entire geometry, then the result of the dot product is constant for the entire geometry and thus the light, too.
    See also GLSL fixed function fragment program replacement


    In your code there is a basic mistake. A static variable is initialized only once:

    static GLfloat cp[] = { u[1]*v[2] - u[2]*v[1], 
                           u[2]*v[0] - u[0]*v[2], 
                           u[0]*v[1] - u[1]*v[0] };
    

    You've to assign the variable every time when you call the function:

    static GLfloat cp[3];
    cp[0] = u[1]*v[2] - u[2]*v[1]; 
    cp[1] = u[2]*v[0] - u[0]*v[2]; 
    cp[2] = u[0]*v[1] - u[1]*v[0];  
    

    When you calculate the normal vector, then you've to ensure, that the normal vector points out of the mesh. Invert the normal vector to see how the direction of the vector effects the light.

    GLfloat* calcNormal(GLfloat* a,GLfloat* b,GLfloat* c){
        // define target plane
        GLfloat u[] = {b[0]-a[0], b[1]-a[1], b[2]-a[2]};
        GLfloat v[] = {c[0]-a[0], c[1]-a[1], c[2]-a[2]};
        // calculate cross product
        static GLfloat cp[3];
        cp[0] = u[1]*v[2] - u[2]*v[1]; 
        cp[1] = u[2]*v[0] - u[0]*v[2]; 
        cp[2] = u[0]*v[1] - u[1]*v[0];
        // normalize
        GLfloat length = sqrt(cp[0]*cp[0] + cp[1]*cp[1] + cp[2]*cp[2]);
        for(int i=0;i<3;i++) cp[i] /= -length; // <- invert the normal vector
        return cp;
    }
    

    Since the normal vectors are calculated by the cross product of 2 sides of the triangular primitives, the direction of the normal vector depends on the winding order of the primitives. This means the winding order of all the primitives has to be the same.


    If the winding order varies, this means some primitives are clockwise and others are counter-clockwise, then you can try to compensate this by setting (see glLightModel)

    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
    

    In this case the material parameters for front and back faces have to be equal. (See glMaterial).