OpenGL:n perusteet - Osa 3: Teksturointi
OpenGL on laajassa käytössä oleva käyttöjärjestelmäriippumaton rajapinta 2D- ja 3D-grafiikan piirtoon. Tämä artikkelisarja opettaa sinulle 3D-grafiikan perusteet OpenGL:ää käyttäen. Esimerkeissä käytetään C\C++ kieltä. Tämä on artikkelisarjan kolmas osa.
7.7.2004 julkaistun artikkelin on kirjoittanut markus.
1. Johdatus tekstuureihin #
Monitahokkaalla voidaan siis helposti jäljitellä kappaleen muotoa, mutta entäpä sen pintakuviota? Tietysti voimme värittää monitahokkaan glColor3f()-funktion kutsuilla, mutta harvan kappaleen pinnan väritys on kauniin tasainen. Voisimme tietenkin jakaa monitahokkaan tahot yhä pienempiin ja pienempiin polygoneihin, jolloin saisimme aikaiseksi millaisen värikuvion tahansa, mutta vähänkään shakkilautaa monimutkaisemmissa kuvioissa polygonien määrä nousisi valtavan suureksi. Olisikin hyvä, jos voisimme päällystää monitahokkaan valmiilla kuvalla, joka sisältäisi tarvittavan pintakuvion. Juuri tätä varten ovat olemassa tekstuurit. Tekstuuri voidaan piirtää erikseen jollakin kuvankäsittelyohjelmalla tai se voidaan generoida ajon aikana. Esim. shakkilautakuvion tuottaminen algoritmillisesti ei ole kovin hankalaa.
Kuinka sitten määritellään miten tekstuuri asettuu monitahokkaan pinnalle? Tämä tehdään tekstuurikoordinaattien avulla. Jokaiselle verteksille määritellään sen sijainnin lisäksi myös missä kohtaa tekstuuria se sijaitsee. Tekstuurin origo sijaitsee sen vasemmassa alakulmassa. X-akseli kasvaa oikealle ja Y-akseli ylöspäin. Siis tavallisen koordinaatiston tapaan. Huomattavaa kuitenkin on, että riippumatta tektuurin koosta tai sen leveyden ja korkeuden suhteesta oikean ylänurkan koordinaatit ovat aina ( 1, 1 ).
Tekstuurikoordinaatit annetaan OpenGL:lle glTexCoord2f()-funktiolla. Funktiota kutsutaan jokaiselle verteksille erikseen. Sen prototyyppi näyttää tältä:
void glTexCoord2f(GLfloat s, GLfloat t);
s ja t ovat siis tekstuurikoordinaatin x ja y komponentit. Huomattavaa lisäksi on, että teksturointi täytyy myös laittaa päälle glEnable()-funktiolla, jolle annetaan parametrina GL_TEXTURE_2D. Käytimme tässä siis 2D-tekstuureja. On myös muita tekstuurityyppejä kuten 1D-tekstuurit. Silloin koordinaatti annettaisiin funktiolla glTexCoord1f() ja teksturointi laitettaisiin päälle antamalla glEnable()-funktiolle parametriksi GL_TEXTURE_1D.
Kuten glVertex-sarjan funktiolla annetut koordinaatit kerrottiin modelview-matriisilla, niin glTexCoord-sarjan funktioilla annetut koordinaatit kerrotaan tekstuurimatriisilla. Tätä matriisia voi muokata samoin kuin modelview-matriisiakin. Tekstuurimatriisi valitaan glMatrixMode()-funktiolla, jolle annetaan parametrina GL_TEXTURE ja sitä voi muokata samoilla funktioilla kuin kaikkia muitakin matriiseja eli esim. glTranslatef() ja glRotatef().
2. Tekstuuriobjektit #
OpenGL:ssä tekstuurit ovat näytönohjaimen muistissa sijaitsevia objekteja (olioita), jotka täytyy luoda ennen käyttöä. Tekstuurin luominen tehdään funktiolla glGenTextures(). Sen prototyyppi näyttää tältä:
void glGenTextures(GLsizei n, GLuint *textures);
Parametri n kertoo kuinka monta tekstuuriobjektia luodaan ja parametri textures on osoitin taulukkoon, johon luotujen tekstuurien tunnukset laitetaan.
Teksturoinnin hoitaa näytönohjaimen teksturointiyksikkö. Jotta teksturointi olisi mahdollista, pitää tekstuuri ensin sitoa tähän teksturointiyksikköön glBindTexture()-funktiolla. Sen prototyyppi näyttää tältä.
void glBindTexture(GLenum target, GLuint texture);
Texture on sidottavan tekstuurin tunnus eli se, joka saatiin glGenTextures()-funktiolta. Vain yksi tekstuuri voi olla sidottuna kerrallaan (tästä on tosin poikkeus, josta puhumme kappaleessa multiteksturointi)!
Target on sidottavan tekstuurin tyyppi. OpenGL:ssä on useita eri tekstuurityyppejä, mutta hyödyllisin niistä on GL_TEXTURE_2D eli 2D-tekstuuri, joka vastaa tavallista kuvaa. Luodessa tekstuuri on tyypitön ja tekstuuri omaa sen tyypin, jona se ensimmäisen kerran sidotaan teksturointiyksikköön.
Kun polygoni piirretään, teksturoidaan se sillä tekstuurilla, joka on sillä hetkellä sidottuna teksturointiyksikköön. Myös kaikki tekstuureihin vaikuttavat funktiot, joista tässä artikkelissa puhumme vaikuttavat siihen tekstuuriin, joka on sillä hetkellä sidottuna.
Luotu tekstuuri on kuitenkin tyhjä, eikä sisällä mitään dataa. Jos kokeilisit teksturoida tyhjällä tekstuurilla saisit tulokseksi pelkkää valkoista väriä. Niinpä tekstuuriin täytyy ladata dataa. 2D-tekstuuriin data ladataan funktiolla glTexImage2D(). Prototyyppi on tämän näköinen:
void glTexImage2D( GLenum target, GLint level, GLint components, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
Parametrin target arvon pitää olla aina GL_TEXTURE_2D.
Parametrien level ja border arvojen pitää olla nolla.
Parametri Components kertoo formaatin, jossa OpenGL tallentaa tekstuurin sisäisesti näytönohjaimen muistiin. Tämä voi olla joko tarkka formaatti tai haluttavienvärikomponenttien määrä, jolloin OpenGL valitsee automaattisesti sopivan formaatin. Pitääksemme asiat yksinkertaisena käytämme värikomponenttien määrää. Tämä on yleensä 3 (red, green ja blue). Joskus myös 4 (red, green, blue ja alpha), jos alphakanava on mukana, mutta alphakanavasta enemmän kappaleessa läpinäkyvyys.
Width ja height kertovat tekstuurin leveyden ja korkeuden. Näiden lukujen täytyy olla kakkosen potensseja eli 32, 64, 128, 256 jne. Näillä arvoilla on myös jokin yläraja, jonka saat selville funktiolla glGetIntegerv(), parametrilla GL_MAX_TEXTURE_SIZE. Hyvin vanhoissa näytönohjaimissa raja on 256, kun taas uusissa 4096 tai jopa enemmän.
Format kertoo tekstuuriin syötettävän datan muodon. Tämä on yleensä joko GL_RGB tai GL_RGBA jos alphakin on mukana. Ladattavan datan pitää siis sisältää tekstuurin kaikkien pikselien värit tyyliin RGBRGBRGB... jne. Eli ensimmäisen pikselin värin Red, Green ja Blue komponentit, sitten seuraavan jne. Jos tekstuurissa on alphakanava mukana (GL_RGBA), pitää datan taas olla muodossa RGBARGBARGBA... jne.
Type-parametri kertoo ladattavan datan tyypin. Vaihtoehdot ovat: GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, ja GL_FLOAT. Yleisin käytetty muoto on GL_UNSIGNED_BYTE.
Viimeinen parametri on osoitin varsinaiseen ladattavaan dataan, joka on esim. piirretty kuvankäsittelyohjelmalla tai generoitu jollakin algoritmilla. Jos datan tyyppi on GL_FLOAT pitää näiden arvojen olla välillä 0-1. Muuten suurin käytetyllä tyypillä esitettävissä oleva luku tulkitaan luvuksi 1 ja pienin luvuksi 0. Huomaa, että data ladataan siihen tekstuuriin, joka on sillä hetkellä sidottuna teksturointiyksikköön. Jos tekstuuri sisältää jo dataa, korvautuu se uudella.
Kun tekstuuri käy turhaksi, se voidaan poistaa funktiolla:
glDeleteTextures(GLsizei n, const GLuint * textures);
Parametrien merkitykset ovat samat kuin glGenTextures()-funktiossakin, eli poistettavien tekstuurien määrä ja osoitin taulukkoon, joka sisältää poistettavien tekstuurien tunnukset.
3. Tekstuurin reunat ja suodatus #
Mitä mahtaakaan tapahtua, jos annetut tekstuurikoordinaatit menevät tekstuurin reunojen ulkopuolelle? Sen voit valita itse. Vaihtoehdot ovat joko korvata ulkopuoliset pikselit jollain vakio värillä (mustalla), korvata ne lähimmällä reunapikselillä tai alkaa toistaa pikseleitä tekstuurin toisesta reunasta. Reunakäyttäytyminen asetetaan funktiolla glTexParameteri(). Prototyyppi näyttää tältä:
void glTexParameteri(GLenum target, GLenum pname, GLint param);
Ensimmäinen parametri on tekstuurin tyyppi (tässä tapauksessa GL_TEXTURE_2D). Toisen parametrin on oltava GL_TEXTURE_WRAP_S tai GL_TEXTURE_WRAP_T. Se ilmaisee asetetaanko käyttäytyminen vaaka vai pystyakselille. Voit siis laittaa kummallekkin akselille erilaisen. Viimeinen parametri on GL_CLAMP, GL_CLAMP_TO_EDGE, tai GL_REPEAT. Vaihtoehdot on kuvattu alla olevassa kuvassa.
Itseasiassa GL_CLAMP:n käyttäytyminen on hieman erilainen kuin tässä annetaan ymmmärtää ja kappaleessa 6 kerrotusta syystä GL_CLAMP_TO_EDGE on määritelty tiedostosssa glext.h eikä gl.h kuten muut, mutta asiat selviävät tarkemmin virallisista spekseistä jos kiinnostaa.
3D-korttien alkuaikoina oli Voodoo-korttien ajureissa bugi, jonka takia GL_CLAMP käyttäytyi samoin kuin GL_CLAMP_TO_EDGE. Monet sovelluksen tekijät luottivat sokeasti tähän käyttäytymiseen tarkistamatta asiaa dokumentaatiosta ja näin ollen kun ohjelmia käytettiin korteilla, joissa tätä bugia ei ollut, oli grafiikka joskus virheellistä. Tämän ja sen takia, että GL_CLAMP:n oikeaoppinen käyttäytyminen on käytännössä hyödytön, monet näytönohjainvalmistajat jättivät mahdollisuuden laittaa tämä bugi päälle omissa näytönohjaimissaan.
Kun teksturoitua kappaletta katsotaan niin läheltä, että yksi tekstuurin kuvapiste eli tekseli näyttää suuremmalta kuin pikseli, kuva ns. puuroutuu eli muuttuu (lego)palikkamaiseksi. Asia voidaan korjata suodatuksella, joka saadaan päälle tutulla glTexParameteri()-funktiolla, joka saa toisena parametrinaan GL_TEXTURE_MAG_FILTER ja viimeisenä parametrinaan joko GL_NEAREST, jos suodatus halutaan pois päältä, tai GL_LINEAR jos suodatus halutaan päälle. Oletuksena suodatus on jo valmiina päällä.
Vastaavasti kun kappale on niin kaukana, että yhden pikselin alalle mahtuu monta tekseliä alkaa kuva ”vilkkua”. Tähänkin ongelmaan on ratkaisu. Sitä kutsutaan termillä ”mipmapping”. Ideana on tallentaa tekstuurista monta erikokoista versiota ja valita niistä sitten se, joka lähinnä vastaa oikeaa kokoa. Mipmapping asetetaan päälle glTexParameteri()-funktiolla. Tällä kertaa keskimmäisenä parametrina on GL_TEXTURE_MIN_FILTER ja viimeisenä GL_LINEAR_MIPMAP_LINEAR. Kun mipmapping halutaan pois päältä, käytetään GL_LINEAR_MIPMAP_LINEAR parametrin sijasta parametria GL_LINEAR tai GL_NEAREST, riippuen siitä, halutaanko myös kaukana olevat tekselit suodattaa. Oletuksena mipmapping on päällä.
Lisäksi meidän on generoitava mipmap:it. OpenGL osaa tehdä tämän automaattisesti useammallakin eri tavalla, mutta helpoimmalla pääset kun lataat datan glTexImage2D()-funktion sijasta gluBuild2Dmipmaps()-funktiolla. Sen prototyyppi on seuraavanlainen.
int gluBuild2DMipmaps(
GLenum target,
GLint components,
GLint width,
GLint height,
GLenum format,
GLenum type,
const void * data
);
Parametrien merkitykset ovat samat kuin glTexImage2D()-funktiollakin.
Koska mipmapping on oletuksena päällä, törmää usein tilanteeseen, jossa unohtaa generoida mippappien tarvitseman datan (käyttää siis pelkästään funktiota glTexImage2D). Tämän takia mipmaping tulee muistaa kääntää pois päältä aina, kun sitä ei tarvitse.
4. Läpinäkyvyys #
Yksi kysymys, joka usein nousee esille on, kuinka jokin tietty tekstuurin väri saadaan läpinäkyväksi. Mitään väriä ei voida suoraan asettaa läpinäkyväksi, vaan värille täytyy määrittää tavallisten red, green ja blue komponenttien lisäksi vielä neljäs läpinäkyvyyskomponentti eli ns. alphakomponentti. Useimmat kuvankäsittelyohjelmat kuten Paint Shop Pro ja Photo Shop tukevat alphakanavaa. Kuvaformaateista esim. TGA ja PNG tukevat alphakanavaa.
Voit myös generoida alphakanavan ajon aikana. Kun lataat tekstuurin, testaa onko pikselin väri se, jonka haluat läpinäkyväksi. Jos on aseta sen alpha nollaksi, muuten ykköseksi (tai jos et käytä GL_FLOAT-tyyppiä, niin käyttämäsi tyypin maksimi arvoon).
Kuvassa vasemmalla tekstuuri, joka sisältää kuvan avaruushirviöstä. Keskellä tämän tekstuurin alphakanava. Oikealla tekstuuri piirretty taustan päälle, niin että pikselit joiden alphan arvo on pienempi kuin 0.5 (musta) on jätetty piirtämättä.On kaksi tapaa saada aikaan läpinäkyvyys alphan avulla. Ne ovat GL_ALPHA_TEST ja GL_BLEND. Ne voidaan laittaa päälle glEnable()-funktiolla antamalla kyseinen symboli parametrina.
GL_ALPHA_TEST:n ideana on asettaa funktio, joka joko hyväksyy tai hylkää pikselin sen alphan arvon perusteella. Tämä funktio asetetaan glAlphaFunc()-funktiolla. Sen prototyyppi näyttää tältä:
void glAlphaFunc(GLenum func, GLclampf ref);
Ensimmäinen parametri kertoo funktion. Sen mahdolliset arvot ovat GL_NEVER, GL_LESS, GL_EQUAL, GL_GREATER ja GL_ALWAYS. Nimestä pystyy helposti näkemään mitä ne tekevät. Viimeinen kertoo tämän funktion käyttämän vertailuarvon. Esim. jos haluaisimme, että pikseli piirretään vain kun sen alphan arvo on yli 0.5 käyttäisimme kutsua glAlphaFunc(GL_GREATER, 0.5);.
GL_BLEND:n idea on hieman erilainen. Siinä pikselin lopullinen väri lasketaan sen alkuperäisestä väristä ja ikkunassa pikselin paikalla olevan aikaisemman pikselin väristä. Lopullinen väri lasketaan kaavalla S*uusiPikseli+D*vanhaPikseli. Parametrit S ja D asetetaan funktiolla glBlendFunc(). Prototyyppi on seuraavan näköinen:
glBlendFunc(GLenum sfactor, GLenum dfactor);
sFactor asettaa S parametrin ja sFactor D parametrin. Kummatkin arvot on valittava seuraavista: GL_ZERO, GL_ONE, GL_DST_COLOR, GL_SRC_COLOR, GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA ja GL_ONE_MINUS_DST_ALPHA. Nimistä on helppo päätellä mitä ne ovat, kun panet merkille, että SCR tarkoittaa uutta pikseliä ja DST vanhaa pikseliä. Esim. jos haluaisimme tekstuurin alphakanavan määräävän pikselin läpinäkyvyyden käyttäisimme kutsua: glBlendFunc(GL_SCR_ALPHA, GL_ONE_MINUS_SCR_ALPHA). GL_BLEND siis mahdollistaa osittaisen läpinäkyvyyden toisin kuin GL_ALPHA_TEST.
GL_ALPHA_TEST:llä ja GL_BLEND:llä on vielä yksi merkittävä ero. GL_ALPHA_TEST nimittäin jättää läpinäkyvät pikselit piirtämättä syvyyspuskuriin, kun taas GL_BLEND piirtää kaikki pikselit syvyyspuskuriin. Myös täysin läpinäkyvät. Olettaen tietenkin, että GL_DEPTH_TEST on päällä.
Kaksiulotteista teksturoitua suorakaiteen muotoista osittain läpinäkyvää polygonia kutsutaan joskus kuvahahmoksi (englanniksi sprite). Ne ovat varsin käytännöllisia 2D-grafiikassa. Kaksiulotteisten kuvien piirtoon löytyy OpenGL.stä ihan oma funktionsa: glBitmap(), mutta se on hitaampi ja vähemmän käytännöllinen kuin teksturoidun nelikulmion piirto, joten en suosittele sen käyttöä.
5. Texture environment #
Polygoneillehan voitiin määrätä väri glColor3f()-funktiolla, mutta kuinka tämä väri sitten yhdistyy tekstuurin väriin? Tästä asiasta huolehtii ”texture environment”. Texture environment on itse asiassa funktio, joka saa syötteenään varsinaisen värin ja tekstuurin värin ja laskee näistä pikselin lopullisen värin. Tämä funktio pitää valita muutamasta valmiista vaihtoehdosta. Texture environment:in funktio valitaan funktiolla glTexEnvi(), jonka prototyyppi näyttää tältä:
void glTexEnvi(GLenum target, GLenum pname, GLint param);
Ensimmäisen parametrin arvon on aina oltava GL_TEXTURE_ENV ja toisen GL_TEXTURE_ENV_MODE. Kolmas on asetettava funktio. Sen mahdolliset arvot ovat: GL_MODULATE, GL_DECAL ja GL_ADD. GL_MODULATE kertoo värit keskenään ja GL_ADD taas laskee ne yhteen. GL_DECAL sen sijaan interpoloi näiden arvojen välillä käyttäen viitteenä tekstuurin alphakanavaa. Alphan arvolla yksi väri on tekstuurin väri ja arvolla nolla varsinainen väri. Muilla arvoilla jossakin tässä välissä. Jos tekstuurissa ei ole alphakanavaa, katsotaan alphan arvon olevan yksi.
6. OpenGL:n laajennukset #
Ennen kuin voimme jatkaa, pitää meidän puhua jokunen sana OpenGL:n laajennuksista (englanniksi extensions).
Näytönohjaimet kehittyvät koko ajan ja niihin ilmestyy uusia ominaisuuksia. Tämän takia myös niitä käyttävien rajapintojen on kehityttävä mukana. Direct3D:n tapauksessa tämä on ratkaistu julkaisemalla siitä uusi versio vuoden parin välein. Tällä uudella versiolla ei ole välttämättä mitään yhteistä vanhan kanssa ja koko rajapinta on pahimmillaan opeteltava uudestaan. OpenGL:ssä sen sijaan rakennetaan vanhan päälle. Aina, kun jokin uusi ominaisuus ilmestyy näytönohjaimiin, julkaistaan siitä laajennus OpenGL:ään. Laajennus tuo mukanaan nipun uusia funktioita ja/tai uusia parametrejä vanhoihin funktioihin. Joskus ei kumpaakaan. Laajennuksista pitää kirjaa SGI:n laajennusrekisteri osoitteessa: http://oss.sgi.com/projects/ogl-sample/r... Lisäksi kaikkien näytönohjainvalmistajien kotisivuilta on löytyy sama rekisteri.
Voidaksesi käyttää laajennuksia tarvitset uuden otsikkotiedoston glext.h. Saat sen osoitteesta: http://oss.sgi.com/projects/ogl-sample/A... Se mitä laajennuksia käytössä on riippuu koneen näytönohjaimesta. Uudet näytönohjaimet tukevat lähes kaikkia laajennuksia, kun taas hyvin vanhat vain muutamaa. Saat selville tuetut laajennukset funktiolla glGetString(), jolle annetaan parametrinä GL_EXTENSIONS. Se palauttaa osoittimen merkkijonoon, joka sisältää kaikkien tuettujen laajennusten nimet välilyönnillä eroteltuna.
Laajennus ei siis tuo välttämättä mitään uusia funktioita tai parametrejä. Se vain sallii jotain sellaista, joka oli aikaisemmin kiellettyä. Hyvä esimerkki tästä on ARB_texture_non_power_of_two-laajennus, joka sallii käytettävän tekstuureja, joiden koko ei ole kakkosen potenssi. Eli jos glGetString(GL_EXTENSIONS) palauttamassa merkkijonossa esiintyy nimi GL_ARB_texture_non_power_of_two voit käyttää minkä kokoisia tekstuureja tahansa.
Jos taas laajennus tuo uusia parametrejä ei laajennuksen käyttö ole hankalaa silloinkaan. Kaikki uudet symbolit on nimittäin määritelty tiedostossa glext.h. Hyvä esimerkki tällaisesta laajennuksesta on GL_ARB_texture_env_combine-laajennos, joka tuo texture environment:iin rutkasti lisää vaihtoehtoja perinteisten GL_MODULATE:n ja GL_ADD:n lisäksi.
Vasta, kun laajennus tuo mukanaan uusia funktioita, on laajennuksen käyttö hieman hankalampaa. Funktioihin pitää nimittäin käydä noutamassa osoittimet. wglGetProcAddress() funktiolla. Sen prototyyppi näyttää tältä:
PROC wglGetProcAddress(
LPCSTR lpszProc //Name of the extension function
);
Eli sille annetaan parametrinä uuden funktion nimi ja se palauttaa osoittimen siihen.
Laajennuksia on tällä hetkellä useampi sata, joten niiden hallinnointi käsin alkaa olla sulaa hulluutta. Tämän takia netti on pullollaan erilaisia kirjastoja laajennusten hallintaan. Niiden periaate on, että ne tarjoavat jonkin helppokäyttöisen funktion, joka lataa yhdellä kutsulla osoittimet kaikkiin näytönohjaimen tukemiin laajennuksiin. Yksi hyvä kirjasto tähän on GLEW: http://glew.sourceforge.net/ .
Suosittelen lämpimästi, että heti, kun alat tarvita laajennuksia, lataa GLEW ja käytä sitä. ÄLÄ ala pelleilemään niiden kanssa ”käsin”. GLEW:n avulla sinun tarvitsee tehdä yksi ainut funktio kutsu ja kaikki laajennukset ovat suoraan käytössäsi.
Huomaa vielä, että jokaisella laajennuksen nimellä on jokin etuliite. Esim. ARB, EXT, NV tai ATI. ARB ja EXT ovat laajennuksia, joita kaikkien näytönohjainvalmistajien kortit tukevat. Kun taas muut ovat valmistajakohtaisia laajennuksia. Vältä niiden käyttöä, sillä ne ovat tuettuja yleensä vain yhden valmistajan kortilla. Pyri käyttämään ensisijaisesti ARB-laajennuksia ja toissijaisesti EXT-laajennuksia.
7. Eksoottisemmat tekstuurimuodot #
1D- ja 2D-tekstuurien lisäksi OpenGL:ssä kaksi muutakin tekstuurimuotoa: 3D-tekstuurit (GL_TEXTURE_3D_EXT) ja cubemap-tekstuurit (GL_TEXTURE_CUBE_MAP_ARB). Kummatkin nämä tekstuurimuodot ovat laajennuksia eli sinun pitää tarkistaa ennen niiden käyttöä, että käytössä oleva näytönohjain tukee niitä.
7.1 3D-tekstuurit
3D-tekstuurit ovat looginen jatko 1D- ja 2D-tekstuureille. 3D tekstuurit ovat laajennus OpenGL:ään ja tämän laajennuksen nimi on GL_EXT_texture3D. Eli niitä voidaan käyttää vain, jos glGetString(GL_EXTENSIONS)-kutsun palauttama merkkijono sisältää kyseisen nimen. Tämä laajennus tuo mukanaan yhden uuden funktion glTexImage3DEXT(), jonka prototyyppi näyttää tältä:
void glTexImage3DEXT(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid* pixels);
Meidän tarvitsee siis hakea osoitin tähän funktioon ennen sen käyttöä. Se tehdään seuraavasti:
void (*glTexImage3DEXT)(GLenum, GLint, GLenum, GLsizei, GLsizei,
GLsizei, GLint, GLenum, GLenum, const GLvoid*) =
wglGetProcAddress("glTexImage3DEXT ");
Jätin tyyppimuunnoksen selkeyden vuoksi tekemättä. Tai helpommin:
PFNGLTEXIMAGE3DEXTPROC glTexImage3DEXT=
(PFNGLTEXIMAGE3DEXTPROC )wglGetProcAddress("glTexImage3DEXT ");
PFNGLTEXIMAGE3DEXTPROC-typedef on määritelty tiedostosssa glext.h. Kun osoitin on haettu voidaan funktiota käyttää normaalisti. glTexImage3DEXT()-funktio toimii aivan samoin kuin glTexImage1D() ja glTexImage2D():kin. Siinä vain on tekstuurin leveyden ja korkeuden lisäksi mukana syvyys. Lisäksi tekstuurikoorndinaatit pitää antaa glTexCoord3f()-funktiolla ja teksturointi laittaa päälle kutsulla glEnable(GL_TEXTURE_3D_EXT);
7.2 Cube map-tekstuurit
"Cube map"-tekstuuri on nimensä mukaisesti kuution muotoinen tekstuuri. Se muodostuu kuudesta eri 2D-tekstuurista, joista jokainen muodostaa kuution yhden sivun. Myös cube map on laajennus. Sen nimi on ARB_texture_cube_map. Cube map ei tuo uusia funktioita ainoastaan uusia parametrejä.
Data cube map:iin ladataan glTexImage2D()-funktiolla. Ainoa ero on, että ensimmäiseksi parametriksi tulee GL_TEXTURE_2D:n sijaan TEXTURE_CUBE_MAP_POSITIVE_X_ARB, TEXTURE_CUBE_MAP_NEGATIVE_X_ARB, TEXTURE_CUBE_MAP_POSITIVE_Y_ARB, TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB, TEXTURE_CUBE_MAP_POSITIVE_Z_ARB tai TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB, riippuen siitä mikä kuution kuudesta eri sivusta halutaan asettaa. Vastaavasti cube map teksturointi laitetaan päälle glEnable()-functiolla, jolle annetaan parametrina TEXTURE_CUBE_MAP_ARB.
Tekstuurikoordinaatit toimivat cube map:issa hieman oudosti. Tekstuurikoordinaatti annetaan glTexCoord3f()-funktiolla, mutta koordinaattia vastaa kuutiolla piste, joka saadaan kuution keskipisteestä lähtevän glTexCoord3f()-funktiolla annetun vektorin suuntaisen suoran ja kuution leikkauspisteestä.
8. Multiteksturointi #
Multiteksturointi tarkoittaa mahdollisuutta päällystää polygoni usealla tekstuurilla yhtä aikaa. Näytönohjaimissa on ns. teksturointiyksikköjä joista jokaiseen voi sitoa oman tekstuurin. Kun pikseli sitten piirretään vaikuttaa sen väriin jokainen teksturointiyksikkö.
Nykyisissä näytönohjaimissa voi olla jopa 8-16 teksturointiyksikköä, kun taas vanhoissa yleensä vain 2-4. Saat selville näytönohjaimen teksturointiyksiköiden määrän glGetIntegerv()-funktiolla, jolle annetaan parametrina GL_MAX_TEXTURE_UNITS_ARB. Kuten muutkin nykyiset hienoudet on myös multiteksturointi laajennus. Multiteksturointi tuo OpenGL:ään useitakin uusia funktioita kuten:
void glMultiTexCoord1fARB(GLenum texture, GLfloat s); void glMultiTexCoord2fARB(GLenum texture, GLfloat s, GLfloat t); void glActiveTextureARB(GLenum texture);.
Jokaiseen teksturointiyksikköön voi olla sidottuna kerrallaan yksi tekstuuri. glBindTexture() sitoo tekstuurin kulloinkin aktiivisena olevaan teksturointi yksikköön. Yksi teksturointiyksiköistä voi olla aktiivisena kerrallaan ja aktiivinen yksikkö valitaan glActiveTextureARB()-funktiolla joka saa syötteenään GL_TEXTURE0_ARB, GL_TEXTURE1_ARB, GL_TEXTURE2_ARB jne. eli aktivoitavan teksturointi yksikön. Huomaa myös, että kutsu glEnable(GL_TEXTURE_2D) laittaa teksturoinnin päälle siihen teksturointiyksikköön, joka on sillä hetkellä aktiivisena, eli funktiota on kutsuttava jokaiselle teksturointiyksikölle erikseen.
Tekstuurikoordinaatit vuorostaan annetaan glMultiTexCoord2fARB()-funktiolla. Sen ensimmäinen parametri kertoo teksturointiyksikön samoin kuin glActiveTextureARB()-funktiollakin ja loput tekstuurikoordinaatit. glTexCoord2f()-funktion kutsu vastaa glMultiTexCoord2fARB()-funktion kutsua, jonka ensimmäinen parametri on GL_TEXTURE0_ARB.
Vielä viimeinen ongelma on, mikä sitten määrää kuinka nämä tekstuurit yhdistyvät? Vastaus on tietenkin teksture environment. glTextureEnvi()-funktio asettaa texture environment funktion aktivoituna olevaan teksturointiyksikköön. Jokaisella teksturointiyksiköllä on siis oma texture environment funktio. Kun pikseli piirretään saa se aluksi glColor3f()-funktiolla annetun värin. Sen jälkeen pikselin väriin vaikuttaa vuoronperään jokainen teksturointiyksikkö, jolle teksturointi on laitettu päälle, omalla texture environment funktiollaan.
9. Esimerkkiohjelma #
Vaikka tässä artikkelissa puhuimmekin OpenGL:än laajennuksista ja monista edistyneimmistä teksturointitekniikoista on esimerkkiohjelma varsin yksinkertainen.
Se lataa levyltä kaksi tekstuuria tiedostoista ”alien.kuva” ja ”tausta.kuva”. Ohjelma piirtää kaksi nelikulmaista polygonia, joista toisen se teksturoi alien-tekstuurilla ja toisen taustatekstuurillla.
Data näissä tiedostoissa on ns. raakamuodossa eli se voidaan antaa sellaisenaan glTexImage2D()-funktiolle. Raakadatakuvia voi tallentaa yleisimmillä kuvankäsittelyohjelmilla. Tällaisten raakatiedostojen käyttö ei kuitenkaan ole suositeltavaa sillä ne eivät sisällä esimerkiksi tietoa kuvan koosta, joten se on tiedettävä erikseen.
Yleensä kannattaakin käyttää jotain hyvää standardia tiedostomuotoa kuten JPG tai PNG. Näiden formaattien lataamisesta voisi kuitenkin kirjoittaa vaikka oman artikkelinsa, joten käytän tässä helpompaa raakadataformaattia. On olemassa valmiita kirjastoja, jotka osaavat lukea lukuisia kuvaformaatteja. Yksi hyvä tällainen on DEVIL. DEVIL kirjaston käytöstä on tässä artikkelissa liite seuraavalla sivulla.
Saadaksesi esimerkkiohjelman toimimaan sinun tarvitsee imuroida kuvatiedostot ( http://www.suomipelit.com/files/artikkel... ). Paketti sisältää myös oheisen lähdekoodin ja valmiiksi käännetyn version.
#include <windows.h>
#include <stdio.h>
#include <math.h>
#include <gl\gl.h>
#include <gl\glu.h>
//#include <gl\glext> // Ei tarvita tässä ohjelmassa
// Määrittele laitekonteksti globaaliksi sitä nimittäin tarvitaan myös pääfunktiossa.
HDC hdc;
// Viestinkäsittelijä
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
// Koska piirrämme ikkunan sisällön pääsilmukassa jatkuvasti uudelleen
// reakoimme WM_PAINT-viestiin vain tyhjentämällä ikkunan mustaksi.
case WM_PAINT:
{
PAINTSTRUCT p;
BeginPaint(hwnd, &p);
glClear(GL_COLOR_BUFFER_BIT);
SwapBuffers(hdc);
EndPaint(hwnd, &p);
return 0;
}
// Ikkuna yritetään sulkea kutsu PostQuitMessage()-funktiota.
case WM_CLOSE:
{
PostQuitMessage(0);
return 0;
}
// Käsittele myös WM_SIZE se lähetetään ikkunalle aina kun sen kokoa muutetaan.
// Tämä on oiva tilaisuus muuttaa viewport
// oikean kokoiseksi peittämään koko ikkuna.
case WM_SIZE:
{
// Ikkunan uusi koko saadaan lParam parametrista LOWORD ja HIWORD makroilla.
glViewport(0, 0, LOWORD(lParam), HIWORD(lParam));
return 0;
}
}
// Viestiä ei käsitelty kutsu DefWindowProc()-funktiota.
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int luoIkkuna(unsigned int leveys, unsigned int korkeus,
char *otsikko)
{
// Rekisteröi ikkunaluokka
WNDCLASS wc;
memset(&wc, 0, sizeof(WNDCLASS));
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.hCursor= LoadCursor(NULL, IDC_ARROW);
wc.lpfnWndProc = (WNDPROC) WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = "OpenGLtutoriaali";
if (!RegisterClass(&wc)) return 0;
// Luo ikkuna
RECT r;
r.left=GetSystemMetrics(SM_CXSCREEN)/2-leveys/2;
r.top=GetSystemMetrics(SM_CYSCREEN)/2-korkeus/2;
r.right=r.left+leveys;
r.bottom=r.top+korkeus;
AdjustWindowRectEx(&r,
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
FALSE, WS_EX_APPWINDOW);
HWND hwnd;
hwnd=CreateWindowEx(WS_EX_APPWINDOW,
"OpenGLtutoriaali", otsikko,
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
r.left, r.top, r.right-r.left, r.bottom-r.top,
NULL, NULL, GetModuleHandle(NULL), NULL);
// Luo laitekonteksti
hdc=GetDC(hwnd);
if (!hdc) return 0;
// Valitse pikseliformaatti
PIXELFORMATDESCRIPTOR pfd;
memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize=sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion=1;
pfd.dwFlags=PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER;
pfd.iPixelType=PFD_TYPE_RGBA;
pfd.cRedBits=8;
pfd.cGreenBits=8;
pfd.cBlueBits=8;
pfd.cAlphaBits=8;
pfd.cStencilBits=8;
pfd.cDepthBits=16;
pfd.iLayerType=PFD_MAIN_PLANE;
int pixelFormat;
pixelFormat=ChoosePixelFormat(hdc, &pfd);
if (!pixelFormat) return 0;
if (!SetPixelFormat(hdc, pixelFormat, &pfd)) return 0;
// Luo renderöintikonteksti
HGLRC hrc;
hrc=wglCreateContext(hdc);
if (!hrc) return 0;
if (!wglMakeCurrent(hdc, hrc)) return 0;
// Tuo ikkuna näkyviin
ShowWindow(hwnd, SW_SHOW);
SetForegroundWindow(hwnd);
SetFocus(hwnd);
// Palauta onnistuminen
return 1;
}
// Pääfunktio
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
unsigned char *data;
float angle=0;
GLuint taustaTekstuuri=0;
GLuint alienTekstuuri=0;
FILE *tiedosto=NULL;
unsigned int aika;
unsigned int piirtoaika;
unsigned int alkuaika;
// Luo ikkuna
if (!luoIkkuna(800, 600, "OpenGL:n perusteet - Osa 3: Teksturointi")) return 0;
// Varaa muistia väliaikaiselle datalle
data=(unsigned char *)malloc(256*256*4*sizeof(unsigned char));
if (!data) return 0;
// luo yksi tekstuuriobjekti
glGenTextures(1, &taustaTekstuuri);
// Sido äsken luotu tekstuuri teksturointiyksikköön
glBindTexture(GL_TEXTURE_2D, taustaTekstuuri);
// Lataa kuvadata tiedostosta.
tiedosto=fopen("tausta.kuva", "rb");
if (!tiedosto) return 0;
fread(data, 3, 128*128, tiedosto);
fclose(tiedosto);
// Lataa data tekstuuriin
glTexImage2D(GL_TEXTURE_2D, 0, 3, 128, 128, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
// Suodatus päälle
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// luo yksi tekstuuriobjekti
glGenTextures(1, &alienTekstuuri);
// Sido äsken luotu tekstuuri teksturointiyksikköön
glBindTexture(GL_TEXTURE_2D, alienTekstuuri);
// Lataa kuvadata tiedostosta
tiedosto=fopen("alien.kuva", "rb");
if (!tiedosto) return 0;
fread(data, 4, 256*256, tiedosto);
fclose(tiedosto);
// Lataa data tekstuuriin. Huomaa, että tässä on nyt alpha mukana!
glTexImage2D(GL_TEXTURE_2D, 0, 4, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
// Suodatus päälle
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Vapauta väliaikainen data
free(data);
// Teksturointi päälle
glEnable(GL_TEXTURE_2D);
// Viestinkäsittelysilmukka
alkuaika=GetTickCount();
MSG msg;
while(1)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message==WM_QUIT) break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
// Käytämme GetTickCount()-funktiota, joka palauttaa ajan millisekunneissa,
// laskemaan kuvan piirtämiseen kuluneen ajan.
// Näin voimme ajastaa alienin pyörimään samalla nopeudella kaikilla kokeilla.
aika=GetTickCount();
piirtoaika=aika-alkuaika;
if (piirtoaika>0)
{
alkuaika=aika;
// Kasvata pyörityskulmaa hieman seuraavaa framea varten.
// Alien pyörii nyt 0.06 astetta millisekunnissa
// eli 1 kierroksen 6 sekunnissa
angle+=0.06*piirtoaika;
// Tyhjennä puskuri
glClear(GL_COLOR_BUFFER_BIT);
// Piirretään ensin tausta
// Luo 2D koordinaatisto
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 800, 0, 600);
// Aseta modelview-matriisi
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Aseta tekstuurimatriisi niin, että taustaan saadaan hieman liikettä.
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glTranslatef(sin(0.01*angle), cos(0.01*angle), 0);
// Ota alpha test pois päältä sitä ei tarvita taustassa
glDisable(GL_ALPHA_TEST);
// Sido taustatekstuuri
glBindTexture(GL_TEXTURE_2D, taustaTekstuuri);
// Piirrä koko ikkunan peittävä nelikulmio
glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex2f(0, 0);
glTexCoord2f(6, 0);
glVertex2f(800, 0);
glTexCoord2f(6, 4);
glVertex2f(800, 600);
glTexCoord2f(0, 4);
glVertex2f(0, 600);
glEnd();
// piirretään sitten alieni
// Aseta 3D-koordinaatisto
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60, 800.0/600.0, 1, 10);
// Aseta modelview-matriisi
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0, 0, -2); // Siirrä alienia hieman kauemmaksi kamerasta
glRotatef(angle, 0, 1, 0); // Pyöritä alienia y-akselin ympäri
// Aseta tekstuurimatriisi
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
// laita alpha test päälle
glEnable(GL_ALPHA_TEST);
// aseta alpha test niin että vain pikselit,
// joiden alpha arvo on yli 0.5 piirretään
glAlphaFunc(GL_GREATER, 0.5);
// Sido alien tekstuuri
glBindTexture(GL_TEXTURE_2D, alienTekstuuri);
// Piirrä alien
glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex3f(-1, 1, 0);
glTexCoord2f(1, 0);
glVertex3f( 1, 1, 0);
glTexCoord2f(1, 1);
glVertex3f( 1, -1, 0);
glTexCoord2f(0, 1);
glVertex3f(-1, -1, 0);
glEnd();
// Vaihda puskuri näytölle.
SwapBuffers(hdc);
}
}
}
return 0;
}
10. Loppusanat #
Tässä artikkelissa opit teksturoimaan. Seuraavassa ja viimeisessä osassa puhumme valoista ja varjoista. Raportoithan kaikki tästä artikkelista löytämäsi virheet (niin kirjoitus-, kuin asiavirheetkin) osoitteeseen markus.ilmola@pp.inet.fi , niin korjaan ne mahdollisimman nopeasti. Myös kaikki kommentit ja kysymykset ovat tervetulleita.
Liite 1: DEVIL – kuvatiedostojen pikkupaholainen #
Esimerkkiohjelmassa käyttämäni kuvat olivat ns. raakadatakuvia, koska data on niissä sellaisessa muodossa, että se kelpaa suoraan OpenGL:lle, eikä niiden lataus siis vaadi mitään ylimääräisiä toimenpiteitä. Raakadatakuvien käyttö ei kuitenkaan ole suositeltavaa, koska ne eivät sisällä mitään tietoa kuvan tyypistä, koosta jne. Lisäksi raakadatakuvien tallentaminen kuvankäsittelyohjelmilla on ongelmallista. Onkin suositeltavaa käyttää jotain oikeaa kuvaformaattia kuten jpg tai png.
Oikeissa kuvaformaateissa on kuitenkin se vika, että niissä oleva data EI kelpaa sellaisenaan OpenGL:lle. Kuvadata voi olla esim. pakattu tilan säästämiseksi, sen seassa voi olla jotain ylimääräistä metatietoa tai se on vain yksinkertaisesti ”väärässä” järjestyksessä. Jotta kuva voidaan ladata, pitäisi olla tarkka tieto sen rakenteesta. Ensin mahdollinen pakkaus pitäisi purkaa, ylimääräiset tiedot poistaa ja järjestää data sopivaan järjestykseen. Vasta tämän jälkeen datan voi antaa OpenGL:lle.
Kuvatiedostojen speksit ovat yleensä julkisia ja ne löytyvät netistä, mutta koska kuvaformaatteja on tuhottoman paljon, ja eri formaatit sopivat eri tilanteisiin, olisi liian iso urakka kirjoittaa latausfunktiota niille kaikille. On kuitenkin olemassa ilmaisia apukirjastoja, jotka osaavat ladata valtavan määrän erilaisia kuvaformaatteja. Yksi niistä on nimeltään DEVIL. Se on siinä mielessä hyvä aisapari OpenGL:lle, että sen käyttöliittymä on apinoitu suoraan OpenGL:stä.
DEVIL-kirjaston voi ladata osoitteesta: http://openil.sourceforge.net/. Imuroi uusin versio ja kopioi ”.lib”-tiedostot kääntäjäsi ”lib”-hakemistoon ja ”.h”-tiedostot kääntäjäsi ”include/il”-hakemistoon. ”.dll”-tiedostot tulevat samaan hakemistoon projektisi kanssa. Paketti sisältää itseasiassa 3 eri dll-tiedostoa, mutta tavallisesti tarvitset vain tiedoston ”devil.dll”. Jotta voisit käyttää DEVIL:iä, sinun pitää sisällyttää projektiisi otsikkotiedosto il.h. Tämä tehdään seuraavasti:
#include <il\il.h>
Lisäksi ohjelma pitää linkittää tiedoston devil.lib kanssa.
Ensimmäinen toimenpide DEVIL:iä käytettäessä on alustus. Tämä tehdään yhdellä funktiokutsulla.
ilInit();
Siinä kaikki. Alustus pitää tehdä ennen kuin yhtään DEVIL:in funktiota saa kutsua.
Nyt pääsemmekin itse asiaan. Kuten tulet huomaamaan DEVIL:in käyttö on lähes identtistä OpenGL:n tekstuurin latauksen kanssa.
Ensiksi pitää luoda kuvaobjekti. Se tehdään funktiolla:
ILvoid ilGenImages( ILsizei Num, ILuint *Images );
”Num” kertoo kuinka monta kuvaa luodaan ja ”Images” on osoitin taulukoon, jonne kuvien tunnukset tallennetaan. Kun kuva on luotu, se pitää sitoa nykyiseksi kuvaksi. Kaikki kuvaan vaikuttavat funktiot operoivat sille kuvalle, joka on sillä hetkellä sidottuna nykyisenä kuvana. Kuvan sitominen tehdään funktiolla:
ILvoid ilBindImage(ILuint Image);
”Image” on siis sidottavan kuvan tunnus. Nykyiseen kuvaan voidaan ladata dataa kuvatiedostosta funktiolla:
ILboolean ilLoadImage(char *FileName);
Vain yksi parametri, eli ladattavan tiedoston nimi. DEVIL tunnistaa tiedoston tyypin automaattisesti ja osaa ladata seuraavan tyyppisiä kuvia: bmp, cut, dcx, dds, ico, gif, jpg, lbm. Lif, mdl, pcd, pcx, pic, png, pnm, psd, psp, raw, sgi, tga, tif, wal, act, pal ja hdr.
Funktio palauttaa nollan, jos lataus epäonnistui, ja eri suuren kuin nolla, jos onnistui.
Kuvan latauksen jälkeen kuva on muistissa ja enää tarvitsee saada data ulos siitä OpenGL:n ymmärtämässä muodossa. Sitä ennen tarvitsee kuitenkin udella kuvan tiedot kuten koko jne. Tämä tehdään funktiolla:
ILint ilGetInteger( ILenum Mode );
”Mode” kertoo mikä tieto halutaan ja funktio palauttaa kyseisen tiedon. ”Mode”:lle on iso kasa mahdollisia arvoja, mutta tässä tärkeimmät.
IL_IMAGE_WIDTH – kuvan leveys pikseleinä.
IL_IMAGE_HEIGHT – kuvan korkeus pikseleinä.
IL_IMAGE_FORMAT – Kertoo missä muodossa kuvan data ladatussa tiedostossa oli. Mahdolliset arvot ovat: IL_COLOUR_INDEX, IL_RGB, IL_RGBA, IL_BGR, IL_BGRA ja IL_LUMINANCE. Tätä tarvitaan tutkimaan oliko ladatussa kuvassa alphakanava. Jos formaatti on IL_RGBA tai IL_BGRA kuvassa oli alphakanava, muuten ei.
Varsinainen data saadaan ulos funktiolla:
ILvoid ilCopyPixels( ILuint XOff, ILuint YOff, ILuint ZOff, ILuint Width, ILuint Height, ILuint Depth, ILenum Format, ILenum Type, ILvoid *Data );
”Off” parametrit kertovat mistä kohdasta lähtien data kopioidaan. Koska yleensä halutaan koko kuva, ovat nämä nollia.
Width, Height ja Depth kertovat kopioitavan palan koon, jotka jälleen kopioitaessa koko kuva, ovat itse kuvan koko (joka saatiin ilGetInteger funktiolla). Depth parametrin pitää olla 1.
”Format” kertoo formaatin, jossa data halutaan ulos. Ei ole mitään merkitystä missä muodossa data ladatussa kuvassa oli, sillä sen saa ulos missä formaatissa haluaa. Jotta data kelpaa OpenGL:lle on sen oltava formaatissa IL_RGB tai IL_RGBA, jos alpha kanavakin halutaan mukaan.
”Data” on osoitin taulukkoon, johon kuvan data kopioidaan. ”Type” on tämän taulukon alkion tyyppi esim.: IL_UNSIGNED_BYTE tai IL_FLOAT .
Lopuksi, kun data on kopioitu talteen, pitää kuva tuhota pois turhia resursseja tuhlaamasta. Tämä tehdään funktiolla:
ILvoid ilDeleteImages( ILsizei Num, const ILuint *Images);
Seuraavassa esimerkissä teemme funktion, joka lataa kuvan ja tekee siitä tekstuurin. DEVIL pitää toki alustaa kutsulla ”ilInit()” ennen kuin tätä funktiota saa kutsua.
GLuint lataaTekstuuri(char *fileName)
{
unsigned char *data=NULL;
GLuint tekstuuri=0;
ILuint kuva=0;
unsigned int korkeus=0, leveys=0, komponentteja=0;
ILuint ilFormat;
GLuint glFormat;
// Lataa kuva
ilGenImages(1, &kuva);
ilBindImage(kuva);
if (!ilLoadImage(fileName)) return 0;
// Kysy kuvan tiedot
leveys=ilGetInteger(IL_IMAGE_WIDTH);
korkeus=ilGetInteger(IL_IMAGE_HEIGHT);
// Onko alpha mukana?
if (ilGetInteger(IL_IMAGE_FORMAT)==IL_RGBA ||
ilGetInteger(IL_IMAGE_FORMAT)==IL_BGRA)
{
komponentteja=4;
ilFormat=IL_RGBA;
glFormat=GL_RGBA;
}
else
{
komponentteja=3;
ilFormat=IL_RGB;
glFormat=GL_RGB;
}
// Varaa muistia väliaikaiselle datalle
data=(unsigned char *)malloc(leveys*korkeus*komponentteja*sizeof(unsigned char));
if (!data) return 0;
// Kopio data
ilCopyPixels(0,0,0, leveys,korkeus,1, ilFormat, IL_UNSIGNED_BYTE, data);
// Tuhoa kuva
ilDeleteImages(1, &kuva);
// luo yksi tekstuuriobjekti
glGenTextures(1, &tekstuuri);
// Sido äsken luotu tekstuuri teksturointiyksikköön
glBindTexture(GL_TEXTURE_2D, tekstuuri);
// Lataa data tekstuuriin
glTexImage2D(GL_TEXTURE_2D, 0, komponentteja, leveys, korkeus, 0,
glFormat, GL_UNSIGNED_BYTE, data);
// Suodatus päälle
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Vapauta väliaikainen data
free(data);
// Palauta luotu tekstuuri
return tekstuuri;
}








