【Unity Shaders】使用CgInclude让你的Shader模块化——建立CgInclude文件存储光照模型

本系列主要參考《Unity Shaders and Effects Cookbook》一书(感谢原书做者),同一时候会加上一点我的理解或拓展。函数

这里是本书所有的插图。ui

这里是本书所需的代码和资源(固然你也可以从官网下载)。spa

========================================== 切割线 ==========================================操作系统



写在前面


了解内置的CgInclude文件固然很是好,但是假设咱们想要建立本身的CgInclude文件来存储光照模型和辅助函数又该怎么办呢?.net


好消息是咱们的确可以建立本身的CgInclude文件。坏消息是咱们需要再了解一点代码语法。好啦,那就開始吧!code



准备工做


好消息是此次的准备工做最终有点不一样了。。orm

。坏消息是我不能再复制粘贴了。blog

ip

资源

  1. 首先,建立一个新的文本文件,好比MyCgInclude.txt。
  2. 而后。把文件后缀改成.cginc。

    固然,操做系统一般会给你一些警示信息。说这个文件将变得不可用,但相信我,咱们这个是可用的。

  3. 将新的.cginc文件导入到咱们的Unity项目中(注意,在个人项目里,它的位置在一个新的名为CgIncludes的目录下)。等编译完毕后,咱们可以看到Unity把该文件当成一个CgInclude文件编译好了。像如下这样:

现在,咱们已经作好准备可以建立本身定义的CgInclude代码啦。双击CgInclude文件。在MonoDevelop中打开它吧~


实现


打开CgInclude文件后。開始键入例如如下代码。
  1. 首先,使用如下的预处理指令開始咱们的CgInclude文件。

    这些声明和#pragma、#include类似,在这里,咱们想要去定义一个新的代码集合,仅仅要咱们的Surface Shader在它的编译指令里面包括了这个文件,这些代码就可以运行了。在CgInclude文件的最開始键入例如如下代码:

    #ifndef MY_CG_INCLUDE
    #define MY_CG_INCLUDE

  2. 而后。咱们必须确保#ifndef或者#ifdef要有一个#endif来结束定义检查。

    就和一个if语句需要两个花括号同样。

    #define指令如下键入例如如下代码:

    #endif

  3. 接下来,咱们就可以填充剩余部分了。键入例如如下代码:
    // Custom Build-in Variables
    fixed4 _MyColor;
    
    // Lighting models
    inline fixed4 LightingHalfLambert (SurfaceOutput s, fixed3 lightDir, fixed atten) {
    	fixed diff = max (0, dot (s.Normal, lightDir));
    	
    	diff = (diff + 0.5) * 0.5;
    	
    	fixed4 c;
    	c.rgb = s.Albedo * _LightColor0.rgb * ((diff * _MyColor.rgb) * atten * 2);
    	c.a = s.Alpha;
    	return c;
    }
    

  4. 如下是完整的MyCgInlcude.cginc文件:
    #ifndef MY_CG_INCLUDE
    #define MY_CG_INCLUDE
    
    // Custom Build-in Variables
    fixed4 _MyColor;
    
    // Lighting models
    inline fixed4 LightingHalfLambert (SurfaceOutput s, fixed3 lightDir, fixed atten) {
    	fixed diff = max (0, dot (s.Normal, lightDir));
    	
    	diff = (diff + 0.5) * 0.5;
    	
    	fixed4 c;
    	c.rgb = s.Albedo * _LightColor0.rgb * ((diff * _MyColor.rgb) * atten * 2);
    	c.a = s.Alpha;
    	return c;
    }
    
    #endif

    上面至关于一个头文件,但想要完整利用它还需要一些其它的步骤。

    咱们需要告诉当前的Shader。咱们想要使用本身的文件和代码。

  5. 返回上一节所用的Shader。

    咱们需要在块中包括咱们本身的CgInclude文件,就像C++中需要在开头加入头文件引用同样。

    同一时候,以前咱们的Shader使用内置的Lambert光照模型,但现在咱们想要使用本身定义的Half Lambert光照模型。

    因为咱们已经包括了该CgInclude文件,咱们可以直接在#pragma指令中指明这一模型:

    		CGPROGRAM
    		#include "../CgIncludes/MyCgInclude.cginc"
    		#pragma surface surf HalfLambert

    解释:这里需要指明.cginc文件的相对与该Shader的路径。

    也就是说。假设它和Shader放在同一个目录下,那么直接写名称就能够。

    但在个人项目中,Shader放在了Shaders目录下,而.cginc放在了CgIncludes目录下。所以需要上述写法。

  6. 最后,还记得咱们在CgInclude文件里声明了一个_MyColor变量吗?咱们还需要在Shader的Properties中加入该属性:
    	Properties {
    		_MainTex ("Base (RGB)", 2D) = "white" {}
    		_DesatValue ("Desaturate", Range(0, 1)) = 0.5
    		_MyColor ("My Color", Color) = (1, 1, 1, 1)
    	}


最后,返回Unity。假设出现编译错误。说找不到.cginc文件。那么就是你的位置写的有问题。又一次看上面的解释更改一下就可以喽。

最后的结果例如如下所看到的。注意到。这里Unity已经使用了咱们新的Half Lambert光照模型(和原来相比。就是提亮了背光面的亮度),并且加入了一个新的样色样本。左側为上一篇结果,右側为本篇结果。
 



解释


当编写Shader的时候,咱们可以像使用C++中的头文件同样,使用#include预处理指令来包括其它代码集合。这告诉Unity咱们想要当前的Shader使用包括的这些文件里的代码。咱们这样作其实是在对应位置包括了Cg代码片断。

一旦咱们声明了#include指令,Unity就可以在项目中找到该文件,而后Unity会在文件里查找定义的代码片断。

也就是指。咱们使用#ifndef指令和#ifndef指令的地方。

当咱们声明#ifndef指令时,咱们就是在告诉Unity,假设未定义这个名字,那么就使用这个名字去定义一些东西!

在本节中。咱们是想要去#define MY_CG_INCLUDE

所以,假设Unity没有找到一个名为MY_CG_INCLUDE的定义,它就会在编译该CgInclude文件时建立它。而#endif就是告诉Unity,这是该定义在这里结束啦。如下的不用再找啦。


现在,你看到了本身定义的CgInclude文件是多么强大(和C++中的头文件类似),咱们可以使用它们来存储所有的本身定义光照模型。以下降代码的反复。

其它优势,像灵活性等。你可以联想C++头文件来得出啦~