Simple Raytracer woes...

Started by
11 comments, last by MrEvil 19 years, 6 months ago
I'm having trouble getting reflection working with my Java raytracer. So far, everything up to it seems to be working correctly -- tracing the rays, object intersection, lighting, hard shadows. I've tried implementing reflection from many tutorials/source code, but nothing has worked. That lead me to believe that it is some other part of my program that doesn't work, but like I said.. the other parts work fine. And, I removed other sections (lighting, shadows) and it still doesn't work. I pretty much understand the theory behind reflection (and some math), but I can't get it working. Perhaps I am using the wrong points for the ray or ray direction (I feel like I've tried almost everything though). I was wondering if anyone could take a look at my code and tell me of anything they see that needs to be changed/fixed. Here is an image of the current output. It uses a sphere, a box, an ambient light, and a point light directly above the objects. The following is part of the source code. If anyone needs me to, I can post more: part of main raytracer class

    public double[] trace(Ray ray, int depth) {

        double[] illum = { 0, 0, 0 };

        double closestHitDistance = 9999;
        Solid solidHit = null;

        double[] objHitDistance = new double[2];


        // check intersection of all the objects to find the closest one
        for (int i = 0; i < solidList.size(); i++) {
            Solid solid = (Solid) solidList.elementAt(i);

            int numHits = solid.intersect(ray, objHitDistance);

            if (numHits > 0) {
                if (objHitDistance[0] < closestHitDistance) {
                    closestHitDistance = objHitDistance[0];
                    solidHit = solid;
                }
            }
        }


        // a solid was hit -- continue with calculations
        if (solidHit != null) {

            // calculate hit point
            Point hitPoint = new Point(ray.sx + closestHitDistance * ray.dx,
                                       ray.sy + closestHitDistance * ray.dy,
                                       ray.sz + closestHitDistance * ray.dz);

            // calculate normal
            Vect normal = solidHit.normal(hitPoint);      // already normalized

            // get the texture of the solid hit
            Texture hitTexture = null;
            Object[] param = { hitPoint, solidHit };

            try {
                hitTexture = (Texture)solidHit.texture.invoke(null, param);
            } catch (Exception e) {
                System.out.println("Texture Error!");
                e.printStackTrace();
            }


            // BEGIN LOOP THROUGH LIGHTS
            for (int i = 0; i < lightList.size(); i++) {
                Light light = (Light) lightList.elementAt(i);


                // ambient light
                if (light.type == Light.AMBIENT) {
                    illum[0] += hitTexture.r * light.r;
                    illum[1] += hitTexture.g * light.g;
                    illum[2] += hitTexture.b * light.b;
                }

                // point light
                else if (light.type == Light.POINT) {

                    // check to see if the hitpoint is in the shadows
                    Ray rayToLight = new Ray(hitPoint.x + TINY*normal.x, // move hitpoint away from
                                             hitPoint.y + TINY*normal.y, // surface a small amount
                                             hitPoint.z + TINY*normal.z,
                                             light.x - hitPoint.x,
                                             light.y - hitPoint.y,
                                             light.z - hitPoint.z);
                    rayToLight.normalize();

                    boolean inShadow = false;

                    double[] a = new double[2];

                    // check for shadows
                    for (int j = 0; j < solidList.size(); j++) {
                        Solid solid = (Solid) solidList.elementAt(j);

                        // (ensure hitpoint is in front of camera)
                        if (solid.intersect(rayToLight, a) > 0 && a[0] > 0) {
                            inShadow = true;
                            break;
                        }
                    }

                    // if not in shadow, calculate light
                    if (!inShadow) {

                        double cos =          (light.x*normal.x  + light.y*normal.y  + light.z*normal.z) /
                                     Math.sqrt(light.x*light.x   + light.y*light.y   + light.z*light.z)  /
                                     Math.sqrt(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);

                        if (cos < 0) cos = 0;

                        // add light from point light source
                        illum[0] += hitTexture.r * light.r * cos;
                        illum[1] += hitTexture.g * light.g * cos;
                        illum[2] += hitTexture.b * light.b * cos;

                    }
                }
            } // END LOOP THROUGH LIGHTS


            if (depth < 1) {

                // reflection
                if (hitTexture.refl > 0) {

                    Vect eyeVect = new Vect(-ray.dx, -ray.dy, -ray.dz); // already normalized

                    Vect reflVect = new Vect();
                    double nDotE = normal.x*eyeVect.x + normal.y*eyeVect.y + normal.z*eyeVect.z;
                    reflVect.x = 2*nDotE*normal.x - eyeVect.x;
                    reflVect.x = 2*nDotE*normal.y - eyeVect.y;
                    reflVect.x = 2*nDotE*normal.z - eyeVect.z;
                    reflVect.normalize();

                    // move hitpoint out from hit surface a small amount
                    Point hp = new Point(hitPoint.x + TINY*normal.x,
                                         hitPoint.y + TINY*normal.y,
                                         hitPoint.z + TINY*normal.z);
                    hp.normalize();

                    // cast new reflected ray from hit point to reflected direction
                    Ray reflRay = new Ray(hp.x, hp.y, hp.z, reflVect.x, reflVect.y, reflVect.z);
                    reflRay.normalize();

                    double[] reflIllum = trace(reflRay, depth + 1);

                    illum[0] += reflIllum[0] * hitTexture.refl;
                    illum[1] += reflIllum[1] * hitTexture.refl;
                    illum[2] += reflIllum[2] * hitTexture.refl;

                    // check bounds and round for illum (0.0 < illum < 1.0)
                    for (int i = 0; i < 3; i++)
                        if (illum > 1) illum = 1;
                }
            }
            return illum;
        }

        // no solids were hit -- return background color
        else {
            return illum;
        }
    }





    /**
     * Render the picture.
     */
    public void paint(Graphics g) {
        double sx, sy;
        double hei = getHeight(), wid = getWidth();

        for (int x = 0; x < wid; x++) {
            for (int y = 0; y < hei; y++) {
                sx = (x - wid/2) / (wid/2);
                sy = (hei/2 - y) / (wid/2);

                Ray ray = new Ray(0,0,5,sx,sy,-5);
                ray.normalize();

                // trace the ray
                double[] illum = trace(ray, 0);

                g.setColor(new Color((float)illum[0],(float)illum[1],(float)illum[2]));

                g.fillRect(x, y, 1, 1);
            }
        }
    }



