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

AR.js+Three.js场景截图功能失效求助:已参照旧帖仍无法实现

解决AR.js + Three.js 无法截取WebGL页面截图的问题

我仔细看了你的代码和问题描述,发现几个关键问题导致截图功能失效,咱们一步步来修复:

1. 首先解决Three.js版本冲突问题

你在head里同时引入了两个不同版本的Three.js:

<script src='js/three.js'></script>
<!-- 下面这个旧版本会和上面的新版本冲突 -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r69/three.min.js"></script>

这会导致全局THREE对象被覆盖,很多新API(比如你用的MTLLoader、OBJLoader的写法)会失效。直接删掉r69版本的引用,确保只保留项目里的js/three.js,同时要保证OBJLoader.js和MTLLoader.js的版本和它匹配。

2. 修正Renderer的截图配置与时机

虽然你设置了preserveDrawingBuffer: true,但在AR场景中,因为实时渲染的特性,有时候buffer会被快速覆盖。可以在截图前手动触发一次渲染,确保当前帧被正确捕获:

function saveAsImage() {
  var imgData;
  try {
    // 截图前手动渲染一次当前场景
    renderer.render(scene, camera);
    var strMime = "image/jpeg";
    imgData = renderer.domElement.toDataURL(strMime, 0.9); // 第二个参数是画质,范围0-1
    saveFile(imgData.replace(strMime, strDownloadMime), "test.jpg");
  } catch (e) {
    console.error("截图失败:", e);
    alert("截图失败,请查看控制台日志");
    return;
  }
}

3. 修复DOM结构的错误

你的script标签嵌套在了<div id="capture">里面,而且saveLink的创建逻辑放在了初始化函数里,容易导致DOM层级问题。建议把HTML结构整理清晰:

<body style='margin : 0px; overflow: hidden; font-family: Monospace;'>
  <!-- 把saveLink的容器放在最外层,避免嵌套问题 -->
  <div id="capture" style='position: absolute; top: 10px; width:100%; z-index:1'>
    <a href="#" id="saveLink" style="color: white; text-align: center; display: block;">Save Frame</a>
  </div>
  <!-- 脚本放在body末尾,确保DOM加载完成 -->
  <script>
    // 你的所有JS代码放在这里
  </script>
</body>

同时把JS里创建saveLink的代码删掉,直接用HTML里的元素绑定事件:

// 初始化函数里只需要绑定事件
document.getElementById("saveLink").addEventListener('click', saveAsImage);

4. 优化saveFile函数的兼容性

有些浏览器对dataURL的处理有大小限制,改用Blob对象来生成下载链接会更可靠:

var saveFile = function (strData, filename) {
  // 将dataURL转为Blob
  var byteString = atob(strData.split(',')[1]);
  var mimeString = strData.split(',')[0].split(':')[1].split(';')[0];
  var ab = new ArrayBuffer(byteString.length);
  var ia = new Uint8Array(ab);
  for (var i = 0; i < byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i);
  }
  var blob = new Blob([ab], {type: mimeString});
  
  var link = document.createElement('a');
  if (typeof link.download === 'string') {
    link.download = filename;
    link.href = URL.createObjectURL(blob);
    link.click();
    // 释放Blob URL,避免内存泄漏
    setTimeout(() => URL.revokeObjectURL(link.href), 100);
  } else {
    console.error("当前浏览器不支持下载功能");
    alert("当前浏览器不支持下载功能");
  }
}

修改后的完整代码

<head>
  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  <title>TESTE 4</title>
  <!-- 只保留一个Three.js版本 -->
  <script src='js/three.js'></script>
  <script src='js/OBJLoader.js'></script>
  <script src='js/MTLLoader.js'></script>
  <script src="jsartoolkit5/artoolkit.min.js"></script>
  <script src="jsartoolkit5/artoolkit.api.js"></script>
  <script src="threex/threex-artoolkitsource.js"></script>
  <script src="threex/threex-artoolkitcontext.js"></script>
  <script src="threex/threex-arbasecontrols.js"></script>
  <script src="threex/threex-armarkercontrols.js"></script>
  <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
  <script src="http://threejs.org/examples/js/libs/stats.min.js"></script>
