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>