当前位置: 首页 > news >正文

第六章:透明度-Transparency《Unity Shaders and Effets Cookbook》

 

Unity Shaders and Effets Cookbook

《着色器和屏幕特效制作攻略》

这本书可以让你学习到如何使用着色器和屏幕特效让你的Unity工程拥有震撼的渲染画面。

                                                                                                       —— Kenny Lammers


第六章:透明度

介绍

第1节.  使用 Alpha 创建透明度

1.1、准备工作

1.2、如何实现...

1.3、实现原理...

第2节.Transparent cutoff着色器

1.1、准备工作

1.2、如何实现...

1.3、实现原理...

第3节.使用渲染队列进行深度排序

1.1、准备工作

1.2、如何实现...

1.3、实现原理...

第4节.GUI 和透明度

1.1、准备工作 

1.2、如何实现...

1.3、实现原理..


I like this book!


第六章:透明度

在本章节中,你会学习到:

  • 使用 Alpha 创建透明度
  • Transparent cutoff着色器
  • 使用渲染队列进行深度排序
  • GUI 和透明度

介绍

        在编写 Shader 时,透明度和显卡一开始可能有点棘手。通过使用 Unity 的表面着色器,我们可以很容易地开始构建着色器,这些着色器可以支持完全透明效果的物体,例如玻璃等,还可以支持物体表面半透明的效果,比如对头发和树叶等等。

        接下来我们首先了解如何先使用透明度来构建一个非常简单的着色器,我们构建的知识集会包含Shaders 例如头发,同时也会去了解透明度如何影响对象的绘制顺序。

第1节.  使用 Alpha 创建透明度

        实现透明表面着色器的第一步,是需要清楚在我们的着色器中添加哪些代码才能启用透明度。Unity 再次为我们提供了一些新的内置参数供我们使用,我们把这些代码参数写进着色器中,可以快速的得到透明度效果。

        我们只需要在Shader中使用 alpha 参数,它是一个简单的过程。这是在告诉 Unity 我们要在Shader中使用透明度参数了。而创建透明着色器需要注意的是,在我们的代码中绘制顺序会变成一个元素。我们会在本小节中介绍它的基础知识,我们在最后会在场景中得到一个透明的物体。接下来,我们将在以下步骤中介绍其它的透明方法。

1.1、准备工作

        在开始之前我们需要收集一些资源,并在 Unity 编辑器中创建新场景。让我们按照以下步骤来准备Shader的编写过程:

  • 1. 在 Unity 中创建新场景,并在场景中放置球体、平面和平行光。
  • 2. 接下来创建一个新的 Shader 和一个新的 Material。应将 Shader 赋予给 Material,并将 Material 赋予给场景中的球体。
  • 3. 最后,我们需要制作一张纹理,在场景中的物体上用来显示,哪些是透明的,哪些不是透明的。

        下 图1.1 是我们用在本小节中的纹理示例。它的颜色是红绿蓝(RGB)和白色,所以我们可以使用纹理的各个通道作为透明度的值,这个数值的范围是0~1,这代表着白色表示不透明,黑色表示完全透明。对应到数字上就是,白色是1,黑色是0。0到1之间的数值表示灰色地带。

图1.1 

1.2、如何实现...

        准备工作做好以后,我们就可以开始用 Surface Shader 来创建我们的第一个透明的 Shader。

  • 步骤1在 Properties 块中输入新的属性值 ,用来控制 Shader 的全局:
Properties {_MainTex("Base (RGB)", 2D) = "white"{}_TransVal("Transparency Value", Range(0, 1)) = 0.5}
  • 步骤2然后,我们需要使用 alpha 参数来修改 #pragma 语(这个alpha 参数是我们新的知识点)。
CGPROGRAM
#pragma surface surf Lambert alpha
  • 步骤3最后,我们通过将 O.Alpha 行添加到 surf() 函数中来完成着色器:
void surf (Input IN, inout SurfaceOutput o) {half4 c = tex2D (_MainTex, IN.uv_MainTex);o.Albedo =  c.rgb;o.Alpha = c.r * _TransVal;}

        以下屏幕截 图1.2 是 Unity 编辑器里所显示的透明着色器的渲染结果:

图1.2

1.3、实现原理...

        我们可以看到,使用 Unity 的表面着色器很容易启动和运行透明着色器。在我们实现透明 Shader 时,它目前依赖于两个元素。一个是 #pragma 语句,另一个是用于 SurfaceOutput 结构中的 alpha 值。

        一旦我们在 #pragma 语句中声明了参数 alpha,这就表示了告诉 Unity 允许把
透明表面渲染到屏幕上。所需要去做的就是为 SurfaceOuput 结构体中的 O.Alpha 值来提供0~1的像素值。在颜色方面,白色或数值 1,代表完全不透明的表面,而黑色或数值 0 代表了完全透明的表面。

        虽然有很多实现方法,但在使用透明着色器时,我们一定谨记这是最基础的实现方式。当我们阅读本章时,我们会去讨论在实时渲染器(如 Unity)中使用 alpha 或半透明着色器时出现的问题。
 

第2节.Transparent cutoff着色器

        Unity 实际上给 #pragma 语句提供了另一种类型的参数,能让我们创建更简单的透明效果,它被称为 cutoff 透明度。这种类型的透明度实现起来比较简单,就是让某些像素值不绘制到屏幕上,来实现完全不透明或者完全透明的效果。而在上一小节中的着色器能够使用0~1的范围值来影响透明度,也称为半透明着色器。

        接下来让我们来实现如何在 Unity 中构建这种类型的着色器。

1.1、准备工作

        我们会通过将一些方法的串联,来展示我们的着色器编写过程。

  • 1. 首先,创建一个新场景,里面放置一个简单的球体和一盏平行光。
  • 2. 然后创建一个新的着色器和一个新的材质球。
  • 3. 最后,把色器赋予给材质球,再把材质球赋予给场景中的球体。
  • 4. 这次我们需要一张新纹理。最好找一张具有灰度值的纹理,这样我们就可以直观的看到 cutoff 值的效果。

        下 图2.1 是我们将用在本小节中的纹理图。我们只需使用 Photoshop 中的“渲染差异云”滤镜就可创建此纹理图。也可以从本书的网站中获得 www.packtpub.com/support。

图2.1

1.2、如何实现...

        准备好后,我们就开始此着色器的编写。

  • 步骤1首先,在Shader中的 Properties 块中添加一个浮点值,作为我们的 cutoff 值 :
Properties {_MainTex("Base (RGB)", 2D) = "white"{}_Cutoff("Cutoff Value", Range(0, 1)) = 0.5}
  • 步骤2然后告诉着色器,我们要把这个着色器设置为 cutoff 类型的着色器:
CGPROGRAM
#pragma surface surf Lambert alphatest:_Cutoff
  • 步骤3最后,把表面的每个像素值(也就是表面的颜色值)赋予给 O.Alpha:
void surf (Input IN, inout SurfaceOutput o) {float4 c = tex2D (_MainTex, IN.uv_MainTex);o.Albedo =  c.rgb;o.Alpha = c.r;}

        下 图2.2 显示了 cutoff着色器的结果,cutoff 滑块位于 0 和 1 之间不同值的效果展示:
 

图2.2

1.3、实现原理...

        Unity 为我们提供了相当多的参数,我们可以与 #pragma 指令一起使用。所有这些都能够更改和优化我们的表面着色器。这是表面着色器在编写 Shader 和迭代过程时如此强大和高效的另一个原因。

        我们的 cutoff 着色器在 #pragma 指令中使用了一个的新参数,名为 alphatest:VariableName 。使用了这个参数之后就会立即将着色器附带了简化版本的透明度。我们的透明度不是半透明的,意思是黑白范围内的每个灰色值都会影响透明度,只有_CutOff变量传递的值才能确定透明度。这意味着,如果我们把_CutOff值设置为 0.4 ,则每个低于 0.4 的灰色值都将被视为透明,高于该值的所有值都将是不透明或纯色的。

        当性能成为问题时,使用这种类型的透明度是很有利的,因为处理半透明着色器的混合比 cutoff 透明着色器会更昂贵。但在移动设备上,情况恰恰相反,因为测试纹理的像素值对于这些小 GPU 来说变得非常昂贵。因此,如果使用 Unity3D 制作移动应用程序,请记住使用半透明技术并谨慎使用 cutoff透明技术。
 

