admin管理员组

文章数量:1539746

2024年1月20日发(作者:)

从3DS文件中导入网格数据

这份文档是根据英文文当翻译的。

1. 介绍

======================

3ds文件是基于“块”存储的,这些块描述了诸如场景数据,每个编辑窗口(Viewport)的状态,材质,网格数据(我们最关心的就是这个)等等数据。每个块都包含一个ID和块长度的块头(这里原文写的是下一个块的偏移量,我认为不精确),如果你对该块的信息不感兴趣的话,可以直接跳过该块读取下一个块。跟许多文件格式类似,为了读取的方便,3ds文件中数据的存储方式是Intel式的,也就是说是高位放在后面,低位放在前面。比如:网格块的块头ID,0x4000在文件里是以00 40存放的,对于windows程序员来说,无需做任何转换。

每个块都以这样的块头开始:

开始 结束 长度 作用

0 1 2 块的ID

2 5 4 该块的长度

6…………………… 块数据

3ds文件是严格按照块来划分、分层的,通常一个块包含其他子块作为自己的数据。

所有的3ds文件都是以一个ID是0x4d4d主块开始的,他总是位于整个文件的最开始(可以把它当作识别3ds文件的标志),长度就是整个文件的长度。以下是一个描述块组织方式的图表。

MAIN3DS (0x4D4D)

|(注意,此处并不是紧接着EDIT块的,还有一些描述文件版本信息的块)

+--EDIT3DS (0x3D3D)

| |

| +--EDIT_MATERIAL (0xAFFF)

| | |

| | +--MAT_NAME01 (0xA000)

| |

| +--EDIT_CONFIG1 (0x0100)

| +--EDIT_CONFIG2 (0x3E3D)

| +--EDIT_VIEW_P1 (0x7012)

| | |

| | +--TOP (0x0001)

| | +--BOTTOM (0x0002)

| | +--LEFT (0x0003)

| | +--RIGHT (0x0004)

| | +--FRONT (0x0005)

| | +--BACK (0x0006)

| | +--USER (0x0007)

| | +--CAMERA (0xFFFF)

| | +--LIGHT (0x0009)

| | +--DISABLED (0x0010)

| | +--BOGUS (0x0011)

| |

| +--EDIT_VIEW_P2 (0x7011)

| | |

| | +--TOP (0x0001)

| | +--BOTTOM (0x0002)

| | +--LEFT (0x0003)

| | +--RIGHT (0x0004)

| | +--FRONT (0x0005)

| | +--BACK (0x0006)

| | +--USER (0x0007)

| | +--CAMERA (0xFFFF)

| | +--LIGHT (0x0009)

| | +--DISABLED (0x0010)

| | +--BOGUS (0x0011)

| |

| +--EDIT_VIEW_P3 (0x7020)

| +--EDIT_VIEW1 (0x7001)

| +--EDIT_BACKGR (0x1200)

| +--EDIT_AMBIENT (0x2100)

| +--EDIT_OBJECT (0x4000)

| | |

| | +--OBJ_TRIMESH (0x4100)

| | | |

| | | +--TRI_VERTEXL (0x4110)

| | | +--TRI_VERTEXOPTIONS (0x4111)

| | | +--TRI_MAPPINGCOORS (0x4140)

| | | +--TRI_MAPPINGSTANDARD (0x4170)

| | | +--TRI_FACEL1 (0x4120)

| | | +--TRI_SMOOTH (0x4150)

| | | +--TRI_MATERIAL (0x4130)

| | | |(原文SMOOTH和MATERIAL是属于TRI_FACE的,但观察源代码

| | | 发现应该是属于TRIMESH的,如果有问题可以E我)

| | | +--TRI_LOCAL (0x4160)

| | | +--TRI_VISIBLE (0x4165)

| | |

| | +--OBJ_LIGHT (0x4600)

| | | |

| | | +--LIT_OFF (0x4620)

| | | +--LIT_SPOT (0x4610)

| | | +--LIT_UNKNWN01 (0x465A)

| | |

| | +--OBJ_CAMERA (0x4700)

| | | |

| | | +--CAM_UNKNWN01 (0x4710)

| | | +--CAM_UNKNWN02 (0x4720)

| | |

| | +--OBJ_UNKNWN01 (0x4710)

| | +--OBJ_UNKNWN02 (0x4720)

| |

| +--EDIT_UNKNW01 (0x1100)

| +--EDIT_UNKNW02 (0x1201)

| +--EDIT_UNKNW03 (0x1300)

| +--EDIT_UNKNW04 (0x1400)

| +--EDIT_UNKNW05 (0x1420)

