Unity Shader入门精要之Unity 提供的内置文件和变量

网友投稿 1816 2022-11-20

Unity Shader入门精要之Unity 提供的内置文件和变量

Unity Shader入门精要之Unity 提供的内置文件和变量

Unity系列文章目录

文章目录

​​Unity系列文章目录​​​​前言​​

​​5.3.1 内置的包含文件​​​​5.3.2 内置的变量​​

​​二、Unity 提供的Cg/HLSL 语义​​​​5.5 程序员的烦恼:Debug​​​​5.6 小心:渲染平台的差异​​​​5.7 Shader 整洁之道​​​​参考​​

前言

5.3.1 内置的包含文件

包含文件(include file),是类似于C++中头文件的一种文件。在Unity 中,它们的文件后缀 是.cginc。在编写Shader 时,我们可以使用#include 指令把这些文件包含进来,这样我们就可以使 用Unity 为我们提供的一些非常有用的变量和帮助函数。例如: CGPROGRAM // … #include “UnityCG.cginc” // … ENDCG 那么,这些文件在哪里呢?我们可以在官方网站(archive)上选择- -> 内置着色器来直接-这些文件,图5.3 显示了由官网压缩包得到的文件。

从图5.3 中可以看出,从官网-的文件中包含了多个文件夹。其中,CGIncludes 文件夹中 包含了所有的内置包含文件;DefaultResources 文件夹中包含了一些内置组件或功能所需要的 Unity Shader,例如一些GUI 元素使用的Shader;DefaultResourcesExtra 则包含了所有Unity 中内 置的Unity Shader;Editor 文件夹目前只包含了一个脚本文件,它用于定义Unity 5 引入的Standard Shader(详见第18 章)所用的材质面板。这些文件都是非常好的参考资料,在我们想要学习内置 着色器的实现或是寻找内置函数的实现时,都可以在这里找到内部实现。但在本节中,我们只关 注CGIncludes 文件夹下的相关文件。 我们也可以从Unity 的应用程序中直接找到CGIncludes 文件夹。在Mac 上,它们的位置是: /Applications/Unity/Unity.app/Contents/CGIncludes;在Windows 上,它们的位置是:Unity 的安装 路径/Data/CGIncludes。

5.3.2 内置的变量

我们在4.8 节给出了一些用于坐标变换和摄像机参数的内置变量。除此之外,Unity 还提供了 用于访问时间、光照、雾效和环境光等目的的变量。这些内置变量大多位于UnityShader Variables.cginc 中,与光照有关的内置变量还会位于Lighting.cginc、AutoLight.cginc 等文件中。当 我们在后面的学习中遇到这些变量时,再进行详细的讲解。

二、Unity 提供的Cg/HLSL 语义

读者在平时的Shader 学习中可能经常看到,在顶点着色器和片元着色器的输入输出变量后还 有一个冒号以及一个全部大写的名称,例如在5.2 节看到的SV_POSITION、POSITION、COLOR0。 这些大写的名字是什么意思呢?它们有什么用呢? 5.4.1 什么是语义 实际上,这些是Cg/HLSL 提供的语义(semantics)。如果读者从前接触过Cg/HLSL 编程的 话,可能对这些语义很熟悉。读者可以在微软的关于DirectX 的文档(en-us/library/windows/desktop/bb509647(v=vs.85).aspx#VS)中找到关于语义的详细说明页面。根据 文档我们可以知道,语义实际上就是一个赋给Shader 输入和输出的字符串,这个字符串表达了这 个参数的含义。通俗地讲,这些语义可以让Shader 知道从哪里读取数据,并把数据输出到哪里, 它们在Cg/HLSL 的Shader 流水线中是不可或缺的。需要注意的是,Unity 并没有支持所有的语义。 通常情况下,这些输入输出变量并不需要有特别的意义,也就是说,我们可以自行决定这些 变量的用途。例如在上面的代码中,顶点着色器的输出结构体中我们用COLOR0 语义去描述color 变量。color 变量本身存储了什么,Shader 流水线并不关心。 而Unity 为了方便对模型数据的传输,对一些语义进行了特别的含义规定。例如,在顶点着 色器的输入结构体a2v 用TEXCOORD0 来描述texcoord,Unity 会识别TEXCOORD0 语义,以把 模型的第一组纹理坐标填充到texcoord 中。需要注意的是,即便语义的名称一样,如果出现的位 置不同,含义也不同。例如,TEXCOORD0 既可以用于描述顶点着色器的输入结构体a2v,也可 用于描述输出结构体v2f。但在输入结构体a2v 中,TEXCOORD0 有特别的含义,即把模型的第一 组纹理坐标存储在该变量中,而在输出结构体v2f 中,TEXCOORD0 修饰的变量含义就可以由我 们来决定。 在DirectX 10 以后,有了一种新的语义类型,就是系统数值语义(system-value semantics)。 这类语义是以SV 开头的,SV 代表的含义就是系统数值(system-value)。这些语义在渲染流水线 中有特殊的含义。例如在上面的代码中,我们使用SV_POSITION 语义去修饰顶点着色器的输出 变量pos,那么就表示pos 包含了可用于光栅化的变换后的顶点坐标(即齐次裁剪空间中的坐标)。 用这些语义描述的变量是不可以随便赋值的,因为流水线需要使用它们来完成特定的目的,例如 渲染引擎会把用SV_POSITION 修饰的变量经过光栅化后显示在屏幕上。读者有时可能会看到同 一个变量在不同的Shader 里面使用了不同的语义修饰。例如,一些Shader 会使用POSITION 而非 SV_POSITION 来修饰顶点着色器的输出。SV_POSITION 是DirectX 10 中引入的系统数值语义,

在绝大多数平台上,它和POSITION 语义是等价的,但在某些平台(例如索尼 PS4)上必须使用 SV_POSITION 来修饰顶点着色器的输出,否则无法让Shader 正常工作。同样的例子还有COLOR 和SV_Target。因此,为了让我们的Shader 有更好的跨平台性,对于这些有特殊含义的变量我们最 好使用以SV 开头的语义进行修饰。我们在5.6 节中会总结更多这种因为平台差异而造成的问题。 5.4.2 Unity 支持的语义 表5.5 总结了从应用阶段传递模型数据给顶点着色器时Unity 使用的常用语义。这些语义虽 然没有使用SV 开头,但Unity 内部赋予了它们特殊的含义。

上面的语义中,除了SV_POSITION 是有特别含义外,其他语义对变量的含义没有明确要求, 也就是说,我们可以存储任意值到这些语义描述变量中。通常,如果我们需要把一些自定义的数 据从顶点着色器传递给片元着色器,一般选用TEXCOORD0 等。 表5.7 给出了Unity 中支持的片元着色器的输出语义。

等。下面的代码给出了一个使用语义来修饰不同类型变量的例子: struct v2f { float4 pos : SV_POSITION; fixed3 color0 : COLOR0; fixed4 color1 : COLOR1; half value0 : TEXCOORD0; float2 value1 : TEXCOORD1; }; 关于何时使用哪种变量类型,我们会在5.7.1 节给出一些建议。但需要注意的是,一个语义可 以使用的寄存器只能处理4 个浮点值(float)。因此,如果我们想要定义矩阵类型,如float3×4、 float4×4 等变量就需要使用更多的空间。一种方法是,把这些变量拆分成多个变量,例如对于 float4×4 的矩阵类型,我们可以拆分成4 个float4 类型的变量,每个变量存储了矩阵中的一行数据。

5.5 程序员的烦恼:Debug

有这样一个笑话,据说只有程序员才能看懂:

问:程序员最讨厌康熙的哪个儿子? 答:胤禩。因为他是八阿哥(谐音:bug)。 调试(debug),大概是所有程序员的噩梦。而不幸的是,对一个Shader 进行调试更是噩梦中 的噩梦。这也是造成Shader 难写的原因之一—如果发现得到的效果不对,我们可能要花非常多 的时间来找到问题所在。造成这种现状的原因就是在Shader 中可以选择的调试方法非常有限,甚 至连简单的输出都不行。 本节旨在给出Unity 中对Unity Shader 的调试方法,这主要包含了两种方法。 5.5.1 使用假彩色图像 假彩色图像(false-color image)指的是用假彩色技术生成的一种图像。与假彩色图像对应的 是照片这种真彩色图像(true-color image)。一张假彩色图像可以用于可视化一些数据,那么如 何用它来对Shader 进行调试呢? 主要思想是,我们可以把需要调试的变量映射到[0, 1]之间,把它们作为颜色输出到屏幕上, 然后通过屏幕上显示的像素颜色来判断这个值是否正确。读者心里可能已经在咆哮:“什么?!这 方法也太原始了吧!”没错,这种方法得到的调试信息很模糊,能够得到的信息很有限,但在很长 一段时间内,这种方法的确是唯一的可选方法。 需要注意的是,由于颜色的分量范围在[0, 1],因此我们需要小心处理需要调试的变量的范围。 如果我们已知它的值域范围,可以先把它映射到[0, 1]之间再进行输出。如果你不知道一个变量的 范围(这往往说明你对这个Shader 中的运算并不了解),我们就只能不停地实验。一个提示是, 颜色分量中任何大于1 的数值将会被设置为1,而任何小于0 的数值会被设置为0。因此,我们可 以尝试使用不同的映射,直到发现颜色发生了变化(这意味着得到了0~1 的值)。 如果我们要调试的数据是一个一维数据,那么可以选择一个单独的颜色分量(如R 分量)进 行输出,而把其他颜色分量置为0。如果是多维数据,可以选择对它的每一个分量单独调试,或 者选择多个颜色分量进行输出。 作为实例,下面我们会使用假彩色图像的方式来可视化一些模型数据,如法线、切线、纹理 坐标、顶点颜色,以及它们之间的运算结果等。我们使用的代码如下:

Shader "Unity Shaders Book/Chapter 5/False Color" {SubShader {Pass {CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct v2f {float4 pos : SV_POSITION;fixed4 color : COLOR0;};v2f vert(appdata_full v) {v2f o;o.pos = mul(UNITY_MATRIX_MVP, v.vertex);// 可视化法线方向o.color = fixed4(v.normal * 0.5 + fixed3(0.5, 0.5, 0.5), 1.0);// 可视化切线方向o.color = fixed4(v.tangent.xyz * 0.5 + fixed3(0.5, 0.5, 0.5), 1.0);// 可视化副切线方向fixed3 binormal = cross(v.normal, v.tangent.xyz) * v.tangent.w;o.color = fixed4(binormal * 0.5 + fixed3(0.5, 0.5, 0.5), 1.0);// 可视化第一组纹理坐标o.color = fixed4(v.texcoord.xy, 0.0, 1.0);// 可视化第二组纹理坐标o.color = fixed4(v.texcoord1.xy, 0.0, 1.0);// 可视化第一组纹理坐标的小数部分o.color = frac(v.texcoord);if (any(saturate(v.texcoord) - v.texcoord)) {o.color.b = 0.5;}o.color.a = 1.0;// 可视化第二组纹理坐标的小数部分o.color = frac(v.texcoord1);if (any(saturate(v.texcoord1) - v.texcoord1)) {o.color.b = 0.5;}o.color.a = 1.0;// 可视化顶点颜色//o.color = v.color;return o;}fixed4 frag(v2f i) : SV_Target {return i.color;}ENDCG}}}```在上面的代码中,我们使用了Unity 内置的一个结构体—appdata_full。我们在5.3 节讲过该结构体的构成。我们可以在UnityCG.cginc 里找到它的定义:```cppstruct appdata_full {float4 vertex : POSITION;float4 tangent : TANGENT;float3 normal : NORMAL;float4 texcoord : TEXCOORD0;float4 texcoord1 : TEXCOORD1;float4 texcoord2 : TEXCOORD2;float4 texcoord3 : TEXCOORD3;#if defined(SHADER_API_XBOX360)half4 texcoord4 : TEXCOORD4;half4 texcoord5 : TEXCOORD5;#endiffixed4 color : COLOR;};

可以看出,appdata_full 几乎包含了所有的模型数据。 我们把计算得到的假彩色存储到了顶点着色器的输出结构体—v2f 中的color 变量里,并且 在片元着色器中输出了这个颜色。读者可以对其中的代码添加或取消注释,观察不同运算和数据 得到的效果。图5.4 给出了这些代码得到的显示效果。读者可以先自己想一想代码和这些效果之 间的对应关系,然后再在Unity 中进行验证。 为了可以得到某点的颜色值,我们可以使用类似颜色拾取器的脚本得到屏幕上某点的RGBA 值,从而推断出该点的调试信息。在本书的附带工程中,读者可以找到这样一个简单的实例脚本: Assets -> Scripts -> Chapter5 -> ColorPicker.cs。把该脚本拖曳到一个摄像机上,单击运行后,可以 用鼠标单击屏幕,以得到该点的颜色值,如图5.5 所示。

5.6 小心:渲染平台的差异

Unity 的优点之一是其强大的跨平台性—写一份代码可以运行在很多平台上。绝大多数情 况下,Unity 为我们隐藏了这些细节,但有些时候我们需要自己处理它们。本节给出了一些常见 的因为平台不同而造成的差异。 5.6.1 渲染纹理的坐标差异 在2.3.4 节和4.2.2 节中,我们都提到过OpenGL 和DirectX 的屏幕空间坐标的差异。在水平 方向上,两者的数值变化方向是相同的,但在竖直方向上,两者是相反的。在OpenGL(OpenGL ES 也是)中,(0, 0)点对应了屏幕的左下角,而在DirectX(Metal 也是)中,(0, 0)点对应了左上 角。图5.8 可以帮助读者回忆它们之间的这种不同。

void vert (inout appdata_full v, out Input o) { // 使用Unity 内置的UNITY_INITIALIZE_OUTPUT 宏对输出结构体o 进行初始化 UNITY_INITIALIZE_OUTPUT(Input,o); // … } 除了上述两点语法不同外,DirectX 9 / 11 也不支持在顶点着色器中使用tex2D 函数。tex2D 是一个对纹理进行采样的函数,我们在后面的章节中将会具体讲到。之所以DirectX 9 / 11 不支持 顶点阶段中的tex2D 运算,是因为在顶点着色器阶段Shader 无法得到UV 偏导,而tex2D 函数需 要这样的偏导信息(这和纹理采样时使用的数学运算有关)。如果我们的确需要在顶点着色器中访 问纹理,需要使用tex2Dlod 函数来替代,如: tex2Dlod(tex, float4(uv, 0, 0)). 而且我们还需要添加#pragma target 3.0,因为tex2Dlod 是Shader Model 3.0 中的特性。 5.6.3 Shader 的语义差异 我们在5.4 节讲到了Shader 中的语义是什么,其中我们讲到了一些语义在某些平台下是等价 的,例如SV_POSITION 和POSITION。但在另一些平台上,这些语义是不等价的。为了让Shader 能够在所有平台上正常工作,我们应该尽可能使用下面的语义来描述Shader 的输入输出变量。  使用SV_POSITION 来描述顶点着色器输出的顶点位置。一些Shader 使用了POSITION 语 义,但这些Shader 无法在索尼PS4 平台上或使用了细分着色器的情况下正常工作。  使用SV_Target 来描述片元着色器的输出颜色。一些Shader 使用了COLOR 或者COLOR0 语义,同样的,这些Shader 无法在索尼PS4 上正常工作。 5.6.4 其他平台差异 本书只给出了一些最常见的平台差异造成的问题,还有一些差异不再列举。如果读者发现一 些Shader 在平台A 下工作良好,而在平台B 下出现了问题,可以去Unity 官方文档(unity3d.com/Manual/SL-PlatformDifferences.html)中寻找更多的资料。

5.7 Shader 整洁之道

参考

Unity Shader入门精要 作者:冯乐乐

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:错误 C2338 ‘radians‘ only accept floating-point input
下一篇:openGL实现阴影映射(Shadow Mapping)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~