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

9.8 KiB
Raw Permalink Blame History

建立自己的管线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因为你定义了三个宏

#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模型函数。比如PerceptualSmoothnessToPerceptualRoughnessPerceptualRoughnessToRoughness

#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中声明。

cbuffer UntiyPreMaterial
{
float4 _BaseColor;
}

CBUFFER_START(UntiyPreMaterial)
    float4 _BaseColor;
CBUFFER_END

之后在管线的构造函数中添加设置:

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来访问材质中的属性数据。
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中将enableDynamicBatchingenableInstancing设置为true。
  2. CustomRenderPipelineGraphicsSettings.useScriptableRenderPipelineBatching = useSPRBatcher

给RP添加变量控制

大致步骤:

  • CameraRenderer中给DrawVisibleGeometryRender添加useDynamicBatchinguseGPUInstancing形参,这两变量将用来设置DrawVisibleGeometry中的enableuseDynamicBatchingenableInstancing
  • CustomRenderPipeline中添加useDynamicBatchinguseGPUInstancing变量,并且给构造函数添加useSRPBatcheruseDynamicBatchinguseGPUInstancing形参,用于修改上述变量以及设置是否启用SRPBatcher,并且给Render中渲染函数添加上述形参。
  • CustomRenderPipelineAsset添加useSRPBatcheruseDynamicBatchinguseGPUInstancing变量,并修改对应函数的形参。

在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]

添加纹理

  1. Properties中添加 _BaseMap("Texture",2D)="white" {}
  2. 因为要支持GPUInstancing的关系代码比较复杂后续步骤见git。