| +--EDIT_UNKNW06 (0x1450)

| +--EDIT_UNKNW07 (0x1500)

| +--EDIT_UNKNW08 (0x2200)

| +--EDIT_UNKNW09 (0x2201)

| +--EDIT_UNKNW10 (0x2210)

| +--EDIT_UNKNW11 (0x2300)

| +--EDIT_UNKNW12 (0x2302)

| +--EDIT_UNKNW13 (0x2000)

| +--EDIT_UNKNW14 (0xAFFF)

|

+--KEYF3DS (0xB000)

|

+--KEYF_UNKNWN01 (0xB00A)

+--............. (0x7001) ( viewport, same as editor )

+--KEYF_FRAMES (0xB008)

+--KEYF_UNKNWN02 (0xB009)

+--KEYF_OBJDES (0xB002)

|

+--KEYF_OBJHIERARCH (0xB010)

+--KEYF_OBJDUMMYNAME (0xB011)

+--KEYF_OBJUNKNWN01 (0xB013)

+--KEYF_OBJUNKNWN02 (0xB014)

+--KEYF_OBJUNKNWN03 (0xB015)

+--KEYF_OBJPIVOT (0xB020)

+--KEYF_OBJUNKNWN04 (0xB021)

+--KEYF_OBJUNKNWN05 (0xB022)

另外还有一些块是在整个文件中都会经常出现的,那就是颜色块

COL_RGB1 0x0010 以float存放3个分量

COL_RGB2 0x0011 以char存放3个分量

COL_RGB3 0x0012 不知道是什么格式。我手头上的两分源代码在这里有不一样的地方,一个写的是0x0012,一个是0x0013

2.主编辑块

主块下包含两个块:一个描述场景数据的主编辑块和一个描述关键帧数据的关键帧块。我们主要关心的是主编辑块的数据,其中包含了网格数据。

主编辑块的ID是0x3d3d,关键帧块的ID是0xb000。

编辑块包含了场景中使用的材质(纹理是材质的一部分),配置,视口的定义方式,背景颜色,物体的数据„„等等一系列数据,其中对我们有用的是材质块(0xafff)和物体块(0x4000)。

这里从物体块开始。

一个场景中可以有许多物体,这意味着物体块可以有许多个,3ds文件中并不包含描述物体数目这样的量,所以你必须根据块的长度判断是否已经读取所有的块(不要简单的使用文件读取函数的返回值来判断,这样可能会导致该文件对象在下面的操作中产生不可预知的错误)。

每个物体块都以0x4000开始,然后是一个表示他长度的DWORD。之后,接着是一个NULL Terminated String用来存放他的名称(比如Box-01)。以下是两个例子。

0x4000 dwLength “Box-01” 数据„„

0x4000 dwLength “Camera01” 数据„„

随后放置的是相应的块数据,可以是网格(0x4100)、光源(0x4600)或者摄像机(0x4700)等等。

对于网格块(0x4100)来说,存储的有该网格的顶点数据,面数据,贴图座标数据,变换数据,他们对应的ID分别是:

id 对应块

0x4110 顶点列表

0x4111 顶点选择表

0x4120 面列表

0x4130 面材质

0x4140 纹理坐标数据列表

0x4150 Face smoothing group

0x4160 变换矩阵块(用来表示物体姿态和位置)

0x4165 物体是否可见

0x4170 Standard Mapping

其中0x4150、0x4160、0x4170三个块的意义不明,对我们真正有用的块是0x4110、0x4120、0x4130、0x4140、0x4160这几个块

顶点列表(0x4110)

每个顶点列表在块头之后的两个字节给出了该物体顶点的数目的,因此,一个物体最多只能有2^16-1=65535个顶点。在顶点数目之后是对应的顶点列,以float三元组的方式给出。以下是一个例子:

50 数目,2Bytes

1.0f 第一个顶点的X,4Bytes

1.0f 第一个顶点的Y,4Bytes

1.0f 第一个顶点的Z,4Bytes

1.0f 第二个顶点的X,4Bytes

1.0f 第二个顶点的Y,4Bytes

1.0f 第二个顶点的Z,4Bytes

1.0f 第三个顶点的X,4Bytes

1.0f 第三个顶点的Y,4Bytes

1.0f 第三个顶点的Z,4Bytes

1.0f 第四个顶点的X,4Bytes

1.0f 第四个顶点的Y,4Bytes

1.0f 第四个顶点的Z,4Bytes

.

.

.

1.0f 第50个顶点的X,4Bytes

1.0f 第50个顶点的Y,4Bytes

