OpenGL:n perusteet - Osa 1: Ikkunan luominen



Samankaltaiset tiedostot
OpenGL:n perusteet - Osa 2: 3D grafiikka

Windowsin sanomanvälitys. Juha Järvensivu 2007

Windowsin sanomanvälitys. Juha Järvensivu 2008

OpenGL:n perusteet Osa 4: Valot ja varjot

OpenGL:n perusteet Osa 3: Teksturointi

Tapahtumapohjainen ohjelmointi. Juha Järvensivu 2007

Peliohjelmointirajapinnoista

Tapahtumapohjainen ohjelmointi. Juha Järvensivu 2008

Winapi. Juha Järvensivu 2007

Peliohjelmointirajapinnoista

1 of

Peilaus pisteen ja suoran suhteen Pythonin Turtle moduulilla

Graafisen käyttöliittymän ohjelmointi

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

1.1 Pino (stack) Koodiluonnos. Graafinen esitys ...

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Tiedostot. Tiedostot. Tiedostot. Tiedostot. Tiedostot. Tiedostot

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

Kuvat. 1. Selaimien tunnistamat kuvatyypit

Harjoitustyö: virtuaalikone

GeoGebra-harjoituksia malu-opettajille

ASCII-taidetta. Intro: Python

Graafisen käyttöliittymän ohjelmointi Syksy 2013

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

12. Javan toistorakenteet 12.1

Muuttujien roolit Kiintoarvo cin >> r;

Valintanauhan komennot Valintanauhan kussakin välilehdessä on ryhmiä ja kussakin ryhmässä on toisiinsa liittyviä komentoja.

4.1 Kaksi pistettä määrää suoran

Johdanto: Jaetut näytöt Jaetun näytön asetukset ja näytöstä poistuminen Aktiivisen sovelluksen valitseminen

6. Harjoitusjakso II. Vinkkejä ja ohjeita

Jypelin käyttöohjeet» Ruutukentän luominen

OHJE Jos Kelaimeen kirjautuminen ei onnistu Mac-koneella Sisällys

Tietorakenteet ja algoritmit

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

QT tyylit. Juha Järvensivu 2008

UpdateIT 2010: Editorin käyttöohje

CSS-kielen avulla määritellään HTML-dokumentin tyyli. CSS avulla voidaan tarkemmin määritellä eri elementtien ominaisuuksia.

Ajokorttimoduuli Moduuli 2. - Laitteenkäyttö ja tiedonhallinta. Harjoitus 1

OpenOffice.org Impress 3.1.0

Java layoutit. Juha Järvensivu 2007

Loppukurssin järjestelyt C:n edistyneet piirteet

Octo käyttöohje 1. Sisältö

IDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit

Ohjelmointiharjoituksia Arduino-ympäristössä

Harjoitus Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

Ohjelmoinnin perusteet Y Python

ATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014

Muita kuvankäsittelyohjelmia on mm. Paint Shop Pro, Photoshop Elements, Microsoft Office Picture Manager

YH1b: Office365 II, verkko-opiskelu

Luento 2: 2D Katselu. Sisältö

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

CEM DT-3353 Pihtimittari

Demokoodaus Linuxilla, tapaus Eternity

Tietorakenteet ja algoritmit

Loppukurssin järjestelyt

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

12. Javan toistorakenteet 12.1

ETAPPI ry JOOMLA 2.5 Mediapaja. Artikkeleiden hallinta ja julkaisu

Sukelluskeräily, Pelihahmon liikuttaminen. Tee uusi hahmo: Pelihahmo. Nimeä se. Testaa ikuisesti -silmukassa peräkkäisinä testeinä (jos) onko jokin

Toinen harjoitustyö. ASCII-grafiikkaa

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti

TIE Ohjelmistojen suunnittelu

Ohjelmoinnin perusteet Y Python

Pong-peli, vaihe Aliohjelmakutsu laskureita varten. 2. Laskurin luominen. Muilla kielillä: English Suomi

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.

KUVAT. Word Kuvat

Ohjeita Porin Lyseon koulun yrittäjuuskasvatuksen blogin kirjoittamiseen

