久久精品国产精品国产精品污,男人扒开添女人下部免费视频,一级国产69式性姿势免费视频,夜鲁夜鲁很鲁在线视频 视频,欧美丰满少妇一区二区三区,国产偷国产偷亚洲高清人乐享,中文 在线 日韩 亚洲 欧美,熟妇人妻无乱码中文字幕真矢织江,一区二区三区人妻制服国产

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

Qt Creator中的3D绘图及动画教程(参照NeHe)

發布時間:2023/12/15 综合教程 34 生活家
生活随笔 收集整理的這篇文章主要介紹了 Qt Creator中的3D绘图及动画教程(参照NeHe) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Qt Creator中的3D繪圖及動畫教程(參照NeHe)

http://blog.csdn.net/cly116/article/details/47184729

剛剛學習了Qt Creator,發現Qt提供了QtOpenGL模塊,對OpenGL做了不錯的封裝,這使得我們可以很輕松地在Qt程序中使用OpenGL進行繪圖渲染。雖然里面還是由不少專業的解釋照搬原文的,但還是加入了大量自己的分析。而且Qt中寫OpenGL框架上比VC簡單太多了,有不少東西都封裝優化好了,代碼上還是由有很多區別的。當然,其中原教程沒解釋好的問題我都作了深入的解釋,以及一些多余部分解釋、代碼都被我刪掉簡化了。

這份Qt OpenGL的3D繪圖及動畫教程,我基本會按照Nehe的OpenGL教程,只是將代碼的實現運用到Qt Creator中,當然其中加了。

下面對Qt中OpenGL做一個簡要介紹:

Qt中OpenGL主要是在QGLWidget類中完成的,而要使用QtOpenGL模塊,需要在項目文件( .pro)中添加代碼"QT+=opengl"。

QGLWidget類是一個用來渲染OpenGL圖形的部件,提供了在Qt中顯示OpenGL圖形的功能。這個類使用起來很簡單,只需要繼承該類,然后像使用其他QWidget部件一樣來使用它。QGLWidget提供了3個方便的純虛函數,可以在子類中通過重新實現它們來執行典型的OpenGL任務:

initializeGL():設置OpenGL渲染環境,定義顯示列表等。該函數只在第一次調用resizeGL()或paintGL()前被自動調用一次。

resizeGL():設置OpenGL的視口、投影等。每次部件改變大小時都會自動調用該函數。

paintGL():渲染OpenGL場景。每當部件需要更新時都會調用該函數。

(以上3個虛函數更具體的調用情況我會用另一篇文章來講明)

也就是說,Qt中當創建并顯示出一個QGLWidget子對象時,會自動依次調用initializeGL()、resizeGL()、paintGL(),完成當前場景的繪制;而當某些情況發生時,會根據情況決定是否自動調用initializeGL()、resizeGL(),一旦調用initializeGL()、resizeGL()了,會緊跟著調用paintGL()對場景進行重新繪制。

以上就是對Qt中OpenGL機制的一個簡單介紹,后面的Qt OpenGL的3D繪圖及動畫教程,我基本會按照Nehe的OpenGL教程,只是將代碼的實現運用到Qt Creator中;教程有看不懂的,大家可以給我留言或者參考Nehe的OpenGL教程http://www.yakergong.net/nehe/

教程目錄索引:

01:OpenGL窗口

02:多邊形

03:添加顏色

04:旋轉

05:3D空間

06:紋理映射

07:光照和鍵盤

08:混合透明

09:移動圖像

10:3D世界

11:飄動的旗幟

12:顯示列表

13:圖像字體

14:圖形字體

15:紋理圖形字

16:霧

17:2D圖像文字

18:二次幾何體

19:粒子系統

20:蒙板

全部教程中需要的資源文件點此下載 http://download.csdn.net/download/cly116/8957317

第01課:創建一個OpenGL窗口(參照NeHe)
在這個教程里,我們將在Qt Creator環境中創建OpenGL對象,它將顯示一個空的OpenGL窗口,可以在窗口和全屏模式下切換,按ESC退出,它將是我們后面應用程序的基礎框架。
Qt中寫OpenGL與在VC上還是有不少差別的,對Qt機制不熟悉的朋友,請先大致了解下Qt的機制,再往下看教程。

程序運行時效果如下:


下面進入教程:

新建空的Qt項目,項目名稱為myOpenGL,然后往項目中添加新的C++類,類名為MyGLWidget,基類為QGLWidget,類型信息選擇“繼承自QWidget”。添加完成后,打開項目文件myOpenGL.pro,將代碼補全如下:

TARGET=myOpenGL
TEMPLATE=app

HEADERS+=
myglwidget.h

SOURCES+=
main.cpp
myglwidget.cpp

QT+=coregui

greaterThan(QT_MAJOR_VERSION,4):QT+=widgets

QT+=opengl


然后保存該文件。下面打開myglwidget.h文件,將類聲明補全如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
boolfullscreen;//是否全屏顯示
};

#endif//MYGLWIDGET_H

再到myglwidget.cpp文件中先包含#include<GL/glu.h>,#include<QKeyEvent>頭文件,然后添加類中函數的定義:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
}

MyGLWidget::~MyGLWidget()
{

}

構造函數中只需對fullscreen初始化,析構函數暫時并不需要做什么。

下面是initializeGL()的定義:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
glClearColor(0.0,0.0,0.0,0.0);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑

glClearDepth(1.0);//設置深度緩存
glEnable(GL_DEPTH_TEST);//啟用深度測試
glDepthFunc(GL_LEQUAL);//所作深度測試的類型
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正
}

glClearColor()函數用來設置清除屏幕時使用的顏色,4個參數分別用來設置紅、綠、藍顏色分量和Alpha值,它們的取值范圍都是0.0~1.0,這里4個參數都為0.0,表示純黑色。然后設置了陰影平滑,這樣可以使色彩和光照更加精細。
接下來的三行必須做的是關于depth buffer(深度緩存)的。將深度緩存設想為屏幕后面的層。深度緩存不斷地對物體進入屏幕內部有多深進行跟蹤。我們本節的程序其實沒有真正的使用深度緩存,但幾乎所有在屏幕上顯示3D場景OpenGL程序都使用深度緩存。它的排序決定那個物體先畫。這樣就不會將一個圓形后面的正方形畫到圓形前面來。深度緩存是OpenGL十分重要的部分。最后我們希望進行最好的透視修正。這會十分輕微的影響性能,但使得透視圖看起來好一點。

下面是resizeGL()的定義:

voidMyGLWidget::resizeGL(intw,inth)//重置OpenGL窗口的大小
{
glViewport(0,0,(GLint)w,(GLint)h);//重置當前的視口
glMatrixMode(GL_PROJECTION);//選擇投影矩陣
glLoadIdentity();//重置投影矩陣
//設置視口的大小
gluPerspective(45.0,(GLfloat)w/(GLfloat)h,0.1,100.0);
glMatrixMode(GL_MODELVIEW);//選擇模型觀察矩陣
glLoadIdentity();//重置模型觀察矩陣
}

glViewport()函數用來設置視口的大小。使用glMatrixMode()設置了投影矩陣,投影矩陣用來為場景增加透視,后面使用了glLoadIdentity()重置投影矩陣,這樣可以將投影矩陣恢復到初始狀態。gluPerspective()用來設置透視投影矩陣,這里設置視角為45°,縱橫比為窗口的縱橫比,最近的位置為0.1,最遠的位置為100,這兩個值是場景中所能繪制的深度的臨界值。可以想象,離我們眼睛比較近的東西看起來比較大,而比較遠的東西看起來就比較小。最后設置并重置了模型視圖矩陣。

下面是paintGL()的定義:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置當前的模型觀察矩陣
}

paintGL()函數包含了所以的繪圖代碼,任何想在屏幕上顯示的東西都將在此段代碼中出現。以后每個教程中都會在這個函數增加代碼,已達到繪圖目的。

最后是鍵盤事件處理函數KeyPressEvent()的定義,由于這與OpenGL關系不大,不做過多解釋:

voidMyGLWidget::keyPressEvent(QKeyEvent*event)
{
switch(event->key())
{
//F1為全屏和普通屏的切換鍵
caseQt::Key_F1:
fullscreen=!fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
}
updateGL();
break;
//ESC為退出鍵
caseQt::Key_Escape:
close();
}
}

最后再向項目中添加main.cpp文件,更改內容如下:

#include<QApplication>
#include"myglwidget.h"

intmain(intargc,char*argv[])
{
QApplicationapp(argc,argv);

MyGLWidgetw;
w.resize(400,300);
w.show();

returnapp.exec();
}

現在就可以運行程序查看效果了!

第02課:你的第一個多邊形(參照NeHe)

這次教程中,我們將添加一個三角形和一個四邊形。或許你認為這很簡單,但要知道任何復雜的繪圖都是從簡單開始的,或者說任何復雜的模型都是可以分解成簡單的圖形的。所以,我們還是從簡單的圖形開始吧。

讀完這一次教程,你還會學到如何在空間放置模型以及了解OpenGL中坐標變化。

程序運行時效果如下:


下面進入教程:

我們將使用GL_TRIANGLES來創建一個三角形,GL_QUADS來創建一個四邊形。在第01課代碼的基礎上,我們只需在paintGL()函數中增加代碼。

下面我將重寫整個paintGL()函數,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置當前的模型觀察矩陣

glTranslatef(-1.5f,0.0f,-6.0f);//左移1.5單位,并移入屏幕6.0單位
glBegin(GL_TRIANGLES);//開始繪制三角形
glVertex3f(0.0f,1.0f,0.0f);//上頂點
glVertex3f(-1.0f,-1.0f,0.0f);//左下
glVertex3f(1.0f,-1.0f,0.0f);//右下
glEnd();//三角形繪制結束

glTranslatef(3.0f,0.0f,0.0f);//右移3.0單位
glBegin(GL_QUADS);//開始繪制四邊形
glVertex3f(-1.0f,1.0f,0.0f);//左上
glVertex3f(1.0f,1.0f,0.0f);//右上
glVertex3f(1.0f,-1.0f,0.0f);//左下
glVertex3f(-1.0f,-1.0f,0.0f);//右下
glEnd();//四邊形繪制結束
}

當調用了glLoadIdentity()之后,我們實際上將當前點移到了屏幕中心,x軸從左到右,y軸從下到上,z軸從里到外。其中,中心右面,上面,外面的坐標值為正值。glTranslatef(x, y, z)沿著x,y和z軸移動,要注意,在glTranslatef(x, y, z)移動的時候,并不是相對屏幕中心移動,而是相對于當前所在的屏幕位置。
glBegin(GL_TRIANGLES)的意思是開始繪制三角形,glEnd()告訴OpenGL三角形已經創建好了。通常我們會需要畫3個頂點,可以使用GL_TRIANGLES;而要畫4個頂點時,使用GL_QUADS會更方便。最后,如果想要畫更多的頂點時,可以使用GL_POLYGON。

本節的簡單示例中,我們只畫了一個三角形。如果要畫第二個三角形的話,可以在這三點之后,再加三行代碼(3點)。所以6點代碼都應該包含在glBegin(GL_TRIANGLES)和glEnd()之間,這樣不會出現多余的線,這是由于glBegin(GL_TRIANGLES)和glEnd()之間的點都是以3點為一個集合的。這同樣適用于四邊形。另一方面,多邊形可以由任意個頂點組成,繪制多邊形時不在乎glBegin(GL_POLYGON)和glEnd()之間或多少行代碼。

glBegin()之后的第一行設置了多邊形的第一個頂點,glVertex的三個參數依次是x,y和z軸坐標。glEnd()告訴OpenGL沒有其他點了,這樣將顯示一個填充的三角形。

然后類比畫出一個四邊形后,就可以運行程序看效果了!

第03課:添加顏色(參照NeHe)
這次教程中,我們將在第02課的基礎上,教大家如何使用顏色。我們將一起理解兩種著色模式(光滑著色與平面著色),并運用這兩種模式分別給第02課的三角形和正方形著色。我們將使用平面著色給四邊形著色,即給三角形涂上一種固定的顏色;使用平滑著色給三角形著色,將三角形的三個頂點的不同顏色混合在一起,創建漂亮的色彩混合。

程序運行時效果如下:



下面進入教程:


要對三角形和四邊形進行著色,只需在第02課代碼的基礎上,對paintGL()函數作一定的修改。
下面我將重寫整個paintGL()函數,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置當前的模型觀察矩陣

glTranslatef(-1.5f,0.0f,-6.0f);//左移1.5單位,并移入屏幕6.0單位
glBegin(GL_TRIANGLES);//開始繪制三角形
glColor3f(1.0f,0.0f,0.0f);//設置當前色為紅色
glVertex3f(0.0f,1.0f,0.0f);//上頂點
glColor3f(0.0f,1.0f,0.0f);//設置當前色為綠色
glVertex3f(-1.0f,-1.0f,0.0f);//左下
glColor3f(0.0f,0.0f,1.0f);//設置當前色為藍色
glVertex3f(1.0f,-1.0f,0.0f);//右下
glEnd();//三角形繪制結束

glTranslatef(3.0f,0.0f,0.0f);//右移3.0單位
glColor3f(0.5f,0.5f,1.0f);//一次性將當前色設置為藍色
glBegin(GL_QUADS);//開始繪制四邊形
glVertex3f(-1.0f,1.0f,0.0f);//左上
glVertex3f(1.0f,1.0f,0.0f);//右上
glVertex3f(1.0f,-1.0f,0.0f);//左下
glVertex3f(-1.0f,-1.0f,0.0f);//右下
glEnd();//四邊形繪制結束
}

其實與第02課相比,只是增加了4行代碼而已。我們利用glColor3f(r, g, b)函數來選擇顏色進行著色,該函數三個參數依次是紅、綠、藍三色分量,范圍從0.0到1.0之間,類似于之前所講的清除屏幕背景函數。當我們將顏色設為某種顏色時,接下來的代碼繪制出的對象的顏色就都是對應顏色的。
對于用光滑著色的三角形,我們需要分別對于3個頂點分別選擇顏色,再分別進行繪點。故我們在每次繪點之前都需要調用一次glColor3f(r, g, b)進行選色,glEnd()之后,三角形將被填充,但因為每個頂點有不同的顏色,因此看起來顏色從每個頂點噴出,并剛好在三角形的中心匯合,三種顏色相互混合,這就是平滑著色;而對于使用平面著色的四邊形,我只需要在一開始就選擇好顏色,直接繪制四邊形即可。
還有一點值得提的是,順時針繪制圖形時,意味著我們所看見的是圖形的背景,這在后面對圖形有一定影響。
現在就可以運行程序查看效果了!

第04課:旋轉(參照NeHe)

這次教程中,我們將在第03課的基礎上,教大家如何旋轉三角形和四邊形。我們將讓三角形沿y軸旋轉,四邊形沿x軸旋轉,最終我們能得到一個三角形和四邊形自動旋轉的場景。

程序運行時效果如下:

下面進入教程:

首先打開myglwidget.h文件,我們需要增加兩個變量來控制這兩個對象的旋轉。這兩個變量加在類的私有聲明處,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
boolfullscreen;//是否全屏顯示

GLfloatm_rtri;//控制三角形的角度
GLfloatm_rquad;//控制四邊形的角度
};

#endif//MYGLWIDGET_H

我們增加了兩個浮點類型的變量,使得我們能夠非常精確地旋轉對象,你漸漸會發現浮點數是OpenGL編程的基礎。新變量中叫做m_rtri的用來旋轉三角形,m_rquad旋轉四邊形。

接下來,我們需要打開myglwidget.cpp,在構造函數中對兩個新變量進行初始化,這部分很簡單,不作過多解釋,代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_rtri=0.0f;
m_rquad=0.0f;
}

然后進入重點的paintGL()函數了,我們只需在第03課代碼的基礎上,做一定的修改,就能實現三角形和四邊形的旋轉了。

下面我將重寫整個paintGL()函數,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置當前的模型觀察矩陣

glTranslatef(-1.5f,0.0f,-6.0f);//左移1.5單位,并移入屏幕6.0單位
glRotatef(m_rtri,0.0f,1.0f,0.0f);//繞y軸旋轉三角形
glBegin(GL_TRIANGLES);//開始繪制三角形
glColor3f(1.0f,0.0f,0.0f);//設置當前色為紅色
glVertex3f(0.0f,1.0f,0.0f);//上頂點
glColor3f(0.0f,1.0f,0.0f);//設置當前色為綠色
glVertex3f(-1.0f,-1.0f,0.0f);//左下
glColor3f(0.0f,0.0f,1.0f);//設置當前色為藍色
glVertex3f(1.0f,-1.0f,0.0f);//右下
glEnd();//三角形繪制結束

glLoadIdentity();//重置模型觀察矩陣
glTranslatef(1.5f,0.0f,-6.0f);//右移1.5單位,并移入屏幕6.0單位
glRotatef(m_rquad,1.0f,0.0f,0.0f);//繞x軸旋轉四邊形
glColor3f(0.5f,0.5f,1.0f);//一次性將當前色設置為藍色
glBegin(GL_QUADS);//開始繪制四邊形
glVertex3f(-1.0f,1.0f,0.0f);//左上
glVertex3f(1.0f,1.0f,0.0f);//右上
glVertex3f(1.0f,-1.0f,0.0f);//左下
glVertex3f(-1.0f,-1.0f,0.0f);//右下
glEnd();//四邊形繪制結束

m_rtri+=0.5f;//增加三角形的旋轉變量
m_rquad-=0.5f;//減少四邊形的旋轉變量
}

上面的代碼繪制三角形時多了一新函數glRotatef(Angle, Xvector, Yvector, Zvector)。該函數負責讓對象繞某個軸旋轉,這個函數有諸多用處。Angle通常是個變量代表對象轉過的角度,后三個參數則共同決定旋轉軸的方向。故(1.0f, 0.0f, 0.0f)、(0.0f, 1.0f, 0.0f)、(0.0f, 0.0f, 1.0f)表示依次繞x、y、z軸旋轉,參照此原理,我們也能實現四邊形的旋轉。

我們會發現畫完三角形后,相比原來的代碼多了一行glLoadIdentity(),目的是為了重置模型觀察矩陣。如果我們沒有重置,直接調用glTranslate的話,會發現可能沒有朝著我們所希望的方向旋轉,這是由于坐標軸以前已經旋轉了。所以我們本來要左右移動對象的,可能就變成上下移動了。還不理解的朋友可以試著將glLoadIdentity()試注釋掉之后,看會出現什么結果。

重置模型觀察矩陣之后,x、y、z軸都復位,我們調用glTranslate時只向右移動了1.5單位,而不是之前的3.0單位。因為我們重置場景的時候,焦點又回到了場景的中心,這樣只需右移單位即可。

最后我們通過增加m_rtri和減少m_rquad使得物體自己旋轉起來,我們可以嘗試改變代碼中的+和-,來體會對象旋轉的方向是如何改變的。并嘗試著將0.5改成4.0,。這個數字越大,物體就轉得越快,這個數字越小,物體轉的就越慢。

至此,我們似乎已經完成了,但是運行程序時發現,三角形和四邊形并沒有自動旋轉起來。這是由于paintGL()被調用一次之后,沒有發生其他的事件使得它被自動調用。我們可以通過拉伸窗口的大小,發現三角形和四邊形就動起來了,這是由于我們改變了窗口大小,調用了reszieGL()之后緊接著調用了paintGL()對場景進行重繪。顯然,我們不能一直通過拉伸窗口來實現旋轉,這樣顯得很拙,我們可以在構造函數中利用Qt的定時器事件來控制paintGL()的調用。先在myglwidget.cpp中添加頭文件#include <QTimer>。構造函數代碼如下:(具體initializeGL()、reszieGL()、paintGL()的調用情況請參見)

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_rtri=0.0f;
m_rquad=0.0f;

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

這里將定時器的timeout()信號與updateGL()槽綁定,每過10ms就會調用一次updateGL(),而updateGL()調用后會調用paintGL()對場景進行重繪,這樣就通過對場景不停地重繪實現對象的旋轉。(對Qt定時器不了解的朋友請先百度了解下其機制)
現在就可以運行程序看效果了!

第05課:3D模型(參照NeHe)

這次教程中,我們將之前幾課的基礎上,教大家如何創建立體的3D模型。我們將開始生成真正的3D對象,而不是像之前那幾課那樣3D世界中的2D對象。我們會把之前的三角形變為立體的金字塔模型,把四邊形變為立方體。

我們給三角形增加左側面、右側面、后側面來生成一個金字塔。給正方形增加左、右、上、下及背面生成一個立方體。我們混合金字塔上的顏色,創建一個平滑著色的對象;給立方體的每一面來個不同的顏色。

程序運行時效果如下:

下面進入教程:

要實現3D模型,只需在第04課代碼的基礎上,對paintGL()函數作一定的修改。

下面我將重寫整個paintGL()函數,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置當前的模型觀察矩陣

glTranslatef(-1.5f,0.0f,-6.0f);//左移1.5單位,并移入屏幕6.0單位
glRotatef(m_rtri,0.0f,1.0f,0.0f);//繞y軸旋轉三角形
glBegin(GL_TRIANGLES);//開始繪制金字塔
glColor3f(1.0f,0.0f,0.0f);//紅色
glVertex3f(0.0f,1.0f,0.0f);//上頂點(前側面)
glColor3f(0.0f,1.0f,0.0f);//綠色
glVertex3f(-1.0f,-1.0f,1.0f);//左下(前側面)
glColor3f(0.0f,0.0f,1.0f);//藍色
glVertex3f(1.0f,-1.0f,1.0f);//右下(前側面)

glColor3f(1.0f,0.0f,0.0f);//紅色
glVertex3f(0.0f,1.0f,0.0f);//上頂點(右側面)
glColor3f(0.0f,0.0f,1.0f);//藍色
glVertex3f(1.0f,-1.0f,1.0f);//左下(右側面)
glColor3f(0.0f,1.0f,0.0f);//綠色
glVertex3f(1.0f,-1.0f,-1.0f);//右下(右側面)

glColor3f(1.0f,0.0f,0.0f);//紅色
glVertex3f(0.0f,1.0f,0.0f);//上頂點(后側面)
glColor3f(0.0f,1.0f,0.0f);//綠色
glVertex3f(1.0f,-1.0f,-1.0f);//左下(后側面)
glColor3f(0.0f,0.0f,1.0f);//藍色
glVertex3f(-1.0f,-1.0f,-1.0f);//右下(后側面)

glColor3f(1.0f,0.0f,0.0f);//紅色
glVertex3f(0.0f,1.0f,0.0f);//上頂點(左側面)
glColor3f(0.0f,0.0f,1.0f);//藍色
glVertex3f(-1.0f,-1.0f,-1.0f);//左下(左側面)
glColor3f(0.0f,1.0f,0.0f);//綠色
glVertex3f(-1.0f,-1.0f,1.0f);//右下(左側面)
glEnd();//金字塔繪制結束

glLoadIdentity();//重置模型觀察矩陣
glTranslatef(1.5f,0.0f,-6.0f);//右移1.5單位,并移入屏幕6.0單位
glRotatef(m_rquad,1.0f,0.0f,0.0f);//繞x軸旋轉四邊形
glBegin(GL_QUADS);//開始繪制立方體
glColor3f(0.0f,1.0f,0.0f);//綠色
glVertex3f(1.0f,1.0f,-1.0f);//右上(頂面)
glVertex3f(-1.0f,1.0f,-1.0f);//左上(頂面)
glVertex3f(-1.0f,1.0f,1.0f);//左下(頂面)
glVertex3f(1.0f,1.0f,1.0f);//右下(頂面)

glColor3f(1.0f,0.5f,0.0f);//橙色
glVertex3f(1.0f,-1.0f,1.0f);//右上(底面)
glVertex3f(-1.0f,-1.0f,1.0f);//左上(底面)
glVertex3f(-1.0f,-1.0f,-1.0f);//左下(底面)
glVertex3f(1.0f,-1.0f,-1.0f);//右下(底面)

glColor3f(1.0f,0.0f,0.0f);//紅色
glVertex3f(1.0f,1.0f,1.0f);//右上(前面)
glVertex3f(-1.0f,1.0f,1.0f);//左上(前面)
glVertex3f(-1.0f,-1.0f,1.0f);//左下(前面)
glVertex3f(1.0f,-1.0f,1.0f);//右下(前面)

glColor3f(1.0f,1.0f,0.0f);//黃色
glVertex3f(1.0f,-1.0f,-1.0f);//右上(后面)
glVertex3f(-1.0f,-1.0f,-1.0f);//左上(后面)
glVertex3f(-1.0f,1.0f,-1.0f);//左下(后面)
glVertex3f(1.0f,1.0f,-1.0f);//右下(后面)

glColor3f(0.0f,0.0f,1.0f);//藍色
glVertex3f(-1.0f,1.0f,1.0f);//右上(左面)
glVertex3f(-1.0f,1.0f,-1.0f);//左上(左面)
glVertex3f(-1.0f,-1.0f,-1.0f);//左下(左面)
glVertex3f(-1.0f,-1.0f,1.0f);//右下(左面)

glColor3f(1.0f,0.0f,1.0f);//紫色
glVertex3f(1.0f,1.0f,-1.0f);//右上(右面)
glVertex3f(1.0f,1.0f,1.0f);//左上(右面)
glVertex3f(1.0f,-1.0f,1.0f);//左下(右面)
glVertex3f(1.0f,-1.0f,-1.0f);//右下(右面)
glEnd();//立方體繪制結束

m_rtri+=0.5f;//增加金字體的旋轉變量
m_rquad-=0.5f;//減少立方體的旋轉變量
}

首先創建一個繞著其中心軸旋轉的金字塔,金字塔的上頂點高出原點一個單位,底面中心低于原點一個單位,上頂點在底面的投影位于底面的中心。要注意的是所有的面-三角形都是逆時針次序繪制的,這點十分重要,在以后的課程中我會做出解釋。現在,我們只需明白要么都逆時針,要么都順時針,但永遠不要將兩種次序混在一起,除非我們有足夠的理由必須這么做。

開始繪制金字塔,應注意到四個側面處于同一glBegin(GL_TRIANGLES)和glEnd()語句之間,由于我們是用過三角形來構造這個金字塔的,OpenGL知道每三個點構成一個三角形,當它畫完一個三角形之后,如果還有余下的點出現,它就以為新的三角形要開始繪制了。OpenGL在這里并不會將四個點畫成一個四邊形,而是假定新的三角形開始了,千萬不要無意中增加任何多余的點。對于顏色的選擇,我們只需對應好位置,就能取得不錯的效果。

開始繪制立方體,它由六個四邊形組成,所有的四邊形都以逆時針次序繪制,即按照右上、左上、左下、右下的次序繪畫。你也許認為畫立方體的背面的時候這個次序看起來好像順時針,但別忘了我們從立方體背后看背面的時候,與你現在所想的正好相反(我們是從立方體外面來觀察立方體的)。當然,你也可以嘗試用平滑著色來繪制立方體。

現在就可以運行程序查看效果了!

第06課:紋理映射(參照NeHe)
這次教程中,我教會大家如何把紋理映射到立方體的六個面上。學習texture map(紋理映射)有諸多好處。比如說想讓一顆導彈飛過屏幕。根據前幾課的知識,我們最可行的辦法可能是很多個多邊形來構建導彈的輪廓并加上有趣的顏色。而使用紋理映射,我們可以使用真實的導彈圖像并讓它飛過屏幕。你覺得哪個更好看?使用紋理映射的好處還不止是更好看,而且程序的運行會更快。導彈貼圖可能只是一個飛過窗口的四邊形,而一個導彈卻需要成百上千的多邊形組成,很明顯,紋理映射極大的節省了CPU的時間。

程序運行時效果如下:


下面進入教程:

我們這次將在第01課得到的基礎框架上開始添加代碼,首先打開myglwidget.h文件,我們需要增加一些變量,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
boolfullscreen;//是否全屏顯示

GLfloatm_xRot;//繞x軸旋轉的角度
GLfloatm_yRot;//繞y軸旋轉的角度
GLfloatm_zRot;//繞z軸旋轉的角度

QStringm_FileName;//圖片的路徑及文件名
GLuintm_Texture;//儲存一個紋理
};

#endif//MYGLWIDGET_H

增加的前三個變量用來使立方體繞x、y、z軸旋轉,m_FileName用于儲存圖片的路徑及文件名,m_Texture為一個紋理分配存儲空間。如果需要不止一個紋理,可以創建一個數組來儲存不同的紋理。

接下來,我們需要打開myglwidget.cpp,加上聲明#include <QTimer>,在構造函數中對新增變量(除了m_Texture)進行初始化,同樣不作過多解釋,代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_xRot=0.0f;
m_yRot=0.0f;
m_zRot=0.0f;
m_FileName="D:/QtOpenGL/QtImage/Nehe.bmp";//應根據實際存放圖片的路徑進行修改

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

然后這次我們需要對initializeGL()函數作一定的修改了,具體代碼如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
m_Texture=bindTexture(QPixmap(m_FileName));//載入位圖并轉換成紋理
glEnable(GL_TEXTURE_2D);//啟用紋理映射

glClearColor(0.0f,0.0f,0.0f,0.0f);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑

glClearDepth(1.0);//設置深度緩存
glEnable(GL_DEPTH_TEST);//啟用深度測試
glDepthFunc(GL_LEQUAL);//所作深度測試的類型
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正
}

我們增加了兩行代碼,首先調用了Qt提供的bindTexture()函數將圖片載入并轉換成紋理,然后啟用2D紋理映射。如果忘記啟用的話,我們的對象看起來永遠都是純白色的,這明顯與我們的預期大相徑庭。

最后我們該開始繪制貼圖過的立方體了,paintGL()函數具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置模型觀察矩陣
glTranslatef(0.0f,0.0f,-5.0f);//移入屏幕5.0單位
glRotatef(m_xRot,1.0f,0.0f,0.0f);//繞x軸旋轉
glRotatef(m_yRot,0.0f,1.0f,0.0f);//繞y軸旋轉
glRotatef(m_zRot,0.0f,0.0f,1.0f);//繞z軸旋轉

