Houdini技術體系 基礎管線(四) :Houdini驅動的UE4植被系統 上篇

背景

以前在《Houdini技術體系 過程化地形系統(一):Far Cry5的植被系統分析》一文中已經對AAA遊戲中過程化植被的需求有了必定的定義,後續工做就是如何用Houdini開發功能需求定義的節點,以及對應UE4的Houdiin Engine來制定過程化管線。Houdini的HDA的開發放在 過程化地形系統部分講解,這裏主要是講解工做流程的制定。FC5的分析以前,也大體介紹了UE4的植被系統。這裏再肯定下植被系統工做流方面的需求:
  • Houdini Input:UE4 要輸入什麼給Houdini
    • 所選擇Landscape Component部分的地形信息
    • 美術提供的選擇區域,能夠是繪製的Mask,也能夠是Curve的選框
    • 與HDA中對應的植被資源參數,簡單的能夠用Houdini Engine ,完善一些的話最好是一個json或xml的描述文件,或者UE4的DataTabel。
    • 除了HeightMap外的Mask信息,還有峭壁,湖泊,電線杆,柵欄等不能擺放植被的區域Mask
  • Houdini Ouptut:Houdini要輸出什麼給UE4
    • Entity Point Cloud:每一個點包含了對應的植被實體,以及實體的Positon,Rotation,Scale等Transform信息
    • Terrain  Data:例如樹根對地形隆起的變化,樹根部分對地表材質Mask的影響,地表的顏色等
    • Mask Data:草體佈局信息的Mask貼圖
  • UE4植被系統的支持
    • Houdini Instance與UE4 Foliage Type的Instance的對應
    • Houdini Biome Mask與UE4 Grass Type的對應
    • Houdini閉環修改與UE4 Foliage System的對應。
 
那麼,這裏就以實現這些需求爲目標,介紹下實現基於Houdini的UE4植被系統的基礎管線所要注意的事項。

Houdini的Input設置

    在以前FC5的植被系統的需求分析中也能夠得知,最新的AAA遊戲中,一平方千米場景裏就有60w左右的植被實體,而最近的一些UE4大世界遊戲和手遊,也一般在6x6~8x8km左右。在策劃和美術的迭代開發中,每次都要生成整個場景的植被再看效果的話,會極大的影響開發效率,對大團隊的多人協做開發也很不友好。因此這裏也要像以前的地形管線同樣,以FC5的過程化系統爲原型,能夠支持美術或策劃來選擇和繪製生成區域,讓Houdini的過程化生成隻影響這一部分選擇區,這樣不但能夠利用UE4的WorldComposition的功能多人工做,也能夠經過UE4自帶的繪製Selection Region Tool,讓美術更進一步的控制過程化的影響區域,減小生成時間,提高迭代效率。
 
以下圖所示,FC5的植被系統,支持相似UE4的Component和Paint Region來做爲Terrain Data,
    
 
在上文中,也總結到UE4裏FC5植被系統的Input,有如下幾項:
  • 所選擇Landscape Component部分的地形信息
  • 美術提供的選擇區域,能夠是繪製的Mask,也能夠是Curve的選框
  • 與HDA中對應的植被資源參數,簡單的能夠用Houdini Engine ,完善一些的話最好是一個json或xml的描述文件,或者UE4的DataTabel。
  • 除了HeightMap外的Mask信息,還有峭壁,湖泊,電線杆,柵欄等不能擺放植被的區域Mask
    
    關於如何肯定Input Terrain Data, Landscape Component的選擇在地形管線部分已經幾回講到了。幸運的是,UE4除了Component Select Tool以外,在Terrain Sculpt Tool裏提供Region Selection Tool的功能,這個要比Component Select Tool更加靈活和便捷。但和Component Select Tool同樣,這個功能對原生的Houdini Engine並不適用...
如上圖所示,Region Selection Tool是Terrain Sculpt Tool裏的功能,不過能夠借用這個功能來做爲Input的Mask Paint來使用。接下來看下怎麼擴展Houdini Engine把這個Mask做爲Input傳到Houdini裏去。
     Region Selection Tool繪製的Mask的方式,當使用者繪製時,會在FLandscapeToolStrokeMask::Apply函數里根據筆觸和權重值,把繪製值添加到class ULandscapeInfo的TMap<FIntPoint,float> SelectedRegion 裏的, 只有繪製過的區域纔會保存在 SelectedRegion裏,其中FIntPoint表明的是在Landscape裏的XY位置信息,做爲整型保存,而float爲繪製Mask的權重。
     拿到了SelectedRegion後,就是要在Houdini Engine裏把它做爲Mask,輸入到HDA中進行處理。Houdini Engine是在FHoudiniLandscapeUtils::CreateHeightfieldFromLandscapeComponent函數裏對Height Data和Mask Data進行Input打包的,這裏選擇在這個函數里加入SelectedRegion的Mask的打包工做。
    首先,是根據Houdini裏一個Landscape Component的大小MaxX x MaxY,建立出對應的 SelectedRegion大小的Mask數組。在LandscapeInfo的SelectedRegion裏查找每一個點的信息,若是有就複製到對應位置,沒有則設置爲0。這樣,提供給Houdini使用的SelectedRegionData就完成了。
   TArray<float> SelectedRegionData;
  for (int32 X = MinX; X <= MaxX; X++)
  {
      for (int32 Y = MinY; Y <= MaxY; Y++)
      {
        float RegionSelect =  
        LandscapeInfo->SelectedRegion.FindRef(FIntPoint(X, Y));
        SelectedRegionData.Add(RegionSelect);
      }
  }                

  

接下里,跟LayerMask一樣的方式,經過C++代碼建立一個名爲SelectedRegion的Mask節點,並跟其餘的Volume Merge到一塊兒。
 
  FString LayerName = "SelectedRegion";

  HAPI_NodeId LayerVolumeNodeId = -1;
  if (!CreateVolumeInputNode(LayerVolumeNodeId, LayerName, ParentId))
    return false;

  HAPI_PartId CurrentPartId = 0;
  if (!SetHeighfieldData(LayerVolumeNodeId, CurrentPartId, 
    SelectedRegionData, SelectedRegionLayerVolumeInfo, LayerName, 
    ComponentIndex))
      return false;

  if (!CommitVolumeInputNode(LayerVolumeNodeId, InputMergeNodeId, 
    MergeInputIndex))
    return false;

  MergeInputIndex++;            

  

另外, ULandscapeInfo提供了把繪製的SelectedRegion轉爲Selected Component的功能,這樣繪製過過程化的影響區域後,就不用再選擇一次Landscpe Component了。這個修改也很簡單,在FHoudiniEngineUtils::HapiCreateInputNodeForLandscape函數裏,當沒有selected components時,就把繪製的區域轉換成SelectedComponents。
if ( LandscapeInfo )
{
  // Get the currently selected components
  SelectedComponents = LandscapeInfo->GetSelectedComponents();
  // 若是沒有selected components,則從繪製區域獲取selected components
  if (SelectedComponents.Num() == 0)
    SelectedComponents = LandscapeInfo->GetSelectedRegionComponents();	
}        
 
把名爲「 SelectedRegion」的Mask做爲Input輸入到HDA後,須要在HDA裏對應這個Mask Layer來識別。在HDA的Heightfield Noise節點裏,把 SelectedRegion做爲Mask Layer來使用
 
這樣HeightField只有在有Mask的部分會有Noise的效果,這個一樣也能夠用在植被的Entity Point Cloud的生成上。
 
下圖的效果,就是在繪製的'X'的區域內,對9個Landscape Component產生噪聲變化。
 
繪製選區一般是控制比較大的區域,而若是是要生成小範圍的區域,建議像下圖這樣用Curve Input來控制區域了。
    如何建立一個Curve Input的方法,在Houdini技術體系 基礎管線(三) :UE4以選擇區域的方式對地形作生成和更新 上篇 和官方文檔 https://www.sidefx.com/docs/unreal/_curves.html裏都有詳細介紹,也是有建立SOP或添加 Operate Path兩種方法。這裏就很少作敘述了。
 
     除了HeightData選區外,Input還須要有擺放的UE4植被列表(Biomes List),植被列表一般是用xml,json,或者ue4的datatable來記錄每種植被在HDA節點裏屬性以及在UE4中使用資源的對應關係。而後在HDA裏經過Python腳原本加載讀取,即使是比下圖FC5用例更復雜的HDA節點串聯和配置,也能夠藉助Python自動化,徹底擺脫人力基於配置文件自動化的建立和鏈接。Houdini的Python腳本使用在後續的基礎管線部分講解,本節出於篇幅關係,使用Geometry Input做爲簡化版的Biome Input。
 
  而相似各類像湖泊,峭壁,電線杆等的Mask過程化生成的Mask,這裏假設你已經經過其餘方式導出了Mask圖,或者準備在Houdini裏經過Mask By Feature或Mask By Object來生成。在管線部分也就不浪費篇幅了,會在以後具體的地形篇的植被製做部分再作詳細介紹。
 

HDA的製做以及Ouput的對應

FC5的HDA的Output分爲兩大類
  • Entity Point Cloud
  • Terrain Data
    基於Input生成Entity Point Cloud的原理很是簡單,在去年的 在 Houdini HIVE at SIGGRAPH 2017上,Procedural Scattering in Houdini Engine and Unity 的Talk上介紹的就涵蓋了所有的基礎知識。就以下圖所示,根據Terrain,Curve以及資源實體的Inptu,按必定規則在引擎裏進行過程化擺放。
視頻連接: https://vimeo.com/228231127   ,對應hda的下載地址:https://www.dropbox.com/s/iv840ldw5tn4lw1/scatter_tool.hda?dl=0 。雖然跟FC5的實現相比還有不小的差距,在管線篇中做爲基礎參考綽綽有餘,有興趣的能夠下載來看看。這裏也借用它簡單介紹下使用的相關節點和功能。
下圖中,Setup中的三個Input,分別對應的Terrain Data,Curve Select和Biome Instance。
 
這裏先建立個臨時的Terrain和Curve來作測試使用。
結果就是在Curve選擇範圍的Terrain上,隨機Scatter了必定數量的Entity,這裏用紅色的Box做爲代理體顯示。
 
這裏參數不變,把scatter換成heightfield scatter
 
這樣,只有繪製了Mask的紅色區域纔會被放置Entity。
 
可是示例的這個Scatter Tool也有不適用的地方。首先就是把物體賦予到Entity Point Cloud的Copy節點,並不能對應UE4的Instanced Mesh,這樣Output到UE4裏的話,每一個樹木實體都會建立爲一個Static mesh,會讓過程化生成和最後運行的效率都變得很是差。最好映照按照官方文檔的建議,用支持Instance的Copy to Point節點替換掉Copy Stamp節點。
 
這裏根據地形坡度生成mask,而後把Mask轉爲Point Cloud來擺放樹木,來比較 Copy to Point節點和Copy Stamp節點的區別。
這裏使用heightfield_maskbyfeature,生成Slope爲20~70範圍的Mask。在 一塊小的地形上生成100顆樹來對比下生成效果。
 
首先是用 Copy Stamp節點,選擇好地形和植被實體後,通過20~30秒的卡頓後,有90多個植被生成出來。這個生成的時間和植被數量比,使用體驗很難讓場景美術接受。
 
不管是Bake成BP仍是Actor,全部的樹都會被Batch成一個Static Mesh,這樣對圖形程序作植被渲染優化也很是不友好
 
和Copy Stamp那20多秒的處理時間相比。。替換爲Copy to Point節點,在選擇完Input的植被實體和地形後的瞬間(1秒內),幾乎沒有感受到任何延遲的,就完成了100顆樹木實例的擺放工做。
把植被Bake成一個BP後,能夠看到全部的樹木被Bake到一個Instanced Static Mesh組件裏,而每顆樹做爲一個Instance保存。也正是由於它採用的Instance的方式,纔會有那麼快捷的生成速度。這個效率對場景美術迭代來講足夠了,但InstancedStaticMesh並不支持LOD模式,用來批量放置的植被實體原有的LOD信息也丟失了。這裏應該是建立 HierarchicalInstancedStaticMeshComponent才能支持LOD。
 
    如何生成 HierarchicalInstancedStaticMeshComponent的Actor或BP,能夠參考  void  UHoudiniAssetInstanceInputField::AddInstanceComponent( int32 VariationIdx )的函數。當Static Mesh有多個LOD時,Houdini Engine會用HierarchicalInstancedStaticMeshComponent替換Instanced Static Mesh
UInstancedStaticMeshComponent * InstancedStaticMeshComponent = nullptr;
if ( StaticMesh->GetNumLODs() > 1 )
{
    // If the mesh has LODs, use Hierarchical ISMC
    InstancedStaticMeshComponent = NewObject< UHierarchicalInstancedStaticMeshComponent >(
    RootComp->GetOwner(), UHierarchicalInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional);
}
else
{
    // If the mesh doesnt have LOD, we can use a regular ISMC
    InstancedStaticMeshComponent = NewObject< UInstancedStaticMeshComponent >(
    RootComp->GetOwner(),UInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional );
}

  

這裏保證樹的實體有LOD,而且在HDA Input 勾選Export LODs    
再次Bake,能夠看到是 HierarchicalInstancedStaticMeshComponent,每一個Instance也有以前的LOD信息了。
 
    姑且算是支持了最基礎的Entity Cloud Point的Output,但距離實際項目需求還很遙遠,就像Input須要支持Biomes List配置表的讀取同樣,Output出於維護和調試的考慮,最好也是能支持Biomes List的輸出,並且,雖然提供給場景美術繪製影響區域的功能,但BP的保存方式,對Houdini的閉環迭代也並不足夠的友好,當美術須要對一些細小地區作頻繁迭代時,新生成的植被如何去替換BP裏已經生成植被也是個問題,解決方法只能整個BP對應區域從新生成一次。 這須要美術在設計初期就對植被佈局策略和種類考慮清楚。 還有就是UE4除了Foliage Type外,還有Grass Type的植被的支持。 這些會放在以後如何UE4植被系統整合的部分來說解。
 
FC5的植被系統除了 Entity Cloud Point以外,還有如下的Terrain Data的Output。
其中Terrain HeightMap和Forest Mask,均可以經過以前地形管線中介紹的方法傳遞給UE4,而Texture ID,由於FC5的地形渲染有TextureArray支持,而UE4則受限於每一個Component4個Landscape Layer的限制,因此Layer與Texture的對應又是在Mateiral裏綁定的,須要修改UE4引擎源碼才能支持,但這就超出Houdini技術體系範疇了。這裏介紹一個臨時的折中辦法,經過直接去修改約定好的對應的Layer的Mask權重的方式,來實現 UE4裏要根據輸出的Houdini Output的去改變Layer的Texture ID的需求。
 
首先,用RegionTool繪製一塊區域來生成植被
 
這裏就只簡單的根據繪製的Mask部分Scatter生成Point Cloud,效果以下
 
接下來實現一下Terrain Data的Output的功能,不管是Terrain Texture仍是Terrain Deformation的輸出原理上都是在植被必定範圍內生成Mask,再把這個Mask轉化爲Layer Data或者Height Data,傳遞給UE4。
 
出於跑通管線的目的,這裏在HDA裏作一個簡單的實現。在houdini里根據Scatter生成的Point Cloud,使用Sphere來生成出Mask,再根據這個Mask來提高地形高度,並把Mask輸出位樹根部所對應的Landscape Material Layer的值。
在測試場景上選擇一個生成區域
有擺放植被的周圍地形改成了另一種Layer。
樹根附近的地表高度也被提高了。和FC5那種按照樹根形狀來提高高度的效果相比仍是有差距。
 

總結

上篇中,簡單的介紹瞭如何在UE4裏實現相似FC5植被系統的管線,但還有問題等待解決
  • 雖然植被實體能夠被Bake爲HierarchicalInstancedStaticMeshComponent或InstancedStaticMeshComponent來近似UE4Foliage Type的渲染方式,但並不支持GrassType的類型。
  • Point Cloud生成的植被只能Bake爲場景中的Actor或BP而不是Foliage Type,並不能與UE4的Foliage System融合,在迭代和修改中增長了額外的負擔。
  • 示例中的HDA只支持1種植被,生成策略也很是簡單,並且也不支持BiomeList的讀取,和FC5有至關大的差距。
對於前兩點,會在Houdini驅動的UE4植被系統 下篇中有進一步解決方案的介紹,只有整條UE4的植被基礎管線完成後,才能把魚FC5近似的Houdini功能集成到UE4裏,這部分HDA的製做會在地形系統篇中進行具體講解。
相關文章
相關標籤/搜索