three.js 中截图的几种方式和里面的玄机
两种方式
先熟悉两个 API
- canvas.toDataURL:转成 base64 编码
- canvas.toBlob:不可变、原始数据的类文件对象
第一种是一个比较老的 API,使用也比较简单,这里简单说下 toBlob 的用法
const saveBlob = (function() {
const a = document.createElement('a');
document.body.appendChild(a);
a.style.display = 'none';
return function saveData(blob, fileName) {
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
};
}());
扩展:createObjectURL 与 revokeObjectURL
- createObjectURL:返回一个对象的 URL,可用户指定源 object 的内容
- revokeObjectURL:在每次调用 createObjectURL() 方法时,都会创建一个新的 URL 对象,即使你已经用相同的对象作为参数创建过。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放。浏览器在 document 卸载的时候,会自动释放它们,但是为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。
截图是全黑的?
这里是第一个坑,有时候你会发现你的截图效果是全黑的。这里是第一个坑
出现这个问题的原因在于:出于性能和兼容性的原因,浏览器在绘制完成后,会自动清除 WebGL 的缓存区
解决问题的办法是,在 render 后,同步的方式进行截图。
第二种方式就是设置 WebGL 不清空缓存区。在 Three.js 的中,设置 preserveDrawingBuffer
为 true
即可。
截图不完整
这个问题困扰了我很久,目前也没找到为什么。现象就是截图有一部分是黑的。
是在没办法就想是不是和 canvas 的大小有关。原本的大小是 1200x900,改成 800x600 后问题竟然解决了。
扩展:unproject、Sprite、性能优化
在 three.js 中创建始终朝向相机的 POI 可以使用 Sprite 类
WebGL 中将 3D 物体绘制到屏幕将经过的几个阶段,先熟悉几个坐标系
- 屏幕坐标系
- ndc 坐标系
- 3D 坐标
屏幕坐标和 ndc 坐标的转换,由于2d屏幕并没有z值所以,屏幕点转化成3d坐标的z可以随意取值,一般取0.5(z在-1到1之间)。
function fromScreenToNdc(x, y, container) {
return {
x: x / container.offsetWidth * 2 - 1,
y: -y / container.offsetHeight * 2 + 1,
z: 1
};
}
function fromNdcToScreen(x, y, container) {
return {
x: (x + 1) / 2 * container.offsetWidth,
y: (1 - y) / 2 * container.offsetHeight
};
}
Vector3 提供如下方法进行转换
- unproject:将 ndc 坐标转 3D 坐标。
- project:3D 坐标转 ndc 坐标
在图形学里面有个很重要的概念叫 one draw all
一次绘制,也就是说调用绘图 api 的次数越少,性能越高。比如 canvas 中的 fillRect
、fillText
等,WebGL 中的 drawElements
、drawArrays
;所以这里的解决方案是对相同样式的物体,把它们的侧面和顶面统一放到一个 BufferGeometry 中。这样可以大大降低绘图 api 的调用次数,极大的提升渲染性能。