青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

紋理的使用入門

我們在前一課中,學習了簡單的像素操作,這意味著我們可以使用各種各樣的BMP文件來豐富程序的顯示效果,于是我們的OpenGL圖形程序也不再像以前總是只顯示幾個多邊形那樣單調了。——但是這還不夠。雖然我們可以將像素數據按照矩形進行縮小和放大,但是還不足以滿足我們的要求。例如要將一幅世界地圖繪制到一個球體表面,只使用glPixelZoom這樣的函數來進行縮放顯然是不夠的。OpenGL紋理映射功能支持將一些像素數據經過變換(即使是比較不規則的變換)將其附著到各種形狀的多邊形表面。紋理映射功能十分強大,利用它可以實現目前計算機動畫中的大多數效果,但是它也很復雜,我們不可能一次性的完全講解。這里的課程只是關于二維紋理的簡單使用。但即使是這樣,也會使我們的程序在顯示效果上邁出一大步。
下面幾張圖片說明了紋理的效果。前兩張是我們需要的紋理,后一張是我們使用紋理后,利用OpenGL所產生出的效果。

http://blog.programfan.com/upfile/200707/20070730074740.jpg
http://blog.programfan.com/upfile/200707/20070730074746.jpg
http://blog.programfan.com/upfile/200707/20070730074751.jpg

紋理的使用是非常復雜的。因此即使是入門教程,在編寫時我也多次進行刪改,很多東西都被精簡掉了,但本課的內容仍然較多,大家要有一點心理準備~

1、啟用紋理和載入紋理
就像我們曾經學習過的OpenGL光照、混合等功能一樣。在使用紋理前,必須啟用它。OpenGL支持一維紋理、二維紋理和三維紋理,這里我們僅介紹二維紋理。可以使用以下語句來啟用和禁用二維紋理:

    glEnable(GL_TEXTURE_2D);  // 啟用二維紋理
    glDisable(GL_TEXTURE_2D); // 禁用二維紋理



使用紋理前,還必須載入紋理。利用glTexImage2D函數可以載入一個二維的紋理,該函數有多達九個參數(雖然某些參數我們可以暫時不去了解),現在分別說明如下:
第一個參數為指定的目標,在我們的入門教材中,這個參數將始終使用GL_TEXTURE_2D。
第二個參數為“多重細節層次”,現在我們并不考慮多重紋理細節,因此這個參數設置為零。
第三個參數有兩種用法。在OpenGL 1.0,即最初的版本中,使用整數來表示顏色分量數目,例如:像素數據用RGB顏色表示,總共有紅、綠、藍三個值,因此參數設置為3,而如果像素數據是用RGBA顏色表示,總共有紅、綠、藍、alpha四個值,因此參數設置為4。而在后來的版本中,可以直接使用GL_RGB或GL_RGBA來表示以上情況,顯得更直觀(并帶來其它一些好處,這里暫時不提)。注意:雖然我們使用Windows的BMP文件作為紋理時,一般是藍色的像素在最前,其真實的格式為GL_BGR而不是GL_RGB,在數據的順序上有所不同,但因為同樣是紅、綠、藍三種顏色,因此這里仍然使用GL_RGB。(如果使用GL_BGR,OpenGL將無法識別這個參數,造成錯誤)
第四、五個參數是二維紋理像素的寬度和高度。這里有一個很需要注意的地方:OpenGL在以前的很多版本中,限制紋理的大小必須是2的整數次方,即紋理的寬度和高度只能是16, 32, 64, 128, 256等值,直到最近的新版本才取消了這個限制。而且,一些OpenGL實現(例如,某些PC機上板載顯卡的驅動程序附帶的OpenGL)并沒有支持到如此高的OpenGL版本。因此在使用紋理時要特別注意其大小。盡量使用大小為2的整數次方的紋理,當這個要求無法滿足時,使用gluScaleImage函數把圖象縮放至所指定的大小(在后面的例子中有用到)。另外,無論舊版本還是新版本,都限制了紋理大小的最大值,例如,某OpenGL實現可能要求紋理最大不能超過1024*1024。可以使用如下的代碼來獲得OpenGL所支持的最大紋理:

GLint max;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);


這樣max的值就是當前OpenGL實現中所支持的最大紋理。
在很長一段時間內,很多圖形程序都喜歡使用256*256大小的紋理,不僅因為256是2的整數次方,也因為某些硬件可以使用8位的整數來表示紋理坐標,2的8次方正好是256,這一巧妙的組合為處理紋理坐標時的硬件優化創造了一些不錯的條件。

第六個參數是紋理邊框的大小,我們沒有使用紋理邊框,因此這里設置為零。
最后三個參數與glDrawPixels函數的最后三個參數的使用方法相同,其含義可以參考glReadPixels的最后三個參數。大家可以復習一下第10課的相關內容,這里不再重復。
舉個例子,如果有一幅大小為width*height,格式為Windows系統中使用最普遍的24位BGR,保存在pixels中的像素圖象。則把這樣一幅圖象載入為紋理可使用以下代碼:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);



注意,載入紋理的過程可能比較慢,原因是紋理數據通常比較大,例如一幅512*512的BGR格式的圖象,大小為0.75M。把這些像素數據從主內存傳送到專門的圖形硬件,這個過程中還可能需要把程序中所指定的像素格式轉化為圖形硬件所能識別的格式(或最能發揮圖形硬件性能的格式),這些操作都需要較多時間。
.

2、紋理坐標
我們先來回憶一下之前學過的一點內容:
當我們繪制一個三角形時,只需要指定三個頂點的顏色。三角形中其它各點的顏色不需要我們指定,這些點的顏色是OpenGL自己通過計算得到的。
在我們學習OpneGL光照時,法線向量、材質的指定,都是只需要在頂點處指定一下就可以了,其它地方的法線向量和材質都是OpenGL自己通過計算去獲得。

紋理的使用方法也與此類似。只要指定每一個頂點在紋理圖象中所對應的像素位置,OpenGL就會自動計算頂點以外的其它點在紋理圖象中所對應的像素位置。
這聽起來比較令人迷惑。我們可以這樣類比一下:
在繪制一條線段時,我們設置其中一個端點為紅色,另一個端點為綠色,則OpenGL會自動計算線段中其它各像素的顏色,如果是使用glShadeMode(GL_SMOOTH);,則最終會形成一種漸變的效果(例如線段中點,就是紅色和綠色的中間色)。
類似的,在繪制一條線段時,我們設置其中一個端點使用“紋理圖象中最左下角的顏色”作為它的顏色,另一個端點使用“紋理圖象中最右上角的顏色”作為它的顏色,則OpenGL會自動在紋理圖象中選擇合適位置的顏色,填充到線段的各個像素(例如線段中點,可能就是選擇紋理圖象中央的那個像素的顏色)。

我們在類比時,使用了“紋理圖象中最左下角的顏色”這種說法。但這種說法在很多時候不夠精確,我們需要一種精確的方式來表示我們究竟使用紋理中的哪個像素。紋理坐標也就是因為這樣的要求而產生的。以二維紋理為例,規定紋理最左下角的坐標為(0, 0),最右上角的坐標為(1, 1),于是紋理中的每一個像素的位置都可以用兩個浮點數來表示(三維紋理會用三個浮點數表示,一維紋理則只用一個即可)。
使用glTexCoord*系列函數來指定紋理坐標。這些函數的用法與使用glVertex*系列函數來指定頂點坐標十分相似。例如:glTexCoord2f(0.0f, 0.0f);指定使用(0, 0)紋理坐標。
通常,每個頂點使用不同的紋理,于是下面這樣形式的代碼是比較常見的。

glBegin( /* ... */ );
    glTexCoord2f( /* ... */ );  glVertex3f( /* ... */ );
    glTexCoord2f( /* ... */ );  glVertex3f( /* ... */ );
    /* ... */
glEnd();



當我們用一個坐標表示頂點在三維空間的位置時,可以使用glRotate*等函數來對坐標進行轉換。紋理坐標也可以進行這種轉換。只要使用glMatrixMode(GL_TEXTURE);,就可以切換到紋理矩陣(另外還有透視矩陣GL_PROJECTION和模型視圖矩陣GL_MODELVIEW,詳細情況在第五課有講述),然后glRotate*,glScale*,glTranslate*等操作矩陣的函數就可以用來處理“對紋理坐標進行轉換”的工作了。在簡單應用中,可能不會對矩陣進行任何變換,這樣考慮問題會比較簡單。

3、紋理參數
到這里,入門所需要掌握的所有難點都被我們掌握了。但是,我們的知識仍然是不夠的,如果僅利用現有的知識去使用紋理的話,你可能會發現紋理完全不起作用。這是因為在使用紋理前還有某些參數是必須設置的。
使用glTexParameter*系列函數來設置紋理參數。通常需要設置下面四個參數:
GL_TEXTURE_MAG_FILTER:指當紋理圖象被使用到一個大于它的形狀上時(即:有可能紋理圖象中的一個像素會被應用到實際繪制時的多個像素。例如將一幅256*256的紋理圖象應用到一個512*512的正方形),應該如何處理。可選擇的設置有GL_NEAREST和GL_LINEAR,前者表示“使用紋理中坐標最接近的一個像素的顏色作為需要繪制的像素顏色”,后者表示“使用紋理中坐標最接近的若干個顏色,通過加權平均算法得到需要繪制的像素顏色”。前者只經過簡單比較,需要運算較少,可能速度較快,后者需要經過加權平均計算,其中涉及除法運算,可能速度較慢(但如果有專門的處理硬件,也可能兩者速度相同)。從視覺效果上看,前者效果較差,在一些情況下鋸齒現象明顯,后者效果會較好(但如果紋理圖象本身比較大,則兩者在視覺效果上就會比較接近)。
GL_TEXTURE_MIN_FILTER:指當紋理圖象被使用到一個小于(或等于)它的形狀上時(即有可能紋理圖象中的多個像素被應用到實際繪制時的一個像素。例如將一幅256*256的紋理圖象應用到一個128*128的正方形),應該如何處理。可選擇的設置有GL_NEAREST,GL_LINEAR,GL_NEAREST_MIPMAP_NEAREST,GL_NEAREST_MIPMAP_LINEAR,GL_LINEAR_MIPMAP_NEAREST和GL_LINEAR_MIPMAP_LINEAR。其中后四個涉及到mipmap,現在暫時不需要了解。前兩個選項則和GL_TEXTURE_MAG_FILTER中的類似。此參數似乎是必須設置的(在我的計算機上,不設置此參數將得到錯誤的顯示結果,但我目前并沒有找到根據)。
GL_TEXTURE_WRAP_S:指當紋理坐標的第一維坐標值大于1.0或小于0.0時,應該如何處理。基本的選項有GL_CLAMP和GL_REPEAT,前者表示“截斷”,即超過1.0的按1.0處理,不足0.0的按0.0處理。后者表示“重復”,即對坐標值加上一個合適的整數(可以是正數或負數),得到一個在[0.0, 1.0]范圍內的值,然后用這個值作為新的紋理坐標。例如:某二維紋理,在繪制某形狀時,一像素需要得到紋理中坐標為(3.5, 0.5)的像素的顏色,其中第一維的坐標值3.5超過了1.0,則在GL_CLAMP方式中將被轉化為(1.0, 0.5),在GL_REPEAT方式中將被轉化為(0.5, 0.5)。在后來的OpenGL版本中,又增加了新的處理方式,這里不做介紹。如果不指定這個參數,則默認為GL_REPEAT。
GL_TEXTURE_WRAP_T:指當紋理坐標的第二維坐標值大于1.0或小于0.0時,應該如何處理。選項與GL_TEXTURE_WRAP_S類似,不再重復。如果不指定這個參數,則默認為GL_REPEAT。

設置參數的代碼如下所示:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

4、紋理對象
前面已經提到過,載入一幅紋理所需要的時間是比較多的。因此應該盡量減少載入紋理的次數。如果只有一幅紋理,則應該在第一次繪制前就載入它,以后就不需要再次載入了。這點與glDrawPixels函數很不相同。每次使用glDrawPixels函數,都需要把像素數據重新載入一次,因此用glDrawPixels函數來反復繪制圖象的效率是較低的(如果只繪制一次,則不會有此問題),使用紋理來反復繪制圖象是可取的做法。
但是,在每次繪制時要使用兩幅或更多幅的紋理時,這個辦法就行不通了。你可能會編寫下面的代碼:

glTexImage2D( /* ... */ ); // 載入第一幅紋理
// 使用第一幅紋理
glTexImage2D( /* ... */ ); // 載入第二幅紋理
// 使用第二幅紋理
// 當紋理的數量增加時,這段代碼會變得更加復雜。



在繪制動畫時,由于每秒鐘需要將畫面繪制數十次,因此如果使用上面的代碼,就會反復載入紋理,這對計算機是非常大的負擔,以目前的個人計算機配置來說,根本就無法讓動畫能夠流暢的運行。因此,需要有一種機制,能夠在不同的紋理之間進行快速的切換。

紋理對象正是這樣一種機制。我們可以把每一幅紋理(包括紋理的像素數據、紋理大小等信息,也包括了前面所講的紋理參數)放到一個紋理對象中,通過創建多個紋理對象來達到同時保存多幅紋理的目的。這樣一來,在第一次使用紋理前,把所有的紋理都載入,然后在繪制時只需要指明究竟使用哪一個紋理對象就可以了。

使用紋理對象和使用顯示列表有相似之處:使用一個正整數來作為紋理對象的編號。在使用前,可以調用glGenTextures來分配紋理對象。該函數有兩種比較常見的用法:

GLuint texture_ID;
glGenTextures(1, &texture_ID); // 分配一個紋理對象的編號


或者:

GLuint texture_ID_list[5];
glGenTextures(5, texture_ID_list); // 分配5個紋理對象的編號



零是一個特殊的紋理對象編號,表示“默認的紋理對象”,在分配正確的情況下,glGenTextures不會分配這個編號。與glGenTextures對應的是glDeleteTextures,用于銷毀一個紋理對象。

在分配了紋理對象編號后,使用glBindTexture函數來指定“當前所使用的紋理對象”。然后就可以使用glTexImage*系列函數來指定紋理像素、使用glTexParameter*系列函數來指定紋理參數、使用glTexCoord*系列函數來指定紋理坐標了。如果不使用glBindTexture函數,那么glTexImage*、glTexParameter*、glTexCoord*系列函數默認在一個編號為0的紋理對象上進行操作。glBindTexture函數有兩個參數,第一個參數是需要使用紋理的目標,因為我們現在只學習二維紋理,所以指定為GL_TEXTURE_2D,第二個參數是所使用的紋理的編號。
使用多個紋理對象,就可以使OpenGL同時保存多個紋理。在使用時只需要調用glBindTexture函數,在不同紋理之間進行切換,而不需要反復載入紋理,因此動畫的繪制速度會有非常明顯的提升。典型的代碼如下所示:

// 在程序開始時:分配好紋理編號,并載入紋理
glGenTextures( /* ... */ );
glBindTexture(GL_TEXTURE_2D, texture_ID_1);
// 載入第一幅紋理
glBindTexture(GL_TEXTURE_2D, texture_ID_2);
// 載入第二幅紋理



// 在繪制時,切換并使用紋理,不需要再進行載入
glBindTexture(GL_TEXTURE_2D, texture_ID_1); // 指定第一幅紋理
// 使用第一幅紋理
glBindTexture(GL_TEXTURE_2D, texture_ID_2); // 指定第二幅紋理
// 使用第二幅紋理



提示:紋理對象是從OpenGL 1.1版開始才有的,最舊版本的OpenGL 1.0并沒有處理紋理對象的功能。不過,我想各位的機器不會是比OpenGL 1.1更低的版本(Windows 95就自帶了OpenGL 1.1版本,遺憾的是,Microsoft對OpenGL的支持并不積極,Windows XP也還采用1.1版本。據說Vista使用的是OpenGL 1.4版。當然了,如果安裝顯卡驅動的話,現在的主流顯卡一般都附帶了適用于該顯卡的OpenGL 1.4版或更高版本),所以這個問題也就不算是問題了。

5、示例程序
紋理入門所需要掌握的知識點就介紹到這里了。但是如果不實際動手操作的話,也是不可能真正掌握的。下面我們來看看本課開頭的那個紋理效果是如何實現的吧。
因為代碼比較長,我把它拆分成了三段,大家如果要編譯的話,應該把三段代碼按順序連在一起編譯。如果要運行的話,除了要保證有一個名稱為dummy.bmp,圖象大小為1*1的24位BMP文件,還要把本課開始的兩幅紋理圖片保存到正確位置(一幅名叫ground.bmp,另一幅名叫wall.bmp。注意:我為了節省網絡空間,把兩幅圖片都轉成jpg格式了,讀者把圖片保存到本地后,需要把它們再轉化為BMP格式。可以使用Windows XP帶的畫圖程序中的“另存為”功能完成這一轉換)。

第一段代碼如下。其中的主體——grab函數,是我們在第十課介紹過的,這里僅僅是抄過來用一下,目的是為了將最終效果圖保存到一個名字叫grab.bmp的文件中。(當然了,為了保證程序的正確運行,那個大小為1*1的dummy.bmp文件仍然是必要的,參見第十課)

#define WindowWidth  400
#define WindowHeight 400
#define WindowTitle  "OpenGL紋理測試"

#include <gl/glut.h>
#include <stdio.h>
#include <stdlib.h>

/* 函數grab
 * 抓取窗口中的像素
 * 假設窗口寬度為WindowWidth,高度為WindowHeight
 */
#define BMP_Header_Length 54
void grab(void)
{
    FILE*    pDummyFile;
    FILE*    pWritingFile;
    GLubyte* pPixelData;
    GLubyte  BMP_Header[BMP_Header_Length];
    GLint    i, j;
    GLint    PixelDataLength;

    // 計算像素數據的實際長度
    i = WindowWidth * 3;   // 得到每一行的像素數據長度
    while( i%4 != 0 )      // 補充數據,直到i是的倍數
        ++i;               // 本來還有更快的算法,
                           // 但這里僅追求直觀,對速度沒有太高要求
    PixelDataLength = i * WindowHeight;

    // 分配內存和打開文件
    pPixelData = (GLubyte*)malloc(PixelDataLength);
    if( pPixelData == 0 )
        exit(0);

    pDummyFile = fopen("dummy.bmp""rb");
    if( pDummyFile == 0 )
        exit(0);

    pWritingFile = fopen("grab.bmp""wb");
    if( pWritingFile == 0 )
        exit(0);

    // 讀取像素
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    glReadPixels(0, 0, WindowWidth, WindowHeight,
        GL_BGR_EXT, GL_UNSIGNED_BYTE, pPixelData);

    // 把dummy.bmp的文件頭復制為新文件的文件頭
    fread(BMP_Header, sizeof(BMP_Header), 1, pDummyFile);
    fwrite(BMP_Header, sizeof(BMP_Header), 1, pWritingFile);
    fseek(pWritingFile, 0x0012, SEEK_SET);
    i = WindowWidth;
    j = WindowHeight;
    fwrite(&i, sizeof(i), 1, pWritingFile);
    fwrite(&j, sizeof(j), 1, pWritingFile);

    // 寫入像素數據
    fseek(pWritingFile, 0, SEEK_END);
    fwrite(pPixelData, PixelDataLength, 1, pWritingFile);

    // 釋放內存和關閉文件
    fclose(pDummyFile);
    fclose(pWritingFile);
    free(pPixelData);
}

第二段代碼是我們的重點。它包括兩個函數。其中power_of_two比較簡單,雖然實現手段有點奇特,但也并非無法理解(即使真的無法理解,讀者也可以給出自己的解決方案,用一些循環以及多使用一些位操作也沒關系。反正,這里不是重點啦)。另一個load_texture函數卻是重頭戲:打開BMP文件、讀取其中的高度和寬度信息、計算像素數據所占的字節數、為像素數據分配空間、讀取像素數據、對像素圖象進行縮放(如果必要的話)、分配新的紋理編號、填寫紋理參數、載入紋理,所有的功能都在同一個函數里面完成了。為了敘述方便,我把所有的解釋都放在了注釋里。

/* 函數power_of_two
 * 檢查一個整數是否為2的整數次方,如果是,返回1,否則返回0
 * 實際上只要查看其二進制位中有多少個,如果正好有1個,返回1,否則返回0
 * 在“查看其二進制位中有多少個”時使用了一個小技巧
 * 使用n &= (n-1)可以使得n中的減少一個(具體原理大家可以自己思考)
 */
int power_of_two(int n)
{
    if( n <= 0 )
        return 0;
    return (n & (n-1)) == 0;
}

/* 函數load_texture
 * 讀取一個BMP文件作為紋理
 * 如果失敗,返回0,如果成功,返回紋理編號
 */
GLuint load_texture(const char* file_name)
{
    GLint width, height, total_bytes;
    GLubyte* pixels = 0;
    GLuint last_texture_ID, texture_ID = 0;

    // 打開文件,如果失敗,返回
    FILE* pFile = fopen(file_name, "rb");
    if( pFile == 0 )
        return 0;

    // 讀取文件中圖象的寬度和高度
    fseek(pFile, 0x0012, SEEK_SET);
    fread(&width, 4, 1, pFile);
    fread(&height, 4, 1, pFile);
    fseek(pFile, BMP_Header_Length, SEEK_SET);

    // 計算每行像素所占字節數,并根據此數據計算總像素字節數
    {
        GLint line_bytes = width * 3;
        while( line_bytes % 4 != 0 )
            ++line_bytes;
        total_bytes = line_bytes * height;
    }

    // 根據總像素字節數分配內存
    pixels = (GLubyte*)malloc(total_bytes);
    if( pixels == 0 )
    {
        fclose(pFile);
        return 0;
    }

    // 讀取像素數據
    iffread(pixels, total_bytes, 1, pFile) <= 0 )
    {
        free(pixels);
        fclose(pFile);
        return 0;
    }

    // 在舊版本的OpenGL中
    // 如果圖象的寬度和高度不是的整數次方,則需要進行縮放
    // 這里并沒有檢查OpenGL版本,出于對版本兼容性的考慮,按舊版本處理
    // 另外,無論是舊版本還是新版本,
    // 當圖象的寬度和高度超過當前OpenGL實現所支持的最大值時,也要進行縮放
    {
        GLint max;
        glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
        if( !power_of_two(width)
         || !power_of_two(height)
         || width > max
         || height > max )
        {
            const GLint new_width = 256;
            const GLint new_height = 256; // 規定縮放后新的大小為邊長的正方形
            GLint new_line_bytes, new_total_bytes;
            GLubyte* new_pixels = 0;

            // 計算每行需要的字節數和總字節數
            new_line_bytes = new_width * 3;
            while( new_line_bytes % 4 != 0 )
                ++new_line_bytes;
            new_total_bytes = new_line_bytes * new_height;

            // 分配內存
            new_pixels = (GLubyte*)malloc(new_total_bytes);
            if( new_pixels == 0 )
            {
                free(pixels);
                fclose(pFile);
                return 0;
            }

            // 進行像素縮放
            gluScaleImage(GL_RGB,
                width, height, GL_UNSIGNED_BYTE, pixels,
                new_width, new_height, GL_UNSIGNED_BYTE, new_pixels);

            // 釋放原來的像素數據,把pixels指向新的像素數據,并重新設置width和height
            free(pixels);
            pixels = new_pixels;
            width = new_width;
            height = new_height;
        }
    }

    // 分配一個新的紋理編號
    glGenTextures(1, &texture_ID);
    if( texture_ID == 0 )
    {
        free(pixels);
        fclose(pFile);
        return 0;
    }

    // 綁定新的紋理,載入紋理并設置紋理參數
    // 在綁定前,先獲得原來綁定的紋理編號,以便在最后進行恢復
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture_ID);
    glBindTexture(GL_TEXTURE_2D, texture_ID);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,
        GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);
    glBindTexture(GL_TEXTURE_2D, last_texture_ID);

    // 之前為pixels分配的內存可在使用glTexImage2D以后釋放
    // 因為此時像素數據已經被OpenGL另行保存了一份(可能被保存到專門的圖形硬件中)
    free(pixels);
    return texture_ID;
}

第三段代碼是關于顯示的部分,以及main函數。注意,我們只在main函數中讀取了兩幅紋理,并把它們保存在各自的紋理對象中,以后就再也不載入紋理。每次繪制時使用glBindTexture在不同的紋理對象中切換。另外,我們使用了超過1.0的紋理坐標,由于GL_TEXTURE_WRAP_S和GL_TEXTURE_WRAP_T參數都被設置為GL_REPEAT,所以得到的效果就是紋理像素的重復,有點向地板磚的花紋那樣。讀者可以試著修改“墻”的紋理坐標,將5.0修改為10.0,看看效果有什么變化。

/* 兩個紋理對象的編號
 */
GLuint texGround;
GLuint texWall;

void display(void)
{
    // 清除屏幕
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 設置視角
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(75, 1, 1, 21);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(1, 5, 5, 0, 0, 0, 0, 0, 1);

    // 使用“地”紋理繪制土地
    glBindTexture(GL_TEXTURE_2D, texGround);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-8.0f, -8.0f, 0.0f);
        glTexCoord2f(0.0f, 5.0f); glVertex3f(-8.0f, 8.0f, 0.0f);
        glTexCoord2f(5.0f, 5.0f); glVertex3f(8.0f, 8.0f, 0.0f);
        glTexCoord2f(5.0f, 0.0f); glVertex3f(8.0f, -8.0f, 0.0f);
    glEnd();
    // 使用“墻”紋理繪制柵欄
    glBindTexture(GL_TEXTURE_2D, texWall);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-6.0f, -3.0f, 0.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-6.0f, -3.0f, 1.5f);
        glTexCoord2f(5.0f, 1.0f); glVertex3f(6.0f, -3.0f, 1.5f);
        glTexCoord2f(5.0f, 0.0f); glVertex3f(6.0f, -3.0f, 0.0f);
    glEnd();

    // 旋轉后再繪制一個
    glRotatef(-90, 0, 0, 1);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-6.0f, -3.0f, 0.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-6.0f, -3.0f, 1.5f);
        glTexCoord2f(5.0f, 1.0f); glVertex3f(6.0f, -3.0f, 1.5f);
        glTexCoord2f(5.0f, 0.0f); glVertex3f(6.0f, -3.0f, 0.0f);
    glEnd();

    // 交換緩沖區,并保存像素數據到文件
    glutSwapBuffers();
    grab();
}

int main(int argc, char* argv[])
{
    // GLUT初始化
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(WindowWidth, WindowHeight);
    glutCreateWindow(WindowTitle);
    glutDisplayFunc(&display);

    // 在這里做一些初始化
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    texGround = load_texture("ground.bmp");
    texWall = load_texture("wall.bmp");

    // 開始顯示
    glutMainLoop();

    return 0;
}

小結:
本課介紹了OpenGL紋理的入門知識。
利用紋理可以進行比glReadPixels和glDrawPixels更復雜的像素繪制,因此可以實現很多精彩的效果。
本課只涉及了二維紋理。OpenGL還支持一維和三維紋理,其原理是類似的。
在使用紋理前,要啟用紋理。并且,還需要將像素數據載入到紋理中。注意紋理的寬度和高度,目前很多OpenGL的實現都還要求其值為2的整數次方,如果紋理圖象本身并不滿足這個條件,可以使用gluScaleImage函數來進行縮放。為了正確的使用紋理,需要設置紋理參數。
載入紋理所需要的系統開銷是比較大的,應該盡可能減少載入紋理的次數。如果程序中只使用一幅紋理,則只在第一次使用前載入,以后不必重新載入。如果程序中要使用多幅紋理,不應該反復載入它們,而應該將每個紋理都用一個紋理對象來保存,并使用glBindTextures在各個紋理之間進行切換。
本課還給出了一個程序(到目前為止,它是這個OpenGL教程系列中所給出的程序中最長的)。該程序演示了紋理的基本使用方法,本課程涉及到的幾乎所有內容都被包括其中,這是對本課中文字說明的一個補充。如果讀者有什么不明白的地方,也可以以這個程序作為參考。

posted on 2009-01-03 05:43 RedLight 閱讀(1521) 評論(0)  編輯 收藏 引用 所屬分類: 3D渲染技術

<2009年5月>
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456

導航

統計

公告


Name: Galen
QQ: 88104725

常用鏈接

留言簿(3)

隨筆分類

隨筆檔案

相冊

My Friend

搜索

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美日韩精品一区| 91久久精品国产91久久| 欧美成年人在线观看| 99国产精品久久久久久久久久| 午夜精品一区二区在线观看 | 一区二区免费在线观看| 国产欧美日本在线| 国产精品欧美久久| 欧美日韩亚洲精品内裤| 欧美日韩直播| 欧美日韩在线播放| 欧美夫妇交换俱乐部在线观看| 久久久不卡网国产精品一区| 亚洲欧美激情视频在线观看一区二区三区| 99热免费精品| 久久精品免费| 欧美三级第一页| 国产伪娘ts一区| 亚洲国产午夜| 午夜精品视频在线| 欧美激情精品久久久久久免费印度| 免费在线亚洲欧美| 国产精品99久久久久久白浆小说 | 久久久97精品| 亚洲国产一二三| 欧美影视一区| 国产精品电影网站| 亚洲经典一区| 欧美激情综合在线| 国产精品日日摸夜夜添夜夜av| 揄拍成人国产精品视频| 一区二区三区视频在线| 久久网站免费| 欧美在线免费看| 国产精品激情偷乱一区二区∴| 亚洲国产cao| 久久久青草婷婷精品综合日韩| 日韩亚洲综合在线| 欧美日韩少妇| 日韩视频一区| 亚洲天堂av综合网| 欧美剧在线免费观看网站| 亚洲国产一区二区a毛片| 美国成人毛片| 久久精品二区三区| 国产欧美日韩高清| 久久综合色综合88| 久久只精品国产| 日韩一级免费| 亚洲免费在线播放| 极品少妇一区二区三区| 欧美成人蜜桃| 国产精品久久久久久久久动漫| 午夜影院日韩| 久久综合九色综合久99| 亚洲精品在线观看免费| 亚洲午夜久久久久久尤物| 国产一区二区三区在线观看免费| 久久尤物电影视频在线观看| 久久久久www| 中文亚洲字幕| 久久久久久9999| 中文在线资源观看网站视频免费不卡| 亚洲美女福利视频网站| 在线天堂一区av电影| 尤物视频一区二区| 亚洲欧美高清| 亚洲激情偷拍| 久久成人精品| 久久国产精品久久久| 欧美精品在线视频观看| 另类图片国产| 国产精品美女一区二区| 亚洲国产91色在线| 亚洲第一视频| 久久久久久成人| 免费观看在线综合| 一区二区三区在线免费视频| 亚洲自拍偷拍一区| 欧美一区二区播放| 国产欧美精品| 欧美一级视频精品观看| 欧美亚洲一区二区三区| 国产精品美女久久久久aⅴ国产馆| 亚洲精品国产欧美| 日韩视频一区二区三区在线播放免费观看 | 浪潮色综合久久天堂| 国产主播一区| 欧美激情久久久| 夜夜嗨av一区二区三区中文字幕 | 久久综合中文色婷婷| 欧美激情性爽国产精品17p| 亚洲日本成人| 国产精品久久久久久久app | 久久全国免费视频| 免费看的黄色欧美网站| 99成人精品| 国产一区日韩一区| 欧美人在线观看| 久久天天狠狠| 亚洲小视频在线观看| 老巨人导航500精品| 亚洲视频你懂的| 亚洲激情中文1区| 国产精品一区一区| 欧美精品黄色| 欧美+亚洲+精品+三区| 午夜国产欧美理论在线播放| 亚洲高清免费在线| 老司机精品福利视频| 午夜在线成人av| 亚洲自拍另类| 亚洲免费在线电影| 亚洲私人影院在线观看| 亚洲乱码国产乱码精品精可以看| 国产日韩一区二区三区在线| 欧美精品日韩一区| 美女诱惑一区| 欧美91福利在线观看| 蜜桃久久av| 欧美日本成人| 欧美视频二区| 国产精品自拍三区| 狠狠色狠狠色综合日日小说| 狠狠色丁香婷婷综合影院| 一区二区在线免费观看| 在线观看日韩av电影| 91久久国产精品91久久性色| 亚洲国产精品成人综合| 99综合电影在线视频| 亚洲一区二区三区免费在线观看| 日韩一级裸体免费视频| 亚洲欧美日韩一区二区三区在线观看| 欧美国产免费| 欧美精品福利| 1024成人网色www| 一区二区三区不卡视频在线观看| 在线中文字幕一区| 久久青青草综合| 亚洲电影下载| 一区二区三区毛片| 美女黄毛**国产精品啪啪| 国产精品久久久一区二区| 国产一区在线播放| 亚洲国产一区二区三区在线播| 亚洲人体大胆视频| 欧美 日韩 国产精品免费观看| 99在线|亚洲一区二区| 欧美国产乱视频| 在线日韩av永久免费观看| 欧美夜福利tv在线| 正在播放欧美一区| 欧美日韩一区三区| 一区二区三区日韩精品| 亚洲国产美女| 欧美大片一区二区三区| 激情六月婷婷久久| 久久蜜桃av一区精品变态类天堂| 一区二区三区日韩精品| 欧美视频在线观看视频极品| 亚洲精品女av网站| 欧美插天视频在线播放| 玖玖精品视频| 亚洲精品欧美日韩| 99精品国产一区二区青青牛奶| 欧美日韩国语| 久久国产乱子精品免费女| 久久中文字幕导航| 一本久久a久久免费精品不卡| 亚洲美女黄网| 国产三区二区一区久久| 欧美成人精品h版在线观看| 欧美精品福利视频| 久久成人综合视频| 欧美成人精品h版在线观看| 中文精品一区二区三区 | 欧美第一黄色网| 亚洲视频 欧洲视频| 久久精品国产免费看久久精品| 亚洲国产三级网| 亚洲永久免费精品| 99国产精品久久久久老师| 国产精品99久久99久久久二8| 在线观看亚洲精品视频| 午夜免费久久久久| 一区二区三区视频免费在线观看 | 亚洲一区二区三区四区五区午夜| 亚洲欧美精品在线观看| 9人人澡人人爽人人精品| 午夜精品久久久久久99热| 日韩天堂在线视频| 欧美高清不卡| 亚洲第一页在线| 亚洲美女诱惑| 欧美三级黄美女| 一区二区三区波多野结衣在线观看| 亚洲精品123区| 欧美日韩一区二区在线播放| 亚洲精品综合| 亚洲自拍偷拍一区|