Thursday, June 7, 2018

Trading Moire for Noise in Shaders

Note: the ugly artifacts we're trying to deal with in this post also happen whenever you up/downscale images, so be sure to click the thumbnails to view at full resolution.

A lot of folks like to apply a pincushion/curvature effect to their emulator image to try and capture a bit of the old CRT TV curved tube effect. This can be a problem, though, when paired with a scanline effect, since the curvature introduces an aliasing artifact on the scanlines known as a "moire pattern":
This is a worst-case scenario, used for illustrative purposes.
Notice the ugly pattern fanning out from the middle of the outside edges of the screen
This problem comes up a lot in virtual reality applications, where content often runs through a pincushion effect to compensate for the lens distortion of head-mounted displays, which then leads these same ugly patterns to crop up on any design with parallel/converging lines.

Graphics programmer Timothy Lottes (author of the popular FXAA algorithm and a lovely public domain CRT shader, among others) was working on ways of dealing with the aliasing/moire problem for VR and published this shadertoy showing how to hide it by introducing jitter/noise in where the texture is sampled. Turns out that same strategy can be used to mitigate our scanline moire.

When applied to an actual game image, this:
becomes this (caution, big animated gif; the noise effect doesn't really work in still images):
You can see that the noise is concentrated along the same areas where the moire pattern was visible before, but it's much less obtrusive and sort of melts away when viewed at a distance.

When curvature is added to an existing, normally flat CRT shader (Themaister's crtglow-gaussian shader, one of my favorites), we go from this:
to this, with the noise/jitter-based mitigation:
In this example, the noise also adds a touch of analog warmth/sparkle to the CRT effect, which I like.

Here's the code snippet that does it (all copyrights belong to Mr. Lottes):
This code can be added to many CRT shaders by wrapping the shader's fragment texcoord declaration with the moire_resolve() function to add the jitter/noise and then wrapping that with the Warp() function to add the curvature. You can see an example of where I've done exactly that in the crtglow-gaussian shader here to create the screenshots above.

Update 12/1/2018: A more traditional--and highly effective--method of moire removal is through elliptical weighted average (EWA) filtering, as described here. Fellow shader enthusiast torridgristle whipped up a GLSL version with curvature, which can be added after CRT/scanline shaders to look like this:
It's quite fast, too, making it a very good, flexible choice for curvature plus moire-mitigation.

Analytics Tracking Footer