Unity Shader 之 透明效果

本文引用 Unity Shader入门精要

开启透明混合后,一个物体被渲染到屏幕上时,每个片元除了颜色值和深度值外,还有——透明度。透明度为1,则完全不透明,透明度为0,则完全不会显示。

在Unity中我们有两种方式实现透明度效果

  • 透明度测试(Alpha Test):这种方式无法得到真正的半透明效果。只是0或1(完全透明和完全不透明)
  • 透明度混合(Alpha Blending):使用当前的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合。

那让我讨论第一个问题:渲染顺序

渲染顺序

为什么先说渲染顺序呢?

在之前的两篇文章中,我并没有涉猎到渲染顺序。因为对于不透明的物体,渲染顺序的决定是由深度缓冲决定的。

深度缓冲的基本思想为:根据深度缓冲中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把深度值和已经存在于深度缓冲中的值进行比较(前提:开启深度测试),如果它的值距离摄像机更远,说明该片元不应被渲染。

但是当我们使用透明度混合时,就得关闭深度写入(ZWrite)。

为什么关闭深度写入呢?

一个半透明的物体后面如果有物体的话,应该是可以被看到的,但是深度写入会把它剔除掉。

所以对于渲染顺序就得我们自行控制了。

 

上两个图分别是两种渲染顺序情况,

图一:透明物体在前,不透明物体在后。

  • 情况一:如果先渲染不透明物体(开启深度写入和深度测试),将不透明物体颜色写入颜色缓存,深度写入深度缓冲,然后渲染透明物体(关闭深度写入,开启深度测试),将透明物体的颜色与颜色缓冲中的颜色混合,得到正确结果。
  • 情况二:先渲染透明物体(关闭深度写入,开启深度测试),透明物体颜色写入颜色缓冲,然后渲染不透明物体(开启深度写入和深度测试),深度缓存中没有内容,所以直接覆盖颜色缓冲。得到错误结果。

图二:两个透明物体。

  • 情况一:先渲染后方的透明物体,颜色写入颜色缓冲,然后渲染前方透明物体,颜色和颜色缓冲中的颜色混合,得到正确结果。
  • 情况二:先渲染前方的透明物体,颜色写入颜色缓冲,然后渲染后方透明物体,颜色和颜色缓冲中的颜色混合,得到后方物体在前方物体前的画面,得到错误结果。

基于这种情况Unity给我们一种解决方式(大多数引擎的解决方式):物体排序+分割网格。

  • 物体排序:1.先渲染所有不透明物体,并开启它们的深度测试和深度写入。2.把半透明物体按它们距离摄像机的远近进行排序,然后按照从后往前的顺序渲染半透明物体(开启深度测试,关闭深度写入)。
  • 分割网格:解决物体排序遗留问题:循环重叠(例:3个物体互相重叠),我们把网格分割,分别判断分开后的网格的顺序来进行渲染。

Unity Shader 的渲染顺序

Unity提供了渲染队列(render queue)解决渲染顺序的问题。用SubShader的Queue标签来设置我们的模型在哪个渲染队列。索引越小越先渲染

Unity的5个渲染队列:

  • Background:索引:1000,这个队列是最先渲染的。
  • Geometry:索引:2000,默认渲染队列。不透明物体使用这个队列。
  • Alpha Test:索引:2450,需要透明度测试的物体使用该队列。
  • Transparent:索引:3000,按照从后往前的顺序渲染,使用透明度混合的物体都应该用该队列。
  • Overlay:索引:4000,该队列用于实现一些叠加效果,最后渲染。

透明度测试

只要一个片元的透明度不满足条件,那么这个片元就会被舍弃。用clip来进行透明度测试。

立方体的贴图每个块都是不同的透明度分别是50%、60%、70%、80%,下面是我把Alpha Cutoff设为0.7时的效果。你会发现50%和60%透明度的贴图已经不见了。

