第十章 更高级的着色器技术

在本章节中,你将会学习下面的这些知识点:

  • 使用Unity内建的 CG包含(CgInclude) 文件功能
  • 使用CG包含让着色器模块化
  • 实现一个毛皮效果的着色器
  • 用数组来实现热度图

  • 介绍
    最后这一章涵盖了一些可用于游戏的高级的着色器技术。你需要记住的是很多你在游戏中看到的引人入胜的效果,都来自于对着色器技术反复打磨,力求完美的追求。这本书只是抛砖引玉罢了,教你一些修改和创建着色的知识。所以非常强烈的鼓励你竭尽所能的利用这些知识去实践和实验。制作一款好的游戏并不是一项追求超现实主义的任务;学习着色器不是为了完全模仿现实,因为这是办不到的。相反的,你应该试着把着色器当成一个工具来让你的游戏变得独一无二。有了最后这一章的知识后,你就能够创建自己想要的材质了。

使用Unity内建的CG包含文件功能

编写我们自己的 CG包含文件(CgInclude files) 的第一步就是先去了解Unity都给我们提供了哪些现成的着色器。编写 表面着色器(Surface Shaders) 的时候,其实这隐藏的背后发生了很多的事情,这让编写表面着色器的过程变得非常高效。我们是可以查看这些CG包含文件所包含的代码的,位置就在 Editor | Data | CGIncludes [编辑器的安装位置,译者注]。所有的文件都包含在这个目录下,发挥着跟我们自己的着色器一起将我们的游戏对象渲染到屏幕上的作用。这些文件中,有些负责阴影和光照,有些提供一些有用的功能,还有的负责管理跟平台相关的依赖等。如果没有它们,那么我们的着色器编写体验将会变得更加繁琐。


你可以通过下面的链接查找Unity给我们的提供的相关CG包含文件的信息列表:
https://docs.unity3d.com/Manual/SL-BuiltinIncludes.html


让我们开始了解Unity内建的CG包含文件吧,从 UnityCG.cginc 这个文件中使用一些内建的帮助函数。


  • 始前准备
    在迫不及待开始编写着色器前,我们需要在场景中设置一些东西。我们需要创建下面的这些东西并且在代码编辑器打开着色器:
    • 1.创建一个新的场景然后在场景中添加一个简单的球体。
    • 2.创建一个新的材质和着色器。
    • 3.将着色器挂载到材质上然后将材质应用到球体上。
    • 4.之后,创建一个 平行光(directional light) 并且将它放到我们的球体的上面。
    • 5.最后,我们将要去打开 UnityCG.cginc 这文件了,它在Unity的 包含文件夹(CgInclude folder) 下面,位置在Unity编辑器的安装位置。这可以让我们分析一些帮助函数的代码,好让我们能更好的理解当我们使用它们的过程中都发生了什么。
    • 6.你应该有了一个简单的场景用来跑着色器了,就像下面的屏幕截图一样:
      diagram

  • 操作步骤
    场景准备好后,我们现在就可以开始对一些包含在 UnityCG.cginc 文件中的内建帮助函数进行实验了。在Unity中双击我们之前为场景创建的着色器,这样在代码编辑器中打开它,然后在着色器中输入下面步骤提供的代码:
    • 1.在着色器的 属性块(Properties block) 中添加下面的代码。我们需要给这个演示的着色器添加一张单独的纹理和一个滑动条:
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _DesatValue ("Desaturate", Range(0, 1)) = 0.5
    }
    
    • 2.之后我们需要给着色的 属性块(Properties block)CGPROGRAM代码块(CGPROGRAM blocks) 建立数据链接,在 CGPROGRAM 声明和 #pragma指令(#pragma directives) 后面添加下面的代码:
    sampler2D _MainTex;
    fixed _DesatValue;
    
    • 3.最后,我们要更新 surf() 函数,请使用下面的代码。我们会介绍一个我们还没有见过的新的函数,这个函数就是内建在Unity的 UnityCG.cginc 文件中的:
    void surf (Input IN, inout SurfaceOutputStandard o)
    {
        fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
        c.rgb = lerp(c.rgb, Luminance(c.rgb), _DesatValue);
        o.Albedo = c.rgb;
        o.Alpha = c.a;
    }
    
    [译者:注意上面的函数中的第二行代码,如果你按照原书写的话,请把r.rgb改成c.rgb]
    当代码修改好之后,那么你将会看见跟下面的截屏类似的结果。我们简单的使用了一个Unity的内建包含文件中的帮助函数,它让我们着色器的主纹理有一个 不饱和度的效果(effect of desaturating)
    diagram

  • 原理介绍
    使用名为 Luminance() 的内建帮助函数,我们可以在着色器快速获得不饱和或者灰度效果。这些都是可能的,因为我们在使用表面着色器的时候 UnityCG.cginc 文件会被自动引入进我们的着色器当中。
    当你在代码编辑器中打开这个 UnityCG.cginc 文件,然后搜索 Luminance 这个函数,你会在475行发现它[原文作者说在276行,新版的unity现在在475行,而且可能随时会变,所以请自己亲自打开这个文件搜索一下,译者注]。下面的代码片段就是从该文件中拿出来的:
    // Converts color to luminance (grayscale)
    inline half Luminance(half3 rgb)
    {
      return dot(rgb, unity_ColorSpaceLuminance.rgb);
    }
    
    当这个函数被包含进着色器并且Unity自动编译这个着色器之后,我们同样也就可以在自己的代码中使用这个函数了,正因如此减少了非常多代码量,而这些代码我们如果不这样做可能会一遍又一遍的重复编写。
    如果你有注意的话,Unity还给我们提供了一个名为 Lighting.cginc 的文件。这个文件包含了我们使用的所有的光照模型,比如我们在着色器中声明 #pragma Surface surf Lambert 类似的语句就在引用其中的一些东西。仔细筛查一遍这个文件显示,所有的光照模型都定义在这个文件内,它们可以重用和用于模块化。