【Unity学习笔记】GPU实例化

与静态合批动态合批一样,GPU实例化的目的是对于多个网格同一个材质不同属性,尽可能减少Draw Call的次数,减少合批数量进而达到提高性能的目的

简单的GPU实例化的案例实现

首先创建一个基本的c#脚本

主要的代码是在Start中,设定游戏一开始计算一个for循环,当 i 小于我们设定的固定数量后,结束循环,在循环体中加入下面的计算

固定写法Instantiate

        第一个参数是源数据,即我们需要实例化的模型prefabTree

        第二个参数是position,我们用一个三维向量来填充x,y,z,其中,x,z设置一个随机值,所以在上方声明一个圆形范围的随机值Random.insideUnitCircle,乘以我们设定的Range以控制范围大小

        第三个参数是旋转,使用默认参数

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Practic_Instance : MonoBehaviour
{
    // Start is called before the first frame update

    [Header("生成对象")]
    public GameObject prefabTree;
    [Header("生成数量")]
    public int Count;

    [Header("生成范围")]

    public float Range = 10;
    void Start()
    {
        for (int i = 0; i < Count; i++)
        {
            Vector2 pos = Random.insideUnitCircle * Range;
            GameObject tree = Instantiate(prefabTree,new Vector3(pos.x,6,pos.y),Quaternion.identity);
        
        
        }
    }

    // Update is called once per frame

}

设置好暴露参数后我们尝试运行

效果是有了,但是合批数量依然是每个网格进行一次DrawCall,显然我们还要在shader中支持实例化

根据步骤,我们依次引入了instancing的变体 #pragma multi_compile_instancing 并在材质面板勾选启用Instancing

记录不同实例属性ID的方法 UNITY_VERTEX_INPUT_INSTANCE_ID 

UNITY_SETUP_INSTANCE_ID(v);放在顶点着色器和片段着色器中最开始的地方,用来访问全局unity_InstanceID

当需要将实例化ID传到片段着色器时,在顶点着色器中添加UNITY_TRANSFER_INSTANCE_ID(v, o);

Shader "Unlit/Practic_Instance"
{
    Properties
    {

    }
    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 pos : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };


            v2f vert (appdata v)
            {
                UNITY_SETUP_INSTANCE_ID(v);
                v2f o;
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);
                return 1;
            }
            ENDCG
        }
    }
}

 后面我们需要更改他们的颜色属性,所以暂时将片段着色器中所需要的方法引入进去,其实这时我们已经可以再运行看看了

在shader的支持下,合批数量已经变为1了,因为我们这里删除了灯光并且将Camera中的Clear Flags设为了Solid Color,不再为天空盒与灯光单独DrawCall,所以只有这一个合批

下面我们引入颜色的变化

回到我们的脚本中,这里我们不再尝试直接设置Material的属性(SetColor),此方法一样会导致多个批次,因为会生成新的材质实例,占用单独的合批,unity提供了MaterialPropertyBlock(材质属性块)方便我们对实例的材质属性进行调整,且不会产生额外的合批

首先,先声明一个随机的颜色值方便使用

Color col = new Color(Random.value, Random.value, Random.value);

对于Random.value 会随机生成一个0-1的值,声明三个,填入RGB中

下一步,将我们的主角MaterialPropertyBlock 实例化并命名为prop

MaterialPropertyBlock prop = new MaterialPropertyBlock();

它包含了很多方法,我们只取其中一个,SetColor 第一个参数是变量名称,第二个参数是颜色参数,是我们第一步声明的col

prop.SetColor("_Color", col);

最后,SetPropertyBlock 会与材质属性块对接,只需要填入我们第二步命名的名称即可

tree.GetComponentInChildren<MeshRenderer>().SetPropertyBlock(prop);

下面是完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Practic_Instance : MonoBehaviour
{
    // Start is called before the first frame update

    [Header("生成对象")]
    public GameObject prefabTree;
    [Header("生成数量")]
    public int Count;

    [Header("生成范围")]

    public float Range = 10;
    void Start()
    {
        for (int i = 0; i < Count; i++)
        {
            Vector2 pos = Random.insideUnitCircle * Range;
            GameObject tree = Instantiate(prefabTree, new Vector3(pos.x, 6, pos.y), Quaternion.identity);
            Color col = new Color(Random.value, Random.value, Random.value);
            MaterialPropertyBlock prop = new MaterialPropertyBlock();
            prop.SetColor("_Color", col);
            tree.GetComponentInChildren<MeshRenderer>().SetPropertyBlock(prop);

        }
    }

    // Update is called once per frame

}

别忘了shader!

Shader "Unlit/Practic_Instance"
{
    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"

            // 每个实例化的物体属性都封装在这个常量寄存器(数组)中,括号内填入数组的名称
            // 将需要的每条属性都添加在START和END之间,第一个参数是变量类型,第二个参数是变量名称
            UNITY_INSTANCING_BUFFER_START(prop) 
            // 注意,添加需要实例化的属性后,就不需要再次声明_Color的变量类型了,否则会报错为重复定义
            UNITY_DEFINE_INSTANCED_PROP(fixed4,_Color)
            UNITY_INSTANCING_BUFFER_END(prop)

            struct appdata
            {
                float4 vertex : POSITION;
                // 记录每个实例不同的信息
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };


            v2f vert (appdata v)
            {
                UNITY_SETUP_INSTANCE_ID(v);
                v2f o;
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);
                // 具体的实例变化变量,可直接作为_Color属性输出,注意不要重复声明,当作_Color直接使用
                // 第一个参数为寄存器(数组)名称,第二个参数为变量名称
                return UNITY_ACCESS_INSTANCED_PROP(prop, _Color);
            }
            ENDCG
        }
    }
}

引入需要的方法就可以了,主要是寄存器数组存储指定变量的不同信息,再传入到片段着色器中

最后的效果

 

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GPU实例化是一种在GPU上生成和渲染大量相似物体的技术。在Unity中,GPU实例化可以用于大规模的场景渲染,例如草地、树木、岩石等自然物体。 GPU实例化的实现方式是使用一个网格和一个材质,然后通过修改网格的变换矩阵来渲染大量相似的物体。在Unity中,可以通过以下步骤来使用GPU实例化: 1. 创建一个网格对象,包含一个或多个子网格。 2. 创建一个材质对象,并将其设置为支持GPU实例化。 3. 在脚本中,使用Graphics.DrawMeshInstanced来渲染实例化网格对象。 4. 在渲染之前,需要将实例化的属性发送给GPU,例如变换矩阵、颜色等。 例如,以下代码演示如何在Unity中使用GPU实例化: ```csharp public class InstancedObject : MonoBehaviour { public Mesh mesh; public Material material; public int instanceCount; private Matrix4x4[] matrices; private Vector4[] colors; private void Start() { matrices = new Matrix4x4[instanceCount]; colors = new Vector4[instanceCount]; for (int i = 0; i < instanceCount; i++) { matrices[i] = Matrix4x4.TRS(Random.insideUnitSphere * 10f, Quaternion.identity, Vector3.one); colors[i] = new Vector4(Random.value, Random.value, Random.value, 1f); } } private void Update() { for (int i = 0; i < instanceCount; i++) { matrices[i] = Matrix4x4.TRS(Random.insideUnitSphere * 10f, Quaternion.identity, Vector3.one); colors[i] = new Vector4(Random.value, Random.value, Random.value, 1f); } } private void OnRenderObject() { material.SetVectorArray("_Color", colors); Graphics.DrawMeshInstanced(mesh, 0, material, matrices, instanceCount); } } ``` 在这个例子中,我们创建了一个InstancedObject类,并在Start方法中初始化了变换矩阵和颜色数组。在Update方法中,我们每帧更新变换矩阵和颜色数组,以模拟物体的运动。在OnRenderObject方法中,我们将变换矩阵和颜色数组发送给GPU,并使用Graphics.DrawMeshInstanced方法渲染了实例化网格对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值