書接上文
前文介紹了Unity內(nèi)置 Terrain 刷草的一些缺陷,并且介紹了3款插件:
下面就簡單介紹一下這幾款插件的做法萨醒,以及我們的選擇。
如何刷草
Unity內(nèi)置的刷草工具還是很好用的芦瘾,Advanced Terrain Grass 和 Nature Renderer 沿用 Terrain 的刷草,只是接管了渲染集畅。
參考一下 TerrainData 的API近弟,我們可以通過腳本獲取刷草信息,然后自己來做渲染牡整。
沿用 Terrain 的刷草方式有兼容性上的好處藐吮,但是這里就強迫你必須選擇 Terrain 來做地表了。
uNature 和上面兩個插件不太一樣逃贝,作者自己提供了刷草工具,刷草的對象不局限于 Terrain迫摔,也可以是 普通模型沐扳。
比如下圖,我不但在地表刷了草句占,也在Cube上刷了草沪摄。
GPU Instancing
渲染大面積草,GPU Instancing 是非常合適的纱烘。
然而杨拐,Unity的渲染方案是把地表分成一個一個的 Patch,每個 Patch 的草合并成一個大的Mesh擂啥,以此來降低 Drawcall哄陶,但是 多個Patch 的渲染是無法通過 GPU Instancing 提速的。
我們看一下 GPU Instancing 需要滿足的條件:
Use GPU Instancing to draw (or render) multiple copies of the same Mesh
at once, using a small number of draw calls. It is useful for drawing objects such as buildings, trees and grass, or other things that appear repeatedly in a Scene.
這里每個 Patch 生成的Mesh顯然是不同的......
當(dāng)然哺壶,我們可以突破這個限制屋吨。
既然要求 相同的Mesh,那我們可以 把Mesh的計算從CPU移到GPU:把影響 Mesh差異 的因素 ( 比如 Noise 和 高度 ) 編碼到紋理山宾,然后在 頂點著色器 采樣紋理再把這些差異應(yīng)用到頂點至扰。
這樣我們就可以用相同的Mesh來渲染,即滿足 GPU Instancing 的開啟條件资锰,又可以滿足表現(xiàn)上的多樣性敢课,順帶把前文提到的 運行時合并Mesh產(chǎn)生的CPU峰值 也優(yōu)化掉了。
以 uNature 為例,場景依然會被柵格化直秆,如下圖:
這里的 藍色格子 類似 Terrain 的 Patch濒募,處于同一個 紫色格子 內(nèi)的藍色格子是可以通過 GPU Instancing 來渲染提速的。
如果不考慮 LOD 和 密度 的差異切厘,每個 藍色格子 的Mesh是一樣的萨咳,最終表現(xiàn)上的差異被編碼到了 頂點uv 以及 GrassMap 和 HeightMap 這2張紋理中去了。
HeightMap 一覽:
具體的編碼方式我就不細說了疫稿,大家可以參考源碼培他。
事實上,Unity在 2018.3 及以后的版本遗座,對 Terrain 的渲染也加了 GPU Instancing 的支持舀凛,原理和我上面說的差不多:
When enabled, Unity transforms all of the heavy terrain data, like height maps and splat maps, into textures on the GPU. Instead of constructing a custom mesh for each terrain patch on the CPU, we can use GPU instancing to replicate a single mesh and sample the height map texture to produce the correct geometry. This reduces the terrain CPU workload by orders of magnitude, as a few instanced draw calls replace potentially thousands of custom mesh draws.
不過,一直到我目前在用的版本 2019.3途蒋,Unity對于 地形草(Terrain Detail) 的渲染方式還是老樣子......
GPU Instancing 的 API
關(guān)于 GPU Instancing猛遍,如果通過腳本來操作,Unity提供了如下2個接口:
Graphics.DrawMeshInstanced
Graphics.DrawMeshInstancedIndirect
考慮到移動設(shè)備的兼容性号坡,我們一般會選擇 Graphics.DrawMeshInstanced 這個接口懊烤,不過 Graphics.DrawMeshInstanced 有一個最大數(shù)量 1023 的限制:
Note: You can only draw a maximum of 1023 instances at once.
如果我們以每一株草為單位來渲染,很容易就會突破這個限制宽堆。
Advanced Terrain Grass 就是這么做的腌紧,所以最后他用了 Graphics.DrawMeshInstancedIndirect 接口。
uNature 則是對草先做一定程度的 Mesh合并畜隶,回想一下下圖的 藍色格子壁肋,我們可以通過控制格子的粒度,從而把每個 紫色格子 內(nèi)的 藍色格子 數(shù)控制在 1023 以內(nèi)籽慢,然后就可以通過 Graphics.DrawMeshInstanced 接口一次完成渲染浸遗。
Nature Renderer 的作者并沒提供源碼,不過從反編譯的結(jié)果來看箱亿,他也是用了 Graphics.DrawMeshInstanced 這個接口跛锌,只是對 GPU Instancing 的 Drawcall 做了更細致的管理,如下圖:
每個相同顏色的格子屬于同一個 Drawcall极景,和 uNature 的 9宮格 管理方式并不相同察净。
我們的選擇
好了,插件就介紹到這里盼樟。
最后氢卡,說一下我們的選擇:基于 uNature 做改進。
不選擇 Advanced Terrain Grass晨缴,主要因為它是基于 Graphics.DrawMeshInstancedIndirect 的實現(xiàn)译秦。此外,如果你想實現(xiàn)類似 塞爾達的割草 效果,整個 ComputeBuffer 的數(shù)據(jù)都要重建筑悴,這個開銷在運行時難以承受们拙。
不選擇 Nature Renderer 的原因則更簡單阁吝,作者并不提供源碼。
不過 uNature 本身的問題也不少突勇,如果大家要用這個插件装盯,你得有心里準(zhǔn)備:
- 作者已經(jīng)很久沒有更新了甲馋。
- 代碼有不少bug。
- 針對移動端還要做很多優(yōu)化定躏。
無論如何账磺,二次開發(fā)是必不可少的。
不過痊远,有了 GPU Instancing垮抗,大面積的草海已經(jīng)變得可行了。下面會繼續(xù)介紹草海的其他渲染技巧以及模仿 塞爾達 的一些好玩的效果碧聪。
個人主頁
本文的個人主頁鏈接:https://baddogzz.github.io/2020/01/16/Unity-Grass-02/借宵。
好了,拜拜矾削。