在渲染模型时控制它的透明通道(Alpha Channel)。在Unity中,有两种方法来实现透明效果:
透明度测试(Alpha Test)
透明度混合(Alpha Blending)
对于不透明(opaque)物体,不考虑它们的渲染顺序也能得到正确的排序结果,这是由于强大的深度缓冲的存在(depth buffer,也被称为z-buffer)。如果要渲染的片元,深度值和深度缓冲的比较,距离摄像机更远,就不应该渲染在屏幕上(有物体遮挡住了)。
透明度测试: “霸道极端”,只要一个片元的透明度不满足条件(通常是小于某个阈值),就舍弃。不需要关闭深度写入(ZWrite),要么完全透明看不见,要么完全不透明像不透明物体。
透明度混合: 可以得到真正的半透明效果,使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色值。需要关闭深度写入,不用关闭深度测试,需要非常小心物体的渲染顺序。(没有写入深度,如何进行深度测试,判断这个透明物体是在不透明物体前面还是后面?)
UnityShader的渲染顺序
使用透明度测试的代码类似这样:
SubShader{
Tags{"Queue"="AlphaTest"}
Pass{
...
}
}
使用透明度混合的代码类似这样:
SubShader{
Tags{"Queue"="Transparent"}
Pass{
ZWrite Off
...
}
}
透明度测试
在片元着色器中使用clip函数进行透明度测试,原理:
void clip(float4 x){
if(any(x<0)) discard;
}
Shader中使用
// Alpha test
clip(texColor.a-_Cutoff
// Equal to
// if((texColor.a-_Cutoff)<0.0){discard;}
透明度混合
Blend SrcFactor DstFactor 来进行混合,使用Blend命令的时候,Unity就打开了混合模式。经过混合后的新颜色是:
开启深度写入的半透明效果
为了解决由于关闭深度写入而造成的错误排序的情况,可以使用两个Pass来渲染模型:
第一个Pass开启深度写入,但不输出颜色,仅把该模型的深度值写入深度缓冲
第二个Pass进行正常的透明度混合,因为有了深度信息,就可以按照像素级别的深度排序结果进行渲染
损失了一定的性能,实现模型与后面背景的混合,但模型内部没有真正的半透明效果。
开启深度写入的Pass,只需要在原来使用的Pass前面增加一个新的Pass。
Tags{"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
// Extra pass that renders to depth buffer only
Pass{
ZWrite On
ColorMask 0 // 该Pass不写入任何颜色通道,即不输出任何颜色
}
Pass{
// ...透明度混合
}
ShaderLab的混合命令
混合还有很多其他用处,不仅仅用于透明度混合。
混合的实现:源颜色(source color)和目标颜色(destination color)。
源颜色S:由片元着色器产生的颜色
目标颜色D:从颜色缓冲中读取到的颜色值
输出颜色O:混合后的颜色,重新写入颜色缓冲
混合等式和参数
混合等式(blend equation):已知S和D,得到O的等式。
混合是一个逐片元的操作,不可编程,但高度可配置。可以设置混合时使用的运算操作、混合因子,需要两个等式分别用于RGB和A。
第一个命令使用同样的因子来混合RGB通道和A通道。使用这些因子进行加法混合时使用的混合等式:
ShaderLab支持的混合因子如下:
使用不同参数混合A通道的命令如下:
Blend SrcAlpha OneMinusSrcAlpha, One Zero
混合操作
以上的混合等式默认是使用加法,使用ShaderLab的混合操作命令BlendOp BlendOperation,可以进行更改:
常见的混合类型
// 正常(Normal),即透明度混合
Blend SrcAlpha OneMinusSrcAlpha
// 柔和相加(Soft Additive)
Blend OneMinusDstColor One
// 正片叠底(Multiply),即相乘
Blend DstColor Zero
// 两倍相乘(2x Multiply)
Blend DstColor SrcColor
// 变暗(Darken)
BlendOp Min
Blend One One
// 变亮(Lighten)
BlendOp Max
Blend One One
// 滤色(Screen)
Blend OneMinusDstColor One
// 等同于
Blend One OneMinusSrcColor
// 线性减淡(Linear Dodge)
Blend One One
上混合模式的公式,S源颜色、D颜色缓冲中的颜色(认为是后面物体的颜色吧)、O输出:
正常:O=SxSa+Dx(1-Sa)
柔和相加:O=Sx(1-Drgba)+D
正片叠底:O=SxDrgba=(SrxDr,SgxDg,SbxDb,SaxDa)
两倍相乘:O=SxDrgba+DxSrgba=(SrxDr+DrxSr,SgxDg+DgxSg,SbxDb+DbxSb,SaxDa+DaxSa)=2(SrxDr,SgxDg,SbxDb,SaxDa)
变暗:O=Min(S,D)
变亮:O=Max(S,D)
滤色:O=Sx(1-Drgba)+D,同柔和相加?
线性减淡:O=S+D
双面渲染的透明效果
之前无论是透明度测试还是透明度混合,都无法观测到正方体内部和背面的形状。这是因为默认下渲染引擎剔除了物体背面(相对于相机的方向),只渲染了正面。可以使用Cull指令来控制需要剔除哪个面,语法如下:
Cull Back|Front|Off
Back 背对摄像机的渲染图元不会被渲染(默认情况下的剔除状态)
Front 朝向摄像机的渲染图元不会被渲染
Off 关闭剔除功能 所有渲染图元都会被渲染
透明度测试的双面渲染
在Pass的渲染设置中使用Cull指令来关闭剔除:
Pass{
Tags{"LightMode"="ForwardBase"}
// Turn off culling
Cull Off
CGPROGRAM
...
透明度混合的双面渲染
因为透明度混合需要关闭深度写入,所以不能直接关闭剔除功能,这样无法保证同一物体的正面和背面图元的渲染顺序。
为此我们把双面渲染的工作分成两个Pass——第一个Pass只渲染背面,第二个Pass只渲染正面。
Pass{
Tags{"LightMode"="ForwardBase"}
// First pass renders only back faces
Cull Front
...
}
Pass{
Tags{"LightMode"="ForwardBase"}
// Second pass renders only front faces
Cull Back
...
}