Using dynamic materials in postprocessvolumes

Hello again everyone!

The last week I’ve been playing around with postprocess-shaders, and most of my experiments have gone well. I do however have one problem that I thought I would share with everyone. I can’t seem to get dynamic (code-editable) materials to work with postprocess volumes (they work fine with normal meshes however. The first problem is that the Blendables-collection that contains the materials to be used in post-process is not available through a TSubObjectPtr. I can of course get around this by Get():ing the underlying pointer and access the settings from there. However, doing this and setting the material (I’ve tried in ctor, at startup and during runtime) does not seem to have any effect. I’m guessing that like almost all other properties, setting the correct instance in a collection is not enough. It probably has to be done through an appropriate setter or other properties that are related to the materials init proc are not run.

Now before I start debugging what happens when the editor sets this property and what might not be triggering I’m wondering if anyone else has gotten this to work? Any insights would be much appreciated :slight_smile:

For reference, here is my test code for when I tried adding it in runtime:

Header:



#include "Test.generated.h"

#pragma once

UCLASS(Blueprintable, BlueprintType)
class ATest: public AActor
{
	GENERATED_UCLASS_BODY()

public:
	void BeginPlay() override;

public:
	virtual void OnHoverEnter();
        virtual void OnHoverLeave();
        virtual void OnInteraction();
public:

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ItemProperties)
		TSubobjectPtr<class UStaticMeshComponent> WorldMesh;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ItemProperties)
		TSubobjectPtr<class UPostProcessComponent> PostProcessVolume;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ItemProperties)
		FLinearColor HoverColor;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ItemProperties)
		FLinearColor PendingInteractionHoverColor;

private:
	UMaterialInstance* HoverMaterial;
	UMaterialInstanceDynamic* DynamicMaterial;
};


CPP:



#include "TestProj.h"
#include "Test.h"

#pragma once

AInteractableEntity::AInteractableEntity(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
	WorldMesh = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("WorldMesh"));
	WorldMesh->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	WorldMesh->AttachTo(InteractionBounds);
	RootComponent = WorldMesh;

	PostProcessVolume = PCIP.CreateDefaultSubobject<UPostProcessComponent>(this, TEXT("PostProcessVolume"));
	PostProcessVolume->bUnbound = false;
	UPostProcessComponent* Volume = PostProcessVolume.Get();
	Volume->Settings.Blendables.Empty(); //This is added in runtime now
	PostProcessVolume->AttachTo(WorldMesh);

	HoverColor = FLinearColor(0.0f, 0.8f, 0.0f, 1.0f);
	PendingInteractionHoverColor = FLinearColor(0.8f, 0.0f, 0.0f, 1.0f);

	static ConstructorHelpers::FObjectFinder<UObject> InteractableObjectHoverMaterialFinder(TEXT("Material'/Game/Materials/Test/M_Interactable_Hover_Outline.M_Interactable_Hover_Outline'"));
	UMaterialInstance* HoverMaterial = Cast<UMaterialInstance>(InteractableObjectHoverMaterialFinder.Object);
}

void AInteractableEntity::BeginPlay()
{
	UPostProcessComponent* Volume = PostProcessVolume.Get();
	DynamicMaterial = UMaterialInstanceDynamic::Create(Cast<UMaterialInstance>(HoverMaterial), GetWorld());
	Volume->Settings.Blendables.Empty();
	Volume->Settings.Blendables.Add(DynamicMaterial);
}

void AInteractableEntity::OnHoverEnter()
{
	WorldMesh->SetRenderCustomDepth(true);
}

void AInteractableEntity::OnHoverLeave()
{
	WorldMesh->SetRenderCustomDepth(false);
}

void AInteractableEntity::OnInteraction()
{
	if (DynamicMaterial)
	{
		DynamicMaterial->SetVectorParameterValue(FName(TEXT("OutlineColor")), NewColor);
	}
}


Oki, I managed to get the dynamic materials to work. My problem seems to have more to do with assigning my blueprint material to the array, rather than using it dynamically.

Simply setting the material up in beginplay like this seems to be working fairly well. I’ll come back with more info if I manage to automatically set up the initial assignment as well :slight_smile:



void AInteractableEntity::BeginPlay()
{
	UPostProcessComponent* Volume = PostProcessVolume.Get();
	UMaterial* HoverMaterial = Cast<UMaterial>(Volume->Settings.Blendables[0]);
	DynamicMaterial = Cast<UMaterialInstanceDynamic>(HoverMaterial);

	if (HoverMaterial && !DynamicMaterial)
	{
		// Create and set the dynamic material instance.
		DynamicMaterial = UMaterialInstanceDynamic::Create(HoverMaterial, this);
		Volume->Settings.Blendables[0] = DynamicMaterial;
	}
}


Okay, I figured out how to set the initial one from code, it wasn’t so hard once this other stuff was in place. My only remaining problem is that all my dynamic instances seem to still be pointing at the same original material (I guess they become the original’s children when you create them?). This has the undesired effect that all material instances also change. I guess if I can clone the UMaterial before I create my dynamic instance I could fix it, but just creating a new material from the original doesnt seem to cut it. I’ll post the solution when I figure it out…

I figured out the problem but was too tired yesterday to complete the post. But here goes!

So my last problem was not the problem I originally thought. The instancing of the materials worked fine, but I had misinterpreted how post processing volumes work. I was under the impression that each volume will create or amalgate a new post processing pass, and the volume basically dictated which part of the current view (if any) was subject to the post process. This is NOT the case. When I reread the documentation, in particular this part:
“… A PostProcessVolume will only be applied when the camera is within the bounds of the volume …” from Post Process Effects | Unreal Engine Documentation
So basically the volumes only dictate which areas are subject to a particular effect, only one post process effect can be run in any given post processing pass (although I must assume that several passes are possible) and therefore to have different inputs to different actors affected by that material, you basically have to send it as an input to the “one” PP material instead of trying to apply different PP materials to each individual actor.

Now to solve my particular problem that I’m working on (creating outlines with configurable colors per actor) I’m thinking the easiest way is probably to create an additional GBuffer which I’ll set to a relatively low bit depth and use as a “tag layer”, or if I can’t get that to work, to maybe just set up a render target 2D and use that as an input. Another possibility is maybe to look how the custom depth buffer is implemented and model my extra buffer after that. Another interesting this is, if I get that to work, I might be able to calculate the outline from that buffer too (right now I’m using edge detection by measuring neighboring pixel depths in the depth buffer, but with the tag buffer I could just do some edge detection algorithm instead (sobel etc.)

I saw this blog where someone already seems to have accomplished this: http://www.tomlooman.com/ue4-evolves-outline-post-effect/
Unfortunately he didn’t post any details on how he accomplished the object tagging, but maybe he’ll release some details eventually :slight_smile:

Best regards,
Temaran