Post on 28-Jan-2021
Peliohjelmointi:
Peliohjelmointirajapinnoista Teppo Soininen & Antti Puhakka
Lähteet: www.opengl.org, OpenGL-spesifikaatio,
Tietokonegrafiikka –luentomoniste, MSDN, Introduction to 3D
Game Programming with DirectX 9.0
2
Open Graphics Library • Open Graphics Library (OpenGL) sai alkunsa 1992 SGI:n
esittelemästä 3D rajapinnasta nimeltä IRIS GL
• OpenGL oli alkuaikoina tähdätty ammattikäyttöön
tehovaatimustensa takia − Windows NT 4.0:ssa oli OpenGL jo mukana (muistanette screensaverit
joissa pyöri esim. kellonaika?)
− Grafiikkaraudan kehittyessä myös halvemmat viihdekäyttöön tarkoitetut
laitteet kykenivät vastaamaan OpenGL:n tehovaatimuksiin
− Nykyään yksi eniten käytetty grafiikka API
3
• OpenGL on alustariippumaton grafiikka API − Sisältää ~150 funktiota
− Ei sisällä mitään toiminnallisuutta ikkunoinin tai syötteidenkäsittelyn
toteuttamiseksi
• OpenGL:ää standardoi OpenGL Architecture Review Board
(ARB) − 3DLabs, Apple, ATI, NVIDIA, IBM, Intel, SGI, Sun Microsystems, HP…
• OpenGL rajapinta löytyy muutamalle eri kielelle (C/C++, ADA,
Fortran ja Java)
• OpenGL on käytännössä iso tilakone − Yli sata globaalia tilamuuttujaa (on/off)
4
OpenGL 1.1:n tilakone
5
• Ohjelmointinäkökulmasta OpenGL on kokoelma funktiota
joiden avulla voi: − Määritellä geometrisia 2D/3D objekteja jotka koostuvat kymmenestä
perusprimitiivistä
GL_POINTS
GL_LINES, GL_LINESTRIP, GL_LINE_LOOP
GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN
GL_QUADS, GL_QUAD_STRIP
GL_POLYGON
− Hallita määriteltyjen objektien piirtämistä frame bufferiin
(transforamaatiot, valaistukset jne)
glRotate(), glTranslate(), glScale()...
6
• OpenGL:stä ei löydy funktioita monimutkaisten primitiivien
(pallot, kuutiot jne) piirtämiseen − Monimutkaisempien primitiivien piirtämiseen tehdään usein omat
”Mesh” –luokat
− Jotkut korkeammantason rajapinnat tarjoavat apuja monimutkaisempien
muotojen piirtämiseen (yleensä hyödyttömiä testaamisen ulkopuolella)
7
• OpenGL hoitaa piirtämisen moniosaisella pipelinella − Jokaisessa osassa tehdään yksi primitiivien esittämiseen tarvittava
vaihe
OpenGL Visualization Programming Pipeline
8
• OpenGL v1.5 (29.7.2003) − Yksi merkittävimmistä uudistuksista tässä versiossa olivat BufferObjektit
• OpenGL v2.0 (7.9.2004) − OpenGL Shader Language vakiona (aikaisemmin erillinen)
− Textuureja, joiden koko ei ole 2:n potenssissa, voidaan käyttää
(virheettömästi!)
9
OpenGL tukikirjastot • SGI pitää kirjaa OpenGL:ään tehdyistä kirjastoista
− ARB:n speksaamia kirjastoja n. 40
− Muita kirjastoja tässä listassa on 300+
• Käyttöjärjestelmien mukanan tulee kirjastoja ikkunoinnin
hoitamiseen OpenGL:n kanssa − GLX (Unix)
− WGL (Windows)
− AGL (Apple)
10
• GL Utility Toolkit (GLUT) − Tarjoaa tominnallisuutta mm. ikkunoinnille, menuille, syöttölaitteille ja
joillekkin perus muodoille (esim. pallo ja kuutio)
− Soveltuu lähinnä pieniin OpenGL sovelluksiin
− Rutiinit cross-platform ominaisuuksiensa takia liian hitaita todelliseen
tehokäytöön (esim. peleihin) -> yleensä käytetetään käyttöjärjestelmän
omia ikkunointi menetelmiä (GLX, WGL)
− GLUT ei ole avointa lähdekoodia (Mark Kilgard omistaa oikeudet
GLUT:iin)
− Uusin versio 2.7
11
Simple Directmedia Layer (SDL) • Alustariippumaton grafiikka- ja multimediarajapinta, toimii
Windowsissa, Linuxissa, Macissa, Solariksessa jne. jne.
• Toiminnallisuutta mm. 2D- ja 3D-grafiikka, audio, syötelaitteet
(hiiri, näppis ja joystick), tapahtumat, timerit, ...
• SDL on ohut wrapperikerros, joka kutsuu esim. OpenGL-
rutiineja
• Kirjoitettu alunperin C:llä
• Bindingeja löytyy ainakin seuraaville kielille: Ada, Eiffel, Java,
Lua, ML, Perl, PHP, Pike, Python, Ruby
12
⇒ Bindingit lisäävät käytännössä kuitenkin yhden kerroksen lisää
raudan ja koodin väliin josta seuraa hidastaa koodia (ei usein
käytetä tehoa vaativissa sovelluksissa, kuten peleissä)
• Käytännössä SDL on (kuuleman mukaan) melko hyvä ja
yksinkertainen
• Suosiota lisännyt SDL:n open source -lisenssi − Uusin versio 1.2
13
Microsoft DirectX • DirectX-rajapinnan kehittäminen aloitettiin Microsoftilla joskus
90-luvun alussa − OpenGL:n tehovaatimuksiin pystyttiin vastaamaan ainoastaan kallilla
ammattilais laitteistoilla
− Ideana kehittää viihdekäyttöön tarkoitettu vatine OpenGL:lle
• Microsoftin aloitti DOS:in hyllyttämisen − Piti saada pelien kehittäjät siirtymään Windows ympäristöön
− Tarvittiin rajapinta jonka avulla pelejä voitiin tehdä
14
• Pelikoodaajat olivat aluksi haluttomia siirtymään Windowsiin − DOS:ssa koodaajat pääsivät käsiksi suoraan "rautaan" ja tämän
ansiosta saatiin aikaan tehokasta koodia
− Windowsissa pääsi "rautaan" käsiksi vain WinAPI:n kautta (ei
tehokasta)
• Microsoft yritti korjata tämän ongelman WinG rajapinnan avulla − Rajapinta oli vaikea käyttää ja melko tehoton
− Civilization ja Civilization 2 tiettävästi kuitenkin käyttivät WinG API:a
• DirectX:n ensimmäisen verion kehittely aloitettiin 1994 ja sitä
tuettiin Windows 95:stä eteenpäin
15
• DirectX:n ensimmäiset versiot olivat erittäin kokeellisia ja
hankalia käyttää − OpenGL:n tehovaatimuksiin pystyttiin nyt vastaamaan kuluttajille
sopivan hintaisilla 3D kalustolla ja tämä aiheutti vastarintaa; "miksi
käyttää DX:ää kun hyvä vaihtoehto on jo olemassa?"
• DirectX 5 oli ensimmäinen joka saavutti enemmän
hyväksyntää
• Nykyään DirectX:ää käytetään erittäin paljon viihteeseen
suunnattujen sovellusten ohjelmoinnissa − Laitteistovalmistajat tukevat todella hyvin
− XBox käyttää modifioitua DirectX 8 rajapintaa
− Pelejä: FarCry, Doom 3
16
• Viimeisin versio on DirectX 9.0
• DirectX on kokoelma ohjelmointirajapintoja pelien ja
multimedian ohjelmointiin 1) DirectGraphics - 2D/3D piirtämiseen (yhdistetty DirectDraw ja Direct3D)
2) DirectInput - syöttölaitteiden käsittelyyn (tuki mm. force feedbackille)
3) DirectPlay - rajapinta verkkopelaamista varten
o Ei saavuttanut koskaan erityisen suurta suosiota
4) DirectSound - äänenkäsittelyyn (waveform)
5) DirectMusic - pakatun musiikin toistamiseen (MIDI, DMusic Producer)
o Toimii DirectSound:in päällä
6) DirectShow - multimedian toistoon, tallentamiseen ja käsittelyyn (mm.
videostreamit)
17
o Perustuu erilaisten ”filtterien” yhdistelemiseen
7) DirectSetup - DirectX:n asentamisen hoitava API
8) DirectX Media Objects - streamattavat mediaobjektit (enkooderit,
dekooderit ja efektit)
o Yksinkertaisempi käyttää kuin DirectShow
• DirectX:stä käytetään eniten Direct3D:tä
• Useimmat em. rajapinnoista aiotaan korvata Windows Vistan
julkaisemisen jälkeen − XInput, XACT, Media Foundation... − Lisää tukea suojatulle sisällölle jne.
18
Direct3D • Direct3D toiminta perustuu Hardware Abstraction Layeriin
(HAL) − Grafiikkakorttien valmistajat toteuttavat oman HAL:n DirectX:ää varten
Direct3D Hardware Abstraction Layer
19
• Kaikki mitä on nahdollista tehdä, tehdään "raudalla" − Mikäli grafiikkakortti ei tue jotain toiminnallisuutta, sitä voidaan
mahdollisesti simuloida ohjelmistollisesti
− Direct3D:n kautta voidaan kysellä tukea eri toiminnallisuuksille (erityisen
tärkeää silloin jos jotain toiminnallisuutte ei voi simuloida
ohjelmistollisesti)
• Direct3D on toimintaperiaatteeltaan hyvin samantapainen kuin
OpenGL − Piirrellään vertexeistä koottuja perusprimitiivejä ruudulle (TriangleFan,
TriangleStrip, TriangleList, LineStrip, LineList, PointList)
20
Direct3D Fixed Function and Programmable Transformation and Lighting Pipeline
• Direct3D ei ole alustariippumaton joten sen rutiinit on tehty
ainoastaan Winows mielessä − Tästä johtuen Direct3D:ssä on joitain erikoisen tuntuisia piirteitä
21
Windows-ohjelmoinnista • Käytettäessä suoraan Windows API:a grafiikkaohjelmointiin
täytyy huolehtia itse ikkunoiden luomisesta, viestien
käsittelystä jne.
⇒ Jo pelkän ”Hello World”-tyyppisen sovelluksen aikaansaamiseksi tarvitaan paljon koodia
• Perus-Windows-sovellus sisältää WinMain-pääohjelman, joka
on muotoa int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR
lpCmdLine, int nCmdShow )
22
• Pääohjelma tekee yleensä ainakin seuraavat asiat:
1. Rekisteröi ikkunaluokan
2. Luo tähän luokkaan kuuluvan ikkunan
3. Tekee ikkunan näkyväksi ja asettaa sen päivittymään
4. Pyörii silmukassa, joka lukee viestejä sovelluksen
viestijonosta, ja ohjaa viestit eteenpäin ikkunoiden
viestinkäsittelijöille
5. Lopettaa ohjelman saatuaan WM_QUIT-viestin
• Ikkunaluokan ominaisuudet määrätään sijoittamalla arvoja
WNDCLASS- tai WNDCLASSEX-tyyppisen struktin kenttiin
23
⇒ Strukti annetaan parametriksi RegisterClass- tai RegisterClassEx-funktiolle, joka ”rekisteröi” luokan:
// Create a WNDCLASSEX struct and fill its memory area with zeros WNDCLASSEX wc; ZeroMemory( &wc, sizeof( WNDCLASSEX ) ); // Fill in the needed members of the struct created above wc.cbSize = sizeof( WNDCLASSEX ); // size of the window struct in bytes wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // window styles to use wc.lpfnWndProc = MsgProc; // function name of event handler wc.hInstance = hInstance; // handle to this apps instance wc.hbrBackground = ( HBRUSH )GetStockObject( GRAY_BRUSH ); // background colour of window wc.hIcon = LoadIcon( NULL, IDI_APPLICATION ); // icon for the app window wc.hIconSm = LoadIcon( NULL, IDI_APPLICATION ); // icon when minimized to taskbar wc.hCursor = LoadCursor( NULL, NULL ); wc.lpszClassName = strAppname; // name for this class // Register the window class
RegisterClassEx( &wc );
• Itse ikkuna luodaan CreateWindow tai CreateWindowEx-
funktiolla, jolle annetaan ikkunaluokan nimi g_hWnd = CreateWindow( strAppname, strAppname, WS_OVERLAPPEDWINDOW, 10, 10, g_D3DSettings.m_nDeviceWidth, g_D3DSettings.m_nDeviceHeight, NULL, NULL, wc.hInstance, NULL ); ShowWindow( g_hWnd, nCmdShow ); UpdateWindow( g_hWnd );
24
• Funktio palauttaa HWND-tyyppisen ”kahvan” luotuun ikkunaan
o Samasta luokasta voi halutessaan luoda useamman ikkunan
• Pääsilmukka lukee viestejä PeekMessage-funktiolla
o Vastaava GetMessage-funktio jää odottamaan että viestejä on jonossa
// Create a MSG struct for the widows messages MSG msg; ZeroMemory( &msg, sizeof( msg ) ); // The windows message loop while ( msg.message != WM_QUIT ) { // If messages need to be processed do it, otherwise enter game loop if ( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } else { GameLoop(); } }
25
• DispatchMessage-funktio välittää viestit asianmukaisille
ikkunoille
o TranslateMessage muuttaa mm. ASCII-merkkeihin liittyviä näppäintapahtumaviestejä WM_CHAR-viesteiksi
• Jokaiselle ikkunaluokalle määritellään oma
viestinkäsittelyfunktio, joka käsittelee ko. luokan ikkunoita
koskevat viestit: LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
• Viesti voi olla esimerkiksi näppäimistöviesti tai WM_PAINT, joka
tarkoittaa että ikkuna kaipaa päivittämistä
26
• Käytettäessä perinteistä Windows GDI:tä (Graphics Device
Interface) piirto ikkunaan tapahtuu siihen liittyvän Device
Context:in (DC) kautta
• Device context sisältää tiedon kyseiseen ikkunaan tai
laitteeseen liittyvistä parametreistä:
o käytettävissä olevat grafiikkaobjektit, kuten kynät, bittikartat, fontit, värit
o tausta, skaalaaminen, koordinaatistomuunnokset, leikkausalue (clipping region) jne.
• Kutsumalla BeginPaint-funktiota ikkunafunktio saa ikkunaan
liittyvän device context:in sekä PAINTSTRUCT-tyyppisen struktin
27
⇒ tämän yksi kenttä on RECT-tyyppinen strukti, joka sisältää päivitettävän alueen vasemman yläkulman ja oikean alakulman koordinaatit
hDC = BeginPaint( hWnd, &PaintStruct );
...
EndPaint( hWnd, &PaintStruct );
⇒ DC:n koko ikkunaan saa myös GetDC-kutsulla, ja piirtoalueen GetClientRect-kutsulla
• OpenGL-ohjelmoinnissa tarvitaan lisäksi DC:hen läheisesti
liittyvä Rendering Context, johon piirtäminen tapahtuu
o Tämän saa wglCreateContext(hDC) -kutsulla, ja se aktivoidaan wglMakeCurrent(hDC,hRC) -kutsulla
• DirectX toimii kuitenkin hieman eri tavalla, koska se perustuu
Microsoftin COM-komponenttimalliin
28
DirectX ja COM • DirectX perustuu Microsoftin Component Object Model:iin
(COM)
• COM komponenteissa kantava idea on luoda komponentteja
(olioita) jolta komponenttien käyttäjä pyytää jotain tiettyä
interfacea käyttöönsä − Yksi komponentti sisältää usein monta erillistä interfacea
− Komponenttien erilliset interfacet eivät pääse toistensa sisältämään
tietoon tai toiminnallisuuteen käsiksi (muuten kuin normaaleja reittejä)
− Interfacet eivät muutu niiden julkaisun jälkeen
29
• Kaikki COM komponenttien interfacet periytetään IUnknown
interfacesta jolla on ainoastaan metodit: AddRef(),
QueryInterface() ja Release()
• ”Object” ja ”Interface” käsitteet sekavia MS:n omassa jopa
dokumentaatiossa − In casual usage, an object may sometimes be referred to by the name
of its principle interface. However, strictly speaking, the two terms are
not interchangeable
COM Object
IUnknown
Interface1
Interface2
30
Toimintaperiaate:
1) Käyttäjä pyytää interfacen joltain COM komponentilta
2) Komponentti antaa pyydetyn interfacen käyttäjälle ja lisää omaa reference
counttia AddRef() -metodilla jotta tietää montako interfacea siitä on tehty
3) Käyttäjä tekee saamallaan interfacella mitä haluaa
4) Käyttäjä ei tarvitse interfacea ja tuhoaa sen kaikille COM interfaceille
yhteisellä metodilla Release()
5) Release() -metodin seurauksenä COM komponentti (jolta interface oli
pyydetty) vähentää reference counttiaan, mikäli interface oli viimeinen
käytössä oleva interface tästä komponentista, komponentti tuhotaan.
Muuten siltä voi edelleen käyttää olemassaolevien osoittimien kautta.
31
• Release() ja AddRef() metodien taustalla on muistinkulutuksen
tehostaminen ja hallittavuuden parantaminen − Tavallisimpia ongelmia peleissä on muistin vuotamisesta aiheutuva
pelin kaatuminen (peleissä toistetaan usein samoja rutiineja erittäin
usein)
• COM interfacen metodien paluuarvot ovat usein HREF
tyyppisiä − Kertovat boolean tyyppisesti (tosin moniarvoisesti) tuloksen laadusta
− Tyypillisiä arvoja ovat: S_OK, E_FAIL (S -> Success, E -> Error)
• DirectX:n funktiot ottavat tyypillisesti parametrikseen jonkin
DX:n määrittelemän struktin, jonka jäseniä täytetään tarpeen
mukaan
32
Esimerkki: luodaan IDirect3D9 interface ja pyydetään siltä IDirect3DDevice9 g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
...
result = g_pD3D->CreateDevice( D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWndTarget,
D3DCREATE_HARDWARE_VERTEXPROCESSING,
&d3dpp,
g_pDevice );
LPDIRECT3D9 g_pD3DCopy = g_pD3D;
g_pD3DCopy->AddRef();
...
g_pDevice->Release();
g_pD3DCopy->Release();
...
g_pD3D->Release();