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

本教程涵蓋了 **alpha 紋理貼圖** 的各種常見用途,即具有指定紋素不透明度的 A(alpha)分量的 RGBA 紋理影像。
它將 “紋理球體”部分 的著色器程式碼與在 “截面”部分 和 “透明度”部分 中介紹的概念結合起來。
如果您還沒有閱讀這些教程,現在是一個很好的機會去閱讀它們。
讓我們從 “截面”部分 中解釋的丟棄片段開始。按照 “紋理球體”部分 中描述的步驟,並將左側的影像分配給帶有以下著色器的球體材質
Shader "GLSL texturing with alpha discard" {
Properties {
_MainTex ("RGBA Texture Image", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Float) = 0.5
}
SubShader {
Pass {
Cull Off // since the front is partially transparent,
// we shouldn't cull the back
GLSLPROGRAM
uniform sampler2D _MainTex;
uniform float _Cutoff;
varying vec4 textureCoordinates;
#ifdef VERTEX
void main()
{
textureCoordinates = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor =
texture2D(_MainTex, vec2(textureCoordinates));
if (gl_FragColor.a < _Cutoff)
// alpha value less than user-specified threshold?
{
discard; // yes: discard this fragment
}
}
#endif
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Unlit/Transparent Cutout"
}
片段著色器讀取 RGBA 紋理,並將 alpha 值與使用者指定的閾值進行比較。如果 alpha 值小於閾值,則丟棄該片段,表面看起來是透明的。
與上面描述的效果相同,可以透過 alpha 測試實現。alpha 測試的優勢在於它在不支援 GLSL 的舊硬體上也能執行。以下程式碼的結果與上面的著色器大體一致
Shader "GLSL texturing with alpha test" {
Properties {
_MainTex ("RGBA Texture Image", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Float) = 0.5
}
SubShader {
Pass {
Cull Off // since the front is partially transparent,
// we shouldn't cull the back
AlphaTest Greater [_Cutoff] // specify alpha test:
// fragment passes if alpha is greater than _Cutoff
GLSLPROGRAM
uniform sampler2D _MainTex;
uniform float _Cutoff;
varying vec4 textureCoordinates;
#ifdef VERTEX
void main()
{
textureCoordinates = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor =
texture2D(_MainTex, vec2(textureCoordinates));
}
#endif
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Unlit/Transparent Cutout"
}
在這裡,不需要顯式的 discard 指令,但必須將 alpha 測試配置為僅透過 alpha 值大於 _Cutoff 屬性的片段;否則它們將被丟棄。有關 alpha 測試的更多詳細資訊,請參閱 Unity 的 ShaderLab 文件。
請注意,alpha 測試和 discard 指令在某些平臺上速度很慢,尤其是在移動裝置上。因此,混合通常是更有效率的替代方法。
“透明度”部分 描述瞭如何使用 alpha 混合渲染半透明物件。將此與 RGBA 紋理相結合,得出以下程式碼
Shader "GLSL texturing with alpha blending" {
Properties {
_MainTex ("RGBA Texture Image", 2D) = "white" {}
}
SubShader {
Tags {"Queue" = "Transparent"}
Pass {
Cull Front // first render the back faces
ZWrite Off // don't write to depth buffer
// in order not to occlude other objects
Blend SrcAlpha OneMinusSrcAlpha
// blend based on the fragment's alpha value
GLSLPROGRAM
uniform sampler2D _MainTex;
varying vec4 textureCoordinates;
#ifdef VERTEX
void main()
{
textureCoordinates = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor =
texture2D(_MainTex, vec2(textureCoordinates));
}
#endif
ENDGLSL
}
Pass {
Cull Back // now render the front faces
ZWrite Off // don't write to depth buffer
// in order not to occlude other objects
Blend SrcAlpha OneMinusSrcAlpha
// blend based on the fragment's alpha value
GLSLPROGRAM
uniform sampler2D _MainTex;
varying vec4 textureCoordinates;
#ifdef VERTEX
void main()
{
textureCoordinates = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor =
texture2D(_MainTex, vec2(textureCoordinates));
}
#endif
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Unlit/Transparent"
}
請注意,在這個特定的紋理影像中,所有 alpha 值為 0 的紋素都是黑色的。實際上,此紋理影像中的顏色是“預乘”了它們的 alpha 值。(這種顏色也稱為“不透明度加權”。)因此,對於此特定影像,我們實際上應該在混合方程中指定預乘顏色的混合方程,以避免在混合方程中將顏色與它們的 alpha 值再次相乘。因此,對著色器(對於此特定紋理影像)的改進是採用以下混合規範,用於兩個通道
Blend One OneMinusSrcAlpha

我們不應該在沒有對所呈現技術的更實用應用的情況下結束本教程。左側是帶有半透明藍色海洋的地球影像,我在 Wikimedia Commons 上找到的。有一些照明(或輪廓增強)正在進行,我沒有嘗試重現。相反,我只嘗試重現帶有以下著色器的半透明海洋的基本想法,它忽略了紋理貼圖的 RGB 顏色,並根據 alpha 值將其替換為特定顏色
Shader "GLSL semitransparent colors based on alpha" {
Properties {
_MainTex ("RGBA Texture Image", 2D) = "white" {}
}
SubShader {
Tags {"Queue" = "Transparent"}
Pass {
Cull Front // first render the back faces
ZWrite Off // don't write to depth buffer
// in order not to occlude other objects
Blend SrcAlpha OneMinusSrcAlpha
// blend based on the fragment's alpha value
GLSLPROGRAM
uniform sampler2D _MainTex;
varying vec4 textureCoordinates;
#ifdef VERTEX
void main()
{
textureCoordinates = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor =
texture2D(_MainTex, vec2(textureCoordinates));
if (gl_FragColor.a > 0.5) // opaque back face?
{
gl_FragColor = vec4(0.0, 0.0, 0.2, 1.0);
// opaque dark blue
}
else // transparent back face?
{
gl_FragColor = vec4(0.0, 0.0, 1.0, 0.3);
// semitransparent dark blue
}
}
#endif
ENDGLSL
}
Pass {
Cull Back // now render the front faces
ZWrite Off // don't write to depth buffer
// in order not to occlude other objects
Blend SrcAlpha OneMinusSrcAlpha
// blend based on the fragment's alpha value
GLSLPROGRAM
uniform sampler2D _MainTex;
varying vec4 textureCoordinates;
#ifdef VERTEX
void main()
{
textureCoordinates = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor =
texture2D(_MainTex, vec2(textureCoordinates));
if (gl_FragColor.a > 0.5) // opaque front face?
{
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
// opaque green
}
else // transparent front face
{
gl_FragColor = vec4(0.0, 0.0, 1.0, 0.3);
// semitransparent dark blue
}
}
#endif
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Unlit/Transparent"
}
當然,為這個著色器新增照明和輪廓增強會很有趣。還可以更改不透明的綠色顏色以考慮紋理顏色,例如,使用
gl_FragColor = vec4(0.5 * gl_FragColor.r, 2.0 * gl_FragColor.g, 0.5 * gl_FragColor.b, 1.0);
這會透過乘以 2 來突出綠色分量,並透過乘以 0.5 來使紅色和藍色分量變暗。但是,這會導致過飽和的綠色,它會被鉗制到最大強度。可以透過將綠色分量與最大強度 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 紋理貼圖進行混合。
- 如何使用 alpha 紋理貼圖來確定顏色。
如果您還想了解更多
- 關於紋理貼圖的資訊,您應該閱讀 “紋理球體”部分。
- 關於丟棄片段的資訊,您應該閱讀 “截面”部分。
- 關於 alpha 測試的資訊,您應該閱讀 Unity 的 ShaderLab 文件:Alpha 測試。
- 關於混合的資訊,您應該閱讀 “透明度”部分。