Unity的GPU Instancing

GPU Instancing可以用来批量绘制大量相同几何结构相同材质的物体,以降低绘制所需的batches。要想在Unity中使用,首先需要至少在shader的某个pass中加上#pragma multi_compile_instancing。由于instancing的每个物体所需要的绘制数据可能各不相同,因此还需要在shader中传递一个instanceId:

struct VertexData {UNITY_VERTEX_INPUT_INSTANCE_IDfloat4 vertex : POSITION;…
};

UNITY_VERTEX_INPUT_INSTANCE_ID宏定义如下:

// - UNITY_VERTEX_INPUT_INSTANCE_ID     Declare instance ID field in vertex shader input / output struct.
#   define UNITY_VERTEX_INPUT_INSTANCE_ID DEFAULT_UNITY_VERTEX_INPUT_INSTANCE_ID#if defined(UNITY_INSTANCING_ENABLED) || defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) || defined(UNITY_STEREO_INSTANCING_ENABLED)#ifdef SHADER_API_PSSL#define DEFAULT_UNITY_VERTEX_INPUT_INSTANCE_ID uint instanceID;#else#define DEFAULT_UNITY_VERTEX_INPUT_INSTANCE_ID uint instanceID : SV_InstanceID;#endif#else#define DEFAULT_UNITY_VERTEX_INPUT_INSTANCE_ID
#endif

其实就是在启用gpu instancing时定义一个instanceID。

除此之外,我们需要在shader的开头部分使用UNITY_SETUP_INSTANCE_ID宏进行设置:

InterpolatorsVertex MyVertexProgram (VertexData v) {InterpolatorsVertex i;UNITY_INITIALIZE_OUTPUT(Interpolators, i);UNITY_SETUP_INSTANCE_ID(v);i.pos = UnityObjectToClipPos(v.vertex);…
}

UNITY_SETUP_INSTANCE_ID宏展开如下:

// - UNITY_SETUP_INSTANCE_ID        Should be used at the very beginning of the vertex shader / fragment shader,
//                                  so that succeeding code can have access to the global unity_InstanceID.
//                                  Also procedural function is called to setup instance data.
#   define UNITY_SETUP_INSTANCE_ID(input) DEFAULT_UNITY_SETUP_INSTANCE_ID(input)#define DEFAULT_UNITY_SETUP_INSTANCE_ID(input)          { UnitySetupInstanceID(UNITY_GET_INSTANCE_ID(input)); UnitySetupCompoundMatrices(); }

这个宏主要做了两件事,第一是设置全局的unity_InstanceID变量,该变量用于索引shader用到的各类内置矩阵(例如object to world)的数组:

void UnitySetupInstanceID(uint inputInstanceID){#ifdef UNITY_STEREO_INSTANCING_ENABLED#if defined(SHADER_API_GLES3)// We must calculate the stereo eye index differently for GLES3// because otherwise,  the unity shader compiler will emit a bitfieldInsert function.// bitfieldInsert requires support for glsl version 400 or later.  Therefore the// generated glsl code will fail to compile on lower end devices.  By changing the// way we calculate the stereo eye index,  we can help the shader compiler to avoid// emitting the bitfieldInsert function and thereby increase the number of devices we// can run stereo instancing on.unity_StereoEyeIndex = round(fmod(inputInstanceID, 2.0));unity_InstanceID = unity_BaseInstanceID + (inputInstanceID >> 1);#else// stereo eye index is automatically figured out from the instance IDunity_StereoEyeIndex = inputInstanceID & 0x01;unity_InstanceID = unity_BaseInstanceID + (inputInstanceID >> 1);#endif#elseunity_InstanceID = inputInstanceID + unity_BaseInstanceID;#endif}

第二就是重新定义常用的矩阵:

        void UnitySetupCompoundMatrices(){unity_MatrixMVP_Instanced = mul(unity_MatrixVP, unity_ObjectToWorld);unity_MatrixMV_Instanced = mul(unity_MatrixV, unity_ObjectToWorld);unity_MatrixTMV_Instanced = transpose(unity_MatrixMV_Instanced);unity_MatrixITMV_Instanced = transpose(mul(unity_WorldToObject, unity_MatrixInvV));}

注意这里的unity_ObjectToWorldunity_WorldToObject也已经被重新定义过了:

        #define unity_ObjectToWorld     UNITY_ACCESS_INSTANCED_PROP(unity_Builtins0, unity_ObjectToWorldArray)#define MERGE_UNITY_BUILTINS_INDEX(X) unity_Builtins##X#define unity_WorldToObject     UNITY_ACCESS_INSTANCED_PROP(MERGE_UNITY_BUILTINS_INDEX(UNITY_WORLDTOOBJECTARRAY_CB), unity_WorldToObjectArray)inline float4 UnityObjectToClipPosInstanced(in float3 pos){return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));}inline float4 UnityObjectToClipPosInstanced(float4 pos){return UnityObjectToClipPosInstanced(pos.xyz);}#define UnityObjectToClipPos UnityObjectToClipPosInstanced

开启gpu instancing时,这里实际上就是用instanceId去对应的矩阵数组中进行索引。

正是因为每次batch都需要传递给gpu的是矩阵数组而不是矩阵本身,batch的大小需要进行限制,即最多一次只会将有限数量的几何体合并到一个batch进行gpu instancing。unity定义了一个UNITY_INSTANCED_ARRAY_SIZE宏来表示最大数量的限制。

gpu instancing同样支持阴影和多光源的情况。对于阴影,只需要在shadow caster的pass中加上对应的instancing声明即可:

#pragma multi_compile_shadowcaster
#pragma multi_compile_instancingstruct VertexData {UNITY_VERTEX_INPUT_INSTANCE_ID
};InterpolatorsVertex MyShadowVertexProgram (VertexData v) {InterpolatorsVertex i;UNITY_SETUP_INSTANCE_ID(v);
}

对于多光源的情况,则需要使用延迟渲染路径:

然而,默认的gpu instancing只能支持相同材质,这在使用时会很不方便,有时候可能仅仅想要修改材质的某个属性,例如这里修改不同球体的颜色,会导致instancing失效:

我们可以使用MaterialPropertyBlock来避免修改颜色时创建出新的材质:

         MaterialPropertyBlock properties = new MaterialPropertyBlock();properties.SetColor("_Color", new Color(Random.value, Random.value, Random.value));t.GetComponent<MeshRenderer>().SetPropertyBlock(properties);

为了在shader代码中使用到此属性,需要在instancing buffer中对其定义:

UNITY_INSTANCING_BUFFER_START(InstanceProperties)UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
#define _Color_arr InstanceProperties
UNITY_INSTANCING_BUFFER_END(InstanceProperties)

对宏进行展开,可以发现就是定义了一个包含struct数组的cbuffer,其中struct中定义了我们新增的属性:

    #define UNITY_INSTANCING_BUFFER_START(buf)      UNITY_INSTANCING_CBUFFER_SCOPE_BEGIN(UnityInstancing_##buf) struct {#define UNITY_INSTANCING_BUFFER_END(arr)        } arr##Array[UNITY_INSTANCED_ARRAY_SIZE]; UNITY_INSTANCING_CBUFFER_SCOPE_END#define UNITY_DEFINE_INSTANCED_PROP(type, var)  type var;

如果要把vertex shader中使用的instanceId传递到fragment shader,可以使用unity提供的UNITY_TRANSFER_INSTANCE_ID

InterpolatorsVertex MyVertexProgram (VertexData v) {InterpolatorsVertex i;UNITY_INITIALIZE_OUTPUT(Interpolators, i);UNITY_SETUP_INSTANCE_ID(v);UNITY_TRANSFER_INSTANCE_ID(v, i);…
}

这个宏定义很简单:

    #define UNITY_TRANSFER_INSTANCE_ID(input, output)   output.instanceID = UNITY_GET_INSTANCE_ID(input)

那么最终要如何正确读取这个cbuffer的属性呢?这里Unity也提供了配套的宏:

float3 GetAlbedo (Interpolators i) {float3 albedo =tex2D(_MainTex, i.uv.xy).rgb * UNITY_ACCESS_INSTANCED_PROP(_Color_arr, _Color).rgb;...
}

这个宏定义也很简单,就是从之前定义的struct数组中,根据instanceId进行索引,再取出对应的变量:

    #define UNITY_ACCESS_INSTANCED_PROP(arr, var)   arr##Array[unity_InstanceID].var

经过修改之后,再次运行,可以发现batch降低了,instancing生效了:

如果你觉得我的文章有帮助,欢迎关注我的微信公众号:Game_Develop_Forever

Reference

[1] GPU Instancing

[2] (四)unity自带的着色器源码剖析之——————Unity3D 多例化技术(GUI Instancing)

Unity的GPU Instancing相关推荐

  1. Unity GPU Instancing的使用尝试

    似乎是在Unity5.4中开始支持GPU Instacing,但如果要比较好的使用推荐用unity5.6版本,因为这几个版本一直在改. 这里测试也是使用unity5.6.2进行测试 在5.6的版本里, ...

  2. unity gpu instancing

    Unity gpu instancing unity可以自动合并相同的material对象渲染 将对应shader enable instancing的选项勾上 本文说明一下直接调用unity api ...

  3. 【Unity游戏开发】静态、动态合批与GPU Instancing

    https://zhuanlan.zhihu.com/p/356211912 前言 动态合批与静态合批其本质是对将多次绘制请求,在允许的条件下进行合并处理,减少cpu对gpu绘制请求的次数,达到提高性 ...

  4. unity SRP Batcher与GPU instancing使用情况

    SRP Batcher更合适大量不同的物体, 比如材质上用了不同的贴图.参数.等等,只要shader变种不变,即使不同材质也能合并: GPU instancing 必须同材质同参数,只是可以自定义ma ...

  5. 每天执行一次批处理_关于静态批处理/动态批处理/GPU Instancing /SRP Batcher的详细剖析...

    静态批处理[1] 定义 标明为 Static 的静态物件,如果在使用相同材质球的条件下,在Build(项目打包)的时候Unity会自动地提取这些共享材质的静态模型的Vertex buffer和Inde ...

  6. 闲云野鹤:吃鸡(一)之场景制作:使用GPU instancing方式制作刷草插件

    用GPU instancing方式制作刷草插件(unity版本8.2.2) 先上最终效果图(欢迎加我qq交流:358641634): 十种草 混刷生成比较自然的场景(带阴影.风力.草可见距离可调) 插 ...

  7. Unity3D GPU Instancing测试

    GPU instancing 很早就支持手机了(Android只支持Opengl ES 3.0),最近在调研这个就对它测试了一下. 如果是不动的物体勾选static静态合并批次(40-50帧率) 自定 ...

  8. Unity Shader - URP Instancing

    URP 中的内置 GPU Instancing 的使用,和 Built-in RP 之前的宏定义名字是一样的,而且功能也是一样的,所以:使用方法和 Built-in RP 中没任何却别 Shader ...

  9. GPU instancing介绍

    从Unity5.4开始,多了一个叫做GPU instancing的功能.这个功能是做什么用的呢?下面做一个小例子来说明一下: 还是之前做 MaterialPropertyBlock的例子,在屏幕上面做 ...

最新文章

  1. 框架警察 fxcop 的规则莫名其妙
  2. python乘法口诀代码-浅析一句python代码成生九九乘法表
  3. 多个线程作用于同一个runnable对象
  4. 第三次学JAVA再学不好就吃翔(part9)--基础语法之键盘录入
  5. python 生成pdf页面大小_使用具有自定义大小页面和最佳图像分辨率的Reportlab生成PDF...
  6. 多重句柄怎么处理_golang异常处理详解
  7. 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_02 递归_1_递归概念分类注意事项...
  8. 硬盘数据恢复方法 固态硬盘数据恢复方法
  9. Spring揭秘——什么是IOC和DI
  10. LaTeX(Overleaf)写作笔记
  11. 【学术相关】iccv、cvpr、eccv论文接收率及格式下载(附论文下载)
  12. RS485通讯协议的应用
  13. 使用PS把证件照背景变成白色
  14. 6种摆脱百度竞价恶意点击的技巧
  15. 原生JS写一个首字母排序的通讯录效果
  16. 什么是数据标准化?在Python中如何进行数据标准化?「必学」
  17. switchport mode access
  18. vite+ts+vue3 知识点(定义全局函数和变量)
  19. 图像相似度匹配——距离大全
  20. Maven编译失败: zip file is empty

热门文章

  1. 网络安全基础知识入门!网络安全学习教程
  2. x265编码H265
  3. python box_箱体图Boxplot及Python绘制方
  4. MySQL数据库修改表某一列数据(一整列)
  5. ABBYY FineReader OCR图文识别软件如何快速将纸质文档转为电子档教程
  6. 游戏厂商出海记:韩国内卷严重,其它地区占到什么地盘?
  7. 使用python对指定手机号获取各网站登录的验证码。
  8. 性能测试的实施及总结(二)
  9. 深耕技术,与实践赛跑:一文告诉你如何稳妥快速完善区块链技术并有序推动商用​?...
  10. python绘制花朵图案_Python实现平行坐标图的绘制(plotly)方式