GLSL 程式設計/Blender/紋理球體

本教程介紹了 **紋理對映**。
它是 Blender 中關於在 GLSL 著色器中進行紋理化的一系列教程中的第一個。在本教程中,我們從球體上的單一紋理貼圖開始。更具體地說,我們將地球表面的影像對映到一個球體上。在此基礎上,後續教程將涵蓋諸如紋理化表面的光照、透明紋理、多紋理、光澤對映等主題。


“紋理對映”(或“紋理化”)的基本思想是將影像(即“紋理”或“紋理貼圖”)對映到三角形網格上;換句話說,將平面圖像放到三維形狀的表面上。
為此,定義了“紋理座標”,它們簡單地指定紋理(即影像)中的位置。水平座標正式稱為 S,垂直座標稱為 T。但是,通常將它們稱為 x 和 y。在動畫和建模工具中,紋理座標通常稱為 U 和 V。
為了將紋理影像對映到網格,網格的每個頂點都給定了一對紋理座標。(這個過程(以及結果)有時被稱為“UV 對映”,因為每個頂點都對映到 UV 空間中的一個點。)因此,每個頂點都對映到紋理影像中的一個點。然後,可以為三個頂點之間的任何三角形的每個點插值頂點的紋理座標,從而網格所有三角形的每個點都可以具有一個(插值)紋理座標對。這些紋理座標將網格的每個點對映到紋理貼圖中的特定位置,因此對映到該位置的顏色。因此,渲染紋理對映網格包括對所有可見點進行兩個步驟:插值紋理座標並在插值紋理座標指定的位置查詢紋理影像的顏色。
在 OpenGL 中,任何有效的浮點數都是有效的紋理座標。但是,當 GPU 被要求查詢紋理影像的畫素(或“紋素”(例如使用下面描述的“texture2D”指令))時,它將在內部將紋理座標對映到 0 到 1 之間的範圍,具體方式取決於“環繞模式”。例如,環繞模式“重複”基本上使用紋理座標的小數部分來確定 0 到 1 之間的紋理座標。另一方面,環繞模式“鉗位”將紋理座標鉗位到此範圍。然後,這些 0 到 1 之間的內部紋理座標用於確定紋理影像中的位置: 指定紋理影像的左下角; 右下角; 左上角;等等。OpenGL 的環繞模式對應於 Blender 中 **屬性 > 紋理選項卡 > 影像對映** 下的設定。不幸的是,Blender 似乎沒有設定 OpenGL 環繞模式,但它始終設定為“重複”。
要將左側的地球表面影像對映到 Blender 中的球體上,您首先需要將此影像下載到您的計算機上:單擊左側的影像,直到您看到一個更大的版本,然後將其儲存(通常使用右鍵單擊)到您的計算機上(記住您將它儲存到哪裡)。然後切換到 Blender 並新增一個球體(在 **資訊視窗** 中選擇 **新增 > 網格 > UV 球體**),在 **3D 檢視** 中選擇它(透過右鍵單擊),啟用平滑著色(在 **3D 檢視** 的 **工具架** 中,如果它未處於活動狀態,請按 **t**),確保 **顯示 > 著色:GLSL** 在 **3D 檢視** 的 **屬性** 中設定(如果它們未顯示,請按 **n**),**並且**將 **3D 檢視** 的 **視口著色** 切換到 **紋理**(**3D 檢視** 中主選單右側的第二個圖示)。現在(球體仍然處於選中狀態)新增一個材質(在 **屬性視窗 > 材質選項卡 > 新建** 中)。然後新增一個新的紋理(在 **屬性視窗 > 紋理選項卡 > 新建** 中),為 **型別** 選擇 **影像或電影**,然後單擊 **影像 > 開啟**。在檔案瀏覽器中選擇您的檔案,然後單擊 **開啟影像**(或在檔案瀏覽器中雙擊它)。影像現在應該出現在 **紋理選項卡** 的預覽部分,Blender 應該將它放到 3D 檢視中的球體上。
現在您應該確保 **屬性視窗 > 紋理選項卡 > 對映** 中的 **座標** 設定為 **生成**。這意味著我們的紋理座標將設定為物件空間中的座標。在任何建模工具中指定或生成紋理座標(即 UV)是一個完全不同的主題,超出了本教程的範圍。
使用這些設定,Blender 也會將紋理座標傳送到頂點著色器。(實際上,我們也可以使用 gl_Vertex 中的物件座標,因為在這種情況下它們是相同的。)因此,我們可以編寫一個接收紋理座標並將其傳遞給片段著色器的頂點著色器。然後,片段著色器對四維紋理座標進行一些計算以計算經度和緯度(縮放至 0 到 1 的範圍),這將用作這裡的紋理座標。通常,此步驟是不必要的,因為紋理座標應該已經正確地指定了在哪裡查詢紋理影像。(實際上,應該避免在片段著色器中對紋理座標進行任何此類處理,以提高效能;這裡我只是使用此技巧來避免設定適當的 UV 紋理座標。)設定著色器的 Python 指令碼可能是
import bge
cont = bge.logic.getCurrentController()
VertexShader = """
varying vec4 texCoords; // texture coordinates at this vertex
void main()
{
texCoords = gl_MultiTexCoord0; // in this case equal to gl_Vertex
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
"""
FragmentShader = """
varying vec4 texCoords;
// interpolated texture coordinates for this fragment
uniform sampler2D textureUnit;
// a small integer identifying a texture image
void main()
{
vec2 longitudeLatitude = vec2(
(atan(texCoords.y, texCoords.x) / 3.1415926 + 1.0) * 0.5,
1.0 - acos(texCoords.z) / 3.1415926);
// processing of the texture coordinates;
// this is unnecessary if correct texture coordinates
// are specified within Blender
gl_FragColor = texture2D(textureUnit, longitudeLatitude);
// look up the color of the texture image specified
// by the uniform "textureUnit" at the position
// specified by "longitudeLatitude.x" and
// "longitudeLatitude.y" and return it in "gl_FragColor"
}
"""
mesh = cont.owner.meshes[0]
for mat in mesh.materials:
shader = mat.getShader()
if shader != None:
if not shader.isValid():
shader.setSource(VertexShader, FragmentShader, 1)
shader.setSampler('textureUnit', 0)
請注意最後一行
shader.setSampler('textureUnit', 0)
在 Python 指令碼中:它將統一變數 textureUnity 設定為 0。這指定應該使用 **屬性視窗 > 紋理選項卡** 中列表中的第一個紋理。值 1 將選擇列表中的第二個紋理,等等。實際上,對於您在片段著色器中使用的每個 sampler2D 變數,您都必須使用如上所示的 setSampler 呼叫來設定其值。實際上,一個 sampler2D 統一變數指定了 GPU 的紋理單元。(紋理單元是硬體的一部分,負責查詢和插值紋理影像中的顏色。)GPU 的紋理單元數量在內建常量 gl_MaxTextureUnits 中可用,它通常為 4 或 8。因此,片段著色器中可用的不同紋理影像的數量僅限於此數量。
如果一切順利,當您按下 **p** 啟動遊戲引擎時,紋理影像現在應該正確地對映到球體上。(否則 Blender 會將其以不同的方式對映到球體上。)恭喜!
由於許多技術都使用紋理對映,因此理解這裡發生的事情非常有益。因此,讓我們回顧一下著色器程式碼。
Blender 球體物件的頂點帶有屬性資料 gl_MultiTexCoord0,用於每個頂點,它指定紋理座標,在我們這個特定的例子中,這些紋理座標與屬性 gl_Vertex 中的值相同,gl_Vertex 指定了物體空間中的位置。
然後,頂點著色器將每個頂點的紋理座標寫入到變數 texCoords 中。對於三角形的每個片段(即每個覆蓋的畫素),在三個三角形頂點處此變數的值會進行插值(參見 “光柵化” 中的描述),並將插值後的紋理座標傳遞給片元著色器。在這個特定例子中,片元著色器在 longitudeLatitude 中計算新的紋理座標。通常,這並不必要,因為應該在 Blender 中使用 UV 對映來指定正確的紋理座標。然後,片元著色器使用紋理座標在由 uniform textureUnit 指定的紋理影像中查詢紋理空間中插值位置的顏色,並將此顏色返回到 gl_FragColor 中,然後將其寫入幀緩衝區並在螢幕上顯示。
為了理解其他教程中介紹的更復雜的紋理對映技術,您必須對這些步驟有一個很好的瞭解。
您已經學習完了最重要的教程之一。我們已經學習了
- 如何為紋理設定 Blender 物件。
- 如何匯入紋理影像。
- 頂點著色器和片元著色器如何協同工作將紋理影像對映到網格上。
如果您想了解更多
- 關於頂點著色器和片元著色器之間的資料流進出(即頂點屬性、變數等),您應該閱讀 “OpenGL ES 2.0 管道” 的描述。
- 關於片元著色器中變數的插值,您應該閱讀 “光柵化” 的討論。