1.0f 第50个顶点的Z,4Bytes

顺序读取所有顶点就可以了。

顶点列表之后是顶点选择列表,这个块跟我们需要的数据没有关系。

紧跟在顶点选择列表后面的是纹理坐标列表——如果有的话,假如定义的物体并没有给出纹理,那么这个块不存在。

纹理块的组织方式跟顶点列表很相似,同样以表示数目的WORD打头,对应的坐标列是float型的2元组:

50 数目,2Bytes

1.0f 第一个顶点的U,4Bytes

1.0f 第一个顶点的V,4Bytes

1.0f 第二个顶点的U,4Bytes

1.0f 第二个顶点的V,4Bytes

1.0f 第三个顶点的U,4Bytes

1.0f 第三个顶点的V,4Bytes

1.0f 第四个顶点的U,4Bytes

1.0f 第四个顶点的V,4Bytes

.

.

.

1.0f 第50个顶点的U,4Bytes

1.0f 第50个顶点的V,4Bytes

之后存放的是一个叫standard mapping的块,意义不明,似乎不影响使用网格。

接下来就是重要性仅次于VertexList的FaceList了,面列表块以定点索引的方式存放所有面片的信息。与前面的块一样,面块以一个表示面的数目的WORD开始,之后是4个

以WORD(因为顶点数目以WORD存放,所以使用WORD足够)方式存放的量,分别表示第一个顶点,第二个顶点,第三个顶点和面的信息。

50 数目,2Bytes

1 第一个顶点的索引,2Bytes

2 第二个顶点的索引,2Bytes

3 第三个顶点的索引,2Bytes

4 面的信息,2Bytes

1 第一个顶点的索引,2Bytes

2 第二个顶点的索引,2Bytes

3 第三个顶点的索引,2Bytes

4 面的信息,2Bytes

1 第一个顶点的索引,2Bytes

2 第二个顶点的索引,2Bytes

3 第三个顶点的索引,2Bytes

4 面的信息,2Bytes

.

.

.

1 第一个顶点的索引,2Bytes

2 第二个顶点的索引,2Bytes

3 第三个顶点的索引,2Bytes

4 面的信息,2Bytes

面信息中包含了该面是否被选中,如何被选中,和该面顶点的排列方式是顺时针还是逆时针的信息。一下是其各个位数代表的信息:

bit 0 AC 边方向

bit 1 BC 方向

bit 2 AB 方向

bit 3 Mapping (if there is mapping for this face)?

bit 4-8 0 (not used ?)

bit 9-10 x (chaotic ???)

bit 11-12 0 (not used ?)

bit 13 face selected in selection 3

bit 14 face selected in selection 2

bit 15 face selected in selection 1

这里根据观察3ds文件和读取之后的数据,绝大部分数据(事实上没有发现例外)都是只使用0-2三位。

最重要的是前三位信息,标示了整个面的正方向(按法向量的不同,面的正方向可以有两个方向)

0位说明了AC边的方向,如果该位为1则由A指向C,反之由C指向A。

1位是BC的方向,2位是AB的方向。

举例来说,如果该值为110=6,则表明排列的方法是A->B->C->A,是逆时针方式存放的(和OpenGL缺省的正方向一致)。

(这部分是按原文翻译的,在我实际操作上发现有问题,我写的代码在这里进行了简单的处理,将所有面都认为是逆时针的,事实上判断排列方式的时候遇到问题,如果全部按逆时针处理,就是全部直接拷贝的话,图像是正确的,反之如果做了判断,当开启cull的时候会出现一些面丢失,很明显这些面的排列方式原来是正确的,但经过转换之后就错了)

接下来是SMOOTH(0x4150)和MATERIAL(0x4130)块,SMOOTH块保存的是一组DWORD的数据,表明那些面是属于Smooth组的。(Smooth组的意义不明)

Material(0x4130)块描述了那种材质被使用和使用这种材质的面。所有的材质块都是以一个Null Terminated String描述的材质名(和前面定义的材质名称一样)开始的。紧接着是一个WORD描述有多少个面是用了这个材质,之后是一个WORD数组描述使用了这个材质的面的索引。

然后是Local Axis块,这个块是用来描述物体状态的。按照原文的的解释(没有读取这部分的源代码)。这个块的内容包括四个float*3组(就是4个三维向量),头三个float*3的块用来表示物体自身坐标系(相对于全局)的X,Y,Z坐标轴。最后一个float*3的块是物体象对中心坐标。

最后,是一个Visiable块,对网格读取似乎没有影响。原文也没有这部分。

材质块

to be continue

本文标签: 数据顶点物体方式