1. 程式人生 > >OpenGL立方體貼圖

OpenGL立方體貼圖

OpenGL 立方貼圖 Commercial publication in written, electronic, or other forms without expressed written permission is prohibited. Electronic redistribution for educational or private use is permitted. 摘要 本網路教程解釋了硬體立方貼圖的目的。立方貼圖可以用於環境貼圖(environment mapping)、高光曲線區(stable specular highlights)以及逐畫素光照效果的凹凸貼圖(bump map)。程式設計人員可以下載例項程式碼,瞭解OpenGL
EXT_texture_cube_map擴充套件。 介紹 如今,硬體加速的紋理貼圖已經非常普及。曾經只在那些高階的工作站和飛行模擬中才有的,如今成為了普通PC機和家用視訊遊戲的一個標準特徵。原理很簡單,一個2D影象(紋理)上的一些點被附著到3D模型的頂點上,當渲染3D模型時,紋理影象%26ldquo;桌布%26rdquo;一樣貼在3D幾何模型上,當對3D模型進行動畫或改變形狀時,紋理圖%26ldquo;粘%26rdquo;住模型,下面是一個例子: 寫著%26ldquo;GeForce%26rdquo;的紋理被%26ldquo;包在%26rdquo;一個輪胎上(或者稱之為%26ldquo;油炸餅%26rdquo;)。我們也可以使用其他不同的紋理使得輪胎呈現%26ldquo;木頭%26rdquo;的樣子。我們可以將任何的紋理作用於任何的幾何形狀。
通常的紋理對映幾乎可以又在所有的3D遊戲中,但是它並不適合所有的情況,考慮一個像%26ldquo;銀色燭臺%26rdquo;這樣一個物體,閃亮的表面反射環境光。由於表面是反射性的,沒有一個紋理可以貼在這樣的表面上。如果燭臺或者視點位置移動,燭臺上的反射也將改變位置。因此不能使燭臺頂點和紋理圖上固定的2D位置匹配,而是應該將反射性的紋理以合適的方向對映到物體表面。 不幸的是,通常的硬體紋理對映方法並不適合這種情況,因此不使用2D紋理和表面位置進行對映,而是真正將表面位置在360度方向進行對映。(水平有限,很吃力!) 觀察你的周圍,僅僅旋轉頭部和眼睛,你能看到全部的方向,環境可以被理解為以頭部為中心的全方點陣圖。這和你觀察2D影象上的位置(X,Y)是根本不同的。如果你的環境能夠被編碼為一個紋理圖,那麼它能夠通過一個3D的方向而不是2D位置進行訪問。
使用立方貼圖對環境進行編碼 立方貼圖是紋理貼圖的一種形式,它使用一個3D的向量(即方向)來對應一個紋理,6個方形的2D紋理組成了立方體的6個面。現在請再次考慮你的周圍環境,你可以站在一個位置,並 %26ldquo;捕獲%26rdquo;360度視角(每轉90度拍下6張照片),下圖為6張捕獲的室外場景:

back bottom
front left
right top

下圖描述如何將立方紋理和立法體的面進行對映。 一個立方貼圖的例子 使用硬體加速的立方貼圖使得渲染一個動態反射環境的物體成為可能。最酷的是它能實現%26ldquo;實時%26rdquo;。下面是個%26ldquo;bubble%26rdquo;程式執行在帶有NVIDIA GeForce 256圖形處理器的PC機上的截圖。 程式使用一個物理模型來實時的扭曲%26ldquo;泡泡%26rdquo;的形狀,動盪的%26ldquo;泡泡%26rdquo;表面導致了動態的環境對映。 不同的環境對映方法 計算機圖形研究人員稱上述渲染技術為%26ldquo;環境對映%26rdquo;。這個技術並非新技術,Blinn 和Newell在1976年首次發表了該想法,如電影%26ldquo;Abyss%26rdquo;和%26ldquo;Terminator 2%26rdquo;已經普及了該技術(但是這些特效都是通過離線渲染的,而不是實時的)。 立方貼圖只是實現環境對映的方法之一。其他方法如%26ldquo;球體對映%26rdquo;和%26ldquo;雙拋物面對映%26rdquo;(由Heidrich %26amp; Seidel開發)能夠產生相似的效果(使用通常的環境對映),但是他們都有嚴重的缺點而限制了用處。球體對映非常簡單,並由OpenGL直接支援,但它是視點相關的,不同的視點需要不同的球體對映。另外,球體對映在邊界上會產生%26ldquo;瑕疵%26rdquo;。如下圖所示: 雙拋物面對映能夠克服這些問題,但是它需要兩個紋理單元或者兩遍渲染,因此代價比較高。雙拋物面對映同時需要計算相關的紋理座標(除非由OpenGL擴充套件支援)。創作球體對映或者拋物面對映的紋理圖也不直觀,它需要專業的圖形變形操作。如下圖所示:

球體對映圖

雙拋物面對映圖(前向) 雙拋物面對映圖(後向)

立方貼圖消除了球體對映的%26ldquo;瑕疵%26rdquo;。和雙拋物面對映不同,它僅僅需要一個紋理單元和一遍次渲染。由於立方貼圖的紋理圖僅僅是環境中的六個方形面,可以很方便的通過拍照獲得並動態的進行渲染。立方貼圖完全利用了紋理圖的每個畫素,而球體或拋物面對映可能只使用了78%的紋理畫素。同時它沒有球體或雙拋物面的變形操作。 因此,立方貼圖是更直觀的,但是立方貼圖需要能夠一次性訪問6張紋理圖,這就要求更新的紋理硬體。而且立方貼圖比通常的紋理貼圖更加複雜。快速的半導體技術發展已經使得在PC機上實現了硬體立方貼圖技術。NVIDIA's GeForce 256是第一個支援立方貼圖的GPU。 硬體只有在具備了軟體介面時才能發揮出它的威力。幸運的是,Windows上的Microsoft's Direct3D API和OpenGL都支援立方貼圖。OpenGL使用EXT_texture_cube_map擴充套件支援立方貼圖。 立方貼圖的其他應用 鏡面高光反射: CAD程式在顯示3D物體時會新增反射光照效果傳達表面曲率。球體表面上的一個閃光點就是這種鏡面高光的一個例子。由於人們非常熟悉真實物體上的鏡面高光,因此它提供了關於表面曲率的非常重要的視覺提示。一個麻煩的問題是對這些鏡面高光進行取樣是非常困難的,因為CAD程式通常使用實體網格對3D物體進行渲染,只能在網格頂點上執行昂貴的鏡面高光計算。然後對光照後的顏色在曲面上通過網格頂點進行插值。不幸的是,如果網格不夠細緻,鏡面高光的取樣會不足。當旋轉物體時,將導致%26ldquo;擺動的%26rdquo;鏡面高光,高光的亮度依賴於和網格內頂點的靠近程度。高光的重要視覺提示大打折扣。然而,更細的網格是不切實際的,這將帶來更慢的速度。理想上,鏡面高光應該足夠明亮和穩定,而不受物體頂點網格密度的影響。 立方貼圖提供了直觀的方法來渲染穩定的鏡面高光。多個鏡面高光可以編碼成一個立方貼圖紋理,它們能夠通過表面的反射向量獲得。不必計算每個頂點的顏色並在表明上進行插值,硬體僅計算反射向量(比計算實際的鏡面反射光貢獻要快得多)計算紋理座標。下圖為一個使用立方貼圖產生的穩定高光。左圖是白色和桔紅色的高光。右圖使用通常的逐頂點光照 使用立方貼圖來產生穩定的鏡面高光的另一個優點是:鏡面反射的光源數量(被編碼到單一的立方紋理中)與繪製效能沒有關係,而且不影響互動效能(在交換前已經編碼)。缺點是 它只適合於光源位置比較遠或者無限遠的情況,幸運的是,CAD軟體通常使用無窮光源。 天窗照明 立方貼圖的另一個應用是精確的建模室外照明。計算機圖形程式設計師經常將太陽光視為無窮光源,這是一種粗略的近似,會導致不真實的戶外照明。由於大部分室外光直接來自太陽和大氣散射。通常會提及天窗照明:日子的變化、雲層以及汙染都會影響天窗的照明。 立方貼圖可以捕獲天窗照明中的散射成分。通過表面法向來訪問立方紋理能夠迅速得到天窗的散射光照明。為特定的天窗結構計算立方紋理是複雜的,但已經有些圖形研究人員為此作出了努力並取得了客觀的效果。具體參見SIGGRAPH 99中的論文:"A Practical Analytic Model for Daylight"、Klassen的"Modeling the Effect of the Atmosphere on Light"(ACM Transactions on Graphics, July 1987 )以及Tadamura等的"Modeling of Skylight and Rendering of Outdoor Scenes" (EUROGRAPHICS '93)。 動態立方貼圖反射 如上述%26ldquo;泡泡%26rdquo;的例子中,它可以移動和扭曲,觀察者可以實時的改變視點位置。反射的環境本身不會改變,為了動態的反射環境,每一次改變,都要程式設計師重新產生立方貼圖。 動態立方環境貼圖真是處理這種情況。動態立方紋理是在第一次渲染場景時產生的,在OpenGL中可以使用glCopySubTexImage2D命令拷貝每個面到立方紋理中。然後使用立方貼圖進行繪製物體。可以預知,這個過程比靜態的立方貼圖要耗時,因為需要更多的繪製。下面是一個例子:在%26ldquo;泡泡%26rdquo;移動的過程中,其上的環境對映將會動態的改變。 下圖顯示了立方紋理中動態渲染的面: 如果場景中有多個反射物體,該技術將變得更費時,每個反射物體都需要一個動態的環境對映,如果物體還能反射其他物體反射來的光,事情將變得更加複雜。通常可能需要考慮光線跟蹤演算法來解決問題。下圖顯示了兩個反射的球體,仔細觀察,可以發現下面的球體包含了上面球體的反射。可以通過遞迴在上面的球體中產生下面球體的反射,但是下圖使用了18遍的渲染,因此不適合用來實時互動。 奇特的逐畫素光照 立方貼圖為實現逐畫素光照效果提供了支援。逐畫素光照包括計算每個畫素的散射、反射、環境光等成分(和逐頂點光照並進行插值對應)。前面討論的環境對映、穩定的鏡面高光以及天窗照明等立方貼圖的應用只是通用的逐畫素光照技術的特例。由於光照計算依賴於3D方向向量,通過方向向量訪問的立方貼圖便自然而然的作為表達逐畫素光照操作的方法。藝術級的逐畫素光照需要硬體支援畫素操作如向量點乘和紋理合成等。 在下面的例子中,所有的圖象通過OpenGL程式產生(在GeForce 256 GPU上), 新的立方紋理Targets 現在OpenGL中有1D 、 2D 、3D以及立方紋理。每個紋理Target有個相對應的列舉值,用來傳遞給glBindTexture, glTexParameterglTexImage等紋理函式。2D 紋理為GL_TEXTURE_2D. 1D 和 3D 紋理為GL_TEXTURE_1DGL_TEXTURE_3D。對於立方紋理為GL_TEXTURE_CUBE_MAP_EXT。但和GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D等不同的是,它不能傳遞給glTexImage2D和glCopySubTexImage2D函式。因為立方紋理中有六個mipmap集合(每個面對應一個)六個紋理Target為: GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT
GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT
GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT
GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 方便的地方是,這些Target之間有如下連續性: GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT = GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT + 2,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT = GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT + 5 這些Target會用在glTexImage2D, glCopySubTexImage2D,glGetTexImage, glCopyTexImage2D, glTexSubImage2D以及glGetTexLevelParameter等函式中。 設定立方紋理的圖象 下例為如何裝入立方紋理的六個面 GLubyte face[6][64][64][3]; for (i=0; i<6; i++) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT + i,
0, //level
GL_RGB8, //internal format
64, //width
64, //height
0, //border
GL_RGB, //format
GL_UNSIGNED_BYTE, //type
%26amp;face[i][0][0][0]); // pixel data
} 每個面為64x64的RGB圖象。 和2D紋理一樣,可以使用gluBuild2DMipmaps 函式來建立mipmap。如: gluBuild2DMipmaps(GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT,
GL_RGB8, 64, 64, GL_RGB, GL_UNSIGNED_BYTE, %26amp;face[1][0][0][0]); Enable and Disable立方紋理函式呼叫方法: glEnable(GL_TEXTURE_CUBE_MAP_EXT);
glDisable(GL_TEXTURE_CUBE_MAP_EXT); 將紋理座標對映到面上 方向 target sc tc ma
---------- --------------------------------- --- --- ---
+rx GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT -rz -ry rx
-rx GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT +rz -ry rx
+ry GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT +rx +rz ry
-ry GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT +rx -rz ry
+rz GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT +rx -ry rz
-rz GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT -rx -ry rz s = ( sc/|ma| + 1 ) / 2
t = ( tc/|ma| + 1 ) / 2 glTexCoord3f(s,t,r); // user-supplied direction vector for cube map texturing
glVertex3f(x,y,z); Reflection map example: glTexGenfv(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT);
glTexGenfv(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT);
glTexGenfv(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R); Normal map example: glTexGenfv(GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT);
glTexGenfv(GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT);
glTexGenfv(GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R); For these two modes to operate correctly, correct per-vertex normals must be supplied. 在使用前使用glGetString(GL_EXTENSIONS) ,確保擴充套件可用。如果OpenGL'標頭檔案沒有包括EXT_texture_cube_map擴充套件中的最新的列舉值, 可以將它定義在自己的標頭檔案中: #ifndef GL_EXT_texture_cube_map
/* EXT_texture_cube_map */
#define GL_EXT_texture_cube_map 1
#define GL_NORMAL_MAP_EXT 0x8511
#define GL_REFLECTION_MAP_EXT 0x8512
#define GL_TEXTURE_CUBE_MAP_EXT 0x8513
#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514
#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A
#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B
#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C
#endif