Category Archives: OpenGL ES 2.0

Porttautuva koodi mobiilissa

“Write once, run anywhere” oli Javan silver bullet -mantra aikanaan. Eli kirjoitat koodin kerran, käännät sen kerran ja ajat missä vaan tuetussa ympäristössä ilman että ohjelmasi tarvitsee liiemmin tietää ajonaikaisesti mikä käyttöjärjestelmä / rauta ympäristössä on käytössä. No, vaikkei tuohon ole vieläkään – 18 vuotta myöhemmin – ole ihan päästy, on Javan ja vaikkapa Pythonin kaltaisten tulkattujen/scriptauskielten kanssa useimmiten paljon helpompaa elää monialustaympäristössä kuin natiivikoodin. Mutta entä jos halutaankin kehittää natiivisovellus jossa uudelleenkäytetään mahdollisimman suuri osa koodista sen sijaan että N alustaa varten tehdään N toteutusta? Vaikka nykyisten mobiililaitteiden rautaspeksejä (kotitehtävä: montako 25MHz i386 CPU:ta tarvitaan tuottamaan Samsung Galaxy S4:n CPU-teho?) ihmetellessä ei parhaalla omallatunnollakaan voi sanoa resurssien puutteen pakottavan natiivikoodiin, on sen käyttö esim. Android-alustallakin perusteltua sovelluksissa joiden on puristettava se viimeinenkin pisara suorituskykyä irti laitteesta.

Luonnollisesti kaikenlaisiin sovellustyyppeihin porttautuvaa koodia on turha yrittää ujuttaa; jos sovellus on 80%:sti käyttöliittymää eikä ole tarjolla/ei haluta käyttää kaikilla kohdealustoilla toimivaa porttautuvaa UI-kirjastoa, ei kannata väkisin lyödä päätään seinään vaan kirjoittaa toteutukset kiltisti alustan natiivi-API:a käyttäen – tai valita teknologiaksi HTML5 joka sopii webmäisiin lomakesovelluksiin kuin nyrkki silmään. Ym. UI-kirjastoa käyttämällä saatetaan myös – sen toteutuksesta toki riippuen – menettää alustan oma look-and-feel. Ihan hihasta ravistettuna porttautuvan arkkitehtuurin puolesta / vastaan listat voisivat näyttää vaikkapa tältä:

Vastaan:

  • Väkisin yritetty porttautuvuus tekee arkkitehtuurista kömpelön ja lisää turhaa kompleksisuutta
  • Alustan natiiveja ominaisuuksia saatetaan menettää (hyvässä ja pahassa)
  • Käytettäväksi valittu yhteinen ohjelmointikieli ei välttämättä ole tuttu kaikille projektin jäsenille

Puolesta:

  • Koodin joutuu kirjoittamaan vain kerran eli kehityskustannus pienenee
  • Bugit joutuu korjaamaan vain kerran
  • Koodipohjasta tulee merkittävästi pienempi jolloin sen ylläpidettävyys paranee ja siihen on nopeampaa perehdyttää uusia tekijöitä
  • Ohjelmiston luonteen salliessa paljon koodia kierrättävä arkkitehtuuri on hyvin elegantti

Esimerkkiprojekti: MMark13 – mobiilia suorituskykytestausta

Käytän esimerkkinä kuluneen vuoden aikana hiljalleen syntynyttä harrasteprojektiani MMark13, joka – hyvin tiivistetysti – on mobiililaitteiden CPU/GPU suorituskyvyn mittaamiseen tarkoitettu työkalu. Vastaavalla idealla (joskin merkittävästi isommalla tiimillä & budjetilla :)) rakennettu sovellus löytyy Desktop-puolelta, monelle tuttu suomalaista tekoa oleva Futuremark:in 3DMark. Ja ei, en ole suoraan rinnastamassa harrastelijatuotostani PC-maailman (Ja Futuremark:han puuhaa parasta aikaa myös mobiilibenchmarkingia, Androidille se jo ilmestyikin! -toim.) uraauurtavaan tuotteeseen.. no joka tapauksessa, projekti lähti liikkeelle halusta hioa/päivittää OpenGL ES 2.0 taitoja ja havaittuani ettei tuossa vaiheessa (alkuvuosi 2012) juuri vastaavia sovelluksia ollut tarjolla (paitsi kaupallinen ja maksullinen GLBenchmark 2.5 – joskin tuolloin vain iOS:lle), valitsin kyseisen sovellusalueen jo siksikin että porttautuvan arkkitehtuurin hyväksikäyttäminen laajalti tarjosi ainutlaatuisen edun: kun jokaisella alustalla ajettava koodi on 100% sama niiltä osin jotka grafiikan piirtoon / mittauksiin vaikuttavat, voidaan tuloksia verrata 1:1 toisiinsa alustojen välillä ilman että tarvitsee napista Javan tehottomuudesta Androidilla.

 

