Kodeco Forums

Unreal Engine 4 Paint Filter Tutorial

In this Unreal Engine 4 tutorial, you will learn how to make your game look like a painting by implementing Kuwahara filtering.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/100-unreal-engine-4-paint-filter-tutorial
1 Like

Awesome! Thanks for sharing!

Thanks, really cool tutorial! Just as a heads up, putting custom shader files in ProjectFolder/Shaders doesn’t seem to work anymore with 4.21!

@tommytran Can you please help with this when you get a chance? Thank you - much appreciated! :]

Hi! Thank you very much for these tutorials :D. I was wondering where can I learn the code used in Unreal Engine 4 materials custom node. I am looking for a full guide, if someone exsists. I was trying to recreate a blackhole effect, like the one in the interstellar movie. Something like this: Shader - Shadertoy BETA
but I failed miserably.
Thank you again!

@tommytran Do you have any feedback about this when you get a chance? Thank you - much appreciated! :]

Thanks for the heads up! 4.21 has that as a “bug fix”:

Bug Fix: Replaced automatic virtual shader directory mapping with manual approach in StartupModule(). It should reduce the amount of directory exists queries at Editor startup.

It looks like you need to manually map the directory using AddShaderSourceDirectoryMapping.

Hi makemake,

The custom node uses HLSL so that’d be a good place to start. You can also check our Custom Shaders tutorial for information on how the HLSL is generated.

Thanks for the tip!

I managed to get it working again.
Incase other people come here looking for a fix, here is how I got it working.

In your game project’s C++ solution(replace “Projectname” with your projects name) add/modify the following files:

  • Projectname.Build.cs: (Add “ShaderCore” to your dependency modules)

PublicDependencyModuleNames.AddRange(new string[] { “Core”, “CoreUObject”, “Engine”, “InputCore”, “HeadMountedDisplay”, “RenderCore”, “Niagara”, “ShaderCore” });

  • Projectname.h (Derive of FDefaultGameModuleImpl to override StartupModule())

#pragma once

#include “CoreMinimal.h”

class FModifiedDefaultModuleImpl
: public FDefaultGameModuleImpl
{
virtual void StartupModule() override;
};

  • Projectname.cpp ( replace FDefaultGameModuleImpl with your derrived class, implement the function StartupModule() and call AddShaderSourceDirectoryMapping() to include your projects /Shader/ directory)

#include “Projectname.h”
#include “Modules/ModuleManager.h”
#include “ShaderCore.h”

IMPLEMENT_PRIMARY_GAME_MODULE(FModifiedDefaultModuleImpl, Projectname, “Projectname” );

void FModifiedDefaultModuleImpl::StartupModule()
{
FDefaultGameModuleImpl::StartupModule();
AddShaderSourceDirectoryMapping(TEXT(“/Project”), FPaths::Combine(FPaths::ProjectDir(), TEXT(“Shaders”)));
}

@s0meguy Thank you for sharing the solution - much appreciated!

I followed this to add C++ support for the project:
https://puppet-master.net/docs/unreal-engine-4/programming/how-to-add-c-support-for-a-unreal-project/
and followed s0meguy’s steps for aAddShaderSourceDirectoryMapping(), but it didn’t seem to work. I just jumped back to 4.20 for now.

The filter adds some significant flickering/jitter, especially around the thin parts of of leaves when pulling the camera back a bit.
Research suggested that going into the material, in this case PP_Kuwahara → Post Processing Material → Blendable Location, and setting Before Tonemapping can help for certain post processing effects, but didn’t do much for me in this case.
Turning off Temporal Anti-Aliasing, now accessible only in the project settings, does fix the jitter, but the overall scene doesn’t look as good when moving around.
This post links to a PowerPoint from Epic about the development of TemporalAA, which suggests using sharpening, blurring, or noise to compensate.

Adding a Gaussian Depth of Field to the PostProcessVolume with just a bit of far blur looks best to me. Lots of DoF settings available, helpful for retaining edges/details.

Thanks very much for putting this together! Just wanted to dump my info here for others.

Great tutorial, thank you!
Looks like the variance is miscalculated, though. You have to get the mean value first, then get the sum of the squared differences (pixelvalue - meanvalue), then divide this sum by the number of samples.

for (int x = Range.x; x <= Range.y; x++)
    {
        for (int y = Range.z; y <= Range.w; y++)
        {
            float2 Offset = mul(float2(x, y) * TexelSize, RotationMatrix);
            float3 PixelColor = SceneTextureLookup(UV + Offset, 14, false).rgb;
            Mean += PixelColor;
            Samples++;
        }
    }
    Mean /= Samples;
    
    for (int x = Range.x; x <= Range.y; x++)
    {
        for (int y = Range.z; y <= Range.w; y++)
        {
            float2 Offset = mul(float2(x, y) * TexelSize, RotationMatrix);
            float3 PixelColor = SceneTextureLookup(UV + Offset, 14, false).rgb;
            Variance += (PixelColor - Mean) * (PixelColor - Mean);
        }
    }
    Variance = Variance / Samples;

@phl4m Thank you for sharing this - much appreciated! :]

Hi, Thanks for sharing, so much useful content :grinning:

What if I’d like to add a full vertex and pixel shader? I’ve seen some plugins as examples in C++ but unfortunately I can’t figure It out how to make them work and how to link the shaders :sleepy:
Something like that:

void MainVertexShader(
float4 InPosition : ATTRIBUTE0,
float2 InUV : ATTRIBUTE1,
out float2 OutUV : TEXCOORD0,
out float4 OutPosition : SV_POSITION
)
{
OutPosition = InPosition;
OutUV = InUV;
}

Texture2D TextureParameter;

void MainPixelShader(
in float2 uv : TEXCOORD0,
out float4 OutColor : SV_Target0
)
{

@tommytran Can you please help with this when you get a chance? Thank you - much appreciated! :]

This tutorial is more than six months old so questions are no longer supported at the moment for it. Thank you!