本文Demo参照https://blog.uwa4d.com/archives/1983.html,关于CPU耗时,作者已经分析的极为详细了。我在此补充下渲染相关的数据。
官方API:
https://docs.unity3d.com/ScriptReference/MaterialPropertyBlock.html
跟原作者相同的例子,创建100个Cube,分别测试Render.material.color和Render.SetPropertyBlock的性能。
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
using UnityEngine.Profiling;
using Debug = UnityEngine.Debug;
using Random = UnityEngine.Random;
public class MaterialTest : MonoBehaviour
{
[SerializeField] private GameObject prefabGO;
private int ColorId;
private List<Renderer> list1;
private List<Renderer> list2;
private MaterialPropertyBlock mpb;
// Start is called before the first frame update
private void Awake()
{
list1 = new List<Renderer>();
list2 = new List<Renderer>();
ColorId = Shader.PropertyToID("_Color");
mpb = new MaterialPropertyBlock();
}
void Start()
{
int UPPER = 100;
for (int i = 0; i < UPPER; i++)
{
GameObject go = Instantiate(prefabGO, new Vector3(
Random.Range(-2f, 2f),
Random.Range(-5f, 5f),
Random.Range(-5f, 5f)
),
Quaternion.Euler(Random.Range(0f, 360f),
Random.Range(0f, 360f),
Random.Range(0f, 360f)),
transform);
list1.Add(go.GetComponent<Renderer>());
// list1[i].GetPropertyBlock(mpb);
// mpb.SetColor(ColorId, new Color(Random.Range(0f,1f), Random.Range(0f,1f), Random.Range(0f,1f)));
// list1[i].SetPropertyBlock(mpb);
}
// for (int i = 0; i < UPPER; i++)
// {
// GameObject go = Instantiate(prefabGO, new Vector3(
// Random.Range(-2f, 2f),
// Random.Range(-5f, 5f),
// Random.Range(-5f, 5f)
// ),
// Quaternion.Euler(Random.Range(0f, 360f),
// Random.Range(0f, 360f),
// Random.Range(0f, 360f)),
// transform);
// list2.Add(go.GetComponent<Renderer>());
// }
}
// Update is called once per frame
void Update()
{
GetComponent<Renderer>();
Profiler.BeginSample("mat_1");
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
Stopwatch sw = new Stopwatch();
sw.Start();
int UPPER = list1.Count;
for (int i = 0; i < UPPER; i++)
{
list1[i].material.color = Color.cyan;
}
sw.Stop();
Debug.LogFormat("Test1 = total: {0:F4} ms {1}", (float)sw.ElapsedTicks * 1000 / Stopwatch.Frequency, Stopwatch.Frequency);
}
else if (Input.GetKeyDown(KeyCode.RightArrow))
{
Stopwatch sw = new Stopwatch();
sw.Start();
int UPPER = list1.Count;
for (int i = 0; i < UPPER; i++)
{
//All the cases support GPU Instance
list1[i].GetPropertyBlock(mpb);
//Case 1: Rand Color --- Can not support Dynamic Batch canse different MaterialPropertyBlock set.
mpb.SetColor(ColorId, new Color(Random.Range(0f,1f), Random.Range(0f,1f), Random.Range(0f,1f)));
//Case 2: Same Color --- Support Dynamic Batch
//mpb.SetColor(ColorId, Color.yellow);
list1[i].SetPropertyBlock(mpb);
}
sw.Stop();
Debug.LogFormat("Test2 = total: {0:F4} ms", (float)sw.ElapsedTicks * 1000 / Stopwatch.Frequency);
}
Profiler.EndSample();
}
}
总结:
- 使用Render.material.color缺点:
- 产生Mat(Instance),造成GC。根据SharedMat实例化一个Mat,再赋值给Render。
- 无法Dynamic Batch,即使修改成相同颜色也不可以,因为材质不同。(Batch必须相同材质、相同材质属性块)
- Renderer.SetPropertyBlock优点
- 不会产生Mat(Instance)额外内存
- 不开启GPU Instance状态下,修改为相同颜色,可以Dynamic Batch
- 不开启GPU Instance状态下,修改为不同颜色,不可以Dynamic Batch,因为材质属性块不同
- 开启GPU Instance状态下,不管颜色是否相同,都可以Batch
3.温故下GPU Instance的知识
“如果绘制1000个物体,它将一个模型的vbo提交给一次给显卡,至于1000个物体不同的位置,状态,颜色等等将他们整合成一个per instance attribute的buffer给gpu,在显卡上区别绘制,它大大减少提交次数”
参考【leonwei的Blog】:
https://blog.csdn.net/leonwei/article/details/73274808
4.纯色Shader支持GPU Instance
Shader "LeomonDream/PureColorShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
// Upgrade NOTE: excluded shader from DX11; has structs without semantics (struct appdata members uv_MainTex)
#pragma exclude_renderers d3d11
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#include "UnityCG.cginc"
sampler2D _MainTex;
struct appdata
{
float4 vertex : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
float2 uv : TEXCOORD0;
};
struct v2f
{
fixed4 vertex : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
float2 uv : TEXCOORD0;
};
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)
v2f vert(appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
fixed4 c = tex2D(_MainTex, i.uv) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
return c;
}
ENDCG
}
}
FallBack "Diffuse"
}