3D Fraktali
Projekt izradili: Gabrijel Bartošek & Samuel Picek
Ovdje možete preuzeti:
izvorni kod ovog projekta
-
seminar u PDF formatu
-
prezentacija u PDF formatu
1.Uvod
Jedno od zanimljivijih područja računalne grafike zauzimaju
matematičke umotvorine, tj. fraktali. Cijeli svijet oko nas,
te unutar nas zapravo je i sačinjen od nekakvoga vida fraktala, a
mogli bi slobodno reći da je i metodika našeg razmišljanja
fraktalna.
U davnoj prošlosti matematika je bila pretežito orijentirana na
skupove i funkcije nad kojima su se pretežito primjenjivale metode
klasičnog diferencijalnog računa. Skupovi funkcija koji nisu bili
dovoljno glatki ili regularni, pretežito su se ignorirali. U
posljednjim desetljećima ovakav se način razmišljanja uvelike
promijenio.
Fraktali su čudesni geometrijski oblici koji se sastoje od umanjenih
verzija samih sebe (svaki dio je umanjena kopija cjeline). Nalazimo
ih posvuda oko nas, te ih vidimo svaku dan, mada ih ne
primjemjećujemo. Možemo ih zapaziti na običnoj brokuli, cvjetači,
ali isto tako u planinskim lancima, te deltama rijeka. No međutim
osim onih u prirodi, mi ćemo se u seminarskom radu fokusirati na one
fraktale koje stvara čovjek, kako bi se umjetnički i vizualno
posebno izrazio.
Prvenstveno fraktalna umjetnost je ona umjetnost koja nastaje uz
pomoć računala i matematike. Fraktalna geometrija nam daje
generalni okvir gdje se proučavaju iregularni skupovi. Premda dosta
često u današnjici čujemo riječ fraktali, velika većina neće
razumjeti što oni točno predstavljaju i što znače. Bilo je mnogo
pokušaja da se fraktali definiraju u čisto matematičkom smislu, no
te definicije često su se ispostavile neispravnima u općem smislu
značenja. Međutim, fraktalna geometrija daje podosta tehnika za
upravljanje fraktalima.
2.Povijest fraktala
Kao što smo u uvodu naveli, fraktali koje stvara čovjek nastaju
pomoću računala i matematike. Računala danas pronalaze mnogobrojne
primjene povezujući grafiku i matematiku u pogledu umjetnosti, npr.
digitalno obrađene fotografije i dizajn. S prvom pojavom računala,
umjetnici su se zapitkivali na koji način bi mogli iskoristiti taj
novitet u svom području, tj. za izradu svojih radova.
Računala koja su prva našla svoju primjenu u aspektu našeg
privatnog, ali i poslovnog života, zasigurno su našla svoju
primjenu i u umjetnosti, tako da danas već koristimo izraz za to
„digitalna umjetnost“. Takva umjetnost je izazvana uz pomoć
računala, a na to možemo gledati kao sredstvo, odn. materijal i
tehniku u isto vrijeme baš kao što su slikaru kist, boje i platno.
Poput fraktalnog umjetnika, kipara ili bilo koga drugog umjetnika,
potrebna su pomoćna sredstva da bi stvorili umjetničko djelo, no
bez vlastite kreativnosti i rada to ne bi moglo proizlaziti.
Fraktali su ljudima poznati od pamtivijeka, samo što se oni na taj
način nisu prepoznavali. Prvi dokumentirani prikaz fraktala možemo
naći već 1525. god. u „Priručniku za slikanje“ Albrechta
Dürera gdje se opisuju
uzorci nastali korištenjem pentagona. U 17. st. Leibnitz je
definirao ponavljanje samosličnosti, no međutim uzeo je u obzir da
samo linija može biti sebi slična. Od tada, pa do 19.st. nisu se
javljale nikakve slične definicije. Tek 1872. god. Karl Weierstrass
daje primjer funkcije kojom je definirao samosličnost. Takva
definicija je bila suviše apstraktna, pa je Helge von Koch 1904.
god. dao geometrijsku interpretaciju slične funkcije, koja je danas
poznata kao Kochova pahulja, a to će nam ujedno biti i
projektni zadatak iz ovog kolegija. Poslije toga 1915. god. Waclaw
Sierpinski kreirao je svoj uzorak fraktala pomoću trokuta. U tome
razdoblju pronalazimo dosta fraktalnih prikaza poput onih Pierrea
Fatoua, Henria Poincaréa, Georgea Cantora, Gastona Julie i Felixa
Kleina. Svi oni su djelovali krajem 19. st. i početkom 20. st. i
proučavali su te fascinantne tvorevine dobivane iteracijama, no
međutim bez računala, pa nisu mogli uočiti sav njihov značaj.
Prvi puta se termin „fraktal“ pojavljuje 1975. god. kojeg
je upotrijebio matematičar Benoit Mandelbrot, a fraktalna
umjetnost se počinje razvijati tek sredinom 1980-ih godina. Prva
fraktalna slika je nastala na naslovnoj stranici časopisa
„Scientific American“ 1985. god. i prikazivala je Mandelbrotov
skup.
Slika
1 - Scientific American - Mandelbrotov skup [Shane Bow The Chaos of
Mandelbrot, dostupno
na: http://shanebow.com/projects/mandelbrot/,
učitano: 17.01.2015.]
Takva slika je nastala s intencijom da ponudi vizualne i umjetničke
kvalitete. Nedugo nakon toga izdana je bogato ilustrirana knjiga „The
Beauty of Fractals“ čiji su autori Heinz-Otto Peitgen i
Peter Richter. U toj knjizi velik broj ilustracija se temelji
na drugom najpopularnijem fraktalu, a riječ je Juliaovu skupu.
[Fraktali i umjetnost, Vesna Mišljenović, Zagreb (2011./2012. br.
80), str. 223.]
Kako smo konstatirali da je fraktalna umjetnost vrlo mlada, ona nije
još pronašla svoje pravo mjesto u „mainstream“ umjetničkim
krugovima, a isto tako nije zastupljena na tržištu umjetnosti i
galerijama. Međutim možemo reći da fraktalna umjetnost ima široku
primjenu u računalnoj animaciji, konkretno u simulaciji rasta
biljaka ili primjerice generiranju krajolika.
3.Definicija fraktala
Fraktali su slike nastale uzastopnim ponavljanjem neke
matematičke funkcije, odnosno ponavljanjem određenog geometrijskog
postupka. Mogli bismo reći da su to zapravo objekti koji daju
jednaku razinu detalja neovisno o razlučivosti koju koristimo.
Kako smo na laboratorijskim vježbama mogli vidjeti kod profesora
Horvata, fraktale je moguće uvećavati beskonačno mnogo puta, a pri
tome se prilikom svakog novog uvećanja mogu opaziti detalji koji
prije povećanja nisu bili vidljivi, a da količina novih, možemo
reći i sitnijih detalja uvijek bude otprilike jednaka.
U uvodu je važno za napomenuti da fraktali imaju svoja osnovna
svojstva, a to su: [Uvod u matematičke metode u inženjerstvu,
Fraktali, dostupno na:
http://matematika.fkit.hr/novo/izborni/referati/dobrinic_joskic_brdar_fraktali.pdf,
3. str. učitano: 17.01.2015.]
Samo-sličnost
– svojstvo objekta da sliči sam na sebe, neovisno o tome koji dio
promatramo i koliko ga puta uvećavamo.
Fraktalna
dimenzija – vrijednost koja nam daje uvid u to kojoj mjeri
pojedini fraktal ispunjava prostor u kojem se nalazi. Za razliku od
fraktalne dimenzije, euklidska dimenzija koristi se kako bi se
izrazila linija (jedna dimenzija), površina (dvije dimenzije) i
prostor (tri dimenzije), te može biti bilo koji prirodni broj ili
nula (0, 1, 2, 3, 5, 10, 100, ... ). Fraktalna dimenzija, nasuprot
tome koristi se kako bi se postigla gustoća kojom objekt ispunjava
prostor, tj. koliko se novih dijelova pojavljuje pri uvećavanju
rezolucije. Fraktalna dimenzija nije cijeli broj i u pravilu je veća
od euklidke dimenzije.
Primjer:
Fraktalna dimenzija zapadne obale Engleske je 1,3 dok je od Norveške
obale 1,52. One su veće od 1, na temelju čega možemo zaključiti
da su euklidske obale shvaćene kao krivulje. Isto tako to bi
značilo da Norveška ima razvodnjeniju obalu od Engleske. Samo bi
približno mogli na eksperimentalnoj razini govoriti o fraktalnim
dimenzijama obale.
Izraz po kojem se mjeri dimenzija je:
d=log(n)/log(s),
a pri tome je:
Primjer:
Cantorov skup C3 ima dim C3 = log2/log3 = 0.63..... < 1;
Kochova krivulja KK ima dim KK = log4/log3 = 1.2618..... >
1.
Oblikovanje
iteracijom – svojstvo da se objekt generira nekim matematičkim
ili geometrijskim postupkom, tako da se u osnovni (početni) objekt
iterativno ugrađuju svojstva generatora.
3.1.Podjela fraktala
Fraktale
dijelimo prema:
stupnju
samosličnosti
načinu
nastanka.
3.1.1.Podjela
fraktala prema stupnju samosličnosti
Kod podjele fraktala prema stupnju samosličnosti možemo
razlikovati:
potpuno
samoslične fraktale
kvazi
samoslične fraktale
statičke
samoslične fraktale
Potpuno samoslični fraktali sadrže kopije sebe koje su
slične cijelom fraktalu. Ovdje je vrlo bitno poznavati bazu i
motiv. Baza je bilo koji oblik koji je sastavljen od linijskih
segmenata, dok je motiv neki drugi oblik koji se također sastoji od
linija. Ako se svaka linija baze nadomjesti oblikom motiva i taj
proces nastavi u beskonačnost, dobivamo fraktal.
Slika
2 - Baze i motivi [Uvod u fraktale, M.Paušić, dostupno na:
http://www.fer.unizg.hr/_download/repository/Uvod%20U%20Fraktale%20by%20Mladen%20Pausic.pdf,
učitano: 17.01.2015.]
Potpuno samoslični fraktali su svi geometrijski fraktali, a
primjere za to možemo vidjeti na slikama ispod:
slika 2 –
Kochova krivulja
slika 4 -
Sierpinskijev trokut
slika
5 – Hilbertova krivulja
slika
6 – Cantorov skup
Slika
3 - Kochova krivulja [Hrvatski matematički elektronski časopis
math.e, Galerija fraktala, V. Antočić, A. Galinović, dostupno na:
http://e.math.hr/galerija/galerija_print.html,
učitano: 17.01.2015.]
Kochovu krivulju uveo je švedski matematičar Helge von
Koch. Fraktalna dimenzija Kochove krivulje je log4 / log3 =
1,2619.
Slika
4 - Nastanak Kochove pahuljice (2D) [Hrvatski matematički
elektronski časopis math.e, Galerija fraktala, V. Antočić, A.
Galinović, dostupno na:
http://e.math.hr/galerija/galerija_print.html,
učitano: 17.01.2015.]
Napomena:
razlika između Kochove krivulje i Kochove pahuljice je u tome što
krivulja počinje dužinom, a pahuljica jednakostraničnim trokutom.
Kochovu pahuljicu
ćemo kasnije prikazati u praktičnom primjeru: OpenGL
ES 2.0 & 3.0 na Androidu - 3D fraktali (primjer Kochove
pahuljice).
Slika
5 - Sierpinskijev trokut - kontrukcija otkidanjem trokuta [Hrvatski
matematički elektronski časopis math.e, Galerija fraktala, V.
Antočić, A. Galinović, dostupno na:
http://e.math.hr/galerija/galerija_print.html,
učitano: 17.01.2015.]
Sierpinskijev trokut uveo je poljski matematičar Waclaw
Sierpinski. Fraktalna dimenzija Sierpinskijevog trokuta iznosi
log3 / log4 = 1,584962.
Poslije većeg broja iteracija možemo opaziti da duljina Kochove
krivulje teži u beskonačnost kada i broj iteracija teži u
beskonačnost. Ali cijela ta duljina je i dalje na istoj površini,
samo možemo reći da je malo više zgužvana. Stupanj te zgužvanosti
možemo vidjeti iz fraktalne dimenzije. To nam daje uvid u to u kojoj
mjeri nekakav fraktal zauzima ravninu ili općenito n-dimenzionalni
prostor u kojem se nalazi. Tako primjerice Kochova krivulja ima
fraktalnu dimenziju 1,2619, dok Sierpinskijev trokut približno
1,584962. Iz vrijednosti, kao i sa prethodnih slika, možemo uočiti
da je trokut malo gušći od Kochove krivulje, tj. kažemo da
ispunjava veći dio ravnine.
Slika
6 - Hilbertova krivulja [Wikipedia, Hilbertova krivulja, dostupno na:
http://hr.wikipedia.org/wiki/Hilbertova_krivulja,
učitano: 17.01.2015.]
Hilbertova krivulja je beskonačno gusta krivulja koju je
opisao njemački matematičar David Hilbert 1891. god. Ona
nastaje nakon beskonačno mnogo iteracija.
Slika
7 - Contorov skup [Hrvatski matematički elektronski časopis math.e,
Galerija fraktala, V. Antočić, A. Galinović, dostupno na:
http://e.math.hr/galerija/galerija_print.html,
učitano: 17.01.2015.]
Za fraktalne skupove moguće je definirati njihove fraktalne
dimenzije na nekoliko načina. Pokazuje se da je fraktalna dimenzija
Cantorova skupa strogo manja od 1 i to točno jednaka log2 /
log3 = 0,6309.
Kvazi samoslični fraktali su oni fraktali koji sadrže male kopije
sebe koje nisu slične cijelom fraktalu, već se pojavljuju u
iskrivljenom obliku, a primjere za to možemo vidjeti na donjim
slikama:
Slika
8 - Juliaov skup za c = -1 [Hrvatski matematički elektronski časopis
math.e, Galerija fraktala, V. Antočić, A. Galinović, dostupno na:
http://e.math.hr/galerija/galerija_print.html,
učitano: 17.01.2015.]
Slika
9 - Mandelbrotov skup [Hrvatski matematički elektronski časopis
math.e, Galerija fraktala, V. Antočić, A. Galinović, dostupno na:
http://e.math.hr/galerija/galerija_print.html,
učitano: 17.01.2015.]
Statistički
samoslični fraktali su fraktali koji ne sadrže kopije samoga
sebe, no međutim neke njegove osobine kao što je fraktalna
dimenzija ostaju iste pri različitim mjerilima. Primjer za to možemo
vidjeti na slici 10.
Slika
10 – Dvodimenzionalni Perlinov šum – svjetlije nijanse
predstavljaju više vrijednosti funkcije [Fractal Noise, Neil
Blevins, dostupno na:
http://www.neilblevins.com/cg_education/fractal_noise/fractal_noise.html,
učitano: 17.01.2015.]
3.1.2.Podjela
fraktala prema načinu nastanka
Kod
podjele fraktala prema načinu nastanka razlikujemo:
iterativne
fraktale
rekurzivne
fraktale
slučajne
fraktale
Iterativni fraktali nastaju kopiranjem, te rotiranjem i/ili
translatiranjem kopije, te mogućim zamjenjivanjem nekog elementa
kopijom (npr. Kochova krivulja).
Rekurzivni fraktali su određeni rekurzivnom matematičkom
formulom koja određuje pripada li određena točka prostora (npr.
kompleksna ravnina) skupu ili ne.
Slučajni fraktali posjeduju najmanji stupanj samosličnosti i
možemo ih zapaziti najčešće u prirodi kao što su munje, oblaci,
obale ili drveće.
Slika
11 - Istra i mnogi drugi poluotoci su fraktali [Neverinov blog,
dostupno na:
http://blog.dnevnik.hr/blogodneverina/2010/02/1627237517/fasciniranost-fraktalima.html,
učitano: 17.01.2015.]
Slika
12 - Drvo iz prirode – fraktal [Silvergreen, Deviant art, dostupno
na: http://silvergreen.deviantart.com/art/Fractal-Tree-1646228,
učitano: 17.01.2015.]
3.2.Primjena fraktala
Najjednostavniji primjer gdje se susrećemo s konkretnom primjenom
fraktala u računalnoj grafici je crtanje terena, a posebice se to
odnosi na planine. Njih možemo crtati tako da se horizontalno
položenom trokutu svaki vrh snizi ili povisi za neku slučajno
odabranu vrijednost. Takvom trokutu se zatim spoje polovišta
stranica, te se na taj način dobivaju četiri nova trokuta.
Srednjemu od njih snizimo ili povisimo vrhove kao i prvotnom trokutu,
no međutim tada koristimo dvostruko manje vrijednosti. Isti postupak
se nadalje ponavlja za sva četiri trokuta ispočetka. [Uvod u
matematičke metode u inženjerstvu, Fraktali, dostupno na:
http://matematika.fkit.hr/novo/izborni/referati/dobrinic_joskic_brdar_fraktali.pdf,
5.str. učitano: 17.01.2015.]
Na sljedećoj slici dolje možemo vidjeti funkciju prikazanu u
trodimenzionalnom prostoru. Ovdje su više vrijednosti jednostavno
prikazane na višem položaju. Tako se dobiva model koji uvelike
nalikuje planini.
Slika
13 - Stvorena planina uz pomoć Perlinovog šuma (WebGL Demo –
Fractal Terrain Generator, dostupno na:
http://www.webgl.com/2012/05/webgl-demo-fractal-terrain-generator/
, učitano: 17.01.2015.]
Ako bi uzeli u obzir sustave iteriranih funkcija u tri dimenzije,
mogli bismo iscrtavati razne objekte kao što su drveće, cvijeće,
grmlje, korijenje i tome slično. Ako to isto napravimo u
trodimenzionalnom sustavu i na kraj svake grančice dodamo pokoji
list, rezultat bi bio nevjerovatno sličan stvarnim pojavama u
prirodi, baš kao što je prikazano na slici 14. Od manje bitnijih
primjena tu je predviđanje stohastičkih procesa kao što su recimo
potresi, slaganje snopova optičkih vlakana, oponašanje rada
neuronskih mreža za razvoj umjetne inteligencije i dr. Za uređaje
kao što su mobiteli, proizvode se antene u obliku fraktala koje zbog
toga koriste široki spektar frekvencija, a uz to ne zauzimaju puno
prostora.
Slika
14 - Trodimenzionalni fraktal – drvo [Pythagoras Tree, dostupno na:
http://www.phidelity.com/blog/phidelity/blog/fractal/pythagoras-tree/,
učitano: 17.01.2015.]
3.3.Fraktalna
dimenzija
Fraktalna dimenzija je vrijednost koja nam daje uvid u to u
kojoj mjeri nekakav fraktal ispunjava prostor u kojem se nalazi.
Pronašli smo podosta definicija fraktalnih dimenzija od kojih se
niti jedna nije pokazala univerzalnom. Najbolja je dimenzija
samosličnosti, no međutim nju upotrebljavamo samo kod jako
jednostavnih geometrijskih fraktala. Za teoriju nam je najbitnija ona
Hausdorffova dimenzija, a u konkretnim slučajevima, odn. u praksi
najviše se koristi Minkowski-Bouligandova dimenzija. [Uvod u
matematičke metode u inženjerstvu, Fraktali, dostupno na:
http://matematika.fkit.hr/novo/izborni/referati/dobrinic_joskic_brdar_fraktali.pdf,
6. str. učitano: 17.01.2015.]
Dimenzija samosličnosti koristi razne promjene mjera (primjerice
dužine, površine, obujmi, ...) u odnosu na mijenjanje broja
iteracija kod potpuno samosličnih fraktala. Kod Kochove krivulje
svaka naredna iteracija daje četiri puta više segmenata na
tri puta manje dužine. Ako bismo broj segmenata označili sa N, a
dužinu segmenta s L, ukupno bi dobili dužinu krivulje NL. Možemo
zatim reći da za Kochovu krivulju vrijedi:
ako za mjeru dužine uvrstimo spomenuti „d-dimenzionalni metar“.
Daljnjim preuređivanjem jednadžbe dobivamo
ili češće
.
Tako bi mjerna jedinica za Kochovu krivulju bila otprilike m1.2619
.
Općenito možemo reći da se dimenzija potpuno samosličnog fraktala
računa po formuli
.
Minkowski-Bouligandova dimenzija – ako bismo uzeli fraktal
koji leži u ravnini i prekrili ga proizvoljnim brojem N sukladnih
kvadrata duljine stranice A, smanjivanjem te duljine kvadrata (a
samim time i povećanjem njihovog broja) promijenio bi se i broj
kvadrata koji sadrže fraktal. Upravo ova metoda koristi odnos broja
tih kvadrata i duljine stranica. Na taj način možemo odrediti
dimenziju jednostavnih objekata, čija nam je dimenzija već poznata
(razne definicije dimenzije ne bi trebale davati različite rezultate
kod vrlo jednostavnih objekata) kao što su primjerice kvadrati.
Dobit ćemo opću formulu za kvadrat (pri tome znamo da se radi o
dvodimenzionalnom kvadratu):
.
Ako bismo učinili istu stvar i sa dužinom (jednodimenzionalnom), te
kockom (trodimenzionalnom), dobili bi opće formule:
i
. Iz ovih primjera vidimo opću formulu za objekte bilo koje
dimenzije
,
tj.
.
Valja istaknuti da ova metoda ne daje uvijek potuno točne rezultate,
te da joj se rezultati pomiču stvarnima s povećanjem broja dužina,
kvadrata i kocaka. Najviše se koristi kod određivanja fraktalne
dimenzije nepravilnih objekata. [Uvod u matematičke metode u
inženjerstvu, Fraktali, dostupno na:
http://matematika.fkit.hr/novo/izborni/referati/dobrinic_joskic_brdar_fraktali.pdf,
7. str. učitano: 17.01.2015.]
Topološka dimenzija je ono što nazivamo još i intuitivnom
dimenzijom, tj. broj smjerova u kojima bismo mogli ići da smo u
određenom objektu, odnosno broj stupnjeva slobode. Na takav način
je svaka linija (ravna ili zakrivljena) jednodimenzionalna, jer
postoji samo jedan stupanj slobode, a to je dužina (lijevo-desno).
Svaka je ploha (ravna ili savinuta) dvodimenzionalna, jer postoje dva
stupnja slobode – dužina i širina (lijevo-desno i gore-dolje).
Topološka dimenzija uvijek je pozitivan cijeli broj ili nula. Ako bi
to sagledavali stručnije, topološka dimenzija se može definirati i
kao najmanja moguća vrijednost n, tako da se bilo koji otvoreni
pokrivač (pokrivač čiji su svi elementi otvoreni skupovi) može
podesiti tako da svaka točka bude najviše n+1 element. Primjerice,
ako želimo odrediti topološku dimenziju kružnice, konstruirat ćemo
joj pokrivač od otvorenih kružnih lukova. Zatim ćemo podesiti taj
pokrivač tako da smanjimo preklapanje njegovih elemenata na najmanju
moguću mjeru, ali da cijela kružnica i dalje bude potpuno
pokrivena. Pošto smo uzimali otvorene lukove, preklapanje je
neizbježno, a može se napraviti tako da svaka točka kružnice bude
dio samo jednog ili dva elementa pokrivača. Najveća je vrijednost n
= 2, tako da topološka dmenzija iznosi n – 1, te zaključujemo na
temelju toga da je kružnica jednodimenzionalna. Ovo matematičko
objašnjenje se na prvi pogled možda čini nepotrebnim, ali je vrlo
bitno u nekim elementima više matematike, kao primjerice u našoj
seminarkoj temi o fraktalima. [Uvod u matematičke metode u
inženjerstvu, Fraktali, dostupno na:
http://matematika.fkit.hr/novo/izborni/referati/dobrinic_joskic_brdar_fraktali.pdf,
8. str. učitano:17.01.2015.]
4.Prikaz grafike s OpenGL ES
Android okvir pruža mnogo standardnih alata za stvaranje
funkcionalnog grafičkog sučelja. Ako primjerice želimo postići
veću kontrolu nad onime što želimo prikazati na zaslonu kao što
je modeliranje u trodimenzionalnim slikama trebali bismo koristiti
više vrsta različitih funkcija. OpenGL ES API od strane Android
okvira nudi čitav niz alata za prikaz vrhunske animirane grafike
koje su ograničene samo našom maštom i također možemo imati
koristi od ubrzanja grafičke obrade jedinica (GPU) koje se nalaze na
mnogim Android uređajima.
4.1.Izgradnja OpenGL
ES okruženja
Kako bi mogli iscrtavati grafičke objekte uz pomoć OpenGL ES na
našem Android-u moramo prvo kreirati određene poglede na spremnike
za njih. Jedan od najboljih načina za to je implementacija sučelja
GLSurfaceView i GLSurfaceView.Renderer. GLSurfaceView je spremnik za
grafičko crtanje s OpenGL-om i GLSurfaceView-om. Renderer kontrolira
ono što je nacrtano u tom pogledu. GLSurfaceView je samo jedan od
načina da se u OpenGL ES uključi grafika u takav program.
Primjerice za prikaz punog zaslona to je definitivno najbolji izbor.
4.1.1.Deklariranje
OpenGL koristeći Manifest
Kako bi mogli koristiti OpenGL ES 2.0 API u našoj aplikaciji moramo
ga deklarirati u našem manifestu sa ovim XML isječkom:
<uses-feature
android:glEsVersion="0x00020000" android:required="true"
/>
Ako naša aplikacija koristi kompresiju
teksture, također moramo deklarirati u manifestu koje formate
kompresije naša aplikacija podržava. To vrijedi samo za
kompatibilne uređaje. [Developers, Displaying
Graphics with OpenGL ES, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.]
<supports-gl-texture
android:name="GL_OES_compressed_ETC1_RGB8_texture"
/>
<supports-gl-texture
android:name="GL_OES_compressed_paletted_texture" />
4.1.2.Kreiranje
aktivnosti za OpenGL ES grafiku
Android aplikacije koje koriste OpenGL ES imaju aktivnosti baš kao i
bilo koje druge aplikacije koja ima korisničko sučelje. Glavna
razlika od drugih aplikacija je ono što se stavi u raspored za naše
aktivnosti. Dok se u mnogim aplikacijama koriste TextView, gumb i
ListView, u aplikaciji koji koristi OpenGL ES, također možete
dodati i GLSurfaceView.
Sljedeći kod pokazuje minimalnu provedbu aktivnosti koje koristi
GLSurfaceView kao svoj primarni pogled [Developers,
Displaying Graphics with OpenGL ES, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.]:
public class OpenGLES20Activity extends
Activity {
private GLSurfaceView mGLView;
@Override
public void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
// Create a GLSurfaceView
instance and set it
// as the ContentView for this
Activity.
mGLView = new
MyGLSurfaceView(this);
setContentView(mGLView);
}
}
4.1.3.Gradnja
GLSSurfaceView objekata
GLSurfaceView je specijalizirani pogled u kojem možemo crtati
grafiku OpenGL ES. To samo za sebe ne znači baš mnogo. Stvarni
crtež objekata se kontrolira u GLSurfaceView.Renderer-u koje smo
postavili na ovom prikazu. Naime, kod za ovaj objekt je tako malen,
pa možemo biti u iskušenju da ga preskočimo ili stvorimo
nemodificiranu GLSurfaceView instancu. Morali bismo proširiti ovu
klasu kako bi se omogućilo snimanje događaja. Veoma važan
programski kod za GLSurfaceView je minimalan, tako da za brzu
provedbu, uobičajeno je da samo stvorimo unutarnje klase u
aktivnosti koje koristimo [Developers,
Displaying Graphics with OpenGL ES, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.]:
class MyGLSurfaceView extends
GLSurfaceView {
public MyGLSurfaceView(Context
context){
super(context);
// Set the Renderer for drawing
on the GLSurfaceView
setRenderer(new MyRenderer());
}
}
Kada koristimo OpenGL ES 2.0, moramo dodati još jedan poziv na svoj
GLSurfaceView konstruktor, te naznačimo da želimo koristiti 2.0 API
[Developers, Displaying Graphics with OpenGL
ES, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.]:
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
Jedan drugi izborni dodatak našem GLSurfaceView-u je postavljanje
načina za pogled kada je došlo do promjene crtanja podataka pomoću
GLSurfaceView.RENDERMODE_WHEN_DIRTY postavke [Developers,
Displaying Graphics with OpenGL ES, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.]:
// Render the view only when there is a
change in the drawing data
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
Ova postavka sprječava GLSurfaceView okvir od toga da ponovno
iscrtava dok ne pozovemo funkciju requestRender(), što je
učinkovitije za ovaj uzorak.
4.1.4.Gradnja
Renderer klase (klasa prikazivanja)
Implementacija
GLSurfaceView.Renderer klase unutar aplikacije koja koristi OpenGL ES
je dio gdje stvari počinju biti zanimljive i interesantne. Ova klasa
kontrolira ono što je iscrtano u GLSurfaceView s kojim je i
povezana. Postoje tri metode u renderer klasi koje se pozivaju od
strane Android sustava kako bi shvatili što i kako se iscrtava na
GLSurfaceView-u:
onSurfaceCreated()
– poziva se samo jednom kako bi se postavili pogledi OpenGL ES
okruženja;
onDrawFrame() –
pozivanje prilikom svakog prikaza iscrtavanja;
onSurfaceChanged()
– poziva se prilikom promjene geometrijskog prikaza, primjerice
izmijene orijentacije ekrana uređaja.
Primjer koda ispod je osnovna provedba OenGL ES prikazivača koji ne
radi ništa bitno, samo postavlja sivu pozadinu u GLSurfaceView-u
[Developers, Displaying Graphics with OpenGL
ES, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.]:
public class MyGLRenderer implements
GLSurfaceView.Renderer {
public void onSurfaceCreated(GL10
unused, EGLConfig config) {
// Set the background frame
color
GLES20.glClearColor(0.0f, 0.0f,
0.0f, 1.0f);
}
public void onDrawFrame(GL10 unused)
{
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}
public void onSurfaceChanged(GL10
unused, int width, int height) {
GLES20.glViewport(0, 0, width,
height);
}
}
Kod koji smo naveli iznad je u biti jednostavna mala Android
aplikacija koja prikazuje sivi zaslon pomoću OpenGL-a. Ova klasa je
temelj koji je potreban za početak iscrtavanja grafičkih elemenata
s OpenGL-om. Sada kada smo upoznati s OpenGL ES API-jem, možemo
postaviti OpenGL ES okruženje u aplikaciji i početi crtati grafiku.
4.2.Definiranje
oblika
Biti u stanju definirati oblike koji se mogu izvesti u kontekstu
pogleda OpenGL ES-a je prvi korak u stvaranju visokozahtjevne
grafike. Crtanje s OpenGL ES-om može biti malo teže bez poznavanja
nekoliko osnovnih stvari o tome kako OpenGL ES očekuje definiranje
grafičkih objekata. Ovdje ćemo objasniti kako je OpenGL ES
koordinatni sustav relativan u odnosu na Android-ov zaslon uređaja,
osnove definiranja oblika, oblik lica, te definiranje trokuta i
kvadrata.
4.2.1.Primjer
definiranja trokuta
OpenGL ES omogućuje definiranje crtanja objekata pomoću koordinata
u trodimenzionalnom prostoru. Prije nego što nacrtamo trokut, moramo
definirati svoje koordinate. U OpenGL, tipičan način da to učinimo
je da si definiramo najvišu točku ili tjeme i brojeve s pomičnim
zarezom za koordinate. Za maksimalnu učinkovitost, pišemo ove
koordinate u ByteBuffer, koji je uspješno prošao u OpenGL ES
grafički cjevovod za obradu.
[Developers
, Defining Shapes, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.]
public class Triangle {
private FloatBuffer vertexBuffer;
// number of coordinates per vertex
in this array
static final int COORDS_PER_VERTEX =
3;
static float triangleCoords[] = {
// in counterclockwise order:
0.0f, 0.622008459f, 0.0f,
// top
-0.5f, -0.311004243f, 0.0f,
// bottom left
0.5f, -0.311004243f, 0.0f
// bottom right
};
// Set color with red, green, blue
and alpha (opacity) values
float color[] = { 0.63671875f,
0.76953125f, 0.22265625f, 1.0f };
public Triangle() {
// initialize vertex byte buffer
for shape coordinates
ByteBuffer bb =
ByteBuffer.allocateDirect(
// (number of coordinate
values * 4 bytes per float)
triangleCoords.length *
4);
// use the device hardware's
native byte order
bb.order(ByteOrder.nativeOrder());
// create a floating point
buffer from the ByteBuffer
vertexBuffer =
bb.asFloatBuffer();
// add the coordinates to the
FloatBuffer
vertexBuffer.put(triangleCoords);
// set the buffer to read the
first coordinate
vertexBuffer.position(0);
}
}
Po početnim vrijednostima, OpenGL ES pretpostavlja koordinatni
sustav [0,0,0] (X, Y, Z) i
specificira središte GLSurfaceView okvir. [1,1,0] je gornji desni
kut okvira i [- 1, -1,0] je u donjem lijevom kutu okvira.
4.2.2.Primjer
definiranja kvadrata
Definiranje trokuta je prilično lako u OpenGL, ali što ako želimo
da nešto malo složenije? U ovom primjeru konkretno mislimo na
kvadrat. Postoji nekoliko načina za iscrtavanje kvadrata, ali
tipičan način izrade takvog oblika u OpenGL ES-u je koristiti dva
trokuta nacrtana zajedno kao što je prikazano na slici ispod.
Slika
15 - Crtanje kvadrata uz pomć 2 trokuta [Defining Shapes, dostupno
na:
http://developer.android.com/training/graphics/opengl/shapes.html,
učitano: 25.01.2015.]
Opet bi trebali definirati točke u suprotnom smjeru od kazaljke na
satu kako bi oba trokuta predstavljali ovaj oblik, te postaviti
vrijednosti u ByteBuffer. Kako bi se izbjeglo da dvije koordinate
dijele svaki trokut dva puta, koristimo se popisom za crtanje koji
daje upute OpenGL ES-u na način kako da grafički cjevovod izvuće
te vrhove. Pogledajmo kod za kvadrat [Developers,
Defining Shapes, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.]:
public class Square {
private FloatBuffer vertexBuffer;
private ShortBuffer drawListBuffer;
// number of coordinates per vertex
in this array
static final int COORDS_PER_VERTEX =
3;
static float squareCoords[] = {
-0.5f, 0.5f, 0.0f, // top
left
-0.5f, -0.5f, 0.0f, //
bottom left
0.5f, -0.5f, 0.0f, //
bottom right
0.5f, 0.5f, 0.0f }; // top
right
private short drawOrder[] = { 0, 1,
2, 0, 2, 3 }; // order to draw vertices
public Square() {
// initialize vertex byte buffer
for shape coordinates
ByteBuffer bb =
ByteBuffer.allocateDirect(
// (# of coordinate values * 4
bytes per float)
squareCoords.length *
4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer =
bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
// initialize byte buffer for
the draw list
ByteBuffer dlb =
ByteBuffer.allocateDirect(
// (# of coordinate values * 2
bytes per short)
drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer =
dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
}
}
Ovaj primjer daje nam ono što je potrebno za stvaranje složenijih
oblika s OpenGL-om. Općenito, koristimo zbirke trokuta za crtanje
objekata.
4.3.Crtanje oblika
Nakon što smo prikazali oblike koji se mogu izvesti s OpenGL,
poželjet ćemo ih i iscrtati. Crtanje oblika s OpenGL ES 2.0 traje
malo više koda, jer API omogućuje veliku kontrolu nad slikama koji
pruža cjevovod.
4.3.1.Inicijalizacija
oblika
Prije nego bilo što nacrtamo, moramo inicijalizirati i učitati
oblike koje namjeravamo nacrtati. Osim ako strukturu (izvornih
koordinata) oblika koje koristimo u svom programu promjenimo tijekom
izvršenja, trebali bismo ih inicijalizirati u onSurfaceCreated()
metodi našeg renderer-a (prikazivača) za pamćenje i učinkovitost
obrade. [Developers, Drawing Shapes, dostupno
na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.]:
public void onSurfaceCreated(GL10
unused, EGLConfig config) {
...
// initialize a triangle
mTriangle = new Triangle();
// initialize a square
mSquare = new Square();
}
4.3.2.Crtanje oblika
Crtanjem definiramo oblik pomoću OpenGL ES-a 2.0 i to zahtijeva
značajnu količinu koda, jer moramo pružiti puno detalja u grafici
pruzujući sve kroz cjevovod. Moramo definirati sljedeće:
Vertex
Shader - OpenGL ES kod za grafiku za prikaz vrhova oblika;
Fragment
Shader - OpenGL ES kod za prikazivanje oblika s bojama ili
teksturama.
Program
- OpenGL ES objekt koji sadrži sjenčanje koje ćemo koristiti za
izradu jednog ili više oblika.
Shader je računalni program koji se koristi za napraviti sjenčanje.
Trebamo barem jedan vrh osjenčati kako bi nacrtali oblik i jedan
fragment osjenčati bojom da bi prikazali oblik. To sjenčanje mora
biti iskompajlirano, a zatim je dodano u OpenGL ES program, koji se
potom koristi za iscrtavanje oblika. Ovdje je primjer kako definirati
osnovno sjenčanje koje možemo koristiti za crtanje oblika
[Developers, Drawing Shapes, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.].
private final String vertexShaderCode =
"attribute vec4 vPosition;"
+
"void main() {" +
" gl_Position = vPosition;"
+
"}";
private final String fragmentShaderCode
=
"precision mediump float;"
+
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;"
+
"}";
Shaderi ili zasjenjivači sadrže OpenGL sjenčanje jezika (GLSL) kod
kojeg se mora iskompajlirati prije upotrebe u OpenGL ES okruženju.
Kako bi mogli nacrtati svoje oblike, moramo prvo sastaviti Shader
šifru, dodati ga u OpenGL ES programski objekt, a zatim povezati
program. To možemo napraviti u našem nacrtanom objektu
konstruktora, pa je to dovoljno učiniti samo jednom.
Kompajliranje OpenGL ES shadera i povezivanja programa je skuplji u
odnosu na CPU ciklus i vrijeme obrade, tako da treba izbjegavati to
više puta. Ako ne znamo sadržaj naših shader-a ili zasjenjivača
tijekom instalacije, trebali bismo napraviti svoj kod, dovoljno ga
je samo jednom kreirati i spremiti za kasniju uporabu [Developers,
Drawing Shapes, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.].
public class Triangle() {
...
int vertexShader =
loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader =
loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
mProgram = GLES20.glCreateProgram();
// create empty OpenGL ES Program
GLES20.glAttachShader(mProgram,
vertexShader); // add the vertex shader to program
GLES20.glAttachShader(mProgram,
fragmentShader); // add the fragment shader to program
GLES20.glLinkProgram(mProgram);
// creates OpenGL ES program executables
}
4.4.Primjena
projekcije i pogled kamere
U OpenGL ES okruženju, projekcije i pogled kamere omogućuju
prikazivanje nacrtanih objekata na način da više fizički sliče
kao oni iz stvarnosti. Ova simulacija fizičkog pogleda je načinjena
s matematičkim transformacijama nacrtanih objekata uz pomoć
koordinata:
Projekcija
- ova transformacija postavlja koordinate nacrtanih objekata na
temelju širine i visine GLSurfaceView gdje se prikazuju.
Pogled
kamere - ova transformacija podešava koordinate nacrtanih objekata
na temelju virtualnog položaja kamere.
4.4.1. Definiranje
projekcije
Podaci za transformaciju projekcijom se izračunavaju
onSurfaceChanged() metodom naše GLSurfaceView.Renderer klase.
Sljedećim kodom uzimamo visinu i širinu GLSurfaceView i koristimo
ga za popunjavanje matrice projekcije transformacije pomoću
Matrix.frustumM () metode [Developers, Applying
Projection and Camera Views, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.]:
@Override
public void onSurfaceChanged(GL10
unused, int width, int height) {
GLES20.glViewport(0, 0, width,
height);
float ratio = (float) width /
height;
// this projection matrix is applied
to object coordinates
// in the onDrawFrame() method
Matrix.frustumM(mProjectionMatrix,
0, -ratio, ratio, -1, 1, 3, 7);
Ovaj kod popunjava matricu projekcija, mProjectionMatrix koje možemo
kombinirati s transformacijom kamera u onDrawFrame() metodi.
4.4.2. Definiranje
pogleda kamere
Kompletan proces transformacije nacrtanih objekata završava
dodavanjem transformacija u pogled kamere kao dio procesa crtanja. U
sljedećem primjeru koda, transformacija pogleda kamere se izračunava
pomoću Matrix.setLookAtM() metode, a zatim u kombinaciji s prethodno
izračunatom projekcijom matrice [Developers,
Applying Projection and Camera Views, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.].
public void draw(float[] mvpMatrix) { //
pass in the calculated transformation matrix
...
// get handle to shape's
transformation matrix
mMVPMatrixHandle =
GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
// Pass the projection and view
transformation to the shader
GLES20.glUniformMatrix4fv(mMVPMatrixHandle,
1, false, mvpMatrix, 0);
// Draw the triangle
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,
0, vertexCount);
...
}
4.5. Dodavanje
pokreta
Crtanje objekata na zaslonu je temeljna značajka OpenGL, ali možemo
to učiniti s drugim Android grafičkim okvirima klasa, uključujući
Canvas i Drawable objekte. OpenGL ES nudi dodatne mogućnosti za
kretanje i pretvaranje nacrtanih objekata u tri dimenzije ili druge
jedinstvene načine za kreiranje korisnikovih iskustava.
4.5.1.Rotiranje
oblika
Rotirajunje objekata s OpenGL ES 2.0 je relativno jednostavno. Možemo
jednostavno stvoriti drugu matricu transformacije (matricu rotacije),
a zatim je kombinirati sa svojim projekcijama i pogledom kamere
matricom transformacija [Developers, Adding
Motion,dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.]:
private float[] mRotationMatrix = new
float[16];
public void onDrawFrame(GL10 gl) {
...
float[] scratch = new float[16];
// Create a rotation transformation
for the triangle
long time =
SystemClock.uptimeMillis() % 4000L;
float angle = 0.090f * ((int) time);
Matrix.setRotateM(mRotationMatrix,
0, angle, 0, 0, -1.0f);
// Combine the rotation matrix with
the projection and camera view
// Note that the mMVPMatrix factor
*must be first* in order
// for the matrix multiplication
product to be correct.
Matrix.multiplyMM(scratch, 0,
mMVPMatrix, 0, mRotationMatrix, 0);
// Draw triangle
mTriangle.draw(scratch);
}
4.5.2. Omogućavanje
kontinuiranog prikazivanja (renderiranja)
Ako se slučajno naš oblik ne okreće, moramo komentirati u kodu
dio s postavkom GLSurfaceView.RENDERMODE_WHEN_DIRTY. OpenGL inače
rotira oblik inkrementalno i čeka poziv funkcije requestRender() iz
GLSurfaceView spremnika [Developers, Adding
Motion, dostupno na:
http://developer.android.com/training/graphics/opengl/environment.html,
25.01.2015.]:
public MyGLSurfaceView(Context context)
{
...
// Render the view only when there
is a change in the drawing data.
// To allow the triangle to rotate
automatically, this line is commented out:
//setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
Osim ako imamo objekte koji se mijenjaju bez bilo kakve interakcije s
korisnikom, onda je dobra ideja imati ovu zastavicu podignutu ili
uključenu.
5. Prikaz grafike s
OpenGL ES iz Processinga 2.0
Kako Android platforma sadrži potpunu podršku za Java okruženje u
prethodnom poglavlju obradili smo OpenGL ES iz perspektive Jave. No,
Android je i ujedno open source ekosustav, što znači da Java nije
jedini alat koji nam stoji za manipulaciju OpenGL ES-a. Na Android
platformi možemo pristupiti OpenGL ES-u iz bilo kojeg skriptnog
jezika putem Android SL4A ili nekog trećeg ekosustava kao što je to
Processing 2.0.
Kako bi učinili cijelokupnu stvar nama zanimljivijom, pitali smo se
kako upravljati OpenGL ES-om iz neke druge perspektive koja nije
Javina, istraživanjem došli smo do 2 odgovora. Prvi je, da je to
moguće uz pomoć C/C++ jezika kojeg ćemo ugraditi u Java aplikaciju
putem JNI sučelja. Drugi je, putem Processing 2.0 API-a. Naime,
Processing je veoma popularan jezik i okruženje za programiranjem sa
velikim naglaskom na računalnu grafiku i grafičke elemente. Kada
smo vidjeli da Processing 2.0 sadrži podršku za Android grafiku
putem OpenGL ES-a odlučili smo dalje istražiti tu perspektivu
razvoja.
U ovom poglavlju napraviti ćemo kratak pregled u OpenGL ES iz
perspektive Processing 2.0 jezika. U prethodnim poglavljima
razmatrali smo kako je OpenGL ES zapravo okvir za koji je zadužna
grafička kartica na uređaju (eng. Graphic Processing Unit, u
nastavku GPU). Upravo taj okvir čini direktan način programiranja
3D grafike na Androidu. OpenGL ES je također API koji je namjenjen
da se koristi na više platformi, a ne samo na Androidu, te ujedno
čini i podskup OpenGL-a. Minimalni hardwerski zahtjevi da pokrenemo
neku 3D grafiku na Androidu iz Processinga su sljedeći:
GPU
koji ima podršku minimalno za OpenGL ES 1.1
Android
2.1+, no neka svojstva OpenGL još uvijek nedostaju na Androidu 2.1,
tako da se preporuča upotreba minimalno Android 2.2 platforme
Kada
radimo Processing skicu za Android dostupna su nam 2 stroja
renderiranja. Prvi je sam OpenGL ES, dok je drugi Android 3D (u
nastavku A3D). Cijelokupan Processing na Androidu koristi OpenGL ES
direktno, i sve skice koje izradimo će u runtime-u koristiti OpenGL
ES 2.0. Stroj renderiranja u Processingu je zapravo modul koji je
zadužan za sva crtanja i sve operacije sa kojima crtamo. Stroj
renderiranja specificiramo kada stvaramo objekt ekrana i zadajemo mu
rezoluciju. U slučaju ako ne zadamo neki stroj renderiranja
Processing će upotrebljavati A2D. U nastavku ćemo razmotriti neke
od scenarija korištenja Processinga na Androidu te uvidjeti da je
zapravo puno intuitivnije za koristiti Processingom API za crtanje
nego čiste pozive OpenGL ES-a iz Jave.
Prije
nego se upustimo u naprednije stvari iz Processinga razmotrimo
primjer kako crtati na platno koje nećemo prikazati na ekranu (eng.
Offscreen drawing). Da bi izradili platno u Processingu potrebno je
pozvati se na createGraphics() metodu.
PGraphicsAndroid3D pg;
void setup() {
size(480, 800, A3D);
pg = createGraphics(300, 300, A3D);
...
}
Ovakav crtež koji se ne prikazuje na ekranu, kasnije možemo
iskoristi kao sliku za teksturu objekta ili da kombiramo sa drugim
slojevima. Naprimjer, ovako:
void draw() {
pg.beginDraw();
pg.rect(100, 100, 50, 40);
pg.endDraw();
...
cube.setTexture(pg.getOffscreenImage());
...
}
5.1. Geometrijske
transformacije
Koordinatni sustav u Processingu definiran je sa X osi koja se
proteže od lijeve prema desnoj strani, Y os od doljnoj prema gornjoj
strani i negativna Z os pokazuje od ekrana. Kada bi gledali ishodište
koordinatnog sustava ono bi bilo u gornjom lijevom kutu. Ovo je važno
da znamo jer sve geometrijske transformacije koje ćemo primjeniti
nad našim objektima će se odnositi na cijeli koordinatni sustav. Iz
slike 16. možemo vidjeti kako je koordinatni sustav za 3D
implementiran u Processingu.
Slika
16 Koordinatni sustav kako je implementiran u Processingu
5.1.1. Translacija
Translacija se vrši uz
pomoć translate(dx, dy, dz) funkcije.
void
setup() {
size(240, 400, A3D);
stroke(255, 150);
}
void
draw() {
background(0);
translate(50, 50, 0);
noStroke();
fill(255, 200);
rect(60, 0, 100, 100);
}
Iz slike 17. možemo
vidjeti kako će gornji kod napraviti translaciju rect-a.
Slika
17 Primjer translacije
5.1.2. Rotacija
Rotacija
uvijek sadrži jednu od osi oko koje objekt rotira. Ta os može biti
koordinatna ili može biti neki proizvoljni vektor.
rotateX(angle), rotateY(angle),
rotateZ(angle), rotate(angle, vx, vy, vz)
void setup() {
size(240, 400, A3D);
stroke(255, 150);
}
void draw() {
background(0);
rotateZ(PI / 4);
noStroke();
fill(255, 200);
rect(60, 0, 100, 100);
}
Iz
doljnje slike možemo vidjeti kako gornji isječak koda će rotirati
rect.
Slika
18 Prikaz rotacije
5.1.3. Skaliranje
Skaliranje
može biti uniformno, znači da se objekt proširuje u svim
smjerovima za određenu jednaku duljinu, tj. za neki faktor. Ili
možemo definirati funkciji scale(sx, sy, sz) zasebne vrijednosti za
svaki smjer.
void setup() {
size(240, 400, A3D);
stroke(255, 150);
}
void draw() {
background(0);
scale(1.5, 3.0, 1.0);
noStroke();
fill(255, 200);
rect(60, 0, 60, 60);
}
Iz
gornjeg isječka, naš pravokutnik će se skalirati kako je prikazano
na sljedećoj slici.
Slika
19 Primjer skaliranja u Processingu
5.2. Kamera i
perspektiva
Konfiguriranje pogleda sceneu A3D zahtjeva konfiguraciju lokaciju
kamere i volumena gledanja. Postavljanje kamere je specificirano
pozicijom oka, centrom scene te koja koordinatna os gleda prema gore.
Funkcija za konfiguraciju kamere u Processingu je:
camera(eyeX, eyeY, eyeZ, centerX,
centerY, centerZ, upX, upY, upZ);
Ako se gore definirana funkcija ne poziva, A3D automatski će ju
pozvati sa defaltnim vrijednostima koje su:
camera(width / 2.0, height / 2.0,
(height / 2.0) / tan(PI * 60.0 / 360.0), width / 2.0, height / 2.0,
0, 0, 1, 0);
Razmotrimo sada primjer postavljanja kamere.
void setup() {
size(240, 400, A3D);
fill(204);
}
void draw() {
lights();
background(0);
camera(30.0, mouseY, 220.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0);
noStroke();
box(90);
stroke(255);
line(-100, 0, 0, 100, 0, 0);
line(0, -100, 0, 0, 100, 0);
line(0, 0, -100, 0, 0, 100);
}
Gornji programski isječak nacrtati će sljedeću grafiku iz koje
možemo vidjeti jednostavno kako smo zapravo postavili kameru u
prostor.
Slika
20 Primjer kamere u prostoru
Kako bi napravili pogled iz određene perspekitve koristimo funkciju
perspecitve. Volumen pogleda u tom slučaju je smanjena ili povećana
piramida te konvergencija nacrtanih linija prema oku stvara
perspektivnu projekciju gdje objekti koji su smješteni dalje će se
činiti manji, dok oni bliže će se činiti većima.
Slika
21 Slika objašnjava elemente koji su uključeni u perspektivnu
geometriju
5.3. Stvaranje 3D
objekata
Processing je veoma jednostavan za korištenje, te stvaranje 3D
objekata je puno jednostavnije, brže i intuitivnije nego je to
recimo u Javi. On nam definira u A3D već određene predefinirane
objekte poput sfere i kocke. Kako stvarati 3D objekte je najlakše
objasniti na primjeru. Primjer jednostavnosti upotrebe jednostavnih
grafičkih primitiva nalazi se u nastavku.
void setup() {
size(240, 400, A3D);
stroke(0);
}
void draw() {
background(0);
translate(width/2,height/2,0);
fill(200, 200);
pushMatrix();
rotateY(frameCount*PI/185);
box(150, 150, 150);
popMatrix();
fill(200, 40, 100, 200);
pushMatrix();
rotateX(-frameCount*PI/200);
sphere(50);
popMatrix();
}
Ovaj programski isječak prikazuje nam kako koristiti neke
predefinirane grafičke primitive. Kada ga iskompajliramo i pokrenemo
na Android uređaju ili emulatoru, dobiti ćemo prikaz koji se nalazi
na idućoj slici.
Slika
22 Primjer korištenja predefiniranih 3d grafičkih primitiva
Drugi način stvaranja 3D objekata koji nisu predefinirani je putem
beginShape(), endShape() konstrukata. Ove dvije funkcije omogućavaju
nam da stvorimo složenije objekte tako da specificiramo vrhove i
način njihovog spajanja (te opcionalno možemo specificirati i
normale i koordinata tekstura za svaki verteks). Ova funkcionalnost
je dostupna i u A2D sa malom razlikom da u A3D imamo jednu
koordinatnu os više pa i za nju moramo specificirati koordinate.
Naprimjer, razmotrimo sljedeći isječak Processinga.
beginShape(TRIANGLE_STRIP);
vertex(30, 75, 0);
vertex(40, 20, 0);
vertex(50, 75, 0);
vertex(60, 20, 0);
vertex(70, 75, 0);
vertex(80, 20, 0);
vertex(90, 75, 0);
endShape();
Za ovaj složeni objekt pod nazivom „TRIANGLE_STRIP“ dobiti ćemo
sliku 23. kada ga renderiramo.
Slika
23 Renderiranje složenih objekata
5.3.1. Teksture
Teksture su jedna od najvažnijih tehnika u računalnoj grafici koje
se sastoje od korištenja slike kao papira u koji ćemo omotati neki
3D objekt sa ciljem da simuliramo da taj objekt je načinjen od nekog
materijala, da dobijemo realističnu površinu objekta ili možda
neke efekte.
Slika
24 Slika objašnjava koncept tekstura u računalnoj grafici
Mapiranje
tekstura na objekte je izazovan i kompleksan problem kada trebamo
pridodati teksturu nekom kompliciranom 3D objektu (koji još k tome
je organske prirode). Da bi pronašli pravilno mapiranje 2D slike u
3D objekt zahtjeva precizne matematičke tehnike koje u svojim
izračunima uzimaju u obzir rubove, presjeke, itd. Ako razmotrimo
sliku 25. vidimo koliko zapravo 3D objekt može biti kompleksan.
Slika
25 Slika demonstrira primjerom kako proces mapiranja tekstura može
biti složen za određene 3D objekte
Kako bi u Processingu
iskoristili teksture, pokazati ćemo to na jednostavnom primjeru kako
se to radi.
PImage
img1, img2;
void
setup() {
size(240, 240, A3D);
img1 = loadImage("beach.jpg");
img2 = loadImage("peebles.jpg");
textureMode(NORMAL);
noStroke();
}
void
draw() {
background(0);
beginShape(TRIANGLES);
texture(img1);
vertex(0, 0, 0, 0, 0);
vertex(width, 0, 0, 1, 0);
vertex(0, height, 0, 0, 1);
texture(img2);
vertex(width, 0, 0, 1, 0);
vertex(width, height, 0, 1, 1);
vertex(0, height, 0, 0, 1);
endShape();
}
Kada kompajliramo gornji primjer i pokrenemo ga na Androidu dobivamo
rezultat sa sljedeće slike.
Slika
26 Primjer rada sa teksturama u Processingu
Iz gornjeg primjera vidimo kako raditi sa teksturama u Processingu na
Androidu. Također, prikazali smo još jedno važno svojstvo, a to je
da sa beginShape i endShape u A3D možemo iskoristiti kombinaciju
više tekstura za različite dijelove jednog objekta.
5.3.2. Svijetlo
Android
3D također pruža lokalni model osvijetljenja baziran na OpenGL
modelu. To je jednostavan pravovremeni (eng. realtime) model
osvijetljenja, gdje svaki izvor svijetlosti sadrži 4 komponente:
Ambijent
Difuziju
Spekular
Emisiju
Zbroj
tih 4 komponenata čini total. Ovaj model ne dopušta stvaranje sjena
te može definirati i do 8 različitih izvora svijetla. Odgovarajući
izračuni za svijetlo zahtjevaju da se specificira normala objekta.
Android 3D podržava nekoliko tipova svijetla, a to su:
Ambijentalno –
Ambijentalno svijetlo je takvo svijetlo koje ne dolazi iz nekog
specifičnog smjera, zrake svijetlosti sadrže osvijetljenje na sve
strane tako da su objekti ravnomjerno osvijetljenji sa svih strana.
Ambijentalno svijetlo se gotovo uvijek koristi u kombinaciji sa
drugim tipovima svijetla.
U Android
Processingu definiramo ga funkcijom: ambientLight(v1, v2 ,v3, x, y,
z), gdje su v1, v2, v3 rgb boje svijetla, a x, y, z pozicija
Slika
27 Primjer ambijentalnog svijetla
Slika
28 Primjer direkcionalnog svijetla
Slika
29 Primjer točkastog svijetla
Slika
30 Primjer pjegastog svijetla
6. Kochova pahulja u
3D
Sada kada smo napravili kratak pregled kroz osnove OpenGL ES-a na
Androidu iz dvije različite perspektive, pokušati ćemo
izmodelirati Kochovu pahulju u 3D-u. Kako više naginjemo novijem
pristupu razvoja Android grafike, koristiti ćemo Processing 2.0 za
crtanje našeg 3D fraktala.
Za početak prisjetimo se kako se crta Kochova pahulja u 2D-u. Iz
teorije znamo da se Kochova pahulja crta uz pomoć Kochove krivulje,
a Kochova krivulja se dobiva tako da zadani vektor podijelimo prvo na
3 jednaka dijela, te zatim obrišemo srednji dio te na njegovom
mjestu nacrtamo dva vektora koja se dodiruju u normali početnog
vektora. Ovaj proces ponavljamo rekurzivno za svaki od vektora koji
imamo na platnu. Kochova krivulja počinje od 1 vektora i njega
rekurzivno dijeli, te na taj način tvori fraktal dok Kochova pahulja
za početni oblik uzima trokut, te cijelokupni postupak iz Kochove
krivulje ponavlja za svaku od stranica trokuta.
No pitanje se sada postavlja na koji način možemo nacrtati Kochovu
pahulju u 3D-u. Da bi cijelu priču iz 2D-a pretvorili u 3D moramo
zamijeniti primitivne konstrukte. Jednostavnim i brzim istraživanjem
otkrili smo da Kochovu pahulju u 3D-u možemo nacrtati na dva načina.
Prvi način je da našu pahulju krenemo crtati od piramide. Na svaku
stranicu početne piramide nacrtati ćemo novu umanjenu piramidu. Na
svaku od nacrtanih piramida nacrtati ćemo po još jednu piramidu,
itd. Ovim postupkom dobiti ćemo Kochovu pahulju kao što je
prikazana na sljedećoj slici.
Slika
31 Prvi način crtanja Kochove pahulje [3D Koch Snowflake, dostupno
na: http://blog.3dvision.com/2008/10/30/3d-koch-snowflake/]
Drugi način crtanja Kochove pahulje u 3D također kreće od piramide
kao baze. No u ovom slučaju pojedinu stranicu piramide ćemo
podijeliti na 4 jednakostranična trokuta te na srednjem izgraditi
piramidu. U idućem koraku rekurzije to činimo za svaku stranicu
trokuta uključujući i novonastale trokute.
Slika
32 Drugi način crtanja Kochove pahulje [3D Koch snowflake, Dostupno
na:
https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcT7GePIlCD-sSHFmcFl3OgYiojQo4rovsUVxT2YS7rEgozfG6AjUA]
S obzirom da nam drugi način crtanja je bio mnogo zanimljiviji od
prvog odlučili smo se implementirati drugi način. Uvidjeli smo da
takvim crtanjem Kochove pahulje u 3D, gdje pojedine stranice dijelimo
na isti način kao što i crtamo Sierepinski trokut u 2D dobivamo
mnogo zanimljiviji i bogatiji oblik same pahulje. U nastavku ćemo
razmotriti Android Processing 2.0 kod naše implementacije Kochove
pahulje.
6.1. Analiza izvornog
koda
U ovom potpoglavlju
analizirati ćemo naš izvorni kod.
import processing.opengl.*;
PrintWriter logger;
float pisquared = TWO_PI;
PImage tex;
FraktalniTrokut koch[]=new
FraktalniTrokut[4];
void init_koch(PVector a, PVector b,
PVector c, PVector d, int dubina_rekurzije) {
koch[0] = new FraktalniTrokut(a, b, c,
dubina_rekurzije);
koch[1] = new FraktalniTrokut(a, d, b,
dubina_rekurzije);
koch[2] = new FraktalniTrokut(a, c, d,
dubina_rekurzije);
koch[3] = new FraktalniTrokut(b, d, c,
dubina_rekurzije);
}
void setup() {
logger =
createWriter("calculations.txt");
//stroke(0);
//strokeWeight(1);
size(displayWidth, displayHeight,
OPENGL); // OPO velicina
noStroke();
//fill(150,150,150,20);
//textureMode(NORMALIZED);
fill(255,0,0,120);
//noFill();
int tockaA = 200;
int dubina_rekurzije = 2;
PVector A = new PVector(-tockaA, 0,
0);
PVector B = new PVector(tockaA/2, 0,
-175);
PVector C = new PVector(tockaA/2, 0,
175);
PVector D = new PVector(0, -tockaA *
(sqrt(6) / 3), 0);
init_koch(
A, B, C, D,
dubina_rekurzije
);
}
void setup_camera() {
float tockaX=(mouseX / float(width)) *
pisquared;
float pozicija_x=cos(tockaX);
float pozicija_y=sin(tockaX);
float radius=300.000;
camera(pozicija_x * radius, mouseY,
pozicija_y*radius, // pogled iz X, pogled iz Y, pogled iz Z
0.0, -50.0, 0.0, // center X
osi, center Y osi, center Z osi
0.0, -1.0, 0.0);
}
void draw() {
background(mouseY * (255.0/600), 255,
0);
lights();
//setup_camera();
pushMatrix();
translate(displayWidth / 2,
displayHeight / 2);
//translate(nf(ax, 1, 2), nf(ay, 1,
2));
rotateX(mouseY * 0.01);
rotateY(mouseX * 0.01);
print("Rotated X: " + mouseY
* 0.01);
print("Rotated Y: " + mouseX
* 0.01);
scale(1);
for (int i=0;i<koch.length;i++) {
koch[i].display();
}
popMatrix();
}
class FraktalniTrokut {
PVector PointA;
PVector PointB;
PVector PointC;
FraktalniTrokut podijeljeni_trokuti[]
= new FraktalniTrokut[6];
int rec;
float skaliranje;
FraktalniTrokut(PVector A,PVector
B,PVector C, int recursion){
skaliranje = 0.7;
PointA = A;
PointB = B;
PointC = C;
rec = recursion;
rekurzija ();
}
void rekurzija () {
if (rec != 0) {
// racunamo 3 nova vektora koji
dijele povrsinu
PVector PointAB2 =
PVector.add(PointA,PointB);
PointAB2.div(2);
PVector PointAC2 =
PVector.add(PointA,PointC);
PointAC2.div(2);
PVector PointBC2 =
PVector.add(PointB,PointC);
PointBC2.div(2);
// racunamo sredisnju tocku na
danom trokutu
PVector PointZ =
PVector.add(PointA,PointB);
PointZ.add(PointC);
PointZ.div(3);
// racunamo vektor smjera normale
u kojoj ce se spajati trokuti
PVector PointAB =
PVector.sub(PointA,PointB);
PVector PointAC =
PVector.sub(PointA,PointC);
PVector PointH =
PointAB.cross(PointAC);
PointH.normalize(); // normalizamo
vektor na velicinu 1
// racunamo tocku u kojoj ce se
spajati novi trokuti
PVector PointAAB2 =
PVector.sub(PointA,PointAB2); // ovo je polovica baznog vektora koji
cini stranicu trokuta kojeg dijelimo
float a = PointAAB2.mag(); //
racunamo velicinu tog vektora
float pheight = a * (sqrt(8) / 3)
* skaliranje; // racunamo visinu za novu piramidu, te prilagodavamo
visinu tako da novi trokuti nebudu preveliki
PointH.mult(-pheight); // dizemo
tocku sa povrsine u zrak
PVector PointZH =
PVector.add(PointZ,PointH);
podijeljeni_trokuti[0] = new
FraktalniTrokut(PointA,PointAB2,PointAC2,rec-1); // crtamo trokut 1
podijeljeni_trokuti[1] = new
FraktalniTrokut(PointB,PointBC2,PointAB2,rec-1); // crtamo trokut 2
podijeljeni_trokuti[2] = new
FraktalniTrokut(PointC,PointAC2,PointBC2,rec-1); // crtamo trokut 3
podijeljeni_trokuti[3]=new
FraktalniTrokut(PointZH,PointAC2,PointAB2,rec-1); // crtamo trokut 4
podijeljeni_trokuti[4]=new
FraktalniTrokut(PointZH,PointAB2,PointBC2,rec-1); // crtamo trokut 5
podijeljeni_trokuti[5]=new
FraktalniTrokut(PointZH,PointBC2,PointAC2,rec-1); // crtamo trokut 6
}
}
void display () {
if (rec==0) {
beginShape();
texture(tex);
vertex(PointA.x, PointA.y
,PointA.z);
vertex(PointB.x, PointB.y
,PointB.z);
vertex(PointC.x, PointC.y
,PointC.z);
endShape(CLOSE);
} else {
for (int i=0;
i<podijeljeni_trokuti.length;i++) {
podijeljeni_trokuti[i].display();
}
}
}
}
6.2. Analiza
različitih dubina rekurzije
Dubina rekurzije: 7
Slika
33 Kochova Pahulja u 3D sa dubinom rekurzije 7
Dubina rekurzije: 6
Slika
34 Kochova Pahulja u 3D sa dubinom rekurzije 6
Dubina rekurzije: 5
Slika
35 Kochova Pahulja u 3D sa dubinom rekurzije 5
Dubina rekurzije: 4
Slika
36 Kochova Pahulja u 3D sa dubinom rekurzije 4
Dubina rekurzije: 3
Slika
37 Kochova Pahulja u 3D sa dubinom rekurzije 3
Dubina rekurzije: 2
Slika
38 Kochova Pahulja u 3D sa dubinom rekurzije 2
7. Zaključak
U ovom seminarskom radu obrađivali smo u sklopu kolegija računalne
grafike temu pod nazivom fraktali. Tema je obrađena na dva načina.
Prvi je s teorijskog stajališta općenito o fraktalima kako bi
ostale studente mogli upoznati i povezati s njihovim značenjem i
tematikom, a drugi je praktične prirode gdje smo analizirali -
OpenGL ES iz dvije perspektive – Java i
Processing.
Fraktali predstavljaju u biti objekte koji daju
jednaku razinu detalja neovisno o razlučivosti koju koristimo, a
njihova osnovna svojstva su samosličnost, fraktalna dimenzija i
oblikovanje iteracijom kao što smo i ranije naveli. Moguća je
njihova podjela prema stupnju samosličnosti i prema načinu
nastanka.
Fraktalna umjetnost relativno je mlada u
usporedbi s drugim umjetnostima, jer ne bi mogla nastati bez uporabe
računala, no fraktali su ipak s nama oduvijek. Najvjerovatnije se
upravo iz tih razloga ljudima i sviđaju. U njima pronalazimo nekakvu
neobičnu ljepotu, volimo gledati u njih, djeluju na nas umirujuće.
Nalazimo ih posvuda u prirodi, pa i na nama samima (ako malo bolje
promotrimo ruku i šaku, vidjet ćemo da je svaki prst u omjerima
umanjena verzija ruke). Ljudi su ih intuitivno osjećali i
prepoznavali kao načelo stvaranja prirode, pa onda nije ni čudo da
u povijesti umjetnosti nalazimo fraktale ne samo prije prvih
računala, nego i prije struje i parnoga stroja [Fraktali i
umjetnost, Vesna Mišljenović, Zagreb (2011./2012. br. 80), str.
224.]
Literatura
Developers-Android,
Displaying Graphics with OpenGL ES, dostupno na:
http://developer.android.com/training/graphics/opengl/index.html,
25.01.2015. ;
Fractal
Noise, Neil Blevins, dostupno na:
http://www.neilblevins.com/cg_education/fractal_noise/fractal_noise.html,
učitano: 17.01.2015. ;
Hrvatski
matematički elektronski časopis math.e, Galerija fraktala, V.
Antočić, A. Galinović, dostupno na:
http://e.math.hr/galerija/galerija_print.html,
učitano: 17.01.2015. ;
Interaktivna
računalna grafika kroz primjere u OpenGL-u, M.Čupić,
Ž.Mihajlović, dostupno na:
http://www.zemris.fer.hr/predmeti/irg/knjiga.pdf,
učitano: 17.01.2015. ;
M.
Pašić, Uvod u matematičku teoriju kaosa za inženjere, Skripta
FER, Zagreb, 2005. (57.-83.) ;
Neverinov
blog, dostupno na:
http://blog.dnevnik.hr/blogodneverina/2010/02/1627237517/fasciniranost-fraktalima.html,
učitano: 17.01.2015. ;
Processing
for Android, dostupno na:
http://processing.flosscience.com/processing-for-android,
25.01.2015. ;
Processing/processing-android,
Joel Moniz, dostupno na:
https://github.com/processing/processing-android/wiki,
25.01.2015. ;
Processing
& Android: Mobile app development made (very) easy, dostupno na:
http://blog.blprnt.com/blog/blprnt/processing-android-mobile-app-development-made-very-easy,
25.01.2015.
Pythagoras
Tree, dostupno na:
http://www.phidelity.com/blog/phidelity/blog/fractal/pythagoras-tree/,
učitano: 17.01.2015. ;
Shane
Bow The Chaos of Mandelbrot, dostupno na:
http://shanebow.com/projects/mandelbrot/,
učitano: 17.01.2015. ;
Silvergreen,
Deviant art, dostupno na:
http://silvergreen.deviantart.com/art/Fractal-Tree-1646228,
učitano: 17.01.2015. ;
Uvod
u fraktale, M.Paušić, dostupno na:
http://www.fer.unizg.hr/_download/repository/Uvod%20U%20Fraktale%20by%20Mladen%20Pausic.pdf,
učitano: 17.01.2015. ;
Uvod
u matematičke metode u inženjerstvu, K. Brdar, M. Dobrinić, R.
Joksić, Fraktali, dostupno na:
http://matematika.fkit.hr/novo/izborni/referati/dobrinic_joskic_brdar_fraktali.pdf,
17.01.2015. ;