You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在React及React Native中实现Three.js的BVHLoader动画展示功能?

如何在React及React Native中实现Three.js的BVHLoader动画展示功能?

嘿,我完全懂你现在的头疼——把Three.js原生的BVHLoader示例搬到React或者React Native里,总感觉隔着一层窗户纸摸不着门道对吧?其实有挺直接的实现路径,我给你拆成React和React Native两部分来讲,都是实战能用的方案:

一、React 端实现(Web)

咱直接用React生态里专门对接Three.js的两个库:@react-three/fiber(负责把Three.js的渲染逻辑和React的生命周期绑定)和@react-three/drei(提供一堆现成的工具组件,省得自己造轮子),这俩搭配起来比手动封装原生Three.js爽太多了。

步骤1:安装依赖

先把需要的包装到位:

npm install three @react-three/fiber

如果需要额外的辅助组件(比如骨骼调试线),也可以装上@react-three/drei

npm install @react-three/drei

步骤2:写BVH动画组件

核心逻辑和原生Three.js一致,只是用React Hooks来封装:

import { useLoader, useFrame } from '@react-three/fiber';
import { BVHLoader } from 'three/examples/jsm/loaders/BVHLoader';
import { AnimationMixer, SkinnedMesh, BoxGeometry, MeshStandardMaterial, SkeletonHelper } from 'three';

export default function BVHAnimation() {
  // 加载你的BVH动画文件,替换成自己的文件路径
  const bvhData = useLoader(BVHLoader, '/animations/walk.bvh');

  // 创建一个基础网格作为骨骼的载体(你也可以换成自己的3D模型)
  const geometry = new BoxGeometry(1, 2, 1);
  // 必须开启skinning,不然骨骼动画没法绑定到网格上
  const material = new MeshStandardMaterial({ skinning: true, color: '#44aa88' });
  const skinnedMesh = new SkinnedMesh(geometry, material);

  // 把BVH的骨骼树挂载到网格上,完成绑定
  skinnedMesh.add(bvhData.skeleton.bones[0]);
  skinnedMesh.bind(bvhData.skeleton);

  // 初始化动画混合器,启动动画
  const mixer = new AnimationMixer(bvhData.skeleton.bones[0]);
  const animationAction = mixer.clipAction(bvhData.clip);
  animationAction.play();

  // 用useFrame钩子驱动动画每一帧更新
  useFrame((_, deltaTime) => {
    mixer.update(deltaTime);
  });

  return (
    <>
      {/* 渲染绑定了骨骼的网格 */}
      <primitive object={skinnedMesh} />
      {/* 可选:添加骨骼辅助线,方便调试骨骼结构 */}
      <primitive object={new SkeletonHelper(bvhData.skeleton.bones[0])} />
    </>
  );
}

步骤3:在主组件里用Canvas包裹

@react-three/fiberCanvas组件会帮你处理Three.js的场景、相机、渲染循环这些底层工作:

import { Canvas } from '@react-three/fiber';

function App() {
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Canvas camera={{ position: [0, 2, 5] }}>
        {/* 添加光源,不然网格会黑糊糊的 */}
        <ambientLight intensity={0.5} />
        <directionalLight position={[5, 5, 5]} intensity={1} />
        {/* 引入我们写的BVH动画组件 */}
        <BVHAnimation />
      </Canvas>
    </div>
  );
}

export default App;

二、React Native 端实现(移动端)

React Native里不能直接用Web版的Three.js,得用专门适配移动端的@react-three/native,它基于expo-gl或者react-native-webgl来提供WebGL环境,这里以Expo项目为例(裸项目配置类似,只是要额外处理WebGL的原生依赖)。

步骤1:安装依赖

用Expo CLI安装所需的包:

expo install @react-three/native three expo-gl expo-asset

步骤2:处理BVH资源加载

React Native里的本地资源需要用expo-asset来加载,先把你的BVH文件放到项目的assets文件夹里,然后提前加载资源:

import { Asset } from 'expo-asset';

// 提前加载BVH资源(可以放在App组件的初始化逻辑里)
const loadBVHAsset = async () => {
  const bvhAsset = require('./assets/walk.bvh');
  await Asset.loadAsync(bvhAsset);
  return Asset.fromModule(bvhAsset).uri;
};

步骤3:写移动端的BVH动画组件

逻辑和Web端几乎一致,只是资源路径换成Expo Asset的URI:

import React, { useEffect, useState } from 'react';
import { Canvas, useLoader, useFrame } from '@react-three/native';
import { BVHLoader } from 'three/examples/jsm/loaders/BVHLoader';
import { AnimationMixer, SkinnedMesh, BoxGeometry, MeshStandardMaterial, SkeletonHelper } from 'three';

function BVHAnimation({ bvhPath }) {
  const bvhData = useLoader(BVHLoader, bvhPath);
  const geometry = new BoxGeometry(1, 2, 1);
  const material = new MeshStandardMaterial({ skinning: true, color: '#44aa88' });
  const skinnedMesh = new SkinnedMesh(geometry, material);

  skinnedMesh.add(bvhData.skeleton.bones[0]);
  skinnedMesh.bind(bvhData.skeleton);

  const mixer = new AnimationMixer(bvhData.skeleton.bones[0]);
  const animationAction = mixer.clipAction(bvhData.clip);
  animationAction.play();

  useFrame((_, deltaTime) => {
    mixer.update(deltaTime);
  });

  return (
    <>
      <primitive object={skinnedMesh} />
      <primitive object={new SkeletonHelper(bvhData.skeleton.bones[0])} />
    </>
  );
}

// 主App组件
export default function App() {
  const [bvhPath, setBvhPath] = useState(null);

  useEffect(() => {
    loadBVHAsset().then(path => setBvhPath(path));
  }, []);

  if (!bvhPath) return null;

  return (
    <Canvas camera={{ position: [0, 2, 5] }}>
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 5, 5]} intensity={1} />
      <BVHAnimation bvhPath={bvhPath} />
    </Canvas>
  );
}

一些踩坑提示

  • 如果你加载BVH后没看到动画,先检查AnimationMixer是否正确绑定了骨骼根节点,以及animationAction是否调用了play()
  • 移动端性能有限,如果是复杂的BVH动画,建议简化骨骼数量或者降低动画帧率;
  • 替换成自己的3D模型时,要确保模型的骨骼结构和BVH的骨骼结构匹配,不然动画会错位。

备注:内容来源于stack exchange,提问作者anonon

火山引擎 最新活动