You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

react-three-fiber中useFrame()内多模型动画不同步问题

解决模型旋转同步问题的方案

核心问题是所有模型共享了相同的旋转更新逻辑,要让每个模型旋转独立,需要给每个实例分配专属的旋转参数,并基于时间而非固定步长计算旋转角度。

具体实现步骤

  • 给每个模型实例生成独立的旋转配置:在组件内部用useRef存储每个模型独有的初始旋转角度、旋转速度,以及动画启动的时间偏移。
  • 在useFrame中基于时间计算旋转:用当前帧的时间减去模型的启动时间,结合专属速度计算旋转角度,避免固定步长导致的同步。
  • 结合淡入淡出状态控制旋转:确保旋转只在模型可见阶段生效,或者根据动画进度调整旋转状态。

代码示例(TypeScript)

import { useRef, useState, useEffect } from 'react';
import { useFrame, useLoader } from '@react-three/fiber';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

interface ModelFadeWrapperProps {
  modelPath: string;
}

export default function ModelFadeWrapper({ modelPath }: ModelFadeWrapperProps) {
  const gltf = useLoader(GLTFLoader, modelPath);
  const meshRef = useRef<THREE.Group>(null);
  // 存储当前模型的专属动画参数
  const animParams = useRef({
    startDelay: Math.random() * 3000, // 0-3秒随机延迟启动
    rotationSpeed: (Math.random() - 0.5) * 0.02, // 随机旋转速度,正负方向都有
    initialRotation: Math.random() * Math.PI * 2, // 初始旋转角度随机
    startTime: 0, // 动画实际启动时间
    isActive: false, // 是否处于可见状态
    opacity: 0,
  });
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    const timer = setTimeout(() => {
      animParams.current.startTime = performance.now();
      animParams.current.isActive = true;
    }, animParams.current.startDelay);

    // 这里可以添加循环动画的逻辑,比如淡出后重置位置和参数
    return () => clearTimeout(timer);
  }, []);

  useFrame((state) => {
    if (!meshRef.current || !animParams.current.isActive) return;

    const elapsed = (state.clock.getElapsedTime() * 1000) - animParams.current.startTime;
    // 计算淡入淡出(示例逻辑,可根据需求调整)
    if (elapsed < 1000) {
      animParams.current.opacity = elapsed / 1000;
    } else if (elapsed > 4000) {
      animParams.current.opacity = 1 - (elapsed - 4000) / 1000;
      if (animParams.current.opacity <= 0) {
        animParams.current.isActive = false;
        // 重置位置和动画参数,准备下一次循环
        animParams.current.startDelay = Math.random() * 3000;
        animParams.current.startTime = performance.now() + animParams.current.startDelay;
        animParams.current.initialRotation = Math.random() * Math.PI * 2;
        // 设置新的随机位置
        meshRef.current.position.set(
          (Math.random() - 0.5) * 10,
          (Math.random() - 0.5) * 5,
          (Math.random() - 0.5) * 10
        );
        setTimeout(() => {
          animParams.current.isActive = true;
        }, animParams.current.startDelay);
      }
    }

    // 计算独立的旋转角度:初始角度 + 已过时间 * 旋转速度
    meshRef.current.rotation.y = animParams.current.initialRotation + (elapsed / 1000) * animParams.current.rotationSpeed;
    // 设置透明度
    meshRef.current.children.forEach(child => {
      if ('material' in child && child.material) {
        (child.material as THREE.MeshStandardMaterial).opacity = animParams.current.opacity;
        (child.material as THREE.MeshStandardMaterial).transparent = true;
      }
    });
  });

  useEffect(() => {
    if (gltf) {
      // 设置初始随机位置
      meshRef.current?.position.set(
        (Math.random() - 0.5) * 10,
        (Math.random() - 0.5) * 5,
        (Math.random() - 0.5) * 10
      );
      setIsReady(true);
    }
  }, [gltf]);

  return isReady ? <primitive ref={meshRef} object={gltf.scene} /> : null;
}

关键要点说明

  • 专属参数存储:用useRef保存每个模型的rotationSpeed、initialRotation、startDelay等参数,确保每个实例的值都是随机且独立的。
  • 时间驱动的旋转计算:基于state.clock.getElapsedTime()获取全局时间,减去模型自身的startTime得到该模型的已运行时间,再乘以专属速度计算旋转角度,彻底避免同步问题。
  • 循环动画重置:当模型淡出后,重新生成随机参数和位置,设置新的启动延迟,实现无限循环的独立动画。

内容的提问来源于stack exchange,提问作者BillydogTheKid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 20:38:19