参考网址:
Unity MaterialPropertyBlock 正确用法(解决无法合批等问题)_unity_define_instanced_prop的变量无法srp合批-CSDN博客
URP | 基础CG和HLSL区别 - 哔哩哔哩 (bilibili.com)
【直播回放】Unity 批处理/GPU Instancing/SRP Batcher_哔哩哔哩_bilibili
合批分为四种:
静态合批
动态合批
gpu instance
srp batch
四个优先级排序:

按照出现的历史时间,可以大致这么排序:静态合批和动态合批较早;之后才有了gpu instance,srp batch
按照程序参与度来说:静态合批、动态合批、srp batch只需要勾选一下即可,而gpu instance则需要按照指定的约束编写shader,开启材质gpu instance功能。
从效率来说:静态合批是在build的时候,内存占用大,效率高;动态合批是在运行时的合批,消耗cpu;gpu instance是针对大量同mesh的物体的场景;而srp batch则是针对同shader的不同变体进行合批。
从优先级来说:srp batch》gpu instance
1)静态合批
静批条件:材质球相同+mesh可以不同
相同材质球、相同shader、勾选static、是在build的时候就合并为大的mesh。
mesh可以不同,在build成exe或者移动的可执行的文件的时候,就被合并成了一个大的mesh,在frame debugger中看不到合并结果,只有build之后,才能看到合并结果,通过render doc截帧才能看到。
在player settings的设置中,勾选静态合批。



2)动态合批
动批条件:材质球相同+mesh可以不同(但是必须顶点数小于300)
动态合批,在unity较早的版本,是可以在player settings中找到,并勾选,但是在新版本的unity中,urp的管线中,找不到这个勾选项了。而要在,管线配置中,勾选才行,默认是不勾选的。
默认是不勾选的原因是,dynamic batch是动态合批的,也就是在运行的时候,进行合并mesh操作。这个可能会对cpu有一定的损耗,故要权衡。


如上图所示,圆柱体和立方体,相同的材质球,相同的shader,不同的mesh可以动态合批。
但是动态合批有个特点是,当顶点数量超过300的时候,动批失败,比如将圆柱体改为胶囊体之后,动批失败。

