Objectives:
To Learn about lighting and material properties
General summary on how to add light to a scene:
These are the steps required to add lighting to your scene (RED-Book):
Create, select, and position one or more light sources.
Create and select a lighting model, which defines the
level of global ambient light and the effective location of the
viewpoint (for the purposes of lighting calculations).
Define material properties for the objects in the scene.
We need vertex normals (which are required for the smooth shading
used by OpenGL). As I mentioned in class, we could take the cross
product of two vectors in the plane of a polygon and use this for the
normal at all the vertices of the polygon. However, if we're using a
fixed light direction this will give exactly the same lighting at each
interior point. We could instead come up with different normals at all
the vertices. The way to estimate these is to average the normals of
all polygons (usually triangles) which meet at a given vertex.
Let's look at a tetrahedron with vertices at (0,0,0), (1,0,0),
(0,1,0), and (0,0,1) again. What would the normal be at the origin? One
way to compute the normal is to find two vectors and compute their
cross products, right?
So, for the three faces that meet (left, right, and bottom) the
normals are vectors:
n(left) = - i + 0 j + 0 k n(right) = 0 i + 0 j - k n(bottom) = 0 i - j + 0 k |
<-1,0,0>, <0,-1,0> and <0,0,-1>, so
the normal would be in the direction <-1,-1,-1>. (We'd divide the
three components by sqrt(3) if we want the normal 'normalized'). Try
finding the normals at the other three vertices. Then try lighting the
tetrahedron using the four normals you've estimated. The result should
be a rounded tetrahedron, much less stark than that obtained with
uniform lighting on each face. There are other ways to get surface
normals. In the case of a mathematically defined surface, such as a
sphere, we can calculate the precise normal at each point. In third
semester calculus you learn about finding the "gradient vector" to get
the normal this way. If we have a surface z = f(x,y), then
differentiating the function with respect to x gets the tangent slope
in the x direction, and differentiating with respect to y gets the
tangent slope in the y direction. The cross product of the two
resulting tangent vectors obtains the normal vector.
There are a few predefined shapes -- spheres, cylinders, toruses -- available in OpenGL. Here's a program which rotates a light in a circle at a fixed height above a torus:
/* A shiny torus illuminated by a light source rotating
overhead. */
#include <math.h>
#include <GL/glut.h>
double t = 0;
const double PI2= 2*3.14159265; // 2 PI
// This is global so that it can be changed in the idle function
GLfloat light_position[] = { 2.0, 4.0, 0};
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);
glRotatef(90,1,0,0); // flip the torus on its side
// Torus inner radius .5, outer radius 1
// 20 latitudes and 20 longitudes, sort of
glutSolidTorus(.5,1,20,20);
glFlush();
glutSwapBuffers();
}
void myinit()
{
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 };
glMaterialfv(GL_FRONT_AND_BACK, 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 */
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(){
light_position[0]=2*cos(t);
light_position[2]=2*sin(t);
t+=.1;
if(t>PI2)t-=PI2;
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glutPostRedisplay();
}
int main(int argc,char** argv)
{
glutInit(&argc,argv);
glutInitWindowSize(800,600);
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB|GLUT_DEPTH);
glutInitWindowPosition(100,100);
glutCreateWindow("Cubes");
glutDisplayFunc(display);
glutIdleFunc(idle);
myinit();
glutMainLoop();
return 0;
}
Here's a typical snapshot:
Our specular light is all white -- if we change it to red, then our
blue-green torus will reflect very little of it. The animation in the
idle call back changes the position of the light. In OpenGL, you can
define several light sources -- up to 8. The shininess of the material
is 50 -- this translate roughly to the exponent of the cosine of the
angle between the viewer and the ray reflected from the light source.
If we reduce this number, the surfaces appears softer, more like
plastic than metal.
Lab Activity (1)
Try the above code with four different value sets for values in
the following line:
1) Light at the center (0, 0,
0),
2) Keep only the x component and
set the rest to 0,
3) Keep only the y component and
set the rest to 0, and
4) Set the z component to any
value that creates a desirable image and set the rest to 0.
Lab Activity (2)
Modify the code such that the above torus appears in red. A quick
way to do this is to make a change in the:
GLfloat
mat_amb_diff[] = { 0.1, 0.5, 0.8, 1.0 };
If you have done it a different way, let me know.
Lab Activity (3)
Change the color back to blue. You should try fiddling with the
parameters in this program to get a feel for what they do. Here
is what you will create next. To create this shape, you will add
a new torus that is smaller than the one above and will rotate it 90
degree. You need to play with the dimension a bit and find out
which axis to rotate around.
Lab Activity (4)
Here is what you create next. To create this
one, you will use two different material for the two toruses that you
had created in the previous section.