Unity GPU Instance(大量相同网格物体合批)

冰封百度

前言

在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后解决

测试效果:
GPU Instancing合批
MaterialPropertyBlock

测试代码:

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...

阅读 10.4k

冰封百度的学习笔记
程序生涯中的技术整理

Unity游戏程序员一枚。生命不息,学习不止。

193 声望
36 粉丝
0 条评论

Unity游戏程序员一枚。生命不息,学习不止。

193 声望
36 粉丝
文章目录
宣传栏