Qt實現3D紋理渲染自由旋轉空間立方體

            昨天七夕,關於七夕美好的愛情傳說源自於浩瀚銀河星空,又碰巧最近在學習QtOpenGL實現三維紋理防體重建,突發奇想用Qt實現一個立方體星空模型,並且能隨着鼠標操作實現空間自由旋轉

        核心思想是用到Qt OpenGL模塊,將二維圖片貼到立方體的六個面,鼠標可以自由旋轉立方體,實現三維星空的動態變換,真正做出來後,感覺效果還挺好的,三維立體星空看起來還是很絢麗的,呵呵

       下面直接從代碼層面分析上述實例,我用的ubuntu-12.04  Qt-4.8.1

        GLFrameWork.pro

#-------------------------------------------------
#
# Project created by Wangchuan 2014-08-01T10:46:58
#
#-------------------------------------------------

QT       += opengl

SOURCES += main.cpp\
        mainwindow.cpp \
    nehewidget.cpp

HEADERS  += mainwindow.h \
    nehewidget.h

LIBS+=-lGLU\

        QT += opengl一定不能少,因爲本例中要調用OoenGL模塊,如果沒有此語句,後面的OpenGL模塊中的函數則不能使用,SOURCES包含了本實例所需要的源文件:main.cpp, mainwindow.spp, nehewidget.cpp。Headers包含了頭文件mainwindow.h, nehewidget.h。LIBS是附加庫,因爲後面的gluPerspective()函數屬於本庫,並且在gluPerspective()函數所在的頭文件中要包含#include<GL/glu.h>,這兩個條件缺一不可,否則Qt會報錯:undefined reference to gluPerspective()

       main.cpp

#include <QtGui/QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    
    return a.exec();
}
      幾乎所有的Qt在新建工程時,自動生成了main.cpp,本實例中,也幾乎沒改變mian.cpp,只是頭文件加入了#include "mainwindow.h",簡單對此代碼進行分析如下,#include<QtGui/QApplication>中的QApplication類用於管理應用程序範圍內的資源,對於Qt開發GUI通常只有一個QApplication工程。#include"mainwindow,h",MainWindow主要對圖形界面交互以及邏輯控制設置。QApplication a(int argc, char *argv[ ])主要初始化窗口系統,重建應用工程,並且argc大於0,argv必須至少包含一個字符型數據,w.show()顯示最終圖形界面。return a.exec(),進入主事件循環,直到exit()函數被調用,如果exit()被調用,則返回0值。

      mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui/QMainWindow>
#include <QKeyEvent>
#include "nehewidget.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT
    
public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();
public slots:
    void setXRotation(int angle);
    void setYRotation(int angle);
    void setZRotation(int angle);

signals:
    void xRotationChanged(int angle);
    void yRotationChanged(int angle);
    void zRotationChanged(int angle);

protected:
    //鼠標事件處理
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
private:
    NeHeWidget  *neheWidget;
    QPoint lastPos;

    void normalizeAngle(int *angle);
    int xRot;
    int yRot;
    int zRot;
};

#endif // MAINWINDOW_H

        QMainWindow類主要提供主應用工程窗口。QKeyEvent類主要描述按鍵觸發事件。擴展一下,C++類當中,定義成public的數據和函數,是外部可以訪問的,定義成private的數據和函數,是私有的,外部不可訪問,定義成protected的數據和函數,是保護的,只有friend友元可以訪問。MainWindow類繼承自QMainWindow類,public成員:MainWindow(QWidget *parent=0),將parent傳遞給QWidget類,QWidget類是用戶界面工程的基礎類,新widget表示新建一個窗口,~MainWindow()刪除主窗口。public slots:公共信號槽函數,可以被外界訪問。

       擴展介紹:信號和槽機制是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中的定義編寫實現具體的函數實體,按動鼠標左鍵可以拖動立方體進行空間自由旋轉,按動鼠標右鍵自動退出。

#include "mainwindow.h"
#include "math.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    neheWidget = new NeHeWidget();
    setGeometry(100,100,1000,768);
    setWindowTitle(tr("Nehe's OpenGL Framework"));
    setCentralWidget(neheWidget);
}

MainWindow::~MainWindow()
{

}

void MainWindow::normalizeAngle(int *angle)
{
    while(*angle < 0)
            *angle += 360 * 16;
    while(*angle > 360 * 16)
            *angle -= 360 * 16;
}

void MainWindow::setXRotation(int angle)
{
    normalizeAngle(&angle);
    if(angle != xRot){
        xRot = angle;
        emit xRotationChanged(angle);
        neheWidget->setxRot(xRot);
        neheWidget-> updateGL();
    }
}

void MainWindow::setYRotation(int angle)
{
    normalizeAngle(&angle);
    if(angle != yRot){
        yRot = angle;
        emit yRotationChanged(angle);
        neheWidget->setyRot(yRot);
        neheWidget -> updateGL();
    }
}

void MainWindow::setZRotation(int angle)
{
    normalizeAngle(&angle);
    if(angle != zRot){
        zRot = angle;
        emit zRotationChanged(angle);
        neheWidget->setzRot(zRot);
        neheWidget -> updateGL();
    }
}

void MainWindow::mousePressEvent(QMouseEvent *event)
{
    lastPos = event ->pos();
}

void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
    int dx = event -> x() - lastPos.x();
    int dy = event -> y() - lastPos.y();

    switch(event -> buttons())
    {
        case Qt::LeftButton:
        setXRotation(xRot + 2 * dy);
        setYRotation(yRot + 2 * dx);
        setZRotation(zRot + 2 * dx);
        break;
        case Qt::RightButton:
        close();
        break;
    }
    lastPos = event -> pos();
}

           nehewidget.h代碼

#ifndef NEHEWIDGET_H
#define NEHEWIDGET_H
#include <QGLWidget>
#include <QtGui>
#include <QtOpenGL>


class NeHeWidget:public QGLWidget
{
    Q_OBJECT
public:
    explicit NeHeWidget(QWidget *parent = 0);
    ~NeHeWidget();
    void setxRot(int x){xRot = x;}
    void setyRot(int y){yRot = y;}
    void setzRot(int z){zRot = z;}
protected:
    //設置渲染環境
    void initializeGL();
    //繪製窗口
    void paintGL();
    //響應窗口的大小變化
    void resizeGL(int width, int height);

    //加載紋理函數
    void loadGLTextures();
    //texture用來存儲紋理
    GLuint texture[1];

private:
    int xRot;
    int yRot;
    int zRot;

};
#endif // NEHEWIDGET_H

     該頭文件主要用來定義如何調用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

#include "nehewidget.h"
#include <GL/glu.h>


#define PI 3.1415926

NeHeWidget::NeHeWidget(QWidget *parent):
    QGLWidget(parent)
{
}
NeHeWidget::~NeHeWidget()
{
}

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

    //加載紋理
    loadGLTextures();
    glEnable(GL_TEXTURE_2D);
}

void NeHeWidget::paintGL()
{
    // 清除屏幕和深度緩存
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glLoadIdentity();

    //移到屏幕的左半部分,並且將視圖推入屏幕背後足夠的距離以便我們可以看見全部的場景
    glTranslatef(0.0f,0.0f,-5.0f);

    glRotatef( xRot/16,  1.0,  0.0,  0.0 );
    glRotatef( yRot/16,  0.0,  1.0,  0.0 );
    glRotatef( zRot/16,  0.0,  0.0,  1.0 );

    //選擇使用的紋理

    glBindTexture( GL_TEXTURE_2D, texture[0] );
    glBegin( GL_QUADS );

    glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0,  1.0 );

    glTexCoord2f( 1.0, 0.0 ); glVertex3f(  1.0, -1.0,  1.0 );

    glTexCoord2f( 1.0, 1.0 ); glVertex3f(  1.0,  1.0,  1.0 );

    glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0,  1.0,  1.0 );

    glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );

    glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0,  1.0, -1.0 );

    glTexCoord2f( 0.0, 1.0 ); glVertex3f(  1.0,  1.0, -1.0 );

    glTexCoord2f( 0.0, 0.0 ); glVertex3f(  1.0, -1.0, -1.0 );

    glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0,  1.0, -1.0 );

    glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0,  1.0,  1.0 );

    glTexCoord2f( 1.0, 0.0 ); glVertex3f(  1.0,  1.0,  1.0 );

    glTexCoord2f( 1.0, 1.0 ); glVertex3f(  1.0,  1.0, -1.0 );

    glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, -1.0, -1.0 );

    glTexCoord2f( 0.0, 1.0 ); glVertex3f(  1.0, -1.0, -1.0 );

    glTexCoord2f( 0.0, 0.0 ); glVertex3f(  1.0, -1.0,  1.0 );

    glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0,  1.0 );

    glTexCoord2f( 1.0, 0.0 ); glVertex3f(  1.0, -1.0, -1.0 );

    glTexCoord2f( 1.0, 1.0 ); glVertex3f(  1.0,  1.0, -1.0 );

    glTexCoord2f( 0.0, 1.0 ); glVertex3f(  1.0,  1.0,  1.0 );

    glTexCoord2f( 0.0, 0.0 ); glVertex3f(  1.0, -1.0,  1.0 );

    glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );

    glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0,  1.0 );

    glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0,  1.0,  1.0 );

    glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0,  1.0, -1.0 );

    glEnd();
}

//重置OpenGL窗口大小
void NeHeWidget::resizeGL(int width, int height)
{
    //防止窗口大小變爲0
    if(height == 0)
    {
        height = 1;
    }
    //重置當前的視口
    glViewport(0,0,(GLint)width,(GLint)height);
    //選擇投影矩陣
    glMatrixMode(GL_PROJECTION);
    //重置投影矩陣
    glLoadIdentity();
    //設置視口大小
    gluPerspective(45.0,(GLfloat)width/(GLfloat)height,0.1,100.0);
    //選擇模型觀察矩陣
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

//紋理裝載函數
void NeHeWidget::loadGLTextures()
{
    QImage tex,buf;
    if(!buf.load("/home/wangchuan/qtcreator-2.4.1/bin/Program/GLFrameWork/GLFrameWork/xingkong.jpg"))
    {
        //如果載入不成功,自動生成一個128*128的32位色的綠色圖片
        qWarning("Could not read image file!");
        QImage dummy(128,128,QImage::Format_RGB32);
        dummy.fill(Qt::green);
            buf = dummy;
    }
    //轉換成紋理類型
    tex = QGLWidget::convertToGLFormat(buf);
    //創建紋理
    glGenTextures(1, &texture[0]);
    //使用來自位圖數據生成的典型紋理,將紋理名字texture[0]綁定到紋理目標上
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, tex.bits());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}

            nehewidget.cpp主要針對於nehewidget.h中的定義實現具體相對應的函數實體,對幾個 函數進行解釋說明:

            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()保存當前矩陣。


         啊哈,利用兩天的時間查找和補充資料,終於完成了這篇博客,夜晚浩瀚的星空,世間的一切都顯得如此之渺小,人生數十載如白駒過隙,轉眼光陰即逝,怎樣讓人生過得纔有意義?唯有珍惜光陰,不虛度年華,不忘最初的夢想,爲夢想而堅持奮鬥,這樣的人生纔有意義。腦海中想起範範“最初的夢想”,唯有“不忘初心,方得始終~”共勉!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章