一、前言
通過前面的學(xué)習(xí),基本掌握了怎么繪制圖形,使用紋理,接下來就來創(chuàng)建一個(gè)3D場(chǎng)景。
基本原理
一個(gè)復(fù)雜的場(chǎng)景肯定是由一些簡(jiǎn)單的圖形,通過某種組合方式構(gòu)建起來的,在OPenGL中也不例外;例如:在繪制立方體的時(shí)候,立方體也是由6個(gè)正方形圍起來的;
基本圖形
由于顯卡在渲染三角形時(shí)效率較高,所以我們采用三角形來構(gòu)建復(fù)雜的3D場(chǎng)景;
數(shù)據(jù)結(jié)構(gòu)
- 當(dāng)您想要使用一系列的數(shù)字來完美的表達(dá)3D環(huán)境時(shí),隨著環(huán)境復(fù)雜度的上升,這個(gè)工作的難度也會(huì)隨之上升;
- 出于這個(gè)原因,我們必須將數(shù)據(jù)歸類,使其具有更多的可操作性風(fēng)格,在程序中添加sector結(jié)構(gòu)體(區(qū)段)的定義;
- 每個(gè)3D世界基本上可以看作是sector(區(qū)段)的集合,一個(gè)sector(區(qū)段)可以是一個(gè)房間、一個(gè)立方體、或者任意一個(gè)閉合的區(qū)間;
typedef struct tagVERTEX //三角形的頂點(diǎn)
{
float x, y, z; // 3D 坐標(biāo)
float u, v; // 紋理坐標(biāo)
} VERTEX;
typedef struct tagTRIANGLE //三角形
{
VERTEX vertex[3]; // VERTEX矢量數(shù)組,大小為3
}TRIANGLE;
typedef struct tagSECTOR //Sector區(qū)段結(jié)構(gòu)(三角形集合)
{
int numtriangles; // Sector中的三角形個(gè)數(shù)
TRIANGLE* triangle; // 指向三角數(shù)組的指針
} SECTOR;
數(shù)據(jù)加載
在程序內(nèi)部直接存儲(chǔ)數(shù)據(jù)會(huì)讓程序顯得太過死板和無趣。從磁盤上載入世界資料,會(huì)給我們帶來更多的彈性,可以讓我們體驗(yàn)不同的世界,而不用被迫重新編譯程序。另一個(gè)好處就是用戶可以切換世界資料并修改它們而無需知道程序如何讀入輸出這些資料的。數(shù)據(jù)文件的類型我們準(zhǔn)備使用文本格式(txt)。這樣編輯起來更容易,寫的代碼也更少。
二、效果展示
三、詳細(xì)流程
首先,不懂怎么創(chuàng)建OPenGL窗口的,可以參考:OPenGL筆記–創(chuàng)建一個(gè)OPenGL窗口
在窗口的基礎(chǔ)上,我們?cè)诤瘮?shù)paintGL()
中繪制我們的3D世界(其實(shí)就是一個(gè)貼了紋理的盒子);
之前的教程里我們都是直接在函數(shù)paintGL()
中繪制,這里我們通過加載World.txt中的數(shù)據(jù)來繪制圖形;
這個(gè)World.txt是一個(gè)描述一堆三角形信息的文本文件,眾所周知,一個(gè)三角形由三個(gè)點(diǎn)構(gòu)成,紋理有兩個(gè)坐標(biāo),所以:
在World.txt中,采用(x, y, z, u, v)的形式來描述一個(gè)三角形頂點(diǎn),例如:
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
記錄的是此文本文件中描述三角形的個(gè)數(shù),每3個(gè)點(diǎn)(對(duì)應(yīng)文本文件中的三行)組成一個(gè)三角形;
3.1、World.txt文件規(guī)則
World.txt中第一行表示文本文件中描述的三角形個(gè)數(shù):NUMPOLLIES 12
接下來,例如,我們搭建的是一個(gè)盒子,我們可以將其拆分為6個(gè)面:上、下、左、右、前、后
基本格式如下
NUMPOLLIES 12
//上
一個(gè)三角形
一個(gè)三角形
//下
一個(gè)三角形
一個(gè)三角形
//左
一個(gè)三角形
一個(gè)三角形
//右
一個(gè)三角形
一個(gè)三角形
//前
一個(gè)三角形
一個(gè)三角形
//后
一個(gè)三角形
一個(gè)三角形
例如,我們準(zhǔn)備搭建一個(gè)661的盒子,以x-z平面為底,以原點(diǎn)為底部中心,如下圖所示:
以底面為例進(jìn)行講解,我們將底面正方形劃分為兩個(gè)三角形(怎么劃分不重要),分別是上三角和下三角
上三角:
(-3, 0, -3)
(3, 0, -3)
(-3, 0, 3)
下三角:
(3, 0, 3)
(3, 0, -3)
(-3, 0, 3)
現(xiàn)在確定了三角形的坐標(biāo),那紋理坐標(biāo)怎么確定呢?紋理坐標(biāo)通常在0~1之間(但是紋理坐標(biāo)如果超過1就會(huì)復(fù)制)
以上三角為例:以直角為紋理坐標(biāo)系原點(diǎn),直角邊為紋理坐標(biāo)系坐標(biāo)軸,紋理坐標(biāo)X、Y軸需要垂直世界坐標(biāo)系軸X、Y軸,如下圖示:
所以可以得出上三角的紋理坐標(biāo)為:
(0, 0)
(0, 6)
(6, 0)
三角形坐標(biāo)和紋理坐標(biāo)合起來就得到了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()
函數(shù)來加載World.txt;
- 將讀取的三角形個(gè)數(shù)存儲(chǔ)到區(qū)段結(jié)構(gòu)體數(shù)組
m_sector1
中; - 將三角形信息存儲(chǔ)到區(qū)段結(jié)構(gòu)體數(shù)組
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);
//我們對(duì)區(qū)段進(jìn)行初始化,并讀入部分?jǐn)?shù)據(jù)
QString oneline; // 存儲(chǔ)數(shù)據(jù)的字符串
int numtriangles; // 區(qū)段的三角形數(shù)量
float x, y, z, u, v; // 3D 和 紋理坐標(biāo)
readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
sscanf(oneline.toLatin1().data(), "NUMPOLLIES %d\n", &numtriangles); // 讀入三角形數(shù)量
m_sector1.triangle = new TRIANGLE[numtriangles]; // 為numtriangles個(gè)三角形分配內(nèi)存并設(shè)定指針
m_sector1.numtriangles = numtriangles; // 定義區(qū)段1中的三角形數(shù)量
// 遍歷區(qū)段中的每個(gè)三角形
for (int triloop = 0; triloop < numtriangles; triloop++) // 遍歷所有的三角形
{
// 遍歷三角形的每個(gè)頂點(diǎn)
for (int vertloop = 0; vertloop < 3; vertloop++) // 遍歷所有的頂點(diǎn)
{
readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
// 讀入各自的頂點(diǎn)數(shù)據(jù)
sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
// 將頂點(diǎn)數(shù)據(jù)存入各自的頂點(diǎn)
m_sector1.triangle[triloop].vertex[vertloop].x = x; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 x=x
m_sector1.triangle[triloop].vertex[vertloop].y = y; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 y=y
m_sector1.triangle[triloop].vertex[vertloop].z = z; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 z=z
m_sector1.triangle[triloop].vertex[vertloop].u = u; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 u=u
m_sector1.triangle[triloop].vertex[vertloop].v = v; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 v=v
}
}
//數(shù)據(jù)文件中每個(gè)三角形都以如下形式聲明:
//X1 Y1 Z1 U1 V1
//X2 Y2 Z2 U2 V2
//X3 Y3 Z3 U3 V3
file.close();
}
//讀取World.txt中的有效數(shù)據(jù)行
void GLWidget::readStr(QTextStream *stream, QString &string)
{
do // 循環(huán)開始
{
string = stream->readLine();
} while (string[0] == '/' || string[0] == '\n' || string.isEmpty()); // 考察是否有必要進(jìn)行處理
}
3.3、繪制場(chǎng)景
在加載玩World.txt到區(qū)段結(jié)構(gòu)體數(shù)組m_sector1
中之后,在paintGL()中繪制;
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度緩存
glLoadIdentity(); //重置當(dāng)前的模型觀察矩陣
GLfloat x_m, y_m, z_m, u_m, v_m; // 頂點(diǎn)的臨時(shí) X, Y, Z, U 和 V 的數(shù)值
GLfloat xtrans = -m_xpos; // 用于游戲者沿X軸平移時(shí)的大小
GLfloat ztrans = -m_zpos; // 用于游戲者沿Z軸平移時(shí)的大小
GLfloat ytrans = -m_walkbias-0.25f; // 用于頭部的上下擺動(dòng)
GLfloat sceneroty = 360.0f - m_yrot; // 位于游戲者方向的360度角
int numtriangles; // 保有三角形數(shù)量的整數(shù)
glRotatef(m_lookupdown, 1.0f, 0,0); // 上下旋轉(zhuǎn)
glRotatef(sceneroty, 0, 1.0f, 0); // 根據(jù)游戲者正面所對(duì)方向所作的旋轉(zhuǎn)
glTranslatef(xtrans, ytrans, ztrans); // 以游戲者為中心的平移場(chǎng)景
glBindTexture(GL_TEXTURE_2D, m_texture[0]); // 選擇的紋理
numtriangles = m_sector1.numtriangles; // 取得Sector1的三角形數(shù)量
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; // 第一點(diǎn)的 X 分量
y_m = m_sector1.triangle[loop_m].vertex[0].y; // 第一點(diǎn)的 Y 分量
z_m = m_sector1.triangle[loop_m].vertex[0].z; // 第一點(diǎn)的 Z 分量
u_m = m_sector1.triangle[loop_m].vertex[0].u; // 第一點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector1.triangle[loop_m].vertex[0].v; // 第一點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector1.triangle[loop_m].vertex[1].x; // 第二點(diǎn)的 X 分量
y_m = m_sector1.triangle[loop_m].vertex[1].y; // 第二點(diǎn)的 Y 分量
z_m = m_sector1.triangle[loop_m].vertex[1].z; // 第二點(diǎn)的 Z 分量
u_m = m_sector1.triangle[loop_m].vertex[1].u; // 第二點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector1.triangle[loop_m].vertex[1].v; // 第二點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector1.triangle[loop_m].vertex[2].x; // 第三點(diǎn)的 X 分量
y_m = m_sector1.triangle[loop_m].vertex[2].y; // 第三點(diǎn)的 Y 分量
z_m = m_sector1.triangle[loop_m].vertex[2].z; // 第三點(diǎn)的 Z 分量
u_m = m_sector1.triangle[loop_m].vertex[2].u; // 第三點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector1.triangle[loop_m].vertex[2].v; // 第三點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
glEnd(); // 三角形繪制結(jié)束
}
}
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; // 向左旋轉(zhuǎn)場(chǎng)景
updateGL();
break;
}
case Qt::Key_Left:
{
m_heading += 1.0f;
m_yrot = m_heading; // 向右側(cè)旋轉(zhuǎn)場(chǎng)景
updateGL();
break;
}
case Qt::Key_Up:
{
m_xpos -= (float)sin(m_heading*piover180) * 0.05f; // 沿游戲者所在的X平面移動(dòng)
m_zpos -= (float)cos(m_heading*piover180) * 0.05f; // 沿游戲者所在的Z平面移動(dòng)
updateGL();
break;
}
case Qt::Key_Down:
{
m_xpos += (float)sin(m_heading*piover180) * 0.05f; // 沿游戲者所在的X平面移動(dòng)
m_zpos += (float)cos(m_heading*piover180) * 0.05f; // 沿游戲者所在的Z平面移動(dòng)
updateGL();
break;
}
}//switch (e->key())
}
四、詳細(xì)代碼
素材
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:
//場(chǎng)景描述結(jié)構(gòu)體
//==================================================================
typedef struct tagVERTEX // 創(chuàng)建頂點(diǎn)結(jié)構(gòu)
{
float x, y, z; // 3D 坐標(biāo)
float u, v; // 紋理坐標(biāo)
} VERTEX;
typedef struct tagTRIANGLE // 創(chuàng)建三角形結(jié)構(gòu)
{
VERTEX vertex[3]; // VERTEX矢量數(shù)組,大小為3
}TRIANGLE;// 命名為 TRIANGLE
typedef struct tagSECTO // 創(chuàng)建Sector區(qū)段結(jié)構(gòu)
{
int numtriangles; // Sector中的三角形個(gè)數(shù)
TRIANGLE* triangle; // 指向三角數(shù)組的指針
} SECTOR;
//==================================================================
public:
GLWidget(QWidget* parent = 0, bool fs = false);
~GLWidget();
protected:
/*************************************************************************************************
QGLWidget 類已經(jīng)內(nèi)置了對(duì) OpenGL 的處理,就是通過對(duì) initializeGL()、 paintGL()和 resizeGL()這三個(gè)函數(shù)實(shí)現(xiàn)
*************************************************************************************************/
void initializeGL() override; //用來初始化OPenGL窗口,可以在里面設(shè)定一些有關(guān)選項(xiàng)
void paintGL() override; //用來繪制OPenGL的窗口,只要有更新發(fā)生,這個(gè)函數(shù)就會(huì)被調(diào)用
void resizeGL(int w, int h) override; //用來處理窗口大小變換這一事件,resizeGL()在處理完后會(huì)自動(dòng)刷新屏幕
void keyPressEvent(QKeyEvent* e) override; //Qt鍵盤事件處理函數(shù)
private:
void setupWorld(); //初始化場(chǎng)景
void readStr(QTextStream *stream, QString &string); //讀取頂點(diǎn)信息
void loadTexture(); //加載紋理
private:
bool fullscreen; //用來保存窗口是否處于全屏狀態(tài)的變量
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); //設(shè)置窗口大小
setWindowTitle("The first OpenGL Window"); //設(shè)置窗口標(biāo)題
if(fullscreen) {
showFullScreen();
}
}
GLWidget::~GLWidget()
{
}
void GLWidget::initializeGL()
{
loadTexture(); //加載紋理
glEnable(GL_TEXTURE_2D); //使能紋理
glClearColor(0.0, 0.0, 0.0, 0.0); //清除屏幕時(shí)所用的顏色,rgba【0.0(最黑)~1.0(最亮)】
glClearDepth(1.0); //設(shè)置深度緩存
glDepthFunc(GL_LESS); //所作深度測(cè)試的類型
glEnable(GL_DEPTH_TEST); //啟動(dòng)深度測(cè)試
glShadeModel(GL_SMOOTH); //啟用smooth shading(陰影平滑)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //真正精細(xì)的透視修正,告訴OPenGL我們希望進(jìn)行最好的透視修正,這會(huì)十分輕微的影響性能,但使得透視圖看起來好一點(diǎn)
setupWorld();
}
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度緩存
glLoadIdentity(); //重置當(dāng)前的模型觀察矩陣
GLfloat x_m, y_m, z_m, u_m, v_m; // 頂點(diǎn)的臨時(shí) X, Y, Z, U 和 V 的數(shù)值
GLfloat xtrans = -m_xpos; // 用于游戲者沿X軸平移時(shí)的大小
GLfloat ztrans = -m_zpos; // 用于游戲者沿Z軸平移時(shí)的大小
GLfloat ytrans = -m_walkbias-0.25f; // 用于頭部的上下擺動(dòng)
GLfloat sceneroty = 360.0f - m_yrot; // 位于游戲者方向的360度角
int numtriangles; // 保有三角形數(shù)量的整數(shù)
glRotatef(m_lookupdown, 1.0f, 0,0); // 上下旋轉(zhuǎn)
glRotatef(sceneroty, 0, 1.0f, 0); // 根據(jù)游戲者正面所對(duì)方向所作的旋轉(zhuǎn)
glTranslatef(xtrans, ytrans, ztrans); // 以游戲者為中心的平移場(chǎng)景
glBindTexture(GL_TEXTURE_2D, m_texture[0]); // 選擇的紋理
numtriangles = m_sector1.numtriangles; // 取得Sector1的三角形數(shù)量
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; // 第一點(diǎn)的 X 分量
y_m = m_sector1.triangle[loop_m].vertex[0].y; // 第一點(diǎn)的 Y 分量
z_m = m_sector1.triangle[loop_m].vertex[0].z; // 第一點(diǎn)的 Z 分量
u_m = m_sector1.triangle[loop_m].vertex[0].u; // 第一點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector1.triangle[loop_m].vertex[0].v; // 第一點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector1.triangle[loop_m].vertex[1].x; // 第二點(diǎn)的 X 分量
y_m = m_sector1.triangle[loop_m].vertex[1].y; // 第二點(diǎn)的 Y 分量
z_m = m_sector1.triangle[loop_m].vertex[1].z; // 第二點(diǎn)的 Z 分量
u_m = m_sector1.triangle[loop_m].vertex[1].u; // 第二點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector1.triangle[loop_m].vertex[1].v; // 第二點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector1.triangle[loop_m].vertex[2].x; // 第三點(diǎn)的 X 分量
y_m = m_sector1.triangle[loop_m].vertex[2].y; // 第三點(diǎn)的 Y 分量
z_m = m_sector1.triangle[loop_m].vertex[2].z; // 第三點(diǎn)的 Z 分量
u_m = m_sector1.triangle[loop_m].vertex[2].u; // 第三點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector1.triangle[loop_m].vertex[2].v; // 第三點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
glEnd(); // 三角形繪制結(jié)束
}
}
void GLWidget::resizeGL(int w, int h)
{
if(h == 0) { //防止h為0
h = 1;
}
glViewport(0, 0, (GLint)w, (GLint)h); //重置當(dāng)前的視口(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; // 向左旋轉(zhuǎn)場(chǎng)景
updateGL();
break;
}
case Qt::Key_Left:
{
m_heading += 1.0f;
m_yrot = m_heading; // 向右側(cè)旋轉(zhuǎn)場(chǎng)景
updateGL();
break;
}
case Qt::Key_Up:
{
m_xpos -= (float)sin(m_heading*piover180) * 0.05f; // 沿游戲者所在的X平面移動(dòng)
m_zpos -= (float)cos(m_heading*piover180) * 0.05f; // 沿游戲者所在的Z平面移動(dòng)
updateGL();
break;
}
case Qt::Key_Down:
{
m_xpos += (float)sin(m_heading*piover180) * 0.05f; // 沿游戲者所在的X平面移動(dòng)
m_zpos += (float)cos(m_heading*piover180) * 0.05f; // 沿游戲者所在的Z平面移動(dòng)
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);
//我們對(duì)區(qū)段進(jìn)行初始化,并讀入部分?jǐn)?shù)據(jù)
QString oneline; // 存儲(chǔ)數(shù)據(jù)的字符串
int numtriangles; // 區(qū)段的三角形數(shù)量
float x, y, z, u, v; // 3D 和 紋理坐標(biāo)
readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
sscanf(oneline.toLatin1().data(), "NUMPOLLIES %d\n", &numtriangles); // 讀入三角形數(shù)量
m_sector1.triangle = new TRIANGLE[numtriangles]; // 為numtriangles個(gè)三角形分配內(nèi)存并設(shè)定指針
m_sector1.numtriangles = numtriangles; // 定義區(qū)段1中的三角形數(shù)量
// 遍歷區(qū)段中的每個(gè)三角形
for (int triloop = 0; triloop < numtriangles; triloop++) // 遍歷所有的三角形
{
// 遍歷三角形的每個(gè)頂點(diǎn)
for (int vertloop = 0; vertloop < 3; vertloop++) // 遍歷所有的頂點(diǎn)
{
readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
// 讀入各自的頂點(diǎn)數(shù)據(jù)
sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
// 將頂點(diǎn)數(shù)據(jù)存入各自的頂點(diǎn)
m_sector1.triangle[triloop].vertex[vertloop].x = x; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 x=x
m_sector1.triangle[triloop].vertex[vertloop].y = y; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 y=y
m_sector1.triangle[triloop].vertex[vertloop].z = z; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 z=z
m_sector1.triangle[triloop].vertex[vertloop].u = u; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 u=u
m_sector1.triangle[triloop].vertex[vertloop].v = v; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 v=v
}
}
//數(shù)據(jù)文件中每個(gè)三角形都以如下形式聲明:
//X1 Y1 Z1 U1 V1
//X2 Y2 Z2 U2 V2
//X3 Y3 Z3 U3 V3
file.close();
}
//讀取World.txt中的有效數(shù)據(jù)行
void GLWidget::readStr(QTextStream *stream, QString &string)
{
do // 循環(huán)開始
{
string = stream->readLine();
} while (string[0] == '/' || string[0] == '\n' || string.isEmpty()); // 考察是否有必要進(jìn)行處理
}
//加載紋理
void GLWidget::loadTexture()
{
QImage image(":/Images/Crate.bmp");
image = image.convertToFormat(QImage::Format_RGB888);
image = image.mirrored();
glGenTextures(1, &m_texture[0]);// 創(chuàng)建紋理
// 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());
}
五、舉一反三
通過上面的流程,我們創(chuàng)建了一個(gè)小房間,它使用的紋理都是一樣的,接下來我們通過使用不同的紋理,來創(chuàng)建一個(gè)感官更加豐富的場(chǎng)景;
效果展示
上面使用的是三角形,我們也可以使用矩形來構(gòu)建:
素材
文章來源:http://www.zghlxwxcb.cn/news/detail-512619.html
World1.txt文章來源地址http://www.zghlxwxcb.cn/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:
//場(chǎng)景描述結(jié)構(gòu)體
//==================================================================
typedef struct tagVERTEX // 創(chuàng)建頂點(diǎn)結(jié)構(gòu)
{
float x, y, z; // 3D 坐標(biāo)
float u, v; // 紋理坐標(biāo)
} VERTEX;
typedef struct tagTRIANGLE // 創(chuàng)建三角形結(jié)構(gòu)
{
VERTEX vertex[3]; // VERTEX矢量數(shù)組,大小為3
}TRIANGLE;// 命名為 TRIANGLE
typedef struct tagRECT // 創(chuàng)建四邊形結(jié)構(gòu)
{
VERTEX vertex[4]; // VERTEX矢量數(shù)組,大小為4
}RECT;// 命名為 RECT
typedef struct tagSECTO // 創(chuàng)建Sector區(qū)段結(jié)構(gòu)
{
int numtriangles; // Sector中的三角形個(gè)數(shù)
TRIANGLE* triangle; // 指向三角數(shù)組的指針
RECT* rect;
} SECTOR;
//==================================================================
public:
GLWidget(QWidget* parent = 0, bool fs = false);
~GLWidget();
protected:
/*************************************************************************************************
QGLWidget 類已經(jīng)內(nèi)置了對(duì) OpenGL 的處理,就是通過對(duì) initializeGL()、 paintGL()和 resizeGL()這三個(gè)函數(shù)實(shí)現(xiàn)
*************************************************************************************************/
void initializeGL() override; //用來初始化OPenGL窗口,可以在里面設(shè)定一些有關(guān)選項(xiàng)
void paintGL() override; //用來繪制OPenGL的窗口,只要有更新發(fā)生,這個(gè)函數(shù)就會(huì)被調(diào)用
void resizeGL(int w, int h) override; //用來處理窗口大小變換這一事件,resizeGL()在處理完后會(huì)自動(dòng)刷新屏幕
void keyPressEvent(QKeyEvent* e) override; //Qt鍵盤事件處理函數(shù)
private:
void setupWorld(); //初始化場(chǎng)景
void readStr(QTextStream *stream, QString &string); //讀取頂點(diǎn)信息
void loadTexture(); //加載紋理
private:
bool fullscreen; //用來保存窗口是否處于全屏狀態(tài)的變量
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); //設(shè)置窗口大小
setWindowTitle("The first OpenGL Window"); //設(shè)置窗口標(biāo)題
if(fullscreen) {
showFullScreen();
}
}
GLWidget::~GLWidget()
{
}
void GLWidget::initializeGL()
{
loadTexture(); //加載紋理
glEnable(GL_TEXTURE_2D); //使能紋理
glClearColor(0.0, 0.0, 0.0, 0.0); //清除屏幕時(shí)所用的顏色,rgba【0.0(最黑)~1.0(最亮)】
glClearDepth(1.0); //設(shè)置深度緩存
glDepthFunc(GL_LESS); //所作深度測(cè)試的類型
glEnable(GL_DEPTH_TEST); //啟動(dòng)深度測(cè)試
glShadeModel(GL_SMOOTH); //啟用smooth shading(陰影平滑)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //真正精細(xì)的透視修正,告訴OPenGL我們希望進(jìn)行最好的透視修正,這會(huì)十分輕微的影響性能,但使得透視圖看起來好一點(diǎn)
setupWorld();
}
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度緩存
glLoadIdentity(); //重置當(dāng)前的模型觀察矩陣
GLfloat x_m, y_m, z_m, u_m, v_m; // 頂點(diǎn)的臨時(shí) X, Y, Z, U 和 V 的數(shù)值
GLfloat xtrans = -m_xpos; // 用于游戲者沿X軸平移時(shí)的大小
GLfloat ztrans = -m_zpos; // 用于游戲者沿Z軸平移時(shí)的大小
GLfloat ytrans = -m_walkbias-0.25f; // 用于頭部的上下擺動(dòng)
GLfloat sceneroty = 360.0f - m_yrot; // 位于游戲者方向的360度角
int numtriangles; // 保有三角形數(shù)量的整數(shù)
glRotatef(m_lookupdown, 1.0f, 0,0); // 上下旋轉(zhuǎn)
glRotatef(sceneroty, 0, 1.0f, 0); // 根據(jù)游戲者正面所對(duì)方向所作的旋轉(zhuǎn)
glTranslatef(xtrans, ytrans, ztrans); // 以游戲者為中心的平移場(chǎng)景
glBindTexture(GL_TEXTURE_2D, m_texture[0]); // 選擇的紋理
numtriangles = m_sector1.numtriangles; // 取得Sector1的三角形數(shù)量
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; // 第一點(diǎn)的 X 分量
y_m = m_sector1.rect[loop_m].vertex[0].y; // 第一點(diǎn)的 Y 分量
z_m = m_sector1.rect[loop_m].vertex[0].z; // 第一點(diǎn)的 Z 分量
u_m = m_sector1.rect[loop_m].vertex[0].u; // 第一點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector1.rect[loop_m].vertex[0].v; // 第一點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector1.rect[loop_m].vertex[1].x; // 第二點(diǎn)的 X 分量
y_m = m_sector1.rect[loop_m].vertex[1].y; // 第二點(diǎn)的 Y 分量
z_m = m_sector1.rect[loop_m].vertex[1].z; // 第二點(diǎn)的 Z 分量
u_m = m_sector1.rect[loop_m].vertex[1].u; // 第二點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector1.rect[loop_m].vertex[1].v; // 第二點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector1.rect[loop_m].vertex[2].x; // 第三點(diǎn)的 X 分量
y_m = m_sector1.rect[loop_m].vertex[2].y; // 第三點(diǎn)的 Y 分量
z_m = m_sector1.rect[loop_m].vertex[2].z; // 第三點(diǎn)的 Z 分量
u_m = m_sector1.rect[loop_m].vertex[2].u; // 第三點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector1.rect[loop_m].vertex[2].v; // 第三點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector1.rect[loop_m].vertex[3].x; // 第三點(diǎn)的 X 分量
y_m = m_sector1.rect[loop_m].vertex[3].y; // 第三點(diǎn)的 Y 分量
z_m = m_sector1.rect[loop_m].vertex[3].z; // 第三點(diǎn)的 Z 分量
u_m = m_sector1.rect[loop_m].vertex[3].u; // 第三點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector1.rect[loop_m].vertex[3].v; // 第三點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
glEnd(); // 三角形繪制結(jié)束
}
//------------------------------------------------------------------------------------------------
glBindTexture(GL_TEXTURE_2D, m_texture[1]); // 選擇的紋理
numtriangles = m_sector2.numtriangles; // 取得Sector1的三角形數(shù)量
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; // 第一點(diǎn)的 X 分量
y_m = m_sector2.rect[loop_m].vertex[0].y; // 第一點(diǎn)的 Y 分量
z_m = m_sector2.rect[loop_m].vertex[0].z; // 第一點(diǎn)的 Z 分量
u_m = m_sector2.rect[loop_m].vertex[0].u; // 第一點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector2.rect[loop_m].vertex[0].v; // 第一點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector2.rect[loop_m].vertex[1].x; // 第二點(diǎn)的 X 分量
y_m = m_sector2.rect[loop_m].vertex[1].y; // 第二點(diǎn)的 Y 分量
z_m = m_sector2.rect[loop_m].vertex[1].z; // 第二點(diǎn)的 Z 分量
u_m = m_sector2.rect[loop_m].vertex[1].u; // 第二點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector2.rect[loop_m].vertex[1].v; // 第二點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector2.rect[loop_m].vertex[2].x; // 第三點(diǎn)的 X 分量
y_m = m_sector2.rect[loop_m].vertex[2].y; // 第三點(diǎn)的 Y 分量
z_m = m_sector2.rect[loop_m].vertex[2].z; // 第三點(diǎn)的 Z 分量
u_m = m_sector2.rect[loop_m].vertex[2].u; // 第三點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector2.rect[loop_m].vertex[2].v; // 第三點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector2.rect[loop_m].vertex[3].x; // 第三點(diǎn)的 X 分量
y_m = m_sector2.rect[loop_m].vertex[3].y; // 第三點(diǎn)的 Y 分量
z_m = m_sector2.rect[loop_m].vertex[3].z; // 第三點(diǎn)的 Z 分量
u_m = m_sector2.rect[loop_m].vertex[3].u; // 第三點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector2.rect[loop_m].vertex[3].v; // 第三點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
glEnd(); // 三角形繪制結(jié)束
}
//------------------------------------------------------------------------------------------------
glBindTexture(GL_TEXTURE_2D, m_texture[2]); // 選擇的紋理
numtriangles = m_sector3.numtriangles; // 取得Sector1的三角形數(shù)量
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; // 第一點(diǎn)的 X 分量
y_m = m_sector3.rect[loop_m].vertex[0].y; // 第一點(diǎn)的 Y 分量
z_m = m_sector3.rect[loop_m].vertex[0].z; // 第一點(diǎn)的 Z 分量
u_m = m_sector3.rect[loop_m].vertex[0].u; // 第一點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector3.rect[loop_m].vertex[0].v; // 第一點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector3.rect[loop_m].vertex[1].x; // 第二點(diǎn)的 X 分量
y_m = m_sector3.rect[loop_m].vertex[1].y; // 第二點(diǎn)的 Y 分量
z_m = m_sector3.rect[loop_m].vertex[1].z; // 第二點(diǎn)的 Z 分量
u_m = m_sector3.rect[loop_m].vertex[1].u; // 第二點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector3.rect[loop_m].vertex[1].v; // 第二點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector3.rect[loop_m].vertex[2].x; // 第三點(diǎn)的 X 分量
y_m = m_sector3.rect[loop_m].vertex[2].y; // 第三點(diǎn)的 Y 分量
z_m = m_sector3.rect[loop_m].vertex[2].z; // 第三點(diǎn)的 Z 分量
u_m = m_sector3.rect[loop_m].vertex[2].u; // 第三點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector3.rect[loop_m].vertex[2].v; // 第三點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
x_m = m_sector3.rect[loop_m].vertex[3].x; // 第三點(diǎn)的 X 分量
y_m = m_sector3.rect[loop_m].vertex[3].y; // 第三點(diǎn)的 Y 分量
z_m = m_sector3.rect[loop_m].vertex[3].z; // 第三點(diǎn)的 Z 分量
u_m = m_sector3.rect[loop_m].vertex[3].u; // 第三點(diǎn)的 U 紋理坐標(biāo)
v_m = m_sector3.rect[loop_m].vertex[3].v; // 第三點(diǎn)的 V 紋理坐標(biāo)
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設(shè)置紋理坐標(biāo)和頂點(diǎn)
glEnd(); // 三角形繪制結(jié)束
}
}
void GLWidget::resizeGL(int w, int h)
{
if(h == 0) { //防止h為0
h = 1;
}
glViewport(0, 0, (GLint)w, (GLint)h); //重置當(dāng)前的視口(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; // 向左旋轉(zhuǎn)場(chǎng)景
updateGL();
break;
}
case Qt::Key_Left:
{
m_heading += 1.0f;
m_yrot = m_heading; // 向右側(cè)旋轉(zhuǎn)場(chǎng)景
updateGL();
break;
}
case Qt::Key_Up:
{
m_xpos -= (float)sin(m_heading*piover180) * 0.05f; // 沿游戲者所在的X平面移動(dòng)
m_zpos -= (float)cos(m_heading*piover180) * 0.05f; // 沿游戲者所在的Z平面移動(dòng)
updateGL();
break;
}
case Qt::Key_Down:
{
m_xpos += (float)sin(m_heading*piover180) * 0.05f; // 沿游戲者所在的X平面移動(dòng)
m_zpos += (float)cos(m_heading*piover180) * 0.05f; // 沿游戲者所在的Z平面移動(dòng)
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);
//我們對(duì)區(qū)段進(jìn)行初始化,并讀入部分?jǐn)?shù)據(jù)
QString oneline; // 存儲(chǔ)數(shù)據(jù)的字符串
int numtriangles; // 區(qū)段的三角形數(shù)量
float x, y, z, u, v; // 3D 和 紋理坐標(biāo)
//------------------------------------------------------------------------------------------------
readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
sscanf(oneline.toLatin1().data(), "grass %d\n", &numtriangles); // 讀入三角形數(shù)量
m_sector1.rect = new RECT[numtriangles]; // 為numtriangles個(gè)三角形分配內(nèi)存并設(shè)定指針
m_sector1.numtriangles = numtriangles; // 定義區(qū)段1中的三角形數(shù)量
// 遍歷區(qū)段中的每個(gè)三角形
for (int triloop = 0; triloop < numtriangles; triloop++) // 遍歷所有的三角形
{
// 遍歷三角形的每個(gè)頂點(diǎn)
for (int vertloop = 0; vertloop < 4; vertloop++) // 遍歷所有的頂點(diǎn)
{
readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
// 讀入各自的頂點(diǎn)數(shù)據(jù)
sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
// 將頂點(diǎn)數(shù)據(jù)存入各自的頂點(diǎn)
m_sector1.rect[triloop].vertex[vertloop].x = x; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 x=x
m_sector1.rect[triloop].vertex[vertloop].y = y; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 y=y
m_sector1.rect[triloop].vertex[vertloop].z = z; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 z=z
m_sector1.rect[triloop].vertex[vertloop].u = u; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 u=u
m_sector1.rect[triloop].vertex[vertloop].v = v; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 v=v
}
}
//------------------------------------------------------------------------------------------------
readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
sscanf(oneline.toLatin1().data(), "sky %d\n", &numtriangles); // 讀入三角形數(shù)量
m_sector2.rect = new RECT[numtriangles]; // 為numtriangles個(gè)三角形分配內(nèi)存并設(shè)定指針
m_sector2.numtriangles = numtriangles; // 定義區(qū)段1中的三角形數(shù)量
// 遍歷區(qū)段中的每個(gè)三角形
for (int triloop = 0; triloop < numtriangles; triloop++) // 遍歷所有的三角形
{
// 遍歷三角形的每個(gè)頂點(diǎn)
for (int vertloop = 0; vertloop < 4; vertloop++) // 遍歷所有的頂點(diǎn)
{
readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
// 讀入各自的頂點(diǎn)數(shù)據(jù)
sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
// 將頂點(diǎn)數(shù)據(jù)存入各自的頂點(diǎn)
m_sector2.rect[triloop].vertex[vertloop].x = x; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 x=x
m_sector2.rect[triloop].vertex[vertloop].y = y; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 y=y
m_sector2.rect[triloop].vertex[vertloop].z = z; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 z=z
m_sector2.rect[triloop].vertex[vertloop].u = u; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 u=u
m_sector2.rect[triloop].vertex[vertloop].v = v; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 v=v
}
}
//------------------------------------------------------------------------------------------------
readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
sscanf(oneline.toLatin1().data(), "floor %d\n", &numtriangles); // 讀入三角形數(shù)量
m_sector3.rect = new RECT[numtriangles]; // 為numtriangles個(gè)三角形分配內(nèi)存并設(shè)定指針
m_sector3.numtriangles = numtriangles; // 定義區(qū)段1中的三角形數(shù)量
// 遍歷區(qū)段中的每個(gè)三角形
for (int triloop = 0; triloop < numtriangles; triloop++) // 遍歷所有的三角形
{
// 遍歷三角形的每個(gè)頂點(diǎn)
for (int vertloop = 0; vertloop < 4; vertloop++) // 遍歷所有的頂點(diǎn)
{
readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
// 讀入各自的頂點(diǎn)數(shù)據(jù)
sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
// 將頂點(diǎn)數(shù)據(jù)存入各自的頂點(diǎn)
m_sector3.rect[triloop].vertex[vertloop].x = x; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 x=x
m_sector3.rect[triloop].vertex[vertloop].y = y; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 y=y
m_sector3.rect[triloop].vertex[vertloop].z = z; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 z=z
m_sector3.rect[triloop].vertex[vertloop].u = u; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 u=u
m_sector3.rect[triloop].vertex[vertloop].v = v; // 區(qū)段 1, 第 triloop 個(gè)三角形, 第 vertloop 個(gè)頂點(diǎn), 值 v=v
}
}
//------------------------------------------------------------------------------------------------
file.close();
}
//讀取World.txt中的有效數(shù)據(jù)行
void GLWidget::readStr(QTextStream *stream, QString &string)
{
do // 循環(huán)開始
{
string = stream->readLine();
} while (string[0] == '/' || string[0] == '\n' || string.isEmpty()); // 考察是否有必要進(jìn)行處理
}
//加載紋理
void GLWidget::loadTexture()
{
glGenTextures(1, &m_texture[0]);// 創(chuàng)建紋理
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());
}
到了這里,關(guān)于OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!