Większość efektów tu prezentowanych ma wspólną cechę, aby działały odpowiednio szybko potrzebują odpowiednio wyliczonej wcześniej tablicy tzw. "lookup tables" (LUT). Tablicowanie pozwala pozbyć się najbardziej spowalniających fragmentów programu, operacji mnożenia, funkcji trygonometrycznych. Ostatecznie pozostają najszybsze operacje takie jakie dodawanie i odejmowanie.
Operacje trygonometryczne podane w poniższych przykładach mogą wymagać podania parametru w radianach, stąd przelicznik stopni na radiany ANGLE*PI/180. Funkcja SQR oznacza potęgowanie (np. x*x = SQR(x)), SQRT pierwiastkowanie (np. pierwiastek drugiego stopnia z 9 = SQRT(9)). Funkcja ROUND zaokrągla wartość rzeczywistą do wartości całkowitej (np. ROUND(3.124) = 3), ABS zwraca wartość absolutną czyli bez znaku (np. ABS(-3) = 3), DIV dzielenie bez reszty (np. 9 div 2 = 4), MOD reszta z dzielenia DIV (np. 9 MOD 2 = 1). Operator '<<' oznacza operację przesuwania bitów w lewo (shift left) czyli mnożenie przez potęgę dwójki, operator '>>' oznacza przesuwanie bitów w prawo (shift right) czyli dzielenie przez potęgę dwójki.
Większość załączonych przykładów w Delphi działa w pętli bez końca, zatrzymanie takiego programu wymaga naciśnięcia klawisza ESC.
Najpopularniejszy sposób pzekazywania informacji w różnego typu produkcjach na każdej platformie sprzętowej to płynnie przesuwające się napisy - scrolle, poruszają się najczęściej od prawej do lewej strony ekranu, albo od dołu do góry ekranu.
Przesuw w poziomie jest ułatwiony dzięki programowi ANTIC-a (Display List), wystarczy ustawić adres LMS (Load Memory Scan) dla linii, najlepiej na początku jakiegoś 4KB bloku pamięci, potem tylko modyfikować taki adres zwiększając go kolejno, uzyskamy w ten sposób przesuw zgrubny - co jeden znak, np.:
org $600
dlist dta $70,$70,$70 ; 24 puste linie ($70 = 8 pustych linii)
dta $42 ; rozkaz ANTIC-a LMS ($42) dla trybu $02
adres dta a(text) ; adres scrolla
dta $41,a(dlist) ; zakończenie programu ANTIC-a
main mwa #dlist 560 ; ustawiamy nowy adres programu ANTIC-a
loop ldx #6 ; pętla opóźniająca 6 ramek
@ lda:cmp:req 20
dex
bne @-
inw adres ; zwiększanie adresu (WORD)
jmp loop
org $a000
text dta d' atari xe/xl'
run main ; adres uruchomienia tego przykładu
W serii Atari XE/XL zostały przewidziane rejestry sprzętowe ANTIC-a pozwalające realizować płynny przesuw w poziomie i pionie. Płynne przesuwanie linii w poziomie realizuje rejestr HSCROL ($D404). Wpisując do tego rejestru kolejne wartości 3,2,1,0 uzyskamy płynne przesunięcie o 4 cykle koloru. Poniższy przykład realizuje taki scroll dla trybu $02 ANTIC-a (standardowy tryb znakowy).
org $600
dlist dta $70,$70,$70 ; 24 puste linie ($70 = 8 pustych linii)
dta $42|$10 ; rozkaz ANTIC-a LMS ($42) dla trybu $02 + $10 dla HSCROL
adres dta a(text) ; adres scrolla
dta $41,a(dlist) ; zakończenie programu ANTIC-a
main mwa #dlist 560 ; ustawiamy nowy adres programu ANTIC-a
loop lda tmp ; płynny scroll, przepisanie wartości TMP do rejestru HSCROL
sta hscrol ; koniecznie przed poniższą pętlą opóźniającą
ldx #6 ; pętla opóźniająca 6 ramek
@ lda:cmp:req 20
dex
bne @-
dec tmp ; zmniejszenie komórki TMP [3,2,1,0]
bpl loop ; pętla
mva #3 tmp ; odnowienie wartości komórki TMP
inw adres ; scroll zgrubny
jmp loop
tmp dta 3 ; pomocnicza komórka pamięci TMP
org $a000
text dta d' atari xe/xl'
run main ; adres uruchomienia tego przykładu
Powyższe przykłady scrolli można zakwalifikować jako DXCP (Different X Character Position). Są też inne ich rodzaje, bardziej widowiskowe i bardziej wymagające obliczeniowo jak DYCP (Different Y Character Position), DYPP (Different Y Pixel Position), DXPP (Different X Pixel Position).
Scroll typu DYCP oznacza że nadal używany jest tryb znakowy jednak znaki przemieszczają się dodatkowo w pionie. Dla XE/XL i pewnie wszystkich platform ery 8-bit nie ma możliwości ustawienia sprzętowego scrolla pionowego dla każdego znaku z osobna, dlatego efekt taki realizowany jest poprzez modyfikację pamięci zestawu znaków. Znaki zostają zorganizowane parami lub trójkami lub czwórkami itd. tak aby dostęp do pamięci je opisującej był liniowy. Poniżej przykład takiego układu w którym dostajemy możliwość przemieszczania znaku w zakresie 24-8 linii, w obrębie jednego zestawu 128 znaków:
a d g j...
b e h k...
c f i l...
Pierwszy układ znaków zaczyna się od adresu CHARSET+'a'*8, kolejny CHARSET+'d'*8, CHARSET+'g'*8, CHARSET+'j'*8. Odpowiednia procedura będzie przemieszczała standardowy 8-liniowy znak w przestrzeni 24-8 linii, uzyskując efekt falowania, przesuw poziomy realizowany jest poprzez HSCROL. Taki scroll DYCP to najczęściej sinus-scroll.
Pionowe raster bary, najpewniej po raz pierwszy zaprezentowane przez Amigową grupę Kefrens, stąd nazwa efektu KEFRENS BARS. Wszelkie kombinacje raster barów można obejrzeć w demie z 1990 roku "Copper master by angels"
Zasada działania efektu sprowadza się do wyrenderowania obrazu składającego się z pionowych pasów "raster bara". Pozycja pozioma ustalana jest na podstawie prekalkulowanej tablicy sinus-a, pozycja pionowa jest zwiększana o stałą wartość kroku, "raster bar" jest rysowany od aktualnej pozycji pionowej do końca obrazu.
Przykład pętli renderującej dwa pionowe rasterbary:
c = 0
while c < height
d = 160+sinLUT[ (add1+c) and $ff ] + sinLUT[ (add2+c+32) and $ff ]
draw_raster_bar( Point(d,c), Point(d,height) )
d = 140+sinLUT2[ (add2+c) and $ff ] + sinLUT2[ (add1+c+64) and $ff ]
draw_raster_bar( Point(d,c), Point(d,height) )
c = c + add_y
end
add1 = add1 + 2
add2 = add2 + 3
sinLUT i sinLUT2 to wcześniej prekalkulowane tablice z kształtem sinus-a.
for a = 0 to 255
sinLUT[a] = round( (sin(a*(360/256) * pi/180) )*64)
sinLUT2[a] = round( (sin(a*(360/256) * pi/180) )*48)
next a
Inna odmiana plottera, tym razem stawiamy piksle jakiejś jedno-kolorowej bitmapy (np. 8x8 piksli) dokonując operacji EOR na zawartości ekranu. Im więcej powtórzeń tym bardziej kolorowy efekt końcowy.
"Nieskończona" liczba ruchomych obiektów, czyli zapętlona animacja. Do realizacji potrzebnych jest kilka buforów pamięci (maksymalnie 8 powinno wystarczyć), każdy z takich buforów będzie wyświetlał inną fazę animacji, kiedy trajektoria ruchu naszych obiektów ulega zapętleniu dostajemy złudzenie animacji, wydaje się że porusza się nieskończona liczba obiektów gdy tak naprawdę widzimy tylko kilku klatkową animację. Trochę dodatkowych informacji na ten temat w artykule Jak stworzyć ciekawe efekty?
Efekt toczącego się sześcianu, obracanego tylko wokół jednej osi X (prawoskrętny układ współrzędnych). Jest to "udawany" obrót bo nie mający wiele wspólnego z obliczeniami 3D i perspektywą, małym nakładem wykorzystując funkcję SIN-us można uzyskać efekt przypominający toczący się w naszym kierunku sześcian.
Obliczamy tablicę sinusa sinLut dla SIZE kroków:
size = 64 // rozmiar szescianu
k = 0
for i = 0 to size-1
sinLut[i] = (round(sin(k*pi/180)*(size div 4)))
k = k + (180 / size)
next i
Obliczanie szerokości odcinka na podstawie sinusa, odcinek porusza się z góry na dół. Odcinki górny i dolny wyznaczające krawędzie sześcianu pozostają nieruchome.
l = screen_width div 2 - size div 2 // lewa krawedz szescianu
r = screen_width div 2 + size div 2 // prawa krawedz szescianu
for anm = 0 to size-1
u := sinLut[anm];
Polygon( [Point(l,0), Point(r,0), Point(r+u, anm),
Point(r, size-1), Point(l, size-1), Point(l-u, anm)] )
// poruszajacy sie odcinek symulujacy ruch bryły
Line( Point(l-u, anm), Point(r+u, anm) )
next anm
Efekt ten i jego inne kombinacje najczęściej wykorzystywany jest do pokazywania teksturowanych obracających się brył typu sześcian, prostopadłościan itp. w różnych konfiguracjach. Długości wszystkich odcinków znamy musimy tylko posiadać teksturę we wszystkich wariantach długości odcinka, potem tylko podmieniać odpowiednio linie aby uzyskać efekt obrotu.
Można także pokusić się o dokonanie prawdziwego obrotu 3D bryły sześcianu i stablicować odcinki które zostaną obliczone, na ich podstawie generować teksturowany sześcian, szybkość działania powinna być ta sama kosztem niewiele większej zajętej pamięci na tablicę odcinków.
Tutaj przykład w Delphi obrazujący obrót sześcianu.
Efekt ognia to pochodna efektu BLUR, który polega na postawieniu nowego piksla w kolorze wyliczonym na podstawie piksli sąsiadujących, konkretniej na podstawie średniej kolorów sąsiadujących. Aby ogień mógł płonąć potrzebne jest "paliwo", będą to wstawiane do naszego bufora ognia poza widocznym obszarem piksle o najwyższej wartości koloru, dzięki temu operacja blurowania będzie stale podsycana, dodatkowo losowe pozycje takich podsycających piksli wpłyną na powstawanie mniejszych lub większych płomieni. Nic też nie szkodzi na przeszkodzie aby "palić" inne obiekty na ekranie, wektorowe druciki, napisy itp.
Przykład w pseudokodzie dla operacji ognia:
for y = 0 to height+1
for x = 0 to width-1
pixels[x,y] = ( pixels[x-1,y] +
pixels[x+1,y] +
pixels[x,y+1] +
pixels[x+1,y+1] ) >> 2
next x
next y
Przykład w pseudokodzie dla operacji podsycania ognia:
Popularny bo atrakcyjny wizulanie efekt zniekształceń sinusopodobnych. Do efektu końcowego dochodzimy najczęściej metodą prób i błędów zmieniając poszczególne parametry aż do uzyskania zadowalającego efektu wizualnego.
Przykład w pseudokodzie wyliczenia trzech tablic typu BYTE S4[0..255], S8[0..255], S16[0..255] dla plazmy:
k = 0
for i = 0 to 255
s16[i] = round(32*sin(k)*pi)
s8[i] = round(16*cos(k))
s4[i] = round(8*sin(k)*pi)
k = k+6.284/128
next i
Wartości tablic S4, S8, S16 muszą ulegać zapętleniu, sinus ze względu na swoją okresowość jest do tego celu bardzo przydatny.
Przykład w pseudokodzie głównej pętli realizującej animację plazmy (tablice XBUF, YBUF oraz pozostałe parametry towarzyszące ich wyliczaniu decydują o ostatecznym wyglądzie plazmy):
a1 = a1bak
a2 = a2bak
a3 = a3bak
(* XBUF *)
for i = 0 to width-1
xbuf[i] = s16[a1]+s8[a2]+s4[a3]
a1 = a1 + 1
a2 = a2 + 2
a3 = a3 + 4
next i
a1bak = a1bak + 1
a2bak = a2bak + 3
a3bak = a3bak + 2
a4 = a4bak
a5 = a5bak
(* YBUF *)
for i = 0 to height-1
ybuf[i] = s16[a4]+s8[a5]
a4 = a4 + 1
a5 = a5 + 3
next i
a4bak = a4bak + 1
a5bak = a5bak - 1
(* MAIN LOOP *)
for j = 0 to height-1
for i = 0 to width-1
pixels[i,j] = xbuf[i] + ybuf[j]
next i
next j
Powyższy efekt plazmy wykorzystany był m.in. w takich demach jak TIT, Ilusia, C-Drug
Efekt obracającej, powiększającej i pomniejszającej się bitmapy, wszystko to na podstawie wzoru obrotu punktu wokół osi Z:
for y = 0 to height-1
for x = 0 to width-1
u = round(x*cos(angle)-y*(sin(angle)))
v = round(x*sin(angle)+y*(cos(angle)))
pixels[x,y] = texture[u,v]
next x
next y
Widać że powtarzają się operacje mnożenia sinus i cosinus kąta ANGLE. Aby przyspieszyć działanie potrzebne będzie stablicowanie tych powtarzających się wyników operacji. Przykład w pseudokodzie wyliczenia tablic typu WORD SIN_TABLE[0..255] i COS_TABLE[0..255] dla obrotu i skali powiększenia:
angle = (6.284/128)*96
dzo = 0.05
zo = dzo*21
ml = 256 ; pozwoli zamienić ułamek w liczbę całkowitą
for a = 0 to 255
zo = zo-dzo
if (a mod 64) = 0
dzo = -dzo
endif
sin_table[a] = round((-zo*sin(angle))*ml)
cos_table[a] = round((-zo*cos(angle))*ml)
angle = angle+6.284/128
next a
Tablice SIN_TABLE i COS_TABLE przechowują wartości ułamkowe reprezentowane na 16 bitach, możliwa jest reprezentacja 8 bitowa kosztem mniejszej głębi rotozoomera, czyli większego kąta obrotu i mniejszego zakresu powiększenia/zmniejszenia.
Przykład w pseudokodzie głównej pętli realizującej animację rotozoomera (tablica z teksturą TEXTURE[0..63, 0..63]):
zx = sin_table[angle]
zy = cos_table[angle]
{ centrowanie }
fx_ = 65536-((height div 2)*(zx+zy))
fy_ = 65536-((width div 2)*(zx-zy))
for j = 0 to height-1
fx = fx_
fy = fy_
for i = 0 to width-1
pixels[i,j] = texture[(fx >> 8) mod 64 , (fy >> 8) mod 64]
fx = fx+zx
fy = fy-zy
next i
fx_ = fx_+zy
fy_ = fy_+zx
next j
Wartością całkowitą ułamków reprezentowanych na 16 bitach jest najstarszy bajt słowa, stąd operacja przesuwania ośmiu bitów w prawo '>> 8'. Modulo 'MOD 64' pomaga ograniczyć zakres X:Y dla tekstury 64x64 piksle. Jeśli pozbędziemy się operacji centrowania, środek obrotu rotozoomera znajdzie się w punkcie (0,0) obrazu. Dodatkowe efekty jak STRETCH, PERSPECTIVE uzyskuje się modyfikując parametry FX_, FY_.
W demie Igor znalazł się rotozoomer z animowaną teksturą, efekt polega na umieszczeniu danych tekstury w dodatkowych bankach pamięci i ich przełączaniu.
W demie TIT teksturą rotozoomera liczonego na 8 bitowych ułamkach jest tunel.
Efekt po raz pierwszy zaprezentowany w Just Fancy. Sposób jego realizacji został owiany tajemnicą, którą rozjaśniał jeden z artykułów Barymag-a #2. Od tamtej pory powtórzony tylko w demie Recall.
Dzisiaj chcę przedstawić wam scroll nr 2 - czyli zoomer (można go zobaczyć w moim starym demie "JUST FANCY"). Do dzisiaj niewiele osób wie (nawet widząc kod), w jaki sposób się go uzyskuje. Na początek wyjaśnię, jak każdy by chciał taki scroll zrobić. Pierwszym problemem, jaki napotyka jest jak rozciągnąć znaki w poziomie i zaraz zaczyna kombinować z LSR-owaniem lub ASL-owaniem. Ja też w ten sposób myślałem i nawet coraz lepiej mi szło, ale wciąż nie było mowy o scrollu w jednej ramce. Pewnego dnia przyszło niespodziewane olśnienie, a gdyby tak trzymać w pamięci scrolla bokiem???. Z wolna zaczął się pojawiać coraz bardziej klarowny obraz późniejszej procedury, aż w końcu zjawił się na skrawku kartki (jak większość moich programów) pełny obraz tego jakże prostego do uzyskania efektu.
Teraz pora, żebym Ci wyjaśnił na czym polega owo "trzymanie" scrolla bokiem. Załóżmy, że zwykłego scrolla chcemy zrobić graficznie, czyli litery z rolowanego bufora będziemy przepisywać na ekran, uzyskując efekt "przesuwu poziomego". I teraz... jak wygląda litera "A" w takim scrollu rozpisana na bity, każdy chyba wie:
........ bajt nr 0
...**... bajt nr 1
..****.. bajt nr 2
.**..**. bajt nr 3
.**..**. bajt nr 4
.******. bajt nr 5
.**..**. bajt nr 6
........ bajt nr 7
W zoomscrollerze musimy zmienić nieco nasze przyzwyczajenie i zorganizować bufor tak, aby litera "A" wyglądała następująco:
........ bajt nr 0
.****... bajt nr 1
.*****.. bajt nr 2
..*..**. bajt nr 3
..*..**. bajt nr 4
.*****.. bajt nr 5
.****... bajt nr 6
........ bajt nr 7
I tak powtarzamy to dla ośmiu bajtów bufora. Na końcu przepisujemy komórki ZERO_PAGE na ekran. W ten sposób przepisujemy 8 linii po 256 punktów. Oczywiście takim przepisywaniem nie uzyskamy efektu zoomera, więc musimy wcześniej przygotować tablicę, w której indeksem jest numer kolumny punktów na ekranie, a wartością w tablicy jest odpowiadający jej numer bajtu w buforze. Do naszej procedury dopisujemy przed każdym "LDA" LDX TABLICA:
LDX TABLICA
LDA BUFOR,X
ASL A ...
...a całość smarujemy hektarem kodu, który będzie rysował odpowiednio na ekranie. Ponieważ mamy do czynienia z komputerem posiadającym coś takiego, jak DList, to "rozciąganie" w pionie uzyskujemy właśnie na nim. Ponieważ, jak już wcześniej zaznaczyłem, artykuł ten jest dla BnŚZ (Bardziej niż Średnio Zaawansowanych), nie muszę tłumaczyć w jaki sposób wyliczać TABLICĘ (chociaż muszę powiedzieć od razu, że w "JUST FANCY" nie wykorzystałem dzielenia, przez co uprościłem sobie procedurę).
Modyfikacja głównej pętli renderującej ZOOMROTATOR-a pozwala na uzyskanie obracającej i powiększającej się szachownicy bez potrzeby używania dodatkowych bitmap z teksturą. Efektowną modyfikacją tego efektu jest CHESS ZOOMER realizowany na kilku nachodzących na siebie "bitplanach".
zx = sin_table[angle]
zy = cos_table[angle]
{ konieczne centrowanie aby zachować symetrię względem środka ekranu }
fx_ = 65536-((width div 2)*(zx+zy))
fy_ = 65536-((height div 2)*(zx-zy))
for j = 0 to height-1
fx = fx_
fy = fy_
for i = 0 to width-1
if (fx xor fy) >> 8 and $10 = 0
Pixels[i,j] = $ff
else
Pixels[i,j] = $00
endif
fx = fx+zx ; zaremować dla CHESS ZOOMER-a
fy = fy-zy
next i
fx_ = fx_+zy
fy_ = fy_+zx ; zaremować dla CHESS ZOOMER-a
next j
W tym samym przebiegu stawiamy i kasujemy odpowiednie piksle. Kolor pikseli determinuje wynik operacji XOR na wartościach całkowitych (najstarszy bajt) ułamków przyrostów X i Y.
CHESS ZOOMER najbardziej podatny jest na optymalizację ze względu na powtarzające się linie poziome, dobrym tego przykładem jest jedna z części dema Just Fancy.
Inną właściwością sprzyjającą przyspieszeniu tworzenia tego efektu jest to że każdą klatkę niezależnie od skali powiększenia możemy zaprezentować za pomocą 16 kombinacji bloków 2x2 piksele.
Dodatkowo jeśli nie używamy efektu STRETCH tylko zachowujemy symetryczność względem środka ekranu możemy zauważyć że dolna część szachownicy jest odbiciem lustrzanym górnej części, dodatkowo prawa górna ćwiartka jest odbiciem
lewej ćwiartki obróconej o 90 stotpni i zanegowanej, wystarczy więc generować tylko jedną ćwiartkę szachownicy a pozostałe odpowiednio skopiować. Warunkiem powodzenia dla kopiowania symetrycznego jest ta sama wysokość i
szerokość szachownicy, np. width=128, height=128.
Stworzenie prawej górnej ćwiartki obrazu poprzez obrót o 90 stopni i negację lewej górnej ćwiartki:
for j = 0 to height div 2 - 1
for i = 0 to width div 2-1 do
Pixels[width-i-1,j] = Pixels[j,i] xor $ff
next i
next j
Kopiowanie ostatnich dwóch dolnych ćwiartek obrazu:
for j = 0 to height div 2 - 1
for i = 0 to width - 1
Pixels[width-i-1,height-j-1] = Pixels[i,j]
next i
next j
Warto też zwrócić uwagę że nie trzeba wykonywać pełnego obrotu, wystarczy obrót o 90 stopni, reszta się powtarza tylko jest w inwersie. W przypadku XE/XL oznacza to że po obrocie o 90 stopni zamieniamy kolory w rejestrach na przeciwne i powtarzamy ten sam obrót o 90 stopni, animacja ulega zapętleniu.
Wypełnianie czworokątów. Dysponując przeliczonymi kątami obrotu w zakresie 90 stopni można wyrysować czworokąt o zadanej długości boku.
zx:=txB[byt mod 16];
zy:=tyB[byt mod 16];
for dir := 0 to 3 do begin
case dir of
1: miny:=fy shr 8;
3: maxy:=fy shr 8;
end;
for i := 0 to len-1 do begin
case dir of
0: begin
fx:=fx+zx;
fy:=fy-zy;
edge[fy shr 8]:=fx shr 8;
end;
1: begin
fx:=fx+zy;
fy:=fy+zx;
edge[$100+fy shr 8]:=fx shr 8;
end;
2: begin
fx:=fx-zx;
fy:=fy+zy;
edge[$100+fy shr 8]:=fx shr 8;
end;
3: begin
fx:=fx-zy;
fy:=fy-zx;
edge[fy shr 8]:=fx shr 8;
end;
end;
end;
end;
Efekt powiększenia soczewki albo mapowanie powierzchni sferycznej. Podstawą dla tego efektu jest tablica TFM typu WORD, starszy bajt takiej tablicy będzie pozycją poziomą tekstury, młodszy bajt pozycją pionową. Parametrami są promień soczewki RADIUS i moc powiększenia POWER.
s = sqrt( sqr(radius) - sqr(power) )
for y = -radius to radius-1
for x = -radius to radius-1
if x*x+y*y >= s*s
A = X
B = Y
else
Z = sqrt( sqr(radius)- sqr(x) - sqr(y) )
A = round(X*power/Z)
B = round(Y*power/Z)
endif
tfm[x+radius , y+radius] = (b+radius) + (a+radius) << 8
next x
next y
Dysponując tablicą z prekalkulowanymi współrzędnymi tekstury możemy wyrenderować efekt (parametry PX i PY pozwalają ustalić dodatkową trajektorię poruszania się soczewki):
for y = 0 to radius << 1-1
for x = 0 to radius << 1-1
u = tfm[ x,y ] >> 8
v = tfm[ x,y ] and $ff
pixels[x + px, y + py] = texture[u + px, v + py]
next x
next y
Tutaj przykład w Delphi realizacji efektu soczewki.
Mapowanie wypukłości, efekt podobny obliczeniowo do efektu ognia, z tą różnicą że w obliczeniach kolejnych piksli bierze udział dodatkowa tablica z teksturą światła.
Tablicę TLIGHT z teksturą światła liczymy na podstawie odległości punktu od środka okręgu - wzór Pitagorasa (LIGHT definiuje maksymalny promień okręgu):
for y = 0 to light-1
for x = 0 to light-1
dist = (light div 2) - round(sqrt( sqr(x-light div 2) + sqr(y-light div 2))) - 1
if dist<0
dist = 0
endif
tlight[x,y] = dist
next x
next y
Obliczamy ofset poziomy XDIF i pionowy YDIF na podstawie piksli tekstury, obliczone wartości posłużą nam do odczytania koloru z tablicy tekstury światła TLIGHT:
for y = 1 to height-2
for x = 1 to width-2
xdif = texture[x+1,y] - texture[x-1,y]
ydif = texture[x,y+1] - texture[x,y-1]
pixels[x,y] = tlight[xdif+x, ydif+y]
next x
next y
Przykład w Delphi realizacji efektu Bump Mapping-u dla tekstury w 16 odcieniach szarości.
Efekt wirującej tekstury, polegający na przedstawieniu dwuwymiarowej tekstury za pomocą koncentrycznych okręgów których środek leży w środku symetrii takiej tekstury. Dzięki odpowiednio wyliczonym tablicom ANGLES, RADIUSES oraz odpowiednio przeliczonej tablicy z teksturą OUTPUT_TEXTURE mamy kontrolę nad każdym takim okręgiem, możemy taki okręg z teksturą obracać o zadany kąt. Jeśli kąt obrotu będzie stały dla każdego z okręgów uzyskamy zwykły efekt obrotu tekstury, jeśli kąt obrotu będzie odpowiednio wzrastał dla każdego kolejnego okręgu uzyskamy efekt wirującej tekstury. Twirl można "ożenić" z Wormhol-em albo innym efektem korzystającym z "lookup tables". Taka tablica generująca klatkę "zakręconego" tunelu będzie poddawana przeliczeniu z użyciem Twirl-a uzyskując efekt obracającego się Wormhola. Po odpowiednich modyfikacjach kilkanaście wygenerowanych tablic efektu Twirl dla różnych rozmiarów okna obrazu złoży się na szybki efekt zoomrotatora (Warto nadmienić, że o bardzo kiepskiej precyzji skalowania. Do poprawy potrzeba byłoby dużych ilości pamięci, na które nie stać maluszka. - przypis Konop/Shadows). Inne zastosowania można odnaleźć eksperymentując z główną pętlą renderującą efekt Twirl.
Przykład obliczeń dla tablic ANGLES, RADIUSES (opracował Konop/Shadows):
max_radius = sqrt( sqr(picture_size_x div 2) + sqr(picture_size_y div 2) )
for y = 0 to picture_size_y-1
y_screen = -(y - (picture_size_y div 2))
for x = 0 to picture_size_x-1
x_screen = x - (picture_size_x div 2)
angle = ArcTan2(y_screen, x_screen)
angle = angle / (2.0 * pi)
angle_int = round(angle * circle_steps)
radius = sqrt(x_screen * x_screen + y_screen * y_screen)
radius = radius / (max_radius * sqrt(2))
radius_int = round(radius * (max_radiuses-1))
angles[x, y] = angle_int
radiuses[x, y] = radius_int
next x
next y
Przykład przeliczenia tekstury, odczytane kolejno piksle TEXTURE_INPUT po okręgu zostają zapisane liniowo do TEXTURE_OUTPUT (opracował Konop/Shadows):
for r = 0 to max_radiuses-1
for a = 0 to circle_steps-1
angle = a / circle_steps
angle = angle * 2.0 * pi
x = cos(angle) * r
y = sin(angle) * r
x_int = round(x) + texture_size_x div 2
y_int = round(y) + texture_size_y div 2
output_texture[a,r] = input_texture[x_int, y_int]
next a
next r
Renderowanie klatki efektu będzie polegało na odczytaniu aktualnie rysowanego numeru okręgu z tablicy RADIUSES, odczytana wartość będzie indeksem do tablicy z 16-bitowymi ułamkami reprezentującymi prędkość SPEED [0..MAX_RADIUSES-1], starszy bajt (wartość całkowita z ułamka 16-bit) odczytanej prędkości dodamy do aktualnego kąta obrotu odczytanego z tablicy ANGLES.
for y = 0 to picture_size_y
for x = 0 to picture_size_x
rad = radiuses[x,y]
add = speed[rad] >> 8
c = output_texture[ byte(angles[x,y] + add) , rad ]
pixels[x,y] = c
next x
next y
Po zakończeniu renderowania klatki efektu potrzebujemy już tylko uaktualnić tablicę z prędkościami SPEED, w tym celu wartości tej tablicy podbijamy o prekalkulowane wartości tablicy z ułamkami precyzji 16-bit ADDITION [0..511], zawierającej sinusopodobne wartości gwarantujące okresowość ruchu twirla. Tablice SPEED i ADDITION można zrealizować także z precyzją 8-bitową, odbije się to jednak na mniejszej dynamice efektu.
for i = 0 to max_radiuses-1
speed[i] = WORD( speed[i] + addition[i+i+anm] )
next i
anm = anm + 1
Program w Delphi Twirl prezentujący "zakręconą" i "zwykłą" wersję obrotu tekstury.
Na stronie Lode's Computer Graphics Tutorial podane zostały różne sposoby na uzyskanie efektu trójkąta i dywanu Sierpińskiego. Poniżej przykłady najszybszych metod, które są w zasięgu możliwości XE/XL.
Trójkąt:
for y = 0 to height - 1
for x = 0 to width - 1
if x and y = 0
pixels[x,y] = clWhite
else
pixels[x,y] = clBlack
endif
next x
next y
Dywan, który jest ciekawszy wzorniczo od trókąta i jak pokazuje intro "Unlimited drunk carpet" Epi/Tristesse można wykorzystać go do generowania tekstury.
for y = 0 to height - 1
for x = 0 to width - 1
if
//Not both the first (rightmost) digits are '1' in base 3
not( ((x mod 3)=1) and ((y mod 3)=1) )
and
//Not both the second digits are '1' in base 3
not( ((x div 3) mod 3=1) and ((y div 3) mod 3=1) )
and
//Not both the third digits are '1' in base 3
not( ((x div 9) mod 3=1) and ((y div 9) mod 3=1) )
and
//Not both the fourth digits are '1' in base 3
not( ((x div 27) mod 3=1) and ((y div 27) mod 3=1) )
and
//Not both the fourth digits are '1' in base 3
not( ((x div 81) mod 3=1) and ((y div 81) mod 3=1) )
pixels[x,y] = clWhite
else
pixels[x,y] = clBlack
endif
next x
next y
Dla powyższego przykładu aż prosi się aby stworzyć pięć tablic z prekalkulowanymi wartościami operacji <0..255> div [1,3,9,27,81] mod 3.
Program w Delphi Fraktal Sierpinskiego prezentujący działanie obu powyższych algorytmów.
Ciekawy, mało spotykany efekt zniekształcenia symulujący rozchodzące się fale na wodzie. Mało spotykany na platformie 8-bit ze względu na liczbę obliczeń i zapotrzebowania pamięci. Główna pętla renderująca efekt przypomina tą z Bump-a, a obliczenia efektu wody to rozbudowana wersja obliczeń dla efektu ognia. Aby efekt zadziałał potrzebne są dwie tablice wielkości okna ekranu, w pierwszym przebiegu będziemy przeprowadzać operacje na tablicy 1 i 2, w kolejnym na tablicy 2 i 1, czyli będziemy zamieniać je miejscami. Obie tablice służące obliczeniom efektu wody są typu SHORTINT (-128..127).
Pętla realizująca obliczenia efektu wody, przypomina tą z efektu ognia (DENSITY <1..8> wpływa na wielkość rozchodzącej się fali), obliczenia dokonywane są na ośmiu pikslach co zapewnia ładniejszy efekt, mniejsza liczba piksli to większa szybkość działania kosztem efektu wizualnego:
; CALC_WATER (SOURCE, DEST)
for y = 1 to height-2
for x = 1 to width-2
pixel = (source[x,y+1]
+ source[x,y-1]
+ source[x+1,y]
+ source[x-1,y]
+ source[x-1,y-1]
+ source[x+1, y-1]
+ source[x-1, y+1]
+ source[x+1, y+1]
- dest[x,y] << 2 ) >> 2
dest[x,y] = pixel - (pixel >> density)
next x
next y
Pętla renderująca efekt wody, przypomina tą z efektu bumpa (TEXTURE o rozmiarze okna ekranu to tablica z bitmapą na którą spadają krople wody):
; DRAW_WATER (SOURCE)
for y = 1 to height-2
for x = 1 to width-2
dx = source[x-1,y] - source[x+1,y]
dy = source[x,y-1] - source[x,y+1]
pixels[x,y] = texture[x+dx , y+dy]
next x
next y
Postawienie kropli wody, wystarczy je stawiać tylko w pierwszym buforze:
; DROP_WATER (SOURCE)
x = random(width-2) + 1
y = random(height-2) + 1
drop = random(128)
source[x,y] = drop
source[x-1,y] = drop
source[x+1,y] = drop
source[x,y-1] = drop
source[x,y+1] = drop
Główna pętla zarządzająca wywołaniami CALC_WATER, DRAW_WATER, DROP_WATER (zmienna SWITCH typu BOOLEAN pomaga przełączać kolejność operacji dokonywanych na buforach), jedno wywołanie tej pętli to jedna klatka obrazu z efektem wody:
Efekt poruszających się blobów obdarzonych energią, którą oddziaływują na inne bloby. Efekt nie jest zbyt popularny na 8-bitowym sprzęcie pewnie ze względu na liczbę obliczeń jakie są wykonywane, przynajmniej w tej podstawowej metodzie "brute force". Metoda ta polega na obliczeniu dla każdego piksla obrazu odległości od środka bloba, na tej podstawie liczona jest "energia"/"gęstość" blobów którą sumujemy i reprezentujemy w postaci koloru. Szybkość działania jest tutaj ograniczona poprzez rozmiar okna i liczbę blobów.
for y = 0 to height - 1
for x = 0 to width - 1
pixel_brightness = 0
for i = 0 to number_of_blobs - 1
u = x - blobs[i].x
v = y - blobs[i].y
distance = sqrt ( sqr(u) + sqr(v) )
if distance > blobs[i].r
density = 0
else
fraction = sqr(distance / blobs[i].r)
density = round(sqr(1-fraction)*256)
endif
pixel_brightness = (pixel_brightness+density) mod 160
next i
pixels[x,y] = pixel_brightness
nexy x
nexy y
Tablica BLOBS zawiera informację o pozycji X:Y bloba oraz jego średnicy R (niektórzy nazywają to masą), w powyższym przykładzie nie są wykorzystywane żadne tekstury światła, tylko prymitywne obliczenia z udziałem pierwiastkowania (SQRT), potęgowania (SQR), dzielenia, mnożenia, czyli wszystko to czego chcielibyśmy się pozbyć aby była realna szansa przeniesienia tego efektu na XE/XL. Spore pole do popisu dla programisty, sposobów realizacji znowu może być wiele, np. podzielenie ekranu na kwadraty, wyznaczenie wspólnych obszarów i dokonywania obliczeń tylko w tych obszarach. Bardziej realnie dla XE/XL będzie jeśli coś stablicujemy.
Pierwszym założeniem jakie możemy przyjąć to stała średnica bloba RADIUS, możemy też trochę skrócić obliczenia, doprowadzając je do postaci którą można zastąpić tablicą "lookup table" TDENSITY typu BYTE reprezentującą ułamki.
for y = 0 to height - 1
for x = 0 to width - 1
distance = sqrt ( sqr(x) + sqr(y) )
if distance < radius
g = 1
else
g = radius / distance
endif
tdensity[x, y] = round(g * 64)
next x
next y
Teraz pozostały nam już tylko operacje dodawania, odejmowania oraz funkcja ABS (wartość absolutna bez znaku) wykonywane tylko na bajtach, wszystko to kosztem tablicy TDENSITY której rozmiar zależny jest od wielkości okna, np. dla ekranu wąskiego i trybu Konopa 64x64 piksle tablica zajmie 4KB.
for y = 0 to height - 1
for x = 0 to width - 1
density = 0
for i = 0 to number_of_blobs - 1
u = abs(x - blobs[i].x)
v = abs(y - blobs[i].y)
density = density + tdensity[u, v]
next i
if density > 96
c = 255
else
c = 0
endif
pixels[x,y] = c
next x
next y
Tutaj przykład w Delphi działania obu powyższych przykładów, PLOBS.EXE i szybki FBLOBS.EXE który da się przenieść na platformę 8-bit.
Tunnel, Wormhole, Ceiling-Floor opierają swoje działanie na odpowiednio wyliczonej tablicy z indeksami do tablicy tekstury. Tablicę wyliczamy dokonując
przeliczenia współrzędnych układu kartezjańskiego na współrzędne układu biegunowego (polarnego).
W przypadku tablicy dla tunelu "na wprost" możliwa jest optymalizacja polegająca na przechowywaniu w pamięci tylko ćwiartki tablicy, resztę można skopiować poprzez lustrzane odbicia, albo wygenerować tylko ćwiartkę obrazu a pozostałe fragmenty generować poddając bajty/linie obrazu odpowiednim transformacjom.
Jak w większości przypadków różnych efektów 2D, duża złożoność obliczeniowa dla tunelu to iluzja, można go zrealizować na skróty, w sposób dający dodatkowe możliwości realizacji innych efektów np. Ceiling-Floor (efekt poruszanie się po przestrzeni ograniczonej z góry sufitem, z dołu podłogą).
Aby stworzyć tunel i jemu podobne efekty potrzebujemy "wyrysować" dwie tablice. W przypadku tunelu pierwsza tablica będzie zawierać koncentryczne okręgi, druga promienie wychodzące z centrum. Istotne jest aby kolejne okręgi, promienie rysować innym coraz większym "kolorem", np. z zakresu 0..15 dla tekstur 16x16 piksli. Te dwie tablice dadzą nam indeks (współrzędne X:Y) do tekstury tunelu. Efekt ruchu uzyskujemy zwiększając/zmniejszając indeks, +1, -1, +16, -16 dla tekstury o rozmiarze 16x16 pikseli. Aby uzyskać dodatkowy efekt ruchu gdzie ściany tunelu przemieszczają się po całej widocznej powierzchni ekranu musimy wygenerować tablice większe niż rozmiar ekranu. Potem już tylko odpowiednio ustawiać punkt początkowy X:Y odczytu naszej dużej tablicy indeksów tekstury tunelu.
Dla efektu Ceiling-Floor tablica z promieniami zostaje, zmieniamy tablicę z okręgami na tablicę z liniami poziomymi odpowiednio "wyrysowanymi" dla sufitu i podłogi. Możemy pokusić się o inne kształty, zamiast okręgów "rysować" kwadraty, albo inne wielokąty, zniekształcone okręgi, elipsy.
Kiedy mamy już nasze obie tablice, wyrysowanie tunelu jest proste, przykład w pseudo kodzie:
for y = 0 to height-1
for x = 0 to width-1
u = circles[x,y]
v = rays[x,y]
pixels[x,y] = texture[u,v]
next x
next y
Przykład programu w Delphi realizującego TUNNEL na trzy różne sposoby oraz WORMHOLE, efektem działania jest 16KB plik TUNNEL.DAT z tablicą dla ekranu 128x128, młodszy nibbl bajtu takiej tablicy to pozycja pozioma dla tekstury, starszy nibbl pozycja pionowa dla tekstury.
W przypadku tunelu można zauważyć że poszczególne metody prezentowane w załączonym pliku różnią się jakością odwzorowania głębi oraz symetrii, najlepiej wypada metoda #2. W przypadku metody #1 powstaje najmniej zakłóceń-mory pośrodku tunelu.
Efekt obracającego się prostopadłościanu, który sprowadza się do stworzenia tablicy z wyliczonymi klatkami pełnego obrotu jednej linii, następnie kolejne linie są już tylko kopiowane na ekran z takiej tablicy wg kąta obrotu. Można też sobie utrudnić, w demie Ilusia klatki obrotu linii zostały wyliczone na podstawie obrotu 3D płaszczyzny przecinającej prostopadłościan z perspektywą, na tej podstawie powstała tablica z informacją o kolejnych widocznych pikselach tekstury. Dla każdego kąta obrotu na podstawie wyliczonych tablic została wygenerowana osobna procedura realizująca tworzenie linii obrazu. Takie podejście pozwoliło na realizację twistera z teksturą plazmy liczonej w czasie rzeczywistym.
Sposobów realizacji może być wiele, jednak koniec końców całość sprowadza się do obrotu 4 punktów, perspektywę możemy sobie darować ze względu na niską rozdzielczość.
Przykład twistera w pseudo kodzie:
ang = 0.0
damp = 0.02
amp = 0.0
loop
clear_screen;
for a = 0 to height-1
x1 = round(((Sin(((a*amp)*pi/180)+ang))*40)+160)
x2 = round(((Sin(((a*amp)*pi/180)+ang+90*pi/180))*40)+160)
x3 = round(((Sin(((a*amp)*pi/180)+ang+90*2*pi/180))*40)+160)
x4 = round(((Sin(((a*amp)*pi/180)+ang+90*3*pi/180))*40)+160)
if x1<x2
color = clRed
line( point(x1,a), point(x2,a) )
endif
if x2<x3
color = clYellow
line( point(x2,a), point(x3,a) )
endif
if x3<x4
color = clGreen
line( point(x3,a), point(x4,a) )
endif
if x4<x1
color = clBlue
line( point(x4,a), point(x1,a) )
endif
next a
ang = ang+0.3
amp = amp+damp
if (amp>1) or (amp<-1)
damp = -damp
endif
goto loop
Powyższy kod udostępnia informację o długości odcinków które mamy wyrysować, daje to podstawę do teksturowania twistera.