threejs实现模型粒子化,并实现模型粒子切换动画

效果预览:

模型粒子化切换动画

模型粒子化

直接遍历模型的geometry.attributes.position,不过为了实现模型粒子动画,切换的模型粒子数要和原本模型的粒子数相同,我的解决办法是补齐粒子数少的模型。还要给每一个粒子添加一个随机延迟,不然粒子同步运动效果不好看

constructor({ origin, target, allTime, delayPct, easing }) {
    allTime = allTime || 5000;
    delayPct = delayPct || 0;
    easing = easing || TWEEN.Easing.Sinusoidal.InOut;
    let position1 = origin.geometry.attributes.position;
    let position2 = target.geometry.attributes.position;
    const bool = position1.count < position2.count;
    if (bool) {
      position1 = position2;
      position2 = origin.geometry.attributes.position;
    }
    let x2, y2, z2;
    const vertices1 = [],
      vertices2 = [],
      delays = [];
    for (let i = 0; i < position1.count; i++) {
      const x1 = position1.getX(i);
      const y1 = position1.getY(i);
      const z1 = position1.getZ(i);
      vertices1.push(x1, y1, z1);

      const j = i % position2.count;
      x2 = position2.getX(j);
      y2 = position2.getY(j);
      z2 = position2.getZ(j);
      vertices2.push(x2, y2, z2);

      delays.push(Math.random() * delayPct);
    }

    const geometry = new THREE.BufferGeometry();
    let position_v = vertices1;
    let target_v = vertices2;
    if (bool) {
      position_v = vertices2;
      target_v = vertices1;
    }
    geometry.setAttribute(
      "position",
      new THREE.Float32BufferAttribute(position_v, 3)
    );
    geometry.setAttribute(
      "target",
      new THREE.Float32BufferAttribute(target_v, 3)
    );
    geometry.setAttribute("delay", new THREE.Float32BufferAttribute(delays, 1));
    geometry.setDrawRange(0, position1.count);

    const material = this.getMaterial({
      color: 0xffffff,
      size: 1,
    });
    this.points = new THREE.Points(geometry, material);
    this.animationInit(1 + delayPct, allTime, easing, this.endCallback);
  }

顶点着色器实现动画

传统办法是使用cpu计算每一个点的位置并在requestAnimationFrame中更新,这种方式完全没有利用到gpu的计算优势,这种简单且大量的计算用顶点着色器是很简单的,我们只要一个进程百分比的uniforms就能实现切换动画,问题的关键就变为了找到进程百分比和位置的对应关系,然后通过改变进程百分比就实现了动画。我这里为了动画效果还加了一个周期扰动,让动画不那么死板

getMaterial(options) {
    options = Object.assign(
      {
        color: 0xffffff,
        size: 1,
        map: new THREE.TextureLoader().load(
          `${VITE_PUBLIC_PATH || "/public/"}textures/sprites/circle.png`
        ),
      },
      options
    );
    this.uniforms = {
      color: { value: new THREE.Color(options.color) },
      size: { value: options.size },
      map: { value: options.map },
      percent: { value: 0 },
    };
    const material = new THREE.ShaderMaterial({
      // transparent: true,
      depthTest: true,
      depthWrite: true,
      // blending: THREE.AdditiveBlending,
      uniforms: this.uniforms,
      vertexShader: `
      attribute float delay;
      attribute vec3 target;
      uniform float percent;
      uniform float size;

      void main() {
        float p = clamp(percent - delay,0.0,1.0);  //进程百分比
        float p2 = (0.5 - abs(p-0.5))*6.2831;      //进程百分比相联系的扰动参数
        vec3 translate = vec3(sin(p2),sin(p2*1.4),p2*cos(p2*1.6)*0.3);  //扰动
        vec3 _position = mix( position,target,p);  //根据进程百分比插值计算当前位置
        vec4 mvPosition = modelViewMatrix * vec4(_position+translate*0.1, 1.0); 
        gl_Position = projectionMatrix * mvPosition;
        gl_PointSize =  sqrt(30.0 / -mvPosition.z)*size;
      }
      `,
      fragmentShader: `
      uniform vec3 color;
      uniform sampler2D map;
      void main() {
        gl_FragColor = vec4(color, 1.0);
				// gl_FragColor = gl_FragColor * texture2D( map, gl_PointCoord );
      }
      `,
    });
    // gui.add(material.uniforms.percent, "value", 0, 2, 0.01);
    return material;
  }

更新进程百分比我用了tween,方便实现缓动,由慢到快再又快到慢的缓动可让动画表现更符合直觉

/**
   * 初始化动画,设置对象消失的百分比、持续时间和缓动函数。
   * @param {number} percent - 对象消失的百分比。
   * @param {number} duration - 动画的持续时间,默认为 2000 毫秒。
   * @param {Function} easing - 动画的缓动函数,默认为 TWEEN.Easing.Sinusoidal.InOut。
   * @returns {TWEEN} - 返回一个Tween对象,用于控制动画。
   */
  animationInit(
    percent,
    duration = 2000,
    easing = TWEEN.Easing.Sinusoidal.InOut,
    endCallback = () => {}
  ) {
    if (this.action) {
      this.action = null;
    }
    this.action = new TWEEN.Tween(this.uniforms.percent)
      .to({ value: percent }, duration)
      .easing(easing)
      .onUpdate((obj) => {})
      .onComplete((res) => {
        cancelAnimationFrame(this.animateRequestID);
        this.animateRequestID = null;
        endCallback();
      })
      .onStop(() => {
        cancelAnimationFrame(this.animateRequestID);
        this.animateRequestID = null;
        endCallback();
      });
    this.animateRequestID = null;
    // this.startAnimation();
  }

  // 开始动画
  startAnimation() {
    if (this.animateRequestID) return;

    this.action.start();
    const animate = () => {
      this.animateRequestID = requestAnimationFrame(animate);
      TWEEN.update();
    };
    animate();
  }

  // 停止动画
  stopAnimation() {
    if (!this.animateRequestID) return;
    this.action.stop();
  }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值