</head>
<body style='margin : 0px; overflow: hidden; font-family: Monospace;'>
  <div id="capture" style='position: absolute; top: 10px; width:100%; z-index:1'>
    <a href="#" id="saveLink" style="color: white; text-align: center; display: block;">Save Frame</a>
  </div>
  <script>
    var scene, camera, renderer, clock, deltaTime, totalTime;
    var arToolkitSource, arToolkitContext;
    var markerRoot1;
    var mesh0; // 统一变量名,避免之前的命名混乱
    var strDownloadMime = "image/octet-stream";

    initialize();
    animate();

    function initialize() {
      scene = new THREE.Scene();
      let ambientLight = new THREE.AmbientLight(0xcccccc, 1.0);
      scene.add(ambientLight);

      camera = new THREE.Camera();
      scene.add(camera);

      renderer = new THREE.WebGLRenderer({
        preserveDrawingBuffer: true,
        precision: "mediump",
        antialias: true,
        alpha: true
      });
      renderer.setClearColor(new THREE.Color('lightgrey'), 0)
      renderer.setSize(window.innerWidth, window.innerHeight); // 用窗口大小适配不同设备
      renderer.domElement.style.position = 'absolute'
      renderer.domElement.style.top = '0px'
      renderer.domElement.style.left = '0px'
      document.body.appendChild(renderer.domElement);

      // 绑定截图按钮事件
      document.getElementById("saveLink").addEventListener('click', saveAsImage);

      clock = new THREE.Clock();
      deltaTime = 0;
      totalTime = 0;

      arToolkitSource = new THREEx.ArToolkitSource({
        sourceType: 'webcam',
      });

      function onResize() {
        arToolkitSource.onResize()
        arToolkitSource.copySizeTo(renderer.domElement)
        if (arToolkitContext.arController !== null) {
          arToolkitSource.copySizeTo(arToolkitContext.arController.canvas)
        }
      }

      arToolkitSource.init(function onReady() {
        onResize()
      });

      window.addEventListener('resize', function () {
        onResize()
      });

      arToolkitContext = new THREEx.ArToolkitContext({
        cameraParametersUrl: 'data/camera_para.dat',
        detectionMode: 'mono'
      });

      arToolkitContext.init(function onCompleted() {
        camera.projectionMatrix.copy(arToolkitContext.getProjectionMatrix());
      });

      markerRoot1 = new THREE.Group();
      scene.add(markerRoot1);

      let markerControls1 = new THREEx.ArMarkerControls(arToolkitContext, markerRoot1, {
        type: 'pattern',
        patternUrl: "data/boxa.patt",
      })

      function onProgress(xhr) {
        console.log((xhr.loaded / xhr.total * 100) + '% loaded');
      }

      function onError(xhr) {
        console.log('An error happened');
      }

      new THREE.MTLLoader()
        .setPath('models/')
        .load('space.mtl', function (materials) {
          materials.preload();
          new THREE.OBJLoader()
            .setMaterials(materials)
            .setPath('models/')
            .load('space.obj', function (group) {
              mesh0 = group.children[0];
              mesh0.scale.set(0.25, 0.25, 0.25);
              markerRoot1.add(mesh0);
            }, onProgress, onError);
        });
    }

    var posi = 1; // 统一变量初始化位置

    function update() {
      if (!mesh0) return; // 避免模型未加载时触发报错
      if (posi == 1 && mesh0.position.y <= 1.30) mesh0.position.y += 0.01;
      if (mesh0.position.y > 1.30) posi = 2;
      if (posi == 2) mesh0.position.y -= 0.01;
      if (mesh0.position.y <= 0.00) posi = 1;

      if (arToolkitSource.ready !== false) arToolkitContext.update(arToolkitSource.domElement);
    }

    function render() {
      renderer.render(scene, camera);
    }

    function animate() {
      requestAnimationFrame(animate);
      deltaTime = clock.getDelta();
      totalTime += deltaTime;
      update();
      render();
    }

    function saveAsImage() {
      var imgData;
      try {
        // 手动渲染当前帧确保截图正确
        renderer.render(scene, camera);
        var strMime = "image/jpeg";
        imgData = renderer.domElement.toDataURL(strMime, 0.9);
        saveFile(imgData.replace(strMime, strDownloadMime), "test.jpg");
      } catch (e) {
        console.error("截图失败:", e);
        alert("截图失败,请查看控制台日志");
        return;
      }
    }

    var saveFile = function (strData, filename) {
      var byteString = atob(strData.split(',')[1]);
      var mimeString = strData.split(',')[0].split(':')[1].split(';')[0];
      var ab = new ArrayBuffer(byteString.length);
      var ia = new Uint8Array(ab);
      for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
      }
      var blob = new Blob([ab], { type: mimeString });

      var link = document.createElement('a');
      if (typeof link.download === 'string') {
        link.download = filename;
        link.href = URL.createObjectURL(blob);
        link.click();
        setTimeout(() => URL.revokeObjectURL(link.href), 100);
      } else {
        console.error("当前浏览器不支持下载功能");
        alert("当前浏览器不支持下载功能");
      }
    }
  </script>
</body>
</html>

最后测试注意事项

  • 确保你的摄像头权限已经被允许
  • 确保AR标记(boxa.patt)可以被正确识别,模型加载完成
  • 点击页面顶部的「Save Frame」按钮,就能正常下载截图了

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 10:52:40