Vue3 + Three.js 实现粒子系统

113 阅读1分钟

1. 安装three.js(我使用yarn)

yarn add -d three

2. 函数封装

import * as THREE from 'three'
export default function () {
    const SEPARATION = 100, AMOUNTX = 50, AMOUNTY = 50;
    let camera, scene, renderer;
    let particles, count = 0;
    let mouseX = 0, mouseY = 0;
    let windowHalfX = window.innerWidth / 2;
    let windowHalfY = window.innerHeight / 1;

    // onMounted(() => {
    //     init();
    //     animate();
    // })

    function init(node: Element) {

        camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
        camera.position.z = 1000;

        scene = new THREE.Scene();

        const numParticles = AMOUNTX * AMOUNTY;
        const positions = new Float32Array(numParticles * 3);
        const scales = new Float32Array(numParticles);
        let i = 0, j = 0;

        for (let ix = 0; ix < AMOUNTX; ix++) {
            for (let iy = 0; iy < AMOUNTY; iy++) {
                positions[i] = ix * SEPARATION - ((AMOUNTX * SEPARATION) / 2); // x
                positions[i + 1] = 0; // y
                positions[i + 2] = iy * SEPARATION - ((AMOUNTY * SEPARATION) / 2); // z
                scales[j] = 1;
                i += 3;
                j++;
            }
        }
        // GLSL代码
        const vertexshader = `
        attribute float scale;
        void main() {
            vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
            gl_PointSize = scale * ( 150.0 / - mvPosition.z );
            gl_Position = projectionMatrix * mvPosition;
            
        }`
        const fragmentShader = `
        uniform vec3 color;
        void main() {
            if ( length( gl_PointCoord - vec2( 0.5, 0.5 ) ) > 0.475 ) discard;
            gl_FragColor = vec4( color, 1.0 );  
        }`

        const geometry = new THREE.BufferGeometry();
        geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        geometry.setAttribute('scale', new THREE.BufferAttribute(scales, 1));

        const material = new THREE.ShaderMaterial({
            uniforms: {
                color: {value: new THREE.Color(0xE6EAEA)}
            },
            vertexShader: vertexshader,
            fragmentShader: fragmentShader
        });
        particles = new THREE.Points(geometry, material);
        scene.add(particles);
        renderer = new THREE.WebGLRenderer({antialias: true});
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setClearColor(0xffffff, 1);
        renderer.setSize(window.innerWidth, window.innerHeight);
        node.appendChild(renderer.domElement)
        node.style.touchAction = 'none';
        node.addEventListener('pointermove', onPointerMove, false);
        window.addEventListener('resize', onWindowResize, false);
        animate();
    }

    function onWindowResize() {
        windowHalfX = window.innerWidth / 2;
        windowHalfY = window.innerHeight;
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    }

    function onPointerMove(event) {
        if (event.isPrimary === false) return;
        mouseX = event.clientX - windowHalfX;
        mouseY = event.clientY - windowHalfY;

    }

    function animate() {
        requestAnimationFrame(animate);
        render();
    }

    function render() {
        camera.position.x += (mouseX - camera.position.x) * .05;
        camera.position.y += (-mouseY - camera.position.y) * .05;
        camera.lookAt(scene.position);
        const positions = particles.geometry.attributes.position.array;
        const scales = particles.geometry.attributes.scale.array;
        
        let i = 0, j = 0;
        for (let ix = 0; ix < AMOUNTX; ix++) {
            for (let iy = 0; iy < AMOUNTY; iy++) {
                positions[i + 1] = (Math.sin((ix + count) * 0.3) * 50) +
                    (Math.sin((iy + count) * 0.5) * 50);
                scales[j] = (Math.sin((ix + count) * 0.3) + 1) * 20 +
                    (Math.sin((iy + count) * 0.5) + 1) * 20;
                i += 3;
                j++;
            }
        }

        particles.geometry.attributes.position.needsUpdate = true;
        particles.geometry.attributes.scale.needsUpdate = true;
        renderer.render(scene, camera);
        count += 0.08;
    }

    return {
        init
    }
}

3. template模板使用函数

<template>
    <div>
         <div ref="webgl" id="webgl" class="webgl">
    </div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import usePointStore from '@/hook/three/PointWaves'
// 生成区域
const webgl = ref()
// 粒子系统
const usePoint = usePointStore()
onMounted(() => {
    // 初始化一下
    usePoint.init(webgl.value)
})
</script>
<style scoped>
.webgl {
    position: absolute;
    bottom: 0;
    width: 100%;
}

最后

  • 有什么问题优先思考、各种尝试、问百度,然后询问AI
  • 没有AI的话使用这个吧:rednutmeg.top/aigc_web/pr…
  • 有什么问题都可以加群反馈给我