-
前言
頂點顏色(Vertex Color)是很常見的概念,就是在模型頂點上指定的顏色。在實際情況中,由於多個面共用一個頂點,因此一個頂點的顏色取決於具體在哪個面上。
現代圖形學渲染過程中,模型通常都會被賦予材質屬性來進行渲染,而VertexColor作為頂點的顏色,常用於貼圖、材質混合而不會作為輸出物體最終顏色。在UE4中最常見的應用如:制作路面的水坑,牆面的污泥、苔蘚的混合等,配合高度圖可以表現出很好的效果。
在UE4中主要應用過程是在Editor中通過MeshPaint工具進行頂點顏色繪制,在材質藍圖中取到顏色值,從而和其他材質進行混合運算。
下面打算實現一種在runtime下修改Vertex Color的方案:在游戲運行時動態修改Vertex Color,達到一些實時影響外部環境的目的如:燃燒物的蔓延、火焰的炙烤把牆體變黑、在牆上噴漆等,如下圖效果:
當然這些效果也可以通過材質控制,但是個人覺得通過VertexColor方案會更靈活些。
-
過程
顯示需要實現的功能是:通過傳入的點和距離判斷當前頂點是否在指定范圍內,如果在就修改VertexColor,UE4中提供了兩個修改接口:Paint VerticesLerp Along Axis 和 Paint Vertices Single Color,雖然這兩個功能達不到我的要求,但是可以仿照其代碼開發個插件,添加功能,UE4插件開發的過程不過多贅述,上核心代碼:
頭文件定義函數:
///在指點位置下的球行范圍內修改StaticMesh的頂點顏色 UFUNCTION(BlueprintCallable, meta = (DisplayName = "PaintVerticesCenter", Keywords = "StaticMeshVertexColorSet"), Category = "StaticMeshVertexColorSetTesting") static void PaintVerticesCenter(UStaticMeshComponent* StaticMeshComponent, FVector center, float LimitDistance, const FLinearColor& FillColor, bool bConvertToSRGB = true, bool repaint = true);
函數實現
void UStaticMeshVertexColorSetBPLibrary::PaintVerticesCenterLerp(UStaticMeshComponent* StaticMeshComponent, const FVector ¢er, float LimitDistance, const FLinearColor& FillColorin, const FLinearColor& FillColorout, bool bConvertToSRGB , bool repaint ) { if (!StaticMeshComponent || !StaticMeshComponent->GetStaticMesh()) { return; } #if WITH_EDITOR #else if (!StaticMeshComponent->GetStaticMesh()->bAllowCPUAccess) { UE_LOG(LogTemp, Warning, TEXT("staic mesh must set bAllowCPUAccess true")); } #endif const int32 NumMeshLODs = StaticMeshComponent->GetStaticMesh()->GetNumLODs(); StaticMeshComponent->SetLODDataCount(NumMeshLODs, NumMeshLODs); uint32 LODIndex = 0; for (FStaticMeshComponentLODInfo& LODInfo : StaticMeshComponent->LODData) { FStaticMeshLODResources& LODModel = StaticMeshComponent->GetStaticMesh()->GetRenderData()->LODResources[LODIndex]; const FPositionVertexBuffer& PositionVertexBuffer = LODModel.VertexBuffers.PositionVertexBuffer; const uint32 NumVertices = PositionVertexBuffer.GetNumVertices(); //先把顏色取出來 TArray<FColor> orVertexColors; //如果沒有定點顏色繪制 if (LODInfo.OverrideVertexColors == nullptr) { for (uint32 index = 0; index < NumVertices; ++index) { FColor vcolor = FColor(0, 0, 0, 0); orVertexColors.Add(vcolor); } } else { for (uint32 index = 0; index < NumVertices; ++index) { FColor vcolor = LODInfo.OverrideVertexColors->VertexColor(index); orVertexColors.Add(vcolor); } } //重新繪制時清空 if (repaint) { StaticMeshComponent->RemoveInstanceVertexColorsFromLOD(LODIndex); check(LODInfo.OverrideVertexColors == nullptr); } TArray<FColor> VertexColors; VertexColors.AddZeroed(NumVertices); //判斷是否被應用頂點色 for (uint32 index = 0; index < NumVertices; ++index) { FColor BaseColor = FColor(0, 0, 0, 0); if (!repaint) { BaseColor = orVertexColors[index]; } FVector PointPos = PositionVertexBuffer.VertexPosition(index); VPositionToWorldSpace(StaticMeshComponent, PointPos); float _distance = FVector::Distance(center, PointPos); if (_distance < LimitDistance) { const FLinearColor Color = FMath::Lerp(FillColorin, FillColorout, _distance / LimitDistance); BaseColor = Color.ToFColor(bConvertToSRGB); } VertexColors[index] = BaseColor; }
//清空以前數據
if (LODInfo.OverrideVertexColors)
{
LODInfo.ReleaseOverrideVertexColorsAndBlock();
}
LODInfo.OverrideVertexColors = new FColorVertexBuffer; LODInfo.OverrideVertexColors->InitFromColorArray(VertexColors); BeginInitResource(LODInfo.OverrideVertexColors); LODIndex++; } #if WITH_EDITORONLY_DATA StaticMeshComponent->CachePaintedDataIfNecessary(); #endif StaticMeshComponent->MarkRenderStateDirty(); StaticMeshComponent->bDisallowMeshPaintPerInstance = true; }
主要的實現過程就是獲取staticmesh的PositionVertexBuffer,通過判斷每一個頂點的距離進行判斷,然后通過OverrideVertexColors重新設定頂點顏色。
這只是設置一個球形范圍,我們甚至還可以根據距離大小設置不同的顏色值,這樣就能達到根據遠近造成的影響程度不同的目的。
功能實現很簡單,在編譯插件過程很順利,但是在打包發布后,遇見了兩個主要的問題,耗費的比較多的時間,這里記錄一下解決辦法,希望有緣的小伙伴看見時能輕松避開這些坑:
1)由於我用的是4.27版本,打包發布后啟動系統總是提示找不到插件
兩個解決辦法:1) 將插件拷貝到引擎目錄下的plugin文件夾下
2)打開插件的.uplugin文件,添加"EnabledByDefault" : false
初步懷疑是4.27的bug,也有可能是我寫的插件依賴了其他的插件?系統在加載我的插件過程中失敗了,這個問題就不深究了,畢竟問題解決了,以后遇見注意。
2)在編輯器中系統正常運行,但是打包發布后系統崩潰
這個問題比較嚴重,耗費了我大半天的時間找問題,最后定位到當系統打包后運行,獲取到的PositionVertexBuffer都是空的,最后造成數據溢出崩潰,原來是UE在系統打包發布的版本,為了優化會把PositionVertexBuffer內容回收,這就造成數據丟失。相信大家在我的代碼里會看見這段代碼:
#if WITH_EDITOR #else if (!StaticMeshComponent->GetStaticMesh()->bAllowCPUAccess) { UE_LOG(LogTemp, Warning, TEXT("staic mesh must set bAllowCPUAccess true")); } #endif
這就是為了判斷staticmesh是否通過bAllowCPUAccess控制PositionVertexBuffer回收的,這里我們肯定不能讓PositionVertexBuffer回收,因為我們還要獲取數據,好在UE給我們提供了功能,在編輯器中選中要控制的mesh,將其屬性的bAllowCPUAccess 設為true
OK,問題解決,當再次打包后運行,系統正常!但還是以功能換性能的平衡性的設置,當我們設置bAllowCPUAccess 為true時,頂點數據會一直存在內存中,只需要將需要的mesh設為bAllowCPUAccess 即可,如果全部設為true有點得不償失。
-
后記
以上提供了一種runtime下動態修改VertexColor的方案,如果要實現圖中火焰蔓延的效果,需要在材質函數中進行處理,相關的教程也有很多,下次有時間詳細的補充一下實現過程。