使用压缩纹理
无法减小显存占用
先看一下下面这张纹理图,前端习惯叫做雪碧图,在游戏界一般叫做 Atlas 图集,原始分辨率为 512*512,文件大小为 53KB。
一般情况下,浏览器内部会将 JPEG/PNG 图片文件进行解压,并解码成显卡认识的 RGB(A) 位图格式纹理,如果带 alpha 通道,图片就需要占用内存存储 512*512*4
,即 1M,同时这 1M 的信息也需要加载到 GPU 缓存中(虽然文件格式、磁盘占用、内存占用大小不一样,即无论将图片的存储体积压缩到多么小,其显存开销总是固定的),即:一张图片在引擎中运行时占用的显存大小,与图片本身占用磁盘大小无关,只和图片的宽和高相关。
所以,类似 JPEG/PNG 文件作为纹理时会有以下问题:
- 浏览器解压图片存在耗时,图片越大,耗时越多
- 内存占用较多,除了原始文件的存储,浏览器和 GPU 还会各自保存一份位图数据(如图示1)
图示1:img 对象创建纹理的内存使用情况
可以看出,第二个问题如果在大体量图片应用场景下,这个瓶颈会越来越明显,在移动设备上很容易造成 OOM。
压缩纹理(Compressed Textures)
压缩纹理是一种游戏领域常用的纹理压缩技术,其依赖于特定硬件实现,经过某种算法压缩之后的纹理可直接以固定速率交由 GPU 即时解压使用,只有当 shader 进行纹理查询(texture lookup)才会进行解压操作,找到对应位置的像素颜色。GPU 通常会对解压过程进行优化从而提升性能。
那么,它在内存占用上有质的优势,详细如下:
- 内存占用很少,除了原始文件的存储,只有 GPU 保存的一份压缩纹理数据(如图示2)
- 省去了使用 JPEG/PNG 图片文件作为纹理时的图片解码耗时
- 可以精准控制内存,如图示2中 JSHeap 中的占用可以手动控制释放
图示2:压缩纹理的内存使用情况
有优点肯定也有条件和代价,也可以说是规范or约束:
- 压缩纹理是有损压缩,会对图片的质量有一定减损
- 压缩纹理的传输体积可能比 JPG/PNG 要高1~4倍
- 压缩纹理要求POT,即长宽都是二的幂次,同时 PVRTC 也有长宽相等的要求
- 压缩纹理支持格式在不同平台不一,加上降级需要三份资源
整体来说,这些约束对于大体量图片项目影响不大,收益却相当可观。
如何使用?
制作 POT 等宽 Atlas 图集
鉴于业界优秀软件的合并效果更好,建议使用相关软件/工具制作,比如:TexturePacker,这里推荐 ShoeBox(好用、免费)。
以下是通过 ShoeBox 生成的图集资源:
- png: https://gw.alipayobjects.com/os/tiny/resources/1.0.5/compressedtexture/hao.png
- json: https://gw.alipayobjects.com/os/tiny/resources/1.0.5/compressedtexture/hao.json
生成压缩纹理
通过上面制作生成的 .png
和 .json
,你可以使用 tinyjs-cli 来快速生成压缩纹理。
$ tiny texture-compressed res/hao.png
通过以上命令,会在 res
目录下生成以下格式压缩纹理:
- astc 格式: https://gw.alipayobjects.com/os/tiny/resources/1.0.5/compressedtexture/hao.astc.ktx
- pvr 格式: https://gw.alipayobjects.com/os/tiny/resources/1.0.5/compressedtexture/hao.pvr.ktx
Tips
- 生成 KTX 纹理的功能,
tinyjs-cli
版本需要>=1.3.0
,详细命令请移步:生成压缩纹理
使用压缩纹理
const app = new Tiny.Application({...});
const loader = new Tiny.loaders.Loader();
// 初始化压缩纹理插件
Tiny.plugins.compressedTexture.init(app.renderer);
// 加载 Atlas 图集
loader.add('./res/hao.json', {
metadata: { useCompressedTexture: true }
});
loader.load((loaderInstance, resources) => {
const textures = [];
for (let i = 0; i < 4; i++) {
// 通过 Texture 的 fromFrame 方法创建纹理。frame 名就是 tileset 资源文件里的 frameId
textures.push(Tiny.Texture.fromFrame('hao' + i + '.png'));
}
});
Tips
- 压缩纹理只有 WebGL 模式下才有效,如果当前环境不支持 WebGL 会自动降级到同目录下的
.png
- TinyJS 压缩纹理插件
Tiny.plugins.compressedTexture
会自动识别当前环境支持的压缩纹理格式并在加载时使用,如安卓上会加载.astc.ktx
,iOS 上会加载.pvr.ktx
,不支持的会加载.png
- 在创建并使用
Tiny.Texture
时,和使用普通的 tileset 一样