一、前言
通过前面的学习,基本掌握了怎么绘制图形,使用纹理,接下来就来创建一个3D场景。
基本原理
一个复杂的场景肯定是由一些简单的图形,通过某种组合方式构建起来的,在OPenGL中也不例外;例如:在绘制立方体的时候,立方体也是由6个正方形围起来的;
基本图形
由于显卡在渲染三角形时效率较高,所以我们采用三角形来构建复杂的3D场景;
数据结构
- 当您想要使用一系列的数字来完美的表达3D环境时,随着环境复杂度的上升,这个工作的难度也会随之上升;
- 出于这个原因,我们必须将数据归类,使其具有更多的可操作性风格,在程序中添加sector结构体(区段)的定义;
- 每个3D世界基本上可以看作是sector(区段)的集合,一个sector(区段)可以是一个房间、一个立方体、或者任意一个闭合的区间;
typedef struct tagVERTEX //三角形的顶点
{
float x, y, z; // 3D 坐标
float u, v; // 纹理坐标
} VERTEX;
typedef struct tagTRIANGLE //三角形
{
VERTEX vertex[3]; // VERTEX矢量数组,大小为3
}TRIANGLE;
typedef struct tagSECTOR //Sector区段结构(三角形集合)
{
int numtriangles; // Sector中的三角形个数
TRIANGLE* triangle; // 指向三角数组的指针
} SECTOR;
数据加载
在程序内部直接存储数据会让程序显得太过死板和无趣。从磁盘上载入世界资料,会给我们带来更多的弹性,可以让我们体验不同的世界,而不用被迫重新编译程序。另一个好处就是用户可以切换世界资料并修改它们而无需知道程序如何读入输出这些资料的。数据文件的类型我们准备使用文本格式(txt)。这样编辑起来更容易,写的代码也更少。
二、效果展示
三、详细流程
首先,不懂怎么创建OPenGL窗口的,可以参考:OPenGL笔记–创建一个OPenGL窗口
在窗口的基础上,我们在函数paintGL()
中绘制我们的3D世界(其实就是一个贴了纹理的盒子);
之前的教程里我们都是直接在函数paintGL()
中绘制,这里我们通过加载World.txt中的数据来绘制图形;
这个World.txt是一个描述一堆三角形信息的文本文件,众所周知,一个三角形由三个点构成,纹理有两个坐标,所以:
在World.txt中,采用(x, y, z, u, v)的形式来描述一个三角形顶点,例如:
NUMPOLLIES 12
// Floor 1
-3.0 0.0 -3.0 0.0 6.0
3.0 0.0 -3.0 0.0 0.0
-3.0 0.0 3.0 6.0 0.0
3.0 0.0 3.0 0.0 6.0
3.0 0.0 -3.0 6.0 6.0
-3.0 0.0 3.0 6.0 0.0
在World.txt中,最上面一行NUMPOLLIES 12
记录的是此文本文件中描述三角形的个数,每3个点(对应文本文件中的三行)组成一个三角形;
3.1、World.txt文件规则
World.txt中第一行表示文本文件中描述的三角形个数:NUMPOLLIES 12
接下来,例如,我们搭建的是一个盒子,我们可以将其拆分为6个面:上、下、左、右、前、后
基本格式如下
NUMPOLLIES 12
//上
一个三角形
一个三角形
//下
一个三角形
一个三角形
//左
一个三角形
一个三角形
//右
一个三角形
一个三角形
//前
一个三角形
一个三角形
//后
一个三角形
一个三角形
例如,我们准备搭建一个661的盒子,以x-z平面为底,以原点为底部中心,如下图所示:
以底面为例进行讲解,我们将底面正方形划分为两个三角形(怎么划分不重要),分别是上三角和下三角
上三角:
(-3, 0, -3)
(3, 0, -3)
(-3, 0, 3)
下三角:
(3, 0, 3)
(3, 0, -3)
(-3, 0, 3)
现在确定了三角形的坐标,那纹理坐标怎么确定呢?纹理坐标通常在0~1之间(但是纹理坐标如果超过1就会复制)
以上三角为例:以直角为纹理坐标系原点,直角边为纹理坐标系坐标轴,纹理坐标X、Y轴需要垂直世界坐标系轴X、Y轴,如下图示:
所以可以得出上三角的纹理坐标为:
(0, 0)
(0, 6)
(6, 0)
三角形坐标和纹理坐标合起来就得到了World.txt中的三角形信息:
NUMPOLLIES 1
// Floor 1
-3.0 0.0 -3.0 0.0 0.0
3.0 0.0 -3.0 0.0 6.0
-3.0 0.0 3.0 6.0 0.0
3.2、加载World.txt
World.txt文件编写完成之后,通过setipWorld()
函数来加载World.txt;
- 将读取的三角形个数存储到区段结构体数组
m_sector1
中; - 将三角形信息存储到区段结构体数组
m_sector1
中;
void GLWidget::setupWorld()
{
QFile file(":/world/World.txt");
if(!file.open(QIODevice::ReadOnly))
{
QMessageBox::warning(this, tr("Warning"), tr("Can't open world file."));
return;
}
QTextStream stream(&file);
//我们对区段进行初始化,并读入部分数据
QString oneline; // 存储数据的字符串
int numtriangles; // 区段的三角形数量
float x, y, z, u, v; // 3D 和 纹理坐标
readStr(&stream, oneline); // 读入一行数据
sscanf(oneline.toLatin1().data(), "NUMPOLLIES %d\n", &numtriangles); // 读入三角形数量
m_sector1.triangle = new TRIANGLE[numtriangles]; // 为numtriangles个三角形分配内存并设定指针
m_sector1.numtriangles = numtriangles; // 定义区段1中的三角形数量
// 遍历区段中的每个三角形
for (int triloop = 0; triloop < numtriangles; triloop++) // 遍历所有的三角形
{
// 遍历三角形的每个顶点
for (int vertloop = 0; vertloop < 3; vertloop++) // 遍历所有的顶点
{
readStr(&stream, oneline); // 读入一行数据
// 读入各自的顶点数据
sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
// 将顶点数据存入各自的顶点
m_sector1.triangle[triloop].vertex[vertloop].x = x; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 x=x
m_sector1.triangle[triloop].vertex[vertloop].y = y; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 y=y
m_sector1.triangle[triloop].vertex[vertloop].z = z; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 z=z
m_sector1.triangle[triloop].vertex[vertloop].u = u; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 u=u
m_sector1.triangle[triloop].vertex[vertloop].v = v; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 v=v
}
}
//数据文件中每个三角形都以如下形式声明:
//X1 Y1 Z1 U1 V1
//X2 Y2 Z2 U2 V2
//X3 Y3 Z3 U3 V3
file.close();
}
//读取World.txt中的有效数据行
void GLWidget::readStr(QTextStream *stream, QString &string)
{
do // 循环开始
{
string = stream->readLine();
} while (string[0] == '/' || string[0] == '\n' || string.isEmpty()); // 考察是否有必要进行处理
}
3.3、绘制场景
在加载玩World.txt到区段结构体数组m_sector1
中之后,在paintGL()中绘制;
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
glLoadIdentity(); //重置当前的模型观察矩阵
GLfloat x_m, y_m, z_m, u_m, v_m; // 顶点的临时 X, Y, Z, U 和 V 的数值
GLfloat xtrans = -m_xpos; // 用于游戏者沿X轴平移时的大小
GLfloat ztrans = -m_zpos; // 用于游戏者沿Z轴平移时的大小
GLfloat ytrans = -m_walkbias-0.25f; // 用于头部的上下摆动
GLfloat sceneroty = 360.0f - m_yrot; // 位于游戏者方向的360度角
int numtriangles; // 保有三角形数量的整数
glRotatef(m_lookupdown, 1.0f, 0,0); // 上下旋转
glRotatef(sceneroty, 0, 1.0f, 0); // 根据游戏者正面所对方向所作的旋转
glTranslatef(xtrans, ytrans, ztrans); // 以游戏者为中心的平移场景
glBindTexture(GL_TEXTURE_2D, m_texture[0]); // 选择的纹理
numtriangles = m_sector1.numtriangles; // 取得Sector1的三角形数量
for (int loop_m = 0; loop_m < numtriangles; loop_m++) // 遍历所有的三角形
{
glBegin(GL_TRIANGLES); // 开始绘制三角形
glNormal3f( 0.0f, 0.0f, 1.0f); // 指向前面的法线
x_m = m_sector1.triangle[loop_m].vertex[0].x; // 第一点的 X 分量
y_m = m_sector1.triangle[loop_m].vertex[0].y; // 第一点的 Y 分量
z_m = m_sector1.triangle[loop_m].vertex[0].z; // 第一点的 Z 分量
u_m = m_sector1.triangle[loop_m].vertex[0].u; // 第一点的 U 纹理坐标
v_m = m_sector1.triangle[loop_m].vertex[0].v; // 第一点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector1.triangle[loop_m].vertex[1].x; // 第二点的 X 分量
y_m = m_sector1.triangle[loop_m].vertex[1].y; // 第二点的 Y 分量
z_m = m_sector1.triangle[loop_m].vertex[1].z; // 第二点的 Z 分量
u_m = m_sector1.triangle[loop_m].vertex[1].u; // 第二点的 U 纹理坐标
v_m = m_sector1.triangle[loop_m].vertex[1].v; // 第二点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector1.triangle[loop_m].vertex[2].x; // 第三点的 X 分量
y_m = m_sector1.triangle[loop_m].vertex[2].y; // 第三点的 Y 分量
z_m = m_sector1.triangle[loop_m].vertex[2].z; // 第三点的 Z 分量
u_m = m_sector1.triangle[loop_m].vertex[2].u; // 第三点的 U 纹理坐标
v_m = m_sector1.triangle[loop_m].vertex[2].v; // 第三点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
glEnd(); // 三角形绘制结束
}
}
3.4、交互
绘制完成之后,我们的3D世界基本搭建完成了,然后需要添加键盘交互;
void GLWidget::keyPressEvent(QKeyEvent* e)
{
switch (e->key()) {
case Qt::Key_Q: {
fullscreen = !fullscreen;
if(fullscreen) {
showFullScreen();
}else {
showNormal();
setGeometry(500,500,640,480);
}
updateGL();
break;
}//case Qt::Key_Q
case Qt::Key_Escape: {
close();
}//Qt::Key_Escape
case Qt::Key_PageUp:
{
m_lookupdown-=1.0f;
updateGL();
break;
}
case Qt::Key_PageDown:
{
m_lookupdown+=1.0f;
updateGL();
break;
}
case Qt::Key_Right:
{
m_heading -=1.0f;
m_yrot = m_heading; // 向左旋转场景
updateGL();
break;
}
case Qt::Key_Left:
{
m_heading += 1.0f;
m_yrot = m_heading; // 向右侧旋转场景
updateGL();
break;
}
case Qt::Key_Up:
{
m_xpos -= (float)sin(m_heading*piover180) * 0.05f; // 沿游戏者所在的X平面移动
m_zpos -= (float)cos(m_heading*piover180) * 0.05f; // 沿游戏者所在的Z平面移动
updateGL();
break;
}
case Qt::Key_Down:
{
m_xpos += (float)sin(m_heading*piover180) * 0.05f; // 沿游戏者所在的X平面移动
m_zpos += (float)cos(m_heading*piover180) * 0.05f; // 沿游戏者所在的Z平面移动
updateGL();
break;
}
}//switch (e->key())
}
四、详细代码
素材
World.txt
NUMPOLLIES 12
// Floor 1
-3.0 0.0 -3.0 0.0 6.0
-3.0 0.0 3.0 0.0 0.0
3.0 0.0 3.0 6.0 0.0
-3.0 0.0 -3.0 0.0 6.0
3.0 0.0 -3.0 6.0 6.0
3.0 0.0 3.0 6.0 0.0
// Ceiling 1
-3.0 1.0 -3.0 0.0 6.0
-3.0 1.0 3.0 0.0 0.0
3.0 1.0 3.0 6.0 0.0
-3.0 1.0 -3.0 0.0 6.0
3.0 1.0 -3.0 6.0 6.0
3.0 1.0 3.0 6.0 0.0
// Left
-3.0 1.0 -3.0 0.0 6.0
-3.0 1.0 3.0 0.0 0.0
-3.0 0.0 3.0 1.0 0.0
-3.0 0.0 3.0 0.0 6.0
-3.0 0.0 -3.0 0.0 0.0
-3.0 1.0 -3.0 1.0 0.0
// right
3.0 1.0 3.0 0.0 6.0
3.0 1.0 -3.0 0.0 0.0
3.0 0.0 -3.0 1.0 0.0
3.0 0.0 3.0 0.0 0.0
3.0 0.0 -3.0 0.0 6.0
3.0 1.0 3.0 1.0 0.0
// front
-3.0 1.0 -3.0 0.0 6.0
3.0 1.0 -3.0 0.0 0.0
3.0 0.0 -3.0 1.0 0.0
-3.0 0.0 -3.0 0.0 0.0
3.0 0.0 -3.0 0.0 6.0
-3.0 1.0 -3.0 1.0 0.0
// behind
-3.0 1.0 3.0 0.0 6.0
3.0 1.0 3.0 0.0 0.0
3.0 0.0 3.0 1.0 0.0
-3.0 0.0 3.0 0.0 0.0
3.0 0.0 3.0 0.0 6.0
-3.0 1.0 3.0 1.0 0.0
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QGLWidget>
#include <QKeyEvent>
#include <GL/glu.h>
#include <QMessageBox>
#include <QApplication>
#include <QTextStream>
#include <math.h>
#include <QDebug>
//继承QGLWidget得到OPenGL窗口部件类
class GLWidget : public QGLWidget
{
Q_OBJECT
public:
//场景描述结构体
//==================================================================
typedef struct tagVERTEX // 创建顶点结构
{
float x, y, z; // 3D 坐标
float u, v; // 纹理坐标
} VERTEX;
typedef struct tagTRIANGLE // 创建三角形结构
{
VERTEX vertex[3]; // VERTEX矢量数组,大小为3
}TRIANGLE;// 命名为 TRIANGLE
typedef struct tagSECTO // 创建Sector区段结构
{
int numtriangles; // Sector中的三角形个数
TRIANGLE* triangle; // 指向三角数组的指针
} SECTOR;
//==================================================================
public:
GLWidget(QWidget* parent = 0, bool fs = false);
~GLWidget();
protected:
/*************************************************************************************************
QGLWidget 类已经内置了对 OpenGL 的处理,就是通过对 initializeGL()、 paintGL()和 resizeGL()这三个函数实现
*************************************************************************************************/
void initializeGL() override; //用来初始化OPenGL窗口,可以在里面设定一些有关选项
void paintGL() override; //用来绘制OPenGL的窗口,只要有更新发生,这个函数就会被调用
void resizeGL(int w, int h) override; //用来处理窗口大小变换这一事件,resizeGL()在处理完后会自动刷新屏幕
void keyPressEvent(QKeyEvent* e) override; //Qt键盘事件处理函数
private:
void setupWorld(); //初始化场景
void readStr(QTextStream *stream, QString &string); //读取顶点信息
void loadTexture(); //加载纹理
private:
bool fullscreen; //用来保存窗口是否处于全屏状态的变量
SECTOR m_sector1;
GLfloat m_yrot;
GLfloat m_xpos;
GLfloat m_zpos;
GLfloat m_heading;
GLfloat m_walkbias;
GLfloat m_walkbiasangle;
GLfloat m_lookupdown;
GLuint m_texture[3];
};
#endif // GLWIDGET_H
#include "GLWidget.h"
const float piover180 = 0.0174532925f;
GLWidget::GLWidget(QWidget* parent, bool fs)
: QGLWidget(parent)
{
fullscreen = fs;
m_yrot = 0.0f;
m_xpos = 0.0f;
m_zpos = 0.0f;
m_heading = 0.0f;
m_walkbias = 0.0f;
m_walkbiasangle = 0.0f;
m_lookupdown = 0.0f;
setMinimumSize(1000,1000); //设置窗口大小
setWindowTitle("The first OpenGL Window"); //设置窗口标题
if(fullscreen) {
showFullScreen();
}
}
GLWidget::~GLWidget()
{
}
void GLWidget::initializeGL()
{
loadTexture(); //加载纹理
glEnable(GL_TEXTURE_2D); //使能纹理
glClearColor(0.0, 0.0, 0.0, 0.0); //清除屏幕时所用的颜色,rgba【0.0(最黑)~1.0(最亮)】
glClearDepth(1.0); //设置深度缓存
glDepthFunc(GL_LESS); //所作深度测试的类型
glEnable(GL_DEPTH_TEST); //启动深度测试
glShadeModel(GL_SMOOTH); //启用smooth shading(阴影平滑)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //真正精细的透视修正,告诉OPenGL我们希望进行最好的透视修正,这会十分轻微的影响性能,但使得透视图看起来好一点
setupWorld();
}
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
glLoadIdentity(); //重置当前的模型观察矩阵
GLfloat x_m, y_m, z_m, u_m, v_m; // 顶点的临时 X, Y, Z, U 和 V 的数值
GLfloat xtrans = -m_xpos; // 用于游戏者沿X轴平移时的大小
GLfloat ztrans = -m_zpos; // 用于游戏者沿Z轴平移时的大小
GLfloat ytrans = -m_walkbias-0.25f; // 用于头部的上下摆动
GLfloat sceneroty = 360.0f - m_yrot; // 位于游戏者方向的360度角
int numtriangles; // 保有三角形数量的整数
glRotatef(m_lookupdown, 1.0f, 0,0); // 上下旋转
glRotatef(sceneroty, 0, 1.0f, 0); // 根据游戏者正面所对方向所作的旋转
glTranslatef(xtrans, ytrans, ztrans); // 以游戏者为中心的平移场景
glBindTexture(GL_TEXTURE_2D, m_texture[0]); // 选择的纹理
numtriangles = m_sector1.numtriangles; // 取得Sector1的三角形数量
for (int loop_m = 0; loop_m < numtriangles; loop_m++) // 遍历所有的三角形
{
glBegin(GL_TRIANGLES); // 开始绘制三角形
glNormal3f( 0.0f, 0.0f, 1.0f); // 指向前面的法线
x_m = m_sector1.triangle[loop_m].vertex[0].x; // 第一点的 X 分量
y_m = m_sector1.triangle[loop_m].vertex[0].y; // 第一点的 Y 分量
z_m = m_sector1.triangle[loop_m].vertex[0].z; // 第一点的 Z 分量
u_m = m_sector1.triangle[loop_m].vertex[0].u; // 第一点的 U 纹理坐标
v_m = m_sector1.triangle[loop_m].vertex[0].v; // 第一点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector1.triangle[loop_m].vertex[1].x; // 第二点的 X 分量
y_m = m_sector1.triangle[loop_m].vertex[1].y; // 第二点的 Y 分量
z_m = m_sector1.triangle[loop_m].vertex[1].z; // 第二点的 Z 分量
u_m = m_sector1.triangle[loop_m].vertex[1].u; // 第二点的 U 纹理坐标
v_m = m_sector1.triangle[loop_m].vertex[1].v; // 第二点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector1.triangle[loop_m].vertex[2].x; // 第三点的 X 分量
y_m = m_sector1.triangle[loop_m].vertex[2].y; // 第三点的 Y 分量
z_m = m_sector1.triangle[loop_m].vertex[2].z; // 第三点的 Z 分量
u_m = m_sector1.triangle[loop_m].vertex[2].u; // 第三点的 U 纹理坐标
v_m = m_sector1.triangle[loop_m].vertex[2].v; // 第三点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
glEnd(); // 三角形绘制结束
}
}
void GLWidget::resizeGL(int w, int h)
{
if(h == 0) { //防止h为0
h = 1;
}
glViewport(0, 0, (GLint)w, (GLint)h); //重置当前的视口(Viewport)
glMatrixMode(GL_PROJECTION); //选择投影矩阵
glLoadIdentity(); //重置投影矩阵
gluPerspective( 45.0, (GLfloat)w/(GLfloat)h, 0.001, 1000.0 ); //建立透视投影矩阵
glMatrixMode(GL_MODELVIEW); //选择模型观察矩阵
glLoadIdentity(); //重置模型观察矩阵
}
void GLWidget::keyPressEvent(QKeyEvent* e)
{
switch (e->key()) {
case Qt::Key_Q: {
fullscreen = !fullscreen;
if(fullscreen) {
showFullScreen();
}else {
showNormal();
setGeometry(500,500,640,480);
}
updateGL();
break;
}//case Qt::Key_Q
case Qt::Key_Escape: {
close();
}//Qt::Key_Escape
case Qt::Key_PageUp:
{
m_lookupdown-=1.0f;
updateGL();
break;
}
case Qt::Key_PageDown:
{
m_lookupdown+=1.0f;
updateGL();
break;
}
case Qt::Key_Right:
{
m_heading -=1.0f;
m_yrot = m_heading; // 向左旋转场景
updateGL();
break;
}
case Qt::Key_Left:
{
m_heading += 1.0f;
m_yrot = m_heading; // 向右侧旋转场景
updateGL();
break;
}
case Qt::Key_Up:
{
m_xpos -= (float)sin(m_heading*piover180) * 0.05f; // 沿游戏者所在的X平面移动
m_zpos -= (float)cos(m_heading*piover180) * 0.05f; // 沿游戏者所在的Z平面移动
updateGL();
break;
}
case Qt::Key_Down:
{
m_xpos += (float)sin(m_heading*piover180) * 0.05f; // 沿游戏者所在的X平面移动
m_zpos += (float)cos(m_heading*piover180) * 0.05f; // 沿游戏者所在的Z平面移动
updateGL();
break;
}
}//switch (e->key())
}
void GLWidget::setupWorld()
{
QFile file(":/world/World.txt");
if(!file.open(QIODevice::ReadOnly))
{
QMessageBox::warning(this, tr("Warning"), tr("Can't open world file."));
return;
}
QTextStream stream(&file);
//我们对区段进行初始化,并读入部分数据
QString oneline; // 存储数据的字符串
int numtriangles; // 区段的三角形数量
float x, y, z, u, v; // 3D 和 纹理坐标
readStr(&stream, oneline); // 读入一行数据
sscanf(oneline.toLatin1().data(), "NUMPOLLIES %d\n", &numtriangles); // 读入三角形数量
m_sector1.triangle = new TRIANGLE[numtriangles]; // 为numtriangles个三角形分配内存并设定指针
m_sector1.numtriangles = numtriangles; // 定义区段1中的三角形数量
// 遍历区段中的每个三角形
for (int triloop = 0; triloop < numtriangles; triloop++) // 遍历所有的三角形
{
// 遍历三角形的每个顶点
for (int vertloop = 0; vertloop < 3; vertloop++) // 遍历所有的顶点
{
readStr(&stream, oneline); // 读入一行数据
// 读入各自的顶点数据
sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
// 将顶点数据存入各自的顶点
m_sector1.triangle[triloop].vertex[vertloop].x = x; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 x=x
m_sector1.triangle[triloop].vertex[vertloop].y = y; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 y=y
m_sector1.triangle[triloop].vertex[vertloop].z = z; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 z=z
m_sector1.triangle[triloop].vertex[vertloop].u = u; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 u=u
m_sector1.triangle[triloop].vertex[vertloop].v = v; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 v=v
}
}
//数据文件中每个三角形都以如下形式声明:
//X1 Y1 Z1 U1 V1
//X2 Y2 Z2 U2 V2
//X3 Y3 Z3 U3 V3
file.close();
}
//读取World.txt中的有效数据行
void GLWidget::readStr(QTextStream *stream, QString &string)
{
do // 循环开始
{
string = stream->readLine();
} while (string[0] == '/' || string[0] == '\n' || string.isEmpty()); // 考察是否有必要进行处理
}
//加载纹理
void GLWidget::loadTexture()
{
QImage image(":/Images/Crate.bmp");
image = image.convertToFormat(QImage::Format_RGB888);
image = image.mirrored();
glGenTextures(1, &m_texture[0]);// 创建纹理
// Create Nearest Filtered Texture
glBindTexture(GL_TEXTURE_2D, m_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, 3, image.width(), image.height(),
0, GL_RGB, GL_UNSIGNED_BYTE, image.bits());
}
五、举一反三
通过上面的流程,我们创建了一个小房间,它使用的纹理都是一样的,接下来我们通过使用不同的纹理,来创建一个感官更加丰富的场景;
效果展示
上面使用的是三角形,我们也可以使用矩形来构建:
素材
文章来源:https://www.toymoban.com/news/detail-512619.html
World1.txt文章来源地址https://www.toymoban.com/news/detail-512619.html
grass 1
// grass
-3.0 0.0 -3.0 0.0 6.0
3.0 0.0 -3.0 6.0 6.0
3.0 0.0 3.0 6.0 0.0
-3.0 0.0 3.0 0.0 0.0
sky 1
// sky
-3.0 1.0 -3.0 0.0 6.0
3.0 1.0 -3.0 6.0 6.0
3.0 1.0 3.0 6.0 0.0
-3.0 1.0 3.0 0.0 0.0
floor 4
//floor-left
-3.0 1.0 3.0 0.0 6.0
-3.0 1.0 -3.0 6.0 6.0
-3.0 0.0 -3.0 6.0 0.0
-3.0 0.0 3.0 0.0 0.0
//floor-right
3.0 1.0 -3.0 0.0 6.0
3.0 1.0 3.0 6.0 6.0
3.0 0.0 3.0 6.0 0.0
3.0 0.0 -3.0 0.0 0.0
//floor-front
-3.0 1.0 -3.0 0.0 6.0
3.0 1.0 -3.0 6.0 6.0
3.0 0.0 -3.0 6.0 0.0
-3.0 0.0 -3.0 0.0 0.0
//floor-behind
-3.0 1.0 3.0 0.0 6.0
3.0 1.0 3.0 6.0 6.0
3.0 0.0 3.0 6.0 0.0
-3.0 0.0 3.0 0.0 0.0
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QGLWidget>
#include <QKeyEvent>
#include <GL/glu.h>
#include <QMessageBox>
#include <QApplication>
#include <QTextStream>
#include <math.h>
#include <QDebug>
//继承QGLWidget得到OPenGL窗口部件类
class GLWidget : public QGLWidget
{
Q_OBJECT
public:
//场景描述结构体
//==================================================================
typedef struct tagVERTEX // 创建顶点结构
{
float x, y, z; // 3D 坐标
float u, v; // 纹理坐标
} VERTEX;
typedef struct tagTRIANGLE // 创建三角形结构
{
VERTEX vertex[3]; // VERTEX矢量数组,大小为3
}TRIANGLE;// 命名为 TRIANGLE
typedef struct tagRECT // 创建四边形结构
{
VERTEX vertex[4]; // VERTEX矢量数组,大小为4
}RECT;// 命名为 RECT
typedef struct tagSECTO // 创建Sector区段结构
{
int numtriangles; // Sector中的三角形个数
TRIANGLE* triangle; // 指向三角数组的指针
RECT* rect;
} SECTOR;
//==================================================================
public:
GLWidget(QWidget* parent = 0, bool fs = false);
~GLWidget();
protected:
/*************************************************************************************************
QGLWidget 类已经内置了对 OpenGL 的处理,就是通过对 initializeGL()、 paintGL()和 resizeGL()这三个函数实现
*************************************************************************************************/
void initializeGL() override; //用来初始化OPenGL窗口,可以在里面设定一些有关选项
void paintGL() override; //用来绘制OPenGL的窗口,只要有更新发生,这个函数就会被调用
void resizeGL(int w, int h) override; //用来处理窗口大小变换这一事件,resizeGL()在处理完后会自动刷新屏幕
void keyPressEvent(QKeyEvent* e) override; //Qt键盘事件处理函数
private:
void setupWorld(); //初始化场景
void readStr(QTextStream *stream, QString &string); //读取顶点信息
void loadTexture(); //加载纹理
private:
bool fullscreen; //用来保存窗口是否处于全屏状态的变量
SECTOR* m_sector;
SECTOR m_sector1; //草地
SECTOR m_sector2; //天空
SECTOR m_sector3; //砖墙
GLfloat m_yrot;
GLfloat m_xpos;
GLfloat m_zpos;
GLfloat m_heading;
GLfloat m_walkbias;
GLfloat m_walkbiasangle;
GLfloat m_lookupdown;
GLuint m_texture[3];
};
#endif // GLWIDGET_H
#include "GLWidget.h"
const float piover180 = 0.0174532925f;
GLWidget::GLWidget(QWidget* parent, bool fs)
: QGLWidget(parent)
{
fullscreen = fs;
m_yrot = 0.0f;
m_xpos = 0.0f;
m_zpos = 0.0f;
m_heading = 0.0f;
m_walkbias = 0.0f;
m_walkbiasangle = 0.0f;
m_lookupdown = 0.0f;
setMinimumSize(1000,1000); //设置窗口大小
setWindowTitle("The first OpenGL Window"); //设置窗口标题
if(fullscreen) {
showFullScreen();
}
}
GLWidget::~GLWidget()
{
}
void GLWidget::initializeGL()
{
loadTexture(); //加载纹理
glEnable(GL_TEXTURE_2D); //使能纹理
glClearColor(0.0, 0.0, 0.0, 0.0); //清除屏幕时所用的颜色,rgba【0.0(最黑)~1.0(最亮)】
glClearDepth(1.0); //设置深度缓存
glDepthFunc(GL_LESS); //所作深度测试的类型
glEnable(GL_DEPTH_TEST); //启动深度测试
glShadeModel(GL_SMOOTH); //启用smooth shading(阴影平滑)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //真正精细的透视修正,告诉OPenGL我们希望进行最好的透视修正,这会十分轻微的影响性能,但使得透视图看起来好一点
setupWorld();
}
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
glLoadIdentity(); //重置当前的模型观察矩阵
GLfloat x_m, y_m, z_m, u_m, v_m; // 顶点的临时 X, Y, Z, U 和 V 的数值
GLfloat xtrans = -m_xpos; // 用于游戏者沿X轴平移时的大小
GLfloat ztrans = -m_zpos; // 用于游戏者沿Z轴平移时的大小
GLfloat ytrans = -m_walkbias-0.25f; // 用于头部的上下摆动
GLfloat sceneroty = 360.0f - m_yrot; // 位于游戏者方向的360度角
int numtriangles; // 保有三角形数量的整数
glRotatef(m_lookupdown, 1.0f, 0,0); // 上下旋转
glRotatef(sceneroty, 0, 1.0f, 0); // 根据游戏者正面所对方向所作的旋转
glTranslatef(xtrans, ytrans, ztrans); // 以游戏者为中心的平移场景
glBindTexture(GL_TEXTURE_2D, m_texture[0]); // 选择的纹理
numtriangles = m_sector1.numtriangles; // 取得Sector1的三角形数量
for (int loop_m = 0; loop_m < numtriangles; loop_m++) // 遍历所有的三角形
{
glBegin(GL_QUADS); // 开始绘制三角形
glNormal3f( 0.0f, 0.0f, 1.0f); // 指向前面的法线
x_m = m_sector1.rect[loop_m].vertex[0].x; // 第一点的 X 分量
y_m = m_sector1.rect[loop_m].vertex[0].y; // 第一点的 Y 分量
z_m = m_sector1.rect[loop_m].vertex[0].z; // 第一点的 Z 分量
u_m = m_sector1.rect[loop_m].vertex[0].u; // 第一点的 U 纹理坐标
v_m = m_sector1.rect[loop_m].vertex[0].v; // 第一点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector1.rect[loop_m].vertex[1].x; // 第二点的 X 分量
y_m = m_sector1.rect[loop_m].vertex[1].y; // 第二点的 Y 分量
z_m = m_sector1.rect[loop_m].vertex[1].z; // 第二点的 Z 分量
u_m = m_sector1.rect[loop_m].vertex[1].u; // 第二点的 U 纹理坐标
v_m = m_sector1.rect[loop_m].vertex[1].v; // 第二点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector1.rect[loop_m].vertex[2].x; // 第三点的 X 分量
y_m = m_sector1.rect[loop_m].vertex[2].y; // 第三点的 Y 分量
z_m = m_sector1.rect[loop_m].vertex[2].z; // 第三点的 Z 分量
u_m = m_sector1.rect[loop_m].vertex[2].u; // 第三点的 U 纹理坐标
v_m = m_sector1.rect[loop_m].vertex[2].v; // 第三点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector1.rect[loop_m].vertex[3].x; // 第三点的 X 分量
y_m = m_sector1.rect[loop_m].vertex[3].y; // 第三点的 Y 分量
z_m = m_sector1.rect[loop_m].vertex[3].z; // 第三点的 Z 分量
u_m = m_sector1.rect[loop_m].vertex[3].u; // 第三点的 U 纹理坐标
v_m = m_sector1.rect[loop_m].vertex[3].v; // 第三点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
glEnd(); // 三角形绘制结束
}
//------------------------------------------------------------------------------------------------
glBindTexture(GL_TEXTURE_2D, m_texture[1]); // 选择的纹理
numtriangles = m_sector2.numtriangles; // 取得Sector1的三角形数量
for (int loop_m = 0; loop_m < numtriangles; loop_m++) // 遍历所有的三角形
{
glBegin(GL_QUADS); // 开始绘制三角形
glNormal3f( 0.0f, 0.0f, 1.0f); // 指向前面的法线
x_m = m_sector2.rect[loop_m].vertex[0].x; // 第一点的 X 分量
y_m = m_sector2.rect[loop_m].vertex[0].y; // 第一点的 Y 分量
z_m = m_sector2.rect[loop_m].vertex[0].z; // 第一点的 Z 分量
u_m = m_sector2.rect[loop_m].vertex[0].u; // 第一点的 U 纹理坐标
v_m = m_sector2.rect[loop_m].vertex[0].v; // 第一点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector2.rect[loop_m].vertex[1].x; // 第二点的 X 分量
y_m = m_sector2.rect[loop_m].vertex[1].y; // 第二点的 Y 分量
z_m = m_sector2.rect[loop_m].vertex[1].z; // 第二点的 Z 分量
u_m = m_sector2.rect[loop_m].vertex[1].u; // 第二点的 U 纹理坐标
v_m = m_sector2.rect[loop_m].vertex[1].v; // 第二点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector2.rect[loop_m].vertex[2].x; // 第三点的 X 分量
y_m = m_sector2.rect[loop_m].vertex[2].y; // 第三点的 Y 分量
z_m = m_sector2.rect[loop_m].vertex[2].z; // 第三点的 Z 分量
u_m = m_sector2.rect[loop_m].vertex[2].u; // 第三点的 U 纹理坐标
v_m = m_sector2.rect[loop_m].vertex[2].v; // 第三点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector2.rect[loop_m].vertex[3].x; // 第三点的 X 分量
y_m = m_sector2.rect[loop_m].vertex[3].y; // 第三点的 Y 分量
z_m = m_sector2.rect[loop_m].vertex[3].z; // 第三点的 Z 分量
u_m = m_sector2.rect[loop_m].vertex[3].u; // 第三点的 U 纹理坐标
v_m = m_sector2.rect[loop_m].vertex[3].v; // 第三点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
glEnd(); // 三角形绘制结束
}
//------------------------------------------------------------------------------------------------
glBindTexture(GL_TEXTURE_2D, m_texture[2]); // 选择的纹理
numtriangles = m_sector3.numtriangles; // 取得Sector1的三角形数量
for (int loop_m = 0; loop_m < numtriangles; loop_m++) // 遍历所有的三角形
{
glBegin(GL_QUADS); // 开始绘制三角形
glNormal3f( 0.0f, 0.0f, 1.0f); // 指向前面的法线
x_m = m_sector3.rect[loop_m].vertex[0].x; // 第一点的 X 分量
y_m = m_sector3.rect[loop_m].vertex[0].y; // 第一点的 Y 分量
z_m = m_sector3.rect[loop_m].vertex[0].z; // 第一点的 Z 分量
u_m = m_sector3.rect[loop_m].vertex[0].u; // 第一点的 U 纹理坐标
v_m = m_sector3.rect[loop_m].vertex[0].v; // 第一点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector3.rect[loop_m].vertex[1].x; // 第二点的 X 分量
y_m = m_sector3.rect[loop_m].vertex[1].y; // 第二点的 Y 分量
z_m = m_sector3.rect[loop_m].vertex[1].z; // 第二点的 Z 分量
u_m = m_sector3.rect[loop_m].vertex[1].u; // 第二点的 U 纹理坐标
v_m = m_sector3.rect[loop_m].vertex[1].v; // 第二点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector3.rect[loop_m].vertex[2].x; // 第三点的 X 分量
y_m = m_sector3.rect[loop_m].vertex[2].y; // 第三点的 Y 分量
z_m = m_sector3.rect[loop_m].vertex[2].z; // 第三点的 Z 分量
u_m = m_sector3.rect[loop_m].vertex[2].u; // 第三点的 U 纹理坐标
v_m = m_sector3.rect[loop_m].vertex[2].v; // 第三点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
x_m = m_sector3.rect[loop_m].vertex[3].x; // 第三点的 X 分量
y_m = m_sector3.rect[loop_m].vertex[3].y; // 第三点的 Y 分量
z_m = m_sector3.rect[loop_m].vertex[3].z; // 第三点的 Z 分量
u_m = m_sector3.rect[loop_m].vertex[3].u; // 第三点的 U 纹理坐标
v_m = m_sector3.rect[loop_m].vertex[3].v; // 第三点的 V 纹理坐标
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 设置纹理坐标和顶点
glEnd(); // 三角形绘制结束
}
}
void GLWidget::resizeGL(int w, int h)
{
if(h == 0) { //防止h为0
h = 1;
}
glViewport(0, 0, (GLint)w, (GLint)h); //重置当前的视口(Viewport)
glMatrixMode(GL_PROJECTION); //选择投影矩阵
glLoadIdentity(); //重置投影矩阵
gluPerspective( 45.0, (GLfloat)w/(GLfloat)h, 0.001, 1000.0 ); //建立透视投影矩阵
glMatrixMode(GL_MODELVIEW); //选择模型观察矩阵
glLoadIdentity(); //重置模型观察矩阵
}
void GLWidget::keyPressEvent(QKeyEvent* e)
{
switch (e->key()) {
case Qt::Key_Q: {
fullscreen = !fullscreen;
if(fullscreen) {
showFullScreen();
}else {
showNormal();
setGeometry(500,500,640,480);
}
updateGL();
break;
}//case Qt::Key_Q
case Qt::Key_Escape: {
close();
}//Qt::Key_Escape
case Qt::Key_PageUp:
{
m_lookupdown-=1.0f;
updateGL();
break;
}
case Qt::Key_PageDown:
{
m_lookupdown+=1.0f;
updateGL();
break;
}
case Qt::Key_Right:
{
m_heading -=1.0f;
m_yrot = m_heading; // 向左旋转场景
updateGL();
break;
}
case Qt::Key_Left:
{
m_heading += 1.0f;
m_yrot = m_heading; // 向右侧旋转场景
updateGL();
break;
}
case Qt::Key_Up:
{
m_xpos -= (float)sin(m_heading*piover180) * 0.05f; // 沿游戏者所在的X平面移动
m_zpos -= (float)cos(m_heading*piover180) * 0.05f; // 沿游戏者所在的Z平面移动
updateGL();
break;
}
case Qt::Key_Down:
{
m_xpos += (float)sin(m_heading*piover180) * 0.05f; // 沿游戏者所在的X平面移动
m_zpos += (float)cos(m_heading*piover180) * 0.05f; // 沿游戏者所在的Z平面移动
updateGL();
break;
}
}//switch (e->key())
}
void GLWidget::setupWorld()
{
QFile file(":/world/World1.txt");
if(!file.open(QIODevice::ReadOnly))
{
QMessageBox::warning(this, tr("Warning"), tr("Can't open world file."));
return;
}
QTextStream stream(&file);
//我们对区段进行初始化,并读入部分数据
QString oneline; // 存储数据的字符串
int numtriangles; // 区段的三角形数量
float x, y, z, u, v; // 3D 和 纹理坐标
//------------------------------------------------------------------------------------------------
readStr(&stream, oneline); // 读入一行数据
sscanf(oneline.toLatin1().data(), "grass %d\n", &numtriangles); // 读入三角形数量
m_sector1.rect = new RECT[numtriangles]; // 为numtriangles个三角形分配内存并设定指针
m_sector1.numtriangles = numtriangles; // 定义区段1中的三角形数量
// 遍历区段中的每个三角形
for (int triloop = 0; triloop < numtriangles; triloop++) // 遍历所有的三角形
{
// 遍历三角形的每个顶点
for (int vertloop = 0; vertloop < 4; vertloop++) // 遍历所有的顶点
{
readStr(&stream, oneline); // 读入一行数据
// 读入各自的顶点数据
sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
// 将顶点数据存入各自的顶点
m_sector1.rect[triloop].vertex[vertloop].x = x; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 x=x
m_sector1.rect[triloop].vertex[vertloop].y = y; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 y=y
m_sector1.rect[triloop].vertex[vertloop].z = z; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 z=z
m_sector1.rect[triloop].vertex[vertloop].u = u; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 u=u
m_sector1.rect[triloop].vertex[vertloop].v = v; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 v=v
}
}
//------------------------------------------------------------------------------------------------
readStr(&stream, oneline); // 读入一行数据
sscanf(oneline.toLatin1().data(), "sky %d\n", &numtriangles); // 读入三角形数量
m_sector2.rect = new RECT[numtriangles]; // 为numtriangles个三角形分配内存并设定指针
m_sector2.numtriangles = numtriangles; // 定义区段1中的三角形数量
// 遍历区段中的每个三角形
for (int triloop = 0; triloop < numtriangles; triloop++) // 遍历所有的三角形
{
// 遍历三角形的每个顶点
for (int vertloop = 0; vertloop < 4; vertloop++) // 遍历所有的顶点
{
readStr(&stream, oneline); // 读入一行数据
// 读入各自的顶点数据
sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
// 将顶点数据存入各自的顶点
m_sector2.rect[triloop].vertex[vertloop].x = x; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 x=x
m_sector2.rect[triloop].vertex[vertloop].y = y; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 y=y
m_sector2.rect[triloop].vertex[vertloop].z = z; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 z=z
m_sector2.rect[triloop].vertex[vertloop].u = u; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 u=u
m_sector2.rect[triloop].vertex[vertloop].v = v; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 v=v
}
}
//------------------------------------------------------------------------------------------------
readStr(&stream, oneline); // 读入一行数据
sscanf(oneline.toLatin1().data(), "floor %d\n", &numtriangles); // 读入三角形数量
m_sector3.rect = new RECT[numtriangles]; // 为numtriangles个三角形分配内存并设定指针
m_sector3.numtriangles = numtriangles; // 定义区段1中的三角形数量
// 遍历区段中的每个三角形
for (int triloop = 0; triloop < numtriangles; triloop++) // 遍历所有的三角形
{
// 遍历三角形的每个顶点
for (int vertloop = 0; vertloop < 4; vertloop++) // 遍历所有的顶点
{
readStr(&stream, oneline); // 读入一行数据
// 读入各自的顶点数据
sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
// 将顶点数据存入各自的顶点
m_sector3.rect[triloop].vertex[vertloop].x = x; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 x=x
m_sector3.rect[triloop].vertex[vertloop].y = y; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 y=y
m_sector3.rect[triloop].vertex[vertloop].z = z; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 z=z
m_sector3.rect[triloop].vertex[vertloop].u = u; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 u=u
m_sector3.rect[triloop].vertex[vertloop].v = v; // 区段 1, 第 triloop 个三角形, 第 vertloop 个顶点, 值 v=v
}
}
//------------------------------------------------------------------------------------------------
file.close();
}
//读取World.txt中的有效数据行
void GLWidget::readStr(QTextStream *stream, QString &string)
{
do // 循环开始
{
string = stream->readLine();
} while (string[0] == '/' || string[0] == '\n' || string.isEmpty()); // 考察是否有必要进行处理
}
//加载纹理
void GLWidget::loadTexture()
{
glGenTextures(1, &m_texture[0]);// 创建纹理
QImage image1(":/Images/grass.bmp");
image1 = image1.convertToFormat(QImage::Format_RGB888);
image1 = image1.mirrored();
// Create Nearest Filtered Texture
glBindTexture(GL_TEXTURE_2D, m_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, 3, image1.width(), image1.height(),
0, GL_RGB, GL_UNSIGNED_BYTE, image1.bits());
QImage image2(":/Images/sky.bmp");
image2 = image2.convertToFormat(QImage::Format_RGB888);
image2 = image2.mirrored();
// Create Nearest Filtered Texture
glBindTexture(GL_TEXTURE_2D, m_texture[1]);
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, 3, image2.width(), image2.height(),
0, GL_RGB, GL_UNSIGNED_BYTE, image2.bits());
QImage image3(":/Images/floor.bmp");
image3 = image3.convertToFormat(QImage::Format_RGB888);
image3 = image3.mirrored();
// Create Nearest Filtered Texture
glBindTexture(GL_TEXTURE_2D, m_texture[2]);
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, 3, image3.width(), image3.height(),
0, GL_RGB, GL_UNSIGNED_BYTE, image3.bits());
}
到了这里,关于OPenGL笔记--创建一个3D场景的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!