第3节.使用渲染队列进行深度排序

        为了让我们真正的理解透明度,我们需要看一看深度排序,简单来说就是对象的绘制顺序。Unity 允许我们控制特定对象绘制到屏幕上的顺序,因此我们可以更好的来控制哪些对象是在其他对象之前渲染。这个绘图顺序类似于 Photoshop 中的图层。在处理透明度或用户界面对象等元素时,绘图顺序是尤为重要。

        本小节将会揭示如何使用 Unity 提供给我们的内置标签,通过利用这种分层方法来渲染对象。这非常重要,因为你将会对于对象渲染到游戏视图的方式拥有更大的控制。
 

1.1、准备工作

        首先,我们需要创建一些资源,以便我们了解如何使用 Unity 的绘图顺序,为我们在实时渲染中提供更大的灵活性和控制力。

  • 1. 创建一个新场景,里面创建几个球体,让它们在任意轴上排成一排。我们的目标是研究如何绘制一个对象覆盖在另一个对象上,不限于它们在 3D 空间中的实际位置如何。
  • 2. 为了查看修改对象绘制顺序的效果,我们至少需要有两个着色器。因此,让我们创建两个新的着色器并命名为 Depth001 和 Depth002。
  • 3. 你的场景应如下 图3.1 所示。这样设置会允许我们调整对象的绘制顺序:

图3.1

1.2、如何实现...

        实现此技术的着色器代码非常简单;它只需要两行新代码。

  • 步骤1. 我们首先需要声明这个对象要被绘制到哪个渲染队列中,所以要在 SubShader{} 中修改我们的 Tags{} 块:
Tags { "Queue"="Geometry-20" }
  • 步骤2. 接下来,需要告诉 Unity 我们要控制这个对象的绘制顺序,并且我们不想写入深度缓冲区中。在上一步中添加的 Tags{} 行下方添加以下代码行:
ZWrite Off
  • 步骤3. 将该代码输入到着色器中后,我们现在可以保存并返回到 Unity 编辑器中。完成后,你会看到其中一个球体的排序始终在其它物体的后面,即使它在 3D 空间中的实际位置位于场景中所有对象的前面。但渲染结果显示它始终都在其它物体的后边。以下 图3.2 显示了深度排序着色器的渲染结果:

图3.2
 

1.3、实现原理...

        默认情况下,Unity 会根据与相机的距离对你创建的对象进行排序。因此,离相机远的对象会被离相机近的对象覆盖掉。在大多数情况下,这对于制作游戏来说效果很好,但您会发现在某些情况下,你或许希望更灵活的控制场景中对象的排序。那么就可以使用 Tags{} 块,这样就能轻松的来控制这种排序了。

        Unity 为我们提供了一些默认的渲染队列,每个队列都有一个唯一的值,用于指示 Unity 何时将对象绘制到屏幕上。这些内置渲染队列称为 背景(Background)、几何体(Geometry)、 AlphaTest、透明(Transparent) 和叠加(Overlay)。这些队列不是单单可以随意创建。它们实际上的作用是让我们在编写着色器和与实时渲染器交互时会变得更轻松。有关每个单独渲染队列的用法说明,请参阅下表:

渲染队列渲染队列说明渲染队列值
Background首先渲染此渲染队列。它用于天空盒等。 1000
Geometry   这是默认渲染队列。这用于大多数对象。不透明几何体使用此队列。   2000
AlphaTest   Alpha 测试的几何体使用此队列。它与几何队列不同,因为在绘制所有实体对象后渲染经过 alpha 测试的对象会更有效。   2450
Transparent  此渲染队列在几何体和 AlphaTest 队列之后按从后到前的顺序进行渲染。任何 alpha 混合(即不写入深度缓冲区的着色器)都会转到此处,例如玻璃和粒子效果。  3000
Overlay此渲染队列用于叠加效果。最后渲染的任何内容都会放在这里,例如镜头光晕。 4000

        因此,一旦知道你的对象是属于哪个渲染队列,你就可以分配它的内置渲染队列标签。案例中的着色器使用的是几何队列,因此我们使用了 Tags{“Queue”=“Geometry”}。但我们想让对象,在 Geometry  队列中的所有内容后面 并在Background之前去来绘制的话 ,我们就可以修改 Tags{} 块,为 Tags{“Queue”=“Geometry-20”}。这样做会告诉 Unity 我们希望将此对象视为不透明或实体对象,但会把它渲染在所有其他不透明对象的后面。

        最后,我们必须向 SubShader 块声明 Zwrite 标签。这告诉 Unity 我们会覆盖对象的深度排序,并且我们会为其渲染队列分配一个新值。因此,我们只需将 Zwrite 值设置为 off。

第4节.GUI 和透明度

        现在我们已经介绍了创建透明着色器的基础知识,并知道了如何控制对象的绘制顺序,接下来让我们来看看在具体的实际场景中,使用透明度并控制透明对象的绘制顺序。

        为 Unity 创建 GUI 绝对是一项艰巨的任务。可以使用内置的 OnGUI() 函数,一堆带有 alpha 的 2D 图像创建他们的 GUI,然后让 Unity 将图像绘制到屏幕上。或者,可以创建一个实际的 3D GUI 系统,你可以在其中实际看到 Unity 编辑器中场景视图中的 GUI 元素。我们来看一下最后一种方法。我们用一张 2D 图像并将它们放在场景中的 3D 物体上,以便它们可以用作游戏的 GUI 元素。

        在使用 3D GUI 时我们还会遇到出现的一些问题,例如绘图顺序,同时也会来学习如何解决这些问题的一些方法。

1.1、准备工作 

        在本小节中,我们会创建一个非常简单的GUI示例,因此我们需要为场景构建一张GUI元素表。当以这种 3D 方式创建 GUI 时,会创建一个纹理表,用来节省我们的纹理数量。这意味着所有按钮图形、图标图形,在某些情况下甚至文本图形都布局在一张纹理上,其 alpha 通道设置为遮罩纹理表,应该透明的位置以及不透明或半透明的位置都做好标记。下 图4.1 是我们用于本小节的纹理表:

图4.1

        因此,让我们开始为着色器来编写构建模拟 GUI吧。在制作的 GUI 系统工作中,我们会遇到各种未知状况。为此也增加了我们这方面的经验。

  • 1. 构造一个 GUI 纹理表,类似于上图中看到的纹理表。并且需要在纹理表的 alpha 通道中包含 alpha 纹理。
  • 2. 我们还必须为我们的 GUI 创建一些简单的几何图形(即模型)。在本例中,我们是使用的Maya软件来生成的GUI元素所用的模型。
  • 3. 在 Unity 中创建一个新场景,并在场景中放置一个平面和平行光。
  • 4. 然后,创建一个新的着色器和一个新的材质球,并将着色器赋予给材质球。
  • 5. 现在,我们只需将材质球赋予给场景中的 GUI 对象即可完成设置过程。
  • 6. 完成前面的步骤后,您你的场景应类似于如下 图4.2 

图4.2

        目前画面看起来不是很耐看,由于 alpha 没有创建,所以我们还没有真实游戏 GUI 所显示的透明度效果。我们需要创建我们的 GUI 着色器,如下步骤所示。

1.2、如何实现...

        为了使我们的 GUI 具有透明度,我们需要创建着色器,以便我们可以告诉 Unity 这些对象是透明的。

  • 步骤1.  像之前一样,我们需要使用适当的属性填充我们的 Properties 块,以便我们可以在 Unity 编辑器中与着色器进行交互。
Properties {_GUITint("GUI Tint", Color) =(1,1,1,1)_GUITex("Base (RGB) Alpha (A)", 2D) = "white"{}_FadeValue("Fade Value", Range(0, 1)) = 1}
  • 步骤2.  然后,我们在 SubShader 块中,设置渲染队列类型和光照模型。您还会注意到,我们为 SubShader 块引入了一些新标签。我们将在后边的知识点中介绍这些。现在,在 SubShader 块的顶部输入以下代码:
Tags { "Queue"="Transparent" "IgnoreProjector"="Ture" "RenderType"="Transparent" }
ZWrite Off
Cull Back
LOD 200
  • 步骤3. 声明 SubShader 标签后,我们需要继续使用 #pragma 指令,并声明自定义光照模型,添加一些我们以前从未见过的新参数。结果会创建一个完全无光照的表面,并且让纹理贴图来控制 GUI 的外观:
CGPROGRAM
#pragma surface surf UnlitGUI alpha novertexlights
  • 步骤4. 我们的下一步是在 Properties 块中的值与 CGPROGRAM 块中的变量之间建立连接:
// 将属性链接到CG程序
sampler2D _GUITex;
float4 _GUITint;
float _FadeValue;
  • 步骤5. 完成所有着色器设置后,我们需要编写无光照光照模型。这相当简单,只需将纹理表的颜色值传递给我们的 SurfaceOutput 结构体:
inline half4 LightingUnlitGUI (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten){half4 c;c.rgb = s.Albedo;c.a = s.Alpha;return c;}
  • 步骤6. 与往常一样,如果我们要使用纹理,我们必须确保在 Input 结构体中获取该纹理的 UV:
// 确保在struct中获得纹理的uv
struct Input {float2 uv_GUITex;};
  • 步骤7. 最后,我们简单地对纹理和 alpha 值进行采样,并将它们传递给 surf() 函数中的 SurfaceOutput 结构体:
void surf (Input IN, inout SurfaceOutput o) 
{float4 texColor = tex2D (_GUITex, IN.uv_GUITex);o.Albedo =  texColor.rgb * _GUITint.rgb;o.Alpha = texColor.a * _FadeValue;
}

        完成着色器后,你应该会看到与下图1.1 非常相似的内容,但如果你使用自己的自定义几何体和纹理表,你的场景看起来会有所不同。除此之外,着色器应该生成具有 alpha 透明度的无光照表面的效果:

图4.3

        不过,你会注意到我们在 GUI 中遇到了一个小错误。我们的背景框,就在按钮后面,实际上是在我们的 Play 游戏按钮上渲染。这是因为网格体靠得太近了,以至于 Unity 很难辨别要先绘制哪个对象。由于顺序当前由与相机的距离决定,因此 Unity 在按钮上方显示背景框。

  • 步骤8. 为了解决这个问题,我们必须按材质更改渲染队列。我们不能简单地更改着色器中的渲染队列,因为这会导致我们必须为每个队列级别编写一个着色器。我们需要对我们的材质球进行单独控制。因此,我们必须编写一个小的 C# 脚本来实现这种效果。下面我们开始实现。
  • 步骤9. 我们首先需要创建一个新的 C# 脚本,以便对 GUI 着色器进行此修复。
  • 步骤10. 创建脚本后,双击它以在 MonoDevelop 中打开它。
  • 步骤11. 我们这里的首要任务是告诉这个脚本在编辑器中运行,这样我们就可以在场景视图中实时看到我们更改队列值的效果。为了实现这一点,我们需要在类声明之前声明 [ExecuteInEditMode] 属性:
[ExecuteInEditMode]
public class ObjectRenderQueue : MonoBehaviour
{
  • 步骤12. 为了实时更改队列级别,我们需要创建一个可以在对象的检查器中更改的新变量。因此,我们声明一个名为 queueValue 的新变量并将其公开,以便它显示在 Inspector 中。
public int queueValue = 2000;
  • 步骤13. 然后我们继续我们的 Update() 函数,首先查看此脚本附加到的对象是否分配了材质:
Material curMaterial = transform.renderer.sharedMaterial;
  • 步骤14. 最后,使用 if() 语句,我们检查以确保我们的 curMaterial 变量中有一个 Material 引用,并且它不是空。这只是为了防止控制台窗口中弹出任何不必要的错误消息。
if(curMaterial)
{curMaterial.renderQueue = queueValue;
}
else
{Debug.LogWarning(transform.name + ":cannot find a material to set the render queue!");
}

        脚本完成后,你现在可以将其赋予给我们的任何 GUI 元素,并在编辑器中即时调整队列值并查看绘图顺序的变化。我们的 GUI 场景现已完成,所有 GUI 元素都按正确的顺序绘制。我们已经对 GUI 中的对象进行了精细的控制,这与 Photoshop 中的图层非常相似,所有这些都是通过创建着色器和一个小脚本来实现的。

图4.4

1.3、实现原理...

        从 GUI 着色器开始,我们引入了几个新的子着色器标签,使我们能够微调着色器与 Unity 渲染器的工作方式。通过声明“IgnoreProjector”=“True”,我们告诉 Unity,我们不希望任何投影仪类型的材质或纹理影响我们的对象或着色器。这是因为我们希望 GUI 与我们的场景分开。所有场景效果(例如投影仪)都应仅影响游戏中的对象,而不影响 GUI。“IgnoreProjector”标签是实现此目的的一种方法。

        我们的第二个新标签是“RenderType”=“Transparent”。与“Queue”标签类似,此标签将着色器分类为 Unity 相机效果的透明类别,以便 Unity 可以为您提供更有条理的方式对对象进行排序。

        着色器的最后一个新元素是在 #pragma 指令中添加了 novertexlights。此参数告诉 Unity 我们不想使用任何每个顶点的光源或球面谐波来照亮我们的对象。事实上,我们根本不想使用任何灯;因此,我们可以使用此参数使我们的着色器更便宜一些,这正是我们在开发 3D GUI 系统时所要追求的。

        将注意力转向我们创建的渲染队列脚本,该脚本只是使用 transform.renderer.sharedMaterial 代码访问附加到对象的材质。如果材质实际上附加到脚本分配到的对象,则该代码行将返回该材质。如果找不到材质,它将返回 null。

        然后,我们检查脚本是否找到了材质并更改渲染队列的值。如果找不到材质,我们只需向控制台发送调试消息,让用户知道该对象需要材质。

        很显然,这是一个对透明度和渲染队列操控的一个简单案例,但这也确实为您提供了创建自己更强大系统所需的基础知识!


​这本书可以让你学习到如何使用着色器和屏幕特效让你的Unity工程拥有震撼的渲染画面。

作者:Kenny Lammers

http://www.dtcms.com/a/359352.html

相关文章:

  • 机器视觉学习-day14-绘制图像轮廓
  • 基于Spring Cloud Sleuth与Zipkin的分布式链路追踪实战指南
  • 《深入剖析Kafka分布式消息队列架构奥秘》之Springboot集成Kafka
  • 【重学MySQL】九十四、MySQL请求到响应过程中字符集的变化
  • html添加水印
  • 馈电油耗讲解
  • 特殊符号在Html中的代码及常用标签格式的记录
  • Spring Task快速上手
  • 【多模态】使用LLM生成html图表
  • 【 复习SpringBoot 核心内容 | 配置优先级、Bean 管理与底层原理(起步依赖 + 自动配置) 】
  • 堆排序:高效稳定的大数据排序法
  • Kubernetes 服务发现与健康检查详解
  • 解锁GPU计算潜能:深入浅出CUDA架构与编程模型
  • ESP32学习笔记_Peripherals(5)——SPI主机通信
  • Asible——将文件部署到受管主机和管理复杂的Play和Playbook
  • 局域网中使用Nginx部署https前端和后端
  • Idea启动错误-java.lang.OutOfMemoryError:内存不足错误。
  • Polkadot - ELVES
  • 鸿蒙搭配前端开发:应用端与WEB端交互
  • SCARA 机器人工具标定方法
  • 【算法笔记】算法归纳整理
  • 从零开始的python学习——语句
  • 晶晨线刷工具下载及易错点说明:生成工作流程XML失败
  • 【CVTE】C++开发 (提前批一面)
  • C++Primer笔记——第七章:类(上)
  • Spring/Spring MVC/iBATIS 应用 HTTP 到 HTTPS 迁移技术方案
  • C语言学习笔记(自取)
  • 【大前端】React配置配置 开发(development)、生产(production)、测试(test)环境
  • C语言强化训练(1)
  • VSCode中使用Markdown