Lighting in OpenGL is hard because you have to get light sources, materials, and surface normals all correct. If any one of them is wrong, it doesn't work at all.
As always, the key to successful learning is to keep things simple and change only one value at a time.
I'm going to give a number of 'rules' to follow. These are in fact useful simplifications, not commandments. Experienced OpenGL programmers don't follow these rules, and you will easily find contradictory opinions. I can only say that in my experience they work well to start with, and strongly urge you not to break these rules until you know exactly why you want to break them and what the implications are.
Don't go crazy with lights. Most scenes can be rendered quite comfortably with just one.
The light values for ambient, diffuse, etc are RGB colors. Use only white lights (R=G=B) and the fourth (alpha) value should always be 1.
Ambient light can be either specified globally (my preference) or on your primary light source only. Intensity should be in the 0.1 to 0.3 range, and never have multiple ambient lights.
Diffuse lighting is what photographers call 'soft' lighting. It should always be present, intensity 1.
Specular light is 'hard' lighting that creates highlights, sparkles, etc. Your primary light source always has specular intensity 1. If you need secondary lights as the equivalent of a photographers backlighting or extra flash, set the specular to 0 on those.
Lights have a position (x,y,z,,1) or a direction (x,y,z,0). Directional lights are easier to work with.
For a directional light the coordinates are a vector, the direction from which the light comes. Either the object or the light direction should be rotated away from the axes for the best lighting effect. (-1,1,1,0) is a light coming from over your left shoulder.
You can make something appear yellow by either coloring it yellow and shining a white light on it, or coloring it white and shining a yellow light on it. The first is easier, so lights are set up once and rarely altered. Materials are where most of your design will be done.
Ambient and diffuse material values should be identical. OpenGL allows you to specify both at the same time for this reason.
Very roughly, all materials are either:
When something is described as being a particular color, that means the diffuse color for matte and plastic surfaces, the specular for metals.
OpenGL has the capability to set material values from the current vertex color values through use of glColorMaterial. While this is very useful to experienced programmers, I don't recommend trying to use it straight away. Using glColorMaterial does not mean you don't have to set up materials for your objects, it just adds one more thing that can go wrong.
Surface normals are essential for lighting, and always irritating to calculate.
Where possible, use the glu_ and glut_ primitives such as spheres and cylinders. These library functions calculate the surface normals for you. They are therefore good for testing your own lights and materials because at least you can't get the normals wrong.
A surface normal is a vector perpendicular to the polygon (triangle, quadrangle, whatever) being lit. It is in local object coordinates, before any transformations. If you rotate or translate say, a cylinder, the surface normals get transformed with it so you don't have to worry about it.
When learning to calculate normals, start with a cube that you construct yourself. The top face of a cube has surface normal (0, 1, 0), straight up, and all the others are equally easy. You can write these values directly into an array within the program code.
Once you have a light source and suitable material working properly with the cube, comment out the hard-coded normals. Every 3D programmer should have a copy of the function that takes three vertices as parameters, derives a pair of vectors, and does a cross product to return the surface normal. Test this on the vertices of your cube: since you know what the answers should be, you'll quickly discover any bugs and how to use the function correctly.
Once you can calculate the surface normal for a single cube face, you can do so for any piece of OpenGL geometry.
The surface normal, as the name indicates, has to be a normal
or unit length vector, otherwise the lighting calculations
won't work. If you use glScale anywhere, surface normals may
no longer be correct because the scaling will shorten or lengthen the
vector. At startup, write
so OpenGL will check and if necessary renormalise all your surface
normals. In Olden Times this could slow your program down
significantly, but these days it doesn't matter.
Back to 3D Stuff