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标签中。
搭建基础框架
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| 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代码不做展示,最终实现了以下效果:
![](https://img.bald3r.wang/img/2023-09-28-202309281410262.png)
实现截图功能
这里所说的截图功能,实际是将canvas画布上的内容保存下来,可以通过官方的API获得一个包含图片展示的data URI:HTMLCanvasElement.toDataURL() - Web API 接口参考 | MDN (mozilla.org),然后将img
标签的src
指向它就可以了。
实现:
1 2 3 4 5 6 7
| const elButton = document.querySelector("button") as HTMLButtonElement; const elImg = document.querySelector("img") as HTMLImageElement; elButton.addEventListener("click", () => { const url = canvas.toDataURL(); elImg.src = url; });
|
效果:
![](https://img.bald3r.wang/img/2023-09-28-Kapture%202023-09-28%20at%2014.17.57.gif)
可以看到,img
标签的内容发生了变化,但是图片并没有正常显示。
preserveDrawingBuffer
属性
通过three.js的文档WebGLRenderer – three.js docs (threejs.org)可知,在构造WebGLRenderer
时,可传入的参数中又一个preserveDrawingBuffer
的属性,three.js对其的解释为:
preserveDrawingBuffer -是否保留缓(存)直到手动清除或被覆盖。 默认false.
也就是说,出于性能考虑,three.js默认是会清除canvas画布上的缓存内容的,那么我们截取的图像自然就是空白的,那我们开启这个选项:
1 2 3 4 5 6 7 8
| ...
const renderer = new THREE.WebGLRenderer({ canvas: canvas, preserveDrawingBuffer: true, });
...
|
效果:
![](https://img.bald3r.wang/img/2023-09-28-Kapture%202023-09-28%20at%2014.25.23.gif)
可以看到,已经能正常的截取画面了。
优化
three.js 出于性能方面的考虑,默认将preserveDrawingBuffer
选项设置为false
,说明打开这个选项会造成较大的性能开销。有代码洁癖的诸位看官肯定会忍不住说,不利于性能的代码我们不写!因此,有什么好办法能在关闭这个选项的前提下,也能截取画面呢?
既然three.js会一直清除缓存,那我在截图前,让它重新画一次不就行了?
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ...
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; });
|
效果:
![](https://img.bald3r.wang/img/2023-09-28-Kapture%202023-09-28%20at%2014.31.03.gif)
可以看到,同样实现了截图的功能。
后记
没想到three.js截图还有这么些细节在里面,笔者也只是抛砖引玉,如果各位看官有更好的解决方式,欢迎交流。