9.8 KiB
建立自己的管线Shader以及ShaderLibrary
Shader宏分支控制
multi_complie
常用的两种做法:
- 使用multi_complie或shader_feature来定义宏,根据不同的宏指令编译出多套Shader,Unity内建shader大体也是这么做的。
- 有外部传入参数,在shader内部if判断,选择执行哪部分运算。 因为在shader种使用if、for很影响效率,所以第二种方法使用较少,用于case较少的时候。
这两个宏一般与Shader.EnableKeyword("宏名");
与Shader.DisableKeyword("宏名");
一起使用。
它会无脑的进行组合编译,如果宏指令太多,会产生非常多的variant。
#pragma multi_compile Red Green Blue
会产生三个variant,因为你定义了三个宏
#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
#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中声明。
cbuffer UntiyPreMaterial
{
float4 _BaseColor;
}
CBUFFER_START(UntiyPreMaterial)
float4 _BaseColor;
CBUFFER_END
之后在管线的构造函数中添加设置:
public CustomRenderPipeline()
{
GraphicsSettings.useScriptableRenderPipelineBatching = true;
}
GPU Instancing
大致步骤:
- 在Shader文件中添加
#pragma multi_compile_instancing
,这将使Unity生成我们的着色器的两个变体,一个具有GPU实例化支持,一个不具有GPU实例化支持。材质检查器中还出现了一个切换选项,使我们可以选择每种材质要使用的版本。 - 支持GPU实例化需要更改方法,还需要包含
UnityInstancing.hlsl
,作用是重新定义这些宏来访问实例数据数组。但是要进行这项工作,需要知道当前正在渲染的对象的索引。索引是通过顶点数据提供的,因此需要使其可用。UnityInstancing.hlsl定义了宏来简化此过程,但是它假定顶点函数具有struct参数。 - 声明一个用于传递定点数据的结构体;使用GPU实例化时,对象索引也可用作顶点属性。我们可以在适当的时候通过简单地将
UNITY_VERTEX_INPUT_INSTANCE_ID
放在属性中来添加它。 - 在
VertexShader
中添加添加UNITY_SETUP_INSTANCE_ID(input)
来提取实例的顶点索引数据,这足以使GPU实例化进行工作了。 - 因为SRP批处理程序拥有优先权,所以还需要使用
UNITY_INSTANCING_BUFFER_START
替换CBUFFER_START
以及用UNITY_INSTANCING_BUFFER_END
替换CBUFFER_END
,再将内部属性使用UNITY_DEFINE_INSTANCED_PROP
进行包裹。 - 为了将顶点索引实例传递至
PixelShader
,还需要再创建一个结构体并添加UNITY_VERTEX_INPUT_INSTANCE_ID。 - 最后在
PixelShader
中添加UNITY_SETUP_INSTANCE_ID(input)
来访问实例。使用UNITY_ACCESS_INSTANCED_PROP
来访问材质中的属性数据。
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不关心这个,因此使用起来不用过多担心。
大致步骤:
- 在
CameraRenderer.DrawVisibleGeometry
中将enableDynamicBatching
与enableInstancing
设置为true。 - 在
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
中
[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
中添加
Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]
添加纹理
- 在
Properties
中添加_BaseMap("Texture",2D)="white" {}
- 因为要支持GPUInstancing的关系代码比较复杂,后续步骤见git。