URP下GPU Instance以及IndirectDraw探究

今天一个渲染群群友提出了在URP下无法IndirectDraw的问题,直接使用的官方文档的代码。我们知道DrawMeshInstancedIndirect方法在官方文档上有两个支持的shader,一个是表面着色器,一个是顶点片元着色器。而URP下不支持表面着色器,所以群友把顶点片元着色器拿到了URP下使用,结果发现DrawIndirect并不能正确执行。问题出在了哪里呢?

对于想快速得到答案的小伙伴们,这里先给出结论:

首先必须要加这一句预编译指令:

#pragma instancing_options procedural:setup

setup方法里面是用来处理InstanceID计算出来后去怎么处理数据,如果用不到可以不填充这个方法,但是必须实现。

void setup()
            {

            }

如上,空着就可以,这个预编译指令主要是用来告诉unity我们需要drawIndirect。

然后不能直接从SV_InstanceID中拿instanceID,必须先定义在顶点的输入和输出结构中。

之前错误的代码是这样的:

 Varyings vert(Attributes v,uint instanceID: SV_InstanceID)
            {
                xxxxx
            }

而正确的做法是这样的:

struct Attributes
            {
                float3 positionOS : POSITION;
                float2 uv :TEXCOORD0;
        UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            // 顶点着色器的输出
            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv :TEXCOORD0;
            UNITY_VERTEX_INPUT_INSTANCE_ID
            };
Varyings vert(Attributes v)
            {
                Varyings o = (Varyings)0;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                xxxxxxxxxxxxxxxxxxxxx
                return o;
            }

然后要获取instanceID可以通过unity_InstanceID拿到。

结论讲完说一下原因:

首先第一个预编译指令存在的意义是如果我们只有:

#pragma multi_compile_instancing

当IndirectDraw的时候你会发现UNITY_INSTANCING_ENABLED这个keyword是关闭的。而正常情况下用Renderer渲染时是打开的。为什么会这样呢?因为Unity把这两种渲染方式做了区分。

使用Renderer渲染时当开启GPU Instancing,会激活UNITY_INSTANCING_ENABLED,但是当DrawIndirect的时候,Unity希望我们激活的是UNITY_PROCEDURAL_INSTANCING_ENABLED这个keyword,所以会把UNITY_INSTANCING_ENABLED关闭掉。那么如果我们没有定义上面instancing_options procedural:setup的预编译指令,连UNITY_PROCEDURAL_INSTANCING_ENABLED也不会被激活,所以就无法Instance了。这是为什么必须要这个预编译指令。

那么这个预编译指令后面的setup方法是用来干什么呢?他提供了一个当InstanceID计算完成后处理数据的阶段。比如如何通过InstanceID去取数据之类的。

第二个点是为什么不能在方法参数里面直接使用SV_InstanceID的语义,而必须定义在结构里面呢?

这个就是内置管线和URP的一点区别,目前具体的原因还不太清楚,但是一般我们让自己的shader支持GPU Instance都是通过手动SETUP进行的。

最后就是关于UNITY_PROCEDURAL_INSTANCING_ENABLED这个宏:

这个就得看UnityInstancing.hlsl这个文件里面对于UNITY_GET_INSTANCE_ID这个宏的定义:

 #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
        #ifndef UNITY_INSTANCING_PROCEDURAL_FUNC
            #error "UNITY_INSTANCING_PROCEDURAL_FUNC must be defined."
        #else
            void UNITY_INSTANCING_PROCEDURAL_FUNC(); // forward declaration of the procedural function
            #define DEFAULT_UNITY_SETUP_INSTANCE_ID(input)      { UnitySetupInstanceID(UNITY_GET_INSTANCE_ID(input)); UNITY_INSTANCING_PROCEDURAL_FUNC();}
        #endif
    #else
        #define DEFAULT_UNITY_SETUP_INSTANCE_ID(input)          { UnitySetupInstanceID(UNITY_GET_INSTANCE_ID(input));}
    #endif

可以看到这个宏只能在setup方法里使用,不能在其他方法比如顶点片元使用,那么当我们想判断是否是Instance的情况时,可以使用这个宏来判断:

UNITY_ANY_INSTANCING_ENABLED

这个宏是可以用在顶点片元着色器的,只要两种Instance有一个激活了,这个宏就会返回1.

posted @ 2021-01-18 20:30  syb7384  阅读(2876)  评论(0编辑  收藏  举报