首页 >Unity3D频道 >【Unity3D研究院之游戏开发】 > Unity3D研究院GPU Instancing测试
2018
09-03

Unity3D研究院GPU Instancing测试

GPU instancing 很早就支持手机了(Android只支持Opengl ES 3.0),但是我一直不知道将它应用到哪里,刚好最近在调研这个我对它又重新测试了一下。

如果是不动的物体勾选static静态合并批次(40-50帧率)

WechatIMG2

自定义Shader中勾选Enable GPU Instancing

QQ20180903-222127

帧率竟然还不如静态合批次(帧率 30-40)

WechatIMG3

自定义Shader

Shader "SimplestInstancedShader1"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
        _MainTex ("Texture", 2D) = "white" {}

    }

    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;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                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)


            sampler2D _MainTex;
            float4 _MainTex_ST;

            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);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                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 tex2D (_MainTex, i.uv) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            }
            ENDCG
        }
    }
}

所以,静态不动的物体就没必要用上面的方法了,于是我又测试了Graphics.DrawMeshInstanced()方法,终于满意了。(稳定60帧)

WechatIMG1

Graphics.DrawMeshInstanced()方法不需要游戏对象以及游戏组件的额外开销,在Update()方法中一气呵成,不过它也有限制,最多可以画1023个。
还有个方法是Graphics.DrawMeshInstancedIndirect()它没有画多少的限制,而且更加灵活,我也搞了好一会儿才在游戏中跑起来,后来才知道它不能再手机上用,只有PC上可以.

在shader中接收位置 颜色 矩阵

Shader "Instanced/InstancedIndirectSelection" 
{

 	Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }

	SubShader
	{
		Tags{ "RenderType" = "Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag		
			#pragma target 4.5

			#include "UnityCG.cginc"
			

			StructuredBuffer<float4> positionBuffer;
			StructuredBuffer<float4> colorBuffer;
			StructuredBuffer<float4x4> matrix4x4Buffer;

			struct appdata
			{
				fixed4 color : COLOR;
			    float4 vertex : POSITION;
			    float4 texcoord : TEXCOORD0;
			};


			struct v2f
			{
				float4 color: COLOR;
				float4 vertex : SV_POSITION;
				float2 texcoord : TEXCOORD0;
				
			};

			sampler2D _MainTex;
            float4 _MainTex_ST;

			v2f vert(appdata v, uint instanceID : SV_InstanceID)
			{
				float4 data = positionBuffer[instanceID];
				float4x4 materix = matrix4x4Buffer[instanceID / matrix4x4Buffer.Length];				
				float3 worldPosition = data.xyz + mul(materix,v.vertex.xyz * data.w);
				 
				v2f o;
				o.vertex = mul(UNITY_MATRIX_VP, float4(worldPosition, 1.0f));	
				o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
				o.color = colorBuffer[instanceID];
				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{
				return tex2D (_MainTex, i.texcoord) * i.color;

			}

			ENDCG
		}
	}
}

在代码中将位置 颜色 矩阵传进去就行了

using UnityEngine;
 
public class InstancedIndirectExample : MonoBehaviour
{ 
    public int instanceCount = 100000;
    public Mesh instanceMesh;
    public Material instanceMaterial;
 
    private ComputeBuffer positionBuffer;
    private ComputeBuffer argsBuffer;
	private ComputeBuffer colorBuffer;
	private ComputeBuffer matrix4x4Buffer;


    private uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
 
    void Start()
	{
        argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
        UpdateBuffers();
    }

    void Update()
	{ 
      //  instanceMaterial.SetBuffer("positionBuffer", positionBuffer);
        Graphics.DrawMeshInstancedIndirect(instanceMesh, 0, instanceMaterial, new Bounds(Vector3.zero, new Vector3(100.0f, 100.0f, 100.0f)), argsBuffer);
    }
 
 
    void UpdateBuffers()
	{ 
		if ( instanceCount < 1 ) instanceCount = 1;

        // Positions & Colors
        if (positionBuffer != null) positionBuffer.Release();
		if (colorBuffer != null) colorBuffer.Release();
		if (matrix4x4Buffer != null) matrix4x4Buffer.Release();


        positionBuffer	= new ComputeBuffer(instanceCount, 16);
		colorBuffer		= new ComputeBuffer(instanceCount, 4*4);
		matrix4x4Buffer = new ComputeBuffer(instanceCount,16);


        Vector4[] positions = new Vector4[instanceCount];
		Vector4[] colors	= new Vector4[instanceCount];
		Matrix4x4[] materix4X4 = new Matrix4x4[instanceCount];

        for (int i=0; i < instanceCount; i++)
		{
			positions [i] = new Vector4 (i, 0f, 0f, 1f);
			colors[i]		= new Vector4(  1f,  1f,  1f, 1f );
			materix4X4[i] = Matrix4x4.TRS (positions [i], Quaternion.Euler (0F, 0F, 0F), Vector3.one);
        }

        positionBuffer.SetData(positions);
		colorBuffer.SetData(colors);
		matrix4x4Buffer.SetData (materix4X4);

		instanceMaterial.SetBuffer("positionBuffer", positionBuffer);
		instanceMaterial.SetBuffer("colorBuffer", colorBuffer);
		instanceMaterial.SetBuffer("matrix4x4Buffer", matrix4x4Buffer);

        // indirect args
        uint numIndices = (instanceMesh != null) ? (uint)instanceMesh.GetIndexCount(0) : 0;
        args[0] = numIndices;
        args[1] = (uint)instanceCount;
        argsBuffer.SetData(args);
    }

    void OnDisable()
	{
        if (positionBuffer != null) positionBuffer.Release();
        positionBuffer = null;

		if (colorBuffer != null) colorBuffer.Release();
        colorBuffer = null;

        if (argsBuffer != null) argsBuffer.Release();
        argsBuffer = null;

		if (matrix4x4Buffer != null) matrix4x4Buffer.Release();
		matrix4x4Buffer = null;

    }
}

如果是发生移动的物体顶点数量在900以内会动态合并批次,如果需要支持更多的顶点GPU Instaning的优势就更明显了。 目前来看草、石头、植被、比较合适用它。

最后编辑:
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!

Unity3D研究院GPU Instancing测试》有 7 条评论

  1. 汤圆说:

    你好!请问Image上的材质可以使用GPUInstancing吗?试了下,好像不太行,有什么办法可以让Image也使用GPUInstancing吗?

    • 雨松MOMO说:

      ugui的image是会根据canvas完整的合并mesh,如果想用GPUInstancing那得用spriterender将material block传进去

      • vega说:

        你好,请问这个错误怎么处理,对shader不太了解
        ArgumentException: ComputeBuffer.SetData() : Accessing 64000 bytes at offset 0 for Compute Buffer of size 16000 bytes is not possible.

  2. 曹岩说:

    Unity3D 用几何着色器制作的草可以使用Graphics.DrawMeshInstancedIndirect()吗?

  3. cll说:

    StructuredBuffer的长度是不是不可以通过Length来取了? 我经过了尝试,shader会因此而报错

  4. M.C说:

    Graphics.DrawMeshInstancedIndirect()可以再mobile上面用

留下一个回复

你的email不会被公开。 必填项已用 * 标注。