ray class

class Ray
{
  double sx, sy, sz;
  double dx, dy, dz;

  public Ray() {}

  public Ray(double sx, double sy, double sz, double dx, double dy, double dz)
  {
    this.sx = sx;
    this.sy = sy;
    this.sz = sz;
    this.dx = dx;
    this.dy = dy;
    this.dz = dz;
  }

  public void normalize()
  {
    double t = Math.sqrt(dx*dx + dy*dy + dz*dz);
    if (t != 0)
    {
      dx /= t;
      dy /= t;
      dz /= t;
    }
  }
}



Thanks.
Advertisement
Noticed some bugs:


Vect eyeVect = new Vect(-ray.dx, -ray.dy, -ray.dz); // already normalized

Vect reflVect = new Vect();
double nDotE = normal.x*eyeVect.x + normal.y*eyeVect.y + normal.z*eyeVect.z;
reflVect.x = 2*nDotE*normal.x - eyeVect.x;
reflVect.y = 2*nDotE*normal.y - eyeVect.y;
reflVect.z = 2*nDotE*normal.z - eyeVect.z;
reflVect.normalize();

First,there's simple typo[grin] - bold it's corrected version. You have typed x x x instead of x y z. Common typo.

Other than that looks more or less right but you might have same typo in other places.
And, reflection formule:
V'=V-2*N*(V dot N)
where V it's ray direction vector(your ray.d) and V' it's reflected V, reflVect, N is normal . There's absolutely no point in making "to eye" vector! In fact it only flips sign of result.
Also, V' (reflected vector) have same length after reflection as V.

Also normal _must_ be normalized berofe doing it reflection. General reflection formule is
V'=V-2*N*((V dot N)/(N dot N));
and if N is unit-length, (N dot N) = 1


Also, in other places in you code
double cos = (light.x*normal.x + light.y*normal.y + light.z*normal.z) /
Math.sqrt(light.x*light.x + light.y*light.y + light.z*light.z) /
Math.sqrt(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);

i see you threat your normal as non-unit-length . (if it's unit length ,normal.x*normal.x + normal.y*normal.y + normal.z*normal.z = 1.0 and sqrt(1.0)=1.0)
Thanks for the reply.

I see that dumb .x .x .x error now :). To be honest, I just pounded out one of my 5 versions of the reflected code and didn't check it real well before I posted it. I had previously used the formula you suggested, and now, with it, I get the correct wrong output.

Here's an image with the updated reflected code.

