Start | Super Packer | Atari Graphics Studio | Graph2Font | Mads | MadPascal | Atari Zines | YouTube http://madteam.atari8.info  
MEMBERS
PRODUCTIONS
S C E N E
G A M E S
T O O L S
V B X E
GRAPHICS
WORK IN PROGRESS
Bubble Shooter
HARDWARE
Snes2Joy / Pad4Aatari / TOM rev2
Sio2SD / Pajero
Sio2SD / Rocky
Stereo / Pajero
GTIA / Psychol
ANTIC + VBXE test
ARTICLES / MAGAZINES
DEMO EFFECTS
LINKS
   

WSTĘP

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.

A Compilation of Advanced Atari 8-bit Programming Techniques
HORIZONTAL SCROLL FIRE EFFECT
KEFRENSBAR PLASMA EFFECT
PLOTTER LENS EFFECT
SHADE BOB 2D BUMP MAPPING
UNLIMITED BOBS TWIRL
X-ROTATOR SIERPINSKI FRACTALS
ZOOM SCROLL WATER / RAIN EFFECT
ROTOZOOMER / ZOOMROTATOR METABALLS / BLOBS
CHESS ZOOMROTATOR TEXTURE-MAPPED TUNNEL
FILL RECTANGLE TWISTER

HORIZONTAL SCROLLING

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.


KEFRENS BARS

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

KEFRENSBAR w Delphi.


PLOTTER

Efekt jak największej liczby poruszających się piksli po ciekawej trajektorii.


SHADE BOB

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.


UNLIMITED BOBS

"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?


X-ROTATOR

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.


FIRE EFFECT

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:

x = random(width)

pixels[x,   height]   = 15
pixels[x+1, height]   = 14
pixels[x,   height+1] = 13

Dodatkowe informacje na temat efektu ognia pozwalające ulepszyć jego stronę wizualną, upodobnić zachowanie do prawdziwego ognia.


PLASMA EFFECT

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


ROTOZOOMER / ZOOMROTATOR

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.


ZOOM SCROLL

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.

Efekciarskie efekciki 2 [Soused Teat/Slight], Wiosna 1995 (Barymag #2)

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

...co dla tekstu, np. "ALA" będzie wyglądać tak:

             bajt         bajt         bajt

     ........ $00 ........ $08 ........ $10
     ...**... $01 .**..... $09 ...**... $11
     ..****.. $02 .**..... $0A ..****.. $12
     .**..**. $03 .**..... $0B .**..**. $13
     .**..**. $04 .**..... $0C .**..**. $14
     .******. $05 .**..... $0D .******. $15
     .**..**. $06 .******. $0E .**..**. $16
     ........ $07 ........ $0F ........ $17

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

...czyli tekst "ALA" będzie teraz wyglądał tak:

             bajt         bajt         bajt

     ........ $00 ........ $08 ........ $10
     .****... $01 .******. $09 .****... $11
     .*****.. $02 .******. $0A .*****.. $12
     ..*..**. $03 .*...... $0B ..*..**. $13
     ..*..**. $04 .*...... $0C ..*..**. $14
     .*****.. $05 .*...... $0D .*****.. $15
     .****... $06 .*...... $0E .****... $16
     ........ $07 ........ $0F ........ $17

Taki bufor oczywiście trzeba odpowiednio przepisać na ekran, co załatwi taka oto procedurka:

     LDA BUFOR+$00
     ASL A
     ROL ZERO_PAGE+$00
     ASL A
     ROL ZERO_PAGE+$01  ...

...i tak dalej, żeby wyszedł jeden słupek bitów na ekranie, np.

     0xxxxxxx
     1xxxxxxx
     1xxxxxxx
     1xxxxxxx
     0xxxxxxx
     1xxxxxxx
     0xxxxxxx
     0xxxxxxx

Następnie:

     LDA BUFOR+$01
     ASL A

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ę).


CHESS ZOOMROTATOR

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.

; piksle (0,0) (1,0) (0,1) (1,1)

00,00,00,00
00,00,00,FF
00,00,FF,00
00,00,FF,FF
00,FF,00,00
00,FF,00,FF
00,FF,FF,00
00,FF,FF,FF
FF,00,00,00
FF,00,00,FF
FF,00,FF,00
FF,00,FF,FF
FF,FF,00,00
FF,FF,00,FF
FF,FF,FF,00
FF,FF,FF,FF

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.

CHESS ZOOMROTATOR w Delphi.


FILL RECTANGLE

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;  

LENS EFFECT

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.


2D BUMP MAPPING

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.


TWIRL

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.


SIERPINSKI FRACTALS

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.


WATER / RAIN EFFECT

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:

loop

  drop_water(buf1)
  
  if switch

   calc_water(buf1, buf2)
   draw_water(buf2)

  else

   calc_water(buf2, buf1)
   draw_water(buf1)

  endif 

  switch = !switch	
	
goto loop

Tutaj przykład w Delphi realizacji powyższego pseudo kodu.

Dodatkowe informacje na temat genezy i sposobu działania efektu wody http://freespace.virgin.net/hugo.elias/graphics/x_water.htm


METABALLS / BLOBS

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.


TEXTURE-MAPPED TUNNEL

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.

Opisy tego efektu na innych stronach http://benryves.com/tutorials/tunnel/, Lode's Computer Graphics Tutorial


TWISTER

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.

Tutaj powyższy przykład kodu twistera w Delphi.

 

madteam.atari8.info © MadTeam, hosted: www.atari8.info