🔍

WEBCAM&VOXEL experiments

COMPUTE SHADER UPGRADE COMPUTE SHADING + FOG + BLOOM

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Webcam Voxel Matrix</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            font-family: 'Inter', sans-serif;
            background: #111;
            color: #eee;
        }
        canvas {
            display: block;
        }
        .ui-container {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            pointer-events: none;
            text-align: center;
            z-index: 10;
            background: rgba(17, 17, 17, 0.9);
            transition: opacity 1s ease-in-out;
        }
        .ui-container.controls {
            top: 20px;
            left: 20px;
            width: auto;
            height: auto;
            padding: 20px;
            background: rgba(17, 17, 17, 0.7);
            border-radius: 12px;
            flex-direction: column;
            align-items: flex-start;
        }
        .hidden {
            opacity: 0;
            pointer-events: none;
        }
        button {
            pointer-events: auto;
            background-color: #4CAF50;
            border: none;
            color: white;
            padding: 15px 32px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin-top: 20px;
            cursor: pointer;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            transition: all 0.3s ease;
        }
        button:hover {
            background-color: #45a049;
            transform: translateY(-2px);
            box-shadow: 0 6px 8px rgba(0,0,0,0.2);
        }
        h1 {
            font-size: 2.5rem;
            margin-bottom: 0.5rem;
        }
        p {
            font-size: 1.2rem;
            max-width: 80%;
        }
        #webcam-video, #webcam-canvas {
            display: none;
        }
        .slider-group {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            margin-bottom: 15px;
            pointer-events: auto;
        }
        .slider-group label {
            margin-bottom: 5px;
            font-size: 1rem;
            text-shadow: 1px 1px 2px #000;
        }
        .slider-group input[type="range"] {
            width: 200px;
            -webkit-appearance: none;
            background: #555;
            border-radius: 5px;
            outline: none;
            opacity: 0.7;
            transition: opacity .2s;
        }
        .slider-group input[type="range"]:hover {
            opacity: 1;
        }
        .slider-group input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 15px;
            height: 15px;
            background: #4CAF50;
            border-radius: 50%;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div id="ui-overlay" class="ui-container">
        <h1>Webcam Voxel Matrix</h1>
        <p>This app visualizes your webcam feed as a 3D grid of cubes. Click 'Start Webcam' and grant camera permission to begin.</p>
        <button id="start-webcam-btn">Start Webcam</button>
    </div>

    <div id="control-panel" class="ui-container controls hidden">
        <h2>Controls</h2>
        <div class="slider-group">
            <label for="scale-amount-slider">Scale Amount</label>
            <input type="range" id="scale-amount-slider" min="0.1" max="5.0" step="0.1" value="1.5">
        </div>
        <div class="slider-group">
            <label for="z-offset-slider">Z-Offset</label>
            <input type="range" id="z-offset-slider" min="0.0" max="40.0" step="0.1" value="20.0">
        </div>
        <div class="slider-group">
            <label for="position-smoothing-slider">Position Smoothing</label>
            <input type="range" id="position-smoothing-slider" min="0.01" max="0.5" step="0.01" value="0.05">
        </div>
        <div class="slider-group">
            <label for="rotation-smoothing-slider">Rotation Smoothing</label>
            <input type="range" id="rotation-smoothing-slider" min="0.01" max="0.5" step="0.01" value="0.02">
        </div>
    </div>

    <video id="webcam-video" autoplay playsinline></video>
    <canvas id="webcam-canvas"></canvas>

    <!-- Three.js Library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <!-- OrbitControls for camera interaction -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
    
    <script>
        // Global variables for Three.js
        let scene, camera, renderer, controls;
        let cubes = [];
        const CUBE_COUNT_X = 128;
        const CUBE_COUNT_Y = 64;
        const CUBE_SPACING = 1;
        
        // Webcam and canvas variables
        let video, webcamCanvas, webcamContext;
        
        // UI Control variables
        let scaleAmountMultiplier = 1.5;
        let zOffsetMultiplier = 20.0;
        let positionSmoothingFactor = 0.05;
        let rotationSmoothingFactor = 0.02;
        
        // Smoothing variables
        let smoothedData = [];
        
        // Initialize the 3D scene
        function initThreeJS() {
            // Scene
            scene = new THREE.Scene();
            
            // Camera
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.z = 100;
            
            // Renderer
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setClearColor(0x111111, 1);
            document.body.appendChild(renderer.domElement);
            
            // Controls
            controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.25;
            controls.enableZoom = true;
            
            // Lighting
            const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
            scene.add(ambientLight);
            const pointLight = new THREE.PointLight(0xffffff, 0.5);
            pointLight.position.set(0, 50, 50);
            scene.add(pointLight);
            
            // Create the cubes
            const geometry = new THREE.BoxGeometry(CUBE_SPACING, CUBE_SPACING, CUBE_SPACING);
            const material = new THREE.MeshNormalMaterial();
            
            const totalWidth = CUBE_COUNT_X * CUBE_SPACING;
            const totalHeight = CUBE_COUNT_Y * CUBE_SPACING;
            
            for (let y = 0; y < CUBE_COUNT_Y; y++) {
                for (let x = 0; x < CUBE_COUNT_X; x++) {
                    const cube = new THREE.Mesh(geometry, material);
                    
                    // Center the grid and flip the Y-axis
                    cube.position.x = x * CUBE_SPACING - totalWidth / 2 + CUBE_SPACING / 2;
                    cube.position.y = (CUBE_COUNT_Y - 1 - y) * CUBE_SPACING - totalHeight / 2 + CUBE_SPACING / 2;
                    
                    scene.add(cube);
                    cubes.push(cube);
                    
                    // Initialize smoothed data for each cube
                    smoothedData.push({
                        scale: new THREE.Vector3(1, 1, 1),
                        rotation: new THREE.Vector3(0, 0, 0)
                    });
                }
            }
            
            camera.position.z = Math.max(totalWidth, totalHeight) * 1.5;
        }
        
        // Initialize webcam
        async function initWebcam() {
            video = document.getElementById('webcam-video');
            webcamCanvas = document.getElementById('webcam-canvas');
            webcamCanvas.width = CUBE_COUNT_X;
            webcamCanvas.height = CUBE_COUNT_Y;
            webcamContext = webcamCanvas.getContext('2d', { willReadFrequently: true });
            
            try {
                const stream = await navigator.mediaDevices.getUserMedia({ video: { width: CUBE_COUNT_X, height: CUBE_COUNT_Y } });
                video.srcObject = stream;
                video.onloadedmetadata = () => {
                    video.play();
                    document.getElementById('ui-overlay').classList.add('hidden');
                    document.getElementById('control-panel').classList.remove('hidden');
                    animate(); // Start the animation loop once video is ready
                };
            } catch (err) {
                console.error("Error accessing the webcam: ", err);
                const overlay = document.getElementById('ui-overlay');
                overlay.innerHTML = `
                    <h1>Error</h1>
                    <p>Could not access webcam. Please ensure you have a camera connected and grant permission.</p>
                `;
            }
        }
        
        // Animation loop
        function animate() {
            requestAnimationFrame(animate);
            
            // Update controls
            controls.update();
            
            if (video && video.readyState === video.HAVE_ENOUGH_DATA) {
                // Draw video frame to the hidden canvas
                webcamContext.drawImage(video, 0, 0, CUBE_COUNT_X, CUBE_COUNT_Y);
                const imageData = webcamContext.getImageData(0, 0, CUBE_COUNT_X, CUBE_COUNT_Y).data;
                
                // Update cube properties based on pixel data
                for (let i = 0; i < cubes.length; i++) {
                    const cube = cubes[i];
                    const pixelIndex = i * 4;
                    
                    const r = imageData[pixelIndex];
                    const g = imageData[pixelIndex + 1];
                    const b = imageData[pixelIndex + 2];
                    
                    // Brightness (average of RGB values)
                    const brightness = (r + g + b) / 3;
                    
                    // Map brightness to cube size and Z-position
                    const normalizedBrightness = brightness / 255;
                    const newScale = normalizedBrightness * scaleAmountMultiplier + 0.1;
                    const newZPosition = (normalizedBrightness - 0.5) * zOffsetMultiplier;
                    
                    // Apply smoothing to scale and Z-position
                    smoothedData[i].scale.x += (newScale - smoothedData[i].scale.x) * positionSmoothingFactor;
                    smoothedData[i].scale.y += (newScale - smoothedData[i].scale.y) * positionSmoothingFactor;
                    smoothedData[i].scale.z += (newScale - smoothedData[i].scale.z) * positionSmoothingFactor;

                    cube.scale.copy(smoothedData[i].scale);
                    cube.position.z += (newZPosition - cube.position.z) * positionSmoothingFactor;
                    
                    // Map color channels to rotation (scaled from 0 to 2*PI)
                    const newRotationX = (r / 255) * Math.PI * 2;
                    const newRotationY = (g / 255) * Math.PI * 2;
                    const newRotationZ = (b / 255) * Math.PI * 2;
                    
                    // Apply smoothing twice to rotation
                    smoothedData[i].rotation.x += (newRotationX - smoothedData[i].rotation.x) * rotationSmoothingFactor;
                    smoothedData[i].rotation.y += (newRotationY - smoothedData[i].rotation.y) * rotationSmoothingFactor;
                    smoothedData[i].rotation.z += (newRotationZ - smoothedData[i].rotation.z) * rotationSmoothingFactor;

                    // Second smoothing pass
                    cube.rotation.x += (smoothedData[i].rotation.x - cube.rotation.x) * rotationSmoothingFactor;
                    cube.rotation.y += (smoothedData[i].rotation.y - cube.rotation.y) * rotationSmoothingFactor;
                    cube.rotation.z += (smoothedData[i].rotation.z - cube.rotation.z) * rotationSmoothingFactor;
                }
            }
            
            renderer.render(scene, camera);
        }
        
        // Handle window resizing
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }, false);
        
        // Event listeners for UI controls
        document.getElementById('start-webcam-btn').addEventListener('click', () => {
            initThreeJS();
            initWebcam();
        });

        document.getElementById('scale-amount-slider').addEventListener('input', (e) => {
            scaleAmountMultiplier = parseFloat(e.target.value);
        });

        document.getElementById('z-offset-slider').addEventListener('input', (e) => {
            zOffsetMultiplier = parseFloat(e.target.value);
        });

        document.getElementById('position-smoothing-slider').addEventListener('input', (e) => {
            positionSmoothingFactor = parseFloat(e.target.value);
        });

        document.getElementById('rotation-smoothing-slider').addEventListener('input', (e) => {
            rotationSmoothingFactor = parseFloat(e.target.value);
        });
    </script>
</body>
</html>

COMPUTE SHADER UPGRADE


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Webcam Voxel Matrix</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            font-family: 'Inter', sans-serif;
            background: #111;
            color: #eee;
        }
        canvas {
            display: block;
        }
        .ui-container {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            pointer-events: none;
            text-align: center;
            z-index: 10;
            background: rgba(17, 17, 17, 0.9);
            transition: opacity 1s ease-in-out;
        }
        .ui-container.controls {
            top: 20px;
            left: 20px;
            width: auto;
            height: auto;
            padding: 20px;
            background: rgba(17, 17, 17, 0.7);
            border-radius: 12px;
            flex-direction: column;
            align-items: flex-start;
        }
        .hidden {
            opacity: 0;
            pointer-events: none;
        }
        button {
            pointer-events: auto;
            background-color: #4CAF50;
            border: none;
            color: white;
            padding: 15px 32px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin-top: 20px;
            cursor: pointer;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            transition: all 0.3s ease;
        }
        button:hover {
            background-color: #45a049;
            transform: translateY(-2px);
            box-shadow: 0 6px 8px rgba(0,0,0,0.2);
        }
        h1 {
            font-size: 2.5rem;
            margin-bottom: 0.5rem;
        }
        p {
            font-size: 1.2rem;
            max-width: 80%;
        }
        #webcam-video, #webcam-canvas {
            display: none;
        }
        .slider-group {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            margin-bottom: 15px;
            pointer-events: auto;
        }
        .slider-group label {
            margin-bottom: 5px;
            font-size: 1rem;
            text-shadow: 1px 1px 2px #000;
        }
        .slider-group input[type="range"] {
            width: 200px;
            -webkit-appearance: none;
            background: #555;
            border-radius: 5px;
            outline: none;
            opacity: 0.7;
            transition: opacity .2s;
        }
        .slider-group input[type="range"]:hover {
            opacity: 1;
        }
        .slider-group input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 15px;
            height: 15px;
            background: #4CAF50;
            border-radius: 50%;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <!-- Overlay for initial instructions -->
    <div id="ui-overlay" class="ui-container">
        <h1>Webcam Voxel Matrix</h1>
        <p>This app visualizes your webcam feed as a 3D grid of cubes. Click 'Start Webcam' and grant camera permission to begin.</p>
        <button id="start-webcam-btn">Start Webcam</button>
    </div>

    <!-- Control panel for sliders -->
    <div id="control-panel" class="ui-container controls hidden">
        <h2>Controls</h2>
        <div class="slider-group">
            <label for="scale-amount-slider">Scale Amount</label>
            <input type="range" id="scale-amount-slider" min="0.1" max="5.0" step="0.1" value="1.5">
        </div>
        <div class="slider-group">
            <label for="z-offset-slider">Z-Offset</label>
            <input type="range" id="z-offset-slider" min="0.0" max="40.0" step="0.1" value="20.0">
        </div>
        <div class="slider-group">
            <label for="position-smoothing-slider">Position Smoothing</label>
            <input type="range" id="position-smoothing-slider" min="0.01" max="0.5" step="0.01" value="0.05">
        </div>
        <div class="slider-group">
            <label for="rotation-smoothing-slider">Rotation Smoothing</label>
            <input type="range" id="rotation-smoothing-slider" min="0.01" max="0.5" step="0.01" value="0.02">
        </div>
        <div class="slider-group">
            <label for="light-intensity-slider">Light Intensity</label>
            <input type="range" id="light-intensity-slider" min="0.1" max="2.0" step="0.1" value="1.0">
        </div>
        <div class="slider-group">
            <label for="light-direction-slider">Light Direction</label>
            <input type="range" id="light-direction-slider" min="0" max="360" step="1" value="45">
        </div>
    </div>

    <!-- Hidden video and canvas for webcam feed processing -->
    <video id="webcam-video" autoplay playsinline></video>
    <canvas id="webcam-canvas"></canvas>

    <!-- Three.js Library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <!-- OrbitControls for camera interaction -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
    
    <script>
        // Global variables for Three.js scene, camera, and renderer
        let scene, camera, renderer, controls;
        let instancedMesh;
        let dummy = new THREE.Object3D();
        
        // Webcam and canvas variables for video frame processing
        let video, webcamCanvas, webcamContext;
        
        // Voxel grid resolution and number of instances
        const CUBE_COUNT_X = 256;
        const CUBE_COUNT_Y = 128;
        const NUM_INSTANCES = CUBE_COUNT_X * CUBE_COUNT_Y;
        const CUBE_SPACING = 1.0;
        
        // UI Control variables, initialized with default values
        let scaleAmountMultiplier = 1.5;
        let zOffsetMultiplier = 20.0;
        let positionSmoothingFactor = 0.05;
        let rotationSmoothingFactor = 0.02;
        let lightIntensity = 1.0;
        let lightDirectionAngle = 45;
        
        // Array to store smoothed data for each cube
        let smoothedData = [];
        for(let i = 0; i < NUM_INSTANCES; i++) {
            smoothedData.push({
                scale: new THREE.Vector3(1, 1, 1),
                rotation: new THREE.Euler(0, 0, 0),
                zPos: 0
            });
        }
        
        // Lighting
        let ambientLight;
        let directionalLight;
        
        // Function to create and position the cubes in the scene
        function createAndPositionCubes() {
            // Remove previous instanced mesh if it exists to prevent memory leaks
            if (instancedMesh) {
                scene.remove(instancedMesh);
                instancedMesh.geometry.dispose();
                instancedMesh.material.dispose();
            }

            // Create the cubes using InstancedMesh for high performance
            const geometry = new THREE.BoxGeometry(1, 1, 1);
            const material = new THREE.MeshLambertMaterial({ color: 0xffffff });
            
            instancedMesh = new THREE.InstancedMesh(geometry, material, NUM_INSTANCES);
            scene.add(instancedMesh);
            
            const totalWidth = CUBE_COUNT_X * CUBE_SPACING;
            const totalHeight = CUBE_COUNT_Y * CUBE_SPACING;
            
            // Loop through the grid and set the initial position for each cube
            for (let y = 0; y < CUBE_COUNT_Y; y++) {
                for (let x = 0; x < CUBE_COUNT_X; x++) {
                    const i = y * CUBE_COUNT_X + x;
                    
                    dummy.position.x = x * CUBE_SPACING - totalWidth / 2 + CUBE_SPACING / 2;
                    dummy.position.y = (CUBE_COUNT_Y - 1 - y) * CUBE_SPACING - totalHeight / 2 + CUBE_SPACING / 2;
                    dummy.position.z = 0;
                    dummy.rotation.set(0, 0, 0);
                    dummy.scale.set(1, 1, 1);
                    dummy.updateMatrix();
                    instancedMesh.setMatrixAt(i, dummy.matrix);
                }
            }
            instancedMesh.instanceMatrix.needsUpdate = true;
            camera.position.z = Math.max(totalWidth, totalHeight) * 1.5;
            camera.updateProjectionMatrix();
        }

        // Initialize the main Three.js scene and controls
        function initThreeJS() {
            // Scene
            scene = new THREE.Scene();
            
            // Camera
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            
            // Renderer
            if (!renderer) {
                renderer = new THREE.WebGLRenderer({ antialias: true });
                document.body.appendChild(renderer.domElement);
            }
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setClearColor(0x111111, 1);
            
            // Controls
            if (!controls) {
                controls = new THREE.OrbitControls(camera, renderer.domElement);
                controls.enableDamping = true;
                controls.dampingFactor = 0.25;
                controls.enableZoom = true;
            }
            
            // Lighting
            ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
            scene.add(ambientLight);
            
            directionalLight = new THREE.DirectionalLight(0xffffff, lightIntensity);
            directionalLight.position.set(100, 100, 100);
            scene.add(directionalLight);

            // Create and position cubes initially
            createAndPositionCubes();
        }
        
        // Initialize the webcam and start the video stream
        async function initWebcam() {
            video = document.getElementById('webcam-video');
            webcamCanvas = document.getElementById('webcam-canvas');
            webcamCanvas.width = CUBE_COUNT_X;
            webcamCanvas.height = CUBE_COUNT_Y;
            webcamContext = webcamCanvas.getContext('2d', { willReadFrequently: true });
            
            try {
                const stream = await navigator.mediaDevices.getUserMedia({ video: { width: CUBE_COUNT_X, height: CUBE_COUNT_Y } });
                video.srcObject = stream;
                video.onloadedmetadata = () => {
                    video.play();
                    // Hide the UI overlay and show the controls once the webcam is ready
                    document.getElementById('ui-overlay').classList.add('hidden');
                    document.getElementById('control-panel').classList.remove('hidden');
                    animate(); // Start the animation loop
                };
            } catch (err) {
                console.error("Error accessing the webcam: ", err);
                const overlay = document.getElementById('ui-overlay');
                overlay.innerHTML = `
                    <h1>Error</h1>
                    <p>Could not access webcam. Please ensure you have a camera connected and grant permission.</p>
                `;
            }
        }
        
        // The main animation loop for the scene
        function animate() {
            requestAnimationFrame(animate);
            
            // Update OrbitControls
            controls.update();

            // Update directional light properties from sliders
            directionalLight.intensity = lightIntensity;
            const angle = lightDirectionAngle * Math.PI / 180;
            directionalLight.position.x = Math.sin(angle) * 150;
            directionalLight.position.z = Math.cos(angle) * 150;
            
            if (video && video.readyState === video.HAVE_ENOUGH_DATA) {
                // Draw a frame from the video to the hidden 2D canvas
                webcamContext.drawImage(video, 0, 0, CUBE_COUNT_X, CUBE_COUNT_Y);
                const imageData = webcamContext.getImageData(0, 0, CUBE_COUNT_X, CUBE_COUNT_Y).data;
                
                // Process each pixel and update the corresponding cube
                for (let y = 0; y < CUBE_COUNT_Y; y++) {
                    for (let x = 0; x < CUBE_COUNT_X; x++) {
                        const i = y * CUBE_COUNT_X + x;
                        const pixelIndex = i * 4;
                        
                        const r = imageData[pixelIndex];
                        const g = imageData[pixelIndex + 1];
                        const b = imageData[pixelIndex + 2];
                        
                        // Calculate brightness (average of RGB)
                        const brightness = (r + g + b) / 3;
                        
                        // Map brightness to cube size and Z-position
                        const normalizedBrightness = brightness / 255;
                        const newScale = normalizedBrightness * scaleAmountMultiplier + 0.1;
                        const newZPosition = (normalizedBrightness - 0.5) * zOffsetMultiplier;
                        
                        // Apply smoothing to the new values
                        smoothedData[i].scale.x += (newScale - smoothedData[i].scale.x) * positionSmoothingFactor;
                        smoothedData[i].scale.y += (newScale - smoothedData[i].scale.y) * positionSmoothingFactor;
                        smoothedData[i].scale.z += (newScale - smoothedData[i].scale.z) * positionSmoothingFactor;
                        smoothedData[i].zPos += (newZPosition - smoothedData[i].zPos) * positionSmoothingFactor;
                        
                        // Map color channels to rotation angles
                        const newRotationX = (r / 255) * Math.PI * 2;
                        const newRotationY = (g / 255) * Math.PI * 2;
                        const newRotationZ = (b / 255) * Math.PI * 2;
                        
                        // Apply smoothing twice for a more gradual rotation change
                        smoothedData[i].rotation.x += (newRotationX - smoothedData[i].rotation.x) * rotationSmoothingFactor;
                        smoothedData[i].rotation.y += (newRotationY - smoothedData[i].rotation.y) * rotationSmoothingFactor;
                        smoothedData[i].rotation.z += (newRotationZ - smoothedData[i].rotation.z) * rotationSmoothingFactor;
                        
                        // Update the dummy object with new values
                        dummy.position.x = x * CUBE_SPACING - (CUBE_COUNT_X * CUBE_SPACING) / 2 + CUBE_SPACING / 2;
                        dummy.position.y = (CUBE_COUNT_Y - 1 - y) * CUBE_SPACING - (CUBE_COUNT_Y * CUBE_SPACING) / 2 + CUBE_SPACING / 2;
                        dummy.position.z = smoothedData[i].zPos;
                        
                        dummy.scale.copy(smoothedData[i].scale);
                        
                        dummy.rotation.set(
                            smoothedData[i].rotation.x + (newRotationX - smoothedData[i].rotation.x) * rotationSmoothingFactor,
                            smoothedData[i].rotation.y + (newRotationY - smoothedData[i].rotation.y) * rotationSmoothingFactor,
                            smoothedData[i].rotation.z + (newRotationZ - smoothedData[i].rotation.z) * rotationSmoothingFactor
                        );
                        
                        dummy.updateMatrix();
                        instancedMesh.setMatrixAt(i, dummy.matrix);
                    }
                }
                // Tell Three.js to update the instanced mesh with the new matrices
                instancedMesh.instanceMatrix.needsUpdate = true;
            }
            
            // Render the scene
            renderer.render(scene, camera);
        }
        
        // Handle window resizing
        window.addEventListener('resize', () => {
            if (camera) {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
            }
        }, false);
        
        // Event listener for the "Start Webcam" button
        document.getElementById('start-webcam-btn').addEventListener('click', () => {
            initThreeJS();
            initWebcam();
        });

        // Event listeners for the UI sliders
        document.getElementById('scale-amount-slider').addEventListener('input', (e) => {
            scaleAmountMultiplier = parseFloat(e.target.value);
        });

        document.getElementById('z-offset-slider').addEventListener('input', (e) => {
            zOffsetMultiplier = parseFloat(e.target.value);
        });

        document.getElementById('position-smoothing-slider').addEventListener('input', (e) => {
            positionSmoothingFactor = parseFloat(e.target.value);
        });

        document.getElementById('rotation-smoothing-slider').addEventListener('input', (e) => {
            rotationSmoothingFactor = parseFloat(e.target.value);
        });

        document.getElementById('light-intensity-slider').addEventListener('input', (e) => {
            lightIntensity = parseFloat(e.target.value);
        });
        
        document.getElementById('light-direction-slider').addEventListener('input', (e) => {
            lightDirectionAngle = parseFloat(e.target.value);
        });
    </script>
</body>
</html>


COMPUTE SHADING + FOG + BLOOM

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Webcam Voxel Matrix</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            font-family: 'Inter', sans-serif;
            background: #111;
            color: #eee;
        }
        canvas {
            display: block;
        }
        .ui-container {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            pointer-events: none;
            text-align: center;
            z-index: 10;
            background: rgba(17, 17, 17, 0.9);
            transition: opacity 1s ease-in-out;
        }
        .ui-container.controls {
            top: 20px;
            left: 20px;
            width: auto;
            height: auto;
            padding: 20px;
            background: rgba(17, 17, 17, 0.7);
            border-radius: 12px;
            flex-direction: column;
            align-items: flex-start;
        }
        .hidden {
            opacity: 0;
            pointer-events: none;
        }
        .ui-button {
            pointer-events: auto;
            background-color: #4CAF50;
            border: none;
            color: white;
            padding: 15px 32px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin-top: 20px;
            cursor: pointer;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            transition: all 0.3s ease;
        }
        .ui-button:hover {
            background-color: #45a049;
            transform: translateY(-2px);
            box-shadow: 0 6px 8px rgba(0,0,0,0.2);
        }
        h1 {
            font-size: 2.5rem;
            margin-bottom: 0.5rem;
        }
        p {
            font-size: 1.2rem;
            max-width: 80%;
        }
        #webcam-video, #webcam-canvas {
            display: none;
        }
        .slider-group {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            margin-bottom: 15px;
            pointer-events: auto;
        }
        .slider-group label {
            margin-bottom: 5px;
            font-size: 1rem;
            text-shadow: 1px 1px 2px #000;
        }
        .slider-group input[type="range"] {
            width: 200px;
            -webkit-appearance: none;
            background: #555;
            border-radius: 5px;
            outline: none;
            opacity: 0.7;
            transition: opacity .2s;
        }
        .slider-group input[type="range"]:hover {
            opacity: 1;
        }
        .slider-group input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 15px;
            height: 15px;
            background: #4CAF50;
            border-radius: 50%;
            cursor: pointer;
        }
        .controls-row {
            display: flex;
            gap: 15px;
            margin-top: 15px;
        }
    </style>
