Saturday, July 28, 2012

Customizing cgwg's CRT Pixel Shader

This document identifies some of the user-configurable options available in cgwg's GLSL CRT shader and provides some screenshots to explain their effects.

For individuals interested in recapturing the look of retro gaming consoles played on a CRT television like the ones we grew up with, cgwg's CRT shader provides the closest approximation available for modern LCD displays. The pixel shader is purely cosmetic--nothing could impart the technical advantages of a CRT, such as negligible input lag and insanely high contrast, to an LCD--but it can go a long way to displaying old games as we remember them. Since memory and nostalgia are important to the way we perceive these games, each person will have different feelings about what looks "right" to them.

We will be using the latest (as of the time of this writing, 7/28/12) and most complete version of the shader, known as CRT-Geom. This shader allows for simulated screen curvature, rounded corners, simulation of a back-tilted screen (for arcade emulation) and many other goodies. All images are captured at 4x scale, click the thumbnails to embiggen.

This is what the shader looks like with all default parameters, also known as the 'curved' variant (cgwg chose sane values for all of the variables by default):
The user-configurable parameters are primarily located in two places in the shader, starting at line 87 for the first section and line 135 for the second section. You should be able to open and edit the shader in any text editor, but I recommend Notepad++.

The first parameters, 'gamma of simulated CRT' and 'gamma of display monitor' allow the shader to determine how much gamma correction to apply to the image. CRT displays generally had higher gamma ratings than most LCD displays (2.4 for a typical CRT vs 2.2 for a typical LCD), so the shader adds 0.2 to the gamma to compensate. If you calibrate your LCD to a higher or lower gamma setting, you may wish to change the 'gamma of display monitor' to reflect your individual settings. You can also raise or lower the simulated gamma to make a darker, more saturated look:
or a lighter, desaturated look:

Next, we have 'overscan.' Each CRT showed a slightly different amount of the picture, and the cut-off portion was referred to as overscan. You can modify the amount of over- or underscan by changing this variable. Setting it to (0.00,0.00) will assume you want the picture aligned exactly with the borders of the window. Setting it to a value greater than 1.0 (for example, 1.10,1.10) will cut off a large portion of the screen (approximately 10% in this example):
while setting it to a value less than 1.0 will surround the screen with black bars (i.e., underscan):

Increasing the overscan slightly, say, to 1.02,1.02, can cut off unsightly garbage lines at the top of the screen, which is common on NES games.

Next, we have 'aspect ratio,' which controls how much our screen curvature settings will affect the top/bottom and sides of the image. The default setting of 1.0, 0.75 means the curvature will be more pronounced on the top/bottom than on the sides, to compensate for the standard 4:3 aspect ratio. Setting it to 1.0, 1.0 will make the settings affect both sides of the screen equally, which isn't quite right in that the top/bottom actually appears more curved than the sides:
 If for some reason you want only the sides straight, you can set it to 1.0, 0.5:

or 0.5, 1.0 to make only the top/bottom straight:

Next up, we have 'simulated distance from viewer to monitor.' Similar to the 'aspect ratio' setting, this exaggerates the effect of the 'tilt angle' setting that we'll be covering in just a moment. The default value of 2.0 is reasonable. Increasing the value reduces the effect of the tilt angle:
while lower values magnify it:

Next, we have 'radius of curvature,' which determines how curved our simulated TV tube will be. Setting the value to 2.0 reduces the curvature to something more like what I remember.

'Tilt angle' pincushions one side of the screen. To tilt the screen back, use a negative value for the second number. The default for the 'arcade tilt' variant, (0.0,-0.15), is a sane value:
while increasing the second value tilts it back further, which can be disorienting:

After that, we come to 'cornersize,' which determines the radius of the corner arc. I think a setting of 0.03, as used in the default image, looks appropriate. Increasing the value to 0.10 makes for some silly, super-round corners:
while a value of 0.001 makes for super-pointy corners:

For the last parameter in this section, we have 'cornersmooth,' which determines how sharp (sharp as in sharp/blur, not as in pointy) the corners look. The default value is 1000.0, which looks fine, but I noticed that if you set the value insanely low (i.e., 80), you get a little vignetting around the edges, which reminds me of my crummy old TVs:
On to the next set of parameters.

The second set of options are either enabled or disabled by using 'comments,' which in this case are represented as a double-slash, like this //.

