Jak rozpalić ogień w 3 krokach ?
programowanie grafiki dla początkujących
Oglądałeś kiedyś jakieś starsze demko demoscemowe ? Przypuszczam że tak (jeśli nie to wejdź na ww.hornet.org - tam jest tego cała masa). Na pewno widziałeś w jednym z nich (często też w intrach scenowych) efekt graficzny spalania -ogień. Na początku pomyślałeś pewnie - "jak oni to zrobili to niemożliwe !?", póxniej pewnie zacząłeś się nad tym głębiej zastanawiać. Jeśli do tej pory jeszcze do niczego konkretnego nie doszedłeś, a nadal chcesz zrobić swój własny, niepowtarzalny efekt graficzny ognia to usiadź wygodnie i czytaj dalej...
Krok 1 TEORETYCZNY ZARYS TWORZENIA PŁOMIENI PRZY POMOCY KARTY VGA. (czyli jak to w ogóle działa ?:))
Widziałeś kiedyś płomień ? Na pewno tak. Jaki on jest ? Najjaśniejszy (czyli najbardziej czerwony :)) jest on u swej podstawy a wyciemnia się w miarę wznoszenia. Do tego jego płomienie biegną w górę nieregularnie- powiedzmy w losowo wybranych kierunkach. Tak samo będzie wyglądał nasz symulowany/programowy płomień, tylko że on będzie działał tylko na płaskiej powierzchni i nie będzie mógł się wypalić (to chyba jego plus:)), tak jak to zwykł robić naturalny płomień. Ok., znasz już podstawy. Dzwoń po straż pożarną - niech będą w pogotowiu - przechodzimy do kroku nr 2....
Krok 2 CO BĘDZIE NAM POTRZEBNE ? (czyli opis wszystkiego oprócz kompilatora).
Aby w ogóle zacząć cokolwiek pisać musimy przełączyć się z tekstowego w tryb graficzny. Na kartach VGA (a o takie nam chodzi) jest stosunkowo dużo trybów do wyboru, my jednak zajmiemy się implementacją trybu o wdzięcznym numerze 13h, ze względu na jego dość duże możliwości przy małym stopniu trudności. Aby wydusić z karty VGA tryb 13h należy użyć następującej wstawki assemblerowej:
... asm{mov ah,0 // w AH musi być zero mov al,13h // a w AL. numer trybu - tu 13h int 10h} // włączamy powyższe przez przerwanie karty- 10h ...
Aby wrócić spowrotem do trybu tekstowego zamiast 13h prześlij do al. 03h.
Przykładowa funkcja do zmiany trybów w języku C może wyglądać następująco:
tryb(unsigned char tryb) { asm {mov ah,0 mov al,tryb int 10h };
Okiej ta funkcja nie jest trudna do zrozumienia, więc możemy przejść dalej.
Jako że postanowiłem zrobić mały ogień o wysokości 63 pixeli (to możesz potem b. łatwo zmienić), potrzebujemy więc 63 odcieni czerwieni. Zeby nie utrudniać sprawy i szukać tych kolorów niwiadomo gdzie po standardowej palecie, proponuję zaprogramować własną paletę - składającą się tylko z 64 składników. Jak to zrobić ? Bardzo łatwo wystarczy że:
- powiesz komputerowi że chcesz zmieniać rejestry palety kolorów,
- powiesz że chcesz do nich zapisywać (a możnaby też odczytywać :)),i wktórym indeksie palety chcesz grzebać,
- prześlesz kolejno do rejestru danych karty składniki RGB dla wyżej wymienionego koloru.
- i już.
Aby to zaimplementować trzeba wiedzieć jeszcze o kilku innych ważnych rzeczach:
- otóż maska palety kolorów to 0x3c6
- maska dla oznaczenia rejestru do zapisu to 0x3c8
- maska dla rejestru do odczytu to 0x3c7
- maska dla składników rgb wybranego koloru to 0x3c9 - i tu właśnie trzeba przesłać wszystkie dane o kolorze :)
Okiej. Wszystko już wiesz. Trzeba to zaimplementować !
Najpierw zadeklarujemy jako stałe maski palety:
(w języku C najepiej je po prostu zdefiniować)
...
#define PALETTE_MASK 0x3c6
#define PALETTE_REGISTER_RD 0x3c7
#define PALETTE_REGISTER_WR 0x3c8
#define PALETTE_DATA 0x3c9
...
Aby przesłać kolory do palety można to zrobić kolejno, bezpośredni, ale my żeby było ciekawiej użyjemy do oznaczenia koloru RGB bardzo prostej struktury danych:
...
typedef struct RGB_color_typ
{
unsigned char red;
unsigned char green;
unsigned char blue;
}RGB_color,*RGB_color_ptr;
...
Proste co nie, ta struktura jest tylko opcją, więc jeśli tworzenie struktur sprawia Ci jakiekolwiek trudności olej to i zrób po swojemu :)
Teraz trzeba by napisać funkcję zmieniającą podany kolor. TA funkcja może wyglądać tak:
...
void Set_Palette_Register(int index, RGB_color_ptr color)
{
outp(PALETTE_MASK,0xff); // ustawienie maski palety
outp(PALETTE_REGISTER_WR, index); // ustawienie indeksu w którym będziemy zapisywać
outp(PALETTE_DATA,color->red); // przesłanie danych ze struktury, ale możesz też przesyłać to w inny sposób
outp(PALETTE_DATA,color->green);
outp(PALETTE_DATA,color->blue);
}
...
Aby poprawnie ustawić kolor to jak już pewnie skapowałeś trzeba najpierw odpowiednio wypełnić strukturę.
Dobra jest, idzie nam całkiem dobrze :) Mam nadzieję że po tym nudzeniu jeszcze się nie zniechęciłeś - ale wierz mi operacje na palecie naprawdę Ci się przydadzą - możan dzięki temu tworzyć naprawdę bajer efekty - fady itp.
Jeśli mamy już kolor to trzeba o takim kolorze wyświetlić pixel. To chyba najłatwiejsza sprawa w tym punkcie :)
Aby zaimplementować funkcję wyświetlającą pixel musisz wiedzieć tylko, że:
- bufor karty VGA to najzwyklejszy w świecie, ciągły ciąg znaków (przez to to wszystko jest takie proste),
- zaczyna się on w pamięci od adresu A000:0000 a kończy się na A000:F9FF, i ma dokładnie 64000 bajtów (bo jeden pixel to 1 bajt),
- aby obliczyć współrzędną punktu na ekranie, jako że bufor VGA jest ciągiem/jednowymiarową tablicą musisz:
- pomnożyć współrzędną y przez 320,
- dodać współrzędną x do wyniku,
- przesunąć się o tyle w buforze video,
- zapisać pod ten adres wartość z zakresu 0-255 - czyli wartość koloru.
Prawda że proste ! W praktyce wygląda to mniej więcej tak:
...
unsigned char far *video_buffer = (char far *)0xA0000000L;
... // wskaźnik na pamięć VIDEO
...
void Pixel(int x, int y, unsigned char color)
{
video_buffer[y*320+x]=color;
}
...
I już - pyk,cud i na ekranie świeci się kropeczka (kwadracik:))! Potrzebna będzie nam jeszcze funkcja do pobierania koloru pixela, ale jest to tak proste że nie będę się o tym teraz rozpisywał...
Wszystko już wiemy, wszystko już mamy. Teraz przechodzimy do następnego punktu, by wreszcie wziąć się za konkrety ..
Krok 3 IMPLEMENTACJA OGNIA. (czyli implementacja ognia ! (nareszcie)).
Teraz już pójdzie z górki. Najpierw ustawiamy potrzebną nam paletke, składajacą się z 64 odcieni czerwieni. To naprawdę BARDZO proste, funkcja w języku C będzie wyglądć tak:
void Paletka(void)
{
RGB_color color;
int index;
for (index=0; index < 64; index++)
{
Get_Palette_Register(index,(RGB_color_ptr)&color);
color.red = index;
color.green = 0;
color.blue = 0;
Set_Palette_Register(index, (RGB_color_ptr)&color);
}
}
To bardzo proste - z łatwoscią możesz teraz przekonwertować to na dowolny język programowania !
Teraz trzeba dolać troche paliwa - czyli wygenerować w dole ekranu linię o najjaśniejszym kolorze (bo u podstawy ogień jest najjaśniejszy).
Funkcja to robiaca wygląda tak:
void Podst()
{
int i;
for(i=0;i<320;i++)
Pixel(i,199,63);
};
Po prostu w pętli stawia kolejne pixele o współrzędnej y=199 i x=x++.
Teraz najciekawsza funkcja ze wszystkich - palenie.
Aby palić trzeba po prostu w pętli sprawdzać kolejno od góry czy poniżej jest ogień (czyli czy poniżej jest kolor jaśniejszy od 3). Jak jest to trzeba go zmniejszyć o losową liczbę z przedziału 1..3 - żeby się wygasał. Nie można sprawdzać tylko ostatniej lini - 199 bo ogień by się wypalił. Aby ogień nie szedł pionowo w górę tylko naturalnie płonął należy dać mu jakieś
przesunięcie. Można to zrobić przez sprawdzanie punktu nie przez getpixel(x,y+1), tylko getpixel(x+random(3)-1,y+1). Prawda że proste ! Teraz wystarczy to zaimplementować:
...
Burn()
{
int x,y;
int c;
for(y=0;y<199;y++)
for(x=0;x<320;x++)
{
c=Get_Pixel(x+(rand()&30)/10-1,y+1);
if(c>3)
Pixel(x,y,c-(rand()&30)/10-1);
}
};
...
I już napisałeś efekt ognia ! tylko skompilować i cieszyć się płomieniami.
Efekt ten możesz oczywiście dowolnie przerabiać - fajnie wygląda np. zielony ogień.
Możesz spalać obrazki itp.. (efekt - Burning :: ZONE).
Poniżej pełne kody przykładowego programu w języku c:
//////////////////////////////////////////////////////////////////////////////////
///// Biblioteka graficzna dla trybu 13h karty VGA - PioTraK (c) 2000 Piotrak //////
///// Tylko do wglądu !!! //////
//////////////////////////////////////////////////////////////////////////////////
#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <bios.h>
#include <fcntl.h>
#include <memory.h>
#include <math.h>
#include <string.h>
////////////////////////////////// Definicje /////////////////////////////////////
#define PALETTE_MASK 0x3c6
#define PALETTE_REGISTER_RD 0x3c7
#define PALETTE_REGISTER_WR 0x3c8
#define PALETTE_DATA 0x3c9
unsigned char far *video_buffer = (char far *)0xA0000000L;
///// Struktura danych: RGB_color (kolory: czerwony, zielony, niebieski) //////
typedef struct RGB_color_typ
{
unsigned char red;
unsigned char green;
unsigned char blue;
}RGB_color,*RGB_color_ptr;
////////////////// Ustawienie rejestru palety kart VGA ///////////////////////////
void Set_Palette_Register(int index, RGB_color_ptr color)
{
outp(PALETTE_MASK,0xff);
outp(PALETTE_REGISTER_WR, index);
outp(PALETTE_DATA,color->red);
outp(PALETTE_DATA,color->green);
outp(PALETTE_DATA,color->blue);
}
//////////////////////// Pobranie wartosci rejestru palety ///////////////////////
void Get_Palette_Register(int index, RGB_color_ptr color)
{
outp(PALETTE_MASK,0xff);
outp(PALETTE_REGISTER_RD, index);
color->red = inp(PALETTE_DATA);
color->green = inp(PALETTE_DATA);
color->blue = inp(PALETTE_DATA);
}
///////////////////// Stworzenie fantastycznej palety kolorow ////////////////////
void Paletka(void)
{
RGB_color color;
int index;
for (index=0; index < 64; index++)
{
Get_Palette_Register(index,(RGB_color_ptr)&color);
color.red = index;
color.green = 0;
color.blue = 0;
Set_Palette_Register(index, (RGB_color_ptr)&color);
}
}
////////////////////// Funkcja do rysowania lini pionowej ////////////////////////
void Linia_V(int y1, int y2, int x, unsigned int color)
{
unsigned char far *video_buffer = (char far *)0xA0000000L;
unsigned int line_offset, index;
line_offset =((y1<<8)+(y1<<6))+x;
for(index=0; index<=y2-y1; index++)
{
video_buffer[line_offset]= color;
line_offset+=320;
}
}
/////////////////////// Funkcja do rysowania lini poziomej ///////////////////////
void Linia_H(int x1, int x2, int y, unsigned int color)
{
unsigned char far *video_buffer = (char far *)0xA0000000L;
_fmemset((char far *)(video_buffer + ((y<<8)+(y<<6))+x1),
color,x2-x1+1);
}
////////////////////////// Funkcja do rysowania pixela ///////////////////////////
void Pixel(int x, int y, unsigned char color)
{
unsigned char far *video_buffer = (char far *)0xA0000000L;
video_buffer[y*320+x]=color;
}
unsigned char Get_Pixel(int x,int y)
{
return video_buffer[((y<<8) + (y<<6)) + x];
};
void Podst()
{
int i;
for(i=0;i<320;i++)
Pixel(i,199,63);
};
Burn()
{
int x,y;
int c;
for(y=0;y<199;y++)
for(x=0;x<320;x++)
{
c=Get_Pixel(x+(rand()&30)/10-1,y+1);
if(c>3)
Pixel(x,y,c-(rand()&30)/10-1);
}
};
/////////////////////////////////// Funkcja glowna ///////////////////////////////
void main(void)
{
int a;
RGB_color color, color_1;
asm{mov ah,0
mov al,13h
int 10h
}
Paletka();
Podst();
while(!kbhit())
Burn();
}
Dla magazynu NoName: PioTraK - piotrak@box43.gnet.pl
|