Better

Ethan的博客,欢迎访问交流

three.js 截图踩的坑

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 的中,设置 preserveDrawingBuffertrue 即可。

截图不完整

这个问题困扰了我很久,目前也没找到为什么。现象就是截图有一部分是黑的。

是在没办法就想是不是和 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 中的 fillRectfillText 等,WebGL 中的 drawElementsdrawArrays;所以这里的解决方案是对相同样式的物体,把它们的侧面和顶面统一放到一个 BufferGeometry 中。这样可以大大降低绘图 api 的调用次数,极大的提升渲染性能。

资料



留言