1

I'm writing a basic Ray-tracer in effort to better understand the whole thing. I've come across an issue that's been holding me back for a little while now, Diffuse shading of a sphere. I've used a formula from the following source to calculate Sphere intersections, and the diffuse shading.

http://www.ccs.neu.edu/home/fell/CSU540/programs/RayTracingFormulas.htm

My code which calculates the shading (An attempted replication of the source code at the link) is shown before. For the most part the calculations appear correct on some spheres, at times, however depending on the lights position depends upon how correct/broken the spheres shading appears to be.

TVector intersect   (ray.getRayOrigin().getVectX() + t * (ray.getRayDirection().getVectX() - ray.getRayOrigin().getVectX()),
                    ray.getRayOrigin().getVectY() + t * (ray.getRayDirection().getVectY() - ray.getRayOrigin().getVectY()),
                    ray.getRayOrigin().getVectZ() + t * (ray.getRayDirection().getVectZ() - ray.getRayOrigin().getVectZ()));

//Calculate the normal at the intersect point
TVector NormalIntersect (intersect.getVectX() - (position.getVectX()/r), 
                        intersect.getVectY() - (position.getVectY()/r),
                        intersect.getVectZ() - (position.getVectZ()/r));

NormalIntersect = NormalIntersect.normalize();

//Find unit vector from intersect(x,y,z) to the light(x,y,z)
TVector L1 (light.GetPosition().getVectX() - intersect.getVectX(), 
            light.GetPosition().getVectY() - intersect.getVectY(),
            light.GetPosition().getVectZ() - intersect.getVectZ());
L1 = L1.normalize();
double Magnitude = L1.magnitude();

TVector UnitVector(L1.getVectX() / Magnitude,
                   L1.getVectY() / Magnitude,
                   L1.getVectZ() / Magnitude);

//Normalized or not, the result is the same
UnitVector = UnitVector.normalize();

float Factor = (NormalIntersect.dotProduct(UnitVector));
float kd = 0.9;             //diffuse-coefficient
float ka = 0.1;             //Ambient-coefficient
Color pixelFinalColor(kd * Factor * (color.getcolorRed())  +  (ka * color.getcolorRed()) ,
                      kd * Factor * (color.getcolorGreen())  + (ka * color.getcolorGreen())  ,
                      kd * Factor * (color.getcolorBlue()) +  (ka * color.getcolorBlue()) ,1);

Diffuse Shading Error

As you can see from the picture, some spheres appear to be shaded correctly, while others are completely broken. At first I thought the issue may lie with the UnitVector Calculation, however when I looked over it I was unable to find issue. Can anyone see the reason as to the problem?

Note: I'm using OpenGl to render my scene.

Update: I'm still having a few problems however I think they've mostly been solved thanks to the help of you guys, and a few alterations to how I calculate the unit vector. Updates shown below. Many thanks to everyone who gave their answers.

TVector UnitVector (light.GetPosition().getVectX() - intersect.getVectX(), 
                    light.GetPosition().getVectY() - intersect.getVectY(),
                    light.GetPosition().getVectZ() - intersect.getVectZ());

UnitVector = UnitVector.normalize();
float Factor = NormalIntersect.dotProduct(UnitVector);

//Set Pixel Final Color
Color pixelFinalColor(min(1,kd * Factor * color.getcolorRed())  +  (ka * color.getcolorRed()) ,
                      min(1,kd * Factor * color.getcolorGreen())  + (ka * color.getcolorGreen())  ,
                      min(1,kd * Factor * color.getcolorBlue()) +  (ka * color.getcolorBlue()) ,1);
Unknown
  • 131
  • 1
  • 4
  • 11
  • 2
    Make sure that the final color does not exceed the color range (0 through 1 or 255). Depending on the library, too bright colors may wrap over and become black. Insert a min: `pixelFinalColor(min(1, kd * Factor...` Do the same for `Factor = max(0, NormalIntersect...` – Nico Schertler Dec 26 '13 at 17:28
  • Thanks, good advice. After adding in the min(1) to the pixel Colour it seems to have fixed the problem on some of the spheres, however others in different positions have developed the same issue. If I also add the max(0) to factor, all objects become flat shaded a single tone, with no diffuse shading at all. Appreciate your help all the same .thank you – Unknown Dec 26 '13 at 18:23
  • If everything looks a single tone, it's probably unlit and just showing your ambient lighting. Are you sure your light is where you think it is? – timday Dec 26 '13 at 19:38
  • Positive, before the addition of max(0) Depending on where I move the light (x,y,z) changes how the spheres are shaded, and for the ones which aren't broken, it looks accurate. I've tested multiple lighting positions and that's definitely not the issue. thanks for your time, and input. – Unknown Dec 26 '13 at 19:42

2 Answers2

1

you misplaced braces in normal calculation it should be

TVector NormalIntersect ((intersect.getVectX() - position.getVectX())/r, 
                         (intersect.getVectY() - position.getVectY())/r,
                         (intersect.getVectZ() - position.getVectZ())/r);
Vasaka
  • 1,953
  • 1
  • 19
  • 30
  • I've updated that, sorry there may be some minor mistakes in the code from when I was attempting to fix it, this correction however made no difference to the end result. Thanks very much for your reply – Unknown Dec 26 '13 at 16:25
  • 1
    that is not a minor mistake and should break things badly unless your radius is close to 1 – Vasaka Dec 26 '13 at 17:13
  • Sorry I see your point, I only said minor due to it being something that was previously correct, just simply out of place in the code I've put online here. Thanks very much for your reply. With that fix in place do you have any idea as to why I'm getting these shading errors? Thanks – Unknown Dec 26 '13 at 17:47
1

Firstly, if your getRayDirection() is doing what it says it is, it's producing a direction, and not a gaze point, so you should not be subtracting the ray origin from it, which is a point. (Although directions and points are both represented by vectors, it does not make sense to add a point to a direction)

Also, you are normalising L1 and then taking its magnitude and dividing each of its components by that magnitude, to produce UnitVector, which you then call normalize on again. This is unnecessary: The magnitude of L1 after the first normalization is 1, you're normalizing the same vector 3 times, just use L1.

The last issue, is that of clamping. The variable you call Factor is the value cos(th) where th is the angle between the direction of light and the normal. But cos(th) has a range of [1,-1] and you want a range of only [0,1], so you must clamp Factor to that range:

Factor = max(0, min( NormalIntersect.dotProduct(UnitVector), 1));

(And remove the min calls in your production of color).

This clamping is necessary for faces whose normals are facing away from the light, which will have negative cos(th) values. The angle between their normal and the direction of light are greater than pi/2. Intuitively, they should appear as dark as possible with respect to the light in question, so we clamp them to 0).

Here's my final version of the code, which should work. I'm going to work on the assumption that you have scale(float) and operator +(const TVector &) etc defined on your TVector class, because they will quite plainly make your life easier. Also, for brevity, I'm going to call NormalIntersect, just normal:

TVector 
    intersect = ray.getRayOrigin() + ray.getRayDirection().scale(t),
    normal    = (intersect - position).normalize(),
    L1        = (light.GetPosition() - intersect).normalize();

float
    diffuse   = max(0, min(normal.dotProduct(L1), 1)),
    kd        = 0.1,
    ka        = 0.9;

Color pixelFinalColor = color.scale(kd * diffuse + ka);
amnn
  • 3,657
  • 17
  • 23
  • Great answer, thanks for your time. This has fixed my issues. As for the normalizing L1 too many times, I noticed that late last night, it was a somewhat silly error. Again thanks. – Unknown Dec 27 '13 at 12:21