GEMINIGHT 警告:您的浏览器不支持JavaScript将无法正常浏览!
Warning: Your browser does not support JavaScript!
📋注册(Register) | 📛登录(Login)
🎲

主站(Home) »  论坛(Forum)  » 程序编写(Program)
GEMINIGHT

自称:发贴器2号
等级:发贴器
帖子数:5174
积分:9589
阅读权限:99
OpenGL入门(十四):加载OBJ文件 1楼
Tags: OpenGL,OBJ

Tags引力关联贴
\N

neo6 \N

此处的OBJ文件可不是编译时生成的中间文件,
而是一种3D文件。这次我们要加载一个叫al.obj
的文件,里面是强盗Al (Al the gangster)。 \N

\N

我们要作一个天穹,一片草原,把强盗Al放在
草原上 (有点麦田捕手的感觉)。然后我们希望
我们可以在草原上走来走去欣赏风景。 \N

\N

有一点需要提醒:如果你的显卡没有OpenGL 硬件
加速的话,程序运行起来可能慢得无法忍受。一般
的显卡只有DirectX硬件加速,这跟OpenGL硬件加速
是两码事。现在支持OpenGL硬件加速的显卡还比较
少,而且即使有硬件加速也往往功能不全或者有不
少BUG,这也是OpenGL不如DirectX流行的原因之一。 \N

先来谈谈3D文件。3D文件的格式很多,下面给几个
图形文件格式的网站: \N

Graphics File Formats Home Page
Sculpter.org's 3D Format Page
http://www.wotsit.org/ \N

3D Exploration是一个不错的免费的3D Viewer和
Converter。Rhino3D是一个专业的3D Modeler,
它的网站上有evaluation version可以下载。POV
-Ray(一个免费的光线追踪软件)收集了很多关于
3D软件3D模型的连接,其中许多是免费的。
3D Cafe收集了很多免费的3D模型,其中大多数
是3DS格式的,可以用3D Exploration转成OBJ格
式,就可以在我们的程序中用了。 \N

我们用来加载OBJ文件的代码来自于Nate Robin
的教程( glm.h, glm.c )。OBJ文件本来可以既包含
多边形也可以包含曲线和曲面,但glm只能处理
OBJ文件中的多边形部分。但通常多边形对简
单的3D游戏已经够用了,而且网上下载的3D文
件大多数也只包含多边形。我写了一个很简单
OBJ Viewer,可以测试加载是否正确。 \N

1。OBJ文件 \N

OBJ文件中包含丰富的3D对象类型,而且文档
的说明很详细。因此熟悉OBJ文件的格式对于
理解其他3D文件的格式也是很有用的。 \N

下面我们举例说明OBJ文件的格式: \N

例1:square.obj \N

\N

v 0.000000 2.000000 0.000000
v 0.000000 0.000000 0.000000
v 2.000000 0.000000 0.000000
v 2.000000 2.000000 0.000000
f 1 2 3 4 \N

v 表示顶点坐标(vertex)。 \N

f 表示面(face),就是多边形。最后一行表示一个
有四个顶点的多边形。其顶点是用引用号(reference
number)表示的。引用号就是顶点序号。 \N

例二:cube.obj \N

\N

v 0.000000 2.000000 2.000000
v 0.000000 0.000000 2.000000
v 2.000000 0.000000 2.000000
v 2.000000 2.000000 2.000000
v 0.000000 2.000000 0.000000
v 0.000000 0.000000 0.000000
v 2.000000 0.000000 0.000000
v 2.000000 2.000000 0.000000
f 1 2 3 4
f 8 7 6 5
f 4 3 7 8
f 5 1 4 8
f 5 6 2 1
f 2 6 7 3 \N

这次有8个顶点,6个面。 \N

注意OBJ文件格式规范中规定可以用负的顶点
引用号表示相对顶点序号,但glm不能处理这
种情况(例如cube2.obj,用3D Exploration可以打
开,但objview会crash)。 \N

例3:twosquare.obj \N

\N

v 0.000000 2.000000 0.000000
v 0.000000 0.000000 0.000000
v 2.000000 0.000000 0.000000
v 2.000000 2.000000 0.000000
v 4.000000 0.000000 -1.255298
v 4.000000 2.000000 -1.255298
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.276597 0.000000 0.960986
vn 0.276597 0.000000 0.960986
vn 0.531611 0.000000 0.846988
vn 0.531611 0.000000 0.846988
# 6 vertices
# 6 normals
g all
s 1
f 1//1 2//2 3//3 4//4
f 4//4 3//3 5//5 6//6
# 2 elements \N

# 表示注释 \N

vn 表示顶点法向矢量(vertex normal)。我们知
道在OpenGL中要使用光照需要对每个点指定
法向矢量。在OBJ文件中则把这些法向矢量集
中放在一起,而用序号来引用它们。 \N

