本教程的第一部分介绍了公告牌粒子技术的基本概念,并基于THREE.Sprite实现了一个简单的粒子场景。这次我会再用一个简单的例子,让你对对粒子的速度,加速度等物理特性有进一步的认识,这将是实现第三部分粒子系统的基础。

在three.js中实现公告牌粒子系统有一个更好的选择,就是使用THREE.Points对象,同Sprite一样Points对象同样是始终面向摄像机的,不同于Sprite的平面构造,Points对象是3D空间中的一些顶点,每个顶点就是一个粒子。相对于渲染一个面的开销,THREE.Points的性能优势很明显,且同样支持贴图,最终实现效果和Sprite没什么区别,效率确有了提高。

THREE.Points实现的粒子效果

THREE.Points默认使用BufferGeometry对象创建顶点数据,我们需要在着色器shader中设置粒子顶点的大小,位置,速度,加速度,并根据角度值旋转贴图。

先创建顶点和片段着色器,并创建大小,速度,重力加速度等参数。

const vertexShader = `
  uniform float u_time;  //时间累计
  uniform vec3 u_gravity; //粒子重力加速度
  attribute vec3 velocity; //粒子速度
  void main() {
    vec3 vel = velocity * u_time; //根据时间计算速度  
    vel = vel + u_gravity * u_time * u_time; //根据时间计算加速度 
    vec3 pos = position + vel;  //计算位置偏移
    gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
    gl_PointSize = 6.0;
  }
`
const fragmentShader = `
  void main() {
    vec3 color = vec3(1.0);
    gl_FragColor = vec4(color, 1.0);
  }
`

创建100个粒子,并设置随机的位置和速度值,然后将这些值传递到着色器中。

_initParticles() {
    const uniforms = {
      u_time: { value: 0 },
      u_gravity: { value: new THREE.Vector3(0, -5, 0) }
    }
    const material = new THREE.ShaderMaterial({
      uniforms: uniforms,
      vertexShader,
      fragmentShader
    })
    const COUNT = 100
    const positions = new Float32Array(COUNT*3)
    const velocity = new Float32Array(COUNT*3)
    const geometry = new THREE.BufferGeometry()
    const size = 0
    const speed = 20
    for(let i = 0; i < positions.length; i++) {
      positions[i] = (Math.random() - 0.5) * size
      velocity[i] = (Math.random() - 0.5) * speed
    }
    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
    geometry.setAttribute('velocity', new THREE.BufferAttribute(velocity, 3))
    const mesh = new THREE.Points(geometry, material)
    this.scene.add(mesh)
    this.uniforms = uniforms
}

在render函数中,更新时间参数值到顶点着色器,随着时间值的累积,我们的粒子就嗨起来啦。

_render() {
    this.uniforms.u_time.value += this.clock.getDelta() //更新时间参数
    if(this.uniforms.u_time.value> 3.0) {
      this.uniforms.u_time.value = 0
    }
    this.renderer.render(this.scene, this.camera)
    requestAnimationFrame(this._render.bind(this))
}
头皮屑粒子

嗯,看起来有点平淡,我们再来加入贴图,并赋予粒子不同的颜色,旋转角度和大小,看看效果如何,为此需要在顶点和片段着色器中添加一些新的变量。

//顶点着色器中添加变量
attribute float angle;
varying float vAngle;
varying vec3 vColor;
//片段着色器中根据顶点着色器传过来的变量旋转贴图和设置颜色
uniform sampler2D texture;
varying float vAngle;
varying vec3 vColor;
void main() {
  //旋转贴图
  float c = cos(vAngle);
  float s = sin(vAngle);
  vec2 uv = vec2(
    c * (gl_PointCoord.x - 0.5) + s * (gl_PointCoord.y - 0.5) + 0.5,
    c * (gl_PointCoord.y - 0.5) - s * (gl_PointCoord.x - 0.5) + 0.5
  ); 
  //粒子颜色
  gl_FragColor = vec4(vColor, 1.0);
  vec4 color = texture2D(texture, uv);
  gl_FragColor = gl_FragColor * color;
}

在主程序中将变量值传递到着色器中。

for(let i = 0; i < positions.length; i+=1) {
  positions[i] = (Math.random() - 0.5) * size
  velocity[i] = (Math.random() - 0.5) * speed
  color[i] = Math.random()
}
for(let i = 0; i < COUNT; i++) {
  angle[i] = Math.PI * 2 * Math.random()
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
geometry.setAttribute('velocity', new THREE.BufferAttribute(velocity, 3))
geometry.setAttribute('color', new THREE.BufferAttribute(color, 3))
geometry.setAttribute('angle', new THREE.BufferAttribute(angle, 1))
最终效果

这次就到这里吧,总结一下,第二部分我们学习了如何在顶点和片段着色器里设置粒子的各项属性参数,这些知识都是实现一个粒子系统的基础,也许你觉得还不够酷炫,别着急好戏还在后头。第三部分我们会实现一个基础的粒子系统,并用该系统实现诸如,火焰,烟雾,雪花等酷炫的效果,祝学习愉快,下次见!

源文件和演示链接