Qt实现3D纹理渲染自由旋转空间立方体
??????? 核心思想是用到Qt OpenGL模塊,將二維圖片貼到立方體的六個面,鼠標可以自由旋轉立方體,實現三維星空的動態變換,真正做出來后,感覺效果還挺好的,三維立體星空看起來還是很絢麗的,呵呵
?????? 下面直接從代碼層面分析上述實例,我用的ubuntu-12.04? Qt-4.8.1
??????? GLFrameWork.pro
[html] view plaincopy?????? main.cpp
[html] view plaincopy????? mainwindow.h
[cpp] view plaincopy?????? 擴展介紹:信號和槽機制是Qt的核心機制,信號和槽是一種高級接口,應用于對象之間的通信,它是Qt的核心特征,也是Qt區別與其它工具包的重要地方,信號和槽是Qt自行定義的一種通信機制,它獨立于標準C/C++語言,因此要正確處理信號和槽,必須借助一個成為moc(Meta Object Compiler)的Qt工具,該工具是一個C++預處理程序,它為高層次的事件處理自動生成所需要的附加代碼,在我們熟知的很多GUI工具中窗口小部件(widget) 都有一個回調函數用于響應他們能觸發的每個動作,這個回調函數通常是一個指向某個函數的指針,但是在Qt中信號和槽取代了這種l凌亂的函數指針,它使得我們編寫這些通信程序更為簡潔命了,信號和槽能攜帶任意數量和任意類型的參數,他們是類型完全安全的,不會像回調函數那樣產生core dunps。所有從QObject 或其子類(例如QWidget)派生的類都能購包含信號和槽,當對象改變其狀態時,信號就由該對象發射(emit)出去,這就是對象所要做的全部事情,他不知道另一端是誰在接收這個信號,這就是真正的信息封裝,它確保對象被當作一個真正的軟件組件來使用,槽用于接收信號,但他們是普通的對象成員函數,一個槽并不知道是否有任何信號與自己相鏈接,而且,對象并不了解具體的通信機制。你可以將很多信號與單個槽進行連接,也可將單個信號與很多槽進行連接,甚至將一個信號與另外一個信號連接也是可能的,這時無論第一個信號什么時候發射,系統都會立刻發射第二個信號,總之信號與槽構造類一個強大的部件編程機制。
??????? 信號:當某個信號對其客戶或者所有者發生的內部狀態發生改變,信號被一個對象發射,只有定義過這個信號的類以及其派生類能夠發射這個信號,當一個信號被發射時,與其相關聯的槽會被立刻執行,就像一個正常的函數調用一樣,信號-槽機制完全獨立于任何GUI事件循環,只有當所有的槽返回以后發射函數(emit)才返回,如果存在多個槽與某個信號相關聯,那么當這個信號被發射時,這些槽會一個接一個地執行,但是它們執行順序是隨機的、不確定的,我們不能人為的指定那個先執行、哪個后執行。信號的聲明在頭文件中進行的,QT的signals關鍵字指出進入類信號聲明區,隨后即可聲明自己的信號。
???????? 槽:槽是普通的C++成員函數,可以被正常調用,他們唯一的特殊性就是很多信號可以與其關聯,當與其關聯信號被發射時,這個槽就會被調用。槽可以有參數,但槽的參數不能有缺省值。既然槽是普通成員函數,因此與其他函數一樣,他們也有存取權限,槽的存取權限決定類誰能與其相關聯,同普通的C++成員函數一樣,槽函數也分為三種類型,public slots, private slots, protected slots。public slots:在這個區內聲明的槽意味著任何對象都可將信號與之相連,這對于組件編程非常有用,你可以創建彼此互補了解的對象,將它們的信號與槽進行鏈接以便信息能夠正確的傳遞。protected slots:在這個區內聲明的槽意味著當前類以及其子類可以將信號與之相鏈接,這適用于那些槽,他們是類實現的一部分,但其界面接口卻面向外部。private slots:在這個區內聲明的槽意味著只有類字節可以將信號與之相連接,這適用于聯系非常緊密的類。槽也能夠聲明為虛函數,這也是非常有用的,槽的聲明也是在頭文件中進行的。
???????? 信號與槽的關聯:通過調用QObject對象的connect函數來將某個對象的信號與另外一個對象的槽函數相關聯,這樣當發射者發射信號時,接收者的槽函數將被調用,該函數定義如下:bool QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)[static]這個函數作用就是將發射者sender對象中的信號signal與接收者receiver中的member槽函數聯系起來,當指定信號signal時必須使用QT的宏SIGNAL(),當指定槽函數時必須使用宏SLOT()。如果發射者與接收者屬于同一個對象的話,那么在connect調用中接收者參數可以省略。當信號與槽沒必要繼續保持關聯時,使用disconnect函數來斷開鏈接,其定義如下: bool QObject::disconnect(const QObject *sender, const char *signal, const Object *receiver, const char *member)[static]這個函數可以斷開發射者中的信號與接收者中槽函數之間的關聯。在disconnect函數中0可以用作一個通配符,分別表示任何信號、任何接收對象、接收對象中的任何槽函數。但是發射者sender不能為0,其他三個參數值可以為0.
????????? 元對象編譯器moc(mete object compiler)對C++文件中的類聲明進行分析并產生用于初始化元對象的C++代碼,元對象包含全部信號和槽的名字以及指向這些函數的指針,moc讀C++源文件,如果發現有Q_OBJECT宏聲明類,它會生成另外一個C++源文件,這個新生成的文件中包含該類的元對象代碼,例如,假如我們有一個頭文件mysignal.h,在這個文件中包含有信號或者槽的聲明,那么在編譯之前moc 工具就會根據該文件自動生成一個mysignal.moc.h的C++源文件并將其提交給編譯器,類似地,對應與mysignal.cpp文件moc工具自動生辰mysignal.moc.cpp文件提交給編譯器,元對象代碼是signal/slot機制所必須的,用moc 產生C++源文件必須與類實現一起進行編譯和連接,或者用#include語句將其包含到類的源文件中,moc并不擴展#include或者#define宏定義,它只是簡單的跳過所遇到的任何預處理指令。
???????? 本實例中,信號xRotationChanged(int angle),即就是當angle變化的時候,則信號開始發射給對應的槽,MainWindow類中的受保護成員函數mousePressEvent(QMouseEvent *event)用于處理鼠標按下時的事件響應,mouseMoveEvent(QMouseEvent *event)用于處理鼠標移動時的事件相應,私有成員函數以及參數不能被外部調用,只能內部使用,包括函數normalizeAngle(int *angle)主要用于標準調整鼠標旋轉角度,neheWidget, lastPos, xRot, yRot, zRot都是私有參數。
???????? mainwindow.cpp主要對應于mainwindow.h中的定義編寫實現具體的函數實體,按動鼠標左鍵可以拖動立方體進行空間自由旋轉,按動鼠標右鍵自動退出。
[html] view plaincopy???? 該頭文件主要用來定義如何調用OpenGL模塊實現三維立體渲染。
???? 對具體定義分別介紹:
???? #include<QGLWidget>,其中QGLWidget類用來繪制OpenGL圖形的窗口,QGLWidget提供一系列的函數來在一個QT應用程序里面繪制OpenGL,用起來很簡單,我們可以派生它,然后使用像其他任何窗口一樣使用子類,除非你選擇類使用QPainter和標準的OpenGL繪圖命令,QGLWidget提供三個方便的虛函數,我們可以在子類中重寫他們,來完成一些典型OpenGL任務:1. paintGL()函數,繪制OpenGL圖像,當窗口需要被刷新時候被調用;2.resizeGL()函數,建立OpenGL的視圖窗口等一系列,當窗口大小改變時候被調用,(當第一次顯示時候也會被調用,因為所有新創建的窗口都自動得到一個改變的大小事件);3.intializeGL()建立OpenGL繪圖的上下文環境,聲明播放列表等等,在第一次調用resizeGL()或paintGL()調用前使用。
????? #include<Qtgui>,因為要包含兩個類的定義,所以使用該聲明,NeHeWidget類繼承于QGLWidget類。
????? Q_OBJECT宏作用,只有加入此宏定義,你才能使用QT中的signal和slot機制。
????? NeHeWidget類的公共成員函數:explicit NeHeWidget(QWidget *parent=0),explicit用于構造函數,用來抑制隱式轉換。擴展:widget被創建時都是不可見的,widget中可容納其它widget,Qt中的widget在用戶行為或者狀態改變時會emit signal, QWidget類的構造函數需要一個QWidget*指針作為參數,表示其parent widget(默認值為0,即不存在parent widget ),在parent widget被刪除時,Qt會自動刪除其所有的child widget,Qt中有三種Layout Manager類:QHBoxLayout, QVBoxLayOut, QGridLayOut,基本模式是將widget添加進LayOut,由Layout自動接管widget的尺寸和位置。
??????? nehewidget.cpp
[html] view plaincopy??????????? glShadeModel函數,用于控制OpenGL中繪制指定兩點間其他點顏色的過渡模式,參數一般為GL_SMOOTH(默認),GL_FLAT,OpenGL默認是將制定的兩點顏色進行插值,繪制之間的其他點,如果兩點顏色相同,使用兩個參數效果相同,如果兩點顏色不同,GL_SMOOTH會出現過渡效果,GL_FLAT則只是以指定的某一點的單一色繪制其他的所有點;glClearColor函數來自OPENGL,為顏色緩沖區指定確定的值,指定red,green,blue,alpha(透明)的值,當顏色緩沖區清空時使用,默認值都是0,其取值范圍在0~1之間;glClearDepth函數,設置深度緩存的清除值,depth--指定清除深度緩存時使用的深度值,該值在[0,1]之間,如果設定為0.5,那么物體只有像素深度小于0.5的那部分才可見;glDepthFunc(GLenum func)函數,func:指定“目標像素與當前像素在z方向值大小比較”的函數,符合此函數關系的目標像素才進行繪制,否則目標像素不予繪制,該函數只有啟用“深度測試時”glEnable(GL_DEPTH_TEST)和glDisable(GL_DEPTH_TEST)時才有效,參數:GL_LEQUAL如果目標像素z值<=當前像素z值,則繪制目標像素;函數glHint(GLenum target,GLenum mod),該函數控制OpenGL在某一方面有解釋的余地時,所采取的操作行為,target:指定所控制行為的符號常量,GL_PERSPECTIVE_CORRECTION_HINT指定顏色和紋理坐標的差值質量,如果OpenGL不能有效的支持透視修正參數差值,那么GL_DONT_CARE和CL_FASTEST可以執行顏色、紋理坐標的簡單線性差值計算,mode:指定所采取行為的符號常量,GL_NICEST:選擇最高質量選項。
????????? 紋理裝載函數:LoadGLTextures(),QPixmap和QImge的區別:QPixmap依賴于硬件,QImage不依賴于硬件,QPixmap主要用于繪圖,針對屏幕顯示最佳化而設計,QImage主要是為圖像I/O、圖片訪問和像素修改而設計的,當圖片小的情況下直接用QPixmap進行加載,當圖片大的時候如果直接用QPixmap進行加載,會占很大的內存,一般一張幾十k的圖片,用QPixmap加載進來會放大很多倍,所以一般圖片大的情況下,用QImage進行加載,然后轉乘QPixmap用戶繪制,QPixmap繪制效果是最好的;函數 void glGenTextures(GLsizei n, GLuint *textures)參數n用來生成紋理的數量,textures存儲紋理索引的,glGenTextures函數根據紋理參數返回n個紋理索引,紋理名稱集合不必是一個連續的整數集合,glGneTextures就是用來產生你要操作的紋理對象的索引的,比如你告訴OpenGL,需要5個紋理對象,它會從沒有用到的整數里返回5個給你;函數void glBindTexture(GLenum targt, GLuint texture)參數target紋理被綁定的目標,它只能取值GL_TEXTURE_1D 或者GL_TEXTURE_2D,texture紋理名稱,并且該紋理名稱在當前的應用中不能被再次使用,該函數實際上改變了OpenGL的這個狀態,告訴OpenGL下面對紋理的任何操作都是對它所綁定的紋理對象的,比如glBindTexture(GL_TEXTURE_2D,1)告訴OpenGL下面代碼中對2D紋理的任何設置都是針對索引為1紋理的;函數void glTexImage2D(GLenum target, GLint level, GLint components, GLsizei wifth, glsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels),該函數用來創建一個紋理,本例中GL_TEXTURE_2D告訴OpenGL此紋理是一個2D紋理,數字零代表圖像的詳細程度,通常為0,數字3是數據的成分數,因為圖像由紅綠藍三色組成,tex.width()是紋理的寬度,tex.height()紋理的高度,數字0是邊框值一般為0,GL_RGBA告訴OpenGL圖像由宏綠藍以及alpha通道組成,這是由于QGLWidget類的converToGLFormat()函數原因,GL_UNSIGNES_BYTE表示組成圖像數據是無符號字節類型,最后tex.bits()告訴OpenGL紋理數據來源;glTexParameteri()告訴OpenGL在顯示圖像時,當它比原始紋理放的大(GL_TEXTURE_MAG_FILTER)或比原始紋理縮的小(GL_TEXTURE_MIN_FILTER)時OpenGL采用的濾波方式,通常這兩種情況下都采用GL_LINEAR,這使得紋理從很遠處到離屏幕很近時都能平滑顯示,使用GL_LINEAR需要CPU和顯卡做更多運算,如果機器很慢,應該采用GL_NEAREST,過濾的紋理在放大時候,看起來是斑駁的,因此可以結合這兩種濾波方式,在近處時使用GL_LINEAR,遠處時用GL_NEAREST。
???????? OpenGL坐標系,OpenGL使用右手坐標系,從左到右,x遞增,從下到上,y遞增,從遠到近,z遞增,OpenGL坐標系可分為:世界坐標系和當前繪圖坐標系,世界坐標系以屏幕原點(0,0,0),長度單位定為:窗口范圍按此單位恰好是(-1,-1)到(1,1),當前繪圖坐標系是繪制物體時坐標系,程序初始化時,世界坐標系和當前繪圖坐標系是重合的,當用glTranslatef(),glScalef(),glRotatef()對當前繪圖坐標系進行平移、伸縮、旋轉變換后,世界坐標系和當前繪圖坐標系不再重合,改變以后,再用glVertex3f()等繪圖函數繪圖時,都是在當前繪圖坐標系進行繪圖,所有的函數參數也都是相對當前繪圖坐標系來講的,OpenGL紋理使用分三步:將紋理裝入內存,將紋理發給OpenGL管道,給生成的紋理頂點指定紋理坐標,在paintGL()中定義映射目標物體的頂點時候,我們只需要用glTexCoord2f()將紋理綁定到相應的目標頂點就可以了。
???????? 假設紋理坐標如圖:
???????? 要將其映射到下圖正方形形狀的物體上(地面),那么就需要按照紋理坐標,為正方形每個頂點指定坐標,也稱為UV坐標,橫向為s軸,縱向為t軸,將紋理與映射目標綁定。
?????????? glClear()函數作用是用當前緩沖區清除值,也就是glClearColor或者glClearDepth、glClearIndex、glClearStencil、glClearAccum等函數所指定的值來清除指定的緩沖區,也可以用glDrawBuffer一次清除多個顏色緩存,比如:glClear(GL_COLOR_BUFFER_BIT)表示把整個窗口清除為黑色,glClear()的唯一參數表示需要被清除的緩沖區,像素檢驗、裁剪檢驗、抖動和緩存的寫屏蔽都會影響glClear的操作,其中,裁剪范圍限制了清除的區域,而glClear命令還會忽略alpha函數、融合函數、邏輯操作、模板、紋理映射和Z緩存;glLoadIdentity()這個函數類似于一個復位操作:X坐標、Y坐標、Z坐標均復位,OpenGL屏幕中心位于原點,在適當的位置使用該函數可以復位坐標,否則下一步的坐標操作就基于上一步的坐標了;glTranslatef(x,y,z)移動時候并不是相對屏幕中心移動,而是相對于當前所在屏幕的位置,其作用就是將你匯點坐標的原點在當前原點的基礎上平移一個(x,y,z)向量;旋轉所用的函數為glRotatef(Angle, Xvector, Yvector, Zvector),它負責讓對象繞某個軸旋轉,這個函數有很多用處,Angle通常是個變量代表對象轉過的角度,Xvector, Yvector, Zvector三個參數共同決定旋轉軸的方向,(1,0,0)描述矢量經過X坐標軸的1個單位處并且方向向右,關于旋轉方向確定符合右手定則,大拇指為旋轉矢量方向;glBegin 和 glEnd為一對,標志著一組OpenGL操作的開始和結束,并且在參數中告訴了OpenGL下面的操作是針對什么圖形進行的,GL_QUADS表示四邊形;glVertex3f()確定了矩形的頂點坐標。
????????? 重置OpenGL窗口大小函數:resizeGL():其中函數gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)這個函數定義類觀察的視景體在世界坐標系中的具體大小,一般aspect應該與窗口的寬高比相同,fovy視野角度,跟照相機原理相似,數值越小相當于將鏡頭拉的越近,數值越大,鏡頭越遠,鏡頭的東西就越小,aspect實際窗口的寬高比x/y,zNear表示近處的裁面,zFar表示遠處的裁面;glViewport函數主要負責把視景體截取的圖像按照怎樣的高和寬顯示到屏幕上,該函數還可以調整圖像分辨率;glMatrixMode()函數其實就是對接下來做什么進行一下聲明,參數有3種模式GL_PROJECTION投影,GL_MODELVIEW模型視圖,GL_TEXTURE紋理,如果參數是GL_PROJECTION,這個就是投影的意思,就是要對投影進行相關的操作,也就是把物體投影到一個平面上,就像我們照相一樣,把3維物體投影到2維平面上,這樣接下來的語句跟透視相關的函數,如glFrustum()或者gluPerspective(),在操作投影矩陣以前,需要調用函數glMatrixMode(GL_PROJECTION)將當前矩陣指定為投影矩陣,然后把矩陣設為單位矩陣glLoadIdentity(),然后調用glFrustum()或者gluPerspective(),他們生成的矩陣會與當前的矩陣相乘,生成透視的效果,GL_MODELVIEW是對模型視圖矩陣進行操作,前面GL_PROJECTION設置完成后開始畫圖,需要切換到模型視圖矩陣才能正確畫圖glMatrixMode(GL_MODELVIEW),如果從頭到尾都是畫3D/2D,只需要初始化設置一次,如果有交替那么就緒要glMatrixMode()切換,這樣設置很煩人于是就有類glPushMatrix()保存當前矩陣。
???????? 啊哈,利用兩天的時間查找和補充資料,終于完成了這篇博客,夜晚浩瀚的星空,世間的一切都顯得如此之渺小,人生數十載如白駒過隙,轉眼光陰即逝,怎樣讓人生過得才有意義?唯有珍惜光陰,不虛度年華,不忘最初的夢想,為夢想而堅持奮斗,這樣的人生才有意義。腦海中想起范范“最初的夢想”,唯有“不忘初心,方得始終~”共勉!
總結
以上是生活随笔為你收集整理的Qt实现3D纹理渲染自由旋转空间立方体的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 二叉树实现
- 下一篇: 将查询结果插入到现有表中