g 表示group,all是group的名字。下面的两个
face 都属于这个group,直到下一个g 命令或者
文件结束为止。 \N

s 表示平滑组(smooth group),1是group的序号。 \N

比较有意思的是最后两行。1//1 中第一个1表
示顶点序号(对应于由 v 命令定义的各行),后
一个1表示顶点法向矢量序号(对应于由 vn命令
定义的各行)。中间其实省略了纹理矢量序号
(对应于由 vt 命令定义的各行)。2//2,3//3,4//4,
都作同样的解释。因此这一行还是表示一个
四边形,只不过对每个点不仅定义了顶点坐标,
而且定义了法向矢量。 \N

例4:colorcube.obj colorcube.mtl \N

\N

下面是colorcube.obj的内容: \N

mtllib colorcube.mtl
v 0.000000 2.000000 2.000000
v 0.000000 0.000000 2.000000
v 2.000000 0.000000 2.000000
v 2.000000 2.000000 2.000000
v 0.000000 2.000000 0.000000
v 0.000000 0.000000 0.000000
v 2.000000 0.000000 0.000000
v 2.000000 2.000000 0.000000
# 8 vertices
g front
usemtl red
f 1 2 3 4
g back
usemtl blue
f 8 7 6 5
g right
usemtl green
f 4 3 7 8
g top
usemtl gold
f 5 1 4 8
g left
usemtl orange
f 5 6 2 1
g bottom
usemtl purple
f 2 6 7 3
# 6 elements \N

mtllib colorcube.obj 表示本OBJ文件要用到
一个材料文件colorcube.mtl,这个文件和本
文件在同一个目录下。 \N

usemtl red 表示此后的物体使用名叫red的
材料,直到下一个usemtl 命令为止。材料
red 由colorcube.mtl 定义。 \N

下面是colorcube.mtl的内容: \N

#
# colocube.mtl
# \N

newmtl red
Ka 0 0 0
Kd 1 0 0 \N

newmtl blue
Ka 0 0 0
Kd 0 0 1 \N

newmtl green
Ka 0 0 0
Kd 0 1 0 \N

newmtl gold
Ka 0.247250 0.199500 0.074500
Kd 0.751640 0.606480 0.226480
Ks 0.628281 0.555802 0.366065
illum 0
Ns 51.200001 \N

newmtl orange
Ka 0 0 0
Kd 0.9 0.5 0 \N

newmtl purple
Ka 0 0 0
Kd 0.7 0 0.9 \N

newmtl red 定义一个名叫red的材料。 \N

Ka,Kd,Ks,illum,Ns 分别定义ambient, diffuse,
specular, emission 和 shiness 参数。 \N

到这里,我们对OBJ文件中涉及多边形的
部分有了初步的了解。OBJ文件还包含很
多其它的命令,实现规范中的所有命令是
很繁杂的工作,glm.c 只实现了涉及多边
形的一部分命令,大体上也就是我们这几
个例子中所用到的命令。 \N

2。glm.c的使用 \N

下面我们学习如何使用glm.c。 \N

首先把glm.c加入工程,并在主文件中
#include "glm.h"。 \N

加载文件用如下代码: \N

char* g_model_fn = "data\\al.obj";
GLMmodel* g_model = NULL; \N

g_model = glmReadOBJ(g_model_fn);
if (!g_model) exit(0);
glmUnitize(g_model);
glmScale(g_model,1.8);
glmFacetNormals(g_model);
glmVertexNormals(g_model, 90.0); \N

g_model_fn是文件名。 \N

g_model是指向GLMmodel结构的指针。 \N

glmReadOBJ加载一个OBJ文件,生成一个GL
Mmodel结构,并返回其指针。以后对这个
model的操作都需要这个指针作为标识。 \N

glmUnitize把模型归一化到一个(-0.5,-0.5,-0.5)-
(0.5,0.5,0.5)的盒子内。这样模型的最大尺寸
是1,中心在原点。 \N

glmScale把模型作伸缩变换(Al身高1米8)。 \N

glmFacetNormals为模型的每个面(facet)生成法
向矢量。 \N

glmVertexNormals为模型的每个顶点生成法向
矢量。顶点的法向矢量是对相邻面的法向矢
量取平均值而得到的,因此需要预先调用
glmFacetNormals。第二个参数表示一个界限,
如果某个相邻面与第一个相邻面的夹角超过
此界限,那么该面不参与平均。这样可以保
留大的转折而不致于让整个模型都没楞没角。 \N

显示模型用如下代码: \N

Vector3 his_position(0,0.9,-2); \N

glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDisable(GL_TEXTURE_2D); \N

