|
本文为《Unity Shader入门精要》第八章内容《透明效果》的下半部分笔记,主要涉及深度写入的透明度效果、混合命令、双面渲染。 本文相关代码,详见:
原书代码,详见原作者github:
<hr/>1. 开启深度写入的半透明效果
1.1 概念
前面学习过,关闭深度写入会造成错误排序。
针对这种情况我们可以使用两个Pass来渲染模型:
开启深度写入,但不输出颜色,仅仅是为了把模型的深度值写入深度缓冲;
进行正常的透明度混合,基于上一个Pass,按照像素级别的深度排序进行透明渲染。
但这种方法有2个缺点:
- 多个Pass会对性能造成影响;
- 对于内部存在不同深度结构情况下,模型内部还是实现不了半透明度效果。
1.2 实践:使用两个Pass实现半透明效果
接下来实践一下开启深度写入的半透明效果。
1.2.1 准备工作
完成如下准备工作:
- 新建名为Scene_8_5的场景,并关闭天空盒子;
- 新建名为AlphaBlendZWriteMat材质;
- 新建名为Chapter8-AlphaBlendZWrite的Shader,并赋给上一步创建的材质;
- 在场景中创建一个立方体,并将第二步创建的材质赋给它;
- 保存场景。
详细操作详见《<Unity Shader入门精要>笔记(四)》里的案例常用操作说明。
1.2.2 编写Shader代码
打开Chapter8-AlphaBlendZWrite,删除里面所有代码,粘贴复制上一文笔记中写过的Chapter8-AlphaBlend代码,做部分修改,新代码已用注释说明:
// 修改Shader命名
Shader &#34;Unity Shaders Book/Chapter 8/Alpha Blend ZWrite&#34;
{
Properties
{
_Color (&#34;Main Tint&#34;, Color) = (1, 1, 1, 1)
_MainTex (&#34;Main Tex&#34;, 2D) = &#34;white&#34; {}
_AlphaScale (&#34;Alpha Scale&#34;, Range(0, 1)) = 1
}
SubShader
{
Tags {
&#34;Queue&#34; = &#34;Transparent&#34;
&#34;IgnoreProjector&#34; = &#34;True&#34;
&#34;RenderType&#34; = &#34;Transparent&#34;
}
// 只需要增加一个不输出颜色,开启深度写入的Pass就好
Pass
{
ZWrite On
// ColorMask渲染指令,用于设置颜色通道的写掩码(write mask)
// ColorMask RGB | A | 0 | 其他任何R、G、B、A的组合
// 0表示该Pass不写入任何颜色通道
ColorMask 0
}
Pass
{
Tags { &#34;LightMode&#34; = &#34;ForwardBase&#34; }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;Lighting.cginc&#34;
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
}
}
// 使用内置的Diffuse shader作为Fallback
Fallback &#34;Diffuse&#34;
}
保存代码,可得到如下的效果:
为了方便对比上一篇笔记中没有深度写入的效果,我们拖动两个Knot模型(在Assets/Models/Chap8/里面)场景中,一个赋上有深度值写入的材质,另一个附上没有深度值写入的材质,以立方体模型作为背景,看两者的区别:
可以看出有深度写入的模型内部有遮挡关系,而没有深度写入的模型看不出前后遮挡关系。但前者物体的内部没有半透明颜色的融合效果。
2. ShaderLab的混合命令
源颜色(S):由片元着色器产生的颜色值;
目的颜色(D):从颜色缓冲中读取到的颜色值
输出颜色(O):进行混合后得到的输出颜色,重新写入颜色缓冲中。
颜色混合,包含RGBA四个通道的值,而非仅仅是RGB通道。
在Unity中我们使用Blend(Blend Off命令除外)命令时,除了设置混合状态外,Unity内部帮我们开启了混合。但在其他图形API中,我们还是需要手动开启混合模式的。
2.1 混合等式和参数
混合是逐片元操作,不可编程,但是可高度配置。
混合等式:已知源颜色S、目的颜色D,通过计算得到输出颜色S的公式。
当进行混合时,需要使用2个等式:
当设置混合状态时,实际操作的是混合等式中的操作和因子。默认情况下,混合等式使用加法操作,我们只需要设置混合因子即可。
2个等式,每个等式2个因子,一共4个因子:
- SrcFactor:RGB通道源颜色因子
- DstFactor:RGB通道目的颜色因子
- SrcFactorA:A通道源颜色因子
- DstFactorA:A通道目的颜色因子
ShaderLab中设置混合因子的命令:
- Blend SrcFactor DstFactor
开启混合,并设置混合因子。源颜色(该片元产生的颜色)会乘以SrcFactor,而目的颜色(已经存在于颜色缓冲的颜色)会乘以DstFactor,然后把两者相加后再存入颜色缓冲中。
- Blend SrcFactor DstFactor, SrcFactorA DstFactorA
和上面几乎一样,只是使用不同的因子来混合透明通道。
对应混合公式:
ShaderLab中的混合因子
- One:混合因子为1
- Zero:混合因子为0
- SrcColor:因子为源颜色值。当用于混合RGB的混合等式时,使用SrcColor的RGB分量作为混合因子;当用于混合A的混合等式时,使用SrcFactor的A分量作为混合因子
- SrcAlpha:因子为源颜色的透明度值(A通道)
- DstColor:因子为源颜色值。当用于混合RGB通道的混合等式时,使用DstColor的RGB分量作为混合因子;当用于混合A通道的混合等式时,使用DstColor的A分量作为混合因子
- DstAlpha:因子为目的颜色的透明度值(A通道)
- OneMinusSrcColor:因子为(1 - 源颜色)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子;当用于混合A的混合等式时,使用结果的A分量作为混合因子
- OneMinusSrcAlpha:因子为(1 - 源颜色的透明度值)
- OneMinusDstColor:因子为(1 - 目的颜色)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子;当用于混合A的混合等式时,使用结果的A分量作为混合因子
- OneMinusDstAlpha:因子为(1 - 目的颜色的透明度值)
2.2 混合操作
ShaderLab中的混合操作:
- Add:将混合后的源颜色和目的颜色相加。默认的混合操作。混合等式:
- Sub:用混合后的源颜色减去混合后的目的颜色。混合等式:
- RevSub:用混合后的目的颜色减去混合后的源颜色。混合等式:
- Min:使用源颜色和目的颜色中较小的值,逐分量比较。混合等式:
- Max:使用源颜色和目的颜色中较大的值,是逐分量比较的。混合等式:
混合操作命令通常是和混合因子命令一起工作的,但需要注意的是,当使用Min或Max混合操作时,混合因子实际上是不起任何作用的,它们仅会判断原始的源颜色和目的颜色之间的比较结果。
2.3 常见的混合类型
常见的混合效果列举如下:
// 正常(Normal),即透明度混合
Blend SrcAlpha OneMinusSrcAlpha
// 柔和相加(Soft Additive)
Blend OneMinusDstColor One
// 正片叠底(Multiply),即相乘
Blend DstColor Zero
// 两倍相乘(2x Multiply)
Blend DstColor SrcColor
// 变暗(Darken)
BlendOp Min
Blend One One
// 变亮(Lighten)
Blend Max
Blend One One
// 滤色(Screen)
Blend OneMinusDstColor One
// 等同于
Blend One OneMinusSrcColor
// 线性减淡(Linear Dodge)
Blend One One1
对应的效果:
注意:
- 虽然我们使用Min和Max的混合操作设置了混合因子,但是他们不会对结果产生任何影响,因为Min和Max混合操作会忽略混合因子。
- 没有显式设置混合操作的种类时,默认使用加法操作,即:BlendOp Add。
3. 双面渲染的透明效果
Unity默认剔除了物体背面的渲染图元,只渲染正面。我们客户以使用Cull指令控制渲染哪个面的渲染图元。
Cull Back | Front | Off
- Back:背对相机的图元被剔除,不会被渲染。
- Front:正对相机的图元被剔除,不会被渲染。
- Off:关闭剔除,正面、背面都会被渲染,但是会导致要渲染的图元数量成倍增加,导致更大的性能消耗,除非有特殊需要,否则不建议执行此指令。
3.1 实践:透明度测试的双面渲染
基于之前透明度测试的实践,实现透明度测试的双面渲染。
3.1.1 准备工作
完成如下准备工作:
- 新建名为Scene_8_7_1的场景,并去掉天空盒子;
- 新建名为AlphaTestBothSidedMat的材质;
- 新建名为Chapter8-AlphaTestBothSided的Shader,赋给上一步创建的材质;
- 在场景中创建一个立方体,将第二步的材质赋给它;
- 保存场景。
3.1.2 编写Shader代码
打开Chapter8-AlphaTestBothSided,删除里面代码并粘贴之前写的Chapter8-AlphaTest代码,在Pass中增加一行关闭剔除的代码即可:
// 为Shader命名
Shader &#34;Unity Shaders Book/Chapter 8/Alpha Test Both Sided&#34;
{
Properties
{
_Color (&#34;Main Tint&#34;, Color) = (1, 1, 1, 1)
_MainTex (&#34;Main Tex&#34;, 2D) = &#34;white&#34; {}
_Cutoff (&#34;Alpha Cutoff&#34;, Range(0, 1)) = 0.5
}
SubShader
{
Tags {
&#34;Queue&#34; = &#34;AlphaTest&#34;
&#34;IgnoreProjector&#34; = &#34;True&#34;
&#34;RenderType&#34; = &#34;TransparentCutout&#34;
}
Pass
{
Tags { &#34;LightMode&#34; = &#34;ForwardBase&#34; }
// 关闭剔除功能
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;Lighting.cginc&#34;
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 &#34;Transparent/Cutout/VertexLit&#34;
}
3.1.3 配置材质参数
配置好立方体材质的贴图和透明度剔除数值:
可得到如下效果:
可以看到透过立方体透明表面,可以看到面片背面也被渲染了,我们对比下开启剔除的效果:
3.2 实践:透明度混合的双面渲染
透明度混合的双面渲染更复杂一些,因为透明度混合关闭了深度写入,我们要非常小心渲染顺序。如果我们直接关闭剔除功能,就无法保证同一个物体的正面和背面图元的渲染顺序,所以这里我们需要用两个Pass,第一个处理背面,第二个处理正面。因为Unity会按照Pass的顺序执行,所以先渲染背面再渲染正面可以保证正确的渲染关系。
3.2.1 准备工作
完成如下准备工作:
- 新建名为Scene_8_7_2的场景;
- 新建名为AlphaBlendBothSidedMat的材质;
- 新建名为Chapter8-AlphaBlendBothSided的shader,并赋给上一步创建的材质;
- 在场景中创建一个立方体,将第二步创建的材质赋给它;
- 保存场景。
3.2.2 编写Shader代码
打开Chapter8-AlphaBlendBothSided,删除里面所有代码并粘贴Chapter8-AlphaBlend代码,并做部分修改,修改部分的代码已用注释说明:
// 修改Shader命名
Shader &#34;Unity Shaders Book/Chapter 8/Alpha Blend Both Sided&#34;
{
Properties
{
_Color (&#34;Main Tint&#34;, Color) = (1, 1, 1, 1)
_MainTex (&#34;Main Tex&#34;, 2D) = &#34;white&#34; {}
_AlphaScale (&#34;Alpha Scale&#34;, Range(0, 1)) = 1
}
SubShader
{
Tags {
&#34;Queue&#34; = &#34;Transparent&#34;
&#34;IgnoreProjector&#34; = &#34;True&#34;
&#34;RenderType&#34; = &#34;Transparent&#34;
}
// 新增一个Pass处理背面
Pass
{
Tags { &#34;LightMode&#34; = &#34;ForwardBase&#34; }
// 剔除正面,做背面图元的渲染
Cull Front
// 下面是和之前完全一样的代码,直接复制一份就好
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;Lighting.cginc&#34;
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 { &#34;LightMode&#34; = &#34;ForwardBase&#34; }
// 剔除背面,做正面图元的渲染
Cull Back
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;Lighting.cginc&#34;
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 &#34;Transparent/VertexLit&#34;
}
3.2.3 配置材质参数
设置如下材质参数:
得到如下效果:
对比剔除了背面渲染的效果:
4. 后续预告
至此基础篇的内容已结束,接下来进入中级篇。下一篇笔记我们会学到更复杂的光照。
写在最后
本文内容会同步发在笔者的公众号上,欢迎大家关注交流!
公众号:程序员叨叨叨(ID:i_coder) |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|