Unity3D學習筆記7——GPU實例化(2)

1. 概述

在上一篇文章《Unity3D學習筆記6——GPU實例化(1)》詳細介紹了Unity3d中GPU實例化的實現,並且給出了詳細代碼。不過其着色器實現是簡單的頂點+片元着色器實現的。Unity提供的很多着色器是表面着色器,通過表面着色器,也是可以實現GPU實例化的。

2. 詳論

2.1. 實現

首先,我們還是掛接與上篇文章一樣的腳本:

using UnityEngine;

[ExecuteInEditMode]
public class Note7Main : MonoBehaviour
{
    public Mesh mesh;
    public Material material;
    
    int instanceCount = 200;
    Bounds instanceBounds;

    ComputeBuffer bufferWithArgs = null;
    ComputeBuffer instanceParamBufferData = null;
    
    // Start is called before the first frame update
    void Start()
    {
        instanceBounds = new Bounds(new Vector3(0, 0, 0), new Vector3(100, 100, 100));

        uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
        bufferWithArgs = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
        int subMeshIndex = 0;
        args[0] = mesh.GetIndexCount(subMeshIndex);
        args[1] = (uint)instanceCount;
        args[2] = mesh.GetIndexStart(subMeshIndex);
        args[3] = mesh.GetBaseVertex(subMeshIndex);
        bufferWithArgs.SetData(args);

        InstanceParam[] instanceParam = new InstanceParam[instanceCount];

        for (int i = 0; i < instanceCount; i++)
        {
            Vector3 position = Random.insideUnitSphere * 5;
            Quaternion q = Quaternion.Euler(Random.Range(0.0f, 90.0f), Random.Range(0.0f, 90.0f), Random.Range(0.0f, 90.0f));
            float s = Random.value;
            Vector3 scale = new Vector3(s, s, s);

            instanceParam[i].instanceToObjectMatrix = Matrix4x4.TRS(position, q, scale);
            instanceParam[i].color = Random.ColorHSV();
        }

        int stride = System.Runtime.InteropServices.Marshal.SizeOf(typeof(InstanceParam));
        instanceParamBufferData = new ComputeBuffer(instanceCount, stride);
        instanceParamBufferData.SetData(instanceParam);
        material.SetBuffer("dataBuffer", instanceParamBufferData);
        material.SetMatrix("ObjectToWorld", Matrix4x4.identity);
    }

    // Update is called once per frame
    void Update()
    {
        if (bufferWithArgs != null)
        {
            Graphics.DrawMeshInstancedIndirect(mesh, 0, material, instanceBounds, bufferWithArgs, 0);
        }
    }

    private void OnDestroy()
    {
        if (bufferWithArgs != null)
        {
            bufferWithArgs.Release();
        }

        if (instanceParamBufferData != null)
        {
            instanceParamBufferData.Release();
        }
    }
}

不過,腳本的材質設置需要使用我們新的材質:

imglink1

這個材質可以通過使用Standard Surface Shader作爲我們修改的模板:

imglink2

修改後的着色器代碼如下:

Shader "Custom/SimpleSurfaceIntanceShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows
		#pragma target 4.5
		#pragma multi_compile_instancing
        #pragma instancing_options procedural:setup     
        
		struct InstanceParam
		{			
			float4 color;
			float4x4 instanceToObjectMatrix;
		};

	#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
        StructuredBuffer<InstanceParam> dataBuffer;
    #endif

		float4x4 ObjectToWorld;
	
        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

		void setup()
        {
        #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
            InstanceParam data = dataBuffer[unity_InstanceID];
            unity_ObjectToWorld = mul(ObjectToWorld, data.instanceToObjectMatrix);        
        #endif
        }

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;		
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

最後的顯示效果如下:
imglink3

2.2. 解析

對比修改之前的着色器代碼:

  1. #pragma multi_compile_instancing的意思是給這個着色器增加了實例化的變體,也就是增加了諸如INSTANCING_ON PROCEDURAL_ON這樣的關鍵字,可以編譯實例化的着色器版本。
  2. #pragma instancing_options procedural:setup是搭配Graphics.DrawMeshInstancedIndirect 使用的,在頂點着色器階段開始時,Unity會調用冒號後指定的setup()函數。
  3. setup()函數的意思是通過實例化Id也就是unity_InstanceID,找到正確的實例化數據,並且調整Unity的內置變量unity_ObjectToWorld——也就是模型矩陣。正如上一篇文章所言,GPU實例化的關鍵就在於模型矩陣的重新計算。在Unity API官方示例中,還修改了其逆矩陣unity_WorldToObject。

3. 參考

  1. 《Unity3D學習筆記6——GPU實例化(1)》
  2. Graphics.DrawMeshInstancedIndirect
  3. Declaring and using shader keywords in HLSL
  4. Creating shaders that support GPU instancing

具體實現代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章