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

article/2025/3/16 12:13:37

Unity系列文章目录

文章目录

  • Unity系列文章目录
  • 前言
    • 5.3.1 内置的包含文件
    • 5.3.2 内置的变量
  • 二、Unity 提供的Cg/HLSL 语义
  • 5.5 程序员的烦恼:Debug
  • 5.6 小心:渲染平台的差异
  • 5.7 Shader 整洁之道
  • 参考

前言

上一节讲述了如何在Unity 中编写一个基本的顶点/片元着色器的过程。顶点/片元着色的复杂
之处在于,很多事情都需要我们“亲力亲为”,例如我们需要自己转换法线方向,自己处理光照、
阴影等。为了方便开发者的编码过程,Unity 提供了很多内置文件,这些文件包含了很多提前定
义的函数、变量和宏等。如果读者在学习他人编写的Unity Shader 代码时,遇到了一些从未见过
的变量、函数,而又无法找到对应的声明和定义,那么很有可能就是这些代码使用了Unity 内置
文件提供的函数和变量。
本节将给出这些文件和变量的概览。

5.3.1 内置的包含文件

包含文件(include file),是类似于C++中头文件的一种文件。在Unity 中,它们的文件后缀
是.cginc。在编写Shader 时,我们可以使用#include 指令把这些文件包含进来,这样我们就可以使
用Unity 为我们提供的一些非常有用的变量和帮助函数。例如:
CGPROGRAM
// …
#include “UnityCG.cginc”
// …
ENDCG
那么,这些文件在哪里呢?我们可以在官方网站(http://unity3d.com/cn/get-unity/download/
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。

在这里插入图片描述
可以看出,有一些文件是即便我们没有使用#include 指令,它们也是会被自动包含进来的,
例如UnityShaderVariables.cginc。因此,在前面的例子中,我们可以直接使用UNITY_MATRIX_
MVP 变量来进行顶点变换。除了表5.2 中列出的包含文件外,Unity 5 引入了许多新的重要的包含
文件,如UnityStandardBRDF.cginc、UnityStandardCore.cginc 等,这些包含文件用于实现基于物理
的渲染,我们会在第18 章中再次遇到它们。
UnityCG.cginc 是我们最常接触的一个包含文件。在后面的学习中,我们将使用很多该文件提
供的结构体和函数,为我们的编写提供方便。例如,我们可以直接使用UnityCG.cginc 中预定义的
结构体作为顶点着色器的输入和输出。表5.3 给出了一些结构体的名称和包含的变量。

在这里插入图片描述
强烈建议读者找到UnityCG.cginc 文件并查看上述结构体的声明,这样的过程可以帮助我们快
速理解Unity 中一些内置变量的工作原理。
除了结构体外,UnityCG.cginc 也提供了一些常用的帮助函数。表5.4 给出了一些函数名和它
们的描述。

在这里插入图片描述
我们建议读者在UnityCG.cginc 文件找到这些函数的定义,并尝试理解它们。一些函数我们完
全可以自己实现,例如UnityObjectToWorldDir 和UnityWorldToObjectDir,这两个函数实际上就是
对方向矢量进行了一次坐标空间变换。而UnityCG.cginc 文件可以帮助我们提高代码的复用率。
UnityCG.cginc 还包含了很多宏,在后面的学习中,我们就会遇到它们。

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 的文档(https://msdn.microsoft.com/
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 内部赋予了它们特殊的含义。

在这里插入图片描述
其中TEXCOORDn 中n 的数目是和Shader Model 有关的,例如一般在Shader Model 2(即
Unity 默认编译到的Shader Model 版本)和Shader Model 3 中,n 等于8,而在Shader Model 4 和
Shader Model 5 中,n 等于16。通常情况下,一个模型的纹理坐标组数一般不超过2,即我们往往
只使用TEXCOORD0 和TEXCOORD1。在Unity 内置的数据结构体appdata_full 中,它最多使用
了6 个坐标纹理组。
表5.6 总结了从顶点着色器阶段到片元着色器阶段Unity 支持的常用语义。

在这里插入图片描述

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

在这里插入图片描述
5.4.3 如何定义复杂的变量类型
上面提到的语义绝大部分用于描述标量或矢量类型的变量,例如fixed2、float、float4、fixed4

等。下面的代码给出了一个使用语义来修饰不同类型变量的例子:
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 里找到它的定义:```cpp
struct 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;
#endif
fixed4 color : COLOR;
};

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

在这里插入图片描述
5.5.2 利用神器:Visual Studio
本节是Windows 用户的福音,Mac 用户的噩耗。Visual Studio 作为Windows 系统下的开发利
器,在Visual Studio 2012 版本中也提供了对Unity Shader 的调试功能—Graphics Debugger。
通过Graphics Debugger,我们不仅可以查看每个像素的最终颜色、位置等信息,还可以对顶
点着色器和片元着色器进行单步调试。具体的安装和使用方法可以参见Unity 官网文档中使用
Visual Studio 对DirectX 11 的Shader 进行调试一文(http://docs.unity3d.com/Manual/SL-Debugging
D3D11ShadersWithVS.html)。
当然,本方法也有一些限制。例如,我们需要保证Unity 运行在DirectX 11 平台上,而且Graphics
Debugger 本身存在一些bug。但这无法阻止我们对它的喜爱之情!而Mac 用户可能就只能无奈地眼馋了。
5.5.3 最新利器:帧调试器
尽管Mac 用户无法体验Visual Studio 的强大功能,但幸运的是,Unity 5 除了带来全新的UI
系统外,还给我们带来了一个新的针对渲染的调试器—帧调试器(Frame Debugger)。与其他
调试工具的复杂性相比,Unity 原生的帧调试器非常简单快捷。我们可以使用它来看到游戏图像
的某一帧是如何一步步渲染出来的。
要使用帧调试器,我们首先需要在Window -> Frame Debugger 中打开帧调试器窗口,如图5.6 所示。
在这里插入图片描述
帧调试器可以用于查看渲染该帧时进行的各种渲染事件(event),这些事件包含了Draw Call
序列,也包括了类似清空帧缓存等操作。帧调试器窗口大致可分为3 个部分:最上面的区域可以
开启/关闭(单击Enable 按钮)帧调试功能,当开启了帧调试时,通过移动窗口最上方的滑动条
(或单击前进和后退按钮),我们可以重放这些渲染事件;左侧的区域显示了所有事件的树状图,
在这个树状图中,每个叶子节点就是一个事件,而每个父节点的右侧显示了该节点下的事件数目。
我们可以从事件的名字了解这个事件的操作,例如以Draw 开头的事件通常就是一个Draw Call;
当单击了某个事件时,在右侧的窗口中就会显示出该事件的细节,例如几何图形的细节以及使用
了哪个Shader 等。同时在Game 视图中我们也可以看到它的效果。如果该事件是一个Draw Call
并且对应了场景中的一个GameObject,那么这个GameObject 也会在Hierarchy 视图中被高亮显示
出来,图5.7 显示了单击渲染某个对象的深度图事件的结果。

在这里插入图片描述
如果被选中的Draw Call 是对一个渲染纹理(RenderTexture)的渲染操作,那么这个渲染纹
理就会显示在Game 视图中。而且,此时右侧面板上方的工具栏中也会出现更多的选项,例如在
Game 视图中单独显示R、G、B 和A 通道。
Unity 5 提供的帧调试器实际上并没有实现一个真正的帧拾取(frame capture)的功能,而是
仅仅使用停止渲染的方法来查看渲染事件的结果。例如,如果我们想要查看第4 个Draw Call 的
结果,那么帧调试器就会在第4 个Draw Call 调用完毕后停止渲染。这种方法虽然简单,但得到
的信息也很有限。如果读者想要获取更多的信息,还是需要使用外部工具,例如5.5.2 节中的Visual
Studio 插件,或者Intel GPA、RenderDoc、NVIDIA NSight、AMD GPU PerfStudio 等工具。

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 可以帮助读者回忆它们之间的这种不同。

在这里插入图片描述
需要注意的是,我们不仅可以把渲染结果输出到屏幕上,还可以输出到不同的渲染目标
(Render Target)中。这时,我们需要使用渲染纹理(Render Texture)来保存这些渲染结果。我们
将在第12 章中学习如何实现这样的目的。
大多数情况下,这样的差异并不会对我们造成任何影响。但当我们要使用渲染到纹理技术,
把屏幕图像渲染到一张渲染纹理中时,如果不采取任何措施的话,就会出现纹理翻转的情况。幸
运的是,Unity 在背后为我们处理了这种翻转问题—当在DirectX平台上使用渲染到纹理技术时,
Unity 会为我们翻转屏幕图像纹理,以便在不同平台上达到一致性。
在一种特殊情况下Unity 不会为我们进行这个翻转操作,这种情况就是我们开启了抗锯齿(在
Edit -> Project Settings -> Quality -> Anti Aliasing 中开启)并在此时使用了渲染到纹理技术。在这
种情况下,Unity 首先渲染得到屏幕图像,再由硬件进行抗锯齿处理后,得到一张渲染纹理来供
我们进行后续处理。此时,在DirectX 平台下,我们得到的输入屏幕图像并不会被Unity 翻转,也
就是说,此时对屏幕图像的采样坐标是需要符合DirectX 平台规定的。如果我们的屏幕特效只需
要处理一张渲染图像,我们仍然不需要在意纹理的翻转问题,这是因为在我们调用Graphics.Blit
函数时,Unity 已经为我们对屏幕图像的采样坐标进行了处理,我们只需要按正常的采样过程处
理屏幕图像即可。但如果我们需要同时处理多张渲染图像(前提是开启了抗锯齿),例如需要同时
处理屏幕图像和法线纹理,这些图像在竖直方向的朝向就可能是不同的(只有在DirectX 这样的
平台上才有这样的问题)。这种时候,我们就需要自己在顶点着色器中翻转某些渲染纹理(例如深
度纹理或其他由脚本传递过来的纹理)的纵坐标,使之都符合DirectX 平台的规则。例如:
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
uv.y = 1-uv.y;
#e ndif
其中,UNITY_UV_STARTS_AT_TOP 用于判断当前平台是否是DirectX 类型的平台,而当在
这样的平台下开启了抗锯齿后,主纹理的纹素大小在竖直方向上会变成负值,以方便我们对主纹
理进行正确的采样。因此,我们可以通过判断_MainTex_TexelSize.y 是否小于0 来检验是否开启
了抗锯齿。如果是,我们就需要对除主纹理外的其他纹理的采样坐标进行竖直方向上的翻转。我
们会在第13 章中再次看到上面的代码。
在本书资源的项目中,我们开启了抗锯齿选项。在第12 章中,我们将学习一些基本的屏幕后
处理效果。这些效果大多使用了单张屏幕图像进行处理,因此我们不需要考虑平台差异化的问题,
因为Unity 已经在背后为我们处理过了。但在12.5 节中,我们需要在一个Pass 中同时处理屏幕图
像和提取得到的亮部图像来实现Bloom 效果。由于需要同时处理多张纹理,因此在DirectX 这样
的平台下如果开启了抗锯齿,主纹理和亮部纹理在竖直方向上的朝向就是不同的,我们就需要对
亮部纹理的采样坐标进行翻转。在第13 章中,我们需要同时处理屏幕图像和深度/法线纹理来实
现一些特殊的屏幕效果,在这些处理过程中,我们也需要进行一些平台差异化处理。在15.3 节中,
尽管我们也在一个Pass 中同时处理了屏幕图像、深度纹理和一张噪声纹理,但我们只对深度纹理
的采样坐标进行了平台差异化处理,而没有对噪声纹理进行处理。这是因为,类似噪声纹理的装
饰性纹理,它们在竖直方向上的朝向并不是很重要,即便翻转了效果往往也是正确的,因此我们
可以不对这些纹理进行平台差异化处理。
5.6.2 Shader 的语法差异
读者在Windows 平台下编译某些在Mac 平台下工作良好的Shader 时,可能会看到类似下面
的报错 信息:
in correct number of arguments to numeric-type constructor (compiling for d3d11)
或者
ou tput parameter ‘o’ not completely initialized (compiling for d3d11)
上面的报错都是因为DirectX 9/11 对Shader 的语义更加严格造成的。例如,造成第一个报错
信息的原因是,Shader 中可能存在下面这样的代码:
// v 是float4 类型,但在它的构造器中我们仅提供了一个参数
fl oat4 v = float4(0.0);
在OpenGL 平台上,上面的代码是合法的,它将得到一个4 个分量都是0.0 的float4 类型的变量。
但在DirectX 11 平台上,我们必须提供和变量类型相匹配的参数数目。也就是说,我们应该写成:
fl oat4 v = float4(0.0, 0.0, 0.0, 0.0);
而对于第二个报错信息,往往是出现在表面着色器中。表面着色器的顶点函数(注意,不是
顶点着色器)有一个使用了out 修饰符的参数。如果出现这样的报错信息,可能是因为我们在顶
点函数中没有对这个参数的所有成员变量都进行初始化。我们应该使用类似下面的代码来对这些
参数进行初始化:

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 官方文档(http://docs.
unity3d.com/Manual/SL-PlatformDifferences.html)中寻找更多的资料。

5.7 Shader 整洁之道

在本章的最后,我们给出一些关于如何规范Shader 代码的建议。当然,这些建议并不是绝对
正确的,读者可以根据实际情况做出权衡。写出规范的代码不仅是让代码变得漂亮易懂而已,更
重要的是,养成这些习惯有助于我们写出高效的代码。
5.7.1 float、half 还是fixed
在本书中,我们使用Cg/HLSL 来编写Unity Shader 中的代码。而在Cg/HLSL 中,有3 种精
度的数值类型:float,half 和fixed。这些精度将决定计算结果的数值范围。表5.8 给出了这3 种
精度在通常情况下的数值范围。
在这里插入图片描述
上面的精度范围并不是绝对正确的,尤其是在不同平台和GPU 上,它们实际的精度可能和上
面给出的范围不一致。通常来讲。
 大多数现代的桌面GPU 会把所有计算都按最高的浮点精度进行计算,也就是说,float、
half、fixed 在这些平台上实际是等价的。这意味着,我们在PC 上很难看出因为half 和fixed
精度而带来的不同。
 但在移动平台的GPU 上,它们的确会有不同的精度范围,而且不同精度的浮点值的运算
速度也会有所差异。因此,我们应该确保在真正的移动平台上验证我们的Shader。
 fixed 精度实际上只在一些较旧的移动平台上有用,在大多数现代的GPU 上,它们内部把
fixed 和half 当成同等精度来对待。
尽管有上面的不同,但一个基本建议是,尽可能使用精度较低的类型,因为这可以优化Shader
的性能,这一点在移动平台上尤其重要。从它们大体的值域范围来看,我们可以使用fixed 类型
来存储颜色和单位矢量,如果要存储更大范围的数据可以选择half 类型,最差情况下再选择使用
float。如果我们的目标平台是移动平台,一定要确保在真实的手机上测试我们的Shader,这一点
非常重要。关于移动平台的优化技术,读者可以在第16 章中找到更多内容。
5.7.2 规范语法
在5.6.2 节,我们提到DirectX 平台对Shader 的语义有更加严格的要求。这意味着,如果我们
要发布到DirectX 平台上就需要使用更严格的语法。例如,使用和变量类型相匹配的参数数目来
对变量进行初始化。
5.7.3 避免不必要的计算
如果我们毫无节制地在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 来消除这些错误。表5.9 给出了Unity 目
前支持的一些Shader Target。

在这里插入图片描述
需要注意的是,由于Unity 版本的不同,Unity 支持的Shader Target 种类也不同,读者可以在
官方手册上找到更为详细的介绍。
读者:什么是Shader Model 呢?
我们:Shader Model 是由微软提出的一套规范,通俗地理解就是它们决定了Shader 中各个特
性(feature)的能力(capability)。这些特性和能力体现在Shader 能使用的运算指令数目、寄存器
个数等各个方面。Shader Model 等级越高,Shader 的能力就越大。具体的细节读者可以参见本章
的扩展阅读部分。
虽然更高等级的Shader Target 可以让我们使用更多的临时寄存器和运算指令,但一个更好的
方法是尽可能减少Shader 中的运算,或者通过预计算的方式来提供更多的数据。
5.7.4 慎用分支和循环语句
在我们学习第一门语言的课上,类似分支、循环语句这样的流程控制语句是最基本的语法之
一。但在编写Shader 的时候,我们要对它们格外小心。
在最开始,GPU 是不支持在顶点着色器和片元着色器中使用流程控制语句的。随着GPU 的发
展,我们现在已经可以使用if-else、for 和while 这种流程控制指令了。但是,它们在GPU 上的实现
和在CPU 上有很大的不同。深究这些指令的底层实现不在本书的讨论范围内,读者可以在本章的
扩展阅读中找到更多的内容。大体来说,GPU 使用了不同于CPU 的技术来实现分支语句,在最坏
的情况下,我们花在一个分支语句的时间相当于运行了所有分支语句的时间。因此,我们不鼓励在
Shader 中使用流程控制语句,因为它们会降低GPU 的并行处理操作(尽管在现代的GPU 上已经有
了改进)。
如果我们在Shader 中使用了大量的流程控制语句,那么这个Shader 的性能可能会成倍下降。
一个解决方法是,我们应该尽量把计算向流水线上端移动,例如把放在片元着色器中的计算放到
顶点着色器中,或者直接在CPU 中进行预计算,再把结果传递给Shader。当然,有时我们不可避
免地要使用分支语句来进行运算,那么一些建议是:
 分支判断语句中使用的条件变量最好是常数,即在Shader 运行过程中不会发生变化;
 每个分支中包含的操作指令数尽可能少;
 分支的嵌套层数尽可能少。
5.7.5 不要除以0
虽然在用类似C#等高级语言进行编程的时候,我们会谨记不要除以0 这个基本常识(就算你
没这么做,编辑器可能也会报错),但有时在编写Shader 的时候我们会忽略这个问题。
例如,我们在Shader 里写下如下代码:
fixed4 frag(v2f i) : SV_Target
{
return fixed4(0.0/0.0,0.0/0.0, 0.0/0.0, 1.0);
}
这样代码的结果往往是不可预测的。在某些渲染平台上,上面的代码不会造成Shader 的崩溃,
但即便不会崩溃得到的结果也是不确定的,有些会得到白色(由无限大截取到1.0),有些会得到
黑色,但在另一些平台上,我们的Shader 可能就会直接崩溃。因此,即便在开发游戏的平台上,
我们看到的结果可能是符合预期的,但在目标平台上可能就会出现问题。
一个解决方法是,对那些除数可能为0 的情况,强制截取到非0 范围。在一些资料中,读者
可能也会看到使用if 语句来判断除数是否为0 的例子。另一个方法是,使用一个很小的浮点值,
例如0.000001 来保证分母大于0(前提是原始数值是非负数)。

参考

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

文章来源:https://blog.csdn.net/aoxuestudy/article/details/124234980
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://chatgpt.dhexx.cn/article/mH243FZv.shtml

相关文章

《python数学实验与建模》(10)图论模型

10.1 写出图10.20所示非赋权无向图的关联矩阵和邻接矩阵 绘制图 import networkx as nx import pylab as plt import numpy as np Anp.zeros((6,6)) List[(1,2),(1,4),(2,3),(3,4),(3,5),(3,6),(4,5),(4,6),(5,6)] for i in List:A[i[0]-1,i[1]-1]1 Gnx.Graph(A) posnx.spring…

2021年美国数学建模C题的数据处理

2021年美国数学建模C题的数据处理 C题数据分类存放部分批量提取图像数据转化jpg图像格式 C题数据分类存放部分 在拿到C题的数据后&#xff0c;避让要做的一个事情是图像数据的分类。根据2021MCM_ProblemC_ Images_by_GlobalID表格中&#xff0c;可以将图片和ID号对应起来&…

Unity Shader入门精要笔记(五):其他数学相关介绍

本系列文章由Aimar_Johnny编写&#xff0c;欢迎转载&#xff0c;转载请标明出处&#xff0c;谢谢。 http://blog.csdn.net/lzhq1982/article/details/73747162 前两篇介绍了Unity Shader的主要数学部分&#xff0c;书上还有些相关的数学介绍&#xff0c;将在这篇做最后的总结。…

2020年数维杯国际大学生数学建模B题股票价格的混沌模型求解全过程文档及程序

2020年数维杯国际大学生数学建模 B题 股票价格的混沌模型 原题再现&#xff1a; 上市公司股价的变化可以直接反映上市公司的经营状况和市场的认可度。股票价格的建模和预测一直是一个难题。最重要的因素是股票价格既有趋势因素又有随机因素。因此&#xff0c;股票市场是一个非…

ugpost_tcl文件

########################## TCL Event Handlers ########################## b.tcl - 3_axis_mill 这是 3 轴铣床。 Created by dp Wednesday, November 06, 2019 8:52:33 AM China Standard Time with Post Builder version 10.0.3. #####################################…

数据结构课设 (快餐店 POS 机计费系统、成绩分析、算术表达式)

目录 快餐店 POS 机计费系统 学生成绩分析系统 算术表达式 参考文献 快餐店 POS 机计费系统 【任务描述】 校园快餐店一共出售三大类食品&#xff1a;饮料&#xff0c;主食&#xff0c;小食品。设计一个快餐店的 POS 机计费系统&#xff0c; 对快餐店的食品信息、销售信息…

linux文件系统-文件的写与读

只有打开可文件以后&#xff0c;或者建立起进程与文件之间的连接之后&#xff0c;才能对文件进行读写。文件的读写主要是通过系统调用read和write来完成的&#xff0c;对于读写的进程&#xff0c;目标文件由一个打开文件号代表。 为了提高效率&#xff0c;稍微复杂一点的操作系…

数学模型——泊车模型(2022年Mathorcup数学建模挑战赛C题,含Matlab代码)

写在前面 之前做了一个2022年Mathorcup数学建模挑战赛C题的比赛心得&#xff0c;上一篇文章主要讲了A*算法的改进以及A*算法如何在C题的第3问的应用。本文主要介绍C题的第2问&#xff0c;即三种泊车模型如何建立&#xff0c;因此部分并非我写&#xff0c;在比赛期间&#xff0…

Python小白的数学建模课-16.最短路径算法

最短路径问题是图论研究中的经典算法问题&#xff0c;用于计算图中一个顶点到另一个顶点的最短路径。在图论中&#xff0c;最短路径长度与最短路径距离却是不同的概念和问题&#xff0c;经常会被混淆。求最短路径长度的常用算法是 Dijkstra 算法、Bellman-Ford 算法和Floyd 算法…

数学建模有关DNA序列k-mer index的问题

原问题是这样的&#xff1a; 给定一个DNA序列&#xff0c;这个系列只含有4个字母ATCG&#xff0c;如 S “CTGTACTGTAT”。给定一个整数值k&#xff0c;从S的第一个位置开始&#xff0c;取一连续k个字母的短串&#xff0c;称之为k-mer&#xff08;如k 5&#xff0c;则此短串为CT…

数学建模暑期集训26:遗传算法

遗传算法是优化类问题的经典智能算法。本篇将介绍遗传算法的基本概念以及利用遗传算法来求解单目标规划模型。 达尔文进化论的基本思想 遗传算法的设计是受到达尔文进化论的启发。先看下面这张图的几个基本概念。 一些花构成一个种群。 每朵花被称为个体。 每个个体内有染色…

2021年亚太杯三等奖选手C题思路

文章目录 亚太杯C题第一小问亚太杯C题第二小问亚太杯C题第三小问亚太杯C题第四小问亚太杯C题第五小问 昨天晚上刚出了亚太杯的成绩&#xff0c;获得了三等奖&#xff0c;毕竟是第一次参加数学建模比赛&#xff0c;不是成功参与奖就很高兴了&#xff0c;结束了之后&#xff0c;还…

python使用networks读取txt文件画一个有权有向图

class demo():def __init__(self):self.file_pathtest.txt#图文件 def draw_graph(self):G2 nx.DiGraph() # 创建&#xff1a;空的 有向图f open(self.file_path)lines [l.split() for l in f.readlines() if l.strip()]# print(lines)for i in lines:G2.add_edge(i[0],…

数学建模常用功能

目录 pandas读取数据 查看数据异常 提取指定列 将dataframe数据以numpy形式提取 数据划分 随机森林回归 GBDT回归 特征重要性可视化 输出&#xff1a; ​ 绘制3D散点图 导入自定义包且.py文件修改时jupyter notebook自动同步 dataframe删除某列中重复字段并删除对应行…

c语言文件操作

文件操作读写 1 文件处理原理及基本概念 C语言的文件处理功能&#xff0c;大体上分为两种&#xff1a;一种是设置缓冲区&#xff0c;另一种是不设置缓冲区。因为不设置缓冲区的方法直接对磁盘进行操作&#xff0c;速度较慢&#xff0c;并且由于不是C的标准函数&#xff0c;跨…

无人机视角展示(无人机图像定位 )--某数学建模A题MATLAB代码

近期没啥空&#xff0c;水个简单的。。。。 目前只写了第一问&#xff0c;有空再写。。。。。 问题描述 无人驾驶飞机简称“无人机”&#xff0c;是利用无线电遥控设备和自备的程序控制装置操纵的不载人飞机。搭载图像设备的无人机在高空航拍、区域巡视、军事侦查等方面有广泛…

2020 全国大学生数学建模竞赛C题思路+代码

题目链接&#xff1a;天翼云盘 珍藏美好生活 家庭云|网盘|文件备份|资源分享 前言 又是一年数据挖掘题型&#xff0c;第一次接触这种题型还是在去年的mathorcup上&#xff0c;这种题的难度就在于指标的建立和数据的处理上。后面会出一份关于数据挖掘题型&#xff0c;我的相关经…

PU learning半监督学习

半监督学习 Positive-unlabeled learning 什么是半监督学习 让学习器不依赖外界交互、自动地利用未标记样本来提升学习性能&#xff0c;就是半监督学习&#xff08;semi-supervised learning&#xff09;。 要利用未标记样本&#xff0c;必然要做一些将未标记样本所揭示的数…

详解基于图卷积的半监督学习

Kipf和Welling最近发表的一篇论文提出&#xff0c;使用谱传播规则&#xff08;spectral propagation&#xff09;快速近似spectral Graph Convolution。 和之前讨论的求和规则和平均规则相比&#xff0c;谱传播规则的不同之处在于聚合函数。它使用提升到负幂的度矩阵D对聚合进行…

【半监督医学图像分割 2023】RCPS

文章目录 【半监督医学图像分割 2023 】RCPS摘要1. 介绍2. 相关工作2.1 医学图像分割2.1 半监督学习2.3 对比学习 3. 方法3.1 整体概述3.2 纠正伪监督3.3 双向Voxel对比学习。 4. 实验 【半监督医学图像分割 2023 】RCPS 论文题目&#xff1a;RCPS: Rectified Contrastive Pseu…