THREE shader实现高性能的动画(二)— 结合粒子系统Points实现满屏乱飞的小光点

1,920 阅读4分钟

先来了解一下THREE.Points

THREE.Points - 用来创造点的类,也用来批量管理粒子,这个类的构造函数可以接受两个参数,一个几何体和一个材质,几何体参数用来定义粒子的位置坐标,而材质参数用来格式化粒子. (本文就结合shaderMaterial来实现自定义样式)

实现思路

需求——实现一个满屏乱飞的小点点粒子系统。

首先定义buffer几何体

假设我们要在半径为1000的球体范围空间里面创建500个小光点

  let geometry = new THREE.BufferGeometry()
  const num = 500
  const radius = 1000
  let positions = new Float32Array(num * 3)
  let pulseSpeeds = new Float32Array(num)
  let orbitSizes = new Float32Array(num)
  let orbitSpeeds = new Float32Array(num)

数组壳子已经定义完了接下来往里面填充数据

  let posIndex = 0
  for (let i = 0; i < num; i++) {
    positions[i * 3] = Math.random() * radius - radius / 2
    positions[i * 3 + 1] = Math.random() * radius - radius / 2
    positions[i * 3 + 2] = Math.random() * radius - radius / 2
    pulseSpeeds[i] = pulseSpeed / 2 + (Math.random() * pulseSpeed)
    orbitSizes[i] = orbitSize / 2 + (Math.random() * orbitSize)
    orbitSpeeds[i] = -orbitSpeed / 2 + (Math.random() * orbitSpeed)
  }

先介绍一下里面的三个参数

  1. pulseSpeed - 小光点缩放的速度
  2. orbitSize - 小光点平移的范围
  3. orbitSpeed - 小光点平移的速度

上面这段代码的大体意思就是给500个点随机的分布到半径为1000的球体范围内,然后给每个小光点设置了随机的缩放速度,平移范围,平移速度。

将属性赋值到buffer几何体的attribute里

  geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3))
  geometry.addAttribute('pulseSpeed', new THREE.BufferAttribute(pulseSpeeds, 1))
  geometry.addAttribute('orbitSize', new THREE.BufferAttribute(orbitSizes, 1))
  geometry.addAttribute('orbitSpeed', new THREE.BufferAttribute(orbitSpeeds, 1))

然后是本文的重点了,定义材质中的顶点着色器。

  1. 第一步接受外部传进来的attribute
    attribute float pulseSpeed;
    attribute float orbitSpeed;
    attribute float orbitSize;

有人可能会问,外部不是传进来4个attribute对象么,那是因为position这个对象THREE在框架内部默认做了接受,不需要我们自己在手动接受一次。这就是为什么我们在vertexShader里面默认可以使用position变量的原因。

  1. 第二步我们接受外部传进来的uniform对象
    uniform float time;
    uniform float size;
    uniform float scale;

这里我们传递了3个变量进来,

  • time - 用于动画的时间参数
  • size - 每个小光点的基础大小设置
  • scale - 每个小光点的距离屏幕距离的缩放配置(用于实现粒子的近大远小效果)
  1. 定义小光点们的顶点位置 先看看我们平常的定义顶点的做法
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.);

其实就是对我们场景内的坐标进行一个视图模型矩阵变换,再进行一次相机矩阵的变换,转换成我们屏幕上展示的3d效果。 这里我们对他进行一个小小的变换,给position位置加上一个随时间变化的动画。

怎么实现连续的不违和的动画呢?这里我们选用三角函数,有周期性,又连贯,是个写动画的好工具

我们对position进行一个变换

vec3 transformed = animatedPosition + vec3(sin(time + orbitSpeed) * orbitSize, cos(time * orbitSpeed) * orbitSize, cos(time * orbitSpeed) * orbitSize);

这个就是我们加上动画之后的新的position位置了。

gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.);

到这里我们对于位置信息的处理已经全部完成。

  1. 定义小光点的渲染尺寸(给小光点附加一个周期性的缩放动画)。
float animatedSize = size * (scale / -mvPosition.z);
animatedSize *= 1.0 + sin(time * pulseSpeed);
gl_PointSize = animatedSize;
  1. 最后我们再把uv值存到vUv中传递到fragmentShader中用于贴图。

varying vec2 vUv; vUv = uv; 到这里顶点着色器部分我们已经讲完了

定义片元着色器

直接贴图即可

gl_FragColor = texture2D(map, vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y));

最后我们再贪玩的给贴图加个颜色上面的处理。

gl_FragColor.rgb *= color.rgb * intensity / sqrt((vUv.x - 0.5) * (vUv.x - 0.5) + (vUv.y- 0.5) * (vUv.y - 0.5));

这句代码起啥作用呢 *color是给贴图附加一个可配置的颜色,这样可以在外面随意配置小光点的着色了。 后面那一长串又是干嘛的呢,就是贴图越接近中心越接近白色,用来模仿一个光点的发光的假象,当然传进来的贴图就是个漂亮的光点可以不进行下面那部分操作。

定义材质

有了uniforms,vertexShader,fragmentShader定义ShaderMaterial就很简单了,具体的一些参数看看api就可以了

最后我们定义粒子系统对象加到scene里面就好了

let floater = new THREE.Points(geometry, material)
scene.add(floater)

当然里面具体的参数还需要大家自己去微调,都是些细节操作。越细腻动画效果就越好。

到这里我们就实现了一个满屏乱飞的小光点粒子系统,大家还可以自行实验,修改顶点坐标的动画方式,也可以实现不断上浮的小光点动画,以及各种能想到的一些粒子动画效果,全看脑洞了

是不是很简单!

感谢大家的观看!有错的地方欢迎指正,单纯是个人的理解不一定说的都对。如需转载,请注明个出处哈