剪切渲染
要创建一个透明的材质,我们必须知道每个片段的透明度。透明度信息最常存储在颜色的透明度通道中。在我们的例子中,我们使用的是主反射率纹理的透明度通道和颜色色调的透明度通道。
这里有一个透明度贴图的例子。 它是一个白色纹理,在透明度通道带有平滑衰退噪声。它是白色的,所以我们可以完全专注于透明度,而不会被反射率的模式所分心。
黑色背景下的透明度贴图。
将这个纹理分配给我们的材质只是使材质变成白色。透明度通道被忽略,除非你选择使用透明度通道作为平滑源。但是当你用这种材质选择一个四边形的时候,你会看到一个大致是圆形的选择轮廓。
在实四边形上的选择轮廓。
我如何能使用选择轮廓?
Unity 5.5引入了一种新的选择高亮方法。以前,你总是看到所选网格的线框。 现在,你还可以通过场景视图的Gizmos菜单选择使用轮廓效果。
Unity使用替换着色器来创建轮廓,稍后我们将提到这个着色器。这个着色器采样主纹理的透明度通道。轮廓会在那些透明度值变为零的地方进行绘制。
确定透明度值
要获取透明度的值,我们可以通过添加一个GetAlpha函数到My Lighting导入文件。像反射率一样,我们通过乘以色调和主纹理的透明度值来找到这个值。
floatGetAlpha (Interpolators i) {
return_Tint.a * tex2D(_MainTex, i.uv.xy).a;
}
然而,当我们不使用纹理的透明度通道来确定平滑度的时候,我们应该只使用纹理。如果我们没有检查这一点的话,那么我们可能会曲解数据。
floatGetAlpha (Interpolators i) {
floatalpha = _Tint.a;
#if !defined(_SMOOTHNESS_ALBEDO)
alpha *= tex2D(_MainTex, i.uv.xy).a;
#endif
returnalpha;
}
剪孔
在不透明的材质情况下,每个通过深度测试的片段都会被渲染。所有片段是完全不透明的,并将结果写入深度缓冲区中。透明度的加入使这一点变得复杂。
实现透明度的最简单的方法是保持二进制。一个片段要么是完全不透明的,要么是完全透明的。如果它是透明的话,那么它根本不会被渲染。这使得可以在表面中剪出一个孔来。
要中止渲染片段,我们可以使用clip函数。 如果这个函数的参数为负,则片段将被丢弃。图形处理器不会混合这个片段的颜色,它的结果不会写入深度缓冲区中去。如果发生这种情况,我们不需要担心材质的所有其他属性。所以最有效的是尽早进行裁剪。在我们的例子中,这个事情放在MyFragmentProgram函数开始的地方。
我们将使用透明度值来确定我们是否应该裁剪。由于透明度值位于零和一之间,我们必须将其减去一些,使其为负。通过减去1/2,我们将使透明度值的范围的下半部分变为负。这意味着至少有1/2透明度值对应的片段将被渲染,而所有其他的片段将被裁剪。
float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
floatalpha = GetAlpha(i);
clip(alpha - 0.5);
…
}
剪切一切透明度值低于0.5的片段。
可变的截止阈值
从透明度值中减去1/2是我们随便做的一个选择。我们可以减去另一个数字。如果我们从透明度值中减去一个更高的值,那么更大的范围将被剪切。因此,这个值用作截止阈值。让我们先把这个值变成一个变量。首先,在我们的着色器中添加Alpha Cutoff属性。
Properties {
…
_AlphaCutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
}
然后将相应的变量添加到My Lighting之中,并在剪裁之前从透明度值中减去这个变量的值,而不是减去½。
float_AlphaCutoff;
…
float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
floatalpha = GetAlpha(i);
clip(alpha - _AlphaCutoff);
…
}
最后,我们还必须向我们的自定义着色器的UI里面添加截止阈值。标准着色器显示了反射率线下面的截止阈值,所以我们也这样做。 我们将显示一个缩进的滑块,就像我们对平滑度的滑块所做的事情一样。
voidDoMain () {
GUILayout.Label("Main Maps", EditorStyles.boldLabel);
MaterialProperty mainTex = FindProperty("_MainTex");
editor.TexturePropertySingleLine(
MakeLabel(mainTex,"Albedo (RGB)"), mainTex, FindProperty("_Tint")
);
DoAlphaCutoff();
…
}
voidDoAlphaCutoff () {
MaterialProperty slider = FindProperty("_AlphaCutoff");
EditorGUI.indentLevel += 2;
editor.ShaderProperty(slider, MakeLabel(slider));
EditorGUI.indentLevel -= 2;
}
Alphacutoff属性的滑块。
现在,你可以根据需要来调整截止阈值。 你还可以对其进行动画处理,例如创建添加材质或去除材质的效果。
Alpha cutoff属性变化的效果请见:https://zippy.gfycat.com/SelfishCloudyAyeaye.mp4
着色器编译器将剪辑转换为丢弃指令。这里是相关的OpenGL核心代码片段。
u_xlat10_0 = texture(_MainTex, vs_TEXCOORD0.xy);
u_xlat1.xyz = u_xlat10_0.xyz * _Tint.xyz;
u_xlat30 = _Tint.w * u_xlat10_0.w + (-_AlphaCutoff);
u_xlatb30 = u_xlat30<0.0;
if((int(u_xlatb30) *int(0xffffffffu))!=0){discard;}
这里是相关的Direct3D11核心代码片段版本。
0: sample r0.xyzw, v1.xyxx, t0.xyzw, s1
1: mul r1.xyz, r0.xyzx, cb0[4].xyzx
2: mad r0.w, cb0[4].w, r0.w, -cb0[9].x
3: lt r0.w, r0.w, l(0.000000)
4: discard_nz r0.w
阴影怎么办?
我们将在下一个教程中处理剪切和半透明材质的阴影。在此之前,你可以使用对使用这些材质的对象关闭阴影。
渲染模式
剪裁不是免费的。它在桌面电脑的图形处理器上不那么糟糕,但使用分片渲染的移动图形处理器不喜欢丢弃片段。所以如果我们真的渲染一个剪切材质的话,我们应该只包括clip语句。而完全不透明的材料不需要这一点。为此,让我们依赖一个新的关键字_RENDERING_CUTOUT。
floatalpha = GetAlpha(i);
#if defined(_RENDERING_CUTOUT)
clip(alpha - _AlphaCutoff);
#endif
为这个关键字添加一个着色器特性,包括基本渲染通道和附加渲染通道。
#pragma shader_feature _RENDERING_CUTOUT
#pragma shader_feature _METALLIC_MAP
在我