3.看起来不那么像的汽车
让我们回顾下上一节,我们构建了一个单薄的小人,可它看起来并不让我们满意。这一节中,我们将介绍:
①构建一个三维场景,且里面的物体也是立体的,我们的小人也是立体的。如果我们构造的物体看起来都差不
多,我们会考虑使用displayList显示列表
②构建其他的三维场景,并且我们可以在需要的时候切换这些场景。这里,我们学会构建一些类,用它来储存
我们的场景信息
③从文件读入我们的三维场景,当然,文件中保存了我们所需要的所有信息
④一些基本图形的绘制简介,包括圆柱、贝塞尔曲面(这些都仅作介绍)
首先让我们闭上眼睛想象一下,我们将要构建的是一个真实的立体场景,而不是前两节中所说的看似立体的
场景。这里,我们将构造很多立体的图形,比如,正方体、长方体、圆柱体或是一些复杂图形。当然,简单的
画出这些对象并不是我们的目的。我们将:
①构造一个scene类,它就是我们前两节中构建的糖果盒子。这次,我们将把许多立体元素加入进来,这就需
要我们再构造一些正方体(长方体)类、圆柱体类,和一些复杂图形类。完成之后我们将在scene类中用到它们
。那,让我们思考一下,我们的scene类需要包含些什么东西。
//H文件
class scene:public QGLWidget
{
Q_OBJECT
public:
scene( QWidget* parent = 0);
~scene();
void draw(int id); //调用它,我们将开始paint我们的对象
curbe* myCurbe; /*创建一个curbe类型的指针,我们用它来画我们所有的立方体。当然curbe类需要
我们去定义*/
int R; //对象旋转地角度
GLuint texture[3]; //所需的纹理。因为我不会美工也懒得找图,所以只用了3个简单的纹理图
protected:
void loadGLTextures();
private:
int sceneID; //我们所绘场景的ID号。想象我们需要很多不同的场景,并且通过场景ID进行切换
int objNum; //场景中对象的数量
char sort; //表明我们所画对象的类型,curbe,圆柱或者贝塞尔曲面等
char c; //用来从文件中读入字符的变量
string mapLine; //文件中的每一行我们存贮在mapLine中
int i;
string temp;
void drawCurbe(); //画立方体时调用
void paintMainScene(); //创建主场景,就是我们“舞台”的背景
void creatObj(); //创建对象,它会去读取文件
void recordCurbeData(); //读取到得文件,我们用它来初始化我们的数据
void initializeGL();
void paintGL();
};
②然后我们在cpp文件中进行我们的文件读取以及绘图等操作
首先,我们绘图的入口在这里
void scene::draw(int id)
{
sceneID = id;
paintGL();
}
紧接着我们的paintGL函数是这样的:
void scene::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
paintMainScene();
creatObj(); //then ,record data
loadGLTextures();
R+=5;
}其中,我们先画我们的场景背景(就是我们前面画的糖果盒子),然后用creatObj()建立其他三维对象。
creatObj完成我们对文件的读写。文件中按一定格式存储着我们所画对象的信息。我们在creatObj()和
recordCurbeData()等函数中去完成文件解析。
ps:最开始我只实现了从文件中读取立方体的数据然后将它们画到我们的场景中。而且由于能力有限,我只能
将数据存储在txt文件中。希望对这方面有研究的朋友能告诉我更好的文件存储方法。
存储的数据是这样的:
1 //场景ID
2 //场景中对象数量
0,120,120,120,-120,0,-120,-120,0,0,0,0,0,0,0,-120n
对象类型(0代表curbe),接着三个是长宽高,再接着的是对象底面左上、左下、右下、右上的三维坐标。最后
以n结束
0,120,120,60,-120,0,0,-120,0,60,-60,0,60,-60,0,0n
上面,我构造了两个立方体,我们可以再构造其他类型的对象,这里不再详细介绍。
③上面的操作我没有用displayList显示列表,因为显示列表用于创建许多一样的对象,而上面的操作我画了
很多大小不一的对象。再次回顾下我们的目标,一个三维导航系统(虽然说我只能做的很简陋),上面的步骤我
们用来构建静态的东西,然后我们用显示列表画我们的汽车。当然,这里需要用到我们的贝塞尔曲面,因为我
不知道怎样画汽车,不过我发现贝塞尔曲面和它很像。
关于显示列表,简单的说就是它把我们要画的对象预先画在内存中,以便在实际画的时候能够加快效率。下面
我们创建一个贝塞尔曲面的显示列表,具体的做法请参考Nehe的中文版教程第28讲。我这里只列出相关的函数
④initBezier() //用来初始化贝塞尔曲面的16个控制点(4X4)
LoadCarTexture(); // 载入纹理
mybezier.dlBPatch = genBezier(mybezier, divs); // 创建显示列表
glCallList(mybezier.dlBPatch); // 调用显示列表,绘制贝塞尔曲面
其中genBezier()生成贝塞尔曲面的显示列表,它是这个样子的:
GLuint openGLCar::genBezier(BEZIER_PATCH patch, int divs) {
int u = 0, v;
float py, px, pyold;
GLuint drawlist = glGenLists(1); // 创建显示列表
POINT_3D temp[4];
POINT_3D *last = (POINT_3D*)malloc(sizeof(POINT_3D)*(divs+1)); // 根据每一条曲
线的细分数,分配相应的内存
if (patch.dlBPatch != NULL) // 如果显示列表存在则删除
glDeleteLists(patch.dlBPatch, 1);
temp[0] = patch.anchors[0][3]; // 获得u方向的四个控制点
temp[1] = patch.anchors[1][3];
temp[2] = patch.anchors[2][3];
temp[3] = patch.anchors[3][3];
for (v=0;v<=divs;v++) { // 根据细分数,创建各个分割点额参数
px = ((float)v)/((float)divs);
// 使用Bernstein函数求的分割点的坐标
last[v] = Bernstein(px, temp);
}
glNewList(drawlist, GL_COMPILE); // 创建一个新的显示列表
// glBindTexture(GL_TEXTURE_2D, patch.texture); // 邦定纹理
for (u=1;u<=divs;u++) {
py = ((float)u)/((float)divs); // 计算v方向上的细分点的参数
pyold = ((float)u-1.0f)/((float)divs); // 上一个v方向上的细分点的参数
temp[0] = Bernstein(py, patch.anchors[0]);// 计算每个细分点v方向上贝塞尔曲面的控制点
temp[1] = Bernstein(py, patch.anchors[1]);
temp[2] = Bernstein(py, patch.anchors[2]);
temp[3] = Bernstein(py, patch.anchors[3]);
glBegin(GL_TRIANGLE_STRIP); // 开始绘制三角形带
for (v=0;v<=divs;v++) {
px = ((float)v)/((float)divs); // 沿着u轴方向顺序绘制
glTexCoord2f(pyold, px); // 设置纹理坐标
glVertex3d(last[v].x, last[v].y, last[v].z); // 绘制一个顶点
last[v] = Bernstein(px, temp); // 创建下一个顶点
glTexCoord2f(py, px); // 设置纹理
glVertex3d(last[v].x, last[v].y, last[v].z); // 绘制新的顶点
}
glEnd(); // 结束三角形带的绘制
}
glEglCallList(mybezier.dlBPatch); // 调用显示列表,绘制贝塞尔曲面ndList();
// 显示列表绘制结束
free(last); // 释放分配的内存
return drawlist; // 返回创建的显示列表
}