BlockOS開發(fā)日志(二)著手設(shè)計(jì)方塊世界

剛發(fā)完第一篇總結(jié)性質(zhì)的文章,那么就趁熱打鐵,先把方塊世界的設(shè)計(jì)方案在這寫明白。
如何渲染
首先如何在電腦上渲染出這些方塊就是一大問題。在mc的1.18版本前,方塊的數(shù)量就非常多。java版mc的渲染距離應(yīng)該是256*256*256,如果讓這個距離里填滿方塊,那么這片區(qū)域的方塊數(shù)量將是16777216個?;仡櫹聢D形學(xué)基礎(chǔ)知識,每個立方體有六個面,每個面有兩片三角形,每個三角形有三個頂點(diǎn),假設(shè)頂點(diǎn)數(shù)據(jù)只有位置和uv,也就是3+2=6個浮點(diǎn)數(shù),單精度浮點(diǎn)數(shù)的字節(jié)數(shù)為4,那么直接用立方體渲染這些方塊需要16777216*6*2*3*6*4=14495514624字節(jié),也就是13.5GB。顯然沒那么多顯存給我們耗,而且從內(nèi)存到顯存的帶寬(以GTX1050Ti為例,112GB/s)也不足以支撐60fps的刷新率,CPU除了做數(shù)據(jù)傳輸,也是要跑邏輯的?。ㄟ@就先當(dāng)單核看)。因此合批渲染(Batch?Rendering)是必需的。有人可能說,我用Instanced?Rendering一樣可以啊,確實(shí)好像可以,數(shù)據(jù)確實(shí)少了,但是還有個DrawCall的問題沒解決...這里參考(偷)mc里的設(shè)計(jì),按照一個渲染單元(mc里叫Section)為16*16*16個方塊進(jìn)行渲染。這樣做的好處是可以將數(shù)據(jù)整合成一大塊傳輸給顯卡,提高傳輸效率,減少DrawCall,也可以手動寫出來簡單高效的面剔除算法。壞處就是沒辦法定位到渲染單元中特定的一個方塊了,因?yàn)樽隽嗣嫣蕹?,暴露多的方塊比暴露少的方塊傳輸?shù)臄?shù)據(jù)多,各方塊占用的頂點(diǎn)數(shù)據(jù)參差不齊,這就造成了每次放置和破壞方塊的時候都需要重新計(jì)算渲染單元的網(wǎng)格的問題。
經(jīng)過以上的思考以及摸著mc過河,我決定使用合批渲染的辦法來渲染這個方塊世界。下面簡單描述一下合批的算法思路:在每個渲染單元生成時,首先對其內(nèi)部的方塊按照x,y,z軸的順序依次遍歷。對每個方塊,首先查詢其上下左右前后方向上的方塊是否為透明方塊,如果全都不是,那么就跳過該方塊。如果某一方向是透明方塊,那么就將這個方向的模型數(shù)據(jù)并入渲染單元。
問題解決了嗎?并沒有完全解決。因?yàn)槌酥?,還有不完整方塊、半透明方塊和帶動畫的方塊存在。


先解決最容易的不完整方塊的剔除問題。所以進(jìn)一步的思路是將上面算法的掃描結(jié)果作為一系列標(biāo)志位寫入一個Flags枚舉變量,然后將其作為參數(shù)輸入到以面為最小單位構(gòu)建方塊模型對象中。這個方塊模型對象的每個面都擁有一個掩碼,在實(shí)際的建模過程中方塊模型會向這些面對象分發(fā)這個枚舉,如果該面的掩碼和該枚舉的按位與運(yùn)算結(jié)果不為0,就將該面所持有的模型數(shù)據(jù)并入到渲染單元中。
對于半透明的方塊和動畫方塊,我目前還只是停留在一個初步輪廓的階段。比如對于玻璃這樣的部分全透明部分不透明的方塊,可以直接寫著色器丟棄透明的片段而不做顏色混合,而如果是冰或者彩色玻璃一類的方塊,可能就要用到各種OIT技術(shù)。我對這方面的認(rèn)知還是不完全,還得去做做實(shí)驗(yàn)補(bǔ)補(bǔ)課。而動畫方塊呢,我認(rèn)為可以學(xué)習(xí)戴森球計(jì)劃開發(fā)組的策略,將這些可動的方塊都用Instancing渲染然后用Uniform的方式把當(dāng)前幀傳給頂點(diǎn)著色器的方式來做動畫。但是需要為每種動畫方塊編寫著色器,工作量可能有點(diǎn)大。
如何用數(shù)據(jù)表示
因?yàn)橹翱催^mc的Nbt存檔并且寫過MagicaVoxel的模型讀取器,所以世界的數(shù)據(jù)表示我也是相當(dāng)思維定式地采用了“調(diào)色板”的方法(感覺和圖形API中使用index?buffer來減少頂點(diǎn)輸入的思路也是類似的)。好處當(dāng)然是可以有效縮減空間占用,壞處是增大了復(fù)雜程度從而提高開發(fā)難度和維護(hù)成本。
這里也直接用一下mc里的術(shù)語,把一組方塊稱之為區(qū)塊。那么就接著理一下區(qū)塊的數(shù)據(jù)結(jié)構(gòu)。最基礎(chǔ)的區(qū)塊,由兩個表組成,一個是索引表,一個是方塊元數(shù)據(jù)表。索引表表示在哪里有哪些方塊,方塊元數(shù)據(jù)表表示區(qū)塊內(nèi)總共有哪幾種方塊。為什么不直接用一個方塊ID的三維數(shù)組?因?yàn)殡S著元數(shù)據(jù)的不同,方塊的外觀和性質(zhì)都可能不一樣。比如草方塊和覆雪草方塊,點(diǎn)亮和熄滅的紅石火把等。這個設(shè)計(jì)足以實(shí)現(xiàn)mc早期的各種方塊,但是還有一類會自我更新的方塊無法實(shí)現(xiàn)。如熔爐等。所以mc加入了“方塊實(shí)體”的概念,玄學(xué)點(diǎn)說方塊實(shí)體可以理解成是背后操縱方塊的傀儡師,隨著放置方塊而來,拆除方塊而走,也可以順著線(坐標(biāo))找到它來查詢方塊更細(xì)節(jié)的數(shù)據(jù)。所以還存在一個跟隨游戲更新而更新的方塊實(shí)體列表。
這個我也不知道是思維定式還是殊途同歸,總之想來想去發(fā)現(xiàn)做體素世界的數(shù)據(jù)結(jié)構(gòu)都大體相同,就先這么做著吧。如果有更好的想法也歡迎到評論區(qū)討論。
下一篇就是這個方塊世界的實(shí)現(xiàn)篇了,我大概會在里面寫一寫踩坑經(jīng)歷和技術(shù)難點(diǎn)。
參考
https://fabricmc.net/wiki/tutorial:blocks
https://minecraft.fandom.com/zh/wiki/%E5%8C%BA%E5%9D%97%E6%A0%BC%E5%BC%8F
https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/03%20Blending/
https://indienova.com/indie-game-development/dyson-sphere-devlog-4/