初识ThreeJS粒子特效

2024-01-20 15:39 月霖 640

粒子特效是为模拟现实中的水、火、雾、气等效果,其原理是将无数的单个“点”(粒子)组合使其呈现出固定形态,借由控制器、脚本来控制其整体或单个的运动,模拟出现真实的效果。

一、Points与点材质

ThreeJS中使用Points类来显示点,它的构造函数接收两个参数:

  • geometry:BufferGeometry的实例。
  • material:点材质PointsMaterial。
  • 点材质的主要属性:
  • color:材质颜色。
  • size:点的大小,默认为1.0。
  • map:贴图。
  • alphaMap:透明度贴图。控制不透明度(黑色:完全透明;白色:完全不透明)。
  • fog:是否受雾影响,默认为true。
  • sizeAttenuation:指定点的大小是否因相机深度而衰减,默认为true。
// 创建球几何体
const sphereGeometry = new THREE.SphereGeometry(3, 50, 50)

// 设置点材质
const pointMaterial = new THREE.PointsMaterial({
  color: '#ffffff',
  size: 0.05
})

// 通过球几何体创建点
const points = new THREE.Points(sphereGeometry, pointMaterial)

scene.add(points)

给点材质设置贴图

接着我们给点材质设置一下贴图,这里我使用的threejs版本是0.156,geometry上带有uv属性,这里的uv属性会将贴图作用于整个几何体上(这里是整个球几何体),而不是单独的一个点上,所以这里将uv属性删除,这样每个点都会应用上我们设置的贴图。

这是我们用到的贴图,同时我们将它作为透明度贴图,这样黑色的部分就是完全透明的。而要让材质支持透明度,还需要设置 transparent: true

// 载入纹理
const textureLoader = new THREE.TextureLoader()
const map = textureLoader.load('./texture/particles/1.png')
delete sphereGeometry.attributes.uv // 需要把uv删掉,否则贴图会作用于整个geometry

const pointMaterial = new THREE.PointsMaterial({
  color: '#ffffff',
  size: 0.25,
  map: map,
  alphaMap: map,
  transparent: true,
  depthWrite: false
})

二、使用顶点缓冲区创建点

刚才我们用的是球几何体创建的点,现在我们直接通过BufferGeometry来创建。

ThreeJS中提供了一系列绘制几何体的类,比如BoxGeometry、SphereGeometry,PlaneGeometry、CircleGeometry、CylinderGeometry等等,当这些几何体都不能满足需求的情况下,就需要使用BufferGeometry了,通过它可以向缓存中传输几何体的顶点坐标、面索引、顶点颜色、顶点法向量、顶点UV甚至是自定义属性,使用自定义属性和着色器材质配合使用,非常强大。读取或编辑 BufferGeometry中的数据需要使用BufferAttribute类,这个类用于存储与BufferGeometry相关的各种属性。

我们通过BufferGeometry创建一个包含5000个顶点的几何体,并且让这些点随机分布:

// 顶点个数
const count = 5000
// 设置缓冲区数组
const positions = new Float32Array(count * 3) // 每个顶点由x、y、z三个坐标分量组成
for (let i = 0; i < count * 3; i++) {
  positions[i] = (Math.random() - 0.5) * 30
}

const particleGeometry = new THREE.BufferGeometry()

// 设置缓冲区属性(顶点位置)
particleGeometry.setAttribute(
  'position',
  new THREE.BufferAttribute(positions, 3)
)

const textureLoader = new THREE.TextureLoader()
const map = textureLoader.load('./texture/particles/1.png')

const pointMaterial = new THREE.PointsMaterial({
  color: '#ffffff',
  size: 0.5,
  map: map,
  alphaMap: map,
  transparent: true,
  depthWrite: false
})

const points = new THREE.Points(particleGeometry, pointMaterial)
scene.add(points)

我们还可以为每个点设置不同的颜色:

// 设置顶点颜色
const colors = new Float32Array(count * 3) // 每个颜色有r、g、b三个分量

for (let i = 0; i < count * 3; i++) {
  colors[i] = Math.random()
}

// 设置缓冲区属性(顶点颜色)
particleGeometry.setAttribute(
  'color',
  new THREE.BufferAttribute(colors, 3)
)

pointMaterial.vertexColors = true // 启用顶点着色

三、雪花飞舞实例

最后看一个综合实例,实现了雪花飞舞的效果。完整代码如下:

import * as THREE from 'three'

// 创建场景
const scene = new THREE.Scene()

// 创建相机
const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  3
)
camera.position.set(0, 0, 5)

// 创建粒子
function createPoints(count, size, textureUrl) {
  const textureLoader = new THREE.TextureLoader()
  const map = textureLoader.load(textureUrl)

  const particleGeometry = new THREE.BufferGeometry()

  const vertices = new Float32Array(count * 3)

  for (let i = 0; i < count * 3; i++) {
    vertices[i] = Math.random() * 10 - 5
  }
  particleGeometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))

  const pointMaterial = new THREE.PointsMaterial({
    color: '#ffffff',
    transparent: true,
    map: map,
    alphaMap: map,
    size: size,
    depthWrite: false
  })
  const points = new THREE.Points(particleGeometry, pointMaterial)
  scene.add(points)
}

createPoints(10000, 0.1, './texture/particles/snow_1.png')
createPoints(5000, 0.1, './texture/particles/snow_2.png')

// 创建WebGL渲染器
const renderer = new THREE.WebGLRenderer({
  antialias: true
})
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.toneMapping = THREE.CineonToneMapping
renderer.toneMappingExposure = 1.0
renderer.shadowMap.enabled = true
document.body.appendChild(renderer.domElement)

// 渲染场景
const clock = new THREE.Clock()
function render() {
  const time = clock.getElapsedTime()

  // 让粒子动起来
  for (let i = 0; i < scene.children.length; i++) {
    if (scene.children[i].type === 'Points') {
      scene.children[i].rotation.x = time * 0.2
      scene.children[i].rotation.y = time * 0.02
    }
  }

  renderer.render(scene, camera)
  requestAnimationFrame(render)
}

render()

window.addEventListener('resize', () => {
  // 重置渲染器宽高比
  renderer.setSize(window.innerWidth, window.innerHeight)
  // 重置相机宽高比
  camera.aspect = window.innerWidth / window.innerHeight
  // 更新相机投影矩阵
  camera.updateProjectionMatrix()
})

这里将创建点的代码封装成了一个函数,然后用不同的贴图创建了两种雪花效果,并且在render函数中让点进行旋转,从而模拟了雪花下落的效果。