1. 程式人生 > >【Qt OpenGL教程】25:變形和從檔案中載入3D物體

【Qt OpenGL教程】25:變形和從檔案中載入3D物體

第25課:變形和從檔案中載入3D物體 (參照NeHe)

這次教程中,我們將學會如何從檔案中載入3D模型,並且平滑的從一個模型變形為另一個模型。在這一課裡,我們將介紹如何實現模型的變形過程,這將會是效果很棒的一課!

程式執行時效果如下:


下面進入教程:

我們這次將在第01課的基礎上修改程式碼,其中一些在前面教程中反覆出現的,我就不會多解釋了。首先開啟myglwidget.h檔案,將類宣告更改如下:

#ifndef MYGLWIDGET_H
#define MYGLWIDGET_H

#include <QWidget>
#include <QGLWidget>

struct VERTEX                                       //頂點結構體
{
    float x, y, z;
};

struct OBJECT                                       //物體結構體
{
    int verts;                                      //物體中頂點的個數
    QVector<VERTEX> vPoints;                        //包含頂點資料的向量
};

class MyGLWidget : public QGLWidget
{
    Q_OBJECT
public:
    explicit MyGLWidget(QWidget *parent = 0);
    ~MyGLWidget();

protected:
    //對3個純虛擬函式的重定義
    void initializeGL();
    void resizeGL(int w, int h);
    void paintGL();

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

private:
    void loadObject(QString filename, OBJECT *k);   //從檔案載入一個模型
    VERTEX calculate(int i);                        //計算第i個頂點變形過程每一步的位移

private:
    bool fullscreen;                                //是否全屏顯示

    GLfloat m_xRot;                                 //x軸旋轉角度
    GLfloat m_yRot;                                 //y軸旋轉角度
    GLfloat m_zRot;                                 //z軸旋轉角度
    GLfloat m_xSpeed;                               //x軸旋轉速度
    GLfloat m_ySpeed;                               //y軸旋轉速度
    GLfloat m_zSpeed;                               //z軸旋轉速度
    GLfloat m_xPos;                                 //x軸座標
    GLfloat m_yPos;                                 //y軸座標
    GLfloat m_zPos;                                 //z軸座標

    int m_Key;                                      //物體的標示符
    int m_Step;                                     //當前變形步數
    int m_Steps;                                    //變形的總步數
    bool m_MorphOrNot;                              //是否在變形過程

    OBJECT m_Morph1;                                //要繪製的4個物體
    OBJECT m_Morph2;
    OBJECT m_Morph3;
    OBJECT m_Morph4;
    OBJECT m_Helper;                                //協助繪製變形過程的物體(中間模型)
    OBJECT *m_Src;                                  //變形的源物體
    OBJECT *m_Dest;                                 //變形的目標物體
};

#endif // MYGLWIDGET_H
可以看到我們定義了2個結構體,依次表示頂點和物體模型。由於我們是通過點來繪製物體模型的(不使用紋理),因此一個物體模型包含許多頂點,並且我們的頂點資料只需要空間座標x、y、z的值而不需要紋理座標。定義完後,我們定義OBJECT物件m_Morph1、m_Morph2、m_Morph3、m_Morph4來儲存我們要繪製的四個物體模型的資料,m_Helper來儲存中間模型(變形過程)的資料,OBJECT指標來指定變形過程的源物體和目標物體。

其他增加的變數,前9個與x、y、z相關的變數是用於控制平移和旋轉的,整形變數m_Key表示當前的模型型別,m_Step儲存當前變形過程的步數,m_Steps儲存變形過程需要的總步數,布林變數m_MorphOrNot表示當前是否在變形過程。

然後是兩個新增的函式loadObject()和calculate(),前一個函式用於從檔案中向目標物體模型載入資料,後一個函式用於計算第i個頂點在變換過程中每一步的位移。

接下來,我們需要開啟myglwidget.cpp,加上宣告#include <QTimer>、#include <QTextStream>,由於loadObejct()函式需要在建構函式中被呼叫,我們把兩者放在一起講,具體程式碼如下:

void MyGLWidget::loadObject(QString filename, OBJECT *k)//從檔案載入一個模型
{
    QFile file(filename);
    file.open(QIODevice::ReadOnly | QIODevice::Text);   //將要讀入資料的文字開啟
    QTextStream in(&file);
    QString line = in.readLine();                       //讀入第一行
    sscanf(line.toUtf8(), "Vertices: %d\n", &k->verts); //獲取物體頂點的個數

    for (int i=0; i<k->verts; i++)                      //迴圈儲存每個頂點的資料
    {
        do                                              //讀入資料並保證資料有效
        {
            line = in.readLine();
        }
        while (line[0] == '/' || line == "");

        VERTEX object;
        QTextStream inLine(&line);
        inLine >> object.x
               >> object.y
               >> object.z;
        k->vPoints.push_back(object);
    }

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

    m_xRot = 0.0f;
    m_yRot = 0.0f;
    m_zRot = 0.0f;
    m_xSpeed = 0.0f;
    m_ySpeed = 0.0f;
    m_zSpeed = 0.0f;
    m_xPos = 0.0f;
    m_yPos = 0.0f;
    m_zPos = -10.0f;

    m_Key = 1;                                              //當前模型為球
    m_Step = 0;
    m_Steps = 200;
    m_MorphOrNot = false;

    loadObject("D:/QtOpenGL/QtImage/Sphere.txt", &m_Morph1);//載入球模型
    loadObject("D:/QtOpenGL/QtImage/Torus.txt", &m_Morph2); //載入圓環模型
    loadObject("D:/QtOpenGL/QtImage/Tube.txt", &m_Morph3);  //載入立方體模型
    m_Morph4.verts = 486;
    for (int i=0; i<m_Morph4.verts; i++)                    //隨機設定486個頂點在[-7,7]
    {
        VERTEX object;
        object.x = ((float)(rand()%14000)/1000)-7;
        object.y = ((float)(rand()%14000)/1000)-7;
        object.z = ((float)(rand()%14000)/1000)-7;
        m_Morph4.vPoints.push_back(object);
    }

    loadObject("D:/QtOpenGL/QtImage/Sphere.txt", &m_Helper);//初始化中間模型為球
    m_Src = m_Dest = &m_Morph1;                             //源模型和目標模型都設定為球

    QTimer *timer = new QTimer(this);                       //建立一個定時器
    //將定時器的計時訊號與updateGL()繫結
    connect(timer, SIGNAL(timeout()), this, SLOT(updateGL()));
    timer->start(10);                                       //以10ms為一個計時週期
}
首先是loadObject()函式。首先將檔案開啟,再利用Qt的文字流(QTextStream)先讀取第一行,由於我們檔案的第一行是預先按照“Vertices: x”(x為一個整數)的格式寫好的,我們利用sscanf()函式,讀取並儲存該物體模型k的頂點個數。然後利用迴圈,一行一行的讀取資料並保證讀入的資料是有效的。每當成功讀入一個數據時,就建立一個頂點結構體來儲存這些資料,並在最後把頂點放入物體模型k中。最後錄完資料後,關上檔案。

再來看建構函式。前面一堆變數的值得初始化我覺得沒什麼好說的,寫完程式大家也就明白了,我們直接看呼叫loadObject()函式的部分。我們直接呼叫了三次loadObject()函式,把三個檔案中的模型資料分別儲存到m_Morph1、m_Morph2和m_Morph3中。然後第四個模型我們不從檔案讀取,我們在(-7, -7, -7)到(7, 7, 7)之間隨機生成模型點,當然它和我們前面載入的模型都一樣具有486個頂點。最後我們把中間模型初始化為球體,並源模型和目標模型都設定為球體(這代表初設狀態為球體,因此m_Key應該初始化為1)。

接下來,我們把calculate()函式和initializeGL()函式放在一起解釋,具體程式碼如下:

void MyGLWidget::initializeGL()                         //此處開始對OpenGL進行所以設定
{
    glClearColor(0.0, 0.0, 0.0, 0.0);                   //黑色背景
    glShadeModel(GL_SMOOTH);                            //啟用陰影平滑
    glClearDepth(1.0);                                  //設定深度快取
    glEnable(GL_DEPTH_TEST);                            //啟用深度測試
    glDepthFunc(GL_LESS);                               //所作深度測試的型別
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  //告訴系統對透視進行修正
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
}
VERTEX MyGLWidget::calculate(int i)                     //計算第i個頂點變形過程每一步的位移
{
    VERTEX a;
    a.x = (m_Src->vPoints[i].x - m_Dest->vPoints[i].x) / m_Steps;
    a.y = (m_Src->vPoints[i].y - m_Dest->vPoints[i].y) / m_Steps;
    a.z = (m_Src->vPoints[i].z - m_Dest->vPoints[i].z) / m_Steps;
    return a;
}
先是initializeGL()函式。注意到唯一的改動就是glDepthFunc()函式的引數由GL_LEQUAL變為GL_LESS,兩者的區別在於,當深度相同時LEQUAL顯示的是最先繪製的畫素,而GL_LESS顯示的是最新繪製的畫素。具體為什麼要修改,下面會講到。

然後是calculate()函式。我們定義了一個VERTEX物件a,然後用源物件和目標物件的x、y、z座標的差值除以我們變形過程的總步數保存於a中,再將a返回。這樣a中就儲存了從源模型變形到目標模型每一步應該進行的位移。我們通過指標m_Src和m_Dest知道哪個是源模型,哪個是目標模型。

然後我們來進入重點的paintGL()函式,但其實它並不難,具體程式碼如下:

void MyGLWidget::paintGL()                              //從這裡開始進行所以的繪製
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除螢幕和深度快取
    glLoadIdentity();                                   //重置當前的模型觀察矩陣
    glTranslatef(m_xPos, m_yPos, m_zPos);               //平移和旋轉
    glRotatef(m_xRot, 1.0f, 0.0f, 0.0f);
    glRotatef(m_yRot, 0.0f, 1.0f, 0.0f);
    glRotatef(m_zRot, 0.0f, 0.0f, 1.0f);

    GLfloat tx, ty, tz;                                 //頂點座標臨時變數
    VERTEX q;                                           //儲存計算的臨時頂點
    glBegin(GL_POINTS);                                 //點繪製開始
        for (int i=0; i<m_Morph1.verts; i++)            //迴圈繪製每一個頂點
        {
            if (m_MorphOrNot)
            {
                q = calculate(i);
            }
            else
            {
                q.x = 0.0f;
                q.y = 0.0f;
                q.z = 0.0f;
            }

            m_Helper.vPoints[i].x -= q.x;               //如果在變形過程,則計算中間模型
            m_Helper.vPoints[i].y -= q.y;
            m_Helper.vPoints[i].z -= q.z;
            tx = m_Helper.vPoints[i].x;                 //儲存計算結果到tx、ty、tz中
            ty = m_Helper.vPoints[i].y;
            tz = m_Helper.vPoints[i].z;

            glColor3f(0.0f, 1.0f, 1.0f);                //設定顏色
            glVertex3f(tx, ty, tz);                     //繪製頂點

            glColor3f(0.0f, 0.5f, 1.0f);                //把顏色變藍一些
            tx -= 2*q.x;                                //如果在變形過程,則繪製2步後的頂點
            ty -= 2*q.y;
            tz -= 2*q.z;
            glVertex3f(tx, ty, tz);

            glColor3f(0.0f, 0.0f, 1.0f);                //把顏色變藍一些
            tx -= 2*q.x;                                //如果在變形過程,則繪製2步後的頂點
            ty -= 2*q.y;
            tz -= 2*q.z;
            glVertex3f(tx, ty, tz);
        }
    glEnd();                                            //繪製結束

    if (m_MorphOrNot && (m_Step <= m_Steps))
    {
        m_Step++;                                       //如果在變形過程則把當前變形步數增加
    }
    else
    {
        m_MorphOrNot = false;                           //當前變形步數大於總步數時,退出變形過程
        m_Src = m_Dest;
        m_Step = 0;
    }

    m_xRot += m_xSpeed;                                 //自動增加旋轉角度
    m_yRot += m_ySpeed;
    m_zRot += m_zSpeed;
}
首先我們就走常規步驟,清除螢幕和快取,重置模型觀察矩陣,平移和旋轉。接下來,我們迴圈繪製模型的每一個點,如果在變形過程,則計算得到變形過程每一步的應該進行的位移,儲存在q中,否則q中的各方向位移量均設定為0.0f。然後我們讓m_Helper,移動q對應的位移,如果在變形過程,則m_Helper繪製出來的是下一步移動後的樣子,否則由於q各方向位移量均設定為0.0f,不會移動(此時是變形完成的模型,當然不需要移動)。

下面我們設定顏色為藍色,繪製頂點;然後把顏色變藍一些,把前面一個點的各方向座標均減去2倍的位移量,如果在變形過程就會得到2步後的位置,否則還是在原位置,接著就繪製這個頂點。同樣再重複一次相似的工作,就是把顏色再變藍一些。上面的3次繪製可以看到,如果在變形過程,則三個點會錯開,形成一個比較長的點,顏色從藍綠色到藍色漸變(其實就看得到頭尾顏色不一樣);如果不在變形過程,3次繪製其實是在同一個地方繪製的,那會顯示哪種顏色呢?我們上面講到我們把glDepthFunc()函式的引數設為GL_LESS,當繪製深度相同時顯示的是最先繪製的,所以模型會顯示出藍綠色(當然你可以把引數改回GL_LEQUAL就顯示成藍色,全憑個人喜好)。

最後我們當前是否為變形過程,如果是判斷變形過程是否完成(否就保持原狀沒什麼解釋的),如果未完成則增加當前的步數,等待下一次繪製;如果完成了就設定m_MorphOrNot為false,m_Step為0,m_Src與m_Dest相同。函式結束前,我們根據旋轉速度增加旋轉角度,讓物體模型自動旋轉起來。

最後,我們來修改我們的鍵盤控制函式,不會很難,不過加的控制鍵真不少,具體程式碼如下:

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

    case Qt::Key_PageUp:                                //PageUp按下增加m_zSpeed
        m_zSpeed += 0.1f;
        break;
    case Qt::Key_PageDown:                              //PageDown按下減少m_zSpeed
        m_zSpeed -= 0.1f;
        break;
    case Qt::Key_Down:                                  //Down按下增加m_xSpeed
        m_xSpeed += 0.1f;
        break;
    case Qt::Key_Up:                                    //Up按下減少m_xSpeed
        m_xSpeed -= 0.1f;
        break;
    case Qt::Key_Right:                                 //Right按下增加m_ySpeed
        m_ySpeed += 0.1f;
        break;
    case Qt::Key_Left:                                  //Left按下減少m_ySpeed
        m_ySpeed -= 0.1f;
        break;

    case Qt::Key_Q:                                     //Q按下放大物體
        m_zPos -= 0.1f;
        break;
    case Qt::Key_Z:                                     //Z按下縮小物體
        m_zPos += 0.1f;
        break;
    case Qt::Key_W:                                     //W按下上移物體
        m_yPos -= 0.1f;
        break;
    case Qt::Key_S:                                     //S按下下移物體
        m_yPos += 0.1f;
        break;
    case Qt::Key_D:                                     //D按下右移物體
        m_xPos -= 0.1f;
        break;
    case Qt::Key_A:                                     //A按下左移物體
        m_xPos += 0.1f;
        break;

    case Qt::Key_1:                                     //1按下進入變形過程,變形到模型1
        if ((m_Key != 1) && !m_MorphOrNot)
        {
            m_Key = 1;
            m_MorphOrNot = true;
            m_Dest = &m_Morph1;
        }
        break;
    case Qt::Key_2:                                     //2按下進入變形過程,變形到模型2
        if ((m_Key != 2) && !m_MorphOrNot)
        {
            m_Key = 2;
            m_MorphOrNot = true;
            m_Dest = &m_Morph2;
        }
        break;
    case Qt::Key_3:                                     //3按下進入變形過程,變形到模型3
        if ((m_Key != 3) && !m_MorphOrNot)
        {
            m_Key = 3;
            m_MorphOrNot = true;
            m_Dest = &m_Morph3;
        }
        break;
    case Qt::Key_4:                                     //4按下進入變形過程,變形到模型4
        if ((m_Key != 4) && !m_MorphOrNot)
        {
            m_Key = 4;
            m_MorphOrNot = true;
            m_Dest = &m_Morph4;
        }
        break;
    }
}
新增的前12個關於旋轉和平移的我就不解釋了,我們看下最後4個鍵。當按下數字1、2、3或4時,我們會判斷是否為當前狀態以及當前是否在變形過程,如果都不是,則允許進行變形,進入變形過程(修改m_Key和m_Dest並設定m_MorphOrNot為true)。

現在就可以執行程式檢視效果了!

相關推薦

Qt OpenGL教程25變形檔案載入3D物體

第25課:變形和從檔案中載入3D物體 (參照NeHe) 這次教程中,我們將學會如何從檔案中載入3D模型,並且平滑的從一個模型變形為另一個模型。在這一課裡,我們將介紹如何實現模型的變形過程,這將會是效果很棒的一課! 程式執行時效果如下: 下面進入教程: 我們這次將在第

Qt OpenGL教程04旋轉

第04課:旋轉 (參照NeHe) 這次教程中,我們將在第03課的基礎上,教大家如何旋轉三角形和四邊形。我們將讓三角形沿y軸旋轉,四邊形沿x軸旋轉,最終我們能得到一個三角形和四邊形自動旋轉的場景。 程式執行時效果如下: 下面進入教程: 首先開啟myglwidget.h

Qt OpenGL教程08混合

第08課:混合 (參照NeHe) 這次教程中,我們將在紋理對映的基礎上加上混合,使它看起來具有透明的效果,當然解釋它不是那麼容易但程式碼並不難,希望你喜歡它。 OpenGL中的絕大多數特效都與某些型別的(色彩)混合有關。混色的定義為,將某個畫素的顏色和已繪製在螢幕上與其對應

Qt OpenGL教程06紋理對映

void MyGLWidget::paintGL() //從這裡開始進行所以的繪製 { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除螢幕和深度快取 glLoadIden

Qt OpenGL教程01建立一個OpenGL視窗

void MyGLWidget::resizeGL(int w, int h) //重置OpenGL視窗的大小 { glViewport(0, 0, (GLint)w, (GLint)h); //重置當前的視口 glMatrixMod

Qt OpenGL教程29Blitter函式

第29課:Blitter函式 (參照NeHe) 這次教程中,我們將介紹類似於DirectDraw的blit(其實blit函式在許多繪相簿都有),我們將用程式碼自己來實現它。它的作用非常簡單,就是把一塊紋理的貼到另一塊紋理上。想想,有了這個函式,我們就可以自由拼接紋理了,是不

Qt OpenGL教程28貝塞爾曲面

第28課:貝塞爾曲面 (參照NeHe) 這次教程中,我們將介紹貝塞爾曲面,因此這是關於數學運算的一課(這導致很不好講),來吧,相信你能搞定它的!這一課中,我們並不是要做一個完整的貝塞爾曲面庫(庫的話OpenGL已經完成了),而是一個展示概念的程式,來讓你熟悉曲面是怎麼計算實

ML學習筆記25PCA及繪製降維與恢復示意圖

主成分分析 簡述 主成分分析意在學習一個對映 U r

python學習筆記25scipy值濾波

中值濾波技術能有效抑制噪聲,通過把數字影象中一點的值用該點周圍的各點值的中位數來代替,讓這些值接近,以消除原影象中的噪聲。 *模擬中值濾波 >>> import random >>> import numpy as np

Django2.0Django2.0教程13.分頁shell命令列模式 視訊學習筆記

快速新增博文:Shell命令列模式 $ python manage.py shell 匯入Blog模型: >>> from blog.models import Blog 驗證是否成功引用: >>&

TensorFlow學習筆記5variable_scopename_scope,圖的基本操作

學習《深度學習之TensorFlow》時的一些實踐。 variable_scope 一般的巢狀 上節有學到在巢狀scope中的變數,一般是: import tensorflow as tf # with tf.variable_scope("scopeA") as

小程式開發VSCode-識別wxml,wxss檔案為html,css格式

問題描述:由於wxml 及 wxss檔案不能直接被識別,因此在vscode中作為純文字,可以在右下角純文字出點擊修改,然而!在幾個頁面編輯 需要來回切換,它就偶爾翻臉不認識了,又變回了純文字,反覆改了幾次,決定解決掉這個問題: 處理辦法: 在vscode擴充套件中(快捷鍵s

Qt OpenGLQt Creator3D繪圖及動畫教程

Qt Creator中的3D繪圖及動畫教程(參照NeHe) 剛剛學習了Qt Creator,發現Qt提供了QtOpenGL模組,對OpenGL做了不錯的封裝,這使得我們可以很輕鬆地在Qt程式中使用OpenGL進行繪圖渲染。雖然裡面還是由不少專業的解釋照搬原文的,但還是加入了

Unity3D基礎教程給初學者看的Unity教程(零)如何學習Unity3D

cos 詳解 component lock index unity3d遊戲 design 技術棧 log 【Unity3D基礎教程】給初學者看的Unity教程(零):如何學習Unity3D http://www.cnblogs.com/neverdie/p/How_To_

搜索HDU1181變形

got pan closed namespace 好好學習 麻煩 技術分享 content queue Description 呃......變形課上Harry碰到了一點小麻煩,因為他並不像Hermione那樣能夠記住所有的咒語而隨意的將一個棒球變成刺猬什麽的,但

python學習筆記41認識Pandas的資料變形

學習《Python3爬蟲、資料清洗與視覺化實戰》時自己的一些實踐。 Pandas資料變形 關於stack()和unstack()見這裡和這裡。 import pandas as pd import numpy as np # 讀取杭州天氣檔案 df = pd.read

Qt學習筆記Qt編譯好之後執行程式時提示程式異常結束。The process was ended forcefully. ....exe crashed.

最近在Qt結合imagingsource相機使用時編譯能夠通過,但是一直無法執行出現如下提示 此時進入Debug模式也無法進入一直提示出錯,在網上搜尋了很多資料一般的結論是少了一些配置,後來順著這個思路,折騰了兩三天發現是沒有加入DLL。因為其他的工業相機一般安裝sdk的

Unity3D基礎教程給初學者看的Unity教程(六)理解Unity的新GUI系統(UGUI)

理解UGUI的基礎架構 UGUI是Unity在4.6中引入的新的GUI系統,與傳統的中介軟體NGUI相比,這套新GUI系統有幾個核心亮點: 放棄了Atlas的概念,使用Packing Tag的方式來進行圖集的規劃 放棄了depth來確定UI顯示層級的概念,使用Hierarchy的SiblingIndex

Unity3D基礎教程給初學者看的Unity教程(二)所有指令碼元件的基類 -- MonoBehaviour的前世今生

引子 上一次我們講了GameObject,Compoent,Time,Input,Physics,其中Time,Input,Physics都是Unity中的全域性變數。GameObject是遊戲中的基本物件。GameObject是由Component組合而成的,GameObject本身必須有

Unity3D基礎教程給初學者看的Unity教程(五)詳解Unity3D的協程(Coroutine)

為什麼需要協程 在遊戲中有許多過程(Process)需要花費多個邏輯幀去計算。 你會遇到“密集”的流程,比如說尋路,尋路計算量非常大,所以我們通常會把它分割到不同的邏輯幀去進行計算,以免影響遊戲的幀率。 你會遇到“稀疏”的流程,比如說遊戲中的觸發器,這種觸發器大多數時候什麼也不做,但