OpenGL一个重要应用就是能够读取外部的3D模型文件,比如OBJ,MD2,MD3,3DS等。在我之前的日志里已经写过一篇关于OpenGL读取Obj格式的类,详情可以看“Qt下学习OpenGL之OBJ模型”。而这次我要介绍的是3DS文件的读取。
接下来要贴出的代码已经在Qt4下成功运行,不过里面需要依赖一个CBMPLoader的类,这个可以从《OpenGL游戏编程》这本书里看到,也可以在我博客里找到对应的代码。
最后,还补充一下个人的看法。对于3DS格式,相比OBJ的话,其多了光照、贴图、材质等信息,但如果是工业标准,并且对于贴图和材质没有特别的要求的话,使用代码给每个不同物体写上一个单一颜色就好了。这样速度很快,速度是很重要的因素啊。
好了,贴代码了。
3DSLoader.h内容:
001 |
#ifndef __3DSLOADER_H__ |
002 |
#define __3DSLOADER_H__ |
003 |
|
004 |
#include "Stdafx.h" |
005 |
#include "Vector.h" |
006 |
#include "CBMPLoader.h" |
007 |
|
008 |
/* 下面是定义一些块的ID号 */ |
009 |
|
010 |
/* 基本块(Primary Chunk),位于文件的开始 */ |
011 |
#define PRIMARY 0x4D4D |
012 |
|
013 |
/* 主块(Main Chunks) */ |
014 |
#define OBJECTINFO 0x3D3D /* 网格对象的版本号 */ |
015 |
#define VERSION 0x0002 /* .3ds文件的版本 */ |
016 |
#define EDITKEYFRAME 0xB000 /* 所有关键帧信息的头部 */ |
017 |
|
018 |
/* 对象的次级定义 */ |
019 |
#define MATERIAL 0xAFFF /* 纹理信息 */ |
020 |
#define OBJECT 0x4000 /* 对象的面、顶点等信息 */ |
021 |
|
022 |
/* 材质的次级定义 */ |
023 |
#define MATNAME 0xA000 /* 材质名称 */ |
024 |
#define MATDIFFUSE 0xA020 /* 对象/材质的颜色 */ |
025 |
#define MATMAP 0xA200 /* 新材质的头部 */ |
026 |
#define MATMAPFILE 0xA300 /* 保存纹理的文件名 */ |
027 |
#define OBJ_MESH 0x4100 /* 新的网格对象 */ |
028 |
|
029 |
/* 网格对象的次级定义 */ |
030 |
#define OBJ_VERTICES 0x4110 /* 对象顶点 */ |
031 |
#define OBJ_FACES 0x4120 /* 对象的面 */ |
032 |
#define OBJ_MATERIAL 0x4130 /* 对象的材质 */ |
033 |
#define OBJ_UV 0x4140 /* 对象的UV纹理坐标 */ |
034 |
|
035 |
/* 面的结构定义 */ |
036 |
struct tFace |
037 |
{ |
038 |
int vertIndex[3]; /* 顶点索引 */ |
039 |
int coordIndex[3]; /* 纹理坐标索引 */ |
040 |
}; |
041 |
|
042 |
/* 材质信息结构体 */ |
043 |
struct tMatInfo |
044 |
{ |
045 |
char strName[255]; /* 纹理名称 */ |
046 |
char strFile[255]; /* 纹理文件名称 */ |
047 |
unsigned char color[3]; /* 对象的RGB颜色 */ |
048 |
int texureId; /* 纹理ID */ |
049 |
float uTile; /* u 重复 */ |
050 |
float vTile; /* v 重复 */ |
051 |
float uOffset; /* u 纹理偏移 */ |
052 |
float vOffset; /* v 纹理偏移 */ |
053 |
} ; |
054 |
|
055 |
/* 对象信息结构体 */ |
056 |
struct t3DObject |
057 |
{ |
058 |
int numOfVerts; /* 模型中顶点的数目 */ |
059 |
int numOfFaces; /* 模型中面的数目 */ |
060 |
int numTexVertex; /* 模型中纹理坐标的数目 */ |
061 |
int materialID; /* 纹理ID */ |
062 |
bool bHasTexture; /* 是否具有纹理映射 */ |
063 |
char strName[255]; /* 对象的名称 */ |
064 |
Vector3 *pVerts; /* 对象的顶点 */ |
065 |
Vector3 *pNormals; /* 对象的法向量 */ |
066 |
Vector2 *pTexVerts; /* 纹理UV坐标 */ |
067 |
tFace *pFaces; /* 对象的面信息 */ |
068 |
}; |
069 |
|
070 |
/* 模型信息结构体 */ |
071 |
struct t3DModel |
072 |
{ int numOfObjects; /* 模型中对象的数目 */ |
073 |
int numOfMaterials; /* 模型中材质的数目 */ |
074 |
QVectorpMaterials; /* 材质链表信息 */ |
075 |
QVector pObject; /* 模型中对象链表信息 */ |
076 |
}; |
077 |
|
078 |
/* 块信息的结构 */ |
079 |
struct tChunk |
080 |
{ |
081 |
unsigned short int ID; /* 块的ID */ |
082 |
unsigned int length; /* 块的长度 */ |
083 |
unsigned int bytesRead; /* 需要读的块数据的字节数 */ |
084 |
}; |
085 |
|
086 |
#define MAX_TEXTURES 100 |
087 |
|
088 |
/* 3DS文件载入类 */ |
089 |
class C3DSLoader |
090 |
{ |
091 |
public : |
092 |
/* 构造函数 */ |
093 |
C3DSLoader(); |
094 |
virtual ~C3DSLoader(); |
095 |
void Draw(); //显示3ds模型 |
096 |
void Init( char *filename); |
097 |
|
098 |
private : |
099 |
/* 读一个字符串 */ |
100 |
int GetString( char *); |
101 |
|
102 |
/* 装载3ds文件到模型结构中 */ |
103 |
bool Import3DS(t3DModel *pModel, char *strFileName); |
104 |
|
105 |
/* 从文件中创建纹理 */ |
106 |
void LoadTexture( char * filename, GLuint textureArray[], GLuint textureID); |
107 |
|
108 |
/* 读取一个块 */ |
109 |
void ReadChunk(tChunk *); |
110 |
|
111 |
/* 读取下一个块 */ |
112 |
void ReadNextChunk(t3DModel *pModel, tChunk *); |
113 |
|
114 |
/* 读取下一个对象 */ |
115 |
void ReadNextObjChunk(t3DModel *pModel,t3DObject *pObject,tChunk *); |
116 |
|
117 |
/* 读取下一个材质块 */ |
118 |
void ReadNextMatChunk(t3DModel *pModel, tChunk *); |
119 |
|
120 |
/* 读取对象颜色的RGB值 */ |
121 |
void ReadColor(tMatInfo *pMaterial, tChunk *pChunk); |
122 |
|
123 |
/* 读取对象的顶点信息 */ |
124 |
void ReadVertices(t3DObject *pObject, tChunk *); |
125 |
|
126 |
/* 读取对象的面信息 */ |
127 |
void ReadVertexIndices(t3DObject *pObject,tChunk *); |
128 |
|
129 |
/* 读取对象的纹理坐标 */ |
130 |
void ReadUVCoordinates(t3DObject *pObject,tChunk *); |
131 |
|
132 |
/* 读取赋予对象的材质 */ |
133 |
void ReadObjMat(t3DModel *pModel,t3DObject *pObject,tChunk *pPreChunk); |
134 |
|
135 |
/* 计算对象顶点的法向量 */ |
136 |
void ComputeNormals(t3DModel *pModel); |
137 |
|
138 |
/* 释放内存,关闭文件 */ |
139 |
void CleanUp(); |
140 |
|
141 |
FILE *m_FilePointer; /*< 文件指针 */ |
142 |
tChunk *m_CurrentChunk; /*< 读取过程中当前块 */ |
143 |
tChunk *m_TempChunk; /*< 临时块 */ |
144 |
GLuint m_textures[MAX_TEXTURES]; /*< 纹理 */ |
145 |
t3DModel m_3DModel; /*< 模型 */ |
146 |
CBMPLoader m_BMPTexture; /* 载入位图 */ |
147 |
|
148 |
}; |
149 |
|
150 |
#endif |
3DSLoader.cpp内容:
001 |
#include "Stdafx.h" |
002 |
#include "3DSLoader.h" |
003 |
|
004 |
/* 构造函数 */ |
005 |
C3DSLoader::C3DSLoader() |
006 |
{ |
007 |
m_CurrentChunk = new tChunk; /* 为当前块分配空间 */ |
008 |
m_TempChunk = new tChunk; /* 为临时块分配空间 */ |
009 |
m_3DModel.numOfObjects = 0; |
010 |
m_3DModel.numOfMaterials = 0; |
011 |
for ( int i=0;i0) |
012 |
LoadTexture(m_3DModel.pMaterials[i].strFile,m_textures, i); /* 使用纹理文件名称来装入位图 */ |
013 |
m_3DModel.pMaterials[i].texureId = i; /* 设置材质的纹理ID */ |
014 |
} |
015 |
} |
016 |
|
017 |
/* 显示3ds模型 */ |
018 |
void C3DSLoader::Draw() |
019 |
{ |
020 |
/*保存现有颜色属实性 */ |
021 |
glPushAttrib(GL_CURRENT_BIT); |
022 |
glDisable(GL_TEXTURE_2D); |
023 |
//glEnable(GL_LIGHT0); |
024 |
|
025 |
/* 遍历模型中所有的对象 */ |
026 |
for ( int i = 0; i < m_3DModel.numOfObjects; i++) |
027 |
{ |
028 |
/* 如果对象的大小小于0,则退出 */ |
029 |
if (m_3DModel.pObject.size() <= 0) break ; /* 获得当前显示的对象 */ t3DObject *pObject = &m_3DModel.pObject[i]; /* 判断该对象是否有纹理映射 */ if (pObject->bHasTexture) |
030 |
{ |
031 |
glEnable(GL_TEXTURE_2D); /* 打开纹理映射 */ |
032 |
glBindTexture(GL_TEXTURE_2D, m_textures[pObject->materialID]); |
033 |
} |
034 |
else |
035 |
glDisable(GL_TEXTURE_2D); /* 关闭纹理映射 */ |
036 |
|
037 |
glColor3ub(255, 255, 255); |
038 |
|
039 |
/* 开始绘制 */ |
040 |
|
041 |
glBegin(GL_TRIANGLES); |
042 |
|
043 |
/* 遍历所有的面 */ |
044 |
for ( int j = 0; j < pObject->numOfFaces; j++) |
045 |
{ |
046 |
/* 遍历三角形的所有点 */ |
047 |
for ( int tex = 0; tex < 3; tex++) { /* 获得面对每个点的索引 */ int index = pObject->pFaces[j].vertIndex[tex]; |
048 |
|
049 |
/* 给出法向量 */ |
050 |
glNormal3f(pObject->pNormals[index].x,pObject->pNormals[index].y, |
051 |
pObject->pNormals[index].z); |
052 |
|
053 |
/* 如果对象具有纹理 */ |
054 |
if (pObject->bHasTexture) |
055 |
{ |
056 |
/* 确定是否有UVW纹理坐标 */ |
057 |
if (pObject->pTexVerts) |
058 |
glTexCoord2f(pObject->pTexVerts[index].x,pObject->pTexVerts[index].y); |
059 |
} |
060 |
else |
061 |
{ |
062 |
if (m_3DModel.pMaterials.size() && pObject->materialID>= 0) |
063 |
{ |
064 |
unsigned char *pColor = m_3DModel.pMaterials[pObject->materialID].color; |
065 |
glColor3ub(pColor[0],pColor[1],pColor[2]); |
066 |
} |
067 |
} |
068 |
glVertex3f(pObject->pVerts[index].x,pObject->pVerts[index].y,pObject->pVerts[index].z); |
069 |
} |
070 |
} |
071 |
glEnd(); |
072 |
/* 绘制结束 */ |
073 |
} |
074 |
|
075 |
//glDisable(GL_LIGHT0); |
076 |
glEnable(GL_TEXTURE_2D); |
077 |
|
078 |
/* 恢复前一属性 */ |
079 |
glPopAttrib(); |
080 |
} |
081 |
|
082 |
void C3DSLoader::LoadTexture( char * filename, GLuint textureArray[], GLuint textureID) |
083 |
{ |
084 |
if (!filename) |
085 |
return ; |
086 |
|
087 |
if (! this ->m_BMPTexture.LoadBitmap(filename)) |
088 |
{ |
089 |
QMessageBox::warning(0,QObject::tr( "Load Bitmap Error" ),QObject::tr( "Load Bitmap Error" )); |
090 |
return ; |
091 |
} |
092 |
|
093 |
glGenTextures(1,&m_textures[textureID]); |
094 |
|
095 |
glPixelStorei (GL_UNPACK_ALIGNMENT, 1); |
096 |
glBindTexture(GL_TEXTURE_2D, m_textures[textureID]); |
097 |
|
098 |
/* 控制滤波 */ |
099 |
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); |
100 |
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR); |
101 |
|
102 |
/* 创建纹理 */ |
103 |
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, m_BMPTexture.imageWidth,m_BMPTexture.imageHeight, GL_RGB, |
104 |
GL_UNSIGNED_BYTE, this ->m_BMPTexture.image); |
105 |
|
106 |
} |
107 |
|
108 |
/* 载入3DS文件到模型结构中 */ |
109 |
bool C3DSLoader::Import3DS(t3DModel *pModel, char *strFileName) |
110 |
{ |
111 |
char strMessage[255] = {0}; |
112 |
|
113 |
/* 打开一个3ds文件 */ |
114 |
m_FilePointer = fopen (strFileName, "rb" ); |
115 |
|
116 |
/* 检查文件指针 */ |
117 |
if (!m_FilePointer) |
118 |
{ |
119 |
sprintf (strMessage, "Cann't find the file: %s!" , strFileName); |
120 |
QMessageBox::information(NULL, |
121 |
"Error" , |
122 |
strMessage, |
123 |
QMessageBox::Yes | QMessageBox::No, |
124 |
QMessageBox::Yes); |
125 |
return false ; |
126 |
} |
127 |
|
128 |
/* 将文件的第一块读出并判断是否是3ds文件 */ |
129 |
ReadChunk(m_CurrentChunk); |
130 |
|
131 |
/* 确保是3ds文件 */ |
132 |
if (m_CurrentChunk->ID != PRIMARY) |
133 |
{ |
134 |
QMessageBox::information(NULL, |
135 |
"Error" , |
136 |
"Cann't Loading the Function" , |
137 |
QMessageBox::Yes | QMessageBox::No, |
138 |
QMessageBox::Yes); |
139 |
return false ; |
140 |
} |
141 |
|
142 |
/* 递归读出对象数据 */ |
143 |
ReadNextChunk(pModel, m_CurrentChunk); |
144 |
|
145 |
/* 计算顶点的法线 */ |
146 |
ComputeNormals(pModel); |
147 |
|
148 |
/* 释放内存空间 */ |
149 |
CleanUp(); |
150 |
|
151 |
return true ; |
152 |
} |
153 |
|
154 |
/* 读入一个字符串 */ |
155 |
int C3DSLoader::GetString( char *pBuffer) |
156 |
{ |
157 |
int index = 0; |
158 |
|
159 |
/* 读入一个字节的数据 */ |
160 |
fread (pBuffer, 1, 1, m_FilePointer); |
161 |
|
162 |
/* 直到结束 */ |
163 |
while (*(pBuffer + index++) != 0) |
164 |
{ |
165 |
/* 读入一个字符直到NULL */ |
166 |
fread (pBuffer + index, 1, 1, m_FilePointer); |
167 |
} |
168 |
|
169 |
/* 返回字符串的长度 */ |
170 |
return strlen (pBuffer) + 1; |
171 |
} |
172 |
|
173 |
/* 读入块的ID号和它的字节长度 */ |
174 |
void C3DSLoader::ReadChunk(tChunk *pChunk) |
175 |
{ |
176 |
/* 读入块的ID号 */ |
177 |
pChunk->bytesRead = fread (&pChunk->ID, 1, 2, m_FilePointer); |
178 |
|
179 |
/* 读入块占用的长度 */ |
180 |
pChunk->bytesRead += fread (&pChunk->length, 1, 4, m_FilePointer); |
181 |
|
182 |
} |
183 |
|
184 |
/* 读出3ds文件的主要部分 */ |
185 |
void C3DSLoader::ReadNextChunk(t3DModel *pModel, tChunk *pPreChunk) |
186 |
{ |
187 |
t3DObject newObject = {0}; /* 用来添加到对象链表 */ |
188 |
tMatInfo newTexture = {0}; /* 用来添加到材质链表 */ |
189 |
unsigned int version = 0; /* 保存文件版本 */ |
190 |
int buffer[50000] = {0}; /* 用来跳过不需要的数据 */ |
191 |
m_CurrentChunk = new tChunk; /* 为新的块分配空间 */ |
192 |
|
193 |
/* 继续读入子块 */ |
194 |
while (pPreChunk->bytesRead < pPreChunk->length) |
195 |
{ |
196 |
/* 读入下一个块 */ |
197 |
ReadChunk(m_CurrentChunk); |
198 |
|
199 |
/* 判断块的ID号 */ |
200 |
switch (m_CurrentChunk->ID) |
201 |
{ |
202 |
|
203 |
/* 文件版本号 */ |
204 |
case VERSION: |
205 |
|
206 |
/* 读入文件的版本号 */ |
207 |
m_CurrentChunk->bytesRead += fread (&version, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
208 |
|
209 |
/* 如果文件版本号大于3,给出一个警告信息 */ |
210 |
if (version > 0x03) |
211 |
QMessageBox::information(NULL, |
212 |
"Warning" , |
213 |
"The 3DS File Version is wrong" , |
214 |
QMessageBox::Yes | QMessageBox::No, |
215 |
QMessageBox::Yes); |
216 |
break ; |
217 |
|
218 |
/* 网格版本信息 */ |
219 |
case OBJECTINFO: |
220 |
|
221 |
/* 读入下一个块 */ |
222 |
ReadChunk(m_TempChunk); |
223 |
|
224 |
/* 获得网格的版本号 */ |
225 |
m_TempChunk->bytesRead += fread (&version, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer); |
226 |
|
227 |
/* 增加读入的字节数 */ |
228 |
m_CurrentChunk->bytesRead += m_TempChunk->bytesRead; |
229 |
|
230 |
/* 进入下一个块 */ |
231 |
ReadNextChunk(pModel, m_CurrentChunk); |
232 |
break ; |
233 |
|
234 |
/* 材质信息 */ |
235 |
case MATERIAL: |
236 |
|
237 |
/* 材质的数目递增 */ |
238 |
pModel->numOfMaterials++; |
239 |
|
240 |
/* 在纹理链表中添加一个空白纹理结构 */ |
241 |
pModel->pMaterials.push_back(newTexture); |
242 |
|
243 |
/* 进入材质装入函数 */ |
244 |
ReadNextMatChunk(pModel, m_CurrentChunk); |
245 |
break ; |
246 |
|
247 |
/* 对象名称 */ |
248 |
case OBJECT: |
249 |
|
250 |
/* 对象数递增 */ |
251 |
pModel->numOfObjects++; |
252 |
|
253 |
/* 添加一个新的tObject节点到对象链表中 */ |
254 |
pModel->pObject.push_back(newObject); |
255 |
|
256 |
/* 初始化对象和它的所有数据成员 */ |
257 |
memset (&(pModel->pObject[pModel->numOfObjects - 1]), 0, sizeof (t3DObject)); |
258 |
|
259 |
/* 获得并保存对象的名称,然后增加读入的字节数 */ |
260 |
m_CurrentChunk->bytesRead += GetString(pModel->pObject[pModel->numOfObjects - 1].strName); |
261 |
|
262 |
/* 进入其余的对象信息的读入 */ |
263 |
ReadNextObjChunk(pModel, &(pModel->pObject[pModel->numOfObjects - 1]), m_CurrentChunk); |
264 |
break ; |
265 |
|
266 |
/* 关键帧 */ |
267 |
case EDITKEYFRAME: |
268 |
|
269 |
/* 跳过关键帧块的读入 */ |
270 |
m_CurrentChunk->bytesRead += fread (buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
271 |
break ; |
272 |
|
273 |
default : |
274 |
/* 跳过所有忽略的块的内容的读入 */ |
275 |
m_CurrentChunk->bytesRead += fread (buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
276 |
break ; |
277 |
} |
278 |
|
279 |
/* 增加从最后块读入的字节数 */ |
280 |
pPreChunk->bytesRead += m_CurrentChunk->bytesRead; |
281 |
} |
282 |
|
283 |
/* 释放当前块的内存空间 */ |
284 |
delete m_CurrentChunk; |
285 |
m_CurrentChunk = pPreChunk; |
286 |
} |
287 |
|
288 |
/* 处理所有的文件中对象的信息 */ |
289 |
void C3DSLoader::ReadNextObjChunk(t3DModel *pModel, t3DObject *pObject, tChunk *pPreChunk) |
290 |
{ |
291 |
/* 用于读入不需要的数据 */ |
292 |
int buffer[50000] = {0}; |
293 |
|
294 |
/* 对新的块分配存储空间 */ |
295 |
m_CurrentChunk = new tChunk; |
296 |
|
297 |
/* 继续读入块的内容直至本子块结束 */ |
298 |
while (pPreChunk->bytesRead < pPreChunk->length) |
299 |
{ |
300 |
/* 读入下一个块 */ |
301 |
ReadChunk(m_CurrentChunk); |
302 |
|
303 |
/* 区别读入是哪种块 */ |
304 |
switch (m_CurrentChunk->ID) |
305 |
{ |
306 |
/* 正读入的是一个新块 */ |
307 |
case OBJ_MESH: |
308 |
/* 使用递归函数调用,处理该新块 */ |
309 |
ReadNextObjChunk(pModel, pObject, m_CurrentChunk); |
310 |
break ; |
311 |
|
312 |
/* 读入是对象顶点 */ |
313 |
case OBJ_VERTICES: |
314 |
ReadVertices(pObject, m_CurrentChunk); |
315 |
break ; |
316 |
|
317 |
/* 读入的是对象的面 */ |
318 |
case OBJ_FACES: |
319 |
ReadVertexIndices(pObject, m_CurrentChunk); |
320 |
break ; |
321 |
|
322 |
/* 读入的是对象的材质名称 */ |
323 |
case OBJ_MATERIAL: |
324 |
/* 读入对象的材质名称 */ |
325 |
ReadObjMat(pModel, pObject, m_CurrentChunk); |
326 |
break ; |
327 |
|
328 |
/* 读入对象的UV纹理坐标 */ |
329 |
case OBJ_UV: |
330 |
/* 读入对象的UV纹理坐标 */ |
331 |
ReadUVCoordinates(pObject, m_CurrentChunk); |
332 |
break ; |
333 |
|
334 |
default : |
335 |
/* 略过不需要读入的块 */ |
336 |
m_CurrentChunk->bytesRead += fread (buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
337 |
break ; |
338 |
} |
339 |
/* 添加从最后块中读入的字节数到前面的读入的字节中 */ |
340 |
pPreChunk->bytesRead += m_CurrentChunk->bytesRead; |
341 |
} |
342 |
/* 释放当前块的内存空间,并把当前块设置为前面块 */ |
343 |
delete m_CurrentChunk; |
344 |
m_CurrentChunk = pPreChunk; |
345 |
} |
346 |
|
347 |
/* 处理所有的材质信息 */ |
348 |
void C3DSLoader::ReadNextMatChunk(t3DModel *pModel, tChunk *pPreChunk) |
349 |
{ |
350 |
/* 用于读入不需要的数据 */ |
351 |
int buffer[50000] = {0}; |
352 |
|
353 |
/* 给当前块分配存储空间 */ |
354 |
m_CurrentChunk = new tChunk; |
355 |
|
356 |
/* 继续读入这些块 */ |
357 |
while (pPreChunk->bytesRead < pPreChunk->length) |
358 |
{ |
359 |
/* 读入下一块 */ |
360 |
ReadChunk(m_CurrentChunk); |
361 |
/* 判断读入的是什么块 */ |
362 |
switch (m_CurrentChunk->ID) |
363 |
{ |
364 |
case MATNAME: /* 材质的名称 */ |
365 |
/* 读入材质的名称 */ |
366 |
m_CurrentChunk->bytesRead += fread (pModel->pMaterials[pModel->numOfMaterials - 1].strName, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
367 |
break ; |
368 |
case MATDIFFUSE: /* 对象的R G B颜色 */ |
369 |
ReadColor(&(pModel->pMaterials[pModel->numOfMaterials - 1]), m_CurrentChunk); |
370 |
break ; |
371 |
case MATMAP: /* 纹理信息的头部 */ |
372 |
/* 下一个材质块信息 */ |
373 |
ReadNextMatChunk(pModel, m_CurrentChunk); |
374 |
break ; |
375 |
case MATMAPFILE: /* 材质文件的名称 */ |
376 |
/* 读入材质的文件名称 */ |
377 |
m_CurrentChunk->bytesRead += fread (pModel->pMaterials[pModel->numOfMaterials - 1].strFile, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
378 |
break ; |
379 |
default : |
380 |
/* 跳过不需要读入的块 */ |
381 |
m_CurrentChunk->bytesRead += fread (buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); |
382 |
break ; |
383 |
} |
384 |
/* 添加从最后块中读入的字节数 */ |
385 |
pPreChunk->bytesRead += m_CurrentChunk->bytesRead; |
386 |
} |
387 |
/* 删除当前块,并将当前块设置为前面的块 */ |
388 |
delete m_CurrentChunk; |
389 |
m_CurrentChunk = pPreChunk; |
390 |
} |
391 |
|
392 |
/* 读入RGB颜色 */ |
393 |
void C3DSLoader::ReadColor(tMatInfo *pMaterial, tChunk *pChunk) |
394 |
{ |
395 |
/* 读入颜色块信息 */ |
396 |
ReadChunk(m_TempChunk); |
397 |
/* 读入RGB颜色 */ |
398 |
m_TempChunk->bytesRead += fread (pMaterial->color, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer); |
399 |
/* 增加读入的字节数 */ |
400 |
pChunk->bytesRead += m_TempChunk->bytesRead; |
401 |
} |
402 |
|
403 |
/* 读入顶点索引 */ |
404 |
void C3DSLoader::ReadVertexIndices(t3DObject *pObject, tChunk *pPreChunk) |
405 |
{ |
406 |
unsigned short index = 0; /* 用于读入当前面的索引 */ |
407 |
/* 读入该对象中面的数目 */ |
408 |
pPreChunk->bytesRead += fread (&pObject->numOfFaces, 1, 2, m_FilePointer); |
409 |
/* 分配所有面的存储空间,并初始化结构 */ |
410 |
pObject->pFaces = new tFace [pObject->numOfFaces]; |
411 |
memset (pObject->pFaces, 0, sizeof (tFace) * pObject->numOfFaces); |
412 |
/* 遍历对象中所有的面 */ |
413 |
for ( int i = 0; i < pObject->numOfFaces; i++) |
414 |
{ for ( int j = 0; j < 4; j++) { /* 读入当前面的第一个点 */ pPreChunk->bytesRead += fread (&index, 1, sizeof (index), m_FilePointer); |
415 |
if (j < 3) { /* 将索引保存在面的结构中 */ pObject->pFaces[i].vertIndex[j] = index; |
416 |
} |
417 |
} |
418 |
} |
419 |
} |
420 |
|
421 |
/* 读入对象的UV坐标 */ |
422 |
void C3DSLoader::ReadUVCoordinates(t3DObject *pObject, tChunk *pPreChunk) |
423 |
{ |
424 |
/* 读入UV坐标的数量 */ |
425 |
pPreChunk->bytesRead += fread (&pObject->numTexVertex, 1, 2, m_FilePointer); |
426 |
|
427 |
/* 分配保存UV坐标的内存空间 */ |
428 |
pObject->pTexVerts = new Vector2[pObject->numTexVertex]; |
429 |
|
430 |
/* 读入纹理坐标 */ |
431 |
pPreChunk->bytesRead += fread (pObject->pTexVerts, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer); |
432 |
} |
433 |
|
434 |
/* 读入对象的顶点 */ |
435 |
void C3DSLoader::ReadVertices(t3DObject *pObject, tChunk *pPreChunk) |
436 |
{ |
437 |
/* 读入顶点的数目 */ |
438 |
pPreChunk->bytesRead += fread (&(pObject->numOfVerts), 1, 2, m_FilePointer); |
439 |
|
440 |
/* 分配顶点的存储空间,然后初始化结构体 */ |
441 |
pObject->pVerts = new Vector3 [pObject->numOfVerts]; |
442 |
memset (pObject->pVerts, 0, sizeof (Vector3) * pObject->numOfVerts); |
443 |
|
444 |
/* 读入顶点序列 */ |
445 |
pPreChunk->bytesRead += fread (pObject->pVerts, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer); |
446 |
|
447 |
/* 遍历所有的顶点将Y轴和Z轴交换,然后将Z轴反向 */ |
448 |
for ( int i = 0; i < pObject->numOfVerts; i++) |
449 |
{ |
450 |
/* 保存Y轴的值 */ |
451 |
float fTempY = pObject->pVerts[i].y; |
452 |
/* 设置Y轴的值等于Z轴的值 */ |
453 |
pObject->pVerts[i].y = pObject->pVerts[i].z; |
454 |
/* 设置Z轴的值等于-Y轴的值 */ |
455 |
pObject->pVerts[i].z = -fTempY; |
456 |
} |
457 |
} |
458 |
|
459 |
/* 读入对象的材质名称 */ |
460 |
void C3DSLoader::ReadObjMat(t3DModel *pModel, t3DObject *pObject, tChunk *pPreChunk) |
461 |
{ |
462 |
char strMaterial[255] = {0}; /* 用来保存对象的材质名称 */ |
463 |
int buffer[50000] = {0}; /* 用来读入不需要的数据 */ |
464 |
|
465 |
/* 读入赋予当前对象的材质名称 */ |
466 |
pPreChunk->bytesRead += GetString(strMaterial); |
467 |
|
468 |
/* 遍历所有的纹理 */ |
469 |
for ( int i = 0; i < pModel->numOfMaterials; i++) |
470 |
{ |
471 |
/* 如果读入的纹理与当前的纹理名称匹配 */ |
472 |
if ( strcmp (strMaterial, pModel->pMaterials[i].strName) == 0) |
473 |
{ |
474 |
/* 设置材质ID */ |
475 |
pObject->materialID = i; |
476 |
|
477 |
/* 判断是否是纹理映射 */ |
478 |
if ( strlen (pModel->pMaterials[i].strFile) > 0) { |
479 |
|
480 |
/* 设置对象的纹理映射标志 */ |
481 |
pObject->bHasTexture = true ; |
482 |
} |
483 |
break ; |
484 |
} |
485 |
else |
486 |
{ |
487 |
/* 如果该对象没有材质,则设置ID为-1 */ |
488 |
pObject->materialID = -1; |
489 |
} |
490 |
} |
491 |
pPreChunk->bytesRead += fread (buffer, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer); |
492 |
} |
493 |
|
494 |
/* 计算对象的法向量 */ |
495 |
void C3DSLoader::ComputeNormals(t3DModel *pModel) |
496 |
{ |
497 |
Vector3 vVector1, vVector2, vNormal, vPoly[3]; |
498 |
|
499 |
/* 如果模型中没有对象,则返回 */ |
500 |
if (pModel->numOfObjects <= 0) |
501 |
return ; |
502 |
|
503 |
/* 遍历模型中所有的对象 */ |
504 |
for ( int index = 0; index < pModel->numOfObjects; index++) |
505 |
{ |
506 |
/* 获得当前的对象 */ |
507 |
t3DObject *pObject = &(pModel->pObject[index]); |
508 |
|
509 |
/* 分配需要的存储空间 */ |
510 |
Vector3 *pNormals = new Vector3 [pObject->numOfFaces]; |
511 |
Vector3 *pTempNormals = new Vector3 [pObject->numOfFaces]; |
512 |
pObject->pNormals = new Vector3 [pObject->numOfVerts]; |
513 |
|
514 |
/* 遍历对象的所有面 */ |
515 |
for ( int i=0; i < pObject->numOfFaces; i++) |
516 |
{ vPoly[0] = pObject->pVerts[pObject->pFaces[i].vertIndex[0]]; |
517 |
vPoly[1] = pObject->pVerts[pObject->pFaces[i].vertIndex[1]]; |
518 |
vPoly[2] = pObject->pVerts[pObject->pFaces[i].vertIndex[2]]; |
519 |
|
520 |
/* 计算面的法向量 */ |
521 |
vVector1 = vPoly[0] - vPoly[2]; /* 获得多边形的矢量 */ |
522 |
vVector2 = vPoly[2] - vPoly[1]; /* 获得多边形的第二个矢量 */ |
523 |
vNormal = vVector1.crossProduct(vVector2); /* 计算两个矢量的叉积 */ |
524 |
pTempNormals[i] = vNormal; |
525 |
vNormal = vNormal.normalize(); /* 规一化叉积 */ |
526 |
pNormals[i] = vNormal; /* 将法向量添加到法向量列表中 */ |
527 |
} |
528 |
|
529 |
/* 计算顶点法向量 */ |
530 |
Vector3 vSum(0.0,0.0,0.0); |
531 |
Vector3 vZero = vSum; |
532 |
int shared=0; |
533 |
|
534 |
/* 遍历所有的顶点 */ |
535 |
for ( int i = 0; i < pObject->numOfVerts; i++) |
536 |
{ for ( int j = 0; j < pObject->numOfFaces; j++) /* 遍历所有的三角形面 */ |
537 |
{ /* 判断该点是否与其它的面共享 */ |
538 |
if (pObject->pFaces[j].vertIndex[0] == i || |
539 |
pObject->pFaces[j].vertIndex[1] == i || |
540 |
pObject->pFaces[j].vertIndex[2] == i) |
541 |
{ |
542 |
vSum = vSum + pTempNormals[j]; |
543 |
shared++; |
544 |
} |
545 |
} |
546 |
pObject->pNormals[i] = vSum / float (-shared); |
547 |
|
548 |
/* 规一化顶点法向 */ |
549 |
pObject->pNormals[i] = pObject->pNormals[i].normalize(); |
550 |
vSum = vZero; |
551 |
shared = 0; |
552 |
} |
553 |
/* 释放存储空间,开始下一个对象 */ |
554 |
delete [] pTempNormals; |
555 |
delete [] pNormals; |
556 |
} |
557 |
} |
使用方法:
1 |
//使用很简单,声明对象 |
2 |
C3DSLoader m_3DS; |
3 |
//初始化 |
4 |
m_3DS.Init( "d:/plane.3ds" ); |
5 |
//绘制模型 |
6 |
m_3DS.Draw(); |
附上贴图:
http://www.beyondc.cn/opengl-3ds-model-of-reading-qt-version.html