前言
隨著電腦和手機硬件性能越來越高,游戲越來越追求大世界,而大世界非常核心的一環是植被,目前UE5引擎提供給植被生成的主流兩種方式為 手刷植被和LandscapeGrass(WeightMap程序化植被)。當然UE5.3推出新一代PCGFramework 節點程序化生成框架,也可以用于植被生成。(本文暫不涉及到PCGFramework), 主要分析LandscapeGrass方案。
手刷植被
手刷植被是比較傳統的植被生成方式,基本各種引擎都會提供植被編輯器讓美術人力刷植被,這里倒是沒什么好分析的。UE5提供了手刷植被模式FoliageMode, 工具比較齊全,比較易用,能讓場景美術高效率手工刷植被。FoliageMode生成的植被是用HISM組件管理,不明白的可以看看我的另外一篇博客(UE4 4.27) UHierarchicalInstancedStaticMesh(HISM)原理分析
LandscapeGrass(WeightMap程序化植被)
背景
weightmap程序化植被, 顧名思義: 由地形weightmap程序化生成的植被。首先地形WeightMap的概念,說白了就是地形材質混合的權重圖,不懂的可以看下UE地形系統材質混合實現和Shader生成分析(UE5 5.2)。場景美術在地形上頻繁刷了各種材質,比如草原或者沙漠,然后需要再相應的地貌上種植相應植被,比如草原種綠草,沙漠種仙人掌,因為地形的材質層是頻繁改動,每次刷完草或者沙漠后,又得用FoliageMode 在相應地貌刷植被,兩重工作量,而且容易遺忘,這時候就非常需要一種工具:? 在相應的WeightMap上程序化鋪滿一種或許多種植被,每次刷完WeightMap后地形可以自動生成。UE引擎?就有這樣一套方案,叫LandscapeGrass
LandscapeGrass使用簡介
配置LandscapeGrassType
材質Grass采樣層配置
顯示結果
LandscapeGrass生成分析
LandscapeGrass UML圖
LandscapeGrass執行流程圖
這里重點介紹RenderGrassMap,GrassInstancedStaticMeshComponent, FAsyncGrassTask.
這里的三個過程都是逐組件的,針對單獨一個Landscapecomponent進行
RenderGrassMap(Per LandscapeComponent)
GrassMap本質就是一張圖集(TextureAltas)
這里是將LandscapeComponent的HeightMap和Grass用到的所有WeightMap(也就是上面的L2,L3,L4,L5)合并到一張圖集里,格式為PF_B8G8R8A8,也就是所謂的RenderGrassMap。
假設一個Landscapecomponent 分辨率為128,一張HeightMap占16位,一張WeightMap占8位, 由于上面使用了L2,L3,L4,L5四個草權重層,則 (2 + 4)/ 4 = 1.5, 得使用兩張128 * 128,合起來(128 * 2)* 128 = 256 * 128,這就是RenderGrassMap圖集。
GrassMap創建
Render GrassMap
通過DrawLandscapeComponentMesh,設置Ortho正交相機, 進行多次RenderPass, 將HeightMap和WeightMap寫入到GrassMap圖集里。一次RenderPass寫入4個8位通道,6個8位通道就是需要兩次RenderPass.?
核心代碼:
FLandscapeGrassWeightVS,FLandscapeGrassWeightPS,?FLandscapeGrassWeightMeshProcessor等文件
"?grass.CaptureNextGrassUpdate 1 "?可以對RenderGrassMap進行Renderdoc抓幀
GrassMap fetch成cpu數據
上一步生成了GrassMap 圖集,里面存儲了LandscapeComponent地塊的HeightMap數據和所有的Grass相關的WeightMap數據。最終直接對GrassMap進行CPU ReadBack,讀取數據出來,合并到FLandscapeComponentGrassData的 TArray<uint8> HeightWeightData里。HeightWeightData按順序分段存儲了高度數據和所有的Grass各層權重數據。
為什么需要Render GrassMap
回過頭來看RenderGrassMap存在的意義是因為UE引擎地形的各個LandscapeComponent共用了HeightMap和WeightMap,?RenderGrassMap就是把每個組件的HeightData和GrassWeightData從公用紋理抽離出來,存到LandscapeComponent里,方便每個LandscapeComponent生成自己的GrassInstancedStaticMeshComponent.
GrassInstancedStaticMeshComponent
GrassInstancedStaticMeshComponent繼承于HISM組件,兩者基本沒什么區別。這里需要強調的是一個GSMC組件針對一個LandcapeComponent的LandscapeGrassType的GrassVariety來創建的。
FAsyncGrassTask(隨機撒點和HISM構建)
LandscapeComponent已經獲取高度和權重數據,下面就是進行隨機撒點。FAsyncGrassTask
隨機撒點
撒點密度由LandscapeComponent地塊大小和GrassVariety的密度決定
FAsyncGrassBuilder封裝了隨機撒點的核心算法, 目前一共提供了兩種隨機撒點算法: JitterGrid和?Halto序列
勾選了UseGrid就是使用了JitterGrid隨機撒點,反之使用Halto隨機撒點。
JitterGrid
JitterGrid也就是根據密度把整個地形塊均分切成多個塊,每個塊內隨機生成一個二維點,并且加上一定的旋轉和位置偏移,最終得到一個隨機點。具體算法參考我之前一篇博客:?程序化物件放置(procedural placement)之泊松硬盤采樣(poisson disk sampling)
一個隨機點雖然生成出來了,但是是否存活得滿足一定條件:
可以看出當權重為0,百分百不通過。權重為1時,通過率是百分百,如果權重為其它,那就看隨機的運氣了。
Halto序列
Halto隨機數生成參考?應用halton序列生成均勻散點圖
Halto序列生成的隨機點存活條件判斷和JitterGrid是一樣的。比較注意的是,JitterGrid和Halto序列生成的隨機點都是二維的,都需要進行一次SampleHeight來得到高度位置Z,Sample方法是?Bilinear interpolates。
撒完點后最終進行HISM構建。
FoliageMode和LandscapeGrass的區別
未完待續
參考文章
[1]?UE地形系統材質混合實現和Shader生成分析(UE5 5.2)
[2]?(UE4 4.27) UHierarchicalInstancedStaticMesh(HISM)原理分析
[3]?應用halton序列生成均勻散點圖
[4] 源碼文件:LandscapeGrass.cpp