BlueRoseNote/07-Other/Unity/Unity通用渲染管线(URP)系列(二)——Draw Calls(Shaders&Batches).md
2023-06-29 11:55:02 +08:00

185 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 建立自己的管线Shader以及ShaderLibrary
## Shader宏分支控制
### multi_complie
常用的两种做法:
1. 使用multi_complie或shader_feature来定义宏根据不同的宏指令编译出多套ShaderUnity内建shader大体也是这么做的。
2. 有外部传入参数在shader内部if判断选择执行哪部分运算。
因为在shader种使用if、for很影响效率所以第二种方法使用较少用于case较少的时候。
这两个宏一般与`Shader.EnableKeyword("宏名");``Shader.DisableKeyword("宏名");`一起使用。
它会无脑的进行组合编译如果宏指令太多会产生非常多的variant。
`#pragma multi_compile Red Green Blue`
会产生三个variant因为你定义了三个宏
```c#
#pragma multi_compile Red Green Blue
#pragma multi_compile Pink Yellow
```
会产生6个variantRedPinkRedYellowGreenPinkGreenYellowBluePinkBlueYellow),因为他们之间会两两组合。
### shader_feature
该指令的效果和用法基本都与`multi_complie`一样,都是用来添加宏。同时它就是为了multi_compile打包时的爆炸编译的问题。
但如果是需要同时存在两种宏分支的功能就不适合用`shader_feature`了。
## URP ShaderLibrary
### render-pipelines.core
`Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl`中定义了若干空间转换函数,但因为没有定义宏,所以还需要在用之前定义缺少宏,相关矩阵变量使用`UnityInput.hlsl`进行定义。下面的宏声明可能会有bug最好手动复制错误信息中的变量。估计是字符集的问题
`Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl`包含了若干基础类型定义目前只用于定义real类型。
`Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl`为了实现GPUInstancing所需的库。
`Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl`实现了迪士尼BRDF模型函数。比如`PerceptualSmoothnessToPerceptualRoughness`、`PerceptualRoughnessToRoughness`
```c#
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "UnityInput.hlsl"
#define UNITY_MATRIX_M untiy_ObjectToWorld
#define UNITY_MATRIX_I_M untiy_WorldToObject
#define UNITY_MATRIX_V untiy_MatrixV
#define UNITY_MATRIX_VP unity_MatrixVP
#define UNITY_MATRIX_P glstate_matrix_projection
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl"
```
### render-pipelines.universal 7.3.1
Unity-Chan使用URP7.3.1
#### Core.hlsl
`Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl`
- 定义了顶点输入、顶点法线输入输入以及初始化函数。
- 定义`UNITY_Z_0_FAR_FROM_CLIPSPACE`宏。
- 返回`UnityInput.hlsl`中定义的`_WorldSpaceCameraPos`,以及`_ScaledScreenParams`。
- 一些常用函数
#### Lighting
`Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl`
实现了灯光相关逻辑(包括阴影)
- 灯光衰减函数
- 灯光数据结构体以及结构体数据填充与计算函数
- BRDF Functions
- Global Illumination主要是球谐结果
- 光照计算LightingLambert、LightingSpecular、LightingPhysicallyBased、VertexLighting
- Fragment FunctionsUniversalFragmentPBR、UniversalFragmentBlinnPhong、LightweightFragmentPBR
#### LitInput
`Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl`
- 定义了PBR所需的`CBUFFER`
- `SampleMetallicSpecGloss()`、`SampleOcclusion()`、`InitializeStandardLitSurfaceData()`
#### LitForwardPass
`Packages/com.unity.render-pipelines.universal/Shaders/LitForwardPass.hlsl`
URP前向管线具体实现文件。像素着色器最终调用`UniversalFragmentPBR()`来计算最后颜色。
## 减少Draw Call的方法
- SRP批处理器:批处理是组合Drawcall的过程可减少CPU和GPU之间的通信时间。最简单的方法是启用SRP batcher。SRP batcher不会减少Draw Call的数量而是使其更精简。它在GPU上缓存了材质属性因此不必在每次绘制调用时都将其发送出去。
- GPU Instancing:使用GPU Instancing可使用少量绘制调用一次绘制或渲染同一网格的多个副本。
### SRP批处理器
遇到错误`SRP Batcher Material property is found in another cbuffer`这个是因为const buffer的名称不正确造成的。
官方文档有如下一句话:
>For a Shader to be compatible with SRP:
All built-in engine properties must be declared in a single CBUFFER named “UnityPerDraw”. For example, unity_ObjectToWorld, or unity_SHAr.
All Material properties must be declared in a single CBUFFER named “UnityPerMaterial”.
翻译成白话来说Shader中所有的内置属性例如unity_ObjectToWorldunity_SHAr等都要在一个名为UnityPerDraw的CBUFFER中声明而所有的Material属性都要在一个名为UnityPerMaterial的CBUFFER中声明。
```c#
cbuffer UntiyPreMaterial
{
float4 _BaseColor;
}
CBUFFER_START(UntiyPreMaterial)
float4 _BaseColor;
CBUFFER_END
```
之后在管线的构造函数中添加设置:
```c#
public CustomRenderPipeline()
{
GraphicsSettings.useScriptableRenderPipelineBatching = true;
}
```
### GPU Instancing
大致步骤:
1. 在Shader文件中添加`#pragma multi_compile_instancing`,这将使Unity生成我们的着色器的两个变体一个具有GPU实例化支持一个不具有GPU实例化支持。材质检查器中还出现了一个切换选项使我们可以选择每种材质要使用的版本。
2. 支持GPU实例化需要更改方法还需要包含`UnityInstancing.hlsl`,作用是重新定义这些宏来访问实例数据数组。但是要进行这项工作需要知道当前正在渲染的对象的索引。索引是通过顶点数据提供的因此需要使其可用。UnityInstancing.hlsl定义了宏来简化此过程但是它假定顶点函数具有struct参数。
3. 声明一个用于传递定点数据的结构体使用GPU实例化时对象索引也可用作顶点属性。我们可以在适当的时候通过简单地将`UNITY_VERTEX_INPUT_INSTANCE_ID`放在属性中来添加它。
4. 在`VertexShader`中添加添加`UNITY_SETUP_INSTANCE_ID(input)`来提取实例的顶点索引数据这足以使GPU实例化进行工作了。
5. 因为SRP批处理程序拥有优先权所以还需要使用`UNITY_INSTANCING_BUFFER_START`替换`CBUFFER_START`以及用`UNITY_INSTANCING_BUFFER_END`替换`CBUFFER_END`,再将内部属性使用`UNITY_DEFINE_INSTANCED_PROP`进行包裹。
6. 为了将顶点索引实例传递至`PixelShader`,还需要再创建一个结构体并添加UNITY_VERTEX_INPUT_INSTANCE_ID。
7. 最后在`PixelShader`中添加`UNITY_SETUP_INSTANCE_ID(input)`来访问实例。使用`UNITY_ACCESS_INSTANCED_PROP`来访问材质中的属性数据。
```cg
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
UNITY_DEFINE_INSTANCED_PROP(float4,_BaseColor)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
struct Attributes
{
float3 positionOS : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 positionCS: SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
Varyings UnlitPassVertex(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input,output);
float3 positionWS = TransformObjectToWorld(input.positionOS);
output.positionCS=TransformWorldToHClip(positionWS);
return output;
}
float4 UnlitPassFragment(Varyings input) : SV_TARGET
{
UNITY_SETUP_INSTANCE_ID(input);
return UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial,_BaseColor);
}
```
### 动态合批
减少DC的第三种方法称为动态批处理。这是一种古老的技术它将共享相同材质的多个小网格合并为一个较大的网格而该网格被绘制。但如果使用逐对象材质属性per-object material properties会失效。 较大的网格一般按需生成,所以动态合批仅适用于较小的网格。球体还是太大了,但立方体可以使用。
一般来说GPU实例化优于动态批处理。该方法也有一些注意事项例如当涉及不同的比例时不能保证较大网格的法线向量为单位长度。此外绘制顺序也将更改因为它现在是单个网格而不是多个。 还有静态批处理它的工作原理类似但是会提前标记为静态批处理的对象。除了需要更多的内存和存储空间之外它没有任何注意事项。RP不关心这个因此使用起来不用过多担心。
大致步骤:
1. 在`CameraRenderer.DrawVisibleGeometry`中将`enableDynamicBatching`与`enableInstancing`设置为true。
2. 在`CustomRenderPipeline`将`GraphicsSettings.useScriptableRenderPipelineBatching = useSPRBatcher`。
### 给RP添加变量控制
大致步骤:
- `CameraRenderer`中给`DrawVisibleGeometry`与`Render`添加`useDynamicBatching`与`useGPUInstancing`形参,这两变量将用来设置`DrawVisibleGeometry`中的`enableuseDynamicBatching`与`enableInstancing`。
- `CustomRenderPipeline`中添加`useDynamicBatching`与`useGPUInstancing`变量,并且给构造函数添加`useSRPBatcher`、`useDynamicBatching`与`useGPUInstancing`形参,用于修改上述变量以及设置是否启用`SRPBatcher`,并且给`Render`中渲染函数添加上述形参。
- `CustomRenderPipelineAsset`添加`useSRPBatcher`、`useDynamicBatching`与`useGPUInstancing`变量,并修改对应函数的形参。
## 在Shader中添加渲染设置
在`Properties`中
```cg
[Enum(UnityEngine.Rendering.BlendedMode)]
_SrcBlend("Src Blend",Float)=1
[Enum(UnityEngine.Rendering.BlendedMode)]
_DstBlend("Src Blend",Float)=0
[Enum(Off,0,On,1)] _ZWrite ("Z Write",Float)=1
```
在`Pass`中添加
```cg
Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]
```
## 添加纹理
1. 在`Properties`中添加` _BaseMap("Texture",2D)="white" {}`
2. 因为要支持GPUInstancing的关系代码比较复杂后续步骤见git。