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

Three.js + WebXR环境下GLB文件动画无法播放求助

问题:Three.js + WebXR环境中GLB模型动画无法播放

我在Three.js + WebXR环境中加载了包含1个动画片段的GLB模型,模型可正常显示,但动画无法播放。该模型在Three.js编辑器中动画播放正常,恳请技术帮助!

import {
  Mesh,
  AmbientLight,
  Clock,
  AnimationMixer,
  PerspectiveCamera,
  Scene,
  WebGLRenderer,
  MeshBasicMaterial,
  RingGeometry,
  sRGBEncoding
} from 'three';
import {ARButton} from 'three/examples/jsm/webxr/ARButton';
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js'

class App {
  constructor() {
    this.clock = new Clock();
    this.camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    this.camera.position.set(0, 1.6, 3);
    this.scene = new Scene();

    this.renderer = new WebGLRenderer({
      antialias: true,
      alpha: true
    });
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(window.innerWidth, window.innerHeight);
    this.renderer.outputEncoding = sRGBEncoding;
    document.body.appendChild(this.renderer.domElement);

    this.initXR();
    this.initScene();

    window.addEventListener('resize', this.onWindowResize.bind(this), false);
    this.renderer.setAnimationLoop(this.render.bind(this));
  }

  initXR() {
    this.renderer.xr.enabled = true;
    document.body.appendChild(ARButton.createButton(this.renderer, {requiredFeatures: ['hit-test']}));

    this.hitTestSourceRequested = false;
    this.hitTestSource = null;

    this.controller = this.renderer.xr.getController(0);
    this.controller.addEventListener('select', this.onSelect.bind(this));
  }

  initScene() {
    let geometry = new RingGeometry(0.08, 0.10, 32).rotateX(-Math.PI / 2);
    let material = new MeshBasicMaterial();
    this.reticle = new Mesh(geometry, material);
    this.reticle.matrixAutoUpdate = false;
    this.reticle.visible = false;
    this.scene.add(this.reticle);

    const loader = new GLTFLoader();
    loader.load('./models/stylized_character.glb', gltf => {
      this.myObj = gltf;
      gltf.scene.scale.set(.25, .25, .25);
      gltf.scene.visible = true;
      this.scene.add(gltf.scene);

      //todo: this doesnt seem to work
      this.mixer = new AnimationMixer(gltf);
      var action = this.mixer.clipAction(gltf.animations[0]);
      action.play();
    });

    var aLight = new AmbientLight(0xffffff, 1);
    this.scene.add(aLight)
  }

  render(_, frame) {
    const delta = this.clock.getDelta();
    if (this.mixer) {
      this.mixer.update(delta);
    } 
    if (frame) {
      if (this.hitTestSourceRequested === false) {
        this.requestHitTestSource();
      }
      if (this.hitTestSource) {
        this.getHitTestResults(frame);
      }
    }
    this.renderer.render(this.scene, this.camera);
  }

  onWindowResize() {
    this.camera.aspect = window.innerWidth / window.innerHeight;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(window.innerWidth, window.innerHeight);
    this.renderer.render(this.scene, this.camera);
  }

  onSelect() {
    if (this.reticle.visible) {

      this.myObj.scene.position.setFromMatrixPosition(this.reticle.matrix);
      this.myObj.scene.visible = true;

    }
  }

  async requestHitTestSource() {
    const session = this.renderer.xr.getSession();
    session.addEventListener('end', () => {
      this.hitTestSourceRequested = false;
      this.hitTestSource = null;
    });
    const referenceSpace = await session.requestReferenceSpace('viewer');
    this.hitTestSource = await session.requestHitTestSource({space: referenceSpace, entityTypes: ['plane']});
    this.hitTestSourceRequested = true;
  }

  getHitTestResults(frame) {
    const hitTestResults = frame.getHitTestResults(this.hitTestSource);
    if (hitTestResults.length) {
      const hit = hitTestResults[0];
      const pose = hit.getPose(this.renderer.xr.getReferenceSpace());
      this.reticle.visible = true;
      this.reticle.matrix.fromArray(pose.transform.matrix);
    } else {
      this.reticle.visible = false;
    }
  }
}

window.addEventListener('DOMContentLoaded', () => {
  new App();
});
解决方案

核心问题是AnimationMixer的初始化参数错误,它需要接收模型的场景对象而非完整的GLTF加载结果对象。

步骤1:修正AnimationMixer初始化

将原代码中的:

this.mixer = new AnimationMixer(gltf);

修改为:

this.mixer = new AnimationMixer(gltf.scene);

步骤2:可选优化(XR环境下的计时准确性)

WebXR环境中,使用frame.getDeltaTime()能获取更精准的帧间隔,替换render方法中的delta获取逻辑:

render(_, frame) {
  const delta = frame ? frame.getDeltaTime() : this.clock.getDelta();
  if (this.mixer) {
    this.mixer.update(delta);
  } 
  // ...其余代码保持不变
}

验证动画片段存在性

可以在GLTF加载回调中添加日志,确认动画片段是否被正确加载:

loader.load('./models/stylized_character.glb', gltf => {
  console.log('加载到的动画片段:', gltf.animations);
  // ...其余代码
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 13:20:33