glBindTexture(GL_TEXTURE_2D,m_Texture);//選擇紋理
glBegin(GL_QUADS);//開始繪制立方體
glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,-1.0f);//右上(頂面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,-1.0f);//左上(頂面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,1.0f,1.0f);//左下(頂面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,1.0f,1.0f);//右下(頂面)

glTexCoord2f(0.0f,0.0f);
glVertex3f(1.0f,-1.0f,1.0f);//右上(底面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(-1.0f,-1.0f,1.0f);//左上(底面)
glTexCoord2f(1.0f,1.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);//左下(底面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(1.0f,-1.0f,-1.0f);//右下(底面)

glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,1.0f);//右上(前面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,1.0f);//左上(前面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,-1.0f,1.0f);//左下(前面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,-1.0f,1.0f);//右下(前面)

glTexCoord2f(0.0f,0.0f);
glVertex3f(1.0f,-1.0f,-1.0f);//右上(后面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);//左上(后面)
glTexCoord2f(1.0f,1.0f);
glVertex3f(-1.0f,1.0f,-1.0f);//左下(后面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(1.0f,1.0f,-1.0f);//右下(后面)

glTexCoord2f(1.0f,1.0f);
glVertex3f(-1.0f,1.0f,1.0f);//右上(左面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,-1.0f);//左上(左面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);//左下(左面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(-1.0f,-1.0f,1.0f);//右下(左面)

glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,-1.0f);//右上(右面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(1.0f,1.0f,1.0f);//左上(右面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(1.0f,-1.0f,1.0f);//左下(右面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,-1.0f,-1.0f);//右下(右面)
glEnd();//立方體繪制結束

m_xRot+=0.6f;//x軸旋轉
m_yRot+=0.4f;//y軸旋轉
m_zRot+=0.8f;//z軸旋轉
}

這次我們需要讓對象依次繞x、y、z軸旋轉,旋轉多少依賴于變量m_xRot、m_yRot、m_zRot的值。下面我們調用glBindTexture()函數來選擇要綁定的紋理,第2個參數表示所要綁定的紋理。當想改變紋理時,應該綁定新的紋理,要注意的是,我們不能在glBegin()和glEnd()直接綁定紋理,那樣綁定的紋理時無效的。
為了將紋理正確地映射到四邊形上,我們需要將紋理的四個角對應映射到四邊形的四個角上。如果映射錯誤的話,圖像顯示時可能上下顛倒,側向一邊或者什么都不是。glTexCoord2f的兩個參數分別表示x、y坐標,范圍從0.0f到1.0f。
最后我們讓m_xRot、m_yRot、m_zRot的值增加,大家可以嘗試變化每次個變量的改變值來調節立方體的旋轉速度,或改變+/-號來調節立方體的旋轉方向。
現在就可以運行程序查看效果了!

第07課:光照和鍵盤控制(參照NeHe)

這次教程中,我們將添加光照和鍵盤控制,它讓程序看起來更美觀。我將教大家如何使用鍵盤來移動場景中的對象,還會教大家在OpenGL場景中應用簡單的光照,讓我們的程序更加視覺效果更好且受我們控制。

程序運行時效果如下:

下面進入教程:

我們這次將在第06課的基礎上修改代碼,首先打開myglwidget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
boolfullscreen;//是否全屏顯示

QStringm_FileName;//圖片的路徑及文件名
GLuintm_Texture;//儲存一個紋理
boolm_Light;//光源的開/關

GLfloatm_xRot;//x旋轉角度
GLfloatm_yRot;//y旋轉角度
GLfloatm_xSpeed;//x旋轉速度
GLfloatm_ySpeed;//y旋轉速度
GLfloatm_Deep;//深入屏幕的距離
};

#endif//MYGLWIDGET_H

增加了一個布爾變量表示光源的開關,剩下的五個浮點變量用于控制對象的旋轉角度,旋轉速度以及距離屏幕的位置。

接下來,我們需要打開myglwidget.cpp,加上聲明#include <QTimer>,在構造函數中對新增變量(除了m_Texture)進行初始化,同樣不作過多解釋,代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_FileName="D:/QtOpenGL/QtImage/Crate.bmp";//應根據實際存放圖片的路徑進行修改
m_Light=false;

m_xRot=0.0f;
m_yRot=0.0f;
m_xSpeed=0.0f;
m_ySpeed=0.0f;
m_Deep=-5.0f;

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

然后,我們要來添加光照,只需要在initializeGL()函數增加幾行代碼,具體修改后代碼如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
m_Texture=bindTexture(QPixmap(m_FileName));//載入位圖并轉換成紋理
glEnable(GL_TEXTURE_2D);//啟用紋理映射

glClearColor(0.0f,0.0f,0.0f,0.0f);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑

glClearDepth(1.0);//設置深度緩存
glEnable(GL_DEPTH_TEST);//啟用深度測試
glDepthFunc(GL_LEQUAL);//所作深度測試的類型
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正

GLfloatLightAmbient[]={0.5f,0.5f,0.5f,1.0f};//環境光參數
GLfloatLightDiffuse[]={1.0f,1.0f,1.0f,1.0f};//漫散光參數
GLfloatLightPosition[]={0.0f,0.0f,2.0f,1.0f};//光源位置
glLightfv(GL_LIGHT1,GL_AMBIENT,LightAmbient);//設置環境光
glLightfv(GL_LIGHT1,GL_DIFFUSE,LightDiffuse);//設置漫射光
glLightfv(GL_LIGHT1,GL_POSITION,LightPosition);//設置光源位置
glEnable(GL_LIGHT1);//啟動一號光源
}

首先我們分別定義環境光參數,漫射光參數以及光源位置。環境光來自于四面八方,所以場景中的對象都處于環境光的照射中;漫射光由特定的光源產生,并在場景中的對象表明產生反射。處于漫射光直接照射下的任何對象表面都變得很亮,而幾乎未被照到的區域顯得要暗一些。這樣我們所創建的木板箱的棱邊上就會產生很不錯的陰影效果。

創建光源的過程和顏色的創建完全一致,前三個參數分別是RGB三色分量,最后一個是alpha通道參數。最后光源位置前三個參數和glTranslate中的一樣,一次表示x、y、z軸上的位移,最后一個參數取為1.0f,這將告訴OpenGL這里指定的坐標就是光源的位置,以后的教程中我會多加解釋。

接著開始設置光源,使得光源GL_LIGHT1開始發光,然后是設置光源位置(位于木箱原中心在z方向移向觀察者2.0單位),最后我們啟用一號光源。要注意的是,我們還沒有啟用GL_LIGHTING,所以是看不見任何光線的。記住,只對光源進行設置、定位、甚至啟用,光源都不會工作,除非我們啟用GL_LIGHTING。

還有是對paintGL()函數的修改,修改后具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置模型觀察矩陣
glTranslatef(0.0f,0.0f,m_Deep);//移入屏幕
glRotatef(m_xRot,1.0f,0.0f,0.0f);//繞x軸旋轉
glRotatef(m_yRot,0.0f,1.0f,0.0f);//繞y軸旋轉

glBindTexture(GL_TEXTURE_2D,m_Texture);//選擇紋理
glBegin(GL_QUADS);//開始繪制立方體
glNormal3f(0.0f,1.0f,0.0f);
glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,-1.0f);//右上(頂面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,-1.0f);//左上(頂面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,1.0f,1.0f);//左下(頂面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,1.0f,1.0f);//右下(頂面)

glNormal3f(0.0f,-1.0f,0.0f);
glTexCoord2f(0.0f,0.0f);
glVertex3f(1.0f,-1.0f,1.0f);//右上(底面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(-1.0f,-1.0f,1.0f);//左上(底面)
glTexCoord2f(1.0f,1.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);//左下(底面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(1.0f,-1.0f,-1.0f);//右下(底面)

glNormal3f(0.0f,0.0f,1.0f);
glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,1.0f);//右上(前面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,1.0f);//左上(前面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,-1.0f,1.0f);//左下(前面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,-1.0f,1.0f);//右下(前面)

glNormal3f(0.0f,0.0f,-1.0f);
glTexCoord2f(0.0f,0.0f);
glVertex3f(1.0f,-1.0f,-1.0f);//右上(后面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);//左上(后面)
glTexCoord2f(1.0f,1.0f);
glVertex3f(-1.0f,1.0f,-1.0f);//左下(后面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(1.0f,1.0f,-1.0f);//右下(后面)

glNormal3f(-1.0f,0.0f,0.0f);
glTexCoord2f(1.0f,1.0f);
glVertex3f(-1.0f,1.0f,1.0f);//右上(左面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,-1.0f);//左上(左面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);//左下(左面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(-1.0f,-1.0f,1.0f);//右下(左面)

glNormal3f(1.0f,0.0f,0.0f);
glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,-1.0f);//右上(右面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(1.0f,1.0f,1.0f);//左上(右面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(1.0f,-1.0f,1.0f);//左下(右面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,-1.0f,-1.0f);//右下(右面)
glEnd();//立方體繪制結束

m_xRot+=m_xSpeed;//x軸旋轉
m_yRot+=m_ySpeed;//y軸旋轉
}

除了旋轉及移動上作了修改外(相信大家能看懂),多了glNormal3f()函數的調用。該函數指定一條法線,法線告訴OpenGL這個多邊形的朝向,并指明多邊形的正面和背面,如果沒有法線,什么怪事情都可能發生:不該亮的面被照亮了,多邊形的背面也被照亮了…還要注意的是,法線應指向多邊形的外側。
最后兩行代碼作了一定的修改,利用變量m_xSpeed、m_ySpeed來控制立方體的旋轉速度。

最后當然就是鍵盤控制了,具體代碼如下(相信大家結合注釋可以很容易看懂):

voidMyGLWidget::keyPressEvent(QKeyEvent*event)
{
switch(event->key())
{
caseQt::Key_F1://F1為全屏和普通屏的切換鍵
fullscreen=!fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
}
break;
caseQt::Key_Escape://ESC為退出鍵
close();
break;
caseQt::Key_L://L為開啟關閉光源的切換鍵
m_Light=!m_Light;
if(m_Light)
{
glEnable(GL_LIGHTING);//開啟光源
}
else
{
glDisable(GL_LIGHTING);//關閉光源
}
break;
caseQt::Key_PageUp://PageUp按下使木箱移向屏幕內部
m_Deep-=0.1f;
break;
caseQt::Key_PageDown://PageDown按下使木箱移向觀察者
m_Deep+=0.1f;
break;
caseQt::Key_Up://Up按下減少m_xSpeed
m_xSpeed-=0.1f;
break;
caseQt::Key_Down://Down按下增加m_xSpeed
m_xSpeed+=0.1f;
break;
caseQt::Key_Right://Right按下減少m_ySpeed
m_ySpeed-=0.1f;
break;
caseQt::Key_Left://Left按下增加m_ySpeed
m_ySpeed+=0.1f;
break;
}
}

現在就可以運行程序查看效果了!

第08課:混合(參照NeHe)

這次教程中,我們將在紋理映射的基礎上加上混合,使它看起來具有透明的效果,當然解釋它不是那么容易但代碼并不難,希望你喜歡它。

OpenGL中的絕大多數特效都與某些類型的(色彩)混合有關。混色的定義為,將某個像素的顏色和已繪制在屏幕上與其對應的像素顏色相互結合。至于如何結合這兩種顏色則依賴于顏色的alpha通道的分量值,以及所用的混色函數。Alpha通常是位于顏色值末尾的第4個顏色組成分量,一般都認為Alpha分量代表材料的透明度。也就是說,alpha值為0.0時所代表的材料是完全透明的,alpha值為1.0時所代表的材料則是完全不透明的。

在OpenGL中實現混色的步驟類似于我們以前提到的OpenGL過程,接著設置公式,并在繪制透明對象時關閉寫深度緩存。因為我們想在半透明的圖形背后繪制對象,這不是正確的混色方法,但絕大多數時候這種做法在簡單的項目中都工作得很好。正確的混色過程應該是先繪制全部非透明場景之后,再繪制透明的圖形,并且要按照與深度緩存相反的次序來繪制(先畫最遠的物體)。

程序運行時效果如下:

下面進入教程:

我們這次將在第07課的基礎上修改代碼,首先打開myglwidget.h文件,增加一個布爾變量m_Blend來記錄是否開啟混合,修改后代碼如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
boolfullscreen;//是否全屏顯示

QStringm_FileName;//圖片的路徑及文件名
GLuintm_Texture;//儲存一個紋理

boolm_Light;//光源的開/關
boolm_Blend;//是否混合

GLfloatm_xRot;//x旋轉角度
GLfloatm_yRot;//y旋轉角度
GLfloatm_xSpeed;//x旋轉速度
GLfloatm_ySpeed;//y旋轉速度
GLfloatm_Deep;//深入屏幕的距離
};

#endif//MYGLWIDGET_H

接下來打開myglwidget.cpp文件,加上聲明#include <QTimer>,在構造函數中對增加變量進行初始化并更換圖片,使用不同的紋理來繪畫立方體,具體修改后代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_FileName="D:/QtOpenGL/QtImage/Glass.bmp";//應根據實際存放圖片的路徑進行修改
m_Light=false;
m_Blend=false;

m_xRot=0.0f;
m_yRot=0.0f;
m_xSpeed=0.0f;
m_ySpeed=0.0f;
m_Deep=-5.0f;

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}


然后就要進入重點的混合,其他代碼非常簡單,并不像解釋它時那么麻煩,只需要對initializeGL()作一定的修改,具體代碼如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
m_Texture=bindTexture(QPixmap(m_FileName));//載入位圖并轉換成紋理
glEnable(GL_TEXTURE_2D);//啟用紋理映射

glClearColor(0.0f,0.0f,0.0f,0.0f);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑

glClearDepth(1.0);//設置深度緩存
glEnable(GL_DEPTH_TEST);//啟用深度測試
glDepthFunc(GL_LEQUAL);//所作深度測試的類型
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正

//下面是光源部分
GLfloatLightAmbient[]={0.5f,0.5f,0.5f,1.0f};//環境光參數
GLfloatLightDiffuse[]={1.0f,1.0f,1.0f,1.0f};//漫散光參數
GLfloatLightPosition[]={0.0f,0.0f,2.0f,1.0f};//光源位置
glLightfv(GL_LIGHT1,GL_AMBIENT,LightAmbient);//設置環境光
glLightfv(GL_LIGHT1,GL_DIFFUSE,LightDiffuse);//設置漫射光
glLightfv(GL_LIGHT1,GL_POSITION,LightPosition);//設置光源位置
glEnable(GL_LIGHT1);//啟動一號光源

//下面是混合部分
glColor4f(1.0f,1.0f,1.0f,0.5f);//全亮度,50%Alpha混合
glBlendFunc(GL_SRC_ALPHA,GL_ONE);//基于源像素alpah通道值得半透明混合函數
}

增加了兩行代碼,第一行以全亮度繪制此物體,并對其進行50%的alpha混合(半透明),當混合選項開啟時,次物體將會產生50%的透明效果。第二行設置所采用的混合類型。看,代碼真的挺簡單的。

最后是鍵盤控制的代碼,具體代碼如下:

voidMyGLWidget::keyPressEvent(QKeyEvent*event)
{
switch(event->key())
{
caseQt::Key_F1://F1為全屏和普通屏的切換鍵
fullscreen=!fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
}
break;
caseQt::Key_Escape://ESC為退出鍵
close();
break;
caseQt::Key_B://B為開始關閉混合而對切換鍵
m_Blend=!m_Blend;
if(m_Blend)
{
glEnable(GL_BLEND);//開啟混合
glDisable(GL_DEPTH_TEST);//關閉深度測試
}
else
{
glDisable(GL_BLEND);//關閉混合
glEnable(GL_DEPTH_TEST);//打開深度測試
}
break;
caseQt::Key_L://L為開啟關閉光源的切換鍵
m_Light=!m_Light;
if(m_Light)
{
glEnable(GL_LIGHTING);//開啟光源
}
else
{
glDisable(GL_LIGHTING);//關閉光源
}
break;
caseQt::Key_PageUp://PageUp按下使木箱移向屏幕內部
m_Deep-=0.1f;
break;
caseQt::Key_PageDown://PageDown按下使木箱移向觀察者
m_Deep+=0.1f;
break;
caseQt::Key_Up://Up按下減少m_xSpeed
m_xSpeed-=0.1f;
break;
caseQt::Key_Down://Down按下增加m_xSpeed
m_xSpeed+=0.1f;
break;
caseQt::Key_Right://Right按下減少m_ySpeed
m_ySpeed-=0.1f;
break;
caseQt::Key_Left://Left按下增加m_ySpeed
m_ySpeed+=0.1f;
break;
}
}

當B鍵的控制機制與L鍵相似,但注意到,開啟混合時還要關閉深度測試,關閉混合時還要打開深度測試,否則將發現立方體有一些面不見了!

現在就可以運行程序看效果了!

第09課:在3D空間中移動位圖

想知道如何在3D空間中移動物體,想知道如何在屏幕上繪制一個圖像,而讓圖像的背景色變為透明,希望有一個簡單的動畫。這次教程中將教會你所以的一切。當然,這一課是在前面幾課知識的基礎上創建的,請確保你已經掌握了前面幾課的知識,再進入本課教程。

歡迎進入這次教程,這一課將是前面幾課的綜合。前面的學習中,我們學會了設置一個OpenGL窗口的每個細節,學會在旋轉的物體上貼圖并打上光線以及混色(透明)處理。這一課中,我們將在3D場景中移動位圖,并去除位圖上的黑色像素(使用混色)。接著為黑白紋理上色,最后我們將學會創建豐富的色彩,并把混合了不同色彩的紋理相互混合,得到簡單的動畫效果。

程序運行時效果如下:

下面進入教程:

我們這次將在第01課的基礎上修改代碼,其中一些與前幾課重復的地方我不作過多解釋。首先打開myglwdiget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
boolfullscreen;//是否全屏顯示

QStringm_FileName;//圖片的路徑及文件名
GLuintm_Texture;//儲存一個紋理
boolm_Twinkle;//星星是否閃爍

staticconstintnum=50;//星星的數目
structstar{//為星星創建的結構體
intr,g,b;//星星的顏色
GLfloatdist;//星星距離中心的距離
GLfloatangle;//當前星星所處的角度
}m_stars[num];

GLfloatm_Deep;//星星離觀察者的距離
GLfloatm_Tilt;//星星的傾角
GLfloatm_Spin;//星星的自轉
};

#endif//MYGLWIDGET_H

首先是一個布爾變量m_Twinkle用來表示星星是否閃爍。然后我們創建了一個星星的結構體,結構體包含星星的顏色,離中心距離以及所處角度,并創建了一個大小為50的星星數組。最后三個浮點變量依次表示星星離觀察者距離,星星的傾角,星星的自轉,這三個浮點變量用于對整體視圖的控制。

接下來,我們還是打開myglwidget.cpp,加上聲明#include <QTimer>,在構造函數中對新增變量進行初始化,只解釋小部分,希望大家結合注釋可以理解,代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_FileName="D:/QtOpenGL/QtImage/Star.bmp";//應根據實際存放圖片的路徑進行修改
m_Twinkle=false;//默認初始狀態為不閃爍

for(inti=0;i<num;i++)//循環初始化所有的星星
{
//隨機獲得星星顏色
m_stars[i].r=rand()%256;
m_stars[i].g=rand()%256;
m_stars[i].b=rand()%256;

m_stars[i].dist=((float)i/num)*5.0f;//計算星星離中心的距離,最大半徑為5.0
m_stars[i].angle=0.0f;//所以星星都從0度開始旋轉
}

m_Deep=-15.0f;//深入屏幕15.0單位
m_Tilt=90.0f;//初始傾角為90度(面對觀察者)
m_Spin=0.0f;//從0度開始自轉

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

利用循環對星星的數據進行初始化,第i 顆星星離中心的距離是將i 的值除以星星的總數,然后乘上5.0f。基本上這樣使得后一顆星星比前一顆星星離中心更遠一點,這樣當i = 50時,就剛好達到最大半徑5.0f了。然后我們選擇顏色都是從0~255之間取一個隨機數,為何這里不是通常的0.0f~1.0f呢?這里我們使用的顏色設置函數時glColor4ub,而不是之前的glColor4f,ub意味著參數是Unsigned Byte型的,同時這里去隨機數整數似乎要比取一個浮點的隨機數更容易一些。

然后我們要對initializeGL()函數作一定的修改,修改后代碼如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
m_Texture=bindTexture(QPixmap(m_FileName));
glEnable(GL_TEXTURE_2D);

glClearColor(0.0,0.0,0.0,0.0);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑
glClearDepth(1.0);//設置深度緩存
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正

glBlendFunc(GL_SRC_ALPHA,GL_ONE);//設置混色函數取得半透明效果
glEnable(GL_BLEND);//開啟混合(混色)
}

這里我們不打算使用深度測試,如果你使用第01課的代碼的話,請確認是否已經去掉了glDepthFunc(GL_LEQUAL);和glEnable(GL_DEPTH_TEST);兩行。否則,你所見到的最終效果會一團糟。這里我們使用了紋理映射,因此請你確認你已經加入了這些這一課中所沒有的代碼。同樣要注意的是我們也開啟了混合(混色),這是為了給紋理上色,產生不同顏色的星星。

還有就是最重點的paintGL()函數,我會一一作出解釋,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glBindTexture(GL_TEXTURE_2D,m_Texture);//選擇紋理

for(inti=0;i<num;i++)
{
glLoadIdentity();//繪制每顆星星之前,重置模型觀察矩陣
glTranslatef(0.0f,0.0f,m_Deep);//深入屏幕里面
glRotatef(m_Tilt,1.0f,0.0f,0.0f);//傾斜視角
glRotatef(m_stars[i].angle,0.0f,1.0f,0.0f);//旋轉至當前所畫星星的角度
glTranslatef(m_stars[i].dist,0.0f,0.0f);//沿x軸正向移動
glRotatef(-m_stars[i].angle,0.0f,1.0f,0.0f);//取消當前星星的角度
glRotatef(-m_Tilt,1.0f,0.0f,0.0f);//取消視角傾斜

if(m_Twinkle)//啟動閃爍效果
{
//使用byte型數據值指定一個顏色
glColor4ub(m_stars[num-i-1].r,m_stars[num-i-1].g,m_stars[num-i-1].b,255);
glBegin(GL_QUADS);//開始繪制紋理映射過的四邊形
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,-1.0f,0.0f);
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,-1.0f,0.0f);
glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,0.0f);
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,0.0f);
glEnd();//四邊形繪制結束
}

glRotatef(m_Spin,0.0f,0.0f,1.0f);//繞z軸旋轉星星
//使用byte型數據值指定一個顏色
glColor4ub(m_stars[i].r,m_stars[i].g,m_stars[i].b,255);
glBegin(GL_QUADS);//開始繪制紋理映射過的四邊形
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,-1.0f,0.0f);
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,-1.0f,0.0f);
glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,0.0f);
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,0.0f);
glEnd();//四邊形繪制結束

m_Spin+=0.01f;//星星的自轉
m_stars[i].angle+=(float)i/num;//改變星星的公轉角度
m_stars[i].dist-=0.01f;//改變星星離中心的距離
if(m_stars[i].dist<0.0f)//星星到達中心了么
{
m_stars[i].dist+=5.0f;//往外移5.0單位
m_stars[i].r=rand()%256;
m_stars[i].g=rand()%256;
m_stars[i].b=rand()%256;
}
}
}

首先是清屏和綁定紋理,接著進入循環,畫每顆星星前當然要重置模型觀察矩陣并進行視圖的移動旋轉,然后我們來移動星星。我們要做的第一件事是把場景沿y軸旋轉。如果我們旋轉90度的話,x軸就不再是從左到右的了,它將從里到外穿出屏幕。第二行代碼沿x軸移動一個正值,通常這樣代表移向了屏幕的右側,但由于我們繞y軸旋轉了坐標系,x軸的正向可以使任意方向。因此,當我們沿x軸正向移動時,可能向左、向右、向前、向后。

接著的代碼帶一點小技巧。我們繪制的星星實際上是一個平面的紋理,現在我們在屏幕中心畫了個平面的四邊形然后貼上紋理,這看起來很不錯。但是當我們繞著y軸轉上個90度的話,紋理在屏幕上就只剩下右側和左側的兩條邊朝著我們了,看起來就是一條細線,這不并不是我們所想要的,我們希望星星永遠正面朝著我們。因此,在繪制星星之前,我們通過逆序旋轉來抵消之前對星星所作的任何旋轉,當然旋轉的角度就要加上- 號了。

然后到了if 條件從句,如果m_Twinkle為TRUE,我們在屏幕上先畫一次不旋轉的星星,當我們畫第i顆星星時,將采用第num-i-1顆星星的顏色使得顏色不同。由于開啟了m_Twinkle,每顆星星最后會被繪制兩遍,兩遍繪制的星星顏色相互融合,會產生很棒的效果,看起來比原來亮了許多。值得注意的是,給紋理上色是件很容易的事,盡管紋理本身是黑白的,紋理將變成我們在繪制它之前選定的任意顏色。if 條件從句后,我們要繪制第二遍的星星,和前面不同的是,這一遍的星星肯定會被繪制,并且這次的星星繞著z軸旋轉(星星的自轉)。

后面的代碼代表星星的運動,我們增加m_Spin的值來控制星星自轉,然后將每顆星星的公轉角度增加 i/num這使得離中心更遠的星星轉得更快,最后減少每顆星星離屏幕中心的距離,這樣看起來星星們好像被不斷地吸入屏幕的中心。

最后幾行是檢查星星是否已經碰到了屏幕中心。當星星碰到屏幕中心時,我們為它賦上新顏色,然后往外移5.0單位,這顆星星將重新踏上回歸屏幕中心的旅程。

最后就是鍵盤控制部分了,具體代碼如下(相信大家結合注釋可以很容易看懂):

voidMyGLWidget::keyPressEvent(QKeyEvent*event)
{
switch(event->key())
{
caseQt::Key_F1://F1為全屏和普通屏的切換鍵
fullscreen=!fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
}
updateGL();
break;
caseQt::Key_Escape://ESC為退出鍵
close();
break;
caseQt::Key_T://T為星星開啟關閉閃爍的切換鍵
m_Twinkle=!m_Twinkle;
break;
caseQt::Key_Up://Up按下屏幕向上傾斜
m_Tilt-=0.5f;
break;
caseQt::Key_Down://Down按下屏幕向下傾斜
m_Tilt+=0.5f;
break;
caseQt::Key_PageUp://PageUp按下縮小
m_Deep-=0.1f;
break;
caseQt::Key_PageDown://PageDown按下放大
m_Deep+=0.1f;
break;
}
}

現在就可以運行程序查看效果了!

第10課:加載3D世界,并在其中漫游(參照NeHe)
這次教程中,我將教大家如何加載一個3D世界,并在3D世界中漫游。這相較于我們只能創造一個旋轉的立方體或一群星星時有很大的進步了,當然這節課代碼難度不低,但也不會很難,只要你跟著我慢慢一步一步來。
一個3D世界當然不像我們之前那樣,只要幾個對象就搞定了,因此,我們會選擇將3D環境用數據來表達,并存放在一個文本中。隨著環境復雜度的上升,這個工作得難度也會隨之上升。出于這個原因,我們必須將數據歸類,使其具有更多的可操作性風格。后面程序中,我們會把3D世界看作是區段(sector)的集合。一個區段可以是一個房間、一個立方體或者任意一個閉合的空間。

程序運行時效果如下:



下面進入教程:

我們這次將在第01課的基礎上修改代碼,其中一些與前幾課重復的地方我不作過多解釋。首先打開myglwidget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

typedefstructtagVERTEX//創建Vertex頂點結構體
{
floatx,y,z;//3D坐標
floatu,v;//紋理坐標
}VERTEX;

typedefstructtagTRIANGLE//創建Triangle三角形結構體
{
VERTEXvertexs[3];//3個頂點構成一個Triangle
}TRIANGLE;

typedefstructtagSECTOR//創建Sector區段結構體
{
intnumtriangles;//Sector中的三角形個數
QVector<TRIANGLE>vTriangle;//儲存三角形的向量
}SECTOR;

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
boolfullscreen;//是否全屏顯示

QStringm_FileName;//圖片的路徑及文件名
GLuintm_Texture;//儲存一個紋理
QStringm_WorldFile;//存放世界的路徑及文本名
SECTORm_Sector;//儲存一個區段的數據

staticconstfloatm_PIOVER180=0.0174532925f;//實現度和弧度直接的折算
GLfloatm_xPos;//儲存當前位置
GLfloatm_zPos;
GLfloatm_yRot;//視角的旋轉
GLfloatm_LookUpDown;//記錄抬頭和低頭
};

#endif//MYGLWIDGET_H

可以看到我們定義了3個結構體,依次表示頂點,三角形和區段。一個區段包含一系列的多邊形(三角形),三角形本質上是由三個以上頂點組合的圖形,頂點就是我們最基本的分類單位了。頂點包含了OpenGL真正感興趣的數據,我們用3D空間中的坐標值(x, y, z)以及它們的紋理坐標(u, v)來定義三角形的每個頂點。這次教程中,我們只加載了一個區段的數據,故只需一個m_Sector數據就夠了(當然有興趣的可以自己設計區段數據,多加載幾個看看)。
其他增加的變量,m_PIOVER180就是一個度數和弧度制的折算因子,m_xPos、m_zPos用于記錄游戲者的位置,m_yRot用于記錄游戲者視角的旋轉,m_LookUpDown用于控制游戲者的仰視俯視,簡單點說就是抬頭低頭啦。

接下來,我們需要打開myglwidget.cpp,加上聲明#include <QTimer>、#include <QTextStream>、#include <QtMath>,在構造函數中對數據進行初始化,具體代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_FileName="D:/QtOpenGL/QtImage/Mud.bmp";//應根據實際存放圖片的路徑進行修改
m_WorldFile="D:/QtOpenGL/QtImage/World.txt";
m_Sector.numtriangles=0;