glTranslatef(his_position.x,his_position.y,his_position.z);
glmDraw(g_model,GLM_SMOOTH | GLM_MATERIAL); \N

his_position 是记录Al位置的全局变量。Al的
位置用他的重心坐标表示,因此y坐标是0.9
(y坐标竖直向上)。 \N

首先用glTranslatef把局部坐标系设置到Al的位
置。 \N

glmDraw显示模型。第二个参数表示显示方式。
GLM_SMOOTH表示光滑着色,GLM_MATERIAL
表示使用模型自带的材料。此外还有其它一
些方式:GLM_NONE只显示顶点,GLM_FLAT
使用非光滑的着色(参见glShadeModel),GLM_TEXTURE
使用纹理坐标。 \N

程序结束时还应删除模型: \N

if(g_model) glmDelete(g_model); \N

glm还包括其它一些函数: \N

GLvoid glmDimensions(GLMmodel* model, GLfloat* dimensions);
得到模型的长宽高
GLvoid glmReverseWinding(GLMmodel* model);
颠倒多边形的顶点顺序
GLvoid glmLinearTexture(GLMmodel* model);
用投影到平面的方法生成纹理坐标
GLvoid glmSpheremapTexture(GLMmodel* model);
用投影到球面的方法生成纹理坐标
GLvoid glmWriteOBJ(GLMmodel* model, char* filename, GLuint mode);
把模型存成OBJ文件
GLuint glmList(GLMmodel* model, GLuint mode);
生成模型的display list
GLvoid glmWeld(GLMmodel* model, GLfloat epsilon);
合并距离小于epsilon的顶点
GLubyte* glmReadPPM(const char* filename, int* width, int* height);
加载一个PPM文件 \N

3。简单的碰撞检测 \N

我们不希望在草原上漫游的时候会穿过Al的
身体(否则我们会看到Al的大眼珠子隐藏在他
的脑袋里面,那是很难看的),因此我们希望
如果我们计算出下一步会走得离Al太近,就不
允许走那一步。这就是碰撞检测(collision detection)。
如何高效而又准确地作碰撞检测是3D游戏的
一个重要问题。但是在我们这个简单的教程
里,碰撞检测可以很简单地实现。 \N

Vector3 his_position(0,0.9,-2);
Vector3 his_size(0.5,1,0.5);
Vector3 my_position(0,1.5,0);
Vector3 my_step(0,0,-0.3);
float my_orientation = 0;
float my_rotate_speed = 10;
float my_step_size = 0.3; \N

void special(int key, int x, int y)
//特殊按键回调函数:当功能键或光标键被按下时系统调用此函数
{
float angle;
Vector3 new_position;
switch(key)
{
case GLUT_KEY_LEFT:
my_orientation += my_rotate_speed;
angle = my_orientation * ML_PI / 180;
my_step.x = -my_step_size * sin(angle);
my_step.z = -my_step_size * cos(angle);
break;
case GLUT_KEY_RIGHT:
my_orientation -= my_rotate_speed;
angle = my_orientation * ML_PI / 180;
my_step.x = -my_step_size * sin(angle);
my_step.z = -my_step_size * cos(angle);
break;
case GLUT_KEY_UP:
new_position = my_position + my_step;
if(fabs(new_position.x-his_position.x)>his_size.x
|| fabs(new_position.z-his_position.z)>his_size.z)
my_position = new_position;
break;
case GLUT_KEY_DOWN:
new_position = my_position - my_step;
if(fabs(new_position.x-his_position.x)>his_size.x
|| fabs(new_position.z-his_position.z)>his_size.z)
my_position = new_position;
break;
}
glutPostRedisplay();
}
\N

碰撞检测是在特殊按键回调函数中作的。我
们把Al简化成一个矩形,而我们自己简化成
一个点。当我们按UP或DOWN键时,如果
这个点下一步会落在矩形里,下一步就走不
成,除非改变方向。 \N

Oct. 14, 2000 \N

下载tut14.zip \N

你需要glut32.dll来运行此程序,下载glut32.dll

🗓2005-8-28 08:57(约19年前)  👁3694


自称:德菲力修士
注册于:2006年3月25日
等级:注册会员
帖子数:24
积分:146
阅读权限:20
2楼
好东西啊~~~还有其他篇么?[em02][em02]
SIGNATURE
在信息时代,所谓的障碍都是主观上的。如果你想动手开发什么全新的技术,你不需要几百万美元的资金,你只需要在冰箱里放满比萨和可乐,再有一台便宜的计算机,和为之献身的决心。我们在地板上睡过,我们从河水中趟过。 --Jhon Carmack
🗓2006-3-25 23:12(约18年前)

标题(Title):
关键字标签(Tags):
路人:回贴可以不必登录