抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

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

4x4贴图

4x4不压缩

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仅有两个比特位

BC1

color_0是这16个颜色中的最小值,color_1是最大值,我们先用这两个颜色生成两个中间值

color_2 = 2/3*color_0 + 1/3*color_1
color_3 = 1/3*color_0 + 2/3*color_1

每个index只有两个比特位,只能存储四个信息,于是正好就可以存下这四个颜色的索引,对于每个像素,我们找到和他颜色最接近的color_x,并将他的index改为x

color_0 = 00
color_1 = 01
color_2 = 10
color_3 = 11

可以看出BC1的误差是相当大的,这个4x4个像素最后只有四种颜色

BC2

BC2的RGB通道算法和BC1完全一致,然后为每一个像素加一个Alpha通道,也就是说,BC2的Alpha是完全没有被压缩的

BC2

原理和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记录他们的位数

BC3

为了精度,Alpha的位数是三个比特位,最多能表示八种Alpha,也就是说这16个像素最多会被分成八种Alpha

alpha_0和alpha_1的大小对比提供了两个模式,使用第二个模式时可以直接得到0和255两个极值,而其他颜色还是在color间插值,效果会比所有颜色都直接在0和255两个极值间插8份更好,适用于颜色中存在极值的情况

if( alpha_0 > alpha_1 )
{
// 6 interpolated alpha values.
alpha_2 = 6/7*alpha_0 + 1/7*alpha_1; // bit code 010
alpha_3 = 5/7*alpha_0 + 2/7*alpha_1; // bit code 011
alpha_4 = 4/7*alpha_0 + 3/7*alpha_1; // bit code 100
alpha_5 = 3/7*alpha_0 + 4/7*alpha_1; // bit code 101
alpha_6 = 2/7*alpha_0 + 5/7*alpha_1; // bit code 110
alpha_7 = 1/7*alpha_0 + 6/7*alpha_1; // bit code 111
}
else
{
// 4 interpolated alpha values.
alpha_2 = 4/5*alpha_0 + 1/5*alpha_1; // bit code 010
alpha_3 = 3/5*alpha_0 + 2/5*alpha_1; // bit code 011
alpha_4 = 2/5*alpha_0 + 3/5*alpha_1; // bit code 100
alpha_5 = 1/5*alpha_0 + 4/5*alpha_1; // bit code 101
alpha_6 = 0; // bit code 110
alpha_7 = 255; // bit code 111
}

BC4

BC4极少使用

与BC1那种将4x4的像素转化为四种颜色不同,BC4是在两个颜色间做线性插值,red_0和red_1是最大最小颜色,像素数据转化为一个权重值(是一个0到1的float),使用这个权重值对red_0和red_1做线性插值即可还原颜色

BC4

不过三个比特位存一个0~1的浮点,精度其实很也很差吧,和离散的比没啥区别?

BC4_UNORM和BC4_SNORM仍然是使用BC3的离散Table算法

BC5

BC4的进阶版,简单来说就是将两个BC4拼在了一起

BC5

BC5_UNORM和BC5_SNORM也就是把对应的BC4拼起来

法线贴图

值得一提的是,BC5常用于压缩法线贴图,因为法线贴图有两个性质

  1. 法线是归一化的$z=\pm\sqrt{1-x^2-y^2}$
  2. 法线各通道间是独立的

BC1的方案将RGB三个通道进行杂糅,整体混合,这对于法线贴图来说是不可接受的,梯度太小,精度太差了,使用BC5存法线的两个通道,使用时再还原第三个通道,这些效果会更好

BC5法线

承上启下

DX11提供了两个新的编码格式BC6H和BC7,再研究他们之前,我们先细数BC1压缩算法的问题吧

  1. 信道不均,这很好理解,RGB通道分别被分了5 : 6 : 5位,这会导致三个通道全取最大值时会更偏绿(相较于三个通道相同尺寸)
  2. 精度太低,16个像素只能被转化位4种不同的颜色
  3. 三个通道耦合,且只使用两个颜色进行插值,这是一个线而非三角形

BC1的问题

BC7

核心思想

  1. 存多个color,两个index
  2. 使用预计算的partition sets(调色盘)

如果16个方块使用两个颜色涂抹,那么一共只会有$2^{16}$个情况,我们将这些情况全部离线存储在本地,并称之为调色盘(partition sets),使用一个索引即可得到对应的调色盘,还原出当前情况。

事实上由于颜色是存在连续性的,于是并不会出现这么多情况,于是我们可以大幅简化,使得几百种调色盘就足够使用。

更进一步,我们可以在调色盘种类和颜色数量间做平衡,使用更多种类的颜色和更少的调色盘种类,得到更丰富的模式

BC7不同模式下总位数是确定的,于是color占的位数多了,调色盘占的位数就会变少

BC7

BC7_2

BC7有很多模式(mode),我们可以为每块(4x4)选择一种模式,模式存储在BC7的开头(上图00001和1)

在不同的模式下,color数量和精度会改变,color越少越低精度,调色盘的选择就越多,反之color数据越多,调色盘选择就越少

BC7mode

BC1是对两个color做个插值得到4个或者8个颜色,而BC7不做这个插值,需要多少颜色就存多少颜色,然后根据剩余的位数选择调色盘即可

![partition sets](/images/partition sets.jpg)

能看出下图上面一排只存了两个颜色,但调色盘变化更多样,下面一排存了三个颜色,调色盘变化略显单调

BC7_3

BC6H

干翻了RGBM,是BCN中唯一一个支持HDR的格式

插值算法与BC7相同,用无符号整数来表示浮点数,将16位的数据的Sign位移除,加到Fraction位上,以提高float范围和精度

BC6H

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
{
uint32_t size;
uint32_t flags;
uint32_t fourCC;
uint32_t RGBBitCount;
uint32_t RBitMask;
uint32_t GBitMask;
uint32_t BBitMask;
uint32_t ABitMask;
};

struct DDSHeader
{
uint32_t Magic; // Must be DDS_MAGIC
uint32_t size;
uint32_t flags;
uint32_t height;
uint32_t width;
uint32_t pitchOrLinearSize;
uint32_t depth;
uint32_t num_mips;
uint32_t reserved1[11];
PixelFormat ddspf;
uint32_t caps;
uint32_t caps2;
uint32_t caps3;
uint32_t caps4;
uint32_t reserved2;
};

DX10 Header

在DX9中BC叫DXT,DX10以后才改为BC

struct DX10Header
{
uint32_t dxgi_format;
uint32_t resource_dimension;
uint32_t misc_flag; // see D3D11_RESOURCE_MISC_FLAG
uint32_t array_size;
uint32_t misc_flag2;
};

dxgi format

dxgi_format:DirectX Graphics Infrastructure,格式为DXGI_FORMAT_{编码类型}_{规范类型},比如DXGI_FORMAT_BC1_UNORMDXGI_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的样例代码,可以说非常地干净清晰

#include <nvtt.h>
int main() {
std::string raw_tga_path = "D:\\art\\raw.tga";
std::string output_dds_path = "D:\\art\\out\\nvtt_out.dds";
int mipmap_count = 3;
nvtt::Surface image;
image.load(raw_tga_path.c_str());
nvtt::Context context(true);
nvtt::CompressionOptions compression_options;
compression_options.setFormat(nvtt::Format_BC7);
nvtt::OutputOptions output_options;
output_options.setFileName(output_dds_path.c_str());

if (!context.outputHeader(image, mipmap_count, compression_options, output_options))
{
std::cerr << "Writing the DDS header failed!";
return 1;
}

// Compress and write the compressed data.
for (int mipmap_index = 0; mipmap_index < mipmap_count; mipmap_index++)
{
if (!context.compress(image, 0 , mipmap_index, compression_options, output_options))
{
std::cerr << "Compressing and writing the DDS file failed!";
return 1;
}
if(mipmap_index == mipmap_count - 1) break;
image.toLinearFromSrgb();
image.premultiplyAlpha();
image.buildNextMipmap(nvtt::MipmapFilter_Triangle);
image.demultiplyAlpha();
image.toSrgb();
}
}

参考

微软开源的贴图工具,包含Texconv

微软关于BCN的介绍

英伟达开源的贴图工具NVTT

UE使用的商业贴图工具Oodle官网

BCN算法

评论