three.js 截图是空白?看看这里!
前言
目前公司有一个需求,在 three.js 中展示模型,并截图作为模型的预览图。由于 three.js 本身是通过 canvas 画出来的,而 canvas 标签本身就可以输出成图片,本以为是一个鸽鸽下蛋这么简单的事情,但是还是有一些细节在的,于是记录下来,和大家分享一下。
环境
node | v18.16.1 |
Microsoft Edge | 116.0.1938.62 |
three.js | 0.156.1 |
需要实现的效果
点击页面上的按钮,将three.js当前的画面截取下来,显示在img标签中。
搭建基础框架
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
const canvas = document.querySelector("canvas.webgl") as HTMLCanvasElement;
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
});
renderer.setSize(300, 300, false);
const camera = new THREE.PerspectiveCamera(75, 300 / 300, 0.1, 1000);
camera.position.z = 3;
scene.add(camera);
const light = new THREE.AmbientLight(0xffffff, 2);
scene.add(light);
const directionLight = new THREE.DirectionalLight(0xffffff, 2);
directionLight.position.set(0, 0, 2);
scene.add(directionLight);
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;
const gltfLoader = new GLTFLoader();
gltfLoader.load("/DamagedHelmet.glb", (gltf) => {
scene.add(gltf.scene);
});
const tick = () => {
requestAnimationFrame(tick);
controls.update();
renderer.render(scene, camera);
};
tick();
css代码不做展示,最终实现了以下效果:
实现截图功能
这里所说的截图功能,实际是将canvas画布上的内容保存下来,可以通过官方的API获得一个包含图片展示的data URI:HTMLCanvasElement.toDataURL() - Web API 接口参考 | MDN (mozilla.org),然后将img
标签的src
指向它就可以了。
实现:
const elButton = document.querySelector("button") as HTMLButtonElement;
const elImg = document.querySelector("img") as HTMLImageElement;
elButton.addEventListener("click", () => {
const url = canvas.toDataURL();
elImg.src = url;
});
效果:
可以看到,img
标签的内容发生了变化,但是图片并没有正常显示。
preserveDrawingBuffer
属性
通过three.js的文档WebGLRenderer – three.js docs (threejs.org)可知,在构造WebGLRenderer
时,可传入的参数中又一个preserveDrawingBuffer
的属性,three.js对其的解释为:
preserveDrawingBuffer -是否保留缓(存)直到手动清除或被覆盖。 默认false.
也就是说,出于性能考虑,three.js默认是会清除canvas画布上的缓存内容的,那么我们截取的图像自然就是空白的,那我们开启这个选项:
...
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
preserveDrawingBuffer: true,
});
...
效果:
可以看到,已经能正常的截取画面了。
优化
three.js 出于性能方面的考虑,默认将preserveDrawingBuffer
选项设置为false
,说明打开这个选项会造成较大的性能开销。有代码洁癖的诸位看官肯定会忍不住说,不利于性能的代码我们不写!因此,有什么好办法能在关闭这个选项的前提下,也能截取画面呢?
既然three.js会一直清除缓存,那我在截图前,让它重新画一次不就行了?
代码如下:
...
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
});
renderer.setSize(300, 300, false);
...
elButton.addEventListener("click", () => {
renderer.render(scene, camera); //关键代码
const url = canvas.toDataURL();
elImg.src = url;
});
效果:
可以看到,同样实现了截图的功能。
后记
没想到three.js截图还有这么些细节在里面,笔者也只是抛砖引玉,如果各位看官有更好的解决方式,欢迎交流。