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的方案,如果要实现图中火焰蔓延的效果,需要在材质函数中进行处理,相关的教程也有很多,下次有时间详细的补充一下实现过程。

posted @ 2022-04-02 10:33  小手一拿  阅读(2021)  评论(0编辑  收藏  举报