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