« 为什么 skynet 提供的包协议只用 2 个字节表示包长度 | 返回首页 | Lua 5.3 正式发布以及文档翻译计划 »

如何拼接 PVR 压缩贴图

2d 游戏通常都用到很多图素,由于显卡硬件特性,我们又不会把单个图素放在独立贴图中。这样会导致渲染批次过多。在移动设备上,非常影响渲染效率。

所以,游戏运行时,这些图素一般都会合并在很少几张贴图上。要么采用离线合并的方式(利用 texture packer 这样的工具),或者在运行时使用装箱算法。

最近,朱光同学一直在为 ejoy2d 编写运行时合并图素的模块。今天我们讨论了一下他做的诸多尝试。

为了提高贴图利用率,我们可以把大的不规则图素先拆分成小块,然后再组合起来。对于不压缩的贴图,怎么拆分合并都是没有问题的。而对于压缩贴图就要多一些技巧了。

对于 S3TC(DXT) 和 ETC 等压缩贴图,都是按 block 为单位压缩的。block 大小通常是 4x4 像素,block 之间是没有关联的。所以只要按 block 为单位切开,再合并时就不会有任何损失。

PVR 贴图要复杂的多。

了解 PVR 算法可以读一下这一篇 paper

这里简述一下:

PVR (4bpp 版本)贴图以 4x4 block 为单位,为每个 block 生成一个 Color A 和一个 Color B 。然后在采样的时候,根据 A 和 B 以及这个像素对应的两个 Modulation bits 插值计算出这个像素的 Color 。

如果仅是这样,对之前的 S3TC 算法改进就有限。

S3TC 压缩有一个问题是在跨越 block 边界上的两个像素很容易发生明显的跳变。这是因为每个 block 独立编码造成的。PVR 是这样解决这个问题的:

贴图上每 2x2 个像素,都会对应到 4 个 block 。也就是说,采样时,会从周围的四个 block 取到 4 对 Color A/B 。每个像素的采样都先先从这对 Color 中做线性插值,再利用 Modulation bits 再插一次。


也就是说,PVR 贴图上的每个像素最终的颜色,都贴图上一个 8x8 区域相关。这可以在同等压缩比下,提高图像的质量。但坏处是即使是采用一些简化算法, PVR 的压缩代价还是很大;如果使用 CPU 解压缩,也会很慢。在 N7 上测试,一张 2048 的贴图,pvr4 编码需要 1.7s , 解码 1.4s 。

但如果根据 pvr 算法的特性,我们还是可以找到方法绕过编解码过程来做切分合并工作:

把每个单独的图素的轮廓外留下 4 像素宽的空白带(alpha 为 0 ),再进行离线 PVR 压缩。作为边界上的空 block ,我们只需要保留其中 Color A/B 的 32bit ,而抛弃 32bit 的 Modulation Data 。因为后 32bit 一定是固定值(0xAAAAAAAA) 。

ps. 这里要提一下 PVR 贴图为 alpha 通道做了特别的优化。只为需要 alpha 通道的 block 描述 alpha 通道,且专门为 alpha 为 0 的情况做了处理。

在合并后,也同样需要让图素间保持 2 倍 block ,也就是 8 像素的距离。

Comments

请问云风会PS么?
亲,LUA5.3正式版发布了
嗯,iphone4的话,估计drawcall影响会比较大,但现在已经过时了. 一般不到200个drawcall就不用太费力优化了.
@dwing 我们更新缓存纹理不是用glTexSubImage2D这个从内存到显存传数据,用的fbo直接从纹理到纹理。 减少drawcall还是有用的,一年前测mmzb在iphone4上,数据量大些的场景,记得是从4、5帧提高到接近20帧
我印象中, 移动设备上更新局部纹理数据的性能代价非常高, 尤其使用压缩纹理的时候, GLES驱动很可能是先把整张纹理从显存传回内存,更新后再完整地传回显卡. 所以更新纹理的做法在移动平台应该尽量避免. 其实Draw Call数量对性能的影响没有想象中那么大.

Post a comment

非这个主题相关的留言请到:留言本