EFEKT PRZE¼ROCZYSTOªCI

raj▒c w Jazz Jack Rabbit nie mo┐na nie ulec ciekawym efektom graficznym. Bo nie w ka┐dej grze z okresu < 1998 widaµ takie rzeczy. Szczeg≤lnie zainspirowa│a mnie transparencja...

Transparencja to po prostu przezroczysto╢µ. Chodzi mi o wy╢wietlanie jakiego╢ obrazka na tle innego, z tym, ┐e wy╢wietlany obrazek bΩdzie nieco prze╢witywa│.

Okazuje siΩ, ┐e tak┐e i my mo┐emy zrobiµ taki efekt, co wiΩcej to wcale nie jest trudne.

Paleta

Tutaj bΩd▒ nam potrzebne wszystkie odcienie jakiego╢ koloru, np. czerwonego. Naj│atwiej by│oby zrobiµ tak:

For i := 0 To 255 Do SetCol(i, i Div 4, 0, 0);

Ale taka paleta to marnotrawstwo! PamiΩtajmy bowiem, ┐e karta VGA pozwala na wy╢wietlenie do 64 odcieni danego koloru, a nie 256. Dlatego te┐ do stworzenia tego i wielu innych efekt≤w (np. bump mapping) wystarczy taka paleta:

For i := 0 To 63 Do SetCol(i, i, 0, 0);

Tym sposobem mamy jeszcze 192 kolory do dyspozycji. Mo┐emy je po╢wiΩciµ na np. napisy, czy te┐ na t│o, kt≤re nie musi byµ wype│nione odcieniami tylko jednego koloru.

Zmienne, sta│e, typy

Najwa┐niejszy jest w tym efekcie bufor ramki. Co to oznacza? Wiele gier wykorzystuje tΩ technikΩ. A wszystko po to, aby do minimum ograniczyµ bezpo╢rednie zapisywanie do pamiΩci karty. DziΩki temu unikniemy migotania obrazu, a sam program bΩdzie znacznie szybszy.

Type
  ScreenArray = ^ScreenType;
  ScreenType  = Array[0..63999] Of Byte;

Var
  Screen : ScreenArray;

Potrzebne s▒ jeszcze dwie dodatkowe tablice. W pierwszej bΩdzie t│o, w drugiej za╢ przezroczysty obrazek. Pierwsza tablica bΩdzie tego samego typu, co bufor ramki, tak wiΩc piszemy:

Var
  Image1 : ScreenArray;

Druga tablica bΩdzie wymaga│a odrΩbnego typu. BΩdzie on podobny do bufora ramki, z tym, ┐e bΩdzie mia│ rozmiar szeroko╢ci nak│adanego obrazka pomno┐onej przez jego wysoko╢µ i ca│a ta liczba musi byµ zmniejszona o jeden. Przyk│adowo obrazek bΩdzie mia│ rozmiar 128x128:

Type
  Image2Array = ^Image2Type;
  Image2Type  = Array[0..16383] Of Byte;  { 16383 = 128 x 128 - 1 }

Var
  Image2 : Image2Array;

Potrzebne bΩd▒ jeszcze liczniki:

Var
  i, x, y : Word;

oraz parΩ tymczasowych zmiennych:

Var
  TempCol : Byte;
  LX, LY  : Word;

Inicjalizacja

Najpierw tworzymy w pamiΩci konwencjonalnej wszystkie tablice, pisz▒c:

New(Screen);
New(Image1);
New(Image2);

S▒ one po│o┐one poza standardowym segmentem danych programu, tak wiΩc bez tych trzech instrukcji komputer siΩ zawiesi.

Teraz trzeba wyzerowaµ wszystkie tablice, aby unikn▒µ "zaszumionego" ekranu:

FillChar(Screen^[0], 64000, 0);
FillChar(Image1^[0], 64000, 0);
FillChar(Image2^[0], SizeOf(Image2^), 0);

NastΩpnie musimy zapisaµ w Image1 t│o, za╢ w Image2 nak│adany obrazek. Nie bΩdΩ bawi│ siΩ w prezentowanie sposob≤w na za│adowanie pliku BMP, PCX, czy GIF, ale wygenerujΩ obrazki w programie.

For x := 0 To 319 Do
For y := 0 To 199 Do Image1^[320 * y + x] := x Xor y;

For i := 0 To 16383 Do Image2^[i] := Random(10) + 40;

Tak by│oby najpro╢ciej i chyba najlepiej.

Oczywi╢cie przed u┐yciem instrukcji Random trzeba j▒ zainicjalizowaµ poprzez instrukcjΩ Randomize i sprawdziµ, czy program u┐ywa modu│u CRT.

G│≤wna pΩtla

Teraz najlepsze! Musimy stworzyµ pΩtlΩ Repeat..Until, a w niej umie╢ciµ, co nastΩpuje:

  1. Wyczy╢ciµ bufor ramki
  2. Umie╢ciµ w buforze ramki t│o
  3. Nanie╢µ przezroczysty obrazek:
    1. wpisaµ do zmiennej TempCol kolor piksela z t│a, na kt≤ry ma byµ naniesiony piksel z drugiego obrazka
    2. obliczyµ nowy kolor metod▒:
      • pomno┐yµ warto╢µ zawart▒ w TempCol przez kolor aktualnie nak│adanego piksela
      • podzieliµ wynik przez 63
      • je┐eli wykorzystuje siΩ tylko pierwsze 64 kolory, to podzieliµ wynik przez 4
      • finalny punkt - do bufora ramki
  4. Przenie╢µ zawarto╢µ bufora ramki na ekran
Uff. Nie wiem, czy zrozumiale t│umaczΩ, ale na pewno da siΩ co╢ z tego wyci▒gn▒µ.

Efekt latarki

W tytule wspomnia│em o latarce. No wiΩc, spos≤b tworzenia takiego efektu jest bardzo podobny do przedstawionego wy┐ej algorytmu. Wystarczy tylko:

  1. W tablicy Image2 umie╢ciµ obrazek przypominaj▒cy ciemniej▒c▒ po brzegach kulΩ
  2. Nie przenosiµ t│a do bufora ramki
To wystarczy, aby uzyskaµ zupe│nie inny efekt!

Nak│adany obrazek mo┐na odczytaµ z pliku, mo┐na go tak┐e wygenerowaµ w programie metod▒ cieniowania Phonga (Phong shading). Procedura jest zawarta w kodzie ╝r≤d│owym.

Kontakt

To ju┐ wszystko. Pozosta│y siΩ jeszcze dwa programy przyk│adowe. Je╢li macie jakie╢ pytania, czekam!
Piotr Horzycki/MediaWorks Entertainment
http://mediaworks.w.interia.pl/
e-mail: mediaworks@interia.pl

================================= TRANSPAR.PAS ================================
{$G+}
Program Transparency;

Uses Crt, DOS;

Type
{ Bufor ramki }
  ScreenArray = ^ScreenType;
  ScreenType = Array[0..64000] Of Byte;

{ Nak│adany obrazek }
  ImageArray = ^ImageType;
  ImageType = Array[0..16383] Of Byte;

Const
  kUp = 72;  { Kody klawiszy }
  kDown = 80;
  kLeft = 75;
  kRight = 77;
  kEsc = 1;

Var
  Screen     : ScreenArray;               { Bufor ramki                     }
  Image1     : ScreenArray;               { T│o                             }
  Image2     : ImageArray;                { Nak│adany obrazek               }
  i, j, x, y : Word;                      { Liczniki                        }
  KeyDown    : Array[0..255] Of Boolean;  { Stany wszystkich klawiszy       }
  OldKbdInt  : Pointer;                   { Stary sterownik klawiatury      }
  LX, LY     : Word;                      { Pozycja obiektu                 }
  TempCol    : Byte;                      { Tymczasowa zmienna              }

Procedure NewKbdInt; Interrupt;
{ Umie╢ci│em tutaj t▒ procedurΩ, gdy┐ chcia│em, aby mo┐na by│o
  sterowaµ przezroczystym obiektem, a korzystanie z ReadKey'a
  to koszmar }
Var
  Key : Byte;
Begin
  Key := Port[$60];
  If Key And $80 = $80 Then
  Begin
    Key := Key And $7F;
    KeyDown[Key] := False
  End
  Else KeyDown[Key] := True;
  Port[$20] := $20
End; { NewKbdInt }

Procedure SetCol(Color, R, G, B : Byte);
Begin
  Port[$3C8] := Color;
  Port[$3C9] := R;
  Port[$3C9] := G;
  Port[$3C9] := B;
End; { SetCol }

Begin
  Randomize;

  For i := 0 To 255 Do KeyDown[i] := False;
  GetIntVec($09, OldKbdInt);
  SetIntVec($09, @NewKbdInt);

  New(Screen);
  New(Image1);
  New(Image2);

  FillChar(Screen^[0], 64000, 0);
  FillChar(Image1^[0], 64000, 0);
  FillChar(Image2^[0], 6400, 0);

  For i := 0 To 16383 Do Image2^[i] := 40 + Random(20);

  Asm
    mov   ax,13h
    int   10h
  End;

{ Paleta... }
  For i := 0 To 63 Do SetCol(i, i, 0, 0);
  For i := 64 To 127 Do SetCol(i, 0, i, 0);
  For i := 128 To 191 Do SetCol(i, 0, 0, i);
  For i := 192 To 255 Do SetCol(i, i, i, i);

{ Narysuj t│o }
  For x := 0 To 319 Do
  For y := 0 To 199 Do Image1^[320 * y + x] := x Xor y;

  x := 159;  { Kwadrat ma byµ w ╢rodku }
  y := 99;

  Repeat
    FillChar(Screen^[0], 64000, 0);
    LX := 0; LY := 0;
    Move(Image1^[0], Screen^[0], 64000);

  { Na│≤┐ kwadrat }
    For i := (x - 64) To (x + 63) Do
    For j := (y - 64) To (y + 63) Do
    Begin
      TempCol := Image1^[320 * j + i];
      TempCol := (TempCol * Image2^[128 * LY + LX] Div 63) Div 4;
      Screen^[320 * j + i] := TempCol;
      Inc(LX);
      If LX = 128 Then
      Begin
        LX := 0; Inc(LY);
      End;
    End;

  { Czy strza│ka zosta│a naci╢niΩta? }
    If KeyDown[kUp] Then If (y - 64) > 0 Then Dec(y);
    If KeyDown[kDown] Then If (y + 63) < 199 Then Inc(y);
    If KeyDown[kLeft] Then If (x - 64) > 0 Then Dec(x);
    If KeyDown[kRight] Then If (x + 63) < 319 Then Inc(x);

  { Z bufora ramki - na ekran }
    Move(Screen^[0], Mem[$A000:0], 64000);
  Until KeyDown[kEsc];

  Dispose(Screen);
  Dispose(Image1);
  Dispose(Image2);

  TextMode(C80);
  SetIntVec($09, OldKbdInt);

  Writeln('Transparency Effect');
  Writeln('Designed by Piotrala');
  Writeln;
  Writeln('Contact us!');
  Writeln('http://mediaworks.w.interia.pl/');
  Writeln('e-mail: mediaworks@interia.pl');
End. { TRANSPAR.PAS }
================================= REDLIGHT.PAS ================================
{$G+}
Program RedLight;

Uses CRT, DOS;

Type
{ Bufor ramki }
  ScreenArray = ^ScreenType;
  ScreenType = Array[0..64000] Of Byte;

{ Kszta│t ╢wiat│a }
  LightArray = ^LightType;
  LightType = Array[0..16383] Of Byte;

Const
  kUp    = 72;  { Kody klawiszy }
  kDown  = 80;
  kLeft  = 75;
  kRight = 77;
  kEsc   = 1;

Var
  Screen     : ScreenArray;
  Image      : ScreenArray;               { T│o                             }
  Light      : LightArray;                { Kszta│t ╢wiat│a                 }
  i, j, x, y : Word;                      { Liczniki                        }
  KeyDown    : Array[0..255] Of Boolean;  { Stan wszystkich klawiszy        }
  OldKbdInt  : Pointer;                   { Stary sterownik klawiatury      }
  LX, LY     : Word;                      { Takie dwa liczniki              }
  PhongMap   : Pointer;                   { Tam odbywa siΩ Phong shading    }
  TempCol    : Byte;                      { Tymczasowa zmienna              }

Procedure NewKbdInt; Interrupt;
{ Umie╢ci│em tutaj t▒ procedurΩ, bo chcia│em, aby mo┐na by│o
  sterowaµ ╢wiat│em, a wykorzystanie zwyk│ego ReadKey'a to
  koszmar }
Var
  Key : Byte;
Begin
  Key := Port[$60];
  If Key And $80 = $80 Then
  Begin
    Key := Key And $7F;
    KeyDown[Key] := False
  End
  Else KeyDown[Key] := True;
  Port[$20] := $20
End; { NewKbdInt }

Procedure SetCol(Color, R, G, B : Byte);
Begin
  Port[$3C8] := Color;
  Port[$3C9] := R;
  Port[$3C9] := G;
  Port[$3C9] := B;
End; { SetCol }

Procedure CalcLight;
{ To ta procedura oblicza kszta│t ╢wiat│a - Phong shading
  Kszta│t jest obliczany w rozdzielczo╢ci 256x256, a potem
  jest przeskalowywany do 128x128 }

Function Length(x1, y1, x2, y2 : LongInt) : Word;
Begin
  Length := Round(Sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)));
End; { Length }

Begin
  GetMem(PhongMap, 65535);
  For y := 0 To 127 Do
  For x := 0 To 127 Do
  Begin
    TempCol := Length(x, y, 128, 128);
    If TempCol > 127 Then TempCol := 127;
    TempCol := 129 - TempCol;
    Mem[Seg(PhongMap^):y Shl 8 + x] := TempCol;
    Mem[Seg(PhongMap^):(255 - y) Shl 8 + x] := TempCol;
    Mem[Seg(PhongMap^):y Shl 8 + (255 - x)] := TempCol;
    Mem[Seg(PhongMap^):(255 - y) Shl 8 + (255 - x)] := TempCol;
  End;
  For y := 0 To 127 Do
  For x := 0 To 127 Do
  Begin
    TempCol := Mem[Seg(PhongMap^):256 * (y * 2) + (x * 2)];
    If TempCol > 63 Then TempCol := 63;
    Light^[128 * y + x] := TempCol;
  End;
  FreeMem(PhongMap, 65535);
End; { CalcLight }

Begin
  Randomize;

  For i := 0 To 255 Do KeyDown[i] := False;
  GetIntVec($09, OldKbdInt);
  SetIntVec($09, @NewKbdInt);

  New(Screen);
  New(Image);
  New(Light);

  FillChar(Screen^[0], 64000, 0);
  FillChar(Image^[0], 64000, 0);
  FillChar(Light^[0], 6400, 0);

  CalcLight;

  Asm
    mov   ax,13h
    int   10h
  End;

{ Paleta... }
  For i := 0 To 63 Do SetCol(i, i, 0, 0);
  For i := 64 To 127 Do SetCol(i, 0, i, 0);
  For i := 128 To 191 Do SetCol(i, 0, 0, i);
  For i := 192 To 255 Do SetCol(i, i, i, i);

{ Narysuj "XORowane" t│o }
  For x := 0 To 319 Do
  For y := 0 To 199 Do Image^[320 * y + x] := x Xor y;

  x := 159;  { Umie╢µ ╢wiat│o w ╢rodku ekranu }
  y := 99;

  Repeat
    FillChar(Screen^[0], 64000, 0);
    LX := 0; LY := 0;

  { O╢wietl dane miejsce }
    For i := (x - 64) To (x + 63) Do
    For j := (y - 64) To (y + 63) Do
    Begin
      TempCol := Image^[320 * j + i];
      TempCol := (TempCol * Light^[128 * LY + LX] Div 63) Div 4;
      Screen^[320 * j + i] := TempCol;
      Inc(LX);
      If LX = 128 Then
      Begin
        LX := 0; Inc(LY);
      End;
    End;

  { Poka┐ paletΩ na dole - dla niedowiark≤w, kt≤rzy nie wierz▒ mojej tezie, }
  { i┐ mo┐na mieµ jeszcze dodatkowe 192 kolory do dyspozycji                }
    For i := 0 To 255 Do Screen^[320 * 197 + i] := i;
    For i := 0 To 255 Do Screen^[320 * 198 + i] := i;
    For i := 0 To 255 Do Screen^[320 * 199 + i] := i;

  { Czy naci╢niΩto klawisz strza│ki? }
    If KeyDown[kUp] Then If (y - 64) > 0 Then Dec(y);
    If KeyDown[kDown] Then If (y + 63) < 199 Then Inc(y);
    If KeyDown[kLeft] Then If (x - 64) > 0 Then Dec(x);
    If KeyDown[kRight] Then If (x + 63) < 319 Then Inc(x);

  { Z bufora ramki - na ekran }
    Move(Screen^[0], Mem[$A000:0], 64000);
  Until KeyDown[kEsc];

  Dispose(Screen);
  Dispose(Image);
  Dispose(Light);

  TextMode(C80);
  SetIntVec($09, OldKbdInt);

  Writeln('Red Light Effect');
  Writeln('Designed by Piotrala');
  Writeln;
  Writeln('Contact us!');
  Writeln('http://mediaworks.w.interia.pl/');
  Writeln('e-mail: mediaworks@interia.pl');
End. { LIGHT.PAS }

Mo┐esz pobraµ gotowy program pokazuj▒cy powy┐szy efekt wraz ze ╝r≤d│ami w pascalu.


Autorem artyku│u jest Piotr Horzycki.


Baner reklamowy: