wolfenstein 3d

  unrolling loops
  c2p part I (st)
  c2p part II (st)
  avoiding c2p (st)
  interlacing (st)
  fat mapping
  3d pipeline
  portal rendering
  8bpp color mixing
  fixedpoint math
  blitter (mst/ste)
  sample replay (st)
  blitter gouraud (falc)
  blitter fading (falc)
  arbitrary mapping
  frustrum clipping etc.

  mc68000 math lib
  32 bytes sin-gen
  24 bit tga-viewer
  blitter example
  lz77 packer
  lz78 packer
  protracker replayer
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
                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
                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. -