使用 BASIS 纹理

关于

"Basis Universal Supercompressed GPU Texture Codec"——这是来着 basis_universal 的官方介绍,关键词是 Supercompressed

相比于 .ktx 为容器的压缩纹理,可以理解为它是以 .basis 为容器的压缩纹理格式,不同的是,它是通过 WebAssembly 运行时解码为各端可支持的压缩纹理格式。

优点:

  • 一套资源:.basis
  • 文件体积更小,常规的比 png 还小
  • 有损性较低:最终的显示效果与原图基本相差不大

缺点:

  • 需要额外加载两个解码相关的文件:basis_transcoder.js 和 basis_transcoder.wasm
  • 有运行时开销:实时解码(借助 WebWorker 可缓解)

数据对比

文件尺寸(px)大小(kb)
.png.basis.astc.ktx.pvr.ktx
a.png256*25613.4410.21729.68432.868
b.png512*51268.86436.697118.436131.172
c.png1024*1024353.482134.412467.956524.388
d.png2048*20481391.118417.7871871.5242097.252

【表1】各类型压缩纹理与 png 的大小对比

a.basisb.basisc.basisd.basis6张basis6张basis且可用
尺寸(px)256*256512*5121024*10242048*20482048*20482048*2048
iPhone 7P 耗时(ms)59.498.09126195.84784.621388.4
iPhone 7P Worker 耗时(ms)---245.521103.041002.8
OPPO R11 耗时(ms)24.2963175.5420.32485.443228.54
OPPO R11 Worker 耗时(ms)---435.422804.551186.64

【表2】BASIS 纹理解码耗时(不含加载)对比

* 注:以上数据仅供参考,不代表真实场景。

Tips

  • 从【表1】可以看出:.basis 格式甚至比 .png 还要小,尺寸越大越明显
  • 从【表2】可以看出:
    1. 从 d.basis 的耗时分析可以估算 Worker 本身创建+通信是有一定损耗的(平均一次在 50ms 左右)
    2. “6张basis” 的纯解码耗时因设备而异,Worker 的优势是可以并行执行,不阻塞后面的逻辑,正如“6张basis+可用”栏目,平均 1s 就能达到可用

如何使用?

关于制作 POT 等宽 Atlas 图集可阅读 《使用压缩纹理 / 如何使用?》篇。

生成 BASIS 纹理

通过上面制作生成的 .png,你就可以通过 tinyjs-cli 来快速生成 BASIS 纹理。

# 全量生成 .astc.ktx、.pvr.ktx、.basis
$ tiny texture-compressed res/hao.png

# 只生成 .basis
$ tiny texture-compressed res/hao.png -f basis

通过以上命令,会在 res 目录下生成以下 BASIS 格式压缩纹理:

  • basis 格式: https://gw.alipayobjects.com/os/tiny/resources/1.0.8/compressedtexture/hao.basis

Tips

  • 生成 BASIS 纹理的功能,tinyjs-cli 版本需要 >=1.4.0,详细命令请移步:生成压缩纹理

使用 BASIS 纹理

使用之前,你最好已经对 WebAssembly 有了大致的了解,起码知道它是个什么东西。

1、常规姿势

引用 BASIS 的解码器:

<script crossorigin="anonymous" src="https://gw.alipayobjects.com/os/tiny/owl/1.0.8/libs/basis_transcoder.js"></script>

Tips

  • 此解码器会自动加载同目录下的 basis_transcoder.wasm 文件

初始化压缩纹理插件并执行:

const app = new Tiny.Application({...});
const loader = new Tiny.loaders.Loader();

// 初始化压缩纹理插件
Tiny.plugins.compressedTexture.init(app.renderer);

// 1. 检测当前环境是否支持 WebAssembly
// 2. 只有 WebGL 渲染模式下才走 BASIS 纹理,避免执行无用的 WebAssembly 计算
if (window.WebAssembly && app.renderer.gl) {
  // 此 BASIS 方法是 basis_transcoder.js 提供的 wasm 模块
  BASIS()
  .then((Module) => {
    const { BasisFile, initializeBasis } = Module;

    // run module
    initializeBasis();

    // 执行绑定
    Tiny.plugins.compressedTexture.BASISLoader.bindTranscoder(BasisFile);

    start(true);
  })
  .catch(e => {
    console.log(e);
  });
} else {
  start();
}

function start(useCT) {
  let metadata;

  if (useCT) {
    // 设置 metadata.useFormat 为 basis,告诉加载器明确只使用 BASIS 纹理
    metadata = { useCompressedTexture: true, useFormat: 'basis' };
  }

  loader
    .add('logo', './res/logo.png', { metadata })
    .load((loaderInstance, resources) => {
      const sprite = new Tiny.Sprite(Tiny.TextureCache['logo']);
    });
}

Tips

  • TinyJS 1.6.0 及以上版本才支持 BASIS 纹理。
  • 与 KTX 容器的压缩纹理一样,BASIS 纹理也只有 WebGL 模式下才有效,如果当前环境不支持 WebGL 会自动降级到同目录下的 .png,支持的话则使用与 .png 同目录下同文件名的 .basis 文件

2、多 Worker 姿势

使用之前,你最好已经对 Web Workers 有了大致的了解,知道是怎么回事。

const app = new Tiny.Application({...});
const loader = new Tiny.loaders.Loader();

// 初始化压缩纹理插件
Tiny.plugins.compressedTexture.init(app.renderer);

// 1. 检测当前环境是否支持 Worker 和 WebAssembly
// 2. 只有 WebGL 渲染模式下才走 BASIS 纹理,避免执行无用的 WebAssembly 计算
if (window.Worker && window.WebAssembly && app.renderer.gl) {
  // 加载转换器并执行转换
  Tiny.plugins.compressedTexture.WorkedBASISLoader.loadAndRunTranscoder({
    // basis_transcoder 的 js 及 wasm 文件路径,可使用相对路径。
    // 加载后的完整链接为:
    // https://gw.alipayobjects.com/os/tiny/owl/1.0.8/libs/basis_transcoder.js
    // https://gw.alipayobjects.com/os/tiny/owl/1.0.8/libs/basis_transcoder.wasm
    path: 'https://gw.alipayobjects.com/os/tiny/owl/1.0.8/libs',
    // Worker 线程数,默认为 2,最高 8,可按资源数量设置
    threads: 6,
  })
  .then(start.bind(this, true))
  .catch(e => {
    console.log(e);
  });
} else {
  start();
}

function start(useCT) {
  // 使用方法同:1、常规姿势
}

Tips

  • 多 Worker 模式比较适合资源量较大的场景,如果只有1、2个资源,其实常规模式就可以了,因为 Worker 创建和通信的耗时可能反而会让你得不偿失