前言
在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
/// <para>Blog:https://segmentfault.com/a/1190000019553301</para>
/// </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...
转载请标明原文地址:https://segmentfault.com/a/11...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。