infinite-agents-public/threejs_viz/threejs_viz_10.html

372 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js - Cosmic Bloom Garden</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #000000;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
#info {
position: absolute;
top: 10px;
left: 10px;
color: white;
background: rgba(0, 0, 0, 0.7);
padding: 15px;
border-radius: 8px;
font-size: 14px;
max-width: 400px;
z-index: 100;
}
#info h2 {
margin: 0 0 10px 0;
font-size: 18px;
}
#info .web-source {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid rgba(255,255,255,0.3);
font-size: 12px;
opacity: 0.8;
}
#controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.7);
padding: 15px 25px;
border-radius: 8px;
color: white;
font-size: 12px;
z-index: 100;
}
.control-row {
margin: 5px 0;
display: flex;
align-items: center;
gap: 10px;
}
.control-row label {
min-width: 80px;
}
.control-row input {
flex: 1;
}
</style>
</head>
<body>
<div id="info">
<h2>Cosmic Bloom Garden</h2>
<p><strong>Technique:</strong> Post-Processing with UnrealBloomPass</p>
<p><strong>Learning:</strong> Implemented EffectComposer pipeline with bloom effects using emissive materials. Learned how to set up multi-pass rendering, configure bloom parameters (threshold, strength, radius), and integrate post-processing into the render loop.</p>
<div class="web-source">
<strong>Web Sources:</strong><br>
<a href="https://github.com/mrdoob/three.js/blob/dev/examples/webgl_postprocessing_unreal_bloom.html" target="_blank" style="color: #4fc3f7;">Three.js Official Bloom Example</a><br>
<a href="https://waelyasmina.net/articles/post-processing-with-three-js-the-what-and-how/" target="_blank" style="color: #4fc3f7;">Wael Yasmina's Post-Processing Guide</a><br>
<em>Applied: EffectComposer setup, RenderPass → UnrealBloomPass → OutputPass pipeline, emissive materials for glow</em>
</div>
</div>
<div id="controls">
<div class="control-row">
<label>Strength:</label>
<input type="range" id="strength" min="0" max="3" step="0.1" value="1.5">
<span id="strengthValue">1.5</span>
</div>
<div class="control-row">
<label>Radius:</label>
<input type="range" id="radius" min="0" max="1" step="0.01" value="0.4">
<span id="radiusValue">0.4</span>
</div>
<div class="control-row">
<label>Threshold:</label>
<input type="range" id="threshold" min="0" max="1" step="0.01" value="0.15">
<span id="thresholdValue">0.15</span>
</div>
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
// Scene setup
let camera, scene, renderer, controls;
let composer, bloomPass;
let glowingSpheres = [];
let clock;
// Bloom parameters
const params = {
threshold: 0.15,
strength: 1.5,
radius: 0.4,
exposure: 1
};
init();
animate();
function init() {
// Clock for animations
clock = new THREE.Clock();
// Camera setup
camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 5, 15);
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000510);
scene.fog = new THREE.Fog(0x000510, 20, 50);
// Renderer (WebGL with tone mapping for better bloom)
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ReinhardToneMapping;
renderer.toneMappingExposure = params.exposure;
document.body.appendChild(renderer.domElement);
// OrbitControls for interaction
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 5;
controls.maxDistance = 30;
// Create visualization
createCosmicGarden();
// Setup Post-Processing Pipeline
// This is the key learning: EffectComposer chains multiple passes
setupPostProcessing();
// Setup UI controls
setupControls();
// Handle resize
window.addEventListener('resize', onWindowResize);
}
function createCosmicGarden() {
// Ambient light for base illumination
const ambientLight = new THREE.AmbientLight(0x222244, 0.3);
scene.add(ambientLight);
// Point light for dramatic lighting
const pointLight = new THREE.PointLight(0xffffff, 1, 100);
pointLight.position.set(10, 10, 10);
scene.add(pointLight);
// Create glowing spheres in a garden pattern
// Emissive materials are key for bloom effects
const colors = [
0xff0080, // Hot pink
0x00ffff, // Cyan
0xff8800, // Orange
0x00ff88, // Green
0x8800ff, // Purple
0xffff00, // Yellow
0xff0044, // Red
0x0088ff, // Blue
];
// Create multiple rows of glowing orbs
for (let row = 0; row < 3; row++) {
for (let col = 0; col < 8; col++) {
const geometry = new THREE.SphereGeometry(0.5, 32, 32);
// Emissive material is crucial for bloom
// Higher emissive intensity = stronger glow
const material = new THREE.MeshStandardMaterial({
color: colors[col],
emissive: colors[col],
emissiveIntensity: 2.5,
metalness: 0.8,
roughness: 0.2
});
const sphere = new THREE.Mesh(geometry, material);
// Position in grid pattern
sphere.position.x = (col - 3.5) * 2.5;
sphere.position.y = 1 + row * 2.5;
sphere.position.z = -row * 3;
// Store animation data
sphere.userData = {
originalY: sphere.position.y,
phase: Math.random() * Math.PI * 2,
speed: 0.5 + Math.random() * 0.5,
amplitude: 0.3 + Math.random() * 0.4
};
scene.add(sphere);
glowingSpheres.push(sphere);
}
}
// Create glowing ground plane
const groundGeometry = new THREE.PlaneGeometry(50, 50);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x001133,
emissive: 0x001133,
emissiveIntensity: 0.5,
metalness: 0.9,
roughness: 0.1
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = 0;
scene.add(ground);
// Add glowing particles in the background
const particleCount = 500;
const particleGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
positions[i * 3] = (Math.random() - 0.5) * 50;
positions[i * 3 + 1] = Math.random() * 30;
positions[i * 3 + 2] = (Math.random() - 0.5) * 50;
// Bright colors for particles
const color = new THREE.Color();
color.setHSL(Math.random(), 1.0, 0.7);
colors[i * 3] = color.r;
colors[i * 3 + 1] = color.g;
colors[i * 3 + 2] = color.b;
}
particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
particleGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const particleMaterial = new THREE.PointsMaterial({
size: 0.15,
vertexColors: true,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending
});
const particles = new THREE.Points(particleGeometry, particleMaterial);
scene.add(particles);
}
function setupPostProcessing() {
// Key Learning: Post-processing pipeline setup
// EffectComposer manages multiple rendering passes
// 1. Create the composer with the renderer
composer = new EffectComposer(renderer);
// 2. First pass: Render the scene normally
const renderScene = new RenderPass(scene, camera);
composer.addPass(renderScene);
// 3. Second pass: Apply Unreal Bloom effect
// Parameters: resolution, strength, radius, threshold
bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
params.strength, // Bloom strength (intensity)
params.radius, // Bloom radius (spread)
params.threshold // Luminance threshold (what glows)
);
composer.addPass(bloomPass);
// 4. Final pass: Output to screen with tone mapping
const outputPass = new OutputPass();
composer.addPass(outputPass);
// Note: Order matters! RenderPass → Effects → OutputPass
}
function setupControls() {
// Connect UI controls to bloom parameters
const strengthSlider = document.getElementById('strength');
const radiusSlider = document.getElementById('radius');
const thresholdSlider = document.getElementById('threshold');
strengthSlider.addEventListener('input', (e) => {
bloomPass.strength = parseFloat(e.target.value);
document.getElementById('strengthValue').textContent = e.target.value;
});
radiusSlider.addEventListener('input', (e) => {
bloomPass.radius = parseFloat(e.target.value);
document.getElementById('radiusValue').textContent = e.target.value;
});
thresholdSlider.addEventListener('input', (e) => {
bloomPass.threshold = parseFloat(e.target.value);
document.getElementById('thresholdValue').textContent = e.target.value;
});
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
// Important: Resize composer too!
composer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
const elapsedTime = clock.getElapsedTime();
// Animate glowing spheres with floating motion
glowingSpheres.forEach((sphere) => {
const data = sphere.userData;
sphere.position.y = data.originalY +
Math.sin(elapsedTime * data.speed + data.phase) * data.amplitude;
// Pulse the emissive intensity for extra glow
sphere.material.emissiveIntensity = 2.0 +
Math.sin(elapsedTime * data.speed * 2 + data.phase) * 0.5;
});
// Update controls
controls.update();
// Key Learning: Render using composer instead of renderer.render()
// This applies all post-processing effects
composer.render();
}
</script>
</body>
</html>