310 lines
10 KiB
HTML
310 lines
10 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 Particle Universe</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
overflow: hidden;
|
|
background: #000;
|
|
}
|
|
|
|
#canvas-container {
|
|
width: 100vw;
|
|
height: 100vh;
|
|
display: block;
|
|
}
|
|
|
|
#info {
|
|
position: absolute;
|
|
top: 20px;
|
|
left: 20px;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
color: #fff;
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
font-size: 14px;
|
|
max-width: 350px;
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
#info h1 {
|
|
margin: 0 0 15px 0;
|
|
font-size: 24px;
|
|
color: #4fc3f7;
|
|
text-shadow: 0 0 10px rgba(79, 195, 247, 0.5);
|
|
}
|
|
|
|
#info p {
|
|
margin: 8px 0;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
#info strong {
|
|
color: #81c784;
|
|
}
|
|
|
|
#info a {
|
|
color: #4fc3f7;
|
|
text-decoration: none;
|
|
}
|
|
|
|
#info a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.label {
|
|
color: #90caf9;
|
|
font-weight: 600;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="canvas-container"></div>
|
|
|
|
<div id="info">
|
|
<h1>Particle Universe</h1>
|
|
<p><span class="label">Technique:</span> GPU-accelerated particle system</p>
|
|
<p><span class="label">Learning:</span> BufferGeometry with Points for efficient particle rendering</p>
|
|
<p><span class="label">Features:</span> 10,000 particles with color gradients, pulsing animation, and orbital camera</p>
|
|
<p><span class="label">Web Source:</span> <a href="https://threejs.org/examples/?q=points" target="_blank">Three.js Points Examples</a></p>
|
|
</div>
|
|
|
|
<script type="importmap">
|
|
{
|
|
"imports": {
|
|
"three": "https://cdn.jsdelivr.net/npm/three@0.164.1/build/three.module.js",
|
|
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.164.1/examples/jsm/"
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<script type="module">
|
|
import * as THREE from 'three';
|
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
|
|
// Scene setup
|
|
const scene = new THREE.Scene();
|
|
scene.background = new THREE.Color(0x000510);
|
|
scene.fog = new THREE.FogExp2(0x000510, 0.0008);
|
|
|
|
// Camera setup
|
|
const camera = new THREE.PerspectiveCamera(
|
|
75,
|
|
window.innerWidth / window.innerHeight,
|
|
0.1,
|
|
1000
|
|
);
|
|
camera.position.set(0, 50, 150);
|
|
|
|
// Renderer setup
|
|
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
renderer.setPixelRatio(window.devicePixelRatio);
|
|
document.getElementById('canvas-container').appendChild(renderer.domElement);
|
|
|
|
// Controls
|
|
const controls = new OrbitControls(camera, renderer.domElement);
|
|
controls.enableDamping = true;
|
|
controls.dampingFactor = 0.05;
|
|
controls.autoRotate = true;
|
|
controls.autoRotateSpeed = 0.5;
|
|
controls.minDistance = 50;
|
|
controls.maxDistance = 300;
|
|
|
|
// Create particle system
|
|
const particleCount = 10000;
|
|
const particles = new THREE.BufferGeometry();
|
|
const positions = new Float32Array(particleCount * 3);
|
|
const colors = new Float32Array(particleCount * 3);
|
|
const sizes = new Float32Array(particleCount);
|
|
const velocities = new Float32Array(particleCount * 3);
|
|
|
|
// Color palette for gradient effect
|
|
const color1 = new THREE.Color(0x4fc3f7); // Cyan
|
|
const color2 = new THREE.Color(0x9c27b0); // Purple
|
|
const color3 = new THREE.Color(0xff6b9d); // Pink
|
|
const color4 = new THREE.Color(0xffd54f); // Gold
|
|
|
|
// Initialize particle attributes
|
|
for (let i = 0; i < particleCount; i++) {
|
|
const i3 = i * 3;
|
|
|
|
// Create spherical distribution with varying density
|
|
const radius = Math.random() * 100 + 50;
|
|
const theta = Math.random() * Math.PI * 2;
|
|
const phi = Math.acos(Math.random() * 2 - 1);
|
|
|
|
positions[i3] = radius * Math.sin(phi) * Math.cos(theta);
|
|
positions[i3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
|
|
positions[i3 + 2] = radius * Math.cos(phi);
|
|
|
|
// Assign colors based on position for gradient effect
|
|
const colorMix = Math.random();
|
|
let particleColor;
|
|
|
|
if (colorMix < 0.25) {
|
|
particleColor = color1.clone();
|
|
} else if (colorMix < 0.5) {
|
|
particleColor = color2.clone();
|
|
} else if (colorMix < 0.75) {
|
|
particleColor = color3.clone();
|
|
} else {
|
|
particleColor = color4.clone();
|
|
}
|
|
|
|
// Add some color variation
|
|
particleColor.offsetHSL(Math.random() * 0.1 - 0.05, 0, 0);
|
|
|
|
colors[i3] = particleColor.r;
|
|
colors[i3 + 1] = particleColor.g;
|
|
colors[i3 + 2] = particleColor.b;
|
|
|
|
// Varying particle sizes for depth effect
|
|
sizes[i] = Math.random() * 3 + 0.5;
|
|
|
|
// Random velocities for floating animation
|
|
velocities[i3] = (Math.random() - 0.5) * 0.02;
|
|
velocities[i3 + 1] = (Math.random() - 0.5) * 0.02;
|
|
velocities[i3 + 2] = (Math.random() - 0.5) * 0.02;
|
|
}
|
|
|
|
particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
particles.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
|
particles.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
|
|
|
|
// Custom shader material for enhanced particle rendering
|
|
const particleMaterial = new THREE.ShaderMaterial({
|
|
uniforms: {
|
|
time: { value: 0 },
|
|
pixelRatio: { value: renderer.getPixelRatio() }
|
|
},
|
|
vertexShader: `
|
|
attribute float size;
|
|
attribute vec3 color;
|
|
varying vec3 vColor;
|
|
uniform float time;
|
|
uniform float pixelRatio;
|
|
|
|
void main() {
|
|
vColor = color;
|
|
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
|
|
// Pulsing effect
|
|
float pulse = sin(time * 2.0 + position.x * 0.01) * 0.3 + 1.0;
|
|
|
|
gl_PointSize = size * pulse * pixelRatio * (300.0 / -mvPosition.z);
|
|
gl_Position = projectionMatrix * mvPosition;
|
|
}
|
|
`,
|
|
fragmentShader: `
|
|
varying vec3 vColor;
|
|
|
|
void main() {
|
|
// Create circular particles with soft edges
|
|
vec2 center = gl_PointCoord - vec2(0.5);
|
|
float dist = length(center);
|
|
|
|
if (dist > 0.5) discard;
|
|
|
|
// Soft glow effect
|
|
float alpha = 1.0 - smoothstep(0.0, 0.5, dist);
|
|
alpha = pow(alpha, 2.0);
|
|
|
|
// Add glow
|
|
vec3 glow = vColor * (1.0 + alpha * 0.5);
|
|
|
|
gl_FragColor = vec4(glow, alpha * 0.8);
|
|
}
|
|
`,
|
|
transparent: true,
|
|
depthWrite: false,
|
|
blending: THREE.AdditiveBlending
|
|
});
|
|
|
|
const particleSystem = new THREE.Points(particles, particleMaterial);
|
|
scene.add(particleSystem);
|
|
|
|
// Add ambient light for scene
|
|
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
|
|
scene.add(ambientLight);
|
|
|
|
// Add some accent lights
|
|
const light1 = new THREE.PointLight(0x4fc3f7, 1, 200);
|
|
light1.position.set(50, 50, 50);
|
|
scene.add(light1);
|
|
|
|
const light2 = new THREE.PointLight(0x9c27b0, 1, 200);
|
|
light2.position.set(-50, -50, -50);
|
|
scene.add(light2);
|
|
|
|
// Animation variables
|
|
let time = 0;
|
|
|
|
// Animation loop
|
|
function animate() {
|
|
requestAnimationFrame(animate);
|
|
|
|
time += 0.01;
|
|
particleMaterial.uniforms.time.value = time;
|
|
|
|
// Animate particle positions with floating motion
|
|
const positions = particles.attributes.position.array;
|
|
|
|
for (let i = 0; i < particleCount; i++) {
|
|
const i3 = i * 3;
|
|
|
|
// Floating motion
|
|
positions[i3] += velocities[i3];
|
|
positions[i3 + 1] += velocities[i3 + 1];
|
|
positions[i3 + 2] += velocities[i3 + 2];
|
|
|
|
// Boundary check - reverse direction if too far
|
|
const distance = Math.sqrt(
|
|
positions[i3] ** 2 +
|
|
positions[i3 + 1] ** 2 +
|
|
positions[i3 + 2] ** 2
|
|
);
|
|
|
|
if (distance > 200 || distance < 30) {
|
|
velocities[i3] *= -1;
|
|
velocities[i3 + 1] *= -1;
|
|
velocities[i3 + 2] *= -1;
|
|
}
|
|
}
|
|
|
|
particles.attributes.position.needsUpdate = true;
|
|
|
|
// Rotate entire particle system slowly
|
|
particleSystem.rotation.y += 0.0005;
|
|
particleSystem.rotation.x = Math.sin(time * 0.1) * 0.1;
|
|
|
|
// Update controls
|
|
controls.update();
|
|
|
|
// Render scene
|
|
renderer.render(scene, camera);
|
|
}
|
|
|
|
// Handle window resize
|
|
window.addEventListener('resize', () => {
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
camera.updateProjectionMatrix();
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
});
|
|
|
|
// Start animation
|
|
animate();
|
|
</script>
|
|
</body>
|
|
</html> |