透明度测试Shader代码:

 1 Shader "My Shader/AlphaShader"
 2 {
 3     Properties
 4     {
 5         _Color ("Color", Color) = (1,1,1,1)
 6         _MainTex ("Texture", 2D) = "white" {}
 7         _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
 8     }
 9     SubShader
10     {
11         // 透明度测试队列为AlphaTest,所以Queue=AlphaTest
12         // RenderType标签让Unity把这个Shader归入提前定义的组中,以指明该Shader是一个使用了透明度测试的Shader
13         // IgonreProjector为True表明此Shader不受投影器(Projectors)影响
14         Tags { "Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout" }
15 
16         Pass
17         {
18             Tags { "LightMode"="ForwardBase" }
19 
20             CGPROGRAM
21             #pragma vertex vert
22             #pragma fragment frag
23             
24             #include "UnityCG.cginc"
25             #include "Lighting.cginc"
26 
27             struct a2v
28             {
29                 float4 vertex : POSITION;
30                 float3 normal : NORMAL;
31                 float4 texcoord : TEXCOORD0;
32             };
33 
34             struct v2f
35             {
36                 float4 pos : SV_POSITION;
37                 float2 uv : TEXCOORD0;
38                 float3 worldNormal : TEXCOORD1;
39                 float3 worldPos : TEXCOORD2;
40             };
41 
42             sampler2D _MainTex;
43             float4 _MainTex_ST;
44             fixed4 _Color;
45             // 用于决定调用clip函数时进行的透明度测试使用的判断条件
46             fixed _Cutoff;
47             
48             v2f vert (a2v v)
49             {
50                 v2f o;
51 
52                 o.pos = UnityObjectToClipPos(v.vertex);
53                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
54                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
55                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
56 
57                 return o;
58             }
59             
60             fixed4 frag (v2f i) : SV_Target
61             {
62                 fixed3 worldNormal = normalize(i.worldNormal);
63                 fixed3 worldPos = normalize(i.worldPos);
64                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
65                 // 纹素值
66                 fixed4 texColor = tex2D(_MainTex, i.uv);
67                 // 原理
68                 // if ((texColor.a - _Cutoff) < 0.0) { discard; }
69                 // 如果结果小于0,将片元舍弃
70                 clip(texColor.a - _Cutoff);
71                 // 反射率
72                 fixed3 albedo =  texColor.rgb * _Color.rgb;
73                 // 环境光
74                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
75                 // 漫反射
76                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
77                 return fixed4(ambient + diffuse, 1.0);
78             }
79             ENDCG
80         }
81     }
82 }

透明度混合

那我们看看Unity给我们提供的混合命令——Blend。给出Blend的常用语义。

  • Blend Off:关闭混合
  • Blend SrcFactor DstFactor:开启混合,设置混合因子。源颜色(片元颜色)乘以SrcFactor,目标颜色(已经在颜色缓冲中的颜色)乘以DstFactor,然后把两者相加
  • Blend SrcFactor DstFactor,SrcFactorA DstFactorA:同上,不过把透明通道(a)与颜色通道(rgb)用不同的因子。
  • BlendOp BlendOperation:使用BlendOperation对其进行其他操作,非简单相加混合。

混合公式:DstColorNew = SrcAlpha * SrcColor + (1 - SrcAlpha) * DstColorOld

下面是最简单的调整透明通道值的效果,AlphaScale = 0.6

Shader代码为:

 1 Shader "My Shader/AlphaShader"
 2 {
 3     Properties
 4     {
 5         _Color ("Color", Color) = (1,1,1,1)
 6         _MainTex ("Texture", 2D) = "white" {}
 7         _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
 8     }
 9     SubShader
10     {
11         // 透明度混合队列为Transparent,所以Queue=Transparent
12         // RenderType标签让Unity把这个Shader归入提前定义的组中,以指明该Shader是一个使用了透明度混合的Shader
13         // IgonreProjector为True表明此Shader不受投影器(Projectors)影响
14         Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
15 
16         Pass
17         {
18             Tags { "LightMode"="ForwardBase" }
19             
20             // 关闭深度写入
21             ZWrite Off
22             // 开启混合模式,并设置混合因子为SrcAlpha和OneMinusSrcAlpha
23             Blend SrcAlpha OneMinusSrcAlpha
24 
25             CGPROGRAM
26             #pragma vertex vert
27             #pragma fragment frag
28             
29             #include "UnityCG.cginc"
30             #include "Lighting.cginc"
31 
32             struct a2v
33             {
34                 float4 vertex : POSITION;
35                 float3 normal : NORMAL;
36                 float4 texcoord : TEXCOORD0;
37             };
38 
39             struct v2f
40             {
41                 float4 pos : SV_POSITION;
42                 float2 uv : TEXCOORD0;
43                 float3 worldNormal : TEXCOORD1;
44                 float3 worldPos : TEXCOORD2;
45             };
46 
47             sampler2D _MainTex;
48             float4 _MainTex_ST;
49             fixed4 _Color;
50             // 用于决定调用clip函数时进行的透明度测试使用的判断条件
51             fixed _AlphaScale;
52             
53             v2f vert (a2v v)
54             {
55                 v2f o;
56 
57                 o.pos = UnityObjectToClipPos(v.vertex);
58                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
59                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
60                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
61 
62                 return o;
63             }
64             
65             fixed4 frag (v2f i) : SV_Target
66             {
67                 fixed3 worldNormal = normalize(i.worldNormal);
68                 fixed3 worldPos = normalize(i.worldPos);
69                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
70                 // 纹素值
71                 fixed4 texColor = tex2D(_MainTex, i.uv);
72                 // 反射率
73                 fixed3 albedo =  texColor.rgb * _Color.rgb;
74                 // 环境光
75                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
76                 // 漫反射
77                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
78                 // 返回颜色,透明度部分乘以我们设定的值
79                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
80             }
81             ENDCG
82         }
83     }
84 }

