Custom SRP - Draw Calls
该教程部分完成的工程状态可见:Draw Calls
#Shaders
为了让 GPU 绘制物体,CPU 必须告诉 GPU 绘制什么以及如何绘制,绘制的内容通常是一系列网格,绘制方式则由 Shaders 来定义。
#Unlit Shader
首先要定义的最简单的 Shader 类型就是 Unlit Shader,这是一系列不考虑任何光照影响的 Shader。
创建一个 Shader 并将其删减到最少的结构,如下所示,其中 Properties 为会在 Inspector 面板中显示的属性, SubShader 中定义的 Pass 表示一种渲染的方式,一个 SubShader 中可以有多个 Pass :
1 | Shader "Custom RP/Unlit" |
如果使用这个 Shader 创建一个材质,则该材质会默认的渲染白色,如下所示:
![]() | ![]() | ![]() |
#HLSL Programs
SRP 中通常用来书写 Shader 的语言是 HLSL(High-Level Shading Language) 。所有 HLSL 语言需要写在 Pass 中,且必须在 HLSLPROGRAM 和 ENDHLSL 中,如下所示:
1 | Pass |
Unity 定义的 .shader 文件属于 ShaderLab 。ShaderLab 中可以使用不同的语言来写,如 CG 和 HLSL 。HLSL 可以同时被用在 DRP,URP,HDRP 和自定义 SRP 中。 CG 仅可以被用在 DRP 和自定义 SRP 中。
因此为了保证与 Unity 的 RP 的统一性,在自定义 SRP 中建议使用 HLSL 。
在 Shader 中需要指定顶点着色器和片元着色器的函数名称,如下所示, UnlitPassVertex 和 UnlitPassFragment 分别为两者的名称:
1 | Pass |
但此时 Shader 中并没有 UnlitPassVertex 和 UnlitPassFragment 的具体实现,因此会产生编译错误。
可以直接在 HLSLPROGRAM 和 ENDHLSL 间定义上述函数的实现,也可以选择将函数的实现放在 .hlsl 文件中,并 include 到 .shader 文件中,如下所示:
1 | Pass |
HLSL 和 C++ 的 include 逻辑类似,即直接将被 include 的文件的所有内容拷贝到 include 语句所在地。
#Include Guard && Shader Functions
.hlsl 文件不能通过 Unity 直接创建,但在文件浏览器中创建后,可以在 Unity 中直接查看,如下所示:
一个可通过编译的 hlsl 文件如下所示:
1 |
|
其中 #ifndef 等宏编译是为了避免, .hlsl 文件被多次时 include 时产生重定义,并导致编译错误。
UnlitPassVertex 和 UnlitPassFragment 为需要的顶点着色器和片段着色器函数。
在函数声明后的 SV_POSITION 和 SV_TARGET 为 semantics ,它告诉了编译器函数的返回值的具体含义。
其中 SV_TARGET 表示渲染对象的颜色, SV_POSITION 表示在其次裁剪空间的位置。
如果没有定义 semantics ,则会导致编译失败。
更多关于 HLSL 的内容,可见 High-Level Shading Language
其中的 semantics 可见 Semantics
Unity 官方也有相关的教程 Shader semantics
此时通过该 Shader 并不能渲染出任何物体,因为在顶点着色器中直接返回了 0.0 表示,即所有物体渲染的结果都会集中在屏幕正中间的一个像素上,所以不可见。
#Space Transformation
为了让物体可以正常的被渲染,需要将传入的顶点数据通过顶点着色器进行 MVP 矩阵的转换,如下所示:
1 |
|
传入的 positionOS 参数后的 POSITION 也是 semantics,表示传入的数据是表示位置的。
POSITION 和 SV_POSITION 的差异可见 Half-Pixel Offset
其中的 TransformObjectToWorld 和 TransformWorldToHClip 为自定义的坐标系转换的函数,如下所示:
1 | // In ShaderLibrary/Common.hlsl |
两个函数中用到了两个矩阵 unity_ObjectToWorld 和 unity_MatrixVP ,定义如下所示:
1 | // In ShaderLibrary/UnityInput.hlsl |
在 HLSL 中直接定义的变量即为 Uniform 变量,上述两个变量的命名与 Unity 内置着色器的变量名相同,因此 Unity 可以找到这两个 Uniform 变量并为其赋值。
UnityInput.hlsl 和 Common.hlsl 为新增的 .hlsl 文件,并放置在 ShaderLibrary 文件夹中,前者是为了封装 Unity 内置 Uniform 变量输入,后者是为了封装一些常用的函数。即此时文件结构为:
此时的结果如下所示,即可以看到一个正确的小球,因为我们已经完成了对于顶点着色器的设置的,但小球仍然是黑色的,因为我们尚未对片段着色器做设置:
#Core RP Library
像上述的 TransformObjectToWorld 和 TransformWorldToHClip 是非常通用的函数,Unity 提供了 包 Core RP Pipeline 封装了这些函数的实现。可通过 Unity Package Manager 并选择 Core RP Library 进行安装:
当导入了 Core RP Pipeline 后,相关的源码可以在 <ProjectPath>Library\\PackageCache\\com.unity.render-pipelines.core@<version>\\ 中查看
因此可以使用 Core RP Pipeline 的 SpaceTransform.hlsl 中 的内容替代自己实现的版本。
1 | // In Common.hlsl |
因为 Core RP Pipeline 中使用的变量与 Unity 中使用的 Shader 参数命名不相同,因此需要通过 define 将两者进行转换。如 unity_ObjectToWorld 变量,在 SpaceTransform.hlsl 中对应使用的变量为 Unity_MATRIX_M ,因此需要将 unity_ObjectToWorld 转换为 Unity_MATRIX_M,以保证 SpaceTransform.hlsl 可以正常工作,转换语句为:
1 |
与 UNITY_MATRIX_M 一样,SpaceTransform.hlsl 中还有许多变量需要相同的处理,如下的代码将所有这些代码与 Unity 内置的 Shader 参数命名对应在一起:
1 |
所有需要用到的参数,都需要在 UnityInput.hlsl 中定义,即最终的 UnityInput.hlsl 如下所示:
1 | float4x4 unity_ObjectToWorld; |
其中的 real4 是一个根据平台定义的参数,根据不同的平台,它可能被定义为 half4 或 float4 。 real4 的定义在 Core RP Pipeline 的 Common.hlsl 中。
在 UnityInput.hlsl 中定义的变量相当于是声明,这些变量都是在 Unity Default Shader 中可以找寻到的,Unity 会负责将这些变量的值传递给 Shader。Common.hlsl 中则是通过 define,将 Unity Default Shader 中的变量名与 Core RP Pipeline 中函数所依赖的变量名对应起来。
以定义在 Core RP Pipeline 中的 SpaceTransforms.hlsl 的函数 GetObjectToWorldMatrix 为例:
1 | float4x4 GetObjectToWorldMatrix() |
它依赖变量 UNITY_MATRIX_M ,而 UNITY_MATRIX_M 正是通过我们在 Common.hlsl 中定义的 #define UNITY_MATRIX_M unity_ObjectToWorld 而得到的。unity_ObjectToWorld 又进一步是因为我们在 UnityInput.hlsl 中声明了float4x4 unity_ObjectToWorld,才能正确获取到 unity_ObjectToWorld 的值。
因此,最终自定义的 Common.hlsl 如下所示:
1 |
上述引入的相互依赖关系如下:
- 先引入
Core RP Pipeline的Common.hlsl文件,保证real4变量被定义。 - 引入自定义的
UnityInput.hlsl文件,保证需要的变量被定义,且用 Unity 定义的着色器变量的名称。 - 使用一系列
define语句,将 Unity 引擎中定义的变量与Core RP Pipeline中需要用的变量联系在一起。 - 引入
Core RP Pipeline的SpaceTransforms.hlsl,其中的TransformObjectToWorld和TransformWorldToHClip即为需要的函数。
此时可以看到和之前一样被绘制出来的黑色小球:
#Color
在 UnlitPass.hlsl 中新增变量 _BaseColor 并将该颜色作为像素输出的颜色,如下所示:
1 | float4 m_BaseColor; |
为了让该变量可以在 Unity 的 Material 面板中展现出来,需要在 .shader 文件的 Properties 中加入,如下所示:
1 | Properties |
其中的 m_BaseColor 对应在 UnlitPass.hlsl 中定义的变量, "Color" 为最终在 Inspector 面板中显示的名称, Color 为变量的类型,(1.0, 1.0, 1.0, 1.0) 为变量的初始值。
即在 Properties 中定义的变量,格式为:
1 | <Target Parameter>("<DisplayName>", <Type>) = <default Value> |
#Batch
使用上述着色器,生成四个颜色不同的材质,如下所示:



在场景内添加 76 个小球,并用上述的材质随机给小球添加,效果如下所示:

在 Frame Debugger 中可以看到此时一共需要用到 78 个 DrawCall ,其中 76 个绘制小球,一个绘制天空盒,一个用来 Clear。如下所示
但如果 Game 窗口的 Statistic 界面中,只能看到 77 个 Batches ,这是因为 Batches 的计算无视了 Clear 。
#SRP Batcher
Batching 是将多个 Draw Call 结合在一起的过程。在 SRP 中最简单使用 Batching 的方法就是激活 SRP Batcher 功能,但这功能仅能在兼容的 Shader 中开启,上述自定义的 Unlit Shader 还不支持此功能,如下所示:
SRP Batcher 本质上并没有减少 Draw Call 的数量,它只是将一些材质的 Uniform 数据缓存在 GPU 上,让 CPU 不需要每帧都去设置。这样同时减少了 CPU 处理数据的时间以及 CPU 向 GPU 传输的数据量。
所有可以被 SRP Batcher 缓存在 GPU 的 Uniform 数据都必须定义在一块地址不变的内存中,在 SRP 中可以通过将数据包裹在 cbuffer(Shader Constants Buffer) 定义的数据块中。
针对我们之前定义的 UnlitPass.hlsl Shader 就需要将 m_BaseColor 变量用 cbuffer 包裹,如下所示:
1 | // In UnlitPass.hlsl |
SRP Batcher 要求自定义的数据类型必须要放在名为 UnityPerMaterial 的数据块中,所有 Unity 内置的数据类型要放在名为 UnityPerDraw 的数据库中 [^2]。
但 cbuffer 并不是在所有的平台下都支持,如 OpenGL ES 2.0 就不支持,所以为了保证兼容性,可以可以使用如下的方式进行替代:
1 | // In UnlitPass.hlsl |
同理,还需要将一些坐标转换的数据也放到 cbuffer 中,如下所示,其中的 unity_LODFADE 虽然没用到,但同样必须包裹在 Cbuffer 中:
1 | // In UnityInput.hlsl |
当定义完后,Shader 就变为兼容 SRP Batcher ,如下所示:
此时在自定义的渲染管线中,开启 SRP Batcher 即可,可以在自定义渲染管线构造时直接启用,如下所示:
1 | // In CustomRendererPipeline.cs |
此时在 Game 界面的 Statistic 窗口查看,可以看到仍然显示 77 个 Batches,而 Saved by batching 则变成了 个,如下所示:
之所以 Saved by batching 出现负数是因为 Unity 的 Statistic 窗口对于 SRP 存在 Bug,因此更好的选择是通过 Frame Debugger 查看,如下所示可以看到仅有一个 Batch:
上图中 Draw Calls 仍然是 76,因为 SRP Batcher 并未合并 DrawCall,只是在 GPU 缓存了数据,减少了数据的传输和准备时间。
SRP Batcher 的实现原理中,真正关心的是材质的 GPU 的内存的分布是否相同。因此不同的材质只要使用相同的着色器时,他们的 UnityPerMaterial 的内存分布都是相同的,因此可以被合并。
如在上述的例子中,虽然使用了四种不同的材质来绘制小球,但最终所有的都被合并到一个 Batch 中,这是因为这四个材质实际上都是使用同一个 Shader。
Unity 实际上判断的是着色器是否相同。因此如果两个不同的着色器定义了相同的 UnityPerMaterial 内存,仍然是没法被合并的。
#Many Colors
在开发过程中,如果有更多的小球需要有更多不同的颜色,为每种颜色都创建一个材质是不现实的。因此需要在运行时去修改已有的材质。
如下定义了脚本 PerObjectMaterialProperties,该脚本通过 m_BaseColor 去修改 MaterialPropertyBlock达到修改材质颜色的目标 :
1 | using UnityEngine; |
关于 MaterialPropertyBlock.SetXXX 和 Material.SetXXX 的区别见 Material Property Blocks 。概括而言 MaterialPropertyBlock 保证了材质不会被拷贝,虽然每个物体都设置了自己的 Material Property,但它们仍然使用的是一个材质。
因为小球的颜色实际上上是由 PerObjectMaterialProperties 通过 Material Property 决定的,所有此时所有小球使用同一个材质仍然可以拥有不同的颜色。
此时的效果为:
SRP Batch 并无法处理运行时的 Per-Object 的 Material Property,因此此时查看 Frame Debugger,会发现所有的小球都是单独的 DrawCall 进行绘制的:

针对这种 Per-Object 的 Material Property 修改情况,可以使用 GPU Instancing 进行处理。
#GPU Instancing
对于同一个材质,但是因为使用了 Material Property Blocks 而打断 Batch 的情况,可以使用 GPU Instancing 将它们合并为一个 DrawCall 进行渲染。 CPU 会将这些物体各自对于材质的修改组合成一个数组( Instanced Data)并一次性送给 GPU,GPU 在渲染它们时使用 index 进行区分。
OpenGL 中 GPU Instancing 的实现可见 Instancing
目前实现的 Shader 是不支持 GPU Instancing 的。为了让其支持 Instancing,首先需要加上 multi_compile_instancing 的关键字,如下所示:
1 | // Unlit.shader |
此时可以看到使用了该 Shader 的材质面板中出现了 Enable GPU Instancing 关键字,如下所示,带上该关键字后,Unity 在编译时会为 Shader 生成两份代码,一份支持 Instancing,一份不支持:
但勾选了选项后会发现使用了同一材质的物体并没有被合并为一个 Shader 进行渲染,这是因为 Unity 在编译时需要知道哪些数据需要被组合为 Instanced Data的,因此 Shader 具体的实现也需要对应的更改。
为了构建 Instanced Data,首先需要引入 Core RP Library 中的 UnityInstancing.hlsl ,该 Shader 封装了一系列 Instancing 相关的函数,如通过 Instancing 的 Index 去访问 Instanced Data。
1 | // Common.hlsl |
Unity 中整个支持 Instancing 的 Shader 的逻辑大致为,Instancing Index 在顶点着色器中被输入,经过转换传递给片段着色器,最终在片段着色器中根据 Index 获取到对应的数值。UnlitPass.hlsl 中的主体代码将修改为如下形式:
1 | // In UnlitPass.hlsl |
相较于之前 UnlitPass.hlsl 的代码,这里的改动主要在于:
- 使用
Attributes和Varying结构体封装顶点着色器和片段着色器的输入,且每个结构体中,都用UNITY_VERTEX_INPUT_INSTANCE_ID标记其中的相关数据是与 Instancing Index ID 绑定的。 - 使用
UNITY_INSTANCING_BUFFER_START和UNITY_INSTANCING_BUFFER_END包裹需要 Instanced 的数据 - 使用
UNITY_SETUP_INSTANCE_ID获取输入数据所对应的 instancing index id,使用UNITY_TRANSFER_INSTANCE_ID将输入的 instancing index id 拷贝给输出。 - 使用
UNITY_ACCESS_INSTANCED_PROP访问 Instanced Data
通常 UnityPerMaterial 的数据需要被标记为 Instanced Data 。 UnityPerDraw 的数据,如 unity_ObjectToWorld 是不需要被标记为 Instanced Data。
Instanced Data 同样兼容 SRP Batcher,两者并不是相互冲突的设置,即一个材质可以同时支持 SRP Batcher 和 GPU Instancing。
当 Shader 修改完成后,选择小球使用的 Material,并在其中选择 Enable GPU Instancing:
此时通过 Frame Debugger 可以看到之前所有的 DrawCall 都被一个 Instanced Draw 合并:
#Drawing Many Instanced Meshes
上一节中,已经可以让场景内多个使用相同材质,但被 MaterialPropertyBlock 修改的物体通过 GPU Instancing 一次性被渲染。
但如果要一次性生成大量的物体,如 1000 个小球,每个都需要有不同的颜色。此时继续通过 PerObjectMaterialProperties 进行修改的话,那就需要创建许多的 MonoBehaviour 实例,而这对于性能是不友善的。
针对这种需求,更普遍的做法是在代码中通过 Graphics.DrawMeshInstanced 进行绘制,如下所示:
1 | public class DrawMassiveMeshBall : MonoBehaviour |
Graphics.DrawMeshInstanced 所接纳的形参分别是绘制用的 Mesh,Mesh 中的 SubMeshIndex(这里为 0),绘制用的材质,表示绘制 Mesh 位姿的 Matrices 绘制的数量,以及所用的 Material Property Block。注意这里通过 MaterialPropertyBlock.SetVectorArray 将绘制小球所需要的 Colors 一次性设置给了 Material Property Block。
此时可以看到的效果为:
此时可以从 Frame Debugger 中看到绘制了 1023 个小球仅用了 3 个 DrawCall:

#Dynamic Batching
还有一种方法减少 DrawCall 的方法称为 Dynamic Batching,该方法将多个拥有相同材质小的 Mesh 动态结合为一个大的 Mesh,达到可以一次性渲染的目的。
Dynamic Batching 与 GPU Instancing 是互斥的,因此当需要用 Dynamic Batching 时,需要在 DrawSetting 中将 GPU Instancing 关闭,如下所示:
1 | // CameraRenderer.cs |
且 SRP Batcher 比 Dynamic Batching 也有更高的优先级,所以也需要将其关闭:
1 | // CustomRenderPipeline.cs |
对于可以被Dynamic Batching 的小 Mesh,Unity 也有一系列的限制,如:
- 顶点数必须在 300 以下,顶点数据(一个顶点可能有多个顶点数据)的数量必须在 900 以下
- 不能有镜像的大小,如一个物体的尺寸是 ,另一个物体的尺寸是 ,这两物体不会被 Batch 在一起。
- 不能拥有一样的材质
- 带有不同烘焙贴图参数的物体不能被 Batch 在一起
- 不能被
Multi-Pass的 Shader 打断
Dynamic Batching 还可能造成的一些Bug,如当物体有不同的 Scale 时,较大物体的法线不能保证为 Unit Vector。
因为 Unity 中的默认的 Sphere 物体,顶点数是 个,不满足上述条件 1,因此无法被 Dynamic Batching 在一起。而默认的 Cube 物体,顶点数为 个,满足条件,因此可使用 Cube 作为测试 Dynamic Batching 的物体。
如下为 76 Cube,使用了四种不同的材质,渲染的效果如下所示:
此时查看 Frame Debugger,可以看到 76 个 Cubes 使用了 7 个 DrawCall 完成了渲染:

通常情况下, GPU Instancing 是比 Dynamic Batching 更好的解决方法,因为少了很多限制,也不会产生 Bug。
当多个小 Mesh 使用了同一材质,但是用了 MaterialPropertyBlock 修改时, Dynamic Batching 也不生效。
#Configuring Batching
上述介绍了三种减少 DrawCall 的方法, SRP Batcher , GPU Instancing ,Dynamic Batching 。而 Dynamic Batching 与前两者互斥,在之前的代码中,是直接通过 Hard Code 来切换不同的特性,而理想上需要动态的根据所选择的减少 DrawCall 的方式去调整代码。
解决思路为创建 CustomRenderPipeline 时指定需要使用的特性,然后将选择一路传递给具体的 Renderer,Renderer 以此去调整参数,如下所示:
1 | // CustomRenderPipelineAsset.cs |
#Transparency
在材质中的 Render Queue 部分,可以看到有 Transparent 选项,如下所示,但这里的 Transparent 仅是修改物体的渲染顺序,而并不会改变物体的渲染特性。即此时将 Base Color 调整为半透明的,最终渲染的结果仍然是完全不透明的。
#Blend Modes
为了真正实现半透明效果,需要开启 Alpha Blending ,在 Unity 中通过 Blend [<SrcBlend>] [<DstBlend>] 语句切换 Alpha Blending。当 <SrcBlend> 为 1, <DstBlend> 为 0 时 Alpha Blending 关闭,其余情况为打开。
为了让 Unlit Shader 支持半透明,可将其修改为:
1 | // Unlit.shader |
此时新建一个 CustomUnlitTransparentYellow 材质,并按如下设置:
并用该材质替代原来使用 CustomUnlitYellow 材质的小球,此时的效果如下:

#Not Writing Depth
通常而言 Transparent 的物体渲染顺序为从远到近,也因此深度检测对 Transparent 在很多情况下是不产生效果的(从远到近渲染,新绘制的东西通常都会过深度测试)。因此可以选择在渲染 Transparent 时将深度缓冲的写入关闭,为了支持关闭深度检测,需要将 Shader 修改如下:
1 | // Unlit.shader |
此时的材质应当修改如下:
#Texturing
为了让小球半透明,还可以使用带有半透明信息的贴图,在这里可以自定义一个 Shader UnlitTransparentTexture.shader,在 Unlit.shader 的基础上为了让材质支持纹理采样,首先需要在 Shader 的 Properties 中增加:
1 | // UnlitTransparentTexture.shader |
其中 2D 表示为一张二维的纹理, White 表示默认值为 Unity 定义的白色纹理,最后的 {} 为早期 Unity 版本中对纹理的设置选项,目前已经废弃,但仍需要定义,避免一些奇怪的错误。
对纹理也需要定义特定的 Uniform 变量,变量的类型为 TEXTURE2D ,且需要额外增加一个 SAMPLER 类型的变量,作为控制纹理 Filter 和 Wrap 模式的采样器,我们将这些数据定义在新建的 UnlitTransparentTexturePass.hlsl 中,该文件以 UnlitPass.hlsl 作为基础,如下所示:
1 | // UnlitTransparentTexturePass.hlsl |
确保要将 UnlitTransparentTexture.shader 中 include 的 hlsl 修改为 UnlitTransparentTexturePass.hlsl。
SAMPLER 变量的命名应该与 TEXTURE2D 相同,只不过前面添加 sampler_ 字段。
此时创建的 Material,可以看到多了 Texture 的数据,除了纹理贴图的设置外,还有 Tiling 和 Offset 选项的设置,前者表示纹理 UV 的大小,后者表示 UV 的起始点。整体如下所示:
Tiling 和 Offset 为 Unity 为每个纹理定义的 Special Texture properties,需要在着色器中定义对应的 float4 变量才行。该变量的命名规则为 <TextureName>_ST ,如上述纹理命名为 m_BaseMap ,则需要定义 m_BaseMap_ST ,且该变量可以作为 instanced data。因此定义如下所示:
1 | // UnlitTransparentTexturePass.hlsl |
之后就是在顶点着色器的输入中加入 UV 信息,并在经过 Tiling 和 Offset 的调整后传入片段着色器中,片段着色器中根据调整后的 UV 通过 SAMPLE_TEXTURE2D 函数采样纹理。整个改动如下所示:
1 | // UnlitTransparentTexturePass.hlsl |
此时将原先场景中使用 CustomUnlitRed 材质的小球修改为使用 CustomUnlitTextureRed 材质,材质如下所示:
此时的场景效果如下:
在这个场景下,如果需要将 Batch 数降低到最低,应该首先开启 CustomRP 的 Use SRP Batcher,并让半透明的黄色小球的材质,也换为使用 UnlitTransparentTexture.shader 只不过材质为空,如下所示:
此时查看 Frame Debugger,可以发现所有的小球,使用了两个 Batch 就绘制完成了:
因为所有不透明的小球都是使用的一个 Shader Unlit.shader,而所有半透明的小球(无论使用了贴图与否)都使用了 UnlitTransparentTexture.shader。
#Alpha Clipping
Alpha Clipping 是将一些不满足要求的像素直接丢弃掉避免渲染的方法,在 Unity 中也被称为 Cutoff 。
我们在 UnlitTransparentTexture.shader 和 UnlitTransparentTexturePass.hlsl 的基础上定义 UnlitTransparentTextureCutoff.shader 和 UnlitTransparentTextureCutoffPass.hlsl 以支持 Cutoff 特性。
为使用 Alpha Clipping ,首先需要定义它丢弃的阈值,即 cutoff threshold ,该变量也可以放在 Unity Per Material 中作为 Instanced Data。在片段着色器中通过 clip 函数剔除不需要的像素,该函数接受一个 float 类型的形参,当形参值小于 0 时,该像素会被丢弃。整个流程如下所示:
1 | // UnlitTransparentTextureCutoff.shader |
此时效果如下所示:
上示例子中,同时使用了 Alpha Blending 和 Alpha Cutoff 两个技术,但通常而言,这两个技术并不会一起使用。 Alpha Blending 使用时通常不会写入深度信息,而 Alpha Cutoff 使用时通常会写入深度信息。
当关闭了 Alpha Blending 并只使用 Alpha Cutoff 时,效果如下所示:
在现代 GPU 中, Alpha Clipping 可能会打断 Early-Z 造成性能的下降,因此最好仅在需要的时候开启 Alpha Clipping 功能。
#Shader Features
现在实现的 UnlitTransparentTextureCutoff.shader 中,无论何种情况都会启用 Cutoff,而在某些情况下,我们会希望 Cutoff 被关闭。
Unity 的 Shader Features 功能可以根据 Shader 中 Toggle 的值增加或移除特定的宏,并根据宏调整 Shader 的编译。整体流程如下所示:
1 | // UnlitTransparentTextureCutoff.shader |
其中 #pragma shader_feature 会让 Unity 根据在 Toggle 中定义的宏,即 _CLIPPING ,编译出两份不同的代码,一份是定义了 _CLIPPING ,一份是不定义的。
即对于使用了 shader_feature 且进行了不同配置的 Shader 而言,其在运行时实际上是不同的两份 Shader,也因此 SRP Batcher 并不生效。
#Cutoff Per Object
在之前的 PerObjectMaterialProperties 脚本中可以加入 Cutoff 的设置,如下所示:
1 | // PerObjectMaterialProperties.cs |
结果如下:
#Ball of Alpha-Clipped Spheres
同理在使用 Instanced Drawing 时,也可以随机设置小球的透明度,并以此触发 Alpha Clipping 效果,如下所示:
1 | // InstancedDrawingMeshBall.cs |
结果如下:
#Reference
Draw Calls (catlikecoding.com)
ShaderLab: adding shader programs




