The art of good 3D lighting effects are achieved by making use of vectors. A vector can be defined as a quantity that has a direction and a magnitude. When vectors are used in 3D lighting effects, we are really only concerned with the direction of vectors and not so much with the size of the vector.
Each polygon you create for your scene or model has a normal vector. The normal vector of your polygon, is the direction perpendicular to the surface of the polygon (the direction that your polygon is facing). For example, if you look towards a cube, the direction of the front polygon's normal is directed towards you.
To make use of OpenGL's lighting features, you add "lights" (or light sources) to your scene. Lights has a position and ambient, diffuse and specular properties.
OpenGL makes it super easy to add light sources to your scene. To add a light source to your scene, all you need to do is to give it a position; configure a few settings such as the ambient, specular and diffuse values; set up the normals for your polygons; and OpenGL will automatically perform all the complex maths required to make your model shine.
Ambient light, is the light that is every where present in your scene, and is distributed equally to all polygons. The direction of your polygon normal does not affect the ambience. The brighter the ambient light, the brighter ALL polygons and models will appear to be in your scene.
Diffuse light comes from a particular point source, such as the sun. It determines how bright your polygon will be, based on the direction of your polygon's normal vector. You can imagine for yourself that if your polygon is facing towards a diffuse light source it will appear to be brighter than when it is facing away from the light source.
Specular light also comes from a single point, but reflects differently than diffuse light. Specular light uses the properties of a material to determine brightness. For example, a smooth surface such as a metal will reflect specular light better than a dull surface such as plaster on a brick wall.
Light and color, when applied correctly, can really give your models and scenes exceptional beauty. As a habit, learn to experiment a lot with light and colors, for it will help you become a great 3D programming artist.
' make sure only back faces are culled and enable culling OpenGL.glCullFace OpenGL.GL_BACK OpenGL.glEnable OpenGL.GL_CULL_FACE ' instantiate memory blocks used to configure our light with Dim light_position As new MemoryBlock(16) Dim light_ambience As new MemoryBlock(16) Dim light_diffANDspec As new MemoryBlock(16) ' define position of the light light_position.SingleValue(0) = 0.0 light_position.SingleValue(4) = 0.0 light_position.SingleValue(8) = 1.0 light_position.SingleValue(12) = 0.0 ' define ambience of the light light_ambience.SingleValue(0) = 0.0 light_ambience.SingleValue(4) = 0.0 light_ambience.SingleValue(8) = 0.0 light_ambience.SingleValue(12) = 1.0 ' define diffuse and specular of the light light_diffANDspec.SingleValue(0) = 1.0 light_diffANDspec.SingleValue(4) = 1.0 light_diffANDspec.SingleValue(8) = 1.0 light_diffANDspec.SingleValue(12) = 1.0 ' apply light settings OpenGL.glLightfv OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, light_position ' set position OpenGL.glLightfv OpenGL.GL_LIGHT0, OpenGL.GL_AMBIENT, light_ambience ' set ambience OpenGL.glLightfv OpenGL.GL_LIGHT0, OpenGL.GL_DIFFUSE, light_diffANDspec ' set diffuse OpenGL.glLightfv OpenGL.GL_LIGHT0, OpenGL.GL_SPECULAR, light_diffANDspec ' set specular ' enable our light OpenGL.glEnable OpenGL.GL_LIGHT0 ' enable lighing OpenGL.glEnable OpenGL.GL_LIGHTING ' enable overwriting of material properties with vertex colors OpenGL.glEnable OpenGL.GL_COLOR_MATERIAL7. Add the following code to the Render event of OpenGLSurface1 (You can access the Render event by double clicking on OpenGLSurface1 and then selecting the event from the list):
OpenGL.glPushMatrix ' save matrix ' clear the background OpenGL.glClearColor(0, 0, 0, 1) OpenGL.glClear(OpenGL.GL_COLOR_BUFFER_BIT) ' move back a bit so that we can see the object OpenGL.glTranslatef 0.0, 0.0, -5.0 ' rotate our model OpenGL.glRotated(30, 1, 0, 0) ' 30 degrees around x-axis OpenGL.glRotated(30, 0, 1, 0) ' and then 30 degrees around y-axis ' set color to green OpenGL.glColor3d 0, 1, 0 ' draw green cube OpenGL.glNormal3d 0, 0, 1 ' front polygon normal OpenGL.glBegin OpenGL.GL_POLYGON ' front polygon OpenGL.glVertex3d 1, 1, 1 OpenGL.glVertex3d -1, 1, 1 OpenGL.glVertex3d -1, -1, 1 OpenGL.glVertex3d 1, -1, 1 OpenGL.glEnd OpenGL.glNormal3d 1, 0, 0 ' right polygon normal OpenGL.glBegin OpenGL.GL_POLYGON ' right polygon OpenGL.glVertex3d 1, 1, 1 OpenGL.glVertex3d 1, -1, 1 OpenGL.glVertex3d 1, -1, -1 OpenGL.glVertex3d 1, 1, -1 OpenGL.glEnd OpenGL.glNormal3d -1, 0, 0 ' left polygon normal OpenGL.glBegin OpenGL.GL_POLYGON ' left polygon OpenGL.glVertex3d -1, 1, 1 OpenGL.glVertex3d -1, 1, -1 OpenGL.glVertex3d -1, -1, -1 OpenGL.glVertex3d -1, -1, 1 OpenGL.glEnd OpenGL.glNormal3d 0, 0, -1 ' back polygon normal OpenGL.glBegin OpenGL.GL_POLYGON ' back polygon OpenGL.glVertex3d 1, 1, -1 OpenGL.glVertex3d 1, -1, -1 OpenGL.glVertex3d -1, -1, -1 OpenGL.glVertex3d -1, 1, -1 OpenGL.glEnd OpenGL.glNormal3d 0, 1, 0 ' top polygon normal OpenGL.glBegin OpenGL.GL_POLYGON ' top polygon OpenGL.glVertex3d 1, 1, 1 OpenGL.glVertex3d 1, 1, -1 OpenGL.glVertex3d -1, 1, -1 OpenGL.glVertex3d -1, 1, 1 OpenGL.glEnd OpenGL.glNormal3d 0, -1, 0 ' bottom polygon normal OpenGL.glBegin OpenGL.GL_POLYGON ' bottom polygon OpenGL.glVertex3d 1, -1, 1 OpenGL.glVertex3d -1, -1, 1 OpenGL.glVertex3d -1, -1, -1 OpenGL.glVertex3d 1, -1, -1 OpenGL.glEnd OpenGL.glPopMatrix() ' restore matrix8. Add the following code to the Resized event of OpenGLSurface1 (You can access the Resized event by double clicking on OpenGLSurface1 and then selecting the event from the list):
' set the viewport rectangle OpenGL.glViewport 0, 0, OpenGLSurface1.Width, OpenGLSurface1.Height ' set up the perspective projection settings OpenGL.glMatrixMode OpenGL.GL_PROJECTION OpenGL.glLoadIdentity OpenGL.gluPerspective 60.0, OpenGLSurface1.Width / OpenGLSurface1.Height, 1, 100.0 ' select and reset the modelview matrix OpenGL.glMatrixMode OpenGL.GL_MODELVIEW OpenGL.glLoadIdentity9. Add the following code to the Paint event of Window1 (You can access the Paint event by double clicking on the titlebar of Window1 and then selecting the event from the list):
' refresh the OpenGL surface OpenGLSurface1.Render10. Save and run the project.
In the OpenGLSurface1.Open event we make a call to glCullFace using the GL_BACK parameter. This instructs OpenGL to only draw polygons that faces toward us. Polygons facing away from us can be ignored since they are not visible. Passing GL_CULL_FACE to the glEnable function call, enables the feature of OpenGL to ignore away-facing polygons. Enabling this culling method saves a lot of processing time.
Next we instantiate three MemoryBlocks that will be used to set up our lighting settings. light_position will store the 3D position of our light in our 3D world. light_ambience stores the ambient properties of our light and light_diffANDspec stores the diffuse and specular values of our light. We use one MemoryBlock for both the diffuse and specular values, since the values for these two settings are exactly the same.
We assign the position of out light to the light_position MemoryBlock, with SingleValue(0) representing the x-coordinate, SingleValue(4) the y-coordinate and SingleValue(8) the z-coordinate. Our light will therefore therefore be positioned at (0, 0, 1).
In the light_ambience MemoryBlock, SingleValue(0) represents the red ambient value, SingleValue(4) the green value, SingleValue(8) the blue value and SingleValue(12) the alpha value. This gives us the black color (0,0,0,1). We will get into alpha values in a later tutorial. For a desciption on color values have a look at Tutorial 3 - A splash of color. In a similar fashion we assign a pure white color, (1, 1, 1, 1) to the light_diffANDspec MemoryBlock.
Once we've assigned our preferred position, ambient, diffuse and specular values to our MemoryBlocks, we apply these settings to our light with the calls to glLightfv. OpenGL allows you to configure up to 8 lights. The GL_LIGHT0 parameter indicates to OpenGL that we want to apply the settings to Light 0.
Now we can enable our light (light 0) by calling glEnable with the GL_LIGHT0 parameter. Then we enable OpenGL's lighting features with a call to glEnable, using the GL_LIGHTING parameter. To add a second light you simply repeat the process, but use the GL_LIGHT1 parameter instead of the GL_LIGHT0 parameter. Note that you only have to call glEnable with the GL_LIGHTING parameter once, and that it is not necessary to call it for each light you set up in your 3D world.
For our model to reflect its colors correctly, we need to enable GL_COLOR_MATERIAL with a call to glEnable. For now you don't have to worry about why this is required, but just take note that colors and materials are two different approaches to filling your polygons with some kind of a texture.
Experiment with the position and light settings. What happens when you set the ambient light to a pure white (1,1,1,1) or even a light red (1,0,0,1)? or change the position of your light to (2,0,1) and see what happens to the colors of the cube.