<p>I made this ray tracer in javascript to run on html with imageData. I have been using the book &quot;Ray Tracing in One Weekend&quot; as a reference (<a href="https://raytracing.github.io/books/RayTracingInOneWeekend.html" rel="nofollow noreferrer">https://raytracing.github.io/books/RayTracingInOneWeekend.html</a>) 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):</p>
<pre><code>var ctx = cvs.getContext(&quot;2d&quot;);
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: &quot;sphere&quot;,
      point: {
        x: 0,
        y: 0,
        z: 0,
      },
    },
    {
      type: &quot;plane&quot;,
      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&lt;scene.objects.length; i++){
    var object = scene.objects[i];
    if (object.type == &quot;plane&quot;){
      object.vector = unitVector(object.vector);
    }
  }
  for (var x =0; x&lt;w; x++){
      for(var y =0; y&lt;h; y++){
        var color = {x:0,y:0,z:0};
        for (var i=0; i&lt;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&lt;scene.objects.length; i++){
    var object = scene.objects[i];
    if (object.type == &quot;sphere&quot;){
      var dist = raySphereIntersect(object, ray);
    }else if(object.type == &quot;plane&quot;){
      var dist = rayPlaneIntersect(object, ray);
    }
    if (dist !== undefined &amp;&amp; dist &lt; 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&lt;min) return min;
  if (x&gt;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 &lt;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&lt;0){
    return;
  }else{
    return d;
  }
}

function surface(ray, scene, object, p, iter){
  if (iter &lt;= 0) {
    return {x:0,y:0,z:0};
  }
  if (p == null){
    return scene.skybox.color;
  }else{
    if (object.type == &quot;sphere&quot;){
      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) &lt; 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};
}
</code></pre>
<p>html here:</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;raytracing&lt;/title&gt;
    &lt;canvas id='viewport' width = '256' height = '256'&gt;&lt;/canvas&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;main.js&quot;&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;/body&gt;
&lt;/html&gt;
</code></pre>

CC BY-SA 4.0
2022-03-31T00:34:19.620Z
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.
playerJX1
CC BY-SA 4.0
2022-04-02T22:59:40.163Z
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.
pmw1234
<raytracing><shading><javascript><diffuse>
Ray tracing of a diffuse material sphere doesn't work, the sphere just shades black
playerJX1
# Ray tracing of a diffuse material sphere doesn't work, the sphere just shades black

(Asked by: username_0 on 2022-03-31)

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>

```

**Tags:** raytracing, shading, javascript, diffuse

> **Comments:**
> **username_0**: 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.  
> **username_1**: 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.  

---

## 0 answers

