Unity3D学习笔记6——GPU实例化(1)

简介: Unity3D学习笔记6——GPU实例化(1)

Unity3D学习笔记6——GPU实例化(1)

目录

1. 概述

在之前的文章中说到,一种材质对应一次绘制调用的指令。即使是这种情况,两个三维物体使用同一种材质,但它们使用的材质参数不一样,那么最终仍然会造成两次绘制指令。原因在于,图形工作都是一种状态机,状态发生了变化,就必须进行一次绘制调用指令。

GPU实例化用于解决这样的问题:对于像草地、树木这样的物体,它们往往是数据量很大,但同时又只存在微小的差别如位置、姿态、颜色等。如果像常规物体那样进行渲染,所使用的绘制指令必然很多,资源占用必然很大。一个合理的策略就是,我们指定一个需要绘制物体对象,以及大量该对象不同的参数,然后根据参数在一个绘制调用中绘制出来——这就是所谓的GPU实例化。

2. 详论

首先,我们创建一个空的GameObject对象,并且挂接如下脚本:

using UnityEngine;
//实例化参数
public struct InstanceParam
{  
    public Color color;
    public Matrix4x4 instanceToObjectMatrix;        //实例化到物方矩阵
}
[ExecuteInEditMode]
public class Note6Main : 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();
        }        
    }
}

这个脚本的意思是,设置一个网格和一个材质,通过随机获取的实例化参数,渲染这个网格的多个实例:

GPU实例化的关键接口是Graphics.DrawMeshInstancedIndirect()。Graphics对象的一系列接口是Unity的底层API,它是需要每一帧调用的。Graphics.DrawMeshInstanced()也可以实例绘制,但是最多只能绘制1023个实例。所以还是Graphics.DrawMeshInstancedIndirect()比较好。

实例化参数InstanceParam和GPU缓冲区参数bufferWithArgs都是存储于一个ComputeBuffer对象中。ComputeBuffe定义了一个GPU数据缓冲区对象,能够映射到Unity Shader中的 StructuredBuffer中。实例化参数InstanceParam存储了每个实例化对象的位置,姿态、缩放以及颜色信息,通过Material.SetBuffer(),传递到着色器中:

Shader "Custom/SimpleInstanceShader"
{
    Properties
    {        
    }
    SubShader
    {
    Tags{"Queue" = "Geometry"}
    Pass
    { 
      CGPROGRAM
      #include "UnityCG.cginc" 
      #pragma vertex vert 
      #pragma fragment frag
      #pragma target 4.5
      sampler2D _MainTex;
      
      float4x4 ObjectToWorld;
  
      struct InstanceParam
      {     
        float4 color;
        float4x4 instanceToObjectMatrix;
      };
  
    #if SHADER_TARGET >= 45     
      StructuredBuffer<InstanceParam> dataBuffer;
    #endif
    
      //顶点着色器输入
      struct a2v
      {
        float4  position : POSITION;
        float3  normal: NORMAL;
        float2  texcoord : TEXCOORD0; 
      };
      //顶点着色器输出
      struct v2f
      {
        float4 position: SV_POSITION;
        float2 texcoord: TEXCOORD0;
        float4 color: COLOR;
      };
      v2f vert(a2v v, uint instanceID : SV_InstanceID)
      {
      #if SHADER_TARGET >= 45
        float4x4 instanceToObjectMatrix = dataBuffer[instanceID].instanceToObjectMatrix;
        float4 color = dataBuffer[instanceID].color;
      #else
        float4x4 instanceToObjectMatrix = float4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
        float4 color = float4(1.0f, 1.0f, 1.0f, 1.0f);
      #endif
        float4 localPosition = mul(instanceToObjectMatrix, v.position);
        //float4 localPosition = v.position;
        float4 worldPosition = mul(ObjectToWorld, localPosition);           
        v2f o;
        //o.position = UnityObjectToClipPos(v.position);
        o.position = mul(UNITY_MATRIX_VP, worldPosition);   
        o.texcoord = v.texcoord;
        o.color = color;
        return o;
      }
      fixed4 frag(v2f i) : SV_Target 
      {                       
        return i.color;         
      }
            ENDCG
        }
    }
  Fallback "Diffuse"
}

这是一个改进自《Unity3D学习笔记3——Unity Shader的初步使用》的简单实例化着色器。实例化绘制往往位置并不是固定的,这意味着Shader中获取的模型矩阵UNITY_MATRIX_M一般是不正确的。因而实例化绘制的关键就在于对模型矩阵的重新计算,否则绘制的位置是不正确的。实例化的数据往往位置比较接近,所以可以先传入一个基准位置(矩阵ObjectToWorld),然后实例化数据就可以只传入于这个位置的相对矩阵(instanceToObjectMatrix)。

最终的运行结果如下,绘制了大量不同位置、不同姿态、不同大小以及不同颜色的胶囊体,并且性能基本上不受影响。

3. 参考

  1. 《Unity3D学习笔记3——Unity Shader的初步使用》
  2. Graphics.DrawMeshInstanced

具体实现代码

分类: Unity3D

标签: Unity3D , 实例化


相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
2月前
|
缓存 图形学
Unity3D学习笔记12——渲染纹理
Unity3D学习笔记12——渲染纹理
33 2
|
2月前
|
API C# 图形学
Unity3D学习笔记9——加载纹理
Unity3D学习笔记9——加载纹理
29 2
|
2月前
|
API 图形学 异构计算
Unity3D学习笔记7——GPU实例化(2)
Unity3D学习笔记7——GPU实例化(2)
15 2
|
2月前
|
存储 缓存 图形学
Unity3D学习笔记11——后处理
Unity3D学习笔记11——后处理
29 1
|
2月前
|
测试技术 C# 图形学
Unity3D学习笔记10——纹理数组
Unity3D学习笔记10——纹理数组
41 0
|
2月前
|
图形学 异构计算
Unity3D学习笔记8——GPU实例化(3)
Unity3D学习笔记8——GPU实例化(3)
31 0
|
2月前
|
API 图形学 索引
Unity3D学习笔记5——创建子Mesh
Unity3D学习笔记5——创建子Mesh
24 0
|
2月前
|
API C# 图形学
Unity3D学习笔记4——创建Mesh高级接口
Unity3D学习笔记4——创建Mesh高级接口
33 0
|
2月前
|
C# 图形学 C语言
Unity3D学习笔记3——Unity Shader的初步使用
Unity3D学习笔记3——Unity Shader的初步使用
40 0
|
2月前
|
存储 算法 C#
Unity3D学习笔记2——绘制一个带纹理的面
Unity3D学习笔记2——绘制一个带纹理的面
24 0
下一篇
无影云桌面