It is currently Sat Oct 21, 2017 12:39 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Wed May 23, 2012 11:36 am 
Offline
User avatar

Joined: Fri Oct 14, 2011 1:09 am
Posts: 248
A common trick in NES games is to fade a palette to black, and back, by progressively ANDing the correct palette values with a bitmask such as 3F, 2F, 1F and 0F.

An example of such game is Simon's Quest, as shown in the screenshot below.
This algorithm is cheap and easy to implement. However, it is restricted into fading through from/to black, which is why the game fades through black.

However, if one wants to fade between two arbitrary palettes, things are not so easy. Has anyone attempted it?
I can think of an algorithm, which is not cheap:
Precalculate a 64x64x4 array of values and place it in a vacant ROM page. It takes 16384 bytes (4000h). Key 1 is the source palette value, key 2 is the target palette value, and key 3 is the degree of interpolation (20%, 40%, 60%, 80%). An program populates the array by determining which NES color index most closely resembles the given mixture of NES colors.
Fading the palette is a matter of reprogramming the palette six times:
First, with the source palette (srccolor) (is optional).
Then four times with table[srccolor][dstcolor][i] for each color in the screen palette.
And finally with the target palette (dstcolor).
Resourcewise, it is not cheap.
It could be made smoother by including fewer degrees of interpolation (such as one: 50% or two: 33% and 67%): This would take 1000h or 2000h bytes respectively. Additional degrees of interpolation could be synthesized at runtime by flickering between the previous and the next value in the table. Such flickering might not look nice, though.

Are there other, perhaps better algorithms for arbitrary palette fades on NES?



Image
(Screenshot is from my Finnish translation, captured in my emulator in a three-way-fields-merge NTSC mode.)


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 23, 2012 11:42 am 
Offline
User avatar

Joined: Fri Nov 19, 2004 7:35 pm
Posts: 3943
How about a cheap hue/brightness fade? Move left or right along the hue, then move up or down along brightness.

_________________
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 23, 2012 11:43 am 
Offline

Joined: Sat Jan 23, 2010 11:41 pm
Posts: 1161
I would simply attempt to do brightness fade with hue change in large steps into closest direction.

Edit: the same what Dwedit said, basically.


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 23, 2012 12:09 pm 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7232
Location: Chexbres, VD, Switzerland
I think there is a problem if you use the AND-ing approach.

For example if you use color $20, then with your sequence it will become $20, $20, $00, $00, which is not what you want since you probably wanted sometihng like $20, $10, $00, $0f.

Personally I use the method to substract $10 every time, and if the result is negative I use $0f to get black. After 4 passes, all colors will be black.

If I were to fade between two arbitrary palettes, I would probably separate into hue and brightness and gradually decrease or increase each of them until you get the right value. It might be tricky to code, but you get the idea.

For example to fade from $13 to $3a you'd get something like :
$13, $12, $21, $2c, $3b, $3a

_________________
Life is complex: it has both real and imaginary components.


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 23, 2012 12:19 pm 
Offline

Joined: Tue Jul 12, 2011 10:58 am
Posts: 264
Could you just treat all the NES colors as a 2d array and "walk" the source color to the destination color? A 2D grid of 52 colors should take too much ROM space, right?


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 23, 2012 12:23 pm 
Offline
User avatar

Joined: Fri Oct 14, 2011 1:09 am
Posts: 248
Bregalad wrote:
For example if you use color $20, then with your sequence it will become $20, $20, $00, $00, which is not what you want since you probably wanted sometihng like $20, $10, $00, $0f.

Actually Simon's Quest partially solves this problem by setting palette[x] into
min(origpalette[x], (origpalette[x] & 0x0F) | fadelevel) where fadelevel goes from 0x00 to 0x30 or from 0x30 to 0x00.
This gets $00,$10,$20,$20 instead of $00,$00,$20,$20. For $13, it gets $03,$13,$13,$13. For $34, it gets $04,$14,$24,$34...


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 23, 2012 12:26 pm 
Offline
User avatar

