Unity Shader 之 透明效果

时间:2022-04-24 05:14:58

透明效果

透明效果一般有两种实现方法:

  • 第一种,使用透明度测试(Alpha Test)
  • 第二种,使用透明度混合(Alpha Blending)

透明度测试透明度混合机制

透明度测试(Alpha Test):只要一个片元的透明度不满足条件(小于某阀值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响;否则,就按照普通的不透明物体处理,即进行深度测试、深度写入。透明度测试不需要关闭深度写入。

透明度混合(Alpha Blending):使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色值。但是,这需要关闭深度写入。虽然关闭了深度写入,但是没有关闭了深度缓冲,此时深度缓冲还是可读的。

不同的渲染顺序带来的结果:(A半透明,B不透明)

Unity Shader 之 透明效果

第一种:先渲染B,再渲染A.

结果:能得到正确的渲染效果。

第二种:先渲染A,再渲染B.

结果:不能得到正确的渲染效果,因为在渲染A时已经关闭了深度写入,所以A不会修改深度缓冲,等到渲染B时,B直接覆盖了A的颜色。

渲染常用顺序:

  1. 先渲染所有不透明物体,开启深度测试和深度写入。
  2. 半透明物体按离摄像机距离远近排序,从后往前渲染,开启深度测试,关闭深度写入。

部分相互重叠的物体不适用,解决方法是分割网格。

unity shader的渲染顺序

Unity为了解决渲染顺序问题提供了渲染队列(render queue)解决方案。

Unity Shader 之 透明效果

实现透明度测试时需要添加代码:

SubShader{
Tags {"Queue" = "AlphaTest"}
Pass{
...
}
}

实现透明度混合需要添加代码:

SubShader{
Tags {"Queue" = "Transform"}
Pass{
ZWrite Off
...
}
}

透明度测试

关键函数

clip函数是CG中的一个函数:

void clip(float4 x);
void clip(float3 x);
void clip(float2 x);
void clip(float1 x);
void clip(float x);

全部代码

Shader "Unity Shaders Book/Chapter 8/Alpha Test" {
Properties {
_Color ("Color 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 : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
}; v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(_Object2World, 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);
// Equal to
// if ((texColor.a - _Cutoff) < 0.0) {
// discard;
// } 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"
}

效果

Unity Shader 之 透明效果

透明度混合

透明度混合的本质就是用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新颜色。

混合公式如下:

Unity Shader 之 透明效果

unity提供的混合命令:

Unity Shader 之 透明效果

透明度混合完整代码

Shader "Unity Shaders Book/Chapter 8/Alpha Blend" {
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 = mul(UNITY_MATRIX_MVP, v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(_Object2World, 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"
}

效果

Unity Shader 之 透明效果

开启深度写入的半透明效果

前面提到由于关闭深度写入而造成的错误排序的情况。可以使用两个Pass的办法来解决

第一个Pass: 开启深度写入,但不输出颜色,它的目的仅仅是为了把该模型的深度值写入深度缓冲中

第二个Pass: 进行正常的透明度混合,由于上个Pass已经的到了像素的正确深度信息,所以可以得到正确的渲染结果。

这种办法的缺点是对性能有一定影响

ShaderLab的混合命令

ShaderLab中的混合因子

参数 描述
One The value of one - use this to let either the source or the destination color come through fully.
Zero The value zero - use this to remove either the source or the destination values.
SrcColor The value of this stage is multiplied by the source color value.
SrcAlpha The value of this stage is multiplied by the source alpha value.
DstColor The value of this stage is multiplied by frame buffer source color value.
DstAlpha The value of this stage is multiplied by frame buffer source alpha value.
OneMinusSrcColor The value of this stage is multiplied by (1 - source color).
OneMinusSrcAlpha The value of this stage is multiplied by (1 - source alpha).
OneMinusDstColor The value of this stage is multiplied by (1 - destination color).
OneMinusDstAlpha The value of this stage is multiplied by (1 - destination alpha).

ShaderLab中的混合操作

操作 描述
Add Add source and destination together.
Sub Subtract destination from source.
RevSub Subtract source from destination.
Min Use the smaller of source and destination.
Max Use the larger of source and destination.
LogicalClear Logical operation: Clear (0) DX11.1 only.
LogicalSet Logical operation: Set (1) DX11.1 only.
LogicalCopy Logical operation: Copy (s) DX11.1 only.
LogicalCopyInverted Logical operation: Copy inverted (!s) DX11.1 only.
LogicalNoop Logical operation: Noop (d) DX11.1 only.
LogicalInvert Logical operation: Invert (!d) DX11.1 only.
LogicalAnd Logical operation: And (s & d) DX11.1 only.
LogicalNand Logical operation: Nand !(s & d) DX11.1 only.
LogicalOr Logical operation: Or (s | d) DX11.1 only.
LogicalNor Logical operation: Nor !(s | d) DX11.1 only.
LogicalXor Logical operation: Xor (s ^ d) DX11.1 only.
LogicalEquiv Logical operation: Equivalence !(s ^ d) DX11.1 only.
LogicalAndReverse Logical operation: Reverse And (s & !d) DX11.1 only.
LogicalAndInverted Logical operation: Inverted And (!s & d) DX11.1 only.
LogicalOrReverse Logical operation: Reverse Or (s | !d) DX11.1 only.
LogicalOrInverted Logical operation: Inverted Or (!s | d) DX11.1 only.

双面渲染的透明效果

上面的透明度测试和透明度混合都无法将物体的背面显示出来,这是因为渲染引擎剔除了物体的背面,所以需要在渲染透明物体使用Cull指令控制需要剔除的渲染图元。

  • Cull Back | Front | Off

分别是:剔除背面的图元 | 剔除前面的图元 | 关闭功能

完整代码

Shader "Unity Shaders Book/Chapter 8/Alpha Blend With Both Side" {
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" } // First pass renders only back faces
Cull Front 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 = mul(UNITY_MATRIX_MVP, v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(_Object2World, 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
} Pass {
Tags { "LightMode"="ForwardBase" } // Second pass renders only front faces
Cull Back 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 = mul(UNITY_MATRIX_MVP, v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(_Object2World, 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"
}

效果

Unity Shader 之 透明效果