第5章 Unity 3D引擎的多例化技术

5.1 多例化技术概述

        在渲染时,如果能够将数据一次性发送给GPU,然后使用一个绘制函数让渲染流水线利用这些数据绘制多个相同的物体将会大大提升性能,这种技术就是GPU多例化(GPU instancing)技术。

        使用GPU多例化技术,能够在一个绘制调用中渲染多个相同的物体。

        GPU多例化的思想,就是把每个实例的不同信息存储在缓冲区(可能是顶点缓冲区,也可能是存储着着色器uniform变量的常量缓冲区)中,然后直接操作缓冲区中的数据来设置。

5.1.1 不使用GPU多例化技术绘制多个相同模型的伪代码

for(unsigned inti = 0; i < amount_of_models_to_draw; i++)

{

    //准备模型的顶点数据、准备世界变换等用到的着色器参数

    //调用绘制操作,把顶点数据投递到渲染流水线中

    //执行流水线的每一步,最终把模型渲染到屏幕

}

        GPU多例化技术过程:

        1. 准备好顶点数据、设置好顶点缓冲区

        2. 输入组装阶段

        输入组装阶段是使用硬件实现的。此阶段根据用户输入的顶点缓冲区信息、图元拓扑(primitive topology)结构信息和描述顶点布局(vertice layout)格式信息,把顶点组装成图元,然后发送给顶点缓冲区。

        3. 顶点着色器和片元着色器的设置

5.2 如何在材质中启用多例化技术

        要在Unity 3D中启用GPU多例化技术,首先应在材质文件的Inspector面板中选中Enable Instancing复选框(只有材质文件使用的着色器代码文件中声明了支持GPU多例化技术,材质文件的Inspector面板中才会出现Enable Instancing复选框)。

5.3 添加逐实例数据

        每一次多例化绘制调用(instanced draw call)时,默认地,Unity 3D仅对使用了同一网格材质,但是有着不同的位置变换信息的游戏对象进行批次化。

        GPU多例化技术不仅应用在只有不同位置变换信息的游戏对象,如位置变换信息相同,但材质颜色不同的游戏对象,也可以使用GPU多例化技术。

5.3.2 在C#层改变game object中的多例化材质颜色属性

        在C#代码段中,通过使用MaterialPropertyBlock类设置每一个多例化实例的不同颜色,在一个绘制调用中渲染不同颜色的多例化游戏对象。

MaterialPropertyBlock props = newMaterialPropertyBlock();

MeshRenderer renderer;

foreach(GameObject obj inobjects)

{

    floatr = Random.Range(0.0f, 1.0f);

    floatg = Random.Range(0.0f, 1.0f);

    floatb = Random.Range(0.0f, 1.0f);

    props.SetColor("_Color", newColor(r, g, b));

    renderer = obj.GetComponent<MeshRenderer>();

    renderer.SetPropertyBlock(props);

}

5.4 在顶点着色器和片元着色器中使用多例化技术

        默认地外观着色器是开启了多例化技术支持的,对于普通的顶点着色器和片元着色器也可以使用此项技术。

5.4.1 在顶点着色器和片元着色器中启用GPU多例化技术

// 第1步,必须要使用这个编译指示符宣告使用GPU多例化技术
         #pragma multi_compile_instancing
         #include "UnityCG.cginc" // 包含头文件,使用启用多例化技术要用到宏

//第2步,如果要访问片元着色器中的多例化属性变量,需要使用此宏

           UNITY_VERTEX_INPUT_INSTANCE_ID

//第3步,使用和外观着色器相同的宣告多例化属性变量语句

            UNITY_INSTANCING_CBUFFER_START(Props)

             UNITY_DEFINE_INSTANCED_PROP(float4, _Color)

              UNITY_INSTANCING_CBUFFER_END(Props)

