Snapshot of the frame which the YouTube thumbnail is based on. A meerkat investigates the outside of a large egg

Snapshot of the frame which the YouTube thumbnail is based on. A meerkat investigates the outside of a large egg

A meerkat balances atop a large egg in a desert

A meerkat balances atop a large egg in a desert

A meerkat gets ready to pounce from its burrow

A meerkat gets ready to pounce from its burrow

A large egg sits alone in a desert

A large egg sits alone in a desert

A meerkat whose head is poking through a hole in a large egg

A meerkat whose head is poking through a hole in a large egg

A meerkat in its lookout position, searching the source of an eagle call

A meerkat in its lookout position, searching the source of an eagle call

A bird flies in the sky, with a lens flare behind it

A bird flies in the sky, with a lens flare behind it

An eagle flies at a high rate of speed, attempting to pick up a meerkat with its talons

An eagle flies at a high rate of speed, attempting to pick up a meerkat with its talons

A meerkat shakes in fear, deep inside its burrow as an eagle's beak pokes through the entrance

A meerkat shakes in fear, deep inside its burrow as an eagle's beak pokes through the entrance

An eagle's beak pokes through a hole in a large egg in comedic fashion

An eagle's beak pokes through a hole in a large egg in comedic fashion

An eagle attempts to free its beak from a hole in a large egg

An eagle attempts to free its beak from a hole in a large egg

A meerkat searches the skies for the location of an eagle, with an egg beside it

A meerkat searches the skies for the location of an eagle, with an egg beside it

A meerkat burrow's entrance is covered by a large egg

A meerkat burrow's entrance is covered by a large egg

Meerkat Demo

07 January, 2024

progress_activity
0:00
0:00
/
0:00

meerkat demo with custom lens flare and chromatic aberration

After several years using Unreal, I finally said enough is enough and vowed to replace Unreal's chromatic aberration & lens flare effects with far superior implementations.

A few weeks of tinkering later and a small demo video to show off my work, voila.

Why though?

Anyone with an attentive eye who's played around with a Post Process Volume knows about Unreal's crappy chromatic aberration & lens flare effects, largely unchanged since they were first committed to the Unreal Engine repo 10 years ago.

Chromatic Aberration

Out of the box, Unreal's chromatic aberration effect just samples the scene 3 times (for red, green & blue) at 3 different offsets, then appends each of the sampled channels back into one final image. Yes really, it's that simple.

Unreal Engine's out-of-the-box chromatic aberration effect, as shown on the official Unreal Engine documentation

Unreal Engine's out-of-the-box chromatic aberration effect, as shown on the official Unreal Engine documentation

Whilst very performant, it doesn't look great. In fact, it looks so not great that any user with a very fundamental knowledge of how computer graphics works can easily pick apart exactly how the effect works just by looking at it. And that just so happens to be the point where I deem an effect needs to be updated.

Of course, there's no need to reinvent the wheel on something as popular as chromatic aberration, so I went looking for chromatic aberration shaders that I could implement into Unreal (which consists of launching RDR2 with a trainer and ReShade installed and going through shaders on GitHub until I find something performant & beautiful).

A few hours later, I settled on Prism by Jakub Maksymilian Fober.

Prism in Red Dead Redemption 2

Like most chromatic aberration shaders, it samples the scene nn times, each at a different offset, then multiplies the sample by a color determined by the offset. Take a look:

float3 Spectrum(float hue)
{
	float3 hueColor;
	hue *= 4f; // slope
	hueColor.rg = hue - float2(1f, 2f);
	hueColor.rg = saturate(1.5 - abs(hueColor.rg));
	hueColor.r += saturate(hue - 3.5);
	hueColor.b = 1f - hueColor.r;
	return hueColor;
}

void ChromaticAberrationPS(
	float4 pixelPos  : SV_Position,
	float2 viewCoord : TEXCOORD,
	out float3 color : SV_Target
)
{
	// Get radius at increasing even powers
	float4 pow_radius;
	pow_radius[0] = dot(viewCoord, viewCoord);     // r²
	pow_radius[1] = pow_radius[0] * pow_radius[0]; // r⁴
	pow_radius[2] = pow_radius[1] * pow_radius[0]; // r⁶
	pow_radius[3] = pow_radius[2] * pow_radius[0]; // r⁸
	// Brown-Conrady division model distortion
	float2 distortion = viewCoord * (rcp(1f + dot(K, pow_radius)) - 1f) / normalize(BUFFER_SCREEN_SIZE) * 0.5; // radial distortion
	// Get texture coordinates
	viewCoord = pixelPos.xy * BUFFER_PIXEL_SIZE;
	// Get maximum number of samples allowed
	uint evenSampleCount = min(ChromaticSamplesLimit - ChromaticSamplesLimit % 2, CHROMATIC_ABERRATION_MAX_SAMPLES); // clamp value
	// Get total offset in pixels for automatic sample amount
	uint totalPixelOffset = uint(ceil(length(distortion * BUFFER_SCREEN_SIZE)));
	// Set dynamic even number sample count, limited in range
	evenSampleCount = clamp(totalPixelOffset + totalPixelOffset % 2, 4, evenSampleCount);

	// Sample background with multiple color filters at multiple offsets
	color = 0f; // initialize color
	for (uint i = 0; i < evenSampleCount; i++)
	{
		float progress = i / float(evenSampleCount - 1u) - 0.5;
		progress = lerp(progress, 0.5 - abs(progress), AchromatAmount);
		color +=
			// Manual gamma correction
			GammaConvert::to_linear(
				tex2Dlod(
					BackBuffer, // image source
					float4(
						progress // aberration offset
						* distortion // distortion coordinates
						+ viewCoord, // original coordinates
					0f, 0f)).rgb
			)
			* Spectrum(i / float(evenSampleCount)); // blur layer color
	}
	// Preserve brightness
	color *= 2f / evenSampleCount;
	color = GammaConvert::to_display(color); // linear workflow
	color = BlueNoise::dither(color, uint2(pixelPos.xy)); // dither
}

It's self-contained and quite small. A great candidate for a quick implementation into Unreal.

Style

You might've noticed that the chromatic aberration in Meerkat Demo and Prism isn't quite 1-to-1. That's because I'm a massive fan of anamorphic lenses and the visual artifacts that come along with them.

Ever since the (now long forgotten) reveal trailer for Minecraft Earth in 2019, I've been obsessed with the camera and lens setup used in the trailer. It's what inspired me to turn the chromatic aberration into a radial effect instead of the typical (and physically accurate) zoom.

Notice the horizontal stretching of the bokeh at the top & bottom of the frame and vertical stretching at the sides