Temporal-Aware Animated Textures in UE5

Temporal Anti-Aliasing is a necessary evil in modern real-time engines. It has one the best bang-for-your-buck cost compared to alternatives, let alone dealing with other artifacts like texture shimmering that MSAA can’t account for.

Still, there’s is a delicate balance between ghosting and jittering that has to be juggled on the rendering side, and the relevant parameters may not be exposed to the user in intuitive ways.

So your game can be at the mercy of the engine’s defaults if your team is not aware of issues caused by TAA, or until the review bombs kicks-in.

Actual footage of temporal ghosting bullying a gamer.

And now we’re entering the reconstruction era: It’s simply not worth the GPU cost to render all the pixels at native resolution anymore. And of course, with all the benefits it comes a whole new can of problems we’ll have to buckle up and learn how to solve.

Credit: Digital Foundry’s awesome behind the scenes of The Surge 2. Source: https://youtu.be/WjPiJn9dkxs?t=1758

It recently caught my attention how Unreal 5 Temporal Super Resolution completely muddies animated textures. It makes sense, in retrospect: Temporal effects like TAA need Motion Vectors to build a history of how a pixel moved over time, so it can be snapped back in place correctly, factor in the reconstruction step and that can paint a clearer picture of how things can go south. Or Just watch the gif below instead:

I promise what you see is not heavy gif compression.

Network of the material above. As simple as it gets.

Someone pointed out that Epic’s recommendation is tweak TAA settings, meaning, console variables. Which is an approach, I guess, but not really a solution if the settings for your use cause issues in other rendering aspects. Still, here are some of the variables that you can experiment from the get go:

r.TemporalAASamples (8 by default)

r.TemporalAACurrentFrameWeight (0.04 by default)

But in the case of animated textures, the problem can be tackled from where it came from: The shader.

The solution is to inject the same vector that you’re passing to transform your UVs, into the Vertex Position Offset, so the history buffer of the engine will build the Motion Vectors for temporal accumulations. But you might not actually want to offset your vertex position, so the trick is do the offset only in the previous frame. Luckily for us, there is a node in the material editor responsible for switching between current and previous frames contexts: PreviousFrameSwitch.

You have to slightly modify the original vector, like passing it through Delta Time, which is the time elapsed between the frames, and the world UV scale. Unreal being in cm(urgh), 100 units as the default is a safe bet. Here’s a network that fixes the issues on the examples above.

Beautiful. Just like the day I lost you to TSR.

By the way, you need to change change the “Output velocities due to vertex deformation” to On in Project Settings in order to get the setup above working. Also worth taking a look at the tooltip as well.

Never thought I could be happy by solving issues for things taken for granted but that’s the world we live now, for better or for worse. You can bet there will be future posts dedicated to solving Lumen’s reflection jank. A sneak peek related to this very post: https://x.com/tempkinder/status/1502821055202693126?s=20


See you next mission.

Previous
Previous

Exporting clean heightmaps from QGIS: Redux

Next
Next

Installing Proxmox on eMMC Devices