Note: This is a section I removed from my normal mapping tutorial because register combiners are outdated and I expect I'll never use them again.


I use Register Combiners instead, which unfortunately are specific to the GeForce series of graphics cards, because you have much more control of rendering and can achieve many more effects (adding specular lighting, for instance.) Here's a way to use register combiners to set up basic per-pixel lighting for normal-mapping. This is the simplest lighting setup I use, just a 1 colored light normalmapping + ambient. I had accidentally left out the colored light in the first version I put up.

This setup uses 1 general combiner for 1 colored light and colored ambient.
   glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 1);

   // constant0 = 1 - Ambient
   // constant1 = Light Color
   glCombinerParameterfvNV(GL_CONSTANT_COLOR0_NV, constant0);
   glCombinerParameterfvNV(GL_CONSTANT_COLOR1_NV, constant1);

   // RGB   A = glcolor, B = Texture0 output = A dot B
   // C = constant1 D = Diffuse Texture  output = C*D
   glCombinerInputNV (GL_COMBINER0_NV,  GL_RGB, GL_VARIABLE_A_NV, GL_PRIMARY_COLOR_NV, GL_EXPAND_NORMAL_NV, GL_RGB);
   glCombinerInputNV (GL_COMBINER0_NV,  GL_RGB, GL_VARIABLE_B_NV, GL_TEXTURE0_ARB, GL_EXPAND_NORMAL_NV, GL_RGB);
   glCombinerInputNV (GL_COMBINER0_NV,  GL_RGB, GL_VARIABLE_C_NV, GL_CONSTANT_COLOR1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
   glCombinerInputNV (GL_COMBINER0_NV,  GL_RGB, GL_VARIABLE_D_NV, GL_TEXTURE1_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
   glCombinerOutputNV(GL_COMBINER0_NV,  GL_RGB,  GL_SPARE0_NV, GL_SPARE1_NV, GL_DISCARD_NV,  GL_NONE, GL_NONE, GL_TRUE, GL_FALSE, GL_FALSE);

   // FINAL COMBINER  E*F = Lighting * (Texture * Light Color)  A = Ambient C = Texture   equation: AB + (1-A)C
   glFinalCombinerInputNV(GL_VARIABLE_A_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);	
   glFinalCombinerInputNV(GL_VARIABLE_B_NV, GL_E_TIMES_F_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);	
   glFinalCombinerInputNV(GL_VARIABLE_C_NV, GL_TEXTURE1_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB);	
   glFinalCombinerInputNV(GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB); 

   glFinalCombinerInputNV(GL_VARIABLE_E_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB); 
   glFinalCombinerInputNV(GL_VARIABLE_F_NV, GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);   // Spare0RGB = lighting

   glEnable(GL_REGISTER_COMBINERS_NV);

If you wanted to do two colored lights in 1 pass you could use the secondary color for the light vectors of the second light. If you wanted to add specular lighting, you could use the secondary color for half-angle vector instead.
OpenGL Setup for secondary color is like this:
     glEnableClientState(GL_SECONDARY_COLOR_ARRAY_EXT);
     glSecondaryColorPointerEXT ( 3, GL_FLOAT, 0, HalfAngle);

GL_SECONDARY_COLOR_NV is the constant used to access the secondary color array for Register Combiners.