共计 4776 个字符,预计需要花费 12 分钟才能阅读完成。
前言
在 Unity 中 同网格同材质的模型是可以合批的
动态批处理和静态批处理都可以合批 但是都有其限制
动态批处理有顶点数不能超过 900 的限制 只适合比较简单的模型
静态批处理的物体不能移动、旋转、缩放 并且需要消耗额外的内存来存储合并后的物体
如果动态静态批处理都无法使用 能否用其他方式合批呢?
可以尝试一下 GPU Instance 虽然也有所限制 但是提供了更多可能
使用 GPU Instancing 的条件
1.Shader 支持 GPU Instancing
2. 硬件支持 GPI Instancing
3. 代码动态绘制物体
硬件需求:
GPU Instancing is available on the following platforms and APIs:
·DirectX 11 and DirectX 12 on Windows
·OpenGL Core 4.1+/ES3.0+ on Windows, macOS, Linux, iOS and Android
·Metal on macOS and iOS
·Vulkan on Windows and Android
·PlayStation 4 and Xbox One
·WebGL (requires WebGL 2.0 API)
限制情况:
下列情况不能使用 Instancing:·使用 Lightmap 的物体
·受不同 Light Probe / Reflection Probe 影响的物体
·使用包含多个 Pass 的 Shader 的物体,只有第一个 Pass 可以 Instancing 前向渲染时,受多个光源影响的物体只有 Base Pass 可以 instancing,Add Passes 不行
GPU Instance 测试
GPU Instancing 确实可以动态合批 但是需要 Shader + 硬件 + 代码的支持
好处是可以解决动态批处理解决不了的问题 没 900 顶点的限制
ps: 虽然官方的 Standard Shader 提供了 GPU Instance 的选项 但是给材质设置不同颜色后合批失败了 这里有坑 使用了其他 Shader 后解决
测试效果:
测试代码:
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// PropertyBlockTest
/// ZhangYu 2019-06-17
/// </summary>
public class PropertyBlockTest : MonoBehaviour {
public GameObject prefab;
public int count = 100;
private Mesh insMesh;
private Material insMaterial;
private List<Matrix4x4> insMatrices;
private MaterialPropertyBlock insBlock;
private List<Color> insColors;
private int colorID;
private void Start () {GPUInstanceByBlock();
//GPUInstanceByDrawMesh();}
private void Update() {if (insMesh != null) DrawMeshes();}
// 方法 1:通过 Shader + PropertyBlock 实现 GPU Instance
private void GPUInstanceByBlock() {MaterialPropertyBlock block = new MaterialPropertyBlock();
int colorID = Shader.PropertyToID("_Color");
GameObject[] objs = new GameObject[count];
for (int i = 0; i < count; i++) {Vector3 position = new Vector3(Random.Range(-8, 8f), Random.Range(-4, 4f), 3);
Color color = new Color(Random.Range(0, 1f), Random.Range(0, 1f), Random.Range(0, 1f));
block.SetColor(colorID, color);
GameObject obj = Instantiate(prefab);
// 用 Block 代替 Material 设置值 这样就能合批了
obj.GetComponent<MeshRenderer>().SetPropertyBlock(block);
obj.transform.position = position;
obj.SetActive(true);
}
}
// 方法 2:通过 DrawMesh + Shader + PropertyBlock 实现 GPU Instance
private void GPUInstanceByDrawMesh() {insMesh = prefab.GetComponent<MeshFilter>().mesh;
insMaterial = prefab.GetComponent<Renderer>().material;
insMatrices = new List<Matrix4x4>();
insColors = new List<Color>();
insBlock = new MaterialPropertyBlock();
colorID = Shader.PropertyToID("_Color");
for (int i = 0; i < count; i++) {Vector3 position = new Vector3(Random.Range(-8, 8f), Random.Range(-4, 4f), 3);
Quaternion rotation = prefab.transform.rotation;
Color color = new Color(Random.Range(0, 1f), Random.Range(0, 1f), Random.Range(0, 1f));
// Position + Rotation + Scale > Matrix4x4
Matrix4x4 matrix = TransformToMatrix(position, rotation);
insMatrices.Add(matrix);
insColors.Add(color);
}
}
private void DrawMeshes() {
// 测试结果:// 同网格 同材质 可以合批 需要 Shader 支持 GPU Instance + 用 PropertyBlock 设置参数
// DrawMeshInstanced() 一次绘制多个物体 调用一次 一个 DrawCall
//Graphics.DrawMeshInstanced(insMesh, 0, insMaterial, insMatrices, insBlock);
// DrawMesh() 一次绘制一个物体 多次调用 可以合成一批
for (int i = 0; i < count; i++) {insBlock.SetColor(colorID, insColors[i]);
Graphics.DrawMesh(insMesh, insMatrices[i], insMaterial, 1, Camera.main, 0, insBlock);
}
}
private Matrix4x4 TransformToMatrix(Vector3 position) {return Matrix4x4.TRS(position, Quaternion.identity, Vector3.one);
}
private Matrix4x4 TransformToMatrix(Vector3 position, Quaternion rotation) {return Matrix4x4.TRS(position, rotation, Vector3.one);
}
private Matrix4x4 TransformToMatrix(Vector3 position, Quaternion rotation, Vector3 scale) {return Matrix4x4.TRS(position, rotation, scale);
}
}
测试 Shader:
Shader "SimplestInstancedShader"
{
Properties
{_Color("Color", Color) = (1, 1, 1, 1)
}
SubShader
{Tags{ "RenderType" = "Opaque"}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID // necessary only if you want to access instanced properties in fragment Shader.
};
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); // necessary only if you want to access instanced properties in the fragment Shader.
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{UNITY_SETUP_INSTANCE_ID(i); // necessary only if any instanced properties are going to be accessed in the fragment Shader.
return UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
}
ENDCG
}
}
}
MaterialPropertyBlock 的说明:
材质属性块被用于 Graphics.DrawMesh 和 Renderer.SetPropertyBlock 两个 API,当我们想要绘制许多相同材质但不同属性的对象时可以使用它。例如你想改变每个绘制网格的颜色,但是它却不会改变渲染器的状态。
简单来说利用 PropertyBlock 设置属性不会产生新的 Mesh 和 Material 速度快性能高
SkinMeshRender 的 GPU Instancing
Unity 官方开源的 Animation Instacing: https://blogs.unity3d.com/cn/…
CSDN 博主
《Unity 中使用 GPU Instancing 优化 SkinnedMesh 渲染》https://blog.csdn.net/xoyojan…
参考资料:
《[unity]GPU Instance 学习》:https://www.jianshu.com/p/ecf…
《使用 MaterialPropertyBlock 来替换 Material 属性操作》https://blog.uwa4d.com/archiv…
《Unity3D 研究院 GPU Instancing 实战(九十七)》https://www.xuanyusong.com/ar…