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 测试.