Houdini技術體系 基礎管線(三) :UE4以選擇區域的方式對地形作生成和更新 上篇

背景

    前一節裏,解決了Houdini地形無縫導入到UE4的流程問題。但這種方法也有它的侷限性,在實際遊戲項目裏,LA和LD仍是偏向在遊戲引擎編輯器裏工做,他們的一些設計也會影響到地形的信息,那麼就須要Houdini對已經導入UE4中並Bake成Landscape的地形資源作二次修改。一般會選擇兩種方案:
  • 方案一:把整個地形和建築都導回到Houdini裏,從新過程化和調整生成後,再所有導入回UE4作處理。
  • 方案二:使用HDA節點的Input和Output,經過調用Houdini Engine API,直接在UE4裏完成調用Houdini過程化節點對地形作修改。
    這裏方案一不但要求美術和策劃對Houdini有必定了解,並且由於Houdini裏和引擎的渲染效果不一致。可能還須要導入到UE4裏才能看到最終效果,大地形還要作 WorldCompositon和 LandscapeStreamingProxy的生成。除了地形之外的的場景和建築部分,還會和GamePlay以及優化顯示邏輯相關,一般會包裝成BP或Prefab的形式,這些東西要導入Houdini再導回也不只僅是資源處理的工做。這麼看方法一是很是費時費力的方法。這也致使了國內一部分項目雖然是把Houdini的地形引入到製做管線裏,但也僅僅是做爲WordMachined的替代品,並不能徹底發揮Houdini的所有功能。
    因此,咱們的目標仍是方案二的開發方式,除了第一次在Houdini裏作完初始地形導入到UE4裏生成 WorldCompositon後,就再也不須要從新導回到Houdini,而是在UE4裏調用預先封裝好過程化功能的HDA節點來完成功能。這也是近年來Ubisoft在Tom Clancy’s Ghost Recon: Wildlands和Far Cry 5裏使用Houdini的方案。具體案例在GDCVault上有GDC2017和GDC2018的相關視頻,這裏的目標也是要UE4裏用相似他們的方法來實現功能。
 
圖:Far Cry 5的地形編輯工具示例。能夠直接在編輯器裏調用Houdini功能對地形作修改。
 
    可是使用原生的UE4 Houdini Engine的前提下,無縫大地型的UE4內部修改仍是會有如下幾個問題:
  • 雖然Houdini Engine支持Landscape的讀回到Houdini,但他Output只支持使用回讀的Landacape Data信息建立一個新的Landscape:
    • 每次修改,都要從新跑一遍上節提到的生成WorldCompositon和LandscapeStreamingProxy的過程,從新對地形對切割,仍是很是耽誤時間
    • 雖然提供了基於Landscape Component的Input,但Output時每一個Component會Cook成一個landscape,致使生成多個Component
  • 在原生配置下,即使只處理一部分地形的迭代也必須把整個Landsacape Data經過HDA Input到Houdini來進行處理:
    • 假如使用Houdini裏讀入8x8k的地形,內存佔用和過程化處理和交換數據量都會變大,從而致使Cook時間變長,下降迭代的速度。
    • 不能基於Componment的控制生成範圍,那就須要給HDA額外加入一個選擇區域的Input,致使美術工做上變的更加繁瑣。
  • 很難在UE裏作資源的版本管理,也不方便多人合做地形
 
    對前面提到的一些概念和方法不瞭解也不要緊,我接下來會用示例還原這些過程,直到最終的目標方案的原型,也就是 Far Cry 5的功能效果。
圖 Far Cry 5 地形編輯器,能夠選擇一個Terrain的Section或Sector區域來作過程化生成,極大的減少了處理和引擎與Houdini交換的資源量。
    時間和篇幅的緣故,本節會分爲上下篇,上半部分主要是在如何修改Houdini Engine源碼,能夠在UE4裏基於Landscape Component進行小規模的迭代 迭代的功能,下篇則是實現Houdini Engine管線的基礎上,如何製做HDA節點,實現各類不一樣的編輯效果。

使用Houdini製做閉環

    本節繼續使用上一節的場景資源作做爲測試用例,講解一些HDA Input基礎知識,讓沒有Houdini開發經驗的程序人員也能很快接手Houdini Engine的改造。首先建立一個用來開發調試用的HDA節點,它的功能就是能夠選中UE4場景裏的一個Landscape做爲Input,經過Houdini Engine把UE4  Landscape Height Data和Layer Data 轉換爲Heightfiled Height和Mask Data傳入到HDA,在HDA裏不作任何處理直接輸出原始的Height和Mask,再次通過Houdini Engine生成UE4的Landscape Data。
