OpenGL:n perusteet - Osa 2: 3D grafiikka



Samankaltaiset tiedostot
OpenGL:n perusteet - Osa 1: Ikkunan luominen

OpenGL:n perusteet Osa 4: Valot ja varjot

OpenGL:n perusteet Osa 3: Teksturointi

Windowsin sanomanvälitys. Juha Järvensivu 2007

Windowsin sanomanvälitys. Juha Järvensivu 2008

Luento 3: 3D katselu. Sisältö

T Tietokonegrafiikan perusteet. OpenGL-ohjelmointi

Sisällys. T Tietokonegrafiikan perusteet. OpenGL-ohjelmointi 11/2007. Mikä on OpenGL?

Matikkaa KA1-kurssilaisille, osa 3: suoran piirtäminen koordinaatistoon

Luento 6: Piilopinnat ja Näkyvyys

Kenguru 2012 Student sivu 1 / 8 (lukion 2. ja 3. vuosi)

Osoitin ja viittaus C++:ssa

Peilaus pisteen ja suoran suhteen Pythonin Turtle moduulilla

Lieriö ja särmiö Tarkastellaan pintaa, joka syntyy, kun tasoa T leikkaava suora s liikkuu suuntansa

Tietorakenteet ja algoritmit

Demokoodaus Linuxilla, tapaus Eternity

1 of

2.3 Voiman jakaminen komponentteihin

Pong-peli, vaihe Koordinaatistosta. Muilla kielillä: English Suomi. Tämä on Pong-pelin tutoriaalin osa 2/7. Tämän vaiheen aikana

Tässä osassa ei käytetä laskinta. Selitä päätelmäsi lyhyesti tai perustele ratkaisusi laskulausekkeella, kuviolla tms.

Tampereen yliopisto Tietokonegrafiikka 2013 Tietojenkäsittelytiede Harjoitus

Kartio ja pyramidi

Oppimateriaali oppilaalle ja opettajalle : GeoGebra oppilaan työkaluna ylioppilaskirjoituksissa 2016 versio 0.8

Kerta 2. Kerta 2 Kerta 3 Kerta 4 Kerta Toteuta Pythonilla seuraava ohjelma:

Ohjelmoinnin peruskurssi Y1

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

JAVA on ohjelmointikieli, mikä on kieliopiltaan hyvin samankaltainen, jopa identtinen mm. C++

LIITE 1 1. Tehtävänä on mallintaa kitara ohjeiden mukaan käyttäen Edit Poly-tekniikkaa.

Muuttujien roolit Kiintoarvo cin >> r;

Digikuvan peruskäsittelyn. sittelyn työnkulku. Soukan Kamerat Soukan Kamerat/SV

Luku 6: Grafiikka. 2D-grafiikka 3D-liukuhihna Epäsuora valaistus Laskostuminen Mobiililaitteet Sisätilat Ulkotilat

Lauseen erikoistapaus on ollut kevään 2001 ylioppilaskirjoitusten pitkän matematiikan kokeessa seuraavassa muodossa:

4.1 Kaksi pistettä määrää suoran

Tampereen yliopisto Tietokonegrafiikka 2013 Tietojenkäsittelytiede Harjoitus

Eye Pal Solo. Käyttöohje

Taulukot. Taulukon määrittely ja käyttö. Taulukko metodin parametrina. Taulukon sisällön kopiointi toiseen taulukkoon. Taulukon lajittelu

Tapahtumapohjainen ohjelmointi. Juha Järvensivu 2007

Kenguru 2012 Junior sivu 1 / 8 (lukion 1. vuosi)

Peruskoulun matematiikkakilpailu Loppukilpailu 2010 Ratkaisuja OSA 1

3.3 Paraabeli toisen asteen polynomifunktion kuvaajana. Toisen asteen epäyhtälö

Zeon PDF Driver Trial

Harjoitustyö: virtuaalikone

Kirjoita oma versio funktioista strcpy ja strcat, jotka saavat parametrinaan kaksi merkkiosoitinta.