然而这种实现方式是有问题的。就像前面说的一样,它并不能渲染正确的顺序。

解决方式:用两个Pass来渲染模型

  • 第一个Pass:开启深度写入,但不输出颜色,目的仅仅为了填充深度缓冲。
  • 第二个Pass:正常的透明度混合,由于上一个Pass已经得到了逐像素的正确深度信息,该Pass就可以按照像素级别的深度排序进行透明渲染。
  • 缺点:多了一个Pass性能有所影响。

代码如下:

 1 Shader "My Shader/AlphaShader"
 2 {
 3     Properties
 4     {
 5         _Color ("Color", Color) = (1,1,1,1)
 6         _MainTex ("Texture", 2D) = "white" {}
 7         _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
 8     }
 9     SubShader
10     {
11         // 透明度混合队列为Transparent,所以Queue=Transparent
12         // RenderType标签让Unity把这个Shader归入提前定义的组中,以指明该Shader是一个使用了透明度混合的Shader
13         // IgonreProjector为True表明此Shader不受投影器(Projectors)影响
14         Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
15 
16         Pass
17         {
18             // 开启深度写入
19             ZWrite On
20             // 设置颜色通道的写掩码,0为不写入任何颜色
21             ColorMask 0
22         }
23 
24         Pass
25         {
26             Tags { "LightMode"="ForwardBase" }
27             
28             // 关闭深度写入
29             ZWrite Off
30             // 开启混合模式,并设置混合因子为SrcAlpha和OneMinusSrcAlpha
31             Blend SrcAlpha OneMinusSrcAlpha
32 
33             CGPROGRAM
34             #pragma vertex vert
35             #pragma fragment frag
36             
37             #include "UnityCG.cginc"
38             #include "Lighting.cginc"
39 
40             struct a2v
41             {
42                 float4 vertex : POSITION;
43                 float3 normal : NORMAL;
44                 float4 texcoord : TEXCOORD0;
45             };
46 
47             struct v2f
48             {
49                 float4 pos : SV_POSITION;
50                 float2 uv : TEXCOORD0;
51                 float3 worldNormal : TEXCOORD1;
52                 float3 worldPos : TEXCOORD2;
53             };
54 
55             sampler2D _MainTex;
56             float4 _MainTex_ST;
57             fixed4 _Color;
58             // 用于决定调用clip函数时进行的透明度测试使用的判断条件
59             fixed _AlphaScale;
60             
61             v2f vert (a2v v)
62             {
63                 v2f o;
64 
65                 o.pos = UnityObjectToClipPos(v.vertex);
66                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
67                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
68                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
69 
70                 return o;
71             }
72             
73             fixed4 frag (v2f i) : SV_Target
74             {
75                 fixed3 worldNormal = normalize(i.worldNormal);
76                 fixed3 worldPos = normalize(i.worldPos);
77                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
78                 // 纹素值
79                 fixed4 texColor = tex2D(_MainTex, i.uv);
80                 // 反射率
81                 fixed3 albedo =  texColor.rgb * _Color.rgb;
82                 // 环境光
83                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
84                 // 漫反射
85                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
86                 // 返回颜色,透明度部分乘以我们设定的值
87                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
88             }
89             ENDCG
90         }
91     }
92 }

双面渲染的透明效果

对于刚才的立方体,虽然是透明的,但是却看不到里面的构造,是不是感觉也不太对,如果想看到内部构造怎么办呢?

Unity默认会剔除物体的背面(就是内部),那么我们可以用Cull指令来控制需要剔除哪个面的渲染图元。

  • Cull Back:背对着摄像机的渲染图元不会渲染,默认情况。
  • Cull Front:朝向摄像机的渲染图元不会渲染。
  • Cull Off:关闭剔除功能,所有的都会渲染。缺点:需要渲染的数目成倍增加,除非用于特殊效果,建议不开启。

接下来我们看一下效果:

这回也是用连个Pass来完成:第一个Pass渲染背面,第二个Pass渲染前面

