Unity Shader入门精要笔记(七):Unity Shader内置文件、变量和语义

时间:2021-11-25 10:22:42

本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。

http://blog.csdn.net/lzhq1982/article/details/74279052


1、Unity提供的内置文件和变量

上一篇我们学了一个简单的顶点/片元着色器。复杂的着色器可能需要我们处理法线,光照,阴影等。为了方便开发者的编码,Unity提供了很多内置文件,包含了提前定义的函数、变量和宏等。下面我们逐一讲解。

内置包含文件:类似于C++的include。在Unity中,包含文件的后缀是.cginc。这样我们就可以使用该包含文件为我们提供的变量和函数。写法如下:

CGPROGRAM

//...

#include "UnityCG.cginc"

//...

EndCG

这些文件我们可以在官网下载(下载地址),在对应版本的下载的下拉框里选内置着色器,下下来的样子,这里我用书上的截图:

Unity Shader入门精要笔记(七):Unity Shader内置文件、变量和语义

a、CGIncludes文件夹中包含的是内置的包含文件。我们以后需要常去查阅一些内置函数的写法,就在这里找对应的包含文件。

b、DefaultResources文件夹中包含了一些内置组件或功能所需要的Unity Shader。

c、DefaultResourcesExtra中包含了所有Unity内置的Shader,这也是我们常常光顾的地方,可以去学习并利用这些内置Shader。

d、Editor只包含了一个文件,用于定义Unity5引入的Standard Shader所用的材质面板。

对于包含文件我们也可以从Unity应用程序中找到CGIncludes文件夹。Mac位置:/Applications/Unity/Unity.app/Contents/CGIncludes;Windows位置:Unity安装路径/Data/CGIncludes。

下表给出了CGIncludes中主要的包含文件以及他们的主要用处:

Unity Shader入门精要笔记(七):Unity Shader内置文件、变量和语义

可以看出,像UnityShaderVariables.cginc和HLSLSupport.cginc,即使我们没用#include包含进来,它们也会自动包含进来,例如UnityShaderVariables.cginc里的UNITY_MATRIX_MVP,我们可以直接用。

UnityCG.cginc是最常接触的包含文件。我们常用的结构体和函数基本都在里面。例如,我们可以直接使用其中预定义的结构体作为顶点着色器的输入和输出,如下:

Unity Shader入门精要笔记(七):Unity Shader内置文件、变量和语义

此外,UnityCG.cginc也提供了一些常用的帮助函数。比如下面这些:

Unity Shader入门精要笔记(七):Unity Shader内置文件、变量和语义

读者感兴趣可以自行阅读他们的实现,这些我们在光照那里会常用到,到时候可以回头来查阅。

2、Unity提供的CG/HLSL语义

在前一篇的简单的着色器中,我们看到了SV_POSITION,POSITION,COLOR0等。这些是CG/HLSL提供的语义。语义实际上就是一个赋给Shader输入输出的字符串,这个字符串表达了这个参数的含义。通俗的讲,这些语义可以让Shader知道从哪里读取数据,并把数据输出到哪里,这在Shader中是不可缺少的。

Unity为了方便对模型数据的传输,对一些语义进行了特别的规定。尤其是顶点着色器的输入结构体,像POSITION,NORMAL,TANGENT,TEXCOORD0,Unity会自动识别这些语义,对应的把模型坐标,法线,切线,第一组纹理坐标赋给它们。但对于顶点着色器的输出,也就是片元着色器的输入结构体来说,我们可以自行决定一些语义传递的数据,比如我们在会常用TEXCOORD系列传递一些坐标,颜色啥的数据,在这里它就不是纹理坐标了。

DirectX10以后,有了一种新的语义类型,就是系统数值语义(system-value semantics)。这类语义以SV开头,SV代表系统数值。比如用SV_POSITION修饰顶点着色器的输出变量pos,则pos就是齐次裁剪空间中的坐标。这些语义描述的变量是不可以随便赋值的,流水线需要使用它们完成特定的目的。比如SV_POSITION修饰的变量经过光栅化后会显示在屏幕上。

