紋理映射基礎知識
什么叫紋理映射,一開始我也不明白,感覺這個詞好專業(畢竟沒有學過圖形學),后面經過網上查找資料和這次實驗稍微理解了點。紋理映射簡單的講,就是把一個紋理(其實說白了,紋理可以理解為一幅圖像)映射到空間物體的表面上,因此紋理映射也叫貼圖,這個表明不一定是矩形,比如說我可以是球面,或者是任意曲面。在上一篇文章OpenGL_Qt學習筆記之_04(3D圖形的繪制和旋轉)中,我們繪制的空間體的表面都是一些光滑的顏色,如果要在其表面采用那種方法繪制圖像的話,則必須利用微分的思想,一個一個的繪制小圖像,然后拼接起來,可想而知,這個過程是多么的復雜,opengl的紋理映射就可以很好的解決這一問題。
下面來看看紋理映射的一個示意圖片(百度百科上的):
這個示意圖說明,將中間的紋理(即圖片)映射到左邊的茶壺曲面上,就形成了右邊的圖了。在右邊的圖中,可以看到茶壺的表面上布滿了圖片,這就像中國古代的陶器繪圖一樣。
紋理映射另外一個好處是能夠保證在變換多邊形時,多邊形上的紋理也會隨之變化。
紋理映射相關函數
要進行紋理映射,得先了解下opengl中有關紋理映射的一些相關函數。
void glGenTextures(GLsizei n, GLuint *textures);
該函數的作用是開辟存儲紋理的內存空間,其中參數n為開辟紋理內存的個數,texture為存儲紋理的地址索引。
void glBindTexture(GLenum target, GLuint texture);
該函數的作用是把存儲紋理的對象texture綁定到紋理目標target上,在opengl中紋理目標分為GL_TEXTURE_1D和GL_TEXTURE_2D。該句代碼運行完后,對target的操作也對應於對texture指向的內容的操作。
void glTexImage2D(GLenum target,GLint level,GLint components,GLsizei width, glsizei height,GLint border,GLenum format,GLenum type, const GLvoid *pixels);
參數1為紋理目標;參數2為目標的層次,即目標的詳細程度,一般情況采用0即可;參數3表示的是數據成分的個數,如果數據由RGB構成,則將該參數設置為3;參數4和5分別為創建紋理數據的長和寬;參數6為邊框的值,一般也設為0;參數8為數據的通道格式;參數9為紋理的數據元素類型;參數10為紋理的數據內容。
這個函數的功能是創建一個紋理,並為該紋理分配了數據。
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
該函數表示的是當所顯示的紋理比加載進來的紋理小時,采用GL_LINEAR的方法來處理。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
和上面的函數類似,注意該函數的參數2為GL_TEXTURE_MAG_FILTER,不要誤認為是GL_TEXTURE_MAG_FILTER。
glTexCoord2f(x, y);
第一個參數是X坐標。 0.0f 是紋理的左側。 0.5f 是紋理的中點, 1.0f 是紋理的右側。第二個參數是Y坐標。0.0f 是紋理的底部。0.5f 是紋理的中點,1.0f 是紋理的頂部。
給定一張紋理圖,它的坐標示意圖如下:
其中紋理的中心點坐標為(0.5, 0.5).
實驗說明:
這個實驗室在上一篇博文OpenGL_Qt學習筆記之_04(3D圖形的繪制和旋轉) 繪制的立方體上貼每個面上貼上一張紋理(即圖片)。
我們需要在GLWidget這個類中添加一個函數來加載紋理數據,該函數為loadTextures().
另外,在QGLWidget這個類中,initializeGL(), paintGL(), resizeGL()這3個函數的執行順序是該類啟動時就執行initializeGL(),主要用於初始化opengl,通常用來設置一些前期的背景色,光照參數等等。paintGL()用於渲染整個場景;resizeGL()用於在widget大小變化的時候產生合理view。
注意,本實驗給的圖片長和寬必須是2的n次方,最大不要超過256,最小也不要小於64。我這里采用的是256*256像素的圖片。
實驗結果:
加載紋理的圖像為:

紋理映射后的效果如下:
實驗主要部分代碼及注釋(附錄有工程code下載地址):
#include "glwidget.h" #include "ui_glwidget.h" #include <QtGui> #include <QtCore> #include <QtOpenGL> GLWidget::GLWidget(QGLWidget *parent) : QGLWidget(parent), ui(new Ui::GLWidget) { // setCaption("The Opengl for Qt Framework"); ui->setupUi(this); fullscreen = false; rotate_angle = 0.0; } //這是對虛函數,這里是重寫該函數 void GLWidget::initializeGL() { setGeometry(300, 150, 500, 500);//設置窗口初始位置和大小 loadTextures(); glEnable(GL_TEXTURE_2D);//允許采用2D紋理技術 glShadeModel(GL_SMOOTH);//設置陰影平滑模式 glClearColor(0.0, 0.0, 0.0, 0);//改變窗口的背景顏色,不過我這里貌似設置后並沒有什么效果 glClearDepth(1.0);//設置深度緩存 glEnable(GL_DEPTH_TEST);//允許深度測試 glDepthFunc(GL_LEQUAL);//設置深度測試類型 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);//進行透視校正 } void GLWidget::paintGL() { //glClear()函數在這里就是對initializeGL()函數中設置的顏色和緩存深度等起作用 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /*下面開始畫立方體,並對其進行紋理映射*/ glLoadIdentity(); glRotatef(rotate_angle, -0.2f, 0.2f, -0.3f); glBegin(GL_QUADS); //上頂面 glTexCoord2f(0.0, 1.0);//將2D的紋理坐標映射到3D的空間物體表面上 glVertex3f(-0.3f, 0.3f, -0.3f); glTexCoord2f(0.0, 0.0); glVertex3f(-0.3f, 0.3f, 0.3f); glTexCoord2f(1.0, 0.0); glVertex3f(0.3f, 0.3f, 0.3f); glTexCoord2f(1.0, 1.0); glVertex3f(0.3f, 0.3f, -0.3f); //下頂面 glTexCoord2f(0.0, 1.0); glVertex3f(-0.3f, -0.3f, -0.3f); glTexCoord2f(0.0, 0.0); glVertex3f(-0.3f, -0.3f, 0.3f); glTexCoord2f(1.0, 0.0); glVertex3f(0.3f, -0.3f, 0.3f); glTexCoord2f(1.0, 1.0); glVertex3f(0.3f, -0.3f, -0.3f); //正前面 glTexCoord2f(0.0, 1.0); glVertex3f(-0.3f, 0.3f, 0.3f); glTexCoord2f(0.0, 0.0); glVertex3f(-0.3f, -0.3f, 0.3f); glTexCoord2f(1.0, 0.0); glVertex3f(0.3f, -0.3f, 0.3f); glTexCoord2f(1.0, 1.0); glVertex3f(0.3f, 0.3f, 0.3f); //右側面 glTexCoord2f(0.0, 1.0); glVertex3f(0.3f, 0.3f, 0.3f); glTexCoord2f(0.0, 0.0); glVertex3f(0.3f, -0.3f, 0.3f); glTexCoord2f(1.0, 0.0); glVertex3f(0.3f, -0.3f, -0.3f); glTexCoord2f(1.0, 1.0); glVertex3f(0.3f, 0.3f, -0.3f); //背后面 glTexCoord2f(0.0, 1.0); glVertex3f(-0.3f, 0.3f, -0.3f); glTexCoord2f(0.0, 0.0); glVertex3f(0.3f, 0.3f, -0.3f); glTexCoord2f(1.0, 0.0); glVertex3f(0.3f, -0.3f, -0.3f); glTexCoord2f(1.0, 1.0); glVertex3f(-0.3f, -0.3f, -0.3f); //左側面 glTexCoord2f(0.0, 1.0); glVertex3f(-0.3f, 0.3f, -0.3f); glTexCoord2f(0.0, 0.0); glVertex3f(-0.3f, -0.3f, -0.3f); glTexCoord2f(1.0, 0.0); glVertex3f(-0.3f, -0.3f, 0.3f); glTexCoord2f(1.0, 1.0); glVertex3f(-0.3f, 0.3f, 0.3f); rotate_angle -= 3; glEnd(); } //該程序是設置opengl場景透視圖,程序中至少被執行一次(程序啟動時). void GLWidget::resizeGL(int width, int height) { if(0 == height) height = 1;//防止一條邊為0 glViewport(0, 0, (GLint)width, (GLint)height);//重置當前視口,本身不是重置窗口的,只不過是這里被Qt給封裝好了 glMatrixMode(GL_PROJECTION);//選擇投影矩陣 glLoadIdentity();//重置選擇好的投影矩陣 // gluPerspective(45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0);//建立透視投影矩陣 glMatrixMode(GL_MODELVIEW);//以下2句和上面出現的解釋一樣 glLoadIdentity(); } void GLWidget::keyPressEvent(QKeyEvent *e) { switch(e->key()) { //F1鍵為全屏和普通屏顯示切換鍵 case Qt::Key_F1: fullscreen = !fullscreen; if(fullscreen) showFullScreen(); else { setGeometry(300, 150, 500, 500); showNormal(); } updateGL(); break; //Ese為退出程序鍵 case Qt::Key_Escape: close(); } } /*裝載紋理*/ void GLWidget::loadTextures() { QImage tex, buf; if(!buf.load("../opengl_nehe_05/cherry.jpg")) { qWarning("Cannot open the image..."); QImage dummy(128, 128, QImage::Format_RGB32);//當沒找到所需打開的圖片時,創建一副128*128大小,深度為32位的位圖 dummy.fill(Qt::green); buf = dummy; } tex = convertToGLFormat(buf);//將Qt圖片的格式buf轉換成opengl的圖片格式tex glGenTextures(1, &texture[0]);//開辟一個紋理內存,內存指向texture[0] glBindTexture(GL_TEXTURE_2D, texture[0]);//將創建的紋理內存指向的內容綁定到紋理對象GL_TEXTURE_2D上,經過這句代碼后,以后對 //GL_TEXTURE_2D的操作的任何操作都同時對應與它所綁定的紋理對象 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);//當所顯示的紋理比加載進來的紋理小時,采用GL_LINEAR的方法來處理 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//當所顯示的紋理比加載進來的紋理大時,采用GL_LINEAR的方法來處理 } GLWidget::~GLWidget() { delete ui; }
總結:
了解了opengl中的紋理映射機制后,對空間體的表面進行貼圖還是比較方便的。
參考資料:
http://www.owlei.com/DancingWind/
http://blog.csdn.net/qp120291570/article/details/7853513
http://www.qiliang.net/old/nehe_qt/
附錄:
