three.js-象素地球爆炸

80 阅读2分钟

three.js象素地球3s后爆炸

PixPin_2025-09-05_11-08-15.png

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>象素地球爆炸</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000000;
            color: white;
            font-family: Arial, sans-serif;
        }
        canvas {
            display: block;
        }
        #info {
            position: absolute;
            top: 10px;
            width: 100%;
            text-align: center;
            z-index: 100;
            display: block;
            font-size: 1.5em;
        }
    </style>
</head>
<body>
    <div id="info">象素粒子组成地球...</div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

    <script>
        // --- 核心变量 ---
        let scene, camera, renderer;
        let particles; // 粒子系统
        let positions; // 粒子当前位置
        let velocities; // 粒子爆炸速度
        let targetPositions; // 粒子的目标位置(地球形状)
        const particleCount = 15000; // 粒子数量
        let isExploded = false;

        // --- 初始化 ---
        function init() {
            // 场景
            scene = new THREE.Scene();

            // 相机
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.z = 150;

            // 渲染器
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            document.body.appendChild(renderer.domElement);

            // --- 创建粒子 ---
            const geometry = new THREE.BufferGeometry();
            positions = new Float32Array(particleCount * 3);
            velocities = new Float32Array(particleCount * 3);
            targetPositions = new Float32Array(particleCount * 3);

            // 创建一个球体几何来获取顶点作为粒子的目标位置
            const earthRadius = 60;
            const sphereGeometry = new THREE.SphereGeometry(earthRadius, 64, 64);
            const earthVertices = sphereGeometry.attributes.position.array;

            for (let i = 0; i < particleCount; i++) {
                const i3 = i * 3;

                // 初始位置:随机分布在一个更大的球体中
                const theta = Math.random() * Math.PI * 2;
                const phi = Math.acos((Math.random() * 2) - 1);
                const r = Math.random() * 500 + 200;
                positions[i3] = r * Math.sin(phi) * Math.cos(theta);
                positions[i3 + 1] = r * Math.sin(phi) * Math.sin(theta);
                positions[i3 + 2] = r * Math.cos(phi);

                // 目标位置:从地球模型的顶点中随机选取
                const randomVertexIndex = Math.floor(Math.random() * (earthVertices.length / 3)) * 3;
                targetPositions[i3] = earthVertices[randomVertexIndex];
                targetPositions[i3 + 1] = earthVertices[randomVertexIndex + 1];
                targetPositions[i3 + 2] = earthVertices[randomVertexIndex + 2];
                
                // 爆炸速度:随机方向和大小
                const explosionStrength = (Math.random() - 0.5) * 4;
                velocities[i3] = (Math.random() - 0.5) * explosionStrength;
                velocities[i3 + 1] = (Math.random() - 0.5) * explosionStrength;
                velocities[i3 + 2] = (Math.random() - 0.5) * explosionStrength;
            }

            geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

            const material = new THREE.PointsMaterial({
                color: 0x4169E1, // 皇家蓝
                size: 1.5,
                blending: THREE.AdditiveBlending,
                transparent: true,
                opacity: 0.8
            });

            particles = new THREE.Points(geometry, material);
            scene.add(particles);

            // --- 设置3秒后爆炸 ---
            setTimeout(() => {
                isExploded = true;
                document.getElementById('info').textContent = '爆炸!';
            }, 3000);
            
            // 监听窗口大小变化
            window.addEventListener('resize', onWindowResize, false);
        }

        // --- 动画循环 ---
        function animate() {
            requestAnimationFrame(animate);

            const positionsAttribute = particles.geometry.attributes.position;
            const material = particles.material;

            if (!isExploded) {
                // 聚合阶段:粒子向目标位置移动
                for (let i = 0; i < particleCount; i++) {
                    const i3 = i * 3;
                    positionsAttribute.array[i3] += (targetPositions[i3] - positionsAttribute.array[i3]) * 0.05;
                    positionsAttribute.array[i3 + 1] += (targetPositions[i3 + 1] - positionsAttribute.array[i3 + 1]) * 0.05;
                    positionsAttribute.array[i3 + 2] += (targetPositions[i3 + 2] - positionsAttribute.array[i3 + 2]) * 0.05;
                }
                 particles.rotation.y += 0.001; // 地球聚合时缓慢旋转
            } else {
                // 爆炸阶段:粒子根据速度向外移动
                for (let i = 0; i < particleCount; i++) {
                    const i3 = i * 3;
                    positionsAttribute.array[i3] += velocities[i3];
                    positionsAttribute.array[i3 + 1] += velocities[i3 + 1];
                    positionsAttribute.array[i3 + 2] += velocities[i3 + 2];
                }
                
                // 粒子逐渐消失
                if(material.opacity > 0) {
                    material.opacity -= 0.005;
                }
            }

            positionsAttribute.needsUpdate = true;
            renderer.render(scene, camera);
        }

        // --- 窗口大小调整 ---
        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        // --- 启动 ---
        init();
        animate();
    </script>
</body>
</html>