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

本教程涵蓋了alpha 紋理貼圖的各種常見用途,即具有 A (alpha) 分量的 RGBA 紋理影像,該分量指定了紋素的不透明度。
它將“紋理球體”部分的著色器程式碼與在“切除”部分和“透明度”部分中介紹的概念相結合。
如果您還沒有閱讀這些教程,現在是一個很好的機會。
讓我們從“切除”部分中解釋的丟棄片段開始。按照“紋理球體”部分中描述的步驟,並將左側的影像分配給帶有以下著色器的球體的材質
Shader "Cg 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
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform sampler2D _MainTex;
uniform float _Cutoff;
struct vertexInput {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 tex : TEXCOORD0;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
output.tex = input.texcoord;
output.pos = UnityObjectToClipPos(input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
float4 textureColor = tex2D(_MainTex, input.tex.xy);
if (textureColor.a < _Cutoff)
// alpha value less than user-specified threshold?
{
discard; // yes: discard this fragment
}
return textureColor;
}
ENDCG
}
}
Fallback "Unlit/Transparent Cutout"
}
片段著色器讀取 RGBA 紋理並將 alpha 值與使用者指定的閾值進行比較。如果 alpha 值小於閾值,則丟棄該片段,表面看起來透明。
請注意,discard 指令在某些平臺上速度相當慢,尤其是在移動裝置上。因此,混合通常是一個更有效的替代方案。
“透明度”部分描述瞭如何使用 alpha 混合渲染半透明物體。將此與 RGBA 紋理相結合,得到以下程式碼
Shader "Cg 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
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform sampler2D _MainTex;
struct vertexInput {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 tex : TEXCOORD0;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
output.tex = input.texcoord;
output.pos = UnityObjectToClipPos(input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
return tex2D(_MainTex, input.tex.xy);
}
ENDCG
}
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
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform sampler2D _MainTex;
struct vertexInput {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 tex : TEXCOORD0;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
output.tex = input.texcoord;
output.pos = UnityObjectToClipPos(input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
return tex2D(_MainTex, input.tex.xy);
}
ENDCG
}
}
Fallback "Unlit/Transparent"
}
請注意,所有 alpha 值為 0 的紋素在此特定紋理影像中都是黑色的。實際上,此紋理影像中的顏色已“預乘”其 alpha 值。(此類顏色也稱為“不透明度加權”。)因此,對於此特定影像,我們實際上應該在混合方程中指定預乘顏色的混合方程,以避免在混合方程中將顏色與其 alpha 值再次相乘。因此,對於此特定紋理影像,著色器的改進是採用以下混合規範在兩個通道中
混合一 一減源alpha

我們在結束本教程之前,不應該遺漏對所介紹技術的更實用的應用。左側是具有半透明藍色海洋的地球影像,我在維基共享資源上找到的。有一些燈光(或輪廓增強)在進行,我沒有嘗試複製。相反,我只嘗試用以下著色器複製半透明海洋的基本理念,該著色器忽略紋理貼圖的 RGB 顏色,並根據 alpha 值用特定顏色替換它們
Shader "Cg 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
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform sampler2D _MainTex;
struct vertexInput {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 tex : TEXCOORD0;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
output.tex = input.texcoord;
output.pos = UnityObjectToClipPos(input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
float4 color = tex2D(_MainTex, input.tex.xy);
if (color.a > 0.5) // opaque back face?
{
color = float4(0.0, 0.0, 0.2, 1.0);
// opaque dark blue
}
else // transparent back face?
{
color = float4(0.0, 0.0, 1.0, 0.3);
// semitransparent green
}
return color;
}
ENDCG
}
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
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform sampler2D _MainTex;
struct vertexInput {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 tex : TEXCOORD0;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
output.tex = input.texcoord;
output.pos = UnityObjectToClipPos(input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
float4 color = tex2D(_MainTex, input.tex.xy);
if (color.a > 0.5) // opaque front face?
{
color = float4(0.0, 1.0, 0.0, 1.0);
// opaque green
}
else // transparent front face
{
color = float4(0.0, 0.0, 1.0, 0.3);
// semitransparent dark blue
}
return color;
}
ENDCG
}
}
Fallback "Unlit/Transparent"
}
當然,在該著色器中新增燈光和輪廓增強會很有趣。也可以更改不透明的綠色以考慮紋理顏色,例如使用
color = float4(0.5 * color.r, 2.0 * color.g, 0.5 * color.b, 1.0);
這透過將綠色分量乘以 2 來強調綠色分量,並透過將紅色和藍色分量乘以 0.5 來使它們變暗。但是,這會導致綠色過飽和,並被鉗制到最大強度。可以透過將綠色分量與最大強度 1 之間的差值減半來避免這種情況。這個差值是 1.0 - color.g;它的二分之一是 0.5 * (1.0 - color.g),與該減少的距離到最大強度相對應的值是:1.0 - 0.5 * (1.0 - color.g)。因此,為了避免綠色過飽和,我們可以使用(代替不透明的綠色)
color = float4(0.5 * color.r, 1.0 - 0.5 * (1.0 - color.g), 0.5 * color.b, 1.0);
實際上,人們必須嘗試此類顏色轉換的各種可能性。為此,使用數字著色器屬性(例如上面行中的因子 0.5)對於以互動方式探索可能性特別有用。
恭喜!您已完成本篇相當長的教程。我們已經瞭解了
- 如何將丟棄片段與 alpha 紋理貼圖結合使用。
- 如何將 alpha 紋理貼圖用於混合。
- 如何將 alpha 紋理貼圖用於確定顏色。
如果您還想了解更多資訊
- 關於紋理,請閱讀“紋理球體”部分.
- 關於丟棄片段,請閱讀“切除”部分.
- 關於混合,請閱讀“透明度”部分.
- 關於固定功能 alpha 測試,請閱讀Unity 的 ShaderLab 文件:Alpha 測試.