【Qt OpenGL教程】25:變形和從檔案中載入3D物體
第25課:變形和從檔案中載入3D物體 (參照NeHe)
這次教程中,我們將學會如何從檔案中載入3D模型,並且平滑的從一個模型變形為另一個模型。在這一課裡,我們將介紹如何實現模型的變形過程,這將會是效果很棒的一課!
程式執行時效果如下:
下面進入教程:
我們這次將在第01課的基礎上修改程式碼,其中一些在前面教程中反覆出現的,我就不會多解釋了。首先開啟myglwidget.h檔案,將類宣告更改如下:
可以看到我們定義了2個結構體,依次表示頂點和物體模型。由於我們是通過點來繪製物體模型的(不使用紋理),因此一個物體模型包含許多頂點,並且我們的頂點資料只需要空間座標x、y、z的值而不需要紋理座標。定義完後,我們定義OBJECT物件m_Morph1、m_Morph2、m_Morph3、m_Morph4來儲存我們要繪製的四個物體模型的資料,m_Helper來儲存中間模型(變形過程)的資料,OBJECT指標來指定變形過程的源物體和目標物體。#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
其他增加的變數,前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教程】29:Blitter函式
第29課:Blitter函式 (參照NeHe) 這次教程中,我們將介紹類似於DirectDraw的blit(其實blit函式在許多繪相簿都有),我們將用程式碼自己來實現它。它的作用非常簡單,就是把一塊紋理的貼到另一塊紋理上。想想,有了這個函式,我們就可以自由拼接紋理了,是不
【Qt OpenGL教程】28:貝塞爾曲面
第28課:貝塞爾曲面 (參照NeHe) 這次教程中,我們將介紹貝塞爾曲面,因此這是關於數學運算的一課(這導致很不好講),來吧,相信你能搞定它的!這一課中,我們並不是要做一個完整的貝塞爾曲面庫(庫的話OpenGL已經完成了),而是一個展示概念的程式,來讓你熟悉曲面是怎麼計算實
【ML學習筆記】25:PCA及繪製降維與恢復示意圖
主成分分析 簡述 主成分分析意在學習一個對映 U r
【python學習筆記】25:scipy中值濾波
中值濾波技術能有效抑制噪聲,通過把數字影象中一點的值用該點周圍的各點值的中位數來代替,讓這些值接近,以消除原影象中的噪聲。 *模擬中值濾波 >>> import random >>> import numpy as np
Django2.0:【Django2.0教程】13.分頁和shell命令列模式 視訊學習筆記
快速新增博文:Shell命令列模式 $ python manage.py shell 匯入Blog模型: >>> from blog.models import Blog 驗證是否成功引用: >>&
【TensorFlow學習筆記】5:variable_scope和name_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 OpenGL】Qt Creator中的3D繪圖及動畫教程
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)需要花費多個邏輯幀去計算。 你會遇到“密集”的流程,比如說尋路,尋路計算量非常大,所以我們通常會把它分割到不同的邏輯幀去進行計算,以免影響遊戲的幀率。 你會遇到“稀疏”的流程,比如說遊戲中的觸發器,這種觸發器大多數時候什麼也不做,但