透明效果
透明是游戏中经常要使用的一种效果。在实时渲染中要实现透明效果,通常会子渲染模型时控制它的透明通道(Alpha Channel)。当开启透明混合后,当一个物体被渲染到屏幕上时,每个片元除了颜色值和深度值之外。他还有另一个属性——透明度。当透明度为1时,表示该像素是完全不透明的,而当其为0时,则表示该像素完全不会显示。
在Unity中,我们通常使用两种方法来实现透明效果:第一种是使用透明度测试(Alpha Test),这种方法其实无法得到真正的半透明效果;另一种是透明度混合(Alpha Blending)。
在之前的学习中,我们从没有强调过渲染顺序的问题。也就是说,当场景中包含很多模型时,我们并没有考虑是先渲染A,再渲染B,最后再渲染C,还是按照其他的顺序来渲染。事实上,对于不透明(opaque)物体,不考虑他们的渲染顺序也能得到正确的排列效果,这是由于强大的深度缓冲(depth buffer,也被称为z-buffer)的存在。在实时渲染中,深度缓冲是用于解决可见性(visibility)问题的,它可以决定那个物体的那些部分会被渲染在前面,而那些部分会被其他物体遮挡。它的基本思想是:根据深度缓冲中的值进行比较(如果开启了深度测试),如果它的值距离摄像机更远,那么说明这个片元不应该被渲染到屏幕上(有物体挡住了它);否则,这个片元应该覆盖掉此时颜色缓冲中的像素值,并把它的深度值更新到深度缓冲中(如果开启了深度写入)/
使用深度缓冲,可以让我们不用关心不透明物体的渲染顺序,例如A挡住了B,即便我们先渲染A再渲染B也不用担心B会遮盖掉A,因为在进行深度测试时会判断出B距离摄像机更远,也就不会写入到颜色缓冲中。但如果想要实现透明效果,事情就不那么简单了,这是因为,当使用透明度混合时,我们关闭了深度写入(ZWrite)。
简单来说,透明度测试和透明度混合的基本原理如下。
·透明度测试:它采用了一种“霸道极端”的机制,只要一个片元的透明度不满足条件(通常是小于某个阀值),那么它对应的片元就会被舍弃。被舍弃的片元将不会在进行任何处理,也不会对颜色缓冲产生任何影响;否则,就会按照普通的不透明物体的处理方式来处理它,即进行深度测试、深度写入等。也就是说,透明度测试是不需要关闭深度写入的,它和其他不透明物体最大的不同就是它会根据透明度来舍弃一些片元。虽然简单,但是它产生的效果也很极端,要么完全透明,即看不到,要么完全不透明,就像不透明物体那样。
·透明度混合:这种方法可以得到真正的半透明效果。它会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是,透明度混合需要关闭深度写入,这使得我们要非常小心物体的渲染顺序。需要注意的是,透明度混合只关闭了深度写入,但没有关闭深度测试。这意味着,当使用透明度混合渲染一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值,如果它的深度值距离摄影机更远,那么就不会再进行混合操作。这一点决定了,当一个不透明物体出现在一个透明物体的前面,而我们先渲染了不透明物体,它仍然可以正常地遮挡住透明物体。也就是说,对于透明度混合来说,深度缓冲是只读的。
8.1 渲染顺序很重要
8.2 渲染顺序
Unity为了解决渲染顺序的问题提供了渲染队列(render queue)这一解决方案。我们可以使用SubShader的Queue标签来决定我们的模型将归于哪个渲染队列。Unity在内部使用了一系列整数索引来表示每个渲染列队,且索引号越小表示越早被渲染。在Unity5中,Unity提前定义了5个渲染列队(与Unity5之前的版本相比多了一个AlphaTest渲染队列),当然在每个队列中间我们可以使用其他队列。
因此,如果我们想要通过透明度测试实现透明效果,代码中应该包含类似下面的代码:
SubShader{
Tags{ "Queue" = "AlphaTest"}
Pass{
...
}
}
如果我们想要通过透明度混合来实现透明效果,代码中应该包含类似下面的代码:
SubShader{
Tags{ "Queue" = "Transparent"}
Pass{
ZWrite Off
...
}
}
其中, ZWrite Off用于关闭深度写入,在这里我们选择把它写在Pass中。我们也可以写在SubShader中,这意味着该SubShader下的所有Pass都会关闭深度写入。
8.3 透明度测试
只要一个片元的透明度不满足条件(通常是小于某个阀值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响;否则,就会按照普通的不透明物体的处理方式来处理它。
通常,我们会在片元着色器中使用clip函数来进行透明度测试。clip是CG中的一个函数,它的定义如下:
函数:void clip(float4 x);void clip(float3 x);void clip(float2 x);void clip(float1 x);void clip(float x);
参数:裁剪时使用的标量或矢量条件。
描述:如果给定参数的任何一个分量是负数,就会舍弃当前像素的输出颜色。它等同于下面的代码:
void clip(float4 x)
{
if(any(x<0))
discard;
}
在本节中,我们使用下图中的半透明纹理来实现透明度测试。(transparent_texture.psd)
为此,我们需要进行如下准备工作。
(1)创建新场景(Scene_8_3)。
(2)创建材质(AlphaBlendMat)。
(3)创建Unity Shader(Chapter8-AlphaTest),并赋给第二步中的材质。
(4)创建立方体,并把第二步中的材质赋给该模型。
Shader "Unity Shaders Book/Chapter 8/Alpha Test" {
Properties {
//为了在材质面板中控制透明度测试时使用的阀值,我们在Properties语义块中声明一个范围在[0,1]之间的属性_Cutoff
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
}
//_Cutoff参数用于决定我们调用clip进行透明度测试时使用的判断条件。它的范围是[0,1],
//这是因为纹理像素的透明度就是在此范围内。
SubShader {
Tags {
"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
//已知渲染顺序的重要性,并且知道在Unity中透明度测试使用的渲染队列是名为AlphaTest的队列,
//因此我们需要把Queue标签设置为AlphaTest。而RenderType标签可以让Unity把这个Shader
//归入到提前定义的组(这里就是TransparentCutout组)中,以指明该Shader是一个使用了
//透明度测试的Shader。RenderType标签通常被用于着色器替换功能。我们还把IgnoreProjector
//设置为True,这意味着