Joined: Fri Oct 14, 2011 1:09 am
Posts: 248
The problem with angular palette fades suggested by many here is that fades between monochrome and colored do not work at all.


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 23, 2012 12:42 pm 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7232
Location: Chexbres, VD, Switzerland
It will never work at all since the NES can only output completely monochrome or completely saturated colours.

_________________
Life is complex: it has both real and imaginary components.


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 23, 2012 1:01 pm 
Offline

Joined: Tue Jul 12, 2011 10:58 am
Posts: 264
I guess I don't understand why creating your own arranged 2d array of colors wont work.

If you pre-calculate the table then most of the math goes away, right?

UPDATE: Thank you Drag!


Last edited by slobu on Wed May 23, 2012 1:19 pm, edited 1 time in total.

Top
 Profile  
 
 Post subject:
PostPosted: Wed May 23, 2012 1:17 pm 
Offline

Joined: Mon Sep 27, 2004 2:57 pm
Posts: 1248
The most common method I've seen for fading in and out is just by adding or subtracting 10.

For instance, to fade out, you start with your normal colors, and just subtract 10 for each step, and underflow to 0F.

To fade in, start at brightness 0x (but with your hues filled in), and add 10 to each color that hasn't reached its target yet.

Alternatively, you can take advantage of the fact that the upper 2 bits of the palette are unused, so you could start out by subtracting 40 from your target palette, and add 10 each step, replacing values bigger than $40 with color $0F. This at least gets your fade-in to look the same as your fade-out.

Finally, take a look at the Tiny Toons cartoon maker thing for NES, it fades to blue, but it might be using a LUT for that.

You can't simply walk across the palette space like it's 2D, because complementary colors (like blue and orange) naturally cancel each other out when fading from one to the other, so they'd need to pass through a shade of gray in the process. However, you can do if if you're fading between two colors whose hues are close enough together.


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 23, 2012 1:19 pm 
Offline
User avatar

Joined: Fri Oct 14, 2011 1:09 am
Posts: 248
slobu wrote:
I guess I don't understand why creating your own arranged 2d array of colors wont work.

If you pre-calculate the table then most of the math goes away, right?

Such precalculated arrays might require too much space for some purposes.

In any case, here's a little comparison between two algorithms.
On the left, angular fading. On the right, precalculated table using a search for smallest color difference against linearly interpolated RGB color.
Image
(Pay attention to the highlight in the ground pieces and the rock below it.)

In the angular fading, both the source and the target colors are split into level and color. If both colors are 1..12, the colors are translated into 0..11 range and linearly interpolated along nearest wrapping direction. Otherwise, the colors are just linearly interpolated. Levels are linearly interpolated. Then the level and color is merged back to form the resulting color.
In PHP,
Code:
        $level1 = $c1 >> 4; $color1 = $c1 & 0x0F;
        $level2 = $c2 >> 4; $color2 = $c2 & 0x0F;
       
        $level = (int)($level1 + ($level2-$level1) * $mix);
         
        if($color1 >= 1 && $color1 <= 13
        && $color2 >= 1 && $color2 <= 13)
        { 
          $angle1 = ($color1-1);
          $angle2 = ($color2-1);
         
          if(abs($angle2 - $angle1) > 6)
          {
            if($angle2 < $angle1) $angle2 += 12; else $angle1 += 12;
          }
          $angle = (int)($angle1 + ($angle2-$angle1) * $mix);
          $color = 1 + ($angle%12);
        }
        else
        {
          $color = (int)($color1 + ($color2-$color1) * $mix);
        }
       
        $c = $level*16 + $color;
        $rgb = get_nes_rgb_actual($c);

And the precalculated way was this:
Code:
      // Which color we want?
      $goal = Array( $a[0] + $power * ($b[0]-$a[0]),
                     $a[1] + $power * ($b[1]-$a[1]),
                     $a[2] + $power * ($b[2]-$a[2]) );

      // Figure out which color best represents this
      $best_diff = null;
      $best_color= null;
     
      foreach($nes_rgb as $m => $c)
      {
        $diff = ColorDifference($goal, $c);
        if($m == 0 || $diff < $best_diff)
          { $best_diff = $diff; $best_color = $m; }
      }

      $mixtable[$c1][$c2][$index] = $best_color;