QFilefile(m_WorldFile);
file.open(QIODevice::ReadOnly|QIODevice::Text);//將要讀入數據的文本打開
QTextStreamin(&file);
while(!in.atEnd())
{
QStringline[3];
for(inti=0;i<3;i++)//循環讀入3個點數據
{
do//讀入數據并保證數據有效
{
line[i]=in.readLine();
}
while(line[i][0]=='/'||line[i]=="");
}
m_Sector.numtriangles++;//每成功讀入3個點構成一個三角形
TRIANGLEtempTri;
for(inti=0;i<3;i++)//將數據儲存于一個三角形中
{
QTextStreaminLine(&line[i]);
inLine>>tempTri.vertexs[i].x
>>tempTri.vertexs[i].y
>>tempTri.vertexs[i].z
>>tempTri.vertexs[i].u
>>tempTri.vertexs[i].v;
}
m_Sector.vTriangle.push_back(tempTri);//將三角形放入m_Sector中
}
file.close();

m_xPos=0.0f;
m_zPos=0.0f;
m_yRot=0.0f;
m_LookUpDown=0.0f;

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

我們重點解釋中間對于m_Sector的初始化,我們先將文件打開,再利用Qt的文本流一行一行的讀取(為何一行一行讀,大家看下存放數據的文本文件World.txt就知道了)并保證讀入的數據是有效的。每當成功讀入三行數據時,說明構成了一個三角形,就創建一個三角形來儲存這些數據,并在最后把三角形放入m_Sector中,當然要給m_Sector的numtriangles加上一,說明多了一個三角形。最后錄完數據后,關上文件。或者你會想如果有效數據行數不是3的倍數怎么辦,這個問題其實已經不是我們的問題了,而且提供的數據文本存在問題,因此不必考慮。接著的數據初始化不作解釋了。

然后在initializeGL()函數中,請大家修改代碼如下(不解釋):

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
m_Texture=bindTexture(QPixmap(m_FileName));
glEnable(GL_TEXTURE_2D);

glClearColor(0.0,0.0,0.0,0.0);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑
glClearDepth(1.0);//設置深度緩存
glEnable(GL_DEPTH_TEST);//啟用深度測試
glDepthFunc(GL_LEQUAL);//所作深度測試的類型
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正
}

任何一個不錯的的3D引擎都會允許用戶在這個世界中游走和遍歷,我們的這個也一樣,實現這個功能當然要通過鍵盤控制。具體實現的途徑有一種是直接移動鏡頭并繪制以鏡頭為中心的3D環境,但這樣會很慢并且不易用代碼實現,我們的解決方法如下:
根據用戶的指令旋轉并變換視角位置。
圍繞原點,以與視角相反的旋轉方向來旋轉世界(讓人產生視角旋轉的錯覺)。
以與視角平移方向相反的方向來平移世界(讓人產生視角移動的錯覺)。
這樣實現起來就簡單多了。下面我們先通過鍵盤控制,來實現平移并旋轉視角。

voidMyGLWidget::keyPressEvent(QKeyEvent*event)
{
switch(event->key())
{
caseQt::Key_F1://F1為全屏和普通屏的切換鍵
fullscreen=!fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
}
updateGL();
break;
caseQt::Key_Escape://ESC為退出鍵
close();
break;
caseQt::Key_PageUp://按下PageUp視角向上轉
m_LookUpDown-=1.0f;
if(m_LookUpDown<-90.0f)
{
m_LookUpDown=-90.0f;
}
break;
caseQt::Key_PageDown://按下PageDown視角向下轉
m_LookUpDown+=1.0f;
if(m_LookUpDown>90.0f)
{
m_LookUpDown=90.0f;
}
break;
caseQt::Key_Right://Right按下向左旋轉場景
m_yRot-=1.0f;
break;
caseQt::Key_Left://Left按下向右旋轉場景
m_yRot+=1.0f;
break;
caseQt::Key_Up://Up按下向前移動
//向前移動分到x、z上的分量
m_xPos-=(float)sin(m_yRot*m_PIOVER180)*0.05f;
m_zPos-=(float)cos(m_yRot*m_PIOVER180)*0.05f;
break;
caseQt::Key_Down://Down按下向后移動
//向后移動分到x、z上的分量
m_xPos+=(float)sin(m_yRot*m_PIOVER180)*0.05f;
m_zPos+=(float)cos(m_yRot*m_PIOVER180)*0.05f;
break;
}
}

這個實現很簡單。當左右方向鍵按下后,旋轉變量m_yRot相應的增加或減少。當前后方向鍵按下時,我們使用sin()和cos()函數計算具體在x和z軸方向上的位移量,使得游戲者能準確的移動。

現在我們已經具備了一切所需的數據,可以開始進行步驟2和3了,當然我們也將進入重點的paintGL()函數。雖然重點,但代碼并不難,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置當前的模型觀察矩陣

GLfloatx_m,y_m,z_m,u_m,v_m;//頂點的臨時x、y、z、u、v值
GLfloatxTrans=-m_xPos;//游戲者沿x軸平移時的大小
GLfloatzTrans=-m_zPos;//游戲者沿z軸平移時的大小
GLfloatyTrans=-0.25f;//游戲者沿y軸略作平移,使視角準確
GLfloatsceneroty=360.0f-m_yRot;//游戲者的旋轉

glRotatef(m_LookUpDown,1.0f,0.0f,0.0f);//抬頭低頭的旋轉
glRotatef(sceneroty,0.0f,1.0f,0.0f);//根據游戲者正面所對方向所作的旋轉
glTranslatef(xTrans,yTrans,zTrans);//以游戲者為中心平移場景

glBindTexture(GL_TEXTURE_2D,m_Texture);//綁定紋理
for(inti=0;i<m_Sector.numtriangles;i++)//遍歷所有的三角形
{
glBegin(GL_TRIANGLES);//開始繪制三角形
glNormal3f(0.0f,0.0f,1.0f);//指向前面的法線
x_m=m_Sector.vTriangle[i].vertexs[0].x;
y_m=m_Sector.vTriangle[i].vertexs[0].y;
z_m=m_Sector.vTriangle[i].vertexs[0].z;
u_m=m_Sector.vTriangle[i].vertexs[0].u;
v_m=m_Sector.vTriangle[i].vertexs[0].v;
glTexCoord2f(u_m,v_m);
glVertex3f(x_m,y_m,z_m);

x_m=m_Sector.vTriangle[i].vertexs[1].x;
y_m=m_Sector.vTriangle[i].vertexs[1].y;
z_m=m_Sector.vTriangle[i].vertexs[1].z;
u_m=m_Sector.vTriangle[i].vertexs[1].u;
v_m=m_Sector.vTriangle[i].vertexs[1].v;
glTexCoord2f(u_m,v_m);
glVertex3f(x_m,y_m,z_m);

x_m=m_Sector.vTriangle[i].vertexs[2].x;
y_m=m_Sector.vTriangle[i].vertexs[2].y;
z_m=m_Sector.vTriangle[i].vertexs[2].z;
u_m=m_Sector.vTriangle[i].vertexs[2].u;
v_m=m_Sector.vTriangle[i].vertexs[2].v;
glTexCoord2f(u_m,v_m);
glVertex3f(x_m,y_m,z_m);
glEnd();//三角形繪制結束
}
}

就正如我們之前步驟2和3所說,我們以相反的方式來平移和旋轉場景,使得看上去是視角在平移和旋轉,然后綁定紋理并繪制出整個場景就完成了!
現在就可以運行程序查看效果了!

PS:NeHe教程中有關于移動中視角輕微上下擺動的設定,在這個教程中被我刪掉了,由于我發現這個效果并不明顯,有興趣的朋友請看http://www.yakergong.net/nehe/

第11課:旗幟效果(飄動的紋理)(參照NeHe)

這次教程中,我將教大家如何創建一個飄動的旗幟。我們所要創建的旗幟,說白了就是一個以正弦波方式運動的紋理映射圖像。雖然不會很難,但效果確實很不錯,希望大家能喜歡。當然這次教程是基于第06課的,希望大家確保已經掌握了前6課再進入本次教程。

程序運行時效果如下:

下面進入教程:

我們這次將在第06課的基礎上修改代碼,我們只會解釋增加部分的代碼,首先打開myglwidget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
boolfullscreen;//是否全屏顯示

GLfloatm_xRot;//繞x軸旋轉的角度
GLfloatm_yRot;//繞y軸旋轉的角度
GLfloatm_zRot;//繞z軸旋轉的角度
QStringm_FileName;//圖片的路徑及文件名
GLuintm_Texture;//儲存一個紋理

floatm_Points[45][45][3];//儲存網格頂點的數組
intm_WiggleCount;//用于控制旗幟波浪運動動畫
};

#endif//MYGLWIDGET_H

我們增加了m_Points三維數組來存放網格各頂點獨立的x、y、z坐標,這里網格由45×45點形成,換句話說也就是由44格×44格的小方格子組成的。另一個新增變量m_WiggleCount用來使產生紋理波浪運動動畫,每2幀一次變換波動形狀看起來很不錯。

接下來,我們需要打開myglwidget.cpp,加上聲明#include <QtMath>,在構造函數對新增變量數據進行初始化,具體代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_xRot=0.0f;
m_yRot=0.0f;
m_zRot=0.0f;
m_FileName="D:/QtOpenGL/QtImage/Tim.bmp";//應根據實際存放圖片的路徑進行修改

for(intx=0;x<45;x++)//初始化數組產生波浪效果(靜止)
{
for(inty=0;y<45;y++)
{
m_Points[x][y][0]=float((x/5.0f)-4.5f);
m_Points[x][y][1]=float((y/5.0f)-4.5f);
m_Points[x][y][2]=float(sin((((x/5.0f)*40.0f)/360.0f)*3.141592654*2.0f));
}
}
m_WiggleCount=0;

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

增加的代碼就是一個循環,利用循環來添加波浪效果(只是讓旗幟看起來有起伏效果,還不能達到波動動畫的目的)。值得注意的是,我們在求m_Points[x][y][0]和m_Points[x][y][1]時,都是用x、y除以5.0f,如果除以5的話,由于整數除法取整,會導致畫面出現鋸齒效果,這顯然不是我們想要的。最后減去4.5f這樣使得計算結果落在區間[-4.5, 4.5],也就讓我們的波浪可以“居中”了。點m_Points[x][y][2]最后的值就是一個sin()函數計算的結果(因為我們模擬的是正弦波運動),×8.0f是求相應角度(360度平分到45個點就是8度一個點了),最后角度轉換到弧度制我就不多做解釋了。

然后在initializeGL()函數中,請大家修改代碼如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
m_Texture=bindTexture(QPixmap(m_FileName));//載入位圖并轉換成紋理
glEnable(GL_TEXTURE_2D);//啟用紋理映射

glClearColor(0.0,0.0,0.0,0.0);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑
glClearDepth(1.0);//設置深度緩存
glEnable(GL_DEPTH_TEST);//啟用深度測試
glDepthFunc(GL_LEQUAL);//所作深度測試的類型
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正

glPolygonMode(GL_BACK,GL_FILL);//后表面完全填充
glPolygonMode(GL_FRONT,GL_LINE);//前表面使用線條繪制
}

最后加了兩行代碼,用來指定使用完全填充模式來填充多邊形區域的后表面,而多邊形的前表面則使用輪廓線填充,這些方式完全取決于你的個人喜好,這里我們只是為了區分前后表面罷了。

最后,我們將重寫整個paintGL()函數,當然這依舊是重點,代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置當前的模型觀察矩陣

glTranslatef(0.0f,0.0f,-15.0f);//移入屏幕15.0單位
glRotatef(m_xRot,1.0f,0.0f,0.0f);//繞x旋轉
glRotatef(m_yRot,0.0f,1.0f,0.0f);//繞y旋轉
glRotatef(m_zRot,0.0f,0.0f,1.0f);//繞z旋轉

glBindTexture(GL_TEXTURE_2D,m_Texture);//旋轉紋理
floatflag_x1,flag_y1,flag_x2,flag_y2;//用來將紋理分割成小的四邊形方便紋理映射
glBegin(GL_QUADS);
for(intx=0;x<44;x++)
{
for(inty=0;y<44;y++)
{
//分割紋理
flag_x1=float(x)/44.0f;
flag_y1=float(y)/44.0f;
flag_x2=float(x+1)/44.0f;
flag_y2=float(y+1)/44.0f;

//繪制一個小的四邊形
glTexCoord2f(flag_x1,flag_y1);
glVertex3f(m_Points[x][y][0],m_Points[x][y][1],m_Points[x][y][2]);
glTexCoord2f(flag_x1,flag_y2);
glVertex3f(m_Points[x][y+1][0],m_Points[x][y+1][1],m_Points[x][y+1][2]);
glTexCoord2f(flag_x2,flag_y2);
glVertex3f(m_Points[x+1][y+1][0],m_Points[x+1][y+1][1],m_Points[x+1][y+1][2]);
glTexCoord2f(flag_x2,flag_y1);
glVertex3f(m_Points[x+1][y][0],m_Points[x+1][y][1],m_Points[x+1][y][2]);
}
}
glEnd();

if(m_WiggleCount==3)//用來變換波浪形狀(每2幀一次)產生波浪動畫
{
//利用循環使波浪值集體左移,最左側波浪值到了最右側
for(inty=0;y<45;y++)
{
floattemp=m_Points[0][y][2];
for(intx=0;x<44;x++)
{
m_Points[x][y][2]=m_Points[x+1][y][2];
}
m_Points[44][y][2]=temp;
}
m_WiggleCount=0;//計數器清零
}
m_WiggleCount++;//計數器加一

m_xRot+=0.3f;
m_yRot+=0.2f;
m_zRot+=0.4f;
}

我們創建了四個浮點臨時變量并利用循環和除法,將紋理分割成小的四邊形,使得我們能準確的對應進行紋理映射,然后畫出全部的四邊形拼到一起就是一個波動狀態的旗幟了。

接著我們判斷一下m_WiggleCount是否為2,如果是,就將波浪值m_Points[x][y][2]集體循環左移(最左側波浪值會到最右側)。這樣我們相當于每2幀一次變化了旗幟的波動狀態,看起來就是一個飄動的旗幟,不是靜止的了(大家可以嘗試著注釋掉某一部分代碼看看發生什么改變)。然后計數器清零加一什么的就不過多解釋了!

現在就可以運行程序查看效果了!

第12課:顯示列表(參照NeHe)
想知道如何加速我們的OpenGL程序么?這次教程中,我將告訴你如何使用OpenGL的顯示列表,它通過預編譯OpenGL命令來加速我們的程序,并可以為我們省去很多重復的代碼,聽起來是不是很棒呢!
當我們在制作游戲里的小行星場景時,每一層至少需要兩個行星,你可以用OpenGL中的多邊形來構造每一個行星。但要知道每次把行星畫到屏幕上都是很麻煩的,當我們面臨復雜的場景時,要靠代碼的繪畫方式一個個畫出所有的行星,這對于絕大多數人來說都是一個噩夢。那么,解決辦法是什么呢?用顯示列表,我們只需要一次性建立物體,可以貼圖,用顏色,想怎么弄就怎么弄。然后給顯示列表一個名字,比如給小行星的顯示列表命名為“asteroid”。現在,任何時候,我們想在屏幕上畫出行星,我們只需要調好位置后,調用glCallList(asteroid),之前做好的小行星就會立刻顯示在屏幕上了。由于小行星已經在顯示列表里建造好了,OpenGL不會再計算如何構造它。它已經在內存中建造好了,這將大大降低CPU的使用,讓你的程序跑得更快。

程序運行時效果如下:


下面進入教程:

我們這次同樣將在第06課的基礎上修改代碼,我們只會解釋增加部分的代碼,首先打開myglwidget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
voidbuildLists();//初始化盒子的顯示列表

private:
boolfullscreen;//是否全屏顯示

GLfloatm_xRot;//繞x軸旋轉的角度
GLfloatm_yRot;//繞y軸旋轉的角度
QStringm_FileName;//圖片的路徑及文件名
GLuintm_Texture;//儲存一個紋理

GLuintm_Box;//保存盒子的顯示列表
GLuintm_Top;//保存盒子頂部的顯示列表
};

#endif//MYGLWIDGET_H

我們新增了兩個用于顯示列表的變量m_Box、m_Top,這兩個變量是用于儲存指向顯示列表的指針。另外我們多了一個buildLists()函數,這個函數是用于初始化兩個顯示列表的(注意我去掉了變量m_zRot,但其實影響不大)。

接下來,我們需要打開myglwidget.cpp,修改構造函數同時添加buildLists()函數的定義,具體代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_xRot=0.0f;
m_yRot=0.0f;
m_FileName="D:/QtOpenGL/QtImage/Cube.bmp";//應根據實際存放圖片的路徑進行修改

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

voidMyGLWidget::buildLists()//創建盒子的顯示列表
{
m_Box=glGenLists(2);//創建兩個顯示列表的空間
glNewList(m_Box,GL_COMPILE);//開始創建第一個顯示列表
glBegin(GL_QUADS);
glTexCoord2f(0.0f,0.0f);
glVertex3f(1.0f,-1.0f,1.0f);//右上(底面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(-1.0f,-1.0f,1.0f);//左上(底面)
glTexCoord2f(1.0f,1.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);//左下(底面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(1.0f,-1.0f,-1.0f);//右下(底面)

glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,1.0f);//右上(前面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,1.0f);//左上(前面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,-1.0f,1.0f);//左下(前面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,-1.0f,1.0f);//右下(前面)

glTexCoord2f(0.0f,0.0f);
glVertex3f(1.0f,-1.0f,-1.0f);//右上(后面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);//左上(后面)
glTexCoord2f(1.0f,1.0f);
glVertex3f(-1.0f,1.0f,-1.0f);//左下(后面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(1.0f,1.0f,-1.0f);//右下(后面)

glTexCoord2f(1.0f,1.0f);
glVertex3f(-1.0f,1.0f,1.0f);//右上(左面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,-1.0f);//左上(左面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);//左下(左面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(-1.0f,-1.0f,1.0f);//右下(左面)

glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,-1.0f);//右上(右面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(1.0f,1.0f,1.0f);//左上(右面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(1.0f,-1.0f,1.0f);//左下(右面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,-1.0f,-1.0f);//右下(右面)
glEnd();
glEndList();//第一個顯示列表結束

m_Top=m_Box+1;//m_Box+1得到第二個顯示列表的指針
glNewList(m_Top,GL_COMPILE);//開始創建第二個顯示列表
glBegin(GL_QUADS);
glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,-1.0f);//右上(頂面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,-1.0f);//左上(頂面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,1.0f,1.0f);//左下(頂面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,1.0f,1.0f);//右下(頂面)
glEnd();
glEndList();
}

構造函數不解釋了,就刪掉了m_zRot的初始化。buildLists()函數中,我們會將創造盒子的代碼都放在第一個顯示列表里,所有創造頂部的代碼都在另一個顯示列表里。開始時,我們告訴OpenGL我們要建立兩個顯示列表,glGenLists(2)創建了兩個顯示列表的空間,并返回第一個列表的指針,我們把它儲存在m_Box中,任何時候我們調用glCallList(m_Box)第一個顯示列表就會繪制出來。
接下來我們開始構造第一個顯示列表。我們已經申請了兩個顯示列表的空間了,并且有m_Box指針指向第一個顯示列表,所以我們需要做的是告訴OpenGL要建立什么類型的顯示列表。我們用glNewList()命令來做這件事情,注意到m_Box是第一個參數,這表示OpenGL將把列表儲存到m_Box所指向的內存空間。而第二個參數GL_COMPILE告訴OpenGL我們想預先在內存中構造這個列表,這樣每次畫的時候就不必重新計算怎么構造物體了。
GL_COMPILE類似于編程。在我們寫程序的時候,把它裝載到編譯器里,我們每次運行程序都需要重新編譯。而如果它已經編譯成了.exe文件,那么每次我們只需要點擊那個.exe文件就可以運行它了,不需要編譯。當OpenGL編譯過顯示列表后,就不需要再每次顯示的時候重新編譯它了。這就是為什么用顯示列表可以加快速度。
下面我們就畫了一個沒有頂部的盒子,它不會出現在屏幕上,只會儲存在顯示列表里。我們可以在glNewList()和glEndList()中間加上任何你想加上的代碼,可以設置顏色,貼圖等等。但是,如果是你想在繪畫過程發生改變的代碼就不能加進去,這是由于顯示列表一旦建立,就不能改變它。比如我們想繪制不同顏色的物體,所以我們加上了glColor3ub(rand()%255,rand()%255,rand()%255),但因為顯示列表只會建立一次,所以每次畫出來的物體都是同一種顏色,也就是說在儲存進列表時glColor3ub的三個參數值就固定下來了。
然后我們用glEndList()命令告訴OpenGL我們已經完成了一個顯示列表。在glNewList()和glEndList()之間的任何東西就是顯示列表的一部分。接著,我們來建立第二個顯示列表,在上一個顯示列表的指針上加一,就得到了第二個顯示列表的指針,并儲存在m_Top中。最后同樣建立這個顯示列表就不解釋了。

然后在initializeGL()函數中,將代碼修改如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
m_Texture=bindTexture(QPixmap(m_FileName));//載入位圖并轉換成紋理
glEnable(GL_TEXTURE_2D);//啟用紋理映射
buildLists();//創建顯示列表

glClearColor(0.0f,0.0f,0.0f,0.0f);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑
glClearDepth(1.0);//設置深度緩存
glEnable(GL_DEPTH_TEST);//啟用深度測試
glDepthFunc(GL_LEQUAL);//所作深度測試的類型
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正
<prename="code"class="cpp">

glEnable(GL_LIGHT0); //使用默認的0號燈 glEnable(GL_LIGHTING); //使用燈光 glEnable(GL_COLOR_MATERIAL); //使用顏色材質}


我們在啟用紋理之后調用了buildLists()函數,創建了顯示列表,注意在構造函數中調用buildLists()函數時無法生效的,Qt中使用OpenGL的時候,與內存使用相關的OpenGL函數都需要在initialize()、resize()、paintGL()中直接調用或間接調用,否則無法成功地申請空間。
最后三行使的燈光有效,LIGHT一般是顯卡中預先定義過的。最后一行的GL_COLOR_MATERIAL使得我們可以用顏色來貼紋理。如果沒有這行代碼,紋理將始終保持原來的顏色,glColor3f(r, g,b)就沒有用了。
還有就是paintGL()函數,這次看起來就簡單許多了(麻煩的我們已經通過參數列表搞定了),具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
staticconstGLfloatboxColor[5][3]=//盒子的顏色數組
{
//亮:紅、橙、黃、綠、藍
{1.0f,0.0f,0.0f},{1.0f,0.5f,0.0f},{1.0f,1.0f,0.0f},
{0.0f,1.0f,0.0f},{0.0f,1.0f,1.0f}
};
staticconstGLfloattopColor[5][3]=//頂部的顏色數組
{
//暗:紅、橙、黃、綠、藍
{0.5f,0.0f,0.0f},{0.5f,0.25f,0.0f},{0.5f,0.5f,0.0f},
{0.0f,0.5f,0.0f},{0.0f,0.5f,0.5f}
};

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glBindTexture(GL_TEXTURE_2D,m_Texture);//選擇紋理

for(inty=1;y<6;y++)//循環來控制畫盒子
{
for(intx=0;x<y;x++)
{
glLoadIdentity();
//設置盒子的位置
glTranslatef(1.4f+(float(x)*2.8f)-(float(y)*1.4f),
((6.0f-float(y))*2.4f)-7.0f,-20.0f);
glRotatef(45.0f+m_xRot,1.0f,0.0f,0.0f);
glRotatef(45.0f+m_yRot,0.0f,1.0f,0.0f);
glColor3fv(boxColor[y-1]);//選擇盒子顏色
glCallList(m_Box);//繪制盒子
glColor3fv(topColor[y-1]);//選擇頂部顏色
glCallList(m_Top);//繪制頂部
}
}
}

我們一開始就定義了儲存盒子和頂部顏色的數組,接著我們用雙重循環來畫出10個立方體,并在每次畫時重置模型觀察矩陣,平移和旋轉到需要畫出立方體位置的中心,具體如何算的我看太懂NeHe的用意就不解釋了,反正這并不是重點,我們完全可以根據自己的喜好來擺放這些立方體。
然后我們選擇顏色,接著按前面所講,調用glCallList()就可以畫出我們要的立方體了。相比于之前的幾課,顯示列表讓我們的paintGL()函數看起來簡單了許多。

最后是鍵盤控制的,比較簡單我就不過多解釋了,具體代碼如下:

voidMyGLWidget::keyPressEvent(QKeyEvent*event)
{
switch(event->key())
{
caseQt::Key_F1://F1為全屏和普通屏的切換鍵
fullscreen=!fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
}
updateGL();
break;
caseQt::Key_Escape://ESC為退出鍵
close();
break;
caseQt::Key_Left://Left按下向左旋轉
m_yRot-=1.0f;
break;
caseQt::Key_Right://Right按下向右旋轉
m_yRot+=1.0f;
break;
caseQt::Key_Up://Up按下向上旋轉
m_xRot-=1.0f;
break;
caseQt::Key_Down://Down按下向下旋轉
m_xRot+=1.0f;
break;
}
}

現在就可以運行程序查看效果了!

第13課:位圖字體(參照NeHe)

這次教程中,我們將創建一些基于2D圖像的字體,它們可以縮放平移,但不能旋轉,并且總是面向前方,但作為基本的顯示來說,我想已經足夠了。

或者對于這次教程,你會覺得“在屏幕上顯示文字沒什么難的”,但是你真正嘗試過就會知道,它確實沒那么容易。你當然可以把文字寫在一個圖片上,再把這幅圖片載入你的OpenGL程序中,打開混合選項,從而在屏幕上顯示出文字。但這種做法非常耗時,而且經常圖像會顯得模糊。另外,除非你的圖像包含一個Alpha通道,否則一旦繪制在屏幕上,那些文字就會不透明(與屏幕中的其他物體混合)。

使用位圖字體比起使用圖形字體(貼圖)看起來不止強100倍,你可以隨時改變顯示在屏幕上的文字,而且用不著為它們逐個制作貼圖。只需要將文字定位,再調用我們即將構建的glPrint()函數就可以在屏幕上顯示文字了。

程序運行時效果如下:

下面進入教程:

我們這次將在第01課的基礎上修改代碼,我會對新增代碼一一解釋,希望大家能掌握,首先打開myglwidget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
voidbuildFont();//創建字體
voidkillFont();//刪除顯示列表
voidglPrint(constchar*fmt,...);//輸出字符串

private:
boolfullscreen;//是否全屏顯示
HDCm_HDC;//儲存當前設備的指針

intm_FontSize;//控制字體的大小
GLuintm_Base;//儲存繪制字體的顯示列表的開始位置
GLfloatm_Cnt1;//字體移動計數器1
GLfloatm_Cnt2;//字體移動計數器2
};

#endif//MYGLWIDGET_H

我們新增了幾個變量,第一個變量m_HDC是用來儲存當前設備繪制信息的一種windows數據結構,我們將會把我們自己創建的字體綁定到m_HDC上去,這樣我們繪制文字時就自動采用綁定的字體了。后面幾個變量的作用依次是控制字體大小、儲存繪制字體的顯示列表的開始位置、字體移動計數,具體的會在后面講。

另外我們增加了三個函數,分別用于創建字體、刪除顯示列表、輸出特定的字符串,當然最后一個glPrint()函數在前面已經提到,是個很重要的函數。

接下來,我們需要打開myglwidget.cpp,加上聲明#include <QTimer>、#include <QtMath>,將構造函數和析構函數修改一下,具體代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_FontSize=-18;
m_Cnt1=0.0f;
m_Cnt2=0.0f;

HWNDhWND=(HWND)winId();//獲取當前窗口句柄
m_HDC=GetDC(hWND);//通過窗口句柄獲得HDC

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

MyGLWidget::~MyGLWidget()
{
killFont();//刪除顯示列表
}

幾個普通變量的初始化我不作解釋了,我們重點看m_HDC的初始化。我們要如何獲得當前窗口的HDC呢?方法是我們先得到當前窗口的句柄(HWND),通過調用函數GetCD(HWND)可以獲得HDC。那如何獲得HWND呢?Qt中有一個winId()函數可以返回當前窗口的Id(類型為WId),我們把它強制轉換為HWND類型就可以了,這樣我們就可以初始化關鍵的m_HDC。

注意一下析構函數,在退出程序之前,我們應該確保我們分配的用于存放顯示列表的空間被釋放,所以我們在析構函數處調用killFont()函數刪除顯示列表(具體實現看下面)。

繼續,我們要來定義我們新增的三個函數了,這可是重頭戲,具體代碼如下:

voidMyGLWidget::buildFont()//創建位圖字體
{
HFONTfont;//字體句柄
m_Base=glGenLists(96);//創建96個顯示列表

font=CreateFont(m_FontSize,//字體高度
0,//字體寬度
0,//字體的旋轉角度
0,//字體底線的旋轉角度
FW_BOLD,//字體的重量
FALSE,//是否斜體
FALSE,//是否使用下劃線
FALSE,//是否使用刪除線
ANSI_CHARSET,//設置字符集
OUT_TT_PRECIS,//輸出精度
CLIP_DEFAULT_PRECIS,//剪裁精度
ANTIALIASED_QUALITY,//輸出質量
FF_DONTCARE|DEFAULT_PITCH,//FamilyandPitch的設置
LPCWSTR("CourierNew"));//字體名稱(電腦中已裝的)

wglUseFontBitmaps(m_HDC,32,96,m_Base);//創建96個顯示列表,繪制ASCII碼為32-128的字符
SelectObject(m_HDC,font);//選擇字體
}

voidMyGLWidget::killFont()//刪除顯示列表
{
glDeleteLists(m_Base,96);//刪除96個顯示列表
}

voidMyGLWidget::glPrint(constchar*fmt,...)//自定義輸出文字函數
{
chartext[256];//保存字符串
va_listap;//指向一個變量列表的指針

if(fmt==NULL)//如果無輸入則返回
{
return;
}

va_start(ap,fmt);//分析可變參數
vsprintf(text,fmt,ap);//把參數值寫入字符串
va_end(ap);//結束分析

glPushAttrib(GL_LIST_BIT);//把顯示列表屬性壓入屬性堆棧
glListBase(m_Base-32);//設置顯示列表的基礎值
glCallLists(strlen(text),GL_UNSIGNED_BYTE,text);//調用顯示列表繪制字符串
glPopAttrib();//彈出屬性堆棧
}

