由浅到浅入门批量渲染(三)
上回簡(jiǎn)單總結(jié)了一下動(dòng)態(tài)合批,這次我們繼續(xù)說(shuō)說(shuō)實(shí)例化渲染。
| 實(shí)例化渲染
當(dāng)我們想要呈現(xiàn)這樣的場(chǎng)景:一片茂密的森林、廣闊的草原或崎嶇的山路時(shí),會(huì)發(fā)現(xiàn)在這些場(chǎng)景中存在大量重復(fù)性元素:樹(shù)木、草和巖石。
仙境怕是也不過(guò)如此吧
它們都使用了相同的模型,或者模型的種類(lèi)很少,比如:樹(shù)可能只有幾種;但為了做出差異化,它們的顏色略有不同,高低參差不齊,當(dāng)然位置也各不相同。
使用靜態(tài)合批來(lái)處理它們(假設(shè)它們都沒(méi)有動(dòng)畫(huà)),是不合適的。因?yàn)?strong>數(shù)量太多(林子大了,多少樹(shù)都有),所以合并后的網(wǎng)格體積可能非常大,這會(huì)引起內(nèi)存的增加;而且,這個(gè)合并后的網(wǎng)格還是由大量重復(fù)網(wǎng)格組成的,不劃算。
使用動(dòng)態(tài)合批來(lái)處理他們,雖然不會(huì)“合并”網(wǎng)格,但是仍然需要在渲染前遍歷所有頂點(diǎn),進(jìn)行空間變換的操作;雖然單顆樹(shù)、石頭的頂點(diǎn)數(shù)量可能不多,但由于數(shù)量很多,所以也會(huì)在一定程度上增加CPU性能的開(kāi)銷(xiāo),沒(méi)必要。
那么,對(duì)于場(chǎng)景中這些模型重復(fù)、數(shù)量多的渲染需求,有沒(méi)有適合的批處理策略呢?有吧,實(shí)例化渲染就是為了解決這樣的問(wèn)題。
| 簡(jiǎn)述工作原理
實(shí)例化渲染,是通過(guò)調(diào)用“特殊”的渲染接口,由GPU完成的“批處理”。
它與傳統(tǒng)的渲染方式相比,最大的差別在于:調(diào)用渲染命令時(shí)需要告知GPU這次渲染的次數(shù)(繪制N個(gè))。當(dāng)GPU接到這個(gè)命令時(shí),就會(huì)連續(xù)繪制N個(gè)物體到我們的屏幕上,其效率遠(yuǎn)高于連續(xù)調(diào)用N次傳統(tǒng)渲染命令的和(一次繪制一個(gè))。
舉個(gè)例子,假設(shè)希望在屏幕上繪制出兩個(gè)顏色、位置均不同的箱子。如果使用傳統(tǒng)的渲染,則需要調(diào)用兩次渲染命令(DrawCall = 2),分別為:畫(huà)一個(gè)紅箱子 和 畫(huà)一個(gè)綠箱子。
兩個(gè)顏色、位置各異的箱子
如果使用實(shí)例化渲染,則只需要調(diào)用一次渲染命令(DrawCall = 1),并且附帶一個(gè)參數(shù)2(表示繪制兩個(gè))即可。
當(dāng)然,如果只是這樣,那GPU就會(huì)把兩個(gè)箱子畫(huà)在相同的位置上。所以我們還需要告訴GPU兩個(gè)箱子各自的位置(其實(shí)是轉(zhuǎn)換矩陣)以及顏色。
這個(gè)位置和顏色我們會(huì)按照數(shù)組的方式傳遞給GPU,大概這個(gè)樣子吧:
分別傳遞保存位置和顏色的數(shù)組
那接下來(lái)GPU在進(jìn)行渲染時(shí),就會(huì)在渲染每一個(gè)箱子的時(shí)候,根據(jù)當(dāng)前箱子的索引(第幾個(gè)),拿到正確的屬性(位置、顏色)來(lái)進(jìn)行繪制了。
一個(gè)簡(jiǎn)單的實(shí)例化渲染流程
?
| Unity是如何處理實(shí)例化的
我們通過(guò)一個(gè)簡(jiǎn)單的場(chǎng)景,來(lái)看一下Unity為實(shí)例化渲染做了什么。
實(shí)例化渲染兩個(gè)彩色箱子
顏色屬性通過(guò)MaterialPropertyBlock傳入
通過(guò)GPA觀察Unity做了什么。
GPA中的VertexBuffer和IndexBuffer中的信息
注:Unity默認(rèn)Cube網(wǎng)格,包含24個(gè)頂點(diǎn)和36個(gè)索引。
- 頂點(diǎn)緩沖區(qū)Size = (Position(float3) + Normal(float3) + Tangent(float4) + TexCoord(float2) + TexCoord1(float2)) x 24 = 1344Byte
- 索引緩沖區(qū)Size = Index(ushort) x 36 = 72Byte
可見(jiàn),頂點(diǎn)、索引緩沖區(qū)內(nèi),確實(shí)只有一個(gè)網(wǎng)格的數(shù)據(jù)。
那么GPU如何判斷每個(gè)Cube的繪制位置,及其顏色呢?
結(jié)合引擎為Dx平臺(tái)生成的shader(我的測(cè)試環(huán)境使用的是Pc),可以很容易找到對(duì)應(yīng)的數(shù)據(jù)。
轉(zhuǎn)換矩陣及顏色被分別填入Constant Buffer中
Constant Buffer中的矩陣(Dx為行向量)
Constant Buffer中的屬性(顏色)
可見(jiàn),渲染時(shí)GPU可以通過(guò)當(dāng)前實(shí)例化單位的索引,二手手游賬號(hào)轉(zhuǎn)讓平臺(tái)從Buffer中獲取到對(duì)應(yīng)的屬性,完成正確的繪制。
| Unity中啟用實(shí)例化渲染
當(dāng)然,相比于上述無(wú)用的知識(shí)點(diǎn),如何在Unity中使用實(shí)例化渲染可能更為重要。
在Unity中可以通過(guò)自動(dòng)或手動(dòng)的方式,啟用實(shí)例化渲染。
自動(dòng)啟用實(shí)例化渲染
使用支持實(shí)例化渲染的Shader,并勾選材質(zhì)球上的啟用開(kāi)關(guān),Unity便會(huì)對(duì)滿(mǎn)足條件的物體,自動(dòng)開(kāi)啟實(shí)例化渲染。
有這個(gè)選項(xiàng)即表示該Shader支持實(shí)例化渲染
自定義Shader
如果你希望自己的Shader也支持實(shí)例化渲染,應(yīng)重點(diǎn)注意以下內(nèi)容:
?
#pragma multi_compile_instancing
啟用實(shí)例化渲染(材質(zhì)球上將出現(xiàn)啟用實(shí)例化的勾選框);
UNITY_VERTEX_INPUT_INSTANCE_ID
在a2v及v2f的結(jié)構(gòu)中定義實(shí)例化索引下標(biāo)(SV_InstanceID?),也就是當(dāng)前渲染單位的索引,用于從Constant?Buffer中提取正確的屬性(做顯示差異化用);
UNITY_INSTANCING_BUFFER_START ~ END
在這個(gè)起止區(qū)域內(nèi)定義屬性,才能在著色器中正確的根據(jù)索引提取出當(dāng)前渲染單位所對(duì)應(yīng)的屬性;
UNITY_SETUP_INSTANCE_ID
定義在著色器的起始位置,使頂點(diǎn)著色器(或片段著色器)可以正確的訪(fǎng)問(wèn)到實(shí)例化單位的索引;
UNITY_ACCESS_INSTANCED_PROP
根據(jù)索引訪(fǎng)問(wèn)到這個(gè)單位對(duì)應(yīng)的屬性,如上面例子中每個(gè)箱子的顏色屬性。
這里只是簡(jiǎn)述一些相對(duì)重要的內(nèi)容(湊些字?jǐn)?shù)),官方文檔中有更詳細(xì)內(nèi)容,建議優(yōu)先了解。
手動(dòng)實(shí)例化渲染
使用??Graphics.DrawMeshInstanced?和?Graphics.DrawMeshInstancedIndirect?來(lái)手動(dòng)執(zhí)行 GPU 實(shí)例化,詳見(jiàn)官方文檔中的解釋,這里就不再贅述了。
| 實(shí)例化渲染的使用要求
并非所有設(shè)備都可以使用實(shí)例化渲染。
在Unity官方文檔中,列舉了各平臺(tái)支持實(shí)例化渲染的最低要求。
官方文檔中對(duì)實(shí)例化渲染的最低API支持要求
當(dāng)然,我們也可以通過(guò)引擎中SystemInfo.supportsInstancing屬性來(lái)判斷環(huán)境是否支持實(shí)例化渲染。
那支持實(shí)例化渲染的機(jī)器占比大概是多少呢?由于國(guó)內(nèi)大多數(shù)游戲公司都是以手游項(xiàng)目糊口。所以開(kāi)發(fā)者可能會(huì)更多關(guān)注其在安卓平臺(tái)上的情況。
根據(jù)Android開(kāi)發(fā)者的官方數(shù)據(jù)顯示,截至2020年8月30日,約88%的活躍安卓設(shè)備,都已經(jīng)支持實(shí)例化渲染,所以基本上可以放心使用。
android開(kāi)發(fā)者官網(wǎng)發(fā)布的活躍設(shè)備OpenGL ES版本占比信息
| 與靜、動(dòng)態(tài)合批的差異
靜、動(dòng)態(tài)合批實(shí)質(zhì)上是將可以合批的對(duì)象真正的合并成一個(gè)大物體后,再通知GPU進(jìn)行渲染,也就是其頂點(diǎn)索引緩沖區(qū)中必須包含全部參與合批對(duì)象的頂點(diǎn)信息;因此,可以認(rèn)為是CPU完成的批處理。
?
實(shí)例化渲染是對(duì)網(wǎng)格信息的重復(fù)利用,無(wú)論最終要渲染出幾個(gè)單位,其頂點(diǎn)和索引緩沖區(qū)內(nèi)都只有一份數(shù)據(jù),可以認(rèn)為是GPU完成的批處理。
其實(shí)這么總結(jié)也有點(diǎn)問(wèn)題,本質(zhì)上講:動(dòng)、靜態(tài)合批解決的是合批問(wèn)題,也就是先有大量存在的單位,再通過(guò)一些手段合并成為批次;而實(shí)例化渲染其實(shí)是個(gè)復(fù)制的事兒,是從少量復(fù)制為大量,只是利用了它“可以通過(guò)傳入屬性實(shí)現(xiàn)差異化”的特點(diǎn),在某些條件下達(dá)到了與合批相同的效果。
| 簡(jiǎn)單總結(jié)靜、動(dòng)態(tài)合批及實(shí)例化渲染
無(wú)論是靜態(tài)合批、動(dòng)態(tài)合批或?qū)嵗秩?#xff0c;本質(zhì)上并無(wú)孰優(yōu)孰劣,它們都只是提高渲染效率的解決方案,也都有自己適合的場(chǎng)景或擅長(zhǎng)解決的問(wèn)題。
個(gè)人以為:
如果你的場(chǎng)景中存在多數(shù)靜止的、使用了不同網(wǎng)格、相同材質(zhì)的物體,特別是當(dāng)你的相機(jī)通常只能照到一部分物體時(shí)(如第一視角),可以?xún)?yōu)先嘗試下靜態(tài)合批,通過(guò)犧牲一些內(nèi)存來(lái)提升渲染效率;
針對(duì)那些運(yùn)動(dòng)的、網(wǎng)格頂點(diǎn)數(shù)很少、材質(zhì)相同的物體,比如飛行的各種箭矢、炮彈等,使用動(dòng)態(tài)合批,通過(guò)增加一些CPU處理頂點(diǎn)的性能開(kāi)銷(xiāo),來(lái)提升渲染效率,也許是不錯(cuò)的選擇;
如果有大量模型相同、材質(zhì)相同、或盡管表現(xiàn)上有一些不同,但仍然可以通過(guò)屬性來(lái)實(shí)現(xiàn)這些差異化的物體時(shí),啟用實(shí)例化渲染通常可以在很大程度上提升渲染效率。
| 寫(xiě)在最后
按計(jì)劃下次更新的內(nèi)容應(yīng)該是“優(yōu)化骨骼蒙皮動(dòng)畫(huà),以及兩種常用的批量渲染方式”,但覺(jué)得內(nèi)容有點(diǎn)多,所以將其分為兩個(gè)部分;因此,下次更新的內(nèi)容變?yōu)椤皟?yōu)化骨骼蒙皮動(dòng)畫(huà)”,而“兩種常用的骨骼蒙皮動(dòng)畫(huà)單位的批量渲染方式”,將作為本系列的最后一次更新內(nèi)容。
總結(jié)
以上是生活随笔為你收集整理的由浅到浅入门批量渲染(三)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 由浅到浅入门批量渲染(二)
- 下一篇: 使用LitJson进行序列化和反序列化