Shader 代码如下:

  1 Shader "My Shader/AlphaShader"
  2 {
  3     Properties
  4     {
  5         _Color ("Color", Color) = (1,1,1,1)
  6         _MainTex ("Texture", 2D) = "white" {}
  7         _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
  8     }
  9     SubShader
 10     {
 11         // 透明度混合队列为Transparent,所以Queue=Transparent
 12         // RenderType标签让Unity把这个Shader归入提前定义的组中,以指明该Shader是一个使用了透明度混合的Shader
 13         // IgonreProjector为True表明此Shader不受投影器(Projectors)影响
 14         Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
 15 
 16         Pass
 17         {
 18             Tags { "LightMode"="ForwardBase" }
 19 
 20             // 只渲染背面
 21             Cull Front
 22             // 关闭深度写入
 23             ZWrite Off
 24             // 开启混合模式,并设置混合因子为SrcAlpha和OneMinusSrcAlpha
 25             Blend SrcAlpha OneMinusSrcAlpha
 26 
 27             CGPROGRAM
 28             #pragma vertex vert
 29             #pragma fragment frag
 30             
 31             #include "UnityCG.cginc"
 32             #include "Lighting.cginc"
 33 
 34             struct a2v
 35             {
 36                 float4 vertex : POSITION;
 37                 float3 normal : NORMAL;
 38                 float4 texcoord : TEXCOORD0;
 39             };
 40 
 41             struct v2f
 42             {
 43                 float4 pos : SV_POSITION;
 44                 float2 uv : TEXCOORD0;
 45                 float3 worldNormal : TEXCOORD1;
 46                 float3 worldPos : TEXCOORD2;
 47             };
 48 
 49             sampler2D _MainTex;
 50             float4 _MainTex_ST;
 51             fixed4 _Color;
 52             // 用于决定调用clip函数时进行的透明度测试使用的判断条件
 53             fixed _AlphaScale;
 54             
 55             v2f vert (a2v v)
 56             {
 57                 v2f o;
 58 
 59                 o.pos = UnityObjectToClipPos(v.vertex);
 60                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
 61                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
 62                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
 63 
 64                 return o;
 65             }
 66             
 67             fixed4 frag (v2f i) : SV_Target
 68             {
 69                 fixed3 worldNormal = normalize(i.worldNormal);
 70                 fixed3 worldPos = normalize(i.worldPos);
 71                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
 72                 // 纹素值
 73                 fixed4 texColor = tex2D(_MainTex, i.uv);
 74                 // 反射率
 75                 fixed3 albedo =  texColor.rgb * _Color.rgb;
 76                 // 环境光
 77                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
 78                 // 漫反射
 79                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
 80                 // 返回颜色,透明度部分乘以我们设定的值
 81                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
 82             }
 83             ENDCG
 84         }
 85 
 86         Pass
 87         {
 88             Tags { "LightMode"="ForwardBase" }
 89             
 90             // 只渲染前面
 91             Cull Back 
 92             // 关闭深度写入
 93             ZWrite Off
 94             // 开启混合模式,并设置混合因子为SrcAlpha和OneMinusSrcAlpha
 95             Blend SrcAlpha OneMinusSrcAlpha
 96 
 97             CGPROGRAM
 98             #pragma vertex vert
 99             #pragma fragment frag
100             
101             #include "UnityCG.cginc"
102             #include "Lighting.cginc"
103 
104             struct a2v
105             {
106                 float4 vertex : POSITION;
107                 float3 normal : NORMAL;
108                 float4 texcoord : TEXCOORD0;
109             };
110 
111             struct v2f
112             {
113                 float4 pos : SV_POSITION;
114                 float2 uv : TEXCOORD0;
115                 float3 worldNormal : TEXCOORD1;
116                 float3 worldPos : TEXCOORD2;
117             };
118 
119             sampler2D _MainTex;
120             float4 _MainTex_ST;
121             fixed4 _Color;
122             // 用于决定调用clip函数时进行的透明度测试使用的判断条件
123             fixed _AlphaScale;
124             
125             v2f vert (a2v v)
126             {
127                 v2f o;
128 
129                 o.pos = UnityObjectToClipPos(v.vertex);
130                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
131                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
132                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
133 
134                 return o;
135             }
136             
137             fixed4 frag (v2f i) : SV_Target
138             {
139                 fixed3 worldNormal = normalize(i.worldNormal);
140                 fixed3 worldPos = normalize(i.worldPos);
141                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
142                 // 纹素值
143                 fixed4 texColor = tex2D(_MainTex, i.uv);
144                 // 反射率
145                 fixed3 albedo =  texColor.rgb * _Color.rgb;
146                 // 环境光
147                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
148                 // 漫反射
149                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
150                 // 返回颜色,透明度部分乘以我们设定的值
151                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
152             }
153             ENDCG
154         }
155     }
156 }
posted @ 2017-12-11 16:06  Sooda  阅读(20243)  评论(0编辑  收藏  举报