//第4步,如果要访问片元着色器中的多例化属性变量,需要使用此宏

                UNITY_SETUP_INSTANCE_ID(v);

                UNITY_TRANSFER_INSTANCE_ID(v, o);

//第5步,如果要访问片元着色器中的多例化属性变量,需要使用此宏

                  UNITY_SETUP_INSTANCE_ID(i);

                  returnUNITY_ACCESS_INSTANCED_PROP(_Color);

在以下代码中,在顶点着色器和片元着色器中启用GPU多实例化技术。

Shader "SimplestInstancedShader"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass
        {
            CGPROGRAM
            #pragma vertex vert       // 声明vert函数为顶点着色器入口主函数
            #pragma fragment frag     //声明frag函数为片元着色器入口主函数
            // 第1步,必须要使用这个编译指示符宣告使用GPU多例化技术
            #pragma multi_compile_instancing
            #include "UnityCG.cginc" // 包含头文件,使用启用多例化技术要用到宏


            structappdata             // 传递给顶点着色器的顶点数据结构体
            {
                  float4vertex : POSITION;
                  UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            structv2f                         // 顶点着色器传递给片元着色器的数据结构体
            {
                  float4vertex : SV_Position;
                  //第2步,如果要访问片元着色器中的多例化属性变量,需要使用此宏
                  UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            //第3步,使用和外观着色器相同的宣告多例化属性变量语句
            UNITY_INSTANCING_CBUFFER_START(Props)
            UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
            UNITY_INSTANCING_CBUFFER_END(Props)
            v2f vert(appdata v)              // 顶点着色器入口主函数
            {
                  v2f o;
                  //第4步,如果要访问片元着色器中的多例化属性变量,需要使用此宏
                  UNITY_SETUP_INSTANCE_ID(v);
                  UNITY_TRANSFER_INSTANCE_ID(v, o);
                  o.vertex = UnityObjectToClipPos(v.vertex);
                  returno;
            }
            fixed4frag(v2f i) : SV_Target// 片元着色器入口主函数
            {
                  //第5步,如果要访问片元着色器中的多例化属性变量,需要使用此宏
                  UNITY_SETUP_INSTANCE_ID(i);
                  returnUNITY_ACCESS_INSTANCED_PROP(_Color);
            }
            ENDCG
        }
    }
}


5.4.2 UNlTY_VERTEX_lNPUT_lNSTANCE_lD宏的定义

// 所在文件:UnityInstancing.cginc代码

// 所在目录:CGIncludes

// 从原文件第69行开始,至第71行结束

#if !defined(UNITY_VERTEX_INPUT_INSTANCE_ID)

#define UNITY_VERTEX_INPUT_INSTANCE_IDDEFAULT_UNITY_VERTEX_INPUT_INSTANCE_ID

#endif

5.4.3 DEFAULT_UNlTY_VERTEX_lNPUT_lNSTANCE_lD宏的定义

// 所在文件:UnityInstancing.cginc代码

// 所在目录:CGIncludes

// 从原文件第47行开始,至第67行结束

#if defined(UNITY_INSTANCING_ENABLED)||

    defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) ||

    defined(UNITY_STEREO_INSTANCING_ENABLED)

    // 每一个多例化对象对应的instance id

    static uintunity_InstanceID;

    // 着色器常量缓冲区UnityDrawCallInfo

    GLOBAL_CBUFFER_START(UnityDrawCallInfo)

        // 当前渲染批次中是在多例化对象数组中的起始位置

        intunity_BaseInstanceID;

        // 当前渲染批次中的多例化对象的个数,如果是立体渲染,则以双次渲染之前的个数为准

        intunity_InstanceCount;

    GLOBAL_CBUFFER_END

    #ifdef SHADER_API_PSSL

        #define DEFAULT_UNITY_VERTEX_INPUT_INSTANCE_ID uintinstanceID;

        #define UNITY_GET_INSTANCE_ID(input) _GETINSTANCEID(input)

    #else

        #define DEFAULT_UNITY_VERTEX_INPUT_INSTANCE_ID uintinstanceID : SV_InstanceID;

        #define UNITY_GET_INSTANCE_ID(input) input.instanceID

    #endif

#else

    #define DEFAULT_UNITY_VERTEX_INPUT_INSTANCE_ID

#endif

        只要声明了绑定了SV_InstanceID语义的实例id变量,在输入组装阶段渲染流水线会为其赋值上一个实例id,然后在每一次绘制调用时,实例id变量的值将会加1,当此值超过了232-1之后,将会重置为0。

5.4.4 UNlTY_lNSTANClNG_BUFFER_START及另外两个配套的宏的定义

// 所在文件:UnityInstancing.cginc代码

// 所在目录:CGIncludes

// 从原文件第171行开始,至第181行结束

#if defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE) ||

      defined(SHADER_API_METAL) || defined(SHADER_API_VULKAN)

          #define UNITY_INSTANCING_CBUFFER_START(name) cbuffer

UnityInstancing_##name {

          #define UNITY_INSTANCING_CBUFFER_END }

      #else

          #define UNITY_INSTANCING_CBUFFER_START(name)

                                  CBUFFER_START(UnityInstancing_##name)

          #define UNITY_INSTANCING_CBUFFER_END CBUFFER_END

      #endif

          #define UNITY_DEFINE_INSTANCED_PROP(type, name)

                                            type

name[UNITY_INSTANCED_ARRAY_SIZE];

5.4.5 UNlTY_lNSTANCED_ARRAY_SlZE宏的定义

// 所在文件:UnityInstancing.cginc代码

// 所在目录:CGIncludes

// 从原文件第157行开始,至第166行结束

#ifndef UNITY_MAX_INSTANCE_COUNT

      #define UNITY_MAX_INSTANCE_COUNT 500

#endif

#if (defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE) ||

        defined(SHADER_API_METAL))

&& !defined(UNITY_MAX_INSTANCE_COUNT_GL_SAME)

      #define UNITY_INSTANCED_ARRAY_SIZE (UNITY_MAX_INSTANCE_COUNT / 4)

#else

      #define UNITY_INSTANCED_ARRAY_SIZE UNITY_MAX_INSTANCE_COUNT

#endif

5.4.6 UNlTY_SETUP_lNSTANCE_lD宏的定义

// 所在文件:UnityInstancing.cginc代码

// 所在目录:CGIncludes

// 从原文件第147行开始,至第149行结束

#if !defined(UNITY_SETUP_INSTANCE_ID)

#define UNITY_SETUP_INSTANCE_ID(input) DEFAULT_UNITY_SETUP_INSTANCE_ID(input)

#endif

5.4.7 DEFAULT_UNlTY_SETUP_lNSTANCE_lD和UNlTY_TRANSFER_lNSTANCE_lD宏的定义

// 所在文件:UnityInstancing.cginc代码

// 所在目录:CGIncludes

// 从原文件第120行开始,至第145行结束

5.4.8 着色器常量缓冲区UnityDrawCallInfo的定义

// 所在文件:UnityInstancing.cginc代码

// 所在目录:CGIncludes

// 从原文件第52行开始,至第55行结束

GLOBAL_CBUFFER_START(UnityDrawCallInfo)

    // 当前渲染批次的起始实例,在实例数组中的数组索引值

    intunity_BaseInstanceID;

    // 本渲染批次中对象实例的个数,如果是立体渲染,那么就是在双次渲染前的对象实例个数

    intunity_InstanceCount;

GLOBAL_CBUFFER_END

        Unity 3D引入了用来提高渲染效率的批次化渲染(batch render)技术。此技术的基本原理就是通过将一些渲染状态(render state)一致的待渲染对象组成一组,一次性提交给GPU进行绘制,而不需要来回地设置渲染状态,这可以显著地节省绘制调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值