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.