keil obj 文件 结构_OBJ模型文件的结构、导入与渲染Ⅰ

在[

先总体说一下两种格式的不同处。比起二进制文件为主、连每个块的用途也得试探来试探去的3DS,文本文件为主的OBJ对我们更友好。与3DS文件的树状

1. OBJ,从格式到读入

背景介绍一下吧,它的创始公司是

作为一种文本文件,什么文本查看器都能看,不像3DS那种乱麻麻的乱码。格式说明网上也有很详细的,这里随便找一篇写得挺好的:

#...(#是注释符)

mtllib pCube.mtl

g default

v -0.500000 -0.500000 0.500000

v 0.500000 -0.500000 0.500000

v 0.500000 -0.500000 -0.500000

....

vt 0.000000 0.000000

vt -1.000000 1.000000

.....

vn 0.000000 0.000000 1.000000

vn 0.000000 0.000000 -1.000000

.....

g pCube1

s off

usemtl initialShadingGroup

f 1/1/1 2/2/2 4/4/3

f 3/3/5 4/4/6 6/6/7

f 5/5/9 6/6/10 8/8/11 7/7/12

f 7/7/13 8/8/14 1/9/16

g pCube2

usemtl DefferShadingGroup

.....

整个OBJ文件可以分成三个部分:第一部分是文件前半部的“顶点数据”部分——指定了模型所用到的全部顶点(v)、顶点纹理坐标(vt)、顶点法线(vn)。(括号里是数据的行头标识)其中顶点(位置)数据是必须的,纹理坐标只在对应的物件处有纹理时才必须,法线也是非必须的但是要注意的,也许你还记得在[a.可能包含顶点法线信息而不需自行计算。

当然了如果文件中没有法线信息那还是同样要计算的,不然在OpenGL里渲染起来会悲剧,至于这项数据存在的理由你自己列举吧,但是起码是不再需要相邻面法线取平均这种粗糙的手段,嘛不过储存量大了些;文件的第二部分是后半部的"面数据",与3DS类似的是每个面(f)利用索引指示的顶点属性来表示,但是——

b.索引指向整个数据区,且连同纹理坐标和法线,一个顶点的属性可能需要3个索引。

c.一个面至少要3组顶点属性,但3组以上组成一个面(多边形而非三角形)也是可能的。

前者引入导入时的一堆麻烦事,后面讲解。后者表示如果我们要用3D渲染系统渲染(目前主流是三角面片化,连四角面片也要慢慢不太溶于显卡了),就要在导入时切割面片人为三角面化。其实除了面还可能有点线曲线曲面之类的,此时只能无视之。另外这里还有几个标识符,组(g)标识一个独立物件(也就是3DS文件里的Object概念),每个组有其自己的材质(usemtl指定),如果几个组想共用一种材质的话,只需要改改顺序好了,因为usemtl也类似状态机会一直作用于后面的组直到下一句usemtl。标识s绝对可以选择性无视因为随便找个OBJ文件看上去就知道是个光滑组的概念了,我们没必要理会;第三部分是上面没显示的,它不属于obj后缀的文件但是OBJ文件的一部分——mtl材质库文件。在前面指定材质usemtl后也就简单的一个名字,它代表的东西可以在obj文件附带的mtl文件中找到。这个mtl文件在obj文件中mtllib指定,上面一段,就是pCube.mtl了,注意要在使用材质前指定。相对来说,mtl文件的格式更加复杂:

newmtl InitialShadingGroup

illum 4

Kd 0.50 0.50 0.00

Ka 0.10 0.10 0.10

Tf 1.00 1.00 1.00

map_Kd -s 1 1 1 -o 0 0 0 -mm 0 1 desertHouse_details_color.tga

bump -bm desertHouse_details_normal.tga

Ni 1.00

Ks 0.00 0.00 0.00

map_Ks desertHouse_details_specular.tga

Ns 18.00

newmtl DefferShadingGroup

....

通常mtl文件和纹理文件要和联系的obj文件放在一起。看上面,这里newmtl指定新材质的名称,以供obj文件中对应查询,后面一列会是材质属性,全部属性实在太多了,详情请看这篇文章,很详细:d.Obj模型中各对象都可能包含多种纹理贴图类型

好了,格式解释好,就要开始解析(parsing)了。

首先是要定下导入数据的数据结构。以CLoad3DS类[

//模型信息结构体

typedef struct tag3DModel

{

bool bIsTextured; //是否使用纹理

std::vector tMatInfoVec; // 材质信息

std::vector t3DObjVec; // 模型中对象信息

}t3DModel;

其中材质数据体直接按照所需的材质属性据在mtl材质库中找到对应项,并以名称为标识。绝大多数情况下obj文件里要引用所有的材质(当然是不一定但是为了导入方便就这么假定好了),我的策略是当读obj文件读到mtllib标识的时候就马上打开对应的mtl文件把所有材质读入里面tMatInfoVec,再返回obj文件。当后面读到usemtl的时候再按名称查找tMatInfoVec。遇到纹理文件的时候,直接生成纹理ID并保存,另外每种纹理会有一个对应的nTexObjX*数据变量指示该纹理在使用过程中所在的纹理对象(毕竟同一材质的这些纹理基本是要多重贴图[MultiTexture]的),在读入的时候只给DiffuseTex默认GL_TEXTURE0,其他置0,这些值一般是需要的时候再由应用层去设置的,我们的导入类单纯生成所有纹理但只会在渲染的时候使用DiffuseTex(我对是否该在应用层指定的时候才生成其他对应的纹理,留个问号,毕竟实际场合下如果材质中有,我们多半是要用的,而延后生成纹理就很被动了。空间与效率的争夺啊。):

// 材质信息结构体

typedef struct tagMaterialInfo

{

char strName[MAX_NAME]; // 纹理名称

GLfloat crAmbient[4];

GLfloat crDiffuse[4];

GLfloat crSpecular[4];

GLfloat fShiness;

GLuint nDiffuseMap;

GLuint nSpecularMap;

GLuint nBumpMap;

GLuint TexObjDiffuseMap;

GLuint TexObjSpecularMap;

GLuint TexObjBumpMap;

}tMaterialInfo;

然后就是对象数据了——或者我应该在下篇中再详细结合导入过程讲,先给出数据结构一览:

// 对象信息结构体

typedef struct tag3DObject

{

int nMaterialID; // 纹理ID

bool bHasTexture; // 是否具有纹理映射

bool bHasNormal; // 是否具有法线

std::vector PosVerts; // 对象的顶点

std::vector Normals; // 对象的法向量

std::vector Texcoords; // 纹理UV坐标

std::vector Indexes; // 对象的顶点索引

unsigned int nNumIndexes; // 索引数目

GLuint nPosVBO;

GLuint nNormVBO;

GLuint nTexcoordVBO;

GLuint nIndexVBO;

}t3DObject;

纹理ID是用来指涉tMatInfoVec的,对象名字就免去了,减少储存。值得注意的是,我这里没有使用任何跟“面”有关的数据,但是导入过程是读“面”无误,这里头有个转化关系。不然什么是”OBJ文件优化了存储但劣化了读写“呢?——很明显我要动用VBO(顶点缓存对象,见【

2011-2-9

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值