## 建立自己的管线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。