Unity为了解决渲染顺序的问题,提供了渲染队列这一解决方案。我们可以使用 SubShader 的 Queue 标签决定模型归于哪个渲染队列。Unity 中提供了一系列整数索引来表示每个渲染队列(索引号越小表示越早被渲染)
Unity5 中提前定义了五个渲染队列:
每个队列中间可以使用其他队列
此外,我们可以使用 ZWrite Off 语句关闭深度写入。可以将其放在 SubShader 或 Pass 语句块中
透明度测试:只要一个片元的透明度不满足条件,其对应片元就会被舍弃,不再会对颜色缓冲产生任何影响;否则按照不透明物体的方式处理
通常我们会在片元着色器中使用 clip 函数进行透明度测试:
代码如下:
Shader "Unity Shaders Book/Chapter8/Alpha Test"
{
Properties
{
_Color ("Color",Color) = (1,1,1,1)
_MainTex ("Texture", 2D) = "white" {}
_Cutoff ("Alpha Cutoff",Range(0,1)) = 0.5
}
SubShader
{
Tags { "Queue"="AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
//设置SubShader的渲染队列为AlphaTest,设置为不受投影器影响,使用RenderType标签将Shader归入提前定义的组以指明Shader使用了透明度测试
Pass
{
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#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);
//等效语句
/*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提供了设置混合模式的命令 Blend 。想要实现半透明效果就需要把当前自身的颜色和已经存在于颜色缓冲区的颜色进行混合。混合时使用的函数由该指令决定:
在书中,会使用 Blend SrcFactor DstFactor 来进行混合(开启混合后,设置片元的透明通道才有意义)
我们会将颜色混合因子 SrcFactor 设置为 SrcAlpha ,目标颜色的混合因子 DstFactor 设置为 OneMinusSrcAlpha,这样混合后的新颜色为:
代码如下:
Shader "Unity Shaders Book/Chapter8/Alpha Blend"
{
Properties
{
_Color ("Color",Color) = (1,1,1,1)
_MainTex ("Texture", 2D) = "white" {}
_AlphaScale ("Alpha Scale",Range(0,1)) = 1
}
SubShader
{
Tags { "Queue"="Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
//设置SubShader的渲染队列为AlphaTest,设置为不受投影器影响,使用RenderType标签将Shader归入提前定义的组以指明Shader使用了透明度测试
Pass
{
Tags {"LightMode" = "ForwardBase"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#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"
}
效果如下:
由于关闭了深度写入,在模型本身有复杂的遮挡关系或包含复杂的非凸网格时,就会有各种各种因排序错误产生的错误透明效果
对于关闭深度写入而造成错误排序的情况,一种解决方法是使用两个Pass来渲染模型:一个开启深度写入,但不输出颜色,仅用于将模型的深度值写入深度缓存中;另一个Pass进行正常的透明度混合,由于上一个Pass已经得到了逐像素的正确的深度信息,因此这一Pass可以按照像素级别的深度排序结果进行透明渲染
实现这一效果时,我们只需要在上一节的代码中添加新的Pass块:
而FallBack改为"Diffuse"(原因不明)
完成后即可看到相应的透明效果:
(缺少相繁复的模型,这里仅呈现透明效果的正常实现)
使用这一方法时,多使用一个Pass会对性能造成一定的影响,且模型内部之间不会有任何真正的半透明效果
混合的基本实现:当片元着色器产生一个颜色时,可以选择与颜色缓存中的颜色进行混合。这样一来,混合就与两个操作数有关:从片元着色器产生的源颜色S,从颜色缓存中读取到的目标颜色D。对他们进行混合后,可以得到输出颜色O,他会被重新写入到颜色缓存。(这些颜色都包含RGBA四个通道)
想要使用混合,就需要开启这一功能。当在Unity中使用Blend命令时,除了设置混合状态以外也同时开启了混合
想通过操作数S和D得到输出颜色O,就需要相应的混合等式。在进行混合时,我们需要两个混合等式,一个用于混合RGB通道,一个用于混合A通道。在设置混合状态时,实际上设置的就是混合等式中的操作和因子。默认情况下,混合等式使用的都是加操作,因此我们只需要再设置一下混合因子即可。由于需要用到两个等式,因此我们一共需要四个因子
ShaderLab中设置混合因子的命令:
ShaderLab中的混合因子:
我们可以使用BlendOp BlendOperation命令(混合操作命令)来选择其他的混合操作
ShaderLab中支持的混合操作如下:
通过混合操作和混合因子命令的组合,我们可以得到一些类似PS混合模式中的混合效果:
值得一提的是,上面使用Min和Max操作时仍设置了混合因子,但它们实际上不会对结果又任何影响(Min和Max混合操作会忽略混合因子)。另外,上面有一些混合模式没有设置混合操作的种类,因为它们默认使用加法操作
在前面实现的透明效果中,无论是透明度测试还是透明度混合,我们都无法看到正方体内部及其表面的形状,视觉上就好像物体只有半个一样。这是由于默认情况下渲染引擎剔除了物体物体背面(背对摄像机的方向)的渲染图元。如果想要的到双面渲染的效果,可以使用Cull指令来控制需要剔除哪个面的渲染图元:
使用 Back ,则背对摄像机的图元不会被渲染
使用 Front ,则朝向摄像机的图元不会被渲染
使用 Off ,则所有的渲染图元都会被渲染(需要渲染的图元数目会成倍增加,因此除有特殊效果需求,否则不会关闭剔除功能)
只需要在透明度测试的代码中添加关闭剔除的一行代码即可:
(由于缺少相应的透明纹理素材,暂时不能呈现代码效果)
我们可以把双面渲染的工作为两个Pass,第一个只渲染背面,第二个只渲染正面。由于Unity会顺序执行SubShader中的各个Pass,因此可以保证背面总在正面之前被渲染,从而保证正确的深度渲染关系
Shader "Unity Shaders Book/Chapter8/Alpha Blend With Both Side"
{
Properties
{
_Color("Color",Color) = (1,1,1,1)
_MainTex("Texture", 2D) = "white" {}
_AlphaScale("Alpha Scale",Range(0,1)) = 1
}
SubShader
{
Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
//设置SubShader的渲染队列为AlphaTest,设置为不受投影器影响,使用RenderType标签将Shader归入提前定义的组以指明Shader使用了透明度测试
Pass
{
Tags {"LightMode" = "ForwardBase"}
Cull Front
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#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
}
Pass
{
Tags {"LightMode" = "ForwardBase"}
Cull Back
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#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"
}