|
着色器简介 - Unity 手册
着色器:GPU 上运行的程序
Unityshader分类:①作为图形管线一部分的着色器,②计算着色器,③光线追踪着色器
- shader对象:shader类的一个实例。是着色器程序和其他信息的封装器
- shaderLab :编写着色器的Unity特定语言
- shader 资源:.shader 的文件,定义一个 shader 对象
Shader 类
Shader 类 - Unity 手册
Shader对象包含
- ①Shader&#34;&#34;{ } ②更改GPU上设置的指令 <渲染状态> ③告诉 Unity 如何使用它们的信息
- 与材质共同确定场景的外观
- 一个着色器资源,扩展名为 .shader
shader对象内部嵌套结构

Subshader和 pass的结构,Shader对象将着色器程序&lt;CGPROGRAM...ENDCG&gt;组织成变体
- Subshader包含
- 将Shader对象分成多个部分,分别兼容不同的硬件 / RP / 运行时设置
- 标签:提供有关子着色器的信息的键值对
- 一个 / 多个pass,可定义对所有通道通用的渲染状态
- pass包含:
- 标签:运行着色器程序之前更新渲染状态的说明
- 着色器程序:组织成一个或多个变体,还可定义其他信息
shader variants
- Pass中着色器程序被组织成变体,共享通用代码
- 变体数量:取决于着色器代码中定义的关键字数量 / 目标平台
Unity使用 Shader 对象之前:
- 为Shader对象创建一个子着色器列表,添加所有子着色器,按顺序添加回退Shader对象中的所有子着色器
Unity首次使用 Shader对象渲染几何体时 / 着色器 LOD值或活动渲染管道更改时:
- 遍历列表以确定它们是否:与硬件 / 当前着色器 LOD / 活动RP 兼容
- 列表包含一个或多个满足这些要求的子着色器,选第一个子着色器。这是活动子着色器
列表不包含任何满足所有要求的子着色器
- i. 满足硬件要求 但不满足 LOD / RP要求的子着色器,则Unity选择第一个子着色器
- ii. 不满足硬件要求:Unity将显示错误着色器
Unity可以识别使用相同变体的几何体并将其组织成批次实现更高效的渲染
- Unity确定它应该渲染活动子着色器中的哪些通道,以及帧中的哪个点。此行为因RP而异
对于渲染的每个pass:
- 如果当前渲染状态与pass中定义的渲染状态不匹配,Unity会根据pass中的定义设置渲染状态
- GPU使用相关的变体渲染几何体
Shader 资源
着色器资源 - Unity 手册

①:Shader对象的基本信息。编译和检查已编译代码的控件
- ②:用于查看和编辑与 Shader 对象本身相关的设置,以及着色器编译器对它的处理方式
Compile and show code

着色器在构建之前不会为所有图形API 编译所有变体。可手动强制执行此操作以检查错误
- 面向所选平台检查已编译的着色器代码。此功能在优化着色器性能时非常有用
- 可知道生成了多少低级指令。可以将生成的代码粘贴到GPU着色器性能分析工具<AMD GPU ShaderAnalyzer> / <PVRShaderEditor>中
Shader 编译
着色器编译 - Unity 手册
- 构建项目时,编辑器都会编译构建所需的所有着色器:针对每个所需的API编译每个所需的变体
在编辑器中
- 不会提前编译所有内容。因为为每个图形API 编译每个变体需要很长时间
编辑器这样做:
- 当导入着色器资源时,会执行最小的处理,当需要显示变体时,它会检查 Library/ShaderCache文件夹,找到:使用先前编译的变体,使用该变体。找不到:编译所需的变体并将结果保存到缓存中
异步着色器编译:占位着色器
着色器编译:
- 使用UnityShaderCompiler 的进程。可启动多个编译器进程,这样在播放器构建时就可以并行完成着色器编译
编辑器不编译着色器时
- 不消耗计算机资源,如果有许多经常更改的着色器,ShaderCache文件夹会变得非常大。可删除,到要重编译
- 在构建时,所有“尚未编译”的变体都将被编译,因此即使编辑器不会使用这些变体,它们也会存在于游戏数据中