Kun olet valmis tekemään tilauksen, rekisteröidy sovellukseen seuraavasti:

Ohjelmoinnin peruskurssi Y1

Tutoriaaliläsnäoloista

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti

Jypelin käyttöohjeet» Ruutukentän luominen

KUVANKÄSITTELY THE GIMP FOR WINDOWS OHJELMASSA

Word 2010 Pikaopas Hannu Matikainen Päivitetty:

Tietorakenteet ja algoritmit

C++ rautaisannos. Kolme tapaa sanoa, että tulostukseen käytetään standardikirjaston iostreamosassa määriteltyä, nimiavaruuden std oliota cout:

C-kielessä taulukko on joukko peräkkäisiä muistipaikkoja, jotka kaikki pystyvät tallettamaan samaa tyyppiä olevaa tietoa.

Se mistä tilasta aloitetaan, merkitään tyhjästä tulevalla nuolella. Yllä olevassa esimerkissä aloitustila on A.

Kirkkopalvelut Office365, Opiskelijan ohje 1 / 17 IT Juha Nalli

Ohjelman käyttöön ei sisälly muita kuluja kuin ohjelman lisenssimaksu ja mahdolliset webbipalvelusi käyttömaksut.

811312A Tietorakenteet ja algoritmit , Harjoitus 2 ratkaisu

Ohjeissa pyydetään toisinaan katsomaan koodia esimerkkiprojekteista (esim. Liikkuva_Tausta1). Saat esimerkkiprojektit opettajalta.

Näin asennat Windows käyttöjärjestelmän virtuaalikoneeseen

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

Luento 5. Timo Savola. 28. huhtikuuta 2006

Racket ohjelmointia osa 1. Tiina Partanen Lielahden koulu 2014

Tehtävä 3 ja aikakausilehden kansi pastissi 4. runokirjan kansi

Scratch ohjeita. Perusteet

Muuttujien määrittely

Osoitin ja viittaus C++:ssa

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

Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen

Moodle-oppimisympäristö

Ohjelmoinnin perusteet Y Python

Verkkokaupan ohje. Alkutieto. Scanlase verkkokauppa. Sisäänkirjautuminen

Kiipulan ammattiopisto. Liiketalous ja tietojenkäsittely. Erja Saarinen

TAMK Ohjelmistotekniikka G Graafisten käyttöliittymien ohjelmointi Herkko Noponen Osmo Someroja. Harjoitustehtävä 2: Karttasovellus Kartta

Drupal-sivuston hallintaopas

FOTONETTI BOOK CREATOR

Transkriptio:

OpenGL:n perusteet - Osa 1: Ikkunan luominen 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 ensimmäinen osa. 1 Johdanto Ennen vanhaan grafiikkaa piirrettiin kirjoittamalla se suoraan näytönohjaimen muistiin. Kun tiettyyn osoitteeseen näytönohjaimen muistissa kirjoitettiin tavu, syttyi vastaavaan kohtaan näyttöä vastaavan värinen pikseli. Ja kun yksi pikseli kerran osattiin piirtää, niin monesta pikselistähän sai aikaan mitä tahansa kuvia. Kaikki kuitenkin muuttui 3D-kiihdytinten myötä. Enää grafiikkaa ei piirrettykään tietokoneen prosessorin avulla, vaan näytönohjaimen prosessorin avulla, joka on varta vasten suunniteltu grafiikan piirtämiseen ja suoriutuu tehtävästä näin ollen jopa 300 kertaa nopeammin. Ongelmaksi muodostuu kuitenkin se, että jokainen näytönohjain on erilainen ja eri grafiikkakoodin kirjoittaminen kaikille mahdollisille näytönohjaimille olisi mahdotonta. Tämän takia asia hoidetaan niin sanottujen rajapintojen kautta. Eli näytönohjaimesi ajurit tarjoavat sinulle standardoituja funktioita, jotka toimivat samoin kaikilla näytönohjaimilla. 3D-korttien alkuaikoina tällaisia rajapintoja oli kolme: Glide, Direct3D ja OpenGL. Glide:ä ylläpitäneen 3Dfx:n hävittyä myös Glide kuoli pois. Niinpä nykyään on olemassa kaksi rajapintaa: Microsoftin DirectX-paketin osana oleva Direct3D ja SGI:n alun perin kehittämä, nykyään ARB:n (Architectural Review Board, suurimpien näytönohjainvalmistajien, kuten ATI:n ja Nvidian, yhteenliittymä) ylläpitämä OpenGL. Molemmat ovat suurinpiirtein yhtä nopeita ja molemmilla voi tehdä samat asiat. Ainut ero on, että siinä missä Direct3D toimii vain Windows-käyttöjärjestelmässä (ja Xbox-pelikonsolissa?), OpenGL toimii käytännössä kaikissa käyttöjärjestelmissä ja jopa kämmenmikroissa ja matkapuhelimissa (OpenGL ES). Tämä artikkeli käsittelee OpenGL-rajapintaa, joka on aloittelijalle Direct3D:tä hieman helpompikin. 2 Tarvittavat kirjastot OpenGL:n otsikkotiedostot tulevat kaikkien yleisimpien kääntäjien mukana. Ne ovat nimeltään opengl.h ja glu.h. Lisäksi, koska tässä artikkelissa teemme Windows-ohjelmia, pitää mukaan liittää windows.h. Ohjelma pitää myös linkittää kirjastojen opengl32.lib ja glu32.lib kanssa. Katso

