BCN编码
之前在知乎看到有美术在吵PNG和TGA哪一个更好用,但实际上引擎里使用的贴图是需要经过编码处理为GPU友好的资源格式,比如Windows上常用BCN编码,iOS常用ASTC编码
BCN原理
By storing some colors in their original size, and other colors using an encoding scheme, you can dramatically reduce the amount of memory required to store the image
BCN是指一系列使用Block Compression技术的格式,比如BC3、BC7,在过去也被称为DXT
4x4的未压缩贴图在内存中会被排成一个长度为16的数组,如果一个像素要1bytes,那么未压缩的容量为16bytes
BCN Format
原始数据 | 编码精度需求 (单位bits) | 推荐编码格式 | 每4x4像素大小 |
---|---|---|---|
RGBA | 5 : 6 : 5 : 0 或 5 : 6 : 5 : 1 | BC1 | 8 |
RGBA | 5 : 6 : 5 : 4 | BC2 | 16 |
RGBA | 5 : 6 : 5 : 8 | BC3 | 16 |
单通道灰度 | 8 | BC4 | 8 |
双通道 | 8 : 8 | BC5 | 16 |
RGB HDR | 16 : 16 : 16 | BC6H | 16 |
RGB(A) | 4 : 4 : 4 : (0~8) 或 7 : 7 : 7 : (0~8) | BC7 | 16 |
DX9:BC1~3
DX10:BC4~5
DX11:BC7,BC6H
BC1
下面这些图左边这个n bytes是指每一行是n bytes
4x4的颜色被存储为两个color和16个index,每个index仅有两个比特位
color_0是这16个颜色中的最小值,color_1是最大值,我们先用这两个颜色生成两个中间值
color_2 = 2/3*color_0 + 1/3*color_1 |
每个index只有两个比特位,只能存储四个信息,于是正好就可以存下这四个颜色的索引,对于每个像素,我们找到和他颜色最接近的color_x,并将他的index改为x
color_0 = 00 |
可以看出BC1的误差是相当大的,这个4x4个像素最后只有四种颜色
BC2
BC2的RGB通道算法和BC1完全一致,然后为每一个像素加一个Alpha通道,也就是说,BC2的Alpha是完全没有被压缩的
原理和BC1相同,都是用color_0和color_1生成四个颜色,每个index有两个位,存最相近的颜色的索引。唯一不同的是,BC2专门为每一个像素提供了一个 4位(0.5bytes)的Alpha通道
BC3
BC2是完全不做Alpha的压缩,于是在BC3开始对其进行压缩,体现了技术的进步?
BC3的RGB与BC1完全相同,Alpha使用类似思想进行压缩,将16个Alpha转化为两个alpha_x和16个index,两个alpha_x会插值出Alpha Table,16个像素各自去找最接近的Alpha,index记录他们的位数
为了精度,Alpha的位数是三个比特位,最多能表示八种Alpha,也就是说这16个像素最多会被分成八种Alpha
alpha_0和alpha_1的大小对比提供了两个模式,使用第二个模式时可以直接得到0和255两个极值,而其他颜色还是在color间插值,效果会比所有颜色都直接在0和255两个极值间插8份更好,适用于颜色中存在极值的情况
if( alpha_0 > alpha_1 ) |
BC4
BC4极少使用
与BC1那种将4x4的像素转化为四种颜色不同,BC4是在两个颜色间做线性插值,red_0和red_1是最大最小颜色,像素数据转化为一个权重值(是一个0到1的float),使用这个权重值对red_0和red_1做线性插值即可还原颜色
不过三个比特位存一个0~1的浮点,精度其实很也很差吧,和离散的比没啥区别?
BC4_UNORM和BC4_SNORM仍然是使用BC3的离散Table算法
BC5
BC4的进阶版,简单来说就是将两个BC4拼在了一起
BC5_UNORM和BC5_SNORM也就是把对应的BC4拼起来
法线贴图
值得一提的是,BC5常用于压缩法线贴图,因为法线贴图有两个性质
- 法线是归一化的$z=\pm\sqrt{1-x^2-y^2}$
- 法线各通道间是独立的
BC1的方案将RGB三个通道进行杂糅,整体混合,这对于法线贴图来说是不可接受的,梯度太小,精度太差了,使用BC5存法线的两个通道,使用时再还原第三个通道,这些效果会更好
承上启下
DX11提供了两个新的编码格式BC6H和BC7,再研究他们之前,我们先细数BC1压缩算法的问题吧
- 信道不均,这很好理解,RGB通道分别被分了5 : 6 : 5位,这会导致三个通道全取最大值时会更偏绿(相较于三个通道相同尺寸)
- 精度太低,16个像素只能被转化位4种不同的颜色
- 三个通道耦合,且只使用两个颜色进行插值,这是一个线而非三角形
BC7
核心思想
- 存多个color,两个index
- 使用预计算的partition sets(调色盘)
如果16个方块使用两个颜色涂抹,那么一共只会有$2^{16}$个情况,我们将这些情况全部离线存储在本地,并称之为调色盘(partition sets),使用一个索引即可得到对应的调色盘,还原出当前情况。
事实上由于颜色是存在连续性的,于是并不会出现这么多情况,于是我们可以大幅简化,使得几百种调色盘就足够使用。
更进一步,我们可以在调色盘种类和颜色数量间做平衡,使用更多种类的颜色和更少的调色盘种类,得到更丰富的模式
BC7不同模式下总位数是确定的,于是color占的位数多了,调色盘占的位数就会变少
BC7有很多模式(mode),我们可以为每块(4x4)选择一种模式,模式存储在BC7的开头(上图00001和1)
在不同的模式下,color数量和精度会改变,color越少越低精度,调色盘的选择就越多,反之color数据越多,调色盘选择就越少
BC1是对两个color做个插值得到4个或者8个颜色,而BC7不做这个插值,需要多少颜色就存多少颜色,然后根据剩余的位数选择调色盘即可
![partition sets](/images/partition sets.jpg)
能看出下图上面一排只存了两个颜色,但调色盘变化更多样,下面一排存了三个颜色,调色盘变化略显单调
BC6H
干翻了RGBM,是BCN中唯一一个支持HDR的格式
插值算法与BC7相同,用无符号整数来表示浮点数,将16位的数据的Sign位移除,加到Fraction位上,以提高float范围和精度
DDS
注意区分贴图Compress和zlib、lz4压缩
使用BCN编码格式的文件在Windows操作系统下后缀为.dds
DDS文件:DDS Header + DX10 Header + Mip0 raw data + Mip1 raw data + …
BCN是一种GPU友好型的数据格式,DDS文件大小仅与贴图大小、Mipmap数量、像素格式有关,一张1024x1024一级Mipmap一个像素4 bytes的贴图,无论其质量如何,其大小是确定的,都是1KB(Header) + 1024KB
尽管DDS比TGA小很多,但还是有进一步的压缩空间,UE使用Oodle进行BCN编码,通过设置RDO来影响数据的紧凑程度,经由lz4二压可以再降低10%~40%的包体大小
DDS Header
struct PixelFormat |
DX10 Header
在DX9中BC叫DXT,DX10以后才改为BC
struct DX10Header |
dxgi format
dxgi_format:DirectX Graphics Infrastructure,格式为DXGI_FORMAT_{编码类型}_{规范类型}
,比如DXGI_FORMAT_BC1_UNORM
、DXGI_FORMAT_R32_FLOAT
规范格式有:
- UNORM:Unsigned Normalized,无符号归一化
- SNORM:Signed Normalized,有符号归一化
- FLOAT:浮点数,动态范围更大,但储存空间更大
- TYPELESS:没有具体的规范类型,用于运行时确定格式
- UINT:无符号整数(不归一化)
- SINT:有符号整数(不归一化)
BCN Compress
常见的BCN压缩工具有Texconv、NVTT、Oodle
- 这里面我感觉NVTT是优雅最方便的,很适合嵌入引擎或者小项目使用
- Texconv有很浓厚的微软风格。。。
- Oodle是商业软件,编码速度极慢,而且没有内置的Mipmap和贴图IO工具,他只会处理raw block data,说实话挺不方便的,而且网上资料特别少,用过的人也因为版权不敢去分享,传Github还会被删库,但是Oodle生成的dds二压后包体特别小,正式项目还是建议用Oodle
下面是NVTT读TGA生成BC7格式DDS的样例代码,可以说非常地干净清晰
|