</head>
<body>
    <!-- Overlay for initial instructions -->
    <div id="ui-overlay" class="ui-container">
        <h1>Webcam Voxel Matrix</h1>
        <p>This app visualizes your webcam feed as a 3D grid of cubes. Click 'Start Webcam' and grant camera permission to begin.</p>
        <button id="start-webcam-btn" class="ui-button">Start Webcam</button>
    </div>

    <!-- Control panel for sliders -->
    <div id="control-panel" class="ui-container controls hidden">
        <h2>Controls</h2>
        <div class="slider-group">
            <label for="scale-amount-slider">Scale Amount</label>
            <input type="range" id="scale-amount-slider" min="0.1" max="5.0" step="0.1" value="1.5">
        </div>
        <div class="slider-group">
            <label for="z-offset-slider">Z-Offset</label>
            <input type="range" id="z-offset-slider" min="0.0" max="80.0" step="0.1" value="20.0">
        </div>
        <div class="slider-group">
            <label for="position-smoothing-slider">Position Smoothing</label>
            <input type="range" id="position-smoothing-slider" min="0.01" max="0.5" step="0.01" value="0.05">
        </div>
        <div class="slider-group">
            <label for="rotation-smoothing-slider">Rotation Smoothing</label>
            <input type="range" id="rotation-smoothing-slider" min="0.01" max="0.5" step="0.01" value="0.02">
        </div>
        <div class="slider-group">
            <label for="light-intensity-slider">Light Intensity</label>
            <input type="range" id="light-intensity-slider" min="0.1" max="2.0" step="0.1" value="1.0">
        </div>
        <div class="slider-group">
            <label for="light-direction-slider">Light Direction</label>
            <input type="range" id="light-direction-slider" min="0" max="360" step="1" value="45">
        </div>

        <div class="controls-row">
            <button id="toggle-bloom-btn" class="ui-button">Disable Bloom</button>
            <button id="fullscreen-btn" class="ui-button">Go Fullscreen</button>
        </div>

        <div id="bloom-controls">
            <div class="slider-group">
                <label for="bloom-strength-slider">Bloom Strength</label>
                <input type="range" id="bloom-strength-slider" min="0.0" max="3.0" step="0.1" value="1.0">
            </div>
            <div class="slider-group">
                <label for="bloom-threshold-slider">Bloom Threshold</label>
                <input type="range" id="bloom-threshold-slider" min="0.0" max="1.0" step="0.01" value="0.5">
            </div>
            <div class="slider-group">
                <label for="bloom-radius-slider">Bloom Radius</label>
                <input type="range" id="bloom-radius-slider" min="0.0" max="1.0" step="0.01" value="0.4">
            </div>
        </div>
        
        <div id="fog-controls">
            <div class="slider-group">
                <label for="fog-near-slider">Fog Near</label>
                <input type="range" id="fog-near-slider" min="1" max="200" step="1" value="100">
            </div>
            <div class="slider-group">
                <label for="fog-far-slider">Fog Far</label>
                <input type="range" id="fog-far-slider" min="200" max="1000" step="1" value="300">
            </div>
        </div>
    </div>

    <!-- Hidden video and canvas for webcam feed processing -->
    <video id="webcam-video" autoplay playsinline></video>
    <canvas id="webcam-canvas"></canvas>

    <!-- Three.js Library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <!-- OrbitControls for camera interaction -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
    <!-- Post-processing shaders -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/shaders/CopyShader.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/shaders/LuminosityHighPassShader.js"></script>
    <!-- Post-processing passes -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/EffectComposer.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/RenderPass.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/ShaderPass.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/UnrealBloomPass.js"></script>
    

    <script>
        // Global variables for Three.js scene, camera, and renderer
        let scene, camera, renderer, controls, composer;
        let instancedMesh;
        let dummy = new THREE.Object3D();
        
        // Webcam and canvas variables for video frame processing
        let video, webcamCanvas, webcamContext;
        
        // Post-processing passes
        let renderPass, unrealBloomPass;
        let isBloomEnabled = true;

        // Voxel grid resolution and number of instances
        const CUBE_COUNT_X = 400;
        const CUBE_COUNT_Y = 300;
        const NUM_INSTANCES = CUBE_COUNT_X * CUBE_COUNT_Y;
        const CUBE_SPACING = 1.0;
        
        // UI Control variables, initialized with default values
        let scaleAmountMultiplier = 1.5;
        let zOffsetMultiplier = 20.0;
        let positionSmoothingFactor = 0.05;
        let rotationSmoothingFactor = 0.02;
        let lightIntensity = 1.0;
        let lightDirectionAngle = 45;
        let fogNear = 100;
        let fogFar = 300;
        
        // Array to store smoothed data for each cube
        let smoothedData = [];
        for(let i = 0; i < NUM_INSTANCES; i++) {
            smoothedData.push({
                scale: new THREE.Vector3(1, 1, 1),
                rotation: new THREE.Euler(0, 0, 0),
                zPos: 0
            });
        }
        
        // Lighting
        let ambientLight;
        let directionalLight1;
        
        // Function to create and position the cubes in the scene
        function createAndPositionCubes() {
            // Remove previous instanced mesh if it exists to prevent memory leaks
            if (instancedMesh) {
                scene.remove(instancedMesh);
                instancedMesh.geometry.dispose();
                instancedMesh.material.dispose();
            }

            // Create the cubes using InstancedMesh for high performance
            const geometry = new THREE.BoxGeometry(1, 1, 1);
            const material = new THREE.MeshLambertMaterial({ color: 0xffffff });
            
            instancedMesh = new THREE.InstancedMesh(geometry, material, NUM_INSTANCES);
            scene.add(instancedMesh);
            
            const totalWidth = CUBE_COUNT_X * CUBE_SPACING;
            const totalHeight = CUBE_COUNT_Y * CUBE_SPACING;
            
            // Loop through the grid and set the initial position for each cube
            for (let y = 0; y < CUBE_COUNT_Y; y++) {
                for (let x = 0; x < CUBE_COUNT_X; x++) {
                    const i = y * CUBE_COUNT_X + x;
                    
                    dummy.position.x = x * CUBE_SPACING - totalWidth / 2 + CUBE_SPACING / 2;
                    dummy.position.y = (CUBE_COUNT_Y - 1 - y) * CUBE_SPACING - totalHeight / 2 + CUBE_SPACING / 2;
                    dummy.position.z = 0;
                    dummy.rotation.set(0, 0, 0);
                    dummy.scale.set(1, 1, 1);
                    dummy.updateMatrix();
                    instancedMesh.setMatrixAt(i, dummy.matrix);
                }
            }
            instancedMesh.instanceMatrix.needsUpdate = true;
            camera.position.z = Math.max(totalWidth, totalHeight) * 1.5;
            camera.updateProjectionMatrix();
        }

        // Initialize the main Three.js scene and controls
        function initThreeJS() {
            // Scene
            scene = new THREE.Scene();
            scene.fog = new THREE.Fog(0x111111, fogNear, fogFar);
            
            // Camera
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            
            // Renderer
            if (!renderer) {
                renderer = new THREE.WebGLRenderer({ antialias: true });
                document.body.appendChild(renderer.domElement);
            }
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setClearColor(0x111111, 1);
            
            // Controls
            if (!controls) {
                controls = new THREE.OrbitControls(camera, renderer.domElement);
                controls.enableDamping = true;
                controls.dampingFactor = 0.25;
                controls.enableZoom = true;
            }
            
            // Lighting
            ambientLight = new THREE.AmbientLight(0xffffff, 0); // Ambient light set to black
            scene.add(ambientLight);
            
            directionalLight1 = new THREE.DirectionalLight(0xffffff, lightIntensity);
            directionalLight1.position.set(100, 100, 100);
            scene.add(directionalLight1);

            // Post-processing setup
            composer = new THREE.EffectComposer(renderer);
            renderPass = new THREE.RenderPass(scene, camera);
            composer.addPass(renderPass);
            
            // Initialize Bloom pass, starting with a default strength
            unrealBloomPass = new THREE.UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.0, 0.4, 0.85);
            composer.addPass(unrealBloomPass);

            // Create and position cubes initially
            createAndPositionCubes();
        }
        
        // Initialize the webcam and start the video stream
        async function initWebcam() {
            video = document.getElementById('webcam-video');
            webcamCanvas = document.getElementById('webcam-canvas');
            webcamCanvas.width = CUBE_COUNT_X;
            webcamCanvas.height = CUBE_COUNT_Y;
            webcamContext = webcamCanvas.getContext('2d', { willReadFrequently: true });
            
            try {
                const stream = await navigator.mediaDevices.getUserMedia({ video: { width: CUBE_COUNT_X, height: CUBE_COUNT_Y } });
                video.srcObject = stream;
                video.onloadedmetadata = () => {
                    video.play();
                    // Hide the UI overlay and show the controls once the webcam is ready
                    document.getElementById('ui-overlay').classList.add('hidden');
                    document.getElementById('control-panel').classList.remove('hidden');
                    animate(); // Start the animation loop
                };
            } catch (err) {
                console.error("Error accessing the webcam: ", err);
                const overlay = document.getElementById('ui-overlay');
                overlay.innerHTML = `
                    <h1>Error</h1>
                    <p>Could not access webcam. Please ensure you have a camera connected and grant permission.</p>
                `;
            }
        }
        
        // The main animation loop for the scene
        function animate() {
            requestAnimationFrame(animate);
            
            // Update OrbitControls
            controls.update();

            // Update directional light properties from sliders
            directionalLight1.intensity = lightIntensity;
            const angle1 = lightDirectionAngle * Math.PI / 180;
            directionalLight1.position.x = Math.sin(angle1) * 150;
            directionalLight1.position.z = Math.cos(angle1) * 150;
            
            // Update fog properties
            scene.fog.near = fogNear;
            scene.fog.far = fogFar;

            if (video && video.readyState === video.HAVE_ENOUGH_DATA) {
                // Draw a frame from the video to the hidden 2D canvas
                webcamContext.drawImage(video, 0, 0, CUBE_COUNT_X, CUBE_COUNT_Y);
                const imageData = webcamContext.getImageData(0, 0, CUBE_COUNT_X, CUBE_COUNT_Y).data;
                
                // Process each pixel and update the corresponding cube
                for (let y = 0; y < CUBE_COUNT_Y; y++) {
                    for (let x = 0; x < CUBE_COUNT_X; x++) {
                        const i = y * CUBE_COUNT_X + x;
                        const pixelIndex = i * 4;
                        
                        const r = imageData[pixelIndex];
                        const g = imageData[pixelIndex + 1];
                        const b = imageData[pixelIndex + 2];
                        
                        // Calculate brightness (average of RGB)
                        const brightness = (r + g + b) / 3;
                        
                        // Map brightness to cube size and Z-position
                        const normalizedBrightness = brightness / 255;
                        const newScale = normalizedBrightness * scaleAmountMultiplier + 0.1;
                        const newZPosition = (normalizedBrightness - 0.5) * zOffsetMultiplier;
                        
                        // Apply smoothing to the new values
                        smoothedData[i].scale.x += (newScale - smoothedData[i].scale.x) * positionSmoothingFactor;
                        smoothedData[i].scale.y += (newScale - smoothedData[i].scale.y) * positionSmoothingFactor;
                        smoothedData[i].scale.z += (newScale - smoothedData[i].scale.z) * positionSmoothingFactor;
                        smoothedData[i].zPos += (newZPosition - smoothedData[i].zPos) * positionSmoothingFactor;
                        
                        // Map color channels to rotation angles
                        const newRotationX = (r / 255) * Math.PI * 2;
                        const newRotationY = (g / 255) * Math.PI * 2;
                        const newRotationZ = (b / 255) * Math.PI * 2;
                        
                        // Apply smoothing twice for a more gradual rotation change
                        smoothedData[i].rotation.x += (newRotationX - smoothedData[i].rotation.x) * rotationSmoothingFactor;
                        smoothedData[i].rotation.y += (newRotationY - smoothedData[i].rotation.y) * rotationSmoothingFactor;
                        smoothedData[i].rotation.z += (newRotationZ - smoothedData[i].rotation.z) * rotationSmoothingFactor;
                        
                        // Update the dummy object with new values
                        dummy.position.x = x * CUBE_SPACING - (CUBE_COUNT_X * CUBE_SPACING) / 2 + CUBE_SPACING / 2;
                        dummy.position.y = (CUBE_COUNT_Y - 1 - y) * CUBE_SPACING - (CUBE_COUNT_Y * CUBE_SPACING) / 2 + CUBE_SPACING / 2;
                        dummy.position.z = smoothedData[i].zPos;
                        
                        dummy.scale.copy(smoothedData[i].scale);
                        
                        dummy.rotation.set(
                            smoothedData[i].rotation.x + (newRotationX - smoothedData[i].rotation.x) * rotationSmoothingFactor,
                            smoothedData[i].rotation.y + (newRotationY - smoothedData[i].rotation.y) * rotationSmoothingFactor,
                            smoothedData[i].rotation.z + (newRotationZ - smoothedData[i].rotation.z) * rotationSmoothingFactor
                        );
                        
                        dummy.updateMatrix();
                        instancedMesh.setMatrixAt(i, dummy.matrix);
                    }
                }
                // Tell Three.js to update the instanced mesh with the new matrices
                instancedMesh.instanceMatrix.needsUpdate = true;
            }
            
            // Render the scene using the composer for post-processing effects
            composer.render();
        }
        
        // Handle window resizing
        window.addEventListener('resize', () => {
            if (camera && renderer && composer) {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
                composer.setSize(window.innerWidth, window.innerHeight);
            }
        }, false);
        
        // Event listener for the "Start Webcam" button
        document.getElementById('start-webcam-btn').addEventListener('click', () => {
            initThreeJS();
            initWebcam();
        });

        // Event listeners for the UI sliders
        document.getElementById('scale-amount-slider').addEventListener('input', (e) => {
            scaleAmountMultiplier = parseFloat(e.target.value);
        });

        document.getElementById('z-offset-slider').addEventListener('input', (e) => {
            zOffsetMultiplier = parseFloat(e.target.value);
        });

        document.getElementById('position-smoothing-slider').addEventListener('input', (e) => {
            positionSmoothingFactor = parseFloat(e.target.value);
        });

        document.getElementById('rotation-smoothing-slider').addEventListener('input', (e) => {
            rotationSmoothingFactor = parseFloat(e.target.value);
        });

        document.getElementById('light-intensity-slider').addEventListener('input', (e) => {
            lightIntensity = parseFloat(e.target.value);
        });
        
        document.getElementById('light-direction-slider').addEventListener('input', (e) => {
            lightDirectionAngle = parseFloat(e.target.value);
        });
        
        document.getElementById('fog-near-slider').addEventListener('input', (e) => {
            fogNear = parseFloat(e.target.value);
        });
        
        document.getElementById('fog-far-slider').addEventListener('input', (e) => {
            fogFar = parseFloat(e.target.value);
        });
        
        // Toggle button for bloom effect
        document.getElementById('toggle-bloom-btn').addEventListener('click', () => {
            isBloomEnabled = !isBloomEnabled;
            const button = document.getElementById('toggle-bloom-btn');
            if (isBloomEnabled) {
                unrealBloomPass.strength = parseFloat(document.getElementById('bloom-strength-slider').value);
                button.textContent = 'Disable Bloom';
            } else {
                unrealBloomPass.strength = 0;
                button.textContent = 'Enable Bloom';
            }
        });

        // Event listeners for Bloom sliders
        document.getElementById('bloom-strength-slider').addEventListener('input', (e) => {
            if (isBloomEnabled) {
                unrealBloomPass.strength = parseFloat(e.target.value);
            }
        });
        document.getElementById('bloom-threshold-slider').addEventListener('input', (e) => {
            if (isBloomEnabled) {
                unrealBloomPass.threshold = parseFloat(e.target.value);
            }
        });
        document.getElementById('bloom-radius-slider').addEventListener('input', (e) => {
            if (isBloomEnabled) {
                unrealBloomPass.radius = parseFloat(e.target.value);
            }
        });

        // Fullscreen button event listener
        document.getElementById('fullscreen-btn').addEventListener('click', () => {
            if (!document.fullscreenElement) {
                document.documentElement.requestFullscreen().then(() => {
                    document.getElementById('fullscreen-btn').textContent = 'Exit Fullscreen';
                }).catch(err => {
                    console.error(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);
                });
            } else {
                document.exitFullscreen().then(() => {
                    document.getElementById('fullscreen-btn').textContent = 'Go Fullscreen';
                });
            }
        });
    </script>
</body>
</html>