MMark13 koodin rakenne

 

Koodin uudelleenkäytön suunnittelussa on hyvä lähteä liikkeelle siitä että kartoittaa tarkasti kohdealustansa ja niiden tarjoamat asiat ja sitten hahmottelee niiden pohjalta suurimman yhteisen nimittäjän. Valitsin kohdealustoikseni iOS (Applen laitteet) ja Qt (Android, kaikki merkittävät desktop-käyttöjärjestelmät, Meego, Blackberry10, Symbian, Sailfish OS, Ubuntu Phone, luultavasti myös Tizen ja Firefox OS). Kun alustat ovat tiedossa, aloittaa voi vaikka ohjelmointikielestä; se on valinta jota alemmalle tasolle ei juuri pääse. C olisi aina varma valinta, mutta puhtaalla C:llä syntyy tulosta kovin hitaasti ja dynaamisten tietorakenteiden / merkkijonojen / jne käsittely on melko kömpelöä. Valitsinkin siksi pykälää korkeamman abstraktiotason eli C++:n. Ohjelmointikielen version valintaan kannattaa paneutua myös hetken eikä sännätä käyttämään heti uusinta; itse en uskaltanut vielä tähän projektiin valita tuoreinta C++11 versiota vaikka se oliskin tarjonnut muutamat elämää helpottavat asiat kuten “auto” tyypin muuttujille, sisäänrakennetun säiekirjaston ja paljon muuta. Sittemmin olen perehtynyt tarkemmin C++11 feature support matriisiin merkittävillä kääntäjillä (GCC, clang, MSVC++) ja vaikuttaa että aika on kypsä sen käyttöönotolle.

Ohjelmointikielen valinnan jälkeen seuraava looginen askel on kartoittaa APIen/ulkoisten kirjastojen tarve ja koettaa vaikkapa proof-of-concept -hengessä varmistaa että kirjastovalinnat täyttävät tarpeet. MMark13 -projektissa tarvitsin säikeistystä, joten käytin hyväkseni tietoa että kaikki kohdealustani ovat UNIX-perillisiä ja siten tarjoavat POSIX threads (pthreads) paketit. Tarvittiin myös JSON kirjastoa; pienellä googlauksella löysin “jsoncpp” paketin jonka koodi/dokumentaatio oli poikkeuksellisen laadukkaan oloista ja joka istui suoraan tarpeisiini. Kenties tuosta löydöksestä riemuitessani kirjoitin seuraavan askeleen eli tuloslaskelma-JSON lähetyksen palvelimelle abstraktion läpi siten että kirjoitin alustoille omat toteutuksensa sen sijaan että olisin käyttänyt ilmiselvää ratkaisua libcurlia. Tajusin mokani vasta projektin loppumetreillä enkä rupea yleensä korjaamaan jotain joka toimii, joten saivat jäädä. Lisäksi otin mukaan simppelin C++ MD5-toteutuksen. Halusin toteuttaa softani käyttöliittymän alustariippumattomasti siten että se käyttäytyy kaikilla laitteilla samalla tavalla ja myös näyttää samalta. Etsinkin melko laajalti OpenGL-pohjaista Widget-kirjastoa mutta löytämäni paketit olivat joko aivan järjettömiä bloatteja tai sitten erittäin huonolaatuisen näköistä koodia (usein molempia) että päädyin kirjoittamaan hyvin lightweight toteutuksen aivan alusta erään intensiivisen viikonlopun aikana. Eli ainakin yksi pyörä tuli keksittyä uusiksi – toisaalta aikaa ei tuohon uponnut paljoa, lopputulos miellyttää silmää, homma oli erittäin hauskaa ja lisäksi vieläpä opin paljon.

Lisäksi tarvittiin seuraavat asiat alustariippuvasti: softan bootstrapping, softa/hardisinformaation penkomista laitteelta, joitain sekalaisia pikkujuttuja sekä resurssifilejen lukeminen systeemistä – tähän olisi voinut käyttää ihan fread():ia mutta halusin toisaalta käyttää Qt:n mainiota resource systeemiä jonka kanssa tuo taas ei toimi. Alla muutama konkreettinen koodiesimerkki alustariippuvasta koodin jaosta:

CommonFunctionsQT.cpp:

std::string RandomUuid()
{
    // .mid(1,36) strips the curly braces added by toString(), duh
    return std::string(QUuid::createUuid().toString().mid(1,36).toUtf8());
}

CommonFunctionsIOS.cpp:

std::string RandomUuid()
{
    CFUUIDRef theUUID = CFUUIDCreate(NULL);
    CFStringRef cfuuid = CFUUIDCreateString(NULL, theUUID);
    CFRelease(theUUID);
    NSString* uuid = (NSString*)cfuuid;
    [uuid autorelease];

    return std::string([uuid UTF8String]);
}

Kurkistetaan vielä korkealla tasolla ohjelmiston toimintaa ja sitä miten alustariippuva ja porttautuva osa koodia pelaavat yhteen. iOS-buildissa tarvittava wrapperkoodi on luonnollisesti Objective-C(++):aa; kokemus osoitti ettei sen ja C++ -maailman sekoittaminen keskenään ole triviaalia tai kovin eleganttia, ja kehotankin eristämään kyseisen rajapinnan mahdollisimman pienelle alalle. Käytännössä toimiva pattern on antaa Objective-C++ koodille yksisuuntainen has-a -patternin mukainen suhde porttautuvaan C++ koodiin ja rakentaa iOS-specific toteutus C++ luokalle joka hoitaa callbackien tarvitseman alustariippuvan koodin. Qt maailma on natiivisti C++:aa, joten siellä ei tarvittu taikatemppuja. Ohjelman toimintalogiikka pääpiirteittäin:

  1. Alustariippuvainen osa ottaa vastaan inputit, muuttaa niiden datatyypit alustariippumattomaan muotoon ja välittää ne eteenpäin allaolevalle porttautuvalle osalle. Käytännössä inputit ovat:
    • Redraw timer. iOS:ssa CADisplayLink, Qt:ssä QTimer.
    • Käyttäjän kosketukset. iOS:ssa UITouch*, Qt:ssa QTouchEvent.
  2. Alustariippumaton osa päivittää tilan / menussa ollessa UI:n tilan touchien / kuluneen ajan avulla
  3. Uuden tilan perusteella piirretään uusi frame sekä mm. aloitetaan fysiikkalaskenta seuraavaa framea varten
  4. Tarvittaessa kutsutaan abstraktoituja callbackeja kun tarvitaan alustariippuvaa toiminnallisuutta; esimerkiksi kun ladataan testin vaatimia tekstuureita levyltä tai halutaan lähettää tulokset serverille

Jollekulle on saattanut tätä lukiessa tulla mieleen että mitenkäs se Windows Phone sitten? WP8 pois jättämiseen kohdealustojen joukosta on oikeastaan vain yksi syy – OpenGL APIn puute. Vaikka ymmärtääkseni WP8 hardis on OpenGL ES 2.0 yhteensopivaa, Microsoft ei tapansa mukaan tarjoa standardia rajapintaa vaan pakottaa kehittäjät porttaamaan koodinsa Direct3D:lle. Ja koska softani tärkeimpiä ominaisuuksia on olla suoraan vertailukelpoinen ympäristöjen välillä, ei tuo oikein ollut optio. Jos Googlen ANGLE projektista tulee ikina luotettava port WP:lle *tai* jostain taivaasta putoaa OpenGL ajuri, teen ilomielin myös WP-buildin. Tämän vuoden aikana on tarkoitus suoltaa ulos buildit ainakin Blackberry10:lle, Sailfish OS:lle sekä Ubuntu Phonelle riippuen siitä saadaanko moisia laitteita käsiimme.

Hyvää kevättä kaikille!

Linkkejä

Lisää luettavaa:

OpenGL ES 3.0 tulee – oletko valmis?

Aivan lyhykäisyydessään viimeaikojen uutisvirrasta poimittuna, Khronos Group on hiljattain julkaissut OpenGL ES 3.0 standardin speksin. ARM satsaa kovasti raudallaan sekä softatyökaluillaan mobiiligrafiikkaan ja toivon mukaan ensimmäiset laitteet nähdään jo pian!

Poimintoja tärkeimmistä uusista ominaisuuksista:

  • Alustariippumaton tekstuurien pakkaus on nyt osa standardia. Erittäin suuri helpotus cross-platform softan tekijöille.
  • Geometry instancing; monta kopiota samasta geometriasta (helppo esimerkki: metsän puut) voidaan nyt piirtää säästäen väylän kaistaa kun geometria tarvitsee lähettää vain kerran.
  • Occlusion queries: rautatuki artefaktien näkyvyyden selvittämiseen. Nopeuttaa ja suoraviivaistaa dramaattisesti vaikkapa lens flarejen yms. efektien toteuttamista.
  • Floating point, depth jne tekstuurit pakolliseksi osaksi speksiä
  • Ja mikä parasta, 3.0 speksi on 100% taaksepäin yhteensopiva 2.0 kanssa.

Lähteet:

 

Reaaliaikaista kuvankäsittelyä OpenGL ES 2.0:lla

Näytönohjaimen (GPU) shader prosessorit tarjoavat hyvän mahdollisuuden tehdä tehokasta – ja toteutuksesta riippuen rinnakkaista – laskentaa CPUn ohella. Nykyisten mobiililaitteidenkin shaderit ovat huomattavan tehokkaita; niiden on kyettävä pyörittämään 3D-pelejä ~30fps jotta käyttökokemus ei kärsi, ja tämä puolestaan tarkoittaa hurjaa määrää laskutoimituksia sekunnissa. Kuten edeltävässä postauksessani mainitsin, signaalinkäsittely on yksi luonnollinen sovelluskohde. Tässä postauksessa katsotaan hieman miten valjastaa GPU reaaliaikaisen kuvankäsittelyn käyttöön ja toteutetaan geneerinen konvoluutiomatriisi shaderilla. Toteutus on iPhone-spesifinen mutta helposti portattavissa muille alustoille.

Esimerkkiämme varten kuvadata haetaan laitteen kameralta; aina kun saadaan uusi kuva, se uploadataan OpenGL:lle tekstuuriksi. Tämä tekstuuri sitten renderöidään konvoluutioshaderin läpi toiseen tekstuuriin. Tämän uuden tekstuurin data luetaan ulos OpenGL:ltä ja käytetään – esimerkin vuoksi vain näytetään ruudulla. Pelkästään tähän käyttöön tuloksen kierrättäminen tekstuurin / UIImage kautta on tietysti kamalan tehotonta, kun se voitaisiin suoraan piirtää näyttömuistiin.  Tämänkaltainen toteutus kuitenkin mahdollistaisi monenlaista käsittelyä tulosdatalle. Lisäksi koodiesimerkki näyttää kuinka OpenGL ES 2.0:lla renderöidään tekstuuriin. Katso prosessin kuvaava tilakaavio alla:

Konvoluutiofiltterit

Konvoluutio ja kuvankäsittelyyn käytetyt matriisit (“kernel”) ovat tämän artikkelin laajuuden ulkopuolista asiaa – katso linkit postauksen lopussa – mutta esittelen toimintaperiaatteen nopeasti niin shaderin koodi aukeaa ehkä paremmin.

Käytän tässä 3×3 kerneleitä suorituskyvyn ja koodin selkeyden vuoksi; 5×5 kernelillä saadaan aikaan huomattavasti parempia tuloksia. Otetaan käsittelyyn helppotajuisin käytetyistä kerneleista, blur kerneli:

|  1/9     1/9     1/9   |
|  1/9     1/9     1/9   |
|  1/9     1/9     1/9   |

Tehdessä muunnosta tälläisellä matriisilla, jokainen kuvan pikseli käsitellään asettamalla matriisin keskimmäinen alkio sen kohdalle, jolloin matriisin muut alkiot asettuvat ympäröivien pikselien “kohdalle”; sitten keskipikselille lasketaan uusi arvo kertomalla jokainen pikseli arvolla joka matriisissa on sen kohdalla ja laskemalla näiden kertolaskujen tulokset yhteen. Blur-matriisin tapauksessa pikseli ja kaikki sen naapurit kerrotaan 1/9:llä, jolloin pikselin arvoksi tulee 9 pikselin keskiarvo. Konvoluutiokerneleillä voi toteuttaa vaikkapa viivantunnistusta, blurreja ja sharpenointia.

Esimerkkitoteutus

