Almost all of the articles and books that I've read state that the composition of the final color is: finalColor = ambientColor + lambertianTerm * diffuseColor ( = material color ) + specularIntensity * specularColor ( = light color )
lambertianTerm = dot( surfaceNormal, normalize( lightPos - surfacePos ) );
halfVector = normalize( normalize( lightPos - surfacePos ) + normalize( eyePos - surfacePos );
specularFactor = max( dot( halfVector, surfaceNormal ), 0.0f );
specularIntensity = pow( specularFactor, surfaceShininessLevel );
Here are some of the places that use the aforementioned approach for calculating the final color: OpenGL SuperBible 6th edition -> Rendering Techniques -> Lighting Models -> Blinn-Phong Lighting, Wikipedia -> Blinn–Phong shading model (see the fragment shader), and others.
There is a problem in the calculation of the final color: the light's color (also consider the multiple light sources case) does not participate in the formulation of the diffuse color. Consider the case where the shininess factor is a large number - 128.0 let's say, so that the specular spot is small and most of the object's area is colored using the diffuse term. Also let the object's color be green and the light's one - red. The output of the equation above if there is no ambient color is a partially lit purely green object with a small yellow spot on it = green + red = the light from the red light source reflected from the green object: This is not correct. Of course, green + red is yellow, but for sure you will see neither purely green ball nor a yellow specular spot. Hold a green shiny ball in your hand - for instance the big 6 from a billiard game, and then light it up with a red light - I could assure you you won't see only green diffuse and yellow specular. Instead you would see a blended green + red for the diffuse and more red specular spot. The best way I've found so far to calculate the final color - average blending:
finalColor = ambient
+ lambertianTerm * ( surfaceColor + lightColor ) / 2.0
+ specularIntensity * lightColor;
const vec4 srgbLuminanceFactor = vec4( 0.2125f, 0.7154f, 0.0721f, 1.0f );
vec4 overlay( vec4 baseColor, vec4 blendColor )
{
float luminance = dot( baseColor, srgbLuminanceFactor );
vec4 lowerLumOverlay = 2.0f * blendColor * baseColor;
if ( luminance < 0.45f )
{
return lowerLumOverlay;
}
const vec4 whiteColor = vec4( 1.0 );
vec4 higherLumOverlay = whiteColor - 2.0f * ( whiteColor - blendColor ) * ( whiteColor - baseColor );
return luminance > 0.55f
? higherLumOverlay
: mix( lowerLumOverlay, higherLumOverlay, ( luminance - 0.45f ) * 10.0f );
}
... but doesn't look good. Probably the light and object's color should be mixed in another linear ratio:
mix( surfaceColor, lightColor, ratio );
... or in a non-linear way which I cannot think of.
So the final question: what is the best calculation of the complete color? Also, tell me if I am missing something but to my opinion the pure green picture + pure yellow specular highlight is totally not a real world scenario.