【Unity Shader学习笔记】Unity透明效果-透明度测试与透明度混合
1、基本概念
1.1、基本概念
在实时渲染中实现透明效果,需要在渲染模型时控制它的透明通道(Alpha Channel)。
透明度为1代表该像素是完全不透明的;0代表完全透明。
Unity中可以使用两种方式得到透明效果:透明度测试(alpha test,无法得到真正的透明效果)、透明度混合(alpha blending)。
对于不透明物体,深度缓冲(depth buffer)就能正确判断物体的遮挡关系。
- 透明度测试:一个片元的透明度达不到某个标准,那么就会被舍弃;否则就按不透明物体进行处理。因此,透明度测试不需要关闭深度写入。
- 透明度混合:使用当前片元作为混合因子,与已经存储在颜色缓冲区中的颜色进行混合。透明度混合需要关闭深度写入(即离镜头更近的片元不会覆盖原有的同一位置的片元)。因此我们需要小心物体的渲染顺序。但是仍然可以进行深度测试。
1.2、渲染顺序
为了保证半透明物体后面的物体能够被我们看到,渲染时我们必须关闭深度写入。
因此,渲染顺序会很大程度上影响显示效果。
理想状况下,我们需要先正常渲染所有不透明物体,再由远及近渲染透明物体。
但是现实情况中,可能涉及到不同透明物体交错排列的情况,因此这种方法会遇到问题。
尽管这种方法存在问题,但是实现简单,因此大多数游戏引擎都使用了这样的方法。
为了减少bug,我们可以让模型尽可能是凸面体,将复杂的模型拆分成多个独立排序的子模型。
2、Unity Shader的渲染顺序
Unity 使用渲染队列 (render queue) 解决渲染顺序问题。
使用SubShader的Queue标签来决定我们的模型属于哪个渲染队列。Unity 内部使用一系列渲染数表示每个渲染队列,索引号越小表示越早被渲染。
Unity5 中, Unity 提前定义了5个渲染队列。
下面是五个渲染队列及其描述:
透明度测试代码中应该包含如下代码:
SubShader{
Tags {"Queue" = "AlphaTest"}
Pass{
...
}
}
透明度混合则是这样的:
SubShader{
Tags {"Queue" = "Transparent"}
Pass{
Zwrite Off
...
}
}
也可以在SubShader打开Zwrite Off,这样一个SubShader中的全部Pass都会关闭深度写入。
3、透明度测试
上面已经介绍了透明度测试:一个片元的透明度达不到某个标准,那么就会被舍弃;否则就按不透明物体进行处理。
使用Cg中的Clip函数:给定的参数的任何一个分量是负数,就会舍弃当前像素的输出颜色。等同于下面的代码:
void clip(float4 x){
if (any(x < 0)){
discard;
}
}
在SubShader中,我们定义Tags:
Tags{"Queue" = "AlphaTest" "IgnoreProjector"="True" "RenderType" = "TransparentCutout"}
- 将队列Queue设置为AlphaTest。
- 将RenderType标签指定为TransparentCutout,让Unity将此Shader归入提前定义的组中。该标签通常用于着色器替换功能。
- 将IgnoreProjector设置为True,意味着这个Shdaer不会受到投影器(Projectors)的影响。
代码为:
Shader "Unity Shader Book/Chapter 8/AlphaTest"
{
Properties
{
_Color ("Main Tint", Color) = (1,1,1,1)
_MainTex("Main Tex", 2D) = "white" {}
_Cutoff("Alpha CutOff", Range(0, 1)) = 0.5//透明度测试的判断条件
}
SubShader
{
Tags{"Queue" = "AlphaTest" "IgnoreProjector"="True" "RenderType" = "TransparentCutout"}
Pass{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
//属性
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;//纹理缩放
fixed _Cutoff;
//输入输出结构体
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert (a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
//Alpha Test
clip(texColor.a - _Cutoff);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, 1.0);
}
ENDCG
}
}
FallBack "Transparent/Cutout/VertexLit"
}
4、透明度混合
使用Blend命令控制混合。
Shader "Unity Shader Book/Chapter 8/AlphaBlend"
{
Properties
{
_Color ("Main Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
}
SubShader
{
Tags{"Queue" = "Transparent" "IgnoreProjector"="True" "RenderType" = "Transparent"}
Pass{
Tags {"LightMode"="ForwardBase"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
//属性
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;//纹理缩放
fixed _AlphaScale;
//输入输出结构体
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
//设置返回值的透明通道:即纹理像素的透明通道与材质参数_AlphaScale的乘积。
return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
产生的效果如下所示:
需要注意的是,由于我们关闭了深度写入,因此在显示重叠物体的时候,前后关系显示会出现问题。
5、开启深度写入的半透明效果
可以使用两个Pass来渲染模型,第一个Pass开启深度写入,但是不输出颜色;第二个Pass使用前一个Pass写入的深度缓冲中的值进行透明度混合。
使用这种方法,可以实现正常的遮挡关系,但是在半透明物体后面的半透明物体会被完全覆盖,不能显示。
在新创建的Pass中,我们使用新的渲染命令——ColorMask。ShaderLab中,ColorMask用于设置颜色通道的写掩码(write mask)。
ColorMask RGB | A | 0 | 其他任何R、G、B、A的组合
当ColorMask设置为0时,意味着该Pass不写入任何颜色通道。
6、Shdaer Lab 的混合命令
这是使用加法混合的混合公式(S为源颜色,D为目标颜色):
下面是混合因子可能的值:
6.1、其他混合操作
使用BlendOP BlendOperation
命令(混合操作命令),使用其他运算模式。
混合操作命令通常与混合因子命令一同工作。
6.2、常见混合
//正常混合,即透明度混合
Blend SrcAlpha OneMinusSrcAlpha
//柔和相加(Soft Additive)
Blend OneMinusDstColor One
//正片叠底(Multiply),即相乘
Blend DstColor SrcColor
//两倍相乘(2X Multiply)
Blend DstColor SrcColor
//变暗(Darken)
BlendOp Min
Blend One One
//变亮(Lighten)
BlendOp Max
Blend One One
//滤色(Screen)
Blend OneMinusDstColor One
//等同于
Blend One MinusSrcColor
//线性减淡(Linear Dodge)
Blend One One
7、双面渲染的透明效果
可以使用Cull指令选择需要剔除那个面的渲染图元。
Cull Back | Front | Off
- Back:背对摄像机的图元不会被渲染(默认)
- Front:朝向摄像机的图元不会被渲染
- Off:关闭剔除功能,所有图元都会被渲染
7.1、透明度测试的双面渲染
直接在Pass的Tags后面添加
Cull Off
7.2、透明度混合的双面渲染
复制透明度混合的Pass,形成两个Pass。
第一个Pass只渲染背面,第二个Pass只渲染正面,这样就能保证背面总是在正面渲染之前被渲染。
本篇文章为读《Unity Shader 入门精要》时写的笔记