0
votes

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: purely green diffuse color + yellow specular highlight 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;

diffuse = yellow with a red accent, specular spot = almost purely red Tried overlay blending:

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.

1

1 Answers

2
votes

Surface color meaning is "what light's colors can be diffused from this surface".

Specular color meaning is "what light's colors can be reflected from this surface".

Light color meaning is "what light's colors reaching surface".

So you must multiply them:

   finalColor = ambient
             + lambertianTerm * surfaceColor * lightColor
             + specularIntensity * specularColor * lightColor;