A downloadable ray tracer for Windows, macOS, Linux, and Android

Copy and paste the text into w3schools html editor for the best reaults.

can generate metal plastic and glass.

My code:


<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Ray Tracing v1</title>

  <style>

    body { margin: 0; overflow: hidden; }

    canvas { display: block; }

  </style>

</head>

<body>

  <canvas id="rayCanvas"></canvas>

  <script>

    const canvas = document.getElementById("rayCanvas"), ctx = canvas.getContext("2d");

    canvas.width = window.innerWidth;

    canvas.height = window.innerHeight;

    const light = { x: -5, y: 5, z: -5 }, camera = { x: 0, y: 0, z: 0 };

    const v3 = {

      add: (a, b) => ({ x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }),

      sub: (a, b) => ({ x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }),

      dot: (a, b) => a.x * b.x + a.y * b.y + a.z * b.z,

      mul: (a, s) => ({ x: a.x * s, y: a.y * s, z: a.z * s }),

      norm: a => {

        const len = Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z);

        return { x: a.x / len, y: a.y / len, z: a.z / len };

      }

    };

    class Sphere {

      constructor(c, r, col, refl) {

        this.center = c;

        this.radius = r;

        this.color = col;

        this.reflective = refl;

      }

      intersect(o, d) {

        const oc = v3.sub(o, this.center);

        const a = v3.dot(d, d), b = 2 * v3.dot(oc, d), c = v3.dot(oc, oc) - this.radius * this.radius;

        const D = b * b - 4 * a * c;

        return D > 0 ? (-b - Math.sqrt(D)) / (2 * a) : null;

      }

      normal(p) {

        return v3.norm(v3.sub(p, this.center));

      }

    }

    const spheres = [

      new Sphere({ x: 0, y: 0, z: -5 }, 1, { r: 255, g: 165, b: 0 }, true),

      new Sphere({ x: 2, y: -1, z: -8 }, 2, { r: 255, g: 255, b: 255 }, true)

    ];

    function traceRay(o, d, depth = 0) {

      let tmin = Infinity, hit = null, pt = null;

      for (let s of spheres) {

        const t = s.intersect(o, d);

        if (t && t < tmin) {

          tmin = t;

          hit = s;

          pt = v3.add(o, v3.mul(d, t));

        }

      }

      if (!hit) return { r: 0, g: 0, b: 0 };

      const n = hit.normal(pt), l = v3.norm(v3.sub(light, pt)), diff = Math.max(v3.dot(n, l), 0);

      let r = hit.color.r * diff, g = hit.color.g * diff, b = hit.color.b * diff;

      return { r: Math.min(r, 255), g: Math.min(g, 255), b: Math.min(b, 255) };

    }

    function render() {

      for (let y = 0; y < canvas.height; y++) {

        for (let x = 0; x < canvas.width; x++) {

          const d = v3.norm({

            x: (x - canvas.width / 2) / canvas.width,

            y: (y - canvas.height / 2) / canvas.height,

            z: -1

          });

          const col = traceRay(camera, d);

          ctx.fillStyle = `rgb(${col.r}, ${col.g}, ${col.b})`;

          ctx.fillRect(x, y, 1, 1);

        }

      }

    }

    // Camera movement

    const keys = { w: false, a: false, s: false, d: false };

    const speed = 0.2;

    window.addEventListener("keydown", (e) => {

      if (keys.hasOwnProperty(e.key)) keys[e.key] = true;

    });

    window.addEventListener("keyup", (e) => {

      if (keys.hasOwnProperty(e.key)) keys[e.key] = false;

    });

    function update() {

      if (keys.w) camera.z -= speed;

      if (keys.s) camera.z += speed;

      if (keys.a) camera.x -= speed;

      if (keys.d) camera.x += speed;

      render();

      requestAnimationFrame(update);

    }

    render();

    update();

  </script>

</body>

</html>








Emissive rendering:



    






<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Ray Tracing v1</title>

  <style>

    body { margin: 0; overflow: hidden; }

    canvas { display: block; }

  </style>

</head>