下圖就是一個Houdini閉環處理地形的流程展現,不須要打開Houdini,在UE4裏就能完成閉環的操做。
 如何建立一個SOP(Surface OPerators or geometry nodes )類型的Houdini Node Input的流程,Houdini Engine的官方文檔裏也有講解 https://www.sidefx.com/docs/unreal/_inputs.html  
方法一,Houdini裏File->New Asset,按下圖的建立一個新的Operator
方法二 建立一個Gemotry節點,Create Digital Asset,在HDA的Parameter裏添加一個Operate Path的參數,把這個參數的與Object_Merge的Object作關聯。
 
    不論用這兩種哪一個方法制做均可以獲得這個HDA文件,把它加入到UE資源並拖入到Level的話,按下圖那樣把 Input類型選擇爲Landscape Input,就能夠選擇要處理LandscapeStreamingProxy。而勾選上最下面的「Export Selected Landscape Components Only 」,就能夠把Landscape Component做爲Input輸入給Houdini。但就像一開始提到的,原生Houdini Engine的這個功能並不能知足咱們的需求。
 

基於component的更新的問題

以下圖所示,原生UE4的Landscape的是支持多選Component Selection的,Houdini Engine也是支持多個Landscape  Component 的Input。
選擇4x4個Component做爲要 處理的Landscape信息,而後用Recommit看下結果.
 
    以下圖所示,雖然在效果面上,Houdini Enine把讀入的LandScape Data轉成HeightField Data 輸入給Houdini又沒有絲毫偏差的的Output後轉爲 LandScape Data,但Houdini Engine把這16個Component建立成了16個Landscape,這明顯不是想要的結果。
 
    另外要注意的是,新建立的Landscape的Transform和老的Landscape的Transform也不同,這是Houdini和UE4的高度信息單位不一樣致使,這個問題也會在後面修改Houdini Engine時形成必定的困擾。並且原生的 Component多選功能在Height和Mask更新上也會有一些問題。另外預先提到的一點,雖然Houdini Engine的Landscape的更新上有以上各類問題,但相似讀取Landscape的信息來動態擺放,生成植物生態系統等不會修改LandscapeData的功能並不會受影響,這個具體的HDA開發也會在下篇裏涉及到。
圖:相似FarCry5根據選擇地塊的Mask信息生成植被的管線,原生的Houdini Engine也是能夠勝任的。只須要一些Houdini HDA的功能開發就能夠了。
 
定製Houdini Engine支持基於Component的生成和更新
由於時間和篇幅關係,這裏提供一個不須要修改UE4源碼,只少許修改 Houdini Engine就能夠解決問題的方法,先進入到UE4的Houdini Engine Plugin工程代碼裏。
 
和Landscape相關的功能,是在HoudiniLandscapeUtils和HoudiniEngineUtils裏,建議有時間仍是全看一遍,這裏用註釋簡單描述下整個數據流程方便定位問題。
Input部分
調用FHoudiniLandscapeUtils:: CreateHeightfieldFromLandscapeComponentArray函數,把選擇的Landscape Component的Height Data信息轉爲Houdini的HeightField Data。
 1. Extracting the height data
 2. Convert the height uint16 data to float
 3. Set the HeightfieldData in Houdini
 4. Extract and convert all the layers
     // 1. Extract the uint8 values from the layer
     // 2. Convert unreal uint8 to float
     // 3. Set the heighfield data in Houdini

Output部分:
當數據在Houdini裏處理完成後,調用 FHoudiniLandscapeUtils:: CreateAllLandscapes基於Houdini的volume數據生成Landscape。
     // First, we need to extract proper height data from FoundVolumes
    // Check that all layers/mask have not changed too
     // Extract the Float Data from the Heightfield
     // Convert the height data from Houdini's heightfield to Unreal's Landscape
  // Look for all the layers/masks corresponding to the current heightfield
  // Extract and convert the Landscape layers
  // Create the actual Landscape
定位到 FHoudiniLandscapeUtils::CreateLandscape函數裏,它的核心功能把轉換後的的uint16的Landsacpe的Height信息(TArray< uint16 >& IntHeightData)和Layer信息(TArray< FLandscapeImportLayerInfo >& ImportLayerInfos),經過調用UE4的ALandscapeProxy::Import來生成一個全新的Landscape。
    這裏選擇的解決方案是不建立新的Landscape,把HeightData和LayerData作適當的封裝,直接使用FLandscapeEditDataInterface的SetHeightData和SetAlphaData輸入到須要修改的Landscape的對應Component的數據作更新。

