Three.js模型粒子变换效果

Three.js模型粒子变换

在Three中,模型都是通过顶点和面来组成的,通过points基类渲染所有顶点可实现模型点云效果;通过Tween.js实现各个顶点的位置转移动画,即可实现两个模型间无缝的粒子变换效果
在这里插入图片描述

原理

  • 通过points粒子系统,渲染出模型的所有顶点,实现粒子点云效果。
  • 通过修改points粒子系统几何体的顶点信息,渲染不同模型的粒子效果。
  • 通过Tween.js,实现粒子系统中两个不同模型顶点信息的平滑切换,实现粒子变换效果。
  • 不同模型间的顶点数量可能不一致,在转换时需要补齐或者删减,使其一致。
  • 通过加载外部模型,可以实现更加复杂的效果,重点在于对粒子系统顶点坐标变换的控制。

核心方法

      const initpointsMeshAnimate = (pointsMesh, targetMesh) => {
        // 源模型的顶点
        const originVertices = pointsMesh.geometry.vertices;
        // 目标模型的顶点
        const targetVertices = targetMesh.geometry.vertices;

        // 源粒子数大于目标模型顶点数 需减少
        if (originVertices.length > targetVertices.length) {
          pointsMesh.geometry.vertices = originVertices.slice(0, targetVertices.length);
        }
        // 源粒子数小于目标模型顶点数 需补齐
        if (originVertices.length < targetVertices.length) {
          pointsMesh.geometry.vertices = originVertices.concat(
            new Array(targetVertices.length - originVertices.length)
              .fill(0)
              .map(() => new THREE.Vector3())
          );
        }

        // 遍历每一个粒子
        pointsMesh.geometry.vertices.forEach(async (v, i, vertices) => {
          // 粒子从原始位置到目标位置的平滑移动,完成时间2000ms
          new TWEEN.Tween({
            x: vertices[i % originVertices.length].x,
            y: vertices[i % originVertices.length].y,
            z: vertices[i % originVertices.length].z,
          })
            .to(
              {
                x: targetVertices[i].x,
                y: targetVertices[i].y,
                z: targetVertices[i].z,
              },
              2000
            )
            .onUpdate(({ x, y, z }) => {
              pointsMesh.geometry.vertices[i].set(x, y, z);
              pointsMesh.geometry.verticesNeedUpdate = true;
            })
            .delay(500)
            .yoyo(true)
            .repeat(Infinity)
            .start(); 
        });
      };

完整源码

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <script type="module">
      import * as util from './js/util.js';
      import * as THREE from './node_modules/three/build/three.module.js';
      import { creatWallByPath } from './js/effect.js';
      import TWEEN from './js/tween.module.js';

      const scene = util.initScene();
      const stats = util.initStats();
      const camera = util.initCamera();
      const renderer = util.initRender();
      const controls = util.initOrbitControls(camera, renderer);

      util.windowReSize(renderer, camera);
      util.addAxisHelper(scene);
      util.addAmbientLight(scene);
      util.addDirectionalLight(scene);

      // 渲染函数
      const render = () => {
        TWEEN.update();
        renderer.render(scene, camera);
        stats.update();
        requestAnimationFrame(render);
      };

      const initpointsMeshAnimate = (pointsMesh, targetMesh) => {
        // 源模型的顶点
        const originVertices = pointsMesh.geometry.vertices;
        // 目标模型的顶点
        const targetVertices = targetMesh.geometry.vertices;

        // 源粒子数大于目标模型顶点数 需减少
        if (originVertices.length > targetVertices.length) {
          pointsMesh.geometry.vertices = originVertices.slice(0, targetVertices.length);
        }
        // 源粒子数小于目标模型顶点数 需补齐
        if (originVertices.length < targetVertices.length) {
          pointsMesh.geometry.vertices = originVertices.concat(
            new Array(targetVertices.length - originVertices.length)
              .fill(0)
              .map(() => new THREE.Vector3())
          );
        }

        // 遍历每一个粒子
        pointsMesh.geometry.vertices.forEach(async (v, i, vertices) => {
          // 粒子从原始位置到目标位置的平滑移动,完成时间2000ms
          new TWEEN.Tween({
            x: vertices[i % originVertices.length].x,
            y: vertices[i % originVertices.length].y,
            z: vertices[i % originVertices.length].z,
          })
            .to(
              {
                x: targetVertices[i].x,
                y: targetVertices[i].y,
                z: targetVertices[i].z,
              },
              2000
            )
            .onUpdate(({ x, y, z }) => {
              pointsMesh.geometry.vertices[i].set(x, y, z);
              pointsMesh.geometry.verticesNeedUpdate = true;
            })
            .delay(500)
            .yoyo(true)
            .repeat(Infinity)
            .start(); // 开始动画
        });
      };

      const main = () => {
        const material = new THREE.MeshBasicMaterial({ color: 0x008888 });

        // 盒子
        const cubeGeometry = new THREE.BoxGeometry(40, 40, 40, 20, 20, 20);
        const cube = new THREE.Mesh(cubeGeometry, material);
        cube.position.x = -60;

        // 球
        const ballGeometry = new THREE.SphereGeometry(20, 40, 32);
        const ball = new THREE.Mesh(ballGeometry.clone(), material);
        ball.position.x = 60;

        // points
        const points = new THREE.Points(
          ballGeometry,
          new THREE.PointsMaterial({
            size: 0.4,
            color: 0x00ffff,
          })
        );
        points.scale.x = 1.5;
        points.scale.y = 1.5;
        points.scale.z = 1.5;

        initpointsMeshAnimate(points, cube);

        scene.add(ball);
        scene.add(cube);
        scene.add(points);
      };

      main();
      render();
    </script>
  </body>
</html>

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值