I also tried the flickering method, but it did not look nearly as nice as I hoped...

EDIT: Actually, if I reduce the maximum difference between the two constituting colors in the pair that is flickered, it DOES begin to look quite nice: image...


Last edited by Bisqwit on Wed May 23, 2012 1:36 pm, edited 2 times in total.

Top
 Profile  
 
 Post subject:
PostPosted: Wed May 23, 2012 1:28 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19114
Location: NE Indiana, USA (NTSC)
Drag wrote:
Finally, take a look at the Tiny Toons cartoon maker thing for NES, it fades to blue, but it might be using a LUT for that.

I don't have that game, but I can tell you this: The fade code used for cut scenes in Thwaite is based on subtracting $10, $20, or $30. It reassigns $F0 (what you get when you subtract $10 from dark gray) to $02 (dark blue), $0D (sync-fooling black) to $0F, and any other "negative" value ($80-$FF) to $0F.


Top
 Profile  
 
 Post subject:
PostPosted: Fri May 25, 2012 10:06 pm 
Offline

Joined: Mon Sep 27, 2004 2:57 pm
Posts: 1248
TL;DR: Walking across the palette as though it's 2D is close, when dealing with only hues (not grays), but there's still some funkiness with saturation that may be hard (not impossible!) to deal with on the NES.

Long version:
After giving it a bunch of thought, I basically figured out what's going on, in terms of the NTSC signal.

Let's say color 21 looks like this:
Code:
¯¯¯¯¯¯______¯¯¯¯¯¯______

...and color 25 looks like this:
Code:
____¯¯¯¯¯¯______¯¯¯¯¯¯__


50% of 21 combined with 50% of 25 should look something like this:
Code:
----¯¯----__----¯¯----__


It's still a wave that's oscillating at the colorburst frequency, so it'd be considered a valid color, however, it's much less saturated than either of the colors alone. As for which HUE this is, I'm not sure... it's probably hue 23 (half way between 21 and 25) or something, but again, It's at 1/3 the SATURATION of both 21 and 25.

Let's take color 21 again:
Code:
¯¯¯¯¯¯______¯¯¯¯¯¯______

...and color 27, its complement:
Code:
______¯¯¯¯¯¯______¯¯¯¯¯¯


Combine 50% of 21 with 50% of 27, and you get this:
Code:
------------------------


...which is a gray color, the same luminance as the two colors. On the NES, this isn't completely possible, because there isn't a shade of gray available that represents the half-way point between color 20 and 2D.


So, when fading between two hues of the same brightness (let's call them A and B), the halfway point is more and more desaturated, the closer B is to A's complement.

Ok, so that's how the hue and saturation works, fading between two colors of different brightnesses would simply be linear. For instance, fading between color 21 and color 17, the high nybble would act the same way as if you were fading between 21 and 11.


Top
 Profile  
 
 Post subject:
PostPosted: Mon May 28, 2012 2:12 pm 
Offline

Joined: Mon Sep 27, 2004 2:57 pm
Posts: 1248
I looked into the Tiny Toons cartoon maker for NES, and there's a large lookup table at CPU$FEDB that the game uses to fade arbitrary colors to color $02 and back, in 1/3 increments.

When they generated this table, I wonder if they faded the colors in YIQ space, or if they interpolated to RGB and back.


Top
 Profile  
 
 Post subject:
PostPosted: Mon May 28, 2012 3:21 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19114
Location: NE Indiana, USA (NTSC)
Drag wrote:
When they generated this table, I wonder if they faded the colors in YIQ space, or if they interpolated to RGB and back.

It'd be the same thing, as the conversion from YIQ to device-RGB is linear.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: zzo38 and 8 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group