Last Updated April 15, 2010
By default, OpenGL renders the colour of a primitive depending on the value set at the vertices by glColor*(). This produces a very artificial view of the scene. In reality, the apparent color of objects depends on three things;
In order to display lighting effects, lighting needs to be enabled (lighting is disabled by default). When lighting is enabled, OpenGL ignores the effects of glColor*() [unless glColorMaterial() is enabled).
OpengGL allows us to set various properties of an objects material as well as the lighting of the scene. OpenGL will use these settings in a lighting model to attempt the render each part of the scene accurately give the various material and lighting settings.
The lighting model is calculated at the vertices and the results used to colour the faces.
Check out this application to demonstrate the effects of materials and lighting.
Ambient light: Light bouncing around a scene (usually assumed constant throughout a scene).
Diffuse Light: Light direct from a light source.
Specular Reflection: Very bright intense spot of light reflected from shiny objects.
The apparent color of an object is a result of the object reflecting or absorbing the light that strikes it. A 'Red' object appears red because it absorbs the Green and blue components of the light and reflects most of the red light. A red object under a blue light appears black, as there is no red light to reflect.
OpenGL allows us to specify how much red, green and blue light is reflected for both ambient and diffuse light. For most materials the setting for ambient and diffuse reflection is the same.
GLfloat materialAmbDiff[] = {0.9f, 0.1f, 0.1f, 1.0f}; // create an array of RGBA values glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, materialAmbDiff); // set the diffuse & ambient reflection colour for the front of faces
There are two settings to consider for specular reflection;
GLfloat materialSpecular[] = {1.0f, 1.0f, 1.0f, 1.0f}; // create an array of RGBA values (White) GLfloat materialShininess[] = {128.0f}; // select value between 0-128, 128=shiniest glMaterialfv(GL_FRONT, GL_SPECULAR, materialSpecular); // set the colour of specular reflection glMaterialfv(GL_FRONT, GL_SHININESS, materialShininess); // set shininess of the material
Once material properties have been set, all rendered surfaces will take these settings, until glMaterial*() is called again.
A list of relectivity settings for some materials is available here.
The windows application at H:\Graphics\OpenGLTutorials\lightmaterial.exe allows you to dynamically play with material settings. Drag the mouse over the green numbers to change them. Right click on the model, to change it.
If lighting is disabled, vertices are give the current colour. To enable lighting calculations;
glEnable(GL_LIGHTING); // switch on lighting ... glDisable(GL_LIGHTING); // switch off lighting
Lighting can be switched on and off in the same scene, e.g. display text would normally be rendered without lighting, while the scene should be lit.
By default, there is a small amount of ambient light in the scene, so objects are visible, even if no light sources are defined. Global ambient light can be changed using glLightModel*().
OpenGL supports at least 8 individual light sources (known as GL_LIGHTo, GL_LIGHT1, ..., GL_LIGHT7). Lights need to be enabled before their effects are seen.
glEnable(GL_LIGHT0); // switch on light0
The most important properties of a light are it's colour and position. The following code gives GL_LIGHT0 a Blue colour and moves it high on the y-axis;
GLfloat light_color[]={0.0, 0.0, 1.0, 1.0}; GLfloat light_position[]={0.0, 10.0, 0.0, 0.0}; glLightfv(GL_LIGHT0, GL_DIFFUSE, light_color); // set color of diffuse component glLightfv(GL_LIGHT0, GL_SPECULAR, light_color); // set color of specular component glLightfv(GL_LIGHT0, GL_POSITION, position); // set position
The function glLight*() can be used to set many more light settings, such as attenuation, spot light properties, additional ambient light.
OpenGL treats the position and direction of a light source just as it treats the position of a geometric primitive. In other words, a light source is subject to the same matrix transformations as a primitive. More specifically, when glLight*() is called to specify the position or the direction of a light source, the position or direction is transformed by the current modelview matrix and stored in eye coordinates. This means you can manipulate a light source's position or direction by changing the contents of the modelview matrix stack.
Now suppose you want to rotate or translate the light position so that the light moves relative to a stationary object. One way to do this is to set the light position after the modeling transformation, which is itself changed specifically to modify the light position. You can begin with the same series of calls in an init() routine early in the program. Then, probably within an event loop, you need to perform the desired modeling transformation (on the modelview stack) and reset the light position. Here's what such code might look like:
void display(GLint spin) { GLfloat light_position[] = { 0.0, 0.0, 1.5, 1.0 }; glPushMatrix(); glTranslatef(0.0, 0.0, -5.0); glPushMatrix(); glRotated((GLdouble) spin, 1.0, 0.0, 0.0); glLightfv(GL_LIGHT0, GL_POSITION, light_position); glPopMatrix(); auxSolidTorus(); glPopMatrix(); ... }
The windows application at H:\Graphics\OpenGLTutorials\lightposition.exe will let you play with relative position of the light. Right Click on the model, to change it.
An important factor in calculating the appearance of a surface with respect to light sources is the relative orientation of the surface. A normal vector a is a vector perpendicular to a surface. A normal vector should be specified for each vertex. OpenGL performs lighting calculations for each vertex and interpolates the vertex lighting across the polygon to which it belongs.
Specifying a normal for a vertex is simple, just call glNormal*() before specifying the vertex;
GLfloat normalA[]={1.0,1.0,0.0); // fill an array with 3 components of a vector glNormal3fv(normalA); // specify the current normal glVertex3f(1.0,2.0,5.0); // specify a vertex
void NormalVector(GLfloat p1[3], GLfloat p2[3], GLfloat p3[3], GLfloat n[3]){ GLfloat v1[3],v2[3]; // two vectors //calculate two vectors lying on the surface // v1=p2-p1 // v2=p3-p2 for(int i=0;i<3;i++){ v1[i]=p2[i]-p1[i]; v2[i]=p3[i]-p2[i]; } // calculate cross product of two vectors ( n= v1 x v2) n[0]=v1[1]*v2[2] - v2[1]*v1[2]; n[1]=v1[2]*v2[0] - v2[2]*v1[0]; n[2]=v1[0]*v2[1] - v2[0]*v1[1]; } //done
This code displays a lit rotating cube (here is the win32 .exe).
bool Prog_Init() { //////////////////////////////////// //your initialization code goes here //////////////////////////////////// glEnable(GL_DEPTH_TEST); // check for depth glEnable(GL_NORMALIZE); // automatically convert normals to unit normals glEnable(GL_LIGHTING); // switch on lighting glEnable(GL_LIGHT0); // switch on light0 GLfloat light_color[]={1.0, 1.0, 1.0, 1.0}; GLfloat light_position[]={10.0, 10.0, 10.0,0.0}; glLightfv(GL_LIGHT0, GL_DIFFUSE, light_color); // set color of diffuse component glLightfv(GL_LIGHT0, GL_SPECULAR, light_color); // set color of specular component glLightfv(GL_LIGHT0, GL_POSITION, light_position); // set position return(true);//return success } ////////////////////////////////////////////////////////////////////////////// //CLEANUP ////////////////////////////////////////////////////////////////////////////// void Prog_Done() { ////////////////////////// //clean up code goes here ////////////////////////// } //Calculate a normal to a the surface containing 3 points //last parameter is an array to be filled with components of the normal vector //A normal vector is the cross product of two vectors lying on the surface //NOTE: code does not produce a unit normal!! void NormalVector(GLfloat p1[3], GLfloat p2[3], GLfloat p3[3], GLfloat n[3]){ GLfloat v1[3],v2[3]; // two vectors //calculate two vectors lying on the surface // v1=p1-p2 // v2=p3-p2 for(int i=0;i<3;i++){ v1[i]=p2[i]-p1[i]; v2[i]=p3[i]-p2[i]; } // calculate cross product of two vectors n[0]=v1[1]*v2[2] - v2[1]*v1[2]; n[1]=v1[2]*v2[0] - v2[2]*v1[0]; n[2]=v1[0]*v2[1] - v2[0]*v1[1]; } //done ////////////////////////////////////////////////////////////////////////////// //MAIN GAME LOOP ////////////////////////////////////////////////////////////////////////////// void Prog_Loop() { /////////////////////////// //main game logic goes here /////////////////////////// static float angle =0.0f; // angle of rotation (in degrees) // Prepare to draw glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//clear buffers glMatrixMode(GL_MODELVIEW); // reset modelview matrix glLoadIdentity(); if(!stopped)angle+=0.2f; // increase angle of rotation if(angle>=360.0f){angle-=360.0f;} // reset angle glTranslatef(0.0f,0.0f,-5.0f); // move everything back 5 units ... glRotatef(30, 1.0f, 0.0f, 0.0f); //... then rotate everything a little around x-axis glRotatef(angle, 0.0f, 1.0f, 0.0f); //... then rotate everything around y-axis // points of a cube static GLfloat points[][3]={ {1.0f,1.0f,1.0f}, {1.0f,1.0f,-1.0f}, {-1.0f,1.0f,-1.0f}, {-1.0f,1.0f,1.0f}, {1.0f,-1.0f,1.0f}, {1.0f,-1.0f,-1.0f}, {-1.0f,-1.0f,-1.0f}, {-1.0f,-1.0f,1.0f}}; GLfloat normal[3]; GLfloat materialAmbDiff[] = {0.9f, 0.5f, 0.3f, 1.0f}; // create an array of RGBA values glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, materialAmbDiff); // set the diffuse & ambient reflection colour for the front of faces GLfloat materialSpecular[] = {1.0f, 1.0f, 1.0f, 1.0f}; // create an array of RGBA values (White) GLfloat materialShininess[] = {128.0f}; // select value between 0-128, 128=shiniest glMaterialfv(GL_FRONT, GL_SPECULAR, materialSpecular); // set the colour of specular reflection glMaterialfv(GL_FRONT, GL_SHININESS, materialShininess); // set shininess of the material // there are more efficient ways of specifying vertexes and normals, // we'll look at those later in the course glBegin(GL_QUADS); //top NormalVector(points[0],points[1],points[2],normal); glNormal3fv(normal); glVertex3fv(points[0]); glVertex3fv(points[1]); glVertex3fv(points[2]); glVertex3fv(points[3]); //front NormalVector(points[0],points[3],points[7],normal); glNormal3fv(normal); glVertex3fv(points[0]); glVertex3fv(points[3]); glVertex3fv(points[7]); glVertex3fv(points[4]); //back NormalVector(points[1],points[5],points[6],normal); glNormal3fv(normal); glVertex3fv(points[1]); glVertex3fv(points[5]); glVertex3fv(points[6]); glVertex3fv(points[2]); //left NormalVector(points[3],points[2],points[6],normal); glNormal3fv(normal); glVertex3fv(points[3]); glVertex3fv(points[2]); glVertex3fv(points[6]); glVertex3fv(points[7]); //right NormalVector(points[1],points[0],points[4],normal); glNormal3fv(normal); glVertex3fv(points[1]); glVertex3fv(points[0]); glVertex3fv(points[4]); glVertex3fv(points[5]); //bottom NormalVector(points[4],points[7],points[6],normal); glNormal3fv(normal); glVertex3fv(points[4]); glVertex3fv(points[7]); glVertex3fv(points[6]); glVertex3fv(points[5]); glEnd(); // finished drawing, show our work SwapBuffers(g_hdc); // bring backbuffer to foreground }
OpenGL Programming Guide (Red Book) Chapter 6 - Lighting
© Ken Power 1996-2016