Opengl是行业领域中最为广泛接纳的2D/3D图形API。通过一些转换程序可以很方便地将3DS/3DSMAX等3D图形设计软件制作的3DS模型文件转换成OpenGL顶点数组。
在这里我将两者结合起来实现了3DS格式模型的导入。其实,在网上有很多类似的源码或者demo。但是把Qt和OpenGL结合起来运用的例子其实并不多,在这篇文章,我将仔细教大家如何将漂亮的3D模型放到程序中演示(有不足的地方希望大家多多指正,我的代码参考了OpenGL游戏编程和学OpenGL编3D游戏)。
3ds文件结构是由“块”组成的。它们描述了接在它们后面的数据的信息,即这些数据是如何组成的。“块”是由两部分组成的:1.ID;2.下一个数据块的位置。也就是说,如果你不明白这个块的用处,你可以迅速地跳过它。因为下一个数据区的相对位置(字节数)已经得到了。二进制的3ds文件是用一种特殊的方法写成的:也就是低字节在前,高字节在后(译者:二进制文件都是这样的)。3ds.h里面的一些define主要是对基本块,主块,对象的纹理、面、顶点信息,材质的定义进行解释。
// 基本块(Primary Chunk),位于文件的开始
#define PRIMARY 0x4D4D
// 主块(Main Chunks)
#define OBJECTINFO 0x3D3D// 网格对象的版本号
#define VERSION 0x0002// .3ds文件的版本
#define EDITKEYFRAME 0xB000// 所有关键帧信息的头部
// 对象的次级定义(包括对象的材质和对象)
#define MATERIAL 0xAFFF// 保存纹理信息
#define OBJECT 0x4000// 保存对象的面、顶点等信息
// 材质的次级定义
#define MATNAME 0xA000// 保存材质名称
#define MATDIFFUSE 0xA020// 对象/材质的颜色
#define MATMAP 0xA200// 新材质的头部
#define MATMAPFILE 0xA300// 保存纹理的文件名
#define OBJ_MESH 0x4100// 新的网格对象
#define MAX_TEXTURES 100// 最大的纹理数目
// OBJ_MESH的次级定义
#define OBJ_VERTICES 0x4110// 对象顶点
#define OBJ_FACES 0x4120// 对象的面
#define OBJ_MATERIAL 0x4130// 对象的材质
#define OBJ_UV 0x4140// 对象的UV纹理坐标
这是3ds.h文件的主要信息。
class CVector3//定义3D点的类,用于保存模型中的顶点
{public:float x, y, z;
};
class CVector2//定义2D点类,用于保存模型的UV纹理坐标
{public:float x, y;
};
struct tFace//面的结构定义
{int vertIndex[3];// 顶点索引
int coordIndex[3];// 纹理坐标索引
};
struct tMatInfo//材质信息结构体
{char strName[255];// 纹理名称
char strFile[255];// 如果存在纹理映射,则表示纹理文件名称
BYTE color[3];// 对象的RGB颜色
int texureId;// 纹理ID
float uTile;// u 重复
float vTile;// v 重复
float uOffset; // u 纹理偏移
float vOffset;// v 纹理偏移
} ;
struct t3DObject//对象信息结构体
{int numOfVerts;// 模型中顶点的数目
int numOfFaces;// 模型中面的数目
int numTexVertex;// 模型中纹理坐标的数目
int materialID;// 纹理ID
bool bHasTexture;// 是否具有纹理映射
char strName[255];// 对象的名称
CVector3 *pVerts;// 对象的顶点
CVector3 *pNormals;// 对象的法向量
CVector2 *pTexVerts;// 纹理UV坐标
tFace *pFaces;// 对象的面信息
};
struct t3DModel//模型信息结构体
{int numOfObjects;// 模型中对象的数目
int numOfMaterials;// 模型中材质的数目
vector<tMatInfo>pMaterials;// 材质链表信息
vector<t3DObject> pObject;// 模型中对象链表信息
};
struct tChunk//保存块信息的结构
{unsigned short int ID;// 块的ID
unsigned int length;// 块的长度
unsigned int bytesRead;// 需要读的块数据的字节数
};
之后又定义了CLoad3DS类处理所有的装入代码
class CLoad3DS// CLoad3DS类处理所有的装入代码
{
public:
CLoad3DS();// 初始化数据成员
virtual ~CLoad3DS();
void show3ds(int j0,float tx,float ty,float tz,float size);//显示3ds模型
void Init(char *filename,int j);
private:
bool Import3DS(t3DModel *pModel, char *strFileName);// 装入3ds文件到模型结构中
void CreateTexture(UINT textureArray[], LPSTR strFileName, int textureID);// 从文件中创建纹理
int GetString(char *);// 读一个字符串
void ReadChunk(tChunk *);// 读下一个块
void ReadNextChunk(t3DModel *pModel, tChunk *);// 读下一个块
void ReadNextObjChunk(t3DModel *pModel,t3DObject *pObject,tChunk *);// 读下一个对象块
void ReadNextMatChunk(t3DModel *pModel, tChunk *);// 读下一个材质块
void ReadColor(tMatInfo *pMaterial, tChunk *pChunk);// 读对象颜色的RGB值
void ReadVertices(t3DObject *pObject, tChunk *);// 读对象的顶点
void ReadVertexIndices(t3DObject *pObject,tChunk *);// 读对象的面信息
void ReadUVCoordinates(t3DObject *pObject,tChunk *);// 读对象的纹理坐标
void ReadObjMat(t3DModel *pModel,t3DObject *pObject,tChunk *pPreChunk);// 读赋予对象的材质名称
void ComputeNormals(t3DModel *pModel);// 计算对象顶点的法向量
void CleanUp();// 关闭文件,释放内存空间
FILE*m_FilePointer;// 文件指针
tChunk*m_CurrentChunk;
tChunk*m_TempChunk;
};
具体的函数实现都在3ds.cpp中。
在这里,我想向大家解释一下大家从其他论坛或网站下载下来的程序无法导入3DS模型文件的主要原因:
1.程序解析3DS结构错误,确认摄像机的位置是否设置正确(建议在3dsMax建模的时候,将模型的中心点设置在坐标系中心点附近)
2.3DS模型附带的图片不符合导入的规则。(切记3ds文件的路径要设置正确,附带的图片放到同目录下)
3.确认模型是否被缩放或者放大了,也就是说你导入的模型可能已经导入了,只不过你没看到。。
http://pan.baidu.com/s/1ntFMou1这是导入类的源码。
这是我导入的家具模型图片,导入后很是兴奋大笑。希望能和大家多多交流。是时候为下一个目标3d模型拾取进发了。。