UE4 runtime下修改StaticMesh的VertexColor


  • 前言

頂點顏色(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 &center, 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的方案,如果要實現圖中火焰蔓延的效果,需要在材質函數中進行處理,相關的教程也有很多,下次有時間詳細的補充一下實現過程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM