關於地形更新我們還差最後一步了啦!
對於按component提交更新而言,不方便的地方還是很顯而易見的,所以我們需要祭出最好用的大殺器,按選取更新。這樣我們就可以最細緻的對一塊地形提交修改了。
當然,還有兩個遺留問題會在本文中提到。
UE4自帶了一個畫刷選區的功能,我們就是利用這個功能,把選出來的區域單獨的提交修改。
選區更新的話,我們就不再需要去一塊一塊的點選component了,我們可以直接通過GetSelectedRegionComponents() 函數來獲得選區所在的componet。
//......... if ( bExportOnlySelected ) { const ULandscapeInfo * LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); if ( LandscapeInfo && !LandscapeInfo->IsPendingKill() ) { // Get the currently selected components SelectedComponents = LandscapeInfo->GetSelectedComponents(); }
//////////////////////////////// if (SelectedComponents.Num() <= 0) { // if no components is selected, maybe use region to fetch components SelectedComponents = LandscapeInfo->GetSelectedRegionComponents(); } //////////////////////////////// if ( bAutoSelectComponents && SelectedComponents.Num() <= 0 && AssetBounds.IsValid ) //.......
bool FHoudiniLandscapeUtils::GetLandscapeRegionLayerData(ULandscapeInfo* LandscapeInfo, int32 MinX, int32 MinY, int32 SizeX, int32 SizeY, TArray<float>& LayerData, HAPI_VolumeInfo& LayerVolumeInfo) { LayerData.Empty(); bool HasRegionInfo = false; for (int32 Y = MinY; Y < MinY + SizeY; Y++) { for (int32 X = MinX; X < MinX + SizeX; X++) { float RegionSelect = LandscapeInfo->SelectedRegion.FindRef(FIntPoint(X, Y)); LayerData.Add(RegionSelect); if(RegionSelect > 0.0f) { HasRegionInfo = true; } } } if (!HasRegionInfo) { // no region info return false; } // init VolumeInfo }
通過上面的方法我們已經可以把選區作為一個mask提交到houdini engine中了,我們可以在hda里利用這個mask來做更新,當然,這麼做顯然不夠方便。
所以這裡我們把返回的數據用插件做一個mask功能,只更新選區內的數據,選區外的數據依然保留原狀
void FHoudiniLandscapeUtils::AdjustHeightDataByRegion(ULandscapeInfo* LandscapeInfo, TArray<uint16>& HeightData, ComponentRectInfo& rect, int32& UnrealXStride, TArray<float>& RegionData) { int XSize,YSize; TArray<uint16> OldHeightData; GetLandscapeData(LandscapeInfo, rect.MinX, rect.MinY, rect.MaxX, rect.MaxY, OldHeightData, XSize, YSize);
// new height data and old height data should be same size check(HeightData.Num() == OldHeightData.Num())
for (int32 x = 0; x < XSize; x++) { for (int32 y = 0; y < YSize; y++) { int32 OriginIndex = (y + rect.RelativeMinY) * UnrealXStride + (x + rect.RelativeMinX); float RegionValue = RegionData[OriginIndex]; int32 i = y * XSize + x; // adjust the final value by region value // 把原有高度和更新後高度的差值乘以region的值,讓region不為1的地方能夠有漸變。 HeightData[i] = OldHeightData[i] + (uint16)(((float)HeightData[i] - (float)OldHeightData[i]) * RegionValue); } } }
對於layer就省事兒一些,我們只需要把更新的weight值乘上region的強度就行了。
void FHoudiniLandscapeUtils::AdjustWeightDataByRegion(TArray<uint8>& WeightData, ComponentRectInfo& rect, int32& UnrealXStride, TArray<float>& RegionData) { int XSize = rect.MaxX - rect.MinX + 1; int YSize = rect.MaxY - rect.MinY + 1;
for (int32 x = 0; x < XSize; x++) { for (int32 y = 0; y < YSize; y++) { int32 OriginIndex = (y + rect.RelativeMinY) * UnrealXStride + (x + rect.RelativeMinX); float RegionValue = RegionData[OriginIndex]; int32 i = y * XSize + x; // adjust the final value by region value WeightData[i] = (uint8)((float)WeightData[i] * RegionValue); } } }
當然,更新高度之前先獲取選區信息,我們的選區也不用生成真實layer。
做完後我們可以就看到地形按照我們的選區做出了修改。
法線不連續是什麼樣呢,就是如下圖所示:
可以看到中間有一個很明顯的十字,如同更新地形的時候把她們遺漏掉了一樣。
是的沒錯就是遺漏掉了。在LandscapeEdit.SetHeightData中,component的邊緣是不會去計演算法線的,因為UE4官方認為邊緣法線會涉及其他區域所以乾脆不管了。
那麼我們一塊一塊component去提交就會發現component之間的法線都是沒有被處理到的,雖然我們之前已經將選區邊緣做了鎖進來避免整體外緣的法線問題,但是多個選區交接的法線還是不好處理。
苦於沒有什麼好辦法,在不修改引擎源碼的前提下,只能辛苦自己重構代碼了。
借鑒地形雕刻工具的設計思路,我們把所有選區的原有數據先作為一個cache,然後在cache上面更新我們本次要做的修改,最後整個cache一起提交,就能解決component接縫處的問題。
// copy the origin height data as a cache TSet<ULandscapeComponent*> CurrentComponents; int RelativeMaxX = RelativeMinX + UnrealXSize - 1; int RelativeMaxY = RelativeMinY + UnrealYSize - 1; PreviousInfo->GetComponentsInRegion(RelativeMinX + 1, RelativeMinY + 1, RelativeMaxX - 1, RelativeMaxY - 1, CurrentComponents); for (ULandscapeComponent* CurComponent : CurrentComponents) { CombineHeighData(CurComponent, RelativeMinX, RelativeMinY, RelativeMaxX, RelativeMaxY, CachedHeightData); }
// overide the cache with new data for (ComponentRectInfo& node : ComponentRects) { TArray<uint16> ComponentHeightData; GetDataByComponentRect(IntHeightData, UnrealXSize, node, ComponentHeightData); RemapHeightData(ComponentHeightData, LandscapeTransform.GetScale3D(), FoundLandscape->GetTransform().GetScale3D(), ZeroInDigit); if (HasRegionData) { AdjustHeightDataByRegion(PreviousInfo, ComponentHeightData, node, UnrealXSize, RegionData); } CombineArrayData(CachedHeightData.GetData(), ComponentHeightData.GetData(), 0, 0, UnrealXSize - 1, UnrealYSize - 1, node.RelativeMinX, node.RelativeMinY, node.RelativeMaxX, node.RelativeMaxY);
}
LandscapeEdit.SetHeightData(RelativeMinX, RelativeMinY, RelativeMaxX, RelativeMaxY, CachedHeightData.GetData(), 0, true);
這樣接縫處的法線不連續問題就得到了解決。
之前我們提過,houdini engine plugin中在提交地形更新之前會先把由houdini 導入的layer全部刪除。對於整體更新來說影響不大,但是對於選區更新來說,更新一個小區域意味著整個地形的layer都丟了,這可不行。所以我們希望在清除layer的時候也只清楚我們選擇的component內的layer。
bool FHoudiniLandscapeUtils::RestoreLandscapeFromFile( ALandscapeProxy* LandscapeProxy, bool bLandscapeExportSelectionOnly /*= false*/) { //......... // Delete the added layer FName LayerName = LandscapeInfo->Layers[LayerIndex].LayerName; if (!bLandscapeExportSelectionOnly) { LandscapeInfo->DeleteLayer(CurrentLayerInfo, LayerName); } else { TSet< ULandscapeComponent * > SelectedComponents; // Get the currently selected components SelectedComponents = LandscapeInfo->GetSelectedComponents();
if (SelectedComponents.Num() <= 0) { // if no components is selected, maybe use region to fetch components SelectedComponents = LandscapeInfo->GetSelectedRegionComponents(); }
for (ULandscapeComponent* Comp : SelectedComponents) { FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); Comp->DeleteLayer(CurrentLayerInfo, LandscapeEdit); } }
//......... }
到這裡,Houdini Terrian & UE4部分基本就告一段落了。Terrian部分的功能肯定不止這麼多,houdini還支持植被散布等功能,等後續涉及到的時候再回頭來寫了。
當然。我肯定是去研究更加黑科技的功能啦!是什麼呢,點個關注敬請期待吧~