不同shader编译器:不同平台使用不同的着色器编译器来编译着色器程序
- DirectX :用 Microsoft 的 FXC HLSL 编译器
- OpenGL (Core & ES) :同上…,然后使用 HLSLcc 将字节代码转换为 GLSL
- Metal:同上…,然后使用 HLSLcc 将字节代码转换为Metal
- Vulkan:同上…,然后使用 HLSLcc 将字节代码转换为SPIR-V
- 表面着色器:使用HLSL和 MojoShader 来完成代码生成分析步骤
缓存 shader 预处理器
- 之前版本编辑器:使用当前平台的shader编译器提供的预处理器
- 现在编辑器:使用它自己的预处理器<缓存Shader预处理器> ,为更快的着色器导入和编译进行了优化。它通过缓存中间预处理数据工作,所以编辑器只需要在包含文件的内容发生变化时解析包含文件,这使得编译同一shader的多个变体更加高效
构建时剥离
- 在构建游戏时,Unity可以检测到一些内部着色器变量没有被游戏使用,并剥离它们
<hr/>Branching, variants, and keywords
Branching, variants, and keywords - Unity 手册
shader中的条件
在不同的情况下做不同的事情
- ①材质配置。②为不同的硬件定义功能。③运行时动态更改着色器的行为。④避免在不需要时执行计算量大的代码,例如纹理读取、顶点输入、插值器或循环。⑤定义GPU仅在特定条件下执行的行为
在着色器中使用条件,有以下方法:
- 静态 branching:着色器编译器在编译时评估条件代码
- 动态branching:GPU 在运行时评估条件代码
- 着色器variants:Unity使用静态分支将shader源代码编译成多个shader程序。Unity然后在运行时使用符合条件的着色器程序
在编辑时切换代码 branching,不需要着色器在运行时切换
- 向材质的某些实例添加镜面反射
- 水下的物体场景,如果使用这种方法,着色器代码更易于编写和维护,并且不太可能影响构建时间、文件大小和性能
方法:
- Shader_feature关键字:
- 静态 branching:使用预处理程序常量和宏
shader_feature
- Unity会在构建中保留材质使用的着色器变体,并“剥离”其他变体
- 避免使用C#脚本Shader_feature在运行时启用/禁用关键字
如果确实需要在运行时启用或禁用关键字,使用以下方法确保构建包含需要的所有变体:
- 在预加载着色器列表中包含一个着色器变体集合,其中包含需要的变体
- shader_feature:为要使用的每个关键字组合在构建中包含一个材质
在运行时切换代码branching,使用 C# 脚本使着色器在运行时切换到不同的代码分支
- 动态更改材质:特定时间有雪
- 当用户更改质量设置时更改材质,例如让用户动态控制是否出现雾
方法:
- shader variants:multi_compile
- 动态 branching
multi_compile
- 为每个可能的着色器代码分支组合构建一个变体,包括构建中材质未使用的组合
- 可以在运行时启用和禁用关键字,但会大大增加构建时间、文件大小、加载时间和内存使用量
动态分支不会创建变体,但意味着色器在 GPU 上运行得更慢
- 着色器在功能较弱的 GPU 上运行
- 条件代码具有“非对称分支”,一个分支比另一个分支更长或更复杂
检查多少变体,是否可以使用动态分支而不会对GPU 性能造成太大影响
在着色器中branching:分支是将条件行为引入的一种方式着色器代码:①静态分支 ②动态分支
① 静态分支 Branching
- 当着色器程序包含在编译时评估的条件时,编译器将代码从未使用的分支中排除,因此它不会出现在编译的着色器程序中。Unity 在创建着色器变体时使用静态分支;没有任何性能缺点
优点:运行时性能没有负面影响
缺点:只能在编译时使用它,编译器从着色器程序中排除不需要的代码。不能在运行时针对不同条件执行代码
代码:
- 使用#if、#elif、#else和#endif预处理器指令,或#ifdef和#ifndef预处理器指令
- 尽管if语句也可用于动态分支,但编译器会检测编译时常量值并创建静态分支
- Unity为一些可与静态分支一起使用的编译时常量提供了内置宏
②动态分支 Branching两种类型
- 基于统一变量:动态分支和基于任何其他运行时值的动态分支
- 基于统一值:分支通常更有效,因为统一值对于整个绘制调用是恒定的
优点:运行时使用条件,而无需增加项目中着色器变体的数量
缺点:会影响 GPU 性能,GPU 性能影响因硬件和着色器代码而异。原因是:
- 基于非均匀变量的分支:意味着 GPU 必须同时执行不同的操作(会破坏并行性),或者“扁平化分支”并通过对两个分支执行操作然后丢弃一个结果来保持并行性
- 基于统一变量的分支:意味着 GPU 必须展平分支。这两种方法都会导致 GPU 性能降低
注:对于任何类型的动态分支,GPU必须分配寄存器空间,如果一个分支比另一个分支成本高得多,这意味着GPU浪费了寄存器空间。这会导致并行调用着色器程序的次数减少,从而降低性能
- 基于统一值的分支:两个分支的工作负载大致相似,那么对 GPU 性能的影响可能很小
注:动态分支也会导致大型着色器程序,因为所有条件的代码都被编译到同一个着色器程序中。但是,这些较大文件对加载时间和内存使用的影响通常不如变体的影响显着
使用动态Branching
- code:使用评估运行时状态的if 语句。可使用属性强制 GPU 执行两个分支,或只执行一个分支
- sg:使用Branch Node。这总是执行两个分支
Shader variants:着色器变体,也称为着色器排列
- 将条件行为引入着色器代码的一种方法。 Unity编译 着色器源文件到着色器程序。每个编译的着色器程序 都有一个或多个变体
- 针对不同条件的着色器程序的不同版本。在运行时,Unity使用与当前需求相匹配的变量。使用shader关键字配置变量
Advantages and disadvantages of shader variants
- 变体优点:允许在着色器程序中使用运行时条件,而不受动态分支对GPU性能的影响
- 变体缺点:大量的着色器变体会导致 构建/运行 性能问题
当 Unity创建变体时,使用静态分支:
- 来创建多个小的、专门的着色器程序。在运行时,Unity使用匹配条件的着色器程序。而不会影响GPU性能
- 大量的变量会增加构建时间、文件大小、运行时内存使用和加载时间
- 当手动预加载(“预热”)着色器时,它也会导致更大的复杂性
因此:了解Unity如何确定着色器变量的数量,如何从编译中排除(“strip”)不需要的变量,以及何时在着色器中使用其他类型的条件是非常重要的
Number of shader variants
- Unity必须决定为当前的构建目标和图形API组合编译多少着色程序。 对于每个包含在构建中的着色器源文件,Unity决定它定义了多少个独特的着色器程序:
code:着色器程序的数量取决于代码
- 源文件本身的所有pass中的所有着色器阶段<顶点><片元>
- 源文件依赖的所有pass中的所有着色器阶段。<回退着色器> <UsePass>
sg:一个着色器源文件包含在构建中的场景中,被资源文件夹中的某些东西引用,或包含在图形设置窗口的始终包含着色器部分中
Keywords that affect a shader program
- 当Unity决定了它必须为当前构建目标和图形API编译多少个着色器程序时,它就会决定它必须为每个着色器程序编译多少个着色器变体
- 对于每个着色器程序,Unity决定着色器关键字的组合,导致不同的变体。这包括:
- 在该着色器的源文件中声明的着色器变量关键字集,Unity自动添加的着色器关键字集
Unity为一个着色器程序编译的着色器变体的数量是关键字集 的乘积
集合A和B:分别包含 三/ 四个着色器变量关键字
- COLOR_R,COLOR_G,COLOR_B
- QUALITY_L,QUALITY_M,QUALITY_H,QUALITY_U
- 受这些着色器变量关键字影响的着色器程序将产生以下12个变量
例:
- 其中一个着色器有许多着色器变量关键字集,每个关键字包含两个关键字 <NAME>_ON和<NAME>_OFF
- 如果着色器有两个这样的关键字集,这将导致四个变体。如果着色器有10个这样的关键字集,这将导致1024个变体
Deduplication of shader variants
编译后
- Unity会识别相同Pass中的相同变量,并确保这些变量指向相同的字节码 <重复数据删除>
删除重复数据
- 防止相同Pass中的相同变量增加文件大小,然而,相同的变体仍然会导致编译过程中的工作浪费,并增加内存使用量和运行时的着色器加载时间,记住这一点,去掉不需要的变量总是最好的
声明着色器关键字<key>
在集合中声明着色器关键字。一组互斥的关键字
- 集合A:color_r color_g color_b
sg中:两组关键字分别称为 Keyword / states功能是相同的,Unity 以相同的方式编译它们
声明着色器关键字的方式带来影响:
- type:会影响Unity如何为关键字创建变体
- scope:会影响关键字是本地/还是全局的。这决定了它们在运行时的行为
- stage:会影响关键字影响的着色器阶段
type: “multi compile” or “shader feature”
须选择Unity 在内部定义关键字的方式;带来的影响 Unity编译的变体数量
- multi compile / shader feature:用来创建一组用于变体的关键字
- 在内部,Unity 使用这些关键字来创建#define预处理器指令
multi compile
- 声明着色器变体的关键字 <集合>
- Unity为集合中的所有关键字编译着色器变体
- 使用:C# 脚本在运行时启用和禁用关键字,防止变体被错误地剥离
shader feature
- 声明了着色器变体的关键字,并且还指示编译器编译未启用这些关键字的变体
- Unity在构建时检查项目的状态,并且只编译正在使用的关键字的变体
- 使用:配置项目中的材质
注:在图形设置窗口中将着色器添加到始终包含的着色器列表中,Unity会在构建中包含所有集中的所有关键字
scope: Local or global
- 当声明一组关键字时,集合中关键字是具有局部 / 还是全局 作用域
- 这决定了是否可以在运行时使用全局着色器关键字覆盖此关键字的状态
默认情况下
- 声明具有全局 / 局部范围的关键字:这意味着可以在运行时使用全局着色器关键字覆盖/不能覆盖此关键字的状态
- 注:如果着色器源文件及其依赖项中存在同名关键字,则源文件中关键字的范围将覆盖依赖项中的范围。依赖项包括通过<Fallback >命令包含的所有着色器,<UsePass>命令包含的通道
Stage-Specific keywords
默认:Unity会为着色器的每个阶段生成关键字变体
例如
- 着色器包含一个顶点阶段和一个片段阶段,Unity 会为顶点和片段着色器程序的每个关键字组合生成变体
- 如果仅在其中一个阶段使用一组关键字,则会导致另一个阶段产生相同的变体
带来影响
- Unity会自动识别并删除相同的变体,这样它们就不会增加构建大小,但它们仍然会导致编译时间浪费、着色器加载时间增加以及运行时内存使用量增加
解决方案
- code着色器中:声明一组关键字时,可以指示 Unity仅针对给定的着色器阶段编译它们。然后确保仅在指定的着色器阶段使用关键字
注:以下图形 API 不完全支持特定于阶段的关键字
- OpenGL / Vulkan 中,在编译时,Unity 会自动将所有阶段特定的关键字指令转换为常规关键字指令
- 在 Metal 中,任何针对顶点阶段的关键字也会影响曲面细分阶段,反之亦然
Making behavior conditional with shader keywords
- 可使用着色器关键字使着色器的部分有条件,这样某些行为只在给定关键字处于给定状态时执行
Enabling and disabling shader keywords
- 当启用或禁用shader关键字时,Unity会渲染适当的shader变体,或者GPU执行适当的分支
两种方法来启用和禁用着色器关键字:
- 在运行时或在Unity编辑器中使用脚本
- 材质检查器
Unity’s predefined shader keywords
- Unity使用预定义的着色器关键字集来生成支持通用功能的着色器变体
Unity在编译时添加了以下shader变量关键字集: 将这组关键字添加到所有图形shader
- STEREO_INSTANCING_ON
- STEREO_MULTIVIEW_ON, STEREO_CUBEMAP_RENDER_ON, UNITY_SINGLE_PASS_STEREO。可以使用编辑器脚本删除这些关键字
默认:Unity将这组关键字添加到标准着色器
- LIGHTMAP_ON
- DIRLIGHTMAP_COMBINED, DYNAMICLIGHTMAP_ON
- lightmap_shadow_mixed
- SHADOWS_SHADOWMASK
- 可以使用图形设置窗口去掉这些关键字
在BRP中,如果项目使用了不同的层设置,Unity将这组关键字添加到所有的图形着色器:
- UNITY_HARDWARE_TIER1
- UNITY_HARDWARE_TIER2
Shader keyword limits
- 全局着色关键字:多达4,294,967,294个
- 地着色器关键字:单个着色器和计算着色器可以使用多达65,534个
- 着色器源文件中:声明的每个关键字及其依赖项都计入此限制。依赖项含<UsePass><回退>
- 如果Unity多次遇到同名的shader关键字,它只计算一次限制
- 如果一个着色器总共使用不超过128个关键字,它会导致运行时性能损失很小;因此,最好保持低的关键字数量。Unity总是为每个着色器保留4个关键字
<hr/>Using shader keywords with C# scripts
Local and global shader keywords
当Unity在c#中表示着色器关键字时,它使用了局部着色器关键字和全局着色器关键字的概念
- 本地着色器关键字:包括你在着色器源文件中声明的所有关键字。局部着色关键字影响单个着色器或计算着色器。局部关键字可以具有局部或全局作用域,这将影响全局着色器关键字是否可以覆盖它们
- 全局着色器关键字:作用是覆盖本地着色器关键字。你不需要在shader源文件中声明这些;它们只存在于c#代码中。全局着色关键字可以影响多个着色器,并同时计算着色器
Local shader keywords
- 当在着色器源文件中声明一个着色器关键字时,Unity用c#中的LocalKeyword结构来表示
LocalKeyword的 isOverridable属性
- 指示该关键字是在源文件中声明为全局作用域还是本地作用域
被覆盖:
- 如果关键字声明为全局作用域,因此可以被同名的全局shader关键字覆盖,则为真
不被覆盖:
- 如果关键字是用局部作用域声明的,则为false,因此不能被同名的全局shader关键字覆盖。
Unity将所有影响着色器或计算着色器的本地着色器关键字存储在一个LocalKeywordSpace结构中
- 对于图形着色器:通过shader.keywordspace访问它
- 对于计算着色器:通过ComputeShader-keywordSpace访问它
Global shader keywords
Unity维护了一个单独的全局着色器关键字列表。你不需要在shader源文件中声明这些
- 它们是c#中使用的局部着色器关键字的运行时覆盖。全局着色关键字可以影响多个着色器,并同时计算着色器
用GlobalKeyword结构表示全局着色器关键字
- 当需要为许多材质和计算着色器启用或禁用相同的着色器关键字时,设置一个全局着色器关键字可以很方便
缺点:
- 设置关键字的全局状态可能会导致意想不到的后果,如果着色器不小心定义了一个名称相同的关键字
- 可以通过使用局部作用域声明关键字,或者以降低冲突可能性的方式命名关键字来防止这种情况。
- 当你创建一个新的GlobalKeyword, Unity更新它的内部映射全局和本地关键字空间的所有着色器和计算着色器在此时加载。这可能是一个cpu密集型操作。为了减少此操作的影响,请尝试在应用程序启动后不久创建所有全局关键字,同时应用程序正在加载
How local and global shader keywords interact
当具有相同名称的全局着色器和本地着色器关键字具有不同的状态时
- Unity使用 LocalKeyword的isOverridable属性来确定该关键字是否为单个材质或计算着色器启用或禁用
- 如果关键字是用全局作用域声明的,isOverridable为真
- 如果是用局部作用域声明的,isOverridable为假
当isOverridable为true时
- 如果存在同名的全局关键字并启用,Unity使用全局关键字的状态。否则,Unity使用local关键字的状态
当isOverridable为false时
- Unity总是使用local关键字的状态
- 因此,要了解一个着色器关键字是否为单个材质或计算着色器启用或禁用,你必须检查isOverridable属性的状态和全局和/或局部关键字状态。
检查Unity是否认为一个mat的关键字是启用的还是禁用的:
using UnityEngine;
using UnityEngine.Rendering;
public class KeywordExample : MonoBehavioury{
public Material material;
void Start(){
CheckShaderKeywordState();
}
void CheckShaderKeywordState(){
// Get the instance of the Shader class that the material uses
var shader = material.shader;
// Get all the local keywords that affect the Shader
var keywordSpace = shader.keywordSpace;
// Iterate over the local keywords
foreach (var localKeyword in keywordSpace.keywords)
{
// If the local keyword is overridable (i.e., it was declared with a global scope),
// and a global keyword with the same name exists and is enabled,
// then Unity uses the global keyword state
if (localKeyword.isOverridable && Shader.IsKeywordEnabled(localKeyword.name))
{
Debug.Log(&#34;Local keyword with name of &#34; + localKeyword.name + &#34; is overridden by a global keyword, and is enabled&#34;);
}
// Otherwise, Unity uses the local keyword state
else
{
var state = material.IsKeywordEnabled(localKeyword) ? &#34;enabled&#34; : &#34;disabled&#34;;
Debug.Log(&#34;Local keyword with name of &#34; + localKeyword.name + &#34; is &#34; + state); }}}}
Enabling and disabling shader keywords
要检查图形着色器是否启用了本地关键字
- 使用Material.IsKeywordEnabled或Material.EnableKeyword
使用计算着色器
- 使用ComputeShader。IsKeywordEnabled或ComputeShader.EnableKeyword。
要检查是否启用了global关键字
- 使用Shader.IsKeywordEnabled或Shader.EnableKeyword或ComputeShader.enabledKeywords。
要启用或禁用图形着色器的本地着色器关键字
- 使用Material.SetKeyword.material,EnableKeyword或Material.DisableKeyword
要使用计算着色器
使用ComputeShader.SetKeyword ComputeShader.EnableKeyword或ComputeShader.DisableKeyword
要启用或禁用全局着色器关键字
- 请使用shader .SetKeyword ComputeShader。EnableKeyword或ComputeShader.DisableKeyword
要使用命令缓冲区启用或禁用局部或全局关键字
- 使用CommandBuffer.EnableKeyword,或者CommandBuffer.DisableKeyword
注: 当你启用或禁用与着色器变体工作的关键字时,Unity将使用不同的着色器变体
在运行时:
- 更改着色器变体可能会影响性能。如果关键字的更改需要第一次使用某个变体,则可能会在图形驱动程序准备着色器程序时出现故障
- 对于大型或复杂的着色器来说,这可能是一个特殊的问题,或者如果全局关键字状态的改变影响了多个着色器
为了避免这种情况
- 如果在着色器变量中使用关键字,请确保在着色器加载和预热策略中考虑关键字变量
Managing sets of keywords at runtime
- 编写着色器时,需要在set中声明关键字。集合中包含互斥的关键字。
在运行时
- Unity没有这些集合的概念。它允许您独立地启用或禁用任何关键字
- 启用或禁用一个关键字不会对其他关键字的状态产生影响。这意味着可以启用同一个关键字集合中的多个关键字,也可以禁用一个集合中的所有关键字。
- 当一个集合中有多个关键字被启用或没有关键字被启用时,Unity会选择一个它认为“足够好”的变体。无法保证到底会发生什么,它可能会导致意想不到的结果。为了避免这种情况,最好谨慎地管理关键字的状态
Using shader keywords with the material Inspector
- 材质检查器中查看材质时,可以启用或禁用它的本地着色关键字
使用原因
- 为不同的材质设置不同的关键字值,不需要使用代码
- 当使用MaterialPropertyDrawer启用一个关键字时,Unity会自动禁用集合中的其他关键字
- 这确保了在任何时候,一个集合中只有一个关键字被启用。 与任何着色器设置或数据一样,着色器关键字只有在材质检查器中被声明为着色器源文件中的材质属性时才可用
SG创建的着色器:
- 关键字默认是材质属性。这意味着这些设置在material Inspector中是自动可用的。要改变这一点,打开黑板,改变暴露的属性
code创建着色器
- 必须确保ShaderLab代码定义了一个表示关键字集的material属性
- material属性必须是Float类型,并且必须使用[Toggle]、[ToggleOff]或[KeywordEnum] MaterialPropertyDrawer属性才能正确地将它暴露给检视器
Shader variant stripping
- 剥离:可以阻止各种着色器被编译
- 剥离不需要的变量可以大大减少构建时间、文件大小、着色器加载时间和运行时内存使用
- 在大型项目中,或者使用复杂着色器的项目中,这是一个非常重要的考虑因素
Limiting shader variants when you declare shader keywords
声明关键字的方式可以限制它们产生的变量数量
- 尽可能用shader_feature
- 确保没有在multi_compile中定义未使用的关键字。 指定着色器关键字何时只影响给定的着色器阶段
Stripping shader variants in the Editor UI
Unity编辑器,以下可以配置着色器剥离:
图形设置窗口
- 在着色器剥离部分配置设置: 确保始终包含的着色器设置中没有包含不需要的着色器。 与GPU实例化、光照映射和雾相关的条带变体
- BRP中:如果你的层设置不同并不重要,请确保它们彼此相同
- URP中:禁用URP资源中未使用的功能
Stripping shader variants using Editor scripts
对于无法通过其他方式剥离的着色器变体,可以在编辑器脚本中使用以下API来执行构建时剥离:
- IPreprocessShaders,OnProcessShader:在Unity编译一个图形着色器传递到构建之前接收一个回调
变体集合
着色器变体集合 - Unity 手册
变体集合
- 实际上是一个着色器变体列表。使用着色器变量集合来预热着色器变量,或者确保在运行时需要但没有在场景中引用的着色器变量不被排除(“剥离”)
创建着色器变体集合资源
- Create > Shader Variant Collection

编辑器可以跟踪应用程序在运行时使用哪些变体,并自动创建包含一个着色器变体集合资源来包含这些着色器变体
在 Unity 项目中
- 选择变体集合资源时,使用控件来构建传递类型和shader关键字组合的列表,以提前加载。也可以使用ShaderVariantCollectionAPI 来配置着色器变体集合资源
预热着色器变体集合
- 预热:为避免在性能开销大时出现明显的停顿,Unity 可以要求图形驱动程序在首次需要着色器变体之前创建它们的GPU表示形式
<hr/>运行时替换着色器
在BRP中
- 可以让摄像机在运行时更改用于渲染特定几何体的着色器。可以这样做以实现视觉效果,例如边缘检测
- 通过脚本使用函数Camera.RenderWithShader 或 Camera.SetReplacementShader 来实现着色器替换
- 以上函数均采用 shader 和 replacementTag
工作方式:
- 摄像机按正常方式渲染场景,对象仍使用自己的材质,但要更改最终使用的实际着色器:
如果 replacementTag为空
如果 replacementTag不为空
- 则对于将要渲染的每个对象:查询真实对象的着色器以获取标签值
- 如果没有该标签,则不渲染对象。在替换着色器中找到一个子着色器,并且该子着色器的一个给定标签具有找到的值。如果找不到此类子着色器,则不渲染对象。现在,使用该子着色器来渲染对象
比如说
- 所有着色器都要有一个值为“Opaque”、“Transparent”、“Background”或“Overlay”的“RenderType”标签
- 则可编写一个替换着色器,该着色器只使用一个具有 RenderType = Solid 标签的子着色器来渲染实体对象
- 在替换着色器中找不到其他标签类型,因此不会渲染对象。或者也可以为不同“RenderType”标签值编写若干子着色器。顺便提一下,所有内置 Shader 对象都设置了一个“RenderType”标签
光照着色器替换
- 使用着色器替换时,将使用摄像机上配置的渲染路径来渲染场景。这意味着用于替换的着色器可以包含阴影和光照通道(您可以使用表面着色器进行着色器替换)。这对于渲染特殊效果和场景调试很有用
内置着色器中的着色器替换标签

所有内置着色器都设置了一个“RenderType”标签,可以在使用替换着色器进行渲染时使用此标签
内置场景深度/法线纹理
- 摄像机内置了渲染深度或深度+法线纹理的功能,
- 注意:在某些情况下(取决于硬件),可以使用着色器替换方法在内部渲染深度和深度+法线纹理。因此,务必在着色器中设置正确的“RenderType”标签
代码示例
Start() 函数指定替换着色器:
void Start() {
camera.SetReplacementShader (EffectShader, &#34;RenderType&#34;);
}

此函数请求EffectShader 使用 RenderType 键。对于所需的每个 RenderType,EffectShader 都有一个键/值标签
- SetReplacementShader 将检查场景中的所有对象,不使用它们的普通着色器,而是使用第一个具有指定键的匹配值的子着色器。在此示例中,任何对象的着色器若具有 Rendertype=“Opaque” 标签,都将被 EffectShader 中的第一个子着色器替换,任何对象若具有 RenderType=“SomethingElse” 着色器都将使用第二个替换子着色器,以此类推
- 如果任何对象的着色器不具有替换着色器中指定键的匹配标签值,则不会渲染这样的对象
<hr/>内置着色器
内置着色器 - Unity 手册
标准着色器

支持各种着色器类型 diff、Spec、BumpSpec,refl 组合到同一个可处理所有材质类型的着色器中
- 包含一种称为PBS的高级光照模型。以一种模仿现实的方式模拟材质和光照之间的相互作用

材质编辑器中:使用/不使用各种纹理字段和参数即可启用或禁用此着色器的功能
基于物理着色背后的理念:在不同的光照条件下,实现一致、合理的外观
遵循规则:能量守恒,菲涅耳反射,表面自遮挡,动态范围
光照数学实现:
- 漫射: Disney 模型
- 镜面反射: GGX 模型
- 遮挡:采用 Smith 联合 GGX 可见性术语和 Schlick 菲涅耳近似法
Content and Context
在思考Unity中的光照时,将概念划分为
- 内容:光照和渲染的对象
- 上下文:场景中会影响光照对象的光照
Context:对象发生光照时,哪些光源会影响对象
- 直接光源:可能是放置在场景中的游戏对象光源
- 间接光源:反射和反射光。这些光源都会对对象的材质产生影响,产生摄像机在对象表面上看到的结果
- 这种划分并非绝对的,可能被认为的“内容”也可能是另一个对象的光照上下文的一部分
光照上下文
- 空场景:已具有默认的光照上下文,附带有<环境>、基于天幕的<反射>和<方向光>
- 所有光照上下文都是从天空盒和方向光派生的,可添加更多光照和反射探针

添加的球体使用标准着色器
- 天空盒<烘焙或程序化>可作为光照设置的组成部分
反射天空盒 / 探针:通常情况下需要改变对象使用的反射,可用反射探针可以对某个空间点进行采样
全局光照:Unity的组成部分
- 标准的着色器:是数据驱动的,Unity将仅使用为材质设置的配置所需的着色器代码
- 全局照明系统:负责创建和跟踪反弹的光,来自 Emiss / Ambient

上下文是图像整体外观的关键部分
- 内容:描述正在渲染的对象的术语。外观是上下文和材质的结果
材质编辑器

标准着色器:检视面板中:显示材质的所有参数,包括纹理、混合模式、遮罩和辅助贴图
创建材质:
- 标准着色器允许进行多种配置,以便表示各种材质类型
- 需要UV与纹理相结合来描述网格的哪个部分指向纹理贴图的哪个部分
- 标准着色器材质:允许在同一网格上具有不同的材质属性,纹理分辨率可超过多边形拓扑,从而允许材质类型之间实现平滑的边界和过渡
材质的纹理生成:
- PS / 烘培/ 渲染 生成
- 渲染/烘焙,还可使用更高分辨率的模型生成法线贴图和遮挡贴图
- 纹理贴图不应包含固有光照

Metallic / Specular 工作流:两者接受的数据不同
- &#34;Metallic”值:表示材质是否为金属性,Base Map控制镜面反射的颜色。非金属性材质:具有与入射光颜色相同的镜面反射,并且在正面观察表面时几乎不会反射
- &#34;Specular&#34; 控制材质中镜面反射的颜色和强度。此设置可使镜面反射具有与漫射不同的颜色
材质参数

标准着色器材质参数列表。根据选择在 Metallic / Specular 工作流下工作,这些参数会略有不同。但大多数相同
- 查看更改渲染模式时 Unity 所做的更改。...\builtin_shaders-2019.3.0f6\Editor

使用脚本更改 Surface Type:当更改Surface Type 时,Unity 将对材质应用许多更改
反照率颜色和透明度

默认参数并且未分配任何值或纹理

Base Map 参数控制着表面的基色。为 Albedo 值指定单一颜色/纹理,纹理应表示对象表面的颜色
透明度

②:Albedo 参数指定的纹理时,可确保纹理具有Alpha值来控制材质的透明度
Specular 模式:Specular 参数

镜面反射本质:是场景中光源的直接反射,通常会在对象表面上显示为明亮的高光和反光
Specular / Metallic setup

①:镜面高光的强度和颜色会作为其他参数设置的结果。②可直接控制镜面高光的亮度和色调
Specular模式

①:RGB 颜色将控制镜面反射率的强度和色调。这包括来自光源的光泽和来自环境的反射。②:控制着镜面反射效果的清晰度
分配纹理贴图
- 需要不同材质,不同高光。①的参数:由纹理的R、G和B通道中的值控。②的参数: 由纹理的 Alpha 通道控制
Metallic 模式

表面的反射率和光响应将由 Metallic 和Smoothness控制,仍会生成镜面反射,不是进行显式定义
Metallic参数:决定了表面有多么“像金属”
- 当表面具有较高的金属性时:它会在更大程度上反射环境,并且反照率颜色将变得不那么明显。在最高金属性级别下,表面颜色完全由来自环境的反射驱动
- 当表面的金属性较低时:其反照率颜色会更清晰,并且所有表面反射均在表面颜色的基础之上可见,而不是遮挡住表面颜色

为Metallic参数分配纹理:由纹理的 R 中的值控制Smoothness级别:由纹理的 A 通道控制,忽略 G 和 B
分配金属/平滑度纹理贴图
- 最右边的黑白贴图:显示了金属为较亮区域,而皮革为中低灰色
平滑度

未分配 ① 或 ② 纹理映射,材质的平滑度由拉条控制。分配纹理:则会从该贴图中获取平滑度值
- Unity中,“微表面细节”不是直接可见的。是光照计算中使用的概念,但是,可以看到这个微表面细节的效果,它表示当光线从对象反弹时散射的光量

光滑的表面上:所有光线都倾向于以可预测和一致的角度反弹,非常低的微表面细节,形成清晰的反射

不光滑的表面上:在较宽的角度范围内反光,因此反射具有较少的细节并以更倾向于漫射的方式在表面上扩散
- 在其微表面细节中具有高峰和低谷,因此光线在很宽的角度范围内反弹,平均下来将产生漫射颜色,看不到清晰的反射
- 分配纹理贴图,控制表面上各种平滑级别材质

source 选择存储了平滑度值的纹理通道。①和②::数据存储在用于金属性或镜面反射纹理贴图的Alpha中

禁用①和②:移动端的可选性能优化选项

法线贴图:一种凹凸贴图 (Bump Map)。可将表面细节添加到模型,从而捕捉光线
- 使用法线贴图来表示精细的表面细节,使用分辨率较低的多边形表面来表示模型的较大形状
创建和使用凹凸贴图
- 创建高细节度时图形所需的核心方法之一。凹凸贴图通常也称为法线贴图 或 高度贴图
表面法线
- 实时光照中使用法线:一个模型的每个表面多边形根据相对于光线的表面角度获得光照
表面角度
- 可表示为表面垂直方向突出的一条线,而相对于表面的该方向即称为“表面法线”

相同数量的多边形:左侧带有平面着色,右侧带有平滑着色

平面着色圆柱体外侧的三个表面多边形
表面法线用橙色箭头表示
- 这些值用于计算光线如何从表面反射,所以可以看到光线沿着每个多边形的长度具有相同响应,因为表面法线指向相同的方向。因此就会产生“平面着色”
- 然而,对于平滑着色的圆柱体,表面法线在平面多边形上发生变化

红色箭头:表示每个顶点存储的法线方向。橙色箭头:表示多边形区域上的内插法线方向
- 使用这种基本平滑着色时,实际上只根据每个顶点来存储确定法线方向的数据,因此该表面上的变化值是从一个顶点到下一个顶点之间进行插值的
法线贴图
- 表面法线更进一步修改,使用纹理来存储修改模型上的表面法线的信息
- 是映射到模型表面的图像纹理,法线贴图纹理中的每个像纹理像素,表示平面法线方向与平面多边形“真实”表面法线之间的偏差

以 2D 图的形式查看时,三个多边形的法线贴图
- 橙色箭头:对应于法线贴图纹理中的像素,下面的是法线贴图纹理的单像素切片
在中心位置
- 法线已被修改,在多边形的表面上呈现出几个凹凸的外观。因为这些修改过的法线将用于光照计算,所以这些凹凸只会由于表面上的光照显示情况而变得明显
原始法线贴图文件:可见的颜色通常具有蓝色,并且不包含任何实际的浅色或深色着色
实际上
- 每个纹理像素的 RGB 值表示方向矢量的 X、Y 和 Z 值,并作为对多边形表面的基本内插平滑法线的修改而应用

不会影响网格的实际多边形性质,只会影响在表面上计算光照的方式
获取或制作法线贴图
- ①:通常镜像出反照率贴图的布局和内容
- ②:手工制作的
- ③:3D应用程序中渲染出来的
基本概念
- 制作模型的两个版本:高/低分辨率
- 高分辨率模型 :生成法线贴图
- 低分辨率版本:忽略非常精细的几何细节,这些细节现在已存储在法线贴图中,因此可以使用法线贴图来渲染该模型
有一些软件包可以分析常规摄影纹理中的光照,并从中提取法线贴图
法线贴图/高度贴图区别
- 法线贴图和高度贴图都是凹凸贴图的类型
- 二者都包含一些数据,用于表示较简单多边形网格的表面上的明显细节,但各自却以不同的方式存储这些数据

左侧:高度贴图,像素颜色越白,该区域看起来越高。右侧:法线贴图。其中每个像素表示表面看起来应该面向的方向的差异,此类型的贴图包含了必要的矢量来修改在表面上反射光线的方式
贴图紫蓝色
- RGB 颜色值用于存储矢量的 X、Y、Z 方向,其中的 Z 为“向上” ,经过 n *2-1

RGB 值 (0.5, 0.5, 1) 将得到矢量 (0,0,1),这便是用于法线贴图的“向上”,并表示模型表面没有变化
- 一个 (0.43, 0.91, 0.80) 值将得出 (–0.14, 0.82, 0.6) 矢量,这是对表面的大幅修改

明亮青色区域显示了针对每块石头顶部边缘多边形表面法线的大幅修改,使它们能够以正确的角度捕捉光线
辅助法线贴图

材质检视面板:第二个Normal Map 字段。使用额外的法线贴图来创建额外的细节,应当使用不同的平铺比例或频率

高度贴图与法线贴图类似的概念,这种技术更复杂,性能成本也更高。它与法线贴图结合使用,用于为表面提供额外的定义
法线贴图/高度图
- 法线贴图:可修改纹理表面上的光照
- 视差高度贴图:更进一步并实际上可移动可见表面纹理的区域,从而实现一种表面级遮挡效果。这意味着,对于明显的凸起,它们的近侧(面向摄像机)将膨胀和扩大,而它们的远侧(背离摄像机)将减小并且看起来被遮挡
- 高度贴图应为灰度图像,其中以白色区域表示纹理的高区域,以黑色表示低区域

①:分配了反照率贴图。②:分配了法线贴图。表面上的光照经过修改,但岩石不会相互遮挡”。③:分配了法线贴图和高度贴图的最终效果。岩石看起来从表面突出,较近的岩石似乎遮挡了它们后面的岩石

用于提供关于模型哪些区域应接受高/低间接光照的信息
遮挡纹理贴图

灰度图像:白色表示应接受完全间接光照的区域,以黑色表示没有间接光照

遮挡贴图:指明了角色袖子上暴露或隐藏在环境光照下的区域
Emission

可见光源:材质发光属性用于控制材质表面发光的颜色和强度
使用 Emission 属性
- 可使用单个颜色和发光级别来定义基本发光材质。选中Emission复选框可使材质发光。此时将显示Color和Global Illumination属性
- Color : 指定发光的颜色和强度。单击&#34;Color框&#34;可打开HDR Color拾色器
- 要指定材质的哪些区域发光,可以向该属性分配一个&#34;发光贴图&#34;
Global Illumination :
- 指定此材质发出的光如何影响附近其他游戏对象的环境光照。有三个选项:
- Realtime:将此材质的自发光添加到场景的Realtime Global Illumination 计算中。意味着此自发光会影响附近游戏对象的光照
- Baked:将此材质的自发光烘焙到场景的静态全局光照中。此材质会影响附近静态游戏对象的光照,但不会影响动态游戏对象的光照。可使用光照探针
- None:此材质本身具有发光颜色,不会影响场景中物体

辅助贴图(细节贴图)和细节遮罩
细节纹理用途

此角色具有皮肤纹理贴图,但还没有细节纹理
- 细节遮罩纹理 : 允许在模型的某些区域禁止应用细节纹理
材质图表 :

金属性设置的参考图表

镜面反射设置的参考图表
- 本质上,此过程关系到选择工作流程(默认或金属性)并获取贴图或拾色器的相关值
- 制作闪亮的白色塑料
- 需要白色反照率 (Albedo)。由于不是金属,需要深色镜面反射 (Specular) 或非常低的金属性 (Metallic) 值,最后需要非常高的平滑度 (Smoothness)
粒子着色器

可用于渲染各种粒子系统效果的内置着色器
属性
- 标准粒子着色器具有与标准着色器相同的属性集(或这些属性的子集,具体取决于着色器)
Flip-Book Mode:
- 将翻页渲染为单独的帧或将帧混合在一起以提供更流畅的动画。设置为以下选项之一:
- Simple- 将翻页中的帧渲染为单帧序列
- Blended- 混合翻页中的帧以将翻页渲染为平滑动画。
Autodesk Interactive 着色器
- 与 Autodesk® 3DsMax 和 Autodesk® Maya 中的 Interactive PBS 着色器类似,可供在 Unity 中使用
- 当 Unity 导入从这些 FBX 时,它会检查 FBX 是否包含具有 Interactive PBS 着色器的材质。如果包含,Unity 会将这些材质导入为 Autodesk Interactive 材质
创建 Autodesk Interactive 材质

①:自动创建:当Unity导入一个 FBX 文件且其中具有兼容的 Autodesk 着色器。②:手动创建 URP- Autodesk Interactive
- Autodesk Interactive 着色器的属性与标准着色器中的材质参数的工作方式相同,但 Roughness 除外

ShaderGraph : 能够直观地构建着色器。可以在图形框架中创建并连接节点,而不必手写代码
- 渲染管线兼容性
- 是:BRP、URP,HDRP
- 否:自定义可编程渲染管线
编写着色器概述
- 语言
- HLSL 的编程语言
- ShaderLab 的 Unity 特定语言。使用它可定义 Shader 对象,它充当着色器程序的容器
- 不需要为不同的平台使用不同的语言;Unity 针对不同的图形 API 将 HLSL 和 ShaderLab 代码编译为不同语言
- 编写着色器的不同方法
- 使用HLSL 编写“顶点&#34;和&#34;片元着色器&#34;
- BRP 中,可编写表面着色器
- Unity 还支持“固定函数”ShaderLab 命令。因此,使用 ShaderLab 编写着色器,无需使用 HLSL
- 为不同的图形 API 编写着色器
- 在某些情况下,必须根据所针对的图形 API 以不同方式编写着色器代码
ShaderLab
- &#34;ShaderLab &#34;是一种在着色器源文件中使用的声明性语言。使用嵌套大括号语法来描述 Shader 对象
- 在 ShaderLab 中可以定义很多内容
- 定义 &#34;Shader 对象&#34;的整体结构
- 使用代码块,添加用 HLSL 编写的&#34;着色器程序&#34;
- 在执行&#34;着色器程序&#34;或执行涉及另一个&#34;pass&#34;的操作之前,使用命令设置 GPU 的渲染状态
- 从着色器代码中公开属性,以便在 Material Inspector 中编辑它们并将其保存为材质资源的一部分
- 指定子着色器和pass的包要求。这使得Unity能够运行某些子着色器,并且只有在Unity项目中安装了特定的包时才能通过
- 定义当 Unity 无法在当前硬件上使用 Shader 对象运行任何 SubShader 时的回退行为
定义Shader对象
- 要使用 ShaderLab 定义 Shader 对象,可以使用 shader 代码块
- 概述
- Shader 对象是 Unity 特定的概念;它是着色器程序和其他信息的封装器。允许您在同一个文件中定义多个着色器程序,并告诉 Unity 如何使用它们
- Shader 对象具有嵌套结构;它将信息组织成结构<SubShader / Pass>
- Shader代码块
- [Properties 代码块]:定义材质属性
- [SubShader 代码块] :定义一个或多个子着色器
- [自定义编辑器],它确定着色器资源在 Unity 编辑器中的显示方式。可以为不同的RP分配不同的自定义编辑器
- [Fallback] 代码块分配一个回退
- Shader对象

包含单个pass的SubShader,定义材质属性、一个 CustomEditor 和一个 Fallback
定义材质属性
- 在ShaderLab代码中使用Properties代码块为 Shader对象定义材质属性的信息
- 材质属性是 Unity 作为材质资源一部分存储的属性
- 如果使用材质属性
- 通过对材质调用函数(Material.SetFloat)来获取或设置 Shader 对象中的变量值
- Inspector 查看和编辑值,Unity 会将进行的更改保存为材质资源的一部分,它们可在会话之间持续存在
- 如果不使用材质属性
- 仍可以通过对材质调用函数来获取或设置 Shader 对象中的变量值。 这些值没有可视化
Properties代码块
- 要在ShaderLab中将材质属性分配给 Shader 对象,在Shader代码块内放置一个Properties代码块
Properties{
<Material property declaration>}
- 将给定属性保存为材质资源的一部分,并在渲染期间使用存储在材质资源中的值
//材质属性声明
[optional: attribute] name(&#34;display text in Inspector&#34;, type name) = default value
按 [类型] 划分的材质属性声明语法

在着色器代码中,通常所有属性名称都以下划线字符开头
材质属性特性
- 材质属性声明可以具有一个可选特性,用于告知 Unity 如何处理它们
- 添加MaterialPropertyDrawer:通过以下内容可以控制材质属性在检视员窗口中的显示方式
- [Gamma]:指示浮点数或矢量属性使用 sRGB 值,这意味着如果项目中的颜色空间需要,则它必须与其他 sRGB 值一起转换
- [HDR]:指示纹理或颜色属性使用HDR值对于纹理属性,如果分配了 LDR 纹理,则 Unity 编辑器会显示警告
- [HideInInspector]:告知Unity在 Inspector 中隐藏此属性
- [MainTexture] :为材质设置主纹理,可用Material.mainTexture 进行访问,如果多次使用此特性,则 Unity 会使用第一个属性并忽略后续属性。注:使用此特性设置主纹理时,如果使用纹理串流调试视图模式或自定义调试工具,则该纹理在游戏视图中不可见
- [MainColor]:为材质设置主色,可用Material.color 访问,如果颜色具有其他属性 (property) 名称,但希望 Unity 将这个颜色视为主色,用此属性 (attribute)
- [NoScaleOffset]:告知 Unity隐藏此纹理属性的平铺和偏移字段
- [Normal]:指示纹理属性需要法线贴图。如果分配了不兼容的纹理,则 Unity会显示警告
- [PerRendererData]:指示纹理属性将来自每渲染器数据,形式为 MaterialPropertyBlock。材质 Inspector 会将这些属性显示为只读
将材质属性与 C# 代码结合使用
材质属性
- 在 C# 代码中通过MaterialProperty 类进行表示。要访问 HLSL 代码中定义的变量,可以调用 Material.GetFloat、Material.SetFloat
Unity中
- 控制材质属性在 Inspector 窗口中的显示方式:MaterialPropertyDrawer
- 对于更复杂的需求,可以使用 MaterialEditor、MaterialProperty 和 ShaderGUI 类
在 ShaderLab 代码中使用材质属性设置变量

通过材质属性设置变量的值,请在ShaderLab 代码中将材质属性名称置于方括号中
在 HLSL 代码中使用材质属性设置变量
- 要使用材质属性在 HLSL 代码中设置变量的值,请为材质属性提供与着色器属性相同的名
分配回退
- 在 ShaderLab 代码中使用Fallback代码块向 Shader 对象分配回退的信息

使用 Fallback 代码块
ShaderLab:指定自定义编辑器
CustomEditor/CustomEditorForRenderPipeline块来指定
- 用自定义编辑器可显示 Unity 无法使用其默认材质 Inspector 显示的数据类型
CustomEditor block
CustomEditorForRenderPipeline block
CustomEditor / CustomEditorForRenderPipeline 代码块
- ShaderLab中,可以为所有RP指定一个自定义编辑器。为此,可以在Shader代码块中放置一个CustomEditor代码块
- 还可以为基于 SRP 指定不同的自定义编辑器
方法
- Shader代码块中放置CustomEditorForRenderPipeline代码块
- 如果代码同时包含CustomEditor和CustomEditorForRenderPipeline块
- 则特定于渲染管线的块会覆盖CustomEditor块
//Unity 使用在命名类中定义的自定义编辑器,除非它被 CustomEditorForRenderPipeline 代码块覆盖。
CustomEditor “[custom editor class name]”
//当活动渲染管线资源是命名类型时,Unity 使用该命名类中定义的自定义编辑器
CustomEditorForRenderPipeline “[custom editor class name]” “[render pipeline asset class name]”
为着色器资源创建自定义编辑器类
- 要为代表给定 Shader 对象的着色器资源定义一个自定义编辑器,需创建一个继承自ShaderGUI 类的脚本。将脚本放在名为 Editor 的文件夹中

此代码演示了使用 CustomEditor 块和 CustomEditorForRenderPipeline 块的语法,

前者:为着色器资源指定默认自定义编辑。后者:为特定渲染管线资源指定两个额外自定义编辑器
定义 subshader
- ShaderLab定义子着色器,使用 SubShader代码块
- Shader对象:包含一个或多个 SubShader。通过 SubShader 您可以为不同的硬件、RP 和运行时设置定义不同的 GPU 设置和着色器程序
- 包含多个 SubShader :支持一系列不同的配置
SubShader代码块:
通过将SubShader代码块置于shader代码块中,可以定义子着色器
- 使用LOD代码块为 SubShader 分配 LOD(细节级别)值
- Tags代码块将数据的键值对分配给子着色器
- ShaderLab 命令将 GPU 指令或着色器代码添加到 SubShader
- 使用 pass 代码块定义一个或多个pass
- 使用packagerrequirements块指定包需求。这使得Unity只在安装了所需的包时才运行SubShader
示例

创建包含单个子着色器的 Shader 对象的语法,而每个子着色器又包含一个通道
ShaderLab:向子着色器分配标签
- 标签:数据的键/值对
- Unity 使用预定义键 和 值确定如何以及何时使用给定子着色器,也可以使用自定义值创建自己的自定义子着色器标签。可以从 C# 代码访问子着色器标签
在子着色器中使用 Tags 代码块
- 通过将 Tags 代码块置于 SubShader 代码块中来向子着色器分配标签
注:子着色器和 Pass 都使用Tags代码块,但其工作方式不同。向 Pass分配子着色器标签没有效果
- 定义通道标签,请将Tags代码块置于 Pass 代码块内部
- 定义子着色器标签,请将Tags代码块置于 SubShader 代码块内部
Tags {“[name1]” =“[value1]”“[…2]” = “[…2]”}
在 C# 代码中使用子着色器标签

可以使用 Material.GetTagAPI 从 C# 脚本中读取子着色器标签
语法和有效值
“RenderPipeline” = “[name]” //向 Unity 告知此子着色器是否与 URP 或 HDRP 兼容。
[name]:
- UniversalRenderPipeline //此子着色器仅与 URP 兼容
- HighDefinitionRenderPipeline //此子着色器仅与 HDRP 兼容
Shader &#34;...Shader&#34; {
SubShader {
Tags { &#34;RenderPipeline&#34; = &#34;UniversalRenderPipeline&#34; }
Pass {
…
}}}
- Queue 标签:向 Unity 告知要用于它渲染的几何体的渲染队列。渲染队列是确定 Unity 渲染几何体的顺序的因素之一
语法和有效值:通过两种方式使用Queue标签
“Queue” = “[queue name]” // 使用命名渲染队列
“Queue” = “[queue name] + [offset]”//在相对于命名队列的给定偏移处使用未命名队列
// 这种用法十分有用的一种示例情况是透明的水,它应该在不透明对象之后绘制,但是在透明对象之前绘制
- [queue name] :Background、Geometry,AlphaTest,Transparent,Overlay
- [offset]:值 = 整数,指定 Unity 渲染未命名队列处的索引(相对于命名队列)
将此标签与 C# 代码一起使用
- 可以使用 Shader.renderQueue 读取 Shader 对象的活动子着色器的 Queue 标签值
默认情况下
- [Queue] 标签指定的渲染队列中渲染几何体。可以基于每种材质覆盖此值
- 设置渲染队列属性,在材质 Inspector 中执行此操作
- C#中,可以通过使用 Rendering.RenderQueue 枚举设置 Material.renderQueue 的值来执行此操作

将几何体作为透明渲染队列的一部分进行渲染

在几何体队列之后的未命名队列中渲染几何体
RenderType 标签:可覆盖 Shader 对象的行为
- 在BRP中,可以使用一种称为着色器替换的技术在运行时交换子着色器
- 工作方式:标识具有匹配 RenderType 标签值的子着色器。这在某些情况下用于生成摄像机的深度纹理
- 在基于SRP的渲染管线中,可以使用 RenderStateBlock 结构覆盖在 Shader 对象中定义的渲染状态。可以使用 RenderType 标签的值标识要覆盖的子着色器
语法和有效值
&#34;RenderType&#34; =&#34;[renderType]&#34; // 为此子着色器设置 RenderType 值
[renderType] :值 = String
//RenderType = TransparentCutout 的子着色器:
Shader &#34;……&#34; {
SubShader {
Tags { &#34;RenderType&#34; = &#34;TransparentCutout&#34; }
Pass { … }}}
ForceNoShadowCasting 标签 :
- 阻止子着色器中的几何体投射(有时是接收)阴影。确切行为取决于渲染管线和渲染路径。如果使用着色器替换,但是不希望从其他子着色器继承阴影通道,这可能非常有用
// 是否对所有使用此子着色器的几何体阻止阴影投射(有时是接收)。
“ForceNoShadowCasting” = “[state]”
[state] : True / False
//此示例代码创建一个 ForceNoShadowCasting 值为 True 的子着色器:
Shader &#34;ExampleShader&#34; {
SubShader {
Tags { &#34;ForceNoShadowCasting&#34; = &#34;True&#34; }
Pass { … } }}
DisableBatching 标签:阻止 Unity 将动态批处理应用于使用此子着色器的几何体
- 对于执行对象空间操作的着色器程序十分有用。动态批处理会将所有几何体都变换为世界空间,这意味着着色器程序无法再访问对象空间
- 因此,依赖于对象空间的着色器程序不会正确渲染。为避免此问题,请使用此子着色器标签阻止 Unity 应用动态批处理
//“DisableBatching” = “[state]” Unity 是否对使用此子着色器的所有几何体阻止动态批处理
[state] : True / False / LODFading
- 对于属于 Fade Mode 值不为 None 的LODGroup一部分的所有几何体,Unity 会阻止动态批处理。否则,Unity 不会阻止动态批处理
//此示例代码创建一个 DisableBatching 值为 True 的子着色器
Shader &#34;ExampleShader&#34; {
SubShader {
Tags { &#34;DisableBatching&#34; = &#34;True&#34; }
Pass { … }}}
IgnoreProjector 标签 : 在BRP中,IgnoreProjector子着色器标签向 Unity 告知几何体是否受投影器影响。这对于排除投影器不兼容的半透明几何体多半很有用
“IgnoreProjector” = “[state]” Unity 在渲染此几何体时是否忽略投影器。[state] : True / False
//此示例代码创建一个 IgnoreProjectors 值为 True 的子着色器:
Shader &#34;ExampleShader&#34; {
SubShader {
Tags { &#34;IgnoreProjector&#34; = &#34;True&#34; }
Pass {
… } }}
- PreviewType 标签 :告知 Unity 编辑器如何在材质 Inspector 中显示使用此子着色器的材质
“PreviewType” = “[shape]”//Unity 编辑器用于显示使用此子着色器的材质预览的形状
- [shape]:球体 ,平面 (Plane),Skybox
//此示例代码创建一个 PreviewType 值为 Plane 的子着色器:
Shader &#34;ExampleShader&#34; {
SubShader {
Tags { &#34;PreviewType&#34; = &#34;Plane&#34; }
Pass {
… }}}
- CanUseSpriteAtlas 标签:在使用Legacy Sprite Packer的项目中使用此子着色器标签可警告用户着色器依赖于原始纹理坐标,因此不应将其纹理打包到图集中
“CanUseSpriteAtlas” = “[state]”//使用此子着色器的精灵是否与 Legacy Sprite Packer 兼容。
//此示例代码创建一个 CanUseSpriteAtlas 值为 False 的子着色器:
Shader &#34;ExampleShader&#34; {
SubShader {
Tags { &#34;CanUseSpriteAtlas&#34; = &#34;False&#34; }
Pass { …} }}
ShaderLab:为子着色器指定LOD值
可以将 LOD 值指定给子着色器。此值指示其计算方面的需求
- 在运行时,您可以为单个 Shader 对象或所有 Shader 对象设置 着色器 LOD 的值。然后 Unity 优先考虑具有较低 LOD 值的子着色器
- 注: 尽管此方法以用于渲染网格的 LOD 功能命名,但是仍然存在重要区别:着色器 LOD 与相对于摄像机的距离无关,并且 Unity 不会自动计算着色器 LOD。必须手动设置最大着色器 LOD
概述:使用此方法来微调不同硬件上的着色器性能。在用户的硬件理论上支持,但无法很好地运行子着色器时,这很有用
使用 LOD 代码块 : 在 ShaderLab 中,可以通过将LOD代码块置于SubShader代码块中来为子着色器指定 LOD 值
LOD [value] //将给定的 LOD 值指定给子着色器。

Shade中,须将子着色器按 LOD 降序排列.,因为 Unity 选择所找到的第一个有效子着色器,它将始终使用它
通过 C# 代码使用子着色器 LOD 值
- 为给定的着色器对象设置着色器LOD,可使用shader . maximumlod
- 为所有着色器对象设置着色器LOD,可以使用shader . globalmaximumlod,默认情况下,没有最大LOD
Unity 内置着色器的LOD值
- 在BRP中,Unity的内置着色器具有以下 LOD 值:
- LOD 值 : 100 着色器名称 :Unlit/Texture,Unlit/Color,Unlit/Transparent,Unlit/Transparent Cutout
- LOD 值 : 300 着色器名称 :Standard、Standard (Specular Setup),Autodesk Interactive
着色器 - Unity 手册
定义 Pass
- Pass 是 Shader 对象的基本元素。包含设置 GPU 状态的指令/ GPU 上运行的着色器程序
- 可以为Shader对象不同部分定义单独的pass实现不同的工作方式。如:更改渲染状态/不同的着色器程序/不同的LightMode标签的部分
- 注:SRP的渲染管线中,可以使用 RenderStateBlock 来更改 GPU 上的渲染状态,而无需单独的通道

Sub Shader中放置Pass代码块
为pass指定名称
- 在UsePass命令及某些C# API 中按名称引用一个通道。通道的名称在帧调试器工具中可见
- 在内部,Unity将名称转换为大写
通过C#使用pass名称,可以使用API:①②③

①:Material.FindPass。②:Material.GetPassName ③:ShaderData.Pass.Name
- 注:Material.GetShaderPassEnabled 和 Material.SetShaderPassEnabled 不按名称引用通道;而是使用LightMode标签的值引用 pass

为pass分配Tags
- 确定如何以及何时渲染给定的通道,还可以使用自定义值创建自己的自定义通道标签,并从C#代码访问它们
- 最常用的预定义通道标签是 LightMode 标签;用于所有RP。其他通道标签因RP而异
使用 Tags 代码块:
- 为通道指定标签,可在Pass 代码块内放置一个Tags代码块
LightMode 标签
- 一个预定义的通道标签,Unity 使用它来确定是否在给定帧期间执行该通道,在该帧期间 Unity 何时执行该通道,以及 Unity 对输出执行哪些操作
- 注:每个RP都使用 LightMode 标签,但预定义的值及其含义各不相同
BRP中:不设置 LightMode 标签,Unity 会在没有任何光照或阴影的情况下渲染通道;这本质上相当于 LightMode 的值为 Always
SRP中:可以使用 SRPDefaultUnlit 值来引用没有 LightMode 标签的通道

语法和有效值
通过 C# 脚本使用 LightMode 标签
- Material.SetShaderPassEnabled和ShaderTagId使用LightMode标签的值来确定Unity如何处理给定的传递
- 在SRP中:
- 可以为LightMode标签创建自定义值。然后,通过配置一个DrawingSettings结构,您可以使用这些自定义值来确定在给定ScriptableRenderContext.DrawRenderers调用期间要绘制哪些通道

BRP中的预定义通道标签
LightMode 标签有效值

这些是BRP中Light Mode通道标签的有效值

PassFlags 标签 : 在BRP中,使用PassFlags通道标签来指定 Unity 提供给通道的数据

PassFlag 标签的代码示例

RequireOptions 标签 : BRP中,Require Options通道标签根据项目设置启用或禁用一个通道

着色器代码块的类型,添加 HLSL 代码
HLSL 和 CG 前缀区别
- cg:默认包含几个Unity内置的shader include文件。内置的包含只与BRP兼容
- hlsl:默认不包含 Unity 的内置着色器 include 文件,必须手动包含要使用的任何库代码。它们适用于任何 RP
PROGRAM 和 INCLUDE 后缀区别
- PROGRAM为后缀的着色器代码块被称为 着色器程序块。可以使用它们来编写着色器程序
- INCLUDE为后缀的着色器代码块被称为 着色器 include 块。可以使用它们在同一源文件中的着色器程序块之间共享公共代码

Unity将代码包含在HLSLINCLUDE /CGINCLUDE块中定义的所有着色器程序中,可位于此源文件的任何位置
Specifying Package Requirements
- 有些着色器需要同时支持多个RP。将包需求添加到SubShaders和Passes中,可以在着色器代码使用未安装的包中的include文件或需要特定版本的包才能编译时避免编译错误
Using the PackageRequirements block
- 要指定SubShader或Pass的包要求,可以使用packagerrequirements块
- ShaderLab支持每个SubShader或Pass的单个packagerrequirements块,但每个块可以指定多个包需求。
- 注:提供了一个packagerrequirements块,它必须在SubShader或Pass中所有其他声明之前
//定义Pass或SubShader的包要求
PackageRequirements{[requirementdefinition]}声明包需求有多种方法。每个都提供了不同的行为。它们是:
- &#34; <package name> &#34;:指定子着色器或传递适用于包的任何版本
- &#34; <package name> &#34;:&#34; <version restrictions> &#34;:指定子着色器或传递仅适用于包版本的子集
- &#34; <package name> &#34;:&#34; unity=<version restrictions> &#34;:指定子着色器或传递仅适用于unity版本的子集,并要求具有给定名称的包
- &#34; unity &#34;: &#34; <version restrictions> &#34;: 指定子着色器 或传递仅适用于unity版本的一个子集
版本限制定义了一组版本范围。如果所需软件包的安装版本不在任何范围内,则不满足软件包要求。类似地,如果一个需求指定了一组Unity版本限制,同样适用于Unity的当前版本
- 如果SubShader或Pass声明了项目不满足的包要求,Unity就会从进一步的处理和编译中排除该SubShader或Pass
- 如果项目没有包含所需的包,或者包含了它们但版本不兼容,就会发生这种情况。如果一个着色器中没有满足条件的子着色器,或者没有满足条件的子着色器,控制台窗口就会显示一条警告消息
Version syntax
- 在ShaderLab的包需求中,一个版本使用major。Minor或major.minor.patch格式
- 如果你只使用major。Unity使用 0 作为补丁。包版本也可以包含-preview或-preview.N后缀,其中-preview等价于-preview.0预览版本在非预览版本之前
有多种方法可以指定版本范围。每个都提供了不同的行为。它们是:
- <version>:包括您输入的版本以及之后的所有版本。例如,1.2.3包含从1.2.3开始的所有版本
- [<version>]:精确版本。例如,[1.2.3]只包含版本1.2.3
- [<version1>,<version2>]:从<version1>到<version2>。使用方括号和圆括号会导致范围分别包含或排除版本号。左括号影响<version1>,右括号影响<version2>。例如:[1.2.3,2.3.4)包括从1.2.3到2.3.3的所有版本
还可以为单个包指定一组版本范围。要从单个范围创建一组版本范围,可以使用分号作为分隔符。例如,[2.0,3.4.5];[3.7];4.0包括从2.0.0到3.4.5、3.7.0和4.0.0及更高版本
设置包的版本时,请注意以下事项:
- 版本号、版本范围和版本范围集合不能包含任何额外字符
- 版本范围不能为空
- 版本范围集合不能有交集
下面的代码示例展示了如何在SubShader和Pass中指定包需求。这个子着色器为一个名为com.my的包声明了一个包需求。并适用于此包的任何版本,从2.2.0开始。子着色器有两遍:
- 第一遍需要:
- 通用渲染管道包,版本在10.2.1到11.0之间。
- Text Mesh Pro软件包的版本为3.2或以上。
- 第二遍需要:
- HDRP包的版本在8.0到8.5之间
Shader &#34;…/ExampleShader&#34;{
SubShader {
PackageRequirements {
&#34;com.my.package&#34;: &#34;2.2&#34;
}
Pass {
PackageRequirements
{
&#34;com.unity.render-pipelines.universal&#34;: &#34;[10.2.1,11.0]&#34;
&#34;com.unity.textmeshpro&#34;: &#34;3.2&#34;
} }
Pass{
PackageRequirements
{
&#34;com.unity.render-pipelines.high-definition&#34;: &#34;[8.0,8.5]&#34;
} } }}
Error checking
// 格式错误的版本或空的包名
PackageRequirements {
&#34;com.some.package.x&#34;: &#34;[10.2.1,9.0]&#34; // Error, empty version range
&#34;com.some.package.y&#34;: &#34;[10.2.1.9,11.0]&#34; // Error, incorrect version format
&#34;com.some.package.z&#34;: &#34;[2.3,3.5],[3.0,4.0]&#34; // Error, ranges intersect
&#34;&#34; : &#34;[2.3.4,3.4.5]&#34; // Error, no package name provided
}
//同一个包上有多个依赖项
PackageRequirements {
&#34;com.some.package.x&#34;: &#34;[10.2.1,11.0]&#34;
&#34;com.some.package.x&#34;: &#34;[11.2.1,12.0]&#34; // Error, dependency on &#34;com.some.package.x&#34; declared twice
&#34;unity&#34; : &#34;2021.2&#34;
&#34;unity&#34; : &#34;2021.3&#34; // Error, dependency on Unity version declared twice
}
//冲突的依赖声明
PackageRequirements {
&#34;com.some.package.x&#34;: &#34;unity=[2021.2.1,2021.3.3]&#34;
&#34;unity&#34; : &#34;2021.2&#34; // Error: Unity version requirement cannot be declared on a package and on its own simultaneously
}
//子着色器和传递之间的依赖声明冲突
SubShader {
PackageRequirements {
&#34;com.some.package.x&#34;: &#34;[2.3.4,3.4.5]&#34;
&#34;com.some.package.z&#34;: &#34;[1.1,3.2]&#34;
&#34;unity&#34;: &#34;2021.2&#34;
}
Pass {
PackageRequirements {
&#34;com.some.package.y&#34;: &#34;[1.2.2,2.5]&#34; // Fine, SubShader doesn’t declare a dependency on &#34;com.some.package.y&#34;
&#34;com.some.package.z&#34;: &#34;[2.0,3.1]&#34; // Fine, SubShader dependency intersects the provided version range
&#34;com.some.package.x&#34;: &#34;[1.1.1, 2.2.2]&#34; // Error, SubShader version range for &#34;com.some.package.x&#34; does not intersect the provided range
&#34;com.some.package.w&#34;: &#34;unity=[2021.2.1,2021.2.5]&#34; // Fine, SubShader dependency intersects the provided version range
&#34;com.some.package.u&#34;: &#34;unity=[2020.2.1,2020.2.5]&#34; // Error, SubShader Unity version range does not intersect the provided range
}
}
}
命令 类别
- 用于在 GPU 上设置 渲染状态 的命令
- 用于创建具有特定用途的 通道
- 可以通过 Category 代码块将 ShaderLab 命令组合起来
渲染状态的命令

①:Pass代码块中使用这些命令可为该Pass 置渲染状态。②:SubShader 代码块中使用这些命令可为其中的所有Pass设置渲染状态
SubShader 中使用这些命令可定义具有特定用途的通道
- UsePass 定义一个通道,它从另一个 Shader 对象导入指定的通道的内容
- GrabPass 创建一个通道,将屏幕内容抓取到纹理中,以便在之后的通道中使用
Category 代码块对命令进行分组
- 对设置渲染状态的命令进行分组,这样可以“继承”该代码块内的分组渲染状态。
- 如Shader 对象可能有多个子着色器,每个都需要混合设置为加法。Category 代码块

如Shader对象可能有多个子着色器,每个都需要混合设置为加法。Category 代码块
AlphaToMask

AlphaToMask &lt;state&gt; AlphaToMask &lt;On / Off&gt;
Blend
- 启用混合会禁用GPU 上的一些优化(Early-Z),这些优化会增加 GPU 帧时间
- 此命令会更改渲染状态
启用混合
- 使用 BlendOp 命令,混合操作默认为Add
- 如果混合操作是 Add、Sub、RevSub、Min 或 Max,GPU 会将片元着色器的输出值乘以源系数,将渲染目标中现有的值乘以目标系数

GPU 对结果值执行混合操作

有效参数值:①:render target :值 = 整数,范围 0 到 7 !功能:渲染目标索引。②:state : 值 = Off ! 功能:禁用混合
- ③:factor:值 = One / Zero / SrcColor / SrcAlpha / DstColor / DstAlpha / OneMinusSrcColor / OneMinusSrcAlpha /OneMinusDstColor / OneMinusDstAlpha
- ④:功能:使用源或目标的颜色的值 / 删除源或目标值 / GPU 将此输入的值乘以源颜色值 / GPU 将此输入的值乘以源 Alpha 值

常见混合类型
BlendOp
- 并非所有设备都支持所有混合操作,支持取决于图形 API 和硬件
- 对于不支持的混合操作,不同的图形 API 以不同的方式处理:GL 跳过不支持的操作,Vulkan 和 Metal 回退到 Add 操作
//设置 Blend 命令使用的混合操作
BlendOp <operation> BlendOp Sub
ColorMask
- 默认:GPU 写入所有通道 (RGBA)。例如,可以禁用颜色渲染来渲染无色阴影
- 另一个常见的用例是完全禁用颜色写入,以便您在一个缓冲区中填充数据而无需写入其他缓冲区;如,可能需要在不写入渲染目标的情况下填充模板缓冲区
//写入默认渲染目标的给定通道
ColorMask <channels> || ColorMask RGB
//如上,但针对给定的渲染目标
ColorMask <channels> <render target> || ColorMask RGB 2

有效参数值
Conservative
- 光栅化是一种渲染技术,通过确定哪些像素被三角形覆盖,将矢量数据(三角形投影)转换为像素数据(渲染目标)
- 通常情况下,GPU 通过对像素内的点进行采样,判断是否被三角形覆盖来确定是否对像素进行光栅化;如果覆盖范围足够,则 GPU 确定该像素被覆盖
保守光栅化
- 是指 GPU 对被三角形部分覆盖的像素进行光栅化,无论覆盖范围如何。这在需要确定性时很有用,例如在执行遮挡剔除、GPU上的碰撞检测或可见性检测时
- 保守光栅化:意味着 GPU 在三角形边上生成更多的片元;这会导致更多片元着色器调用,从而导致 GPU 帧时间增加

保守光栅化:意味着GPU在三角形边上生成更多的片元;这会导致更多片元着色器调用,从而导致 GPU 帧时间增加
Offset
调整深度偏差
- 以强制 GPU 在具有相同深度的其他几何体之上绘制几何体。如深度冲突和阴影暗斑
- 要为特定几何体设置深度偏差,请使用此命令或 RenderStateBlock
设置影响所有几何体的全局深度偏差
- 使用 CommandBuffer.SetGlobalDepthBias
- 除了全局深度偏差之外,GPU 还为特定几何体应用深度偏差,为了减少阴影暗斑,可以使用 light bias 设置实现类似的视觉效果
//根据给定的值,将几何体绘制得更靠近或更远离摄像机
Offset <factor>, <units> || Offset 1 , 1
有效参数值:
factor
// 浮点数,范围 –1 到 1。缩放最大 Z 斜率,也称为深度斜率,以生成每个多边形的可变深度偏移
//不平行于近剪裁平面和远剪裁平面的多边形具有 Z 斜率。调整此值以避免此类多边形上出现视觉瑕疵
units
//缩放最小可分辨深度缓冲区值,以产生恒定的深度偏移。最小可分辨深度缓冲区值(一个 _unit_)因设备而异
//负值意味着 GPU 将多边形绘制得更靠近摄像机。正值意味着 GPU 将多边形绘制得更远离摄像机
模板
ShaderLab 命令:模板 - Unity 手册
- 配置与 GPU 上的模板缓冲区相关的设置
- 模板缓冲区为帧缓冲区中的每个像素存储一个 8 位整数值,为给定像素执行片元着色器之前,GPU 可以将模板缓冲区中的当前值与给定参考值进行比较。这称为模板测试
模板测试通过
如果模板测试失败
- 则 GPU 会跳过对该像素的其余处理。这意味着可以使用模板缓冲区作为遮罩来告知 GPU 要绘制的像素以及要丢弃的像素
- 通常会将模板缓冲区用于特殊效果,例如门户或镜子。此外,在渲染硬阴影或者构造型实体几何 (CSG) 时,有时会使用模板缓冲区
用法:
可以使用Stencil命令做两件不同的事情:
- 配置Stencil测试
- 以及配置GPU写入Stencil缓冲区的内容
- 你可以在同一个命令中完成这两件事,但最常见的用例是创建一个着色器对象,屏蔽掉其他着色器对象无法绘制的屏幕区域。要做到这一点,你需要配置第一个着色器对象,使其始终通过stencil测试并写入stencil缓冲区,并配置其他着色器对象,使其执行stencil测试而不写入stencil缓冲区
//模板测试方程为
(ref & readMask) comparisonFunction (stencilBufferValue & readMask)
UsePass

UsePass 命令插入来自另一个 Shader 对象的指定通道
GrabPass
- GrabPass 是一个创建特殊类型通道的命令,该通道将帧缓冲区的内容抓取到纹理中。在后续通道中即可使用此纹理,从而执行基于图像的高级效果
- 此命令会显著增加 CPU 和 GPU 帧时间。除了快速原型制作之外,通常应该避免使用此命令,并尝试通过其他方式实现您的效果
- 如果确实使用了此命令,尽量减少屏幕抓取操作的次数;方法是减少您对该命令的使用,或者使用将屏幕抓取到命名纹理的签名
在SubShader代码块中使用此命令
- GrabPass 仅适用于帧缓冲区。不能使用此命令来获取其他渲染目标、深度缓冲区等的内容
- 两种不同的签名具有不同的功能和不同的性能影响。使用命名纹理可以显著减少屏幕抓取
GrabPass { }:
- 将帧缓冲区内容抓取到一个纹理中,而后可以在同一个子着色器中的后续通道中使用该纹理。使用 _GrabTexture 名称引用该纹理
- Unity 每次渲染包含此命令的批处理时都会抓取屏幕。这意味着 Unity 可以每帧多次抓取屏幕:每批次一次
GrabPass { &#34;ExampleTextureName&#34; }

使用给定名称引用该纹理。当您使用此签名时,Unity会在渲染批处理的帧中第一次抓取屏幕,该批处理包含具有给定纹理名称的此命令
ZClip
- 将 GPU 的深度剪辑模式设置为钳位对于模板阴影渲染很有用;这意味着当几何体超出远平面时不需要特殊处理,从而减少渲染操作。但是,它可能会导致不正确的 Z 排序
// 设置深度剪辑模式
ZClip [enabled] ZClip True

示例
ZTest
ShaderLab 命令:ZTest - Unity 手册
- 深度测试:可使具有 “Early-Z” 功能的 GPU 在管线早期拒绝几何体,并确保几何体的正确排序。通过改变深度测试的条件,可以实现物体遮挡等视觉效果
ZWrite
ShaderLab 命令:ZWrite - Unity 手册
- 通常,ZWrite 对不透明对象启用,对半透明对象禁用
- 禁用 ZWrite 会导致不正确的深度排序。这种情况下,您需要在 CPU 上对几何体进行排序

将 HLSL 代码添加到 ShaderLab 代码中
HLSL中的预处理器指令
在内部,着色器编译有多个阶段
- 第一个阶段是预处理,一个被称为预处理器的程序为编译准备代码。预处理器指令是用于预处理器的指令
include and include_with_pragmas directives in HLSL
- #include指令是一种预处理器指令:它们指示编译器将一个HLSL文件的内容包含在另一个HLSL文件中。它们包含的文件称为包含文件(include file)。
- Unity还提供了一个额外的、特定于Unity的 #include_with_pragmas指令
#include_with_pragmas指令
- 工作原理与普通的#include指令相同,但它还允许你在include文件中使用 #pragma 指令。这意味着#include_with_pragmas指令允许你在多个文件之间共享 #pragma指令
Using the include_with_pragmas directive
- 注:要使用#include_with_pragmas指令,你必须启用缓存着色器预处理器
- 使用unity特有的#include_with_pragmas指令来实现一个通用的工作流改进:能够切换多个着色器调试的开关,而不需要每次都编辑每个着色器源文件
// Comment out the following line to disable shader debugging
#pragma enable_d3d11_debug_symbols
- 在要调试的每个着色器中,添加一个指向include文件的 #include_with_pragmas 指令。把这个指令放在其他#pragma指令旁边,像这样:
// Example pragma directives
#pragma target 4.0
#pragma vertex vert
#pragma fragment frag
//Replace path-to-include-file with the path to the include file
#include_with_pragmas &#34;path-to-include-file&#34;
// The rest of the HLSL code goes here
- 如果想在使用包含文件的所有着色器中打开和关闭调试开关,只需要修改包含文件中的一行代码。当Unity 重新编译着色器时,它包括修改的行
pragma directives in HLSL
- #pragma 指令是一种预处理器指令。它们为着色器编译器提供了其他类型的预处理器指令所没有的额外信息
使用 pragma 指令

可以在HLSL代码的任何地方放置#pragma指令,但通常的约定是将它们放在开头
List of supported pragma directives
- Unity支持所有作为标准HLSL一部分的#pragma指令,只要这些指令在常规的include文件中
- 此外,Unity支持以下特定于Unity的#pragma指令:
表面着色器
- 使用这个指令来告诉编译器使用哪个函数作为Surface函数,并将数据传递给该函数
//编译指定名称的函数作为表面着色器,以便它可以与给定的光照模型一起工作
#pragma surface <surface function> <lighting model> <optional parameters>
着色器阶段

使用这些指令,来告诉编译器在不同的着色器阶段使用哪些函数
着色器变体和关键字

使用这些指令告诉着色器编译器如何处理着色器变量和关键字
GPU需求和着色器模型支持

使用这些指令来告诉编译器你的着色器需要特定的GPU功能
图形 API

使用这些指令来告诉Unity包含或排除给定图形API的代码
其他pragma 指令

针对HLSL中的着色器模型和GPU功能
Targeting shader models and GPU features in HLSL - Unity 手册
使用#pragma指令
- 指示着色器需要某些GPU特性。在运行时,Unity使用这些信息来确定着色器程序是否与当前硬件兼容
可以使用#pragma require指令
- 指定单个GPU特性,也可以使用#pragma target指令指定着色器模型。着色器模型是一组GPU特性的简写;它与#pragma require指令相同,具有相同的功能列表
正确描述着色器所需的GPU特性很重要
- 如果着色器使用了需求列表中没有的功能,可能会导致编译时错误,或者设备在运行时无法支持着色器
以HLSL中的图形api和平台为目标
Targeting graphics APIs and platforms in HLSL - Unity 手册
- 有些#pragma指令需要一些参数,允许你针对特定的图形api和平台设定目标
包含或排除图形API
默认情况下
- Unity为当前构建目标编译列表中每个图形API的所有着色器程序。有时,你可能只想为特定的图形api编译特定的着色器程序;例如,如果您使用的功能并非在所有平台上都支持。
- 要仅针对给定的api编译着色器程序,请使用#pragma only_renderers指令。您可以传递多个值,以空格分隔
在HLSL中声明和使用着色器关键字
Declaring and using shader keywords in HLSL - Unity 手册
- 在HLSL代码中,使用#pragma指令来声明着色器关键字,使用#if指令来表明一段着色器代码依赖于着色器关键字的状态。你可以在常规图形着色器中使用着色器关键字,也可以计算着色器
着色器语义
着色器语义 - Unity 手册
- 编写HLSL着色器程序时, 输入和输出变量需要通过语义来表明 其“意图”
顶点着色器输入语义
- 主顶点着色器函数(由 #pragma vertex 指令表示)需要在所有输入参数上都有语义。 这些对应于各个网格数据元素,如顶点位置、法线网格和纹理坐标
片元着色器输出语义
- 片元(像素)着色器会输出颜色,并具有SV_Target语义
- 函数 frag 的返回类型为 fixed4(低精度 RGBA 颜色)。因为它只返回一个值,所以语义由函数自身指示: SV_Target。也可以返回包含输出的结构

从片元着色器返回结构对于不止返回单个颜色的 着色器非常有用
SV_TargetN:多个渲染目标
- SV_Target1、SV_Target2 等等:这些是着色器写入的附加颜色。这在一次渲染到多个渲染目标(称为“多渲染目标”渲染技术,简称 MRT)时使用。SV_Target0 等同于 SV_Target。
SV_Depth:像素着色器深度输出
- 通常情况下, 片元着色器不会覆盖 Z 缓冲区值,并使用 常规三角形栅格化中的默认值。但是, 对于某些效果,输出每个像素的自定义 Z 缓冲区深度值很有用
- 请注意,在许多 GPU 上,这会关闭一些深度缓冲区优化,因此如果没有充分的理由,请不要覆盖 Z 缓冲区值。SV_Depth 产生的成本取决于 GPU 架构,但总体上与 Alpha 测试(使用 HLSL 中的内置 clip() 函数)的成本非常相似。通过渲染着色器在所有常规不透明着色器之后修改深度(例如,使用 AlphaTest渲染队列)。
- 深度输出值必须为单个 float
顶点着色器输出和片元着色器输入
- 顶点着色器需要输出顶点的最终裁剪空间位置,以便 GPU 知道屏幕上的栅格化位置以及深度。此输出需要具有 SV_POSITION 语义,并为 float4 类型。
- 顶点着色器生成的所有其他输出(“插值器”或“变换”)都是特定着色器需要的。从顶点着色器输出的值将在渲染三角形的面上进行插值,并且每个像素的值将作为输入传递给片元着色器
- 许多现代 GPU 并不真正关心这些变量的语义;然而,一些旧系统(最主要的是 Direct3D 9 上的着色器模型 2 GPU)存在关于语义的特殊规则:
- TEXCOORD0、TEXCOORD1 等语义用于指示任意高精度数据,如纹理坐标和位置
- 顶点输出和片元输入的 COLOR0 和 COLOR1 语义用于低精度 0 到 1 范围的数据
- 为了获得最佳的跨平台支持,应将顶点输出和 片元输入标记为 TEXCOORDn 语义
插值器数量限制
对于总共可以使用多少个插值器变量将信息 从顶点传递到片元着色器,存在一些限制。该限制 取决于平台和 GPU,一般准则如下:
- 最多 8 个插值器:OpenGL ES 2.0 (Android)、Direct3D 11 9.x 级别 (Windows Phone) 和 Direct3D 9 着色器模型 2.0(老旧 PC)。由于插值器 数量受到限制,但每个插值器可以是一个 4 分量矢量, 所以一些着色器将内容打包在一起以便不会超过限制。例如,两个纹理 坐标可以在一个 float4 变量中传递(.xy 表示一个坐标,.zw 表示第二个坐标)。
- 最多 10 个插值器:Direct3D 9 着色器模型 3.0 (#pragma target 3.0)。
- 最多 16 个插值器:OpenGL ES 3.0 (Android) 和 Metal (iOS)。
- 最多 32 个插值器:Direct3D 10 着色器模型 4.0 (#pragma target 4.0)。
无论特定目标硬件如何,出于性能原因,通常最好使用尽可能少的插值器
屏幕空间像素位置:VPOS
- 片元着色器可以接收渲染为特殊 VPOS 语义的像素的位置。 此功能仅从着色器模型 3.0 开始存在,因此着色器需要具有 #pragma target 3.0 编译指令。
- 在不同的平台上,屏幕空间位置输入的基础类型会有所不同,因此为了获得最大的可移植性,请对其使用 UNITY_VPOS_TYPE 类型(在大多数平台上将是 float4,在 Direct3D 9 上将是 float2

使用“像素位置语义&quot;将导致难以让裁剪空间位置 (SV_POSITION) 和 VPOS 处于相同的顶点到片元结构中。因此顶点着色器应将裁剪空间位置输出为单独的“out”变量
面对方向:VFACE
- 片元着色器可以接收一种指示渲染表面是面向摄像机还是背对摄像机的变量。这在渲染应从两侧可见的几何体时非常有用 - 通常用于树叶和类似的薄型物体。VFACE 语义输入变量将包含表示正面三角形的正值,以及表示背面三角形的负值

此功能从着色器模型 3.0 开始才存在,因此着色器需要具有 #pragma target 3.0 编译指令
顶点 ID:SV_VertexID
- 顶点着色器可以接收具有“顶点编号”为无符号整数的变量。当想要从纹理或 ComputeBuffers 中 获取额外的每顶点数据时,这非常有用

此功能从DX10(着色器模型 4.0)和 GLCore/OpenGL ES 3 开始才存在,因此着色器需要具有 #pragma target 3.5 编译指令
使用 Cg/HLSL 访问着色器属性
使用 Cg/HLSL 访问着色器属性 - Unity 手册
- 如果要在着色器程序中访问其中一些属性,则需要声明具有相同名称和匹配类型的 Cg/HLSL 变量
- Cg/HLSL 还可以接受uniform关键字,但该关键字并不是必需的:
ShaderLab 中的属性类型以如下方式映射到 Cg/HLSL 变量类型:
- Color 和 Vector 属性映射到 float4、half4 或 fixed4 变量。
- Range 和 Float 属性映射到 float、half 或 fixed 变量。
- 对于普通 (2D) 纹理,Texture 属性映射到 sampler2D 变量;立方体贴图 (Cubemap) 映射到 samplerCUBE;3D 纹理映射到 sampler3D
向着色器提供属性值
在下列位置中查找着色器属性值并提供给着色器:
- MaterialPropertyBlock 中设置的每渲染器值。这通常是“每实例”数据(例如,全部共享相同材质的许多对象的自定义着色颜色)
- 在渲染的对象上使用的材质中设置的值
- 全局着色器属性,通过 Unity 渲染代码自身设置<内置着色器变量>,或通过您自己的脚本来设置<Shader.SetGlobalTexture>
优先顺序如上所述
- 每实例数据覆盖所有内容;然后使用材质数据;最后,如果这两个地方不存在着色器属性,则使用全局属性值。最终,如果在任何地方都没有定义着色器属性值,则将提供“默认值”(浮点数的默认值为零,颜色的默认值为黑色,纹理的默认值为空的白色纹理)
序列化和运行时材质属性
- 材质可以同时包含序列化的属性值和运行时设置的属性值
序列化的数据
- 是在着色器的 Properties 代码块中定义的所有属性。通常,这些是需要存储在材质中的值,并且可由用户在材质检视面板中进行调整。
材质也可以具有着色器使用的一些属性
- 但不在着色器的 Properties 代码块中声明。通常,在运行时从脚本代码Material.SetColor设置的属性
- 注意,矩阵和数组只能作为非序列化的运行时属性存在
特殊纹理属性
- 对于设置为着色器/材质属性的每个纹理,Unity 还会在其他矢量属性中设置一些额外信息
纹理平铺和偏移
- 材质通常具有其纹理属性的 Tiling 和 Offset 字段。此信息将传递到着色器中的 float4{TextureName}_ST属性:X 平铺值 // Y 平铺值 //z 包含 X 偏移值 // w 包含 Y 偏移值
纹理大小
- {TextureName}_TexelSize- float4 属性包含纹理大小信息:
- x 包含 1.0/宽度,y 包含 1.0/高度,z 包含宽度,w 包含高度
纹理 HDR 参数
- {TextureName}_HDR- 一个 float4 属性,其中包含有关如何根据所使用的颜色空间解码潜在 HDR(例如 RGBM 编码)纹理的信息
向顶点程序提供顶点数据
- 对于 Cg/HLSL 顶点程序, 网格顶点数据作为输入传递给顶点 着色器函数。每个输入都需要有指定的语义:例如,POSITION 输入表示顶点位置,NORMAL 表示顶点法线。
通常,顶点数据输入在结构中声明,而不是 逐个列出。在 UnityCG.cginc include 文件中 定义了几个常用的顶点结构,在大多数情况下, 仅使用它们就足够了

要访问不同的顶点数据,需要声明顶点结构,或者将输入参数添加到顶点着色器。顶点数据由 Cg/HLSL语义标识
当网格数据包含的分量少于顶点着色器输入所需 的分量时,其余部分用零填充,但默认值为 1 的.w分量除外。例如,网格纹理坐标 通常是仅包含 x 和 y 分量的 2D 矢量。如果 顶点着色器使用TEXCOORD0语义声明一个float4输入,则 顶点着色器接收的值将包含 (x,y,0,1)
内置着色器 include 文件
内置着色器 include 文件 - Unity 手册

Unity 提供了若干文件供着色器程序用于引入预定义的变量和 helper 函数。这可以通过标准#include指令来完成

Unity 中的着色器 include 文件采用.cginc扩展名,内置的着色器 include 文件
查看任何 helper 代码
- unity安装路径/Data/CGIncludes/UnityCG.cginc
---HLSLSupport.cginc
- 编译 CGPROGRAM 着色器时会自动包含此文件(但不会对 HLSLPROGRAM 着色器包含此文件)。此文件声明各种预处理器宏以帮助进行多平台着色器开发
--UnityShaderVariables.cginc
- 编译 CGPROGRAM 着色器时会自动包含此文件(但不会对 HLSLPROGRAM 着色器包含此文件)。此文件声明着色器中常用的各种内置全局变量。
--UnityCG.cginc
- Shader 对象中通常会包含此文件。此文件声明大量<span class=&#34;nolink&#34;>内置 helper 函数和数据结构。

UnityCG.cginc 中的数据结构
内置宏
内置宏 - Unity 手册
- Unity 在编译着色器程序时会定义几个预处理器宏
目标平台

SHADER_API_MOBILE 是针对所有常规移动平台(GLES、GLES3、METAL)定义的。
- 此外,当目标着色语言为 GLSL 时,还会定义 SHADER_TARGET_GLSL(对于 OpenGL/GLES 平台来说始终会定义)
着色器目标模型
- SHADER_TARGET被定义为与着色器目标编译模型匹配的数值(即匹配#pragma target指令)

当编译到着色器模型 3.0 时,SHADER_TARGET为30。可以在着色器代码中使用此宏来进行条件检查
Unity 版本
- UNITY_VERSION包含 Unity 版本的数值。例如,对于 Unity 5.0.1,UNITY_VERSION为501。如果您需要编写使用不同着色器内置功能的着色器,则可以将其用于版本比较。例如,#if UNITY_VERSION >= 500预处理器检查仅在版本为 5.0.0 或更高时可以通过
编译的着色器阶段

编译每个着色器阶段时会定义预处理器宏
- 通常,在像素着色器和计算着色器之间共享着色器代码时,这些宏非常有用,可以解决某些工作必须以略有不同的方式来完成的情况
平台差异helper
- 不鼓励直接使用这些平台宏,因为它们并非始终有助于代码的未来验证。例如,如果您正在编写一个检查 D3D11 的着色器,您可能希望确保在将来将这项检查扩展为包含 Vulkan。应改用 Unity 定义的几个 helper 宏(在HLSLSupport.cginc中)
阴影贴图宏
- 根据平台的不同,声明和采样阴影贴图可能会有很大差异。Unity 有几个宏可帮助解决这个问题:
- UNITY_DECLARE_SHADOWMAP(tex):声明一个名为“tex”的阴影贴图纹理变量
- UNITY_SAMPLE_SHADOW(tex,uv):在给定的“uv”坐标处采样阴影贴图纹理“tex”(XY 分量是纹理位置,Z 分量是要比较的深度)。返回单个浮点值,阴影项的范围在 0 到 1 之间。
- UNITY_SAMPLE_SHADOW_PROJ(tex,uv):与上面类似,但是会读取投影阴影贴图。“uv”是一个 float4,所有其他分量除以 .w 来执行查找
常量缓冲区宏
- Direct3D 11 将所有着色器变量分组为“常量缓冲区”。Unity 的大多数内置变量已经分组,但对于自己的着色器中的变量,更加理想的做法是,根据预期的更新频率将它们放入单独的常量缓冲区

对此,请使用 CBUFFER_START(name) 和 CBUFFER_END 宏
纹理/采样器声明宏
- 通常,在着色器代码中使用texture2D来声明纹理和采样器对。 但是在某些平台(例如 DX11)上,纹理和采样器是单独的对象, 并且可能的采样器最大数量非常有限
- Unity 有一些宏来声明 没有采样器的纹理,并使用另一个纹理中的采样器对纹理进行采样

如果您遇到采样器限制,并且知道几个纹理实际上可以共享同一个采样器 (采样器定义纹理过滤和包裹模式)。使用这些宏
表面着色器通道指示符

编译表面着色器时,表面着色器会为各种通道生成大量代码以产生光照。编译每个通道时,将定义以上宏之一
深度纹理 helper 宏
大多数情况下,深度纹理用于渲染距离摄像机的深度。在此情况下,UnityCG.cginc include 文件包含的一些宏可以处理上述复杂问题:
- UNITY_TRANSFER_DEPTH(o):计算顶点的眼睛空间深度并将其在 o 中输出(必须是 float2)。当渲染到深度纹理时,在顶点程序中使用此宏。在具有本机深度纹理的平台上,此宏完全不执行任何操作,因为 Z 缓冲区值是隐式渲染的。
- UNITY_OUTPUT_DEPTH(i):从 i 返回眼睛空间深度(必须为 float2)。当渲染到深度纹理时,在片元程序中使用此宏。在具有本机深度纹理的平台上,此宏始终返回零,因为 Z 缓冲区值是隐式渲染的。
- COMPUTE_EYEDEPTH(i):计算顶点的眼睛空间深度并将其在 o 中输出。当__不__渲染到深度纹理时,在顶点程序中使用此宏。
- DECODE_EYEDEPTH(i)/LinearEyeDepth(i):通过深度纹理 i 给出高精度值时,返回相应的眼睛空间深度。
- Linear01Depth(i):通过深度纹理 i 给出高精度值时,返回相应的线性深度,范围在 0 到 1 之间

着色器:将渲染其游戏对象的深度:
帮助 函数
- Unity 具有许多内置实用函数,旨在使编写着色器更简单,更轻松

UnityCG.cginc 中的顶点变换函数
- UnityCG.cginc 中的前向渲染 helper 函数

仅当使用前向渲染(ForwardBase 或 ForwardAdd )时,这些函数才有用
- UnityCG.cginc 中的屏幕空间 helper 函数

采样屏幕空间纹理的坐标。其中用于纹理采样的最终坐标可以通过透视除法(例如xy/w)计算得出。这些函数还处理渲染纹理坐标中的平台差异
- UnityCG.cginc 中的顶点光照 helper 函数

仅当使用每顶点光照着色器,这些函数才有用
内置着色器变量
- Unity 的内置文件包含着色器的全局变量:当前对象的变换矩阵、光源参数、当前时间等等。就像任何其他变量一样,可在着色器程序中使用这些变量,但如果已经包含相关的 include 文件,则不必声明这些变量。
变换

所有这些矩阵都是float4x4类型,并且是列主序的
摄像机和屏幕

这些变量将对应于正在渲染的摄像机。如,在阴影贴图渲染中,它们仍将引用摄像机组件值,而不是用于阴影贴图投影的“虚拟摄像机”

时间:秒位,并由项目Time 设置中的时间乘数 (Time multiplier) 进行缩放。没有内置变量可用于访问未缩放的时间
光照:
- 光源参数以不同的方式传递给着色器,具体取决于使用哪个渲染路径, 以及着色器中使用哪种光源模式通道标签

前向渲染(ForwardBase和ForwardAdd通道类型)使用

延迟着色和延迟光照,在光照通道着色器中使用(全部在 UnityDeferredLibrary.cginc 中声明)
- 为ForwardBase、PrePassFinal和Deferred通道类型设置了球谐函数系数 (由环境光和光照探针使用)。这些系数包含由世界空间法线求值的三阶 SH 函数(参阅UnityCG.cginc中的ShadeSH9)。 这些变量都是 half4 类型、unity_SHAr和类似名称
最多可为Vertex通道类型设置 8 个光源;始终从最亮的光源开始排序。因此,如果您希望 一次渲染受两个光源影响的对象,可直接采用数组中前两个条目。如果影响对象 的光源数量少于 8,则其余光源的颜色将设置为黑色

光照贴图

雾效和环境光

其他
着色器数据类型和精度
着色器数据类型和精度 - Unity 手册
- Unity 中的标准着色器语言为HLSL,支持一般 HLSL 数据类型。但是,Unity 对 HLSL 类型有一些补充,特别是为了在移动平台上提供更好的支持。
基本数据类型

浮点类型有几种变体:以及它们的矢量/矩阵变体。这些类型的精度不同功耗也不同
- 整数数据类型 :
- 整数(int 数据类型)通常用作&#34;循环计数器&#34;或&#34;数组索引&#34;
- 为此,它们通常可以在各种平台上正常工作。根据平台的不同,GPU 可能不支持整数类型
- Direct3D 9 和 OpenGL ES 2.0 GPU 仅对&#34;浮点数据&#34;进行运算,并且可以使用相当复杂的浮点数学指令来模拟简单的整数表达式(涉及位运算或逻辑运算)
- Direct3D 11、OpenGL ES 3、Metal 和其他现代平台都对整数数据类型有适当的支持,因此使用位移位和位屏蔽可以按预期工作
- 复合矢量/矩阵类型

HLSL 具有从基本类型创建的&quot;内置矢量&quot;和&quot;矩阵类型&quot;

通常按照此方式在HLSL 代码中声明纹理。对于移动平台,这些转换为“低精度采样器”,即纹理中预期具有低精度数据

更改整个Unity项目的默认采样精度


PC 端
- 仅当目标平台是移动端 GPU 时
- half 和 fixed 类型才变得重要,在这种情况下,这些类型主要面临功耗(有时候是性能)约束。要确认是否遇到精度/数值问题,必须在移动设备上测试“着色器“
- 即使在移动端 GPU 上,不同的精度支持也会因 GPU 产品系列而异。下面概述了个每个移动端 GPU 产品系列如何处理每个浮点类型(以用于该产品系列的位数来表示)
- 大多数现代移动端 GPU
- 实际上只支持 32 位数字(用于 float 类型)或 16 位数字(用于 half 和 fixed 类型)。一些较旧的 GPU 对顶点着色器和片元着色器计算具有不同的精度
- 使用较低的精度通常可以更快,这可能是由于改进的 GPU 寄存器分配,或是由于某些低精度数学运算的特殊“快速路径”执行单元
- 即使没有原始性能优势,使用较低的精度通常也会降低 GPU 的功耗,从而延长电池续航时间
- 一般的经验法则
- 全部都从半精度开始(但位置和纹理坐标除外)。仅当半精度对于计算的某些部分不足时,才增加精度
- 使用采样器状态
耦合的纹理和采样器
- 大多数情况下,当在着色器中采样纹理时,纹理采样状态应该来自纹理设置

本质上,纹理和采样器是耦合在一起的。这是使用“dx9“风格的着色器语法时的默认行为
- 使用 HLSL 关键字 sampler2D、sampler3D 和 samplerCUBE 可声明纹理和采样器,但在较旧的图形 API (OpenGL ES) 中,这是唯一受支持的选项
单独的纹理和采样器
- 许多图形&#34;api&#34;和&#34;gpu&#34;允许使用比纹理更少的采样器,而耦合的纹理+采样器语法可能不允许编写更复杂的着色器
- 如,Direct3D 11允许在单个着色器中使用多达128个纹理,但只有最多16个采样器
- Unity 允许使用 DX11 风格的 HLSL 语法来声明纹理和采样器,但需要通过一个特殊的命名约定来让它们匹配:名称为“sampler”+TextureName 格式的采样器将从该纹理中获取采样状态

之前着色器代码片段可以用 DX11 风格的 HLSL 语法重写,并且也会执行相同的操作:
- 然而,通过这种方式,着色器可以被编写为“重用”其他纹理的采样,同时对多个纹理进行采样

三个纹理被采样,但是所有纹理都只用了一个采样器:
- Unity 提供了一些着色器宏帮助您使用这种“单独采样器”方法来声明和采样纹理
内联采样器状态
- 除了能识别名为“sampler”+TextureName 的 HLSL SamplerState 对象

Unity 还能识别采样器名称中的某些其他模式。这对于直接在着色器中声明简单硬编码采样状态很有用
- 名称 “my_point_clamp_sampler”将被识别为应该使用点(距离最近)纹理过滤和钳制纹理包裹模式的采样器。
- 采样器名称被识别为“内联”采样器状态(全都不区分大小写)
设置纹理过滤模式
- “Point”、“Linear”或“Trilinear”
设置纹理包裹模式
- “Clamp”、“Repeat”、“Mirror”或“MirrorOnce”
可根据每个轴 (UVW) 来指定包裹模式,例如&#34;ClampU_RepeatV&#34;。
- *“Compare”(可选)设置用于深度比较的采样器;与 HLSL SamplerComparisonState 类型和 SampleCmp/SampleCmpLevelZero 函数配合使用。
- “AnisoX” (where X can be 2/4/8 or 16, for example, Ansio8) 可以添加请求各向异性过滤。
以下是分别使用 sampler_linear_repeat 和 sampler_point_repeat 采样器状态进行纹理采样的示例,说明了如何通过名称控制过滤模式:

- 下面是一个分别具有SmpClampPoint、SmpRepeatPoint、SmpMirrorPoint、SmpMirrorOncePoint、Smp_ClampU_RepeatV_Point SamplerStates的示例,说明名称如何控制包装模式。在最后一个示例中,为水平(U)轴和垂直(V)轴设置了不同的缠绕模式。在所有情况下,纹理坐标都从-2.0到+2.0

- 就像单独的纹理+采样语法一样,内联采样状态在一些平台上不支持。目前它们是在Direct3D 11/12和Metal上实现的。
- 请注意,“MirrorOnce”纹理包装模式在大多数移动gpu / api上都不支持,当不支持时将回退到镜像模式。
- 请注意,“各向异性”过滤模式是基于平台功能和选定API所做的努力。实际值将基于最大支持的各向异性水平进行限制(包括在不支持各向异性过滤的情况下禁用)。
编写表面着色器
理解着色器性能
- 移动平台和低端pc上的GPU性能可能比你的开发机器上的GPU性能低得多。建议您手动优化着色器以减少计算和纹理读取,以便在低端GPU机器上获得良好的性能。例如,一些内置着色器对象具有“移动”等效的速度快得多,但有一些限制或近似值。
仅执行所需的计算
- 着色器代码需要执行的计算和处理越多,它对游戏性能的影响就越大。例如,支持每种材质的颜色可以使着色器更加灵活,但如果始终将该颜色设置为白色,则会对屏幕上渲染的每个顶点或像素执行无用的计算。
- 计算的频率也会影响游戏的性能。通常,与顶点数(顶点着色器执行次数)相比,渲染的像素数会更多(因此像素着色器执行次数也更多),而渲染的顶点数比渲染的对象更多。在可能的情况下,可将计算从像素着色器代码移动到顶点着色器代码中,或者将它们完全移出着色器并在脚本中设置值。
计算的精度
用 Cg/HLSL 编写着色器时,有三种基本数字类型:float、half 和 fixed
为了获得良好的性能,总是尽可能使用最低的精度。这在低端硬件上尤其重要。好的经验法则是:
- 对于posWS 和 uv,请使用 float 精度
- 对于所有其他情况(矢量、HDR 颜色等),请首先尝试 half 精度。仅在必要的情况下再提高精度。
- 要对纹理数据进行非常简单的运算,请使用 fixed 精度。
实际上,具体应该使用哪种数字类型取决于平台和 GPU。一般来说:
- 所有新款的桌面端 GPU 将始终以完整 float 精度进行所有计算,因此 float/half/fixed 最终产生完全相同的结果。这可能会使测试变得困难,因为更难以确定 half/fixed 精度是否真正够用,因此请始终在目标设备上测试着色器以获得准确的结果。
- 移动端 GPU 实际支持 half 精度。这种精度通常速度更快,并且使用更少的性能来执行计算。
- Fixed 精度通常仅对于较旧的移动端 GPU 有用。大部分新款 GPU(可运行 OpenGL ES 3 或 Metal 的 GPU)在内部以相同方式来处理 fixed 和 half 精度
复杂数学运算
- 超越数学函数(如pow, exp, log, cos, sin, tan)是相当消耗资源的,所以尽量避免在低端硬件上使用它们。如果可以的话,考虑使用查找纹理作为复杂数学计算的替代方案
- 避免编写自己的运算(如normalize、dot、inversesqrt)。Unity 的内置选项确保驱动程序可以生成好得多的代码。请记住,Alpha 测试 (discard) 运算通常会使片元着色器变慢
优化的表面着色器
表面着色器非常适合编写与光照交互的着色器。但是,它们的默认选项已调整为涵盖大量的一般情况。可针对特定情况调整这些选项以使着色器运行速度更快,或至少让着色器变得更小巧:
- 使用视图方向(即镜面反射)的着色器的 approxview 指令使视图方向按照顶点(而不是按像素)进行标准化。这是近似值,但通常足够好。
- 适用于镜面反射着色器类型的 halfasview 速度更快。半矢量(光照方向和视图矢量之间)按照顶点进行计算和标准化,并且光照函数接受半矢量作为参数,而不是视图矢量。
- noforwardadd 使着色器仅完全支持前向渲染中的单方向光。其余的光源仍然可提供每顶点光源或球谐函数光源的效果。这样可以使着色器更小并确保它始终在一个通道中渲染,即使存在多个光源也是如此。
- noambient 在着色器中禁用环境光照和球谐函数光源。这样可以稍稍提高性能。
Alpha 测试
固定函数 AlphaTest(或者其可编程的等效函数 clip())在不同平台上具有不同的性能特征:
- 通常,在使用该函数来移除大多数平台上的完全透明像素时,可获得少量优势。
- 但是,在 iOS 和某些 Android 设备的 PowerVR GPU 上,Alpha 测试是资源密集型任务。不要试图在这些平台上使用这种测试进行性能优化,因为它会导致游戏运行速度比平常慢
颜色遮罩 (Color Mask)
- 在某些平台(主要是 iOS 和 Android 设备的移动端 GPU)上,使用ColorMask省略一些通道(例如ColorMask RGB)可能是资源密集型的操作,所以除非绝对需要,否则请不要使用
使用 Visual Studio 调试着色器
使用 Visual Studio 调试着色器 - Unity 手册
- 在使用 DirectX 11 或 12 的 Windows 平台上,可以使用 Visual Studio 调试 Unity 应用程序中的着色器。本页面包含有关如何执行此操作的信息。
- 注意:如果使用的是 DirectX 12,Microsoft 建议使用 PIX 而不是 Visual Studio 来调试着色器
准备着色器
要调试着色器,必须包含调试符号进行编译。为此,需要在每个要调试的着色器的源代码中插入 #pragma enable_d3d11_debug_symbols 指令。
警告:此 pragma 指令会对性能产生负面影响。在进行最终构建之前,应将其从着色器代码中删除
为 Windows 独立平台创建占位符 Visual Studio 项目
如果为 Windows 独立平台构建应用程序,必须创建占位符 Visual Studio 项目。如果为通用 Windows 平台构建应用程序,Unity 会生成一个 Visual Studio 项目。
1.启动 Visual Studio
2.转到 File > New > Project > Visual C++ > Empty Project。 3.选择 Project > Properties > Configuration Properties > Debugging 4.在 Command 字段中,将 $(TargetPath) 替换为 Windows 独立平台应用程序(例如 C:\MyApp\MyApp.exe) 5.如果要强制项目在 DirectX 11 下运行,请选择 Command Arguments 并键入 -force-d3d11。
使用 PIX 来调试 DirectX 12 着色器
使用 PIX 来调试 DirectX 12 着色器 - Unity 手册
- PIX 是 Microsoft 为 Windows 开发人员提供的性能调优和调试工具。此工具提供了一系列用于分析应用程序性能的模式,并包括从应用程序中捕获 DirectX 项目的帧以进行调试的功能。
- 使用 PIX 可调查 Windows 64位 (x86_64) 独立平台或通用 Windows 平台应用程序中的问题。
- 要安装 PIX,请下载并运行 Microsoft PIX 安装程序,按照说明进行操作。
- 有关 PIX 的更多信息,请参阅 Microsoft 的 PIX 说明 (Introduction) 和 PIX 文档 (Documentation)。
使用 PIX 来调试 DirectX 着色器
- 应使用已构建的 Unity 应用程序版本来捕获帧,而不是使用 Unity Editor 中运行的版本。这是因为需要从 PIX 中启动目标应用程序来才能捕获 GPU 帧。
- 使用开发版可为 PIX 添加额外的信息,从而使得场景捕获操作更加容易。
使用支持调试的着色器来创建项目
- 要在 PIX 中使用源代码调试着色器,必须将以下 pragma 插入到着色器代码中:#pragma enable_d3d11_debug_symbols
示例
创建一个基本项目:
- 1. Create a new Unity project (see the Hub documentation on Projects).
- 2. 在顶部菜单中,选择 Assets > Create > Shader > Standard Surface Shader。此时将在 Project 文件夹中创建一个新的着色器文件。
- 3. 选择着色器文件,然后在 Inspector 窗口中单击 Open。此时将在脚本编辑器中打开着色器文件。将 #pragma enable_d3d11_debug_symbols 插入到着色器代码中的其他 #pragma 行下面。
- 4. 创建新的材质(菜单:__Assets__ > Create > __Material__)。
- 5. 在 Material Inspector 窗口中,选择 Shader 下拉选单,选择 __Custom__,然后选择刚创建的着色器。
- 6. 创建 3D 立方体游戏对象(菜单:__GameObject__ > 3D Object > __Cube__)。
- 7. 将新材质分配给新的游戏对象。要执行此操作,请将材质从 Project 窗口拖到 3D 立方体上。
从 Windows 独立平台应用程序中捕获帧:
File -->Build Settings,并在平台下,选择Windows、Mac、Linux。将目标平台设置为Windows,将架构设置为Intel 64位,并单击Development Build复选框。
- 2.单击 Build。
- 3.启动 PIX。
- 4.单击 Home__,然后单击 Connect__。
- 5.选择计算机 localhost 以使用您的 PC 进行捕获,然后单击 Connect。
- 6.在 Select Target Process 对话框中,选择 Launch Win32 选项卡,然后使用 Browse 按钮选择应用程序的可执行文件。注意,这里的“Win32”表示非 UWP 应用程序;您的应用程序文件必须是 64 位二进制文件。
- 7.启用 Launch for GPU Capture__,然后使用 Launch__ 按钮启动应用程序。
|
|