这是我寒假的一个作业吧,到最后基本是在学校完成的了。这个程序主要完成的功能是读取.obj文件,顺便读取了其附带.mtl文件,以及.mtl文件附带的.tga格式的纹理(我没有处理其它格式了,因为自己下载的几个模型的纹理都是这种图片格式)。然后,用透视投影,调整视点对准模型正中央,渲染在视口里面。程序也提供了键盘上下左右旋转,上下左右移动,放大放小的功能,已经鼠标旋转模型的功能,不过鼠标旋转的效果不好。
首先要介绍下obj模型文件的格式,这个百度或者google肯定能找到不少文章。.obj文件里面主要是定义了点,线,面,当然还描述了其他元素。.mtl文件里面定义了模型的材质以及纹理的路径。我主要描述下我的代码用到的和假定的obj文件格式。
v 1.0 2.3 4.5 //代表一个三维点
v 1.0 2.3 4.5 //代表一个三维点
v 1.0 2.3 4.5 //代表一个三维点
v 1.0 2.3 4.5 //代表一个三维点
vn 1.0 1.0 1.0 //法线
vt 0.3 0.4 //纹理坐标
p 1 //第一个v代表的点
l 1 2 //第一个v和第二个v连接的直线
f 1// 2// 3// 4// //1,2,3,4构成的面,没有法线和纹理
f 1/1/1 2/1/1 3/1/1 4/1/1 //1,2,3,4构成的面,结构是顶点索引,纹理索引,法线索引
我的代码基本上假定没有法线和纹理索引的时候,后面也必须带//。
另外一个最重要的是定义材质库的路径,mtllib xxx.mtl。
至于obj文件更加详细的描述,请查阅更标准的文档吧。
然后,介绍下.mtl的格式,我也只介绍我用到的东西。
比如下面这个材质文件的内容,
newmtl Arakkoa_Gray //定义材质设置的名字,一个材质库可能会出现多个材质设置
d 1.0 //透明度
Kd 1.000000 1.000000 1.000000 //模型散射颜色
Ka 0.250000 0.250000 0.250000 //模型环境颜色
Ks 0.000000 0.000000 0.000000//模型镜面颜色
Ke 0.000000 0.000000 0.000000//模型发射颜色
Ns 0.000000//模型镜面指数
map_Kd Arakkoa_Gray.tga//纹理路径
其它设置我就没有用到了。
至于tga图片的格式,请自行google吧。我只处理了未压缩版本的.tga纹理,格式类似于.bmp文件,处理起来也比较简单。
以下是我渲染一个魔兽世界模型的效果,
基本上启用光照加上纹理之后的渲染效果还行。下面我来讲述我整个代码的结构吧。
说实话,这个程序虽然功能不多,但是也可以写出不少代码来的,虽然没多少啰嗦代码,也有近千行。我也定义了不少结构体和类。如下图,
S开头的是结构体,主要定义了顶点,法线,纹理的数据,以及三者的索引(SVertexData),rgb,Tga文件头。其余的,比如CPoint,CLine,CFace对应obj文件里面的p,l,f元素,这三个类里面有各自的渲染函数。
CObj类最复杂,里面有顶点,法线,纹理坐标的数据集合,为了方便,我使用了vector,同时用reserve预留空间。还有,点集合,线集合,面集合,材质设置的集合。CObj的渲染函数则是调用点,线,面集合里面的元素对应的渲染函数。其中,最复杂的是面的渲染函数。面的渲染函数会根据材质设置是否改变来更换材质,然后再渲染每个面,同时每个面里面也有个vector集合包含顶点等的索引,所以我的程序不仅仅支持三角形面。CObj类里面最复杂的是读取.obj模型的函数,函数写了100多行,我也没进行功能拆分了,不细说了,至于读取材质的相对简单点。
CMaterial就描述了我使用的集中材质设置,包括一个CTgaTex类成员等。基本上是在读取的时候初始化下材质,更换材质的时候改变下。CTgaTex主要是读取未压缩的tga文件,并且用读取的数据初始化opengl的纹理设置,更改纹理设置的时候,CMaterial会调用其更改纹理。
CBox类也比较复杂,因为该类关系到透视投影,视点等。我是这样考虑的,我读取模型的时候,确定包括整个模型顶点的最小长方体,存储在CBox里面。然后,CBox会求出该长方体的外接球,我再把操作的对象改变成该外接球,也就是我的透视投影啊,观察点设置,平移,放缩,旋转什么的都针对该外接球了。所以啊,我对模型操作的函数全部放置在CBox类里面了。我的透视函数是调用的gluPerspective,近平面是球的前切面,远平面是球的后切面,我的观察点是球心,视点是正对球心离开其4-10倍半径距离的点。
至于其余全局函数的实现就不介绍了。
给出一些关键的类定义,看了这些类定义,就知道我的代码结构了。
//obj的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| #ifndef _XPC_YX_OBJ_H #define _XPC_YX_OBJ_H #include "vertex.h" #include "mtl.h" #include using std::vector;
class CPoint { friend class CObj; public: void Render(CObj obj);
private: int m_nVI; };
class CLine { friend class CObj; public: void Render(CObj obj);
private: vector m_vis; };
class CFace { friend class CObj; public: CFace() { m_nNewMtlI = -1; } void Render(CObj obj);
private: vector m_vds; int m_nNewMtlI; };
class CBox;
class CObj { friend class CPoint; friend class CLine; friend class CFace;
enum {VERTEX = 0, TEXTURE = 1, NORMAL = 2}; public: CObj(CBox* pBox) { m_pBox = pBox; } CObj(const char* pcszFileName) { LoadFromFile(pcszFileName); } int LoadFromFile(const char* pcszFileName); int LoadMaterials(const char* pcszFileName); void Render();
private: static const int VERTEX_INIT_NUM; static const int POINT_INIT_NUM; static const int LINE_INIT_NUM; static const int FACE_INIT_NUM; vector m_vs; vector m_vts; vector m_vns; vector m_ps; vector m_ls; vector m_fs; vector m_ms; CBox* m_pBox; }; #endif
|
//CBox的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class CBox { public: void Init() { m_fXMin = m_fYMin = m_fZMin = INT_MAX; m_fXMax = m_fYMax = m_fZMax = INT_MIN; } void InitBoxInfo(); void Adjust(SVertex v) { Adjust(v.fX, v.fY, v.fZ); } void Adjust(double fX, double fY, double fZ); void LookAtBox(int nW, int nH); void Big(int nPercent); void Small(int nPercent); void Left(int nPercent); void Right(int nPercent); void Up(int nPercent); void Down(int nPercent); bool IsInBox(SVertex vertex); void Move(SVertex one, SVertex two); void Rotate(SVertex one, SVertex two); void RotateLeft(double fTheta); void RotateRight(double fTheta); void RotateUp(double fTheta); void RotateDown(double fTheta); void SetLight();
private: double m_fXMin; double m_fXMax; double m_fYMin; double m_fYMax; double m_fZMin; double m_fZMax; SVertex m_center; double m_fRadius; double m_fDistance; double m_fTheta; };
|
//材质类的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #ifndef _XPC_YX_MTL_H #define _XPC_YX_MTL_H
#include "tga.h" #include <windows.h>
class CObj;
struct SRgb { float fR; float fG; float fB; float fA; };
class CMaterial { friend class CObj; public: CMaterial() { m_emission.fR = m_emission.fG = m_emission.fB = 0.0; m_fTrans = 1.0; } void InitTex(); void Set();
private: SRgb m_ambient; SRgb m_diffuse; SRgb m_specular; SRgb m_emission; float m_fShiness; float m_fTrans; char m_szName[MAX_PATH]; char m_szTextureName[MAX_PATH]; CTgaTex m_tgaTex; }; #endif
|
//tga纹理的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #ifndef _XPC_YX_TGA_H #define _XPC_YX_TGA_H #include <windows.h>
#define RGB16(r,g,b) ( ((r>>3) << 10) + ((g>>3) << 5) + (b >> 3) ) #define RGB24(r,g,b) ( ((r) << 16) + ((g) << 8) + (b) )
typedef unsigned char BYTE;
struct STgaHeader { BYTE byImageInfoByteCnt; BYTE byColorTableExist; BYTE byImageType; BYTE byColorTableInfo[5]; unsigned short uXOrigin; unsigned short uYOrigin; unsigned short uWidth; unsigned short uHeight; unsigned char chBitsPerPixel; unsigned char chImageDescriptor; };
class CTgaTex { public: CTgaTex() { byData = 0; m_szTexName[0] = 0; } CTgaTex(const char* pcszFileName) { byData = 0; LoadTga(pcszFileName); } ~CTgaTex() { if (byData) free(byData); } int LoadTga(const char* pcszFileName); void InitTex(); void SetTexture();
private: STgaHeader m_header; BYTE* byData; char m_szTexName[MAX_PATH]; unsigned m_uTexName; };
#endif
|
如果你需要完整的代码,可以留言或者给我发邮件,代码写得不怎么样,不过还是尽力写好一点的了。里面肯定会有让老手觉得不规范的地方的。
由于很多人留言询问代码,为了方便,已上传百度网盘,分享地址:http://pan.baidu.com/s/1eQJ9mF4 密码:ovnv