3D Studio 文件格式 (3ds)
内容:
1. 介绍
2. 块的宏定义
3. 3D编辑块
4. 关键帧块
5. 源代码(参见SRTP档案袋光盘)
1. 介绍
3ds文件由块组成。这些块说明了紧跟其ID的内容和下一个块的地址。假如对某一个块不明白,可跳过。
下一个块的指针与当前块的首地址和长度有关,3ds文件以二进制形式存储。与许多文件格式一样,3ds二进制文件中的数据也是按低位在前、高位在后的方式组织的,例如,两个十六进制字节4A 5C 组成的整形数,表明5C是高位字节,4A是低位字节;对于长整形数,如:4A 5C 3B 8F表明5C4A是低位字,而8F3B是高位字。
起点 |
终点 |
长度 |
名称 |
0 |
1 |
2 |
块Id |
2 |
5 |
4 |
指向下一个块的指针 |
每个块实际上是一个层次结构,不同类型的块,其层次结构也不相同。每一个3ds文件首块ID是4D4D。
首块就是主块。
为了对快的层次结构有一个初步的认识,下面给出一个图标来说明不同类型(ID)的块及其各自在文件中的位置,如下图,这些块都赋予了一个名字,可以便于理解或者转换为源程序。
MAIN3DS (0x4D4D)
|
+--EDIT3DS (0x3D3D)
| |
| +--EDIT_MATERIAL (0xAFFF)
| | |
| | +--MAT_NAME01 (0xA000) (See mli Doc)
| |
| +--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)
| | | |
| | | +--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_RGB COL_TRU COL_UNK 。
2. 块的宏定义
=========================
现在,我将用宏定义定义这些块,这些宏定义取自源程序代码。然而,依然需要小心,因为很多新块未能得到及时的说明。
//------ Primary chunk
#define MAIN3DS 0x4D4D
//------ Main Chunks
#define EDIT3DS 0x3D3D // this is the start of the editor config
#define KEYF3DS 0xB000 // this is the start of the keyframer config
//------ sub defines of EDIT3DS
#define EDIT_MATERIAL 0xAFFF
#define EDIT_CONFIG1 0x0100
#define EDIT_CONFIG2 0x3E3D
#define EDIT_VIEW_P1 0x7012
#define EDIT_VIEW_P2 0x7011
#define EDIT_VIEW_P3 0x7020
#define EDIT_VIEW1 0x7001
#define EDIT_BACKGR 0x1200
#define EDIT_AMBIENT 0x2100
#define EDIT_OBJECT 0x4000
#define EDIT_UNKNW01 0x1100
#define EDIT_UNKNW02 0x1201
#define EDIT_UNKNW03 0x1300
#define EDIT_UNKNW04 0x1400
#define EDIT_UNKNW05 0x1420
#define EDIT_UNKNW06 0x1450
#define EDIT_UNKNW07 0x1500
#define EDIT_UNKNW08 0x2200
#define EDIT_UNKNW09 0x2201
#define EDIT_UNKNW10 0x2210
#define EDIT_UNKNW11 0x2300
#define EDIT_UNKNW12 0x2302
#define EDIT_UNKNW13 0x3000
#define EDIT_UNKNW14 0xAFFF
//------ sub defines of EDIT_OBJECT
#define OBJ_TRIMESH 0x4100
#define OBJ_LIGHT 0x4600
#define OBJ_CAMERA 0x4700
#define OBJ_UNKNWN01 0x4010
#define OBJ_UNKNWN02 0x4012 //---- Could be shadow
//------ sub defines of OBJ_CAMERA
#define CAM_UNKNWN01 0x4710
#define CAM_UNKNWN02 0x4720
//------ sub defines of OBJ_LIGHT
#define LIT_OFF 0x4620
#define LIT_SPOT 0x4610
#define LIT_UNKNWN01 0x465A
//------ sub defines of OBJ_TRIMESH
#define TRI_VERTEXL 0x4110
#define TRI_FACEL2 0x4111
#define TRI_FACEL1 0x4120
#define TRI_SMOOTH 0x4150
#define TRI_LOCAL 0x4160
#define TRI_VISIBLE 0x4165
//------ sub defs of KEYF3DS
#define KEYF_UNKNWN01 0xB009
#define KEYF_UNKNWN02 0xB00A
#define KEYF_FRAMES 0xB008
#define KEYF_OBJDES 0xB002
//------ these define the different color chunk types
#define COL_RGB 0x0010
#define COL_TRU 0x0011
#define COL_UNK 0x0013
//------ defines for viewport chunks
#define TOP 0x0001
#define BOTTOM 0x0002
#define LEFT 0x0003
#define RIGHT 0x0004
#define FRONT 0x0005
#define BACK 0x0006
#define USER 0x0007
#define CAMERA 0x0008 // 0xFFFF is the actual code read from file
#define LIGHT 0x0009
#define DISABLED 0x0010
#define BOGUS 0x0011
3.3D编辑块
最开始出现的主块是基本块,它包含了整个文件。因此这个块的大小就是文件的大小减去主块的大小。还有两种主块:3D编辑程序块和关键帧块,前者的ID是3D3D,后者的ID是B000 。3D编辑程序块表明编辑程序数据开始,也就是说物体的形体数据定义从此处开始。关键帧块表明下面开始定义关键帧信息。
* 3D3D的子块
id 描述
0100 配置的一部分
1100 未知
1200 背景颜色
1201 未知
1300 未知
1400 未知
1420 未知
1450 未知
1500 未知
2100 环境颜色块
2200 雾
2201 雾
2210 雾
2300 未知
3000 未知
3D3E 编辑程序配置主块
4000 物体定义
AFFF 材质列表开始
id 描述
7001 视口指示器
7011 视口定义 ( 类型2 )
7012 视口定义 ( 类型 1 )
7020 视口定义 ( 类型 3 )
3D3E块中包含了许多冗余数据,其中较重要的块石7020块,这个块定义编辑程序中4个活动的视口。假设在编辑程序布局中使用了4个视口,编辑程序配置中包含5个7020视口定义块和五个7011视口定义块。但事实上只有开始的四个7020块对用户的视口的外观有影响,其它的视口中只包含一些附加信息。该块的第6、7字节表明视图的类型,合法的ID及其对应视图如下所示:
id Description
0001 顶视图
0002 底视图
0003 左视图
0004 右视图
0005 前视图
0006 后视图
0007 用户视图
FFFF 相机视图
0009 光源视图
0010 无效
4000(物体描述块)的子块
第一项是物体名称.它是一个零结尾串,这里所指物体可以是一个相机或一个光源.也可
以是物体的形体。
ID 含义
4010 未知
4012 阴影块
4100 三角形列表块
4600 光源块
4700 相机块
4100(三角形列表)的子块
ID 含义
4110 顶点列表
4111 顶点选项
4120 面列表
4130 面材质
4140 纹理映射坐标
4150 面平滑组
4160 平移矩阵
4165 物体可见性
4170 标准映射
4110 – 定点列表
起始点 |
终点 |
大小 |
类型 |
名字 |
0 |
1 |
2 |
unsigned int |
物体顶点数目 |
2 |
5 |
4 |
float |
X坐标值 |
6 |
9 |
4 |
float |
Y坐标值 |
10 |
13 |
4 |
float |
Z坐标值 |
从第2个字节到第13个字节定义了一个顶点的坐标,重复这项定义VerteNum次,便得
到了所有顶点的坐标。
4111(顶点选项块)的子块
该子块由一些整型数组成,第一个整型数表明顶点个数,然后对每个顶点用一个整型数表
示一些位信息。其中,0~7.11~12位影响物体的可见性,8~10位是随机信息,13~15位
表明该顶点是否在某个选择集中被选中。顶点选项块不是很重要,即使将其删除, 3DS也能
正确地将物体装入。
4120面列表块
起始点 |
终点 |
大小 |
类型 |
名称 |
0 |
1 |
2 |
unsigned int |
物体中三角形个数 |
2 |
3 |
2 |
unsigned int |
顶点A序号 |
4 |
5 |
2 |
unsigned int |
顶点B序号 |
6 |
7 |
2 |
unsigned int |
顶点C序号 |
8 |
9 |
2 |
unsigned int |
面信息 |
重复2~9定义PolyNum次,就给出了所有的三角形。2~7所给出的3个整型数是三角
形面的的3个顶点序号,序号为0的顶点表示顶点列表中定义的第一个顶点。顶点的顺序影响
着面的法向量方向。一般情况下三角形应按逆时针方向定义,但有些3DS文件使用顺时针方
向,这时就需要在程序中将其调整过来。面信息是一个整型数,其中前三个二进制位给出了三
角形每条边的顺序,可以根据它们判别三角形是以逆时针还是顺时针顺序给出的。这三个数宇
要么全是0要么全是1,若这三位全为1,其二进制方式为111,意味着三角形的三顶点的正
确顺序应是A->C->B,即A->C->B给出的是顺时针方向:反之,这三位全是0,则意味着A
->B->C给出的就是逆时针方向。
下面给出面信息中每一位的具体含义;
bito AC边顺序,1:C->A 0.A->C
bit1 BC边顺序,1:B->C 0:C->B
bit2 AB边顺序,1:A->B 0:B->A
bit3纹理映射(如果有)
bit4~8保留(0)
bit9~10随机数
bit11~12保留(0)
bit13面在第3迷择集中被选中
bit14面在秦3选择集中波选中
bit15面在第3选择集中彼选中4130画村质块
4130 – 面材质
材质块给出了物体中使用的每一种材质,但并不是每个物体都有材质块,只使用默认材质
的物体就没有材质块。每一个4130面材质块以一个0结尾的材质名称字符串开始,接着跟一
个由数字表示的与该材质相关的面的数目,然后就是一个一个的面。0000表示4120面列表中
的第一个面。
4140纹理映射坐标
前2个字节表示定义的顶点数,然后为每个顶点定义2个浮点型的纹理映射坐标。也就是
说,如果一个顶点的纹理映射在纹理平面的中心,那么其映射坐标即为(0.5,0.5)。
4150面平滑组块
块的大小为面的个数*4个字节,即每个基本数据是一个长整型数字,第n个数字表示该
面是否属千第n个平滑组。
4160局部坐标轴块
局部坐标系轴信息,开始的三个厚点数定义了物体的局部坐标轴在绝对坐标系内的X、Y、
Z坐标、最后的3个浮点数是物体的局部中心。
4170标准映射
前2个字节表明映射的类型,其中,0表明平面映肘或指定映射,当指定映射时,与这个
块中的信息就无关了,1表明圆柱映射,2表明球映射.接着用21个浮点数宋描述这个映射.
光源块
ID 含义
0010 RGB 颜色块
0011 24位颜色
4610 点光源
4620 光源被关闭
起始点 |
终点 |
长度 |
类型 |
名称 |
0 |
3 |
4 |
浮点 |
光源位置x坐标 |
4 |
7 |
4 |
浮点 |
光源位置y坐标 |
8 |
11 |
4 |
浮点 |
光源位置z坐标 |
12 |
15 |
4 |
浮点 |
热点 |
16 |
19 |
4 |
浮点 |
发散 |
0010 - RGB 颜色块
起点 |
终点 |
长度 |
类型 |
名字 |
0 |
3 |
4 |
浮点 |
红 |
4 |
7 |
4 |
浮点 |
绿 |
8 |
11 |
4 |
浮点 |
蓝 |
0011 - RGB 24位颜色块
起点 |
终点 |
长度 |
类型 |
名字 |
0 |
1 |
1 |
字节 |
红 |
1 |
1 |
1 |
字节 |
绿 |
2 |
2 |
1 |
字节 |
蓝 |
4700 – 相机块
描述场景中相机块信息
起始点 |
终点 |
长度 |
类型 |
名称 |
0 |
3 |
4 |
浮点 |
相机位置x坐标 |
4 |
7 |
4 |
浮点 |
相机位置y坐标 |
8 |
11 |
4 |
浮点 |
相机位置z坐标 |
12 |
15 |
4 |
浮点 |
相机目标x坐标 |
16 |
19 |
4 |
浮点 |
相机目标y坐标 |
20 |
23 |
4 |
浮点 |
相机目标z坐标 |
24 |
27 |
4 |
浮点 |
相机倾斜(旋转角) |
26 |
31 |
4 |
浮点 |
相机镜头 |
4. 关键帧块
==========================
关键帧块
id 描述
B00A 未知
7001 块首要描述
B008 帧信息块
B009 未知
B002 信息开始块
* B008 – 帧信息块
simple structure describing frame info
描述帧块信息结构
起始点 |
终点 |
长度 |
类型 |
名称 |
0 |
3 |
4 |
unsigned long |
首帧 |
4 |
7 |
4 |
unsigned long |
尾帧 |
B002 – 物体信息开始块
子块
id 描述
B010 名称与结构层次
B011* 亚元物体命名
B013 未知
B014* 未知
B015 未知
B020 物体中心点
B021 未知
B022 未知
B010 - Name & Hierarchy descriptor
起始点 |
终点 |
长度 |
类型 |
名称 |
0 |
未确定 |
未确定 |
ASCII值 |
物体名 |
未确定 |
未确定 |
2 |
unsigned int |
未知 |
未确定 |
未确定 |
2 |
unsigned int |
未知 |
未确定 |
未确定 |
2 |
unsigned int |
物体层次结构 |
物体的层次结构井不复杂,场景中给于每个物体一个数字以标识其在场景树中的顺序。相
应地。3DS文件中也用相同的方法标识了物体在场景树中的位置。作为根物体给予数字-1
(FFFF)作为其数字标识。当读取文件的时候.就会得到一系列的物体数字标识。如果当前
数字标识比前一个大,那么当前物体是前一个物体的子物体:反之,当数宇标识比前一个数字
标识小.则又回到上一层结构。
例如:
object hierarchy
name
A -1
B 0 This example is taken
C 1 from 50pman.3ds
D 2
E 1 I would really reccomend
F 4 having a look at one of the
G 5 examples with the hierarchy
H 1 numbers to help work it out.
I 7
J 8
K 0
L 10
M 11
N 0
O 13
P 14
A
+-----------------+----------------+
B K N
+----+----+ | |
C E H L O
| | | | |
D F I M P
| |
G J
这个块尚未结束,假如这个块名为$$$DUMMY,这是一个亚元物体,还需加入一些其它块。
B011 - 亚元物体名
B020 – 轴点
前五个浮点数的作用尚不清楚。
起始点 |
终点 |
长度 |
类型 |
名称 |
0 |
3 |
4 |
浮点 |
未知 |
4 |
7 |
4 |
浮点 |
未知 |
8 |
11 |
4 |
浮点 |
未知 |
12 |
16 |
4 |
浮点 |
未知 |
16 |
19 |
4 |
浮点 |
未知 |
20 |
23 |
4 |
浮点 |
未知 |
24 |
27 |
4 |
浮点 |
Y轴坐标 |
28 |
32 |
4 |
浮点 |
X轴坐标 |
12.(附录3)程序读入文件源代码简介
运行程序,选择[文件/打开],发出ID_FILE_OPEN消息,触发函数
BOOL CMy3DSLoaderDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;
if( ((CMy3DSLoaderApp*)AfxGetApp())->OpenFile(lpszPathName) ) return TRUE;
return TRUE;
}
调用
BOOL CMy3DSLoaderApp::OpenFile(LPCTSTR lpszPathName)
{
return ((CMy3DSLoaderView*)((CFrameWnd*)m_pMainWnd)->GetActiveView())->OpenFile(lpszPathName);
}
((CFrameWnd*)m_pMainWnd)->GetActiveView()获得一个指向CMy3DSLoaderView类指针,由于OpenFile函数在CMy3DSLoaderView中被重载,所以调用
CMy3DSLoaderView::OpenFile。
BOOL CMy3DSLoaderView::OpenFile(LPCTSTR lpszPathName)
{
char* file = new char[strlen(lpszPathName)];
strcpy(file, lpszPathName);
C3dsReader Loader;
BOOL result;
if( m_triList.getNumObjects() > 0 ) m_triList.removeAllObjects();//清空数组
result = Loader.Reader(file, &m_triList);
if( result)
{
m_3dsLoaded = TRUE;
m_triList.doAfterMath();
}
return result;
}
调用Loader.Reader读入3ds文件
BOOL C3dsReader::Reader( char* filename, CTriList* _list)
{
FILE* fp;
long fileSize;
Chunk3DS chunk;
DaList = _list;
// 以“二进制”的方式打开一个3DS文件
if ((fp = fopen(filename, "rb")) != NULL)
{
long chunkStart = ftell(fp);
// 获得文件大小
fseek(fp, 0, SEEK_END);
fileSize = ftell(fp);
fseek(fp, 0, SEEK_SET);
// 验证文件类型
if (!Is3DSFile(fp))
{
return FALSE;
}
// 循环所有的块
while (chunkStart < fileSize &&
Read3DSChunk(fp, chunk))
{
switch (chunk.id)
{
case M3DMAGIC:
if (!Read3DSFile(fileSize, chunkStart, chunk.len, fp))
{
fclose(fp);
return FALSE;
}
break;
default:
// 忽略一些不需要的块
fseek(fp, chunkStart + chunk.len, SEEK_SET);
}
chunkStart = ftell(fp);
}
fclose(fp);
}
else
return FALSE;
return TRUE;
}
读入文件后 ,触发CMy3DSLoaderView::OnDraw函数
void CMy3DSLoaderView::OnDraw(CDC* pDC)
{
CMy3DSLoaderDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
//////////////////////////////////////////////////////////////////
RenderScene(); //渲染场景
//////////////////////////////////////////////////////////////////
}
调用CMy3DSLoaderView::RenderScene
BOOL CMy3DSLoaderView::RenderScene()
{
::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
::glMatrixMode(GL_MODELVIEW);
::glLoadIdentity();
::glTranslatef( camPos[0], camPos[1], camPos[2] );
::glRotatef( camRot[0], 1.0F, 0.0F, 0.0F );
::glRotatef( camRot[1], 0.0F, 1.0F, 0.0F );
::glRotatef( camRot[2], 0.0F, 0.0F, 1.0F );
::glPushMatrix();
::glTranslatef(scenePos[0], scenePos[1], scenePos[2]);
::glRotatef( sceneRot[0], 1.0F, 0.0F, 0.0F );
::glRotatef( sceneRot[1], 0.0F, 1.0F, 0.0F );
::glRotatef( sceneRot[2], 0.0F, 0.0F, 1.0F );
DrawAxis(); //画坐标系
Draw3ds();
::glPopMatrix();
::SwapBuffers(m_pDC->GetSafeHdc()); //交互缓冲区
return TRUE;
}
调用 Draw3ds();
void CMy3DSLoaderView::Draw3ds()
{
if (m_3dsLoaded)
{
m_triList.drawGL();
}
}
调用CTriList::drawGL()函数
void CTriList::drawGL()
{
for (int i=0; i <numobjects; i++)
{
objects[i]->drawGL();
}
}
转而调用CTriObject::drawGL()建模
void CTriObject::drawGL()
{
if (normalapplied)
{
int j;
glBegin(GL_TRIANGLES);
for (i=0; i <numfaces/3; i++)
{
j = 3*i;
if(materialsapplied)
glColor4f(materials[matfaces[i]].diffuseColor[0],
materials[matfaces[i]].diffuseColor[1],
materials[matfaces[i]].diffuseColor[2],
1/materials[matfaces[i]].transparency );
else glColor3f( 0.0f, 0.0f, 1.0f );
::glNormal3f( nx[i], ny[i], nz[i]);
::glVertex3f( x[faces[j]] , y[faces[j]] , z[faces[j]]);
::glVertex3f( x[faces[j+1]], y[faces[j+1]], z[faces[j+1]]);
::glVertex3f( x[faces[j+2]], y[faces[j+2]], z[faces[j+2]]);
}
glEnd();
}
else
{
glBegin(GL_TRIANGLES);
for (i=0; i <numfaces; i+=3)
{
if(materialsapplied)
glColor3f(materials[matfaces[i/3]].diffuseColor[0], materials[matfaces[i/3]].diffuseColor[1], materials[matfaces[i/3]].diffuseColor[2] );
else glColor3f( 0.0f, 0.0f, 1.0f );
glVertex3f( x[faces[i]] , y[faces[i]] , z[faces[i]]);
glVertex3f( x[faces[i+1]], y[faces[i+1]], z[faces[i+1]]);
glVertex3f( x[faces[i+2]], y[faces[i+2]], z[faces[i+2]]);
}
glEnd();
}
glPopMatrix();
}
至此,程序读入3ds文件过程基本清晰。