Raytracing - how to combine diffuse and specular color?

OMH picture OMH · Mar 25, 2013 · Viewed 9.4k times · Source

I've been reading numerous articles about ray tracing and shading, but my ray traced image does not look too good. I'm talking about the very bright green area near the specular highlight. The result green color is maxed out here, it looks like. How to adjust colors and/or shading calculations to make this look correct?

(Never mind the silly code, I'm just trying to get the principles correct first).

Here's how it looks:

enter image description here

Here is the diffuse component only:

enter image description here

Here is the specular component only:

enter image description here

EDIT: Changing diffuse to Color diffuseColor = ColorMake(0.0f, 0.6f, 0.0f); Then the image looks like this:

enter image description here

Point lightPosition = PointMake(-100.0f, 100.0f, -100.0f);
Color diffuseColor  = ColorMake(0.0f, 1.0f, 0.0f);
Color specularColor = ColorMake(1.0f, 1.0f, 1.0f);
Color pixelColor    = ColorMake(0.0f, 0.0f, 0.0f);

//  Trace...

            // Diffuse
            Point intersectionPosition = PointMake(x, y, z);
            Vector intersectionNormal = VectorMake((x - xs) / rs, (y - ys) / rs, (z - zs) / rs);
            Vector intersectionNormalN = VectorNormalize(intersectionNormal);
            Vector lightVector          = VectorSubtract(lightPosition, intersectionPosition);
            VectorlightVectorN         = VectorNormalize(lightVector);
            float      cosTheta        = VectorDotProduct(intersectionNormalN, lightVectorN);
            if (cosTheta < 0.0f)
            {
                cosTheta = 0.0f;
            }

            pixelColor = ColorMultScalar(diffuseColor, cosTheta);

            // Specular
            Vector incomVector    = VectorSubtract(intersectionPosition, lightPosition);
            Vector incomVectorN   = VectorNormalize(incomVector);

            float myDot = - VectorDotProduct(incomVectorN, intersectionNormalN);
            float myLen = 2.0f * myDot;

            Vector tempNormal     = VectorMultScalar(intersectionNormalN, myLen);
            Vector reflectVector  = VectorAdd(tempNormal, incomVectorN);
            Vector reflectVectorN = VectorNormalize(reflectVector);

            float mySpec = MAX(-VectorDotProduct(reflectVectorN, incomVectorN), 0);
            mySpec       = powf(mySpec, 5);

            specularColor = ColorMultScalar(specularColor, mySpec);
            pixelColor    = ColorAdd(pixelColor, specularColor);
            pixelColor    = ColorClamp(pixelColor);

            [self putPixelatX:i andY:j andR:pixelColor.r andG:pixelColor.g andB:pixelColor.b];

Answer

Marcus Borkenhagen picture Marcus Borkenhagen · Mar 25, 2013

The problem is, when you compute the diffuse color of the sphere you already have a small area of pixels that are 1 or very near 1 (in the green channel). Adding up the "phong" component (which has a value near one in all the channels) to that, gives an area of pixels that are >= 1. When you then clamp the color value to 1, that area of >=1 stands out.

You can test this by using a picture editing program and do the "addition" overlay of the two layers (the phong layer above the diffuse one). This gives the result you see - and what is to be expected.

You can avoid the problem by a number of measures:

  1. You can dim the light source a bit, that is multiply the diffuse-strength you calculated form the cosine by the brightness - say 0.8 or 0.7.
  2. You could limit the color-saturation (green that is) of the sphere and make it less greeen ;)
  3. Use a tone mapping operator to normalize the color values of the pixels to a [0..1] range - that topic however is vast - wikipedia might give a good introduction. You don't even have to go full-on with these, as for non-physically based rendering simpler tone mapping operators may well suffice and produce results that are pleasant to the eye.

My ray tracing experiments date back some years, but you can try these things.


Update 1:

One thing I noticed, when you gamma correct your output image - the effect is less pronounced ;) - OK, that is kinda patchy.

The ultimate solution is to go phsically-correct or just use another shading model: Wikipedia on Specular highlight.


Update 2:

One actual solution would be to calculate the phong contribution to the final pixel color (that is your variable mySpec). The idea is to only use a part of the diffuse component where the specular is actually not 0, that is, if you have some specular component, you don't actually see the diffuse component that much (or at all) so it can be adjusted for:

float diffuseContrib = 1.f - mySpec;

That should look nice, but I am not really sure how correct it actually is :).

Note however; this assumes that your specular and diffuse components are in the range [0..1].

My result looks that way:

diffuse contribution calculated using the specular contribution