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 测试。
- 关于混合的信息,您应该阅读 “透明度”部分。