How to set APostProcessVolume Brush Settings in C++?

I am attempting to create APostProcessVolume(PPV) in code so I can highlight different parts of a mesh which is comprised of several smaller meshes in different colors. Like highlighting the individual hand mesh of a character one color, and the individual leg mesh a different color. While leaving the rest of the mesh body unaffected. This is what I want to see in the Brush Settings after my PPV spawns in the editor.
image

I want to spawn the PPV with a brush volume that is just big enough to encompass the individual smaller pieces of the mesh and highlight them a unique color. I figured out how to spawn the PPV, set the blendable attributes, but got stuck on making a brush for it.

Searching on the forums I found this guy that attempted this in 2014, but got nowhere.

And this guy who apparently figured out how to create a brush, but I tried his method and got linker errors.

Any help would be appreciated, especially if there is a simpler way of doing this.

My full code is below, you’ll just need to swap in your own materials in the Init() and use your own meshes to test with.

Header:

#pragma once

#include "CoreMinimal.h"
#include "Engine/PostProcessVolume.h"
#include "HighlightManager.generated.h"

UENUM(BlueprintType)
enum class EHighlightColor : uint8
{
    GLOW_RED = 0,
    GLOW_BLUE,
    GLOW_GREEN,
    GLOW_YELLOW
};

USTRUCT(BlueprintType)
struct MYTEST_API FHighlight
{
    GENERATED_BODY()

    UPROPERTY(BlueprintReadWrite)
    APostProcessVolume* PPV;

    UPROPERTY(BlueprintReadWrite)
    TArray<UPrimitiveComponent*> Meshes;

    UPROPERTY(BlueprintReadWrite)
    bool Active;

    FHighlight()
    {
        PPV = nullptr;
        Active = false;
    }

    FHighlight(UPrimitiveComponent* MeshToHighlight)
    {
        PPV = nullptr;
        Active = false;
        Meshes.Add(MeshToHighlight);
    }
};

UCLASS()
class MYTEST_API UHighlightManager : public UGameInstanceSubsystem
{
    GENERATED_BODY()

    //Automatically fills in the weighted blendables into a passed in PPV
    void FillWeightedBlendables(APostProcessVolume* PPV);

    //Stores the loaded materials on startup for later use.
    TArray<UMaterialInstance*> Mats;

    //Holds on to all highlights that have been spawned
    TArray<FHighlight> Highlights;
public:
    UHighlightManager();
    ~UHighlightManager();

    //Loads the materials for use later.
    void Init();

    UFUNCTION(BlueprintCallable, Category = "Highlighting | Mesh Highlight")
    void HighlightMesh(UPrimitiveComponent* MeshToToggle, EHighlightColor Color);
};

CPP:

#include "_General/Managers/Highlight/HighlightManager.h"
#include "Components/PrimitiveComponent.h"
#include "Kismet/GameplayStatics.h"
#include "ActorFactories/ActorFactory.h"
#include "Editor.h"
#include "ActorFactories/ActorFactoryBoxVolume.h"
#include "Materials/MaterialInstance.h"

#include "Engine/Brush.h"
#include "Engine/BrushBuilder.h"
#include "Builders/CubeBuilder.h"

UHighlightManager::UHighlightManager()
{
    Init();
}

UHighlightManager::~UHighlightManager()
{

}

void UHighlightManager::Init()
{
    Mats.Add(LoadObject<UMaterialInstance>(nullptr, TEXT("/Game/Art/PPMI_HighlightGlowViewer.PPMI_HighlightGlowViewer")));
    Mats.Add(LoadObject<UMaterialInstance>(nullptr, TEXT("/Game/Art/PPMI_HighlightGlowVIE.PPMI_HighlightGlowVIE")));
    Mats.Add(LoadObject<UMaterialInstance>(nullptr, TEXT("/Game/Art/PPMI_DetectiveVision.PPMI_DetectiveVision")));
    Mats.Add(LoadObject<UMaterialInstance>(nullptr, TEXT("/Game/Art/PPMI_DetectiveVision_Blue.PPMI_DetectiveVision_Blue")));
    Mats.Add(LoadObject<UMaterialInstance>(nullptr, TEXT("/Game/Art/PPMI_DetectiveVision_Green.PPMI_DetectiveVision_Green")));
    Mats.Add(LoadObject<UMaterialInstance>(nullptr, TEXT("/Game/Art/PPMI_DetectiveVision_Yellow.PPMI_DetectiveVision_Yellow")));
}

void UHighlightManager::FillWeightedBlendables(APostProcessVolume* PPV)
{
    for (int i = 0; i < Mats.Num(); i++)
    {
        UMaterialInstanceDynamic* MatInstance = UMaterialInstanceDynamic::Create(Mats[i], this);

        FWeightedBlendable Blendable;
        Blendable.Weight = 0.0f;
        Blendable.Object = MatInstance;

        PPV->Settings.WeightedBlendables.Array.Add(Blendable);
    }
}

