GLSL 程式設計/Blender/透明紋理

本教程涵蓋了Alpha 紋理圖的各種常見用途,即 RGBA 紋理影像,其 A(Alpha)分量指定了紋素的不透明度。
它將紋理球體教程的著色器程式碼與截面教程和透明度教程中介紹的概念相結合。
如果您還沒有閱讀這些教程,這將是一個很好的閱讀機會。
讓我們從截面教程中解釋的丟棄片段開始。按照紋理球體教程中描述的步驟,將左側的影像分配到具有以下著色器的球體的材質
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 = """
uniform float cutoff;
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);
if (gl_FragColor.a < cutoff)
// alpha value less than user-specified threshold?
{
discard; // yes: discard this fragment
}
}
"""
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.setUniform1f('cutoff', cont.owner.get('cutoff'))
此指令碼使用名為cutoff的遊戲屬性來控制相同名稱的統一變數。如果您啟動遊戲,Blender 應該在系統控制檯中(可以透過幫助 > 切換系統控制檯在資訊視窗的選單中獲取)抱怨在以下行中缺少浮點數
shader.setUniform1f('cutoff', cont.owner.get('cutoff'))
因為我們還沒有定義遊戲屬性。要做到這一點,請轉到邏輯編輯器並單擊新增遊戲屬性(如果未開啟,請按n開啟屬性)。名稱應為cutoff,型別應為float。將值設定為(任何略微遠離 0 和 1 的值都可以)。
如果您現在啟動遊戲引擎,片段著色器應該讀取 RGBA 紋理並比較 Alpha 值與遊戲屬性cutoff中指定的閾值。如果 Alpha 值小於閾值,則片段將被丟棄,表面將顯示為透明。
由於我們可以透過透明的部分,因此可以像截面教程中描述的那樣停用背面剔除:在資訊視窗的選單中選擇Blender 遊戲引擎,然後(在球體的屬性視窗的材質選項卡中)取消選中遊戲設定 > 背面剔除。(注意,即使未在材質選項卡中選中透明度,通常的深度測試也會確保三角形遮擋是正確的。)
透明度教程描述瞭如何使用 Alpha 混合渲染半透明物體。將此與 RGBA 紋理相結合,得到以下片段著色器(頂點著色器與上面相同)
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 = """
uniform float cutoff;
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);
gl_FragColor = texture2D(textureUnit, longitudeLatitude);
}
"""
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)
mat.setBlending(bge.logic.BL_SRC_ALPHA,
bge.logic.BL_ONE_MINUS_SRC_ALPHA)
不幸的是,如果您停用背面剔除,即使啟用了透明度 > Z 透明度(在屬性視窗的材質選項卡中),您也會在 Blender 2.63 中看到嚴重的渲染偽像。顯然,Blender 2.63 並不總是根據深度對所有三角形進行排序,這在這種情況下是一個問題。但是,將遊戲設定 > Alpha 混合更改為Alpha 排序將告訴 Blender 對該材質的所有三角形進行排序,從而糾正該問題。(這應該只在需要時使用,因為排序成本很高,通常沒有必要)
請注意,在該特定紋理影像中,所有 Alpha 值為 0 的紋素都是黑色的。實際上,該紋理影像中的顏色已與其 Alpha 值“預乘”。(這種顏色也稱為“不透明度加權”。)因此,對於該特定影像,我們實際上應該指定預乘顏色的混合方程,以避免在混合方程中將顏色與其 Alpha 值再次相乘。因此,著色器(對於該特定紋理影像)的改進是使用以下混合規範
mat.setBlending(bge.logic.BL_ONE, bge.logic.BL_ONE_MINUS_SRC_ALPHA)

我們不應該在沒有對所介紹技術的更實際應用的情況下結束本教程。左側是地球的影像,帶有半透明的藍色海洋,我在 Wikimedia Commons 上找到的。有一些照明(或輪廓增強),我沒有嘗試再現。相反,我只嘗試使用以下著色器再現半透明海洋的基本思想,該著色器忽略了紋理圖的 RGB 顏色,並根據 Alpha 值用特定顏色替換它們
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 = """
uniform float cutoff;
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);
gl_FragColor = texture2D(textureUnit, longitudeLatitude);
if (gl_FragColor.a > 0.5) // opaque
{
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); // opaque green
}
else // transparent
{
gl_FragColor = vec4(0.0, 0.0, 0.5, 0.7);
// semitransparent dark blue
}
}
"""
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)
mat.setBlending(bge.logic.BL_SRC_ALPHA,
bge.logic.BL_ONE_MINUS_SRC_ALPHA)
如果您停用背面剔除,您會注意到 Blender 2.63 中存在渲染偽像。希望這個問題會在未來的版本中得到改善。
當然,將照明和輪廓增強新增到此著色器會很有趣。還可以更改不透明的綠色以考慮紋理顏色,例如使用
gl_FragColor = vec4(0.5 * gl_FragColor.r, 2.0 * gl_FragColor.g, 0.5 * gl_FragColor.b, 1.0);
它透過將綠色分量乘以來強調綠色分量,並透過將紅色和藍色分量乘以來減弱紅色和藍色分量。但是,這會導致綠色過飽和,並被鉗制到最大強度。這可以透過將綠色分量到最大強度 1 的差值減半來避免。此差值是1.0 - gl_FragColor.g;它的一半是0.5 * (1.0 - gl_FragColor.g),對應於此減小的最大強度距離的值是:1.0 - 0.5 * (1.0 - gl_FragColor.g)。因此,為了避免綠色過飽和,我們可以使用(而不是不透明的綠色)
gl_FragColor = vec4(0.5 * gl_FragColor.r, 1.0 - 0.5 * (1.0 - gl_FragColor.g), 0.5 * gl_FragColor.b, 1.0);
在實踐中,人們必須嘗試各種這種顏色變換的可能性。為此,使用數值著色器屬性(例如上面一行中的因子 0.5)對於互動式地探索可能性特別有用。
恭喜!您已經完成了本篇相當長的教程。我們已經瞭解了
- 如何將丟棄片段與 Alpha 紋理圖結合起來。
- 如何將 Alpha 紋理圖用於混合。
- 如何使用 Alpha 紋理圖來確定顏色。
如果您仍然想知道更多