August 15 Update: New NTSC shader with configurable lowpass/bandpass filtering and colorburst rate/offset
I have just done some final touches on a new NTSC shader which I’m calling Patchy NTSC. It started out as an attempt to combine the features of artifact-colors and mame-hlsl’s NTSC passes into one shader, but I ended up making it much more advanced than I meant to. I plan to submit a poll request of it (so that it can officially get into shaders_slang), after getting a little bit of feedback on this forum and making some final changes and fixes.
Using the settings, you should be able to convincingly match specific consoles’ NTSC video signals. I’ve included presets that emulate the NES/FC and Genesis/MegaDrive (the latter by eye, not quite accurate, too much blur and too little luma fringing), and I’ll allow anyone to post their own settings for more consoles.
Download here. Some quick and dirty CRT presets are in the main folder, and the NTSC implementation itself is located at resources/patchy-ntsc/patchy-ntsc.slangp. For more info about what’s in the resources folder, read the included readme and changelog.
Full list of settings and features
- Automatic detection and adjustment for cropped overscan, assuming the same amounts were cropped on opposite sides.
- There are a few built in test patterns
- NES support. Hue error amounts for 2C02G and 2C02E are emulated. There is a manual toggle for the different artifact pattern seen in Battletoads.
- Genesis/MegaDrive jailbars on solid backgrounds.
- Customizable color carrier, to match a real console’s artifact pattern. You can adjust its offsets (initial, per-line, and per-frame), amplitude (now x0.2 by default), and frequency, and you can adjust the duration of the on-screen part of the scanline.
- (Edit: Calculating from the CXA1145’s data sheet, the correct amplitude is 1.0, but this causes much more interference into luma than I get on my real Genesis. 0.2 is more like a guess; I think it’s more like 0.5, but for something this fundamental, we should be using real data, not guess-and-check.)
- Customizable low-pass filter on the luma signal (Y) before adding chroma onto it. Low-pass filters to choose from include the sinc window from artifact-colors, Guest.r’s gaussian blur, aliaspider’s GTU signal resolution, and the 3 band equalizer from NTSC-CRT. For the 3-band equalizer, you might or might not want to go into patchy-ntsc-inc-filters.inc and increase HISTLEN.
- Customizable filters to separate the luma (Y) and chroma (IQ or UV) signals from each other. To get luma, you get the same low-pass filters as above, plus a simple crappy “2-point comb” filter. To get chroma, you can use the sinc window bandpass from artifact colors, a simple “2-point comb”, or the 3-band equalizer from NTSC-CRT. For the 3-band equalizer, you might or might not want to go into patchy-ntsc-inc-filters.inc and increase HISTLEN.
- After getting decoding YUV (not YIQ), a customizable R-Y/G-Y/B-Y correction is applied, using data from real jungle chips’ data sheets. The default decoding is automatically sync’d to standard color bars, although the resulting RGB values are still prone to getting clamped.
- Contrast, brightness, color, and tint can be customized. Tint 0.0 and saturation 1.0 are matched to standard color bars.
- A problem on old CRTs where bright red (and some bright blue) bleeds to the right can optionally be emulated. A working CRT should not have this problem. I’m not sure of this, but what I believe happens is, whenever RGB values go over some threshold (1.0 by default), whatever is over that threshold will instead get added onto the next pixel, so when there is a long line of these exceedingly bright pixels, they will keep compounding, resulting in a long smear to the right.
- Gamma and the phosphor gamut are emulated. Grade’s EOTF function is used for gamma, and Chthon’s gamutthingy lookup tables based on Trinitron phosphors are used for the phosphor gamut. Chromatic adaptation can be toggled on or off.
A separate shader called Patchy Color can be used for just the color grading features.
Screenshots of the included presets (JPEG-compressed, unfortunately)
This game would get panned and called offensive in 2024. It has a LOT of dithering that the SEGA Genesis smooths over, as well as a lot of undersaturated reds that look fully saturated on 90s hardware (though that might be unintentional).
Also, there’s this jailbar pattern, even on solid backgrounds. This happens on many model 1s prior to the VA6 revision.
I don't know much about electricity, so could someone please click here and explain what's happening in this circuit?
These screenshots are from a schematic of the Model 1 VA3 and from the data sheet of the CXA1145. I believe this might be the part that’s causing the jailbars, but I don’t understand what this is doing.
The disgusting red bleed can easily be turned off in the settings. It only happens on CRTs that are wearing out and have their contrast too high.
The artifacts on the NES flicker rapidly, so they become much more jarring when looking at an individual frame. Notice this distinct diagonal-line pattern that the NTSC NES has.
(These screenshots are using hyllian instead of advanced.)
Note: the NES mode requires you to set your emulator’s color palette to “Raw”. The shader needs that information to emulate the NES’s colors correctly.
Edit August 16
There are some details I want to note about my implementation, in case anyone in the future is going to work on this.
The most important rule of Patchy NTSC
The most important rule of Patchy NTSC is that NTSC for video games shouldn’t be emulated using the broadcast standards, but they should instead be emulated using things like service manuals, data sheets, and other documentation about actual CRTs and video game consoles. Neither video game consoles nor consumer CRT TVs implemented the standards exactly right; they were more like a hack. Notice how I’ve intentionally not used different bandwidths for I and Q, and I’ve included nonstandard color changes that the CRTs actually did.
Regarding the console’s modulator chip
I’ve looked at the NTSC chips that appeared in the SEGA Genesis and SNES. (SNES ones can be found here. Genesis ones can be found with a few google searches.) They all use about the correct YUV matrix, albeit with some slight variation, probably not intentional. I’ve chosen to skip this slight difference and use the standard YUV matrix instead.
Even so, these consoles didn’t all use these chips in the exact same way. The Genesis in particular has been known for its messy composite signal, which varied from good to bad across its different hardware revisions. Inside this post, I’ve included a screenshot from the CXA1145’s data sheet and a scre aboveenshot from a schematic of the VA3 Model 1 Genesis, showing the part that I think causes the worse signal. The data sheet wants you to put a delay line on Y and a band pass on C, but the Genesis seems to have replaced the delay line with some other filter that I don’t understand. I haven’t properly learned what these electrical components do.
To emulate the VA3 Model 1 Genesis’s jailbars, I simply added a sine wave onto the signal. It’s nothing complex.
Regarding clamping in the jungle chip
- In the jungle chip data sheets that I’ve looked at, there are clamping levels at every step. That is, the Y signal input is clamped, the C signal input is clamped, the resulting R-Y and B-Y are clamped, and the final R, G, and B are clamped.
- The setting for Tint goes to the demodulation of R-Y and B-Y, so it affects those values before they’ve been clamped. The Saturation setting, on the other hand, is applied right before converting from Y, R-Y, and B-Y to R, G, and B; in other words, Saturation is is applied after clamping Y, R-Y and B-Y.
- My current implementation is assumes that R-Y and B-Y never reach their clamping levels in practice as long as the video signal is sane; in other words, they’re never clamped at all. In other words, no clamping happens until the final RGB are output. This made the shader easier to implement.
- The main problem with the above is that the NES’s video signal is not sane. Determining where R-Y and B-Y get clamped on NES will be a hassle, since different chips have different clamping levels for it. I don’t even know how the jungle chip interprets the NES’s square wave colorburst. Patchy NTSC doesn’t clamp anything until the final RGB.
- One more problem is that I’m assuming that these data sheets are not hiding any of the chip’s features. These data sheets are giving the impression that things are only getting clamped whenever they’re input into a pin on the chip, but are there things happening in the chip that aren’t being detailed in the sheet?
- One last thing: This implementation doesn’t demodulate R-Y and B-Y directly. Instead, it demodulates standard YUV, and it uses a matrix to convert from YUV to RGB with the correct R-Y, G-Y, and B-Y factored in. This is possible to do because of R-Y and B-Y not getting clamped.
About bright red bleeding to the right
This is a problem that happens on some old CRTs. Judging by the fact that only bright red smears, and not just the red component of bright white, this is evidence that these CRTs allow their RGB to be oversaturated well over 1.0 before (possibly) getting clamped, and they probably clamp well above the actual maximum values, if those clamping levels are even ever reached at all in practice. As for how the smear happens, I’m assuming that three parts of the CRT that correspond to each of the three RGB colors are getting worn out over time, and this means that if it receives an amount of electricity that’s more than it can handle, it’ll only pass on the amount it can handle, and the rest gets delayed.
My shader implementation sets the maximum possible handled RGB to 1.0 by default, with an option to raise that above 1.0. I don’t know whether I should’ve put three separate limits for R, G, and B.