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.