start . me .
Directory path . web , canvas .

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>