下表总结了应用阶段传递模型数据给顶点着色器是Unity常用的语义:

Unity Shader入门精要笔记(七):Unity Shader内置文件、变量和语义

TEXCOORDn中n的数目和Shader Model有关,Shader Model 2(Unity默认的Shader Model版本)和3中,n等于8,Shader Model 4和5中,n等于16。通常一个模型的纹理坐标组数不超过2,用TEXCOORD0和TEXCOORD1就够了。在内置的结构体appdata_full中,最多使用6个纹理坐标。

Unity Shader入门精要笔记(七):Unity Shader内置文件、变量和语义

上图是顶点着色器给片元着色器传递数据时用的语义,除了SV_POSITION有特别含义,其他语义没有明确要求。也就是说我们可以存任何值到这些语义描述的变量中。通常我们习惯用TEXCOORDn来传递我们自定义的数值。

Unity Shader入门精要笔记(七):Unity Shader内置文件、变量和语义

上图是Unity中片元着色器的输出语义。

3、Shader整洁之道

1)float、half还是fixed

在CG/HLSL中,有3种精度类型:float,half和fixed。这些精度将决定计算结果的数值范围。如下表:

Unity Shader入门精要笔记(七):Unity Shader内置文件、变量和语义

上面的精度并不是绝对正确的,尤其是在不同平台和GPU上,实际精度可能与上面的范围不一致。

a、大多数桌面GPU会按最高的浮点精度计算,也就是说float、half、fixed在这些平台上是等价的。所以在PC上很难看出它们的不同。

b、重点是在移动平台上,它们的精度范围很重要,会导致运算速度有差异。如果做移动端产品,我们要确保在真正的移动设备上验证我们的Shader。

c、fixed精度实际上只在一些较旧的移动平台上有用,在大多数现代GPU上,它们内部会把fixed和half当初同等精度对待。

尽管有上面的不同,但一个基本建议是,尽可能使用精度较低的类型,因为这可以优化Shader的性能,在移动平台尤其重要。我们可以用fixed类型存储颜色和单位矢量,用half存储较大精度范围的数据,最差情况下用float。

2)避免不必要的计算

如果毫无节制的在Shader(尤其是片元着色器)中进行了大量计算,那么我们可能很快就会收到Unity的错误提示:

temporary register limit of 8 exceeded

Arithmetic instruction limit of 64 exceeded; 65 arithmetic instructions needed to compile program

出现这些错误大多是因为我们在Shader中进行了过多的运算,使得需要的临时寄存器数目或指令数目超过了当前可支持的数目。不同的Shader Target、不同的着色器阶段,我们可用的临时寄存器和指令数目不同。我们可以通过指定更高等级的Shader Target来消除这些错误。如下表:

Unity Shader入门精要笔记(七):Unity Shader内置文件、变量和语义

注意,所有类似OpenGL的平台(包括移动平台)被当成是支持到Shader Model 3.0的。而WP8/WinRT平台则只支持到Shader Model 2.0。

前面说的Shader Model是由微软提出的一套规范,它们决定了Shader中各个特性的能力。而这些特性和能力体现在Shader能使用的运算指令数目、寄存器个数等各个方面。Shader Model等级越高,Shader的能力就越强。虽然这样,但一个更好的方法是尽可能减少Shader中的运算,或通过预计算的方式提供更多数据。

3)慎用分支和循环

在各种编程语言中,类似分支、循环这样的流程控制语言是最基础的语法之一,但在Shader中,要对它们格外小心。

它们在GPU的实现和CPU有很大不同,在最坏的情况下,我们花在一个分支语句的时间相当于运算了所有分支语句的时间。所以我们不鼓励使用流程控制语句,它们会降低GPU的并行处理操作。一个解决方案是我们尽量把计算向流水线上端移动,例如把片元着色器的运算放在顶点着色器,或在CPU预计算,把结果传给Shader。如果非要用它们,建议是:

a、分支判断语句中使用的条件变量最好是常数,不会发生变化。

b、每个分支中包含的操作指令尽可能少。

c、分支的嵌套层数尽可能少。