Toteutus sisältää geneerisen convolution kernel shaderin (Convolution.fsh) ja neljä eri kerneliä: emboss, blur, gradient, sharpen. Käytettyä kerneliä voi vaihtaa lennosta täppäämällä ruutua. Sovellus syöttää kuvia iPhonen pääkameralta GLSLn läpi ja piirtää tuloksen ruudulle. Ohessa muutama screenshot:

Macbook Pro piirrettynä Embossinging filterin läpi
Macbook Pro piirrettynä Embossinging filterin läpi
Macbook Pro piirrettynä Sharpening filterin läpi
Macbook Pro piirrettynä Sharpening filterin läpi

Siinäpä tämänkertainen anti, ensi episodissa sitten taas jotain aivan muuta!

Lisää luettavaa

Esimerkkitoteutuksen koodi (36kB)

OpenGL ES 2.0 kehityksen aloittaminen

OpenGL ES 2.0 on ollut speksinä olemassa vuodesta 2007 ja tarjolla laitteissakin jo tovin. 3D-grafiikkaan nojaavia mobiileja menestystarinoita ei ihan Angry Birds -mittakaavassa ole näkynyt mutta laitteiden alati parantuessa ja käyttökokemusten karttuessa 3D-renderöinti saattaa hyvinkin nousta keskeisempään rooliin mobiilimaailmassakin. Eivätkä ohjelmoitavan OpenGL ES 2.0 shader pipelinen mahdollisuudet grafiikan piirtämiseen lopu!

Tässä artikkelissa käydään läpi muutama asia jotka on tiedostettava – ja ehkä jopa osattava – aloittaessaan OpenGL ES 2.0 kehitystä. Varsinaisena kohdeyleisönä ovat ne joilla on edes jonkinlainen käsitys 3D-grafiikan periaatteista ja ovat tehneet joitain asioita vanhanmallisella fixed pipeline OpenGL:llä (kuten OpenGL ES 1.1 tai desktop maailman OpenGL 1.x) mutta tarjonnee teksti hieman lohtua aivan vasta-alkajallekin. Ensin käydään läpi uudet asiat jotka tulevat vastaan kun siirrytään fixed function renderöinnistä programmable pipelinen maailmaan ja lopuksi rakennetaan yksinkertainen 3D-softa iOS:lle käyttäen Xcoden OpenGL ES wizardia.

Matriisit

3D-grafiikan kulmakivi, kuvausmatriisit, olivat sisäänrakennettuina vanhassa OpenGL:ssä glPushMatrix()/glPopMatrix()/glRotate() jne myötä. Programmable pipelinessä matriisipinoa ei ole tarjolla ja matriisioperaatiot on toteutettava itse. Käytännössä kaikki alustat tarjoavat omat matriisikirjastonsa tähän (QMatrix4x4 Qt:ssä, android.opengl.Matrix Androidille sekä iOS 5.0 alkaen GLKMatrix4 iPhonelle) tarkoitukseen. Jos affiinit kuvaukset eivät ole tuttuja, aika oppia ne on nyt! Kuvausmatriisien hallinta on käytännössä ehdoton vaatimus modernin OpenGL:n taitamiselle.

Shaderit

Matriisien ohella shaderit ovat suurin muutos. Fixed pipeline hoiti käyttäjän puolesta geometrian transformoinnin ja pikselien piirtämisen; programmable pipelinessä kaikki tehdään itse. Tämä mahdollistaa valtavan paljon enemmän kontrollia siihen mitä ollaan tekemässä: vanha OpenGL käytännössä vaan konfiguroitiin (uploadattiin geometria ja tekstuurit, asetettiin valot ja transformaatiot) ja sen jälkeen OpenGL hoiti loput; lopputulokseen ei voinut vaikuttaa kauheasti. Kaikki tämä on nyt toisin ja ainakin ne, joilla on softwarerenderöintitaustaa, iloinnevat tästä! Shaderin kirjoittajalla on täysi valta (ja toisaalta myös vastuu) piirtää geometria juuri sillä tavalla kuin huvittaa.

