Merlin0216
Merlin0216

Reputation: 335

Ray Tracing Reflections do not look right

I am currently writing a simple demo for a 3D ray tracing engine. The program basically has the following structure: An array stores all the planes using three coordinates. For every (2D) pixel on the screen I cast a ray and calculate the collision point with every plane. If the point is on the bounded plane (since mathematical planes are infinitely large), which is defined by its three points, AND the collision point is nearest to the camera, it is the selected collision point. Based on that information I cast a few more rays recursively to achieve a reflection effect. The result looks almost right, all the planes showed up at the correct positions, but it has (had) a problem: The reflections did not look correct: enter image description here

One problem I realised I had, was that if the ray hits a plane from the other side than the normal vector is pointing at. If that's the case I invert the normal vector by negating its components. The problem is, when I do that, exactly nothing changes. The result still looks the same. That's a bit suspicious, but maybe that's because I have a misconception of how the maths exactly works.

A second problem was that I needed to prevent reflection of the same plane the ray reflected from. And also if the nearest object is in front of the ray (direction of the reflection vector) and not behind it. After "fixing" both of these issues, this is the result I am stuck at: enter image description here

Nothing is how it should be and I am a bit frustrated since I looked over my code over and over and cannot figure out what the issue is.

This is the main code of my program:

function update() {
  ctx.clearRect(0, 0, width, height);
  for (let z = 0; z < resolution; z++) {
    for (let x = 0; x < resolution; x++) {

      // Create Ray
      let dx = cameraPosition.x + x - resolution/2;
      let dy = cameraPosition.y + focalLength;
      let dz = cameraPosition.z - z + resolution/2;
      let direction = new Vector(dx, dy, dz);
      let ray = new Ray(cameraPosition, direction);

      let color = castRay(ray, 3);
      ctx.fillStyle = color.toString();
      ctx.fillRect(x, y, 1, 1);
    }
  }
}


function castRay(ray, bounces) {
  let shortestDistance = Infinity;
  let intersectionInfo;
  let planeIndex;
  let intersects = false;

  for (let i = 0; i < planes.length; i++) {
    let plane = planes[i];
    let res = Geometry.intersect(plane, ray);
    if (res == null) continue;
    let isp = res.point;
    let dist = isp.toVector().length();

    let rightDirection = false;
    if (Geometry.dotProduct(res.reflectionVector, isp.toVector().sub(ray.startPoint)) >= 0) {
       if (!isp.equals(ray.startPoint)) {
         rightDirection = true;
       }
    }

    let planeWidth = Math.max(Math.abs(plane.p1.x - plane.p2.x), Math.abs(plane.p1.x - plane.p3.x), Math.abs(plane.p2.x - plane.p3.x));
    let planeHeight = Math.max(Math.abs(plane.p1.z - plane.p2.z), Math.abs(plane.p1.z - plane.p3.z), Math.abs(plane.p2.z - plane.p3.z));
    if (isp.x >= Math.min(plane.p1.x, plane.p2.x, plane.p3.x) && isp.x <= plane.p1.x + planeWidth) {
      if (isp.z >= Math.min(plane.p1.z, plane.p2.z, plane.p3.z) && isp.z <= plane.p1.z + planeHeight) {
        if (dist < shortestDistance && rightDirection) {
          intersects = true;
          shortestDistance = dist;
          planeIndex = i;
          intersectionInfo = res;
        }
      }
    }
  }

  if (!intersects) {
    let noiseValue = Math.floor(Math.random()*25);
    return new Color(noiseValue, noiseValue, noiseValue);
  }

  if (bounces > 0) {
    let color = planes[planeIndex].color;
    let direction = intersectionInfo.reflectionVector;
    let ray = new Ray(intersectionInfo.point, direction);
    return castRay(ray, bounces-1).combine(color, 1-planes[planeIndex].reflectivity);
  }

  return new Color(0, 0, 0);
}

That is the main part. All of the reflection and vector calculations are performed in the following code:

class Geometry {
  static intersect(plane, ray) {
    let a = plane.p1.toVector();
    let b = plane.p2.toVector();
    let c = plane.p3.toVector();
    let rayStart = ray.startPoint.toVector();
    let rayDirection = ray.direction.normalize();

    // Intersection Point
    let n = Geometry.crossProduct(a.sub(b), a.sub(c));
    let angle = Geometry.angleBetweenVectors(rayDirection, n.negate());
    if (angle >= Math.PI/2) {
       n = n.negate();
       angle -= Math.PI/2;
    }
    let t = -(Geometry.dotProduct(n, rayStart) - Geometry.dotProduct(n, a)) / Geometry.dotProduct(n, rayDirection);
    let intersectionPoint = rayStart.add(rayDirection.scale(t)).toPoint();

    // Reflection Vector
    n = n.normalize();
    n = n.scale(2*Geometry.dotProduct(rayDirection, n));
    let reflectionVector = rayDirection.sub(n);
    return {point: intersectionPoint, angle: angle, reflectionVector: reflectionVector};
  }

  static angleBetweenVectors(a, b) {
    return Math.acos(Geometry.dotProduct(a, b)/(a.length()*b.length()));
  }

  static dotProduct(v1, v2) {
    return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
  }

  static crossProduct(v1, v2) {
    let x = (v1.y*v2.z) - (v1.z*v2.y);
    let y = (v1.z*v2.x) - (v1.x*v2.z);
    let z = (v1.x*v2.y) - (v1.y*v2.x);
    return new Vector(x, y, z);
  }

}

So that's where I am stuck at. I would really appreciate it, if someone could point me in the correct direction... Thanks a lot!

Upvotes: 2

Views: 318

Answers (1)

Merlin0216
Merlin0216

Reputation: 335

I finally figured out what the error was. In fact it were two (easy to fix, not so easy to find) errors:

  • the 11th line of the castRay(...) function should be:

    let dist = isp.toVector().sub(ray.startPoint).length();

I calculated the distance relative to the origin and not to the start point of the ray vector. Now it is fixed.

  • the 14th line of the same function should be:

    if (Geometry.dotProduct(ray.direction, isp.toVector().sub(ray.startPoint)) >= 0) { ...

This is what the result looks like now: enter image description here

Upvotes: 1

Related Questions