new reflection code:
                // reflection                if (hitTexture.refl > 0) {                    // V'=V-2*N*(V dot N)                    Vect refl = new Vect();                    double vDotN = ray.dx*normal.x + ray.dy*normal.y + ray.dz*normal.z;                    refl.x = ray.dx - 2*normal.x*vDotN;                    refl.y = ray.dy - 2*normal.y*vDotN;                    refl.z = ray.dz - 2*normal.z*vDotN;                    // move hitpoint out from hit surface a small amount                    Point hp = new Point(hitPoint.x + TINY*normal.x,                                         hitPoint.y + TINY*normal.y,                                         hitPoint.z + TINY*normal.z);                    hp.normalize();                    // cast new reflected ray from hit point to reflected direction                    Ray reflRay = new Ray(hp.x, hp.y, hp.z, refl.x, refl.y, refl.z);                    reflRay.normalize();                    double[] reflIllum = trace(reflRay, depth + 1);                    illum[0] += reflIllum[0] * hitTexture.refl;                    illum[1] += reflIllum[1] * hitTexture.refl;                    illum[2] += reflIllum[2] * hitTexture.refl;                    // check bounds and round for illum (0.0 < illum < 1.0)                    for (int i = 0; i < 3; i++)                        if (illum > 1) illum = 1;                }


Also, I normalize the the normal in my solid's (object's) code, before I actually return it from the function, so it is always normalized.

I'm slightly confused as to when things should and should not be normalized, if you (or someone else) could clear that up for me. Is it a good idea to normalize it right away? Is there any reason I would not want to use a normalized vector? Or does it all just depend on the way I want to do my math? I admit I could use a little reading up on my math, if anyone has any good links.
Quote:Original post by retard
Thanks for the reply.

I see that dumb .x .x .x error now :). To be honest, I just pounded out one of my 5 versions of the reflected code and didn't check it real well before I posted it. I had previously used the formula you suggested, and now, with it, I get the correct wrong output.

Here's an image with the updated reflected code.

new reflection code:
*** Source Snippet Removed ***

Also, I normalize the the normal in my solid's (object's) code, before I actually return it from the function, so it is always normalized.

I'm slightly confused as to when things should and should not be normalized, if you (or someone else) could clear that up for me. Is it a good idea to normalize it right away? Is there any reason I would not want to use a normalized vector? Or does it all just depend on the way I want to do my math? I admit I could use a little reading up on my math, if anyone has any good links.

edit: image: nice, you're almost there. Probably it looks so because of that unnecessary "normalize" on the end of my post

Normalized vector have length of 1. It's not "right" or "wrong" to normalize direction vectors, just if you pre-normalize directions and normals you typically can use simplified formules.
For example: if |N|?=1 *, you should use
V'=V-2*N*((V dot N)/(N dot N));
for reflection.
and if |N|=1 then (N dot N)=1 so
V'=V-2*N*(V dot N);

In your text i saw
double cos = (light.x*normal.x + light.y*normal.y + light.z*normal.z) /
Math.sqrt(light.x*light.x + light.y*light.y + light.z*light.z) /
Math.sqrt(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);

and if normal is surely unit length i would write

double cos = (light.x*normal.x + light.y*normal.y + light.z*normal.z) /
Math.sqrt(light.x*light.x + light.y*light.y + light.z*light.z)

because if normal is unit-length,

Math.sqrt(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z)=1

It's just matter of convention - it's easier to find if you expect vector to be unit-length or not.

*******
*(by "?=" mean we don't know if it's == or != or > or <)

and i found other strange thing:

hp.normalize();
hp it's hitpoint, right? So it's should not be normalized. Remember, normalize mean scale the vector to make it be unit-length. Imagine line from 0,0,0 to point. Imagine you step 1.0 along line from 0,0,0 (or imagine a intersection with sphere at 0,0,0 with radius 1). You'll have normalized position.

[Edited by - Dmytry on October 10, 2004 2:28:59 AM]
Thanks for the explanation. Makes more sense now. I fixed the "cos" formula.

I had only normalized the hitpoint because it's the only way I got results that resembled something (bad reason, huh).

Without normalizing it, I get this
i did small copypaste typoo , for cos should be
double cos = (light.x*normal.x + light.y*normal.y + light.z*normal.z) /
Math.sqrt(light.x*light.x + light.y*light.y + light.z*light.z)

anyway.
You probably have several other x y z -> x x x and similar typos everywhere. Probably you have _wrong_ normal and don't notice it w/o refnections.Or your rays intersect with back side of sphere.

