
                       2D color bump-mapping in mode 13h
                      -----------------------------------
                          a tutorial by T.C.P. / d4z


1. Introduction
----------------
Most of you demo-coders will know the effect: bump-mapping.
This effect uses a picture as a bumpmap, that represents height, and calculates
an environment map containing the normals for each pixel. Using this technique,
a picture can be "transformed" into a 3D surface.
This effect can be seen in lots of today's demos and it really looks cool.
But most times the effect only uses a particular color with 256 shades, because
of the limitations the VGA sets in mode 13h.
This document will show a way to avoid this limit and make fully colored bump-
mapping possible, with a minimal loss of speed (an additional memory-access, to
be exact).


2. Standard bump-mapping
-------------------------
I will not go into details here, because there are very good explanations of
this effect available. The basic idea is this:
You simply can't calculate the normals and dot product with light vector for
each pixel in real-time, so you have to calculate an environment-map.
Normals have unit length, so you can calculate their z-value by taking their x-
and y-coordinates, just like this: z = 1 - sqrt(x + y)
Now, calculate this value for every pixel in your bump-map and store them into
the environment-map. I have some pseudo-code for you here:

EnvMap : array[0..255,0..255] of byte  ; the environment-map 256x256 bytes

for y = 0 to 255 do
  for x = 0 to 255 do
    Nx = (x - 128) / 128   ; Nx, Ny, Nz should be floats (reals)
    Ny = (y - 128) / 128
    Nz = 1 - sqrt(Nx * Nx + Ny * Ny)
    EnvMap[x][y] = Nz * 64


In the last line, we only use 64 shades, instead of 256, I'll explain this
later. Maybe this code is not quite correct, you might have to do some range-
checking...

Now, because we want a moving lightsource, we have to calculate x- and y-values
for our normals again each time. This can be simplified by taking the
difference between the height of the pixel and the height of its neighbours.
So for Normal.x we get:
Normal.x = BumpMap[y*320+x+1] - BumpMap[y*320+x-1]
And for Normal.y:
Normal.y = BumpMap[(y+1)*320+x] - BumpMap[(y-1)*320+x]
Where BumpMap is an array of 64000 bytes and we have stored our bump-map in it.

The rest is really simple:
Just take the x and y of your lightsource and substract them from your current
x and y. Now substract these 2 values from your normals' x and y. Add 128 (for
we have a 256x256 environment-map) and do range checking. Then take the normals
as indices in your environment-map and write the byte from this array to the
screen. *chakka!* you have a bump-mapper.
Need some pseudo-code?

for y = 0 to 199 do
  for x = 0 to 319 do
    Vx = x - Light.x
    Vy = y - Light.y
    Normal.x = BumpMap[y*320+x+1] - BumpMap[y*320+x-1] - Vx + 128
    Normal.y = BumpMap[(y+1)*320+x] - BumpMap[(y-1)*320+x] - Vy + 128
    [$A000:y*320+x] = EnvMap[Normal.x][Normal.y]   ; put the pixel

Of course, this can be optimized like hell, my latest version doesn't need
any MULs, DIVs or CMPs, and only one variable in the loop, so I may say that
it's quite fast.


3. Fully colored bump-mapping
------------------------------
Now for the real stuff.
With the above technique, you can only have images of one colour, with a
maximum of 256 shades. If you want to use a colored picture with 256 colors,
you would need a palette of 256 colors * 256 shades = 65536 colors.
You now have two possibilities to do this. One would be to use a 16bit color
graphics mode, the other one color-space-testing [CST].
CST means that you take the RGB-values of the color and scan the whole palette
for the color which is most similar to it. You need a square root for this, so
it would be clever to do a color look-up-table [LUT].
Again, do it like this: Go through the palette of your picture, and calculate
64 shades for every color (because we have a range from 0 to 63 for RGB).
Now check the palette if the color you just calculated is stored there. If not,
take the color which has the smallest difference to it.
In pseudo code this looks like this:

ColorLUT : array[0..255,0..63] of byte

for i = 0 to 255 do
  for j = 0 to 63 do begin
    R = Palette[i,1] * j / 32  ; divide by 32 to get 32 darker and 32 brighter
    G = Palette[i,2] * j / 32  ; shades of the color, depends on how dark your
    B = Palette[i,3] * j / 32  ; picture is
    ColorLUT[i,j] = NearestColor(R,G,B)  ; get nearest color


You can of course use Phong's equations here, in simple form:
Color = Ambient + cos(x) * Diffuse + cos(x)^n * Specular
But you won't notice the difference on most pictures. Well, worth trying,
though!

Now, to get the nearest color to the one you have calculated, you can use the
following formula:
Difference = sqrt((r1 - r2) + (g1 - g2) + (b1 - b2))
This requires heavy calculus, and we don't need to be THIS exact here. This
means we can optimise it (although it's a precalculation...) to the following:
Difference = Abs(r1 - r2) + Abs(g1 - g2) + Abs(b1 - b2)
Where Abs is a function which gives you the positive (or absolute) value of
its argument.
No long explanations, the function NearestColor should be clear.
Here it follows, though :)

MinDif = 256   ; set minimal difference to big value
color = 0
for i = 0 to 255 do
  r2 = r - Palette[i,1]
  g2 = g - Palette[i,2]
  b2 = b - Palette[i,3]
  Difference = Abs(r2) + Abs(g2) + Abs(b2)
  if Difference < MinDif then begin
    MinDif = Difference   ; new minimal difference
    color = i
  end
  if Difference = 0 then break   ; stop if perfect match is found
end
NearestColor = color


That's it. Almost.
You still have to change your bump-mapper now. Simply don't take the value
from EnvMap directly, but use it as the second index in the ColorLUT. For the
first index you of course take the respective value from your bump-map.

The changed line:
[$A000:y*320+x] = ColorLUT[BumpMap[y*320+x],EnvMap[Normal.x][Normal.y]]

That's it. Finally.

No, of course NOT! If you use this bump-mapper on a randomly selected 256 color
picture, it will look correctly, but not cool.
That's because the height of the pixels is no longer taken care of, it's more
or less incidently if a pic looks good or not.
So, what to do now? Simple: just sort the palette according to brightness of
colors, i.e. put darker colors at the beginning and the brightest colors at the
end. This makes sense: the darker a color is, the lower is its palette index,
the lower it is displayed in the bump-map. OK?
To get the brightness of a color, just add its R, G and B-values. This isn't
quite correct, but you won't notice the difference. Now do a quick Bubble-Sort
and that's about it.
After you have implemented this feature, you can use whatever picture you like,
use your bump-mapper with it, and it will simply look great! Just try the
included BUMPMAP.EXE with a 320x200x256 GIF picture.
Pseudo code again (the last one!):

PalSum : array[0..255] of byte     ; r + g + b
PalNum : array[0..255] of byte     ; color numbers
Buffer : array[0..63999] of byte

for i = 0 to 255 do begin
  PalSum[i] = Palette[i,1] + Palette[i,2] + Palette[i,3]
  PalNum[i] = i
end

for i = 0 to 255 do
  for j = 0 to 255 do
    if PalSum[i] < PalSum[j] then begin
      temp = PalSum[i]        ; exchange values
      PalSum[i] = PalSum[j]
      PalSum[j] = temp
      temp = PalNum[i]
      PalNum[i] = PalNum[j]
      PalNum[j] = temp
      R = Palette[i,1]
      Palette[i,1] = Palette[j,1]
      Palette[j,1] = R
      G = Palette[i,2]
      Palette[i,2] = Palette[j,2]
      Palette[j,2] = G
      B = Palette[i,3]
      Palette[i,3] = Palette[j,3]
      Palette[j,3] = B
    end

for i = 0 to 255 do     ; exchange colors in the picture
  for j = 0 to 63999 do if BumpMap[j] = PalNum[i] then Buffer[j] = i

move(Buffer,BumpMap,64000)  ; Buffer -> BumpMap


You're through! Hope it makes you happy.
If not, contact me:

jspohr@t-online.de

http://home.t-online.de/home/jspohr/d4z_home.htm


Thanks for some information must go to:

Sqrt(-1)
and
Sirmikey / Chaotic Order


Live long, code for free.

T.C.P. of Diabolic Force
