0
$\begingroup$

I made this ray tracer in javascript to run on html with imageData. I have been using the book "Ray Tracing in One Weekend" as a reference (https://raytracing.github.io/books/RayTracingInOneWeekend.html) and I am on the step where I create a diffuse material (essentially the surfaces with diffuse materials picks up the color of another object through randomly generated rays in a recursive process) Apparently, the diffuse material works on the plane I create but it doesn't work on the sphere. I have been trying to troubleshoot the problem but I don't seem to be able to find the root of the problem. The places that could potentially be problematic are the ray-sphere intersection function and the normal calculation but I just couldn't debug it. It would be very helpful if someone could hel me debug this code. This is the script (main.js):

var ctx = cvs.getContext("2d");
var h = cvs.height;
var w = cvs.width;
var data = ctx.getImageData(0,0, w, h);
var scene ={};
scene.skybox = {
  color: {
    x:150,
    y:200,
    z:250,
  }
}
scene.camera = {
    point: {
      x: 0,
      y: 0,
      z: 10,
    },
    fieldOfView: 45,
    vector: {
      x: 0,
      y: 0,
      z: 0,
    },
  };

scene.objects = [
    {
      type: "sphere",
      point: {
        x: 0,
        y: 0,
        z: 0,
      },
    },
    {
      type: "plane",
      point: {
        x: 0,
        y: 1,
        z: 0,
      },
      vector:{
        x:0,
        y:-1,
        z:0,
      },
    },
  ];
window.onload=init();
function init(){
  var w_key = 87;
  var a_key = 65;
  var s_key = 83;
  var d_key = 68;
  render();
  window.onkeydown= function(gfg){
    if(gfg.keyCode === a_key){
      scene.camera.point.x+=1;
      render();
    };
    if(gfg.keyCode === d_key)
    {
      scene.camera.point.x-=1;
      render();
    };
    if(gfg.keyCode === w_key){
      scene.camera.point.y-=1;
      render();
    };
    if(gfg.keyCode === s_key)
    {
      scene.camera.point.y+=1;
      render();
    };
  };
}
function render(){
  var start = performance.now();
  var camera = scene.camera;
  var eVec = unitVector(subtract(camera.vector, camera.point));
  var eVecRight = unitVector(crossProduct(eVec, {x:0, y:1, z:0}));
  var eVecUp = unitVector(crossProduct(eVecRight, eVec));
  var halfFOV = (Math.PI * (camera.fieldOfView / 2)) / 180;
  var halfW = Math.tan(halfFOV);
  var halfH = (h/w)*halfW;
  var W = halfW*2;
  var H = halfH*2;
  var Wpx = W/(w-1);
  var Hpx = H/(h-1);
  var index;
  var spx = 50;
  var ray = {
      point: camera.point,
  };
  for (var i=0; i<scene.objects.length; i++){
    var object = scene.objects[i];
    if (object.type == "plane"){
      object.vector = unitVector(object.vector);
    }
  }
  for (var x =0; x<w; x++){
      for(var y =0; y<h; y++){
        var color = {x:0,y:0,z:0};
        for (var i=0; i<spx; i++){
          var xcomp = scaleVector(eVecRight, (x+Math.random()) * Wpx - halfW);
          var ycomp = scaleVector(eVecUp, (y+Math.random()) * Hpx - halfH);
          ray.vector = unitVector(add3Vectors(eVec, xcomp, ycomp));
          color = addVectors(gamma(scaleVector(trace(ray, scene, 20), 1/spx)),color);
        }
        index = (y*w+x)*4;
        data.data[index]=clamp(color.x, 0, 255);
        data.data[index+1]=clamp(color.y, 0, 255);
        data.data[index+2]=clamp(color.z, 0, 255);
        data.data[index+3]=255;
      }
  }
  ctx.putImageData(data, 0,0);
  var end = performance.now();
  console.log(end-start);
}

function trace(ray, scene, iter){
  var distObj = SceneIntersect(ray, scene);
  if (distObj[0] === Infinity){
    var pointOfIntersect = null;
  }else{
    var pointOfIntersect = addVectors(scene.camera.point, scaleVector(ray.vector, distObj[0]));
  }
  return surface(ray, scene, distObj[1], pointOfIntersect, iter);
}

function SceneIntersect(ray, scene){
  var closest = [Infinity, null];
  for (var i=0; i<scene.objects.length; i++){
    var object = scene.objects[i];
    if (object.type == "sphere"){
      var dist = raySphereIntersect(object, ray);
    }else if(object.type == "plane"){
      var dist = rayPlaneIntersect(object, ray);
    }
    if (dist !== undefined && dist < closest[0]){
      closest = [dist, object];
    }
  }
  return closest;
}

function gamma(a){
  return {
    x:Math.sqrt(a.x),
    y:Math.sqrt(a.y),
    z:Math.sqrt(a.z),
  };
}
function dotProduct(a,b){
    return a.x*b.x+a.y*b.y+a.z*b.z;
}

function unitVector(a){
    m = Math.sqrt(dotProduct(a,a));
    return {
        x: a.x/m,
        y: a.y/m,
        z: a.z/m
    };
}

function scaleVector(a, m){
  return {
    x:a.x*m,
    y:a.y*m,
    z:a.z*m
  }
}

function subtract(a,b){
    return {
        x: a.x- b.x,
        y: a.y- b.y,
        z: a.z - b.z
    };
}

function crossProduct(a,b){
    return {
       x: a.y*b.z-a.z*b.y,
       y: a.z*b.x-a.x*b.z,
       z: a.x*b.y-a.y*b.x
    };
}

function add3Vectors(a,b,c){
  return {
    x:a.x+b.x+c.x,
    y:a.y+b.y+c.y,
    z:a.z+b.z+c.z
  }
}

function addVectors(a,b){
  return{
    x:a.x+b.x,
    y:a.y+b.y,
    z:a.z+b.z
  }
}

function reflect(a,n){
  return subtract(scaleVector(n, 2*dotProduct(n,a)),a);
}

function clamp(x, min, max){
  if(x<min) return min;
  if (x>max) return max;
  return x;
}

function raySphereIntersect(sphere, ray){
  var ec = subtract(sphere.point, ray.point);
  var v = dotProduct(ray.vector, ec);
  var ecDot = dotProduct(ec, ec);
  var s2 = sphere.radius * sphere.radius - ecDot + v*v;
  if (s2 <0){
      return;
  }else{
      return v - Math.sqrt(s2);
  }
}

function rayPlaneIntersect(plane, ray){
  var n = unitVector(plane.vector);
  var d = dotProduct(n,subtract(plane.point,ray.point))/dotProduct(ray.vector,n);
  if (d<0){
    return;
  }else{
    return d;
  }
}

function surface(ray, scene, object, p, iter){
  if (iter <= 0) {
    return {x:0,y:0,z:0};
  }
  if (p == null){
    return scene.skybox.color;
  }else{
    if (object.type == "sphere"){
      var normal = sphereNormal(object, p, ray);
      var target = add3Vectors(p, normal, randSphere());
      console.log(Math.sqrt(dotProduct(subtract(p,object.point),subtract(p,object.point))));
      return scaleVector(trace({point:p, vector: unitVector(subtract(target, p))}, scene, --iter), 0.5);
    }else{
      var normal = object.vector;
      var target = add3Vectors(p, normal, randSphere());
      return scaleVector(trace({point:subtract(p,{x:0.005, y:0.005, z:0.005}), vector: unitVector(subtract(target, p))}, scene, --iter), 0.5);
    }
  }
}

function sphereNormal(sphere, p, ray){
  var normal = scaleVector(subtract(p, sphere.point), 1/sphere.radius);
  var front = dotProduct(ray.vector, normal) < 0;
  normal = front ? normal : scaleVector(normal, -1);
  return normal;
}

function randSphere(){
  var a = Math.random()*2*Math.PI;
  var b = Math.random()*2*Math.PI;
  var x = Math.sin(a)*Math.cos(b);
  var y = Math.sin(a)*Math.sin(b);
  var z = Math.cos(a);
  return {x:x,y:y,z:z};
}

html here:

<!DOCTYPE html>
<html>
<head>
    <title>raytracing</title>
    <canvas id='viewport' width = '256' height = '256'></canvas>
    <script type="text/javascript" src="main.js"></script>
</head>
<body>

</body>
</html>
$\endgroup$
2
  • $\begingroup$ I highly doubt the problem is caused by the ray-sphere intersection function since I have used the same algorithm for a Phong shading ray tracer and it worked for shadows, Lambertian reflection, and specular reflection. I tried generating a normal map for the sphere and I thought it looked sort of incorrect. $\endgroup$
    – playerJX1
    Mar 31, 2022 at 0:34
  • $\begingroup$ While it is good to include code snippets when posting questions, try to keep the code snippets more focused on the area with the problem, leaving a link to an online repository like GitHub is better for long postings. $\endgroup$
    – pmw1234
    Apr 2, 2022 at 22:59

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.