游戲開發中,可能會碰到一次繪制多個擁有相同 mesh 的物體,它們可能是位置,旋轉等不同,或者是材質的某些引數不同,我們要做的就是配合 Unity 的渲染方式,盡可能地減少繪制的操作,
由于下文主要討論的是 DrawInstance 和 MaterialPropertyBlock,所以其他的一下影響合批的內容沒有討論,
動態合批
繪制多個相同 Mesh,相同 Material 的物體時,打開 Material 的 Gpu Instance 選項,在滿足頂點限制的條件下,會進行動態合批(動態合批需要打開),


不同 Material 會打斷合批
修改 Material 的引數,這樣會打斷合批,并且 Unity 會為每一個物體創建一個 Material 的實體,
public class TestDrawInstance : MonoBehaviour
{
public Transform[] Cubes;
void Start()
{
foreach (var cube in Cubes)
{
cube.GetComponent<Renderer>().material.
SetColor("_Color", new Color(
Random.Range(0f, 1f),
Random.Range(0f, 1f),
Random.Range(0f, 1f)));
}
}
}




通過 MaterialPropertyBlock 避免創建 Material 實體
這時可以借助 MaterialPropertyBlock 來設定不同的屬性,但是這只會節省,創建多個 Material 的消耗,并不會影響到渲染,依然會打斷合批, 不會打斷,滿足動態合批的條件時,會進行合批,
MaterialPropertyBlock propertyBlock = new MaterialPropertyBlock();
foreach (var cube in Cubes)
{
// 設定不同的屬性
propertyBlock.SetColor("_Color", new Color(
Random.Range(0f, 1f),
Random.Range(0f, 1f),
Random.Range(0f, 1f)));
// 設定 PropertyBlock 替代設定 material 屬性
cube.GetComponent<Renderer>()
.SetPropertyBlock(propertyBlock);
}
DrawMeshInstanced 一次性繪制
直接使用 DrawMeshInstanced API 也可以達到合批的作用,但是無法設定不同的引數,同時需要注意一次 DrawInstance 呼叫最多繪制 1023 個物體的限制,
public Transform[] Cubes;
private Material drawMat;
Mesh drawMesh;
Matrix4x4[] trsMat;
private void Update()
{
// 使用 DrawInstance 繪制一次性繪制
drawMat = Cubes[0].GetComponent<Renderer>().sharedMaterial;
drawMesh = Cubes[0].GetComponent<MeshFilter>().sharedMesh;
trsMat = new Matrix4x4[Cubes.Length];
for (int i = 0; i < Cubes.Length; i++) {
trsMat[i] = Matrix4x4.TRS(
Cubes[i].position,
Cubes[i].rotation,
Cubes[i].lossyScale);
Cubes[i].GetComponent<Renderer>().enabled = false;
}
Graphics.DrawMeshInstanced(
drawMesh,
0,
drawMat,
trsMat,
Cubes.Length,
null,
ShadowCastingMode.Off,
false
);
}

DrawMeshInstanced 配合 MaterialPropertyBlock 進行繪制
DrawMeshInstanced 和 MaterialPropertyBlock 同樣是可以進行配合的,但是需要撰寫能處理 MaterialPropertyBlock 資料的 shader,
首先要 撰寫支持 GPU Instancing 的 Shader,以頂點片元 shader 為例,需要如下步驟
- 開啟 gpu instance,
#pragma multi_compile_instancing - 在結構體中添加 id 宣告,
UNITY_VERTEX_INPUT_INSTANCE_ID - 定義資料,
- setup,在 vert/frag 中使用需要先
UNITY_SETUP_INSTANCE_ID,在 frag 中使用還要在 vert 中使用UNITY_TRANSFER_INSTANCE_ID - 獲取資料,
UNITY_ACCESS_INSTANCED_PROP
shader sample
Shader "Custom/TestDrawInstance"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
// 1. 開啟 gpu instancing
#pragma multi_compile_instancing
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
// 2. 在資料中添加一個語意為 SV_InstanceID 的元素
// 在 vertex 中使用
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
// 2. 在資料中添加一個語意為 SV_InstanceID 的元素
// 在 fragment 中使用
UNITY_VERTEX_INPUT_INSTANCE_ID
};
// 3. 定義資料, name 為任意字串, START 和 END 成對使用
UNITY_INSTANCING_BUFFER_START(Props)
// 定義一個實體屬性
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
// 4. setup
UNITY_SETUP_INSTANCE_ID(v);
// 在 fragment 中使用,需要在此進行設定
UNITY_TRANSFER_INSTANCE_ID(v, o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 4. setup
UNITY_SETUP_INSTANCE_ID(i);
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// 5. 獲取實體資料
col = UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
使用代碼控制繪制
public class TestDrawInstance : MonoBehaviour
{
public Transform[] Cubes;
private Material drawMat;
private Mesh drawMesh;
private Matrix4x4[] trsMat;
private MaterialPropertyBlock instanceBlock;
private Vector4[] colors;
private void Start()
{
drawMat = Cubes[0].GetComponent<Renderer>().sharedMaterial;
drawMesh = Cubes[0].GetComponent<MeshFilter>().sharedMesh;
trsMat = new Matrix4x4[Cubes.Length];
colors = new Vector4[Cubes.Length];
instanceBlock = new MaterialPropertyBlock();
for (int i = 0; i < Cubes.Length; i++)
{
colors[i] = new Vector4(
Random.Range(0f, 1f),
Random.Range(0f, 1f),
Random.Range(0f, 1f),
1);
Cubes[i].GetComponent<Renderer>().enabled = false;
}
instanceBlock.SetVectorArray("_Color", colors);
}
private void Update()
{
for (int i = 0; i < Cubes.Length; i++) {
trsMat[i] = Matrix4x4.TRS(
Cubes[i].position,
Cubes[i].rotation,
Cubes[i].lossyScale);
}
Graphics.DrawMeshInstanced(
drawMesh,
0,
drawMat,
trsMat,
Cubes.Length,
instanceBlock,
ShadowCastingMode.Off,
false
);
}
}
參考鏈接
- Material Property Block 使用
- DrawMeshInstanced 配合 MeterialPropertyBlock 進行繪制
- GPU Instancing
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/472379.html
標籤:其他
上一篇:HO引擎近況20220512
下一篇:紅日靶場(內網滲透)——1
