本教程介绍了alpha纹理贴图的各种常见用法,即具有A(alpha)分量的RGBA纹理图像,该分量指定了纹理像素的不透明度。
它将“Textured Spheres” 部分的着色器代码和“Cutaways”部分和“Transparency”部分中引入的概念结合在一起。
丢弃透明片元
让我们从“Cutaways”一节中介绍的丢弃片元开始。请按照“Textured Spheres”部分中所述的步骤进行操作,并使用以下着色器将图像分配到球体的左侧:
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值小于阈值,则片元被丢弃,并且表面看起来是透明的。
清注意,在某些平台上,尤其是在移动设备上,丢弃指定的速度非常慢,因此Blend通常是更有效的选择。
Blend
“Transparency”部分介绍了如何使用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值再次相乘。 因此,着色器的改进(针对此特定的纹理图像)是在两个过程中均采用以下混合规范:
Blend One OneMinusSrcAlpha
与自定义颜色混合
左侧是作者在Wikimedia Commons上发现的具有半透明蓝色海洋的地球图像。有一些照明(或轮廓增强)正在进行,作者没有尝试重现。相反,仅尝试使用以下着色器重现半透明海洋的基本概念,该着色器将忽略纹理贴图的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纹理贴图如何用于确定颜色。