Peliohjelmointirajapinnoista

T Vuorovaikutteinen tietokonegrafiikka Tentti

Tietorakenteet, laskuharjoitus 7, ratkaisuja

Avaa ohjelma ja tarvittaessa Tiedosto -> Uusi kilpailutiedosto

Kolmion kulmien summa. Maria Sukura

Tasogeometriaa GeoGebran piirtoalue ja työvälineet

Ohjelmoinnin perusteet Y Python

Kenguru 2015 Student (lukiosarja)

Sisältö. 2. Taulukot. Yleistä. Yleistä

ETAPPI ry JOOMLA 2.5 Mediapaja. Artikkeleiden hallinta ja julkaisu

Anna jokaisen kohdan vastaus kolmen merkitsevän numeron tarkkuudella muodossa

Kortinhaltijat joilla on maksukeskeytys Maksuryhmään liitettyjen kortinhaltijoiden lukumäärä, joiden maksut ovat tilapäisesti keskeytetty.

5. Grafiikkaliukuhihna: (1) geometriset operaatiot

origo III neljännes D

FOTONETTI BOOK CREATOR

Ohjelmoinnin perusteet Y Python

Määrittelydokumentti

Harjoitus Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

5.3 Ensimmäisen asteen polynomifunktio

1 Aritmeettiset ja geometriset jonot

Helsingin seitsemäsluokkalaisten matematiikkakilpailu Ratkaisuita

Luento 2: Viivan toteutus

Ohjelmointiharjoituksia Arduino-ympäristössä

Harjoitus 4 (viikko 47)

Winapi. Juha Järvensivu 2007

Sisältö. 22. Taulukot. Yleistä. Yleistä

Algoritmit 2. Luento 7 Ti Timo Männikkö

Yleistä. Nyt käsitellään vain taulukko (array), joka on saman tyyppisten muuttujien eli alkioiden (element) kokoelma.

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

YKSIKÖT Tarkista, että sinulla on valittuna SI-järjestelmä. Math/Units Ohjelma tulostaa/käyttää laskennassaan valittua järjestelmää.

Tietorakenteet ja algoritmit

Peliohjelmointirajapinnoista

6. Harjoitusjakso II. Vinkkejä ja ohjeita

Esimerkkejä. OpenGL ohjelma. OpenGL tilakone. Geometriset primitiivit. Hyvät ja huonot polygonit. OpenGL Pipeline. Rasterointi

Ohjelmointi 1 Taulukot ja merkkijonot

Kenguru 2013 Cadet (8. ja 9. luokka)

Luento 6: Tulostusprimitiivien toteutus


! 7! = N! x 8. x x 4 x + 1 = 6.

v1.2 Huom! Piirto-ohjelmissa asioita voi tehdä todella monella tavalla, tässä esitellään yksi esimerkkitapa tällaisen käyrän piirtämiseen.

GeoGebran 3D paketti


Ohjelmoinnin perusteet Y Python

Passikuva - Käyttöohje Pispalan Insinööritoimisto Oy

Muuttujien määrittely

Aluksi Kahden muuttujan lineaarinen yhtälö

Avaruuslävistäjää etsimässä

7.4 PERUSPISTEIDEN SIJAINTI

Käyttöliittymän muokkaus

Taulukot. Jukka Harju, Jukka Juslin

Merkkijono määritellään kuten muutkin taulukot, mutta tilaa on varattava yksi ylimääräinen paikka lopetusmerkille:

TYÖPAJA 1: Tasogeometriaa GeoGebran piirtoalue ja työvälineet

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2

1 YLEISTÄ 1. 2 KARTAT yleistä Avoimien aineistojen tiedostopalvelu 2 3 KARTAN TEKEMINEN JA SIIRTÄMINEN PUHELIMEEN 4

Tietorakenteet ja algoritmit

1 Kannat ja kannanvaihto

Transkriptio:

OpenGL:n perusteet - Osa 2: 3D grafiikka OpenGL on käyttöjärjestelmäriippumaton kirjasto 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 toinen osa. 1 3-ulotteisen kappaleen esittäminen tietokoneella Tapoja kuvata 3-ulotteinen kappale tietokoneella on monta, mutta se tapa, jota lähes kaikki pelit käyttävät, on aproksimoida eli arvioida kappaletta monitahokkaalla (englanniksi polyhedron). Monitahokas on 3-ulotteinen kappale, jota rajoittaa monikulmioista eli polygoneista koostuva suljettu pinta. Nämä polygonit ovat monitahokkaan tahoja. Tahkojen leikkausviivoja kutsutaan särmiksi ja särmien leikkauspisteitä kärjiksi eli vertekseiksi. Alla oleva kuva esittää monitahokasta, jossa verteksit on merkitty sinisellä, särmät punaisella ja polygonit harmaalla. Monitahokkaat voidaan jakaa kahteen ryhmään: kuperiin ja koveriin. Monitahokas on kupera, jos mitkä tahansa kaksi sen vertekseistä voidaan yhdistää viivalla niin, että tämä viiva ei käy monitahokkaan ulkopuolella. Muussa tapauksessa monitahokas on kovera.

Litteälle pinnalle, kuten paperille tai monitorille piirrettynä monitahokas muodostaa verkon, josta käytetään yleensä englanninkielistä termiä mesh. Jos monitahokkaasta piirretään pelkät särmät kutsutaan kuvaa rautalankamalliksi (englanniksi wire-frame mesh). Kun monitahokkaalla halutaan aproksimoida jotain kappaletta rakennetaan monitahokas, jonka pinta mukailee mahdollisimman tarkasti alkuperäisen kappaleen pintaa. Mitä enemmän polygoneja monitahokkaassa on, sitä tarkemmin se jäljittelee alkuperäistä kappaletta. Se ei voi kuitenkaan koskaan mallintaa kappaletta täydellisen tarkasti (ellei sitten alkuperäinen kappale ollut monitahokas itsekkin). Kuitenkin hyvin suurilla polygonimäärillä ihmissilmä ei enää huomaa eroa. Monitahokkaan tallentaminen tietokoneen muistiin on helppoa. Meidän pitää vain tallentaa jokaisen verteksin koordinaatit, sekä tieto siitä mitkä verteksit aina muodostavat tahon. Tämä voidaan toteuttaa vaikka indeksoimalla verteksejä. Vaikka monitahokkaan tahot voivat olla mitä tahansa monikulmioita, voidaan mikä tahansa monikulmio muodostaa kolmioista. Tämän takia riittää, että voimme tallentaa ainoastaan kolmioita. Esim. Kuutio, jonka keskipiste sijaitsee origossa ja säde on yksi, voitaisiin tallentaa seuraavasti: float vertex[8][3]=-1,-1,-1,1,-1,-1,-1,1,-1,1,1,-1,-1,-1, 1,1,-1, 1,-1,1, 1,1,1, 1; int index[6*4]= 0,2,3,1, 4,5,7,6, 5,1,3,7, 4,6,2,0, 7,3,2,6, 4,0,1,5 ; Eli tallennetaan kuution jokaisen 8 verteksin sijainti, sekä indeksit, jotka ilmoittavat mitkä 4 verteksiä aina muodostavat tahon.

2 3D-koordinaatisto Tämän artikkelisarjan ensimmäisessä osassa loimme 2D-koordinaatiston gluortho2d()-funktiolla. 3D-grafiikan tapauksessa tarvitsemme 3D-koordinaatiston. Tälläinen koordinaatisto luodaan OpenGL:ssä funktiolla gluperspeksive(), jonka prototyyppi näyttää tältä: void gluperspective( GLdouble fovy, GLdouble aspect, GLdouble znear, GLdouble zfar ); Koordinaatisto on helpoin käsittää jos ajattelet, että jossain päin kyberavaruutta kelluu kamera. Origo sijaitsee tämän kameran linssin keskellä. X-akseli kulkee vaakatasossa ja sen arvot kasvavat oikealle mentäessä. Y-akseli kulkee pystysuuntaan ja sen arvot kasvavat ylöspäin mentäessä. Kamera katsoo kohti negatiivista Z-akselia. Kameralle on myös määritelty näkökentän leveys (englanniksi field of view eli FOV), joka määrää kuinka monen asteen levyisen kaistaleen kamera näkee. Näin ollen kameran kerralla näkemä alue muodostaa kartion. Koska kappaleita, jotka ovat hyvin kaukana kamerasta tai hyvin lähellä sitä, ei ole järkevää piirtää, rajoitetaan kameran katselukartiota vielä kahdella tasolla ns. lähi- ja kaukoleikkaustasolla. Kaikki kaukotason takana olevat kappaleet jätetään piirtämättä samoin kaikki lähitason edessä olevat. Näin ollen näkyväksi alueeksi jää enää katkaistu kartio eli frustum. gluperspective()-funktion fovy-parametri kertoo kameran näkökentän leveyden. Tämän arvon on oltava suurempi kuin 0 ja pienempi kuin 180. Suurilla arvoilla saadaan laajakulmanäkymä ja pienillä arvoilla putkinäkömäinen efekti. Vaikka ihmissilmän näkökentän leveys on noin 170 astetta

astetta (tarkan näön alue noin 30 astetta) peittää monitori vain pienen osan näkökentästä, joten realistinen arvo on noin 45-60 astetta. Near ja far-parametrit kertovat lähi- ja kaukotason etäisyydet kamerasta. Huomaa, että lähitäson etäisyys pitää olla suurempi kuin 0 ja kaukotason on aina oltava kauempana kuin lähitason. Lähitaso kannattaa pitää mahdollisimman kaukana kamerasta ja kaukotaso mahdollisimman lähellä kameraa. Ei siis toisin päin. Aspect-parametri kertoo kuvasuhteen. Sen on oltava viewportin_leveys/viewportin_korkeus tai muuten kuva vääristyy. gluortho2d() ja gluperspektive()-funktioista on olemassa hieman monipuolisemmat versiot glortho() ja glfrustum(), mutta en käsittele niitä tässä artikkelissa. 3 Renderöinti OpenGL:ssä monitahokas piirretään yksinkertaisesti polygoni kerrallaan. Piirtäminen aloitetaan glbegin()-funktiolla, jolle annetaan parametriksi GL_TRIANGLES, jos halutaan piirtää kolmioita tai GL_QUADS, jos halutaan piirtää nelikulmioita. Tämän jälkeen annetaan verteksien sijainnit glvertex3f()-funktiolla. Jos glbegin()-funktion parametri oli GL_TRIANGLES, piirretään jokaisen kolmen annetun verteksin välille aina kolmio. Tai jos se oli GL_QUADS piirretään jokaisen neljän verteksin välille aina nelikulmio. Lopuksi kutsutaan glend()-funktioita. Jokaiselle verteksille voidaan myös määrätä väri kutsumalla glcolor3f()-funktiota ennen jokaista glvertex3f()-funktion kutsua. Esim. kappaleessa 1 määritelty kuutio voitaisiin piirtää esimerkiksi seuraavalla tavalla: glbegin(gl_quads); int i; for (i=0; i<6*4; i++) glvertex3f(vertex[index[i]][0], vertex[index[i]][1], vertex[index[i]][2]); glend();

Eli kutsutaan glvertex3f()-funktiota neljä kertaa jokaista kuution kuutta tahoa kohti. Kun polygonien määrä kasvaa suureksi käy glbegin()-,lend()-parin käyttäminen tehottomaksi, sillä jokaista kolmiota kohden tarvitaan aina kolme glvertex3f()-funktion kutsua. Esim. jos meillä olisi 10000 kolmekulmaista polygonia tarvittaisiin 30000 glvertex3f()-funktion kutsua. Ei hyvä! Tämän takia OpenGL:ssä on kolme muutakin tapaa piirtää polygoneja. Ne ovat display list, vertex array ja vertex buffer object. Display list:in ideana on nauhoittaa funktion kutsuja, jonka jälkeen kaikki nauhoitetut kutsut voidaan toistaa yhdellä funktion kutsulla. Ensin display list pitää luoda glgenlists()-funktiolla. Funktio palauttaa luodun display list:n tunnuksen. Nauhoitus aloitetaan glnewlist()-funktiolla, jolle annetaan parametrina glgenlists()-funktiolta saatu tunnus ja symboli GL_COMPILE. Tämän jälkeen voidaan kutsua vapaasti lähes mitä tahansa OpenGL:n funktioita ja ne nauhoittuvat. Kun display list on valmis kutsutaan glendlist()-funktioita. Myöhemmin nauhoitus voidaan toistaa kuinka monta kertaa tahansa glcalllist()-funktiolla, jolle annetaan parametrina display list:in tunnus. Huomaa, että display list:in sisältöä ei voi muuttaa. Display list voidaan tuhota gldeletelist()-funktiolla. Esim. äsken piirretty kuutio voitaisiin nauhoittaa display list:iin seuraavasti: int tunnus; tunnus=glgenlists(1); glnewlist(tunnus, GL_COMPILE); glbegin(gl_quads); int i; for (i=0; i<6*4; i++) glvertex3f(vertex[index[i]][0], vertex[index[i]][1], vertex[index[i]][2]); glend(); glendlist(); Vastaavasti se voitaisiin tämän jälkeen piirtää koska tahansa kutsulla glcalllist(tunnus);.

Vertex array:n ideana on antaa OpenGL:lle osoitin verteksidataan, jonka jälkeen kaikki polygonit voidaan piirtää yhdellä funktion kutsulla. Ennen kuin vertex array:ta voidaan käyttää pitää se laittaa päälle glenableclientstate()-funktiolla, jolle annetaan parametrina GL_VERTEX_ARRAY. Tämän jälkeen annetaan osoitin verteksidataan glvertexpointer()-funktiolla, jonka prototyyppi näyttää tältä: void glvertexpointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ); Ensimmäinen parametri kertoo komponenttien määrän per verteksi (yleensä 3). Seuraava datan tyypin (yleensä GL_FLOAT). Kolmas verteksien etäisyyden toisistaan muistissa (yleensä sizeof(verteksi)). Ja viimeinen on osoitin verteksidataan. Kun osoitin on annettu voidaan varsinainen renderöinti tehdä yhdellä gldrawelements()-funktion kutsulla. Sen prototyyppi näyttää tältä: void gldrawelements( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices ); Ensimmäisellä parametrilla on sama merkitys kuin glbegin()-funktionkin parametrilla. Viimeinen on osoitin taulukkoon, joka indeksoi glvertexpointer()-funktiolla annettuja verteksejä. Toinen parametri kertoo alkioiden määrän tässä taulukossa. Toiseksi viimeinen kertoo tämän taulukon alkioden tyypin (yleensä GL_UNSIGNED_INT). Seuraava esimerkki piirtää kappaleessa yksi määritellyn kuution käyttäen vertex array:ta. glenableclientstate(gl_vertex_array);

glvertexpointer(3, GL_FLOAT, sizeof(float[3]), vertex); gldrawelements(gl_quads, 6, GL_UNSIGNED_INT, index); Se viimeinen tapa on sitten vertex buffer object eli VBO. Se on muuten sama kuin vertex array, mutta siinä verteksidata siirretään keskusmuistista nopeampaan näytönohjaimen muistiin. Koska VBO:n edut tulevat esiin vasta valtavan suurilla polygonimäärillä ja tämä on vain OpenGL:n perusteet-artikkeli, en käsittele sitä. 4 Näkymättömien pintojen poisto Koska monitahokkaat ovat määritelmän mukaan suljettuja, emme voi koskaan nähdä tahojen takapuolia, ainoastaan niiden etupuolet. Tämän takia olisi tehokkuuden kannalta järkevää, jos polygoni jätettäisiin piirtämättä silloin kun sen takapuoli on kameraan päin. Nyt ilmestyykin ongelma. Mistä oikein tietää kumpi polygonin puolista on sen takapuoli ja kumpi etupuoli? Eivätkös ne ole ihan samanlaisia kummatkin? OpenGL:ssä polygonin etupuoli määritellään niin, että jos numeroimme sen verteksit järjestyksessä, on etupuoli se, jolta katsottuna verteksien numerot kasvavat vastapäivään. Näkymättömien pintojen poisto kytketään päälle glenable()-functiolla, jolle annetaan parametrina GL_CULL_FACE. Vastaavasti sen saa pois päältä gldisable()-funktiolla samalla parametrilla. Voit myös halutessasi päättää jätetäänkö piirtämättä taka-, vai etupuolet. Tämä tehdään glcullface()- funktiolla, jolle annetaan parametrina, joko GL_FRONT tai GL_BACK. Oletusarvo on GL_BACK. Sinun ei tietenkään tarvitse itse numeroida verteksejä, vaan OpenGL numeroi ne siinä järjestyksessä kun se saa ne esim. glvertex3f()-funktiolta. 5 Päällekkäisyysongelma Aina kun piirrät OpenGL:ssä jotain, se peittää alleen kaiken ennen sitä piirretyn. Tästä seuraa se, että jos monitahokkaan takenpana olevat polygonit piirretään etummaisten jälkeen, peittävät ne etummaiset alleen ja saatu kuva on näin ollen virheellinen. Jos näkymättömien pintojen poisto laitetaan päälle, ei tätä ongelmaa esiinny, jos monitahokas on kupera, sillä siinä mitkään kaksi

kameraan päin olevaa polygonia ei peitä toisiaan. Sen sijaan koveran monitahokkaan tapauksessa ei pelkkä näkymättömien pintojen poisto auta, vaan tarvitaan avuksi jotain toista algoritmia. Kaksi kuuluisinta ovat: maalarin algoritmi ja syvyyspuskurialgoritmi. 5.1 Maalarin algoritmi Maalarin algoritmin idea on yksinkertainen. Polygonit lajitellaan ja piirretään tämän jälkeen järjestyksessä takimmaisesta etummaiseen, samoin kuin maalari tekee maalatessaan taulua. On kuitenkin muutama tapaus, joissa maalarin algoritmi ei toimi, eli kuva on aina virheellinen riippumatta siitä missä järjestyksessä polygonit piirretään. Alla pari esimerkkiä. Tämän takia maalarin algoritmia ei kannatakkaan käyttää kuin joissakin erikoistapauksissa. 5.2 Syvyyspuskurialgoritmi Syvyyspuskurialgoritmin ideana on tallentaa pikselin värin lisäksi myös sen etäisyys kamerasta. Tämä syvyysarvo tallennetaan ns. syvyyspuskuriin eli Z-puskuriin. Aina ennen pikselin piirtoa lasketaan sen syvyysarvo. Tarkistetaan puskurista, onko kyseisellä paikalla olevan pikselin syvyysarvo jo pienempi kuin uuden pikselin syvyys. Jos on, niin pikseli jätetään piirtämättä. Jos taas ei, niin pikseli piirretään ja syvyyspuskuriin päivitetään uusi arvo. OpenGL:ssä on syvyyspuskuri sisäänrakennettuna. Se tarvitsee vain kytkeä päälle glenable()- funktiolla, jolle annetaan parametrina GL_DEPTH_TEST. Syvyyspuskuri on tietenkin tyhjennettävä ennen piirtämistä. Tämä tehdään glclear()-funktiolla, jolle annetaan parametrina GL_DEPTH_BUFFER_BIT. 6 Matriisit Voidaksesi käyttää OpenGL:ää sinun ei tarvitse ymmärtää matriisien syvintä olemusta. Riittää, että tiedät niiden perusperiaatteen. Matriisi on taulukko (OpenGL:n tapauksessa 4x4 taulukko), jonka jokaisessa solussa on jokin mielivaltainen luku. Matriisilla voidaan kertoa vektori, jolloin vektori

muuntuu toiseksi vektoriksi. Millaiseksi, se riippuu siitä, mitä lukuja matriisi sisälsi. Valitsemalla matriisin alkiot sopivasti saadaan vektori vaikka pyörimään jonkin akselin ympäri jonkin kulman verran. Jos matriisin alkiot valitaan niin, että vinorivillä on ykkösiä ja kaikkialla muualla nollia, saadaan ns. yksikkömatriisi, jolla kertomalla vektori ei muutu miksikään. Myös matriiseja voidaan kertoa keskenään, jolloin matriisien ominaisuudet yhdistyvät. Esim. jos meillä on matriisi, joka pyörittää 30 astetta Z-akselin ympäri ja matriisi, joka pyörittää 50 astetta Z-akselin ympäri saadaan niiden tulona matriisi, joka pyörittää 80 astetta Z-akselin ympäri. Jos haluat tarkempia tietoja matriiseista lue artikkeli: Matriisimatematiikkaa peliohjelmoijille. OpenGL:ssä on sisäänrakennettuna useitakin matriiseja. Tässä artikkelissa olemme kiinnostuneita modelview-matriisista ja projektiomatriisista. Aina kun annat OpenGL:lle verteksin glvertex-sarjan funktiolla kerrotaan kyseinen verteksi ensin modelview-matriisilla ja sitten projektiomatriisilla. Kummatkin nämä matriisit ovat oletuksena yksikkömatriiseja eli verteksi ei muutu miksikään. OpenGL sisältää kasan funktioita, joilla sen sisäisiä matriiseja voidaan muokata. Ennen kuin voimme käyttää niitä meidän on kuitenkin päätettävä mitä matriisia haluamme muokata. Tämä tehdään glmatrixmode()-funktiolla, joka saa parametrikseen, joko GL_MODELVIEW tai GL_PROJECTION riippuen siitä kumpaa matriiseista haluamme muokata. Myös muita vaihtoehtoja on, mutta emme käsittele niitä tässä artikkelissa. Matriiseja muokkaavista funktioista tärkeimmät ovat glloadidentity(), joka korvaa nykyisen matriisin yksikkömatriisilla, glrotatef(glfloat angle, GLfloat x, GLfloat y, GLfloat z), joka kertoo nykyisen matriisin matriisilla, joka pyörittää vektoria angle astetta akselin x, y, z ympäri, sekä gltranslatef(glfloat x, GLfloat y, GLfloat z ), joka siirtää vektoria parametrien x, y ja z verran. Seuraava esimerkki siirtää ensin -5 yksikköä z-akselin suuntaan ja pyörittää sitten 60 astetta x-akselin ympäri. glloadidentity(); gltranslatef(0, 0, -5); glrotatef(60, 1, 0, 0); Myös koordinaatistot tallennetaan matriisiin. Niille on varattu varta vasten projektiomatriisi. gluortho2d() ja gluperspektive() ovatkin itse asiassa matriiseja muokkaavia funktioita, jotka kertovat nykyisen matriisin luomallaan koordinaatistomatriisilla. Tämän takia projektiomatriisi on ensin valittava glmatrixmode()-funktiolla ennen kummankaan kutsua.

Nykyinen matriisi voidaan halutessa tallentaa pinoon päällimmäiseksi kutsulla glpushmatrix();. Pinon päällimmäinen matriisi taas voidaan ladata nykyiseksi kutsulla glpopmatrix();. Pinoon mahtuu maksimissaan 32 modelview-matriisia ja 2-projektiomatriisia (kummallakin on oma pino). 7 Esimerkkiohjelma Seuraava ohjelma avaa ikkunan ja piirtää siihen pyörivän värikkään kuution. Koska se kuinka monta kertaa sekunnissa tietokone piirtää kuvan riippuu sen tehosta, pyörisi kuutio eri nopeudella eri kokeilla. Tämän takia käytämme GetTickCount()-funktiota, joka palauttaa ajan millisekunteina, mittaamaan kuvan piirtämiseen kuluvan ajan. Näin voimme ajastaa pyörimisnopeuden samaksi kaikilla koneilla. Voit imuroida oheisen lähdekoodin ja valmiiksi käännetyn version tästä: http://www.suomipelit.com/files/artikkelit/testi2.zip. #include <windows.h> #include <gl\gl.h> #include <gl\glu.h> //#include <gl\glext.h> // 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 // reagoimme 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) // Data piirrettävää kuutiota varten float vertex[8][3]=-1,-1,-1,1,-1,-1,-1,1,-1,1,1,-1, -1,-1,1, 1,-1, 1,-1,1, 1,1,1, 1; float color[8][3]=0,0,0,1,0,0,0,1,0,1,1,0, 0,0,1, 1,0, 1,0,1, 1,1,1, 1; int index[6*4]= 0,2,3,1, 4,5,7,6, 5,1,3,7, 4,6,2,0, 7,3,2,6, 4,0,1,5 ; float angle=0; unsigned int aika; unsigned int piirtoaika; unsigned int alkuaika; // Luo ikkuna if (!luoikkuna(800, 600, "OpenGL:n perusteet - Osa 2: 3D grafiikka")) return 0; // Määrittele viewport koko ikkunan kokoiseksi glviewport(0, 0, 800, 600); // Koska koordinaatisto on itseasiassa matriisi täytyy meidän ottaa // projektiomatriisi käsiteltäväksi ennen gluperspective-kutsua. glmatrixmode(gl_projection); gluperspective(60, 800.0/600.0, 1, 100); // Kaikki matriisia muuttavat käskyt vaikuttavat tämän jälkeen modelview-matriisiin glmatrixmode(gl_modelview);

// Laita näkymättömien pintojen poisto ja sysyyspuskurialgoritmi päälle. glenable(gl_cull_face); glenable(gl_depth_test); // 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 kuution pyörimään samalla nopeudella kaikilla kokeilla. aika=gettickcount(); piirtoaika=aika-alkuaika; if (piirtoaika>0) alkuaika=aika; // Kasvata pyörityskulmaa hieman // Kuutio pyörii nyt 0.06 astetta millisekunnissa // eli 1 kierroksen 6 sekunnissa angle+=0.06*piirtoaika; // Tyhjennä väripuskuri ja syvyyspuskuri glclear(gl_color_buffer_bit GL_DEPTH_BUFFER_BIT); // Aseta modelview-matriisi glloadidentity(); // "Resetoi" matriisi yksikkömatriisiksi gltranslatef(0, 0, -5); // Siirrä kuutiota hieman kauemmaksi kamerasta glrotatef(angle, 1, 0, 0); // Pyöritä kuutiota hieman joka akselin ympäri glrotatef(angle, 0, 1, 0);

glrotatef(angle, 0, 0, 1); // Piirrä kuutio glbegin(gl_quads); int i; for (i=0; i<6*4; i++) glcolor3f(color[index[i]][0], color[index[i]][1], color[index[i]][2]); glvertex3f(vertex[index[i]][0], vertex[index[i]][1], vertex[index[i]][2]); glend(); // Toinen tapa piirtää kuutio vertex array:lla. /* glenableclientstate(gl_vertex_array); glenableclientstate(gl_color_array); glvertexpointer(3, GL_FLOAT, sizeof(float[3]), vertex); glcolorpointer(3, GL_FLOAT, sizeof(float[3]), color); gldrawelements(gl_quads, 4*6, GL_UNSIGNED_INT, index); */ // Vaihda puskuri näytölle. SwapBuffers(hdc); return 0; 8 Loppusanat Tässä artikkelissa opit piirtämään kolmeulotteisia kappaleita OpenGL:llä. Seuraavassa osassa puhumme tekstuureista. 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.