第五章 顶点函数

**着色器(shader)**这个术语最开始源于Cg领域,它主要用于模拟现实中的光照(阴影)在3D模型中的表现。而如今,着色器的用途远不止上面这些。它不仅可以定义物体的外观表现方式,还可以完全重新定义它们的形状。 如果你想学习如何通过着色器来修改3D模型的几何形状,这一章的内容非常适合你。

在这一章,你将会学习下面这些知识点:

  • 在表面着色器(Surface Shader)中访问顶点颜色
  • 对表面着色器中的顶点使用动画
  • 模型挤压
  • 实现下雪效果着色器
  • 实现范围体爆炸

介绍

第一章创建你的第一个着色器中,我们 解释了3D模型不仅仅是一些三角形的集合。每一个顶点都包含了要正确渲染这个模型所必须的一些数据。这一章,为了在着色中能使用这些数据,我们将探索如何访问这些信息。而且我们还将探索如何使用Cg代码对物体进行形变的具体细节。


在表面着色器中访问顶点颜色

在这个章节,让我们来看看如何在着色器中用顶点函数来访问这些模型的顶点信息。这些知识旨在让我利用模型顶点包含的信息元素来创造一些真正实用的和引人入胜的视觉效果。
顶点函数中的顶点能返回一些我们想要的信息。你可以用float3这类值来获得顶点的法线方向信息,顶点的位置信息。你甚至可以将颜色值保存在每个顶点中,并且用float4这类值来返回该颜色。这些将是这个知识点我们将要讲的东西。 我们需要理解在表面着色器(Surface Shader)中,如何将颜色信息保存到顶点中以及如何获取这些颜色信息。


  • 始前准备

    为了编写这个着色器,我们需要准备一些资源。下面这些步骤是我们创建这个顶点着色器(Vertex Shader)的准备和设置:
    1.为了能够看见顶点颜色,我们需要一个顶点设置了颜色的模型。当然你可以用Unity来添加这些颜色,这样的话你不得不编写一个工具来单独的应用这些颜色,或者编写一些脚本来获取刚刚添加的颜色。但是在这里,我们简单的利用Maya 来把颜色应用到我们的模型顶点上。但是现在这个模型你可以在这本书的支持页面获得https://www.packtpub.com/books/content/support【实际上这个地址根本没有这个模型,这本书 的代码我放在这里了本书的代码包】
    2.创建一个新的场景,并且把导入的模型放到场景中。
    3.创建一个新的着色器和一个新的材质。完成创建后,把着色器添加到材质上,然后把材质添加到模型上。

    你的场景看起来应该跟下面的屏幕截图一样:

    diagram


  • 操作步骤

    随着我们的场景,着色器,材质这些创建好后,我们就可以开始为我们的着色器编写代码了。我们在编辑器的Project面板中双击我们创建的Shader打开它。然后跟着下面的步骤走:
    1.因为我们创建的着色器很简单,所以在着色器的 属性(Properties) 块中没有必要包含任何属性。但我们仍然需要有一个全局的tint颜色,仅仅是为了跟书中的其他着色器形式上保持一致而已。在着色器的属性块中写入以下代码:

    Properties
    {
        _MainTint("Global Color Tint", Color) = (1,1,1,1)
    }
    

    2.下面这一步中, 是告诉Unity我们将在着色器中包含一个顶点函数:

    CGPROGRAM
    #pragma surface surf Lambert vertex:vert
    

    3.跟往常一样,如果我们在属性块中包含了属性,那么我们要确保在 CGPROGRAM 声明中定义与之对应的变量才行。我们在 #pragma 声明的正下方输入下面的代码:

    float4 _MainTint;
    

    4.接下来我们要关注 输入结构体(Input struct)。我们还要再定义一个新的变量,主要是给我们的 surf() 方法用,用来接收从 vert() 这个函数中返回的数据:

    struct Input
    {
        float2 uv_MainTex;
        float4 vertColor;
    };
    

    5.现在我们可以来实现 vert() 函数了,这个函数可以访问我们每一个保存在网格中的顶点色:

    void vert(inout appdata_full v, out Input o)
    {
        o.vertColor = v.color;
    }
    

    6.最后,我们可以使用来自 **输入结构体(Input struct)**的顶点颜色数据,并且通过内建 SurfaceOutput struct 把数据赋值给 o.Albedo 参数:

    void surf (Input IN, inout SurfaceOutput o)
    {
        o.Albedo = IN.vertColor.rgb * _MainTint.rgb;
    }
    

    7.随着我们的代码写完,现在我们重新进入我们的Unity编辑器,让着色器的代码编译完。如果一切进展顺利,那么你将会看到如下屏幕截图所展示的一样:

    diagram


  • 原理介绍 Unity通过着色器给我们提供了访问模型的顶点信息的方法。我们也因此有了修改顶点位置和颜色的能力。在这个知识点中,我们从Maya(你用其他任何一个3D软件也行)中导入了一个网格,网格的顶点颜色被添加到了Verts。 在导入这个模型的时候你可能会注意到,默认的材质并不会显示出顶点颜色来。事实上我们不得不写一个着色器来提取出顶点颜色并且把这些颜色显示在模型的表面。当我们在使用表面着色器的时候,Unity其实给我们提供了大量的内 建函数,让我们在提取顶点信息的时候变得非常的快速和高效。

    当我们创建着色器的时候,我们的第一个任务是告诉Unity我们将要使用顶点函数。为了达此目的,我们在 CGPROGRAM#pragma 声明中添加了 vertex:vert 参数。这使得Unity在编译着色器的时候会自动去寻找名为 vert 的顶点函数。如果它没有找到,Unity救护抛出一个编译异常并且会要求你添加一个vert函数到着色器中去。

    这才让我们有了下一步。我们需要编写 vert 函数,正如第5步中展示的那样。当有了这个函数后,我们就可以访问内建的 appdata_full 数据结构。这个内建的结构体就是保存顶点信息的地方。然后我们通过 o.vertColor = v.color 这行代码提取到了顶点颜色信息,并且传递给了我们的 输入结构体(Input struct)
    变量 o 表示了我们的 输入结构体(Input struct),然后 v 就是我们的 appdata_full 顶点数据。在这个例子中,我们只是简单的从我们的 appdata_full 结构体中拿到颜色信息并且把这个信息放到我们的 输入结构体(Input struct) 中。一旦这个顶点颜色在我们的 输入结构体(Input struct) 后,我们就可以在 surf() 函数中使用它了。在我们这个知识点的该例子中,我们简单的把颜色赋值给了内建的 表面输出(SurfaceOutput) 结构体的 o.Albedo 参数。


  • 额外内容
    我们可以从 vert 颜色数据中获得第四个额外信息。如果你有留意的话,我们在Input struct中定义的这个vertColor变量是一个 float4的类型。也就是说我们也传递了顶点颜色的alpha值。知道这一点后,利用这个优点你就可以通过保存第四个 顶点颜色参数用来实现诸如半透明效果或者通过两张纹理来实现遮罩效果。当然我只是在这里稍微提醒一下,你具体要不要使用顶点颜色的第四个参数还是取决于你和你的产品需求。

    通过Unity5,我们有能力为DirectX11编写着色器了。这非常好,但也意味着着色器的编译过程也更加严格了。也就是说在着色器中我们需要添加额外的一行代码来合理的初始化输出的顶点信息。如果你要在你的着色器中使用DirectX11的话,那么代码 应该像下面展示的那样编写:

    void vert(inout appdata_full v, out Input o)
    {
        UNITY_INITIALIZE_OUTPUT(Input, o);
        o.vertColor = v.color;
    }
    

    通过添加这么一行代码,你的顶点着色器就不会再抛出类似于“它在DirectX11中可能不会被正确的编译”之类的任何异常了。