The first option is 'linear processing,' which calculates the interpolation in linear gamma:
The effect is subtle (black outlines will be thinner), but disabling it via commenting it out (//#define LINEAR_PROCESSING) can improve the framerate with some video cards.

Next, we have 'Enable screen curvature.' Pretty self-explanatory, but if you comment this line out, you'll get straight top/bottom/sides of the screen, but with the corner arcs intact (barely visible in this shot):
By commenting this out and setting 'cornersize' to 0.0, you get an idealized flat CRT tube, which is represented by the 'CRT-flat' shader variant:

The next two options work together (that is, if you uncomment one, you should comment the other). By default, the shader uses 3x oversampling of the beam profile, which makes brighter pixels bleed further into the surrounding scanlines. Here it is with the default oversampling, scaled up 400% (look closely; it's most obvious on the bright red of Mario's sleeve):

and here it is with the gaussian beam profile:
The gaussian beam profile may have better performance on some video cards.

Those are all of the options available for the standard CRT shader, but there are a few more things we can add in. The first of which is support for interlaced video. Some games did a sort of fake doubling of their vertical resolution by showing different lines of a progressive signal on alternating frames in a process known as interlacing. If handled incorrectly, you will see a ton of combing artifacts (weird horizontal lines on moving objects), as well as distorted scanlines (first image taken using this older, non-interlace-supported version):

These pictures unfortunately cannot show the jutter in the lower image that occurs from showing different lines on each alternating frame.

Next, we have support for pseudo-hires transparency--whereby games created a fake transparency effect by doubling the horizontal resolution to 512 and then covering the transparent area with thin vertical lines, which were then squished together and blurred when the CRT displayed the image--in the games that utilize it, including Jurassic Park and Kirby's Dreamland 3:

This blurring is accomplished by adding another pass to the shader that does nothing but squish the horizontal resolution from 512 to 256. Note: this variant is multipass and therefore incompatible with the official bsnes/higan GUI. Retroarch can use it just fine with bsnes-derived emulation cores, but it is incompatible with FBO/2-pass shader stacking.

Finally, to simulate the natural phosphor glow of a CRT display, cgwg created a multipass version of his CRT shader that includes a "halation" pass, which provides a soft blur that is most noticeable against a black background, such as the Nintendo logo screen in Super Mario World (it also seems to be doing some aspect ratio correction, though I'm unsure if that's intentional):
Here's a shot of Super Metroid's title screen without and with halation, respectively:

Note: Like the TV blur variant, this halation variant is multipass and thus incompatible with the official bsnes/higan GUI. It works fine with Retroarch but is incompatible with the FBO/2-pass feature.

Now, beyond the CRT display, our childhood gaming experiences were also colored by limitations of the NTSC signal standard and the lossy, analog hookups we used to connect our consoles. To capture these effects, we turn to blargg's NTSC filter, which runs on the CPU rather than the GPU and can thus be combined with our CRT shader.

The NTSC filter is available in these CPU filter packs (compatible with RetroArch but not bsnes/higan):
Linux 32-bit
Linux 64-bit
Mac OS X

The least intrusive variant of the filter is the RGB preset, which simulates a high-quality RGB connection. This variant provides NTSC color gamut correction but leaves the signal otherwise untouched:
Next, we have the S-video variant, which has color correction and some fringing, but is still relatively high-quality. This is the variant I use most of the time:
The third variant, composite, brings in a lot of signal noise and color bleed, just as you would get through a composite, RCA-cable connection:
The worst-quality variant is RF, which looks just as messy as the composite signal, but with added jutter every other frame, just like when using the RF adapter bundled with classic consoles:
One thing to be aware of, though, when using any variant of the NTSC filter is that it also does some gamma correction, which can be overwhelming to some people when combined with the CRT shader. To mitigate this effect, you can modify the CRT shader's 'gamma of simulated CRT' to match the 'gamma of display monitor' value to disable the shader's own gamma correction (shown here with the S-video variant):

My personal preferences, depending on the game/system: 'radius of curvature' of 2.0, cornersize of 0.03, cornersmooth of 80.0 and NTSC-Svideo. Sometimes I'll use the flat variant (especially on displays that have problems with moire patterns caused by curvature+scanlines), and I often use the halation variant when I'm on a machine with sufficient resources. I always use interlacing support, which has been incorporated into all of the latest versions, and TV blur when possible (i.e., when using Retroarch rather than the official bsnes/higan GUI).


kimonoz67 said...

Nice article! I am left wondering, though, have you used some kind of integer-scaling filter for the 4x-scaled shots? Or, if just leaving bsnes to default settings takes care to respect integer scaling and centering the display through the use of black bars on all four sides?

Less importantly, between the "default" and the "ntsc-rgb" shots, what exactly has been changed? (Was it only the "cornersmooth" value?) Because it seems to me the resulting picture is midway between the flat and the curved versions, which I appreciate.

Hunter K. said...

The shots were taken with Retroarch in windowed mode at 4x scale, so yes, they are perfectly integer-scaled for the most part (I believe the oversampling/gaussian pictures are not exactly 4x, but very close). bsnes should indeed default to integer scaling, IIRC, and pads the image with black bars.

Yes, the curvature radius in that image is set to 2.0--my preferred value--instead of the default 1.5.

kimonoz67 said...

I think then, not falling at the "hype" of the halation shader, which seems overdone at times -- indeed, I am living in an imaginary world in which idealized CRTs are flat and do not feature bright-glow halation effects.

I may going to settle down with the flat version of the one-pass shader, therefore, although I admit your preferred tweaks look neat.

(Note: 0.3 or 0.33 for cornersize -- typographical-error contradiction?)

kimonoz67 said...

All I am needing to decide (when I have some free time) is whether or not to stack the NTSC-RGB filter on top of the gamma-uncorrected flat CRT shader. Again, I admit I am living in an ideal world, where CRTs are flat, produce no halation effects, and provide digital inputs, at the least RGB input possibilities.

Hunter K. said...

Ah, yes, 0.3 was a typo. It was supposed to be 0.03. Thanks for catching that!

I don't see any problem with going for something idealized. :) I, too, will use the flat version on some games, and usually shy away from the halation version with SNES games, though I prefer it for NES games.

kimonoz67 said...

I have now chosen my side: to be going _without_ the NTSC filters as such.

Rei dos Vermes said...

Amazing shader. But unfortunatelly it doesn't work with Gens core (black screen)

Hunter K. said...

@Rei dos Vermes
Hmm. Works fine here. You're using RetroArch with the Genesis Plus GX core?

Anonymous said...

Can blargg be used in Retroarch?

Hunter K. said...

Most definitely! I updated the post with links to download blargg's NTSC filter, along with other CPU-based filters.

Unknown said...

Hi Hunter,
I'm currently running the latest windows release of Retroarch/Libretro. And can't work out how to achieve that curved NTSC effect you have in the last 5 images. I really like the curved effect of the CRT shader and bleeding of colours in the ntsc ones. Could you briefly explain how to get the best of both as you have achieved in your post?

Hunter K. said...

Hi Gary,
If you've already downloaded the CPU filters linked there, you'll need to open your retroarch.cfg file in a text editor and add the line:
video_filter = /path/to/filter.filter
In the latest git code, this CPU filter format has been deprecated, but Squarepusher has already ported blargg's NTSC for SNES (also works with NES) to the new format.
Note: this CPU filter is hardcoded for certain resolutions, so it won't work on every source. Genesis Plus GX has a core option that will enable its own version. For other platforms, you'll have to use maister's shader version, which is more forgiving but requires some serious GPU grunt :)

Unknown said...

Thanks Hunter.
I've been trying to work that out for over 6 months. I Really should have asked you sooner. The two effects are now working together beautifully. Thanks for your help, I'll be keeping an eye on your excellent blog from now on.

Anonymous said...

Hi, great tutorial. However I cannot see how to load the shader. I have placed the unarchived folders into the shaders folder but when I try to load the preset inside of retroarch the folders appear to be empty. What is the difference between the .cg and .cpg extension files. Many thanks. J

Hunter K. said...

'Load shader preset' only works with cgp files, which stands for...Cg Preset. If you only want to load a single Cg shader file, just go into the shader menu, change the number of passes from 0 to 1 and then navigate to the Cg file you want to load from there.

Anonymous said...

Thanks for your help. Do you have the preset for your edits above?

Many thanks

Anonymous said...

I just today found this post.

Here in France we had RGB output on Super Nintendo. Almost all French TVs still in usage in the 90s had one Peritel (SCART) input, and the default cable with Sega and Nintendo consoles was an RGB one.

We had the best colors, but not everithing was ideal. We had to live with 50Hz, so while this also increased vertical resolution, we experienced slower gameplay, and most games where "letterboxed" because of the increased resolution. Only a few games where "pal optimized".

On my crappy 14 inch bedroom TV, the halation effect was as pronounced as in these pictures on the Nintendo Logo.
In Super Mario bros in All Stars, the cloud bar in plant bonus stages distorded the picture so much that the plant was half a bloc off on the left at the level of the clouds. It was really better on the living room set, wich was bigger, more stable and with stereo sound.

Unknown said...

Hi, I love this shader, thank you for creating it.
I have a question though where abouts in the shader code can I implement the pseudo hi-res transparency effect? I am unfortunately unable to work out what I need to edit or add.

Analytics Tracking Footer