Black hole ray tracer
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>
Status | Released |
Platforms | Windows, macOS, Linux, Android |
Author | G Magma JavaScript |
Tags | black-hole, ray, ray-tracer, tracer |
Leave a comment
Log in with itch.io to leave a comment.