Houdini技術體系 基礎管線(三) :UE4 Landscape Component的多選支持 下篇

背景

上篇中,咱們介紹瞭如何修改Houdini Enigne來設置單個Landscape Compnent的Height和Layer的數據,但原生Houdini Engine並不支持多選Component的寫回功能,下篇中,咱們來解決這個問題。

Component多選支持的修改

    Houdini Engine雖然支持多個Landscape Component的選擇,可是並不支持寫回到 Landscape Component,須要本身來實現這個功能。單個Component的實現方法上文已經接受。
多選和單選區別只是在要把所選的Landscape Component按提交給Houdini的順序來保存。
     經過閱讀Houdini Engine代碼能夠看到FHoudiniLandscapeUtils::CreateHeightfieldFromLandscapeComponentArray的參數LandscapeComponentArray裏有全部提交的Landscape Component,不過LandscapeComponentArray中保存順序並非真正的提交順序,而Houdini Engine Output出來的處理結果的順序是Input的順序的是一致的,若是直接用LandscapeComponentArray的結果,就會致使Component的不對應,因此這裏我是在FHoudiniLandscapeUtils::CreateHeightfieldFromLandscapeComponent函數裏把Input的Component保存起來,也保證了保存順序。
 
    而後在FHoudiniLandscapeUtils::CreateAllLandscapes函數中,就能夠把每一個FoundHeightfield和LandscapeComponent作對應,來調用LandscapeEdit.SetHeightData來更新這個 Component的Height Data了。
 
  for ( TArray< const FHoudiniGeoPartObject* >::TConstIterator IterHeighfields
    ( FoundHeightfields ); IterHeighfields; ++IterHeighfields )
  {
      SelectLandscapeComponent = 
      SelectLandscapeComponentArray[ComponentIndex];

 

    而Layer Data的保存方式和Height Data,全部Landscape Component的Layer都保存在一個ImportLayerInfos,好比選了4個 Component,每一個Component有4個Layer,ImportLayerInfos裏就有4x4 16個Layer Data的信息,這裏也須要本身按Component和每一個Component的Layer數量來提交。下面僞代碼,LayerNum爲每一個Component的Layer的數量,ComponentIndex爲處理的Component的編號,而實際開發狀況下,可能每一個Component的Layer的數量和命名都不同,那就須要根據規則來定製這裏的算法了。
 
// Set Current Component's Layer Data
for (int32 LayerIndex = LayerNum * ComponentIndex; 
LayerIndex < LayerNum * (ComponentIndex + 1); LayerIndex++)
{
    LandscapeEdit.SetAlphaData
}
ComponentIndex++;

  

這樣修改後,Houdini Engine就能夠支持多選Landscape Component的Input和Output了。這裏使用上節用到HDA文件,選中4個Component作HeightField Noise的生成
但結果跟咱們預想的並不同,並且只有第一個Component被作了Noise處理。。。
這是UE4原生的Houdini Engine的Input的數據和咱們的HDA的處理算法不匹配致使的。

修改HDA對Input的支持

形成這個結果的緣由,要從Houdini Engine生成Input的函數FHoudiniEngineUtils::HapiCreateInputNodeForLandscape入手:
// 1. Create the heightfield input node.
// We'll use its mergeId to connect all the landscape layers,
// while it's displayId will be our connected asset ID
FString LandscapeName = LandscapeProxy->GetName() + TEXT("_Merge");
HAPI_NodeId MergeId = -1;
if ( !FHoudiniLandscapeUtils::CreateHeightfieldInputNode( ConnectedAssetId, MergeId, LandscapeName ) )
    return false; 
    
  這裏是經過Houdini Engine,直接建立了一個Houdini的Merge節點,而後把每一個Component的Landscape Height Data和Layer Data轉爲HeightField的Height和Mask Volume,在Merge到一塊兒,也就是用C++代碼來生成HDA節點, 這樣就保證了全部Input的總體處理,並且也能夠程序化的去對應不一樣的Input狀況,在後面的章節裏,不少Input項目也是要使用C++或Python來生成HDA節點來節省開發成本。 下圖就是程序生成HDA節點的效果示意:
再增長一個Heightfield noise 看下效果:和以前閉環測試效果同樣,由於 默認的Houdini HeightField節點並不支持這種多個Volume Merge的處理。
 
這裏介紹 三種解決方法:
方法一是直接修改或重寫HeightField Noise節點來支持整個Merged Volume:
方法二:用Loop處理每一個Height Volume
方法三:用tilesplice把Volume合併到一塊兒作處理,而後再用split從新切開
 
若是不想本身修改或定製節點的話,方法二和三均可以,感受方法三還更省事,但方法三有如下幾個問題:
一個是Tile順序的問題。UE4裏的Tile和Houdini的Tile的行列是不一樣的,一樣一個2x2的Compnent,他的Input和Output的順序有所不一樣:
Input  Output
1 2      1 3
3 4      2 4
這個須要本身開發功能調整,另外,方法三所選的Component也必須是Nx M這種連續的Volume,不然tilesplice節點會像下圖這樣幫你補齊。因此方法三也有很多的限制。
若是時間容許,仍是本身開發一套Heightfield的節點來定製須要的功能,原生的HeightField系列節點在內存上也有些浪費,限制也比較多,而方法二和三能夠做爲臨時應急方法。

邊緣法線問題處理

再運行一次HDA處理,2x2的4個Component的節點確實都作了處理,可是在Component邊緣有很明顯的接縫問題,在World Normal視圖下更明顯。
    形成法線接縫是Component之間是共享邊緣形成的,單個Landscape Component的LandscapeEdit.SetHeightData即使選擇計算法線,也會致使邊緣由於採樣不到旁邊Component的頂點,而致使兩個Component的不連續,這裏我暫時使用了比較暴力的方法,全部的Landscape Comonent 在 SetHeightData都不計算法線,而是在最後從新計算整個Landscape的Normal。
再看下修改後多選Component增長一個HeightField Noise的效果。
 
Height Data處理後,就是Layer Data的處理了,這裏把HeightField Noise 改爲 HeightField Mask Noise,對Landscpae的4個Layer的Mask作噪聲處理,和以前Height Data導入時同樣,也會有Component之間的接縫問題。
    這種分割圖接縫的問題,之前用WorldMachine作Tile Mask時也常常遇到,也就是Tile邊緣之間共享頂點的問題。用WM能夠少輸出一圈邊緣的Map的方法來解決,在UE4裏也可使用相似的方法。
 
在調用的LandscapeEdit.SetAlphaData參數上,把Stride比默認的寬度減小1(XSize - 1),就能夠不傳邊緣的Mask Data進去了。
 
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(), XSize - 1);

  

再看一下效果,接縫的問題基本上已經解決了。
 

總結

至此,Houdini技術體系的幾個問題的基礎解決方案已經完成,後面的文章會逐漸傾向Houdini的地形實際製做部分。
而這些技術案例,大多要基於這個閉環+可選組件的方式來實現,隨着技術介紹流程,我也會在Github上定製一個相似Far Cry5的UE4 Houdini Engine版本,但願你們多提寶貴意見。
相關文章
相關標籤/搜索