kääntäjäsi ohjeista kuinka tämä tehdään. Esim. Dev-Cpp:lla tämä tehdään kirjoittamalla projektin asetuksista löytyvään linker-kenttään "-lopengl32 -lglu32". Myöhemmin tulet tarvitsemaan vielä tiedostoa glext.h. Kyseinen tiedosto päivittyy vähän väliä, joten imuroi itsellesi uusin versio osoitteesta: http://oss.sgi.com/projects/ogl-sample/abi/glext.h. OpenGL:ää käyttävän C/C++kielisen tiedoston alku näyttäisi siis tyypillisesti tältä: #include <windows.h> #include <gl\gl.h> #include <gl\glu.h> #include <gl\glext.h> 3 Ikkunan luominen Ennen kuin voit piirtää yhtään mitään on sinun luotava ikkuna. Ikkunalla ei varsinaisesti ole mitään tekemistä OpenGL:n kanssa. Se on vain välttämätön paha, joka on tehtävä ennen kuin pääsemme asiaan. Vaikka OpenGL onkin käyttöjärjestelmäriippumaton on ikkunan luominen jokaisella käyttöjärjestelmällä aina erilainen prosessi. Tämä artikkeli käsittelee Windows-ohjelmointia, joten näytän kuinka ikkuna luodaan Windowssissa. Ikkunan luonnissa on periaatteessa 3 eri vaihetta: 1. Rekisteröi ikkunaluokka 2. Luo ikkuna 3. Luo ikkunaan renderöintikonteksti Ensimmäinen vaihe on helppo. Täytetään WNDCLASS-tyyppinen rakenne ja rekisteröidään se RegisterClass()-funktiolla. Kummatkin on määritelty windows.h:ssa. Sinun ei siis itse tarvitse toteuttaa kumpaakaan! WNDCLASS-rakenteen määrittely näyttää tältä: typedef struct _WNDCLASS UINT style; WNDPROC lpfnwndproc; int cbclsextra; int cbwndextra;

HANDLE hinstance; HICON hicon; HCURSOR hcursor; HBRUSH hbrbackground; LPCTSTR lpszmenuname; LPCTSTR lpszclassname; WNDCLASS; ja RegisterClass()-funktion prototyyppi tältä: ATOM RegisterClass( CONST WNDCLASS *lpwndclass // address of structure with class data ); Luodaan nyt yksi WNDCLASS-rakenne, täytetään se asianmukaisesti ja rekisteröidään se. 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; Huomattavaa tässä olivat kentät lpfnwndproc ja lpszclassname. lpszclassname:en pitää keksiä jokin uniikki nimi tälle ikkunaluokalle. Tätä nimeä tarvitaan myöhemmin. Jokainen ikkuna tarvitsee toimiakseen viestinkäsittelijäfunktion. lpfnwndproc sisältää tämän funktion. Palaamme viestinkäsittelijöihin myöhemmin. Loput kentät sisältävät erilaisia asetuksia kuten käytettävän hiiren kursorin jne. Anna niiden olla sellaisena kuin ne ovat yllä, sillä niiden muuttaminen saattaa tehdä ikkunasta OpenGL-yhteensopimattoman. Seuraava vaihe on vähintään yhtä helppo. Luodaan ikkuna CreateWindowEx()-funktiolla. Sen prototyyppi näyttää tältä:

HWND CreateWindowEx( DWORD dwexstyle, // extended window style LPCTSTR lpclassname, // pointer to registered class name LPCTSTR lpwindowname, // pointer to window name DWORD dwstyle, // window style int x, // horizontal position of window int y, // vertical position of window int nwidth, // window width int nheight, // window height HWND hwndparent, // handle to parent or owner window HMENU hmenu, // handle to menu, or child-window identifier HINSTANCE hinstance, // handle to application instance LPVOID lpparam // pointer to window-creation data ); Luodaan nyt ikkuna käyttäen kyseistä funktiota. HWND hwnd; hwnd=createwindowex(ws_ex_appwindow, "OpenGLtutoriaali", " OpenGL:n perusteet - Osa 1: Ikkunan luominen", WS_CLIPSIBLINGS WS_CLIPCHILDREN WS_OVERLAPPEDWINDOW, 0, 0, 800, 600, NULL, NULL, GetModuleHandle(NULL), NULL); Jälleen ainoat huomionarvoiset parametrit ovat lpclassname, johon siis pitää antaa äsken rekisteröimämme ikkunaluokan nimi, lpwindowname, joka sisältää ikkunan otsikkorivillä näkyvän tekstin ja nwidth ja nheight, jotka sisältävät ikkunan koon. x ja y ovat ikkunan sijainti suhteessa näytön vasempaan ylänurkkaan. Funktio palauttaa kahvan luotuun ikkunaan. Ota se talteen, sillä sitä tarvitaan myöhemmin. Loput parametrit ovat jälleen erilaisia asetuksia, jotka vaikuttavat ikkunan ulkonäköön ja käyttäytymiseen. Jos esim. haluat, että ikkunan kokoa ei voi muuttaa, lisää dwstyle-parametrin perään vielä liput "& ~WS_MAXIMIZEBOX & ~WS_SIZEBOX" ja jos haluat, että ikkunalla ei ole reunoja eikä otsikkoriviä vaihda dwstyle-parametrin WS_OVERLAPPEDWINDOW-lippu WS_POPUP-lippuun. Erilaisia asetuksia on siis tuhottomasti.

Width ja Height parametrien ilmaisema ikkunan koko on siis koko ikkunan koko. Osa ikkunasta kuitenkin jää otsikkopalkin ja reunojen alle, joten haluamme ehkä mieluummin määrittää sen alueen koon, jolle voi piirtää, eli ns. asiakasalueen koon. Tähän voimme käyttää apuna AdjustWindowRectEx()-funktiota, joka laskee koko ikkunan koon asiakasalueen koosta. Se käyttää apunaan RECT-rakennetta. Prototyypit ovat tämän näköiset: BOOL AdjustWindowRectEx( LPRECT lprect, // pointer to client-rectangle structure DWORD dwstyle, // window styles BOOL bmenu, // menu-present flag DWORD dwexstyle // extended style ); typedef struct _RECT LONG left; LONG top; LONG right; LONG bottom; RECT; // rc RECT-rakenne sisältää ikkunan vasemman ylänurkan kooridinaatit (left ja top) ja oikean alanurkan koordinaatit (right ja bottom). Se pitää esitäyttää asiakasalueen koolla. Tämän jälkeen kutsumme AdjustWindowRectEx()-funktiota, joka muuttaa RECT-rakenteen vastaamaan koko ikkunan kokoa. Seuraavassa paranneltu ikkunan luonti, joka vielä keskittää ikkunan utelemalla näytön resoluution GetSystemMetrics()-funktiolla. RECT r; r.left=getsystemmetrics(sm_cxscreen)/2-800/2; r.top=getsystemmetrics(sm_cyscreen)/2-600/2; r.right=r.left+800; r.bottom=r.top+600; AdjustWindowRectEx(&r, WS_CLIPSIBLINGS WS_CLIPCHILDREN WS_OVERLAPPEDWINDOW, FALSE, WS_EX_APPWINDOW); HWND hwnd;

hwnd=createwindowex(ws_ex_appwindow, "OpenGLtutoriaali", " OpenGL:n perusteet - Osa 1: Ikkunan luominen", WS_CLIPSIBLINGS WS_CLIPCHILDREN WS_OVERLAPPEDWINDOW, r.left, r.top, r.right-r.left, r.bottom-r.top, NULL, NULL, GetModuleHandle(NULL), NULL); Viimeinen vaihe on hankalin. Meidän pitää luoda ikkunaan renderöintikonteksti. Tässäkin on kolme vaihetta: ensin tehdään laitekonteksti, sitten valitaan pikseliformaatti ja lopuksi vasta luodaan renderöintikonteksti. Laitekonteksti luodaan GetDC()-funktiolla, jolle annetaan parametrinä ikkunan kahva. HDC hdc; hdc=getdc(hwnd); if (!hdc) return 0; Luodaksemme pikseliformaatin meidän täytyy täyttää PIXELFORMATDESCRIPTOR-rakenne. Sen määrittely näyttää tältä: typedef struct tagpixelformatdescriptor // pfd WORD nsize; WORD nversion; DWORD dwflags; BYTE ipixeltype; BYTE ccolorbits; BYTE credbits; BYTE credshift; BYTE cgreenbits; BYTE cgreenshift; BYTE cbluebits; BYTE cblueshift; BYTE calphabits; BYTE calphashift; BYTE caccumbits; BYTE caccumredbits;

BYTE caccumgreenbits; BYTE caccumbluebits; BYTE caccumalphabits; BYTE cdepthbits; BYTE cstencilbits; BYTE cauxbuffers; BYTE ilayertype; BYTE breserved; DWORD dwlayermask; DWORD dwvisiblemask; DWORD dwdamagemask; PIXELFORMATDESCRIPTOR; Täytetään nyt kyseinen rakenne asianmukaisesti, jonka jälkeen valitsemme ja asetamme pikseliformaatin funktioilla ChoosePixelFormat() ja SetPixelFormat(). Ideana on, että täytämme PIXELFORMATDESCRIPTOR-rakenteeseen tiedot siitä millaisen pikseliformaatin haluamme. Valitsemme sitten lähimmän vastaavan ChoosePixelFormat()-funktiolla ja asetamme sen palauttaman pikseliformaatin varsinaiseksi pikseliformaatiksi. 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;

Lopuksi luomme renderöintikontekstin. Tämä tehdään ns. wigle-funktioilla wglcreatecontext() ja wglmakecurrent(). HGLRC hrc; hrc=wglcreatecontext(hdc); if (!hrc) return 0; if (!wglmakecurrent(hdc, hrc)) return 0; Lopuksi ikkuna tehdään vielä näkyväksi ShowWindow()-funktiolla ja tuodaan etualalle funktioilla SetForegroundWindow() ja SetFocus(). ShowWindow(hwnd, SW_SHOW); SetForegroundWindow(hwnd); SetFocus(hwnd); 4 Viestinkäsittelijä Koska Windowsissa pyörii useampi ohjelma yhtä aikaa täytyy niillä olla jokin tapa kommunikoida toistensa ja Windowssin kanssa. Tämä tapa on ns. viestinkäsittelijäfunktio, jollainen jokaisella ikkunalla täytyy olla. Aina kun Windowssilla on jokin viesti jollekkin ohjelmalle se kutsuu kyseisen ohjelman viestinkäsittelijää. Sinun on siis itse toteutettava ohjelmallesi viestinkäsittelyfunktio ja sen prototyypin on aina näytettävä seuraavalta: LRESULT CALLBACK WindowProc( HWND hwnd, // handle of window UINT umsg, // message identifier WPARAM wparam, // first message parameter LPARAM lparam // second message parameter ); Funktiolla on siis neljä parametria, joista meitä kiinnostaa erityisesti umsg. Se sisältää itse viestin. Sillä on tuhottomasti erilaisia mahdollisia arvoja esim: WM_ACTIVE, WM_CLOSE ja WM_PAINT. Sinun ei kuitenkaan tarvitse reagoida niihin kaikkiin vaan ainoastaan niihin joihin haluat. Ainut "pakollinen" käsiteltävä on WM_CLOSE, johon reagoidaan kutsumalla PostQuitMessage()-funktiota parametrillä 0. Ikkuna nimittäin saa kyseisen viestin, kun käyttäjä

yrittää sulkea sen esim. painamalla sulkemispainiketta. Muita tärkeitä viestejä ovat WM_SIZE, jonka ohjelma saa aina kun sen ikkunan kokoa muutetaan ja WM_PAINT, jonka ohjelma saa aina kun ikkuna täytyy piirtää uudestaan. Näin voi käydä esim. kun ikkunan edessä ollut toinen ikkuna on siirretty syrjään. WM_PAINT-viestin käsittely alkaa aina BeginPaint()-funktiolla ja loppuu EndPaint()-funktioon. Jos käsittelet jonkin viestin palauta 0 ja jos taas et, lähetä se DefWindowProc()-funktiolle ja palauta sen palauttama arvo. Viestin käsittely lienee helpointa toteuttaa switch-rakenteella. Seuraavassa yksinkertainen esimerkkitoteutus: LRESULT CALLBACK WindowProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) switch (umsg) // Ikkuna yritetään sulkea kutsu PostQuitMessage()-funktiota. case WM_CLOSE: PostQuitMessage(0); return 0; // Viestiä ei käsitelty kutsu DefWindowProc()-funktiota. return DefWindowProc(hwnd, umsg, wparam, lparam); 5 Pääfunktio DOS:issa ja Linuxsissa pääfunktio on yleensä muotoa: int main(int argc, char* argv[]). Windowsissa asia on hieman monimutkaisempi. Pääfunktio on muotoa: int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow); Paljon mutkikkaita parametrejä, mutta eipä hätää, niistä ei tarvitse välittää. Lisäksi pääfunktiosta täytyy löytyä standardi viestinkäsittelysilmukka: MSG msg; while(1)

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) if (msg.message==wm_quit) break; TranslateMessage(&msg); DispatchMessage(&msg); else // Lisää oma suoritettava ohjelmakoodisi tähän // Tyypillisesti tässä välissä suoritetaan // pelilogiikkaa ja renderöidään yksi "frame". Eli kyseessä on loputon silmukka, joka purkaa jonosta Windowssin ohjelmalle lähettämiä viestejä ja käsittelee ne. Ainoastaan silloin, kun ei ole yhtään viestiä käsiteltävänä voidaan suorittaa ohjelman omaa koodia. Silmukasta saa poistua vasta kun ohjelma saa sulkemisviestin WM_QUIT. Jonka jälkeen ohjelman tulee sammua. 6 Grafiikan piirtäminen Kun ikkuna on luotu päästään itse asiaan eli grafiikan piirtoon. Ensin on määriteltävä viewport eli se alue ikkunasta, jolle piirretään. Tämä tapahtuu glviewport()-funktiolla. Sen prototyyppi näyttää tältä: void glviewport(glint x, GLint y, GLsizei width, GLsizei height); x ja y ovat viewportin vasemman alareunan koordinaatit suhteessa ikkunan vasemman alareunan koordinaatteihin ja width ja height viewportin leveys ja korkeus (siis pikseleissä ilmaistuna). Esim. jos meillä on 800x600 ikkuna ja haluamme koko sen alan piirtämistä varten käyttäisimme kutsua glviewport(0, 0, 800, 600);. Viewport voi myös periaatteessa mennä ikkunan reunojen yli, jolloin ikkunan ulkopuolisia alueita ei yksinkertaisesti piirretä, mutta bugisimmilla näytönohjaimenajureilla tämä saattaa aiheuttaa mitä omituisempia ongelmia. Kun viewport on määritelty täytyy vielä määritellä koordinaatisto (tai pidemminkin projektiomatriisi, mutta siitä enemmän joskus toiste). OpenGL:ssä on kaksi erilaista koordinaatistoa: 2D-koordinaatisto 2D-grafiikkaa varten ja 3D-koordinaatisto 3D-grafiikkaa varten.

Käytämme tässä esimerkissä hieman helpompaa 2D-koordinaatistoa. Se luodaan funktiolla gluortho2d(), jonka prototyyppi näyttää tältä: void gluortho2d(gldouble left, GLdouble right, GLdouble bottom, GLdouble top); OpenGL:ssä X-akseli kulkee vaakasuorassa ja Y-akseli pystysuorassa. left-parametri kertoo X- akselin arvon viewportin vasemmassa reunassa ja right X-akselin arvon viewportin oikeassa reunassa. Vastaavasti bottom ja top Y-akselille. Esim. jos haluaisimme määritellä koordinaatiston niin, että origo olisi viewportin keskellä ja sekä Y- että X-akseli ulottuisivat 10 yksikköä joka suuntaan käyttäisimme kutsua gluortho2d(-10, 10, -10, 10);. Jos taas meillä olisi kokoa 800x600 oleva viewport ja haluaisimme koordinaatiston, jossa origo on vasemmassa alanurkassa ja yksi pikseli vastaa aina yhtä yksikköä käyttäisimme kutsua gluortho2d(0, 800, 0, 600);. Vihdoin ja viimein pääsemme piirtämään itse grafiikkaa. OpenGL:ssä on monta tapaa piirtää, mutta helpoin niistä on varmasti glbegin() glend() parin käyttäminen. Ensin kutsutaan glbegin()- funktiota, jonka prototyyppi näyttää tältä: void glbegin(glenum mode); Sillä on siis vain yksi parametri mode, joka kertoo mitä piirretään. Sen mahdolliset arvot ovat: GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_QUADS, GL_QUAD_STRIP ja GL_POLYGON. Näistä tärkeimmät ovat GL_POINTS, joka piirtää pisteitä, GL_LINES, joka piirtää viivoja ja GL_TRIANGLES, joka piirtää kolmioita. glbegin()-kutsun jälkeen kutsumme glvertex2f()-funktiota toistuvasti. Sille annetaan piirrettävän primitiivin lakipisteen koordinaatit. Sen prototyyppi näyttää tältä. void glvertex2f(glfloat x, GLfloat y); Eli X ja Y koordinaatit annetaan parametrina. GL_POINTS tapauksessa jokaiseen glvertex2f()- funktion määräämiin koordinaatteihin piirretään piste. GL_LINES tapauksessa aina kahden glvertex2f()-kutsun määräämien koordinaattien välille piirretään viiva ja GL_TRIANGLES tapauksessa aina kolmen kutsun välille kolmio. Käytimme tässä siis glvertex2f()-funktiota koska

meillä on 2D koordinaatisto. Myöhemmin 3D-koordinaatistossa käytämme glvertex3f()-funktiota. Lopuksi kun kaikki tarvittavat glvertex2f() kutsut on tehty kutsutaan glend()-funktiota. Se ei ota yhtään parametriä. Seuraava esimerkki piirtää viivan pisteestä (0, 0) pisteeseen (200, 100). glbegin(gl_lines); glvertex2f(0, 0); glvertex2f(200, 100); glend(); Piirrettävälle primitiiville voidaan myös asettaa väri glcolor3f()-funktiolla. Prototyyppi näyttää tältä: void glcolor3f(glfloat red, GLfloat green, GLfloat blue);. Se ottaa parametrinaan värin red, green ja blue arvot. Nämä arvot ovat väliltä 0-1 ja niitä sekoittamalla voidaan muodostaa kaikki mahdolliset värit. Väri voidaan asettaa halutessa jokaiselle primitiivin lakipisteelle erikseen. Jos primitiivin lakipisteet ovat eri väriset niiden väliin jäävien pikselien värit interpoloidaan. Eli jos viivan toinen pää on musta ja toinen valkea, on viiva keskeltä harmaa. Seuraava esimerkki piirtää kolmion, jonka yksi nurkka on punainen, yksi sininen ja yksi vihreä. glbegin(gl_triangles); glcolor3f(1, 0, 0); glvertex2f(0, 5); glcolor3f(0, 1, 0); glvertex2f(-5, -5); glcolor3f(0, 0, 1); glvertex2f(5, -5); glend(); Vielä pari juttua ennen kuin kaikki on täydellistä. Nimittäin ennen piirtoa ikkuna on tyhjennettävä kaikesta mahdollisesta muusta grafiikasta, tämä tapahtuu glclear()-funktiolla, jolle annetaan parametriksi GL_COLOR_BUFFER_BIT. Se toinen juttu on sitten niin sanottu kaksoispuskurointi.

Nimittäin OpenGL.ssä on kaksi piirtopintaa (tosin vain silloin kun pikseliformaaatti luotiin PFD_DOUBLEBUFFER parametrillä), joista toinen on aina näyttövuorossa ja toinen aina piirtovuorossa. Niinpä aina kun piirrät jotain se ei ilmesty näytölle vaan sille toiselle piilossa olevalle pinnalle. Jotta grafiikka saataisiin näkyväksi täytyy nämä pinnat piirtämisen jälkeen vaihtaa keskenään SwapBuffers()-funktiolla, jonka prototyyppi näyttää tältä: BOOL SwapBuffers( HDC hdc //Device context whose buffers get swapped ); Eli se ottaa parametrinään ikkunan luonnin yhteydessä saadun laitekontekstin. 7 Esimerkkiohjelma Lopuksi täydellinen esimerkkiohjelma, joka luo ikkunan ja piirtää sen keskelle kolmion. Huomaa kuinka itse ikkunan luominen vie suurimman osan koodista, kun taas varsinainen grafiikan piirto vie vain muutaman rivin. Tämän takia Internet on pullollaan erilaisia "kehys"-kirjastoja, joilla voit luoda ikkunan nopeasti parilla funktion kutsulla. Näistä kuuluisimpia ovat GLUT ( http://www.xmission.com/~nate/glut.html ) ja GLFW ( http://glfw.sourceforge.net/ ). Vielä mainittakoon, että jos haluat saada käyttäjältä syötettä, niin näppäimen tila voidaan lukea Windowsissa GetAsyncKeyState()-funktiolla ja hiiren tila GetCursorPos()-funktiolla. Lisää tietoa näiden käytöstä löytyy msdn:stä ( http://msdn.microsoft.com/ ). Jos käytät GLUT:a tai GLFW:tä, niin näistä kyllä löytyy taas omat funktionsa näppäinten lukuun. Voit imuroida oheisen lähdekoodin ja valmiiksi käännetyn version tästä: http://www.suomipelit.com/files/artikkelit/testi1.zip. #include <windows.h> #include <gl\gl.h> #include <gl\glu.h> #include <math.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 // 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) // Luo ikkuna if (!luoikkuna(800, 600, "OpenGL:n perusteet - osa 1: Ikkunan luominen")) return 0; // Määrittele viewport ja koordinaatisto koko ikkunan kokoiseksi glviewport(0, 0, 800, 600); glmatrixmode(gl_projection); gluortho2d(-13, 13, -10, 10); // Viestinkäsittelysilmukka

MSG msg; while(1) if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) if (msg.message==wm_quit) break; TranslateMessage(&msg); DispatchMessage(&msg); else // Tyhjennä puskuri glclear(gl_color_buffer_bit); // Piirrä kolmio glbegin(gl_triangles); glcolor3f(1, 0, 0); glvertex2f(0, 5); glcolor3f(0, 1, 0); glvertex2f(-5, -5); glcolor3f(0, 0, 1); glvertex2f(5, -5); glend(); // Vaihda puskuri näytölle. SwapBuffers(hdc); return 0; 8 Loppusanat Tässä artikkelissa opit luomaan OpenGL-yhteensopivan ikkunan Windows-käyttöjärjestelmässä. Seuraavassa osassa siirrymme varsinaisen 3D-grafiikan piirtoon. 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.