Shaderit ovat käytännössä pienia C-kieltä muistuttavia OpenGL Shader Language (“GLSL”) ohjelmia joita ajetaan erittäin tehokkaissa shader prosessoreissa näytönohjaimella. GLSL:n eri versiot ovat keskenään hieman erilaisia; versio 1.20 on yhteensopiva OpenGL ES 2.0:n kanssa. OpenGL ES 2.0:ssa näitä shadereita on kahdenlaisia; ns. Vertex Shaderit sekä Fragment Shaderit. Rutkasti tiivistäen shaderit toimivat seuraavasti: vertex shader -ohjelmaa kutsutaan (ajurin/raudan toimesta) kerran per polygonin vertex. Vertex shader hoitaa geometrian transformoinnin ja voi laskea lisää asioita kuten valaistuksia, distance fogia jne. Vertex shaderin laskutoimitusten lopputulokset (outputit) interpoloidaan yli piirrettävänä olevan polygonin ja tarjoillaan inputteina fragment shaderille “varying” muuttujina. Fragment shader laskee näiden avulla lopullisen piirrettävän pikselin (“fragmentin”) väriarvon. Tämä on fragment shaderin ainoa output. Nyrkkisääntönä, tee kaikki laskenta minkä voit vertex shaderissa ja säilytä fragment shader mahdollisimman yksinkertaisena.

Esimerkki Vertex Shaderista:

// "Attribuutteja" jotka lähetetään shaderille OpenGL:stä
// automaattisesti jahka ne on enabloitu
attribute vec4 position; // transformoimaton "object space" koordinaatti
attribute vec3 normal; // transformoimaton "object space" vertex normaali
attribute vec2 tex_coords; // tekstuurikoordinaatit u,v

// "Uniformeja"; OpenGL:stä manuaalisesti lähetettyjä arvoja jotka ovat
// samat koko piirrettävälle geometrialle
uniform mat4 modelViewProjectionMatrix;
uniform mat3 normalMatrix;

// Fragment shaderin outputit
varying lowp float ndotVarying;
varying mediump vec2 texCoordsVarying;

// Vakio
const vec3 lightPosition = vec3(0.0, 0.0, 1.0);

void main()
{
  // Lasketaan valaistusarvo pinnan normaalin ja valon suunnan välisestä
  // kulmasta kosinilla (dot()) joka rajataan väliin 0..1
  vec3 eyeNormal = normalize(normalMatrix * normal);
  float ndot = max(0.0, dot(eyeNormal, normalize(lightPosition)));

  // Valmistellaan outputit; näiden interpoloidut arvot päätyvät
  // fragment shaderille inputeiksi
  ndotVarying = ndot;
  texCoordsVarying = tex_coords;

  // Geometrian transformointi. gl_Position on varattu sana output
  // muuttujalle johon tämä informaatio kirjoitetaan.
  gl_Position = modelViewProjectionMatrix * position;
}

Esimerkki Fragment Shaderista:

uniform lowp sampler2D texture; // OpenGL:stä passattu "handle" tekstuuriin

// Shaderin inputit; nämä laskettiin fragment shaderissa, interpoloitiin
// raudan toimesta ja ovat nyt tarjolla lopullisen pikselin värin laskemiseksi
varying lowp float ndotVarying;
varying mediump vec2 texCoordsVarying;

void main()
{
  // Tämän shaderin tapauksessa lopullinen pikselin väri lasketaan seuraavasti:
  // sampletaan tekstuurista oikealta kohdalta pikseli ja kerrotaan se
  // valoisuusarvolla (0..1). Lopputulos on kauniisti Lambert/flat shadeutuva
  // pinta jossa valo paistaa katselusuunnasta.
  highp vec4 color = texture2D(texture, texCoordsVarying) * ndotVarying;

  // gl_FragColor on (kuten kaikki gl_ prefiksin GLSL nimet) varattu sana
  // muuttujalle johon lopullinen fragment shaderin output kirjoitetaan.
  gl_FragColor = color;
}

Muut asiat

Koska meillä on shaderit ja niiden myötä valta piirtää miten haluamme, monia vanhan OpenGL:n kutsuja ei tarvita enää mihinkaan. Esimerkkeinä vaikkapa glEnableClientState() ja glEnable(GL_TEXTURE_2D). Lisäksi GLUT-kirjastoa ei ole tarjolla suoraan; matriisikirjaston onkin kyettävä tarjoaamaan toiminnallisuus paitsi rotaatioiden/translaatioiden myös perspektiivimatriisien luomiseksi. Ideaalinen tila tietysti on että kehittäjä tietää miten glFrustum() toimii. Shadereihin liittyvät attributet ja uniformit pähkinänkuoressa: attributet ovat mekanismi tuoda geometriaan liittyvä data vertex shaderille. Niitä hallitaan glBindAttribLocation() / glEnableVertexAttribArray() / glVertexAttribPointer() kutsuilla. Uniformit taas ovat kehittäjän shadereilleen syöttämää dataa jotka ovat yhtenäisiä (tästä nimi..) läpi koko kappaleen (tai sen osan, tai usean kappaleen) geometrian, esimerkiksi transformaatiomatriisit, tekstuurit jne. Uniformeja hallitaan glGetUniformLocation() / glUniform*() kutsuilla.

