Tuesday, June 14, 2016

Vertex painting on meshes

My original idea for showing the effects of player movement on the materials in the world was to use vertex painting. With this technique it is possible to set the red, green, blue and alpha channels of a material per vertex of the mesh. If we also count the amount of times a player passes a mesh we can use this to show player movement. While developing I mostly use the RGB-channels, the idea is to later use the alpha channel to create a nice blend between textures.

I make as much use of blueprints as I can, since this is a very simple and intuitive way to program for people with no background in programming, so my code can easily be used and possibly maintained by others in the future. However not everything I want to achieve can be done with just using blueprints so I make some functions in C++. These C++ functions can then be called from within blueprints.

Vertex painting is something that is a standard Unreal Engine 4 feature. It is however something that is usually done in the editor, so before compiling the game. In our case we want to paint the vertices dynamically after compiling, so at runtime. Some searching on the internet taught me that this cannot be achieved in blueprints, as they don't allow you to access material properties. At this moment I'm still very unfamiliar with the structure of Unreal Engine 4 I started looking into examples of how to access these properties in C++. I found a couple that helped me a lot, like this one.

Inspired by what I read I was sure that I could achieve realtime vertex painting so I put together my own test code example. This consisted of a very simple blueprint that called a C++ function on every tick and passed a mesh and a color (that I didn't use yet) as parameters.

One extra thing that needed to be done was to adjust the material to make use of vertex colors, to test this I made an extremely simpel material that only uses vertex colors. A real material would of course be way more complicated. But this shows enough for testing. The material looks like this:

Naturally the C++ function is a little more complex:

int APixelPainter::drawAllPixels(UStaticMeshComponent* StaticMeshComponent, const FLinearColor& FillColor, bool bConvertToSRGB)
{
  if (!StaticMeshComponent || !StaticMeshComponent->StaticMesh)
  {
    return 0;
  }

  const int32 NumMeshLODs = StaticMeshComponent->StaticMesh->GetNumLODs();
  StaticMeshComponent->SetLODDataCount(NumMeshLODs, NumMeshLODs);

  const FColor Color = FillColor.ToFColor(bConvertToSRGB);
  uint32 LODIndex = 0;
  for (FStaticMeshComponentLODInfo& LODInfo : StaticMeshComponent->LODData)
  {
    StaticMeshComponent->RemoveInstanceVertexColorsFromLOD(LODIndex);
    check(LODInfo.OverrideVertexColors == nullptr);

    LODInfo.OverrideVertexColors = new FColorVertexBuffer;
  
    FColor colors[24] = {FColor::Blue, FColor::White,FColor::White, FColor::White, FColor::White, FColor::White,
       FColor::Blue, FColor::White, FColor::White, FColor::White, FColor::White, FColor::White,
       FColor::Blue, FColor::White, FColor::White, FColor::White, FColor::White, FColor::White,
       FColor::Blue, FColor::White, FColor::White, FColor::White, FColor::White, FColor::White };
       
    TArray<FColor> colorArr;
    colorArr.Append(colors, ARRAY_COUNT(colors));
    LODInfo.OverrideVertexColors->InitFromColorArray(colorArr);
    BeginInitResource(LODInfo.OverrideVertexColors);
    
    LODIndex++;
  }

  StaticMeshComponent->CachePaintedDataIfNecessary();
  StaticMeshComponent->MarkRenderStateDirty();
  StaticMeshComponent->bDisallowMeshPaintPerInstance = true;
  return 1;
}

The code is quite self explanatory, I'll go through it quickly. Basically the code overrides the colors of the vertices for each level of detail (LOD). In my example I only have one LOD so I'm not sure if it would work with more, I didn't test that. The new colors are specified per vertex in an array. In a later iteration the new color could be specified on the world position of the vertices. After the override colors are set, the render state of mesh is marked as being dirty, to guarantee that the renderer picks it up to be redrawn.

In the following gif you can see the result. The object is all white before the game runs, no vertex colors are specified. When the game is started the colors are changed in realtime.

I'm quite pleased with this result, of course a lot of additional work is still needed to change this into a good looking desire path. But it shows that I can change the color (or texture for that matter) per vertex in the game at runtime.

No comments:

Post a Comment