首先是buildFont()函數。我們先定義了字體句柄變量(HFONT),用來存放我們將要創建和使用的字體。接著我們在定義m_Base的同時使用glGenLists(96)創建了一組共96個顯示列表。然后我們調用Windows的API函數CreateFont()來創建我們自己的字體,前13個參數的意義大家請參考注釋,我覺得沒必要一個個解釋了(有興趣了解CreateFont各個參數請點擊此處),最后一個參數是字體類型,我們可以使用我們電腦已安裝的任何字體,在WindowsFonts目錄可查看電腦已安裝的字體。

然后我們從ASCII碼第32個字符(空格)開始建立96個顯示列表。如果你愿意,也可以建立所有256個字符,只要確保使用glGenLists建立256個顯示列表就可以了。最后我們將font對象指針選入HDC,如此就完成了字體的創建及綁定。

然后是killFont()函數。它很簡單,就是調用glDeleteLists()函數從m_Base開始刪除96個顯示列表。

最后是glPrint()函數。首先第一行我們創建一個大小為256個字符的字符數組,將用來保存我們要輸出的字符串。第二行我們創建了一個指向一個變量列表的指針,我們在傳遞字符串的同時也傳遞了這個變量列表。然后是排除字符串為空的情況。接著的三行代碼將文字中的所有符號轉換為它們的字符編號,最終文字和轉換的符號被儲存在字符串text中。然后我們將GL_LIST_BIT壓入屬性堆棧,它會防止glListBase影響到我們的程序中的其它顯示列表。

glListBase(m_Base-32)是告訴OpenGL去哪找對應字符的顯示列表,由于每個字符對應一個顯示列表,通過m_Base設置一個起點,OpenGL就知道到哪去找到正確的顯示列表。減去32是因為我們沒有構造前32個顯示列表,那么久跳過它們就好了。于是,我們不得不通過從m_Base的值減去32來讓OpenGL知道這一點。

現在OpenGL知道字母的存放位置了,我們就可以讓它在屏幕上顯示文字了。glCallLists()函數能同時將多個顯示列表的內容顯示在屏幕上,第一個參數是要顯示在屏幕上的字符串長度,第二個參數告訴OpenGL將字符串當作一個無符號數組處理,它們的值都介于0到255之間,第三個參數通過傳遞text來告訴OpenGL顯示的具體內容。最后,我們將GL_LIST_BIT屬性彈出堆棧,恢復到我們使用glListBase(m_Base-32)之前的狀態。

也許你想知道為什么字符不會彼此重疊堆積在一起。那是因為每個字符的顯示列表都知道字符的右邊緣在哪里,在寫完一個字符后,OpenGL自動移動到剛剛寫過的字符的右邊,再寫下一個字或畫下一個物體時就會從最后的位置開始,也就是最后一個字符的右邊。

然后我們修改一下initializeGL()函數,不作解釋,代碼如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
glClearColor(0.0,0.0,0.0,0.0);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑
glClearDepth(1.0);//設置深度緩存
glEnable(GL_DEPTH_TEST);//啟用深度測試
glDepthFunc(GL_LEQUAL);//所作深度測試的類型
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正

buildFont();//創建字體
}


還有,我們該進入paintGL()函數了,很簡單,難的都過去了,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置當前的模型觀察矩陣

glTranslatef(0.0f,0.0f,-10.0f);//移入屏幕10.0單位
//根據字體位置設置顏色
glColor3f(1.0f*float(cos(m_Cnt1)),1.0f*float(sin(m_Cnt2)),
1.0f-0.5f*float(cos(m_Cnt1+m_Cnt2)));
//設置光柵化位置,即字體位置
glRasterPos2f(-4.5f+0.5f*float(cos(m_Cnt1)),1.92f*float(sin(m_Cnt2)));
//輸出文字到屏幕上
glPrint("ActiveOpenGLTextWithNeHe-%7.2f",m_Cnt1);
m_Cnt1+=0.051f;//增加兩個計數器的值
m_Cnt2+=0.005f;
}

值得注意的是,深入屏幕并不能縮小字體,只會給字體變化移動范圍(這一點大家自己改改數據就知道了)。然后字體顏色設置和位置設置我覺得沒必要解釋了,都是數學的東西,我們主要是為了得到一個變化的效果,并不在乎它是怎么實現的。然后就是調用glPrint()函數輸出文字,最后增加兩個計數器的值就OK了。

最后就是鍵盤控制的代碼了,大家自己看吧,很簡單,具體代碼如下:

voidMyGLWidget::keyPressEvent(QKeyEvent*event)
{
switch(event->key())
{
caseQt::Key_F1://F1為全屏和普通屏的切換鍵
fullscreen=!fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
}
updateGL();
break;
caseQt::Key_Escape://ESC為退出鍵
close();
break;
caseQt::Key_PageUp://PageUp按下字體縮小
m_FontSize-=1;
if(m_FontSize<-75)
{
m_FontSize=-75;
}
buildFont();
break;
caseQt::Key_PageDown://PageDown按下字體放大
m_FontSize+=1;
if(m_FontSize>-5)
{
m_FontSize=-5;
}
buildFont();
break;
}
}

現在就可以運行程序查看效果了!

第14課:輪廓字體(參照NeHe)
這次教程中,我將教大家繪制3D的輪廓字體,當然肯定不是貼圖方式了,它們可像一般的3D模型一樣進行旋轉,放縮。
創建輪廓字體的方法與13課位圖的位圖字體類似,但輪廓字體要酷得多!輪廓字體可以在屏幕中以3D方式旋轉,而且輪廓字體還可以有一定的厚度,而不再是平面的2D字符了。使用輪廓字體,我們可以將計算機中的任何字體轉換為OpenGL的3D字體,是不是聽起來很誘人呢?

程序運行時效果如下:



下面進入教程:

我們這次將在第13課的基礎上修改代碼,由于有13課代碼的基礎,這節課會簡單許多。我只解釋新增的代碼,首先打開myglwidget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
voidbuildFont();//創建字體
voidkillFont();//刪除顯示列表
voidglPrint(constchar*fmt,...);//輸出字符串

private:
boolfullscreen;//是否全屏顯示
HDCm_HDC;//儲存當前設備的指針

GLYPHMETRICSFLOATm_Gmf[256];//記錄256個字符的信息
GLfloatm_Deep;//移入屏幕的距離
GLuintm_Base;//儲存繪制字體的顯示列表的開始位置
GLfloatm_Rot;//用于旋轉字體
};

#endif//MYGLWIDGET_H

由于我們沒有準備讓輪廓字體移動,所以刪掉兩個計數器。接著增加m_Deep來控制移入屏幕的距離,其實就是來控制字體的放大縮小的。然后再增加m_Rot來控制字體的旋轉。最后增加了GLYPHMETRICSFLOAT變量數組m_Gmf[256]用來保存256個輪廓字體顯示列表中對應的每一個列表的位置和方向信息,我們通過m_Gmf[num]來選擇字母。要注意的是,每個字符的寬度可以不相同,使用GLYPHMETRICS會大大簡化我們的工作。

接下來,我們需要打開myglwidget.cpp,先修改構造函數,不多解析了,具體代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_Deep=-10.0f;
m_Rot=0.0f;

HWNDhWND=(HWND)winId();//獲取當前窗口句柄
m_HDC=GetDC(hWND);//通過窗口句柄獲得HDC

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

繼續,我們需要對buildFont()、killFont()、glPrint()三個函數作一定的修改,具體代碼如下:

voidMyGLWidget::buildFont()//創建輪廓字體
{
HFONTfont;//字體句柄
m_Base=glGenLists(256);//創建256個顯示列表
font=CreateFont(-18,//字體高度
0,//字體寬度
0,//字體的旋轉角度
0,//字體底線的旋轉角度
FW_BOLD,//字體的重量
FALSE,//是否斜體
FALSE,//是否使用下劃線
FALSE,//是否使用刪除線
ANSI_CHARSET,//設置字符集
OUT_TT_PRECIS,//輸出精度
CLIP_DEFAULT_PRECIS,//剪裁精度
ANTIALIASED_QUALITY,//輸出質量
FF_DONTCARE|DEFAULT_PITCH,//FamilyandPitch的設置
LPCWSTR("ComicSansMS"));//字體名稱(電腦中已裝的)

SelectObject(m_HDC,font);//選擇字體

wglUseFontOutlines(m_HDC,//當前HDC
0,//從ASCII碼第一個字符開始
255,//字符數
m_Base,//第一個顯示列表的名稱
0.0f,//字體光滑度,越小越光滑
0.2f,//在z方向突出的距離(字體的厚度)
WGL_FONT_POLYGONS,//使用多邊形來生成字符,每個頂點具有獨立法線
m_Gmf);//用于儲存字形度量數據(高度,寬度等)
}

voidMyGLWidget::killFont()//刪除顯示列表
{
glDeleteLists(m_Base,256);//刪除96個顯示列表
}

voidMyGLWidget::glPrint(constchar*fmt,...)//自定義輸出文字函數
{
floatlength=0;
chartext[256];//保存字符串
va_listap;//指向一個變量列表的指針

if(fmt==NULL)//如果無輸入則返回
{
return;
}

va_start(ap,fmt);//分析可變參數
vsprintf(text,fmt,ap);//把參數值寫入字符串
va_end(ap);//結束分析

for(unsignedinti=0;i<strlen(text);i++)//計算整個字符串的長度
{
length+=m_Gmf[(int)text[i]].gmfCellIncX;
}
glTranslatef(-length/2,0.0f,0.0f);//左移字符串一半的長度

glPushAttrib(GL_LIST_BIT);//把顯示列表屬性壓入堆棧
glListBase(m_Base);//設置顯示列表的基礎值
glCallLists(strlen(text),GL_UNSIGNED_BYTE,text);//調用顯示列表繪制字符串
glPopAttrib();//彈出屬性堆棧
}

首先是buildFont()函數。首先創建字體的方法與上一課基本一致,只是把m_FontSize換成了-18。接著,將wglUseFontBitmaps()函數替換成wglUseFontOutlines()函數,這個函數包含了8個參數前4個參數大家自己看注釋,第5個參數為光滑度系數,這個值越小,字體看起來會越光滑(其實看不出明顯差別)。第6個參數簡單點說指的是字體的厚度,有厚度的字體才有立體感嘛,如果這個值為0.0就變成2D字體了。第7個參數告訴OpenGL用多邊形來生成字符,使每個頂點都會具有獨立的法線,這樣加上光源后會有不錯的效果(光源效果我們的代碼中沒有,大家可以自己加加看)。最后一個參數告訴OpenGL把創建的顯示列表的度量數據(高度、寬度等)放在數組m_Gmf[]中。
然后是killFont()函數。它很簡單,就是調用glDeleteLists()函數從m_Base開始刪除256個顯示列表。
最后是glPrint()函數。我們只是在原來的基礎上加了一些代碼,我們首先看到我們增加了一個浮點變量length用來統計整個字符串的寬度。接著我們利用循環,在循環中,由于我們已經將每個字符的度量值儲存在m_Gmf[]中,我們利用m_Gmf[text[i]].gmfCellIncX來獲得每個字符的寬度,累加起來就得到字符串的總寬度。然后,我們把視圖原點左移length/2的距離,這樣就保證字符串總是在屏幕的中心了。最后,把glListBase(m_Base-32)換成glListBase(m_Base),這是因為我們這次是從ASCII碼第一個字符開始創建的顯示列表。

然后,我們修改一下paintGL()函數,與之前的代碼很神似,只是更改了旋轉和平移部分的代碼,最后讓旋轉變量增加,不過多解釋了,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置當前的模型觀察矩陣

glTranslatef(0.0f,0.0f,m_Deep);//移入屏幕10.0單位
glRotatef(m_Rot,1.0f,0.0f,0.0f);//繞x軸旋轉
glRotatef(m_Rot*1.5f,0.0f,1.0f,0.0f);//繞y軸旋轉
glRotatef(m_Rot*1.4f,0.0f,0.0f,1.0f);//繞z軸旋轉
//根據字體位置設置顏色
glColor3f(1.0f*float(cos(m_Rot/20.0f)),1.0f*float(sin(m_Rot/25.0f)),
1.0f-0.5f*float(cos(m_Rot/17.0f)));
//輸出文字到屏幕上
glPrint("NeHe-%3.2f",m_Rot/50.0f);
m_Rot+=0.5f;//旋轉變量增加
}

最后,修改鍵盤控制的代碼,就是按PageUp和PageDown可以放大縮小字體,具體代碼如下:

voidMyGLWidget::keyPressEvent(QKeyEvent*event)
{
switch(event->key())
{
caseQt::Key_F1://F1為全屏和普通屏的切換鍵
fullscreen=!fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
}
updateGL();
break;
caseQt::Key_Escape://ESC為退出鍵
close();
break;
caseQt::Key_PageUp://PageUp按下字體縮小
m_Deep-=0.2f;
break;
caseQt::Key_PageDown://PageDown按下字體放大
m_Deep+=0.2f;
break;
}
}

現在就可以運行程序查看效果了!

第15課:圖形字體的紋理映射(參照NeHe)

這次教程中,我們將在第14課的基礎上創建帶有紋理的字體,它真的很簡單。也許你想知道如何才能給字體賦予紋理貼圖?我們可以使用自動紋理坐標生成器,它會自動為字體上的每一個多邊形生成紋理坐標。

這次課中我們還將使用Wingdings字體來顯示一個海盜旗(骷髏頭和十字骨頭)的標志,為此我們需要修改buildFont()函數代碼。如果你想顯示文字的話,就不用改動第14課中buildFont()函數的代碼了,當然你也可以選擇另一種字體,這都不是什么大事。

程序運行時效果如下:

下面進入教程:

我們這次將在第14課的基礎上修改代碼,由于有前兩課代碼的基礎,這節課會更簡單。我只解釋新增的代碼,首先打開myglwidget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
voidbuildFont();//創建字體
voidkillFont();//刪除顯示列表
voidglPrint(constchar*fmt,...);//輸出字符串

private:
boolfullscreen;//是否全屏顯示
HDCm_HDC;//儲存當前設備的指針

GLYPHMETRICSFLOATm_Gmf[256];//記錄256個字符的信息
GLfloatm_Deep;//移入屏幕的距離
GLuintm_Base;//儲存繪制字體的顯示列表的開始位置
GLfloatm_Rot;//用于旋轉字體

QStringm_FileName;//圖片的路徑及文件名
GLuintm_Texture;//儲存一個紋理
};

#endif//MYGLWIDGET_H

注意到唯一的變化就是最后增加了兩個變量,這個兩個變量相信大家已經很熟悉了,m_FileName用來保存我們將用于紋理映射的圖片路徑名,m_Texture用于儲存紋理。

接下來我們需要打開myglwidget.cpp,先修改構造函數初始化m_FileName,不多解釋了,具體代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_Deep=-3.0f;
m_Rot=0.0f;
m_FileName="D:/QtOpenGL/QtImage/Lights.bmp";//應根據實際存放圖片的路徑進行修改

HWNDhWND=(HWND)winId();//獲取當前窗口句柄
m_HDC=GetDC(hWND);//通過窗口句柄獲得HDC

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

繼續,我們需要略微修改一下buildFont()函數,修改后代碼如下:

voidMyGLWidget::buildFont()//創建位圖字體
{
HFONTfont;//字體句柄
m_Base=glGenLists(256);//創建256個顯示列表
font=CreateFont(-18,//字體高度
0,//字體寬度
0,//字體的旋轉角度
0,//字體底線的旋轉角度
FW_BOLD,//字體的重量
FALSE,//是否斜體
FALSE,//是否使用下劃線
FALSE,//是否使用刪除線
SYMBOL_CHARSET,//設置字符集
OUT_TT_PRECIS,//輸出精度
CLIP_DEFAULT_PRECIS,//剪裁精度
ANTIALIASED_QUALITY,//輸出質量
FF_DONTCARE|DEFAULT_PITCH,//FamilyandPitch的設置
LPCWSTR("Wingdings"));//字體名稱(電腦中已裝的)

SelectObject(m_HDC,font);//選擇字體

wglUseFontOutlines(m_HDC,//當前HDC
0,//從ASCII碼第一個字符開始
255,//字符數
m_Base,//第一個顯示列表的名稱
0.1f,//字體光滑度,越小越光滑
0.2f,//在z方向突出的距離(字體的厚度)
WGL_FONT_POLYGONS,//使用多邊形來生成字符,每個頂點具有獨立法線
m_Gmf);//用于儲存字形度量數據(高度,寬度等)
}

注意到我們只是修改了CreateFont()函數的第9個參數(設置字符集)和最后一個參數(字體名稱),修改最后一個參數好理解,因為我們要使用字體Wingdings嘛。但其實這樣修改后,我們想用的Wingdings字體并不會工作。這是由于Wingdings里的字體都不是標準字符字體,我們必須告訴Windows這種字體是一種符號字體而不是一種標準字符字體,因此我們在設置字符集時,把參數改為SYMBOL_CHARSET,如此Wingdings字體就能正常工作了。

然后我們需要來修改initializeGL()函數,這是本節課的重點部分,具體代碼如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
//自動生成紋理
glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR);
glTexGeni(GL_T,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);

m_Texture=bindTexture(QPixmap(m_FileName));//載入位圖并轉換成紋理
glEnable(GL_TEXTURE_2D);//啟用紋理映射

glClearColor(0.0,0.0,0.0,0.0);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑
glClearDepth(1.0);//設置深度緩存
glEnable(GL_DEPTH_TEST);//啟用深度測試
glDepthFunc(GL_LEQUAL);//所作深度測試的類型
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正

buildFont();//創建字體
}

注意到,我們一開始增加了四行新代碼,這四行代碼將為我們繪制在屏幕上的任何物體自動生成紋理坐標。函數glTexGen非常強大,而且復雜,在這里我們沒法完全講清楚。我們只需要知道GL_S和GL_T是紋理坐標就可以了。默認狀態下,它被設置為提取物體此刻在屏幕上的x坐標和y坐標,并把它們裝換為頂點坐標。我們運行程序時會發現到物體在z平面沒有紋理,只顯示一些斑紋,而正面和反面都被賦予了紋理,這些都是由glTexGen函數產生的。

GL_TEXTURE_GEN_MODE允許我們選擇我們想在S和T紋理坐標上使用的紋理映射模式,我們有三種選擇:GL_EYE_LINEAR - 會使紋理固定在屏幕上,它不會移動,物體將被賦予處于它通過的地區的那一塊紋理;GL_OBJECT_LINEAR - 紋理被固定于屏幕上運動的物體上;GL_SPHERE_MAP - 創建一種有金屬質感的物體(大家可以變化著試試,效果都很不錯)。

當然下面增加的兩行用于生成紋理和啟用紋理映射,和第06課的代碼一樣的,不解釋了。

最后,我們來修改一下paintGL()函數,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置當前的模型觀察矩陣
glBindTexture(GL_TEXTURE_2D,m_Texture);

glTranslatef(1.1f*float(cos(m_Rot/16.0f)),
0.8f*float(sin(m_Rot/20.0f)),m_Deep);//物體移動及控制大小
glRotatef(m_Rot,1.0f,0.0f,0.0f);//繞x軸旋轉
glRotatef(m_Rot*1.2f,0.0f,1.0f,0.0f);//繞y軸旋轉
glRotatef(m_Rot*1.4f,0.0f,0.0f,1.0f);//繞z軸旋轉
//輸出文字到屏幕上
glPrint("N");
m_Rot+=0.1f;//旋轉變量增加
}

首先是綁定我們已經生產的紋理,接著由于我們這次紋理不需要融合顏色,于是去掉了選擇顏色的代碼。然后是物體移動和旋轉的代碼,我也不解釋了,這只是其中一種變換方式,使得能產生動畫,大家完全可以自己設計平移和旋轉部分的代碼(如加上鍵盤控制等)。然后就需要來輸出我們的“海盜旗”了,如果你不知道我是如何從字母“N”中得到海盜旗符號的,那就打開寫字板,在字體出選擇Wingdings字體。輸入大寫字母“N”,就會顯示出海盜旗符號了。

現在可以運行程序查看效果了!

第16課:看起來很酷的霧(參照NeHe)

這次教程中,我們將在第07課代碼的基礎上,為木箱的四周填上霧效果。我們將會學習三種不同的霧模式,以及怎么設置霧的顏色和霧的范圍。雖然這次教程非常簡單,但我們得到的霧效果確實很棒!希望大家能喜歡,當然你也可以把霧效果加到任何一個OpenGL程序中,我相信總能檫出美麗的火花!

程序運行時效果如下:

下面進入教程:

我們這次將在第07課的基礎上修改代碼,我只會講解有修改的部分,希望大家先找到第07課的代碼再跟著我一步步走。首先打開myglwidget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
boolfullscreen;//是否全屏顯示

QStringm_FileName;//圖片的路徑及文件名
GLuintm_Texture;//儲存一個紋理

boolm_Light;//光源的開/關
GLuintm_Fog;//霧的模式

GLfloatm_xRot;//x旋轉角度
GLfloatm_yRot;//y旋轉角度
GLfloatm_xSpeed;//x旋轉速度
GLfloatm_ySpeed;//y旋轉速度
GLfloatm_Deep;//深入屏幕的距離
};

#endif//MYGLWIDGET_H

我們只是增加了一個變量m_Fog來儲存當前霧的模式(我們會使用三種霧模式),方便我們后面利用鍵盤來控制霧模式的切換。

接下來,我們需要打開myglwidget.cpp,在構造函數中初始化新增變量,具體代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_FileName="D:/QtOpenGL/QtImage/Crate.bmp";//應根據實際存放圖片的路徑進行修改
m_Light=false;
m_Fog=0;

m_xRot=0.0f;
m_yRot=0.0f;
m_xSpeed=0.0f;
m_ySpeed=0.0f;
m_Deep=-5.0f;

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

我們給m_Fog賦初始值0,表示第一種霧模式(具體是哪一種下面會講到)。

然后我們需要來修改initializeGL()函數,霧效果的數據初始化都這里完成的,具體代碼如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
m_Texture=bindTexture(QPixmap(m_FileName));//載入位圖并轉換成紋理
glEnable(GL_TEXTURE_2D);//啟用紋理映射

glClearColor(0.5f,0.5f,0.5f,1.0f);//設置背景的顏色為霧氣的顏色
glShadeModel(GL_SMOOTH);//啟用陰影平滑

glClearDepth(1.0);//設置深度緩存
glEnable(GL_DEPTH_TEST);//啟用深度測試
glDepthFunc(GL_LEQUAL);//所作深度測試的類型
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正

//光源部分
GLfloatLightAmbient[]={0.5f,0.5f,0.5f,1.0f};//環境光參數
GLfloatLightDiffuse[]={1.0f,1.0f,1.0f,1.0f};//漫散光參數
GLfloatLightPosition[]={0.0f,0.0f,2.0f,1.0f};//光源位置
glLightfv(GL_LIGHT1,GL_AMBIENT,LightAmbient);//設置環境光
glLightfv(GL_LIGHT1,GL_DIFFUSE,LightDiffuse);//設置漫射光
glLightfv(GL_LIGHT1,GL_POSITION,LightPosition);//設置光源位置
glEnable(GL_LIGHT1);//啟動一號光源

//霧部分
GLfloatfogColor[]={0.5f,0.5f,0.5f,1.0f};//霧的顏色
glFogi(GL_FOG_MODE,GL_EXP);//設置霧氣的初始模式
glFogfv(GL_FOG_COLOR,fogColor);//設置霧的顏色
glFogf(GL_FOG_DENSITY,0.35);//設置霧的密度
glHint(GL_FOG_HINT,GL_DONT_CARE);//設置系統如何計算霧氣
glFogf(GL_FOG_START,1.0f);//霧的開始位置
glFogf(GL_FOG_END,5.0f);//霧的結束位置
glEnable(GL_FOG);//啟動霧效果
}

首先我們改一下glClearColor()函數的參數,讓清除屏幕的顏色與下面霧的顏色相同。我們在函數末尾加上了我們的霧效果代碼,首先我們定義霧的顏色(我們定為白色霧,你完全可以根據自己的喜好修改),接著我們設置了霧氣的初始模式為GL_EXP,這是m_Fog等于0時對應的模式,先別急著問為什么,下面會告訴你答案。

然后我們設置霧的密度,glFogf()函數的第二個參數越大霧會越濃,越小霧會越稀。glHint()函數用于設置修正,我們使用了GL_DONT_CARE因為我們不關心它的值。再接下去兩行設置了霧的起始位置和結束位置,1.0f和5.0f均表示離屏幕的距離,我們完全可以自己根據需要修改這兩個值。最后我們應用glEnable()啟用了霧效果,注意沒有這行是無法產生無效果的。

最后是關于鍵盤控制函數的修改,我們將利用它來控制霧模式的切換,具體代碼如下:

voidMyGLWidget::keyPressEvent(QKeyEvent*event)
{
staticGLuintfogMode[]={GL_EXP,GL_EXP2,GL_LINEAR};

switch(event->key())
{
caseQt::Key_F1://F1為全屏和普通屏的切換鍵
fullscreen=!fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
}
break;
caseQt::Key_Escape://ESC為退出鍵
close();
break;
caseQt::Key_L://L為開啟關閉光源的切換鍵
m_Light=!m_Light;
if(m_Light)
{
glEnable(GL_LIGHTING);//開啟光源
}
else
{
glDisable(GL_LIGHTING);//關閉光源
}
break;
caseQt::Key_G://G為霧模式的切換鍵
m_Fog++;
if(m_Fog==3)
{
m_Fog=0;
}
glFogi(GL_FOG_MODE,fogMode[m_Fog]);
break;
caseQt::Key_PageUp://PageUp按下使木箱移向屏幕內部
m_Deep-=0.1f;
break;
caseQt::Key_PageDown://PageDown按下使木箱移向觀察者
m_Deep+=0.1f;
break;
caseQt::Key_Up://Up按下減少m_xSpeed
m_xSpeed-=0.1f;
break;
caseQt::Key_Down://Down按下增加m_xSpeed
m_xSpeed+=0.1f;
break;
caseQt::Key_Right://Right按下減少m_ySpeed
m_ySpeed-=0.1f;
break;
caseQt::Key_Left://Left按下增加m_ySpeed
m_ySpeed+=0.1f;
break;
}
}

注意到我們定義了一個靜態GLuint數組fogMode[]來儲存我們要切換的霧模式GL_EXP、GL_EXP2、GL_LINEAR三種模式。GL_EXP - 充滿整個屏幕的只是基本渲染的霧,并不是特別像霧;GL_EXP2 - 比GL_EXP更進一步,它也是充滿整個屏幕,但它使屏幕看起來更有深度;GL_LINEAR - 最好的渲染模式,物體淡入淡出的效果更自然(我們可以通過切換鍵比較看看效果就知道了)。由于GL_EXP放在fogMode[0]處,故m_Fog為0時對應的模式是GL_EXP。

每次按下G鍵,我們就讓m_Fog加一,如果加后等于3,就讓它重新回到0,然后調用glFogi()函數重新選擇霧模式。

現在就可以運行程序查看效果了!

第17課:2D圖像文字(參照NeHe)

這次教程中,我們將學會如何使用四邊形紋理貼圖把文字顯示在屏幕上。我們將把256個不同的文字從一個256×256的紋理圖像中一個個提取出來,接著創建一個輸出函數來創建任意我們希望的文字。

還記得在第一篇字體教程中我提到使用紋理在屏幕上繪制文字嗎?通常當你使用紋理繪制文字時你會調用你最喜歡的圖像處理程序,選擇一種字體,然后輸入你想顯示的文字或段落,然后保存下來位圖并把它作為紋理讀入到你的程序里,問題是這對一個需要很多文字或者文字在不停變化的程序來說,這么做效率并不高。這次教程中我們只使用一個紋理來顯示任意256個不同的字符。

程序運行時效果如下:

下面進入教程:

由于相較于之前幾課字體教程的代碼改動較大,我們將直接在第01課的基礎上修改代碼,我會一一解釋新增的代碼,首先myglwidget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
voidbuildFont();//創建字體
voidkillFont();//刪除顯示列表
//輸出字符串
voidglPrint(GLuintx,GLuinty,constchar*string,intset);

private:
boolfullscreen;//是否全屏顯示

GLuintm_Base;//儲存繪制字體的顯示列表的開始位置
GLfloatm_Cnt1;//字體移動計數器1
GLfloatm_Cnt2;//字體移動計數器2

QStringm_FileName[2];//圖片的路徑及文件名
GLuintm_Texture[2];//儲存兩個紋理
};

#endif//MYGLWIDGET_H

我們增加了變量m_Base、m_Cnt1、m_Cnt2,函數聲明buildFont()、killFont(),這些和之前都講過的作用都一樣就不重復了。而m_FileName和m_Texture變為了長度為2的數組,這是因為程序中我們會用兩種不同的圖來建立兩個不同的紋理。最后是glPrint()函數的聲明,注意下它的參數和前幾課不同,但作用是相同的,具體的下面會講到。

接下來,我們需要打開myglwidget.cpp,加入聲明#include <QTimer>、#include<QtMath>,將構造函數和析構函數修改如下(比較簡單不具體解釋了):

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_Cnt1=0.0f;
m_Cnt2=0.0f;
m_FileName[0]="D:/QtOpenGL/QtImage/Font.bmp";//應根據實際存放圖片的路徑進行修改
m_FileName[1]="D:/QtOpenGL/QtImage/Bumps.bmp";

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

[cpp]view plaincopy

MyGLWidget::~MyGLWidget()
{
killFont();//刪除顯示列表
}

繼續,我們要來定義我們增加的三個函數,同樣是重頭戲,具體代碼如下:

voidMyGLWidget::buildFont()//創建位圖字體
{
floatcx,cy;//儲存字符的x、y坐標
m_Base=glGenLists(256);//創建256個顯示列表
glBindTexture(GL_TEXTURE_2D,m_Texture[0]);//選擇字符紋理

for(inti=0;i<256;i++)//循環256個顯示列表
{
cx=float(i%16)/16.0f;//當前字符的x坐標
cy=float(i/16)/16.0f;//當前字符的y坐標

glNewList(m_Base+i,GL_COMPILE);//開始創建顯示列表
glBegin(GL_QUADS);//使用四邊形顯示每一個字符
glTexCoord2f(cx,1-cy-0.0625f);
glVertex2i(0,0);
glTexCoord2f(cx+0.0625f,1-cy-0.0625f);
glVertex2i(16,0);
glTexCoord2f(cx+0.0625f,1-cy);
glVertex2i(16,16);
glTexCoord2f(cx,1-cy);
glVertex2i(0,16);
glEnd();//四邊形字符繪制完成
glTranslated(10,0,0);//繪制完一個字符,向右平移10個單位
glEndList();//字符顯示列表完成
}
}

voidMyGLWidget::killFont()//刪除顯示列表
{
glDeleteLists(m_Base,256);//刪除256個顯示列表
}

voidMyGLWidget::glPrint(GLuintx,GLuinty,//輸入字符串
constchar*string,intset)
{
if(set>1)//如果字符集大于1
{
set=1;//設置其為1
}

glBindTexture(GL_TEXTURE_2D,m_Texture[0]);//綁定為字體紋理
glDisable(GL_DEPTH_TEST);//禁止深度測試
glMatrixMode(GL_PROJECTION);//選擇投影矩陣
glPushMatrix();//保存當前的投影矩陣
glLoadIdentity();//重置投影矩陣
glOrtho(0,640,0,480,-1,1);//設置正投影的可視區域
glMatrixMode(GL_MODELVIEW);//選擇模型觀察矩陣
glPushMatrix();//保存當前的模型觀察矩陣
glLoadIdentity();//重置模型觀察矩陣

glTranslated(x,y,0);//把字符原點移動到(x,y)位置
glListBase(m_Base-32+(128*set));//選擇字符集
glCallLists(strlen(string),GL_BYTE,string);//把字符串寫到屏幕
glMatrixMode(GL_PROJECTION);//選擇投影矩陣
glPopMatrix();//設置為保存的矩陣
glMatrixMode(GL_MODELVIEW);//選擇模型觀察矩陣
glPopMatrix();//設置為保存
glEnable(GL_DEPTH_TEST);//啟用深度測試
}

首先是buildFont()函數。我們先是定義兩個臨時變量來儲存字體紋理中每個字的位置,cx儲存水平方向位置,cy儲存豎直方向位置。接著我們告訴OpenGL我們要建立256個顯示列表,變量m_Base指向第一個顯示列表,然后選擇我們的字體紋理。現在我們開始循環,來創建所以256個字符,并存在顯示列表里。一開始我們計算得到cx、cy,對16取余和除以16是由于一行是16個字符,最后都除以16.0f是按16個字符把紋理寬度高度均為1.0分成16份。

后面就開始創建顯示列表了,繪制四邊形對應紋理時,+或-0.0625f是指一個字符的高或寬,還有由于紋理坐標(0, 0)是在左下角,所以glTexCoord2f(x, y)的第二參數是1-cy、1-cy-0.0625而不是cy、cy+0.0625(比如說cx、cy同時為0,那它對應的字符紋理左下角坐標就應是(0.0, 1-0.0f-0.0625f)了,希望大家能明白)。要注意的是,我們使用glVertex2i()而不是glVertex3f(),我們的字體是2D字體,所以不需要z值。因為我們使用的是正交投影,我們不需要移進屏幕,在一個正交投影平面繪圖你所需要的是指定x、y坐標。又因為我們的屏幕是以像素形式從0到639(寬),從0到479(高),因此我們既不需要用浮點數也不需要負數。

畫完四邊形后,我們右移了10個像素,至于紋理有病。如果我們不平移,文字將會重疊。有由于我們的字體太窄太瘦,我們不想右移16個像素那么多,如果那樣的話,每個字符之間將有很大的間隔,只移動10個像素是個不錯的選擇。

接著是killFont()函數。它很簡單,就是調用glDeleteLists()函數從m_Base開始刪除256個顯示列表。

最后是glPrint()函數。首先我們判斷一下set字符集,如果大于1,就將set置0。這是由于我們的字體紋理中只有兩種字體,第一種是普通的,第二種是斜體,如果選擇的字符集有誤,我們就把它設為默認的普通字符集。接著我們再次選擇字體紋理,我們這么做事防止我們在決定往屏幕上輸出文字前選擇了別的紋理,導致出錯。然后我們禁用了深度測試,我們這么做事因為混合的效果會更好。如果我們不禁用深度測試,文字可能會被什么東西擋住,或者得不到正確的混合效果。當然,如果你不打算混合文字(那樣文字周圍的黑色區域就不會顯示),你就可以啟用深度測試。

下面幾行十分重要!我們選擇投影矩陣,然后調用glPushMatrix()函數,保存當前投影矩陣(其實就是把投影矩陣壓入堆棧)。保存投影矩陣后,我們重置矩陣并調用glOrtho()設置正交投影屏幕,第一和第三個參數表示屏幕的底邊和左邊,第二和第四個參數表示屏幕的上邊和右邊。由于我們不需要用到深度測試,所以我們將第五和第六個參數設為-1和1。我們再選擇模型觀察矩陣,用glPushMatrix()保存當前設置。然后我們重置模型觀察矩陣以便在正交投影視點下工作。

現在我們可以繪制文字了,我們從移動到繪制文字的位置開始。我們使用glTranslated()而不是glTranslatef(),因為我們處理的是像素,所以浮點數沒有意義。接著我們用glListBase()來選擇字符集,如果我們想使用第二個字符集,我們在當前的顯示列表基數上加上128,通過加128,我們跳過了前128個字符。而減去32是因為我們的字符集是從“空格”開始的,即ASCII碼第33個字符開始,故我們減去32,告訴OpenGL跳過前面32個字符。然后我們使用glCallLists()繪制文字,這個之前解釋過,不再解釋了。

最后我們要做的是恢復透視視圖。我們選擇投影矩陣并用glPopMatrix()恢復我們先前glPushMatrix()保存的設置,接著選擇模型觀察矩陣做相同的工作。你或許會問,難道不用按相反順序彈出矩陣嗎?不用,這是用于投影矩陣和模型觀察矩陣的堆棧并不是同一個(這樣說其實并不準確,不過道理是差不多的),所以無論選擇哪個矩陣先彈出都沒有問題。值得注意的是,如果你把代碼中的最后兩句glMatrixMode()調換位置,運行程序時你是看不到圖像紋理的只能看到文字,這是由于我們最后選擇的矩陣是GL_PROJECTION,而我們繪制圖像紋理是在GL_MODEWIEW上繪制的,所以你看不到圖像紋理。當然解決辦法就是,在恢復了投影矩陣后,開始深度測試之前,再次調用glMatrix()選擇模型觀察矩陣GL_MODEVIEW。那為什么我們能看到文字呢?這是由于做了平面正交投影后,在任何矩陣所繪制的東西都是在平面繪制的,OpenGL自動會把它們投影到屏幕上,所以總能看到文字的。函數最后我們啟用了深度測試,如果你沒有在上面的代碼中關閉深度測試,就不需要這行。

然后我們修改一下initializeGL()函數,具體代碼如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
m_Texture[0]=bindTexture(QPixmap(m_FileName[0]));//載入位圖并轉換成紋理
m_Texture[1]=bindTexture(QPixmap(m_FileName[1]));
glEnable(GL_TEXTURE_2D);//啟用紋理映射

glClearColor(0.0,0.0,0.0,0.0);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑
glClearDepth(1.0);//設置深度緩存
glEnable(GL_DEPTH_TEST);//啟用深度測試
glDepthFunc(GL_LEQUAL);//所作深度測試的類型
glBlendFunc(GL_SRC_ALPHA,GL_ONE);//設置混合因子
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正

buildFont();//創建字體
}

最開始三行載入位圖轉換紋理,啟用紋理映射就不解釋了。中間部分有小的改動,由于我們要給字體上色,所以要設置混合因子。最后調用buildFont()創建字體。

最后,我們該進入paintGL()函數,這次難度還行,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置當前的模型觀察矩陣

glBindTexture(GL_TEXTURE_2D,m_Texture[1]);//設置為圖像紋理
glTranslatef(0.0f,0.0f,-5.0f);//移入屏幕5.0單位
glRotatef(45.0f,0.0f,0.0f,1.0f);//繞z軸旋轉45度
glRotatef(m_Cnt1*30.0f,1.0f,1.0f,0.0f);//繞(1,1,0)軸旋轉
glDisable(GL_BLEND);//關閉融合
glColor3f(1.0f,1.0f,1.0f);//設置顏色為白色
glBegin(GL_QUADS);//繪制紋理四邊形
glTexCoord2d(0.0f,0.0f);
glVertex2f(-1.0f,1.0f);
glTexCoord2d(1.0f,0.0f);
glVertex2f(1.0f,1.0f);
glTexCoord2d(1.0f,1.0f);
glVertex2f(1.0f,-1.0f);
glTexCoord2d(0.0f,1.0f);
glVertex2f(-1.0f,-1.0f);
glEnd();

glRotatef(90.0f,1.0f,1.0f,0.0);//繞(1,1,0)軸旋轉90度
glBegin(GL_QUADS);//繪制第二個四邊形,與第一個垂直
glTexCoord2d(0.0f,0.0f);
glVertex2f(-1.0f,1.0f);
glTexCoord2d(1.0f,0.0f);
glVertex2f(1.0f,1.0f);
glTexCoord2d(1.0f,1.0f);
glVertex2f(1.0f,-1.0f);
glTexCoord2d(0.0f,1.0f);
glVertex2f(-1.0f,-1.0f);
glEnd();

glEnable(GL_BLEND);//啟用混合
glLoadIdentity();//重置視口
//根據字體位置設置顏色
glColor3f(1.0f*float(cos(m_Cnt1)),1.0*float(sin(m_Cnt2)),
1.0f-0.5f*float(cos(m_Cnt1+m_Cnt2)));
glPrint(int((280+250*cos(m_Cnt1))),
int(235+200*sin(m_Cnt2)),"NeHe",0);
glColor3f(1.0*float(sin(m_Cnt2)),1.0f-0.5f*float(cos(m_Cnt1+m_Cnt2)),
1.0f*float(cos(m_Cnt1)));
glPrint(int((280+230*cos(m_Cnt2))),
int(235+200*sin(m_Cnt1)),"OpenGL",1);
glColor3f(0.0f,0.0f,1.0f);
glPrint(int(240+200*cos((m_Cnt1+m_Cnt2)/5)),2,
"GiuseppeD'Agata",0);
glColor3f(1.0f,1.0f,1.0f);
glPrint(int(242+200*cos((m_Cnt1+m_Cnt2)/5)),2,
"GiuseppeD'Agata",0);

m_Cnt1+=0.01f;//增加兩個計數器的值
m_Cnt2+=0.0081f;
}

函數中我們先繪制3D物體最后繪制文字,這樣文字將顯示在3D物體上面,而不會被3D物體遮住。我們之所以加入一個3D物體是為了演示透視投影和正交投影可同時使用。首先我們選擇紋理,為了看見3D物體,我們往屏幕內移動5個單位。我們繞z軸旋轉45度,這將使我們的四邊形順時針旋轉45度,讓我們的四邊形看起來更像磚石而不是矩形,接著我們讓物體同時繞x、y軸旋轉m_Cnt1*30度,這使我們的物體像在一個點上旋轉的鉆石那樣旋轉。然后我們關閉混合,設置顏色為亮白,繪制第一個紋理映射的四邊形。再繞x、y軸旋轉90度,畫另一個四邊形,第二個四邊形從第一個四邊形中間切過去,來形成一個好看的形狀。

在繪制完有紋理貼圖的四邊形后,我們開啟混合并繪制文字,下面的根據文字選擇顏色,打印“NeHe”、“OpenGL”就不解釋了。我們來看打印“Giuseppe D'Agata”時,我們用深藍色和亮白色兩次繪制(作者的名字),并在x方向上平移2個像素,這樣創造出一種亮白色字附帶深藍色陰影的效果,感覺真的很棒啊!要注意的是,這里必須打開混合,如果沒有打開是不會出現這樣的效果的(大家可以注釋掉glEnable(GL_BLEND)看看,我就不解釋了),甚至其它兩個字符串也變得糟糕透了。最后一件事是以不同的速率遞增我們的計數器,這使得文字移動,3D物體自轉。

現在就可以運行程序查看效果了!

第18課:二次幾何體(參照NeHe)

這次教程中,我將介紹二次幾何體。利用二次幾何體,我們可以很容易創建球、圓盤、圓柱和圓錐。

我們先介紹一下二次幾何體GLUquadric(NeHe教程用的是GLUquadricObj,源代碼中GLUquadricObj是GLUquadric的別名),其實它本質上是一個二次方程,即a1x^2 + a2y^2 + a3z^2 + a4xy + a5yz + a6zx + a7x + a8y + a9z + a10 = 0。要知道,任何一個空間規則曲面(包括平面)都是可以用二次方程表示出來的,因此OpenGL利用二次幾何體來實現一些函數,幫助用戶更簡單的繪畫出常用的空間曲面。

程序運行時效果如下:

下面進入教程:

我們將在第07課的基礎上修改代碼,我只會對新增代碼作解釋,首先打開myglwidget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classGLUquadric;
classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
voidglDrawCube();//繪制立方體

private:
boolfullscreen;//是否全屏顯示

QStringm_FileName;//圖片的路徑及文件名
GLuintm_Texture;//儲存一個紋理

boolm_Light;//光源的開/關
GLfloatm_xRot;//x旋轉角度
GLfloatm_yRot;//y旋轉角度
GLfloatm_xSpeed;//x旋轉速度
GLfloatm_ySpeed;//y旋轉速度
GLfloatm_Deep;//深入屏幕的距離

intm_Part1;//圓盤的起始角度
intm_Part2;//圓盤的結束角度
intm_P1;//增量1
intm_P2;//增量2
GLUquadric*m_Quadratic;//二次幾何體
GLuintm_Object;//繪制對象標示符
};

#endif//MYGLWIDGET_H

首先我們在類前面增加了GLUquadric的類聲明。接著我們增加了6個變量,前4個變量用于控制繪制“部分圓盤”的,下面會解釋。然后我們定義一個二次幾何體對象指針和一個GLuint變量,二次幾何體就不解釋了,m_Object是配合鍵盤控制來完成圖形之間切換的。最后我們增加了一個函數聲明glDrawCube(),這個函數是用來繪制立方體的。

接下來,我們需要打開myglwidget.cpp,在構造函數中初始化新增變量(除了m_Quadratic)并修改析構函數(刪除掉創建的二次幾何體),很簡單不多解釋,具體代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_FileName="D:/QtOpenGL/QtImage/Wall1.bmp";//應根據實際存放圖片的路徑進行修改
m_Light=false;

m_xRot=0.0f;
m_yRot=0.0f;
m_xSpeed=0.0f;
m_ySpeed=0.0f;
m_Deep=-5.0f;

m_Part1=0;
m_Part2=0;
m_P1=0;
m_P2=1;
m_Object=0;

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

MyGLWidget::~MyGLWidget()
{
gluDeleteQuadric(m_Quadratic);
}

繼續,我們需要定義我們新增的glDrawCube()函數了,其實就是畫一個紋理立方體,完全可以從第07課的paintGL()函數中復制過來,不再多作解釋,代碼如下:

voidMyGLWidget::glDrawCube()
{
glBegin(GL_QUADS);//開始繪制立方體
glNormal3f(0.0f,1.0f,0.0f);
glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,-1.0f);//右上(頂面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,-1.0f);//左上(頂面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,1.0f,1.0f);//左下(頂面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,1.0f,1.0f);//右下(頂面)

glNormal3f(0.0f,-1.0f,0.0f);
glTexCoord2f(0.0f,0.0f);
glVertex3f(1.0f,-1.0f,1.0f);//右上(底面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(-1.0f,-1.0f,1.0f);//左上(底面)
glTexCoord2f(1.0f,1.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);//左下(底面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(1.0f,-1.0f,-1.0f);//右下(底面)

glNormal3f(0.0f,0.0f,1.0f);
glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,1.0f);//右上(前面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,1.0f);//左上(前面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,-1.0f,1.0f);//左下(前面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,-1.0f,1.0f);//右下(前面)

glNormal3f(0.0f,0.0f,-1.0f);
glTexCoord2f(0.0f,0.0f);
glVertex3f(1.0f,-1.0f,-1.0f);//右上(后面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);//左上(后面)
glTexCoord2f(1.0f,1.0f);
glVertex3f(-1.0f,1.0f,-1.0f);//左下(后面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(1.0f,1.0f,-1.0f);//右下(后面)

glNormal3f(-1.0f,0.0f,0.0f);
glTexCoord2f(1.0f,1.0f);
glVertex3f(-1.0f,1.0f,1.0f);//右上(左面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.0f,1.0f,-1.0f);//左上(左面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.0f,-1.0f,-1.0f);//左下(左面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(-1.0f,-1.0f,1.0f);//右下(左面)

glNormal3f(1.0f,0.0f,0.0f);
glTexCoord2f(1.0f,1.0f);
glVertex3f(1.0f,1.0f,-1.0f);//右上(右面)
glTexCoord2f(0.0f,1.0f);
glVertex3f(1.0f,1.0f,1.0f);//左上(右面)
glTexCoord2f(0.0f,0.0f);
glVertex3f(1.0f,-1.0f,1.0f);//左下(右面)
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.0f,-1.0f,-1.0f);//右下(右面)
glEnd();//立方體繪制結束
}

然后我們需要修改一下initializeGL()函數,在其中完成對m_Quadratic的初始化,具體代碼如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
m_Texture=bindTexture(QPixmap(m_FileName));//載入位圖并轉換成紋理
glEnable(GL_TEXTURE_2D);//啟用紋理映射

glClearColor(0.0f,0.0f,0.0f,0.0f);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑

glClearDepth(1.0);//設置深度緩存
glEnable(GL_DEPTH_TEST);//啟用深度測試
glDepthFunc(GL_LEQUAL);//所作深度測試的類型
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正

m_Quadratic=gluNewQuadric();//創建二次幾何體
gluQuadricNormals(m_Quadratic,GLU_SMOOTH);//使用平滑法線
gluQuadricTexture(m_Quadratic,GL_TRUE);//使用紋理

//光源部分
GLfloatLightAmbient[]={0.5f,0.5f,0.5f,1.0f};//環境光參數
GLfloatLightDiffuse[]={1.0f,1.0f,1.0f,1.0f};//漫散光參數
GLfloatLightPosition[]={0.0f,0.0f,2.0f,1.0f};//光源位置
glLightfv(GL_LIGHT1,GL_AMBIENT,LightAmbient);//設置環境光
glLightfv(GL_LIGHT1,GL_DIFFUSE,LightDiffuse);//設置漫射光
glLightfv(GL_LIGHT1,GL_POSITION,LightPosition);//設置光源位置
glEnable(GL_LIGHT1);//啟動一號光源
}

注意到我們增加了三行代碼,首先調用gluNewQuadric()創建了一個二次幾何體對象,并讓m_Quadratic指向這個二次幾何體。然后第二行代碼將在二次曲面的表面創建平滑的法向量,這樣當燈光照上去的時候將會好看些。最后我們使在二次曲面表面的紋理映射有效。

還有就是paintGL()函數了,最近幾課,我們通過分過程漸漸讓paintGL()函數看起來趨于簡化,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置模型觀察矩陣
glTranslatef(0.0f,0.0f,m_Deep);//移入屏幕5.0單位
glRotatef(m_xRot,1.0f,0.0f,0.0f);//繞x軸旋轉
glRotatef(m_yRot,0.0f,1.0f,0.0f);//繞y軸旋轉

glBindTexture(GL_TEXTURE_2D,m_Texture);//選擇紋理
switch(m_Object)
{
case0://繪制立方體
glDrawCube();
break;
case1://繪制圓柱體
glTranslatef(0.0f,0.0f,-1.5f);
gluCylinder(m_Quadratic,1.0f,1.0f,3.0f,64,64);
break;
case2://繪制圓盤
gluDisk(m_Quadratic,0.5f,1.5f,64,64);
break;
case3://繪制球
gluSphere(m_Quadratic,1.3f,64,64);
break;
case4://繪制圓錐
glTranslatef(0.0f,0.0f,-1.5f);
gluCylinder(m_Quadratic,1.0f,0.0f,3.0f,64,64);
break;
case5://繪制部分圓盤
m_Part1+=m_P1;
m_Part2+=m_P2;

if(m_Part1>359)
{
m_P1=0;
m_Part1=0;
m_P2=1;
m_Part2=0;
}
if(m_Part2>359)
{
m_P1=1;
m_P2=0;
}

gluPartialDisk(m_Quadratic,0.5f,1.5f,64,64,m_Part1,m_Part2-m_Part1);
break;
}

m_xRot+=m_xSpeed;//x軸旋轉
m_yRot+=m_ySpeed;//y軸旋轉
}

我們將原來的繪制立方體部分的代碼換成了一個switch()語句,我們利用m_Object來確定畫哪一種物體(具體哪個值對應哪個,請大家參照注釋)。我們后面討論繪制這些物體調用的函數時,會忽略第一個參數m_Quadratic,這個參數將被除立方體外的所有對象使用。由于前面已經解釋過二次幾何體的實質,我們在討論下面函數的參數時將忽略它。

我們創建的第2個對象是一個圓柱體:參數2是圓柱的底面半徑;參數3是圓柱的頂面半徑;參數4是圓柱的高度(表面我們也可以繪制圓臺的);參數5是緯線(環繞z軸有多少細分);參數6是經線(沿著z軸有多少細分)。細分越多該對象就越細致,其實我們可以用gluCylinder來繪制多棱柱的,只要把參數5和參數6換成對應的棱數就行了。

第3個對象是一個CD一樣的盤子:參數2是盤子的內圓半徑,該參數可以為0.0,則表示在盤子中間沒孔,內圓半徑越大孔越大;參數3表示外圓半徑,這個參數必須比內圓半徑大;參數4是組成該盤子切片的數量;參數5是組成盤子的環的數量,環很像唱片上的軌跡。同樣,把參數4改成邊數,同樣可以得到帶孔(不帶孔)的多邊形。

第4個對象是球:參數2是球的半徑;和圓柱一樣,參數3是緯線;參數4是經線。細分越多球看起來就越平滑。

第5個對象是圓錐:其實和繪制圓柱是一樣的,只是把頂面半徑設置為0.0,這樣頂面就成了一個點。同樣參考上面說的方法可以繪制多棱錐。

第6個對象將被gluPartialDisk()函數創建。相比于gluDisk()函數,gluPartialDisk()多了兩個新參數。參數6是我們想要繪制的分部盤子的開始角度,參數6是旋轉角,也就是轉過的調度。我們將要增加旋轉角,這將引起盤子沿順時針方向緩慢的被繪制在屏幕上。一旦旋轉角達到360度,我們將開始增加開始角度,這樣盤子看起來就像是被逐漸地抹去一樣,我們將重復這兩個過程。

最后我們修改一下鍵盤控制函數,不多解釋了,具體代碼如下:

voidMyGLWidget::keyPressEvent(QKeyEvent*event)
{
switch(event->key())
{
caseQt::Key_F1://F1為全屏和普通屏的切換鍵
fullscreen=!fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
}
break;
caseQt::Key_Escape://ESC為退出鍵
close();
break;
caseQt::Key_L://L為開啟關閉光源的切換鍵
m_Light=!m_Light;
if(m_Light)
{
glEnable(GL_LIGHTING);//開啟光源
}
else
{
glDisable(GL_LIGHTING);//關閉光源
}
break;
caseQt::Key_Space://空格為物體的切換鍵
m_Object++;
if(m_Object==6)
{
m_Object=0;
}
break;
caseQt::Key_PageUp://PageUp按下使木箱移向屏幕內部
m_Deep-=0.1f;
break;
caseQt::Key_PageDown://PageDown按下使木箱移向觀察者
m_Deep+=0.1f;
break;
caseQt::Key_Up://Up按下減少m_xSpeed
m_xSpeed-=0.1f;
break;
caseQt::Key_Down://Down按下增加m_xSpeed
m_xSpeed+=0.1f;
break;
caseQt::Key_Right://Right按下減少m_ySpeed
m_ySpeed-=0.1f;
break;
caseQt::Key_Left://Left按下增加m_ySpeed
m_ySpeed+=0.1f;
break;
}
}

現在就可以運行程序查看效果了!

第19課:粒子系統(參照NeHe)

這次教程中,我們將創建一個簡單的粒子系統,并用它來創建一種噴射效果。利用粒子系統,我們可以實現爆炸、噴泉、流星之類的效果,聽起來是不是很棒呢!

我們還會講到一個新東西,三角形帶(我的理解就是畫很多三角形來組合成我們要的形狀),它非常容易使用,而且當需要畫很多三角形的時候,它能加快你程序的運行速度。這次教程中,我將教你該如何做一個簡單的微粒程序,一旦你了解微粒程序的原理后,再創建例如:火、煙、噴泉等效果將是很輕松的事情。

程序運行時效果如下:

下面進入教程:

我們這次將在第06課代碼的基礎上修改代碼,這次需要修改的代碼量不少,希望大家耐心跟著我一步步來完成這個程序。首先打開myglwidget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
boolfullscreen;//是否全屏顯示
QStringm_FileName;//圖片的路徑及文件名
GLuintm_Texture;//儲存一個紋理

staticconstintMAX_PARTICLES=1000;//最大粒子數
staticconstGLfloatCOLORS[12][3];//彩虹的顏色
boolm_Rainbow;//是否為彩虹模式
GLuintm_Color;//當前的顏色

floatm_Slowdown;//減速粒子
floatm_xSpeed;//x方向的速度
floatm_ySpeed;//y方向的速度
floatm_Deep;//移入屏幕的距離

structParticle//創建粒子結構體
{
boolactive;//是否激活
floatlife;//粒子生命
floatfade;//衰減速度

floatr,g,b;//粒子顏色
floatx,y,z;//位置坐標
floatxi,yi,zi;//各方向速度
floatxg,yg,zg;//各方向加速度
}m_Particles[MAX_PARTICLES];//存放1000個粒子的數組
};

#endif//MYGLWIDGET_H

首先我們定義了一個靜態整形常量MAX_PARTICLES來存放粒子的最大數目,和一個靜態GLfloat常量數組來存放彩虹的顏色。接著是一個布爾變量m_Rainbow來表示當前模式是否為彩虹模式,然后是GLuint變量m_Color來表示當前的粒子的顏色,它將在控制粒子顏色在彩虹顏色數組中切換。粒子顏色會與紋理融合,我們用紋理而不用電的重要原因是,點的速度慢,而且挺麻煩的,其次紋理很酷,也好控制。

下面四行是定義了四個浮點變量。m_Slowdown控制粒子移動的快慢,數值越高移動越快,數值越低移動越慢,粒子的速度將影響它們在屏幕上移動的距離,要注意速度慢的粒子不會移動很遠就會消失。m_xSpeed和m_ySpeed控制尾部的方向,m_xSpeed為正時粒子將會向右移動,負時則向左移動,m_ySpeed為正時粒子將會向上移動,負時則向下移動,m_xSpeed和m_ySpeed有助于在我們想要的方向上移動粒子。最后是變量m_Deep,我們用該變量移入移除我們的屏幕,在粒子系統中,有時當接近你時,可以看見更多美妙的圖像。

最后我們定義了結構體Particle,用來描述某一粒子的狀態屬性。我們用布爾變量active開始,如果為true,我們的粒子為活躍的;如果為false則粒子為死的,此時我們就不繪制它。變量life和fade來控制粒子顯示多久以及顯示時候的亮度,隨著life數值的降低fade的數值也相應減低,這將導致一些粒子比其他粒子燃燒的時間長。后面是記錄粒子顏色,位置,速度,加速度等狀態屬性的變量,作用我想大家會點高中物理都能明白的,最后我們創建一個長度為MAX_PARTICLES的結構體數組。

接下來,我們打開myglwidget.cpp,在構造函數中對新增變量進行初始化,具體代碼如下:

constGLfloatMyGLWidget::COLORS[][3]=//彩虹的顏色
{
{1.0f,0.5f,0.5f},{1.0f,0.75f,0.5f},{1.0f,1.0f,0.5f},
{0.75f,1.0f,0.5f},{0.5f,1.0f,0.5f},{0.5f,1.0f,0.75f},
{0.5f,1.0f,1.0f},{0.5f,0.75f,1.0f},{0.5f,0.5f,1.0f},
{0.75f,0.5f,1.0f},{1.0f,0.5f,1.0f},{1.0f,0.5f,0.75f}
};

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_FileName="D:/QtOpenGL/QtImage/Particle.bmp";//應根據實際存放圖片的路徑進行修改
m_Rainbow=true;
m_Color=0;
m_Slowdown=2.0f;
m_xSpeed=0.0f;
m_ySpeed=0.0f;
m_Deep=-40.0f;

for(inti=0;i<MAX_PARTICLES;i++)//循環初始化所以粒子
{
m_Particles[i].active=true;//使所有粒子為激活狀態
m_Particles[i].life=1.0f;//所有粒子生命值為最大
//隨機生成衰減速率
m_Particles[i].fade=float(rand()%100)/1000.0f+0.001;

//粒子的顏色
m_Particles[i].r=COLORS[int(i*(12.0f/MAX_PARTICLES))][0];
m_Particles[i].g=COLORS[int(i*(12.0f/MAX_PARTICLES))][1];
m_Particles[i].b=COLORS[int(i*(12.0f/MAX_PARTICLES))][2];

//粒子的初始位置
m_Particles[i].x=0.0f;
m_Particles[i].y=0.0f;
m_Particles[i].z=0.0f;

//隨機生成x、y、z軸方向速度
m_Particles[i].xi=float((rand()%50)-26.0f)*10.0f;
m_Particles[i].yi=float((rand()%50)-25.0f)*10.0f;
m_Particles[i].zi=float((rand()%50)-25.0f)*10.0f;

m_Particles[i].xg=0.0f;//設置x方向加速度為0
m_Particles[i].yg=-0.8f;//設置y方向加速度為-0.8
m_Particles[i].zg=0.0f;//設置z方向加速度為0
}

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

注意到我們在構造函數之前對定義的靜態常量數組COLORS進行初始化,一共包含12種漸變顏色,從紅色到紫羅蘭。進入構造函數一開始是更換紋理圖片以及增加變量的初始化,這些沒什么好解釋的,下面我們重點看循環部分。我們利用循環來初始化每個粒子,我們讓粒子變活躍(不活躍的粒子在屏幕上是不會顯示的)之后,我們給它lfie。life滿值是1.0f,這也給粒子完整的光亮。值得一提,把粒子的生命衰退和顏色漸暗綁到一起,效果真的很不錯!

我們通過隨機數來設置粒子退色的快慢,我們取0~99的隨機數,然后平分1000份來得到一個很小的浮點數,最后結果加上0.001f來使fade速度值不為0。我們既然給了粒子生命,我們當然要給它其他的屬性狀態附上值,為了使粒子有不同的顏色,我們用i 變量乘以數組中顏色的數目(12)與MAX_PARTICLES的商,再轉換成整數,利用得到的整數取對應的顏色就可以了。然后讓粒子從(0, 0, 0)出發,在設定速度時,我們通過將結果乘上10.0f來創造開始時的爆炸效果,加速度就由我們統一指定初始值了。

然后,我們來略微修改initializeGL()函數,代碼如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
m_Texture=bindTexture(QPixmap(m_FileName));//載入位圖并轉換成紋理
glEnable(GL_TEXTURE_2D);//啟用紋理映射

glClearColor(0.0f,0.0f,0.0f,0.0f);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑
glClearDepth(1.0);//設置深度緩存
glDisable(GL_DEPTH_TEST);//禁止深度測試
glEnable(GL_BLEND);//啟用融合
glBlendFunc(GL_SRC_ALPHA,GL_ONE);//設置融合因子
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);//告訴系統對透視進行修正
glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);
}

我們在中間啟用了融合并設置了融合因子,這是為了我們的粒子能有不同顏色。然后我們禁用了深度測試,因為如果啟用深度測試的話,紋理之間會出現覆蓋現象,那樣畫面簡直一團糟。

還有,我們要進入有趣的paintGL()函數了,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置模型觀察矩陣
glBindTexture(GL_TEXTURE_2D,m_Texture);

for(inti=0;i<MAX_PARTICLES;i++)//循環所以的粒子
{
if(m_Particles[i].active)//如果粒子為激活的
{
floatx=m_Particles[i].x;//x軸位置
floaty=m_Particles[i].y;//y軸位置
floatz=m_Particles[i].z+m_Deep;//z軸位置
//設置粒子顏色
glColor4f(m_Particles[i].r,m_Particles[i].g,
m_Particles[i].b,m_Particles[i].life);
glBegin(GL_TRIANGLE_STRIP);//繪制三角形帶
glTexCoord2d(1,1);glVertex3f(x+0.5f,y+0.5f,z);
glTexCoord2d(0,1);glVertex3f(x-0.5f,y+0.5f,z);
glTexCoord2d(1,0);glVertex3f(x+0.5f,y-0.5f,z);
glTexCoord2d(0,0);glVertex3f(x-0.5f,y-0.5f,z);
glEnd();

//更新各方向坐標及速度
m_Particles[i].x+=m_Particles[i].xi/(m_Slowdown*1000);
m_Particles[i].y+=m_Particles[i].yi/(m_Slowdown*1000);
m_Particles[i].z+=m_Particles[i].zi/(m_Slowdown*1000);
m_Particles[i].xi+=m_Particles[i].xg;
m_Particles[i].yi+=m_Particles[i].yg;
m_Particles[i].zi+=m_Particles[i].zg;

m_Particles[i].life-=m_Particles[i].fade;//減少粒子的生命值
if(m_Particles[i].life<0.0f)//如果粒子生命值小于0
{
m_Particles[i].life=1.0f;//產生一個新粒子
m_Particles[i].fade=float(rand()%100)/1000.0f+0.003f;

m_Particles[i].r=colors[m_Color][0];//設置顏色
m_Particles[i].g=colors[m_Color][1];
m_Particles[i].b=colors[m_Color][2];

m_Particles[i].x=0.0f;//粒子出現在屏幕中央
m_Particles[i].y=0.0f;
m_Particles[i].z=0.0f;

//隨機生成粒子速度
m_Particles[i].xi=m_xSpeed+float((rand()%60)-32.0f);
m_Particles[i].yi=m_ySpeed+float((rand()%60)-30.0f);
m_Particles[i].zi=float((rand()%60)-30.0f);
}
}
}

if(m_Rainbow)//如果為彩虹模式
{
m_Color++;//進行顏色的變換
if(m_Color>11)
{
m_Color=0;
}
}
}

paintGL()函數中,我們在循環中沒有重置模型觀察矩陣,因為我們并沒有使用過glRotate和glTranslate函數,我們在畫粒子位置的時候,計算出相應坐標,用glVertex3f()函數來代替glTranslate函數,這樣在我們畫粒子的時候就不會改變模型觀察矩陣了。

然后我們建立一個循環,在循環中更新繪制每一個粒子。首先檢查粒子是否活躍,如果不活躍則不被更新(在這個程序中,它們始終都是活躍的)。接著定義三個臨時變量存放粒子的x、y、z值,設置粒子顏色,然后就來繪制它了,我們用一個三角形帶來代替四邊形這樣使程序運行快一點(一般情況是這樣,關于三角形帶點此有相關文章)。

接下來我們來移動粒子。首先我們取得當前粒子的x位置,然后把x運動速度加上粒子被減速1000倍后的值。所以如果粒子在x軸(0)上屏幕中心的位置,x軸速度(xi)為+10,而m_Slowdown為1,我們可以以10/(1*1000)或0.01f速度移向右邊。如果,m_slowDown值到2我們的速度就只有0.005f了。這也是為什么yong10.0f乘開始值來叫像素移動快速,制造一個爆發效果。然后我們要根據加速度更新我們粒子的速度,根據衰退速度更新我們粒子的生命。

最后我們檢查粒子是否還活著(生命值大于0),如果粒子燒盡,我們會使它恢復,我們給它滿值生命和新的衰退速度。當然我們也重新設定粒子回到屏幕中心,然后重新隨機生成速度。要注意,我們沒有將移動速度乘10,我們這次不想要一個爆發效果,而要比較慢地移動粒子;然后我們要相應的加上m_xSpeed和m_ySpeed,這個控制了粒子大體得移動方向。最后我們給粒子分配當前的顏色就搞定循環了。

函數最后,我們判斷是否為彩虹模式,如果是就改變當前的顏色,這樣不同時間“重生”后的粒子就可能得到不同的顏色,從而出現彩虹效果。

最后就是鍵盤控制了,由于為了增加點趣味性,這次鍵盤控制比較“麻煩”,但是調理很清晰,具體代碼如下:

voidMyGLWidget::keyPressEvent(QKeyEvent*event)
{
switch(event->key())
{
caseQt::Key_F1://F1為全屏和普通屏的切換鍵
fullscreen=!fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
}
updateGL();
break;
caseQt::Key_Escape://ESC為退出鍵
close();
break;
caseQt::Key_Tab://Tab按下使粒子回到原點,產生爆炸
for(inti=0;i<MAX_PARTICLES;i++)
{
m_Particles[i].x=0.0f;
m_Particles[i].y=0.0f;
m_Particles[i].z=0.0f;

//隨機生成速度
m_Particles[i].xi=float((rand()%50)-26.0f)*10.0f;
m_Particles[i].yi=float((rand()%50)-25.0f)*10.0f;
m_Particles[i].zi=float((rand()%50)-25.0f)*10.0f;
}
break;
caseQt::Key_8://按下8增加y方向加速度
for(inti=0;i<MAX_PARTICLES;i++)
{
if(m_Particles[i].yg<3.0f)
{
m_Particles[i].yg+=0.05f;
}
}
break;
caseQt::Key_2://按下2減少y方向加速度
for(inti=0;i<MAX_PARTICLES;i++)
{
if(m_Particles[i].yg>-3.0f)
{
m_Particles[i].yg-=0.05f;
}
}
break;
caseQt::Key_6://按下6增加x方向加速度
for(inti=0;i<MAX_PARTICLES;i++)
{
if(m_Particles[i].xg<3.0f)
{
m_Particles[i].xg+=0.05f;
}
}
break;
caseQt::Key_4://按下4減少x方向加速度
for(inti=0;i<MAX_PARTICLES;i++)
{
if(m_Particles[i].xg>-3.0f)
{
m_Particles[i].xg-=0.05f;
}
}
break;
caseQt::Key_Plus://+號按下加速粒子
if(m_Slowdown>1.0f)
{
m_Slowdown-=0.05f;
}
break;
caseQt::Key_Minus://-號按下減速粒子
if(m_Slowdown<3.0f)
{
m_Slowdown+=0.05f;
}
break;
caseQt::Key_PageUp://PageUp按下使粒子靠近屏幕
m_Deep+=0.5f;
break;
caseQt::Key_PageDown://PageDown按下使粒子遠離屏幕
m_Deep-=0.5f;
break;
caseQt::Key_Return://回車鍵為是否彩虹模式的切換鍵
m_Rainbow=!m_Rainbow;
break;
caseQt::Key_Space://空格鍵為顏色切換鍵
m_Rainbow=false;
m_Color++;
if(m_Color>11)
{
m_Color=0;
}
break;
caseQt::Key_Up://Up按下增加粒子y軸正方向的速度
if(m_ySpeed<400.0f)
{
m_ySpeed+=5.0f;
}
break;
caseQt::Key_Down://Down按下減少粒子y軸正方向的速度
if(m_ySpeed>-400.0f)
{
m_ySpeed-=5.0f;
}
break;
caseQt::Key_Right://Right按下增加粒子x軸正方向的速度
if(m_xSpeed<400.0f)
{
m_xSpeed+=5.0f;
}
break;
caseQt::Key_Left://Left按下減少粒子x軸正方向的速度
if(m_xSpeed>-400.0f)
{
m_xSpeed-=5.0f;
}
break;
}
}

我感覺注釋已經寫得比較清楚了,就不解釋太多了,具體里面的值是怎么得到的,其實就是一點點嘗試,感覺效果好久用了,就這么簡單!大家注意一下Tab鍵按下后,全部粒子會回到原點,重新從原點出發,并且我們給它們重新生成速度,方式和初始化時是相同的,這樣就又產生了爆炸效果。

現在就可以運行程序查看效果了!

第20課:蒙板(參照NeHe)

這次教程中,我們教介紹OpenGL的蒙板技術。到目前為止,我們已經學會如何使用alpha混合,把一個透明物體渲染到屏幕上了,但有時使用它看起來并不是那么的復合我們的心意。使用蒙板技術,將會使圖像按照我們設定的蒙板位置精確地繪制。

直到現在,我們在把圖像加載到屏幕上時都沒有檫除背景色,因為這樣簡單高效,但是效果并不總是很好。大部分情況下,把紋理混合到屏幕,紋理不是太少就是太多。當我們使用精靈圖時,我們不希望背景從精靈的縫隙中透出光來;但在顯示文字時,我們又希望文字的間隙可以顯示背景色。

基于上述原因,我們需要使用“掩模”。使用“掩膜”需要兩個步驟,首先我們在場景上放置黑白相間的紋理,白色代表透明部分,黑色代表不透明部分。接著我們使用一種特殊的混合方式,只有在黑色部分上的紋理才會顯示在場景中。

程序運行時效果如下:

下面進入教程:

我們這次將在第06課代碼的基礎上修改代碼,總體上并不會太難,希望大家能理解蒙板技術,這技術真的很好用。首先打開myglwidget.h文件,將類聲明更改如下:

#ifndefMYGLWIDGET_H
#defineMYGLWIDGET_H

#include<QWidget>
#include<QGLWidget>

classMyGLWidget:publicQGLWidget
{
Q_OBJECT
public:
explicitMyGLWidget(QWidget*parent=0);
~MyGLWidget();

protected:
//對3個純虛函數的重定義
voidinitializeGL();
voidresizeGL(intw,inth);
voidpaintGL();

voidkeyPressEvent(QKeyEvent*event);//處理鍵盤按下事件

private:
boolfullscreen;//是否全屏顯示
boolm_Masking;//是否使用"<spanstyle="font-size:12px;">掩模</span>"
boolm_Scene;//控制繪制哪一層

GLfloatm_Rot;//控制紋理滾動
QStringm_FileName[5];//圖片的路徑及文件名
GLuintm_Texture[5];//儲存五個紋理
};

#endif//MYGLWIDGET_H

我們增加了兩個布爾變量m_Masking和m_Scene來控制是否開啟“掩模”以及繪制哪一個場景。然后我們增加一個控制圖形滾動旋轉的變量m_Rot,當然要去掉之前控制旋轉的變量。最后把m_FileName和m_Texture變成長度為5的數組,因為我們需要載入5個紋理。

接下來,我們打開myglwidget.cpp,在構造函數中對新增變量進行初始化,比較簡單,大家參照注釋理解,不多作解釋,具體代碼如下:

MyGLWidget::MyGLWidget(QWidget*parent):
QGLWidget(parent)
{
fullscreen=false;
m_Masking=true;
m_Scene=false;

m_FileName[0]="D:/QtOpenGL/QtImage/Logo.bmp";//紋理0
m_FileName[1]="D:/QtOpenGL/QtImage/Mask1.bmp";//<spanstyle="font-size:12px;">掩模</span>紋理1,作為"<spanstyle="font-size:12px;">掩模</span>"使用
m_FileName[2]="D:/QtOpenGL/QtImage/Image1.bmp";//紋理1
m_FileName[3]="D:/QtOpenGL/QtImage/Mask2.bmp";//<spanstyle="font-size:12px;">掩模</span>紋理2,作為"<spanstyle="font-size:12px;">掩模</span>"使用
m_FileName[4]="D:/QtOpenGL/QtImage/Image2.bmp";//紋理2

QTimer*timer=newQTimer(this);//創建一個定時器
//將定時器的計時信號與updateGL()綁定
connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
timer->start(10);//以10ms為一個計時周期
}

然后,我們略微修改下initializeGL()函數,就是載入5個位圖并轉換成紋理,不多解釋了,具體代碼如下:

voidMyGLWidget::initializeGL()//此處開始對OpenGL進行所以設置
{
//載入位圖并轉換成紋理
for(inti=0;i<5;i++){
m_Texture[i]=bindTexture(QPixmap(m_FileName[i]));
}
glEnable(GL_TEXTURE_2D);//啟用紋理映射

glClearColor(0.0f,0.0f,0.0f,0.0f);//黑色背景
glShadeModel(GL_SMOOTH);//啟用陰影平滑
glClearDepth(1.0);//設置深度緩存<prename="code"class="cpp"><prename="code"class="cpp">}



繼續,我們要進入最有趣的paintGL()函數,當然這也是重點,具體代碼如下:

voidMyGLWidget::paintGL()//從這里開始進行所以的繪制
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除屏幕和深度緩存
glLoadIdentity();//重置模型觀察矩陣
glTranslatef(0.0f,0.0f,-2.0f);//移入屏幕2.0單位

glBindTexture(GL_TEXTURE_2D,m_Texture[0]);//選擇Logo紋理
glBegin(GL_QUADS);//繪制紋理四邊形
glTexCoord2f(0.0f,-m_Rot+0.0f);
glVertex3f(-1.1f,-1.1f,0.0f);
glTexCoord2f(3.0f,-m_Rot+0.0f);
glVertex3f(1.1f,-1.1f,0.0f);
glTexCoord2f(3.0f,-m_Rot+3.0f);
glVertex3f(1.1f,1.1f,0.0f);
glTexCoord2f(0.0f,-m_Rot+3.0f);
glVertex3f(-1.1f,1.1f,0.0f);
glEnd();

glEnable(GL_BLEND);//啟用混合
glDisable(GL_DEPTH_TEST);//禁用深度測試

if(m_Masking)//是否啟用"<spanstyle="font-size:12px;">掩模</span>"
{
glBlendFunc(GL_DST_COLOR,GL_ZERO);//使用黑白"<spanstyle="font-size:12px;">掩模</span>"
}

if(m_Scene)
{
glTranslatef(0.0f,0.0f,-1.0f);//移入屏幕1.0單位
glRotatef(m_Rot*360,0.0f,0.0f,1.0f);//繞z軸旋轉

if(m_Masking)//"<spanstyle="font-size:12px;">掩模</span>"是否打開
{
glBindTexture(GL_TEXTURE_2D,m_Texture[3]);//選擇第二個"<spanstyle="font-size:12px;">掩模</span>"紋理
glBegin(GL_QUADS);//開始繪制四邊形
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.1f,-1.1f,0.0f);
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.1f,-1.1f,0.0f);
glTexCoord2f(1.0f,1.0f);
glVertex3f(1.1f,1.1f,0.0f);
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.1f,1.1f,0.0f);
glEnd();
}

glBlendFunc(GL_ONE,GL_ONE);//把紋理2復制到屏幕上
glBindTexture(GL_TEXTURE_2D,m_Texture[4]);//選擇第二個紋理
glBegin(GL_QUADS);//繪制四邊形
glTexCoord2f(0.0f,0.0f);
glVertex3f(-1.1f,-1.1f,0.0f);
glTexCoord2f(1.0f,0.0f);
glVertex3f(1.1f,-1.1f,0.0f);
glTexCoord2f(1.0f,1.0f);
glVertex3f(1.1f,1.1f,0.0f);
glTexCoord2f(0.0f,1.0f);
glVertex3f(-1.1f,1.1f,0.0f);
glEnd();
}
else
{
if(m_Masking)//"<spanstyle="font-size:12px;">掩模</span>"是否打開
{
glBindTexture(GL_TEXTURE_2D,m_Texture[1]);//選擇第一個"<spanstyle="font-size:12px;">掩模</span>"紋理
glBegin(GL_QUADS);//繪制四邊形
glTexCoord2f(m_Rot+0.0f,0.0f);
glVertex3f(-1.1f,-1.1f,0.0f);
glTexCoord2f(m_Rot+4.0f,0.0f);
glVertex3f(1.1f,-1.1f,0.0f);
glTexCoord2f(m_Rot+4.0f,4.0f);
glVertex3f(1.1f,1.1f,0.0f);
glTexCoord2f(m_Rot+0.0f,4.0f);
glVertex3f(-1.1f,1.1f,0.0f);
glEnd();
}

glBlendFunc(GL_ONE,GL_ONE);//把紋理1復制到屏幕
glBindTexture(GL_TEXTURE_2D,m_Texture[2]);//選擇第一個紋理
glBegin(GL_QUADS);//繪制四邊形
glTexCoord2f(m_Rot+0.0f,0.0f);
glVertex3f(-1.1f,-1.1f,0.0f);
glTexCoord2f(m_Rot+4.0f,0.0f);
glVertex3f(1.1f,-1.1f,0.0f);
glTexCoord2f(m_Rot+4.0f,4.0f);
glVertex3f(1.1f,1.1f,0.0f);
glTexCoord2f(m_Rot+0.0f,4.0f);
glVertex3f(-1.1f,1.1f,0.0f);
glEnd();
}

glEnable(GL_DEPTH_TEST);//啟用深度測試
glDisable(GL_BLEND);//禁用混合

m_Rot+=0.002f;//增加調整紋理滾動旋轉變量
if(m_Rot>1.0f)
{
m_Rot-=1.0f;
}
}

函數一開始,清除背景色,重置矩陣,把物體移入屏幕2.0單位。接著我們選擇logo紋理,繪制紋理四邊形,注意到我們調用glTexCoord選擇紋理坐標時,有的數是大于1.0的,這時候OpenGL默認截取小數部分進行處理,這樣就可以得到無縫的循環紋理(具體效果大家看上面的圖或自己運行程序時再看)。然后我們啟用混合并禁用深度測試。

接著我們需要根據m_Masking的值設置是否使用“掩模”,如果是,我們需要設置相應的混合因子。一個“掩模”只是一幅繪制到屏幕的紋理圖片,但只有黑色和白色,白色的部分代表透明,黑色的部分代表不透明。我們設置的混合因子GL_DST_COLOR、GL_ZERO使得任何紋理(OpenGL并不知道這是不是“掩模”)黑色的部分會變為黑色,白色的部分會保持原來的顏色,就是變成透明,透過了原來的顏色。

然后我們檢查是繪制哪一個場景(圖層),true繪制第二層,false繪制第一層。true時先開始繪制第二層,為了不使得它看起來太大,我們把它移入屏幕1.0單位,并把它按m_Rot的值繞z軸旋轉。接著我們檢查m_Marking的值,如果為true,我們就把“掩模”繪制到屏幕上,當我們完成這個操作時,將會看到一個鏤空的紋理出現在屏幕上。然后我們變換混合因子GL_ONE、GL_ONE,這次我們告訴OpenGL把任何黑色部分對應的像素復制到屏幕,這樣看起來紋理就像被鏤空一樣貼在屏幕上。要注意的是,我在變換了混合因子后才選擇的紋理。如果我們沒有使用 “掩模”,我們的圖像將與屏幕顏色融合。

下面我繪制第一層與第二層的繪制基本相同,不多解釋了。最后我們啟用深度測試,禁用混合,然后增加m_Rot變量,如果大于1.0,把它的值減去1.0。

最后我們修改一下鍵盤控制函數,就是加上了空格和M鍵作為切換鍵,很簡單不多解釋了,具體代碼如下:

voidMyGLWidget::keyPressEvent(QKeyEvent*event)
{
switch(event->key())
{
caseQt::Key_F1://F1為全屏和普通屏的切換鍵
fullscreen=!fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
}
updateGL();
break;
caseQt::Key_Escape://ESC為退出鍵
close();
break;
caseQt::Key_Space://空格為場景(圖層)的切換鍵
m_Scene=!m_Scene;
break;
caseQt::Key_M://M為是否"掩膜"的切換鍵
m_Masking=!m_Masking;
break;
}
}

現在就可以運行程序查看效果了!

一點內容的補充:上面我們提到當調用glTexCoord選擇紋理坐標時,如果大于1.0,OpenGL默認截取小數部分進行處理。其實這只是OpenGL默認的處理模式:GL_REPEAT。對于紋理坐標大于1.0,OpenGL有以下幾種處理模式:

GL_CLAMP - 截取

GL_REPEAT - 重復(OpenGL默認的模式)

GL_MIRRORED_REPEAT - 鏡像重復

GL_CLAMP_TO_EDGE - 忽略邊框截取

GL_CLAMP_TO_BORDER - 帶邊框的截取

我們可以利用glTexParameter函數來進行模式的轉換,如:x方向的轉換為glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP),變換模式只需更改第三個參數。而第二參數代表方向,GL_TEXTURE_WRAP_S代表x方向,GL_TEXTURE_WRAP_T代表y方向,GL_TEXTURE_WRAP_R代表z方向。

總結

以上是生活随笔為你收集整理的Qt Creator中的3D绘图及动画教程(参照NeHe)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