Toteutus: OpenGL ES 2.0 “Hello, World”

Uusi Xcode ja iOS 5.0 ovat tehneet tämän helpoksi; Xcodesta löytyy projektinluontiwizard jolla syntyy toiminnallisuudeltaan täydellinen OpenGL ES 2.0 sovellus shadereineen kaikkineen. Wizardin luoma toteutus piirtää sinisen ja punaisen laatikon jotka pyörivät toistensa sekä omien keskipisteidensä ympäri. Toinen laatikko piirretään GLSL:llä, toinen GLKitin avulla. GLKit on hyvin laaja ja ominaisuusrikas OpenGL-apukirjasto joka
esiteltiin iOS 5.0 päivityksen myötä. Wizardin/GLKitin käytössä on puolensa; toisaalta pääsee nopeasti liikkeelle toimivalla rungolla, mutta toisaalta se myös tarjoaa kehittäjälle mahdollisuuden olla oikeasti oppimatta mitä konepellin alla tapahtuu.

Tässä harjoituksessa luodaan wizardilla oletusprojekti ja muokataan sita siten
että sininen kuutio piirretään tekstuurimapattynä väritetyn sijaan.
Valitaan Xcode:sta File > New > New Project.. ja valitaan GLKit wizard templaatiksi:

Korvataksemme alkuperäisen kovakoodatun värityksen tekstuurilla, on tehtävä
jokunen asia:

  1. Vertex attribuutteihin lisätään tekstuurikoordinaatit; muuttuja gCubeVertexData[] sisältämään taulukkoon lisätään u,v arvot ja muutetaan sitä käyttäviä osia koodissa sopivasti. (Tähän liittyen, omasta mielestäni on paljon elegantimpaa käyttää C:n structia kuin float-arrayta vertex attributejen kuvaamiseen).
  2. Lisätään attribute ATTRIB_TEXCOORD ja uniform UNIFORM_TEXTURE sekä niiden vaatimat muutokset muualle koodiin.
  3. Ladataan tekstuuri kuvatiedostosta ja uploadataan se OpenGL:n puskureihin;kuvan lataaminen tehdaan UIImage:lla ja byte datan ekstraktoimiseen käytetään seuraavaa apukirjastoa: UIImage-Conversion (Huom., GLKitin mukana voi hyvinkin olla jouhevampikin tapa tehdä tämä).
  4. Muutetaan shaderien koodi tukemaan tekstuuria; katso Shader-esimerkit yllä!

Kuvakaappaukset ennen ja jälkeen muutosten:

Wizardin generoima koodi; kuutiot piirretään pelkin värein
Wizardin generoima koodi; kuutiot piirretään pelkin värein
Muokattu koodi; toinen kuutio piirretään teksturoituna
Muokattu koodi; toinen kuutio piirretään teksturoituna

 

 

 

 

 

 

 

 

 

 

Esimerkin lähdekoodi Xcode 4.2 projektina (598kB)

 

Loppusanat

OpenGL -osaaminen on jossain määrin harvinaista mobiilikehittäjien joukossa, luultavasti lähinnä siksi että sen sovellusalueet ovat olleen rajalliset. Asiaa ei auta korkeahko oppimiskynnys. Sen tarjoamat mahdollisuudet ovat kumminkin valtavat; shadereissa voidaan suorittaa mitä tahansa laskentaa rinnakkain CPUn corejen kanssa – mahdollistaen vaikkapa reaaliaikaisen signaalinkäsittelyn sijoittamisen pois itse suorittimelta.

Tämä artikkeli aloittaa 3D grafiikka / OpenGL ES 2.0 -juttusarjan; seuraavalla kerralla katsotaankin sitten jotain mielekkäämpää kuin ihan perusteita! Kysymyksia, palautetta, muuta sanottavaa? Jätä kommentti!

Lisää luettavaa

WikiBooks: Modern OpenGL Introduction

Lighthouse GLSL Tutorials