Raytracing - how to combine diffuse and specular color?

10,108

Solution 1

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

Solution 2

This has long been an issue with the "specular + diffuse + ambient" model of illumination. Namely, it is a hack and therefore, has no guarantee of correctness.

If you're so keen on cementing your fundamentals first, take a look at the excellent book "Physically Based Ray Tracing" by Matt Pharr and Greg Humphreys.

Solution 3

You should read up on the Blinn/Phong model. Here some sample shader fragment code. Basically you scale the single components (ambient, diffuse, specular term) with their respective angles and add them up.

varying vec3 N;
varying vec3 v;    
void main (void)  
{  
   vec3 L = normalize(gl_LightSource[0].position.xyz - v);   
   vec3 E = normalize(-v); // we are in Eye Coordinates, so EyePos is (0,0,0)  
   vec3 R = normalize(-reflect(L,N));  

   //calculate Ambient Term:  
   vec4 Iamb = gl_FrontLightProduct[0].ambient;    

   //calculate Diffuse Term:  
   vec4 Idiff = gl_FrontLightProduct[0].diffuse * max(dot(N,L), 0.0);
   Idiff = clamp(Idiff, 0.0, 1.0);     

   // calculate Specular Term:
   vec4 Ispec = gl_FrontLightProduct[0].specular 
                * pow(max(dot(R,E),0.0),0.3*gl_FrontMaterial.shininess);
   Ispec = clamp(Ispec, 0.0, 1.0); 
   // write Total Color:  
   gl_FragColor = gl_FrontLightModelProduct.sceneColor + Iamb + Idiff + Ispec;     
}

taken from: http://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/lighting.php

Solution 4

Why not do (light + ambient) * diffuse + specular? ie adding the specular component after you have multiplied the lighting and shadows with the diffuse? This will give clear highlight.

Share:
10,108
OMH
Author by

OMH

iOS developer

Updated on July 19, 2022

Comments

  • OMH
    OMH almost 2 years

    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];
    
  • Marcus Borkenhagen
    Marcus Borkenhagen about 11 years
    It is not correct, that's the point. There cannot be a "guarantee of correctness" ;) - it's really just a way of coloring pixels with handy mathematical functions (e.g. pow for phong and plus some arbitrary value for ambient). At least that's the way I see it. Most importantly; PBRT FTW!!1
  • Rahul Banerjee
    Rahul Banerjee about 11 years
    While your point is 100% valid, a lot of people who jump into raytracing don't realize this fact -- they're usually swayed by the pretty pictures, without knowing that there's a whole bunch of tweaking necessary :)