start .
me .
HTML Document File : raymarched-reflections.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ray-ai</title>
<style>
canvas {
border: 1px solid #333;
}
/*
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: #1a1a1a;
}
*/
</style>
</head>
<body>
<canvas id="canvas" width="400" height="400"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.createImageData(canvas.width, canvas.height);
const data = imageData.data;
// Vector operations
const vec3 = (x, y, z) => ({x, y, z});
const add = (a, b) => vec3(a.x + b.x, a.y + b.y, a.z + b.z);
const sub = (a, b) => vec3(a.x - b.x, a.y - b.y, a.z - b.z);
const mul = (v, s) => vec3(v.x * s, v.y * s, v.z * s);
const dot = (a, b) => a.x * b.x + a.y * b.y + a.z * b.z;
const normalize = (v) => {
const len = Math.sqrt(dot(v, v));
return mul(v, 1 / len);
};
const reflect = (v, n) => sub(v, mul(n, 2 * dot(v, n)));
// Scene objects
const spheres = [
{pos: vec3(0, 1, 0), radius: 1},
{pos: vec3(2, 0.5, 2), radius: 0.5},
];
const boxes = [
{pos: vec3(-2, 1, -1), size: vec3(0.8, 0.8, 0.8)},
];
// Distance functions
const sdSphere = (p, s) => {
return Math.sqrt(dot(p, p)) - s.radius;
};
const sdBox = (p, b) => {
const q = vec3(
Math.abs(p.x) - b.size.x,
Math.abs(p.y) - b.size.y,
Math.abs(p.z) - b.size.z
);
const exterior = Math.sqrt(
Math.max(q.x, 0)**2 +
Math.max(q.y, 0)**2 +
Math.max(q.z, 0)**2
);
const interior = Math.min(Math.max(q.x, Math.max(q.y, q.z)), 0);
return exterior + interior;
};
const sdFloor = (p) => p.y;
// Scene SDF
const map = (p) => {
let minDist = sdFloor(p);
// Check spheres
for(let sphere of spheres) {
const sphereP = sub(p, sphere.pos);
const dist = sdSphere(sphereP, sphere);
minDist = Math.min(minDist, dist);
}
// Check boxes
for(let box of boxes) {
const boxP = sub(p, box.pos);
const dist = sdBox(boxP, box);
minDist = Math.min(minDist, dist);
}
return minDist;
};
// Normal calculation
const calcNormal = (p) => {
const e = 0.001;
const n = vec3(
map(vec3(p.x + e, p.y, p.z)) - map(vec3(p.x - e, p.y, p.z)),
map(vec3(p.x, p.y + e, p.z)) - map(vec3(p.x, p.y - e, p.z)),
map(vec3(p.x, p.y, p.z + e)) - map(vec3(p.x, p.y, p.z - e))
);
return normalize(n);
};
// Checkerboard pattern
const checkerboard = (p) => {
const scale = 2;
const x = Math.floor(p.x * scale);
const z = Math.floor(p.z * scale);
return (x + z) % 2 === 0 ? 0.2 : 0.8;
};
// Ray marching
const march = (ro, rd, maxSteps = 100, maxDist = 100, minDist = 0.01) => {
let t = 0;
for(let i = 0; i < maxSteps; i++) {
const p = add(ro, mul(rd, t));
const d = map(p);
if(d < minDist) return {hit: true, dist: t, pos: p};
if(t > maxDist) break;
t += d;
}
return {hit: false};
};
// Main render function
const render = () => {
const time = Date.now() * 0.001;
const cameraPos = vec3(
Math.sin(time * 0.5) * 6,
4,
Math.cos(time * 0.5) * 6
);
const lookAt = vec3(0, 0, 0);
const up = vec3(0, 1, 0);
// Camera setup
const forward = normalize(sub(lookAt, cameraPos));
const right = normalize(cross(forward, up));
const realUp = normalize(cross(right, forward));
for(let y = 0; y < canvas.height; y++) {
for(let x = 0; x < canvas.width; x++) {
const fx = (x / canvas.width) * 2 - 1;
const fy = (y / canvas.height) * 2 - 1;
// Ray direction
const rd = normalize(add(
add(
mul(forward, 1.5),
mul(right, fx)
),
mul(realUp, -fy)
));
let color = vec3(0, 0, 0);
let contribution = 1;
let ro = cameraPos;
let rayDir = rd;
// Reflection bounces
for(let bounce = 0; bounce < 3; bounce++) {
const result = march(ro, rayDir);
if(result.hit) {
const normal = calcNormal(result.pos);
// Basic lighting
const lightDir = normalize(vec3(1, 1, -1));
const diff = Math.max(0, dot(normal, lightDir));
// Material properties
let albedo;
if(result.pos.y < 0.01) {
// Floor
albedo = checkerboard(result.pos);
contribution *= 0.5;
} else {
// Objects
albedo = 1;
contribution *= 0.7;
}
color = add(color, mul(vec3(diff * albedo, diff * albedo, diff * albedo), contribution));
// Setup next bounce
ro = add(result.pos, mul(normal, 0.01));
rayDir = reflect(rayDir, normal);
} else {
// Sky color
color = add(color, mul(vec3(0.2, 0.3, 0.5), contribution));
break;
}
}
// Set pixel color
const i = (y * canvas.width + x) * 4;
data[i] = Math.min(255, color.x * 255);
data[i + 1] = Math.min(255, color.y * 255);
data[i + 2] = Math.min(255, color.z * 255);
data[i + 3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
requestAnimationFrame(render);
};
// Helper function for cross product
function cross(a, b) {
return vec3(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
);
}
// Start rendering
render();
</script>
<hr>
<a href=".">go to current directory contents</a>
<hr>
<script src="https://arkenidar.com/web/show-source.js"></script>
</body>
</html>