KTX 格式和 SBM 格式

写这一篇文章的由来是,自己看到《OpenGL 超级宝典》第五章的纹理一节,发现直接调用几个封装好的函数就完事了,自己也不是很清楚内部到底做了什么。主要是因为这块涉及到了两个文件格式:KTX 和 SBM,它们封装了纹理图片和顶点相关的数据。

为了了解这两个文件格式,并且在之后的学习中能更加方便的提取其中的数据,自己特意不嫌麻烦的写了相对应的解析小工具。

KTX 格式

KTX 格式算是一种标准格式,可用来存储纹理数据。书籍配套所使用的 KTX 格式版本相对简单,文件格式头部定义也很简洁:

  • struct header
  • {
  •     unsigned char       identifier[12];
  •     unsigned int        endianness;
  •     unsigned int        gltype;
  •     unsigned int        gltypesize;
  •     unsigned int        glformat;
  •     unsigned int        glinternalformat;
  •     unsigned int        glbaseinternalformat;
  •     unsigned int        pixelwidth;
  •     unsigned int        pixelheight;
  •     unsigned int        pixeldepth;
  •     unsigned int        arrayelements;
  •     unsigned int        faces;
  •     unsigned int        miplevels;
  •     unsigned int        keypairbytes;
  • };

各个成员的含义,这边也不细究:这也是写额外工具的原因,可以结合实际的文件,借此了解它们的值;同时在用到这个文件的时候,还能结合其值进一步了解。头部后面跟着的就是所需要的纹理数据。

图1 纹理文件解析

如图 1 所示,就是一个 KTX 文件的解析结果:右上角展示了头部中各个成员的值,并且显示了纹理图片。

SBM 格式

SBM 格式相较于 KTX 格式会复杂的多,它是书籍作者自定义的一种文件格式,详细的说明在书中附录一节有介绍。在此处,如图 2 所示,我们直接结合一个具体文件的解析内容进行说明,这样更加直观。

图2 SBM 文件解析

首先和 KTX 格式一样,SBM 一开始也有一个头部定义:

  • typedef struct SB6M_HEADER_t
  • {
  •     union
  •     {
  •         unsigned int    magic;
  •         char            magic_name[4];
  •     };
  •     unsigned int        size;
  •     unsigned int        num_chunks;
  •     unsigned int        flags;
  • } SB6M_HEADER;

这边主要关注 num_chunks 成员。SBM 格式的总体布局就是由头部加上若干个区块组成的,而这里的 num_chunks 成员就是指明后续有多少个区块。图 2 中解析的文件有两个区块。

此处就不特意讲解 size 或者 offset 相关的成员含义了,它们是基于文件头还是区块头这些细节,自己处理的时候需要多加注意。

区块头部的定义如下:

  • typedef struct SB6M_CHUNK_HEADER_t
  • {
  •     union
  •     {
  •         unsigned int    chunk_type;
  •         char            chunk_name[4];
  •     };
  •     unsigned int        size;
  • } SB6M_CHUNK_HEADER;

有许多不同的区块,它们依据 magic_name 成员进行区分。size 成员可以跳过当前区块,以处理下一个区块。

如图 2 所示,此文件中定义了两个区块,分别是顶点数据块和顶点属性块,下面我们来依次了解它们。其他没有介绍到的区块也都大同小异,在用到的时候,再结合书中附录进行详细了解。

顶点数据块

顶点数据块的定义如下所示:

  • typedef struct SB6M_CHUNK_VERTEX_DATA_t
  • {
  •     SB6M_CHUNK_HEADER   header;
  •     unsigned int        data_size;
  •     unsigned int        data_offset;
  •     unsigned int        total_vertices;
  • } SB6M_CHUNK_VERTEX_DATA;

顶点数据块的 magic_nameVRTXdata_size 指示数据大小;data_offset 指示数据偏移;total_vertices 指示顶点个数。

顶点属性块

顶点属性块的定义如下:

  • typedef struct SB6M_VERTEX_ATTRIB_CHUNK_t
  • {
  •     SB6M_CHUNK_HEADER           header;
  •     unsigned int                attrib_count;
  •     SB6M_VERTEX_ATTRIB_DECL     attrib_data[1];
  • } SB6M_VERTEX_ATTRIB_CHUNK;

顶点数据块的 magic_nameATRBattrib_data 是个可变长度的数组,用于描述顶点属性信息,其个数由 attrib_count 指定。顶点属性的定义如下:

  • typedef struct SB6M_VERTEX_ATTRIB_DECL_t
  • {
  •     char                name[64];
  •     unsigned int        size;
  •     unsigned int        type;
  •     unsigned int        stride;
  •     unsigned int        flags;
  •     unsigned int        data_offset;
  • } SB6M_VERTEX_ATTRIB_DECL;

其中,name 指示不同的属性,比如顶点位置为 position,纹理映射为 map1data_offset 指示属性对应数据的偏移;sizetypestride 成员指定具体数据的含义和布局,这些跟 OpenGL 数据相关。

调试

如上,我们已经可以获取到顶点位置数据、纹理映射数据以及纹理贴图数据。我们可以把顶点位置数据、纹理映射数据转化成 obj 格式,然后结合纹理贴图导入到 Maya 中进行查看。

obj 格式在之前的文章中有简单提及,这边也再次简单说明一下。

都是简单提及,可以看出 obj 格式是真的非常容易上手。

  • v 0.499815 0.097545 -0.099419

像这样 v 打头的,定义一个顶点坐标。

  • vt 0.015625 0.968750

像这样 vt 打头的,定义一个纹理坐标。

  • f 1/1 2/2 3/3

像这样 f 打头的,定义一个三角面,第一组定义顶点坐标索引,第二组定义纹理坐标索引。

如图 3 所示,把 obj 文件用 Maya 打开,可以看到此物体的样子以及其 UV 坐标。并且可以使用提取得到的贴图图片进行渲染查看。

图3 Maya 显示结果

以上验证的方式可以发现,学习的过程何尝不是一种“造轮子”的过程。