3DS 文件格式
1、讀取規則 3ds文件的讀取規則如下:
字節:直接讀取;
字:先讀低位字節,后讀高位字節,如ed 3c讀出后的字為3c ed;
雙字:先讀低位字,后讀高位字,如ed 3c 25 43讀出后的雙字為43 25 3c ed;
浮點數:直接讀取四個字節。
2、CHUNK
chunk是3ds文件的基本構成單位。每一個chunk包括一個頭和一個主體。chunk是相互嵌套的,這就決定了你必須以遞歸的方式讀取它們。chunk的頭又由兩部分組成:ID長一個字,chunk的長度(以字節為單位,包括頭)長一個雙字。ID表示chunk的含義。事實上有上千個chunk,它們構成了一個復雜但靈活的文件系統,你不需要知道所有的就可以順利的讀完整個文件。我基本搞清楚的chunk有:
0x4D4D:根chunk,每一個3ds文件都起自它,它的長度也就是文件的長度。它包含了兩個chunk:編輯器,和關鍵幀。
父chunk:無
子chunk:0x3D3D、0xB000
長度:頭長度+子chunk長度
內容:無
0x3D3D:編輯器主chunk,它包含有:網格信息、燈光信息、攝象機信息和材質信息。
父chunk:0x4D4D
子chunk:0x4000、0xafff
長度:頭長度+子chunk長度
內容:無
0x4000:網格主chunk,它包含了所有的網格。
父chunk:0x3D3D
子chunk:0x4100
長度:頭長度+子chunk長度+內容長度
內容:名稱(以空字節結尾的字符串)
0x4100:網格信息,包含網格名稱、頂點、面、紋理坐標等。
父chunk:0x4000
子chunk:0x4110、0x4120、0x4140、0x4160
長度:頭長度+子chunk長度
內容:無
0x4110:頂點信息。
父chunk:0x4100
子chunk:無
長度:頭長度+內容長度
內容:頂點個數(一個字)頂點坐標(三個浮點數一個坐標x、y、z,個數*3*浮點數)
0x4120:面信息。
父chunk:0x4100
子chunk:0x4130
長度:頭長度+子chunk長度+內容長度
內容:面個數(一個字)頂點索引(三個字一個索引1、2、3,個數*3*字)
0x4130:與網格相關的材質信息。
父chunk:0x4120
子chunk:無
長度:頭長度+內容長度
內容:名稱(以空字節結尾的字符串)與材質相連的面的個數(一個字)與材質相連的面的索引(個數*字)
0x4140:紋理坐標。
父chunk:0x4100
子chunk:無
長度:頭長度+內容長度
內容:坐標個數(一個字)坐標(兩個浮點數一個坐標u、v,個數*2*浮點數)
0x4160:轉換矩陣。
父chunk:0x4100
子chunk:無
長度:頭長度+內容長度
內容: x軸的向量(三個浮點數u、v、n) y軸的向量(三個浮點數u、v、n) z軸的向量(三個浮點數u、v、n)源點坐標(三個浮點數x、y、z)
0xafff:材質信息。
父chunk:0x4D4D
子chunk:0xa000、0xa020、0xa200
長度:頭長度+子chunk長度
內容:無
0xa000:材質名稱。
父chunk:0xafff
子chunk:無
長度:頭長度+內容長度
內容:名稱(以空字節結尾的字符串)
0xa020:滿射色。
父chunk:0xafff
子chunk:0x0011、0x0012
長度:頭長度+子chunk長度
內容:無
0xa200:紋理帖圖。
父chunk:0xafff
子chunk:0xa300
長度:頭長度+子chunk長度
內容:無
0xa300:帖圖名稱。
父chunk:0xa200
子chunk:無
長度:頭長度+內容長度
內容:名稱(以空字節結尾的字符串)
0xB000:關鍵幀主chunk,包含所有的關鍵幀信息。
父chunk:0x4D4D
子chunk:0xB008、0xB002
長度:頭長度+子chunk長度
內容:無
0xB008:關鍵幀的起點和終點。
父chunk:0xB000
子chunk:無
長度:頭長度+內容長度
內容:起始幀(一個雙字)結尾幀(一個雙字)
0xB002:網格的關鍵幀信息。
父chunk:0xB000
子chunk:0xB010、0xB011、0xB013、0xB020、0xB021、0xB022、0xB030
長度:頭長度+子chunk長度
內容:無
0xB010:關鍵幀的層次信息,包括名稱和上一級關鍵幀的索引,名稱與它指向的網格名稱一致。
父chunk:0xB002
子chunk:無
長度:頭長度+內容長度
內容:名稱(以空字節結尾的字符串)兩個未知的字上一級關鍵幀的索引(一個字)
0xB011:關鍵幀的dummy名稱,我不知道dummy在這里的具體含義,但只要你在上一個chunk中讀到的名稱是“$$$DUMMY”那么你就要到這里來讀它真正的名稱。因為這說明它指向的不是網格而是虛擬的組。
父chunk:0xB002
子chunk:無
長度:頭長度+內容長度
內容:名稱(以空字節結尾的字符串)
0xB013:支點坐標。
父chunk:0xB002
子chunk:無長度:頭長度+內容長度
內容:三個浮點數x,y,z
0xB020:移動的關鍵幀信息。
父chunk:0xB002
子chunk:無
長度:頭長度+內容長度
內容:五個未知的字幀個數(一個字)一個個數那么多的循環結構{ 幀索引(一個字)一個未知的雙字移動的向量(三個浮點數x,y,z) }
0xB021:轉動的關鍵幀信息。
父chunk:0xB002
子chunk:無
長度:頭長度+內容長度
內容:五個未知的字幀個數(一個字)一個個數那么多的循環結構{ 幀索引(一個字)一個未知的雙字轉動角度(一個浮點數)繞之轉動的向量(三個浮點數x,y,z) }
0xB022:縮放的關鍵幀信息。
父chunk:0xB002
子chunk:無
長度:頭長度+內容長度
內容:五個未知的字幀個數(一個字)一個個數那么多的循環結構{ 幀索引(一個字)一個未知的雙字伸縮的向量(三個浮點數x,y,z) }
0xB030:關鍵幀的索引。
父chunk:0xB002
子chunk:無
長度:頭長度+內容長度
內容:關鍵幀的索引(一個字)
以下的chunk可能出現在任何chunk中:
0x0010:浮點數格式的顏色。
父chunk:任何可能的chunk
子chunk:無
長度:頭長度+內容長度
內容:顏色(三個浮點數red,green,blue)
0x0011:字節格式的顏色。
父chunk:任何可能的chunk
子chunk:無
長度:頭長度+內容長度
內容:顏色(三個字節red,green,blue)
0x0012:字節格式的gamma矯正。
父chunk:任何可能的chunk
子chunk:無
長度:頭長度+內容長度
內容:顏色(三個字節red,green,blue)
0x0013:浮點數格式的gamma矯正。
父chunk:任何可能的chunk
子chunk:無
長度:頭長度+內容長度
內容:顏色(三個浮點數red,green,blue)
0x0030:字格式的百分比。
父chunk:任何可能的chunk
子chunk:無
長度:頭長度+內容長度
內容:百分比(一個字0~100)
0x0031:浮點數格式的百分比。
父chunk:任何可能的chunk
子chunk:無
長度:頭長度+內容長度
內容:百分比(一個浮點數0~100)
3ds文件是基於“塊”存儲的,這些塊描述了諸如場景,每個編輯窗口(Viewport)的狀態,材質,網格對象等等數據。
1、3DS塊的組織方式
MAIN3DS (0x4D4D) //基本信息塊
|
+--VERSION (0x0002) //版本信息塊
|
+--EDIT3DS (0x3D3D) //編輯信息塊
| |
| +--EDIT_MATERIAL (0xAFFF) //材質
| | |
| | +--MAT_NAME01 (0xA000) //材質名稱
| | +--MAT_AMBCOL (0xA010) //環境色
| | +--MAT_DIFCOL (0xA020) //漫射色
| | +--MAT_SPECOL (0xA030) //反射色
| | +--MAT_SHININESS (0xA040) //亮度
| | +--MATMAP (0xA200) //材質的紋理?????
| | +--MATMAPFILE (0xA300) //保存紋理的文件名
| |
| +--EDIT_CONFIG1 (0x0100) //配置信息1
| +--EDIT_CONFIG2 (0x3E3D) //配置信息2
| +--EDIT_VIEW_P1 (0x7012) //視窗1
| | |
| | +--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) //視窗2
| | |
| | +--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) //視窗3
| +--EDIT_VIEW1 (0x7001) //視圖
| +--EDIT_BACKGR (0x1200) //背景
| +--EDIT_AMBIENT (0x2100) //環境
| +--EDIT_OBJECT (0x4000) //對象(包括面、點等信息)
| | |
| | +--OBJ_TRIMESH (0x4100) //三角形網格對象
| | | |
| | | +--TRI_VERTEX (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) //未知塊
+--EDIT_VIEW1 (0x7001) //視圖
+--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_OBJTRANSLATE (0xB020) //偏移
+--KEYF_OBJROTATE (0xB021) //旋轉
+--KEYF_OBJSCALE (0xB022) //縮放
另外還有一些塊是在整個文件中都會經常出現的,那就是顏色塊
COL_RGB 0x0010 //RGB色彩模式,以float存放3個分量
COL_TRU 0x0011 //真彩色模式,以char存放3個分量
COL_UNKNOWN 0x0013 //未知塊
SHI_PER 0x0030 //百分比亮度
3ds文件中數據的存儲方式是Intel式的,也就是說是高位放在后面,低位放在前面。比如:網格對象塊的塊頭ID:0x4000在文件里是以00 40存放的,對於windows程序員來說,無需做任何轉換。
按3ds文件的划分方式,有一個塊總是位於整個文件的最開始,是其他所有塊的根塊,我們稱之為主塊或基本信息塊(MAIN3D塊)。主塊下包含兩個塊,他們是一級子塊:一個描述場景數據的主編輯塊和一個描述關鍵幀數據的關鍵幀塊。相對於關鍵幀塊,主編輯塊對我們更重要。它包含了場景中使用的材質(紋理是材質的一部分),配置,視口的定義方式,背景顏色,物體的數據等等一系列數據,可以說他就表示了我們當前編輯場景的狀況和當前窗口的配置數據。
主編輯塊的子塊雖然是按照一定的次序存放,但其中有些塊並不是一定存在的(比如:如果你沒有定義材質,使用缺省材質,這里將不存在材質塊)。材質塊定義了使用於物體上的材質的屬性,包括:材質名稱,環境色,漫射色,反射色,亮度,材質的紋理及保存紋理的文件名,其中材質名稱是一字符串,環境色、漫射色、反射色是顏色塊。然后是網格對象塊,其包含了大部分我們所關心的物體的幾何數據以及燈光、相機等相關信息。
2、基本塊的數據結構及讀取方式
每個塊都包含一個塊頭:塊ID+塊長度,緊跟着塊頭便是相應的數據了。以一個最基本的塊TRI_VERTEXL(0x4110)頂點塊為例,在文件中其存儲形式如圖:
如此可見,塊頭包括ID和Size兩項,共占6位,Size指得是整個塊的長度,而不是數據的長度,一定要注意了。所以讀一個頂點塊的代碼可寫為:
ReadChunckHeader(&CH); //讀入一個塊頭信息
switch(CH.ChunckID) //判斷塊的類型
{ ……
case TRI_VERTEXL: //是對象的頂點塊
p3DObject->NumVerts = ReadInt();//獲得頂點的總數目
p3DObject->pVertices = ::new CVertex[p3DObject->NumVerts]; //分配足夠空間
if(!p3DObject->pVertices)
AfxMessageBox( "Error Allocating Memory");
for(i = 0; i < p3DObject->NumVerts; i++)//逐個頂點讀入其三維坐標值
{
fread(&p3DObject->pVertices[i].x, 4, 1, m_3dsFile);
fread(&p3DObject->pVertices[i].y, 4, 1, m_3dsFile);
fread(&p3DObject->pVertices[i].z, 4, 1, m_3dsFile);
}
}
而要跳過一個塊的代碼可寫為:(兩種方法是等價的)
方法1: fseek(m_3dsFile, -6, SEEK_CUR);//文件指針后退6位
fseek(m_3dsFile, CH.ChunckSize, SEEK_CUR);//跳過塊的長度
方法2: fseek(m_3dsFile, CH.ChunckSize-6, SEEK_CUR);
3、塊的層級結構及讀取方式
3ds文件格式並不是一些基本的塊堆積而成的,而是一個層級結構,基本塊是一個文件必須有的,處於頂級,主塊有版本信息塊、編輯信息塊和關鍵幀信息塊,編輯信息塊中的一級塊又包括材質信息塊、對象信息塊等等。對象信息塊中的二級塊包括網格對象信息塊、燈光信息塊、相機信息等。網格對象信息塊中的三級信息塊包括頂點信息、面信息、材質名稱、映射坐標等。所以說,3ds文件是一個復雜而有序的整體,若從基本塊開始講起,那就得繪出整個文件了,呵呵,這樣一個浩大的工程留給比較有耐心的你吧。現在從EDIT_OBJECT開始,我們來分析一下網格對象塊中數據的讀取問題。
如圖所示Data22是EDIT_OBJECT塊的數據,Size22等於Data22+6 {注:ID號是int型2位,Size22是long型4位},在Data22中包含三級子塊,其中一個三級子塊是OBJ_TRIMESH,對應的數據是Data31,Size31等於Data31+6,同理在Data31中包含一系列四級子塊,TRI_VERTEX是其中的一個子塊,而且TRI_VERTEX是一個基本塊,不能再往下細分。Data41中存儲的是頂點的坐標值。例如,網格對象中一共有100個頂點,Data41等於100*4=400位{注:頂點三級坐標是以float存儲的,故為4位},所以Size41等於Data41+6=406,代表了整個TRI_VERTEX的長度{注:包括ID號和Size41本身}。
塊長編號設為:
MAIN3DS(0x4D4D) Size00
|
+--VERSION(0x0002) Size11
+--EDIT3DS(0x3D3D) Size12
| |
| +--EDIT_MATERIAL(0xAFFF) Size21
| +--EDIT_OBJECT(0x4000) Size22
| | |
| | +--OBJ_TRIMESH(0x4100) Size31
| | | |
| | | +--TRI_VERTEX(0x4110) Size41
| | | +--TRI_VERTEXOPTIONS(0x4111) Size42
| | | +--……
所以,我們如果要讀入網格對象的頂點坐標,可以用一個switch塊來逐級判斷,找到TRI_VERTEX塊,然后將其數據Data41讀入到我們的自定義數據結構中。
ReadChunckHeader(&CH); //讀入一個塊頭信息
switch(CH.ChunckID) //判斷塊的類型
{ ……
case EDIT_OBJECT: //是網格對象塊
ObjectName = ReadName(); //獲得對象的名稱
ObjectFound = 2; //置計數為2,意味着要讀出對象的面信息和頂點信息2個塊
Success= true; //成功標識
delete[] ObjectName; //清空字符串用於下一個對象的讀取
ObjectName = NULL;
break; //跳出循環讀下一個塊頭
case TRI_VERTEXL: //是對象的頂點塊
if(ObjectFound) //計數不為0
{
ObjectFound--; //將計數減一
p3DObject->NumVerts = ReadInt(); //獲得頂點的總數目
p3DObject->pVertices = ::new CVertex[p3DObject->NumVerts]; //分配足夠空間
if(!p3DObject->pVertices)
AfxMessageBox("Error Allocating Memory");
for(i = 0; i < p3DObject->NumVerts; i++) //逐個頂點讀入其三維坐標值
{
fread(&p3DObject->pVertices[i].x, 4, 1, m_3dsFile);
fread(&p3DObject->pVertices[i].y, 4, 1, m_3dsFile);
fread(&p3DObject->pVertices[i].z, 4, 1, m_3dsFile);
}
}
else //計數為0時將跳過此頂點塊,跳過一基本塊的方法在上一節有介紹
{
fseek(m_3dsFile, -6, SEEK_CUR);
fseek(m_3dsFile, CH.ChunckSize, SEEK_CUR);
}
break; //跳出循環讀下一個塊頭
……
}
結論:雖然說Autodesk公司並沒有發布官方的3ds文件的文檔,但是從網上收集的資料來看,我們完全可以將我們所關心的數據讀入到自定義的數據結構中,然后利用OpenGL強大的圖形函數庫將它繪制出來。以上是一篇入門文檔,只對其原理和數據塊的讀取做了一個簡單的介紹,如果你有更高的要求可以參考以下幾篇文檔:
[1]《3DS文件結構(.3ds)》——作者:Martin van Velsen 翻譯:櫻
[2]《The Unofficial 3DStudio 3DS File Format》——作者:Jeff Lewis
[3]《從3DS文件中導入網格數據》——作者:故鄉的雲
肖建清
2006.5.25於南大