今天一個渲染群群友提出了在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.