自制C#版3DS文件的解析器並用SharpGL顯示3DS模型
據說*.3ds格式的3D模型文件是很古老和過時的格式。本文參考了(http://www.spacesimulator.net/wiki/index.php?title=Tutorials:3ds_Loader)和(http://www.cnblogs.com/lookof/archive/2009/03/27/1423695.html),在此表示感謝。本文講解如何從零開始用C#寫一個3ds文件的解析器,然后用SharpGL(C#對opengl的封裝)來顯示3ds模型。有圖有真相。
上圖使用的3ds模型文件和貼圖文件在此。(spaceship.zip)(spaceshiptexture.bmp)
3DS文件格式
3ds文件是二進制的。3ds格式的基本單元叫塊(chunk)。我們就是讀這樣一塊一塊的信息。目錄樹如下,縮進風格體現了塊的父子關系。可見3ds模型文件和XML文件類似,都是只有1個根結點的樹狀結構。

1 MAIN CHUNK 0x4D4D 2 3D EDITOR CHUNK 0x3D3D 3 OBJECT BLOCK 0x4000 4 TRIANGULAR MESH 0x4100 5 VERTICES LIST 0x4110 6 FACES DESCRIPTION 0x4120 7 FACES MATERIAL 0x4130 8 MAPPING COORDINATES LIST 0x4140 9 SMOOTHING GROUP LIST 0x4150 10 LOCAL COORDINATES SYSTEM 0x4160 11 LIGHT 0x4600 12 SPOTLIGHT 0x4610 13 CAMERA 0x4700 14 MATERIAL BLOCK 0xAFFF 15 MATERIAL NAME 0xA000 16 AMBIENT COLOR 0xA010 17 DIFFUSE COLOR 0xA020 18 SPECULAR COLOR 0xA030 19 TEXTURE MAP 1 0xA200 20 BUMP MAP 0xA230 21 REFLECTION MAP 0xA220 22 [SUB CHUNKS FOR EACH MAP] 23 MAPPING FILENAME 0xA300 24 MAPPING PARAMETERS 0xA351 25 KEYFRAMER CHUNK 0xB000 26 MESH INFORMATION BLOCK 0xB002 27 SPOT LIGHT INFORMATION BLOCK 0xB007 28 FRAMES (START AND END) 0xB008 29 OBJECT NAME 0xB010 30 OBJECT PIVOT POINT 0xB013 31 POSITION TRACK 0xB020 32 ROTATION TRACK 0xB021 33 SCALE TRACK 0xB022 34 HIERARCHY POSITION 0xB030
實際上完整的chunk列表有上千種類型,我們只需解析其中的頂點列表、面列表和紋理UV列表就行了。
以類型標識為0x4D4D的MAIN CHUNK為例,整個3ds文件的前兩個byte必須是0x4D4D,否則就說明這個文件不是3ds模型文件。然后從第3到第6個byte是一個Uint32型的數值,表示整個MAIN CHUNK的長度。由於MAIN CHUNK是整個3ds文件的根結點,它的長度也即整個3ds文件的長度。
塊(Chunk)的結構
每一個“chunk”的結構如下所示:
偏移量 |
長度 |
|
0 |
2 |
塊標識符 |
2 |
4 |
塊長: 塊數據 + 子塊內容 |
6 |
n |
塊數據 |
6+n |
m |
S子塊 |
讀取的思路是:首先根據偏移量和長度找到一個塊的標識符,然后據此來判斷它是什么塊,遇到我們需要的塊,就進一步讀取,如果不需要,直接跳過這一塊,讀取下面的塊。
我們的解析器需要頂點、面和貼圖UV信息,根據3ds模型文件的樹狀結構,可以找到需要解析的Chunk如下。
MAIN CHUNK |
|
Identifier |
0x4d4d |
Length |
0 + sub-chunks length |
Chunk father |
None |
Sub chunks |
3D EDITOR CHUNK |
Data |
None |
3D EDITOR CHUNK |
|
Identifier |
0x3D3D |
Length |
0 + sub-chunks length |
Chunk father |
MAIN CHUNK |
Sub chunks |
OBJECT BLOCK, MATERIAL BLOCK, KEYFRAMER CHUNK |
Data |
None |
OBJECT BLOCK |
|
Identifier |
0x4000 |
Length |
Object name length + sub-chunks length |
Chunk father |
3D EDITOR CHUNK |
Sub chunks |
TRIANGULAR MESH, LIGHT, CAMERA |
Data |
Object name |
TRIANGULAR MESH |
|
Identifier |
0x4100 |
Length |
0 + sub-chunks length |
Chunk father |
OBJECT BLOCK |
Sub chunks |
VERTICES LIST, FACES DESCRIPTION, MAPPING COORDINATES LIST |
Data |
None |
VERTICES LIST(點數據在這) |
|
Identifier |
0x4110 |
Length |
varying + sub-chunks length |
Chunk father |
TRIANGULAR MESH |
Sub chunks |
None |
Data |
Vertices number (unsigned short) |
FACES DESCRIPTION(面數據在這) |
|
Identifier |
0x4120 |
Length |
varying + sub-chunks length |
Chunk father |
TRIANGULAR MESH |
Sub chunks |
FACES MATERIAL |
Data |
Polygons number (unsigned short) |
MAPPING COORDINATES LIST(貼圖數據在這) |
|
Identifier |
0x4140 |
Length |
varying + sub-chunks length |
Chunk father |
TRIANGULAR MESH |
Sub chunks |
SMOOTHING GROUP LIST |
Data |
Vertices number (unsigned short) |
據此給出Chunk的枚舉類型

1 enum ChunkType 2 { 3 MainChunk = 0x4D4D, 4 _3DEditorChunk = 0x3D3D, 5 CVersion = 0x0002, 6 KeyFramerChunk = 0xB000, 7 MaterialBlock = 0xAFFF, 8 MaterialName = 0xA000, 9 AmbientColor = 0xA010, 10 DiffuseColor = 0xA020, 11 SpecularColor = 0xA030, 12 C_MATSHININESS = 0xA040, 13 TextureMap = 0xA200, 14 MappingFilename = 0xA300, 15 ObjectBlock = 0x4000, 16 TriangularMesh = 0x4100, 17 VerticesList = 0x4110, 18 FacesDescription = 0x4120, 19 FacesMaterial = 0x4130, 20 MappingCoordinatesList = 0x4140 21 }
解析結果
解析結果為一個3dsFile類型的實例,它包含若干模型(稱為entity)。每個entity都含有描述三維模型的頂點、面和貼圖UV信息,據此我們用SharpGL來將其顯示出來。

1 foreach (var entity in _3dsFile.Entities) 2 { 3 gl.Enable(OpenGL.GL_TEXTURE_2D); 4 gl.BindTextue(OpenGL.GL_TEXTURE_2D, this.texture.TextureName); 5 gl.Begin(SharpGL.Enumerations.BeginMode.Triangles); 6 foreach (var triangle in entity.indices) 7 { 8 var point1 = entity.vertices[triangle.vertex1]; 9 var uv1 = entity.texcoords[triangle.vertex1]; 10 gl.TexCoord(uv1.U, uv1.V); 11 gl.Vertex(point1.X, point1.Y, point1.Z); 12 var point2 = entity.vertices[triangle.vertex2]; 13 var uv2 = entity.texcoords[triangle.vertex2]; 14 gl.TexCoord(uv2.U, uv2.V); 15 gl.Vertex(point2.X, point2.Y, point2.Z); 16 var point3 = entity.vertices[triangle.vertex3]; 17 var uv3 = entity.texcoords[triangle.vertex3]; 18 gl.TexCoord(uv3.U, uv3.V); 19 gl.Vertex(point3.X, point3.Y, point3.Z); 20 } 21 gl.End(); 22 } 23 24
需要注意的一點是,SharpGL加載的貼圖是上下反向的,所以你必須把准備好的貼圖上下翻轉,才能在SharpGL里正常使用。