Layer Data的處理方法

    爲了講解簡單起見,直接在CreateAllLandscapes函數的後面加上這部分功能。其中的Height Layer的更新相對簡單,把以前Import用的TArray< FLandscapeImportLayerInfo > ImportLayerInfos的數據對應的用LandscapeEdit.SetAlphaData傳給老的Landscape就能夠了。
// Set Layer Data
for (int32 LayerIndex = 0; LayerIndex < ImportLayerInfos.Num(); LayerIndex++)
{
    LandscapeEdit.SetAlphaData(ImportLayerInfos[LayerIndex].LayerInfo, 
    SelectLandscapeComponent->GetSectionBase().X, 
    SelectLandscapeComponent->GetSectionBase().Y, 
    SelectLandscapeComponent->GetSectionBase().X + 
    SelectLandscapeComponent->ComponentSizeQuads, 
    SelectLandscapeComponent->GetSectionBase().Y + 
    SelectLandscapeComponent->ComponentSizeQuads, 
    (uint8*)ImportLayerInfos[LayerIndex].LayerData.GetData(), 0);
}

 

而後選中一個Component, 給HDA配置上Landscape材質,以及一個HeightField  Mask Noise節點,給地形的Groud信息圖層信息增長一些噪聲 。
左邊是處理前的效果,右邊是增長噪聲後的效果。
能夠看到,這裏已經把Houdini處理過的Height Mask信息寫回到了UE4本來的Landscape  Layer上,實現了目標的效果。

Height Data的處理辦法

和處理Layer Data的方法相似,把Houdini Engine Output的( TArray< uint16 >& IntHeightData)用LandscapeEdit.SetHeightData函數傳回給Landscape Component。
ULandscapeInfo* LandscapeInfo = 
SelectLandscapeComponent->GetLandscapeProxy()->GetLandscapeInfo();
FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo);

int Num = IntHeightData.Num();
for (int i = 0; i < Num; i++)
{
    // Convert Transform 
    IntHeightData[i] = (IntHeightData[i] - ZeroValueInDigit)* 
    SelectLandscapeScale.Z / OldLandscapeScale.Z + 32768.f;
}
// Set HeightData
LandscapeEdit.SetHeightData(SelectLandscapeComponent->GetSectionBase().X,
    SelectLandscapeComponent->GetSectionBase().Y, 
    SelectLandscapeComponent->GetSectionBase().X + 
    SelectLandscapeComponent->ComponentSizeQuads , 
    SelectLandscapeComponent->GetSectionBase().Y + 
    SelectLandscapeComponent->ComponentSizeQuads , 
     (uint16*)IntHeightData.GetData(), 0, false );

  

    Convert Transform註釋部分的處理,是由於通過從新處理後Houdini的Height Field的Data Range和以前的發生了變化,致使ConvertHeightfieldDataToLandscapeData裏生成的HeightData和Transform信息和Input時的Transform信息不匹配,ZeroValueInDigit和OldLandscapeScale.Z分別表明了新地形的Transform信息。須要把兩個Transform信息的差別對HeigtData作修正,才能把正確的Height Data寫回到Landscape。若是你發現寫回的地形總體高了一塊或低了一塊,或者高度比例和原來不一致,那一般就是這個HeightData的還原處理出錯了。因此原始的Landscape Transform也要儘可能標準,例如本節示例裏初始Landscape的Transform就作的儘可能正規化
看下修改代碼後的效果,選擇一個Landscape Component地塊,在HDA節點裏增長一個Heightfiled Noise的節點,對Landscape Height Data作一些輕微的噪聲修改:
左側是未處理的,右側是處理完的。能夠看到地塊有了噪聲的高低差的效果。
 
繼續作一個Height Field Erode的測試,用TimeShift來控制Height Field Erode的演算幀數。
 
    下圖結果是 TimeShift = 30 和 TimeShift = 60的效果對比。基本上實現了用Houdini Engine對一個Landsape Component的修改功能。可是問題也很明顯。處理的Component和未處理的Component的邊緣高度沒法很好的銜接。距離推上生產線,還有很多功能須要開發和支持。
圖: Height Field Erode效果生成,相比整個Landscape的演算,一個Component只要幾秒內就能完成效果計算。

總結

Houdini Engine基於Landscape Component的過程化生成,確實能夠大幅度的提高生成效率和速度,可是Houdini Engine和HDA製做都還須要一系列的定製開發
  • 對多選Landscape Component的支持,而且解決多個Component之間的接縫問題。
  • Input不能只有Landscape Component來控制範圍,還須要爲美術提供選區的功能來控制生成範圍,避免Component邊界問題。
在下篇中,咱們會針對這些問題,繼續對Houdini Engine進行定製,以及提供針對不一樣功能的HDA開發的示例。

<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">html

相關文章
相關標籤/搜索