ThreeJs入门48-粒子系统-粒子的运动

1,013 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

示例代码采用three.js-r73版本: github.com/mrdoob/thre…

我们现在完成了多个粒子系统的静态加载,下面我们要开始完成粒子的运动了。

动态效果展示

先让我们来看看最终展示效果: GIF.gif 很酷炫吧,让我们来完成它。我们先看下我们需要做哪些事情。

  • 计算每一帧的时间,让分组进行旋转
  • 根据动态或者静态模型,调整每个模型顶点的位置
    • 如果是静态模型,不进行运动
    • 最开始的时候没有移动,设置移动,向下
    • 然后判断粒子是向下还是向上移动,并做动态点位处理
    • 如果是向下移动完成,那么休息一会,再向上移动
    • 如果是向上移动完成,那么休息一会,再向下移动

计算每一帧时间

  • 创建Clock实例,该实例可用于追踪时间
var clock = new THREE.Clock()
function render() {
  // 计算每一帧时间
  delta = clock.getDelta()
  // 如果每一帧度过的时间大于2微秒,就设置为2,保证每一帧时间都在2微秒以内
  delta = delta < 2 ? delta : 2;
  // 控制整个分组旋转
  parent.rotation.y += -0.02 * delta;
	...
  
  renderer.clear()
  renderer.render(scene, camera);
}

GIF.gif 这样我们场景的旋转就完成了。

动态调整模型顶点位置

  • 遍历物体数组,缓存一些数据
//! 根据动态或者静态模型调整,每个模型的顶点位置
for (var j = 0; j < meshes.length; j++) {
  data = meshes[j]
  mesh = data.mesh

  vertices = data.vertices
  vertices_tmp= data.vertices_tmp
  vl = data.vl
	// 对物体做处理
  ... 
}

静态物体

  • 如果是静态物体,跳过不做处理
//! 如果是静态模型,跳过
if(!data.dynamic) {
  continue;
}

根据开始时间,判断是否开始动画

  • 如最开始的时候是没有移动的
  • 每个物体都缓存了开始时间,每帧开始时间减1,
  • 当开始时间小于0,并且没有运动,设置物体向下运动
//! 最开始的时候,没有移动,设置移动,向下
if(data.start > 0) {
  data.start -= 1 // 每帧减1
}else {
  // 开始动画
  if(!data.started) {
    data.direction = -1; // 向下运动
    data.started = true
  }
}

设置粒子是向下还是向上运动

  • 遍历顶点,通过判断物体是向上还是向下运动,做对应的处理

遍历顶点

for (let i = 0; i < vl; i++) {
  p = vertices[i]; // 顶点
  vt = vertices_tmp[i]; // 顶点缓存的数据
  // 向上向下运动处理
  ... 
}

向下运动

  • 向下运动主要是顶点的 y 坐标移动到 0 的位置
  • 如果 y 大于0,需要向下运动
  • 如果已经小于等于0,说明已经到达底部,记录到达底部顶点数
// 向下运动
if(data.direction < 0){
  if (p.y > 0) {
    p.x += 1.5 * (0.5 - Math.random()) * data.speed * delta;
    // 向下的概率明显大于向上的概率,所有整个人物粒子总有一个时刻是向下的
    p.y += 3.0 * (0.15 - Math.random()) * data.speed * delta;
    p.z += 1.5 * (0.5 - Math.random()) * data.speed * delta;
  } else {
    // 默认为0静止的, x,y,z,down,up
    if (!vt[3]) {
      vt[3] = 1; // 向下运动
      data.down += 1; // 到达底部的顶点数
    }
  }
}
  • x,z控制粒子左右前后摇摆
  • y控制上下移动距离,由于是随机数,所以会有向上移动的粒子
  • 0.15 - Math.random()的范围是[0.15, -0.75],向上移动概率是15%,向下移动概率是75%
  • 所以粒子整体是向下移动的

这个时候我们可以让粒子向下运动了,我们还需要告诉threejs,我更新了几何体的顶点信息

mesh.geometry.verticesNeedUpdate = true;

GIF.gif 我们的向下运动就完成了

向上移动

  • 向上运动我们需要把落到0位置的粒子,移动回原来的高度,需要计算落地点到原始位置的距离
  • 通过让落地点的粒子逐渐靠近原始位置,来达到向上运动的效果
// 向上运动
if (data.direction > 0) {
  // 计算落地点到原始位置的距离
  d = Math.abs(p.x - vt[0]) + Math.abs(p.y - vt[1]) + Math.abs(p.z - vt[2]);
  if (d > 1) {
    // 越来越向vt[0]靠近,也就是原始位置
    p.x += -(p.x - vt[0]) / d * data.speed * delta * (0.85 - Math.random());
    p.y += -(p.y - vt[1]) / d * data.speed * delta * (1 - Math.random());
    p.z += -(p.z - vt[2]) / d * data.speed * delta * (0.85 - Math.random());
  } else {
    // 默认up为0静止的, x,y,z,down,up
    if (!vt[4]) {
      vt[4] = 1; // 向上移动
      data.up += 1; // 到达点对应位置的顶点数
    }
  }
}
  • 有我们向下运动时,粒子随机的落地点不确定,所以xyz都要回到原始位置
  • -(p.x - vt[0]) / d每次移动总距离的一部分

由于第一次是向下运动,我们还没有告诉粒子什么时候向上运动,也就是direction什么时候大于0,接下来我们处理下这个逻辑

向下或向上移动完成切换运动

  • 当向上或向下顶点数等于总顶点数,让停留时间开始计算,每次减1
  • 停留时间结束后,就可以切换运动了
//! 如果是向下移动完成,那么休息一会,再向上移动
// 如果向下的顶点数等于总顶点数,就可以向上移动了
if (data.down === vl) {
  if (data.delay === 0) { // 等待时间结束
    data.direction = 1;
    data.speed = 10;
    data.down = 0;
    data.delay = 300;

    for (let i = 0; i < vl; i++) {
      vertices_tmp[i][3] = 0;
    }
  } else { // 等待时间每次减1
    data.delay -= 1;
  }
}

//! 如果是向上移动完成,那么休息一会,再向下移动
if (data.up === vl) {
  if (data.delay === 0) {
    data.direction = -1;
    data.speed = 10;
    data.up = 0;
    data.delay = 300;

    for (let i = 0; i < vl; i++) {
      vertices_tmp[i][4] = 0;
    }
  } else {
    data.delay -= 1;
  }
}

这样,我们的粒子运动效果就完成啦。 codepen示例代码