<body>

  <canvas id="rayCanvas"></canvas>

  <script>

    const canvas = document.getElementById("rayCanvas"), ctx = canvas.getContext("2d");

    canvas.width = window.innerWidth;

    canvas.height = window.innerHeight;

    const camera = { x: 0, y: 0, z: 0 };

    let angle = 0;

    const v3 = {

      add: (a, b) => ({ x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }),

      sub: (a, b) => ({ x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }),

      dot: (a, b) => a.x * b.x + a.y * b.y + a.z * b.z,

      mul: (a, s) => ({ x: a.x * s, y: a.y * s, z: a.z * s }),

      norm: a => {

        const len = Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z);

        return { x: a.x / len, y: a.y / len, z: a.z / len };

      },

      dist: (a, b) => Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2 + (a.z - b.z) ** 2),

    };

    class Sphere {

      constructor(c, r, col, emissive = false) {

        this.center = c;

        this.radius = r;

        this.color = col;

        this.emissive = emissive;

      }

      intersect(o, d) {

        const oc = v3.sub(o, this.center);

        const a = v3.dot(d, d), b = 2 * v3.dot(oc, d), c = v3.dot(oc, oc) - this.radius * this.radius;

        const D = b * b - 4 * a * c;

        return D > 0 ? (-b - Math.sqrt(D)) / (2 * a) : null;

      }

      normal(p) {

        return v3.norm(v3.sub(p, this.center));

      }

    }

    class Plane {

      constructor(y, col) {

        this.y = y;

        this.color = col;

      }

      intersect(o, d) {

        if (Math.abs(d.y) < 1e-6) return null;

        const t = (this.y - o.y) / d.y;

        return t > 0 ? t : null;

      }

      normal() {

        return { x: 0, y: -1, z: 0 }; // Normal points down for ceiling

      }

    }

    const spheres = [

      new Sphere({ x: -2, y: 0, z: -5 }, 1, { r: 255, g: 165, b: 0 }, false),

      new Sphere({ x: 2, y: -1, z: -8 }, 2, { r: 255, g: 255, b: 255 }, true)

    ];

    const plane = new Plane(3, { r: 255, g: 140, b: 0 }); // Orange ceiling

    function traceRay(o, d) {

      let tmin = Infinity, hit = null, pt = null, normal = null, col = { r: 0, g: 0, b: 0 };

      for (let s of spheres) {

        const t = s.intersect(o, d);

        if (t && t < tmin) {

          tmin = t;

          hit = s;

          pt = v3.add(o, v3.mul(d, t));

          normal = s.normal(pt);

          col = s.color;

        }

      }

      const tPlane = plane.intersect(o, d);

      if (tPlane && tPlane < tmin) {

        tmin = tPlane;

        hit = plane;

        pt = v3.add(o, v3.mul(d, tPlane));

        normal = plane.normal();

        col = plane.color;

      }

      if (!hit) return { r: 0, g: 0, b: 0 };

      if (hit instanceof Sphere && hit.emissive) return hit.color;

      const lightSphere = spheres.find(s => s.emissive);

      const lightDir = v3.norm(v3.sub(lightSphere.center, pt));

      const diff = Math.max(v3.dot(normal, lightDir), 0);

      const dist = v3.dist(pt, lightSphere.center);

      const attenuation = Math.min(1 / (dist * dist * 0.3), 1);

      let r = col.r * diff * attenuation + lightSphere.color.r * 0.2;

      let g = col.g * diff * attenuation + lightSphere.color.g * 0.2;

      let b = col.b * diff * attenuation + lightSphere.color.b * 0.2;

      return { r: Math.min(r, 255), g: Math.min(g, 255), b: Math.min(b, 255) };

    }

    function render() {

      for (let y = 0; y < canvas.height; y++) {

        for (let x = 0; x < canvas.width; x++) {

          const d = v3.norm({

            x: (x - canvas.width / 2) / canvas.width,

            y: (y - canvas.height / 2) / canvas.height,

            z: -1

          });

          const col = traceRay(camera, d);

          ctx.fillStyle = `rgb(${col.r}, ${col.g}, ${col.b})`;

          ctx.fillRect(x, y, 1, 1);

        }

      }

    }

    const keys = { w: false, a: false, s: false, d: false };

    const speed = 0.2;

    window.addEventListener("keydown", (e) => {

      if (keys.hasOwnProperty(e.key)) keys[e.key] = true;

    });

    window.addEventListener("keyup", (e) => {

      if (keys.hasOwnProperty(e.key)) keys[e.key] = false;

    });

    function update() {

      if (keys.w) camera.z -= speed;

      if (keys.s) camera.z += speed;

      if (keys.a) camera.x -= speed;

      if (keys.d) camera.x += speed;

      render();

      requestAnimationFrame(update);

    }

    render();

    update();

  </script>

</body>

</html>

Download

Download
Ray Tracer v1.html 4.3 kB
Download
Ray Tracer v1.txt 4.3 kB

Leave a comment

Log in with itch.io to leave a comment.