3)gpu instance
instance条件:材质球相同+mesh相同
如下图所示,cube1和cube2是相同的mesh,相同的材质球才能被instance。
如果将cube2缓存是胶囊体,则和cube1不能被instance。
cube1和cube3无法instance,是因为他们使用的材质球不同,虽然shader相同。
shader更改为如下内容:
cs
Shader "Unlit/xxxx"
{
Properties
{
//_MainTex ("Texture", 2D) = "white" {}
[PerRendererData] _BaseColor("Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma multi_compile_instancing
#pragma enable_d3d11_debug_symbols
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
//float4 _BaseColor;
// 如果既要srp batch又要兼顾instance就得这么写
// UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
// UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
// UNITY_INSTANCING_BUFFER_END(Props)
struct appdata
{
float4 vertex : POSITION;
//float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
//float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
//如果只支持instance,可以这么写
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_INSTANCING_BUFFER_END(Props)
// sampler2D _MainTex;
// float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
//setup instance id
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
//calculate the position in clip space to render the object
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
float4 frag (v2f i) : SV_Target
{
//setup instance id
UNITY_SETUP_INSTANCE_ID(i);
//get _Color Property from buffer
fixed4 color = UNITY_ACCESS_INSTANCED_PROP(Props, _BaseColor);
//Return the color the Object is rendered in
return color;
}
ENDCG
}
}
}
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_INSTANCING_BUFFER_END(Props)
这个必须是UnityPerMaterial的常量buffer,否则不能同时支持srp batch功能。
加上:multi_compile_instancing
在顶点和片段中加上instance的宏处理才行。
从图上可以看出Cube1和Cube1_1被instance了,而另外两个cube因为深度的原因被instance的两个cube打断了。这里Cube1和Cube1_1使用的是相同的材质球,但是属性不一样。同时开启材质球的instace开关:
测试2使用的是:材质属性块
cs
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
public GameObject m_cube1;
public Color m_color;
private Material m_material1;
private MaterialPropertyBlock propertyBlock;
void Start()
{
if (propertyBlock == null)
propertyBlock = new MaterialPropertyBlock();
propertyBlock.SetColor("_BaseColor", m_color);
MeshRenderer meshRenderer = m_cube1.GetComponent<MeshRenderer>();
meshRenderer.SetPropertyBlock(propertyBlock);
}
}
这个脚本就是将黄色和蓝色的cube在Start中改变为黄色、蓝色,使用的是MaterialPropertyBlock这个方式。为啥要使用MaterialPropertyBlock呢?假如我们改为:
Instancing and Material Property Blocks | Ronja's tutorials (ronja-tutorials.com)
cs
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
public GameObject m_cube1;
public Color m_color;
private Material m_material1;
private MaterialPropertyBlock propertyBlock;
void Start()
{
if (propertyBlock == null)
propertyBlock = new MaterialPropertyBlock();
propertyBlock.SetColor("_BaseColor", m_color);
MeshRenderer meshRenderer = m_cube1.GetComponent<MeshRenderer>();
//meshRenderer.SetPropertyBlock(propertyBlock);
meshRenderer.material.SetColor("_BaseColor", m_color);
}
}
则会看到Cube1和Cube1_1的这两个材质球是新创建的两个材质球,但是有个优点是四个cube都能srp batch了。
同一个材质球+不同属性+shader书写要求+开启gpu instance=》才能instance。
instance的功能不受srp batch开关的影响。
Unity三种合批方式和GPU Instance - 知乎 (zhihu.com)
Unity URP中的Static Batching、GPU Instancing、SRPBatcher简单介绍_gpu instancing 和 srp batcher区别-CSDN博客
gpu instancing开启的条件:
1、首先shader必须符合gpu instancing的书写规则,如上面的例子。
2、材质需要勾选enable gpu intancing。
3、srp batcher的优先级高于gpu instaning,对于game objects,如果srp batcher能被使用(shader兼容srp batcher,节点本身也兼容),则就会使用srp batcher,即便材质开启了enable gpu instancing也没用。
4、如果srp batcher的条件被破坏,例如使用了MaterialPropertyBlock,且开启了Enable GPU Instancing,则GPU Instancing则会启用。
4)srp batch

你太逗了吧
srp batch条件:材质球不同(shader须相同)+ mesh可以不同
条件:
shader要满足srp batch的条件,将属性包在常量buffer里面。
如下图:
cube1和capusle是同材质球;cube3和cube1是不同的材质球,但是shader相同。但他们都被srp batch了。


cs
Shader "Unlit/xxxx"
{
Properties
{
//_MainTex ("Texture", 2D) = "white" {}
_BaseColor("Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma enable_d3d11_debug_symbols
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
//float4 _BaseColor; //_BaseColor作为单独变量声明,没有放置cb中
CBUFFER_START(UnityPerMaterial)
float4 _BaseColor;
CBUFFER_END
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
//o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
float4 frag (v2f i) : SV_Target
{
// sample the texture
//fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return _BaseColor;
}
ENDCG
}
}
}

1)srp batch原理

参考网址:
https://zhuanlan.zhihu.com/p/353687806
https://zhuanlan.zhihu.com/p/433222943
srp batcher使材质数据保留在gpu内存中。如果材质内容不变,srp batcher不需要设置缓冲区并将缓冲区上传到gpu。
2)兼容性
不是所有平台都支持常量缓冲区,所以unity用宏来定义区分什么时候使用。CBUFFER_START宏用来替代开始,CBUFFER_END 用来替代cbuffer的结束。
3)cbuffer
比如:unity_ObjectToWorld或者unity_SHAr。必须在一个名为UnityPerMaterial的cbuffer中声明所有材质属性。只要有一个不走常量buffer中,则合批失败。