Draw to render target

Draw to render target is nice way to add visuals into materials. It allows you to do so many things. Starting from making a useable whiteboards to all the way to adding deformable snow or somekind of alien material that is starting to grow in walls. Basically, only limitation is how skilled you are with the material editor. With Programming side, Drawing to render target is very easy thing to achieve, editing the material is whole different scope that has it’s own artist. In this tutorial, we are doing the basic drawing, with few different examples what you can achieve.

To know what we are actually doing, we need to undestand what the render target actually is. Basically render target is similar as the UV:s. Render target is a square shaped area where you can paint Black and White. For example, if we have a box  that has a 4 sides(duh), the render target would still be a one single square shaped box. If you manually paint a different section of the render target, it will paint same location on our box. We don’t know what location it is, it might be any of the four sides. It is just some location based on the texture UV. We will allow our code to determine the exact location in that UV and paint into that.


Starting up!

To start up, create a First Person C++ Project. After Project is loaded up, we need to enable UV based tracing to get any kind of data from our traces. This is super important. So go ahead into Project settings and search for UV. Tick the Support UV from Hit Results. It is off by default.

After that is done, let’s create a new folder called Whiteboard. Inside whiteboard, we create our Render Target. So right click the Content Browser and select the Materials & Textures -> Canvas Render Target. This will create the Render Target object to us. You can call it RT_WhiteBoard. Open it up and set the X and Y size to be 512. This will give us slightly better accuracy in drawing. Here you can actually see our canvas that is used to make the desired drawings to material.

Go ahead and close the Whiteboard. Next we are going to create a material that tells us what we are actually going to draw and where. So create a material called M_WhiteboardMarker. We start by settings the Vector Parameter called Draw Location. After that there a little logic to tell our drawing to go middle so we are painting exactly in our trace location. In the end, there is a TextureParameter 2D named TextureToUse. We can change our ”Brush” by just changing this texture! In this tutorial we are using two textures. One is UE4 Logo and one is just a circle shaped brush. You can download them here. Notice that it is very important that you set Blend Mode to be additive! Also set Texture to be one of the brushes you downloaded. Just add them to Unreal Engine first. Oh, and by changing the Size, you can set how Thick your brush is. All of these are parameters so if we want, we could change them later.

Let’s create another material called M_WhiteBoard. This material is the one that is is actually set to the mesh. In this material, we would set the logic how the material reacts when drawn upon. Let’s try very basic first. Here we are lerping between the White and the Blue material. Texture Sample is the Actually RT_WhiteBoard! It is the Render Target itself. Because of that, we can easily lerp between the parts we have painted and what we have not! Simple Black and White.

Now, as default our textures are tiling. We don’t want them to be tiling, or else they would cover the whole material. So Go ahead and double click the textures and set the X and Y tiling methods to be clamped. That way we only draw one of them.

 

Finally, we are going to create a class based on Actor. This is the actor we are going to paint on. So go ahead and create a C++ class based on actor, we call that C_WhiteBoardActor.

Inside our code we first include TextureRenderTarget2D so we can use RenderTarget in code.

#include "Engine/TextureRenderTarget2D.h"

In Public section we create a function. This is called when we are drawing.

void DrawOnWhiteBoard(FVector2D Location);

 

In Private section we add few variables

UMaterialInterface* DrawMaterial; // Basic Material
 UMaterialInstanceDynamic* MI_DrawMaterial; // Dynamic Material to be created from Basic Material
 UTextureRenderTarget2D* RenderTarget; // Our render target

In .CPP we add few #includes. Because 4.16 has changed the system. We need to add #includes for dynamic material and ConstructorHelpers also.

#include "Kismet/KismetRenderingLibrary.h"
#include "Kismet/KismetMathLibrary.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "UObject/ConstructorHelpers.h"

In Constructor we will find our objects from Content Browser.

ConstructorHelpers::FObjectFinder<UMaterialInterface> SearchMaterial(TEXT("/Game/Whiteboard/M_WhiteBoardMarker.M_WhiteBoardMarker"));
ConstructorHelpers::FObjectFinder<UTextureRenderTarget2D> SearchRenderTarget(TEXT("/Game/Whiteboard/RT_WhiteBoard.RT_WhiteBoard"));

  if (SearchMaterial.Succeeded()) 
    DrawMaterial = SearchMaterial.Object;

  if (SearchRenderTarget.Succeeded())
    RenderTarget = SearchRenderTarget.Object;

In BeginPlay we create and set our dynamic material. We also Clear the Render Target. This is because everything we ever draw to target, stays there forever if we don’t manually remove it!

if (DrawMaterial)
    MI_DrawMaterial = UMaterialInstanceDynamic::Create(DrawMaterial, this, FName("Dynamic Whiteboard Material"));
  if (RenderTarget)
    UKismetRenderingLibrary::ClearRenderTarget2D(this, RenderTarget, FLinearColor(0, 0, 0, 1));

Finally, we declare our function. We convert incoming Vector2D to Vector3D and then Draw to render target.

void AC_WhiteBoardActor::DrawOnWhiteBoard(FVector2D Location)
{	
  FVector TempVector = UKismetMathLibrary::Conv_Vector2DToVector(Location, 0);
  MI_DrawMaterial->SetVectorParameterValue("DrawLocation", TempVector);
  UKismetRenderingLibrary::DrawMaterialToRenderTarget(this, RenderTarget, MI_DrawMaterial);
}

Nothing else to do here. Our Whiteboard is ready! Now, we need to edit our character so he can paint. First, we go to input and add Draw event to Mouse2. Now, let’s open our character and add variable to Private section.

bool bIsDrawing = false;

Next, we create functions. For some reason, the UV hitresult didn’t come back when using tracing in C++. So we are going to create a BlueprintNativeEvent and do trace in blueprints. It will return us the FHitResult.

void StartDraw(); // When we press button
void StopDraw(); // When we release button

// This is called in blueprint
UFUNCTION(BlueprintNativeEvent)
FHitResult DoTrace();

Inside .CPP

We Declare all the function.  First in keybinding.

PlayerInputComponent->BindAction("Draw", IE_Pressed, this, &AFP_FirstPersonCharacter::StartDraw);
PlayerInputComponent->BindAction("Draw", IE_Released, this, &AFP_FirstPersonCharacter::StopDraw);

In Functions.

void AFP_FirstPersonCharacter::StartDraw()
{
  bIsDrawing = true;
}

void AFP_FirstPersonCharacter::StopDraw()
{
  bIsDrawing = false;
}

We add Implementation for our DoTrace() function, just to avoid errors in compiling. Implementation isn’t actually going to do anything so we just return something empty. The code inside blueprint is the one we primaly run.

FHitResult AFP_FirstPersonCharacter::DoTrace_Implementation()
{
  FHitResult Res;
  return Res;
}

 

Finally inside our Tick event we do the rest. If you don’t have tick event, you need to add it.

Super::Tick(DeltaTime);

  if (bIsDrawing)
  { 
    FHitResult HitResult = DoTrace(); // We call our function we set in blueprints
    if (HitResult.bBlockingHit)
    {
// We are checking if our hit hits the WhiteBoard
      AC_WhiteBoardActor* WhiteBoardAct = Cast<AC_WhiteBoardActor>(HitResult.GetActor());
      if (WhiteBoardAct)
      {
        FVector2D UV; // Variable to store the UV
// We will find UV Collision
        UGameplayStatics::FindCollisionUV(HitResult, 0, UV);
// We  call the Draw function we created earlier
        WhiteBoardAct->DrawOnWhiteBoard(UV);
      }
    }

  }

All is done inside the code! Now, if you don’t have, create a blueprint class based on your C++ FirstPersonCharacter class. and override the DoTrace() function. Code inside function in very simple. We are tracing from our camera and return the FHitResult.


Next, create a Blueprint class based on your C_WhiteBoard. Add Static mesh actor to it and set the material as M_WhiteBoard. After that you can place your Actor into Scene. When you point it and Right Click on mouse, you can paint on top of the material! Now, you can do all kind of cool things by changing the M_WhiteBoard material. For example, if you want to paint transparent you can do something like this. Remember to add Blend mode to Masked to reveal Opacity mask.

Result:

Thats it for now :). Now you should have basic knowledge of how to draw to render target and how to change material to make different effects. Have fun!