Photo by Marek Piwnicki on Unsplash
724 字
4 分钟
Vue+Threejs实现点云效果
index.vue
<script setup lang="ts"> import { useWave } from '.'
const { init } = useWave()
onMounted(() => { init(document.querySelector('#wave-container')) })</script>
<template> <div id="wave-container" class="h-1/3 w-full"></div></template>
<style lang="scss" scoped></style>index.ts
import * as THREE from 'three'
export function useWave() { const scene: THREE.Scene = new THREE.Scene() const renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, }) let camera: THREE.PerspectiveCamera
const GAP = 100 const AMOUNT_X = 50 const AMOUNT_Y = 50
let count = 0 let particles: THREE.Points
function init(container: HTMLDivElement | null) { // 如果container为空,抛出错误 if (!container) { throw new Error('no container') }
// 创建一个透视相机,参数为视角、宽高比、近裁剪面、远裁剪面 camera = new THREE.PerspectiveCamera( 60, container.clientWidth / container.clientHeight, 0.1, 10000, ) // 设置渲染器的大小 renderer.setSize(container.clientWidth, container.clientHeight) // 将渲染器的dom元素添加到container中 container.appendChild(renderer.domElement)
// 设置相机的位置 camera.position.z = 1000 camera.position.y = 500 camera.scale.set(0.5, 1, 1)
// 计算粒子数量 const particlesCount = AMOUNT_X * AMOUNT_Y
const positions = new Float32Array(particlesCount * 3) const scales = new Float32Array(particlesCount)
let i = 0 let j = 0
// 循环计算每个粒子的位置和大小 for (let ix = 0; ix < AMOUNT_X; ix++) { for (let iy = 0; iy < AMOUNT_Y; iy++) { positions[i] = ix * GAP - (AMOUNT_X * GAP) / 2 // x positions[i + 1] = 0 // y positions[i + 2] = iy * GAP - (AMOUNT_Y * GAP) / 2 // z
scales[j] = 1
i += 3 j++ } }
// 创建一个缓冲几何体,并设置位置和大小属性 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(0x4096ff) }, }, vertexShader: ` attribute float scale;
void main() { vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); gl_PointSize = scale * ( 300.0 / - mvPosition.z ); gl_Position = projectionMatrix * mvPosition; } `, fragmentShader: ` uniform vec3 color;
void main() { if ( length( gl_PointCoord - vec2( 0.5, 0.5 ) ) > 0.475 ) discard; gl_FragColor = vec4( color, 1.0 ); } `, })
// 创建一个点云,并添加到场景中 particles = new THREE.Points(geometry, material) scene.add(particles)
// 设置渲染器的大小和像素比例,并设置动画循环 renderer.setSize(container.clientWidth, container.clientHeight) renderer.setPixelRatio(window.devicePixelRatio) renderer.setAnimationLoop(render)
// 将渲染器的dom元素添加到container中,并添加窗口大小改变事件监听器 container.appendChild(renderer.domElement) window.addEventListener('resize', () => onWindowResize(container)) }
// 渲染函数 function render() { // 将相机对准场景的位置 camera.lookAt(scene.position) // 获取粒子的位置和缩放属性数组 const positions = particles.geometry.attributes.position.array const scales = particles.geometry.attributes.scale.array // 初始化索引 let i = 0 let j = 0 // 遍历粒子的位置和缩放属性数组 for (let ix = 0; ix < AMOUNT_X; ix++) { for (let iy = 0; iy < AMOUNT_Y; iy++) { // 计算粒子的位置 positions[i + 1] = Math.sin((ix + count) * 0.3) * 120 + Math.sin((iy + count) * 0.5) * 120 // 计算粒子的缩放 scales[j] = (Math.sin((ix + count) * 0.3) + 1) * 5 + (Math.sin((iy + count) * 0.5) + 1) * 5 // 更新索引 i += 3 j++ } } // 通知位置和缩放属性数组已经更新 particles.geometry.attributes.position.needsUpdate = true particles.geometry.attributes.scale.needsUpdate = true // 渲染场景 renderer.render(scene, camera) // 更新计数器 count += 0.05 }
function onWindowResize(container: HTMLDivElement) { camera.aspect = container.clientWidth / container.clientHeight camera.updateProjectionMatrix() renderer.setSize(container.clientWidth, container.clientHeight) }
return { init, }} Vue+Threejs实现点云效果
https://blog.dcwedu.top/posts/frontend/vue3/vue-threejs-point-cloud/