185 lines
9.8 KiB
Markdown
185 lines
9.8 KiB
Markdown
|
## 建立自己的管线Shader以及ShaderLibrary
|
|||
|
|
|||
|
## Shader宏分支控制
|
|||
|
### multi_complie
|
|||
|
常用的两种做法:
|
|||
|
1. 使用multi_complie或shader_feature来定义宏,根据不同的宏指令编译出多套Shader,Unity内建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个variant(RedPink,RedYellow,GreenPink,GreenYellow,BluePink,BlueYellow),因为他们之间会两两组合。
|
|||
|
|
|||
|
### 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 Functions:UniversalFragmentPBR、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_ObjectToWorld,unity_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。
|