void UHighlightManager::HighlightMesh(UPrimitiveComponent* MeshToToggle, EHighlightColor Color)
{
    UWorld* CurrentWorld = GetWorld();

    FHighlight NewHighlight(MeshToToggle);

    APostProcessVolume* PPV = (APostProcessVolume*)(CurrentWorld->SpawnActor(APostProcessVolume::StaticClass(), &MeshToToggle->GetComponentTransform()));

    FillWeightedBlendables(PPV);

    //PPV->bUnbound = true; //Works for 1 mesh, but need to have smaller volume on a per mesh basis
    PPV->SpawnCollisionHandlingMethod = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;

    //PPV->Brush = ???;
    //PPV->BrushBuilder = ???;
    //PPV->BrushColor = ???;
    //PPV->BrushType = ???;
    //PPV->BrushShape, not sure how to get access to this one, or if it's named something else than what is shown in the editor.

    //Possible solution is the commented out code block below, but results in linker errors.
    //https://forums.unrealengine.com/t/spawn-new-box-brush-in-c/374916

    //ABrush* NewBrush = CurrentWorld->SpawnBrush();
    //PPV->BrushBuilder = NewObject<UBrushBuilder>(NewBrush, UCubeBuilder::StaticClass(), NAME_None, RF_Transactional); //Why linker errors?
    //PPV->Brush = NewObject<UModel>(NewBrush, NAME_None, RF_Transactional);
    //PPV->Brush->Initialize(NewBrush, false);
    //PPV->BrushType = EBrushType::Brush_Add;
    //PPV->BrushBuilder->Build(NewBrush->GetWorld(), NewBrush);
    //PPV->SetNeedRebuild(NewBrush->GetLevel());
    //GEditor->RebuildAlteredBSP();

    NewHighlight.PPV = PPV;

    Highlights.Add(NewHighlight);

    switch (Color)
    {
    case EHighlightColor::GLOW_RED:
        NewHighlight.PPV->Settings.WeightedBlendables.Array[0].Weight = 1.0f;
        break;
    case EHighlightColor::GLOW_BLUE:
        NewHighlight.PPV->Settings.WeightedBlendables.Array[3].Weight = 1.0f;
        break;
    case EHighlightColor::GLOW_GREEN:
        NewHighlight.PPV->Settings.WeightedBlendables.Array[4].Weight = 1.0f;
        break;
    case EHighlightColor::GLOW_YELLOW:
        NewHighlight.PPV->Settings.WeightedBlendables.Array[5].Weight = 1.0f;
        break;
    }

    //If Mesh To Toggle is Valid
    if (MeshToToggle->IsValidLowLevel())
    {
        //Enable Custom Render Depth on MeshToToggle
        MeshToToggle->SetRenderCustomDepth(true);
        NewHighlight.Active = true;
    }
}

Here is how I was testing my code. This is done in the game level so I can drag and drop mesh references into the event graph. You just need to pass a valid mesh component to the HighlighMesh function, be it an individual mesh, or a sub mesh of a larger mesh.

Again, any help on this will be appreciated. Thank you in advance!

How about using a single unbound PPV, but modify/toggle it based on overlap events triggered by collision components added around your meshes? Those are much easier to handle dynamically.

If I understand you right, I think that was the first thing I tried. I set up an unbound PPV, and was able to tell multiple meshes to highlight one after the other. The problem is that they are all the same color based on what the weighted blendables are set to in the PPV. I couldn’t figure out how to do multiple colors that way.

Then I tried spawning a second unbound PPV with a different color and all the highlighted meshes changed to the new PPV color. So that’s how I found out that the meshes only highlight based on the last PPV that is spawned into the scene. Since both PPVs are unbound, the last one added dictates all highlighting to any mesh that is currently

->SetRenderCustomDepth(true)

So that’s why I’m trying to spawn smaller PPV volumes for specific parts of a mesh so they don’t overlap.

Either I’m not getting this right or there’s a misunderstanding.

if your volumes are each for specific parts and do not overlap, you can instead create trigger boxes as components.
Use trigger-enter event (aka. BeginOverlap) to highlight the contained part with the desired color. (even clear & refill the blendables if necessary)
Use trigger-exit event (aka. EndOverlap) to stop highlighting the contained part.
There should be no issue specifying a different color for each part.

Unless you actually DO want to be highlighting several limbs simultaneously? With different colors? In that case yeah, you’ll need multiple PPVs with different custom depth stencils each.

Yes, that is what I am trying to do. Multiple limb highlighting, simultaneously, with different colors. That’s why I am trying to spawn multiple PPVs that are just big enough to encompass the limb that I want highlighted, thus avoiding overlap with any other PPVs for different limbs and colors.

I have an update. I found a post that fixed the linker error I mentioned in the “Spawn new Box Brush in C++” post by mortmaire

I needed to include “UnrealEd” to the Build.cs PublicDependencyModuleNames

Source:

Including that dependency in the build.cs file broke some other files I had due to undefined identifiers, but after including the appropriate headers that was fixed. Not sure why those errors only surfaced after the new additions to build.cs.

