前言:
如何在Unity實作Compute shader與 GPU instance請看之前的文章 :
連結
GPU Instance
Instancing是一種技術,可以在單個渲染調用中一次性繪製多個相同的模型數據,從而節省了每次渲染物體時的CPU到GPU傳輸耗時。傳統的渲染方式需要多次調用glDrawArrays或glDrawElements等函數,這些函數在CPU到GPU之間的通信成本較高。Instancing則通過一次性將所有所需數據發送到GPU,然後使用單個渲染調用告訴GPU如何繪製所有這些實例,從而提高性能。
在Instancing渲染調用中,頂點著色器中有一個內置變量gl_InstanceID。每次渲染一個實例時,gl_InstanceID都會自動遞增,從0開始。
Uniform Array作法
- 使用uniform array裝位置data,但缺點是uniform 有大小限制。
定義資料:
裝去buffer
渲染
簡單的vertex與frag shader
Instanced Array 作法
Vertex Shader改成用參數接收offset:
多一個attib buffer裝offset,並用glVertexAttribDivisor。
在使用Instancing技術時,我們可能需要在多個實例之間共享相同的頂點屬性,例如位置、法向量或顏色。glVertexAttribDivisor 允許我們指定每個實例之間取資料的間隔,以便在渲染多個實例時,這些通用頂點屬性可以按不同的間隔讀資料。
渲染 (結果會得到一樣的)
渲染3D模型可以用:
Compute shader
- compute shader由工作組(work group)組成(x,y,z三軸),這是最小的計算操作單位。在調用時需定義工作組的數量。
- 若要傳遞結構化資料,建議新手全都用vec4,不然因為layout協定的不同,讀取方式可能跟預想的會有誤差。
範例: 用compute shader讓方塊移動
定義資料
定義buffer (把ssbo當vbo bind)
- Shader Storage Buffer Object(SSBO,頂點儲存緩衝區物件)可以比Uniform Buffer Objects(UBOs)更大,並且支持寫入操作,支持atomic操作。
- glMemoryBarrier 的作用是確保先前的記憶體訪問在後續的渲染命令中被正確處理,所以要等待大家都執行完。
Render Loop
Compute Shader
我們在compute shader改寫ssbo中的資訊,接著就可以正常的用vertex shader與fragment shader渲染。
加上Instancing
Init的時候創建offset
Render loop 改成使用glDrawArraysInstanced
DrawIndirect
在OpenGL中,DrawInstance 和 DrawIndirect 都是用於批量繪製多個實例的方法,但它們之間有一些重要的區別。
DrawInstance:
- 功能:DrawInstance 是一個用於在單個渲染調用中繪製多個實例的功能。它允許我們一次性發出一個繪製調用,並在GPU上呈現具有相同網格的多個實例。
- 適用情況:適用於需要繪製大量移動不明顯或只在著色器中進行頂點動畫的物體,例如樹木、草地等。它避免了每個GameObject的沉重開銷。
- 限制:如果移動這些物體,可能需要執行大量循環語句,這可能會降低性能。
DrawIndirect:
- 功能:DrawIndirect 是一個更靈活的方法,它使用間接繪製命令。它不需要在CPU和GPU之間傳遞大量的數據,而是使用緩衝區來存儲繪製命令的參數。
- 適用情況:適用於需要動態生成繪製命令的情況,例如根據遊戲狀態或用戶輸入來繪製不同的實例。
- 優點:避免了在CPU和GPU之間的頻繁數據傳輸,並且可以在運行時動態修改繪製命令。
用DrawIndirect做:
定義Commond
建立Buffer (這次多了draw command buffer)
Render
vertex shader跟之前一樣。
用Draw Indirect跟Compute shader互動
範例: 用compute shader改變drawIndirect的數量
把command buffer id bind到compute shader
Compute shader
執行多個DrawCommand (MultiDraw)
定義draw command array:
Render改成用glMultiDrawArraysIndirect
兩個不同顏色的方塊/三角形疊在一起。
Frustum culling
視錐體剔除(Frustum Culling) 是在渲染過程中從視錐體之外移除物體的過程。這些物體完全不在視錐體內,因此渲染它們將是一種資源浪費,因為它們並不直接可見。
視錐體:
- 視錐體通常用於描述相機的視野區域。它是一個錐形,但在近和遠處有截斷,因此我們稱之為視錐體。
- 視錐體由6個平面組成:近、遠、右、左、上和下平面。這些平面定義了相機的可視區域。
範例作業:
在作業中,我需要用Multi-draw畫出3種不同的草,並且使用Frustum culling優化。場上會有之吃草怪物,碰到的草就不能再出現。
Frustum culling主要包含3個動作: Collect , Render , Reset。
- Collect : 判斷哪些物件需要被畫出來,收集在一個buffer中。
- Render: 渲染物件
- Reset: 重置Collect階段使用的buffer。
在Collect的compute shader中,我需要算出每個instance unique的index (不然物件會閃),然後再判斷這個物件是否需要被畫出來。
詳細可看IndirectInstancedMesh.cpp