I've done quite a bit of experimenting, and it looks like it's hard to come up with a single fader table which works well in a variety of cases. I think it's best to customize it based on the circumstances.
There are a few competing factors to consider, and it's impossible in general to balance them all completely:
- smoothness of gradation between steps (brightness should change at uniform rate)
- color balance (whether the 3 color channels fade evenly)
- noise (does any ugly visual noise appear at any step in the fade)
the last one is the hardest to deal with. When you're at the dark end of the fade, you want to be using only the least significant bits of each color channel. For example, one might be tempted to use 0x49 (which is 01 001 001, in blue green red order) but this tends to end up causing a fair amount of visual noise, due to the least significant bit in each color channel being essentially random for many types of images. (This ends up causing my original table to produce absolute garbage when used with the Megatris title screen, my second test. My original choice of tutorial.c had been a horrible testbed, due to it being black and white.)
pragma's fade values avoid noise by dropping the low-order bits first, and using only the most significant bits at the lowest brightness levels. The problem there is that it has a pretty sharp drop-off between the lowest brightness and full black. It's definitely weighted towards the bright end. (But maybe that's not so bad on a real TV .. I've only been testing stuff in the emulator. On a real TV it might be that pragma's values end up working best. I'm going to have to do some real TV tests this weekend.)
I think a certain amount of trial and error is needed to get something that looks good, and it's going to be different in different games. Here's one which I think works well on the Megatris title screen:
Code: Select all
#define FADER_STEPS 12
unsigned char fader[FADER_STEPS]={
// BB GGG RRR
0x00, // 00 000 000
0x40, // 01 000 000
0x88, // 10 001 000
0x91, // 10 010 001
0xD2, // 11 010 010
0xE4, // 11 100 100
0xAD, // 10 101 101
0xB5, // 10 110 101
0xB6, // 10 110 110
0xBE, // 10 111 110
0xBF, // 10 111 111
0xFF, // 11 111 111
};
This one tries to balance the three factors I listed above, but it gives up a bit of color balance in order to avoid noise, while allowing for a fairly smooth change in brightness (with no big drop off at the low end). You'll see that it is weighted heavily towards blue in the darker end, and switches to more yellow at the brighter end. Also, the brightness of the blue channel ends up jumping around in an odd way to try to balance out the overall brightness while trying to avoid noise in the red and green channels. Noise in the blue channel is harder to see, so I think it's more important to avoid noise in the red and green channels and use blue to control the overall brightness, without worrying too much about artifacts in the blue.
If you're displaying mostly white or just pure blue (0xC0 == 11 000 000), green (0x38 == 00 111 000), and red (0x07 == 00 000 111), then my initial fade table works well. But if you're using any other colors, it looks really bad. Something like the 12 step table above works better for complex images. (And you should test pragma's values too, as they are very good for avoiding noise.) But I think the exact sequence of values to use will vary greatly between different games. There's no one-size-fits-all solution, as far as I can tell.
And of course you'll need to use custom values for special effects. Want to flash the screen red when the player is low on health, or a nuclear reactor is going to blow, or it's red alert on the Enterprise? Pretty easy.
hmm.. it would be fun to set DDRC on a per-scanline basis. That's something to think about...
I've spent too much time on this today.. I'm moving on to something else. (like playing with my fuzebox on the new 46" LCD that I got this week.)