Anyway, the PPV Brush Settings do appear now, but even with a cube size of 5000, 5000, 5000 and spawning directly on top of the mesh I want highlighted it does not highlight. Setting the unbound flag at runtime will highlight the mesh, so I know that the PPV highlighting itself is working, just the volume aspect of it is not working. Here is the updated portion of the UHighlightManager::HighlightMesh function where I make a brush and attempt to set it to the PPV.

        //Spawn the PPV
        APostProcessVolume* PPV = (APostProcessVolume*)(CurrentWorld->SpawnActor(APostProcessVolume::StaticClass(), &MeshToToggle->GetComponentTransform()));

        //Fill PPV with highlights
        FillWeightedBlendables(PPV);

        //PPV->bUnbound = true;
        PPV->SpawnCollisionHandlingMethod = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;

        //Create cube volume
        UCubeBuilder* CubeAdditiveBrushBuilder = Cast<UCubeBuilder>(GEditor->FindBrushBuilder(UCubeBuilder::StaticClass()));
        CubeAdditiveBrushBuilder->X = 5000.0f;
        CubeAdditiveBrushBuilder->Y = 5000.0f;
        CubeAdditiveBrushBuilder->Z = 5000.0f;
        CubeAdditiveBrushBuilder->Build(CurrentWorld);

        //Create the brush
        ABrush* NewBrush = CurrentWorld->SpawnBrush();
        //PPV->BrushBuilder = NewObject<UBrushBuilder>(NewBrush, UCubeBuilder::StaticClass(), NAME_None, RF_Transactional);
        PPV->BrushBuilder = CubeAdditiveBrushBuilder;
        PPV->Brush = NewObject<UModel>(NewBrush, NAME_None, RF_Transactional);
        PPV->Brush->Initialize(NewBrush, false);
        PPV->BrushType = EBrushType::Brush_Add;
        PPV->BrushBuilder->Build(NewBrush->GetWorld(), NewBrush);
        //PPV->SetNeedRebuild(NewBrush->GetLevel());
        //GEditor->RebuildAlteredBSP(); //causes a crash, not sure if necessary or I'm doing something wrong.

        NewHighlight.PPV = PPV;

        Highlights.Add(NewHighlight);

Furthermore, I found another problem. I tried turning off the unbound flag on the manually created PPV that I was initially testing with and then I moved it on top of the object I was trying to highlight. When I toggled the highlight nothing happened. I then ejected the camera at runtime so I could get closer to the object and when I got closer the highlighting turned on. I then realized that for me to be able to see the highlighting the camera AND the object I am highlighting BOTH need to be in side the PPV volume.

This brings me to the realization that even IF I figured out how to spawn smaller PPVs on the different limbs I want to highlight, I wouldn’t be able to see the highlighting because the camera is not inside the PPV volume.

Technically, I could turn all those PPV volumes into narrow cylinders the ends of which intersect the object I am highlighting and the camera so I am looking at each object through it’s own individual PPV cylinder pipe that dynamically repositions to track the camera, but that sounds insane.

There has to be a better method than PPVs to be able to highlight multiple limbs of a mesh, simultaneously, with different colors. If anyone knows a good solution to this, please share your arcane knowledge.

I’m afraid you will not be able to build your game in shipping mode if you include UnrealEd modules. It could still be interesting to figure out how to create volumes at runtime, but for your use case I don’t think that’s relevant. As you pointed out, bound PPVs are only active when camera is within the volume, hence the misunderstanding on my part, I initially assumed that’s what you wanted.

So, a single unbound PPV will do the job. The PPV will highlight all the meshes with CustomDepth enabled, which gives you control over which ones are highlighted. Now all you need is to change the color on a per-mesh basis, right?

For that you can use Custom Stencil. It goes along with CustomDepth, it’s a property you define on the mesh, and which you can retrieve in the PPV material graph to differentiate one mesh from another.

Stencil must be enabled in project settings !
image

Example, in the PPV mat (stencil is an integer) :

Test level :
image

Test level blueprint :

Result :
bbbb

1 Like

This definitely looks like a better direction than spawning multiple PPVs. I’ve never worked with stencils before, so I’m going to try implementing your example to make sure I understand this correctly. I didn’t create the highlight materials and material instances that I am using currently, and their graphs are pretty complicated. So I need to make sure I understand how to apply your example to them.

Anyway, regardless of my understanding of stencils, you definitely answered my initial question on how to highlight multiple meshes with different colors at the same time, so I’m going to mark your post as the answer for my issue. Thank you for the detailed response.

So I finally figured it out. I have no idea how you did this with a lerp3, so here is a screenshot of how I got mine working.

In the level I modified my test code to toggle the render depth passing in 1, 2, and 3 to index the different highlights. The cube on the left is index 0, that’s why it looks normal.

I could pass the lerp4 directly to emissive color, but that results in the highlighting rendering through walls. So I set up the scene depth / custom depth if check to get it looking like your result. Found that info in this very helpful basic tutorial on custom depth.

Figured out that if I want more than 3 colors I have to do my own lerping. Here is the result in the material, you just chain the lerps together.

To get the same effect as your result, the depth test needs to be at the end of the lerp chain.
Any index after the depth test can be made to ignore depth. Pretty cool :slight_smile: