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

Expo React Native结合Three.js加载GLB模型时纹理无法显示的问题求助

Expo React Native结合Three.js加载GLB模型时纹理无法显示的问题求助

你遇到的错误Creating blobs from 'ArrayBuffer' and 'ArrayBufferView' are not supported是核心问题,我来帮你分析原因并给出具体的解决方法:

错误原因分析

这个错误是因为在Expo/React Native环境中,Three.js官方GLTFLoader的默认纹理加载逻辑依赖Web环境的Blob API,但这个API在React Native中是不被支持的。另外你直接使用Asset.fromModule返回的uri而没有等待Asset下载完成,也可能导致模型资源加载不完整,进一步加剧纹理加载失败的问题。

具体解决方法(推荐用方法一,简单可靠)

方法一:使用expo-three适配Expo环境

expo-three是Expo官方提供的Three.js适配库,它已经处理了React Native环境下的纹理加载、资源适配等问题,能直接解决你的Blob不支持问题。

  1. 首先安装依赖
expo install expo-three
  1. 修改你的Character类代码
    将原有的GLTFLoader替换为expo-threeloadAsync方法,同时确保先完成Asset的下载:
import { Group, AnimationMixer, Clock, AnimationAction } from 'three';
import { Asset } from 'expo-asset';
import { loadAsync } from 'expo-three'; // 导入expo-three的加载方法

class Character extends Group {
  private mixer: AnimationMixer | null = null;
  private clock: Clock = new Clock();
  private actions: { [key: string]: AnimationAction } = {};
  private currentAnimationName: string | null = null;
  private activeAction: AnimationAction | null = null;

  constructor() {
    super();
    this.init(); // 调用异步初始化方法(constructor不能是async的)
  }

  private async init() {
    try {
      // 1. 先加载并下载模型Asset,确保资源完全可用
      const modelAsset = Asset.fromModule(require(`./models/Soldier6.glb`));
      await modelAsset.downloadAsync();

      // 2. 使用expo-three的loadAsync加载GLTF模型,自动适配Expo纹理加载逻辑
      const gltf = await loadAsync(modelAsset.uri);

      const modelWrapper = new Group();
      const character = gltf.scene;
      modelWrapper.add(character);

      this.mixer = new AnimationMixer(character);

      // 存储所有动画到字典,方便后续调用
      gltf.animations.forEach((clip) => {
        this.actions[clip.name] = this.mixer!.clipAction(clip);
      });

      // 默认播放第一个动画
      if (gltf.animations.length > 0) {
        this.setActiveAnimation(gltf.animations[0].name);
      }

      this.add(modelWrapper);
      console.log("Animations " + gltf.animations.map(anim => anim.name).join(', '));
    } catch (error) {
      console.error('模型加载失败:', error);
    }
  }

  public setActiveAnimation(animationName: string) {
    if (!this.mixer || !this.actions[animationName]) {
      console.warn(`动画 "${animationName}" 不存在`);
      return;
    }

    if (this.currentAnimationName === animationName) return;

    const newAction = this.actions[animationName];

    if (this.activeAction) {
      this.activeAction.fadeOut(0.2);
    }
    newAction.reset().fadeIn(0.2).play();

    this.activeAction = newAction;
    this.currentAnimationName = animationName;
  }

  public update(delta: number) {
    if (this.mixer) {
      this.mixer.update(delta);
    }
  }
}

export default Character;

方法二:自定义GLTFLoader纹理加载器(不推荐,实现复杂)

如果你不想引入额外依赖,可以自定义纹理加载逻辑避免使用Blob,但这部分实现较繁琐:

你需要给GLTFLoader设置一个自定义的TextureLoader,手动处理ArrayBuffer到纹理的转换(比如用Expo FileSystem将ArrayBuffer保存为本地文件后再加载),但这种方法需要写很多适配代码,容易出问题,所以更推荐方法一。

额外注意事项

  • 确保你的GLB模型纹理是正确嵌入的(meshyAi生成的模型通常默认嵌入纹理),如果是外部独立纹理,需要单独处理纹理的Asset路径。
  • 在App主组件的渲染循环中,记得调用character.update(clock.getDelta())来驱动动画混合器,比如:
// 主组件示例
import { Clock } from 'three';
import { GLView } from 'expo-gl';
import Character from './Character';
import { useRef, useEffect } from 'react';

function App() {
  const characterRef = useRef<Character>(null);
  const clock = new Clock();

  useEffect(() => {
    const animationLoop = () => {
      const delta = clock.getDelta();
      characterRef.current?.update(delta);
      requestAnimationFrame(animationLoop);
    };
    animationLoop();
  }, []);

  return (
    <GLView style={{ flex: 1 }} onContextCreate={(gl) => {
      // 这里初始化Three.js场景、相机、渲染器,并将character添加到场景中
    }} />
  );
}

按照以上方法修改后,你的GLB模型纹理应该就能正常显示,动画也能正常播放了。

火山引擎 最新活动