a在线亚洲男人的天堂 | 成熟女人特级毛片www免费 | 牲欲强的熟妇农村老妇女 | 久久国产精品_国产精品 | 色欲人妻aaaaaaa无码 | 国产综合久久久久鬼色 | 亚洲国产欧美日韩精品一区二区三区 | av无码不卡在线观看免费 | 国产无av码在线观看 | 黑人巨大精品欧美黑寡妇 | 玩弄中年熟妇正在播放 | 无码免费一区二区三区 | 99久久精品无码一区二区毛片 | 久久综合九色综合97网 | 国产高清av在线播放 | 精品国产一区二区三区四区在线看 | 色欲av亚洲一区无码少妇 | 日日橹狠狠爱欧美视频 | 夜夜夜高潮夜夜爽夜夜爰爰 | 蜜桃视频插满18在线观看 | 麻豆精品国产精华精华液好用吗 | 久久精品国产大片免费观看 | 四虎国产精品一区二区 | 蜜桃视频韩日免费播放 | 日日天干夜夜狠狠爱 | 综合网日日天干夜夜久久 | 欧美freesex黑人又粗又大 | 亚洲精品一区三区三区在线观看 | 女人被男人爽到呻吟的视频 | 亚洲欧美国产精品久久 | 亚洲成av人综合在线观看 | 亚洲小说图区综合在线 | 午夜福利电影 | 综合激情五月综合激情五月激情1 | 久久成人a毛片免费观看网站 | 无码国内精品人妻少妇 | 久久精品人妻少妇一区二区三区 | 中文字幕无码日韩欧毛 | 亚洲国产成人av在线观看 | 日本精品人妻无码77777 天堂一区人妻无码 | 麻豆果冻传媒2021精品传媒一区下载 | 国产精品办公室沙发 | 性生交大片免费看女人按摩摩 | 国产精品毛多多水多 | 青草视频在线播放 | 国产绳艺sm调教室论坛 | 少妇愉情理伦片bd | 国产精品久久久久久久9999 | 国产特级毛片aaaaaaa高清 | 波多野结衣av一区二区全免费观看 | 无码精品人妻一区二区三区av | 麻豆蜜桃av蜜臀av色欲av | 麻豆国产人妻欲求不满谁演的 | 一本精品99久久精品77 | 中文字幕 人妻熟女 | 精品乱子伦一区二区三区 | 亚洲国产欧美日韩精品一区二区三区 | 人人爽人人澡人人高潮 | 国产熟妇高潮叫床视频播放 | 国产午夜手机精彩视频 | 午夜精品久久久内射近拍高清 | 亚洲精品无码国产 | 国产极品美女高潮无套在线观看 | 久久99精品国产麻豆蜜芽 | 国产av剧情md精品麻豆 | 国产美女极度色诱视频www | 亚洲成av人片在线观看无码不卡 | av香港经典三级级 在线 | 国产在线一区二区三区四区五区 | 欧美高清在线精品一区 | 国内揄拍国内精品少妇国语 | 亚洲色在线无码国产精品不卡 | 国产色xx群视频射精 | 欧美亚洲国产一区二区三区 | 国产亚洲精品久久久ai换 | 亚洲国产欧美在线成人 | 精品无码成人片一区二区98 | 国产亚洲tv在线观看 | 漂亮人妻洗澡被公强 日日躁 | 人人澡人人妻人人爽人人蜜桃 | 久久精品一区二区三区四区 | 国产麻豆精品一区二区三区v视界 | 一本大道伊人av久久综合 | 熟妇女人妻丰满少妇中文字幕 | 曰韩少妇内射免费播放 | 欧美性猛交xxxx富婆 | 99久久无码一区人妻 | 久久精品无码一区二区三区 | 亚洲狠狠色丁香婷婷综合 | 帮老师解开蕾丝奶罩吸乳网站 | 精品无码一区二区三区的天堂 | 久久成人a毛片免费观看网站 | 欧美精品无码一区二区三区 | 日韩人妻无码中文字幕视频 | 日韩av激情在线观看 | 国产精华av午夜在线观看 | 色婷婷综合中文久久一本 | 国产农村妇女高潮大叫 | 日韩在线不卡免费视频一区 | 鲁鲁鲁爽爽爽在线视频观看 | 亚洲精品国偷拍自产在线麻豆 | 少妇无套内谢久久久久 | 日产国产精品亚洲系列 | 国产精品无码永久免费888 | 51国偷自产一区二区三区 | 中文字幕无码免费久久99 | 欧美丰满熟妇xxxx | 久久精品中文字幕大胸 | 成人精品一区二区三区中文字幕 | 国产精品久久久久久亚洲影视内衣 | 欧美黑人性暴力猛交喷水 | 对白脏话肉麻粗话av | 国内精品一区二区三区不卡 | 国内揄拍国内精品少妇国语 | 亚洲 激情 小说 另类 欧美 | 亚洲国产精品久久久久久 | 亚洲成在人网站无码天堂 | 久久精品人人做人人综合试看 | 在线观看免费人成视频 | 色欲久久久天天天综合网精品 | 国产美女极度色诱视频www | 国产国语老龄妇女a片 | 亚洲一区二区三区在线观看网站 | 精品一区二区三区波多野结衣 | 国产精品理论片在线观看 | 中文字幕av伊人av无码av | 在线视频网站www色 | 欧美老妇交乱视频在线观看 | 成人精品一区二区三区中文字幕 | 国产成人综合色在线观看网站 | 内射后入在线观看一区 | 久久久精品欧美一区二区免费 | 日本精品人妻无码77777 天堂一区人妻无码 | 午夜性刺激在线视频免费 | 久久国产精品精品国产色婷婷 | 香蕉久久久久久av成人 | 日本一区二区更新不卡 | 亚洲大尺度无码无码专区 | 白嫩日本少妇做爰 | 欧美性猛交内射兽交老熟妇 | 一本大道伊人av久久综合 | 日本护士毛茸茸高潮 | 99久久精品日本一区二区免费 | 国产成人无码专区 | 无码纯肉视频在线观看 | 欧美 日韩 人妻 高清 中文 | 成人三级无码视频在线观看 | 色爱情人网站 | 日本熟妇人妻xxxxx人hd | 成人免费视频一区二区 | 欧美怡红院免费全部视频 | 熟妇人妻激情偷爽文 | 亚洲乱码国产乱码精品精 | 狠狠噜狠狠狠狠丁香五月 | 人妻熟女一区 | 四虎永久在线精品免费网址 | 伦伦影院午夜理论片 | 色老头在线一区二区三区 | 图片小说视频一区二区 | 乱人伦中文视频在线观看 | 午夜福利电影 | 国产97在线 | 亚洲 | 亚洲国产欧美日韩精品一区二区三区 | 国产欧美精品一区二区三区 | 久久人人爽人人爽人人片ⅴ | 久久久久99精品成人片 | 无码av中文字幕免费放 | 精品国产av色一区二区深夜久久 | 亚洲日本va午夜在线电影 | 色偷偷人人澡人人爽人人模 | 国产av人人夜夜澡人人爽麻豆 | 自拍偷自拍亚洲精品10p | yw尤物av无码国产在线观看 | 国产人妻人伦精品1国产丝袜 | 人人妻人人澡人人爽欧美一区九九 | 国产艳妇av在线观看果冻传媒 | 中文字幕av无码一区二区三区电影 | 中文字幕无码热在线视频 | 欧美国产亚洲日韩在线二区 | 对白脏话肉麻粗话av | 亚洲欧美综合区丁香五月小说 | 一本色道久久综合狠狠躁 | 欧美大屁股xxxxhd黑色 | 麻豆成人精品国产免费 | 色综合久久88色综合天天 | 亚洲成av人片在线观看无码不卡 | 99久久精品国产一区二区蜜芽 | 男人的天堂av网站 | 欧洲vodafone精品性 | 国产精品鲁鲁鲁 | 亚洲色欲色欲欲www在线 | 成人aaa片一区国产精品 | 久久久久久久人妻无码中文字幕爆 | 蜜臀av在线观看 在线欧美精品一区二区三区 | 人妻无码久久精品人妻 | 无码任你躁久久久久久久 | 成人影院yy111111在线观看 | 亚欧洲精品在线视频免费观看 | 亚洲va中文字幕无码久久不卡 | 精品国偷自产在线视频 | 国产成人精品久久亚洲高清不卡 | 亚洲精品一区二区三区在线 | 99国产精品白浆在线观看免费 | 午夜不卡av免费 一本久久a久久精品vr综合 | 欧美老熟妇乱xxxxx | 麻豆人妻少妇精品无码专区 | 欧美日韩一区二区三区自拍 | 无码人妻黑人中文字幕 | a片在线免费观看 | 日本熟妇浓毛 | 中文精品久久久久人妻不卡 | 亚洲欧洲中文日韩av乱码 | 自拍偷自拍亚洲精品10p | 成人动漫在线观看 | 日本丰满护士爆乳xxxx | 国产在线精品一区二区高清不卡 | 欧美激情一区二区三区成人 | 日本精品人妻无码77777 天堂一区人妻无码 | 性欧美熟妇videofreesex | 夜精品a片一区二区三区无码白浆 | 亚洲 激情 小说 另类 欧美 | 欧美 日韩 人妻 高清 中文 | 欧美人与牲动交xxxx | 精品欧洲av无码一区二区三区 | 又色又爽又黄的美女裸体网站 | 99riav国产精品视频 | 国产又爽又猛又粗的视频a片 | 狂野欧美激情性xxxx | 亚洲精品无码国产 | 国产精品无码一区二区桃花视频 | 一本色道婷婷久久欧美 | 麻豆精产国品 | 国产午夜亚洲精品不卡 | 亚洲成熟女人毛毛耸耸多 | 精品aⅴ一区二区三区 | 激情国产av做激情国产爱 | 图片小说视频一区二区 | 四虎4hu永久免费 | 午夜福利一区二区三区在线观看 | 乱中年女人伦av三区 | 国产卡一卡二卡三 | 麻豆成人精品国产免费 | 久久无码专区国产精品s | 亚洲欧美国产精品久久 | 亚洲精品久久久久avwww潮水 | a片在线免费观看 | 一本久道久久综合婷婷五月 | 99国产精品白浆在线观看免费 | 成人无码精品1区2区3区免费看 | 国内老熟妇对白xxxxhd | 亚洲精品午夜无码电影网 | 人人妻人人藻人人爽欧美一区 | 国产精品-区区久久久狼 | 久精品国产欧美亚洲色aⅴ大片 | 最近的中文字幕在线看视频 | 乱人伦中文视频在线观看 | 丝袜 中出 制服 人妻 美腿 | 美女极度色诱视频国产 | 99麻豆久久久国产精品免费 | 亚洲综合精品香蕉久久网 | 色情久久久av熟女人妻网站 | 色婷婷香蕉在线一区二区 | 久久久精品456亚洲影院 | 99久久亚洲精品无码毛片 | 国产人妖乱国产精品人妖 | 欧美真人作爱免费视频 | 久久久久久av无码免费看大片 | 丝袜人妻一区二区三区 | 丰腴饱满的极品熟妇 | 久久国内精品自在自线 | 日韩精品乱码av一区二区 | 精品欧美一区二区三区久久久 | 亚洲精品久久久久久久久久久 | 丰满人妻精品国产99aⅴ | 日本一区二区三区免费播放 | 日韩在线不卡免费视频一区 | 色欲久久久天天天综合网精品 | 亚洲国产精品无码久久久久高潮 | 久久综合九色综合欧美狠狠 | 蜜桃av抽搐高潮一区二区 | 色一情一乱一伦一区二区三欧美 | 伦伦影院午夜理论片 | 一本久久a久久精品亚洲 | 久久久久久久人妻无码中文字幕爆 | 国产午夜无码精品免费看 | 日韩精品a片一区二区三区妖精 | 伊人久久婷婷五月综合97色 | 日本一卡二卡不卡视频查询 | 久久99精品久久久久久 | 色窝窝无码一区二区三区色欲 | 精品国产一区二区三区av 性色 | 大胆欧美熟妇xx | 清纯唯美经典一区二区 | 亚洲 激情 小说 另类 欧美 | 鲁鲁鲁爽爽爽在线视频观看 | 夜精品a片一区二区三区无码白浆 | 亚洲中文字幕无码中字 | 99久久久无码国产精品免费 | 国产精品欧美成人 | 免费乱码人妻系列无码专区 | 天天拍夜夜添久久精品大 | 激情五月综合色婷婷一区二区 | 久久人人爽人人人人片 | 俺去俺来也在线www色官网 | 色婷婷av一区二区三区之红樱桃 | 亚洲精品成人福利网站 | 国产 精品 自在自线 | 无码国产乱人伦偷精品视频 | 精品乱子伦一区二区三区 | 久久精品人妻少妇一区二区三区 | 日韩精品成人一区二区三区 | 中文字幕无码免费久久9一区9 | 国内精品人妻无码久久久影院蜜桃 | 装睡被陌生人摸出水好爽 | 国产av无码专区亚洲awww | 久久无码中文字幕免费影院蜜桃 | 国产三级精品三级男人的天堂 | 一本色道久久综合狠狠躁 | 久久综合香蕉国产蜜臀av | 欧美日本免费一区二区三区 | 人妻无码αv中文字幕久久琪琪布 | 亚洲熟妇色xxxxx欧美老妇y | 亚洲综合无码一区二区三区 | 九月婷婷人人澡人人添人人爽 | 久久午夜夜伦鲁鲁片无码免费 | 中文字幕av日韩精品一区二区 | 人人妻人人澡人人爽人人精品浪潮 | 欧美肥老太牲交大战 | 久久综合激激的五月天 | 国产精品久久久一区二区三区 | 久久久国产一区二区三区 | 狂野欧美性猛交免费视频 | 精品欧美一区二区三区久久久 | 国产精品va在线观看无码 | 亚拍精品一区二区三区探花 | 亚洲精品www久久久 | 亚洲一区二区三区播放 | 高清不卡一区二区三区 | 亚洲自偷精品视频自拍 | 黑人粗大猛烈进出高潮视频 | 久久久久久久女国产乱让韩 | 爆乳一区二区三区无码 | 无码精品人妻一区二区三区av | 久久综合激激的五月天 | 久久久久免费精品国产 | 久久久久成人片免费观看蜜芽 | 亚洲 日韩 欧美 成人 在线观看 | 蜜桃视频韩日免费播放 | 色窝窝无码一区二区三区色欲 | 亚洲色大成网站www | 国产乱人伦偷精品视频 | 亚洲精品中文字幕 | 久久午夜无码鲁丝片午夜精品 | 国产又粗又硬又大爽黄老大爷视 | 亲嘴扒胸摸屁股激烈网站 | 天天躁夜夜躁狠狠是什么心态 | 亚洲gv猛男gv无码男同 | 亚洲春色在线视频 | 久久综合给久久狠狠97色 | 人人澡人摸人人添 | 欧美高清在线精品一区 | 成人影院yy111111在线观看 | 精品国产一区二区三区四区在线看 | 亚洲国产欧美国产综合一区 | 国产精华av午夜在线观看 | 丁香啪啪综合成人亚洲 | 中国女人内谢69xxxxxa片 | 精品一二三区久久aaa片 | a片免费视频在线观看 | 黑人巨大精品欧美黑寡妇 | 精品一二三区久久aaa片 | 在线亚洲高清揄拍自拍一品区 | 午夜无码人妻av大片色欲 | 内射欧美老妇wbb | 亚洲男人av香蕉爽爽爽爽 | 成熟人妻av无码专区 | 欧美第一黄网免费网站 | 精品无码av一区二区三区 | 少妇高潮一区二区三区99 | 无码人妻丰满熟妇区毛片18 | 国产一区二区三区日韩精品 | 欧美人与禽zoz0性伦交 | 伊人久久大香线蕉午夜 | 精品aⅴ一区二区三区 | 欧美日韩综合一区二区三区 | 日韩欧美群交p片內射中文 | 亚洲国产av美女网站 | 国产在线aaa片一区二区99 | 国产麻豆精品一区二区三区v视界 | 精品久久久久香蕉网 | 久久精品人妻少妇一区二区三区 | 国产成人无码av一区二区 | 免费观看又污又黄的网站 | 亚洲a无码综合a国产av中文 | 377p欧洲日本亚洲大胆 | 男女下面进入的视频免费午夜 | 日日摸夜夜摸狠狠摸婷婷 | 色综合久久久无码中文字幕 | 久久综合网欧美色妞网 | 中文字幕人成乱码熟女app | 中文字幕人成乱码熟女app | 国产莉萝无码av在线播放 | 色妞www精品免费视频 | 大地资源网第二页免费观看 | 国内丰满熟女出轨videos | 熟妇女人妻丰满少妇中文字幕 | v一区无码内射国产 | 少妇太爽了在线观看 | 97久久精品无码一区二区 | 国产一区二区不卡老阿姨 | 日本在线高清不卡免费播放 | 亚洲色大成网站www国产 | 狠狠cao日日穞夜夜穞av | 999久久久国产精品消防器材 | 亚洲欧美国产精品专区久久 | 激情内射亚州一区二区三区爱妻 | 日韩人妻无码一区二区三区久久99 | 国产精品久久国产精品99 | 久久久av男人的天堂 | 青青久在线视频免费观看 | 亚洲国产一区二区三区在线观看 | 精品无码一区二区三区的天堂 | 国产亚洲视频中文字幕97精品 | 久久无码中文字幕免费影院蜜桃 | 亚洲狠狠婷婷综合久久 | 帮老师解开蕾丝奶罩吸乳网站 | 久久久久久av无码免费看大片 | 狠狠色欧美亚洲狠狠色www | 欧美老妇交乱视频在线观看 | 精品成在人线av无码免费看 | 少妇人妻偷人精品无码视频 | 在线成人www免费观看视频 | 精品少妇爆乳无码av无码专区 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 性色欲情网站iwww九文堂 | 未满小14洗澡无码视频网站 | 亚洲精品一区二区三区婷婷月 | 装睡被陌生人摸出水好爽 | 2019nv天堂香蕉在线观看 | 亚洲天堂2017无码 | 无遮挡国产高潮视频免费观看 | 中文字幕精品av一区二区五区 | 无码任你躁久久久久久久 | 色欲av亚洲一区无码少妇 | 国产一区二区不卡老阿姨 | 水蜜桃亚洲一二三四在线 | 国产精品亚洲一区二区三区喷水 | 精品久久久中文字幕人妻 | 又粗又大又硬又长又爽 | 亚洲精品一区三区三区在线观看 | 日本www一道久久久免费榴莲 | 漂亮人妻洗澡被公强 日日躁 | 美女扒开屁股让男人桶 | 国产性生交xxxxx无码 | 亚洲日韩av一区二区三区中文 | 亚洲综合在线一区二区三区 | 给我免费的视频在线观看 | 欧美日韩综合一区二区三区 | 国色天香社区在线视频 | 在线看片无码永久免费视频 | 亚洲中文字幕在线无码一区二区 | 色欲av亚洲一区无码少妇 | 久久精品国产一区二区三区肥胖 | 伊人色综合久久天天小片 | 少妇性荡欲午夜性开放视频剧场 | 免费无码av一区二区 | 中文字幕精品av一区二区五区 | 欧美怡红院免费全部视频 | 欧美日韩久久久精品a片 | 99久久久无码国产aaa精品 | 人人澡人人妻人人爽人人蜜桃 | 乱人伦人妻中文字幕无码久久网 | 久久综合久久自在自线精品自 | 强伦人妻一区二区三区视频18 | 日韩人妻无码一区二区三区久久99 | 性开放的女人aaa片 | 在线观看免费人成视频 | 欧美熟妇另类久久久久久多毛 | 理论片87福利理论电影 | 亚洲成熟女人毛毛耸耸多 | 妺妺窝人体色www在线小说 | 青草青草久热国产精品 | 中文无码精品a∨在线观看不卡 | 特黄特色大片免费播放器图片 | 亚洲 a v无 码免 费 成 人 a v | 少妇久久久久久人妻无码 | 性生交大片免费看女人按摩摩 | 最新国产乱人伦偷精品免费网站 | 内射白嫩少妇超碰 | 亚洲综合无码一区二区三区 | 亚洲人成网站免费播放 | 漂亮人妻洗澡被公强 日日躁 | 国产人妻精品一区二区三区 | а√资源新版在线天堂 | 久久精品国产日本波多野结衣 | 99riav国产精品视频 | 青春草在线视频免费观看 | 免费无码午夜福利片69 | 亚洲熟女一区二区三区 | 久久久av男人的天堂 | 久久综合给久久狠狠97色 | 两性色午夜免费视频 | 啦啦啦www在线观看免费视频 | 无码av免费一区二区三区试看 | 精品久久久无码人妻字幂 | 国内精品人妻无码久久久影院 | 国产成人av免费观看 | 国产精品成人av在线观看 | 亚洲中文字幕无码中字 | 麻豆人妻少妇精品无码专区 | 乱人伦中文视频在线观看 | 国产一区二区三区影院 | 人妻无码久久精品人妻 | 国产性生交xxxxx无码 | 欧美日韩综合一区二区三区 | 女人和拘做爰正片视频 | 欧洲熟妇精品视频 | 无码任你躁久久久久久久 | 中文字幕乱码中文乱码51精品 | 亚洲日韩av片在线观看 | 精品国产成人一区二区三区 | 国产成人综合色在线观看网站 | 欧洲vodafone精品性 | 亚洲精品综合五月久久小说 | 精品人人妻人人澡人人爽人人 | 国产超级va在线观看视频 | 中文字幕无码人妻少妇免费 | 国产极品美女高潮无套在线观看 | 小sao货水好多真紧h无码视频 | 国产亚洲精品精品国产亚洲综合 | 中文字幕av日韩精品一区二区 | 好爽又高潮了毛片免费下载 | 中文字幕无码日韩欧毛 | 久久久久久av无码免费看大片 | 国产真实夫妇视频 | 欧美老妇与禽交 | 国产精品无码一区二区三区不卡 | 99久久99久久免费精品蜜桃 | 精品国产aⅴ无码一区二区 | 一个人看的视频www在线 | 久久亚洲国产成人精品性色 | 在线天堂新版最新版在线8 | 国产成人精品视频ⅴa片软件竹菊 | 青青青爽视频在线观看 | 2020久久香蕉国产线看观看 | 给我免费的视频在线观看 | 亚洲中文字幕在线无码一区二区 | 黑人粗大猛烈进出高潮视频 | 亚洲日韩av一区二区三区四区 | 亚洲狠狠婷婷综合久久 | 久久亚洲国产成人精品性色 | 国产真人无遮挡作爱免费视频 | 亚洲另类伦春色综合小说 | 久久成人a毛片免费观看网站 | 久久精品中文字幕一区 | 老熟女乱子伦 | 少妇性l交大片欧洲热妇乱xxx | 久久久久国色av免费观看性色 | 国产无av码在线观看 | 精品久久综合1区2区3区激情 | 奇米影视888欧美在线观看 | 中文字幕无码免费久久9一区9 | 未满成年国产在线观看 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 无套内谢的新婚少妇国语播放 | 超碰97人人做人人爱少妇 | √天堂资源地址中文在线 | 亚洲国产精品久久人人爱 | 国产精品资源一区二区 | 久久无码中文字幕免费影院蜜桃 | 极品尤物被啪到呻吟喷水 | 亚洲va欧美va天堂v国产综合 | 亚洲国产精华液网站w | 中文字幕无码av激情不卡 | 久久精品国产精品国产精品污 | 丰满岳乱妇在线观看中字无码 | 久久精品国产99久久6动漫 | 女人和拘做爰正片视频 | 亚洲爆乳精品无码一区二区三区 | 人人妻人人澡人人爽人人精品浪潮 | 国产欧美精品一区二区三区 | 国产精品无码mv在线观看 | 国产午夜精品一区二区三区嫩草 | 成人片黄网站色大片免费观看 | 国产女主播喷水视频在线观看 | 亚洲欧洲无卡二区视頻 | 一个人看的www免费视频在线观看 | 国产精品第一国产精品 | 在线成人www免费观看视频 | 狂野欧美性猛交免费视频 | 亚洲午夜无码久久 | 狠狠色丁香久久婷婷综合五月 | 无码人妻精品一区二区三区下载 | 国产成人无码a区在线观看视频app | 扒开双腿吃奶呻吟做受视频 | 国产免费无码一区二区视频 | 国产精品美女久久久久av爽李琼 | 亚洲综合无码久久精品综合 | 少妇激情av一区二区 | 夜夜躁日日躁狠狠久久av | √8天堂资源地址中文在线 | 亚洲国产精品毛片av不卡在线 | 曰韩无码二三区中文字幕 | 麻豆国产人妻欲求不满 | 伊人久久大香线蕉亚洲 | 日韩精品无码免费一区二区三区 | 国产午夜无码视频在线观看 | 高潮喷水的毛片 | av无码久久久久不卡免费网站 | 丰满护士巨好爽好大乳 | 国产成人无码一二三区视频 | 暴力强奷在线播放无码 | 亚洲国产精品久久久久久 | 性欧美熟妇videofreesex | 精品熟女少妇av免费观看 | 99久久精品日本一区二区免费 | 成人免费无码大片a毛片 | 欧美精品无码一区二区三区 | 精品成人av一区二区三区 | 久久国产精品二国产精品 | 日韩av激情在线观看 | 亚洲人成人无码网www国产 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | av无码电影一区二区三区 | 国产人妻精品午夜福利免费 | 无码午夜成人1000部免费视频 | 久久久久久久人妻无码中文字幕爆 | 免费国产成人高清在线观看网站 | 麻豆果冻传媒2021精品传媒一区下载 | 国产欧美熟妇另类久久久 | 久久国语露脸国产精品电影 | 国产精品国产三级国产专播 | 亚洲日韩一区二区 | 三上悠亚人妻中文字幕在线 | 欧美激情综合亚洲一二区 | 午夜精品一区二区三区的区别 | 亚洲综合无码久久精品综合 | 久久久久成人片免费观看蜜芽 | 伊人久久大香线焦av综合影院 | 国产乡下妇女做爰 | 国产精品无码久久av | 亚洲欧洲日本综合aⅴ在线 | 日本熟妇人妻xxxxx人hd | 日韩人妻无码中文字幕视频 | 成人无码视频在线观看网站 | 午夜精品久久久久久久 | 婷婷综合久久中文字幕蜜桃三电影 | 99在线 | 亚洲 | 国产猛烈高潮尖叫视频免费 | 成人无码影片精品久久久 | 亚洲熟妇自偷自拍另类 | 97夜夜澡人人爽人人喊中国片 | 精品久久久无码人妻字幂 | 天天av天天av天天透 | 亚洲热妇无码av在线播放 | 亚洲一区二区三区播放 | 狠狠噜狠狠狠狠丁香五月 | 成人欧美一区二区三区黑人免费 | 色婷婷久久一区二区三区麻豆 | 青草视频在线播放 | 欧美性猛交内射兽交老熟妇 | 亚洲中文字幕成人无码 | 亚洲自偷自偷在线制服 | 99riav国产精品视频 | 国产亚洲欧美日韩亚洲中文色 | 久久无码中文字幕免费影院蜜桃 | 夜夜高潮次次欢爽av女 | 国产欧美亚洲精品a | 中文字幕乱码人妻无码久久 | 久久久中文久久久无码 | 婷婷五月综合激情中文字幕 | 欧美35页视频在线观看 | 欧美日韩一区二区免费视频 | 国产成人精品三级麻豆 | 国内精品久久久久久中文字幕 | 在线亚洲高清揄拍自拍一品区 | 无遮挡国产高潮视频免费观看 | 国产色在线 | 国产 | 亚洲综合色区中文字幕 | 日韩av无码中文无码电影 | 免费无码一区二区三区蜜桃大 | 久久久久国色av免费观看性色 | 十八禁视频网站在线观看 | 在线视频网站www色 | 亚洲国产成人a精品不卡在线 | 中文字幕无码日韩专区 | 久久综合给合久久狠狠狠97色 | 国产精品-区区久久久狼 | 亚洲综合色区中文字幕 | av人摸人人人澡人人超碰下载 | 国产在线aaa片一区二区99 | 国产成人亚洲综合无码 | 精品国精品国产自在久国产87 | 亚洲欧美日韩国产精品一区二区 | 亚洲伊人久久精品影院 | 小sao货水好多真紧h无码视频 | 国产精品亚洲综合色区韩国 | 亚洲七七久久桃花影院 | 亚洲精品中文字幕乱码 | 少妇无码一区二区二三区 | 国产亚洲欧美在线专区 | 亚洲日韩一区二区三区 | 国产精品va在线观看无码 | 欧美日韩一区二区免费视频 | 欧美老熟妇乱xxxxx | 精品久久综合1区2区3区激情 | 久久综合色之久久综合 | 一本久久伊人热热精品中文字幕 | 99久久久国产精品无码免费 | 国产极品美女高潮无套在线观看 | 国色天香社区在线视频 | 麻豆国产丝袜白领秘书在线观看 | 无遮挡啪啪摇乳动态图 | 色婷婷综合激情综在线播放 | 天堂亚洲2017在线观看 | 亚洲国产av精品一区二区蜜芽 | 76少妇精品导航 | 亚洲精品中文字幕 | 国产午夜无码精品免费看 | 97久久精品无码一区二区 | 国产真实乱对白精彩久久 | 成熟妇人a片免费看网站 | 欧美成人家庭影院 | 亚洲国产精品一区二区美利坚 | 日本在线高清不卡免费播放 | 精品国产一区二区三区av 性色 | 大地资源中文第3页 | 亚洲 另类 在线 欧美 制服 | 中文精品无码中文字幕无码专区 | 永久免费观看美女裸体的网站 | 欧美日本精品一区二区三区 | 亚洲精品一区二区三区四区五区 | 黑人粗大猛烈进出高潮视频 | 亚洲国产综合无码一区 | 亚洲综合无码一区二区三区 | 中文字幕日韩精品一区二区三区 | 女人色极品影院 | 欧美亚洲日韩国产人成在线播放 | 精品欧洲av无码一区二区三区 | 亚洲国产精品一区二区第一页 | 久久久精品成人免费观看 | 一本色道久久综合亚洲精品不卡 | 粗大的内捧猛烈进出视频 | 18无码粉嫩小泬无套在线观看 | 午夜丰满少妇性开放视频 | 日本肉体xxxx裸交 | 99久久婷婷国产综合精品青草免费 | 国产亚洲精品久久久ai换 | 免费无码av一区二区 | 亚洲成色在线综合网站 | 亚洲欧洲中文日韩av乱码 | 久热国产vs视频在线观看 | 国产后入清纯学生妹 | 午夜成人1000部免费视频 | 成熟妇人a片免费看网站 | 亚洲色欲久久久综合网东京热 | 人妻插b视频一区二区三区 | 亚洲精品一区国产 | 377p欧洲日本亚洲大胆 | 狠狠色欧美亚洲狠狠色www | 中国大陆精品视频xxxx | 亚洲色在线无码国产精品不卡 | 精品 日韩 国产 欧美 视频 | 麻豆av传媒蜜桃天美传媒 | 国产精品va在线观看无码 | 无码任你躁久久久久久久 | 日本乱人伦片中文三区 | 欧洲熟妇精品视频 | 日本在线高清不卡免费播放 | 7777奇米四色成人眼影 | 免费人成在线观看网站 | 乱人伦人妻中文字幕无码 | 乌克兰少妇xxxx做受 | 牛和人交xxxx欧美 | 天天摸天天碰天天添 | 国产口爆吞精在线视频 | 国产一区二区三区日韩精品 | 婷婷综合久久中文字幕蜜桃三电影 | 无码人妻精品一区二区三区下载 | 秋霞成人午夜鲁丝一区二区三区 | 久久99久久99精品中文字幕 | 一本加勒比波多野结衣 | 亚洲日韩av一区二区三区四区 | 日韩欧美中文字幕公布 | 国产成人午夜福利在线播放 | 伊人久久大香线焦av综合影院 | 18精品久久久无码午夜福利 | 偷窥日本少妇撒尿chinese | 4hu四虎永久在线观看 | 在线观看国产一区二区三区 | 乱人伦中文视频在线观看 | 国产精品久久久久久无码 | 少妇性l交大片欧洲热妇乱xxx | 中文字幕久久久久人妻 | 亚洲男人av天堂午夜在 | 国产精品美女久久久 | 色狠狠av一区二区三区 | 人妻与老人中文字幕 | 国产成人无码av在线影院 | 一区二区三区高清视频一 | av无码久久久久不卡免费网站 | 国产区女主播在线观看 | 婷婷丁香五月天综合东京热 | 亚洲国产精华液网站w | 精品无码国产一区二区三区av | 国产精品人妻一区二区三区四 | 中文字幕人妻丝袜二区 | 国产深夜福利视频在线 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 亚洲精品国偷拍自产在线麻豆 | 国产精品久久久久久久9999 | 精品久久久久久人妻无码中文字幕 | 老熟妇仑乱视频一区二区 | 欧美激情一区二区三区成人 | 人妻互换免费中文字幕 | 精品国产一区二区三区四区 | 亚洲国产精华液网站w | 国产绳艺sm调教室论坛 | 欧美日韩久久久精品a片 | 亚洲欧美日韩综合久久久 | 少妇被粗大的猛进出69影院 | 日韩少妇白浆无码系列 | 超碰97人人做人人爱少妇 | 亚洲精品国偷拍自产在线麻豆 | 国产熟妇高潮叫床视频播放 | 2020久久香蕉国产线看观看 | 国产三级久久久精品麻豆三级 | av小次郎收藏 | 亚洲精品久久久久久一区二区 | 99视频精品全部免费免费观看 | 午夜精品久久久内射近拍高清 | 久久久婷婷五月亚洲97号色 | 国产午夜无码精品免费看 | 粗大的内捧猛烈进出视频 | 色妞www精品免费视频 | 最新国产麻豆aⅴ精品无码 | 国产精品永久免费视频 | 欧美日韩一区二区综合 | 亚洲日韩av一区二区三区中文 | 久久精品国产99久久6动漫 | 欧美乱妇无乱码大黄a片 | 2019午夜福利不卡片在线 | 精品人人妻人人澡人人爽人人 | 亚洲精品国产品国语在线观看 | 国产极品美女高潮无套在线观看 | 国产精品毛片一区二区 | 欧洲精品码一区二区三区免费看 | 国产av久久久久精东av | 国产激情精品一区二区三区 | 国产精品成人av在线观看 | 亚洲一区二区观看播放 | 女人高潮内射99精品 | 亚洲人成影院在线无码按摩店 | 日本一区二区更新不卡 | 男女下面进入的视频免费午夜 | 亚洲国产日韩a在线播放 | 久久视频在线观看精品 | 1000部夫妻午夜免费 | 国产精品丝袜黑色高跟鞋 | av人摸人人人澡人人超碰下载 | 在线播放亚洲第一字幕 | 国产精品无码久久av | 特级做a爰片毛片免费69 | 在线а√天堂中文官网 | 成 人 网 站国产免费观看 | 日韩av无码中文无码电影 | 日韩人妻无码一区二区三区久久99 | 牲交欧美兽交欧美 | 国产精品永久免费视频 | 亚洲色大成网站www | 欧美freesex黑人又粗又大 | 国产97色在线 | 免 | 欧美老妇交乱视频在线观看 | 丰满护士巨好爽好大乳 | 国产三级久久久精品麻豆三级 | 波多野结衣aⅴ在线 | 亚无码乱人伦一区二区 | 久久久久se色偷偷亚洲精品av | 亚洲熟妇色xxxxx欧美老妇y | 久久99精品国产麻豆蜜芽 | 无套内谢的新婚少妇国语播放 | 成人精品一区二区三区中文字幕 | 国产亚洲精品久久久久久久久动漫 | 九九久久精品国产免费看小说 | 国产亚洲欧美日韩亚洲中文色 | 亚洲中文字幕无码中字 | 少妇性l交大片欧洲热妇乱xxx | 亚洲中文字幕在线无码一区二区 | 国产成人无码a区在线观看视频app | 国产精品办公室沙发 | 国产成人av免费观看 | 亚洲国产高清在线观看视频 | 亚洲成a人片在线观看无码3d | 鲁鲁鲁爽爽爽在线视频观看 | 精品国产av色一区二区深夜久久 | 亚洲无人区午夜福利码高清完整版 | 亚洲人成影院在线观看 | 国产综合久久久久鬼色 | 国内老熟妇对白xxxxhd | 丰满护士巨好爽好大乳 | 国产内射爽爽大片视频社区在线 | 久久97精品久久久久久久不卡 | 亚洲一区二区三区在线观看网站 | 日韩av无码一区二区三区 | 久久精品一区二区三区四区 | 亚洲aⅴ无码成人网站国产app | 欧美日韩亚洲国产精品 | 成人三级无码视频在线观看 | 亚洲精品一区二区三区四区五区 | 欧美激情内射喷水高潮 | 亚洲中文字幕无码中文字在线 | 强辱丰满人妻hd中文字幕 | 成人免费视频在线观看 | 午夜精品久久久久久久久 | 欧美性黑人极品hd | 久久精品成人欧美大片 | 少女韩国电视剧在线观看完整 | 日本丰满熟妇videos | 国产精品美女久久久网av | 日日麻批免费40分钟无码 | 久久国产36精品色熟妇 | 欧美xxxxx精品 | 色欲av亚洲一区无码少妇 | 国产精品久久久久久久影院 | 国产sm调教视频在线观看 | 久久人人爽人人爽人人片av高清 | 亚洲人成网站在线播放942 | 亚洲欧美色中文字幕在线 | 无码毛片视频一区二区本码 | 又大又硬又爽免费视频 | 国产深夜福利视频在线 | 丰满护士巨好爽好大乳 | 亚洲中文无码av永久不收费 | 国产在线一区二区三区四区五区 | 99riav国产精品视频 | 帮老师解开蕾丝奶罩吸乳网站 | 国产精品亚洲а∨无码播放麻豆 | 老司机亚洲精品影院无码 | 亚洲精品一区二区三区四区五区 | 人妻夜夜爽天天爽三区 | 亚洲自偷精品视频自拍 | 男人的天堂av网站 | 欧美日韩亚洲国产精品 | 亚洲国产精品久久久天堂 | 国产又爽又猛又粗的视频a片 | 国产成人亚洲综合无码 | 亚洲国产日韩a在线播放 | 国产午夜手机精彩视频 | 性欧美大战久久久久久久 | 中文无码伦av中文字幕 | 国产无遮挡吃胸膜奶免费看 | 欧洲精品码一区二区三区免费看 | 少妇太爽了在线观看 | 国产精品视频免费播放 | 日本欧美一区二区三区乱码 | 亚洲国产欧美在线成人 | 成熟妇人a片免费看网站 | 国产精品久久久久影院嫩草 | 300部国产真实乱 | 日产精品高潮呻吟av久久 | 久久无码中文字幕免费影院蜜桃 | 日本在线高清不卡免费播放 | 国产精品嫩草久久久久 | 欧美35页视频在线观看 | 国产色xx群视频射精 | 日韩精品a片一区二区三区妖精 | 欧美日本免费一区二区三区 | 国产av人人夜夜澡人人爽麻豆 | 成人欧美一区二区三区黑人 | 欧美成人免费全部网站 | 天干天干啦夜天干天2017 | 人人澡人人妻人人爽人人蜜桃 | 国产特级毛片aaaaaa高潮流水 | 无码人妻少妇伦在线电影 | 日韩少妇白浆无码系列 | yw尤物av无码国产在线观看 | 2019nv天堂香蕉在线观看 | 18黄暴禁片在线观看 | 精品 日韩 国产 欧美 视频 | 亚洲日韩一区二区三区 | 日本一卡二卡不卡视频查询 | 久久成人a毛片免费观看网站 | 7777奇米四色成人眼影 | 久久综合香蕉国产蜜臀av | 丰满人妻翻云覆雨呻吟视频 | v一区无码内射国产 | 人人妻人人澡人人爽欧美一区 | 98国产精品综合一区二区三区 | 图片区 小说区 区 亚洲五月 | 亚洲 日韩 欧美 成人 在线观看 | 国产真实乱对白精彩久久 | 国产综合久久久久鬼色 | 亚洲自偷自拍另类第1页 | 精品人妻av区 | 日本欧美一区二区三区乱码 | 国产深夜福利视频在线 | 成人无码精品1区2区3区免费看 | 老头边吃奶边弄进去呻吟 | 婷婷五月综合缴情在线视频 | 亚洲精品综合一区二区三区在线 | 性欧美熟妇videofreesex | 又色又爽又黄的美女裸体网站 | 国产成人午夜福利在线播放 | √天堂中文官网8在线 | 久久综合狠狠综合久久综合88 | 人妻尝试又大又粗久久 | 午夜福利一区二区三区在线观看 | 国产精品高潮呻吟av久久4虎 | 中文字幕人妻无码一夲道 | 亚洲乱码中文字幕在线 | 亚洲va中文字幕无码久久不卡 | 亚洲色在线无码国产精品不卡 | 蜜桃臀无码内射一区二区三区 | 在线播放免费人成毛片乱码 | 免费中文字幕日韩欧美 | 国产亚洲精品久久久久久国模美 | 精品国产青草久久久久福利 | 日本乱人伦片中文三区 | 中国大陆精品视频xxxx | 亚洲阿v天堂在线 | 国产9 9在线 | 中文 | 人人妻人人藻人人爽欧美一区 | 国产免费久久精品国产传媒 | 欧美成人免费全部网站 | 爱做久久久久久 | 日产精品99久久久久久 | 无码av免费一区二区三区试看 | 熟妇人妻激情偷爽文 | 久久人人爽人人爽人人片av高清 | 一本色道久久综合亚洲精品不卡 | 1000部啪啪未满十八勿入下载 | 亚洲一区二区三区在线观看网站 | 色 综合 欧美 亚洲 国产 | 桃花色综合影院 | 成 人 免费观看网站 | 亚洲日韩av片在线观看 | 天天做天天爱天天爽综合网 | 国产激情精品一区二区三区 | 久久午夜夜伦鲁鲁片无码免费 | 欧美丰满少妇xxxx性 | 日本精品人妻无码免费大全 | 日日摸天天摸爽爽狠狠97 | 亚洲人成无码网www | 亚洲欧美日韩成人高清在线一区 | 久久综合色之久久综合 | a在线观看免费网站大全 | 亚洲精品午夜国产va久久成人 | 亚洲精品鲁一鲁一区二区三区 | 粉嫩少妇内射浓精videos | 7777奇米四色成人眼影 | 在线看片无码永久免费视频 | 鲁鲁鲁爽爽爽在线视频观看 | 夫妻免费无码v看片 | 色欲av亚洲一区无码少妇 | 日日摸天天摸爽爽狠狠97 | 久久国产精品偷任你爽任你 | 香港三级日本三级妇三级 | 成人无码视频免费播放 | 少妇无码吹潮 | 午夜福利电影 | 初尝人妻少妇中文字幕 | 免费视频欧美无人区码 | 少妇太爽了在线观看 | 全黄性性激高免费视频 | 98国产精品综合一区二区三区 | 国产性生大片免费观看性 | 久久久久久国产精品无码下载 | 日韩欧美成人免费观看 | 日韩精品久久久肉伦网站 | 51国偷自产一区二区三区 | 一区二区三区高清视频一 | 亚洲色欲色欲天天天www | 国产精品第一区揄拍无码 | 国产sm调教视频在线观看 | 性生交片免费无码看人 | 日韩欧美中文字幕在线三区 | 偷窥日本少妇撒尿chinese | 九月婷婷人人澡人人添人人爽 | 精品久久久无码人妻字幂 | 成人毛片一区二区 | 色婷婷综合中文久久一本 | 成人精品天堂一区二区三区 | 人妻互换免费中文字幕 | 天下第一社区视频www日本 | 领导边摸边吃奶边做爽在线观看 | 午夜福利一区二区三区在线观看 | 国产精品久久久久9999小说 | 亚洲国产精品无码一区二区三区 | 两性色午夜视频免费播放 | 极品嫩模高潮叫床 | 日韩亚洲欧美中文高清在线 | 亚洲精品久久久久中文第一幕 | 免费无码的av片在线观看 | 欧美人与牲动交xxxx | 色情久久久av熟女人妻网站 | 九九久久精品国产免费看小说 | 十八禁视频网站在线观看 | 97久久精品无码一区二区 | 日本熟妇乱子伦xxxx | 熟女少妇人妻中文字幕 | 免费无码午夜福利片69 | 一区二区传媒有限公司 | 精品一区二区三区无码免费视频 | 国产成人无码a区在线观看视频app | 无人区乱码一区二区三区 | 亚洲天堂2017无码中文 | 国产香蕉尹人视频在线 | 久久久精品欧美一区二区免费 | 亚洲色成人中文字幕网站 | 国产成人无码午夜视频在线观看 | 久久精品丝袜高跟鞋 | 国产精品无码成人午夜电影 | 亚洲综合无码久久精品综合 | 亚洲小说春色综合另类 | 成在人线av无码免观看麻豆 | 中文字幕乱码中文乱码51精品 | 国产麻豆精品一区二区三区v视界 | 性生交片免费无码看人 | 中文字幕无码免费久久9一区9 | 少妇激情av一区二区 | 亚洲s色大片在线观看 | 丰满少妇女裸体bbw | 樱花草在线播放免费中文 | 精品久久综合1区2区3区激情 | 性欧美熟妇videofreesex | 狂野欧美性猛交免费视频 | 日产精品99久久久久久 | 国产精品美女久久久久av爽李琼 | 亚洲欧美综合区丁香五月小说 | 99riav国产精品视频 | 1000部夫妻午夜免费 | 日本护士xxxxhd少妇 | 国产亚洲精品精品国产亚洲综合 | 亚洲精品中文字幕 | 无套内谢的新婚少妇国语播放 | 在线а√天堂中文官网 | 国产精品无码成人午夜电影 | 中文字幕+乱码+中文字幕一区 | 亚洲中文字幕无码一久久区 | 丰满少妇熟乱xxxxx视频 | 国产口爆吞精在线视频 | 荫蒂被男人添的好舒服爽免费视频 | 又大又黄又粗又爽的免费视频 | 在教室伦流澡到高潮hnp视频 | 国産精品久久久久久久 | a在线亚洲男人的天堂 | 亚洲欧美国产精品久久 | 蜜臀av无码人妻精品 | 熟妇激情内射com | 人妻无码久久精品人妻 | 国产真人无遮挡作爱免费视频 | 强辱丰满人妻hd中文字幕 | 夜先锋av资源网站 | 亚洲欧美精品伊人久久 | 久久亚洲日韩精品一区二区三区 | 激情五月综合色婷婷一区二区 | aa片在线观看视频在线播放 | 亚洲精品成a人在线观看 | 日本一区二区三区免费高清 | 国产人妻久久精品二区三区老狼 | 曰本女人与公拘交酡免费视频 | 亚洲精品国产精品乱码不卡 | 国产va免费精品观看 | 樱花草在线社区www | 夜夜躁日日躁狠狠久久av | 麻豆国产丝袜白领秘书在线观看 | 国内少妇偷人精品视频 | 男女爱爱好爽视频免费看 | 久久久久久久久蜜桃 | 国产亚洲精品久久久久久久久动漫 | 国产综合久久久久鬼色 | 熟妇人妻中文av无码 | 国产另类ts人妖一区二区 | 999久久久国产精品消防器材 | 香港三级日本三级妇三级 | √天堂资源地址中文在线 | 日本熟妇浓毛 | 久久www免费人成人片 | 久久亚洲中文字幕精品一区 | 久久精品人妻少妇一区二区三区 | 国内综合精品午夜久久资源 | 性做久久久久久久久 | 国内揄拍国内精品人妻 | 亚洲人成影院在线观看 | 久久综合激激的五月天 | 国产极品美女高潮无套在线观看 | 国产免费久久精品国产传媒 | 天天综合网天天综合色 | а天堂中文在线官网 | 免费国产黄网站在线观看 | 久久精品国产亚洲精品 | 中国女人内谢69xxxxxa片 | 久久视频在线观看精品 | 精品国产国产综合精品 | 国产三级久久久精品麻豆三级 | 免费无码av一区二区 | а√天堂www在线天堂小说 | 久久国产自偷自偷免费一区调 | 欧美日韩视频无码一区二区三 | 老头边吃奶边弄进去呻吟 | 久久精品中文字幕大胸 | 亚洲s色大片在线观看 | www国产精品内射老师 | 在线成人www免费观看视频 | 少妇无码av无码专区在线观看 | 蜜桃视频韩日免费播放 | 青春草在线视频免费观看 | 国产成人久久精品流白浆 | 国产精品久久国产精品99 | 亚洲va欧美va天堂v国产综合 | 国产又爽又猛又粗的视频a片 | 鲁大师影院在线观看 | 亚洲人成影院在线无码按摩店 | 国产99久久精品一区二区 | 欧美日本免费一区二区三区 | 精品久久久无码中文字幕 | 国产激情精品一区二区三区 | 女人和拘做爰正片视频 | 亚洲日本va午夜在线电影 | 性做久久久久久久免费看 | 精品无人区无码乱码毛片国产 | 欧美色就是色 | 亚洲精品国偷拍自产在线观看蜜桃 | 日韩精品无码免费一区二区三区 | 国产成人无码午夜视频在线观看 | 成年女人永久免费看片 | 国产亚洲视频中文字幕97精品 | 成人免费视频在线观看 | 亚洲国产精华液网站w | 国产精品爱久久久久久久 | 牲交欧美兽交欧美 | 九九在线中文字幕无码 | 久久午夜无码鲁丝片午夜精品 | 国产精品久免费的黄网站 | 国产精品99爱免费视频 | 精品无码一区二区三区爱欲 | 小泽玛莉亚一区二区视频在线 | 亚洲色欲久久久综合网东京热 | 最新版天堂资源中文官网 | 丰满岳乱妇在线观看中字无码 | 中文字幕人妻无码一区二区三区 | 国产真人无遮挡作爱免费视频 | 国内揄拍国内精品少妇国语 | 四虎4hu永久免费 | 综合人妻久久一区二区精品 | 亚洲欧洲中文日韩av乱码 | 少妇激情av一区二区 | 毛片内射-百度 | 国产黑色丝袜在线播放 | 亚洲欧洲中文日韩av乱码 | 扒开双腿疯狂进出爽爽爽视频 | 国产极品视觉盛宴 | 男人的天堂av网站 | 天堂无码人妻精品一区二区三区 | 女人被爽到呻吟gif动态图视看 | 国产欧美亚洲精品a | 中国女人内谢69xxxxxa片 | 免费无码的av片在线观看 | 小鲜肉自慰网站xnxx | 国产在线精品一区二区三区直播 | 乱码午夜-极国产极内射 | 国产精品人人爽人人做我的可爱 | 国产舌乚八伦偷品w中 | 色婷婷综合中文久久一本 | 漂亮人妻洗澡被公强 日日躁 | 帮老师解开蕾丝奶罩吸乳网站 | 国语自产偷拍精品视频偷 | 又大又硬又黄的免费视频 | 久久国产精品精品国产色婷婷 | 亚洲精品久久久久久久久久久 | 人妻插b视频一区二区三区 | 东京无码熟妇人妻av在线网址 | 四十如虎的丰满熟妇啪啪 | 影音先锋中文字幕无码 | 高清国产亚洲精品自在久久 | 国内少妇偷人精品视频免费 | 国产免费久久精品国产传媒 | 疯狂三人交性欧美 | 人人超人人超碰超国产 | 装睡被陌生人摸出水好爽 | 强伦人妻一区二区三区视频18 | 99精品无人区乱码1区2区3区 | 亚洲爆乳精品无码一区二区三区 | 亚洲中文字幕在线观看 | 日本肉体xxxx裸交 | 美女张开腿让人桶 | 丰满少妇熟乱xxxxx视频 | 性欧美熟妇videofreesex | 色窝窝无码一区二区三区色欲 | 荫蒂被男人添的好舒服爽免费视频 | 成人欧美一区二区三区黑人免费 | 亚洲国产精品一区二区第一页 | 熟妇激情内射com | 欧美丰满老熟妇xxxxx性 | 国产精品久久福利网站 | 国产电影无码午夜在线播放 | 亚洲国产精品久久久天堂 | 国产麻豆精品一区二区三区v视界 | 欧美喷潮久久久xxxxx | 精品国偷自产在线视频 | 小sao货水好多真紧h无码视频 | 亚无码乱人伦一区二区 | 97久久超碰中文字幕 | 久久国产精品萌白酱免费 | 欧美丰满熟妇xxxx性ppx人交 | 欧美 丝袜 自拍 制服 另类 | 97无码免费人妻超级碰碰夜夜 | 少妇邻居内射在线 | 久久国语露脸国产精品电影 | 国产又粗又硬又大爽黄老大爷视 | 水蜜桃亚洲一二三四在线 | 六十路熟妇乱子伦 | 国产亚洲精品久久久久久国模美 | 亚洲精品午夜无码电影网 | 亚洲国产精品久久人人爱 | 色狠狠av一区二区三区 | 一本久道久久综合狠狠爱 | 国产乡下妇女做爰 | 久久无码中文字幕免费影院蜜桃 | 国产人妻精品午夜福利免费 | 精品无人区无码乱码毛片国产 | 呦交小u女精品视频 | 国产又爽又猛又粗的视频a片 | 亚洲国产精品成人久久蜜臀 | 一本久道高清无码视频 | 精品国产麻豆免费人成网站 | a国产一区二区免费入口 | 亚洲自偷精品视频自拍 | 国产精品99久久精品爆乳 | 色欲人妻aaaaaaa无码 | 伊人色综合久久天天小片 | 欧美放荡的少妇 | 久久精品国产一区二区三区肥胖 | 狠狠噜狠狠狠狠丁香五月 | 欧美xxxx黑人又粗又长 | 国产卡一卡二卡三 | 精品一区二区三区无码免费视频 | 亚洲熟悉妇女xxx妇女av | 日本一卡二卡不卡视频查询 | 一区二区传媒有限公司 | 波多野结衣av一区二区全免费观看 | 日日夜夜撸啊撸 | 亚洲综合久久一区二区 | 精品国偷自产在线 | 亚洲呦女专区 | 国产特级毛片aaaaaaa高清 | www国产亚洲精品久久久日本 | 亚洲成色www久久网站 | 成人一在线视频日韩国产 | 强奷人妻日本中文字幕 | 人妻少妇精品无码专区动漫 | 国产精品va在线播放 | 夜夜躁日日躁狠狠久久av | 国产suv精品一区二区五 | 无遮无挡爽爽免费视频 | 婷婷丁香五月天综合东京热 | 国产激情综合五月久久 | 国产美女极度色诱视频www | 欧美一区二区三区 | 性生交片免费无码看人 | 人妻互换免费中文字幕 | 色综合久久中文娱乐网 | 亚洲欧美色中文字幕在线 | 久久精品无码一区二区三区 | 亚洲精品无码国产 | 日本大乳高潮视频在线观看 | 色妞www精品免费视频 | 丰满少妇高潮惨叫视频 | 亚洲成a人片在线观看无码3d | 日本va欧美va欧美va精品 | 无码精品人妻一区二区三区av | 激情爆乳一区二区三区 | 露脸叫床粗话东北少妇 | 西西人体www44rt大胆高清 | 国产精品福利视频导航 | 国产肉丝袜在线观看 | 国产精品无码久久av | 97久久超碰中文字幕 | 国产综合色产在线精品 | 18无码粉嫩小泬无套在线观看 | 久久精品人人做人人综合 | 国产一区二区三区日韩精品 | 欧美丰满老熟妇xxxxx性 | 国产熟女一区二区三区四区五区 | 日本护士毛茸茸高潮 | 18黄暴禁片在线观看 | 午夜无码人妻av大片色欲 | 狠狠色噜噜狠狠狠狠7777米奇 | 美女张开腿让人桶 | 中文字幕乱码亚洲无线三区 | 无码任你躁久久久久久久 | 国产成人无码区免费内射一片色欲 | 麻花豆传媒剧国产免费mv在线 | 亚洲欧美综合区丁香五月小说 | 人人妻人人澡人人爽人人精品浪潮 | 久久综合给合久久狠狠狠97色 | 精品国产福利一区二区 | 成年美女黄网站色大免费全看 | 亚洲欧美精品伊人久久 | 国产精品.xx视频.xxtv | 又粗又大又硬毛片免费看 | 欧美丰满熟妇xxxx | 精品日本一区二区三区在线观看 | 欧美精品一区二区精品久久 | 国产内射老熟女aaaa | 九九久久精品国产免费看小说 | 欧美三级a做爰在线观看 | 欧美性猛交内射兽交老熟妇 | 日本免费一区二区三区最新 | 又大又黄又粗又爽的免费视频 | 成人免费无码大片a毛片 | 少妇一晚三次一区二区三区 | 国产人妖乱国产精品人妖 | 少妇激情av一区二区 | 亚洲人成无码网www | 日日摸夜夜摸狠狠摸婷婷 | 中文字幕无码视频专区 | 中文字幕日产无线码一区 | 欧美精品在线观看 | 亚洲日韩av一区二区三区四区 | 亚洲天堂2017无码中文 | 麻豆精产国品 | 熟女少妇在线视频播放 | 自拍偷自拍亚洲精品被多人伦好爽 | 欧美喷潮久久久xxxxx | 日韩精品a片一区二区三区妖精 | 色诱久久久久综合网ywww | 人妻与老人中文字幕 | 久久久久av无码免费网 | 国产高潮视频在线观看 | 亚洲精品一区国产 | 久久久国产一区二区三区 | 中文字幕亚洲情99在线 | 天天拍夜夜添久久精品大 | 国产亚洲精品久久久久久久 | 在线天堂新版最新版在线8 | 日本熟妇乱子伦xxxx | 精品无码成人片一区二区98 | 99久久婷婷国产综合精品青草免费 | 色欲av亚洲一区无码少妇 | 日日橹狠狠爱欧美视频 | 国产精品亚洲lv粉色 | 免费国产成人高清在线观看网站 | 色偷偷av老熟女 久久精品人妻少妇一区二区三区 | 偷窥日本少妇撒尿chinese | 国产农村乱对白刺激视频 | 亚洲国产精品成人久久蜜臀 | 亚洲热妇无码av在线播放 | 无码人妻出轨黑人中文字幕 | 亚洲人成无码网www | 精品 日韩 国产 欧美 视频 | 久精品国产欧美亚洲色aⅴ大片 | 蜜臀av无码人妻精品 | 国产人妻精品午夜福利免费 | 亚洲天堂2017无码 | 午夜成人1000部免费视频 | 色婷婷欧美在线播放内射 | 人妻少妇精品无码专区动漫 | 又大又黄又粗又爽的免费视频 | 76少妇精品导航 | 久久精品一区二区三区四区 | 无遮挡国产高潮视频免费观看 | 色一情一乱一伦 | 超碰97人人做人人爱少妇 | 久久久久久国产精品无码下载 | 精品日本一区二区三区在线观看 | 日本www一道久久久免费榴莲 | 日韩av无码一区二区三区不卡 | 国产av一区二区精品久久凹凸 | 麻花豆传媒剧国产免费mv在线 | 中文字幕无码免费久久9一区9 | 18禁黄网站男男禁片免费观看 | 午夜男女很黄的视频 | 又色又爽又黄的美女裸体网站 | 丰满少妇女裸体bbw | 亚洲天堂2017无码 | 久久久久se色偷偷亚洲精品av | 日本一区二区三区免费播放 | 18禁黄网站男男禁片免费观看 | 成人三级无码视频在线观看 | 美女扒开屁股让男人桶 | 欧美真人作爱免费视频 | 国产精品99久久精品爆乳 | 久久熟妇人妻午夜寂寞影院 | 午夜成人1000部免费视频 | 18禁黄网站男男禁片免费观看 | 久在线观看福利视频 | 少妇久久久久久人妻无码 | 又大又紧又粉嫩18p少妇 | 中文字幕无码av波多野吉衣 | 性生交大片免费看l | 97精品人妻一区二区三区香蕉 | 亚洲伊人久久精品影院 | 久久综合给合久久狠狠狠97色 | 麻豆国产人妻欲求不满 | 国产又爽又猛又粗的视频a片 | 免费播放一区二区三区 | 亚洲精品成a人在线观看 | 国产精品久久久av久久久 | 免费中文字幕日韩欧美 | 色婷婷久久一区二区三区麻豆 | 亲嘴扒胸摸屁股激烈网站 | 国产sm调教视频在线观看 | 亚洲国产精品一区二区第一页 | 日本xxxx色视频在线观看免费 | 国产一区二区三区日韩精品 | 国产成人精品视频ⅴa片软件竹菊 | 日日躁夜夜躁狠狠躁 | 欧美日韩在线亚洲综合国产人 | 麻豆精品国产精华精华液好用吗 | 国产人妖乱国产精品人妖 | 亚洲乱亚洲乱妇50p | 国产成人午夜福利在线播放 | 激情内射亚州一区二区三区爱妻 | 亚洲人成人无码网www国产 | 亚洲中文无码av永久不收费 | 天天躁夜夜躁狠狠是什么心态 | 亚洲熟悉妇女xxx妇女av | 97精品国产97久久久久久免费 | 久久天天躁夜夜躁狠狠 | 国产激情艳情在线看视频 | 亚洲一区二区三区在线观看网站 | 国产精品亚洲五月天高清 | 欧美熟妇另类久久久久久多毛 | 少妇性俱乐部纵欲狂欢电影 | 国产激情一区二区三区 | 任你躁国产自任一区二区三区 | 无码吃奶揉捏奶头高潮视频 | 2020久久超碰国产精品最新 | 无码国产激情在线观看 | av无码久久久久不卡免费网站 | 一本久道高清无码视频 | 日本乱人伦片中文三区 | 特级做a爰片毛片免费69 | 国产69精品久久久久app下载 | 日日躁夜夜躁狠狠躁 | 国产精品久久久一区二区三区 | 天下第一社区视频www日本 | 免费国产成人高清在线观看网站 | 国产69精品久久久久app下载 | 久久人人爽人人爽人人片av高清 | 亚洲一区二区三区在线观看网站 | 亚洲精品久久久久中文第一幕 | 欧洲精品码一区二区三区免费看 | 精品国产青草久久久久福利 | 国产精品久久国产精品99 | 国产在线精品一区二区高清不卡 | 影音先锋中文字幕无码 | 激情五月综合色婷婷一区二区 | 亚洲精品国产精品乱码视色 | 成熟妇人a片免费看网站 | 97资源共享在线视频 | 亚洲毛片av日韩av无码 | www国产亚洲精品久久久日本 | 色婷婷香蕉在线一区二区 | 国产成人久久精品流白浆 | 亚洲精品国产a久久久久久 | 亚洲国产精品无码一区二区三区 | 激情内射日本一区二区三区 | 极品嫩模高潮叫床 | 日日噜噜噜噜夜夜爽亚洲精品 | 国产精品美女久久久久av爽李琼 | 少妇愉情理伦片bd | 成 人 网 站国产免费观看 | 性色av无码免费一区二区三区 | 国产精品无套呻吟在线 | 久青草影院在线观看国产 | 国产色精品久久人妻 | 日韩欧美群交p片內射中文 | 免费播放一区二区三区 | 人人爽人人澡人人人妻 | 黑人巨大精品欧美一区二区 | 综合网日日天干夜夜久久 | 青春草在线视频免费观看 | 欧美 日韩 人妻 高清 中文 | 欧美午夜特黄aaaaaa片 | 少妇性荡欲午夜性开放视频剧场 | 国产精品久久久久久久影院 | 小sao货水好多真紧h无码视频 | 少妇的肉体aa片免费 | 亚洲日本在线电影 | 精品国产精品久久一区免费式 | 精品久久久久香蕉网 | 亚洲国产精品久久人人爱 | 伊人久久大香线蕉av一区二区 | 又粗又大又硬又长又爽 | 国产极品视觉盛宴 | 最新版天堂资源中文官网 | 最新国产乱人伦偷精品免费网站 | 国产免费观看黄av片 | 国产真人无遮挡作爱免费视频 | 两性色午夜免费视频 | 狠狠噜狠狠狠狠丁香五月 | 岛国片人妻三上悠亚 | 国产精品va在线观看无码 | 亚洲国产午夜精品理论片 | 国产成人无码专区 | 欧美日韩人成综合在线播放 | 精品偷拍一区二区三区在线看 | 伊人色综合久久天天小片 | 国产无遮挡吃胸膜奶免费看 | 亚洲欧美中文字幕5发布 | 亚洲天堂2017无码 | 亚洲性无码av中文字幕 | 中文字幕中文有码在线 | 亚洲熟妇色xxxxx亚洲 | 牲欲强的熟妇农村老妇女视频 | 强开小婷嫩苞又嫩又紧视频 | ass日本丰满熟妇pics | 亚洲国产精品无码一区二区三区 | 狠狠cao日日穞夜夜穞av | 国精产品一品二品国精品69xx | 丰满人妻精品国产99aⅴ | 人人妻在人人 | 精品久久久无码中文字幕 | 2020久久超碰国产精品最新 | 精品乱子伦一区二区三区 | 午夜成人1000部免费视频 | 狠狠色丁香久久婷婷综合五月 | 成人亚洲精品久久久久 | 亚洲综合伊人久久大杳蕉 | 岛国片人妻三上悠亚 | 欧洲美熟女乱又伦 | 亚洲成av人片在线观看无码不卡 | 国产精品鲁鲁鲁 | 久久精品人人做人人综合 | 兔费看少妇性l交大片免费 | 成人精品天堂一区二区三区 | 中文字幕人成乱码熟女app | 精品无码一区二区三区爱欲 | 亚洲精品国产第一综合99久久 | 67194成是人免费无码 | 精品国偷自产在线视频 | 少妇被粗大的猛进出69影院 | 国产尤物精品视频 | 亚洲国产精品久久久久久 | 一本无码人妻在中文字幕免费 | 国产内射爽爽大片视频社区在线 | 好爽又高潮了毛片免费下载 | 综合人妻久久一区二区精品 | 精品人人妻人人澡人人爽人人 | 国产欧美精品一区二区三区 | 少妇一晚三次一区二区三区 | 小鲜肉自慰网站xnxx | 欧美精品国产综合久久 | 国产成人精品必看 | 免费乱码人妻系列无码专区 | 国产亚洲精品久久久久久 | 亚洲阿v天堂在线 | 色综合久久久无码中文字幕 | 欧美国产日韩亚洲中文 | 色婷婷欧美在线播放内射 | 日本精品人妻无码77777 天堂一区人妻无码 | 日韩在线不卡免费视频一区 | 午夜精品久久久久久久久 | 夜夜夜高潮夜夜爽夜夜爰爰 | 精品久久综合1区2区3区激情 | 99久久久无码国产aaa精品 | 国产精品a成v人在线播放 | 最近的中文字幕在线看视频 | 久久亚洲中文字幕精品一区 | 兔费看少妇性l交大片免费 | 色欲av亚洲一区无码少妇 | 成年美女黄网站色大免费全看 | 老头边吃奶边弄进去呻吟 | 水蜜桃亚洲一二三四在线 | 少妇无码一区二区二三区 | 成年美女黄网站色大免费视频 | 人妻天天爽夜夜爽一区二区 | 人妻少妇精品视频专区 | 内射白嫩少妇超碰 | 嫩b人妻精品一区二区三区 | 真人与拘做受免费视频一 | 青青青爽视频在线观看 | 5858s亚洲色大成网站www | 妺妺窝人体色www在线小说 |