Also, your hitpoint distance may be wrong for sphere as well (i'm sure it is), and you would not notice .
You need to assert everything:
Assert that hitPoint is on sphere;
assert that normal is unit-length,

Also, it's bad idea to have intersection routine that returns hitpoint and then have separate function to get normal.
If you "care about performance" (you "optimized" things because you don't need normal for light rays), you should have 2 different ray intersection functions, one for shadows, that takes a line segment and return a boolean(intersected/not intersected),and other function for rays, that take a ray and it's direction and returns hit distance, normal, and optionally hit position for closest hit.

anyway. If you have that "XXX works ok without YYY" but if you try to add YYY and can't do it, it always have bugs, it's mean, something is wrong with your XXX and you don't notice it without YYY. (it doesn't mean YYY have no bugs, but at least, explains why you can't make YYY to work). So your reflection is probably correct and many,many other things may be incorrect and go unnoticed w/o reflection.
Thanks again for the advice, Dmytry. I'm going to go through the whole thing and rework it all. I actually just recently went back to this project after not touching it for many months, so I'm also trying to remember how I did everything in the first place.

I know what you mean when you say adding something can screw it up (by showing that what you had before was screwed up). A while ago I tried to add refraction and I had an assload of errors that I found and fixed because that (imagine me trying to get that working when I have these issues with reflection!).
I think:

// Vrevert = -V
// V'=2*N*(Vrevert dot N) - Vrevert
// (V dot N will be >=0; they both point away from the object)
Vect eyeVect = new Vect(-ray.dx, -ray.dy, -ray.dz);
Vect reflVect = new Vect();
double nDotE = normal.x*eyeVect.x + normal.y*eyeVect.y + normal.z*eyeVect.z;
reflVect.x = 2*nDotE*normal.x - eyeVect.x;
reflVect.x = 2*nDotE*normal.y - eyeVect.y;
reflVect.x = 2*nDotE*normal.z - eyeVect.z;
reflVect.normalize();

is the same as

// V'=V-2*N*(V dot N)
// (V dot N will be <=0; V points towards the object & N away // from the object)
Vect refl = new Vect();
double vDotN = ray.dx*normal.x + ray.dy*normal.y + ray.dz*normal.z;
refl.x = ray.dx - 2*normal.x*vDotN;
refl.y = ray.dy - 2*normal.y*vDotN;
refl.z = ray.dz - 2*normal.z*vDotN;

So his way to calculate the reflection vector was correct.
Quote:Original post by Anonymous Poster
I think:

// Vrevert = -V
// V'=2*N*(Vrevert dot N) - Vrevert
// (V dot N will be >=0; they both point away from the object)
Vect eyeVect = new Vect(-ray.dx, -ray.dy, -ray.dz);
Vect reflVect = new Vect();
double nDotE = normal.x*eyeVect.x + normal.y*eyeVect.y + normal.z*eyeVect.z;
reflVect.x = 2*nDotE*normal.x - eyeVect.x;
reflVect.x = 2*nDotE*normal.y - eyeVect.y;
reflVect.x = 2*nDotE*normal.z - eyeVect.z;
reflVect.normalize();

is the same as

// V'=V-2*N*(V dot N)
// (V dot N will be <=0; V points towards the object & N away // from the object)
Vect refl = new Vect();
double vDotN = ray.dx*normal.x + ray.dy*normal.y + ray.dz*normal.z;
refl.x = ray.dx - 2*normal.x*vDotN;
refl.y = ray.dy - 2*normal.y*vDotN;
refl.z = ray.dz - 2*normal.z*vDotN;

So his way to calculate the reflection vector was correct.

i know. I haven't said he is wrong. He is just doing unnecessary sign flipping. And obviously, because a-b=(-b)-(-a), his formule gives correct result. But if you want to make something work at all you should do things in most readable way and try not to do unnecessary things.

I spotted only 2 bugs: .x .x .x typo , and normalizing hp . Also i think normal or hit distance or both is wrong.
also: ray-sphere intersection routine from my math lib.
//// Ray-sphere intersection. // p=(ray origin position - sphere position),// d=ray direction,// r=sphere radius,// i1=first intersection distance,// i2=second intersection distance// i1<=i2// i1>=0// returns true if intersection found,false otherwise.// bool RaySphereIntersect(const Vec3d &p, const Vec3d &d,double r, double &i1, double &i2){	real64 det,b;	b = -DotProd(p,d);	det=(b*b) - (p.x*p.x + p.y*p.y + p.z*p.z) + r*r;	if (det<0){		return false;	}//else	det= sqrt(det);	i1= b - det;	i2= b + det;// everyone makes typos. I took my intersection code from my // other code that accidently had p=Sphere_position - Ray_origin// and obviously my b had wrong sign, so b - det gave wrong results.// i instantly spotted the bug at my next assertions.// then only after after checking i commented out// the checks.	/*	assert((abs((p+d*i1).Length()-r)/r)<0.0001);        assert((abs((p+d*i2).Length()-r)/r)<0.0001);	//addlog(abs((p+d*i1).Length()-r));        //addlog(abs((p+d*i2).Length()-r));	*/	//	// intersecting with ray?	if(i2<0) return false;	if(i1<0)i1=0;	return true;};

This topic is closed to new replies.

Advertisement