| |
translucency and color mixing in 8 bit modes
heya again...this time i am going to cover a nice method that provides color-mixing as
possible in truecolor modes and known from newer falcon demos to lower quality color
depths such the TT's 256 color mode.
although i am quite a lonely person programming TT compatible demos because it is the
computer that i am working with and as i always thought this "workstation" has been
heavily neglected when it comes to demo coding, i started thinking about how astonishing
transparency and blending effects as known from escape's "hmmm..." or dhs's "don't break
the oath" demos could be done on the TT which is limited to a maximum of 256 colors at
once unless you're going to use interlacing or diffrent tricks to gain more colors.
i guess i am not the first one to use that technique, but nevertheless i think/hope some
people might be interested in starting to make their demos TT compatible ;) - hence i am
writing this tutorial, which could help out a bit here.
there are various methods for color mixing some used for shading, others for dynamic lighting
and so on. i'm gonna describe the most common ones using a bit of pseudocode that should make
things clear. working in a 16 bit truecolor mode this is everything needed to to mix two colors
(assuming r1,r2... are the rgb levels of the colors to be mixed)
additively (results in a lightning effect):
r1 += r2;
g1 += g2;
b1 += b2;
if (r1 > 31) then r1 = 31;
if (g1 > 63) then g1 = 63;
if (b1 > 31) then b1 = 31;
mixed_color = r1<<11|g1<<5|b1;
subtractively (results in a shadowing effect):
r1 -= r2;
g1 -= g2;
b1 -= b2;
if (r1 < 0) then r1 = 0;
if (g1 < 0) then g1 = 0;
if (b1 < 0) then b1 = 0;
mixed_color = r1<<11|g1<<5|b1;
averaging (results in a transparency effect):
r1 = (r1+r2)>>1;
g1 = (r1+g2)>>1;
b1 = (b2+b2)>>1;
mixed_color = r1<<11|g1<<5|b1;
additive alpha blend (results in a translucency effect):
if (r2 > r1) then r1 = r2;
if (g2 > g1) then g1 = g2;
if (b2 > b1) then b1 = b2;
mixed_color = r1<<11|g1<<5|b1;
hmm, now how could that work in an 8 bit mode with palette lookup ? first option: use a fixed
palette that emulates truecolor encoding (something like 3bits for the red, 3bits for the green
and 2bits for the blue channel) then diffrent ways of color mixing can be done just like mentioned
above. however this isn't suited for most situations because convertiong your textures and layers
to such a fixed palette makes them really look very ugly - i've tested it and it didn't look
satisfying in almost every case.
a completely diffrent approach, which produces much better results works as follows: imagine the
job was to mix two out of 256 colors. with two 256 color layers this gives us 256*256 possible
combinations, each combination would result in a new color depending on the mixing method used.
now, just mind that our colorspace is limited to a palette of 256 colors.
so the actual tasks would be about finding a common 256 entry palette for your textures and your
layers (most generic imageing tools should support this) and about finding the color that is nearest
to your mixed color within this 8 bit palette in order to create a 64k bytes lookup table.
64kb is a fair amount of memory considering the fact that no compares, adds or shifts would have
to be done per pixel to be mixed, on the other hand it's the only possible way because looking for
the color nearest to your mix color would be far away from a realtime issue.
assuming you would have a useable and quick 8bpl c2p which can be found at this amiga site for instance
and after you would have created your blend table, two layers could be mixed like this:
; (a0) -> chunkyscreen
; (a1,d0.l) -> texture
; (a2,d1.l) -> layer to be mixed
; (a3) -> mixtable
; d2.l = 0 !
move.w (a1,d0.l),d2 ; Get source pixel into high byte of d2.w
move.b (a2,d1.l),d2 ; Get layer into low byte
move.b (a3,d2.l),(a0)+ ; Write out mixed color
If you add a color decrement/fadeout to your mixtable, you can even do a simple and fast motionblur
with any palette you like:
; (a0) -> chunkyscreen
; (a1,d0.l) -> layer to be blurred
; (a2) -> mixtable
; d1.l = 0 !
move.w (a0),d1 ; Get old pixel into high byte of d1.w
move.b (a1,d0.l),d1 ; Get layer into low byte
move.b (a2,d1.l),(a0)+ ; Write out mixed and decremented color
here's a second approach which can speed up things. the first condition however is a greyscale palette ranging from dark to bright.
if you'd like it to function with more colorful layers your palette's colors will have to be sorted by luminance in order to achieve
correct results, though. this technique works without a mixing table hence your palette must be sorted in suggested way.
the formula lum(color)= 0.299*color.r + 0.587*color.g + 0.114*color.b
complies with pal/ntsc standards for grayscale conversion and seems to achieve decent results. here are the two snippets
for additive and subtractive blending:
; Fast saturated add
; a0->layer
; a1->background
move.b (a0)+,d0
add.b d0,(a1)
scs.b d0
or.b d0,(a1)+
; Fast ceilsub
; a0->layer
; a1->background
move.b (a0)+,d0
sub.b d0,(a1)
scc.b d0
and.b d0,(a1)+
this might look confusing to you at the first sight. but it is not confusing at all. the secret behind our clamping
against 0xff / 0x00 lies in the last two lines of each macro. as you'll perhaps have noticed each first two lines
make up the mixing of background and layer...however those operations may result in over-/underflows which we are
going to catch within the last two lines. if the sum in our add macro exceeded the 0xff range and hence unwanted
wrapping occured d0.b would be set to 0xff. or'ed into (a1), our currently processed background pixel, this would
give a pixel value of 0xff all the time the carry has been set through the addition's result. scs.b sets d0.b to 0x00
otherwise, which wouldn't change the sum in the or.b line - and this is just what we wanted as in those cases our
addition did not overflow - pretty much the same concept in our ceilsub macro, no need to explain here.
but now let's face it: single byte processing is damn slow, we need something quicker. on 020+ processors lsr.l #x,dn
takes a constant time of 4 cycles regardless of the shift value. stepping pixels backwards, i.e. from the right to the left
we can take advantage of this fact by reading 4 source pixels at once in our mixing loop:
move.w #length/4-1,d1
.mixloop move.l -(a0),d0 ; Fetch 4 source pixels
rept 3
add.b d0,-(a1) ; Mix a,b
scs.b d0 ; Overflow?
or.b d0,(a1) ; Clamp accordingly
lsr.l #8,d0 ; Process next pixel
endr
add.b d0,-(a1)
scs.b d0
or.b d0,(a1)
dbra d1,.mixloop
pretty obvious, i guess. ah and before i forget that, here's the alpha blending macro as promised. it works
simply by evaluating and choosing the brighter color, please note that this one progresses backwards as well:
; Fast alpha blending
; a0->end of layer
; a1->end of background
move.b -(a0),d0
cmp.b -(a1),d0
bls.s *+4
move.b d0,(a1)
you can apply the same optimization as above here:
move.w #length/4-1,d1
.mixloop move.l -(a0),d0
rept 3
cmp.b -(a1),d0
bls.s *+4
move.b d0,(a1) ; Choose brighter color
lsr.l #8,d0 ; Process next pixel
endr
cmp.b -(a1),d0
bls.s *+4
move.b d0,(a1)
dbra d1,.mixloop
in this last paragraph i'll show you how to apply average mixing. as usual it's quite simple, this snippet makes
use of the roxr instruction which rotates a register through the x-flag. we have to divide by two having added the
2 colors to be mixed. using roxr we don't loose the carry possibly occuring in the 8th bit of our mixed color. and
guess what, you can use the same optimization as suggested for the previous examples with this one but i'm too lazy
typing it in as again, it should be pretty obvious how to do that:
; Fast average mixing
; a0->layer
; a1->background
move.b (a0)+,d0
add.b (a1),d0
roxr.b #1,d0 ; Divide a+b by two with carry
move.b d0,(a1)+
- 2002/2004 ray//.tscc. -
|
|