Unity Shader 透明度测试和透明度混合
关于透明度测试和透明度混合的理论部分可以翻阅上一篇博客
【Unity Shader】(8)透明度测试与透明度混合的理论基础,本节主要是根据《Unity Shader入门精要》,将代码整理。
1、透明度测试
概念:只要一个片元的透明度不满足条件(通常是小于设定的阈值),那么其对应的片元就会被舍弃。(被舍弃意味着——不会进行任何处理,包括颜色缓冲),如果大于设定的阈值,则会按照普通的不透明度物体的处理方式来处理该片元。
方法:通常是在片元着色器中,使用clip函数进行透明度测试。
函数:clip(),等同于:
void clip(float4 v)
{
if(any(x<0))
discard;
}
代码开始
①新建一个Shader之后,清空其中的代码。 ②添加我们的属性代码,即Properties:
Properties{
_Color ("Base Color",Color) = (1,1,1,1)
_MainTex("Main Tex",2D) = "white"{}
_Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
}
其中,Cutoff就是我们设定的透明度阈值。范围是0~1,因为纹理像素的透明度就是在这个范围内。
③编写我们的SubShader语义块
SubShader{
Tags{"Queue"="AlphaTest" "IgnoreProjector"+"Ture" "RenderType"="TransparentCutout"}
Pass{
Tags{"LightMode"="ForwardBase"}
}
}
这里有所不同的是,我们在Pass之外设置了三个tags。
标签 | 作用 |
---|
Queue | 渲染队列设置为“透明度测试” | RenderType | 让Unity把该Shader归入到提前定义好的TransparentCutout组中(指明该Shader是一个使用了透明度测试的Shader) | IgnoreProjectors | 设置为True,意味着该Shader将不受投影器(Projector)的影响 |
通常,使用透明度测试AlphaTest的Shader都需要在SubShader中设置上面三个标签。
④补充CGPROGRAM ⑤完整代码
Shader"LeonShader/Shader_8_3_AlphaTest"
{
Properties{
_Color ("Base Color",Color) = (1,1,1,1)
_MainTex("Main Tex",2D) = "white"{}
_Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
}
SubShader{
Tags{"Queue" = "AlphaTest" "IgnoreProjector" = "Ture" "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 : 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);
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"
}
效果展示: 值得一提的是,我们可以发现——中间的黑色线框是不透明的,理应来说是完美的黑色,但是由于透明度测试以后,会发现一部分锯齿的效果。这是因为边界处纹理的变化精度问题。因此如果想得到更加柔和的透明效果,可以使用下面的透明度混合。
2、透明度混合
在unity之中,可以使用Blend 打开混合命令。想要实现半透明的效果就需要把当前的自身颜色和已经存在颜色缓冲中的颜色进行混合。下表为Blend命令的语义:
语义 | 描述 |
---|
Blend Off | 关闭混合 | Blend SrcFactor DstFactor | 开启混合,并设置混合因子。源颜色(该片元本身的颜色)会乘以SrcFator,而目标颜色(已存在颜色缓存中的颜色)会乘以DstFator,然后把两者相加后存入颜色缓冲中 | Blend SrcFator DstFator,ScrFatorA DstFatorA | 和上面几乎一样,只是使用了不同的因子来混合透明通道 | BlendOp blendOperation | 并非把源颜色和目标颜色简单相加后混合,而是使用blendOperation对它们进行其他操作 |
Ps:设置了混合因子的同时,也会开启混合模式。这是因为只有开启了混合以后,透明通道才有意义。(在unity中,使用blend就会自动打开。)
综上,得到了如下公式:
代码部分
透明度混合的代码与透明度测试类似。在编写过程中发现有个地方一直报错,但是反复查看都没有找到问题,复制案例代码确实可以允许,这不行啊,我要自己打,自己打了一遍又可以了。 就很迷@-@ 直接上代码:
注意,这个代码是关闭了深度写入 的。
Shader"LeonShader/Shader_8_4_AlphaBlend"
{
Properties {
_Color ("Color 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));
return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
}
ENDCG
}
}
Fallback "Transparent/VertexLit"
}
除此之外,由于我们关闭了深入写入 (Zwrite Off),将会造成深度的视觉错误,如下图:
因此我们需要改进透明度混合的Shader代码。
3、开启深度写入的透明度混合
这里的思路是新开一个Pass通道,用来单独处理深度缓冲所导致的结果。但是不输出颜色。仅仅是将深度值存入颜色缓冲中。 当然了,多使用一个Pass,就会对性能造成一定影响。
代码则是在上述“透明度混合”的基础上,新增一下代码:
Pass (
ZWrite On
ColorMask 0
}
Pass {
}
FallBack "Diffuse"
上述代码中,ColorMask可用于设置颜色通道的写掩码(Wirte Mask)。语义如下: ColorMask + RGB | A | 0 | 其他任何R、G、B、A的组合 ColorMask设为0后,意味着Pass不写入颜色通道,这正是我们需要的,该Pass值写入深度缓存。
最终的结果如下:
4、双面透明
在现实生活中,如果一个物体是透明的,意味着我们不仅可以透过它看到其他物体的样子, 也可以看到它内部的结构。但在前面实现的透明效果中,无论是透明度测试还是透明度混合,我们都无法观察到正方体内部及其背面的形状,导致物体看起来就好像只有半个。这是因为, 默认情况下渲染引擎剔除了物体背面(相对于摄像机的方向)的渲染图元,而只渲染了物体的 正面。 如果我们想要得到双面的渲染效果,可以使用Cull指令来控制需要提出的哪一个面的渲染图元。在Unity中,Cull指令的语法如下:
Cull Back | Front | Off
指令 | 描述 |
---|
Cull Back | 被对着摄像机的渲染图元就不会被渲染,也是默认情况下的剔除状态 | Cull Front | 那么那些朝向摄像机的渲染图元就不会被渲染 | Cull Off | 关闭剔除功能,所有的渲染图元都会被渲染。但由于此时需要渲染的图元数量成倍增加,因此除非特殊效果,例如这里的双面渲染的透明效果,通常是不会关闭剔除功能的。 |
使用方法:
我们在AlphaTest,也就是上文 透明度测试的Shader代码中,添加下面代码:
Pass{
Tags{"LightMode" = "ForwardBase"}
Cull Off
代码添加方式和效果如下:
5、透明度混合的双面透明
|