NeHe 系列教程之十:在3D空间中漫游
英文教程地址:lesson10
本课演示了从外部文件中加载数据构建3D模型的实例,代码基于第一课。
首先是3D模型的数据结构定义:
namespace {
bool fp; // F pressed?
const float piover180 = 0.0174532925f;
float heading;
float xpos;
float zpos;
GLfloat yrot; // Y Rotation
GLfloat walkbias = 0;
GLfloat walkbiasangle = 0;
GLfloat lookupdown = 0.0f;
GLuint filter; // Which filter to use
GLuint texture[3]; // Storage for 3 textures
typedef struct tagVERTEX
{
float x, y, z;
float u, v;
} VERTEX;
typedef struct tagTRIANGLE
{
VERTEX vertex[3];
} TRIANGLE;
typedef struct tagSECTOR
{
int numtriangles;
TRIANGLE* triangle;
} SECTOR;
SECTOR sector1; // Our model goes here:
接着是读取外部数据文件,构建3D模型:
const char* readstr(QFile &f)
{
QByteArray line = f.readLine();
while (line.at(0) == '/' || line.at(0) == '\n') {
line = f.readLine();
}
return line.constData();
}
void SetupWorld()
{
float x, y, z, u, v;
int numtriangles;
const char *oneline;
QFile filein(":/World.txt");
if (!filein.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug("failed to open file");
} else {
qDebug("open file successfully");
}
oneline = readstr(filein);
sscanf(oneline, "NUMPOLLIES %d\n", &numtriangles);
sector1.triangle = new TRIANGLE[numtriangles];
sector1.numtriangles = numtriangles;
for (int loop = 0; loop < numtriangles; loop++)
{
for (int vert = 0; vert < 3; vert++)
{
oneline = readstr(filein);
sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v);
sector1.triangle[loop].vertex[vert].x = x;
sector1.triangle[loop].vertex[vert].y = y;
sector1.triangle[loop].vertex[vert].z = z;
sector1.triangle[loop].vertex[vert].u = u;
sector1.triangle[loop].vertex[vert].v = v;
}
}
filein.close();
return;
}
}
采用了三种不同的纹理过滤方式, 加载纹理代码如下:
void MyGLWidget::loadTextures()
{
QImage image;
if (image.load(":/Mud.bmp")) {
image = convertToGLFormat(image);
glGenTextures(3, texture);
// Create Nearest Filtered Texture
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
// Create Linear Filtered Texture
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
// Create MipMapped Texture
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, image.width(), image.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
}
}
在初始化代码中,调用构建3D模型函数,即:
void MyGLWidget::initializeGL()
{
...
SetupWorld();
}
然后是绘制代码,将3D场景显示出来:
void MyGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the screen and the depth buffer
glLoadIdentity(); // Reset the view
GLfloat x_m, y_m, z_m, u_m, v_m;
GLfloat xtrans = -xpos;
GLfloat ztrans = -zpos;
GLfloat ytrans = -walkbias-0.25f;
GLfloat sceneroty = 360.0f - yrot;
int numtriangles;
glRotatef(lookupdown,1.0f,0,0);
glRotatef(sceneroty,0,1.0f,0);
glTranslatef(xtrans, ytrans, ztrans);
glBindTexture(GL_TEXTURE_2D, texture[filter]);
numtriangles = sector1.numtriangles;
// Process each triangle
for (int loop_m = 0; loop_m < numtriangles; loop_m++)
{
glBegin(GL_TRIANGLES);
glNormal3f( 0.0f, 0.0f, 1.0f);
x_m = sector1.triangle[loop_m].vertex[0].x;
y_m = sector1.triangle[loop_m].vertex[0].y;
z_m = sector1.triangle[loop_m].vertex[0].z;
u_m = sector1.triangle[loop_m].vertex[0].u;
v_m = sector1.triangle[loop_m].vertex[0].v;
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);
x_m = sector1.triangle[loop_m].vertex[1].x;
y_m = sector1.triangle[loop_m].vertex[1].y;
z_m = sector1.triangle[loop_m].vertex[1].z;
u_m = sector1.triangle[loop_m].vertex[1].u;
v_m = sector1.triangle[loop_m].vertex[1].v;
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);
x_m = sector1.triangle[loop_m].vertex[2].x;
y_m = sector1.triangle[loop_m].vertex[2].y;
z_m = sector1.triangle[loop_m].vertex[2].z;
u_m = sector1.triangle[loop_m].vertex[2].u;
v_m = sector1.triangle[loop_m].vertex[2].v;
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);
glEnd();
}
}
最后是键盘控制处理:
void MyGLWidget::keyReleaseEvent(QKeyEvent *e)
{
switch (e->key()) {
case Qt::Key_I:
fp = false;
break;
default:
QGLWidget::keyReleaseEvent(e);
}
}
void MyGLWidget::keyPressEvent(QKeyEvent *e)
{
switch (e->key()) {
case Qt::Key_I:
fp = true;
filter += 1;
if (filter > 2)
filter = 0;
break;
case Qt::Key_F:
fullscreen = !fullscreen;
if (fullscreen) {
showFullScreen();
} else {
resize(640, 480);
showNormal();
}
break;
case Qt::Key_Right:
yrot -= 1.5f;
break;
case Qt::Key_Left:
yrot += 1.5f;
break;
case Qt::Key_Up:
xpos -= (float)sin(heading*piover180) * 0.05f; // Move On The X-Plane Based On Player Direction
zpos -= (float)cos(heading*piover180) * 0.05f; // Move On The Z-Plane Based On Player Direction
if (walkbiasangle >= 359.0f) // Is walkbiasangle>=359?
{
walkbiasangle = 0.0f; // Make walkbiasangle Equal 0
}
else // Otherwise
{
walkbiasangle+= 10; // If walkbiasangle < 359 Increase It By 10
}
walkbias = (float)sin(walkbiasangle * piover180)/20.0f; // Causes The Player To Bounce
break;
case Qt::Key_Down:
xpos += (float)sin(heading*piover180) * 0.05f; // Move On The X-Plane Based On Player Direction
zpos += (float)cos(heading*piover180) * 0.05f; // Move On The Z-Plane Based On Player Direction
if (walkbiasangle <= 1.0f) // Is walkbiasangle<=1?
{
walkbiasangle = 359.0f; // Make walkbiasangle Equal 359
}
else // Otherwise
{
walkbiasangle-= 10; // If walkbiasangle > 1 Decrease It By 10
}
walkbias = (float)sin(walkbiasangle * piover180)/20.0f; // Causes The Player To Bounce
break;
case Qt::Key_Escape:
QMessageBox::StandardButton reply;
reply = QMessageBox::question(NULL, "NeHe",
"Do you want to exit?",
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
if (reply == QMessageBox::Yes) {
qApp->quit();
}
break;
default:
QGLWidget::keyPressEvent(e);
break;
}
}
最后运行效果如下所示: