OpenGL ES2 對象樹繪製與 VBO組織問題?

我現在想繪製一個對象樹,就是每個節點有一個變換矩陣,有一些頂點,然後有下級節點,上層變換矩陣會影響下層,在具體編碼時碰到四個問題,一時難以抉擇,麻煩各位幫看看:

問題1:這些頂點坐標變換我是cpu算好呢?還是扔給vs去算,如果cpu算呢,代價會不會很高?如果扔給vs算呢,好像我每繪製一個新節點,都需要重新設置下vs的uniform成新矩陣,然後再draw,這樣節點多了後,頻繁設置vs的uniform和頻繁提交draw會不會不太好?由於對象樹上層矩陣會影響下層矩陣(乘法),在組織我的對象樹各層頂點時,我需要維護一個類似矩陣棧么?

問題2:每幀繪製時,因為頂點是不固定的(比如2D遊戲,大量活動圖片每幀坐標都在改變,每個節點就是只有矩形的四個頂點,每四個頂點就消耗一個VBO?或者每四個頂點就設置一次矩形的mvp uniform么再提交一次繪製?),那每幀我都要重新計算並填充所有vbo,這樣和不使用vbo有什麼區別呢?直接glVertexAttribPointer指向指針有何區別呢?

問題3:由上,如果繼續使用vbo,那由於我最終渲染多少個頂點是不確定的,vbo創建時候該開多大呢?每幀先把頂點算好放在vector裡面,然後根據vector大小創建或者調整vbo大小么?

問題4:需要創建多個vbo,比如按照shader/紋理兩個緯度來切分vbo,提交頂點的時候,對應的shader+紋理提交到對應的vbo裡面去,然後再分shader/紋理去提交vbo的繪製么?還是先在vector裡面按照shader/紋理排序然後塞到同一個vbo里分段更改狀態並提交draw?

問題5:桌面版OpenGL怎麼那麼慢?我就是800x600的窗口繪製一個鋪滿窗口的矩形紋理,每次vbo裡面重複設置100次,然後提交繪製。什麼事情都沒幹,每秒總共也只能繪製3000次,在系統內存到系統內存的cpu繪製 800x600也可以達到2800次每秒,為啥在顯存中它像素填充率居然那麼低呢?如果一個覆蓋滿全屏的紋理每秒只能繪製3000次,我如果50幀每秒的話,每幀圖像這樣的紋理最多只能繪製60張了?這速度並不比cpu軟體繪製快多少啊?機器配置是雙顯:Intel HD3000和nv4200M,兩個顯卡都試過,性能差不多,雖然顯卡很破,也不至於才比cpu軟體快那麼一點點啊?否則它們存在的意義是啥?


謝題主邀。。。題主問的是效率問題,那我先給你一句話:脫離了性能評測談優化都是耍流氓。

本來不想回答這問題的,但是看了一眼問題的內容和別人的回答之後,我決定強答。

你的問題我大概總結了一下,那就是:

我該不該通過CPU端多做一些工作,來降低drawcall的使用頻率和shader uniform的設置頻率?

這是個好問題。

我們對此類問題有一個通俗的問法:該不該做batch優化?

答案是,也該,也不該。如果題主用的是OpenGL做pc上的3D遊戲,那或許不該。但題主用的是OpenGL ES2.0,做的是2D遊戲。那答案或許就是「應該」了。

我為毛把這兩種情況區別開來,因為兩種情況渲染壓力在不同的地方的可能性太大了。舉個不恰當的例子,所謂3D引擎里的粒子系統都要特別優化,每個粒子的rotation,translation,scaling都不相同,但你見過一個粒子用一個batch的嗎?

而我為毛用「或許」倆字,因為,你看,知乎上俗話說的好,「脫離了劑量談毒性都是耍流氓」,那我也可以仿造一下,「脫離了需求談架構都是耍流氓」。咦,哪裡不對,換成「脫離了性能評測談優化都是耍流氓」好了。

所以,題主你現在最需要做的是,profile。

下面是正經答題。

一般來講,主流pc上中端顯卡每幀幾百個drawcall沒問題。我說的是主流pc,我跟移動端引擎打交道的機會並不多,所以

這些頂點坐標變換我是cpu算好呢?還是扔給vs去算,如果cpu算呢,代價會不會很高?如果扔給vs算呢,好像我每繪製一個新節點,都需要重新設置下vs的uniform成新矩陣,然後再draw,這樣節點多了後,頻繁設置vs的uniform和頻繁提交draw會不會不太好?

這幾個問題,你自己去評測自己的電腦對drawcall有多大的敏感性。

如果一個drawcall全部畫出來的效率跟你扔給vs去算的效率差距不是很大的話,那合併vbo沒有任何意義。當然這麼做也不僅是為了測顯卡,更重要的是測你現在的渲染複雜度能不能使drawcall成為性能瓶頸。

卧槽12點了,題主你先profile,我明天再碼。

-------------

昨晚又仔細看了一下題主的幾個問題,發覺題主還是提供了一些統計信息的。

我就是800x600的窗口繪製一個鋪滿窗口的矩形紋理,每次vbo裡面重複設置100次,然後提交繪製。什麼事情都沒幹,每秒總共也只能繪製3000
次,在系統內存到系統內存的cpu繪製
800x600也可以達到2800次每秒,為啥在顯存中它像素填充率居然那麼低呢?如果一個覆蓋滿全屏的紋理每秒只能繪製3000次,我如果50幀每秒的
話,每幀圖像這樣的紋理最多只能繪製60張了?

在題主的GPU上,100次繪製是30fps。而在使用了CPU計算,通過一次drawcall繪製出來的情況下,28fps。

這種情況基本可以猜測是達到你硬體的性能瓶頸了。扯句題外話,題主這是一個集顯一個核顯?這種單純drawcall開銷,GPU基本沒有什麼運算開銷的情況下,集顯可能會比獨顯快。因為這只是個單純次數頻繁地往GPU提交數據。

不過本著嚴謹的態度,我建議題主試一下去掉你的CPU計算,只一次把這100個矩形畫出來,看一下幀率能快多少。

如果快很多的話,那自然就要嘗試合併drawcall了。

那就往下看。

事實上,無論是設置shader uniform,還是draw,它都是CPU開銷。GPU在過多的drawcall,每次繪製的頂點數卻很少的情況下,根本沒有滿負荷工作。有很多資料都提到這一點。

以上截自http://mathinfo.univ-reims.fr/IMG/pdf/_GDC2003_Optimizing_the_Graphics_Pipeline_Cebenoyan_Wloka_.pdf

早期類似的資料有一大堆https://www.nvidia.com/docs/IO/8228/BatchBatchBatch.pdf

近幾年由於移動平台的興起,該問題常常被重新提出:

opengl - OpenGLES 2.0 batching method and do not draw inactive object

尤其對2D遊戲來說,這裡的優化是非常有必要的。因為2D遊戲的美術資源量有限,要麼是貼圖拼在一起,用網格和貼圖上極小的幾塊像素來繪製,要麼是使用貼圖上的tile來構建場景——場景里有很多東西的貼圖是共用的。共用貼圖不僅降低了讀取存儲設備時的巨大開銷,還提高了batch合併的可能性。

關於如何具體提高你程序的效率,我們後面再說。首先回答你另外幾個問題。

由於對象樹上層矩陣會影響下層矩陣(乘法),在組織我的對象樹各層頂點時,我需要維護一個類似矩陣棧么?

需要。大部分渲染引擎都具有樹形結構。對2D遊戲來說,骨骼動畫(不是指蒙皮動畫,而是說剛體的2D骨骼動畫)就是基於這樣的數據結構來的。

但是這還是取決於你的需求。邏輯上這種數據結構是必須的,但存儲結構上有沒有「棧」這種東西由你決定。

你甚至可以把你的樹展開成線性的,只要你邏輯上保留樹形結構,搞明白怎麼計算就行。

每幀繪製時,因為頂點是不固定的(比如2D遊戲,大量活動圖片每幀坐標都在改變,每個節點就是只有矩形的四個頂點,每四個頂點就消耗一個VBO?或者每四
個頂點就設置一次矩形的mvp
uniform么再提交一次繪製?),那每幀我都要重新計算並填充所有vbo,這樣和不使用vbo有什麼區別呢?

這個問題和之前的問題是一個問題。答案是四個頂點消耗一個VBO實在太浪費,你需要合併相同貼圖的VBO。不要「每四個頂點就設置一次矩形的mvp
uniform么再提交一次繪製」,mvp用CPU算。

我注意到你使用了mvp這個詞。你確定2D遊戲需要projection?而且,就純2D遊戲來說,3x3的矩陣實在是夠用了吧?即使100個,計算開銷也不是很大吧?

直接
glVertexAttribPointer指向指針有何區別呢?

你可以使用它。但是一幀調用100次不會比現在快。glVertexAttribPointer原則上比使用VBO慢點,不過OpenGL的驅動優化得好,沒有Direct3D9的DrawPrimitiveUp那麼慢。但是100個drawcall對你的電腦來說依然慢,這沒有絲毫改變。所以該優化的還是得優化。

由上,如果繼續使用vbo,那由於我最終渲染多少個頂點是不確定的,vbo創建時候該開多大呢?每幀先把頂點算好放在vector裡面,然後根據vector大小創建或者調整vbo大小么?

真聰明。這是個策略問題,具體開多大需要你自己去評測性能。

最簡單的策略,你可以開一個大的vbo,只有在數據變多的情況下才刪掉重建。反正你繪製的時候可以指定offset和元素個數。

需要創建多個vbo,比如按照shader/紋理兩個緯度來切分vbo,提交頂點的時候,對應的shader+紋理提交到對應的vbo裡面去,然後再分
shader/紋理去提交vbo的繪製么?還是先在vector裡面按照shader/紋理排序然後塞到同一個vbo里分段更改狀態並提交draw?

區別不大。但就CPU和GPU交互頻率來說,後者肯定快一些。快不是絕對的,我不能保證你前者的CPU代碼寫得比後這快導致前者的性能評測結果比後者快。

----------------------------------------------------------------------------

以上是對題主問題的回答。

對題主的若干優化建議:

a.將場景分為靜態和動態部分。靜態部分只在場景載入時計算頂點,之後就不需要計算了。

b.可以參考我在材質排序和Batch合併方面有什麼優秀的資料? - Snafloda 的回答中提到的Unity3D使用的優化方法。它的方法用OpenGL描述的話是這樣:

需要使用IBO。場景中靜態物體放到一個或多個大的VBO中,每幀根據繪製需要繪製的物體的可見性排序後拿出indices,重新生成填充IBO的數據。可以一起繪製的相同材質的頂點放到IBO中使之連續地放在一起。然後一口氣畫出來。

這個做法只填了IBO,對CPU的處理非常友好。移動平台上的OpenGL ES2.0,一個index只能是16位的。

相較之如果填充VBO的話,你需要填充一個float的position和兩個float的uv。無論是採用memcpy的方法,還是一個個填,開銷都比前者大。用CPU一個個填的話,cache失效率比前者大。

c.我前面提到了,你可以用3x3 的矩陣,這完全夠用。幾百幾千個矩陣運算對現代CPU來說問題並不太大。關鍵在於優化好內存布局,減少cache miss。很多情況下的數學計算開銷都在cache miss上,CPU純粹計算的開銷很小很小。這方面也有一大堆資料,自己找吧。


1:

vertex shader就是做頂點變換的啊啊啊!你在host端,把節點的矩陣算出來就好了。

2:

如果頂點的數量沒變,你直接更新vertex buffer就好了啊;如果拓撲結構沒變,你連index buffer都不需要動。vertex array object一直不需要動啊!

3:

通常做法是每個單獨的幾何體一個vertex buffer和index buffer。

4:

我猜你想要的是一個對象有不同的材質。那麼通常應當把它分成幾個單獨的幾何體,每種材質一個。

5:

什麼叫「系統內存到系統內存的cpu繪製」?

另外,你至少可以使用instanced drawing,把不同的貼圖拼湊成Texture 2D array,然後在shader里依據instance ID選擇用哪個貼圖。


樓主描述的是骨骼動畫


推薦閱讀:

請問OpenGL的的頂點數據中position必須與color一一對應嗎?
如何正確理解 opengl 的 vao ?
一直說要對不同渲染狀態的對象進行排序 但是排序的優先順序應該是什麼呢?

TAG:遊戲引擎 | OpenGL | OpenGLES |