178 94 8MB
Polish Pages [626] Year 2003
PROGRAMOWANIE GIER
Spis treści List od w ydaw cy s e r ii........................................................................................ .. 0 A u to ra c h ............................................................................................................
P rzed m o w a............................................................................................................ W p ro w a d z e n ie .....................................................................................................2 1
Część I
Wprowadzenie do OpenGL i DirectX..........................................23
R ozdział 1 . O penGL i D ir e c t X ................................................................................................2 5 Dlaczego tworzymy gry?................................................................................................................25 Świat g ie r........................................................................................................................ 26 Elementy g ry ...................................................................................................................... 26 Narzędzia............................................................................................................................. 28 OpenGL........................................................................................................................ 30 Historia OpenGL................................................................................................................... 3 0 Architektura OpenGL.................................................................................................. 31 31 Biblioteka GLU............................................................................................................ Pakiet bibliotek GLUT.......................................................................................................... 3 2 Programy....................................................................................................................... 33 33 DirectX.......................................................................................................................... Historia DirectX.................................................................................................................. 3 4 Architektura DirectX.................................................................................................... 35 DirectX Graphics.............................................................................................................. 3 5 DirectX A udio................................................................................................................ 3 5 Directlnput........................................................................................................................... DirectPlay............................................................................................................................ DirectShow................................................................................................................... 3 5 DirectSetup.......................................................................................................................... Porównanie OpenGL i DirectX..................................................................................................... 3 7 Podsumowanie.................................................................................................... 37 R ozdział 2 . K orzysta nie z OpenGL w s y ste m ie W in d o w s .................................................. 3 9 Wprowadzenie do programowania w systemie Windows.......................................................... 3 9 Podstawowa aplikacja systemu Windows............................................................................. 4 1 Funkcja WinMain()................................................................................................................. .. Procedura okienkowa............................................................................................................ 4 2 Obsługa komunikatów.................................................................................................. 4 3 Klasy okien............................................................................................................... 44 Określenie atrybutów klasy okna.....................................................................................4 5 Ładowanie ikon i kursora m yszy................................................................................. 4 5
6
OpenGL. Program ow anie gier
Rejestracja klasy........................................................................................................................48 Tworzenia okna.........................................................................................................................48 Pętla przetwarzania komunikatów........................................................................................... 50 Kompletna aplikacja systemu Windows................................................................................. 52 Wprowadzenie do funkcji WGL.....................................................................................................55 Kontekst tworzenia grafiki.......................................................................................................56 Korzystanie z funkcji W G L .................................................................................................... 56 Funkcja wglCreateContext().............................................................................................56 Funkcja wglDeleteContext().............................................................................................56 Funkcja wglMakeCurrentQ...............................................................................................57 Formaty pikseli................................................................................................................................ 58 Pole nSize.................................................................................................................................. 58 Pole dwFlags............................................................................................................................. 59 Pole iPixelType.........................................................................................................................59 Pole cColorBits.........................................................................................................................59 Aplikacja OpenGL w systemie Windows......................................................................................60 Pełnoekranowe aplikacje OpenGL................................................................................................ 67 Podsumowanie................................................................................................................................ 69 Rozdział 3.
Przegląd te o rii g rafiki trójw ym iaro w ej..............................................................7 1 Skalary, punkty i wektory............................................................................................................... 71 Długość w ektora.......................................................................................................................72 Normalizacja wektora.............................................................................................................. 72 Dodawanie wektorów............................................................................................................... 73 Mnożenie wektora przez skalar............................................................................................... 73 Iloczyn skalarny wektorów......................................................................................................73 Iloczyn wektorowy....................................................................................................................74 Macierze........................................................................................................................................... 75 Macierz jednostkowa............................................................................................................... 75 Macierz zerowa......................................................................................................................... 75 Dodawanie i odejmowanie macierzy.......................................................................................76 Mnożenie macierzy.................................................................................................................. 76 Implementacja działań na macierzach.....................................................................................78 Przekształcenia................................................................................................................................ 79 Przesunięcie.............................................................................................................................. 80 Obrót.......................................................................................................................................... 80 Skalowanie................................................................................................................................ 82 Rzutowanie.......................................................................................................................................82 Rzutowanie izometryczne........................................................................................................83 Rzutowanie perspektywiczne.................................................................................................. 84 Obcinanie......................................................................................................................................... 8 6 Światło............................................................................................................................................. 8 6 Światło otoczenia......................................................................................................................87 Światło rozproszone................................................................................................................. 87 Światło odbijane........................................................................................................................ 8 8 Odwzorowania tekstur..................................................................................................................... 8 8 Podsumowanie................................................................................................................................ 89
Część II
Korzystanie z OpenGL.................................................................. 91
Rozdział 4.
M aszyna stan ów OpenGL i podstaw ow e elem en ty g r a f ik i............................9 3 Funkcje stanu...................................................................................................................................93 Podstawowe elementy grafiki.........................................................................................................99 Tworzenie punktów w trójwymiarowej przestrzeni............................................................ 100 Zmiana rozmiaru punktów.............................:................................................................ 101 Antialiasing..................................................................................................................... 101
Spis treści
7
Odcinki.................................................................................................................................... 102 Zmiana szerokości odcinka............................................................................................ 103 Antialiasing odcinków....................................................................................................103 Wzór linii odcinka.......................................................................................................... 103 Wielokąty................................................................................................................................ 104 Ukrywanie powierzchni wielokątów..............................................................................105 Ukrywanie krawędzi wielokątów...................................................................................106 Antialiasing wielokątów.................................................................................................106 Wzór wypełnienia wielokątów....................................................................................... 107 Trójkąty.............................................................................................................................107 Czworokąty.................................................................................................................... 108 Dowolne wielokąty........................................................................................................ 109 Przykład wykorzystania podstawowych elementów grafiki...............................................109 Podsumowanie.............................................................................................................................. 110 R ozdział 5.
P rze k szta łce n ia układu w spółrzędnych i m acierze O penG L........................1 1 1 Przekształcenia układu współrzędnych...................................................................................... 111 Współrzędne kamery i obserwatora..................................................................................... 113 Przekształcenia widoku..........................................................................................................114 Wykorzystanie funkcji gluLookAt()..............................................................................114 Wykorzystanie funkcji glRotate*() i glTranslate*()..................................................... 115 Własne procedury przekształceń widoku...................................................................... 116 Przekształcenia modelowania................................................................................................ 117 Rzutowanie............................................................................................................................. 118 Przekształcenie okienkowe.................................................................................................... 118 OpenGL i macierze.......................................................................................................................119 Macierz modelowania............................................................................................................119 Przesunięcie............................................................................................................................ 120 O brót........................................................................................................................................120 Skalowanie.............................................................................................................................. 122 Stos macierzy.......................................................................................................................... 123 Model robota...........................................................................................................................124 Rzutowanie.....................................................................................................................................132 Rzutowanie ortograficzne..................................................................................................... 133 Rzutowanie perspektywiczne................................................................................................ 134 Okno widoku...........................................................................................................................135 Przykład rzutowania............................................................................................................... 136 Wykorzystanie własnych macierzy przekształceń..................................................................... 138 Ładowanie elementów macierzy...........................................................................................138 Mnożenie macierzy................................................................................................................ 139 Przykład zastosowania macierzy definiowanych................................................................139 Podsumowanie.............................................................................................................................. 140
R ozdział 6 . Kolory, łą cze n ie kolorów i o ś w ie tle n ie ......................................................... 1 4 1 Czym są kolory?............................................................................................................................ 141 Kolory w OpenGL.........................................................................................................................142 Głębia koloru..........................................................................................................................143 Sześcian kolorów ................................................................................................................... 143 Model RGB A w OpenGL..................................................................................................... 143 Model indeksowany w OpenGL............................................................................................144 Cieniowanie....................................................................................................................................145 Oświetlenie.....................................................................................................................................147 Oświetlenie w OpenGL i w realnym świecie.......................................................................147 Materiały................................................................................................................................. 148
8
O penG L Program ow anie gier
Normalne..................................................................................................................................148 Obliczanie normalnych....................................................................................................149 Użycie normalnych.......................................................................................................... 151 Normalne jednostkowe....................................................................................................152 Korzystanie z oświetlenia w OpenGL..................................................................................153 Tworzenie źródeł światła.............................................................................................. 158 Położenie źródła światła.................................................................................................. 159 Tłumienie........................................................................................................................ 160 Strumień światła............................................................................................................... 160 Definiowanie materiałów................................................................................................ 163 Modele oświetlenia..........................................................................................................164 Efekty odbicia.................................................................................................................. 166 Ruchome źródła światła.................................................................................................. 167 Łączenie kolorów.......................................................................................................................... 173 Przezroczystość....................................................................................................................... 174 Podsumowanie...............................................................................................................................179 Rozdział 7.
M apy bitow e i obrazy w O pen G L.................................................................... 1 8 1 Mapy bitowe w OpenGL...............................................................................................................181 Umieszczanie map bitowych..................................................................................................182 Rysowanie mapy bitowej....................................................................................................... 183 Przykład zastosowania mapy bitowej....................................................................................183 Wykorzystanie obrazów graficznych........................................................................................... 185 Rysowanie obrazów graficznych........................................................................................... 185 Odczytywanie obrazu z ekranu............................................................................................. 187 Kopiowanie danych ekranu....................................................................................................187 Powiększanie, pomniejszanie i tworzenie odbić..................................................................188 Upakowanie danych mapy pikseli................................................................................................188 Mapy bitowe systemu Windows...................................................................................................188 Format plików BMP................................................................................................................189 Ładowanie plików B M P ........................................................................................................ 190 Zapis obrazu w pliku B M P ....................................................................................................191 Pliki graficzne Targa..................................................................................................................... 193 Format plików Targa...............................................................................................................193 Ładowanie zawartości pliku Targa........................................................................................ 194 Zapis obrazów w plikach Targa............................................................................................ 196 Podsumowanie...............................................................................................................................197
R ozdział 8 . Odwzorowania t e k s t u r .....................................................................................1 9 9 Odwzorowania tekstur................................................................................................................... 199 Przykład zastosowania tekstury............................................................................................. 200 Mapy tekstur...................................................................................................................................205 Tekstury dwuwymiarowe.......................................................................................................205 Tekstury j ednowymiaro w e.....................................................................................................206 Tekstury trójwymiarowe........................................................................................................206 Obiekty tekstur.............................................................................................................................. 207 Tworzenie nazwy tekstury.....................................................................................................207 Tworzenie i stosowanie obiektów tekstur............................................................................ 207 Filtrowanie tekstur.........................................................................................................................208 Funkcje tekstur.............................................................................................................................. 209 Współrzędne tekstury....................................................................................................................209 Powtarzanie i rozciąganie tekstur..........................................................................................210 Mipmapy i poziomy szczegółowości........................................................................................... 212 Automatyczne tworzenie mipmap.........................................................................................213
Spis treści
9
Animacja powiewającej flagi...................................................................................................... 213 Objaśnienia.............................................................................................................................214 Implementacj a ........................................................................................................................ 214 Ukształtowanie terenu..................................................................................................................223 Objaśnienia.............................................................................................................................223 Implementacja........................................................................................................................ 227 Podsumowanie..............................................................................................................................235 R ozd ział 9.
Z aa w an sow ane odw zorow ania te k s tu r......................................................... 2 3 7 Tekstury wielokrotne.................................................................................................................... 237 Sprawdzanie dostępności tekstur wielokrotnych.................................................................238 Dostęp do funkcji rozszerzeń................................................................................................239 Tworzenie jednostek tekstury................................................................................................240 Określanie współrzędnych tekstury...................................................................................... 241 Przykład zastosowania tekstur wielokrotnych.....................................................................242 Odwzorowanie otoczenia............................................................................................................ 250 Torus na niebie...................................................................................................................... .250 Macierze tekstur............................................................................................................................253 Mapy oświetlenia......................................................................................................................... 255 Stosowanie map oświetlenia.................................................................................................255 Wieloprzebiegowe tekstury wielokrotne.................................................................................... 261 Podsumowanie..............................................................................................................................265
R ozdział 1 0 . L isty w y św ie tla n ia i ta b lic e w ie rzch o łk ó w .................................................... 2 6 7 Listy wyświetlania........................................................................................................................ 267 Tworzenie listy wyświetlania................................................................................................268 Umieszczanie poleceń na liście wyświetlania.....................................................................268 Wykonywanie list wyświetlania........................................................................................... 269 Uwagi dotyczące list wyświetlania...................................................................................... 271 Usuwanie list wyświetlania...................................................................................................271 Listy wyświetlania i tekstury.................................................................................................272 Przykład: animacja robota z użyciem list wyświetlania.....................................................273 Tablice wierzchołków................. r...............................................................................................274 Obsługa tablic wierzchołków w OpenGL........................................................................... 275 Stosowanie tablic wierzchołków.......................................................................................... 276 glDrawArrays()............................................................................................................... 278 glDrawElements()........................................................................................................... 278 glDrawRangeElements()................................................................................................ 279 glArrayElement()...................................................................... 279 Tablice wierzchołków i tekstury wielokrotne..................................................................... 280 Blokowanie tablic wierzchołków..................... 280 Przykład: ukształtowanie terenu po raz drugi...................................................................... 281 Podsumowanie..............................................................................................................................284 Rozdział 1 1 . W y św ie tla n ie te k s tó w ..................................................................................... 2 8 5 Czcionki rastrowe......................................................................................................................... 285 Czcionki konturowe..................................................................................................................... 289 Czcionki pokryte teksturą............................................................................................................ 292 Podsumowanie..............................................................................................................................299 R ozdział 1 2 . Bufory O penG L.................................................................................................. 3 0 1 Bufory OpenGL — wprowadzenie............................................................................................. 301 Konfiguracja formatu pikseli................................................................................................301 Opróżnianie buforów............................................................................................................ 304 Bufor koloru.................................................................................................................................. 305 Podwójne buforowanie.......................................................................................................... 305 Buforowanie stereoskopowe.................................................................................................306
10
O penG L Program ow anie gier
Bufor g łę b i...................................................................................................................................................... 306 Funkcje porównania g łę b i...................................................................................................................307 Zastosowania bufora g łę b i..................................................................................................................307 Bufor pow ielania....,..................................................................................................................................... 316 Przykład zastosowania bufora pow ielania...................................................................................... 319 Bufor akumulacji........................................................................................................................................... 324 P odsum ow anie............................................................................................................................................... 326
Rozdział 13. Pow ierzch nie drugiego s to p n ia ....................................................................... 3 2 7 Powierzchnie drugiego stopnia w O penG L...........................................................................................327 Styl p ow ierzchn i.................................................................................................................................... 328 Wektory normalne..................................................................................................................................328 Orientacja..................................................................................................................................................329 Współrzędne tekstury............................................................................................................................329 Usuwanie obiektów pow ierzchn i......................................................................................................329 D y sk i..................................................................................................................................................................329 W a lc e ................................................................................................................................................................. 331 K ule.................................................................................................................................................................... 332 Przykład użycia powierzchni drugiego stop nia.................................................................................... 333 P odsum ow anie................................................................................................................................................336
Rozdział 14. Krzywe i p o w ie rzch n ie....................................................................................... 3 3 7 Reprezentacja krzywych i pow ierzchni.................................................................................................. 337 Równania parametryczne.................................................................................................................... 338 Punkty kontrolne i pojęcie ciągłości..................................................................................................338 Ew aluatory....................................................................................................................................................... 339 Siatka rów noodległa...............................................................................................................................342 P ow ierzchnie................................................................................................................................................... 343 Pokrywanie powierzchni teksturam i.................................................................................................346 Powierzchnie B -sk lejane........................................................................................................................... ...350 P odsu m ow an ie................................................................................................................................................ 354
Rozdział 15. E fekty s p e c ja ln e ................................................................................................ 3 5 5 P lakatow anie................................................................................................................................................... 355 Przykład: kaktusy na pustyni............................................................................................................... 357 Zastosowania system ów c zą ste k ............................................................................................................... 359 Cząstki........................................................................................................................................................ 359 Położenie............................................................................................................................................ 360 Prędkość............................................................................................................................................. 360 Czas ż y c ia ..........................................................................................................................................360 Rozmiar...............................................................................................................................................361 M asa..................................................................................................................................................... 361 Reprezentacja....................................................................................................................................361 K olor................................................................................................................................................ 361 P rzynależność.................................................................................................................................. 362 M etody................................................................................................................................................362 System y cząstek ...................................................................................................................................... 362 Lista cząstek...................................................................................................................................... 362 P ołożenie...................................................................................................... Częstość e m is ji................................................................................................................................ 363 O ddziaływ ania..................................................................................................................................363 Atrybuty cząstek, zakresy ich wartości i wartości dom yśln e............................................363 Stan b ieżący ...................................................................................................................................... 364 Łączenie kolorów .............................................................................................................................364 Reprezentacja.................................................................................................................................... 364 M etody................................................................................................................................................ 364
Spis treści
11
Menedżer systemów cząstek.................................................................................................365 Implementacja........................................................................................................................ 365 Tworzenie efektów za pomocą systemów cząstek............................................................. 368 Przykład: śnieżyca..................................................................................................................369 M gła............................................................................................................................................... 373 Mgła w OpenGL.................................................................................................................... 374 Mgła objętościowa.................................................................................................................375 Odbicia...........................................................................................................................................375 Odbicia światła....................................................................................................................... 376 Obsługa bufora głębi............................................................................................................. 376 Obsługa skończonych płaszczyzn za pomocą bufora powielania......................................377 Tworzenie nieregularnych odbić.......................................................................................... 378 Odbicia na dowolnie zorientowanych płaszczyznach........................................................ 378 Cienie.............................................................................................................................................379 Cienie statyczne..................................................................................................................... 379 Rzutowanie cieni....................................................................................................................380 Macierz rzutowania cieni............................................................................................... 380 Problemy z buforem głębi.............................................................................................. 381 Ograniczanie obszaru cieni za pomocą bufora powielania.......................................... 381 Obsługa wielu źródeł światła i wielu zacienianych powierzchni............................... 382 Problemy związane z rzutowaniem cieni......................................................................382 Bryły cieni w buforze powielania......................................................................................... 383 Inne metody............................................................................................................................ 384 Przykład: odbicia i cienie...................................................................................................... 384 Podsumowanie..............................................................................................................................387
Część III Tworzymy grę............................................................................. 389 Rozdział 1 6 . D irectX : D ire ctln p u t.........................................................................................3 9 1 Dlaczego Directlnput?..................................................................................................................391 Komunikaty systemu Windows............................................................................................ 391 Interfejs programowy W in32............................................................................................... 394 Win32 i obsługa klawiatury........................................................................................... 394 Win32 i obsługa manipulatorów................................................................................... 396 Directlnput..............................................................................................................................397 Inicjacja interfejsu Directlnput....................................................................................................397 Wartości zwracane przez funkcje Directlnput.....................................................................398 Korzystanie z Directlnput........................................................................................................... 399 Dodawanie urządzeń............................................................................................................. 399 Tworzenie urządzeń........................................................................................................400 Tworzenie wyliczenia urządzeń.....................................................................................400 Sprawdzanie możliwości urządzenia............................................................................ 404 Wyliczenia obiektów......................................................................................................405 Określanie formatu danych urządzenia.........................................................................405 Określanie poziomu współpracy....................................................................................407 Modyfikacja właściwości urządzenia............................................................................ 407 Zajmowanie urządzenia................................................................................................. 408 Pobieranie danych wejściowych.......................................................................................... 408 Bezpośredni dostęp do danych.......................................................................................408 Buforowanie danych.......................................................................................................409 „Odpytywanie” urządzeń............................................................................................... 409 Kończenie pracy z urządzeniem........................................................................................... 409 Odwzorowania akcj i.....................................................................................................................410 Tworzenie podsystemu wejścia...................................................................................................410 Przykład zastosowania systemu wejścia.....................................................................................418 Podsumowanie............................................................................................................................. 420
12
OpenGL. Program ow anie gier
Rozdział 17. Z a sto so w a n ia D ire ctX A u d io .......................................................................... 4 2 1 Dźwięk...........................................................................................................................................421 Dźwięk i komputery...............................................................................................................423 Cyfrowy zapis dźwięku................................................................................................... 423 Synteza dźwięku..............................................................................................................424 Czym jest DirectX A udio?...........................................................................................................425 Charakterystyka DirectX Audio............................................................................................426 Obiekty ładujące..............................................................................................................426 Segmenty i stany segmentów......................................................................................... 426 Wykonanie....................................................................................................................... 427 Komunikaty..................................................................................................................... 427 Kanały wykonania............................................... 427 Syntezator DSL................................................................................................................427 Instrumenty i ładowanie dźwięków................................................................................427 Ścieżki dźwięku i bufory.................................................................................................428 Przepływ danych dźwięku..................................................................................................... 428 Ładowanie i odtwarzanie dźwięku w DirectMusic.................................................................... 429 Inicjacja COM.........................................................................................................................430 Tworzenie i inicjacja obiektu wykonania.............................................................................430 Tworzenie obiektu ładującego............................................................................................,..431 Załadowanie segmentu...........................................................................................................431 Ładowanie instrumentów.......................................................................................................432 Odtwarzanie segmentu...........................................................................................................432 Zatrzymanie odtwarzania segmentu..................................................................................... 433 Kontrola odtwarzania segmentu............................................................................................434 Określanie liczby odtworzeń segmentu................................................................................ 434 Kończenie odtwarzania dźwięku...........................................................................................435 Prosty przykład.............................................................................................................................. 435 Zastosowania ścieżek dźwięku.................................................................................................... 445 Domyślna ścieżka dźwięku................................................................................................... 446 Standardowe ścieżki dźwięku............................................................................................... 446 Odtwarzanie za pomocą ścieżki dźwięku.............................................................................447 Pobieranie obiektów należących do ścieżek dźwięku.........................................................449 Dźwięk przestrzenny.................................................................................................................... 450 Współrzędne przestrzeni dźwięku.........................................................................................450 Percepcja położenia źródła dźwięku.................................................................................... 450 Bufor efektu przestrzennego w DirectSound........................................................................451 Parametry przestrzennego źródła dźwięku...........................................................................451 Odległość minimalna i maksymalna..............................................................................453 Tryb działania.................................................................................................................. 453 Położenie i prędkość....................................................................................................... 454 Stożki dźwięku................................................................................................................. 454 Odbiorca efektu dźwięku przestrzennego.............................................................................455 Przykład zastosowania efektu przestrzennego..................................................................... 456 Podsumowanie.............................................................................................................................. 468 Rozdział 18. M od e le tró jw ym iaro w e .....................................................................................4 6 9 Formaty plików modeli trójwymiarowych................................................................................. 469 Format MD2.................................................................................................................................. 470 Implementacja formatu M D2................................................................................................ 472 Ładowanie modelu MD2........................................................................................................476 Wyświetlanie modelu M D 2.................................................................................................. 480 Pokrywanie modelu teksturą................................................................................................. 482 Animacja modelu .............................................................................................................483
Spis treści
13
Klasa CMD2Model............................................................................................................... 488 Sterowanie animacją m odelu............................................................................................... 498 Ładowanie plików P C X ...............................................................................................................501 Podsumowanie..............................................................................................................................503 R ozd ział 1 9 . M ode low an ie fizycznych w ła ściw o ści ś w ia ta ............................................... 5 0 5 Powtórka z fizyki...........................................................................................................................505 Czas..........................................................................................................................................505 Odległość, przemieszczenie i położenie...............................................................................506 Prędkość.................................................................................................................................. 508 Przyspieszenie........................................................................................................................ 509 Siła...........................................................................................................................................510 Pierwsza zasada dynamiki............................................................................................. 511 Druga zasada dynamiki...................................................................................................511 Trzecia zasada dynamiki.................................................................................................511 P ę d ...........................................................................................................................................511 Zasada zachowania pędu................................................................................................ 512 Tarcie...................................................................................................................................... 513 Tarcie na płaszczyźnie....................................................................................................513 Tarcie na równi pochyłej................................................................................................ 514 Modelowanie świata rzeczywistego........................................................................................... 516 Rozkład problemu..................................................................................................................516 Czas..........................................................................................................................................517 W ektor.................................................................................................................................... 521 Płaszczyzna.............................................................................................................................526 Obiekt...................................................................................................................................... 529 Zderzenia obiektów................................................................................................................531 Sfery ograniczeń............................................................................................................. 532 Prostopadłościany ograniczeń........................................................................................ 533 Zderzenia płaszczyzn......................................................................................................535 Obsługa zderzenia........................................................................................................... 538 Przykład: hokej...................................................................................................................... 538 Świat hokeja.....................................................................................................................539 Lodowisko....................................................................................................................... 539 Krążek i zderzenia........................................................................................................... 544 Gracz.................................................................................................................................550 Kompletowanie programu.............................................................................................. 553 Podsumowanie..............................................................................................................................559 R ozdział 2 0 . Tw orzenie s zk ie le tu g ry ................................................................................... 5 6 1 Architektura szkieletu SimpEngine........................................ 561 Zarządzanie danymi za pomocą obiektów klasy CNode....................................................562 Zarządzanie obiektami: klasa CObject.................................................................................566 Trzon szkieletu SimpEngine........................................................................................................ 570 System wejścia....................................................................................................................... 572 Klasa CEngine........................................................................................................................ 573 Cykl g ry ............................................................................................................. 574 Obsługa wejścia..................................................................................................................... 575 Klasa CSimpEngine...............................................................................................................576 Kamera...........................................................................................................................................577 Świat g ry ....................................................................................................................................... 580 Obsługa m odeli.............................................................................................................................580 System dźwięku.............................................................................................................................581 System cząstek..............................................................................................................................583 Podsumowanie..............................................................................................................................583
14
O penG L Program ow anie gier
Rozdział 21. Piszem y grę: „C za s zab ija n ia” ........................................................................5 8 5 Wstępny projekt............................................................................................................................585 Świat g ry ........................................................................................................................................586 Przeciwnicy................................................................................................................................... 588 Sztuczna inteligencja przeciwnika........................................................................................589 O gro.........................................................................................................................................590 Sod...........................................................................................................................................592 Rakiety i eksplozje........................................................................................................................592 Interfejs użytkownika gry.............................................................................................................594 Korzystanie z gry...........................................................................................................................594 Kompilacja gry.............................................................................................................................. 595 Podsumowanie.............................................................................................................................. 596
Dodatki......................................................................................................... 597 Dodatek A
Z asoby s ie c i Inte rn et....................................................................................... 5 9 9 Programowanie g ie r......................................................................................................................599 GameDev.net...........................................................................................................................599 Wyszukiwarka informacji związanych z programowaniem gier....................................... 600 flipCode.................................................................................................................................. 600 Gamasutra............................................................................................................................... 600 OpenGL..........................................................................................................................................600 NeHe Productions.................................................................................................................. 600 OpenGL.org............................................................................................................................ 601 Inne strony poświęcone OpenGL..........................................................................................601 DirectX........................................................................................................................................... 601 DirectX Developer Center......................................................................................................601 Lista mailingowa DirectX......................................................................................................601 Inne zasoby.....................................................................................................................................602 ParticleSystems.com.............................................................................................................. 602 Real-Time Rendering............................................................................................................. 602 Informacja techniczna dla programistów............................................................................. 602 Artykuły dotyczące efektu m gły...........................................................................................602
D odatek B
D ysk C D ............................................................................................................. 6 0 3 Struktura plików na dysku C D .....................................................................................................603 Wymagania sprzętowe.................................................................................................................. 603 Instalacja........................................................................................................................................604 Typowe problemy i ich rozwiązywanie.......................................................................................604 S ko ro w id z...........................................................................................................6 0 5
List od wydawcy serii „OpenGL. Programowanie gier” jest jedną z pierwszych książek serii Prima Game Deve lopment i prawdopodobnie jedną z bardziej ambitnych. Napisano już wiele książek po święconych programowaniu OpenGL i programowaniu grafiki trójwymiarowej, ale żad na z nich nie była poświęcona wykorzystaniu możliwości OpenGL do programowania gier. „OpenGL. Programowanie gier” wypełnia tę lukę, a nawet stawia sobie bardziej ambitne cele. Programowanie w OpenGL pozostaje bowiem w dość luźnym związku z tworzeniem aplikacji na platformie Windows. I właśnie ta książka integruje aspekty programowania aplikacji Windows z programowaniem OpenGL w kontekście tworze nia gier. Za to należą się jej twórcom, Kevinowi Hawkinsowi i Dave’owi Astle’owi (współzałożycielom GamDev.net), duże brawa. Półki mojej biblioteki zapełnia wiele książek poświęconych OpenGL. Jednak z punktu widzenia programisty gier żadna z nich nie odpowiadała w pełni moim potrzebom. Wła śnie na taką książkę jak niniejsza pozycja oczekiwali programiści gier. „OpenGL. Pro gramowanie gier” pozwala rozpocząć przygodę z programowaniem gier nawet nowicju szom. Na wstępie dokonany zostaje krótki przegląd problematyki gier, a za nim pojawia się porównanie specyfikacji OpenGL i DirectX. Następnie przedstawione zostają zasa dy tworzenia grafiki trójwymiarowej w systemie Windows z wykorzystaniem OpenGL zamiast Direct3D. Po zaprezentowaniu podstawowej wiedzy na temat tworzenia aplika cji OpenGL na platformie Windows nadchodzi właściwy czas na omówienie teorii gra fiki trójwymiarowej i wyjaśnienie poprzez odpowiednie ilustracje takich zagadnień jak przekształcenia, oświetlenie, obcinanie, rzutowanie i odwzorowywanie tekstur. Książka ta byłaby warta uwagi nawet wtedy, gdy kończyłaby na tym omówienie grafiki trójwy miarowej. Ma jednak do zaoferowania czytelnikowi jeszcze rozdziały poświęcone bar dziej zaawansowanym zagadnieniom — listom wyświetlania, powierzchniom trójwy miarowym i efektom specjalnym. Zwłaszcza przedstawienie problematyki powierzchni trójwymiarowych jest szczególnie warte uwagi. Po omówieniu zagadnień grafiki trójwymiarowej następuje powrót do problematyki pro gramowania gier i przejście do omówienia zastosowań specyfikacji DirectX, którego nie można znaleźć w żadnej książce poświęconej OpenGL! Przedstawione zostają kompo nenty Directlnput i DirectSound — niezbędne elementy każdej gry na platformie Windows.
16
O penG L Program ow anie gier
Dostarczając w ten sposób wiedzy na temat wszystkich technologii potrzebnych do stworzenia gry, „OpenGL. Programowanie gier” poświęca końcowe rozdziały przedsta wieniu modelowania praw fizyki, projektowaniu kompletnego szkieletu gry i stworze niu przykładowej gry ilustrującej problematykę omówioną w książce. Książka ta znajdowała się w przygotowaniu od dłuższego już czasu, jednak jestem prze konany, że czytelnicy docenią dodatkowy włożony w nią wysiłek, który pozwolił uczynić ją kwintesencją wiedzy dotyczącej programowania gier i OpenGL. Z poważaniem André LaMothe Maj 2001
Autorach Kevin Hawkins jest absolwentem informatyki Uniwersytetu Embry-Riddle w Daytona Beach na Florydzie. Tam też zdobywa obecnie tytuł magisterski w dziedzinie inżynierii oprogramowania. Jego doświadczenie programisty obejmuje blisko 7 lat programowania w C i C++, OpenGL i DirectX oraz w wielu innych językach i interfejsach programo wych. Kevin jest także współzałożycielem i prezesem GamDev.net (www.gamedev.net), najbardziej wszechstronnej witryny w Internecie, która poświęcona jest programowaniu gier. Jest też zawodnikiem uniwersyteckiej drużyny bejsbolowej. Wolny czas poświęca także lekturze, grom komputerowym, grze na gitarze i swojej dziewczynie, Karze. Dave Astle ukończył informatykę na Uniwersytecie Utah, gdzie specjalizował się w gra fice komputerowej, w badaniach nad sztuczną inteligencją, programowaniu sieci kom puterowych oraz opracowywaniu teorii i projektowaniu kompilatorów. Programowa niem gier zajął się prawie 20 lat temu, a obecnie pracuje jako programista w firmie WaveLink. Jest też współzałożycielem i dyrektorem GameDev.net, wiodącej witryny społeczności programistów gier. Pracuje też jako główny programista we własnej firmie Myopic Rhino Games (www.myopicrhino.com), która jest niezależnym producentem gier przeznaczonych na rynek masowy. Gdy nie pochłania promieniowania emitowane go przez monitor, słucha muzyki, czyta książki, jeździ na rolkach, zbiera figurki noso rożców, ćwiczy próbując osiągnąć proporcje Jasona Halla i poświęca czas dzieciom.
18
OpenGL Programowanie gier
Przedmowa Gry komputerowe stanowią doskonałą rozrywkę. Tworzą niezwykłe światy będące wy zwaniem dla zręczności i inteligencji. Takim samym wyzwaniem jest także tworzenie gier. Muszę przyznać, że nie istniał chyba nigdy dotąd bardziej odpowiedni moment do rozpoczęcia nauki programowania gier. Jeśli ktoś planuje zająć się tą niełatwą sztuką, to otrzymuje właśnie odpowiedni do tego podręcznik. Moim zdaniem najbardziej niezwykłym aspektem tworzenia gier jest dzisiaj możliwość łatwego wykorzystania ogromnego potencjału tkwiącego w najnowszych kartach gra ficznych dostępnych dla popularnego sprzętu klasy PC. W niedalekiej przeszłości sytu acja wyglądała zupełnie inaczej. Odpowiedni sprzęt graficzny dostępny był jedynie na dwóch odległych biegunach systemów komputerowych. Z jednej strony odpowiednią wydajność posiadały stacje graficzne pracujące pod kontrolą systemu Unix wytwarzane przez mojego poprzedniego pracodawcę — firmę Silicon Graphics. Ze względu na koszt tego sprzętu był on dostępny jedynie dla naukowców, inżynierów i artystów pracują cych w dużych firmach. Chociaż dla stacji tych napisano kilka ciekawych gier, nikt nie kupował ich z tego właśnie powodu. Natomiast na przeciwległym biegunie istniały kon sole gier dostępne dla szerokiego ogółu konsumentów. Jednak ich programowanie było zarezerwowane tylko dla wąskiej grupy profesjonalistów, którzy posiadali dostęp do wyspecjalizowanych środowisk tworzenia gier. Wysoki koszt zaawansowanych stacji graficznych wymusił powstanie efektywnego i ła twego interfejsu programowego, który pozwoliłby w pełni wykorzystywać tkwiący w nich potencjał możliwości tworzenia grafiki trójwymiarowej. Firma Silicon Graphics stwo rzyła taki interfejs nazwany IRIS GL, gdzie GL oznaczało bibliotekę graficzną (graphics library). Interfejs ten stał się podstawą do opracowania przez wszystkich producentów stacji roboczych wspólnego interfejsu OpenGL. Obecnie jest on wykorzystywany przez tysiące aplikacji w nauce, przemyśle, medycynie i animacji komputerowej. Także firma Microsoft zaimplementowała bibliotekę OpenGL — najpierw dla systemu operacyjnego Windows NT, a następnie systemu Windows 95 i ich kolejnych wersji. OpenGL zaprojektowano przede wszystkim z myślą o tworzeniu trójwymiarowej grafiki wspomaganej sprzętowo. Każde polecenie OpenGL wykonywane przez stację graficzną firmy Silicon Graphics obsługiwane jest sprzętowo. Pierwsze implementacje OpenGL dla komputerów klasy PC wykonywały polecenia OpenGL programowo i dlatego nie były demonem prędkości. Na szczęście opracowanie odpowiedniego sprzętu graficznego dla tej klasy systemów komputerowych okazało się tylko kwestią czasu. Obecnie na przykład rodzina procesorów graficznych GeForce opracowana przez firmę NY1D1A wykonuje
20
OpenGL. Program ow anie gier
sprzętowo polecenia OpenGL. Coraz doskonalsze układy półprzewodnikowe i innowa cyjne architektury procesorów graficznych doprowadziły do sytuacji, w której układy graficzne współczesnych komputerów PC są kilkanaście razy szybsze niż odpowiadają ce im układy stacji graficznych Silicon Graphics sprzed zaledwie kilku lat (a także wielokrotnie tańsze!). Obecna ewolucja specyfikacji OpenGL podyktowana jest w dużej mierze rozwojem sprzętu graficznego klasy PC i jego zastosowaniami w grach. Powstała w ten sposób dość niezwykła sytuacja, w której najdoskonalszy interfejs do tworzenia profesjonalnej grafiki trójwymiarowej jest równocześnie najlepszym interfej sem do tworzenia grafiki gier. OpenGL jest obecnie standardem tworzenia trójwymia rowej grafiki nie tylko na platformie Windows PC, ale także dla komputerów firmy Apple pracujących pod kontrolą najnowszego systemu OS X. Oczywiście OpenGL jest także dostępny dla stacji roboczych firm Sun, HP, IBM i Silicon Graphics. OpenGL stał się także istotnym elementem oprogramowania o ogólnie dostępnym kodzie źródłowym. Takie implementacje OpenGL dostępne są na przykład dla systemu operacyjnego Linux. Nie zdziwiłbym się także, gdyby wkrótce standard OpenGL został przyjęty także przez producentów konsoli gier. Jeśli ktoś korzysta z najnowszych gier dostępnych dla sprzętu PC, to może słyszał już o grach wykorzystujących specyfikację OpenGL i na pewno grał w niektóre z nich. Do najbardziej znanych tytułów należą: seria kolejnych wersji gry Quake oraz oczekiwana gra Doom 3 — obydwie firmy id Software, H alf Life firmy Valwe; opracowane przez Raven Software Soldier o f Fortune i Star Trek: Voyager — Elite Force', Tribes 2 firmy Dynamic i Serious Sam wyprodukowany przez Croteam. Twórcy tych uznanych tytu łów byli kiedyś nowicjuszami, którymi powodowała pasja tworzenia trójwymiarowych światów gier. Każdy ma szansę na to, by dołączyć do nich! Książka ta omawia proces tworzenia kompletnej gry. Oczywiście zasadniczym jej ele mentem jest trójwymiarowa grafika, dlatego też na pierwszy plan wysuwa się przedsta wienie specyfikacji OpenGL. Książka omawia także inne kluczowe aspekty programo wania gry: obsługę wejścia za pomocą DirectX Input, dźwięku i muzyki za pomocą DirectX Audio; ładowanie plików graficznych zawierających obrazy tekstur oraz łado wanie modeli obiektów; wykorzystanie praw fizyki do obsługi zderzeń obiektów; projek towanie architektury gry. Jednak książka ta stanowi dopiero początek przygody z pro gramowaniem gier. Kontynuację stanowić mogą przykłady umieszczone na dysku CD oraz inne źródła wymienione w dodatkach. Pojawienie się tej książki przyprawia mnie o dreszcz emocji — po raz pierwszy pro gramiści otrzymują książkę poświęconą programowaniu gier za pomocą OpenGL! Jeśli nawet czytelnik tej książki nie wiąże swojej kariery zawodowej z tworzeniem gier komputerowych, to jej lektura pomoże lepiej zrozumieć technologię, która wykorzy stywana jest do tworzenia współczesnych gier, a także pozwoli zdobyć umiejętności, które mogą być przydatne także w innych dziedzinach programowania. Zastosowania OpenGL nie ograniczają się bowiem tylko do gier. Ale w tę grę zagrajmy! Mark J. Kilgard Inżynier oprogramowania graficznego, NVIDIA Corporation Autor OpenGL Programming for th eX Window System oraz pakietu OpenGL Utility Toolkit (znanego także pod nazwą GLUT)
Wprowadzenie Zapraszamy do lektury „OpenGL. Programowanie gier”! Książka ta pokazuje, w jaki sposób można korzystać z zaawansowanych bibliotek graficznych i innych podczas programowania gier. Stanowi efekt pracy autorów trwającej ponad rok. Mamy nadzieję, że stanie się doskonałym podręcznikiem tworzenia gier. Jak łatwo jest wywnioskować na podstawie tytułu, grafikę gier będziemy tworzyć ko rzystając z biblioteki OpenGL. Ponieważ OpenGL jest wyłącznie biblioteką graficzną, a gry składają się także z wielu innych elementów, takich jak efekty dźwiękowe, muzy ka, obsługa wejścia, to uzupełnimy ją o bibliotekę DirectX. Zadaniem tej książki jest wypełnienie luki w literaturze przeznaczonej dla programi stów gier. Dostępnych jest wiele książek poświęconych grafice OpenGL i równie wiele dotyczących programowania DirectX. Jak dotąd jednak żadna z nich nie próbowała po łączyć zastosowania obu specyfikacji. Mimo że przedstawiamy w naszej książce specyfikacje OpenGL i DirectX, to nie było naszym zamysłem stworzenie kompletnego ich omówienia. Omawiamy jedynie te kom ponenty, które przydatne są podczas programowania gier. W końcowej części książki podajemy informacje o innych źródłach zawierających więcej informacji na temat OpenGL i DirectX. Książka nasza podzielona jest na trzy części. Zadaniem części I jest dostarczenie wiadomości, które stworzą podstawy do omówienia bardziej zaawansowanych zagadnień pojawiających się w następnych częściach. Przed stawiamy więc historię specyfikacji OpenGL i DirectX omawiając jednocześnie ich ogólny sposób działania. Ponieważ docelową platformą naszych gier będzie system Windows, omawiamy także specyfikę tworzenia aplikacji dla tego systemu. Pierwszą część książki kończy omówienie teoretycznych podstaw grafiki trójwymiarowej. Część II koncentruje się na omówieniu możliwości biblioteki OpenGL, które są przydat ne przy projektowaniu gier. Wszystkie rozdziały tej części książki zawierają przykłady programów ilustrujących przedstawiane możliwości OpenGL. Komplente, działające programy stanowią także punkt wyjściowy do eksperymentów z OpenGL przeprowadza nych na własną rękę.
22
OpenGL. Program ow anie gier
Zadaniem części III jest doprowadzenie do stworzenia kompletnego szkieletu programo wego gier wykorzystującego OpenGL do tworzenia grafiki oraz DirectSound i Direct lnput do obsługi dźwięku i wejścia gry. Kulminacyjnym punktem tej części książki jest stworzenie kompletnej gry wykorzystującej opracowany szkielet i wiele technik omó wionych we wcześniejszych rozdziałach. Do książki dołączyliśmy dysk CD zawierający wiele programów demonstracyjnych, gier i bibliotek stanowiących doskonałe uzupełnienie książki. Proponujemy zatem przejść do lektury zasadniczej części książki.
Część I Wprowadzenie do OpenGL i DirectX
Rozdział 1.
OpenGL i DirectX Przed przystąpieniem do programowaniem gier warto zapoznać się z narzędziami, które będą wykorzystywane. Będą nimi dwa interfejsy programowe: OpenGL i DirectX. Pierw szy z nich wykorzystywany będzie do tworzenia grafiki trójwymiarowej i innych ele mentów wizualnych, natomiast drugi do interakcji z graczem, tworzenia dźwięku oraz gier, w których może uczestniczyć wielu graczy. W tym rozdziale omówiona zostanie historia powstania obu interfejsów, ich podstawowe możliwości, a ponadto czytelnik zostanie wprowadzony w tematykę gier. W bieżącym rozdziale przedstawione zostaną takie zagadnienia jak: ♦ specyfika gier; ♦ podstawy OpenGL; ♦ składniki interfejsu DirectX; ♦ porównanie interfejsów OpenGL i DirectX.
Dlaczego tworzymy gry? Gry komputerowe, które dotychczas zwykło się uważać za rozrywkę dla dzieci, stały się rynkiem wartym kilka miliardów dolarów. Ostatnie lata określiły w tej mierze niezwykle dynamiczny trend, którego granice trudno jest przewidzieć. Eksplozja rynku interaktyw nej rozrywki sprawiła, że dzisiaj właśnie ten sektor napędza rozwój technologiczny kom puterów PC oraz pomaga prowadzić nowe badania w dziedzinach związanych z tworze niem grafiki i sztuczną inteligencją. Ten niezwykły rozwój z pewnością przyciąga wielu ludzi do przemysłu gier, ale czy jest on głównym powodem, dla którego tworzymy gry? Z rozmów z wieloma osobami zajmującymi się tworzeniem gier wyłania się jeden za sadniczy powód, dla którego zajęli się tą dziedziną i odnieśli sukces: zabawa. Tworze nie gier słusznie uważa się za jedno z bardziej kreatywnych zajęć związanych z pro gramowaniem — dowodzą tego niezwykłe gry, które powstały w ostatnich latach. Na przykład „Half-Life” firmy Valve Software odmieniła zupełnie dotychczasowe pojęcie gry komputerowej. Programiści i projektanci przyciągani są do przemysłu tworzenia gier dzięki perspektywie kreowania nowych światów, których doświadczać będą wkrót ce tysiące, jeśli nie miliony innych ludzi. Tworzenie gier polega na odkrywaniu nowych światów i nowych technologii. Jak ujął to Michael Sikora, niezależny twórca gier, jest ono jak „niekończąca się podróż”. Właśnie dlatego tworzymy gry.
26
Część I ♦ W prow adzenie do OpenGL i D irectX
Świat gier Około dziesięciu lat temu firma id Software wypuściła na rynek grę „Wolfenstein 3D”. Rzuciła ona na kolana znawców i miłośników gier dzięki wykorzystaniu trójwymiarowej grafiki i stworzeniu zadziwiających światów, w których gracze spędzali długie godziny. Gra ta zapoczątkowała wyścig twórców gier na niespotykaną dotąd skalę. W 1993 roku pojawiła się kolejna gra firny id Software — „Doom”, która wykorzystywała udosko nalony moduł tworzenia grafiki trójwymiarowej gry „Wolfenstein”. Po raz kolejny fir ma id Software wyznaczyła w ten sposób nowy standard gier wykorzystujących grafikę trójwymiarową. Po kilku latach światem gier wstrząsnął „Quake”. Po raz pierwszy gra ta stworzyła w pełni trójwymiarowy świat, w którym postacie przestały tylko udawać trójwymiarowość i zyskały możliwość poruszania się o 6 stopniach swobody. Jedynym ograniczeniem w przypadku tworzenia nowych światów stała się teraz skończona moż liwość przetwarzania wielokątów przez procesor maszyny i ich wyświetlania przez kartę graficzną. „Quake” wprowadził także możliwość uczestniczenia w grze dużej liczby graczy obecnych w sieci lokalnej lub sieci Internet. Od czasu wprowadzenia gry „Quake” nowe osiągnięcia prezentowane są co kilka mie sięcy. Wprowadzono sprzętowe przetwarzanie grafiki 3D, które podwaja swoją efek tywność średnio co pół roku. Wszystko to sprawia, że obecnie tworzenie gier jest eks cytujące jak nigdy dotąd. Rysunek 1.1 pokazuje ujęcie jednego z ostatnich osiągnięć w tej dziedzinie — gry „Unreal Tournament” firmy Epic. Rysunek 1.1. Ujęcie z gry Unreal Tournament
Elementy gry Można by zapytać teraz, w jaki sposób powstaje gra. Aby odpowiedzieć na tak posta wione pytanie, trzeba uświadomić sobie, że gra na najniższym poziomie abstrakcji jest niczym innym jak programem komputerowym. Współcześnie zdecydowana większość
Rozdział 1 . ♦ OpenGL i D irectX
27
programów jest efektem pracy zespołów programistów, z których każdy pracuje nad pewnym fragmentem projektu. Fragmenty te łączone są na etapie finalnym w spójną całość. Nie inaczej wygląda tworzenie gier (z tą jednak różnicą, że powstanie gry nie ogranicza się wyłącznie do pracy programistów). Artyści-graficy tworzą obrazy i scene rię gry. Efekty ich pracy wykorzystywane są przez projektantów, którzy opracowują mapy kolejnych poziomów gry powołując w ten sposób do życia nowy, wirtualny świat. Technicy dźwięku i muzycy przygotowują efekty dźwiękowe, które podnoszą realizm gry, a ponadto muzykę, która tworzy niepowtarzalną atmosferę. Wszystkie te kompo nenty łączone są potem przez programistów w jedną, działającą całość. Aby możliwe było właściwe wykorzystanie wiedzy i umiejętności specjalistów z tak różnych dziedzin, projekt gry musi zostać podzielony na szereg elementów, takich jak: ♦ grafika; ♦ informacje wejściowe; ♦ dźwięki i muzyka; ♦ logika gry i sztuczna inteligencja; ♦ działanie gry w sieci; ♦ interfejs użytkownika i system menu. Każda z tych dziedzin może zostać następnie podzielona na szereg jeszcze bardziej wy specjalizowanych obszarów. Na przykład logika gry wykorzystywać może mechanikę obiektów oraz system cząstek, a grafika może być tworzona tak w dwu, jak i w trzech wymiarach. Rysunek 1.2 prezentuje uproszczoną architekturę gry. Rysunek 1.2. Gra składa się z różnych
podsystemów
Baza danych o wirtualnym świecie gry
Pliki danych
J
Każdy z wyróżnionych elementów komunikuje się z innymi elementami. Logika gry stanowi element centralny, w obrębie którego podejmowane są decyzje o przetwarzaniu informacji wejściowej i tworzeniu informacji wyjściowej. Architektura pokazana na ry sunku 1.2 jest wyjątkowo uproszczona. Rysunek 1.3 pokazuje, jak mogłaby wyglądać jej bardziej rozbudowana wersja.
28
Część I ♦ W prow adzenie do OpenGL i D irectX
Rysunek 1.3. Bardziej zaawansowana architektura gry
Rysunek 1.3 pokazuje, że bardziej zaawansowana gra wymaga bardziej złożonej archi tektury. Wymaganych jest więcej szczegółowych komponentów implementujących spe cyficzną funkcjonalność wymaganą do właściwego działania gry. Warto w tym miejscu uświadomić sobie, że gry stanowią szczególnie złożoną kompozycję najróżniejszych technologii i dlatego ich projektowanie powinno odbywać się na jeszcze wyższych po ziomach abstrakcji, niż ma to miejsce w przypadku typowego oprogramowania. Pro jektując grę tworzy się dzieło sztuki i w taki sposób należy traktować cały proces twórczy. Dlatego też nie trzeba obawiać się tworzenia nowatorskich projektów gier i wykorzy stania dostępnych technologii w niestandardowy sposób. Nie istnieje jeden ustalony sposób tworzenia gier, podobnie jak nie istnieje na przykład jeden sposób malowania obrazów. Należy starać się o wprowadzenie innowacji i tworzyć nowe standardy!
Narzędzia Aby w pełni skorzystać z materiału zawartego w tej książce, należy zaopatrzyć się w do datkowe oprogramowanie. Oczywiście potrzebny będzie kompilator języka C++. Jako że pisząc tę książkę założyliśmy, że potencjalny czytelnik potrafi programować w języku C++, możemy także przyjąć, że posiada on także odpowiedni kompilator. Wszystkie przy kłady programów zamieszczone w tej książce powstały przy użyciu kompilatora Micro soft Visual C++. Nie powinny jednak pojawiać się żadne problemy związane z ich uru chomieniem przy użyciu innego kompilatora języka C++ dla systemu Windows. Oprócz kompilatora niezbędne będą także biblioteki i pliki nagłówkowe interfejsów OpenGL oraz DirectX. Muszą one zostać zainstalowane tak, by mógł odnaleźć je kom pilator języka C++. Jeśli czytelnik jeszcze ich nie zainstalował lub nie jest pewien, czy zrobił to dobrze, to powinien skorzystać z poniższych wskazówek. Ponieważ specyfikacja interfejsu OpenGL nie jest aktualizowana zbyt często, to praw dopodobnie kompilator używany przez czytelnika zawiera już jej najnowszą wersję. Jeśli kompilatorem tym jest Visual C++, to można mieć pewność, że tak właśnie jest. W każ dym innym przypadku trzeba sprawdzić, czy odpowiednie pliki znajdują się w podka talogach katalogu, w którym zainstalowany został kompilator. W tym celu należy odna leźć katalog plików nagłówkowych (nazwany najczęściej include) oraz katalog bibliotek (najczęściej lib). Katalog plików nagłówkowych powinien zawierać podkatalog o nazwie g/, w którym powinny znajdować się pliki g/./z, glu.h i glawc.h. Natomiast w katalogu bibliotek powinny być umieszczone pliki opengl32.lib, glu32.lib i glaivc.lib. Jeśli, co mało
Rozdział 1. ♦ OpenGL i D irectX
29
prawdopodobne, brakować będzie jednego lub więcej z wymienionych plików, to należy uzupełnić go korzystając z plików znajdujących się na dysku CD. Należy odnaleźć i ścią gnąć plik o nazwie opengl95.exe, rozpakować go i skopiować pliki bibliotek do katalo gu bibliotek kompilatora. Później trzeba w katalogu plików nagłówkowych utworzyć podkatalog o nazwie gl i umieścić w nim pliki nagłówkowe. Jako że OpenGL stanowi przykład architektury otwartej, w sieci Internet można napo tkać wiele wersji bibliotek i plików nagłówkowych. Dwie najważniejsze implementacje OpenGL pochodzą z firm Silicon Graphics i Microsoft. Ponieważ firma Silicon Graphics nie kontynuuje już prac nad im plem entacją OpenGL na platformę Windows, można korzystać jedynie z im plementacji firmy Microsoft.
Po skompletowaniu plików bibliotek i nagłówków trzeba sprawdzić jeszcze, czy dostępna jest najnowsza wersja sterownika OpenGL dla wykorzystywanej karty graficznej. Można sprawdzić to na stronach internetowych producenta karty lub skorzystać z dostępnego bezpłatnie programu GLSetup (http://www.glsetup.com). Na dysku CD umieszczony także został pakiet DirectX 8.0 SDK. Jeśli czytelnik nie po siada zainstalowanej tej lub nowszej wersji pakietu, to powinien zainstalować go, aby skorzystać z przykładów zamieszczonych w dalszej części książki. Jeśli czytelnik po siada starszą wersję pakietu, to najpierw powinien odinstalować ją korzystając z Panelu sterowania, a dopiero potem zainstalować pakiet DirectX 8.0 SDK. Po zainstalowaniu obu pakietów SDK należy sprawdzić, czy kompilator został odpowied nio skonfigurowany i potrafi odnaleźć biblioteki i pliki nagłówkowe. Jeśli czytelnik umie ścił biblioteki i pliki nagłówkowe OpenGL zgodnie z powyższymi zaleceniami i po zwolił programowi instalacyjnemu pakietu DirectX zaktualizować instalację kompilatora, to krok ten nie jest konieczny. W przeciwnym razie należy skonfigurować kompilator Visual C++ w przedstawiony niżej sposób. 1. Należy rozwinąć menu Tools i wybrać polecenie Options. 2. Należy wybrać zakładkę Directories.
3. Do listy katalogów należy dodać katalog, w którym umieszczone zostały pliki
nagłówkowe, a następnie przesunąć go na szczyt listy tak, by nowe wersje plików nagłówkowych były znajdywane przed ich starszymi wersjami dostarczonymi z kompilatorem Visual C++. 4. Należy wybrać zakładkę Libraries. 5. Należy dodać do listy katalog, w którym umieszczone zostały pliki bibliotek
w identyczny sposób jak w przypadku plików nagłówkowych. Za każdym razem, gdy będzie tworzony nowy projekt, należy umieścić w nim odpo wiednie biblioteki. Istnieje kilka sposobów na określenie wykorzystywanych plików bi bliotek. Jednym z lepszych jest wybranie komendy Settings z menu Project, wybranie zakładki Link i dodanie nazwy biblioteki w linii Object/library modules. Na przykład dla wszystkich programów korzystających z OpenGL można umieścić w tej linii nazwy plików opengl32.lib i glu32.lib.
30
Część I ♦ W prow adzenie do OpenGL i D irectX
OpenGL Specyfikacja OpenGL określa interfejs programowy udostępniający programiście moż liwości graficzne platformy, na której działać będzie tworzona aplikacja. OpenGL jest biblioteką niskiego poziomu, która umożliwia generowanie zaawansowanej grafiki, do stępną dla wszystkich najważniejszych platform w różnych konfiguracjach sprzętowych. Zaprojektowano ją z myślą o różnego rodzaju aplikacjach, takich jak na przykład kom puterowe wspomaganie projektowania czy gry. Wiele gier dostępnych na rynku wyko rzystuje OpenGL. Przykładem może być pokazana na rysunku 1.4 gra Quake 3 firmy id Software. Rysunek 1.4. Gra Quake 3
Specyfikacja OpenGL zawiera jedynie operacje graficzne niskiego poziomu, aby umoż liwić programiście pełną kontrolę nad tworzoną grafiką i zwiększyć uniwersalność bi blioteki. Operacje te mogą zostać wykorzystane do stworzenia własnej biblioteki graficz nej wyższego poziomu. Przykładem takiego działania może być biblioteka GLU {OpenGL Utility Library) dostarczana razem z większością dystrybucji biblioteki OpenGL. Należy zwrócić przy tym uwagę na to, że OpenGL jest wyłącznie biblioteką operacji graficz nych i — w przeciwieństwie do DirectX — nie umożliwia operacji związanych z tworze niem dźwięku, interakcją z użytkownikiem, tworzeniem aplikacji sieciowych ani żad nych innych operacji niezwiązanych z tworzeniem grafiki.
Historia OpenGL Specyfikacja OpenGL została opracowana przez firmę Silicon Graphics, Inc. (SGI) jako interfejs służący do tworzenia grafiki, który jest niezależny od platformy. Od roku 1992 rozwój tej specyfikacji prowadzony jest przez OpenGL Architecture Review Board (ARB), w skład której wchodzą przedstawiciele wiodących producentów sprzętu i oprogramo wania — firm ATI, Compaq, Evans & Sutherland, Hewlett-Packard, IBM, Intel, Inter graph, nVidia, Microsoft oraz Silicon Graphics. Zadaniem ARB jest przygotowywanie specyfikacji OpenGL i tym samym dyktowanie funkcjonalności kolejnych dystrybucji interfejsu OpenGL tworzonych przez producentów.
Rozdział 1. ♦ OpenGL i D irectX
31
Gdy pisaliśmy tę książkę, najnowszą wersją specyfikacji OpenGL była wersja 1.2. Jako że specyfikacja OpenGL istnieje już dość długo, fakt, że dostępna jest dopiero wersja 1.2, sugeruje, że specyfikacja ta nie jest aktualizowana zbyt często. Ponieważ specyfikację OpenGL tworzono początkowo z myślą o zastosowaniach zwią zanych jedynie z profesjonalnymi stacjami graficznymi, to tym bardziej była ona wy starczająca dla zwykłych komputerów osobistych. Jednak gwałtowny rozwój możliwo ści kart graficznych komputerów osobistych w ostatnich latach sprawił, że coraz więcej z nich nie było wykorzystywanych przez specyfikację OpenGL. Dlatego też producenci kart graficznych zaczęli tworzyć własne rozszerzenia specyfikacji OpenGL, które z czasem mogą stać się częścią oficjalnego standardu. Specyfikacja OpenGL 1.2 była na przykład pierwszą wersją, która udostępniała wsparcie możliwości wymaganych przez twórców gier (na przykład odwzorowanie wielu tekstur jednocześnie). Jest więc bardzo prawdo podobne, że gry będą miały także znaczący wpływ na postać kolejnych wersji specyfikacji.
Architektura OpenGL Specyfikacja OpenGL stanowi zbiór kilkuset funkcji udostępniających możliwości sprzętu graficznego. Wykorzystuje ona maszynę stanów, której stany opisują sposób wykonywania operacji graficznych. Korzystając z interfejsu programowego OpenGL można określać wiele aspektów maszyny stanów, takich jak na przykład bieżący kolor, oświetlenie, sposób łączenia kolorów i tak dalej. Sposób tworzenia grafiki jest więc określony przez bieżącą konfigurację maszyny stanów. Dlatego też ważne jest, by wła ściwie rozumieć znaczenie poszczególnych stanów maszyny dla sposobu jej działania. Często popełnianym błędem podczas korzystania z biblioteki OpenGL jest niewłaściwy wybór stanu, co skutkuje nieprzewidzianymi efektami operacji graficznych. W książce nie zostaną omówione w całości maszyny stanów OpenGL, ale przedstawione będą te wycinki, które związane będą z wykorzystywanymi operacjami. Jądro biblioteki OpenGL stanowi potok tworzenia grafiki przedstawiony na rysunku 1.5. Na tym etapie nie trzeba dokładnie rozumieć znaczenia wszystkich wykonywanych operacji. Najważniejsze jest to, by uświadomić sobie, że wszystko, co jest widoczne na ekranie, stanowi rezultat wykonania szeregu operacji w potoku. Większość z tych ope racji wykonywana jest na szczęście automatycznie przez bibliotekę OpenGL. Interfejs programowy OpenGL stanowi w systemie Windows alternatywę dla interfejsu GDI (Graphics Device Interface). Zadaniem interfejsu GDI jest ukrycie specyfiki róż nych kart graficznych przed programistą tworzącym aplikacje systemu Windows. Jako że interfejs ten zaprojektowano z myślą o typowych aplikacjach, jego zasadniczą wadą z punktu widzenia programisty tworzącego gry jest niska efektywność. Interfejs OpenGL omija interfejs GDI i operuje bezpośrednio na sprzęcie. Rysunek 1.6 prezen tuje hierarchię programowych interfejsów graficznych w systemie Windows.
Biblioteka GLU Biblioteka GLU (OpenGL Utility Library) uzupełnia bibliotekę OpenGL o funkcje gra ficzne wyższego poziomu. Oferuje ona proste funkcje obudowujące wywołania funkcji OpenGL oraz skomplikowane komponenty umożliwiające wykorzystanie zaawansowa nych technik tworzenia grafiki. Należą do nich:
32
Część I ♦ W prow adzenie do OpenGL i D irectX
Rysunek 1.5. Potok tworzenia grafiki OpenGL
Rysunek 1.6. Interfejs programowy OpenGL w systemie Windows
♦ skalowanie obrazów dwuwymiarowych; ♦ tworzenie trójwymiarowych brył, takich jak kule czy walce; ♦ automatyczne tworzenie mipmap na podstawie pojedynczego obrazu; ♦ wsparcie tworzenia powierzchni NURBS; ♦ wsparcie operacji kafelkowania wielokątów wklęsłych; ♦ specjalizowane przekształcenia i ich macierze. Jeśli czytelnik nie zna części powyższych terminów, to nie ma powodu do niepokoju. Wyjaśnione zostaną stopniowo w dalszej części książki przy korzystaniu z poszczegól nych możliwości biblioteki GLU.
Pakiet bibliotek GLUT Pakiet bibliotek GLUT (OpenGL Utility Toolkit) zawiera zestaw pomocniczych bibliotek i jest dostępny dla najważniejszych platform. Biblioteki te uzupełniają OpenGL o możliwość
Rozdział 1 . ♦ OpenGL i D irectX
33
tworzenia menu, okien czy interakcji z użytkownikiem. Są też niezależne od platformy, co umożliwia łatwe przenoszenie wykorzystujących je aplikacji (na przykład z systemu Unix do Windows). Biblioteki GLUT są łatwe w użyciu i choć nie dostarczają możliwości porównywalnych z oferowanymi przez system operacyjny, są zupełnie wystarczające w przypadku two rzenia prostych aplikacji i programów demonstracyjnych. W przypadku dość złożonych gier możliwości bibliotek GLUT są zbyt ograniczone i dla tego nie będą wykorzystywane w przykładach zawartych w tej książce. Więcej informacji na temat bibliotek GLUT czytelnik znajdzie na stronie http://reality.sgi.com/mjk/glut3/.
Programy Należy teraz wyprzedzić nieco bieg książki i przyjrzeć się programom, które będą two rzone. Rysunek 1.7 prezentuje animację oświetlonego i pokrytego teksturą sześcianu. Kod źródłowy tego programu znajduje się na dysku CD. Chociaż znaczna jego część może jeszcze nie być zrozumiała dla czytelnika, to jednak przykład ten daje ogólny pogląd na to, co będzie tematem kolejnych rozdziałów. Przykład ten — jako jedyny w tej książce — wykorzystuje też biblioteki GLUT. To wyjątkowe rozwiązanie, które pozwala jed nak uprościć kod źródłowy programu przed omówieniem tworzenia aplikacji systemu Windows. Jeśli czytelnik zdecyduje się na skompilowanie tego programu, to powinien najpierw skopiować z dysku CD pliki bibliotek i nagłówków GLUT w podobny sposób jak w przypadku plików biblioteki OpenGL. Rysunek 1.7. Prosty program korzystający z biblioteki OpenGL
DirectX DirectX jest zbiorem interfejsów programowych opracowanych przez firmę Microsoft w celu zapewnienia bezpośredniego dostępu do sprzętu w systemie Windows. Każdy z tych interfejsów udostępnia funkcje dostępu do urządzeń lub emuluje urządzenie, jeśli nie występuje ono w danej konfiguracji. Funkcje te umożliwiają tworzenie grafiki dwu-
34
Część I ♦ W prow adzenie do OpenGL i D irectX
i trójwymiarowej, wykorzystanie ogromnej liczby typów urządzeń wejściowych, mik sowanie dźwięku, tworzenie gier działających w sieci oraz wykorzystanie wielu forma tów plików multimedialnych. Do interfejsów programowych DirectX należą: ♦ DirectDraw; ♦ Direct3D; ♦ Directlnput; ♦ DirectSound; ♦ DirectMusic; ♦ DirectPlay; ♦ DirectShow. W zamierzeniu firmy Microsoft DirectX miał umożliwić tworzenie na platformie Win dows aplikacji multimedialnych działających efektywnie, a ponadto niezależnych od konkretnej konfiguracji sprzętowej. Dopiero jednak w wersji 7 poradzono sobie z istot nymi ograniczeniami poprzednich wersji, a producenci sprzętu zaczęli rozwiązywać problemy konfliktów sprzętowych, które były zmorą nękającą użytkowników interfej sów programowych DirectX. Dodatkowo wersja ta wprowadziła także kilka bibliotek pomocniczych, takich jak na przykład D3DX. Wprowadzenie tej biblioteki pozwoliło uniknąć wielokrotnego wywoływania funkcji inicjalizacji, które utrudniało wcześniej korzystanie z DirectX. Wersja 8 obok wprowadzenia wielu nowych możliwości przy niosła także zupełnie nową, przeprojektowaną architekturę DirectX. Trzeba zatem przyj rzeć się bliżej historii DirectX, aby zobaczyć, w jakim kierunku zmierza rozwój tego rozwiązania.
Historia DirectX W czasach panowania systemu operacyjnego DOS twórcy gier korzystali z bezpośred niego dostępu do sprzętu. Dostęp do przerwań, kart dźwiękowych, portów urządzeń wejściowych, karty grafiki VGA umożliwiał im wykorzystanie możliwości sprzętowych dokładnie w taki sposób, jak to zaprojektowali. Gdy na rynku pojawił się system Win dows 3.1, nie wzbudził on większego zainteresowania twórców gier, głównie ze wzglę du na ograniczenia efektywnościowe, jakie na nich nakładał. Jednak tworzenie gier dla systemu DOS także nie było wolne od poważnych problemów. Na rynku pojawiało się coraz więcej różnych urządzeń i tym samym jeszcze szybciej wzrastała liczba możliwych konfiguracji sprzętu klasy PC. Doszło nawet do tego, że tworzenie sterowników różnych urządzeń pochłaniało więcej nakładów niż programo wanie samej gry! Wprowadzenie przez firmę Microsoft systemu Windows 95 stworzyło nadzieje na zmia nę tej sytuacji. Technologia Pług and Play uprościła instalację nowych urządzeń w sys temie — w tym kart graficznych, dźwiękowych i urządzeń wejścia. System Windows 95 wprowadził nowy sposób zarządzania zasobami, który ułatwiał zarządzanie urządze niami i czynił niezależność aplikacji od urządzeń bardziej realną. System nadal jednak nie zawierał żadnych rozszerzeń, które umożliwiłyby efektywne tworzenie gier.
Rozdział 1 . ♦ OpenGL i D irectX
35
DirectX stworzono więc, aby uczynić z systemu Windows platformę interesującą także dla twórców gier. Projektanci DirectX zrozumieli, że w tym celu należy udostępnić sze reg efektywnych bibliotek niskiego poziomu, które umożliwią programistom gier pełną kontrolę nad tworzonym dziełem. Kolejnym celem DirectX było także uwolnienie twór ców gier od tworzenia sterowników urządzeń, za które odtąd odpowiedzialni stali się ich producenci. Inną. istotną możliwością DirectX było umożliwienie równoczesnego wy konywania zwykłych aplikacji i gier. Technologia DirectX — dysponując takimi moż liwościami — miała też zapewniać efektywność działania gier zbliżoną do osiąganej dotychczas w systemie DOS. Rozwój technologii DirectX na przestrzeni kilku ostatnich lat doprowadził do stworzenia jednego z lepszych interfejsów programowych dostęp nych dla twórców gier i aplikacji multimedialnych. Przyjrzyjmy się więc bliżej archi tekturze DirectX.
Architektura DirectX W przekazywaniu żądań DirectX do urządzeń pośredniczą dwie warstwy: warstwa abs trakcji sprzętu HAL {Hardware Abstraction Layer) oraz warstwa emulacji sprzętu HEL {Hardware Emulation Layer). Na etapie inicjalizacji DirectX sprawdza, czy dana konfi guracja umożliwia sprzętową realizację różnych operacji. Jeśli dana operacja posiada możliwość sprzętowej realizacji, to do jej wykonania wykorzystywana jest warstwa HAL. W przeciwnym razie wykonanie operacji jest emulowane programowo przez war stwę HEL. Ilustruje to rysunek 1.8. Rysunek 1.8. Architektura DirectX
Jak łatwo zauważyć, warstwa HAL korzysta z usług sprzętu, a warstwa HEL sama im plementuje pożądaną funkcjonalność. Taka architektura umożliwia stopniowe wykorzy stywanie pojawiających się możliwości sprzętowych. Można na przykład założyć, że odpowiednia konfiguracja zawiera kartę grafiki, która umożliwia sprzętową realizację odwzorowań zakłócających dla grafiki trójwymiarowej, ale nie wspiera sprzętowo od wzorowań otoczenia. Jeśli ktoś kupi najnowszą grę, która wykorzystuje oba rodzaje odwzorowań, to warstwa HAL będzie realizować odwzorowania zakłócające przy uży ciu możliwości sprzętowych, a warstwa HEL dostarczy programowej implementacji odwzorowań otoczenia. Po pewnym czasie można będzie wymienić kartę grafiki na nową, która będzie realizować sprzętowo oba rodzaje odwzorowań. Odwzorowania oto czenia będą w takim wypadku realizowane z lepszą efektywnością, ponieważ ich reali zację przejmie warstwa HAL i będą one wykonywane sprzętowo.
36
Część I ♦ W prow adzenie do OpenGL i D irectX
Innym aspektem architektury DirectX jest zestaw tworzących ją interfejsów programo wych. Wymienione zostały one już wcześniej, więc teraz zostaną przedstawione bliżej ze wskazaniem na te, które będą wykorzystywane w przykładach zawartych w książce.
DirectX Graphics Interfejs DirectX Graphics stanowi połączenie interfejsów DirectDraw i Direct3D do stępnych w poprzednich wersjach DirectX. Jako że do tworzenia grafiki wykorzystywane będą możliwości oferowane przez interfejs programowy OpenGL, możliwości DirectX Graphics nie będą dla odbiorcy interesujące.
DirectX Audio DirectX Audio stanowi połączenie interfejsów DirectSound dostępnych w poprzednich wersjach DirectX. Direct Audio umożliwia realizację kompletnej ścieżki dźwiękowej z wykorzystaniem możliwości sprzętowych kart dźwiękowych, ładowania dźwięków DLS, obiektów DMO (DirectX Media Objects) oraz efektów pozycjonowania źródeł dźwięku w przestrzeni. Komponent DirectX Audio stanowi mikser dźwięków o ogrom nych możliwościach tworzenia różnego rodzaju efektów wykorzystywanych w grach i aplikacjach multimedialnych. W książce tej przedstawione zostanie wykorzystanie DirectX Audio do implementacji efektów dźwiękowych w programach demonstracyj nych i grach.
Directlnput Interfejs Directlnput umożliwia programiście dostęp do ogromnej liczby najróżniej szych urządzeń wejściowych oraz zawiera wsparcie siłowego sprzężenia zwrotnego dla specjalizowanych manipulatorów. Komponent Directlnput omija kolejkę komunikatów wykorzystywaną przez system Windows i działa bezpośrednio ze sterownikami urzą dzeń w celu zapewnienia najkrótszego czasu reakcji tak pożądanego w grach. Kompo nent Directlnput będzie wykorzystywany we wszystkich przykładach, które wymagać będą interakcji z użytkownikiem.
DirectPlay DirectPlay stanowi zestaw narzędzi upraszczających komunikację w lokalnych sieciach komputerowych, sieci Internet i za pomocą modemów. Narzędzia te pozwalają łatwo odnaleźć sesje gry działające na innych maszynach i zapewniają komunikację pomiędzy nimi. Przykłady przedstawione w książce nie będą wymagały korzystania z komponentu DirectPlay.
DirectShow DirectShow umożliwia odtwarzanie plików multimedialnych w formatach A VI i MPG. Komponent został dołączony do DirectX dopiero w wersji 8, a wcześniej dostępny był oddzielnie. Nie będzie omawiany w książce.
Rozdział 1 . ♦ OpenGL i D irectX
37
DirectSetup DirectSetup umożliwia instalację DirectX przez aplikację. Jako że DirectX jest dość skomplikowanym produktem, interfejs DirectSetup znacząco upraszcza proces jego in stalacji. W tej książce nie zostanie jednak omówione jego wykorzystanie.
Porównanie OpenGL i DirectX Temat ten jest przedmiotem wielu dyskusji i sporów. Pozostanie nim jeszcze przez długi czas. Tutaj zamierzamy jedynie ograniczyć się do zestawienia możliwości oferowanych przez oba interfejsy. Pierwszą istotną różnicą jest to, że interfejs DirectX nie służy jedynie do tworzenia gra fiki, ale posiada także komponenty umożliwiające tworzenie efektów dźwiękowych, inte rakcji z użytkownikiem, aplikacji sieciowych i multimediów. Natomiast OpenGL jest interfejsem przeznaczonym wyłącznie do tworzenia grafiki. Należy porównać zatem moż liwości komponentu graficznego interfejsu DirectX z możliwościami interfejsu OpenGL. Obydwa interfejsy wykorzystują tradycyjne potokowe tworzenie grafiki. Taki sposób tworzenia grafiki stosowany jest od zarania grafiki komputerowej. Pewne modyfikacje wprowadziło w nim wykorzystanie nowych rozwiązań sprzętowcyh, ale zasadnicza idea pozostała taka sama. Obydwa interfejsy opisują punkty wierzchołkowe za pomocą zestawu danych zawiera jących współrzędne określające ich położenie w przestrzeni oraz inne informacje o punk tach. Podstawowe elementy grafiki (punkty, linie, trójkąty) definiowane są za pomocą uporządkowanych zbiorów wierzchołków. Jednak sposób, w jaki informacja o wierz chołkach wpływa na tworzenie tych elementów, jest różny dla obu interfejsów. Istnieje także wiele innych różnic. Ilustruje je tabela 1.1, w której zestawione zostały możliwości oferowane przez oba interfejsy.
Podsumowanie W rozdziale tym przedstawione zostały interfejsy OpenGL i DirectX. Interfejs OpenGL będzie wykorzystywany w dalszej części książki do tworzenia grafiki trójwymiarowej, a interfejs DirectX do tworzenia efektów dźwiękowych i interakcji z użytkownikiem. Interfejs programowy DirectX stworzono, aby umożliwić aplikacjom systemu Windows bardziej bezpośredni i efektywny dostęp do możliwości sprzętu. Interfejs DirectX ko rzysta z warstw HAL i HEL, które umożliwiają realizację operacji DirectX niezależnie od tego, czy dana konfiguracja dysponuje odpowiednim sprzętem. W książce tej wyko rzystywane będą komponenty DirectX Audio oraz Directlnput. Skoro mamy już ogólny pogląd na możliwości interfejsów OpenGL i DirectX, to pora zabrać się do programowania!
38
Część I ♦ W prow adzenie do OpenGL i D irectX
Tabela 1.1. Porównanie interfejsów OpenGL i DirectX Cecha
OpenGL
DirectX 8
Łączenie wierzchołków w jeden
Nie
Tak
Dostępność dla wielu systemów operacyjnych
Tak
Nie
Mechanizm rozszerzeń
Tak
Tak
Prowadzenie prac nad rozwojem specyfikacji
Gremium złożone z przedstawicieli wielu firm
firma Microsoft
Dostępność pełnej specyfikacji
Tak
Nie
Oświetlenia dwustronne
Tak
Nie
Tekstury przestrzenne
Tak
Nie
Bufory Z niezależne od sprzętu
Tak
Nie
Bufory akumulacyjne
Tak
Nie
Pełnoekranowy antialiasing
Tak
Tak
Rozmycie na skutek ruchu
Tak
Tak
Głębokość pola
Tak
Tak
Tworzenie obrazów stereoskopowych
Tak
Nie
Atrybuty określające rozmiary punktów i szerokość linii
Tak
Nie
Picking
Tak
Nie
Krzywe i powierzchnie parametryczne
Tak
Nie
Bufory geometrii
Listy wyświetlania
Bufory wierzchołków
Emulacja programowa
Nie jest realizowany sprzętowo
Pozwala aplikacji określić sposób realizacji
Interfejs
Wywołania procedur
COM
Aktualizacje
Wydawane co roku przez ARB lub za pomocą rozszerzeń
Co roku
Dostępność kodu źródłowego
Przykładowa implementacja
Microsoft DDK
Rozdział 2.
Korzystanie z OpenGL w systemie Windows Programiści, którzy nie mieli jeszcze okazji do tego, by zająć się tworzeniem progra mów dla systemu Windows, często uważają, że jest to niezwykle zagmatwane. Jeśli jednak właściwie zrozumie się sposób działania tego systemu i rolę poszczególnych funkcji, to okaże się, że tworzenie programów w systemie Windows jest także niezwy kle łatwe. W rozdziale tym przedstawiony zostanie krok po kroku sposób tworzenia aplikacji w systemie Windows. Jeśli czytelnik nie przepadał dotąd za systemem Win dows, to może polubi go po przeczytaniu tego rozdziału. Przedstawione w nim zostaną następujące zagadnienia: ♦ architektura systemu Windows; ♦ procedura okienkowa; ♦ klasa okien; ♦ zasoby; ♦ pętla przetwarzania komunikatów systemu Windows; ♦ funkcje WGL; ♦ formaty pikseli; ♦ korzystanie z OpenGL w systemie Windows; ♦ pełnoekranowe aplikacje OpenGL.
Wprowadzenie do programowania w systemie Windows Jako że wszystkie tworzone w tej książce programy będą działać na platformie Windows, trzeba najpierw zapoznać się ze specyfiką tworzenia programów działających w tym systemie. W rozdziale tym przedstawiony zostanie odpowiedni zasób informacji, który wystarczał będzie do tworzenia prostych aplikacji obudowujących OpenGL lub innych prostych aplikacji, takich jak na przykład edytory zasobów gry.
40
Część I ♦ W prow adzenie do OpenGL i D irectX
System Windows opracowany przez firmę Microsoft umożliwia jednoczesne działanie wielu aplikacji lub procesów. Każdy proces uzyskuje pewien przedział czasu, w którym jego wykonanie nie zostanie przerwane przez inny proces. Wielkość tego przedziału czasu ustalana jest przez zarządcę procesów, który stanowi część systemu odpowie dzialną za szeregowanie procesów. Zarządca procesów zapewnia, że każdy proces uzy skuje odpowiednią ilość czasu w zależności od priorytetu procesu oraz bieżącego stanu systemu. Rysunek 2.1 prezentuje sposób, w jaki system Windows zarządza wykonaniem wielu procesów. Rysunek 2.1. Wykonanie wielu procesów w systemie Windows
okno aplikacji aktywne, przydzielony czas procesora: 80 ms
Dla twórców gier interesującą cechą systemu Windows jest jego wielowątkowość. Ozna cza ona możliwość rozbicia każdego procesu na wątki, z których każdy wykonuje własny kod. Rozwiązanie takie umożliwia wielozadaniowość w obrębie pojedynczej aplikacji. Podobnie jak procesy wątki są szeregowane na podstawie ich priorytetów i przyznawany jest im odpowiedni czas wykonania. Dzięki wykorzystaniu wątków gra może więc wy konywać równocześnie wiele różnych działań. Okazuje się jednak, że to jeszcze nie ko niec możliwości związanych z wielozadaniowością. Najnowsze wersje systemu Windows (98, ME) wprowadzają kolejną możliwość po działu wykonania procesu w postaci włókna. Każdy wątek może składać się z wielu włó kien wykonujących niezależne działania. Rysunek 2.2 przedstawia hierarchię procesów, wątków i włókien wykonywanych w wielozadaniowym systemie Windows. Kolejną istotną cechą sposobu wykonywania aplikacji systemu Windows jest sterowa nie za pomocą zdarzeń. Oznacza ono, że działanie aplikacji odbywa się na podstawie zda rzeń, które otrzymują one od systemu operacyjnego. Aplikacja może na przykład ocze kiwać na naciśnięcie przez użytkownika klawisza. Po naciśnięciu klawisza system operacyjny rejestruje to zdarzenie i przesyła je do aplikacji. Na podstawie otrzymanego zdarzenia aplikacja podejmuje decyzję o sposobie dalszego działania. Obsługa zdarzeń wykonywana jest przez tak zwaną procedurę okienkową. Aplikacja sprawdza w pętli własn ^kolejkę zdarzeń. Jeśli oczekują w niej zdarzenia, to podejmowana jest ich obsługa.
Rozdział 2. ♦ Korzystanie z OpenGL w system ie W indows
Rysunek 2.2. Hierarchia procesów, wątków i włókien w systemie Windows
41
Windows
Na tym polega w skrócie działanie aplikacji w systemie Windows. Od programów two rzonych dla innych platform odróżnia je przede wszystkim to, że aplikacje Windows muszą obsługiwać zdarzenia generowane przez system. Po przyswojeniu podstawowej wiedzy można przyjrzeć się działaniu programu w systemie Windows.
Podstawowa aplikacja systemu Windows Programowanie w systemie Windows nie jest wcale trudne. Pierwszą aplikacją będzie — jak zwykle w takich przypadkach — prosty program, który wyświetli na ekranie ko munikat powitania. Przeglądając książki poświęcone podstawom programowania w języku C lub C++ łatwo można natrafić na podobny kod źródłowy: / / Program w y ś w ie tla ją c y kom unikat p o w ita n ia # in c lu d e < s td io .h > v o id m ainO
{
p r in tf ( " W ita m y ! \n " ) ;
} Program ten będzie działać poprawnie na przykład w systemie DOS, ale dla systemu Windows trzeba stworzyć jego nową wersję. # d e fin e WIN32_LEAN_AND_MEAN
/ / "odchudza" a p lik a c ję Windows
# in c lu d e
/ / główny p lik nagłówkowy systemu Windows
/ / p u n k t, w którym rozpoczyna s ię wykonywanie a p lik a c ji i n t WINAPI WinMain(HINSTANCE h ln s ta n c e . HINSTANCE hP re vIn sta n ce . LPSTR IpCmdLine, in t nShowCmd)
{ / / w y ś w ie tla okno dialogow e z a w ie ra ją ce te k s t p o w ita n ia MessageBox(NULL, "Y tW ita m y!", "Moja pierw sza a p lik a c ja Windows". 0 ); re tu rn 0;
} I to wszystko. Jest to prawdopodobnie najprostsza z możliwych aplikacji systemu Win dows. Wyświetla ona okno dialogowe zawierające tekst powitania.
42
Część I ♦ W prow adzenie do OpenGL i D irectX
Teraz należy przyjrzeć się bliżej sposobowi tworzenia bardziej zaawansowanych apli kacji systemu Windows.
Funkcja WinMain() Wykonanie programu w systemie DOS zaczyna się od wywołania funkcji m a in (). Pro jektanci systemu Windows postanowili być nieco bardziej kreatywni i główną funkcję aplikacji systemu Windows nazwali WinMain( ). Jej prototyp przedstawia się następująco: in t WINAPI WinMairUHINSTANCE hlnstance, HINSTANCE hPrevInstance, LPSTR IpCmdLine, in t nShowCmd)
Funkcja ta zwraca wartość typu in t , a WINAPI określa konwencję wywołań stosowaną przez wszystkie 32-bitowe wersje systemu Windows (różną od zwykłej konwencji wy wołań funkcji języka C). Funkcji WinMain( ) system przekazuje podczas uruchomienia aplikacji przedstawione ni żej parametry. ♦ Parametr h ln s ta n c e zawierający uchwyt instancji aplikacji, przez którą należy rozumieć jej pojedynczą działającą kopię. Uchwyt ten wykorzystywany jest przez system do odwoływania się do instancji aplikacji przy obsłudze zdarzeń, przetwarzaniu komunikatów i innych operacjach. ♦ Parametr hP re vInsta nce posiadający zawsze wartość NULL. Parametr ten wywodzi się z systemu Windows 3.1, w którym posiadał wartość różną od NULL, gdy w tle wykonywane były już inne instancje aplikacji. ♦ Parametr IpCmdL i ne przekazujący wskaźnik do łańcucha znaków, który zawiera parametry uruchomienia aplikacji podane w linii poleceń. Jeśli na przykład użytkownik uruchomi aplikację korzystając z okna dialogowego Uruchom, w którym wpisał apl ik a c ja .exe param etr 1, to łańcuch wskazywany przez IpCmdL i ne będzie miał postać param etr 1. ♦ Parametr nShowCmd określa sposób prezentacji aplikacji na ekranie po jej uruchomieniu.
Procedura okienkowa Aplikacja Windows musi obsłużyć komunikaty wysyłane do niej przez system Windows. WM CREATE, WM SIZE czy WM M0VE stanowią jedynie kilka elementów ogromnego zbioru ko munikatów, które może otrzymywać aplikacja. Aby system Windows mógł komunikować się z aplikacją niezbędna jest funkcja zwana procedurą okienkową (zwykle o nazwie WndProc), która określać będzie sposób obsługi poszczególnych komunikatów o zdarzeniach. Proce dura ta nazywana jest często także procedurą obsługi zdarzeń. Funkcja WndProc otrzymuje komunikat od systemu Windows, przetwarza go i podejmuje odpowiednie działania. A oto prototyp procedury okienkowej : LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM IParam):
Typem zwracanym przez procedurę okienkową jest LRESULT, który w systemie Windows zdefiniowany jest jako 1ong. CALLBACK oznacza konwencję wywołań stosowaną w przypadku
Rozdział 2. ♦ Korzystanie z OpenGL w system ie W indows
43
funkcji wywoływanych przez system Windows. Nazwa procedury okienkowej repre zentuje adres funkcji i dzięki temu może być ona wywoływana przez system, ponieważ przekażemy jej adres systemowi tworząc klasę okna. Pierwszym parametrem procedury okienkowej jest uchwyt okna hwnd. Parametr ten ma znaczenie jedynie wtedy, gdy jednocześnie otwartych jest wiele okien tej samej klasy. Umożliwia on wtedy sprawdzenie, którego okna dotyczy komunikat, zanim podjęta zo stanie odpowiednia akcja. Kolejny z parametrów — message — reprezentuje identyfikator komunikatu, który na leży obsłużyć w procedurze okienkowej. Identyfikatory komunikatów rozpoczynają się od przedrostka WM_. Trzeci i czwarty parametr — wParam i IParam — stanowią rozszerzenie parametru message. Zawierają one dodatkową informację, która nie mogła być przekazana za po średnictwem identyfikatora komunikatu.
Obsługa komunikatów W celu obsługi komunikatów procedura okienkowa wykorzystuje zwykle warunkową instrukcję wyboru s w itc h . Chociaż system Windows może wysyłać do aplikacji kilkaset różnych komunikatów, to jednak procedura okienkowa powinna zapewnić jedynie ob sługę tych, które są istotne dla danej aplikacji. W pozostałych przypadkach z reguły system wykonuje domyślną obsługę komunikatów. Poniżej przedstawiamy przykłado wy kod procedury okienkowej wykorzystującej instrukcję s w itch . LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam. LPARAM 1Param)
{ PAINTSTRUCT pa i n tS tr u c t; sw itch(m essage)
{ case WM_CREATE:
{
/ / w odpowiedzi na te n kom unikat tw orzone je s t okno
re tu rn 0;
} case WM_CL0SE:
/ / kom unikat o zam knięciu a p lik a c ji
{ P ostQ uitM essage(0); r e tu rn 0;
/ / w ysyła kom unikat o zakończeniu a p lik a c ji
} case WM_PAINT:
/ / zawartość okna powinna zostać odrysowana
{ B eginP aint(hw nd, &ps); EndPaint(hwnd, &ps);
/ / odrysow uje okno
} d e f a u lt : break;
} / / p rze ka zu je n ie obsłużone kom unikaty systemowi re tu rn (DefWindowProcChwnd, message, wParam, 1Param));
44
Część I ♦ W prow adzenie do OpenGL i D irectX
W obecnej postaci procedura okienkowa prawie nie zawiera kodu obsługi zdarzeń — potrafi jedynie wysłać komunikat o zakończeniu pracy aplikacji. Stanowi jednak dobrą ilustrację ogólnego sposobu obsługi zdarzeń za pomocą instrukcji s w itc h . Pierwszym z obsługiwanych komunikatów jest WM CREATE, który wysyłany jest do aplikacji podczas tworzenia jej okna. Obsługując ten komunikat można dokonać inicjalizacji aplikacji bądź, jak w tym przypadku, nie wykonywać żadnych działań i natychmiast zakończyć wykonywanie procedury okienkowej za pomocą polecenia re tu rn . Kolejnym z obsługiwanych komunikatów jest WM_CL0SE, który jest wysyłany do aplika cji, gdy okno określone przez parametr hwnd jest zamykane. Obsługa tego komunikatu polega typowo na umieszczeniu w kolejce komunikatu WM_QUIT sygnalizującego zakoń czenie pracy aplikacji. W tym celu wywołujemy funkcję PostQ uitM essage(). Ostatni z zaprezentowanych komunikatów — WM PAINT — wysyłany jest, gdy konieczne jest odrysowanie zawartości okna. Obsługując go należy odrysować zawartość okna. W tym przypadku polega ono na wywołaniu funkcji B e ginP aint i EndPaint, które powodują walidację obszaru okna i odrysowanie jego tła za pomocą pędzla określonego przez klasę okna. Jeśli komunikat, który otrzymała aplikacja, nie jest jednym z wymienionych wyżej, to jego obsługę należy przekazać funkcji domyślnej obsługi komunikatów DefWindowProc(). W ten sposób można zyskać pewność, że jeśli komunikat nie zostanie obsłużony przez procedurę okienkową aplikacji, to obsłuży go system.
Klasy okien Przez klasę okna należy rozumieć zbiór atrybutów, które są wykorzystywane przez system podczas tworzenia okna. Każde okno musi należeć do pewnej klasy. Klasa ta posiada własną procedurę okienkową, która jest wspólna dla wszystkich okien danej klasy. W ten sposób każde okno może przetwarzać komunikaty systemu Windows i ob sługiwać je za pomocą procedury okienkowej klasy, do której należy. Klasy okienkowe zdefiniowane są dla wszystkich elementów interfejsu użytkownika systemu Windows: przycisków, okien, list, pól edycji i innych. Wartości atrybutów poszczególnych klas są różne dla różnych elementów interfejsu użytkownika i decydują o ich postaci i za chowaniu. Aplikacja musi najpierw zdefiniować i zarejestrować klasę okien, zanim utworzy okno danej klasy. Definiując klasę okna posługujemy się strukturą WNDCLASSEX. Stanowi ona rozszerzenie stosowanej dawniej struktury WNDCLASS o dwa pola. Pierwsze z nich określa rozmiar struktury, a drugie uchwyt małej ikony okna. Struktura WNDCLASSEX zdefiniowana jest następująco: ty p e d e f s tr u c t UINT UINT WNDPROC ■ in t in t HINSTANCE HICON HCURSOR
_WNDCLASSEX { cbS ize; s ty le : IpfnW ndProc; c b C ls E xtra ; cbWndExtra; h ln s ta n c e ; h lc o n ; hC ursor;
// // // // // // // //
rozm iar s tr u k tu r y WNDCLASSEX s ty l okna adres procedury okienkow ej dodatkowe in fo rm a c je o k la s ie dodatkowe in fo rm a c je o o kn ie uchwyt in s ta n c ji a p lik a c ji uchwyt dużej ikony okna uchwyt kursora myszy
Rozdział 2. ♦ Korzystanie z OpenGL w system ie W indows
HBRUSH LPCWSTR LPCWSTR HICON } WNDCLASSEX;
hbrBackground; IpszMenuName; IpszCIassName; hlconSm;
45
/ / k o lo r t ł a okna / / nazwa głównego menu / / nazwa k la s y okna / / uchwyt m ałej ikony okna
Aby stworzyć klasę okna, trzeba więc zdefiniować poniższą zmienną: WNDCLASSEX w indow C lass;
/ / kla sa okna a p lik a c ji
Określenie atrybutów klasy okna Wypełnienie struktury opisującej klasę okna nie przedstawia większej trudności. Pole cbSi ze określa rozmiar struktury WNDCLASSEX i wobec tego inicjujemy je w poniższy sposób: w indow C lass.cbS ize = sizeof(WNDCLASSEX);
Pole h ln s ta n c e musi zawierać uchwyt bieżącej instancji aplikacji, który otrzymany zo stał jako parametr funkcji W inMainO. Natomiast w polu lpfnWndProc należy umieścić adres procedury okienkowej. Jeśli zdefiniowana zostanie w pokazany wcześniej sposób, to adres ten należy umieścić w polu 1pfnWndProc jak poniżej: w indow C lass.lpfnW ndP roc = WndProc;
Pole stylu okna opisuje ogólne właściwości okna i należy wypełniać je za pomocą pre definiowanych znaczników. Korzystając z operatora sumy bitowej można utworzyć ich dowolną kombinację. Tabela 2.1 prezentuje dostępne znaczniki. Tabela 2.1. Znaczniki stylu okna Znacznik
Znaczenie
CSJDBLCLKS
System będzie wysyłał do procedury okienkowej komunikaty o dwukrotnym kliknięciu myszą gdy nastąpi ono w obszarze okna danej klasy
CS_CLASSDC
Tworzy jeden, wspólny kontekst urządzenia dla wszystkich okien klasy
CS_GLOBALCLASS
Pozwala aplikacji utworzyć okno danej klasy niezależnie od wartości parametru h ln s ta n c e przekazanego funkcji CreateWindowEx
CD_HREDRAW
Powoduje odrysowanie okna jeśli jego szerokość uległa zmianie
CS NOCLOSE
Blokuje możliwość zamknięcia okna za pomocą systemowego menu okna
CS_0WNDC
Tworzy osobny kontekst urządzenia dla każdego z okien klasy
CS_PARENTDC
Udostępnia region przycięcia okna nadrzędnego oknu podrzędnemu, by mogło ono rysować w obszarze okna nadrzędnego
CS_SAVEBITS
Zapisuje w postaci mapy bitowej obszar ekranu przesłonięty przez okno; styl ten przeznaczony jest dla małych okien (na przykład dialogowych lub menu), które pojawiają się na krótko i usuwane są z ekranu, zanim wykonane zostaną inne akcje związane z wyglądem ekranu
CSJ/REDRAW
Powoduje odrysowanie okna, jeśli jego wysokość uległa zmianie
Wszystkie one dostępne są dla programisty definiującego klasę okna, ale na razie wy starczy wykorzystać tylko dwa: w in d o w C la s s .s ty le = CS_HREDRAW | CS_VREDRAW;
46
Część I ♦ W prow adzenie do OpenGL i D irectX
Pola cbC lsE xtra i cbWndExtra otrzymają wartość 0. Pole cbC lsE xtra określa liczbę do datkowych bajtów, które powinny zostać przydzielone do struktury klasy okna, a pole cbWndExtra liczbę dodatkowych bajtów przydzielanych do każdej instancji okna. W na szym przypadku nie będziemy z nich korzystać.
Ładowanie ikon i kursora myszy Klasa okien określa także postać kursora myszy i ikon okien. Zasoby te można zdefi niować indywidualnie lub skorzystać z gotowych, dostarczanych przez system. Przyj rzyjmy się zatem temu zagadnieniu bliżej. Klasa okien określa dwa rodzaje ikon okien: małą i dużą. Mała ikona posiada zazwyczaj wymiary 16x16 pikseli i wyświetlana jest przez systemowe menu okna bądź na pasku zadań, gdy aplikacja jest do niego zwinięta. Duże ikony wykorzystywane są przez sys tem do prezentacji plików, katalogów i pulpitów. Funkcja LoadlconO umożliwia zała dowanie obu rodzajów ikon i posiada następujący prototyp: HICON LoadIcon(HINSTANCE hlnst, LPCSTR IpszName);
Ładuje ona ikonę określoną za pomocą łańcucha IpszName i zwraca uchwyt ikony. Parametr hlnst określa uchwyt modułu zawierającego ikonę. Aby skorzystać z ikon dostarcza nych przez system Windows należy określić jego wartość jako NULL, a jako drugi para metr jedną z makrodefmicj i dostępnych dla zasobów systemu Windows. W tym przypad ku będzie to makrodefinicja ID I APPLICATION, które określa domyślną ikonę aplikacji. Tabela 2.2 prezentuje makrodefmicje dla niektórych ikon oferowanych przez system. Tabela 2.2. Typy ikon Wartość
Opis
IDIAPPLICATION
Domyślna ikona aplikacji
IDI_ASTERISK
Ikona w kształcie gwiazdki
IDIJRROR
Ikona w kształcie dłoni
IDIJXCLAMATION
Wykrzyknik
IDI_HAND
Ikona w kształcie dłoni
IDIJNFORMATION
Ikona w kształcie gwiazdki
IDI_QUESTION
Znak zapytania
IDI_WARNING
Wykrzyknik
IDI_WINLOGO
Logo systemu Windows
Aby związać ikonę z definiowaną klasą okna, trzeba wykonać poniższą operację: w indow C lass.hlcon - LoadlcorKNULL, IDI_APPLICATION);
W ten sposób określone zostało to, że główną ikoną klasy okien będzie domyślna ikona aplikacji. Małą ikonę okien określa się w ten sam sposób: windowClass.hlconSm = LoadIcon(NULL, IDI_WINLOGO);
Tym razem będzie nią logo systemu Windows.
Rozdział 2. ♦ Korzystanie z OpenGL w system ie W indows
47
Załadowanie kursora myszy odbywa się w podobny sposób do załadowania ikon. Wy korzystuje się w tym celu funkcję LoadC ursor( ) zdefiniowaną następująco: HCURSOR LoadCursor(HINSTANCE hlnst, LPCSTR IpszName);
Ładuje ona kursor określony przez łańcuch IpszName i zwraca uchwyt kursora. Podobnie jak w przypadku funkcji LoadlconO pierwszy parametr funkcji LoadC ursor( ) określa uchwyt modułu, który zawiera mapę kursora. Jeśli parametrowi hlnst nadana zostanie wartość NULL, to można będzie skorzystać z jednego z kursorów zdefiniowanych w sys temie Windows. Aby kursor miał standardową postać strzałki, należy skorzystać z makrodefinicji IDC ARROW. Inne typy dostępnych kursorów prezentuje tabela 2.3. Tabela 2.3. Typy kursorów Wartość
Opis
IDC_APPSTARTING
Standardowa strzałka z małą klepsydrą
IDC_ARR0W
Standardowa strzałka
IDC_CR0SS
Celownik
IDC_HELP
Strzałka ze znakiem zapytania
IDCJBEAM
Kursor tekstowy
IDC_N0
Przekreślony okrąg
IDC_SIZEALL
Poczwórna strzałka
IDC_SIZENESW
Podwójna strzałka wskazująca umowne kierunki: północno-wschodni i południowo-zachodni
IDC_SIZENS
Podwójna strzałka wskazująca umowne kierunki: północny i południowy
IDC_SIZENWSE
Podwójna strzałka wskazująca umowne kierunki: północno-zachodni i południowo-wschodni
IDC_SIZEWE
Podwójna strzałka wskazująca umowne kierunki: zachodni i wschodni
IDC_UPARROW
Pionowa strzałka
IDC_WAIT
Klepsydra
Aby związać z daną klasą okien określony kursor, należy więc wykonać poniższą operację: windowClass .hC ursor = LoadCursor(NULL, IDC_ARR0W);
W ten sposób okna tej klasy korzystać będą ze standardowego kursora w postaci strzał ki. Jako że omówione w ten sposób zostały najważniejsze atrybuty definicji klasy okien, można przyjrzeć się teraz sposobowi, w jaki tworzy się i rejestruje definicję klasy: WNDCLASSEX windowClass; w indow C lass.cbS ize = sizeof(WNDCLASSEX); w in d o w C la s s .s ty le = CS_HREDRAW | CS_VREDRAW;
// // // // ' //
w indow C lass.lpfnW ndP roc = WndProc; w ind ow C lass.cb C lsE xtra = 0; windowClass.cbW ndExtra = 0; w indo w C la ss.h ln sta n ce = h ln s ta n c e ; // w indo w C lass.h lco n = NULL; // w indow C lass.hC ursor = LoadCursor(NULLf IDC_ARR0W); / / w indow C lass.hbrBackground = NULL; // windowClass.lpszMenuName = NULL; //
k la sa okna rozm iar o p is u ją c e j ją s tru k tu ry odrysowam e na skutek zmiany rozmiarów adres procedury okienkow ej
in s ta n c ja a p lik a c ji bez ikony standardowa s trz a łk a bez pędzla t ł a bez menu
48
Część I ♦ W prow adzenie do OpenGL i D irectX
windowClass.lpszClassName = "M o ja K la sa "; windowClass.hlconSm = NULL;
/ / nazwa k la s y okien / / bez małych iko n
R egisterC lassEx(& w indow C lass)
/ / r e je s t r u je k la sę
Całkiem proste, prawda?
Rejestracja klasy Po zdefiniowaniu klasy okien należy ją zarejestrować korzystając z funkcji R egisterC lassE x( ) o następującym prototypie: ATOM RegisterClassEx(CONST WNDCLASSEX *lpW C lass);
Wywołać ją można w poniższy sposób: Regi sterC lassE x(& w i ndowClass)
Należy zwrócić uwagę na to, że funkcja ta umożliwia zarejestrowanie klasy okna tylko na podstawie definicji za pomocą struktury WNDCLASSEX, a nie wykorzystywanej wcześniej struktury WNDCLASS. Po zarejestrowaniu klasy okien można stworzyć już pierwsze okno.
Tworzenia okna Okna systemu Windows tworzy się za pomocą funkcji CreateWindow( ) lub CreateWindowE x( ). W programach zaprezentowanych w tej książce wykorzystywana będzie funkcja CreateWindowEx(), ponieważ stanowi ona rozszerzoną wersję funkcji tworzenia okien i korzysta w pełni z klas okien. Trzeba więc przyjrzeć się prototypowi funkcji CreateWindowEx( ): HWND CreateWindowEx( DWORD dwExStyle, LPCTSTR IpClassName, LPCTSTR 1pWindowName, DWORD dwStyle, in t i n t y, in t nNidth, in t nHeight, HWND hNndParent, HMENU hMenu, HINSTANCE hlnstance, LPV0ID IpParam);
II II II II
// II II
// II
// // //
rozszerzony s ty l okna wskaźnik do nazwy z a re je s tro w a n e j k la s y w skaźnik do nazwy okna s ty l okna pozycja okna w poziom ie pozycja okna w p io n ie szerokość okna wysokość okna uchwyt okna nadrzędnego uchwyt menu lu b id e n ty fik a to r okna podrzędnego uchwyt in s ta n c ji a p lik a c ji wskaźnik do danych związanych z tw orzeniem okna
Komentarze po prawej stronie parametrów wyjaśniają ich znaczenie. Szerszego omó wienia wymagają następujące parametry: ♦ IpClassName — parametr ten określa nazwę klasy okna, które ma zostać utworzone (na przykład aby utworzyć okno klasy zarejestrowanej w poprzednim przykładzie, będzie musiał mieć wartość "M ojaK lasa "); ♦ 1pNi ndowName — określa tekst, który pojawi się na belce okna aplikacji (na przykład "Moja pierw sza a p lik a c ja Windows"); ♦ dwStyle — zawiera znaczniki określające sposób wyglądu i zachowania się okna; tabela 2.4 prezentuje dostępne znaczniki.
Rozdział 2 . ♦ Korzystanie z OpenGL w system ie W indows
49
Tabela 2.4. Znaczniki stylu okna Styl
Opis
WS_B0RDER
Okno będzie posiadać cienką ramkę
WS_CAPTION
Okno będzie posiadać belkę tytułową i cienką linię graniczną (zawiera styl WS_B0RDER)
WS_CHILD
Okno podrzędne (styl ten nie może być łączony ze stylem WS POPUP)
WS_HSCROLL
Okno będzie posiadać pasek przewijania w poziomie
WS_ICONIC
Okno będzie po utworzeniu zwinięte do ikony (równoznaczny ze stylem WS_MINIMIZE)
WS_MAXIMIZE
Okno będzie po utworzeniu zajmować cały ekran
WS_MAXIMIZEBOX
Okno będzie zawierać przycisk umożliwiający powiększenie go na cały ekran (styl ten nie może być łączony ze stylem WS_EX_CONTEXTHELP; dla okna tego musi być także określony styl WS SYSMENU)
WS_MINIMIZE
Okno będzie po utworzeniu zwinięte do ikony (równoznaczny ze stylem WS_ICONIC)
WS_MINIMIZEBOX
Okno będzie zawierać przycisk umożliwiający zwinięcie go do ikony (styl ten nie może być łączony ze stylem WS EX CONTEXTHELP; dla okna tego musi być także określony styl WS_SYSMENU)
WS_OVERLAPPED
Okno nakładające się, posiadające belkę tytułową i ramkę (równoważny stylowi WS_TILED)
WS_OVERLAPPEDWINDOW
Okno nakładające się i łączące style WS OVERLAPPED, WS CAPTION, WS SYSMENU, WS THICKFRAME, WS MINIMIZEBOX i WS MAX I MI ZEB0X (równoważny stylowi WS_TILEDWINDOW)
WS_P0PUP
Okno „wyskakujące” (styl ten nie może być łączony ze stylem WS CHILD)
WS_P0PUPWINDÓW
Okno „wyskakujące” łączące style WS_B0RDER, WS_P0PUP i WS_SYSMENU (aby menu okna było widoczne, styl WS_P0PUPWIND0W musi być wykorzystywany w połączeniu ze stylem WS_CAPTION)
WS_SIZEBOX
Okno będzie posiadać ramkę umożliwiającą zmianę jego rozmiarów (równoznaczne ze stylem WS_THICKFRAME)
WS_SYSMENU
Okno będzie posiadać menu okna otwierane na belce tytułowej (dla okna tego musi być także określony styl WS_CAPTION)
WS_VISIBLE
Okno będzie widoczne
WSJ/SCROLL
Okno będzie posiadać pasek przewijania w pionie
Funkcja CreateW indowEx( ) zwraca wartość NULLJeśli utworzenie okna nie powiodło się. W przeciwnym razie zwraca uchwyt utworzonego okna. Poniżej zaprezentowany został przykład wywołania funkcji CreateWindowEx(): hwnd = CreateWindowEx(NULL, / / n ie korzystam y zrozszerzonego s ty lu "M ojaK la sa ", / / nazwa k la s y okna "Moja pierw sza a p lik a c ja Windows", / / t y t u ł okna WS_OVERLAPPEDWINDOW | / / s ty l okna WS_CLI PSIBLINGS | WS_CLIPCHILDREN, 0,0, / / współrzędne x, y 200, 200 / / szerokość i wysokość okna
50
Część I ♦ W prow adzenie do OpenGL i D irectX
NULL. NULL, h ln s ta n c e , NULL);
// // // //
n ie is t n ie je okno nadrzędne okno n ie będzie posiadać menu in s ta n c ja a p lik a c ji brak dodatkowych parametrów okna
Wywołanie to utworzy okno o rozmiarach 200 na 200 pikseli, które będzie umieszczone w gómym-lewym narożniku ekranu. Okno to nie będzie jednak widoczne zaraz po utwo rzeniu. Można temu zaradzić dodając do stylu okna wartość WS_VISIBLE lub korzystając z funkcji ShowWindow() w poniższy sposób: ShowWindow(hwnd, nCmdShow);
Zmienna nCmdShow pochodzi w tym przypadku z wywołania funkcji WinMainO. Należy jeszcze wymusić na systemie Windows odrysowanie zawartości okna poprzez wysłanie komunikatu WM PAINT do procedury okienkowej. W tym celu wywołuje się funkcję UpdateWindow();
W ten sposób wyświetlone zostaje okno aplikacji. Można więc przejść do omówienia pętli przetwarzania komunikatów, która połączy omówione dotąd elementy aplikacji systemu Windows w jedną całość.
Pętla przetwarzania komunikatów Aplikacja komunikuje się z systemem Windows pobierając komunikaty z kolejki, które system umieszcza w niej informując o wystąpieniu zdarzeń. Komunikaty te muszą być pobierane przez, aplikacje w pętli przetwarzania komunikatów wykonywanej wewnątrz funkcji WinMainO. Pętla ta pobiera komunikat z kolejki, a następnie przekazuje go z po wrotem do systemu, który przetwarza komunikat, zanim przekaże go procedurze okien kowej aplikacji. Jak pokazano to na rysunku 2.3, pętla przetwarzania komunikatów wy konuje zawsze dla pobranego komunikatu trzy funkcje: PeekMessage(), TranslateM essage() i DispatchMessageO. Rysunek 2.3. Pętla przetwarzania komunikatów
Funkcja PeekMessageO umożliwia sprawdzenie istnienia komunikatu oczekującego w ko lejce. Jej prototyp przedstawia się następująco: B00L PeekMessage(LPMSG lpMsg, HWND hk/nd, UINT wMsgFi IterMin, UINT wMsgFi lterMax, Ul NT wRemoveMsg);
Rozdział 2 . ♦ Korzystanie z OpenGL w system ie Windows
51
Jeśli w kolejce istniał komunikat, to został z niej pobrany i przekazany za pośrednictwem parametru IpMsg. Przekazanie za pomocą parametru hMnd wartości NULL oznacza, że funk cja PeekMessageO powinna sprawdzić kolejkę komunikatów bieżącej aplikacji. Również parametry wMsgFi IterMin i wMsgFi IterMax będą miały w analizowanych zastosowaniach wartość NULL. Ostatniemu z parametrów — wRemoveMsg — należy nadać wartość PM_ REMOVE, która informuje funkcję PeekMessageO, że należy usunąć komunikat z kolejki po jego przetworzeniu. Dostępna je s t także funkcja GetMessageC ), która wykonuje identyczne działania co funkcja PeekMessageO. Jednak wstrzymuje ona wykonanie aplikacji w przypadku, gdy kolejka kom unikatów je s t pusta (aż do m om entu, w którym w kolejce pojawi się no wy kom unikat). W analizowanych w książce przykładach wykorzystana została funkcja PeekMessageO, która pozwala kontynuować działanie aplikacji, gdy kolejka komuni katów je s t pusta. Jest to szczególnie istotne w przypadku gier.
Funkcja TranslateM essageO wykorzystywana jest do tłumaczenia komunikatów o wy braniu wirtualnych klawiszy na komunikaty o znakach. Są one umieszczane z powrotem w kolejce danej aplikacji i pobierane przy następnym wywołaniu funkcji PeekMessageO. Prototyp funkcji Trans! ateM essage( ) wygląda następująco: BOOL Trans!ateMessage(CONST MSG *lpMsg );
Parametr IpMsg reprezentuje tłumaczony komunikat. Ostatnią z funkcji wywoływanych w pętli przetwarzania komunikatów jest D ispatchMessageO. Powoduje ona przekazanie komunikatu, który jest jej jedynym parametrem, z powrotem do systemu Windows, który przetwarza go i przekazuje do procedury okien kowej aplikacji. Prototyp funkcji DispatchM essageO zaprezentowany został poniżej. LONG DispatchMessage(CONST MSG *lpMsg );
Pętla przetwarzania komunikatów kończy swoje działanie, gdy zajdzie określony przez programistę warunek. Zwykle jest nim pobranie z kolejki komunikatu WM_QUIT. Funkcja Wi nMai n ( ) kończąc swoje działanie przekazuje wtedy systemowi wartość msg. wParam. A oto przykład pętli przetwarzania komunikatów: / / p ę tla p rze tw a rz a n ia komunikatów / / kończy d z ia ła n ie po o trzym aniu kom unikatu WM_QUIT w h ile ( ! done)
{ i f (PeekMessage(&msg, NULL, 0. 0, PM_REM0VE)) / / komunikat w k o le jc e ?
{ i f (msg.message == WM_QUIT) { done = TRUE;
/ / po o trzym aniu komunikatu WM_QUIT / / a p lik a c ja kończy d z ia ła n ie
} e ls e
{ / / p rze tw a rz a n ie TranslateM essage(&m sg); DispatchMessage(&msg);
/ / tłu m a cze n ie komunikatu / / przekazanie komunikatu do Windows
52
Część I ♦ W prow adzenie do OpenGL i D irectX
} }
re tu rn msg.wParam;
/ / zwraca kod zakończenia
Na początku pętli wywoływana jest funkcja PeekMessage() w celu sprawdzenia obecności komunikatu w kolejce i pobrania go. Jeśli w kolejce znajduje się komunikat i nie infor muje on o zakończeniu pracy aplikacji, to podejmowane jest jego przetwarzanie w pętli. Pętla przetwarzania komunikatów stanowi prosty i efektywny zarazem sposób obsługi potencjalnie dużej liczby komunikatów, które system może wysłać do aplikacji.
Kompletna aplikacja systemu Windows Jako że wszystkie elementy tworzące szkielet aplikacji systemu Windows są znane, można stworzyć już kompletny, działający program. Poniżej zaprezentowany został kod źró dłowy aplikacji systemu Windows, która wyświetla tekst powitania niebieską czcionką na białym tle okna. # d e fin e WIN32_LEAN_AND_MEAN
/ / "odchudza" a p lik a c ję Windows
# in c lu d e
/ / standardowy p lik nagłówkowy Windows
'/ / procedura okienkowa LRESULT CALLBACK WndProc(HWND hwnd, UlNT message, WPARAM wParam, LPARAM 1Param)
{
PAINTSTRUCT p a in tS tru c t; HDC hDC; char s t r in g [ ] = "W ita m y!";
/ / k o n te k s t urządzenia / / w yśw ie tla n y te k s t
sw itch(m essage)
{ case WM_CREATE: re tu rn 0; break;
/ / okno je s t tw orzone
case WMCLOSE: P ostQ uitM essage(0); re tu rn 0; break;
/ / okno je s t zamykane
case WM_PAINT: / / zawartość okna musi hDC = B eginP aint(hw nd, & p a in tS tr u c t) ;
być odrysowana
// wybiera k o lo r n ie b ie sk i Se tT e xtC olor( hDC. COLORREF( 0x00FF0000));
/ / w y ś w ie tla te k s t w środku okna TextOut(hDC, 150, 150, s tr in g , s iz e o f ( s t r in g ) - l) ; EndPaint(hwnd, S p a in tS tr u c t) ; re tu rn 0; break; d e fa u lt: break;
Rozdział 2 . ♦ Korzystanie z OpenGL w system ie W indows
53
re tu rn (DefWindowProc(hwnd, message, wParam, IP aram ));
/ / punkt rozp o czę cia wykonywania a p lik a c ji i n t WINAPI W inMain( HINSTANCE h ln s ta n c e , HINSTANCE hP re vIn sta n ce , LPSTR lpCmdLine, i n t nShowCmd)
{ WNDCLASSEX w indowClass; HWND hwnd; MSG msg; bool done;
// // // //
kla sa okna uchwyt okna kom unikat znacznik zakończenia a p lik a c ji
/ / tw o rzy s tr u k tu r ę d e fin iu ją c ą k la sę okna w indow C lass.cbS ize = sizeof(WNDCLASSEX); w in d o w C la s s .s ty le = CS_HREDRAW | CS_VREDRAW; w indowC lass.IpfnW ndProc = WndProc; w indow C lass.cb C lsE xtra = 0; windowClass.cbW ndExtra = 0; w in d o w C la ss.h ln sta n ce = h ln s ta n c e ; w indow C lass.hlcon = LoadIcon(NULL, IDI_APPLICATION); / / domyślna ikona w indow C lass,hC ursor= LoadCursor(NULL, IDC_ARR0W); / / domyślny k u rs o r w indowC lass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); / / b ia łe t ł o windowClass.lpszMenuName = NULL; / / bez menu windowClass.lpszClassNam e = "M o ja K la sa "; w indowClass.hlconSm = LoadIcon(NULL, IDI_WINL0G0); / / logo Windows ja k o mała ikona / / r e je s t r u je k la sę okna i f ( !R e g iste rC la ssE x(& w in d o w C la ss)) re tu rn 0; / / tw o rzy okno hwnd = CreateWindowEx(NULL. / / bez rozszerzonego s ty lu "M o ja K la s a ", / / nazwa k la s y "Prawdziwa a p lik a c ja W indows!", / / nazwa a p lik a c ji WS OVERLAPPEDWINDOW | WS VISIBLE | WS_SYSMENU, / / s ty l okna / / w spółrzędne x ,y 100, 100, 400, 400, / / szerokość, wysokość NULL, / / uchwyt okna nadrzędnego NULL, / / uchwyt menu h ln s ta n c e , / / in s ta n c ja a p lik a c ji / / bez dodatkowych parametrów NULL); / / sprawdza, czy u tw o rze n ie okna n ie pow iodło s ię (wtedy hwnd będzie NULL) i f ( ! hwnd) re tu rn 0; done = fa ls e ;
/ / in ic ju je zmienną warunku p ę t li
/ / p ę tla p rze tw a rz a n ia komunikatów w h ile ( !done)
{ PeekMessage(&msg, hwnd, NULL, NULL, PM_REM0VE); i f (msg.message == WM_QUIT)
/ / czy a p lik a c ja o trzym ała kom unikat WM_QUIT?
{ done = tr u e ;
} e ls e
/ / je ś l i ta k , to musi zakończyć d z ia ła n ie
54
Część I ♦ W prow adzenie do OpenGL i D irectX
{
TranslateM essage(& m sg); DispatchM essage(&msg);
/ / przetw arza kom unikat
} } re tu rn msg.wParam;
} Teraz należy przyjrzeć się bliżej kodowi aplikacji. Pierwszy jego wiersz w postaci # d e fin e WIN32_LEAN_AND_MEAN zapobiega dołączeniu do kodu wynikowego aplikacji zbędnych modułów, które nie zostaną wykorzystane. Kolejny wiersz # i ncl ude jest dyrektywą kompilatora włączającą pliki na główkowe, które są niezbędne do poprawnej kompilacji aplikacji Windows. W innych przypadkach można też dołączyć plik nagłówkowy , który zawiera makrodefinicje użyteczne podczas tworzenia kodu źródłowego aplikacji systemu Windows. Pierwszą funkcją w kodzie programu jest procedura okienkowa W ndProc( ). Dla uprosz czenia wykorzystana została jej wersja, która była już wcześniej omówiona (patrz: pod rozdział „Procedura okienkowa”), a teraz została rozbudowana o kilka elementów. Pierw szy z nich tworzą poniższe wiersze kodu: HDC hDC; char s t r in g [ ] = "W ita m y!";
i i k o n te k st urządzenia i i w yśw ie tla n y te k s t
Deklarują one kontekst urządzenia wykorzystywany do wyświetlania w obszarze okna oraz tekst wyświetlanego powitania. Kontekst urządzenia Kontekst urządzenia je s t strukturą danych wykorzystywaną podczas tworzenia grafiki w oknie lub na drukarce. Zawartość okna można modyfikować jedynie korzystając z kontekstu urządzenia, który można utworzyć za pomocą funkcji GetDC(): hDC = GetDC(hwnd);
Wywołanie to zwraca kontekst urządzenia służący do tworzenia grafiki w oknie o uchwycie okre ślonym przez param etr hwnd.
Następnie w kodzie funkcji WndProc pojawia się instrukcja wyboru switch rozpoznająca typ komunikatu przekazanego do procedury okienkowej. Nowym elementem jest blok obsługi komunikatu WM PAINT: case W M _P A IN T : / / zawartość okna musi być odrysowana hDC = B eginP aint(hw nd. & p a in tS tr u c t) ; / / w ybiera k o lo r n ie b ie s k i S etTextC olor(hD C . COLORREF( 0x00FF0000) ) ; / / w y ś w ie tla te k s t w środku okna TextOut(hDC. 150, 150, s tr in g , s iz e o f ( s t r in g ) - l ) ; Er,dPaint(hwnd, & p a in tS tr u c t) ; re tu rn 0;
Rozdział 2. ♦ Korzystanie z OpenGL w system ie W indows
55
Blok ten wykonywany jest, gdy zachodzi konieczność odrysowania zawartości okna na skutek jego przesunięcia, zmiany rozmiaru bądź innego zdarzenia, które miało wpływ na zawartość okna. Blok ten wykorzystuje kontekst urządzenia hDC zwrócony przez funkcję B e g in P a in tC ) dla okna o uchwycie hwnd. Korzystając z kontekstu urządzenia określa się za pomocą funkcji S e tT e x tC o lo r( ) kolor tekstu wyświetlanego później za pomocą funkcji T e x tO u t( ). Funkcje te nie zostaną teraz bliżej omówione. Jeśli czytelnik pragnie uzyskać więcej informacji na ich temat, to powinien skorzystać z dokumentacji interfejsu programowego systemu Windows, która jest dostępna w MSDN. MSDrN Microsoft Developer Network (w skrócie MSDN) zawiera dokum entację narzędzi programistycz nych firmy Microsoft. Kopia MSDN dołączana je s t do pakietu Visual Studio, jak i jego składowych. MSDN dostępny je s t także w sieci Internet pod adresem http://msdn.nicrosoft.com.
Kolejną funkcją zdefiniowaną w kodzie źródłowym analizowanej aplikacji jest Wi nMai n ( ). Stanowi ona punkt, w którym rozpoczyna się wykonywanie aplikacji. Kod jej jest dość oczywisty, ale warto zwrócić uwagę na kilka elementów. Zmienna msg wykorzystywana jest do przechowania komunikatu pobranego za pomocą funkcji PeekMessage(), który następnie przekazywany jest funkcją TranslateM essage( ) i DispatchM essage( ). Zmienna done stanowi warunek wykonania pętli przetwarzania komunikatów i uzyskuje wartość tr u e po otrzymaniu komunikatu WM_QUIT. Należy zwrócić uwagę na porządek, w jakim wykonywane są kolejne operacje niezbędne do działania aplikacji systemu Windows. Można wyróżnić wśród nich: ♦ definicję klasy okna; ♦ rejestrację klasy okna; ♦ utworzenie okna; ♦ pętlę przetwarzania komunikatów i procedurę okienkową. W ten sposób uzyskany został szkielet aplikacji systemu Windows, który można dopa sowywać do indywidualnych potrzeb. Kolejny krok jego rozwoju będzie polegał na umoż liwieniu aplikacji korzystania z biblioteki OpenGL.
Wprowadzenie do funkcji WGL Ponieważ OpenGL jest jedynie interfejsem graficznym, to takie zadania jak na przykład interakcja z użytkownikiem czy tworzenie okien aplikacji muszą być obsługiwane przez system operacyjny. Aby ułatwić pracę programisty, dla każdego systemu operacyjnego z reguły dostarczany jest zbiór rozszerzeń umożliwiających powiązanie OpenGL z inte rakcją i tworzeniem okien na danej platformie. W systemie UNIX rozszerzenie takie nosi nazwę GLX. Natomiast w systemie Windows korzysta się ze zbioru funkcji WGL. Nazwy funkcji należących do tego zbioru rozpoczynają się przedrostkiem wgl. Również interfejs programowy systemu Windows posiada funkcje umożliwiające wykorzystanie OpenGL.
56
Część I ♦ W prow adzenie do OpenGL i D irectX
Kontekst tworzenia grafiki Aby zachować możliwość przenoszenia aplikacji OpenGL pomiędzy różnymi platfor mami, każdy system operacyjny musi dostarczyć kontekst tworzenia grafiki OpenGL. Jak pokazano wcześniej, aplikacje systemu Windows korzystają z kontekstu urządzenia, który przechowuje informacje o sposobie tworzenia grafiki, natomiast OpenGL korzysta z własnego kontekstu tworzenia grafiki spełniającego podobne funkcje. Kontekst urzą dzenia i kontekst tworzenia grafiki nie mają ze sobą nic wspólnego. Pierwszy z nich po daje się wywołując funkcje graficzne systemu Windows, a drugi jest domyślnie wyko rzystywany przez komendy OpenGL. Dlatego też zanim utworzony zostanie kontekst tworzenia grafiki, trzeba najpierw określić format piksela dla kontekstu urządzenia. Istnieje możliwość korzystania z wielu kontekstów tworzenia grafiki, jeśli tworzy się ją na przykład równocześnie w wielu oknach. Należy jedynie zawsze zapewnić to, że bie żący kontekst tworzenia grafiki będzie właściwym kontekstem dla okna, w którym two rzy się ją. Ważną cechą OpenGL jest możliwość bezpiecznego wykonywania jego pole ceń przez różne wątki. Wiele wątków może tworzyć grafikę korzystając z tego samego kontekstu. Na przykład jeden z wątków może tworzyć grafikę wirtualnego świata gry, a inny elementy interfejsu użytkownika. Jeszcze inny wątek może równocześnie tworzyć grafikę pokazującą inne ujęcie świata gry w osobnym oknie. Podobnych możliwości jest bez liku.
Korzystanie z funkcji WGL Funkcje WGL umożliwiają działanie OpenGL w systemie Windows. Przedstawione te raz zostaną trzy najczęściej wykorzystywane funkcje WGL: ♦ w g lC re a te C o n te x t(); ♦ w g lD e le te C o n te x t(); ♦ w glM akeC urrentO .
Funkcja wglCreateContext() Funkcja w g lC re a te C o n te x t( ) tworzy uchwyt kontekstu tworzenia grafiki OpenGL na podstawie przekazanego jej kontekstu urządzenia systemu Windows. Jej prototyp pre zentuje się następująco: HGLRC wglCreateContext(HDC hDC)
Funkcję tę można wywoływać dopiero po określeniu formatu pikseli dla danego kon tekstu urządzenia (format pikseli zostanie omówiony niebawem.)
Funkcja wglDeleteContext() Podobnie jak w przypadku kontekstu urządzenia także i dla kontekstu tworzenia grafiki powinniśmy pamiętać o jego usunięcie, gdy przestajemy go używać. W tym celu wy wołujemy funkcję wgl Del e teC ontext () o przedstawionym poniżej prototypie: BOOL wglDeleteContext(HGLRC hRC):
Rozdział 2. ♦ Korzystanie z OpenGL w system ie W indows
57
Funkcja wglMakeCurrent() Funkcja wgl MakeCurrent () służy do wyboru bieżącego kontekstu tworzenia grafiki OpenGL. Przekazywany jej kontekst urządzenia musi posiadać taki sam format pikseli jak kon tekst urządzenia, który wykorzystany został do utworzenia danego kontekstu tworzenia grafiki. W praktyce oznacza to, że kontekst urządzenia przekazywany funkcji wglMakeCurrent() nie musi być tym samym kontekstem urządzenia, który został przekazany funkcji wglCreateContext(). Prototyp funkcji wglMakeCurrent() przedstawia się następująco: BOOL wglMakeCurrent(HDC hDC, HGLRC hRC);
Trzeba jednak zapewnić, że oba przekazywane jej konteksty korzystają z tego samego formatu pikseli. Jeślikonieczne okaże się zaprzestanie korzystania zbieżącego kontek stu tworzenia grafiki, należyprzekazać wartość NULLjako parametr hRC funkcji wglMakeCurrent() lub inny kontekst tworzenia grafiki. Funkcje wglCreateContext() i wglMakeCurrent() powinny zostać wywołane po utwo rzeniu okna, czyli wtedy, gdy procedura okienkowa otrzyma komunikat WM CREATE. Funk cja wglDeleteContext() powinna natomiast zostać wywołana podczas usuwania okna (na przykład na skutek otrzymania komunikatu WM DESTROY), jednak po uprzednim wywoła niu funkcji wglMakeCurrent() z wartością NULL parametru hRC. Powyższe uwagi ilustruje następujący fragment kodu: LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM 1Param)
{ s t a t ic HGLRC hRC; s t a t ic HDC hDC;
/ / k o n te k st tw o rze n ia g r a f ik i / / k o n te k st urządzenia
sw itch(m essage)
{ case WM_CREATE: hDC = GetDC(hwnd); hRC = w glC reateC ontext(hD wglMakeCurrent(hDC. hRC); break;
// // // //
okno je s t tworzone p o biera ko n te k st urządzenia d la danego okna tw o rzy k o n te k st tw o rze n ia g r a f ik i o k re ś la bieżący k o n te k s t tw o rze n ia g r a f ik i
case WMJ3ESTR0Y: wglMakeCurrent(hDC, NULL)
// // // // //
okno je s t usuwane p rz e s ta je k o rz y sta ć z bieżącego kontekstu tw o rze n ia g r a f ik i usuwa k o n te k st tw o rze n ia g r a f ik i w ysyła kom unikat WM_QUIT
w g lD e le te C o n te xt(h R C ); P ostQ uitM essage(0); bre a k; } / / kon ie c in s t r u k c ji sw itc h } / / kon ie c WndProc
Powyższy fragment kodu tworzy i usuwa okno grafiki OpenGL. Kontekst urządzenia i kontekst tworzenia grafiki przechowuje się za pomocą zmiennych statycznych, aby nie tworzyć ich przy każdym wywołaniu procedury okienkowej. W ten sposób czyni się jej wykonanie efektywniejszym. Pozostała część kodu jest dość oczywista i opatrzona ko mentarzami. Zanim jednak stworzone zostanie pierwsze okno aplikacji OpenGL, trzeba zapoznać się z formatami pikseli oraz strukturą PIXELFORMATDESCRIPTOR.
58
Część I ♦ W prow adzenie do OpenGL i D irectX
Formaty pikseli Format pikseli stanowi kolejne z rozszerzeń interfejsu programowego systemu Win dows, które umożliwia korzystanie z OpenGL. Określając format pikseli podaje się na przykład tryb koloru, bufor głębi, liczbę bitów opisujących piksel oraz sposób buforo wania zawartości okna. Format pikseli trzeba określić zawsze przed stworzeniem kon tekstu tworzenia grafiki. Aby zdefiniować charakterystykę i zachowania okna graficznego, trzeba posłużyć się strukturą PI XELFORMATDESCRIPTOR. Jest ona zdefiniowana następująco: ty p e d e f s tr u c t tagPIXELFORMATDESCRIPTOR
{
WORD nSize; WORD nV ersion; DWORD dwFlags; BYTE i P ixe l Type; BYTE c C o lo rB its ; BYTE cR edB its; BYTE c R e d S h ift; BYTE cG reenB its; BYTE c G re e n S h ift; BYTE c B lu e B its ; BYTE c B lu e S h ift; BYTE c A lp h a B its ; BYTE c A lp h a S h ift; BYTE cAccum Bits; BYTE cAccumRedBits; BYTE cAccumGreenBits; BYTE cAccum BlueBits; BYTE cAccum AlphaBits; BYTE cD e p th B its; BYTE c S te n c ilB its ; BYTE c A u xB u ffe rs; BYTE iLayerT ype; BYTE bReserved; DWORD dwLayerMask; DWORD dw VisibleM ask; DWORD dwDamageMask; } PIXELFORMATDESCRIPTOR;
// // // // // // // // // // // // // // // // // // // // // // // // // //
rozm iar s tr u k tu r y zawsze w artość 1 zn a cz n ik i w ła ściw o ści b u fo ra p ik s e li ty p danych p ik s e la lic z b a b itó w p ik s e la lic z b a b itó w k o lo ru czerwonego p rz e s u n ię c ie b itó w k o lo ru czerwonego lic z b a b itó w k o lo ru zie lo n e g o p rz e s u n ię c ie b itó w k o lo ru z ie lo n e g o lic z b a b itó w k o lo ru n ie b ie s k ie g o p rz e s u n ię c ie b itó w k o lo ru n ie b ie s k ie g o lic z b a b itó w a lfa p rz e s u n ię c ie b itó w a lfa lic z b a b itó w bu fo ra aku m u la cji lic z b a b itó w a kum ulacji c ze rw ie n i lic z b a b itó w a kum ulacji z ie le n i lic z b a b itó w a kum ulacji b łę k itu lic z b a b itó w a ku m u la cji a lfa lic z b a b itó w b u fo ra g łę b i lic z b a b itó w b u fo ra p o w ie la n ia lic z b a buforów pomocniczych n ie je s t ju ż w ykorzystywany lic z b a podkładanych i nakładanych je d n o ste k n ie je s t ju ż w ykorzystywany indeks podłożonej płaszczyzny n ie je s t ju ż w ykorzystywany
Teraz należy przejść do omówienia najważniejszych pól tej struktury.
Pole nSize Pole to opisuje rozmiar struktury i powinno zostać zainicjowane w poniższy sposób: p fd .n S iz e = sizeof(PIXELFORMATDESCRIPTOR);
Wymaganie to jest dość oczywiste i typowe w przypadku struktur przekazywanych za po średnictwem wskaźnika. Często podczas wykonywania różnych operacji potrzebna jest możliwość określenia wielkości pamięci zajmowanej przez strukturę, a umieszczenie tej wartości w pierwszym polu umożliwia szybkie jej pobranie przez dereferencję wskaźnika.
Rozdział 2 . ♦ Korzystanie z OpenGL w system ie W indows
59
Pole dwFlags Pole dwFlags określa właściwości bufora pikseli. W tabeli 2.5 zaprezentowane zostały najczęściej używane wartości znaczników określających te właściwości. Tabela 2.5. Wartości pola dwFlags Wartość
Znaczenie
PFD_DRAW_TO_WINDÓW
Bufor umożliwia tworzenie grafiki w oknie
PFD_SUPP0RT_0PENGL
Bufor umożliwia tworzenie grafiki OpenGL
PFD_D0UBLEBUFFER
Podwójne buforowanie (w obecnej implementacji znacznik ten oraz znacznik PFD SUPPORT GDI wykluczają się wzajemnie)
PFD_SWAP_LAYER_BUFFERS
Określa, że urządzenie może przełączać pojedyncze plany warstw z formatami pikseli o podwójnym buforowaniu (w przeciwnym razie wszystkie plany warstw przełączane są grupowo); jeśli znacznik jest ustawiony, to dostępna jest funkcja w glSw apLayerBuffers
PFD_DEPTH_D0NTCARE
Dla danego formatu pikseli może być — ale nie musi — wykorzystywany bufor głębi; aby utworzyć format pikseli, który nie korzysta z bufora głębi, należy koniecznie podać ten znacznik (w przeciwnym razie będzie wykorzystywany bufor głębi)
PFD_DOUBLEBUFFER_DONTCARE
Piksele mogą być buforowane pojedynczo lub podwójnie
Pole ¡PixelType Pole i P ixelT yp e określa typ danych piksela. Może ono przyjmować jedną z poniższych wartości: ♦ PFD TYPE RGBA— piksele opisane w standardzie RGBA (dla każdego piksela określona są: kolor czerwony, kolor zielony, kolor niebieski i współczynnik alfa); ♦ PFD TYPE COLORINDEX — piksele opisane są za pomocą wartości indeksu koloru.
W analizowanych zastosowaniach nadawana będzie polu i P ixe l Type zawsze wartość PFD TYPE RGBA, co pozwolą skorzystać ze standardowego modelu kolorów RGB rozsze rzonego o kanał alfa umożliwiający realizację efektu przezroczystości.
Pole cColorBits Pole c C o lo rB its opisuje liczbę bitów określających kolor piksela. Pole to może obecnie przyjmować wartości 8, 16, 24 i 32. Jeśli karta grafiki nie umożliwia reprezentacji koloru pikseli za pomocą żądanej liczby bitów, to przyjmowana jest największa możliwa war tość. Jeśli na przykład nadana zostanie polu c C o lo rB its wartość 24, a karta grafiki nie umożliwia opisu pikseli za pomocą 24 bitów, to przyjęta zostanie wartość 16. Po utworzeniu struktury PIXELFORMATDESCRIPTOR należy przekazać ją funkcji ChooseP ix e lF o rm a t (). Próbuje ona dopasować formaty pikseli dostępne dla danego kontekstu urządzenia do opisu przekazanego za pomocą struktury PI XELFORMATDESCRI PTOR. Funkcja ChoosePi xel Format () zwraca wartość całkowitą reprezentującą indeks dostępnego for matu pikseli. Indeks ten należy przekazać następnie funkcji SetPi xel Format ( ), która wybiera ostatecznie format pikseli dla kontekstu urządzenia.
60
Część I ♦ W prow adzenie do OpenGL i D irectX
Poniższy fragment kodu pokazuje przykład struktury PIXELFORMATDESCRIPTOR i wybór formatu pikseli: in t n P ix e lF o rm a t;
/ / indeks form atu p ik s e li
s t a t ic PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), / rozm iar s tr u k tu r y / w e rs ja , zawsze równa 1 1. PFD_DRAW_T0_WI NDOW | / g r a fik a w oknie PFD_SUPPORT_OPENGL | / g r a fik a OpenGL / podwójne buforow anie PFD_DOUBLEBUFFER, / tr y b kolorów RGBA PFD_TYPE_RGBA, / 3 2 -b ito w y o p is kolorów 32. / n i e s p e c y fik u je b itó w kolorów 0. 0. 0. 0. 0. 0, / bez b u fo ra a lfa 0. / n ie s p e c y fik u je b itu p rz e s u n ię c ia 0, / bez b u fo ra akum ulacji 0, / ig n o ru je b it y akum ulacji 0, 0. 0. 0. / 16-b ito w y b u fo r Z 16. / bez b u fo ra p o w ie la n ia 0. / bez buforów pomocniczych 0. / główna płaszczyzna rysowania PFD_MAIN_PLANE, / zarezerwowane 0, / ig n o ru je maski warstw 0 , 0 . 0 }; / / w ybiera n a jb a rd z ie j zgodny form at p ik s e li i zwraca je g o indeks nP ixelF orm at = ChoosePixelFormat(hDC, & p fd ); / / o k re ś la fo rm a t p ik s e li d la danego ko n te kstu urządzenia S etPixelForm at(hD C , nP ixe lF orm a t. & p fd );
We fragmencie tym zwraca uwagę duża liczba zer przypisywanych różnym polom struk tury PIXELFORMATDESCRIPTOR. Oznacza to, że w praktyce nie trzeba określać wartości więk szości pól, aby zdefiniować format pikseli. Pola te mogą okazać się potrzebne w niektó rych zastosowaniach, ale zwykle nadaje się im wartość 0.
Aplikacja OpenGL w systemie Windows Przedstawione dotąd informacje wystarczą do utworzenia podstawowego szkieletu apli kacji systemu Windows tworzącej grafikę OpenGL. Poniżej przedstawiony został kom pletny kod źródłowy programu, który wyświetla w oknie wirujący trójkąt w kolorze czer wonym na czarnym tle. # d e fin e WIN32_LEAN_AND_MEAN
/ / odchudza a p lik a c ję Windows
I I I I I I P lik i nagłówkowe # in c lu d e # in c lu d e < g l/g l.h > # in c lu d e < g l/g lu .h > # in c lu d e < g l/g la u x .h >
// // // //
/ / / / / / Zmienne g lo b a ln e f l o a t angle = O.Of; HDC g HDC;
/ / bieżący k ą t o b ro tu tr ó jk ą ta / / k o n te k s t urządzenia
standardowy p lik nagłówkowy Windows standard p lik nagłówkowy OpenGL p lik nagłówkowy dodatkowych b ib lio t e k OpenGL fu n k c je pomocnicze OpenGL
Rozdział 2 . ♦ Korzystanie z OpenGL w system ie W indows
/ / fu n k c ja o k re ś la ją c a fo rm a t p ik s e li d la k o n te kstu urządzenia v o id S e tu p P ix e lFormat(HDC hDC) i n t n P ix e lFormat ; s t a t ic PIXELFORMATDESCRIPTOR pfd s i z e o f( PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDÓW | PFD_SUPPORT_OPENGL | PFDJ30UBLEBUFEER, PFD_TYPE_RGBA, 32.
0, 0. 0, 0. 0, 0. 0. 0. 0.
0. 0. 0. 0. 16, 0, 0, PFD_MAIN_PLANE, 0.
0, 0, 0 };
/ / indeks form atu p ik s e li // // // // // // // // // // // // // // // // // //
rozm iar s tr u k tu r y domyślna w ersja g r a fik a w o kn ie g ra fik a OpenGL podwójne buforow anie tr y b kolorów RGBA 3 2 -b ito w y o p is kolorów n ie s p e c y fik u je b itó w kolorów bez bu fo ra a lfa ig n o ru je b i t p rze s u n ię c ia bez bufo ra akum ulacji ig n o ru je b it y akum ulacji 1 6 -b ito w y b u fo r Z bez b u fo ra p o w ie la n ia bez buforów pomocniczych główna płaszczyzna rysowania zarezerwowane ig n o ru je maski warstw
/ / w ybie ra n a jo d p o w ie d n ie js zy fo rm a t p ik s e li n P ix e lFormat = ChoosePixelFormatChDC, & p fd ); / / o k re ś la fo rm a t p ik s e li d la ko n te kstu urządzenia S etP ixelForm at(hD C , n P ixe lF orm a t, & p fd );
i / / procedura okienkowa LRESULT CALLBACK WndProc(HWND hwnd , UINT message, WPARAM wParam, LPARAM 1Pa ram)
{
s t a t ic HGLRC hRC; s t a t ic HDC hDC; i n t w id th , h e ig h t;
/ / k o n te k st tw o rze n ia g r a f ik i / / k o n te k s t urządzenia / / szerokość i wysokość okna
sw itch(m essage)
{
case WM_CREATE: hDC = GetDC(hwnd); g_HDC = hDC; S e tu p P ix e lForm at(hDC);
/ / okno je s t tworzone / / p o biera k o n te k s t urządzenia d la okna / / w yw ołuje fu n k c ję o k re ś la ją c ą form at p ik s e li
/ / tw o rzy k o n te k s t tw o rze n ia g r a f ik i i czyn i go bieżącym hRC = w glC re a te C o n te xt(h D C ); wglMakeCurrent(hDC, hRC); re tu rn 0; b re a k ; case WM_CL0SE:
/ / okno je s t zamykane
/ / dezaktyw uje b ie żą cy k o n te k st i usuwa go wglMakeCurrent(hDC, NULL); w g lD e le te C o n te xt(h R C );
61
62
Część I ♦ W prow adzenie do OpenGL i D irectX
/ / wstawia kom unikat WM_QUIT do k o le jk i P ostQ uitM essage(0); re tu rn 0; break; case WM_SIZE: h e ig h t = HIW0RD(1 Param); w id th = LOWORDO Param); i f (h e ig h t= = 0 )
/ / po b ie ra nową wysokość i szerokość okna
/ / unika d z ie le n ia przez 0
{ hei g h t = l;
/ / nadaje nowe wym iary oknu OpenGL g l V ie w p o rt(0, 0, w id th . h e ig h t); / / o k re ś la m acierz rzutow ania glMatrixMode(GL_PROJECTION); / / re s e tu je m acierz rzutow ania g lL o a d ld e n tity O ; / / wyznacza p ro p o rc je obrazu g lu P e rsp e ct i v e (4 5 .O f. ( GLf1o a t)wi d th /(G L fl o a t) hei g h t, 1 . O f.1 0 0 0 .O f); glMatrixMode(GL_MODELVIEW); / / o k re ś la m acierz widoku modelu g lL o a d ld e n tity O ; / / re s e tu je m acierz widoku modelu re tu rn 0; break; d e fa u lt: break;
re tu rn (DefWindowProc(hwnd, message, wParam, 1P aram ));
/ / p u nkt, w którym rozpoczyna s ię wykonywanie a p lik a c ji i n t WINAPI WinMain(HINSTANCE h ln s ta n c e . HINSTANCE h P re vIn sta n ce , LPSTR IpCmdLine. in t nShowCmd)
{ WNDCLASSEX windowClass; HWND hwnd; MSG msg; bool done;
// // // //
kla sa okien uchwyt okna kom unikat znacznik zakończenia a p lik a c ji
/ / d e fin ic ja k la s y okna window C lass.cbSize= sizeof(WNDCLASSEX); w in d o w C la s s.s ty le = CS_HREDRAW | CS_VREDRAW; wi ndowClass.1 pfnWndProc= WndProc; w indow C lass.cbC lsE xtra= 0; wirdowClass.cbW ndExtra= 0; wi • jwC Ia s s . hln sta n ce = h ln s ta n c e ; w i;.o w C la s s .h Ic o n = LoadIcon(NULL.IDI_APPLICATION); w" )wC lass.hC ursor= LoadCursor(NULL, IDC_ARR0W); wi *: jowC Ia s s . hbrBackground= NULL; w ir-io w C la ss.1 pszMenuName= NULL;
// // // //
domyślna ikona domyślny k u rs o r bez t ł a bez menu
Rozdział 2. ♦ Korzystanie z OpenGL w system ie W indows
wi ndowCla s s .1 pszClassName= "M ojaK lasa"; windowClass.h!conSm = LoadlconCNULL, IDI_WINL0G0);
/ / logo Windows
/ / r e je s t r u je k la sę okna i f ( !R egisterC lassE x(& w indow C lass)) re tu rn 0; / / tw o rzy okno hwnd = CreateWindowEx(NULL, "M o ja K la s a ", "A p lik a c ja OpenGL", WS_OVERLAPPEDWINDOW | W SJISIBLE | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLI PSIBLINGS,
/ / rozszerzony s ty l okna / / nazwa k la s y / / nazwa a p lik a c ji
U ii ii ii ii ii ii
100, 100,
400, 400, NULL, NULL, h ln s ta n c e , NULL);
s ty l w spółrzędne x, y szerokość i wysokość uchwyt okna nadrzędnego uchwyt menu in s ta n c ja a p lik a c ji bez dodatkowych parametrów
i i sprawdza, czy u tw o rze n ie okna n ie pow iodło s ię (wtedy hwnd ma w artość NULL)
i f ( !hwnd) re tu rn 0; ShowWindow(hwnd, SW_SH0W); UpdateW indow(hwnd);
/ / w yś w ie tla okno / / a k tu a liz u je okno
done = fa ls e ;
/ / in ic ju je zmienną warunku wykonania p ę t li
/ / p ę tla p rze tw a rz a n ia komunikatów w h ile ( !done)
{ PeekMessage(&msg. hwnd. NULL, NULL, PM_REM0VE); i f (msg.message == WM_QUIT)
/ / otrzymano komunikat WM_QUIT ?
{ done = tr u e ;
/ / j e ś l i ta k , to a p lik a c ja kończy d z ia ła n ie
} e l se
{ / / tw o rzy g r a fik ę / / z e ru je b u fo r ekranu i b u fo r g łę b i glClear(GL_COLOR_BUEFER_BIT | GL_DEPTH_BUEFER_BIT); g lL o a d ld e n t ity O ; / / re s e tu je m acierz widoku modelu an gle = angle + O . lf ; / / zwiększa lic z n ik kąta o b ro tu i f (a n g le >= 3 6 0 .Of) / / j e ś l i pełen o b ró t, to re s e tu je lic z n ik angle = O.Of; g lT r a n s la t e f ( 0 . 0 f , 0 . 0 f . - 5 . O f); / / p rze s u n ię c ie o 5 je d n o ste k wstecz g lR o ta te f(a n g le , O .O f.O .O f.l.O f); / / o b ró t dookoła o si Z g lC o lo r 3 f( 1 .0 f,O .O f,O .O f) ; gl B e g in (GL_TRIANGLES); g l V e r te x 3 f( 0 .0 f, O .O f, O.Of) g lV e rte x 3 f(1 .0 f,O .O f,O .O f) g lV e r t e x 3 f ( 1 . 0 f, 1 .0 f ,0 . 0 f) glE n d O ;
/ / w ybiera k o lo r czerwony / / ry s u je t r ó jk ą t
63
64
Część I ♦ W prow adzenie do OpenGL i D irectX
SwapBuffers(g_HDC);
/ / p rze łą cza b u fo ry
TranslateM essage(&m sg); DispatchM essage(&msg);
/ / tłum aczy kom unikat i w ysyła go do systemu
re tu rn msg.wParam;
} Większość kodu źródłowego tej aplikacji pochodzi z przykładu aplikacji systemu Win dows zamieszczonego wcześniej. Mimo że bardzo podobne, to jednak oba programy róż nią się kilkoma szczegółami. Już na początku kodu można zauważyć wprowadzenie dwu zmiennych globalnych: angle i g_HDC. / / / / / / Zmienne g lo b a ln e f lo a t angle = O.Of; HDC g_HDC;
/ / bieżący k ą t o b ro tu tr ó jk ą ta / / k o n te k s t urządzenia
Zmienna angle przechowuje bieżącą wartość kąta obrotu. Obroty omówione zostaną szczegółowo w rozdziale 7., teraz potraktowane zostaną jako przykład kodu OpenGL. Zmienna g_HDC przechowywać będzie uchwyt kontekstu urządzenia wykorzystywanego do tworzenia grafiki OpenGL. Kolejna różnica polega na wprowadzeniu definicji funkcji SetupPixel Format( ). Funkcja ta wypełnia pola struktury PIXELFORMATDESCRIPTOR i wybiera opisany w ten sposób format piksela dla przekazanego jej kontekstu urządzenia hDC. Wybór formatu pikseli odbywa się w opisany wcześniej sposób. Po zainicjowaniu struktury PIXELFORMATDESCRIPTOR wywoływana jest funkcja ChoosePixelFormate ), która ustala najodpowiedniejszy format pikseli. Zwrócony przez nią in deks formatu pikseli przekazuje się funkcji SetPixelFormate ), która dokonuje wyboru formatu pikseli dla kontekstu urządzenia. W ten sposób wszystko jest już gotowe do tworzenia grafiki. Teraz należy przyjrzeć się procedurze okienkowej WndProce). Jej kod zawiera między innymi wywołania funkcji tworzących okno grafiki OpenGL. Najpierw trzeba przeana lizować blok kodu, który jest wykonywany po otrzymaniu komunikatu WM_CREATE: case WM_CREATE: hDC = GetDC(hwnd); g_HDC = hDC; S e tu p P ix e lForm at(hDC);
/ / okno je s t tw orzone / / po b ie ra k o n te k s t urządzenia d la okna / / przechowuje k o n te k s t za pomocą zm iennej g lo b a ln e j / / w yw ołuje fu n k c ję o k re ś la ją c ą fo rm a t p ik s e li
/ / tw o rzy k o n te k s t tw o rze n ia g r a f ik i i czyni go bieżącym hRC = w g lC re a te C o n te xt(h D C ); wglMakeCurrent(hDC, hRC); re tu rn 0; b re ak ;
Rozdział 2 . ♦ Korzystanie z OpenGL w system ie W indows
65
Komunikat WM CREATE zostaje przekazany procedurze okienkowej, gdy tworzone jest okno aplikacji. Jest to najlepszy moment, by zainicjować też okno OpenGL. W tym celu trzeba najpierw pobrać kontekst urządzenia dla tworzonego okna korzystając z funkcji GetDCO. Kontekst ten przechowuje się w zmiennej globalnej g_HDC, ponieważ będzie jeszcze wykorzystywany do przełączania buforów okna (wybrany wcześniej format pik seli zapewnia podwójne buforowanie). Następnie wywołuje się omówioną już funkcję SetupPixel Format O. * Kolejne dwa wiersze bloku obsługi komunikatu WM CREATE tworzą kontekst tworzenia grafiki okna OpenGL. Funkcja wglCreateContext() tworzy kontekst, a funkcja wglMakeC urrent() wybiera go jako kontekst bieżący. W ten sposób utworzone zostało okno grafiki OpenGL. Teraz należy przyjrzeć się blokom obsługi okna i tworzenia grafiki. Kolejny blok kodu procedury okienkowej obsługuje komunikat WM SIZE: case WM_SIZE: h e ig h t = HIW0RD(1Param); w id th = LOWORDO Param); i f (h e ig h t= = 0 )
{
/ / pob ie ra nową wysokość i szerokość okna
/ / zapobiega d z ie le n iu przez 0
h e ig h t= l;
} / / nadaje nowe wym iary oknu OpenGL g l V ie w p o rt(0 . 0, w id th , h e ig h t); / / o k re ś la m acierz rzutow ania glMatrixMode(GL_PROJECTION); / / re s e tu je m acierz rzutow ania g lL o a d ld e n t ity O ; / / wyznacza p ro p o rc je obrazu g lu P e rs p e c tiv e (4 5 . O f,( G L flo a t)w id th /( G L flo a t) h e ig h t, 1 . O f,1 0 0 0 .O f) ; glMatrixMode(GL_MODELVIEW); / / o k re ś la m acierz widoku modelu g lL o a d ld e n tity O ; / / re s e tu je m acierz widoku modelu r e tu rn 0; bre ak;
Komunikat WM SIZE przekazywany jest procedurze okienkowej na skutek zdarzenia zmia ny rozmiarów okna. Trzeba wtedy odpowiednio zmienić także rozmiary okna OpenGL. Korzystając z parametru 1Param przekazanego procedurze okienkowej należy ustalić najpierw nowe rozmiary okna. Parametr ten jest typu LPARAM i posiada 32-bitową repre zentację. W przypadku komunikatu WM SIZE zawiera on dwie wartości 16-bitowe okre ślające nową szerokość i wysokość okna. Do ich pobrania wykorzystuje się makrodefinicje LOWORD i HIWORD zdefiniowane jak poniżej: WORD LOWORD(DWORD dwValue) ; / / zwraca młodsze słowo 16-bitow e WORD HIWORD(DWORD dwValue); / / zwraca s ta rs z e słowo 16-bitow e
66
Część I ♦ W prow adzenie do OpenGL i D irectX
Młodsze słowo reprezentuje szerokość okna, a starsze jego wysokość. Po uzyskaniu tych wartości trzeba ustalić sposób przeskalowania okna OpenGL. Najpierw należy zabezpieczyć się jednak przed możliwością wykonania dzielenia przez zero. Błąd taki mógłby wystąpić podczas określania rzutowania perspektywy i dlatego trzeba upewnić się, że nowa wysokość okna jest różna od zero. Funkcja gl Viewport O przeskalowuje okno OpenGL do nowych rozmiarów. Omówienie pozostałego kodu OpenGL trzeba na razie odłożyć do rozdziału 5., w którym przedsta wione zostaną macierze rzutowania. Teraz należy zadowolić się informacją, że kod ten resetuje prezentację grafiki, ponieważ zmieniły się wymiary okna. Ostatni istotny fragment kodu wprowadzony w tej wersji aplikacji znajduje się w pętli przetwarzania komunikatów: / / tw orzy g r a fik ę / / z e ru je b u fo r ekranu i b u fo r g łę b i glC l e a r(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n tity O ; / / re s e tu je m acierz widoku modelu angle = angle + O . lf ; i f (a n g le >= 3 6 0 .Of) angle = O.Of; g lT r a n s la te f ( 0 . 0 f, 0 . 0 f , - 5 . O f); g lR o ta te f(a n g le , 0 . 0 f . 0 . 0 f , 1 . 0 f ) ;
/ / zwiększa lic z n ik kąta o b ro tu / / j e ś l i pełen o b ró t, to re s e tu je
g lC o lo r 3 f ( l. O f,O .O f,O .O f); glBegin(GL_TRIANGLES); g lV e r te x 3 f( 0 .O f,O .O f,O .O f) g lV e r t e x 3 f ( l. O f,O .O f,O .O f) g lV e r t e x 3 f ( 1 . 0 f, 1 .0 f ,0 . 0 f) glE ndO ;
/ / w ybiera k o lo r czerwony / / ry s u je t r ó jk ą t
lic z n ik
/ / p rz e s u n ię c ie o 5 je d n o s te k w stecz / / o b ró t dookoła o si Z
SwapBuffers(g_FIDC);
/ / p rze łą cza b u fo ry
TranslateM essage(& m sg); DispatchM essage(&msg);
//
tłum aczy kom unikat i w ysyła go do systemu
Powyższy fragment kodu umieszczony zostaje w pętli przetwarzania komunikatów po wywołaniu funkcji PeekMessage(). Funkcja gl Cl e a r () zeruje bufory kolorów i głębi, co w efekcie daje wypełnienie okna czarnym tłem. Pozostała część kodu rysuje i obraca trójkąt w kolorze czerwonym. Czytelnik powinien teraz skompilować program i uru chomić go, a następnie poeksperymentować z wartościami przekazywanymi funkcjom OpenGL i sprawdzić, jaki mają wpływ na tworzoną grafikę. Po funkcjach OpenGL można skorzystać jeszcze ze zmiennej globalnej g HDC przecho wującej kontekst urządzenia. Przekazuje się ją jako parametr funkcji S w a p B u ffe rs(), która powoduje przełączenie buforów, zapewniając tym samym płynność animacji. Rysunek 2.4 prezentuje efekt działania programu. I to wszystko! Zbudowana w ten sposób została pierwsza kompletna aplikacja OpenGL. Posłuży ona jako szkielet gier i programów tworzonych w kolejnych rozdziałach. Ist nieje jednak jeszcze jeden aspekt tworzenia aplikacji OpenGL, który powinnien zostać omówiony w tym miejscu: grafika pełnoekranowa.
Rozdział 2. ♦ Korzystanie z OpenGL w system ie W indows
67
Rysunek 2.4. Okno aplikacji OpenGL
Pełnoekranowe aplikacje OpenGL Nie każdy użytkownik będzie zachwycony perspektywą korzystania z gry działającej w oknie. Większość gier korzystających z grafiki trójwymiarowej pracuje w trybie peł noekranowym. Również tworzone tu programy będą posiadać taką możliwość. W tym celu czytelnik powinien stworzyć nową rozbudowaną wersję przedstawionej wcześniej aplikacji OpenGL na podstawie modyfikacji kodu, które teraz zostaną omówione. Na początku należy utworzyć zmienną globalną o nazwie fu llS c re e n , która będzie in formować, czy aplikacja pracuje w trybie pełnoekranowym. Można zdefiniować ją na stępująco: bool fu llS c re e n = TRUE; / / a p lik a c ja rozpocznie pracę w tr y b ie pełnoekranowym
Należy pamiętać, że powinna to być zmienna globalna, ponieważ będzie wykorzysty wana w całym programie do sprawdzenia trybu pracy aplikacji. Kolejna modyfikacja polegać będzie na dodaniu kodu przełączającego tryb okienkowy na pełnoekranowy. W tym celu można wykorzystać strukturę DEVM0DE, która zawiera in formacje o inicjalizacji i konfiguracji urządzenia wyjściowego. Przełączając aplikację w tryb pełnoekranowy należy pamiętać o kilku rzeczach. Należy upewnić się na przy kład, czy wysokość i szerokość okna, która została podana przy jej tworzeniu jest taka sama jak wysokość i szerokość, która jest podana w strukturze DEVM0DE. Trzeba także pamiętać, aby zmienić konfigurację urządzenia wyjściowego, zanim utworzone zostanie okno. Jeśli nie będzie się przestrzegać tych zaleceń, można wpaść w tarapaty. Prostym sposobem ich uniknięcia będzie wykorzystanie zmiennych definiujących rozmiary okna.
68
Część I ♦ W prow adzenie do OpenGL i D irectX
Kod przełączający aplikację na tryb pełnoekranowy jest dość prosty. Sprowadza się on do odpowiedniego wypełnienia kilku pól struktury DEVM0DE i wywołania funkcji ChangeD is p la y S e ttin g s O : DEVM0DE devModeScreen; / / tr y b urządzenia m e m se t(& dm S cre e n S e ttin g s,0 ,size o f(d m S cre e n S e ttin g s)); devModeScreen.dmSize = s ize o f(d m S c re e n S e ttin g s ); devModeScreen.dmPelsWidth = w id th ; devModeScreen.dmPelsHeight = h e ig h t; devModeScreen.dmBitsPerPel = b it s ; devModeScreen.dmFields = DM BITSPERPEL | DM_PELSWIDTH
/ / z e ru je s tr u k tu r ę / / o k re ś la ro zm ia r s tr u k tu r y / / o k re ś la szerokość / / o k re ś la wysokość / / lic z b a b itó w /p ik s e l | DM_PELSHEIGHT;
i f ( C hangeDisplaySettings(&devM odeScreen. CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
{ / / j e ś l i wybór try b u pełnoekranowego n ie pow iódł s ię , / / to a p lik a c ja z n a jd z ie s ie w tr y b ie okienkowym fu llS c re e n = fa ls e ;
} Funkcja ChangeDi spl a y S e tti ngs () zmienia konfigurację domyślnego urządzenia wy świetlania na opisaną przez przekazaną jej strukturę DEVM0DE. Parametr CDS_FULLSCREEN powoduje usunięcie belki zadań z ekranu i sprawia, że system Windows przestaje zajmo wać się zawartością ekranu podczas zmian rozmiarów i przemieszczania okien w nowym trybie. Jeśli zmiana trybu powiedzie się, to funkcja zwróci wartość DISP CHANGE SUCCESFUL i aplikacja znajdzie się w trybie pełnoekranowym. W przeciwnym razie trzeba będzie nadać zmiennej fu l 1Screen wartość fa l se oznaczającą pracę w trybie okienkowym. Po przełączeniu aplikacji w tryb pełnoekranowy mimo wszystko należy utworzyć jej okno. Ponieważ parametry okien w trybie pełnoekranowym są inne niż w trybie okienkowym, trzeba dysponować dwoma ich zestawami. W przypadku trybu okienkowego parametry okna będą takie jak w poprzednich przykładach. Natomiast w trybie pełnoekranowym należy użyć znacznika WS_EX_APPWINDOWS dla rozszerzonego stylu okna i znacznika WS_ POPUP dla zwykłego stylu okna. Znacznik WS_EX_APPWINDOWS wymusza zwinięcie bieżą cego okna do paska zadań po wyświetleniu okna aplikacji. Znacznik WS POPUP powoduje utworzenie okna bez ramki, co jest pożądane w trybie pełnoekranowym. W trybie peł noekranowym należy także usunąć z ekranu wskaźnik myszy korzystając z funkcji ShowC u rso rO . Poniższy fragment kodu ilustruje sposób określenia stylu okna i ukrycia kur sora myszy w trybie pełnoekranowym i okienkowym: i f ( fu llS c re e n )
{ extendedW indowStyle = WS_EX_APPWINDÓW; w indow S tyle = WS_P0PUP; ShowCursor(FALSE);
/ / z w ija aktywne okno / / okno bez ramki / / chowa w skaźnik myszy
i e ls e
{ extendedW indowStyle = NULL; w indow S tyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
} Kolejna modyfikacja ma bardziej ogólny charakter i służy poprawie możliwości wy świetlania grafiki OpenGL w oknie systemu Windows. Funkcja A djustW indow R ectEx( )
Rozdział 2. ♦ Korzystanie z OpenGL w system ie W indows
69
umożliwia wyznaczenie rozmiarów okna systemu Windows na podstawie zadanych roz miarów prostokąta jego zawartości. Dla grafiki OpenGL oznacza to w praktyce, że gra nice utworzonego okna nie będą przesłaniać zewnętrznych fragmentów okna OpenGL. W ten sposób aplikacja OpenGL będzie dysponować zawsze maksymalnie dostępnym oknem graficznym. Funkcji AdjustWindowRectEx() należy przekazać strukturę RECT oraz zwykły i rozszerzony styl okna. W programie trzeba więc dodać zmienną windowRect. Wykorzystany w tym celu zostanie poniższy fragment kodu: RECT windowRect: / / obszaru okna w ykorzystywany do tw o rze n ia g r a f ik i w indow R ect.top = 0 ; / / gó rn y, lewy n a ro żn ik w indow R ect. l e f t = 0; w indow R ect.bottom = s cre e n H e ig h t: / / d o ln y , prawy n a ro żn ik w indow R ect. r ig h t = screenW idth; / / k o ry g u je ro zm ia r okna AdjustW indowRectEx(&windowRect, w indow S tyle, FALSE, extendedW indow S tyle):
Zamiast umieszczać po raz kolejny kompletny kod źródłowy zmodyfikowanej aplikacji, odsyłamy czytelnika do kodu programu o nazwie OpenGLWindow2 zamieszczonego na dysku CD. Dodanie możliwości pracy pełnoekranowej nie wymaga zbyt wielu modyfi kacji w programie. Po odpowiednim rozszerzeniu kodu można także zapytać użytkow nika podczas uruchomiania aplikacji o pożądany tryb jej pracy. Wszystkie przykłady programów omawiane w dalszej części książki będą mogły pracować tak w trybie okien kowym, jak i pełnoekranowym.
Podsumowanie Platforma Microsoft Windows jest wielozadaniowym systemem operacyjnym. Każde mu z procesów przyznawany jest w tym systemie cyklicznie przedział czasu, w którym wykonywany jest jego kod. Procedura szeregująca procesy optymalizuje wykonanie aplikacji biorąc pod uwagę ich wymagania i priorytety. Każdy z procesów może uruchamiać wiele niezależnie wykonywanych wątków, co spra wia, że wielozadaniowość jest możliwa także w obrębie pojedynczej aplikacji. Najnow sze wersje systemu Windows oferują możliwość wykorzystania włókien, które zapew niają wielozadaniowość także na poziomie poszczególnych wątków. Główną funkcję i punkt początkowy aplikacji systemu Windows stanowi funkcja WinMain(). Do obsługi komunikatów wysyłanych do aplikacji przez system służy natomiast procedura okienkowa. Przekazywane jej komunikaty rozpoznawane są zwykle za po mocą instrukcji wyboru switch i odpowiednio obsługiwane. Struktura WNDCLASSEX stanowi zbiór atrybutów wykorzystywanych do zdefiniowania klasy okna. Klasa okna musi zostać zdefiniowana i zarejestrowana, zanim utworzone zostaną okna aplikacji. Pętla przetwarzania komunikatów stanowi część funkcji WinMainO i wykonuje nastę pujące zadania:
70
Część I ♦ W prow adzenie do OpenGL i D irectX
♦ pobiera komunikat z kolejki za pomocą funkcji PeekMessage(); ♦ tłumaczy komunikat ♦ przesyła komunikat do systemu. W rozdziale przedstawiony został przykład kompletnej aplikacji systemu Windows ilu strujący sposób obsługi komunikatów i tworzenia okna aplikacji. Funkcje WGL stanowią rozszerzenie interfejsu programowego systemu Windows, co umożliwia wykonywanie aplikacji OpenGL. Najważniejsze z nich związane są z two rzeniem kontekstu grafiki OpenGL, który służy do przechowywania ustawień i komend OpenGL. Można przy tym używać jednocześnie wielu kontekstów grafiki OpenGL. W strukturze typu PIXELFORMATDESCRIPTOR umieszcza się opis kontekstu urządzenia, który będzie używany do tworzenia grafiki OpenGL. Struktura ta musi zostać zdefinio wana, zanim kod OpenGL rozpocznie tworzenie grafiki w oknie systemu Windows. Większość gier stworzonych na bazie grafiki trójwymiarowej wykorzystuje pełnoekra nowy tryb pracy. W rozdziale niniejszym pokazany został sposób implementacji pełno ekranowego trybu pracy związanej z potrzebami aplikacji OpenGL. Jego ilustracją jest program OpenGLWindow2, którego pełen kod źródłowy znajduje się na dysku CD.
Rozdział 3.
Przegląd teorii grafiki trójwymiarowej Przedstawionych teraz zostanie kilka podstawowych zagadnień z teorii grafiki trójwy miarowej, które będą pomocne przy tworzeniu grafiki gier w kolejnych rozdziałach. Solidne podstawy w zakresie teorii grafiki trójwymiarowej nie są absolutnie konieczne, aby rozpocząć przygodę z programowaniem w OpenGL, ale z pewnością ułatwiają to zadanie. Przedstawione tu zagadnienia stanowią jedynie wierzchołek góry lodowej, nie jest bowiem możliwe wyczerpujące omówienie teorii grafiki trójwymiarowej w poje dynczym rozdziale. Więcej informacji na ten temat odnajdzie czytelnik w materiałach wymienionych w suplemencie książki. W bieżącym rozdziale przedstawionych zostanie jedynie kilka podstawowych zagadnień. Należeć do nich będą: ♦ skalary i wektory; ♦ macierze; ♦ przekształcenia; ♦ rzutowania; ♦ oświetlenie; ♦ odwzorowania tekstur.
Skalary, punkty i wektory Grafika trójwymiarowa reprezentuje szereg obiektów geometrycznych tworzących trój wymiarowy świat gry. Każdy z takich światów jest inny i tworzy go kombinacja różnych obiektów, które zawsze można jednak rozłożyć na poniższe typy proste: ♦ Skalary. Chociaż skalar nie jest typem geometrycznym, to wykorzystywany jest jako jednostka miary. Skalar reprezentuje wartość pewnej wielkości fizycznej. Wartością skalarną będzie na przykład temperatura pokazywana przez termometr, długość odcinka lub natężenie oświetlenia trójwymiarowej sceny.
72
Część I ♦ W prow adzenie do OpenGL i D irectX
♦ Punkty. Punkt stanowi fundamentalne pojęcie geometrii i reprezentuje położenie w trójwymiarowej przestrzeni. W przypadku analizowanych zastosowań położenie to opisane będzie za pomocą trzech wartości skalarnych reprezentujących odległość na każdej z osi układu współrzędnych: x, y i z. ♦ Wektory. Wektor reprezentuje wielkość fizyczną posiadającą wartość oraz kierunek. Na przykład ruch kuli toczącej się po stole bilardowym opisany jest przez określony kierunek oraz wartość reprezentującą prędkość kuli. W ten sam sposób opisać można wektory grafiki trójwymiarowej. Reprezentację wektora stanowi odcinek posiadający pewien kierunek w przestrzeni oraz wartość odpowiadającą długości odcinka. Reprezentację wektorów prezentuje rysunek 3.1. Pokazano na nim pary równych wektorów. Dwa wektory są równe, jeśli posiadają ten sam kierunek i wartość. Natomiast ich położenie w przestrzeni nie jest określone i może być różne. Analogią będzie tutaj ruch dwu różnych kul bilardowych posiadających tę samą prędkość i kierunek, ale znajdujących się w różnych miejscach stołu. W takich sytuacjach mówi się, że wektory prędkości obu kul są równe. Rysunek 3.1. Paiy równych wektorów
Długość wektora Długość wektora — zwana także jego wartością bądź normą — zapisywana jest przez ujęcie nazwy wektora w parę pionowych kresek. Długość wektora/! zapisze się więc jako: W Długość wektora stanowi miarę odległości, można więc wyznaczyć ją korzystając z twier dzenia Pitagorasa. Ponieważ dalsze obliczenia będą prowadzone w przestrzeni, a nie na płaszczyźnie, to twierdzenie to należy rozszerzyć o trzecią współrzędną z. W ten sposób długość wektora A wyznaczyć można będzie w następujący sposób: \A \ = s q r t ( / ! x 2 + Ay2 + Az2)
Normalizacja wektora Znając długość wektora można poddać go normalizacji. Normalizacja polega na zredu kowaniu go do jednostkowej długości, dzięki czemu uzyskuje on szereg właściwości po żądanych przy obliczeniach dotyczących grafiki trójwymiarowej, które zostaną omówione później. Aby znormalizować wektor, należy podzielić go przez jego długość: n = N /
|/V |
Znormalizowany wektor n jest więc równy wektorowi N podzielonemu przez wartość wektora /V.
Rozdział 3 . ♦ Przegląd teorii grafiki trójw ym iarow ej
73
Dodawanie wektorów Podobnie jak w przypadku skalarów, tak i dla wektorów można wykonywać różne działania matematyczne. Podstawowymi operacjami są dodawanie i mnożenie wektora przez skalar. Jeśli wektor zapisany zostanie w postaci !/(xj/,z), to sumę dwu wektorów A(xaja,Za) i B(xb^yb,Zb) przedstawić będzie można jak poniżej: A(Xa,ya,Za) + B(Xb,yb,Zb) = C(Xa + Xb, ya + yb, Za + Zb) Ilustrację tej operacji przedstawia rysunek 3.2. Sumą wektorów A i Bjest nowy wektor C. Rysunek 3.2. Dodawanie wektorów
:b
• t.
y
y
/V a
Odejmowanie wektorów wykonuje się w ten sam sposób co ich dodawanie (z tą jednak różnicą, że odejmuje się ich składowe).
Mnożenie wektora przez skalar Rezultatem mnożenia wektora przez skalar jest wektor, którego długość i zwrot mogły się zmienić, ale kierunek został zachowany. Mnożąc na przykład wektor przez wartość 4 uzyskuje się nowy wektor o tym samym kierunku i zwrocie, ale o czterokrotnie więk szej długości. Mnożąc wektor przez wartość -1 otrzymuje się natomiast wektor o tej samej długości i kierunku, ale o przeciwnym zwrocie. Operację mnożenia wektora przez skalar opisuje poniższe równanie: S * A ( Xa,ya,Za) = £ (S * Xa, S* /a , S* Za)
Mnożenie wektora przez skalar polega więc na pomnożeniu jego składowych przez wartość skalarną. Operację tę ilustruje rysunek 3.3. Rysunek 3.3. Mnożenie wektora przez skalar
y * /
2
= A
0,5
=
^
0,5*A
Iloczyn skalarny wektorów Iloczyn skalamy oraz iloczyn wektorowy stanowi przykład operacji na wektorach często wykorzystywanych w grafice trójwymiarowej. Iloczyn skalamy wektorów jest przydatny
74
Część I ♦ W prow adzenie do OpenGL i D irectX
przede wszystkim do wyznaczania kąta pomiędzy dwoma wektorami. Aby go wyzna czyć, należy dodać do siebie iloczyny odpowiednich składowych wektorów i w ten spo sób otrzymać w wyniku wartość skalarną. Do wyznaczenia wartości iloczynu skalarnego dwu wektorów A i B można wykorzystać poniższe równania: A
.
B =
A
.
B
Ax*Bx
+
Ay*By
= |4 |* |S |* c o s 9
Pierwsze z równań umożliwia wyznaczenie iloczynu skalarnego jako sumy iloczynów składowych wektorów. Przekształcając drugie z równań otrzymuje się zależność pozwa lającą wyznaczyć kąt pomiędzy wektorami. cos
Q
=
(A
.
B)
/
(\A\
*
\B\)
Jeśli wektory A i B posiadają jednostkową długość, to zależność ta uprości się do poniż szej postaci: cos e
=
A
.
B
Zależność w tej postaci jest często wykorzystywana przy wykonywaniu obliczeń zwią zanych z tworzeniem grafiki trójwymiarowej. Iloczyn skalamy posiada także następują ce właściwości: ♦
A .
B = 0, jeśli kąt pomiędzy wektorami A i B wynosi 90°;
♦
A .
B > 0, jeśli kąt pomiędzy wektorami A i Bjest mniejszy od 90°;
♦
A .
B > 0, jeśli kąt pomiędzy wektorami A i Bjest większy od 90°;
♦
A .
B-
\A\2 =
\B\2, jeśli wektory A i B są równe.
Iloczyn wektorowy Kolejnym działaniem na wektorach często wykorzystywanym w grafice trójwymiaro wej jest iloczyn wektorowy, stosowany na przykład do wykrywania zderzeń obiektów czy wyznaczania ich oświetlenia. Iloczyn dwu wektorów/4 i B definiuje się następująco: A
XB =
\A \
* |S| * s in e * n
gdzie 0 jest kątem pomiędzy wektorami, a n jest znormalizowanym wektorem prostopa dłym do wektorów A i B. Korzystając z tego równania można wyznaczyć kąt pomiędzy wektorami A i B lub wek tor n, ale iloczyn wektorowy wyznacza się zwykle w inny sposób. Ponieważ powyższe równanie rzadko stosowane jest w praktyce, poniżej zaprezento wany został inny sposób wyznaczenia iloczynu wektorowego dwu wektorów A i B : C =
A
X B
=
(Ay *
Bz
-
Az
*
By,
Az
*
Bx
-
Ax
*
Bz,
Ax
*
By
-
Ay
*
Bx)
Wynikiem iloczynu wektorowego jest znormalizowany wektor. Równanie to umożliwia wyznaczenie każdej składowej tego wektora na podstawie pozostałych składowych mno żonych wektorów. Na przykład składową C x wyznacza się na podstawie składowych Ay, A z , By i óz.
Rozdział 3 . ♦ Przegląd teorii grafiki trójw ym iarow ej
75
Macierze Macierz jest dwuwymiarową tablicą liczb o określonej liczbie wierszy i kolumn. Macie rze mogą posiadać także trzeci wymiar, ale do opisu grafiki trójwymiarowej wystarczają macierze dwuwymiarowe. Mówiąc, że macierz posiada wymiary mxn, należy rozumieć przez to, że macierz posiada m wierszy i n kolumn. Tak więc na przykład macierz o 3 wierszach i 2 kolumnach będzie zapisywana w skrócie jako macierz 3x2. Poniżej przed stawiona została macierz M o rozmiarach 3x3: M=
|1 |4 |7
2 5 8
3 | 6 | 9 |
Wybrany element macierzy oznacza się podając jego wiersz i kolumnę. W powyższym przykładzie macierzy M element posiadający wartość 5 posiada pozycję (1, 1). Czytelnik może zastanawiać się, dlaczego nie jest to pozycja (2, 2). Macierze będą tu jednak im plementowane w języku C lub C++ jako dwuwymiarowe tablice. Pierwszy element ta kiej tablicy będzie zawsze oznaczony jako (0, 0), drugi jako (0, 1) i tak dalej. Sposób oznaczenia elementów macierzy ilustruje poniższy przykład: M
=
| /7700
0701 0702
|
|0710
0711 0712
|
| 0720
0721 0722
|
Powyższy sposób opisu macierzy stosowany będzie w tekście książki i w kodzie źró dłowym programów. Teraz można już przejść do omówienia działań wykonywanych na macierzach.
Macierz jednostkowa W działaniach wykonywanych na macierzach pojawia się szczególnie użyteczny rodzaj macierzy: macierz jednostkowa. Macierz ta posiada na przekątnej elementy o wartości 1, a pozostałe elementy macierzy posiadają wartość 0. Macierze jednostkowe mogą mieć dowolne rozmiary, ale zawsze muszą być kwadratowe, czyli o rozmiarach 07x 07. Poniżej znajduje się przykład macierzy jednostkowej o rozmiarach 3x3: |1 M= |0 |0
0 0| 1 0| 0 1|
Interesującą właściwością macierzy jednostkowej jest to, że mnożąc przez nią inną ma cierz otrzymuje się w wyniku tę samą macierz. Mnożenie macierzy omówione zostanie wkrótce.
Macierz zerowa Chociaż macierze zerowe nie będą wykorzystywane w praktyce, to należy je jednak omówić. Macierz zerowa posiada wszystkie elementy równe 0. Dodanie lub mnożenie
76
Część I ♦ W prow adzenie do OpenGL i D irectX
innej macierzy przez macierz zerową jest identyczne z dodawaniem lub mnożeniem przez wartość 0 w przypadku skalarów. Poniżej przedstawiamy przykład macierzy ze rowej o rozmiarach 3x3: | 00 0 I M= | |
00 00
0 | 0 |
Dodawanie i odejmowanie macierzy Dodawanie i odejmowanie macierzy jest bardzo proste. Elementy macierzy wynikowej powstają na skutek dodania lub odjęcia odpowiednich elementów macierzy wyjścio wych. Poniżej przedstawiony został przykład dodawania i odejmowania dwu macierzy M i N o rozmiarach 3x3. i 2 M= 1 0
17
2 2 1 1 4 1 3 1 1 12
M+ N = 1 o
17 12 M - N = 1 o
1 7
1 1 2 3 1 5 6 1 17 8 9 1
N = I 4
+
1 1 2 14 5 17 8
3 1 6 ! = 9 1
1 (2+1) 1 (0+4) 1 (7+7)
(2+2) (1+5) (3+8)
(2+3) 1 = (4+6) (1+9) 1
13 14 i 14
4 6 11
5 10 10
2 2 1 1 4 1 3 1 1
1 1 2 14 5 17 8
3 1 6 1 = 9 1
1 (2 -1 ) 1 (0 -4 ) 1 (7 -7 )
(2 -2 ) (1 -5) (3 -8)
(2 -3 ) 1 (4 -6 ) 1 = (1 -9 ) 1
1 1 1 -4 1o
0 -4 -5
-1 -2 -8
2 2 1 1 4 1 3 1 1
Jak łatwo zauważyć, element macierzy wynikowej M+N(0, 0) powstaje przez dodanie ele mentów A?(0, 0) i A/(0, 0). W ten sam sposób powstają pozostałe elementy macierzy bę dącej wynikiem dodawania lub odejmowania macierzy. Działania te są wykonalne je dynie w przypadku macierzy o takich samych rozmiarach. Dodawanie i odejmowanie macierzy jest łączne,czyli M+(N+X) =(M+N)+X. Odejmowanie macierzy nie jest jednak przemienne, ponieważ wynik operacji (M-N) niemusi być równy wynikowi operacji (N-M).
Mnożenie macierzy Macierze można mnożyć przez wartość skalarną lub przez inną macierz. Pierwsza z tych operacji jest bardzo prosta i polega na pomnożeniu wszystkich elementów macierzy przez wartość skalarną. Poniżej przedstawiamy przykład mnożenia macierzy o rozmia rach 2 x2 przez wartość 2 : M= | 1 | 2
3 ]
k = 2
0 i
C = k * A/ = 2 * | 1
3 |
=
| (2 *1 ) | (2 *2 )
(2 *3 ) | = | 2 6 | (2 *0 ) | | 4 0 |
Poniżej przedstawiamy ogólny zapis operacji mnożenia macierzy M o wymiarach 3x3 przez wartość skalarną Ł
Rozdział 3 . ♦ Przegląd teorii grafiki trójw ym iarow ej
M =
k *
| moo m i | m io m u
mo2 mu
\ |
I /7?20
IT122
|
M
ITI21
| /77oo
m oi * |
/7?o2 |
| /r* m o o m io m u m i 2 j
| m 2o
m 2i
m 22 |
| /c *m 2 o
= k
77
k*m i k*m 2 | =\ k*m w k * m n k * m i 2\ k*m i
k*rri22
|
Jednym z przykładów zastosowania mnożenia macierzy przez wartość skalarną w grafi ce jest operacja skalowania, która zostanie omówiona w dalszej części rozdziału. Mnożenie macierzy przez macierz jest dużo bardziej skomplikowane. Operacja ta moż liwa jest tylko wtedy, gdy liczba kolumn pierwszej z macierzy równa jest liczbie wier szy drugiej macierzy. Jeśli zestawiona zostanie na przykład macierz A o rozmiarach 3x2 z macierzą B o rozmiarach 3x3, to nie będzie można ich pomnożyć, ponieważ macierz A ma dwie kolumny, a macierz B trzy wiersze. Jeśli macierz A będzie na przykład macierzą o rozmiarach 2x3, to działanie mnożenia będzie wykonalne. W ogólnym przypadku — jeśli macierz A ma rozmiar mxn, to macierz B musi mieć rozmiar nxr, by mnożenie było wykonalne (m i r są dowolne). Zanim wykonana zostanie operacja mnożenia dwu macierzy, można ustalić wymiary macierzy, która będzie wynikiem tego działania. Macierz ta będzie posiadać tyle wier szy, ile pierwsza z mnożonych macierzy, i tyle kolumn, ile ma druga z nich. Jeśli więc pomnożona na przykład zostanie macierz A o rozmiarach 3x1 przez macierz B o rozmia rach 1x3, to w wyniku tego działania powstanie macierz C o rozmiarach 3x3. W ogól nym przypadku wynikowa macierz C będzie więc posiadać rozmiary mxr. A co z samym mnożeniem? Operacja mnożenia macierzy A przez macierz B polega na pomnożeniu wierszy macierzy A przez kolumny macierzy B. Każdy element wiersza macierzy A mnożony jest przez odpowiedni element kolumny macierzy B, a element macierzy wynikowej powstaje przez zsumowanie wyników poszczególnych mnożeń. Sposób mnożenia macierzy ilustruje rysunek 3.4. Rysunek 3.4. Mnożenie macierzy
Poniżej przedstawiony został przykład mnożenia macierzy A o rozmiarach 2x3 przez macierz B o rozmiarach 3x3: A = \ 1
3
4 |
| 3
| 2 0 2 |
1
2 |
fl = | 0 1 2 | |1 4
A *
B
= | (1*3 + | (2*3 +
3*0 + 4*1) 0*0 + 2*1)
4 *
B
= |7
20
20 |
|8
10
10 |
3|
(1*1 + 3*1 + 4*4) (2*1 + 0*1 + 2*4)
(1*2 + 3*2 + 4*3) | (2*2 + 0*2 + 2*3) |
W ogólnym przypadku zapis operacji mnożenia macierzy o rozmiarach 1x3 przez ma cierz o rozmiarach 3x3 przedstawia się następująco:
78
Część I ♦ W prow adzenie do OpenGL i D irectX
| /7oo noi M = \ mo m i
no2 | m2 |
N =
| mo nn m 2 | | r?2o m i ri22 \
M * N - \ (mo*noo + mi*mo + /7?o2*mo)
(moo*noi + m i*n u + m 2*n 2i)
(/7?oo*no2 + /7?oi*m2 + 77702^/722)
Mnożenie macierzy przez macierz nie jest działaniem przemiennym, czyli: (Af*A/) != (N*M)
Inaczej mówiąc, mnożenie macierzy w jednym porządku spowoduje uzyskania innego wyniku niż mnożenie macierzy w porządku odwrotnym. Wyjątkiem od tej reguły jest mnożenie przez macierz jednostkową, zerową lub mnożenie dwu identycznych macierzy.
Implementacja działań na macierzach Macierze i wykonywane na nich działania można implementować na wiele sposobów. W tym podrozdziale stworzony zostanie osobny typ dla reprezentacji macierzy o roz miarach 3x3 oraz zaimplementowane zostanie każde z działań na macierzach za pomo cą osobnej funkcji. // typ reprezentujący macierze 3x3 typedef stru c t
{ flo a t mat[3][33; } m atrix3x3_t;
Typ ten wykorzystywany będzie przez funkcje implementujące działania na macierzach: void M atrixAdd(m atrix3x3_t* matrixA, m atrix3x3_t* matrixB, m atrix3x3_t* re su ltM a trix )
{ // dodaje macierze matrixA i matrixB, a wynik umieszcza w re su ltM a trix // pętla przeglądająca w szystkie elementy macierzy f o r i i n t row=0; row < 3; row++)
{ f o r i i n t col=0; col < 3; col++)
{ // dodaje bieżące elementy macierzy re su ltM a trix -> m [ro w ][co l] = m atrixA->m [row ][col] + m atrixB->m [row ][col];
} } } void Scal a rM atrixliu lti flo a t scalarValue. m atrix3x3_t* matrixA, m atrix3x3_t* re su ltM a trix )
{ // mnoży macierz matrixA przez sk a la r scalarValue . a wynik umieszcza w re su lt M a trix // pętla przeglądająca w szystkie elementy macierzy f o r ( in t row=0; row < 3; row++)
{ f o r i i n t c o l=0: col < 3; col++)
{ // dodaje bieżące elementy macierzy re su ltM a trix -> m [ro w ][co l] = scalarV alue * m atrixA->m [row ][col];
Rozdział 3 . ♦ Przegląd teorii grafiki trójw ym iarow ej
79
void M a trixM u lt(m a trix 3x3 _t* matrixA. m atrix3x3_t* matrixB, m atrix3x3_t* re su ltM atrix)
{ // mnoży macierz matrixA przez macierz matrixB. a re zu lta t umieszcza w re su ltM a trix f lo a t sum; // przechowuje sumę mnożonych elementów f o r ( i n t row=0; row < 3; row++)
{ f o r ( in t c o l=0; col < 3; col++)
{ sum = 0; // mnoży w iersz macierzy A przez kolumnę macierzy B f o r ( in t k = 0; k < 3; k++)
{ sum += m atrixA->m [row][k] * m atrixA ->m [k][col];
} re su ltM a trix -> m [ro w ][c o l] = sum;
} } } Na tym można zakończyć przedstawianie macierzy. Omówione teraz zostanie ich zasto sowanie w grafice trójwymiarowej.
Przekształcenia Przekształcenia ożywiają trójwymiarowy świat gry. Pozwalają przemieszczać obiekty, powiększać je i pomniejszać, a nawet zmieniać ich wygląd. W rozdziale 5. pokazane zostanie to, że przekształcenia działają w tak zwanym lokalnym układzie współrzędnych. Jeśli początek tego układu znajduje się w początku ogólnego układu współrzędnych, to przekształcenia wykonywane są względem początku tego układu. Jeśli natomiast po czątek lokalnego układu znajduje się w na przykład w punkcie o współrzędnych (0, 10, 4), to przekształcenia będą wykonywane tak, jakby początek układu współrzędnych znaj dował się w punkcie o współrzędnych (0, 10, 4). Wykonanie przekształcenia polega w ogólnym przypadku na pomnożeniu przekształca nego punktu przez macierz przekształcenia. W rezultacie przekształcenia uzyskuje się nowy punkt. Jeśli dla punktu p wykonane zostanie przekształcenie reprezentowane przez macierz M, to uzyskany zostanie punkt p ', co można zapisać jak poniżej: p ' = M* p Przekształcenia korzystają z tak zwanych współrzędnych jednorodnych. Pozwala to wy konywać przesunięcia i skalowania przekształceń, co w praktyce okazuje się przydatne. Aby skorzystać ze współrzędnych jednorodnych, należy umieścić wartość 1 jako ostatni element leżący na przekątnej macierzy przekształcenia. Wyjaśnione to zostanie bliżej przy analizie macierzy poszczególnych przekształceń.
80
Część I ♦ W prow adzenie do OpenGL i D irectX
Przesunięcie Przesunięcie polega na dodaniu do współrzędnych punktu odpowiednich składowych wektora przesunięcia. Macierz przesunięcia wygląda następująco:
| 0
0 0 dx | 1 0 dy | 0 1 dz |
| 0
00
| 1
MP = | 0
1|
Aby wykonać operację przesunięcia punktu korzystając z macierzy przekształcenia, trzeba zapisać współrzędne punktu w postaci następującego wektora: Ix | p = Iy I I z
|
I i I MP
11 * p = 10 1o 1o
0 1 0 0
0 0 1 0
1 * i x i 1 (1*X) 1* m = 1 (0*x) 1 ★ 1 2 1 1 (0*x) 1 1 * ! 1 1 1 (0*x)
dx dy dz
+ + + +
(0*y) (l*y) (0*y) (0*y)
+ + + +
(0*z) (0*z) (l*z) (0*z)
+ + + +
(c/x*l) 1 = 1 x+ d x (dy*l) 1 = ! y +d y (dz*l)1 = 1 z+ d z (1*1) 1 = i 1
W efekcie otrzyma się więc: x' y '
z'
= x + dx = y + dy = z + dz
W ten sposób można przesunąć punkt lub wektor z jednego punktu trójwymiarowej prze strzeni do innego punktu. Należy zwrócić uwagę na to, że punkt opisany został za po mocą współrzędnych jednorodnych i w związku z tym na ostatniej pozycji reprezentu jącego go wektora pojawiła się wartość 1. Jeśli zastąpiona ona zostanie wartością 0, to wykonanie przesunięcia nie będzie możliwe, natomiast każda inna wartość spowoduje dodatkowe przeskalowanie przesunięcia.
Obrót Macierz obrotu wykorzystuje funkcje trygonometryczne, a obrót wokół każdej z osi wymaga osobnej macierzy o innych elementach. Macierz obrotu wokół osi x zdefiniowana jest następująco: | 1 M_0X = | 0
0
i 0
cos 0 s in e
| 0
0
0 - s in e cos e
0
0 I 0 0
| |
1 |
Jak łatwo zauważyć, obrót wokół osi x nie zmienia współrzędnych x. Podobnie w przy padku obrotu wokół osi y nie zmieniają się współrzędne y: |
M_0Y = | |
' 0
cos e 0 0 1 - s in e 0
0
s in e 0 | 0 0 | cos e 0 | 0 11
Rozdział 3 . ♦ Przegląd teorii grafiki trójw ym iarow ej
81
Obrót wokół osi z nie zmienia współrzędnych z: |
cos e - s in e s in e cos 0
M_0Z = |
| |
0 0
0 0
0 0
0 | 0 |
1 0
0 | 1 |
Aby obrócić dany punkt względem środka układu współrzędnych i wybranej osi, należy pomnożyć odpowiednią macierz obrotu przez wektor reprezentujący jednorodne współ rzędne punktu. Poniżej przedstawiony został przykład tej operacji dla obrotu wokół osi z. Ix | p = I y I
Iz | I i I
|
M_0Z*p = | |
cos 0 - s in 0 s i n © cos 0 0 0
| | (x *co s 0) = | ( x * s in 0) +
0 ( y * s in 0) (y*cos 0)
0 0 1
0
0
0 0 0 1
| |x| |* |y | | |z | | |1 |
= = = =
| (x*cos 0 ) - ( y * s in 0) | ( x * s in 0) + (y*cos 0) | (0 *x ) + (0* y ) + ( l* z ) | (0 *x ) + (0 *y ) + (0 *z )
+ + + +
(0 *z ) (0 *z ) (0 *1 ) (1 *1 )
+ (0*1) | + (0 *1 ) | | |
| |
Uzyskana jako wynik macierz kolumnowa oznacza, że: x * = (x*co s 0) - ( y * s in 0) y ' = ( x * s in 0) + (y*cos 0) z' = z
Z wykorzystaniem macierzy obrotów związane jest zagadnienie składania przekształceń. Zamiast mnożyć punkt przez wiele macierzy reprezentujących różne przekształcenia tworzy się jedną macierz reprezentującą złożenie przekształceń.Jako że mnożenie ma cierzy nie jest działaniem przemiennym, porządek składaniaprzekształceń maznaczenie. W przypadku obrotu można stworzyć macierz przekształcenia reprezentującą złożenie obrotu wokół osi z z obrotem wokół osi y i obrotem wokół osi x. Macierz M_0 tego przekształcenia powstanie przez pomnożenie macierzy obrotów dookoła poszczegól nych osi: M_0 = MOZ * M_0Y * M_0X
W wyniku tego działania otrzymany zostanie zestaw równań opisujących współrzędne nowego punktu, gdzie y jest kątem obrotu wokół osi z, (3 kątem obrotu wokół osi y, a a kątem obrotu wokół osi x: X ' = X*(COS Y * COS (3) +
y * (c o s y * s in z *(c o s y * s in y ' = x * ( s in y * cos y * ( s in y * si© z * ( s in y * s in z ' = - x * s in (3 + y * cos (3 * s in
s in a P * cos a (3) + P * s in a p * cos a (3 *
a +
Z * COS (3 * COS a
+
s in y * cos a ) + s in y * s in a )
+ -
cos Y * cos a ) + cos y * s in a )
-
82
Część I ♦ W prow adzenie do OpenGL i D irectX
Równania te można zapisać w postaci macierzy reprezentującej złożenie obrotów wokół trzech osi układu współrzędnych: I
|
M_0 = | y ' |
! Ii
I I
Mnożąc tę macierz przez jednorodne współrzędne punktu można wykonać dowolny obrót punktu za pomocą pojedynczej operacji, zamiast składać kilka różnych obrotów.
Skalowanie Skalowanie polega na mnożeniu współrzędnych punktów przez określoną wartość współczynnika. W przypadku grafiki trójwymiarowej można skalować każdą ze współ rzędnych przez inny współczynnik sx, sy i sz. Macierz skalowania wygląda następująco: | sx
0
| 0
0 0 I 0 0 j 0 sz 0 |
| 0
0
MS = \ 0 sy
01 |
Poniżej przedstawiamy przykład skalowania punktu p: 1X p = 1y 1z 1 i
1 1 i 1
1 sx 0 MS = 1 0 sy 10 1 0
0 0 0 sz 0 0
0 0 0 1
1 (sx*x) + (0 *y) + (0 * z ) i 1X 1 i * 1 y 1 = 1 (0 *x ) + ( s y *y ) + (0 * z ) 1 (0 *x ) + (0 *y ) + (s z * z ) 1 i z i I (0 *x ) + (0 *y ) + (0 *z ) 1 111
+ + + +
(0 *1 ) (0 *1 ) (0 *1 ) (1 *1 )
1 1 x *sx 1 = I y *sy 1 I z *sz 1 1 1
Z macierzy wynikowej można odczytać równania opisujące współrzędne punktu po skalowaniu: X* = x*sx y ' = y*sy z ' = z *s z
Na tym można zakończyć omawianie macierzy i ich zastosowań. Tworząc grafikę OpenGL korzysta się z nich nieustannie. Jeśli czytelnik nie czuje się jeszcze pewnie w świecie macierzy i działań na nich, to dla uzupełnienia swojej wiedzy może skorzystać z jednego ze źródeł, które zostało podane w dodatku A.
Rzutowanie Mimo że książka ta zajmuje się wyłącznie tworzeniem grafiki trójwymiarowej, to jed nak powstaje ona na płaskim ekranie monitora. I właśnie stąd wynika koncepcja rzuto wania przestrzennego świata gry na płaszczyznę ekranu.
Rozdział 3 . ♦ Przegląd teorii grafiki trójw ym iarow ej
83
Na potrzeby grafiki trójwymiarowej wyróżnia się dwa rodzaje rzutowania. Rzutowanie izometryczne zachowuje wymiary obiektów na płaszczyźnie rzutowania bez względu na odległość, w jakiej znajdują się w przestrzeni. Płaszczyzna rzutowania spełnia rolę so czewek obiektywu kamery, przez którą obserwuje się trójwymiarowy świat. Wszystkie punkty przestrzeni muszą więc być rzutowane na płaszczyznę rzutowania. Drugi z rodzajów rzutowania — zwany rzutowaniem perspektywicznym — uwzględnia odległość obiektu od płaszczyzny rzutowania. Rzutowanie to można wyobrazić sobie za pomocą promieni wybiegających z punktów obiektu i zbiegających się w jednym punk cie reprezentującym oko obserwatora. Promienie te po drodze przecinają płaszczyznę rzutowania. Ilustruje to rysunek 3.5. Rysunek 3.5. Promienie biegnące od obiektu do oka obserwatora
Teraz należy przyjrzeć się bliżej obu rodzajom rzutowania.
Rzutowanie izometryczne Rzutowanie izometryczne jest często stosowane przez inżynierów korzystających z kom puterowych systemów wspomagania projektowania. Tworzą oni rzuty trójwymiarowego modelu pod różnymi kątami, które są zwane także rzutami ortograficznymi. Najczęściej wykorzystywane są rzuty ortograficzne modelu z przodu, z góry i z boku, ponieważ prze kazują odbiorcy pełną informację umożliwiającą wizualizację modelowanego obiektu. Rzutowanie izometryczne odbywa się w dwu etapach. Pierwszy etap polega na prze kształceniu płaszczyzny rzutowania na płaszczyznę xy. Drugi etap polega na usunięciu składowej ze wszystkich punktów w przestrzeni. W ten sposób usunięta zostaje infor macja o odległości punktów od płaszczyzny rzutowania, co pozwala zachować wymiary obiektów w rzucie bez względu na ich wcześniejsze położenie w przestrzeni w stosunku do obserwatora. Chociaż tak przydatne przy modelowaniu obiektów przez inżynierów, rzutowanie izo metryczne nie jest przydatne przy tworzeniu grafiki trójwymiarowej. Obrazy otrzymane na drodze rzutowania izometrycznego nie są realistyczne, ponieważ nie zawierają in formacji o głębi (patrz: rysunek 3.6). Z tego powodu tworząc grafikę trójwymiarową należy korzystać głównie z rzutowania perspektywicznego.
84
Część I ♦ W prow adzenie do OpenGL i D irectX
Rysunek 3.6. Rzutowanie izometryczne
Rzutowanie perspektywiczne Jak już wspomniano, rzutowanie perspektywiczne pozwala utworzyć obraz obiektów, na którym rozmiary obiektów zależą od ich odległości od obserwatora. Obiekty bardziej oddalone będą posiadać na tym obrazie mniejsze rozmiary niż obiekty położone bliżej obserwatora. W przypadku rzutowania perspektywicznego traktuje się oko obserwatora jako poje dynczy punkt, w którym zbiegają się promienie światła odbite od obiektów. Założenie takie stanowi bardzo dobre przybliżenie rzeczywistego sposobu widzenia. Na drodze do oka obserwatora promienie światła przecinają płaszczyznę rzutowania. Zbiór punktów przecięcia tworzy płaski obraz przestrzeni na płaszczyźnie rzutowania. Pokazane to zo stało na rysunku 3.5. Rzutowanie perspektywiczne polega więc na znalezieniu na płaszczyźnie rzutowania punktów jej przecięcia przez promienie świetlne odbite od obiektów. W tym celu można skorzystać z macierzy przekształcenia perspektywicznego. Zanim zostanie ona przeana lizowana, należy omówić jeszcze pojęcie ogniskowej. Przez ogniskową należy rozumieć w tym przypadku odległość pomiędzy okiem obser watora a płaszczyzną rzutowania, co ilustruje rysunek 3.7. Pojęcie ogniskowej jest szcze gólnie przydatne podczas tworzenia grafiki trójwymiarowej i wykorzystywane jest do określenia pola widzenia, co zaprezentowano na rysunku 3.8. Zwiększenie ogniskowej powoduje ograniczenie pola widzenia. Natomiast zmniejszenie ogniskowej sprawia, że pole widzenia powiększa się. Tworząc grafikę trójwymiarową często trzeba trochę poeksperymentować z wielkością ogniskowej, aby uzyskać bardziej realistyczny obraz. Powodem tego są zniekształcenia, które mogą wystąpić na skutek zastosowania prze kształcenia perspektywicznego. Rysunek 3.7. Ogniskowa jako odległość pomiędzy okiem obsei^watora i płaszczyzną rzutowania Oko obserwatora
Rozdział 3 . ♦ Przegląd teorii grafiki trójw ym iarow ej
Rysunek 3.8. Pole widzenia w zależności od ogniskowej
85
Ogniskowa | Ogniskowa
Teraz już można przejść do omówienia macierzy przekształcenia perspektywicznego. Aby zastosowanie tego przekształcenia było możliwe, trzeba zawsze stosować współ rzędne jednorodne. Oznacza to, że w wektorze opisującym współrzędne punktu po skła dowych x, y, z musi zawsze występować wartość 1. Również wektor reprezentujący współrzędne punktu po przekształceniu musi zawierać na tej samej pozycji wartość 1. Jeśli raz jeszcze zostaną poddane analizie omówione dotąd przekształcenia, okaże się, że we wszystkich wykorzystywane były właśnie współrzędne jednorodne. Jeśli w wyni ku przekształcenia wektor reprezentujący współrzędne punktu będzie zawierał na ostat niej pozycji wartość różną od 1, to trzeba będzie go ponownie znormalizować. W tym celu należy pomnożyć wektor przez wartość będącą odwrotnością tej wartości. Macierz przekształcenia perspektywicznego prezentuje się następująco: |
M_P =
10
0
0
I
| 01
0
0
|
¡ 0 0 1 1 /ogniskowa \
|
00
0
1
|
Mnożąc tę macierz przez wektor zawierający współrzędne jednorodne punktu wykonuje się przekształcenie perspektywiczne: I x p = I y I z
I i 1 m_p * p
0
0
= | o i o 0 0 1 0 0 0
0
I
o
I
I ł ogniskowa | 1 I
I x I
*
I
I y I -
I
j z j j 1 j
x
I
y
I
j -1 j j z/ogniskow a j
Jak łatwo zauważyć, ostatni element wynikowego wektora będzie zwykle różny od 1. Oznacza to, że wektor ten należy pomnożyć przez wartość będącą odwrotnością jego ostatniego elementu, czyli — ogniskowa/z. ogniskow a/z
I
x
|
I
y
\
I
-1
I
| z/ogniskow a \
x *{o g n isk o w a /z ) y * {ogniskow a/z) -ogniskow a/z
1
Warto przy okazji zauważyć, że jeśli wartość współrzędnej z będzie równa 0, to efek tem będzie błąd dzielenia przez 0. Aby zapobiec takim sytuacjom, przed wykonaniem przekształcenia perspektywicznego stosuje się obcinanie punktów, które mogłyby spo wodować wystąpienie takiego błędu.
86
Część I ♦ W prow adzenie do OpenGL i D irectX
Obcinanie Jeśli obiekty trójwymiarowego świata nie mieszczą się na płaszczyźnie rzutowania, to zastosowanie przekształcenia perspektywicznego może prowadzić do błędów. Zwłasz cza jeśli współrzędna z w przypadku niektórych punktów tych obiektów posiada war tość 0. Gdy współrzędna z posiada wartość ujemną, to wykonywanie dla tych punktów przekształcenia perspektywicznego także nie ma sensu, ponieważ znajdują się one za obserwatorem. Ponieważ obie sytuacje mogą być przyczyną błędów, duże znaczenie ma mechanizm zapobiegania im. Typowe rozwiązanie opisanych problemów polega na utworzeniu bryły widoku. Bryła widoku jest wycinkiem przestrzeni, który widziany jest przez obserwatora. Ponieważ punkty, które leżą na zewnątrz bryły widoku i tak nie są widziane przez obserwatora, nie ma sensu wykonywać dla nich przekształcenia perspektywicznego. Po zastosowaniu rzutowania perspektywicznego bryła widoku posiada kształt ostrosłupa, co przedstawia rysunek 3.9. Rysunek 3.9. Bryła widoku dla rzutowania perspektywicznego ma kształt ostrosłupa
X
Daleko
Blisko
«T Światło Znaczenie światła w trójwymiarowym świecie zostanie zrozumiane najlepiej, jeśli sko jarzone będzie z wrażeniami powstałymi po wejściu do pokoju, w którym nie ma okien i wyłączeniu światła. Nic nie widać! Podobnie obiekty wirtualnego świata gry przestaną istnieć na ekranie bez oświetlenia. Ale nie tylko z tego powodu światło jest tak ważne. Właściwe oświetlenie podnosi realizm wirtualnego świata tworząc cienie, odbicia, kolory i inne efekty. Efektom tym zostanie poświęcona uwaga nieco później, a teraz przesta wione zostaną jedynie trzy rodzaje światła: światło otoczenia, światło rozproszone i świa tło odbite.
Rozdział 3 . ♦ Przegląd teorii grafiki trójw ym iarow ej
87
Światło otoczenia Ten rodzaj światła wypełnia przestrzeń tak, jakby światło nie posiadało żadnego źródła i padało z równą intensywnością ze wszystkich kierunków. Oczywiście źródło światła otoczenia istnieje, ale oświetla ono obiekty ze wszystkich stron równomiernie. Gdy w po koju zostanie umieszczona nieskończona liczba żarówek, to w jego centralnym punkcie zlokalizowane zostanie idealne światło otoczenia. Rysunek 3.10 ilustruje pojęcie światła otoczenia. Rysunek 3.10. Oświetlenie powierzchni za pomocą światła otoczenia
Ponieważ światło otoczenia oświetla równomiernie wszystkie obiekty trójwymiarowego świata, wyznaczenie jego natężenia w dowolnym punkcie nie stanowi problemu. Nie trzeba wnikać w matematyczne szczegóły wyznaczania światła i jego odbicia, ale warto już teraz wiedzieć, że wyznaczenie składowej czerwonej, zielonej i niebieskiej światła otoczenia w każdym punkcie wirtualnego świata możliwe jest za pomocą odpowiednich równań.
Światło rozproszone Ten rodzaj światła spotyka się na co dzień. Pada ono z określonego kierunku i jest rów nomiernie odbijane przez oświetlaną płaszczyznę. Dwie płaszczyzny mogą jednak od bijać światło rozproszone w różnym stopniu, jeśli kąt pomiędzy nimi a kierunkiem światła jest różny. Oczywiście w największym stopniu zostanie odbite światło, które oświetla płaszczyznę pod kątem prostym. Światło słoneczne stanowi dobry przykład światła roz proszonego, gdyż pada pod określonym kątem i równomiernie oświetla płaszczyznę. Je śli obserwator światła słonecznego znajdzie się poza Ziemią, to zauważy, że promienie słoneczne odbijane są w różnym stopniu przez różne obszary naszej planety. W naj większym stopniu odbijać je będą obszary, w przypadku których Słońce znajduje się najwyżej nad horyzontem, a w najmniejszym te, nad którymi pojawią się dopiero nad horyzontem, lub zachodzi za niego. Oczywiście różny stopień odbicia promieni sło necznych przez różne obszary Ziemi nie kłóci się z równomiernym odbiciem światła rozproszonego przez płaszczyznę, ponieważ powierzchnię Ziemi można podzielić na wiele małych płaszczyzn. Rysunek 3.11 stanowi ilustrację światła rozproszonego. Rysunek 3.11. Oświetlenie płaszczyzny przez światło rozproszone
Źródło światła rozproszonego
\
\
,
\
88
Część I ♦ W prow adzenie do OpenGL i D irectX
Światło odbijane Światło odbijane pada także z określonego kierunku i jest intensywnie odbijane przez płasz czyznę jedynie w określonym kierunku. Często tworzy ono na oświetlanej powierzchni jasny punkt odbicia. Światło odbijane używane jest często do tworzenia wrażenia poły sku na oświetlanych obiektach. Pojęcie światła odbijanego przybliża rysunek 3.12. Rysunek 3.12. Oświetlenie płaszczyzny przez światło odbijane
Źródło światła odbijanego
Odwzorowania tekstur Odwzorowania tekstur pozwalają przekształcić wielokąty tworzące grafikę trójwymia rową w realistyczne obrazy wirtualnego świata. Obecnie prawie wszystkie gry wyko rzystują tekstury. Tekstury mogą mieć postać prostych, geometrycznych wzorów lub mogą być stworzone przez artystę-grafika albo na drodze fotograficznej. W rzeczywistym świecie rozpoznaje się obiekty na podstawie ich rozmiarów, kształtu oraz właśnie tekstur. W ten sam sposób można charakteryzować także obiekty wirtual nego świata gry — za pomocą rozmiaru, kształtu i pokrywającej je tekstury. Przykłady analizowane w tej książce koncentrują się na wykorzystaniu tekstur dwuwy miarowych, chociaż możliwe są także tekstury jedno-, trój-, a nawet cztero wymiarowe. Tekstura dwuwymiarowa ładowana jest zwykle z pliku zawierającego grafikę stworzo ną przez artystę lub zeskanowaną ze zdjęcia. Coraz powszechniejsze stają się obecnie tak zwane tekstury proceduralne, które tworzone są w trakcie wykonywania programu. Wyjaśnionych teraz zostanie kilka terminów związanych z teksturami. Jeśli dla tekstury dwuwymiarowej wprowadzony zostanie układ współrzędnych, to uzyska się możliwość manipulacji każdym punktem tekstury. Układ współrzędnych tekstury pokazano na ry sunku 3.13. Opisuje on położenie każdego punktu za pomocą pary (s, t). Tekstura łado wana jest z pliku do dwuwymiarowej tablicy, której każdy element nazywa się tekselem. Rysunek 3.13. Współrzędne tekstury
t
(0 , 0 ) Tekstura o wymiarach (s,t)
Rozdział 3 . ♦ Przegląd teorii grafiki trójw ym iarow ej
89
Odwzorowując teksturę na powierzchni obiektu trzeba ją najpierw odpowiednio zorien tować. W tym celu korzysta się ze współrzędnych parametrycznych (w, v) pokazanych na rysunku 3.14. Wartości u i v należą do przedziału od 0 do 1. Wartości te określają sposób pokrycia powierzchni za pomocą tekstury. Sposób wykorzystania współrzęd nych parametrycznych omówiony zostanie bliżej w rozdziale 8. poświęconym odwzo rowaniom tekstur. Rysunek 3.14. Współrzędne parametryczne pozwalają ustalić sposób pokrycia powierzchni przez teksturę
Powierzchnia
Podsumowanie Do reprezentacji obiektów trójwymiarowego świata używa się punktów, skalarów i wek torów. Skalary opisują takie wielkości jak na przykład temperatura, odległość, szero kość i nie posiadają określonego kierunku ani zwrotu. Wektory opisywane są natomiast przez długość, kierunek i zwrot. Punkty reprezentują poszczególne lokalizacje trójwy miarowej przestrzeni. Macierze wykorzystywane są do reprezentacji wektorów i operacji wykonywanych na wektorach. Do operacji tych — zwanych przekształceniami — należą: przesunięcie, obrót i skalowanie. Przesunięcia te pozwalają przemieszczać obiekty w przestrzeni, obracać je wokół osi układu współrzędnych i zmieniać rozmiary obiektów za pomocą skalowania. Rzutowanie izometryczne wykorzystywane jest przez komputerowe wspomaganie pro jektowania i zachowuje rozmiary rzutowanych obiektów bez względu na ich odległość od obserwatora. Rzutowanie perspektywiczne zniekształca rozmiary obiektów w taki sposób, że rozmiary obiektów znajdujących się dalej od obserwatora stają się mniejsze od rozmiarów obiektów leżących bliżej. Bryłę widoku stanowi wycinek przestrzeni widziany przez obserwatora. Wszystkie obiekty znajdujące się poza bryłą widoku są obcinane i nie są poddawane przekształce niom. Skraca to czas tworzenia grafiki oraz pozwala uniknąć błędów związanych z wy konywaniem działania dzielenia podczas przekształceń.
90
Część I ♦ W prow adzenie do OpenGL i D irectX
Tworząc grafikę należy stosować trzy podstawowe rodzaje światła. Światło otoczenia rozchodzi się równomiernie we wszystkich kierunkach przestrzeni. Światło rozproszone pada z określonego kierunku i jest równomiernie odbijane przez płaszczyznę. Tworze nie efektów odbicia umożliwia światło odbijane jedynie w określonym kierunku. Odwzorowania tekstur pozwalają tworzyć bardziej realistyczny, trójwymiarowy świat poprzez umieszczanie wzorów bądź obrazów na tworzących obiekty wielokątach. Po dobnie jak w świecie rzeczywistym tekstury ułatwiają identyfikację obiektów.
Korzystanie z OpenGL
Rozdział 4.
Maszyna stanów OpenGL i podstawowe elementy grafiki Począwszy od bieżącego rozdziału zacznie się nareszcie praktyczne korzystanie z OpenGL. Zanim jednak zostanie stworzona bardziej zaawansowana grafika, trzeba będzie najpierw poznać zasady działania maszyny stanów OpenGL. Stan maszyny OpenGL opisany jest przez setki parametrów, które określają różne aspekty tworzenia grafiki. Ponieważ stan maszyny stanów OpenGL ma zasadniczy wpływ na po stać tworzonej grafiki, trzeba poznać najpierw jej domyślne wartości parametrów oraz sposób uzyskiwania i zmieniania wartości tych parametrów. Stan maszyny OpenGL może być zmieniany za pomocą szeregu funkcji, które zostaną przeanalizowane w tym rozdziale. W bieżącym rozdziale omówione będą następujące zagadnienia: ♦ sposób dostępu do parametrów maszyny stanów OpenGL i metody zmiany ich wartości; ♦ elementy podstawowe grafiki OpenGL i ich rodzaje; ♦ metody zmiany sposobu obsługi i wyświetlania podstawowych elementów grafiki.
Funkcje stanu Interfejs OpenGL zaprojektowano w taki sposób, że zawiera on wiele funkcji ogólnego przeznaczenia, które mogą zmieniać wiele różnych parametrów w zależności od przeka zywanych im argumentów. Rozwiązanie to zastosowano także w przypadku funkcji zmie niających parametry maszyny stanu, które przedstawione zostaną w tym podrozdziale. Pierwszą z nich jest funkcja glG et(), którą stosuje się w celu uzyskania bieżących pa rametrów maszyny stanów. Funkcja ta posiada cztery różne wersje:
94
Część II ♦ Korzystanie z OpenGL
vo id v o id vo id vo id
geGetBooleanv(GLenum pname, GLboolean *param s); geGetDoublev(GLenum pname, GLdouble *param s); geGetFloatv(GLenum pname. G L flo a t *param s); geGetIntegerv(GLenum pname, G L in t *param s);
Parametr pa rams jest wskaźnikiem tablicy wystarczająco pojemnej, by funkcja g lG e tO mogła umieścić w niej wartość parametru, którego dotyczy pytanie. Parametr pname okre śla natomiast parametr, którego wartość ma być poznana. Parametr pname może mieć jedną z podanych poniżej wartości. Znaczenie poszczególnych z nich omawiane będzie stopniowo, gdy pojawiać się one będą w tworzonych programach. GL_ACCUM_AL PHA_BI TS GL_ACCUM_BLUE_BITS GL_ACCUM_CLEAR_VALUE GL_ACCUM_GREEN_BITS GL_ACCUM_RED_BITS GL_ALPHA_BIAS GL_ALPHA_BITS GL_ALPHA_SCALE GL_ALPHA_TEST GL_ALPHA_TEST_FUNC GL_ALPHA_TEST_REF GL_ATTRI B_STACK_D EPTH GL_AUT0_N0RMAL gl _ aux _ buffers GL_BLEND GL_BLEND_DST GL_BLEND_SRC GL_BLUE_BIAS GL_BLUE_BITS GL_BLUE_SCALE GL_CLIENT_ATTRIB_STACK_DEPTH GL_CLIP_PLANEn (g d zie n je st w a rto ś cią z p rz e d z ia łu od 0 do GL_MAX_CLIP_PANES - 1)
GL_C0L0R_ARRAY GL_C0L0R_ARRAY_SIZE GL_COLOR_ARRAY_STRIDE
GL_COLOR_ARRAY_TYPE GL_COLOR_CLEAR_VALUE GL_C0L0R_L0GIC_0P GL_COLOR_MATERIAL GL_COLOR_MATERIAL_FACE GL_COLOR_MATERIAL_PARAMETER GL_COLOR_WRITEMASK GL_CULL_FACE GL_CULL_FACE_MODE GL_CURRENT_COLOR GL_CURRENT_INDEX GL_CURRENT_NORMAL GL_CURRENT_RASTER_COLOR GL_CURRENT_RASTER_DISTANCE GL_CURRENT_RASTER_INDEX GL_CURRENT_RASTER_POSITION GL_CURRENT_RASTER_POSITION_VALID GL_CURRENT_RASTER_TEXTURE_COORDS GL_CURRENT_TEXTURE_COORDS GL_DEPTH_BIAS GL_DEPTH_BITS GL DEPTH CLEAR VALUE
Rozdział 4 . ♦ M aszyna stanów OpenGL i podstaw ow e elem enty grafiki
GL_DEPTH_FUNC GL_DEPTH_RANGE GL_DEPTH_SCALE GL_DEPTH_TEST GL_DEPTH_WRITEMASK GL_DITHER GL_DOUBLEBUFFER GL_DRAW_BUFFER GL_EDGE_FLAG GL_EDGE_FLAG_ARRAY GL_EDGE_FLAG_ARRAY_STRIDE GL_F0G GL_F0G_C0L0R GL_FOG_DENSITY gl _ fog_ end GL_FOG_HINT GL_FOG_INDEX GL_F0G_M0DE GL_FOG_START GL_FRONT_FACE GL_GREEN_BIAS GL_GREEN_BITS GL_GREEN_SCALE GL_INDEX_ARRAY
GL_INDEX_ARRAY_STRIDE GL_INDEX_ARRAY_TYPE GL_INDEX_BITS GL_INDEX_CLEAR_VALUE GL_INDEX_L0GIC_0P GL_INDEX_MODE GL_INDEXOFFSET GL_INDEX_SHIFT GL_INDEX_WRITEMASK GL_LIGHTn ( g d z ie n je s t w a rto ś c ią z p r z e d z ia łu o d 0 d o GL_MAX_LIGHTS - 1) GL_LIGHTING GL_LIGHT_M0DE L_AMBI ENT GL_LIGHT_M0D EL_L0CAL_VI EWER GL_LIGHT_MODEL_TWO_SIDE GL_LINE_SM00TH GL_LINE_SM00TH_HINT GL_LINE_STIPPLE GL_LINE_STIPPLE_PATTERN GL_LI NE_STI PPL E_RE PEAT GL_LI NE_WI DTH GL_LINE_WIDTH_GRANULARITY GL_LINE_WIDTH_RANGE GL_LIST_BASE GL_L I ST__I NDEX GL_LIST_MODE GL_L0GIC_0P GL_L0GIC_0P_M0DE GL_MAP1_C0L0R_4 GL_MAP1_GRID_D0MAIN GL_MAP1_GRID_SEGMENTS GL_MAP1_INDEX GL_MAP1_N0RMAL GL_MAP1_TEXTURE__C00RD_1 GL MAPI TEXTURE COORD 2
95
96
Część II ♦ Korzystanie z OpenGL
GL_MAP1_TEXTURE_C00RD_3 GL_MAP1_TEXTURE_C00RD_4 GL_MAP1_VERTEX_3 GL_MAP1_VERTEX_4 GL_MAP2_C0L0R_4 GL_MAP2_GRID_D0MAIN GL_MAP2_GRID_ SEGMENTS GL_MAP2_INDEX GL_MAP2_N0RMAL GL_MAP2_TEXTURE_C00RD_1 GL_MAP2_TEXTURE_C00RD_2 GL_MAP2_TEXTURE_C00RD_3 GL_MAP2_TEXTURE_C00RD_4 GL_MAP2_V ERTEX_3 GL_MAP2_V ERTEX_4 GL_MAP_C0L0R gl _ map _ s t e n c il GL_MATRIX_MODE GL_MAX_ATTRIB_STACK_DEPTH GL_MAX_CLIENT_ATTRIB_STACK_DEPTH GL_MAX_CLIP_PLANES GL_MAX_EVAL_ORDER GL_MAX_LIGHTS GL_MAX_LIST_N ESTING GL_MAX_MODELVIEW_STACK_DEPTH GL_MAX_NAME_STACK_D EPTH GL_MAX_PIXEL_MAP_TABLE GL_MAX_PR0J ECT10N_STACK_D EPTH GL_MAX_TEXTURE_SIZE GL_MAX_TEXTURE_STACK_DEPTH GL_MAX_VIEWPORT_DIMS GL_MODELVIEW_MATRIX GL_MODELVIEW_STACK_DEPTH GL_NAME_STACK_D EPTH GL_NORMAL_ARRAY GL_NORMAL_ARRAY_STRIDE GLJORMALIZE GL_PACK_ALIGNMENT GL_PACK_LSB_FIRST GL_PACK_ROW_L ENGTH GL_PACK_SKIP_PIXELS GL_PACK_SKIP_ROWS GL_PACK_SWAP_BYTES GL_PERSPECTIVE_CORRECTION_HINT GL_PIX EL_MAP_A_TO_A_SI ZE GL_PIX EL_MAP_B_TO_B_SIZE GL_PIX EL_MAP_G_TO_G_SI ZE GL_PIX EL_MAP_I_T0_A_SIZ E GL_PIX EL_MAP_I_T0_B_SIZ E GL_PIXEL_MAP_I_TO_G_SI ZE GL_PIXEL_MAP_I_TO_I_SIZE GL_PIX EL_MAP_I_T0_R_SIZ E GL_PIXEL_MAP_R_TO_R_SIZE GL_PIX EL_MAP_S_TO_S_SIZE GL_POINT_SIZE GL_POINT_SIZE_GRANULARITY GL_POINT_SIZE_RANGE GL_POINT_SMOOTH GL POINT SMOOTH HINT
Rozdział 4 . ♦ M aszyna stanów OpenGL i podstaw ow e elem enty grafiki
GL_P0LYG0N_M0DE GL_P0LYG0N_0FFSET_FACT0R GL_POLYGON_OFFSET_UNITS GL_P0LYG0N_0FFSET_FILL GL_POLYGON_OFFSET_LINE GL_P0LYG0N_0FFSET_P0INT GL_P0LYG0N_SM00TH GL_P0LYG0N_SM00TH_HINT GL_POLYGON_STIPPLE GL_PROJECTION_MATRIX GL_PR0JECTION_STACK_DEPTH GL_READ_BUFFER GL_RED_BIAS GL_RED_BITS GL_RED_SCALE GL_RENDER_MODE GL_RGBA_MODE GL_SCISS0R_B0X GL_SCISSOR_TEST GL_SHADE_MODEL GL_STENCIL_BITS GL_STENCIL_CLEAR_VALUE GL_STENCIL_FAIL GL_STENCIL_FUNC GL_STENCIL_PASS_DEPTH_FAIL GL_STENCIL_PASS_DEPTH_PASS GL_STENCIL_REF GL_STENCIL_TEST GL_STENCIL_VALUE_MASK GL_STENCIL_WRITEMASK GL_STERE0 GL_SUBPIXEL_BITS GL_TEXTURE_1D GL_TEXTURE_2D GL_TEXTURE_COORD_ARRAY GL_TEXTURE_COORD_ARRAY_SIZE GL_TEXTURE_COORD_ARRAY_STRIDE GL_TEXTURE_COORD_ARRAY_TYPE GL_TEXTURE_ENV_COLOR GL_TEXTURE_ENV_MODE GL_TEXTURE_GEN_Q GL_TEXTURE_GEN_R GL_TEXTURE_GEN_S GL_TEXTURE_GEN_T GL_TEXTURE_MATRIX GL_TEXTURE_STACK_DEPTH GL_UNPACK_ALIGNMENT GL_UNPACK_LSB_FIRST GL_U NPACK_R0W_L ENGTH GL_UNPACK_SKIP_PIXELS GLJJNPACK_SKIP_R0WS GL_UNPACK_SWAP_BYTES GL_VERTEX_ARRAY GL_V ERTEX_ARRAY_SIZ E GL_VERTEX_ARRAY_STRIDE GL_V ERTEX_ARRAY_TY PE GL_VIEWPORT GL_Z00M_X GL_Z00M_Y
97
98
Część II ♦ Korzystanie z OpenGL
Możliwość uzyskania informacji o stanie maszyny OpenGL jest z pewnością bardzo przydatna, jednak nie tak ekscytująca jak możliwość zmiany jej parametrów. OpenGL nie udostępnia w tym celu jednej uniwersalnej funkcji glSetO , lecz wiele bardziej wy specjalizowanych funkcji, które będą przedstawiane stopniowo. Często przy sprawdzeniu bieżących parametrów maszyny OpenGL trzeba jedynie uzy skać informację, czy dana jej właściwość została aktywowana. Można zrobić to za po mocą funkcji g lG e t( ), ale wygodniej będzie zastosować funkcję gl Is E n a b le d ( ) o nastę pującym prototypie: GLboolean g l IsEnablecKGLenum ca p );
Parametr funkcji gl IsEnabl e d ( ) może mieć jedną z podanych poniżej wartości. Funkcja g l Is E n a b le d () zwraca wartość GL_TRUE, gdy dana właściwość maszyny jest aktywna, a wartość GL__FAL.SE, gdy jest odwrotnie. Podobnie jak w przypadku funkcji gl Get (), także i tym razem poszczególne wartości omawiane będą podczas pierwszego ich wykorzystania. GL_ALPHA_TEST GL_AUT0_N0RMAL GL_BLEND GL_CLIP_PLANEn ( g d z ie n je s t w a rto ś c ią z p r z e d z ia łu o d O d o GL_MAX_CLIP_PANES - 1) GL_C0L0R_ARRAY GL_C0L0R_L0GIC_0P GL_COLOR_MATERIAL GL_CULL_FACE GL_DEPTH_TEST GL_DITHER GL_F0G GL_INDEX_ARRAY GL_INDEX_L0GIC_0P GL LIGHTn (g d z ie n je s t w a rto ś c ią z p r z e d z ia łu o d 0 d o GL_MAX_LIGHTS - 1) GL_LIGHTING GL_LINE_SM00TH GL_LINE_STIPPLE GL_L0GIC_0P GL_MAP1_C0L0R_4 GL_MAP1_INDEX GL_MAP1_N0RMAL GL_MAP1_TEXTURE_C00RD_1 GL_MAP1_TE XTURE_C00RD_2 GL_MAP1_TEXTURE_COORD_3 GL_MAP1_TEXTURE_C00RD_4 GL_MAP1_VERTEX_3 GL_MAP1_VERTEX_4 GL_MAP2_C0L0R_4 GL_MAP2_INDEX GL_MAP2_N0RMAL GL_MAP2_TE XTURE_C00RD_1 GL_MAP2_TEXTURE_C00RD_2 GL_MAP2_TEXTURE_C00RD_3 GL_MAP2_TEXTURE_C00RD_4 GL_MAP2_VERTEX_3 GL_MAP2_VERTEX_4 GL_NORMAL_ARRAY GLJIORMALIZE GL_P0INT_SM00TH GL POLYGON OFFSET FILL
Rozdział 4 . ♦ M aszyna stanów OpenGL i podstaw ow e elem enty grafiki
99
GL_POLYGON_OFFSET_LINE GL_P0LYG0N_0FFSET_P0INT GL_P0LYG0N_SM00TH GL_POLYGON_STIPPLE GL_SCISSOR_TEST GL_STENCIL_TEST GL_TEXTURE_1D GL_TEXTURE_2D GL_TEXTURE_COORD_ARRAY GL_TEXTURE_GEN_Q GL_TEXTURE_GEN_R GL_TEXTURE_GEN_S GL_TEXTURE_GEN_T GL_VERTEX_ARRAY
Najczęściej będą wykorzystywane właśnie funkcje glGet () i gl IsEnablecK), ale istnieje także wiele innych funkcji stanu (właściwie większość funkcji OpenGL ma pewien wpływ na stan maszyny OpenGL). W rozdziale tym przedstawionych zostanie kilka z nich, a większość pozostałych w kolejnych rozdziałach.
Podstawowe elementy grafiki Przez podstawowe elementy grafiki należy rozumieć punkty, odcinki, trójkąty i tym po dobne. Tworzona grafika będzie składać się z tysięcy takich elementów, dlatego też trzeba zapoznać się ze sposobem ich działania. Przed omówieniem poszczególnych typów ele mentów podstawowych przedstawić trzeba najpierw kilka funkcji OpenGL, które będą używane bardzo często. Pierwszą z nich będzie gl Begi n () posiadająca poniższy prototyp: v o id g l B eg in (GLenum mode);
Funkcja ta przekazuje maszynie OpenGL informację o tym, że rozpoczęło się tworzenie grafiki oraz informację o typie wykorzystywanych elementów podstawowych. Typ ten określa się za pomocą parametru mode, który przyjmować może wartości przedstawione w tabeli 4.1. Tabela 4.1. Wartości parametru funkcji glBegin Wartość
Znaczenie
GL_P0INTS
Pojedyncze punkty
GLLINES
Odcinki
GL_LINE_STRIP
Sekwencja połączonych odcinków
GL_LINE_L00P
Zamknięta sekwencja połączonych odcinków
GL_TRIANGLES
Pojedyncze trójkąty
GL_TRIANGLE_STRIP
Sekwencja połączonych trójkątów
GL_TRIANGLE_FAN
Sekwencja trójkątów posiadających jeden wspólny wierzchołek
GL_QUADS
Czworokąty
GL_QUAD_STRIP
Sekwencja połączonych czworokątów
GL_P0LYG0N
Wielokąty o dowolnej liczbie wierzchołków
100
Część II ♦ Korzystanie z OpenGL
Pozostała część tego rozdziału poświęcona będzie szczegółowemu omówieniu poszcze gólnych typów elementów podstawowych. Każdemu wywołaniu funkcji g lB e g in O musi towarzyszyć wywołanie funkcji glE n d O o następującym prototypie: v o id g lE n d O ;
Funkcja gl End( ) nie posiada parametrów. Powiadamia ona maszynę OpenGL, że zakoń czone zostało tworzenie grafiki z wykorzystaniem elementów określonych za pomocą wywołania funkcji g lB e g in O . Należy pamiętać o tym, że pary wywołań funkcji g lB e g in O i g lE n dO nie mogą być zagnieżdżane, czyli nie wolno wywoływać tych funkcji wewnątrz bloku tworzonego przez inną parę wywołań gl Begin () i gl E nd( ). Wewnątrz bloku tworzonego przez parę wywołań funkcji g lB e g in O i glE n d O można wywoływać jedynie następujące funkcje OpenGL i ich wersje: g lV e rte x O , g lC o lo rO , g lln d e x O , gl Normal O , glTexC o ordO , g lE va lC o o rd O , g lE v a lP o in tO , g lM a te r ia lO , g lE d g e F la g O , g lC a llL is t O i g lC a llL is t s O . Funkcje te omówione zostaną szczegó
łowo w dalszej części rozdziału. Wywołanie dowolnej innej funkcji spowoduje błąd ma szyny OpenGL. Zanim nastąpi omówienie typów elementów podstawowych, trzeba przedstawić przy najmniej funkcję, a raczej rodzinę funkcji, g lV e rte x O . Funkcje te wywoływane są we wnątrz bloku wywołań funkcji gl Begi n ( ) i gl End O w celu określenia punktu w przestrzeni, którego interpretacja zależy od wartości parametrów wywołania funkcji g lB e g in O . Istnieje wiele wersji funkcji gl V e rte x ( ), które można opisać za pomocą poniższego schematu: v o id g l V e r t e x [ 2 , 3 , 4 ] [ d , f , i . s ] [ v 3 ( . . . ) ;
Cyfra w nazwie funkcji określa liczbę wymiarów, następująca po niej litera typ danych (double, f lo a t , i n t lub s h o rt). Nazwę funkcji może kończyć litera v, co oznacza, że pa rametry funkcji zostaną przekazane za pomocą tablicy (wektora) zamiast pojedynczo. Liczba i typ tych parametrów określona jest przez nazwę funkcji. Najczęściej korzysta się z wersji g lV e r te x 3 f( ), której parametrami są trzy wartości typu f lo a t opisujące współrzędne x ,y i z punktu w przestrzeni. Teraz nastąpi omówienie typów podstawowych elementów grafiki, które będą używane najczęściej.
Tworzenie punktów w trójwymiarowej przestrzeni Nie ma prostszego elementu grafiki niż pojedynczy punkt. Rysowanie punktu jest pro ste, a zarazem stwarza duże możliwości tworzenia dowolnej grafiki: g 1Begi n ( GLPOINTS) g lV e rte x (0 .0 , 0 .0 , 0 .0 ) ; g lE n d O ;
Pierwszy wiersz powyższego fragmentu kodu informuje maszynę OpenGL, że będą ry sowane punkty. Kolejny wiersz rysuje pojedynczy punkt w środku układu współrzęd nych. Ostatni z wierszy informuje, że zakończone zostało rysowanie punktów. Wcięcia
Rozdział 4 . ♦ M aszyna stanów OpenGL i podstaw ow e elem enty grafiki
101
wierszy kodu wewnątrz bloku wywołań funkcji glBeginO i glEndO stanowią jedynie konwencję zapisu kodu OpenGL i nie są obowiązkowe, choć zdecydowanie poprawiają czytelność kodu. A co w przypadku, gdy ma zostać narysowany kolejny punkt, na przykład o współrzęd nych (0.0, 1.0, 0.0)? Można oczywiście zaprogramować to jak poniżej: g l Begi n(GL_P0INTS) g lV e rte x ( 0 .0 , 0 .0 , 0 .0 ) ; g lE n d O ; glBegin(GL_POINTS) g lV e rte x (0 .0 , 1 .0 , 0 .0 ) ; g lE nd O ;
Jednak taki sposób rysowania punktów jest wyjątkowo nieefektywny. Wewnątrz poje dynczego bloku wywołań funkcji glBeginO i glEndO można rysować wiele punktów i wobec tego kod będzie miał następującą postać: g lB eg i n(GL_P0INTS) g lV e rte x (0 .0 , 0 .0 , 0 .0 ) ; g lV e rte x ( 0 .0 , 1 .0 , 0 .0 ) ; g lE n d O ;
Krócej, szybciej i lepiej. Wewnątrz bloku wywołań funkcji glBeginO i glEndO można wywoływać funkcję gl Vertex() dowolną liczbę razy, a każde wywołanie spowoduje na rysowanie pojedynczego punktu. OpenGL jest zaopatrzony w funkcje umożliwiające modyfikację sposobu rysowania podstawowych elementów grafiki. W przypadku punktów można zmieniać ich wielkość oraz określać, czy podczas rysowania powinien być stosowany antialiasing.
Zmiana rozmiaru punktów Aby zmienić rozmiar rysowanych punktów, należy skorzystać z następującej funkcji: v o id g lP o in tS iz e (G L flo a t s iz e ) ;
Domyślny rozmiar punktów posiada wartość 1. 0. Jeśli stosowanie antialiasingu jest wyłą czone (a tak jest domyślnie), to rozmiar punktu jest zaokrąglany do najbliższej wartości całkowitej (wartości z przedziału od 0 . 0 do 1 . 0 są zawsze zaokrąglane do 1 . 0). Bieżącą wielkość rysowanych punktów można pobrać za pomocą funkcji glGetO, której prze kazana zostanie wartość GL_POINT_SIZE.
Antialiasing Mimo że parametry elementów grafiki można określać z ogromną dokładnością korzy stając z wartości zmiennoprzecinkowych, to jednak na ekranie pozostaje do dyspozycji jedynie skończony zbiór pikseli. W efekcie elementy tworzonej grafiki posiadają „schodkowane” krawędzie. Antialiasing umożliwia ich wygładzenie. Włącza się go przekazu jąc wartość GL_P0INT_SM00TH funkcji g lE n a b le O (a wyłącza przekazując tę samą war tość funkcji g lD is a b le O ). O tym, czy antialiasing jest włączony w danym momencie, można przekonać się przekazując wartość GL_P0INT_SM00TH funkcji g lG e tO lub wywo łuj ąc g 11s Ena bl ed ( GL_P01NTJMOOTH).
102
Część II ♦ Korzystanie z OpenGL
Jeśli włączy się antialiasing, to przestają być dostępne dowolne wielkości punktów. Specyfikacja OpenGL wymaga obecnie od implementacji, by antialiasing był dostępny dla punktów o wielkości 1.0. Jeśli zażąda się wielkości punktu, która nie jest dostępna w danej implementacji przy włączonym antialiasingu, to wielkość ta zostanie zaokrą glona do najbliższej wielkości, dla której możliwy jest antialiasing. Aby uzyskać infor mację o przedziale wielkości punktów dostępnych przy włączonym antialiasingu, nale ży wywołać funkcję glGetO przekazując jej wartość GL_P01NT_SIZE_RANGE. Natomiast wartość GL POINT SIZE GRANULARITY pozwoli poznać różnicę kolejnych wielkości z tego przedziału. Ilustruje to poniższy fragment kodu: G L flo a t s iz e s [ 2 ] ; G L flo a t g r a n u la r ity ; glGetFloatv(GL_POINT_SIZE_RANGE, s iz e s ) G L flo a t m in P o in tS ize = s iz e s [ 0 ] ; G L flo a t m axPointSize = s i z e s f l ] ; glGetFloatv(GL_POINT_SIZE_GRANULARITY, & g r a n u la r ity ) ;
Włączony antialiasing spowoduje, że współrzędne rysowanego punktu zostaną po traktowane jako środek koła, którego średnica będzie równa wielkości punktu. Sposób wypełnienia pikseli leżących na granicy koła zależeć będzie od stopnia ich pokrycia przez koło. Wiadomo już prawie wszystko o punktach, można przejść zatem do omówienia bardziej interesujących elementów grafiki.
Odcinki Rysowanie odcinka nie różni się od rysowania pary punktów, poznanego już w po przednim podrozdziale: glB egi n(G LLIN ES) g lV e rte x ( - 2 .0 , -1 .0 . 0 .0 ); g lV e rte x (3 .0 , 1 .0 . 0 .0 ) ; g lE n d O ;
Tym razem przekazano funkcji gl Begi n () wartość GL LINES, aby poinformować maszynę OpenGL, że tworzone punkty mają być interpretowane jako końce odcinków. Utworzenie drugiego z punktów powoduje wykreślenie odcinka łączącego oba punkty. Podobnie jak w przypadku punktów wewnątrz bloku wywołań funkcji gl Begi n () i gl End() można rysować wiele odcinków. Każde kolejne dwa punkty traktowane są jako końce nowego odcinka. Jeśli w bloku tym nie stworzy się parzystej liczby punktów, to ostatni z nich zostanie zignorowany. OpenGL pozwala zmieniać szerokość odcinków, włączać i wyłączać antialiasing oraz określać wzór linii.
Rozdział 4 . ♦ M aszyna stanów OpenGL i podstaw ow e elem enty grafiki
103
Zmiana szerokości odcinka Domyślnie szerokość odcinka posiada wartość 1.0. Można zmienić ją za pomocą funkcji gl Li neWi dth () o następującym prototypie: v o id g lL i neWi d th ( G L flo a t w id th ) ;
Informację o bieżącej szerokości linii tworzonych odcinków uzyskuje się przekazując funkcji glGet() wartość GL_LINE_WIDTH.
Antialiasing odcinków Antialiasing działa w przypadku odcinków tak samo jak dla punktów. Można włączać go i wyłączać przekazując wartość GL_LINE_SM00TH funkcjom gl Enabl e ( ) i gl Di sabl e (), a bieżący stan określić przekazując tę wartość funkcjom glGet O lub gl Is Enabl ed(). Antialiasing odcinków jest domyślnie wyłączony. Podobnie jak w przypadku punktów specyfikacja OpenGL wymaga obecnie od imple mentacji, by antialiasing był dostępny dla odcinków o szerokości linii równej 1.0. Aby uzyskać informacje o rzeczywistych parametrach antialiasingu odcinków dla danej im plementacji, korzysta się z wartości GL_LINE_WIDTH_RANGE i GL_LINE_WIDTH_GRANULARITY. Ilustruje to poniższy przykład: G L flo a t s iz e s [2 ]; G L flo a t g r a n u la r ity ; g lG e tF lo a tv ( GL_LI NE_WI DTH_RANGE. s iz e s ) G L flo a t m inLineW idth = s iz e s [0 ] ; G L flo a t maxLineW idth = s i zes[1 ] ; g lG e tF lo a tv (G L _ LINE_WI DTH_GRANULARITY. S g r a n u la r ity ) ;
Prawda, że wygląda on prawie identycznie jak w przypadku punktów?
Wzór linii odcinka OpenGL pozwala określić wzór linii rysowanych odcinków. Wzór ten opisuje, które z punktów linii są rysowane i umożliwia w ten sposób tworzenie linii przerywanych. Przed użyciem wzoru linii trzeba najpierw włączyć możliwość stosowania wzorów przekazując funkcji gl Enabl e() wartość GL_LINE_STIPPLE. Następnie określa się wzór linii korzystając z funkcji gl Li neSti ppl e ( ) posiadającej następujący prototyp: v o id g lL i ne S ti pple(G Li n t fa c to r , GLushort p a tte r n );
Parametr factor posiada domyślną wartość 1 i może przyjmować wartości z przedziału od 1 do 256. Określa on, ile razy każdy bit wzorca zostanie powtórzony, zanim wyko rzystany zostanie następny bit wzorca. Parametr pattern stanowi wzorzec linii zapisany za pomocą 16 bitów. Bity posiadające wartość 1 określają, które piksele będą rysowane. Należy przy tym zwrócić uwagę, że bity te wykorzystywane są w odwrotnej kolejności, czyli najpierw wykorzystywane są bity mniej znaczące. Ilustruje to rysunek 4.1.
104
Część II ♦ Korzystanie z OpenGL
Rysunek 4.1. Przykład interpretacji kolejnych bitów wzorca linii
1 1 1 1 1 0 1 0 1 1 1 1 1 0 1 0
Poniższy fragment kodu włącza korzystanie ze wzorców i określa wzorzec składający się na przemian z kresek i kropek: glEnable(GL_LINE_STIPPLE); GLushort s tip p le P a tte rn = OxFAFA; g lL in e S tip p le ( 2 . s tip p le P a tt e r n ) ;
Bieżący wzorzec linii odcinka oraz jego wielokrotność powtórzeń można określić prze kazując funkcji g l G et( ) wartości GL_LINE_STIPPLE i GL_LINE_STIPPLE_REPEAT. W kolejnym podrozdziale zostanie omówiony podstawowy element umożliwiający two rzenie wirtualnego świata gier trójwymiarowych: wielokąt.
Wielokąty Za pomocą punktów i odcinków można stworzyć całkiem interesującą grafikę. Jeszcze bardziej efektywne tworzenie trójwymiarowych światów umożliwiają jednak wielokąty, którym poświęcona zostanie pozostała część bieżącego rozdziału. Zanim nastąpi omó wienie poszczególnych rodzajów wielokątów (to jest trójkątów, czworokątów i pozo stałych wielokątów), trzeba najpierw przedstawić kilka aspektów tworzenia wielokątów. Wielokąt tworzy się określając sekwencję jego wierzchołków w przestrzeni. Jego ob szar zostaje następnie wypełniony określonym kolorem. Taki jest przynajmniej domyśl ny sposób tworzenia wielokątów. Oczywiście maszyna stanów OpenGL umożliwia jego modyfikację. W tym celu stosuje się następującą funkcję: v o id glPolygonMode(GLenum fa c e . GLenum mode);
OpenGL umożliwia osobne traktowanie obu stron wielokąta. Dlatego też wywołując funkcję gl PolygonModeC) trzeba określić za pomocą wartości parametru face, której strony dotyczą modyfikacje. Wartość GL FRONT określa przednią stronę wielokąta, GL BACK tylnią, a GL FRONT AND BACK informuje, ze modyfikacje dotyczyć będą obu stron wielokąta. Parametr mode może natomiast przyjmować jedną z wartości przedstawionych w tabeli 4.2. Jeśli konieczne jest na przykład to, by wypełniane były jedynie przednie powierzchnie wielokątów, a tylne posiadały jedynie krawędzie, to trzeba zastosować poniższy frag ment kodu: glPolygonM ode( GL_FR0NT. GL_FI L L ) ; glPolygonM ode (GL_BACK, G L LIN E );
Rozdział 4 . ♦ M aszyna stanów OpenGL i podstaw ow e elem enty grafiki
105
Tabela 4.2. Tryby rysowania wielokątów Wartość
Znaczenie
GLPOINT
Rysowane są jedynie wierzchołki wielokąta jako pojedyncze punkty, a sposób rysowania wierzchołków określony jest przez funkcje stanu dla rysowania punktów; tryb ten równoważny jest z wywołaniem funkcji gl Begi n ( ) z parametrem GL_P0INTS
G LLINE
Rysowane są jedynie krawędzie wielokąta jako zbiór odcinków, a sposób rysowania krawędzi określony jest przez funkcje stanu dla rysowania odcinków; tryb ten równoważny jest z wywołaniem funkcji gl Begi n ( ) z parametrem GL_L00P
GL FILL
Domyślny tryb rysowania wielokątów (wypełnia obszar wielokąta); jedynie w tym trybie ma zastosowanie wzór wielokąta i antialiasing wielokątów (patrz: kolejne podrozdziały)
Ponieważ domyślnie wielokąty są wypełniane, to pierwszy z wierszy powyższego frag mentu kodu nie jest konieczny, chyba że sposób rysowania przedniej strony wielokąta zmienił wcześniej inny fragment kodu. Aby uzyskać informację o bieżącym trybie rysowania wielokątów, wywołuje się funk cję g lG et () z parametrem GL_P0LYG0N_M0DE.
Ukrywanie powierzchni wielokątów Chociaż wielokąty są nieskończenie płaskie, to jednak posiadają dwie strony, z których może oglądać je obserwator. Czasami wymagane jest, by obie strony wielokąta były pre zentowane w różny sposób i właśnie dlatego wiele funkcji modyfikujących sposób wy świetlania wielokątów wymaga określenia, której ze stron dotyczą wprowadzane zmiany. Obie strony wielokąta tworzone są zawsze oddzielnie. Czasami pojawia się sytuacja, w której wiadomo, że obserwator nie będzie mógł nigdy oglądać jednej ze stron wielo kąta. Ze względu na efektywność tworzenia grafiki nie ma wtedy sensu rysowanie nie widocznej strony wielokąta. Gdy na przykład wielokąty tworzą zamkniętą bryłę w kształ cie kuli, to wnętrze jej nie będzie nigdy widoczne. Można wtedy poinformować maszynę OpenGL, że rysowanie jednej strony wielokątów nie jest konieczne. W tym celu trzeba najpierw aktywować taką możliwość przekazując wartość GL CULL FACE funkcji glEnabl e ( ). Następnie należy określić, która ze stron nie powinna być rysowana. Służy do tego funkcja g l Cul 1F a ce ( ) o następującym prototypie: v o id g lC u llFace(GLenum mode);
Parametr mode może przyjmować jedną z omówionych wcześniej wartości GL_FR0NT, GL_BACK lub GL_FRONT_AND_BACK. Wykorzystanie wartości GL_FRONT_AND_BACK nie ma zwy kle sensu, bo w efekcie wielokąty nie są w ogóle rysowane. Domyślnie przyjmowana jest wartość GL_BACK. Maszyna OpenGL musi dysponować sposobem pozwalającym określić, która ze stron wielokąta jest przednia, a która tylna. W tym celu wykorzystywany jest porządek, w ja kim określone zostały wierzchołki wielokąta. Definiowanie wierzchołków wielokąta można rozpocząć od dowolnego wierzchołka, po czym podaje się kolejne wierzchołki posuwając się zgodnie z kierunkiem ruchu wskazówek zegara lub w kierunku odwrot nym. Ważne jest, aby tworząc grafikę zachować ten sam sposób definiowania wieloką tów, ponieważ jedynie wtedy OpenGL będzie mógł prawidłowo rozróżnić strony wielo kątów. Domyślnie przyjmuje się, że przednią stroną wielokąta jest ta strona, z której
106
Część II ♦ Korzystanie z OpenGL
oglądając wielokąt wierzchołki zostały zdefiniowane w kierunku przeciwnym do obrotu wskazówek zegara. Założenie to można jednak zmienić korzystając z funkcji g lF ro n t F ace ( ) posiadającej poniższy prototyp: v o id glFrontFace(GLenum mode);
Parametr mode może przyjmować wartość GL_CCW określającą kierunek przeciwny do kierunku ruchu wskazówek zegara i wartość GL CW określającą kierunek zgodny z kie runkiem ruchu wskazówek zegara.
Ukrywanie krawędzi wielokątów Przy tworzeniu grafiki trójwymiarowej często zachodzi potrzeba prezentacji szkieletu obiektów. W takim przypadku czasami konieczne jest to, aby niektóre z krawędzi wie lokątów nie były rysowane. Na przykład przy rysowaniu kwadratu za pomocą dwu trój kątów powinna zostać ukryta przekątna kwadratu. Problem ten i jego rozwiązanie ilu struje rysunek 4.2. Rysunek 4.2. Ukrywanie krawędzi wielokątów
Korzystając z funkcji g lE dge F lagO można poinformować maszynę OpenGL, czy dana krawędź wielokąta powinna być rysowana. Wywołanie funkcji glE dge F lagO może przyj ąć j edną z poniższych form: vo id glEdgeFlag(G Lboolean isE d g e ); vo id glE d g e F la g (co n st GLboolean *is E d g e );
Jedyną różnicą pomiędzy nimi jest to, że parametrem pierwszej z nich jest pojedyncza wartość logiczna, a parametrem drugiej wskaźnik tablicy zawierającej pojedynczą war tość logiczną (projektanci OpenGL musieli mieć jakiś powód, by przekazywać poje dynczą wartość za pomocą tablicy, ale nie jest on znany). Jeśli wartością tą będzie GL_ TRUE (wartość domyślna), to krawędź zostanie narysowana. Wartość GL_FALSE zapobiega rysowaniu krawędzi.
Antialiasing wielokątów Podobnie jak w przypadku punktów i odcinków można także wygładzać krawędzie wielokątów stosując antialiasing. Antialiasing krawędzi wielokątów włącza i wyłącza się przekazując wartość GL_P0LYG0N_SM00TH funkcjom g lE n a b le O i g lD is a b le O . Jak zwy kle informację o bieżącym stanie antialiasingu można uzyskać przekazując tę wartość funkcji g lG e tO lub g l IsE nablecK ). Ze względów efektywnościowych antialiasing kra wędzi wielokątów jest domyślnie nieaktywny.
Rozdział 4 . ♦ M aszyna stanów OpenGL i podstawowe elem enty grafiki
107
Wzór wypełnienia wielokątów Ostatnią z ogólnych właściwości wielokątów jest możliwość określenia wzoru ich wy pełnienia. Przypomina ona w ogólnym działaniu omówioną wcześniej możliwość okre ślania wzoru linii odcinków. Zamiast wypełniać wielokąt jednolitym kolorem, maszyna OpenGL może użyć zdefiniowanego przez programistę wzoru. Możliwość ta jest do myślnie nieaktywna. Aby z niej skorzystać, trzeba więc ją najpierw aktywować przeka zując wartość GL_P0LYG0N_STIPPLE funkcji g lE n a b le O . Następnie należy przekazać ma szynie wzór wypełnienia korzystając z poniższej funkcji: v o id g lP o ly g o n S tip p le (c o n s t GLubyte *m ask);
Parametr mask jest wskaźnikiem do tablicy zawierającej wzorzec o rozmiarach 32x32 bity. W przeciwieństwie do bitów wzorca linii odcinka, które używane były w odwrot nej kolejności, bity wzorca wypełnienia stosowane są w naturalnej kolejności ich po jawiania się. Trzeba zwrócić uwagę, że wzór wypełnienia definiowany jest na płasz czyźnie. Dlatego też obrót wielokąta nie powoduje równoczesnego obrotu wzorca jego wypełnienia. Na tym można zakończyć omówienie wspólnych właściwości wielokątów. Przedsta wione teraz zostaną poszczególne rodzaje wielokątów dostępne w OpenGL.
Trójkąty Trójkąty stanowią rodzaj wielokątów szczególnie zalecany przy tworzeniu grafiki trój wymiarowej. Decydują o tym następujące właściwości trójkątów: ♦ wierzchołki trójkąta leżą zawsze na jednej płaszczyźnie (inaczej mówiąc, trzy punkty definiują zawsze płaszczyznę); ♦ trójkąty nie mogą być wklęsłe; ♦ krawędzie jednego trójkąta nie mogą się wzajemnie przecinać. Jeśli spróbuje się narysować wielokąt, który będzie posiadał jedną z właściwości, któ rych nie mają trójkąty, to efekt takiej operacji okaże się trudny do przewidzenia. Stoso wanie wyłącznie samych trójkątów nie ogranicza w żaden sposób możliwości tworzenia grafiki, ponieważ każdy wielokąt można rozłożyć na wiele trójkątów. Narysowanie trójkąta wymaga utworzenia jego wierzchołków i w ogólnym zarysie przy pomina tworzenie punktów czy odcinków. Trzeba jedynie zmienić wartość przekazy waną na wstępie funkcji glBegin(): g l B eg in (GL_TRIANGLES); g lV e rte x ( - 2 .0 . -1 .0 , 0 .0 ); g lV e rte x (3 .0 . 1 .0 , 0 .0 ) ; g lV e rte x (0 .0 , 3 .0 , 0 .0 ); glE ndO ;
Podobnie jak w przypadku punktów lub odcinków wewnątrz jednego bloku wywołań funkcji glBeginO i glEndO można rysować wiele trójkątów. Jeśli liczba zdefiniowa nych wierzchołków nie będzie dzielić się bez reszty przez 3, to dodatkowy wierzchołek lub wierzchołki zostaną zignorowane.
108
Część II ♦ Korzystanie z OpenGL
OpenGL dostarcza także specjalnych mechanizmów mogących poprawić efektywność tworzenia trójkątów w pewnych sytuacjach. Przykład jednej z nich prezentuje rysunek 4.3. Rysunek 4.3. Dwa trójkąty posiadające wspólne wierzchołki
Ukazane na nim trójkąty posiadają wspólne wierzchołki A i C. Rysując te trójkąty w zwy kły sposób (czyli przekazując ftmkcji g lB e g in O wartość GL TRIANGLE) trzeba określić łącznie 6 wierzchołków (A, B i C dla trójkąta 1 oraz A, D i C dla trójkąta 2). Wierzchołki A i C będą więc niepotrzebnie przetwarzane dwa razy. Takie marnotrawstwo może po jawić się na dużo większą skalę niż przedstawiono na rysunku. Skomplikowane układy trójkątów będą wtedy posiadać wiele wspólnych wierzchołków. Jeśli ograniczy się wielo krotne przetwarzanie tych samych wierzchołków, to uzyskać można poprawę efektyw ności tworzenia grafiki. Jednym ze sposobów ograniczenia wielokrotnego przetwarzania wspólnych wierzchoł ków są łańcuchy trójkątów. Tworząc taki łańcuch trzeba na wstępie przekazać funkcji g lB e g in O wartość GL_TRIANGLE_STRIP, a następnie zdefiniować sekwencję wierzchoł ków. OpenGL zinterpretuje ją następująco: z pierwszych trzech wierzchołków utworzy trójkąt, a każdy następny wierzchołek będzie tworzyć trójkąt z dwoma poprzednimi. W ten sposób dla każdego następnego trójkąta przetwarzany będzie tylko pojedynczy wierz chołek! W ogólnym przypadku tworząc łańcuch n trójkątów można w ten sposób ogra niczyć liczbę przetwarzanych wierzchołków z 3n do n + 2. Podobną ideę wykorzystują wieńce trójkątów. Tworzą one sekwencje trójkątów wokół wspólnego centralnego wierzchołka. Rysując wieniec trójkątów trzeba najpierw przeka zać funkcji g lB e g in O wartość GL_TRIANGLE_FAN. Następnie definiuje się centralny wierzchołek wieńca, a po nim podaje się pary punktów tworzących z nim kolejne trój kąty. Podobnie jak w przypadku łańcuchów trójkątów, także i wieńce pozwalają nary sować n trójkątów podając jedynie n + 2 wierzchołków. Jednak zwykle liczba trójkątów tworzących wieniec będzie mniejsza niż w przypadku łańcuchów, ponieważ wieniec wymaga, by wszystkie trójkąty posiadały wspólny wierzchołek. Podstawowy problem związany ze stosowaniem łańcuchów i wieńców trójkątów polega na zidentyfikowaniu tych struktur w tworzonej grafice. Jest to dość proste dla nieskom plikowanych modeli, ale trudność tego zadania wzrasta wraz z ich złożonością. Omó wienie efektywnych sposobów identyfikacji takich struktur w modelach przekracza możliwości niniejszego rozdziału. Zresztą nie będą one tak często potrzebne, ponieważ dla ograniczenia wielokrotnego przetwarzania wierzchołków i tym samym poprawienia efektywności tworzenia grafiki wprowadza się struktury zwane siatkami.
Czworokąty Czworokąty rysuje się za pomocą metody gl Begi n () dla wartości GL_QUADS, a następnie de finiuje się cztery lub więcej wierzchołków. Podobnie jak w przypadku trójkątów można rysować wiele czworokątów wewnątrz jednego bloku wywołań funkcji gl Begi n ( ) i gl End().
Rozdział 4 . ♦ M aszyna stanów OpenGL i podstaw ow e elem enty grafiki
109
OpenGL umożliwia poprawę efektywności tworzenia wielu czworokątów przez zasto sowanie łańcuchów. Wykorzystanie łańcuchów czworokątów sygnalizuje się za pomocą funkcji gl Begi n ( ), której przekazuje się wartość GL_QUAD_STRIP. Dzięki temu kolejne czwo rokąty definiuje się podając jedynie parę wierzchołków.
Dowolne wielokąty OpenGL umożliwia także tworzenie wielokątów o dowolnej liczbie wierzchołków. Jed nak w takim przypadku wewnątrz bloku ograniczonego wywołaniami funkcji gl Begi n () i gl E nd( ) można narysować tylko jeden taki wielokąt. Funkcji gl Begi n() przekazuje się wartość GL POLYGON. Wywołanie funkcji g lE ndO powoduje automatyczne zamknięcie wielokąta, czyli połączenie ostatniego wierzchołka z pierwszym. Jeśli poda się mniej niż 3 wierzchołki, to oczywiście nie zostanie narysowany żaden wielokąt.
Przykład wykorzystania podstawowych elementów grafiki Omówione dotąd zostały wszystkie podstawowe elementy grafiki dostępne w OpenGL. Sposoby tworzenia tych elementów oraz modyfikacji ich właściwości zilustrowane zo stały fragmentami kodu. Pora zebrać te wiadomości w jedną całość i zaprezentować ich rezultaty. Kod program demonstrującego zagadnienia omówione w tym rozdziale znaj duje się na dysku CD. Jeden z efektów jego działania pokazuje rysunek 4.4. Program umożliwia tworzenie wybranych za pomocą klawiatury elementów grafiki oraz modyfi kację sposobu ich rysowania. Dostępne komendy podano w tabeli 4.3. Rysunek 4.4. Łańcuch trójkątów narysowany przez program demonstracyjny
Tabela 4.3. Komendy programu demonstracyjnego Klawisz
Akcja
Klawisz
Akcja
1
Rysuje punkty
6
Rysuje czworokąty
2
Rysuje odcinki
7
Rysuje wielokąt
3
Rysuje trójkąty
A
Włącza i wyłącza antialiasing
4
Rysuje łańcuch trójkątów
S
Włącza i wyłącza wzorce linii i wypełnienia
5
Rysuje wieniec trójkątów
P
Przełącza tryby tworzenia wielokątów
Warto dogłębnie zapoznać się z kodem źródłowym tego programu oraz spróbować zmo dyfikować jego działanie. Dobre zrozumienie sposobów tworzenia podstawowych ele mentów grafiki będzie niezbędne podczas tworzenia każdego z kolejnych programów w tej książce.
110
Część II ♦ Korzystanie z OpenGL
Podsumowanie W rozdziale tym przekazano podstawowe informacje o maszynie stanów OpenGL. Za prezentowano sposób pobierania wartości i jej parametrów za pomocą funkcji glGetO i gl Is E n a b le d ( ). Omówiono także szereg specjalizowanych funkcji umożliwiających mo dyfikację wartości tych parametrów. Pozostałe aspekty maszyny stanów przedstawione będą w kolejnych rozdziałach. W rozdziale omówiono także podstawowe elementy grafiki i sposoby modyfikacji ich właściwości. Przedstawiono sposoby tworzenia punktów, odcinków, trójkątów i innych rodzajów wielokątów.
Rozdział 5.
Przekształcenia układu współrzędnych i macierze OpenGL W rozdziale tym porzucona na chwilę zostanie problematyka tworzenia obiektów gra ficznych na korzyść zagadnień związanych z ich ruchem. Ruch stanowi istotny składnik trójwymiarowych światów gier — bez niego byłyby one statyczne, nudne i pozbawione możliwości interakcji. OpenGL umożliwia łatwe przemieszczanie obiektów w trójwy miarowym świecie za pomocą przekształceń układu współrzędnych, które omówione zostaną w tym rozdziale. Przedstawione także zostaną sposoby wykorzystania własnych macierzy w OpenGL, które często stosowane są do realizacji przekształceń związanych z efektami specjalnymi w grach. W bieżącym rozdziale przedstawione zostaną: ♦ podstawowe przekształcenia układu współrzędnych; ♦ przekształcenia kamery i widoku; ♦ macierze OpenGL i stosy macierzy; ♦ rzutowanie; ♦ wykorzystanie własnych macierzy w OpenGL.
Przekształcenia układu współrzędnych Przekształcenia pozwalają nam przemieszczać, obracać i manipulować obiektami wirtu alnego świata. Ważnym zastosowaniem przekształceń jest także rzutowanie trójwymia rowej przestrzeni na płaszczyznę ekranu. W rozdziale 3. przedstawione zostały teore tyczne podstawy przesunięcia, obrotu i skalowania. Chociaż wydaje się, że przekształcenia te modyfikują bezpośrednio dany obiekt, to jednak w rzeczywistości przekształcają jedy nie jego układ współrzędnych. Gdy na przykład obróci się układ współrzędnych obiektu, to reprezentacja obiektu na ekranie także zostanie obrócona.
112
Część II ♦ Korzystanie z OpenGL
Podczas tworzenia trójwymiarowej grafiki wierzchołki obiektów poddawane są trzem rodzajom przekształceń, zanim zostaną narysowane na ekranie: ♦ przekształceniom widoku, które określają położenie kamery; ♦ przekształceniom modelowania, które przemieszczają obiekty na scenie; ♦ przekształceniom rzutowania, które definiują bryłę widoku oraz płaszczyzny obcięcia. Stosowane jest także przekształcenie okienkowe, które odwzorowuje dwuwymiarowy rzut sceny w oknie na ekranie. Przekształcenie to nie dotyczy oczywiście wierzchołków trójwymiarowych obiektów, a jedynie grafiki tworzonej w oknie. W rozdziale tym omó wiony zostanie jeszcze jeden rodzaj przekształcenia: przekształcenie widoku modelu. Stanowi ono kombinację przekształceń widoku i modelowania. Tabela 5.1 zawiera ze stawienie omawianych przekształceń. Tabela 5.1. Przekształcenia OpenGL Przekształcenie
Opis
Widoku
Określa położenie kamery
Modelowania
Przemieszcza obiekty na scenie
Rzutowanie
Definiuje bryłę widoku oraz płaszczyzny obcięcia
Okienkowe
Odwzorowuje dwuwymiarowy rzut sceny w oknie
Widoku modelu
Stanowi kombinację przekształcenia widoku i przekształcenia modelowania
Przekształcenia te muszą być zawsze wykonywane w odpowiedniej kolejności. Prze kształcenie widoku musi zawsze poprzedzać przekształcenie modelowania. Rzutowanie i przekształcenie okienkowe muszą natomiast poprzedzać tworzenie grafiki na ekranie. Rysunek 5.1 ilustruje kolejność wykonywania przekształceń. Rysunek 5.1. Potok przekształceń wierzchołków
Dane o wierzchołkach (x, y, z, w)
Współrzędne okna (x, y)
Macierz modelowania
Przekształcenie okienkowe
Rozdział 5. ♦ Przekształcenia układu współrzędnych i m acierze OpenGL
113
Współrzędne kamery i obserwatora Jedną z podstawowych koncepcji związanych z przekształceniami w OpenGL jest pojęcie układu współrzędnych kamery zwanego także układem współrzędnych obserwatora. Jest to zwykły kartezjański układ współrzędnych, w którego początku umieszczono kamerę (obserwatora) i skierowano ją wzdłuż ujemnej części osi z, co pokazuje rysunek 5.2. Rysunek 5.2. Obserwator znajduje się w początku układu współrzędnych i spogląda wzdłuż ujemnej części osi z
Układ współrzędnych obserwatora nie ulega zmianom na skutek wykonywanych prze kształceń. Na przykład obracając pewien obiekt, obraca się w rzeczywistości jego układ współrzędnych względem układu współrzędnych obserwatora. Jeśli trójkąt zostanie ob rócony o kąt 45° w kierunku przeciwnym do kierunku ruchu wskazówek zegara, to ozna cza to, że obrócony w tym kierunku został układ współrzędnych tego trójkąta. Ilustruje to rysunek 5.3. Rysunek 5.3. Obrót trójkąta oznacza obrót jego układu współrzędnych względem układu współrzędnych obserwatora
Zrozumienie koncepcji układu współrzędnych jest kluczowe dla właściwego zrozumie nia przekształceń OpenGL. W kolejnych podrozdziałach przedstawione zostaną sposo by modyfikacji układów współrzędnych, w efekcie których następują przekształcenia obiektów.
114
Część II ♦ Korzystanie z OpenGL
Przekształcenia widoku Przekształcenie widoku jest zawsze wykonywane jako pierwsze i służy do określenia położenia kamery i jej skierowania. Domyślnie kamera znajduje się zawsze w początku układu współrzędnych obserwatora i skierowana jest wzdłuż ujemnej części osi z. Poło żenie i orientację kamery można zmieniać za pomocą przesunięć i obrotów, które w efek cie tworzą właśnie przekształcenie widoku. Trzeba zawsze pamiętać o tym, aby zakończyć przekształcenia widoku, zanim rozpocz nie się wykonywanie innych przekształceń, ponieważ przekształcenie widoku prze kształca bieżący układ współrzędnych względem układu współrzędnych obserwatora. Pozostałe przekształcenia odbywają się natomiast w bieżącym układzie współrzędnych. Aby wykonać przekształcenie widoku, trzeba „wyczyścić” bieżącą macierz przekształceń za pomocą funkcji gl L o a d ld e n ti t y () o następującym prototypie: vo id g lL o a d ld e n tity ( v o id ) ;
W wyniku jej wywołania bieżąca macierz staje się macierzą jednostkową. Jest to koniecz ne, ponieważ inne przekształcenia mogły zmodyfikować zawartość bieżącej macierzy. Po zainicjowaniu bieżącej macierzy tworzy się macierz przekształcenia widoku. Istnieje kilka sposobów na jej utworzenie. Jeśli pozostawi się bieżącą macierz jako macierz jed nostkową, to w rezultacie uzyskać będzie można domyślne położenie kamery w początku układu współrzędnych i domyślną jej orientację w kierunku ujemnej części osi z. Pozostałe sposoby polegają na: ♦ wykorzystaniu funkcji gl u L o o kA t( ) do określenia orientacji kamery (funkcja ta hermetyzuje zbiór odpowiednich przesunięć i obrotów); ♦ wykorzystaniu funkcji przekształceń przesunięcia g l T ransl a te * () i obrotu gl R o ta te *( )(funkcje te — omówione zostaną w dalszej części rozdziału — umożliwiają przemieszczenie obiektu w przestrzeni względem kamery); ♦ utworzeniu własnych przekształceń wykorzystujących przesunięcia i obroty we własnym układzie współrzędnych (na przykład układzie współrzędnych biegunowych w przypadku kamery okrążającej obiekt).
Wykorzystanie funkcji gluLookAt() Funkcja gl u L o o kA t( ) posiada następujący prototyp: vo id gluLookAt(G Ldouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz);
Pozwala ona określić położenie i orientację kamery. Pierwsze trzy parametry (eyex, e y ey, eyez) definiują położenie kamery. Jeśli będą one posiadać wartość 0, to kamera zo stanie umieszczona w początku układu współrzędnych. Kolejne trzy parametry (cente rx, centery, centerz) określają punkt, w który wycelowana jest kamera czyli kierunek widzenia obserwatora. Zwykle punkt ten znajduje się w pobliżu środka bieżącej sceny. Ostatnie trzy parametry (upx, upy, upz) określają kierunek „do góry”. Ilustrację znacze nia parametrów funkcji gl u L o o kA t( ) stanowi rysunek 5.4.
Rozdział 5. ♦ Przekształcenia układu współrzędnych i m acierze OpenGL
Rysunek 5.4. Parametry funkcji gluLookAtQ określają położenie i orientacją kamery
115
A (upx, upy, upz)
K
\
\1
(eyex, eyey, eyez)(centerx, centery, centerz) gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz);
Poniżej zaprezentowany został fragment kodu, który ilustruje sposób wykorzystania funk cji gl uLookAt ( ) . Jeśli pewne jego fragmenty wydają się niezrozumiałe, to nie ma powodu do obaw — wyjaśnią się wkrótce. v o id D isplayS ceneO
{
gl Cl e a r (GL_COLOR_BUFFER_BIT); / / o p ró żn ia b u fo r kolorów g lC o lo r 3 f( 1 .0 f. O.Of, O .O f); / / w ybiera k o lo r czerwony g lL o a d ld e n t ity O ; / / czy ś ci bieżącą m acierz p rze k sz ta łce ń / / wykonuje p rz e k s z ta łc e n ie widoku za pomocą fu n k c ji g lu L o o kA tO / / umieszcza kamerę w punkcie o współrzędnych (0 , 0, 10) / / i skie ro w u je ją wzdłuż ujemnej części osi z podając punkt wycelowania (0, 0. -100) / / (eyex, eyey, eyez) = (0 .0 , 0 .0 , 10.0) / / (c e n te rx , c e n te ry , c e n te rz ) = (0 .0 , 0 .0 , -1 0 0 .0 ) / / (upx, upy, upz) = (0 .0 , 1 .0 , 0 .0 ) g lu L o o k A t(0 .0 f, O.Of, 1 0 .Of, O.Of, O.Of, -1 0 0 .Of, O.Of, l. O f , O .O f); / / ry s u je t r ó jk ą t glBegin(GL_TRIANGLE); g lV e r t e x f ( 1 0 . 0 f, O.Of, O .O f); g lV e r t e x f ( 0 . 0 f , 1 0 .Of, O .O f); g lV e r t e x f ( -1 0 . 0 f . O.Of, O .O f); g lE n d O ; / / o p ró żn ia b u fo r g lF lu s h O ;
} Wykorzystanie funkcji gl uLookAt O jest więc bardzo proste. Dobierając odpowiednio wartości jej parametrów można umieścić kamerę w dowolnym punkcie przestrzeni i skie rować ją w wybranym kierunku.
Wykorzystanie funkcji glRotate*() i glTranslate*() Podstawową wadą funkcji gl uLookAt O jest konieczność dołączenia do aplikacji biblio teki GLU. W przeciwnym razie trzeba skorzystać z funkcji g lR o ta te * () i g lT ra n s la t e * ( ) , które reprezentują przekształcenia modelowania. Funkcje te modyfikują położe nie obiektów względem stacjonarnej kamery. Zamiast więc zmieniać położenie kamery przemieszczają obiekty względem niej. Jeśli zrozumienie przekształceń modelowania sprawia czytelnikowi trudność, to przed zapoznaniem się z poniższym fragmentem kodu powinien on przeanalizować podrozdział poświęcony temu rodzajowi przekształceń.
116
Część II ♦ Korzystanie z OpenGL
Poniższy fragment wykorzystuje przekształcenie modelowania dla osiągnięcia takiego samego rezultatu jak przez zastosowanie funkcji gl uLookAt (). v o id D isplayS ceneO
{ glClear(GL_COLOR_BUFFER_BIT); / / o p ró żn ia b u fo r kolorów g lC o lo r 3 f( 1 .0 f, O.Of. O .O f); / / w ybiera k o lo r czerwony g lL o a d ld e n tity O ; / / c zy ś ci bieżącą m acierz p rze k sz ta łce ń / / wykonuje p rz e k s z ta łc e n ie widoku za pomocą fu n k c ji g lT r a n s la te f ( ) / / przesuwa b ie żą cy układ współrzędnych do punktu (0 .0 , 0 .0 , -1 0 .0 ) / / co odpowiada um ieszczeniu kamery w punkcie o w spółrzędnych (0 .0 , 0 .0 , 10) g lT r a n s la te f ( 0 . 0 f, O.Of, -1 0 .O f); / / ry s u je t r ó jk ą t glBegin(GL_TRIANGLE); g lV e r te x f( 1 0 .0 f, O.Of, O .O f); g lV e r t e x f ( 0 . 0 f, 1 0 .Of, O .O f); g lV e r t e x f ( - 1 0 . 0 f, O.Of, O .O f); g lE n d O ; / / o próżn ia b u fo r g lF lu s h O ;
} Powyższy fragment kodu nie różni się istotnie od wykorzystującego funkcję gl uLookAt (), ponieważ przekształcenie widoku polega w tym przypadku jedynie na przemieszczeniu kamery wzdłuż osi z. Jeśli przekształcenie widoku skierowałoby dodatkowo kamerę pod pewnym kątem, to należałoby także skorzystać z funkcji glRotate*(). W takim przy padku najlepiej jest skorzystać z ostatniego sposobu przekształceń widoku za pomocą własnych procedur.
Własne procedury przekształceń widoku Można założyć, że tworzony jest program symulacji lotu. W programie takim kamera umieszczona jest zwykle na miejscu pilota,, a jej orientacja zgodna jest z kierunkiem ru chu samolotu. Ruch ten może odbywać się w dowolnym kierunku przestrzeni i wobec tego orientacja kamery w dowolnym momencie opisana jest przez trzy kąty obrotu względem osi układu współrzędnych. Korzystając z przekształceń modelowania można więc napisać poniższą funkcję przekształcenia widoku: v o id P lan eV ie w (G L flo a t planeX, G L flo a t planeY, G L flo a t planeZ G L flo a t r o l i , G L flo a t p itc h , G L flo a t yaw kamery
{
/ / r o li o k re ś la k ą t o b ro tu względem o si z g lR o ta t e f( r o l1, O.Of, O.Of, l. O f ) ; / / yaw o k re ś la k ą t o b ro tu względem o si y g lR o ta te f(y a w , O.Of, l. O f , O .O f); / / p itc h o k re ś la k ą t o b ro tu względem o si x g lR o t a t e f ( r o l l, l. O f . O.Of, O .O f); / / zm ienia p o ło ż e n ie kamery g lT ra n s la te f(-p la n e X , -planeY , -p la n e Z );
/ / w spółrzędne sam olotu / / k ą ty o r ie n ta c ji
Rozdział 5. ♦ Przekształcenia układu współrzędnych i m acierze OpenGL
117
Dzięki wykorzystaniu możliwości tej funkcji kamera będzie zawsze pozostawać na miejscu pilota niezależnie od ruchu samolotu. Symulacja lotu stanowi jeden z przykła dów wykorzystania własnych przekształceń widoku. Inne zastosowania to na przykład ruch kamery dookoła obiektu wykorzystujący układ współrzędnych biegunowych czy zmiany położenia i orientacji kamery za pomocą myszy i klawiatury podobne do zasto sowanych w grze „Quake”.
Przekształcenia modelowania Przekształcenia modelowania pozwalają zmieniać położenie i orientację obiektów za po mocą przesunięć, obrotów i skalowań. Wszystkie te przekształcenia można wykonać za jednym razem lub po kolei. Rysunek 5.5 ilustruje rodzaje przekształceń modelowania: ♦ przesunięcie — polega na przemieszczeniu obiektu wzdłuż pewnej osi; ♦ obrót — obiekt jest obracany o pewien kąt względem jednej z osi; ♦ skalowanie — zmienia wymiary obiektu (możliwe jest skalowanie z innym współczynnikiem dla każdej osi). Rysunek 5.5. Trzy rodzaje przekształceń modelowania
\ / /
\ \
/
Kolejność wykonywania przekształceń modelowania ma zasadnicze znaczenie dla osta tecznego wyglądu sceny. Na przykład obrót obiektu, a następnie jego przesunięcie bę dzie miało inny efekt niż wykonanie tych samych przekształceń w odwrotnej kolejności, co ilustruje rysunek 5.6. Pokazana na nim strzałka wychodząca ze środka układu współ rzędnych w pierwszym przypadku została obrócona o kąt 30° dookoła osi z, a następnie przesunięta o 5 jednostek wzdłuż osi x. W efekcie strzałka znalazła się ostatecznie w punk cie o współrzędnych (5, 4,33) i skierowana jest pod kątem 30° względem dodatniej czę ści osi x. Natomiast w drugim z pokazanych przypadków strzałka została najpierw prze sunięta o 5 jednostek wzdłuż osi x, a następnie obrócona o kąt 30° dookoła osi z. Choć orientacja strzałki jest taka sama jak w poprzednim przypadku, to jednak współrzędne jej początku są inne (5,0).
118
Część II ♦ Korzystanie z OpenGL
Rysunek 5.6. (A) obrót poprzedzający przesunięcie, (B) przesunięcie poprzedzające obrót
ł+y (A)
y-y
y-y
ł+y
ł +y 4
y-y
v-y
y-y
(B)
y
y-y
Rzutowanie Rzutowanie definiuje bryłę widoku oraz płaszczyzny obcięcia. Wykonywane jest po przekształceniach modelowania, które zostaną omówione szczegółowo w dalszej części rozdziału. Zadaniem rzutowania jest ustalenie tego, które obiekty znajdują się w bryle widoku i jak będą wyglądać na ekranie. Zastosowanie rzutowania można porównać do użycia odpowiedniego obiektywu kamery. Podobnie jak pole widzenia zależy od wyboru obiektywu kamery, tak i obraz uzyskany na ekranie zależy od wyboru przekształcenia rzutowania. Jeśli wybrany zostanie obiektyw szerokokątny, to uzyska się panoramiczny obraz sceny zawierający jednak niewiele szczegółów. Jeśli natomiast pole widzenia zo stanie zawężone, co przypominać będzie zastosowanie teleobiektywu, to uzyskany zo stanie efekt zbliżenia obiektów pozwalający dostrzec więcej szczegółów. OpenGL umożliwia dwa rodzaje rzutowania: ♦
r z u t o w a n i e p e r s p e k t y w i c z n e — rzutowanie to stanowi dobre przybliżenie sposobu widzenia rzeczywistego świata (obiekty położone dalej od obserwatora wydają się mniejsze niż te, które położone są bliżej);
♦
r z u t o w a n i e o r t o g r a f ic z n e — rzutowanie to zachowuje na ekranie rzeczywiste wymiary obiektów niezależnie od ich odległości od obserwatora; znajduje to zastosowanie w komputerowym wspomaganiu projektowania.
Przekształcenie okienkowe Przekształcenie okienkowe jest wykonywane jako ostatnie w potoku przekształceń. Umożliwia ono odwzorowanie dwuwymiarowego obrazu (uzyskanego za pomocą rzu towania perspektywicznego) na obszar okna, w którym tworzona jest grafika. Prze kształcenie okienkowe określa, w jaki sposób powinny zmienić się rozmiary prezento wanego obrazu, by pasował on do rozmiarów okna grafiki.
Rozdział 5. ♦ Przekształcenia układu współrzędnych i m acierze OpenGL
1 19
OpenGL i macierze Podrozdział zajmował się będzie sposobem wykonywania przedstawionych przekształ ceń w OpenGL. OpenGL wykorzystuje w tym celu algebrę macierzy oraz dysponuje specjalnym stosem macierzy ułatwiającym tworzenie skomplikowanych modeli z wielu prostych obiektów.
Macierz modelowania Macierz modelowania definiuje układ współrzędnych wykorzystywany podczas two rzenia obiektów. Macierz ta posiada wymiary 4x4 i mnożona jest o wektory reprezen tujące wierzchołki oraz macierze przekształceń. W wyniku tego mnożenia powstaje ma cierz reprezentująca rezultat przekształceń wierzchołków. Funkcja g lM a trix M o d e ( ) pozwala poinformować maszynę OpenGL, że będzie modyfi kowana macierz modelowania. Funkcja ta posiada następujący prototyp: v o id glMatrixMode(GLenum mode) ;
Przed wykonaniem jakiegokolwiek przekształcenia trzeba określić, czy będzie ono mo dyfikować macierz modelowania czy macierz rzutowania. Jeśli przekazana zostanie funkcji g lM a trix M o d e ( ) wartość GL MODELVIEW, to przekształcenia będą modyfikować macierz modelowania: glMatrixMode(GL_MODELVIEW);
Funkcji g lM a trix M o d e ( ) można także przekazać wartości GL_PR0J ECTION lub GL_TEXTURE. Pierwsza z nich określa macierz rzutowania, a druga macierz tekstury, której zastoso wanie omówione zostanie w rozdziale 8. W większości przypadków po wyborze macierzy modelowania należy ją zresetować za pomocą funkcji gl L o a d Id e n tity ( ) omówionej już wcześniej. W wyniku jej wywołania macierz modelowania stanie się macierzą jednostkową i przywrócony zostanie wyj ściowy układ współrzędnych. Ilustruje to poniższy fragment kodu: I I ... g 1Mat r i xMode( GL_M0DELVI EW); g l L o a d l d e n t i t y O ; / / re s e t u je macierz modelowania U
... wykonuje p r z e k s z ta łc e n ia
glB egi n(GL_P0INTS); g l V e r t e x 3 f ( 0 . 0 f . O.Of, O.Of); g lE n d O ; II...
kontynuacja programu
120
Część II ♦ Korzystanie z OpenGL
Przesunięcie Przesunięcie pozwala przemieścić obiekt z jednego punktu przestrzeni do innego punktu. OpenGL umożliwia wykonanie przesunięć za pomocą funkcji g lT r a n s la te f( ) i g lT ra n s 1ated () zdefiniowanych jak poniżej: vo id g lT r a n s la t e f ( G L f lo a t x. G L flo a t y , G L flo a t z ) ; vo id glT ranslate d (G L d ou b le x, GLdouble y , GLdouble z ) ;
Jedyną różnicą pomiędzy tymi funkcjami jest typ ich parametrów. Wybór jednej z funk cji zależy więc od wymaganej dokładności przekształcenia. Parametry x, y, z opisują wartość przesunięcia wzdłuż odpowiednich osi układu współ rzędnych. Na przykład wywołanie g l T r a n s l a t e f ( 3 . 0 f . l . O f , 8 . O f);
spowoduje przesunięcie obiektu o trzy jednostki w dodatnim kierunku osi x, o jednostkę w dodatnim kierunku osi y i o osiem jednostek w dodatnim kierunku osi z. Można założyć, że należy przesunąć sześcian do punktu o współrzędnych (5, 5, 5). Naj pierw trzeba wybrać macierz modelowania i zresetować ją do macierzy jednostkowej. Następnie należy przesunąć bieżącą macierz do punktu (5, 5, 5) i wywołać funkcję ry sowania sześcianu DrawCube(). Operacje te wykonuje poniższy fragment kodu. glMatrixMode(GL_MODELVIEW); g lL o a d ld e n t it y C ); g l T r a n s l a t e f ( 5 . 0 f , 5 . Of, 5 . 0 ) ; DrawCubeO;
// // // //
wybiera macierz modelowania re s e tu je macierz modelowania p rze s u n ię c ie do punktu ( 5 ,5 , 5 ) ry s u je sześcian
Rysunek 5.7 ilustruje wykonanie tego fragmentu kodu. Rysunek 5.7. Przesunięcie sześcianu z początku układu współrzędnych do punktu (5, 5, 5)
A +y -Z
(5, 5, 5)
-X
# in c lu d e < g l/ g lu . h > # in c lu d e < g l/g la u x .h >
// // // //'
/ / "odchudza" a p lik a c j ę Windows
standardowy p l i k nagłówkowy Windows standardowy p l i k nagłówkowy OpenGL p l i k nagłówkowy b i b l i o t e k i GLU p l i k nagłówkowy pomocniczych f u n k c j i OpenGL
Rozdział 5. ♦ Przekształcenia układu współrzędnych i m acierze OpenGL
/ / / / / / Zmienne g lo b a ln e f l o a t angle = O.Of; / / bieżący k ą t ob ro tu kamery HDC g_HDC; / / k o n te k st urządzenia bool f u l l Screen = f a l s e ;
/ / / / / / Zmienne o p is u ją c e robota f l o a t le g A n g le [2 ] - { O.Of, O.Of } ; f l o a t armAngle[2] = {O.O f, O.Of } ;
/ / bieżące k ą ty o b ro tu nóg i ramion
/ / DrawCube / / o p is : ponieważ każdy element robota powstaje przez p r z e k s z ta łc e n ie sześcianu, // t o do rysowania sześcianu w danym punkcie będziemy używać t e j f u n k c j i . v o id DrawCube( f l o a t xPos, f l o a t yPos, f l o a t zPos)
{
g lP u s h M a t r ix O ; g lT r a n s la te f ( x P o s , yPos, zPos); g l B eg in (GL_P0LYG0N); g l V e r t e x 3 f ( 0 . 0 f , O.Of, O.Of); g l V e r t e x 3 f ( 0 . 0 f , O.Of, - l . O f ) ; g l V e r t e x 3 f ( - 1 .O f, O.Of, - l . O f ) ; g l V e r t e x 3 f ( - 1 . 0 f , O.Of, O.Of); g l V e r t e x 3 f ( 0 . 0 f , O.Of, O.Of); g l V e r t e x 3 f ( - 1 . 0 f . O.Of, O.Of); g l V e r t e x 3 f ( - 1 . 0 f , - l . O f , O.Of); g l V e r t e x 3 f ( 0 . 0 f , - l . O f . O.Of); g l V e r t e x 3 f ( 0 . 0 f , O.Of, O.Of); g l V e r t e x 3 f ( 0 . 0 f . - l . O f , O.Of); g lV e rte x 3 f(0 .0 f, - l.O f, - l.O f) ; g l V e r t e x 3 f ( 0 . 0 f , O.Of, - l . O f ) ; g l V e r t e x 3 f ( - l .O f . O.Of. O.Of); g l V e r t e x 3 f ( - 1 .O f . O.Of, - l . O f ) ; g l V e r t e x 3 f ( - 1 .O f , - l . O f , - l . O f ) ; g l V e r t e x 3 f ( - 1 .O f , - l . O f , O.Of); g l V e r t e x 3 f ( 0 . 0 f , O.Of, O.Of); g lV e rte x 3 f(0 .0 f, - l.O f, - l.O f) ; g lV e rte x 3 f(-1 .0 f. - l.O f, - l.O f) ; g l V e r t e x 3 f ( - 1 .O f , - l . O f , O.Of); g l V e r t e x 3 f ( 0 . 0 f , O.Of, O.Of); g l V e r t e x 3 f ( - 1 . 0 f , O.Of. - l . O f ) ; g lV e rte x 3 f(-1 .0 f, -l.O f, - l.O f) ; g lV e rte x 3 f(0 .0 f, - l.O f, - l.O f) ; g lE n d O ; g l P o p M a t r ix O ;
/ / górna ściana
/ / przednia ściana
/ / ściana po prawej
/ / ściana po lewej
/ / dolna ściana
/ / t y l n i a ściana
} / / DrawArm / / o p is : r y s u je pojedyncze ramię robota v o id Dra w A rm (flo at xPos, f l o a t yPos, f l o a t zPos)
{
g lP u s h M a t r ix ( ); g l C o l o r 3 f ( l . O f . O.Of, O.Of); g lT r a n s la te f ( x P o s , yPos, zPos); g l S c a l e f d . O f , 4 . Of. l . O f ) ; DrawCube(0.0f, O.Of, O.Of); g l P o p M a t r ix O ;
/ / k o lo r czerwony / / ramię j e s t prostopadłościanem 1x4x1
125
126
Część II ♦ Korzystanie z OpenGL
/ / DrawHead / / o p is : r y s u je głowę robota void DrawHead( f l o a t xPos, f l o a t yPos, f l o a t zPos)
{ g lP u s h M a t r ix O ; g l C o lo r 3 f ( 1 . O f, l . O f , l . O f ) ; g lT r a n s la te f ( x P o s , yPos, zPos); gl Seal e f ( 2 . O f, 2 . Of, 2 . O f); DrawCube(0.0f, O.Of, O.Of); g lP o p M a tr ix ( );
/ / k o lo r b i a ł y / / głowa j e s t sześcianem 2x2x2
/ / DrawTorso / / o p is : r y s u je korpus robota vo id D ra w T o rs o (flo a t xPos, f l o a t yPos, f l o a t zPos)
{ g lP u s h M a t r ix O ; g l C o l o r 3 f ( 0 . 0 f . O.Of, l . O f ) ; / / k o lo r n ie b ie s k i g lT r a n s la te f ( x P o s , yPos, zPos); g lS c a l e f ( 3 . 0 f , 5 . Of, 2 . O f); / / korpus j e s t prostopadłościanem 3x5x2 DrawCube(0.0f, O.Of, O.Of); g lP o p M a t r ix O ;
} / / DrawLeg / / o p is : ry s u je pojedynczą nogę robota vo id D ra w Le g (floa t xPos, f l o a t yPos, f l o a t zPos)
{ g lP u s h M a t r ix O ; g l Colo r 3 f ( 1 . O f, l . O f , O.Of); / / k o lo r ż ó ł t y g lT r a n s la te f ( x P o s , yPos, zPos); g l S c a l e f O . O f , 5 . Of, l . O f ) ; / / noga j e s t prostopadłościanem 1x5x1 D rawCube(0.0f, O.Of, O.Of); g lP o p M a t r ix O ;
} / / DrawRobot / / o p is : r y s u je model robota w punkcie (xpos.ypos.zpos) void DrawRobot( f l o a t xPos, f l o a t yPos, f l o a t zPos)
{ s ta tic s ta tic
bool l e g i = t r u e ; bool leg2 = f a l s e ;
s ta tic s ta tic
bool arml = t r u e ; bool arm2 = f a l s e ;
/ / zmienne o k re ś la ją c e p o ło ż e nie nóg i ramion / / t r u e =prze d nie, f a l s e = t y l n i e
g lP u s h M a trix O ; g lT r a n s la te f ( x P o s , yPos, zPos);
/ / ry s u je model robota w określonym punkcie
/ / r y s u je elementy robota D rawHeadd.Of, 2 . Of, O.Of); D ra w Torso(1.5f, O.Of, O.Of); g lP u s h M a t r ix O ; / / w zale żności od położenia ramienia zwiększa lu b zmniejsza jego kąt i f (a rm l) armAngle[0] = armAngle[0] + O . l f ;
Rozdział 5. ♦ Przekształcenia układu współrzędnych i m acierze OpenGL
e ls e armAngle[0] = armAngle[0] - O . l f ; / / j e ś l i ramię o s ią gn ę ło maksymalny k ą t, / / t o zmienia k ie runek i f (arm Angle [0] >= 1 5 .Of) arml = f a l s e ; i f (armAngleCO] = 1 5 .Of) arm2 = f a l s e ; i f (arm AngleCl] = 1 5 .Of) le g i = fa lse ; i f (legAngleCO] = 1 5 .Of) leg2 = f a l s e ; i f (1egAngle[ 1 ] = 3 6 0 .Of) angle = O.Of;
/ / zwiększa ką t kamery / / j e ś l i o k rą ż yła o b ie k t ,
t o z e ru je l i c z n i k
g lP u s h M a t r ix ( ); / / odkłada bieżącą macierz na stos g lL o a d ld e n tity O ; / / re s e tu je macierz g l T r a n s l a t e f ( 0 . 0 f , O.Of, - 3 0 . Of); / / p rz e s u n ię c ie do punktu (0, 0. -30) g lR o ta t e f( a n g le , O.Of, l . O f , O . O f ) ; / / o b ró t robota względem osi y Draw R o b o t(0 .0 f, O.Of, O.Of); / / ry s u je robota g lP o p M a tr ix O ; / / usuwa bieżącą macierz ze stosu g lF lu s h O ; SwapBuffers(g_HDC);
/ / przełą cza b u fory
} / / fu n k c ja o k re ś la ją c a format p i k s e l i void S e tu p P ix e lFormat(HDC hDC)
{
i n t nPixelF ormat;
/ / indeks formatu p i k s e l i
Rozdział 5. ♦ Przekształcenia układu współrzędnych i m acierze OpenGL
s t a t i c PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), / / rozm iar s t r u k t u r y / domyślna wersja 1. / g r a f ik a w oknie PFD_DRAW_TO_WINDÓW / g r a f ik a OpenGL PFD_SUPPORT_OPENGL / podwójne buforowanie PFD_DOUBLEBUFFER, / t r y b kolorów RGBA PFD_TYPE_RGBA, / 32-bito w y o pis kolorów 32, / n i e s p e c y fik u je b itó w kolorów 0, 0. 0. 0, 0, 0. / bez buforu a l f a 0, / n ie s p e c y fik u je b i t u p rze s u n ię c ia 0, / bez bufora akumulacji 0, / ig n o r u je b i t y akumulacji 0, 0, 0, 0, / 1 6 - b it b u fo r z 16. / bez bufora po w ie la nia 0, / bez buforów pomocniczych 0. / główna płaszczyzna rysowania PFD_MAIN_PLANE, / zarezerwowane 0, / ig n o r u je maski warstw 0 , 0 , 0 }; / / w ybiera n a jb a r d z ie j zgodny format p i k s e l i nPixelF orm at = ChoosePixelFormat(hDC, & pfd); / / o k re ś la fo rm at p i k s e l i d la danego kontekstu urządzenia S e t P i x e lFormat(hDC, nPixelF orm at, & pfd );
} / / procedura okienkowa LRESULT CALLBACK WndProc(HWND hwnd, UINT message. WPARAM wParam, LPARAM lParam)
{
s t a t i c HGLRC hRC; s t a t i c HDC hDC; i n t w id th , h e ig h t ;
/ / ko n te k st tw o rze n ia g r a f i k i / / ko n te k st urządzenia / / szerokość i wysokość okna
switch(message) case WM CREATE:
/ / okno j e s t tworzone
hDC = GetDC(hwnd); / / pobiera k o n te k st urządzenia d la okna g_HDC = hDC; SetupPixelF orm at(hD C ); / / wywołuje fu n k c ję o k re ś la ją c ą fo rm at p i k s e l i / / tw o rzy k o n te k st tw o rze n ia g r a f i k i i czyni go bieżącym hRC = w glC reateContext(hD C ); wglMakeCurrent(hDC, hRC); r e tu r n 0; break; case WM_CL0SE:
/ / okno j e s t zamykane
/ / deaktywuje bie żący k o n te k st tw orzenia g r a f i k i i usuwa go wglMakeCurrent(hDC, NULL); w g lD e le teC o nte xt(h R C ); / / wstawia komunikat WM_QUIT do k o l e j k i P ostQuitM essage(0); r e tu r n 0; break;
129
130
Część II ♦ Korzystanie z OpenGL
c a s e W M_SIZE: h e i g h t = H IW O RD (1 P a r a m ) ; w i d t h = LOWORDO P a r a m ) ; i f ( h e ight= = 0)
/ / p o b i e r a nowe r o z m i a r y o k n a
/ / u n ik a d z i e l e n i e p rz e z O
{ h e ig h t= l;
} g l V i e w p o r t (O, O, w i d t h , h e i g h t ) ; g lM a trix M o d e (G L _ P R O JE C T IO N ); g lL o a d ld e n tity O ;
/ / n a d a j e nowe w y m i a r y o k n u O p e n G L / / w y b ie ra m a c ie rz rzu to w a n ia / / r e s e t u je m a c ie rz rzu to w a n ia
// wyznacza p ro p o rc je obrazu g l u P e r s p e c t i v e ( 5 4 . 0 f , (GLf1o a t )wi dth/(GLf1o a t )hei g h t .1 .O f ,1000.Of);
g lM a trix M o d e (G L _ M O D E L V IE W ); g lL o a d ld e n tity O ;
/ / w y b i e r a m a c i e r z m o d e lo w a n i a / / r e s e t u j e m a c i e r z m o d e lo w a n i a
r e t u r n 0; break; d e fa u lt: break;
r e t u r n (D e fW in d o w P ro c (h w n d , m e ssa g e , wParam , l P a r a m ) ) ;
/ / p u n k t , w k tó ry m r o z p o c z y n a s i ę w yk o n y w a n ie a p l i k a c j i i n t W IN AP I W i n M a in ( H I N S T A N C E h l n s t a n c e , H IN S T A N C E h P r e v I n s t a n c e , L P S T R l p C m d L i n e , i n t nShowCmd) WNDCLASSEX w i n d o w C l a s s ; HWND hwnd; MSG msg; bool done; DWORD d w E x S ty le ; DWORD d w S ty le ; RECT wi n d o w R e c t; // in t in t in t
// // // // // //
k la s a okna uchw yt okna k o m u n ika t z n a c z n ik z a k o ń c z e n ia a p l i k a c j i r o z s z e r z o n y s t y l okna s t y l okna
z m ie n n e p o m o c n ic z e w id t h = 800; h e ig h t = 600; b i t s = 32 ;
/ / f u l l S c r e e n = TRU E; windowRect.left=(long)0; // struktura określająca rozmiary okna windowRect.right=(long)width; wi ndowRect.top=(1ong)0; windowRect.bottom=(long)height; / / d e f i n i c j a k la s y okna w in c .v C la s s .c b S ize = s iz e o f(W N D C L A S S E X ); w in d o w C la s s .s ty le = CS_HREDRAW | C S J R E D R A W ; w in d o w C la s s . lp f n W n d P r o c = W ndProc; w i n d o w C l a s s . c b C l s E x t r a = 0;
Rozdział 5. ♦ Przekształcenia układu współrzędnych i m acierze OpenGL
w i n d o w C l a s s . c b W n d E x t r a = 0; w in d o w C la s s .h ln s ta n c e = h ln s ta n c e ; w in d o w C la ss .h lc o n = Lo ad Ico n (N U LL, ID I_APPLICATIO N ); w in d o w C la ss .h C u rs o r = L o a d C u r s o r ( N U L L , IDC_ARR0W ); w i n d o w C l a s s . h b r B a c k g r o u n d = N U LL; w i n d o w C l a s s . l p s z M e n u N a m e = N U LL; w in d o w C la ss .lp s z C la s s N a m e = " M o ja K la s a " ; w in d o w C la ss .h lc o n S m = LoadIcon (N U LL, IDI_W INL0G0);
// // // //
131
d o m y śln a ik o n a d o m y śln y k u r s o r bez t ł a b e z menu
/ / l o g o W in d o w s
// r e j e s t r u j e k la s ę okna i f ( !R e g is te rC la s s E x (& w in d o w C la s s )) r e t u r n 0; i f (fu llS c re e n )
/ / t r y b pełn o ekran o w y?
DEVM0DE d m S c r e e n S e t t i n g s ; // t r y b u rz ą d z e n ia m em se t(& dm S creenSe tti n g s . 0 , s iz e o f( d m S c re e n S e tt i n g s )); d m S c re e n S e ttin g s .d m S iz e = s iz e o f( d m S c r e e n S e t t in g s ) ; d m S c re e n S e ttin g s .d m P e ls W id th = w id th ; I I s z e ro k o ś ć ekranu d m S cre e n S e ttin g s .d m P e ls H e ig h t = h e ig h t; // w ysokość ekra nu d m S c re e n S e ttin g s .d m B itsP e rP e l = b it s ; / / b i t ó w na p i k s e l d m S c r e e n S e t t i n g s . dm Fi e ld s = D M _ B I T S P E R P E L | D M _ P E L S W I D T H | D M _ P E L S H E I G H T ;
i f ( C h a n g e D i s p l a y S e t t i n g s ( & d m S c r e e n S e t t i n g s , C D S _ F U L L S C R E E N ) != D I S P CHANGE_ SUCCESSFUL)
{ / / p r z e ł ą c z e n i e t r y b u n i e p o w io d ło s i ę , z pow rotem t r y b o k ie n k o w y M e s s a g e B o x ( N U L L , " D i s p l a y mode f a i l e d " , N U LL. M B _ 0 K ); f u l lS c re e n = F A L S E ;
i f (fu llS c re e n )
/ / t r y b pełn o ekran o w y?
{ d w E x S ty le = W S _ E X _ A P P W IN D Ó W ; d w S t y le = W S _ P O P U P ; Sh o w C u rso r(FA LS E );
/ / r o z s z e r z o n y s t y l okna / / s t y l okna / / ukryw a k u r s o r m yszy
} e ls e
{ d w E x S ty le = W S _ E X _ A P P W IN D Ó W | WS_EX_WINDOWEDGE; d w S t y 1 e=WS_OVERLAPPEDWINDOW;
/ / d e f i n i c j a k la s y okna / / s t y l okna
} A d ju s tW in d o w R e ctE x (& w in d o w R e c t, d w S t y le , FALSE, d w E x S ty le ) ;
/ / k o r y g u j e r o z m ia r okna
/ / tw o rz y okno hwnd = C r e a t e W i n d o w E x ( N U L L , / / r o z s z e r z o n y s t y l okna "M o ja K la sa ", / / na zw a k l a s y "M o d e l r o b o t a w O p e n G L " , / / n azw a a p l i k a c j i d w S t y l e | W S _ C L IP C H IL D R E N | W S_CLIPSIBLINGS, 0,0, // w sp ó łrzęd n e x ,y w in d o w R e ct. r ig h t - w in d o w R e c t.le ft, w in d o w R e c t.b o tto m - w in d o w R e c t.to p , / / s z e r o k o ś ć , w y so ko ść NULL, // uchwyt okna n adrzędn ego
132
Część II ♦ Korzystanie z OpenGL
N U LL, h ln s ta n c e , NULL);
/ / u c h w y t menu // in sta n c ja a p lik a c ji / / bez dodatkow ych p aram etrów
/ / s p r a w d z a , c z y u t w o r z e n i e o k n a n i e p o w i o d ł o s i ę ( w t e d y w a r t o ś ć hwnd ró w n a N U LL) i f ( !hw n d) r e t u r n 0; S h o w W in d o w (h w n d , SW_SH0W); U p d a te W in d o w (h w n d );
/ / w y ś w ie tla okno / / a k t u a liz u j e okno
done = f a ls e ;
/ / i n i c j u j e zm ien n ą w a run ku p ę t l i
/ / p ę t l a p r z e t w a r z a n ia ko m u n ik a tó w w h ile (!don e)
{ P e e k M e s s a g e ( & m s g , hwnd, N U L L , N U LL, P M _RE M 0V E ); i f ( m s g . m e s s a g e == WM_QUIT)
/ / a p l i k a c j a o t r z y m a ł a k o m u n i k a t WM_QUIT?
{ done = tr u e ;
// j e ś li ta k . to kończy d z ia ła n ie
} e ls e
{ R e n d e r ( ); T ra n s la te M e s s a g e ( & m sg ); D is p a tc h M e s s a g e (& m s g );
/ / tłu m a c z y ko m u n ik a t i w y s y ła do system u
} } i f (fu llS c re e n )
{ C h a n g e D is p la y S e ttin g s (N U L L .O ); S h o w C u rso r(T R U E );
// przyw raca p u lp it / / i w s k a ź n ik m yszy
} r e t u r n m sg.w Param ;
} Całkiem długi kod, ale to dopiero początek naprawdę interesujących rzeczy. Funkcja D r a w R o b o t O ilustruje sposób tworzenia i animacji hierarchicznego modelu. Szczególną uwagę należy zwrócić na zastosowanie funkcji gl PushMatrix() i glPopMatrix() do umieszczenia poszczególnych elementów robota we właściwym miejscu lokalnego układu współrzędnych. Dobrym ćwiczeniem związanym z użyciem tych funkcji będzie rozbudowa robota o takie elementy jak dłonie czy stopy. Rysunek 5.13 prezentuje pro gram animacji robota w działaniu.
Rzutowanie O rzutowaniu wspominano już w tej książce kilka razy, a nawet użyto go w przykładzie programu. Pora więc dokładniej zapoznać się z rzutowaniem w OpenGL. Jak już wspo mniano OpenGL umożliwia dwa rodzaje rzutowania: ortograficzne (inaczej równoległe) oraz perspektywiczne.
Rozdział 5. ♦ Przekształcenia układu współrzędnych i m acierze OpenGL
133
Rysunek 5.13. Program animacji robota w działaniu
Rzutowanie pozwala określić bryłę widoku, która służy do osiągnięcia dwu celów. Pierwszy z nich polega na określeniu płaszczyzn obcięcia, które umożliwiają ustalenie tego, jaka cześć trójwymiarowego świata jest w rzeczywistości widziana przez obser watora. Dzięki temu obiekty znajdujące się poza bryła widoku nie muszą być przetwa rzane ani rysowane. Drugim celem określenia bryły widoku jest ustalenie sposobu ry sowania obiektów. Zależy on od kształtu bryły widoku, który jest różny dla rzutowania ortograficznego i perspektywicznego. Zanim wykonane jednak zostanie jakiekolwiek przekształcenie rzutowania należy upew nić się, że wybrany został stos macierzy rzutowania. Podobnie jak w przypadku macie rzy modelowania służy do tego funkcja glMatrixMode(): g 1 M a t r i x M o d e ( G L _ P R 0 J E C H O N );
W większości przypadków pożądane będzie także „wyczyszczenie” macierzy rzutowa nia za pomocą funkcji gl Loadldenti ty (), aby uniknąć akumulacji poprzednio wykony wanych przekształceń. W odróżnieniu od przekształceń modelowania, rzutowanie wy konywane jest z reguły pojedynczo. Po wybraniu macierzy rzutowania można przystąpić do specyfikowania przekształcenia. Spo sób wykonywania rzutowania omówiony zostanie najpierw dla rzutowania ortograficznego, a następnie dla rzutowania perspektywicznego, które będzie wykorzystywane najczęściej.
Rzutowanie ortograficzne Rzutowanie ortograficzne — zwane też równoległym — nie korzysta z perspektywy. In aczej mówiąc, nie zmienia rozmiarów obiektów w zależności od ich odległości od kamery. Obiekty mają więc na ekranie zawsze taki sam rozmiar bez względu na to, czy znajdują się bliżej czy dalej. Chociaż rzutowanie ortograficzne nie tworzy tak realistycznego obrazu świata jak rzutowanie perspektywiczne, to jednak posiada wiele zastosowań. Tradycyjnie wykorzystywane jest w komputerowym wspomaganiu projektowania i dlatego właśnie zostało udostępnione w OpenGL. Rzutowanie ortograficzne wykorzystywane jest także w grach opartych na grafice dwuwymiarowej bądź izometrycznej.
134
C zęść II ♦ Korzystanie z OpenGL
Rzutowanie ortograficzne je s t przydatne przy tworzeniu grafiki dwuwymiarowej, ale rzadko stosowane je s t w grach, ponieważ tradycyjne metody tworzenia takiej grafiki umożliwiają uzyskanie większej liczby szczegółów. W przyszłości sytuacja ta może jednak ulec zm ianie.
Przekształcenie ortograficzne w OpenGL wykonywane jest za pomocą funkcji gl Ortho(): g l O r t h c K G L d o u b l e le f t, G L d o u b l e rig h t, G L d o u b l e bottom, G L d o u b l e top, G L d o u b l e near, G L d o u b l e far):
Parametry l e f t i r i g h t definiują płaszczyzny obcięcia na osi x — bottom — i — top — na osi y, a parametry n ear i f a r określają odległość do płaszczyzn obcięcia na osi z. Parametry te określają więc bryłę widoku o kształcie prostopadłościanu. Rzutowanie ortograficzne często używane jest podczas tworzenia grafiki dwuwymiaro wej i dlatego też biblioteka GLU udostępnia dodatkową funkcję umożliwiającą rzuto wanie ortograficzne w przypadku scen nie wykorzystujących współrzędnej z: g l u 0 r t h o 2 D ( G L d o u b l e le ft, G L d o u b l e rig h t, G L d o u b l e bottom, G L d o u b l e top):
Parametry funkcji mają takie samo znaczenie jak dla funkcji g l O r t h o ( ). Taki sam rezultat jak za pomocą funkcji g l 0 r t h o 2 D ( ) uzyskać można wywołując funkcję g l O r t h o ( ) , jeśli jej parametry near i f a r będą miały wartości odpowiednio -1.0 i 1.0. Korzystając z funkcji g l u 0 r t h o 2 D ( ) należy korzystać także z wersji funkcji g l V e r t e x O wymagającej podania jedynie dwu parametrów określających współrzędne x i y. Zwykle w takim przypadku korzysta się także z układu współrzędnych odpowiadających współrzędnym ekranowym.
Rzutowanie perspektywiczne Rzutowanie perspektywiczne pozwala uzyskać bardziej realistyczny obraz trójwymia rowego świata i dlatego korzysta się z niego częściej. W efekcie zastosowania prze kształcenia perspektywicznego obiekty znajdujące się dalej od obserwatora są rysowane na ekranie jako mniejsze. Bryła widoku dla rzutowania perspektywicznego ma kształt ściętego ostrosłupa skierowanego mniejszą podstawą w stronę obserwatora. Podczas rzutowania perspektywicznego OpenGL przekształca ten ostrosłup w prostopadłościan. Na skutek tego przekształcenia obiekty znajdujące się dalej od obserwatora są pomniej szane bardziej niż te, które znajdują się bliżej. Stopień pomniejszenia jest tym większy, im większa jest różnica rozmiarów obu podstaw ostrosłupa (jeśli obie podstawy są tych samych rozmiarów, to nie zostanie wprowadzona perspektywa i otrzyma się w efekcie rzutowanie ortograficzne). Istnieje kilka sposobów określenia ostrosłupa rzutowania i tym samym rzutowania per spektywicznego. Najpierw omówiony zostanie poniższy: v o i d g l F r u s t u m ( G L d o u b l e le f t , G L d o u b l e rig h t, G L d o u b l e bottom, G L d o u b l e top, G L d o u b l e near, G L d o u b l e far):
Parametry l e f t , r i g h t , bottom i top wyznaczają płaszczyznę obcięcia zawierającą mniejszą z podstaw ostrosłupa, a parametry near i f a r określają odległość do mniejszej i większej podstawy ostrosłupa. Górny, lewy wierzchołek mniejszej podstawy ma więc współrzędne ( l e f t , top, -near), a dolny, prawy ( r i g h t , bottom, -near). Wierzchołki
Rozdział 5. ♦ Przekształcenia układu współrzędnych i m acierze OpenGL
135
większej podstawy ostrosłupa wyznacza się jako punkty przecięcia linii przechodzących przez punkt obserwatora i wierzchołki niniejszej podstawy z dalszą płaszczyzną obcię cia. Im bliżej więc znajduje się obserwator względem mniejszej z podstaw ostrosłupa, tym większa będzie druga z nich i tym samym większe wrażenie perspektywy. Funkcja g l F r u s t u m O umożliwia zdefiniowanie perspektywy asymetrycznej, która uży teczna jest w pewnych, dość rzadkich przypadkach. Wyobrażanie sobie rzutowania per spektywicznego za pomocą ściętego stożka widoku nie jest zbyt intuicyjne. Dlatego OpenGL jest zaopatrzony także w funkcję pozwalającą określić pole widzenia i na tej podstawie sam wyznacza ścięty ostrosłup widoku. Funkcja ta zdefiniowana jest nastę pująco: v o i d g l u P e r s p e c t i v e ( G L d o i i b l e fo v , G L d o u b l e asp e ct, G L d o u b l e ne a r, G L d o u b l e f a r ) :
Parametr fov określa w stopniach kąt widzenia obserwatora w kierunku osi y. Parametr aspect specyfikuje proporcje pola widzenia, czyli stosunek szerokości do wysokości pola widzenia w kierunku osi x. Parametry near i far posiadają takie samo znaczenie jak w przypadku omówionych wcześniej funkcji rzutowania. Jednym z problemów związanych z właściwym określeniem ściętego ostrosłupa rzuto wania perspektywicznego jest dobór szerokości jego podstaw (czyli szerokości pola wi dzenia). Nie ma optymalnego rozwiązania w tym zakresie, ponieważ właściwy wybór zależy od rodzaju zastosowania. Zwykle przyjmuje się za realistyczne kąty widzenia zbliżone do 90°. Jednak w praktyce dobór pola widzenia wymaga zawsze działania opartego na metodzie prób i błędów.
Okno widoku Niektóre z omówionych dotąd funkcji rzutowania posiadają związek z rozmiarami okna, w którym tworzona jest grafika (na przykład funkcja g l u P e r s p e c t i v e poprzez pa rametr a s p e c t ) . Przekształcenie okienkowe wykony wane, jest zawsze po rzutowaniu, dlatego też pora na jego omówienie. Macierzy okienkowej nie modyfikuje się bezpo średnio, a jedynie określa rozmiary okna grafiki. W przypadku okna grafiki istotne są jego rozmiary oraz orientacja. Określamy je za pomocą funkcji g l V i e w p o r t ( ) : v o i d g l V i e w P o r t ( G L i n t x , G L i n t y , G L s i z e i w id th , G L s i z e i h e ig h t );
Parametry x i y określają współrzędne lewego, dolnego narożnika okienka, a width i height — odpowiednio — jego szerokość i wysokość w pikselach. Gdy tworzy się kontekst grafiki, okienko widoku uzyskuje automatycznie rozmiary okna aplikacji, z którym związany jest kontekst. Rozmiar okna widoku musi być aktu alizowany za każdym razem, gdy zmienia się rozmiar okna aplikacji. Chociaż zwykle rozmiary okna widoku odpowiadają rozmiarom okna aplikacji, to nie jest to oczywiście obowiązkowe. Grafika może być tworzona na przykład tylko w określonym regionie okna aplikacji i wtedy rozmiary okna grafiki są mniejsze.
136
Część II ♦ Korzystanie z OpenGL
Przykład rzutowania Aby ułatwić zrozumienie różnic pomiędzy omówionymi rodzajami rzutowania, przy gotowany został prosty program, który pozwala oglądać tę samą sceną pokazaną z uży ciem różnych rodzajów rzutowań. Po jego uruchomieniu grafika tworzona jest z wyko rzystaniem rzutowania perspektywicznego. Za pomocą klawisza spacji, można jednak w każdej chwili wybierać pomiędzy rzutowaniem perspektywicznym (rysunek 5.14) i rzutowaniem ortograficznym (rysunek 5.15). Rysunek 5.14.’ Rzutowanie perspektywiczne
Rysunek 5.15. Rzutowanie ortograficzne
Najistotniejszymi fragmentami tego programu są funkcje j e c t i o n ( ), których kod zaprezentowany został poniżej.
R e s iz e S c e n e ()
i
U p d ateP ro -
Rozdział 5. ♦ Przekształcenia układu współrzędnych i m acierze OpenGL
/* R e s iz e S c e n e O A k t u a l i z u j e o k n o w i d o k u i r z u t o w a n i e na p o d s t a w i e r o z m i a r ó w o k n a a p l i k a c j i G L v o id R e s iz e S c e n e ( G L s iz e i w id th , G L s iz e i h e ig h t )
{ / / u n ik a d z i e l e n i a p rz e z z e ro i f (h e ig h t= = 0 )
{ h e ig h t= l;
/ / a k t u a l i z u j e r o z m ia r y okna w id o ku g lV ie w p o r t ( 0 , 0, w id th , h e ig h t ) ; / / o k re ś l a -rz u to w a n ie n ie z m ie n ia ją c je g o ro d za ju U p d a te P ro je c tio n O ; } / / k o n ie c f u n k c ji R e s iz e S c e n e O
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * U p d a te P ro je c tio n O O k r e ś l a r o d z a j r z u t o w a n ia . J e ś l i t o g g l e p o s ia d a w a r t o ś ć GL_TRUE, t o z m ie n ia n y j e s t ro d z a j rz u to w a n ia . W p rz e ciw n y m r a z i e s t o s u j e w c z e ś n ie j w ybran y ro d z a j rz u to w a n ia . v o id U p d a te P ro je c tio n (G L b o o le a n to g g le )
{ s t a t i c G L b o o l e a n s _ u s e P e r s p e c t i v e = G L_ T R U E ; / / z m ie n ia ro d z a j rzu to w a n ia i f (to g g le ) s _ u s e P e r s p e c t iv e = !s _ u s e P e rs p e c tiv e ; / / w y b ie ra m a c ie rz rzu to w a n ia i r e s e t u je j ą g l M a t r i x M o d e ( G L _ P R 0 J E C T I O N ); g lL o a d ld e n tit y O ; / / w y b ie ra ro d z a j rzu to w a n ia i f (s _ u s e P e rs p e c tiv e )
{
/ / rzu to w a n ie p e rs p e k ty w ic z n e g lF r u s t u m M . O . 1 .0 . -1 .0 , 1 .0 , 5. 100);
} e ls e // rzu to w a n ie o r t o g r a f ic z n e g ! 0 r t h o ( - 1 . 0 , 1 .0 , - 1 . 0 , 1 .0 , 5, 10 0 );
/ / p r z y w r a c a m a c i e r z m o d e lo w a n i a g lM a trix M o d e (G L _ M O D E L V IE W ); // k o n ie c fu n k c ji U p d a te P ro je c tio n
137
138
Część II ♦ Korzystanie z OpenGL
Wykorzystanie własnych macierzy przekształceń Omówione dotąd funkcje przekształceń umożliwiają wykonywanie operacji na stosach macierzy bez konieczności bezpośredniego określania elementów macierzy. Pozwala to na wykonywanie wielu przekształceń bez znajomości algebry macierzy. Jednak bardziej zaawansowane efekty wymagać będą bezpośrednich manipulacji macierzami. Przed stawiony teraz zostanie sposób ładowania elementów macierzy, mnożenia macierzy znajdującej się na szczycie stosu o inną macierz oraz przykład zastosowania własnych macierzy.
Ładowanie elementów macierzy Macierze OpenGL posiadają rozmiary 4x4, a ich elementy uporządkowane są w kolum nach, co ilustruje rysunek 5.16. Rysunek 5.16. Macierz OpenGL
mo
1T14 rri8 m-i2
mi
IT 15 m g
m2
rri6 m-jo nnią
m3
m7 m u
m iß m is
Macierze OpenGL można zadeklarować w programie jako dwuwymiarowe tablice, ale rozwiązanie takie posiada dość poważną wadę. W językach C i C++ elementy tablic uporządkowane są wierszami. I tak — aby odwołać się do elementu m3 macierzy poka zanej na rysunku 5 . 1 6 , zamiast intuicyjnie określić go jako m a t r i x [ 3 ] [ 0 ] , trzeba podać m a t r i x [ 0 ] [ 3 ] . Takie rozwiązanie będzie przyczyną wielu błędów, dlatego lepiej jest w praktyce wykorzystywać tablice jednowymiarowe do reprezentacji macierzy OpenGL. Tablica taka będzie posiadać 16 elementów, a n-ty element będzie odpowiadać ele mentowi mn macierzy z rysunku 5 . 1 6 . Aby na przykład wyspecyfikować macierz jednostkową (czego w praktyce nie trzeba nigdy robić mając do dyspozycji funkcję g l L o a d I d e n t i t y ) , można zdefiniować poniższą tablicę: G L flo a t id e n t it y [ 1 6 ] = { 1 .0 , 0.0. 0 .0 , 0 .0 , 0 .0 , 1.0,
0 .0 , 0 .0 , 0 .0 , 0 .0 , 1.0, 0 .0 ,
0 . 0 , 0 . 0 , 0 . 0 , 1 . 0 };
Po zdefiniowaniu tablicy należy załadować jej elementy do macierzy korzystając z funkcji g l L o a d M a t r i x ( ) posiadającej dwie wersje: v o i d g l L o a d M a t r i x d ( c o n s t G L d o u b l e * m a tr ix ); v o i d g l L o a d M a t r i x f ( c o n s t G L f l o a t * m a tr ix ) ;
Jedyną różnicą pomiędzy nimi jest typ parametru. Pierwsza z nich ładuje elementy typu d o u b l e , a druga typu f l o a t . Wywołanie funkcji g l L o a d M a t r i x ( ) powoduje, że elementy macierzy znajdującej się na szczycie bieżącego stosu macierzy zastępowane są elemen tami tablicy matrix. Należy więc pamiętać, że ładując elementy macierzy traci się in formację znajdującą się dotąd w macierzy umieszczonej na szczycie stosu.
Rozdział 5. ♦ Przekształcenia układu współrzędnych i m acierze OpenGL
139
Mnożenie macierzy Oprócz funkcji ładowania elementów do macierzy znajdującej się na szczycie stosu do stępna jest także funkcja umożliwiająca pomnożenie jej zawartości o inną macierz. Ma cierz tę określa się w ten sam sposób co funkcję glLoadMatrix() i następnie wywołuje się jedną z wersji funkcji glMultMatrix(): v o i d g l M u l t M a t r i x d ( c o n s t G L d o u b l e *m a trix )-, v o i d g l M u l t M a t r i x f ( c o n s t G L f l o a t * m a tr ix );
Parametr matrix jest tablicą 16 elementów. Jeśli macierz znajdującą się na szczycie stosu oznaczy się jako MA, a macierz zdefiniowaną za pomocą tablicy matrix jako MB, to na skutek wywołania funkcji gl Mul tMatri x () uzyska się na szczycie bieżącego stosu ma cierzy wynik działania MA*MB. Przypomnieć należy w tym miejscu, że mnożenie ma cierzy nie jest przemienne, a więc porządek mnożenia jest istotny i w większości przy padków wynik działania MA*MB będzie różny od wyniku działania MB*MA.
Przykład zastosowania macierzy definiowanych Program, który dołączony został na dysku CD jako ilustracja do pierwszego rozdziału książki, zawiera przykład zastosowania macierzy definiowanych bezpośrednio przez programistę. W tym przypadku służy ona do tworzenia cieni podczas animacji sześcianu. Interesujący tu fragment programu zaprezentowany został poniżej: G L f l o a t s h a d o w M a t r i x [ 1 6 ] = { l i g h t P o s [ l ] , 0 . 0 . 0 . 0 , 0 . 0 , -1 i g h t P o s [ 0 ] , 0 . 0 , - lig h t P o s f 2 ], -1 .0 , 0 .0 , 0 .0 , lig h t P o s C l] , 0 .0, 0 . 0 , 0 . 0 , 0 . 0 , 1 i g h t P o s f l ] }; / / r z u t o w a n ie s z e ś c i a n u za pomocą m a c ie r z y c i e n i g lM u ltM a trix f( s h a d o w M a trix ) ; D raw CubeO ;
Macierz ta definiuje rzut wierzchołków na płaszczyznę y = 0. Jeśli wybrany zostanie w programie kolor czarny (oraz zastosowane zostanie łączenie alfa i bufor powielania, których omówienie wykracza poza tematykę tego rozdziału), to uzyska się w ten sposób możliwość tworzenia cieni rysowanych obiektów. Macierz cieni wykorzystuje się do pomnożenia o nią zawartości macierzy znajdującej się na szczycie stosu macierzy mo delowania za pomocą funkcji gl Mul tMatri x (). Nie stosuje się w tym przypadku funkcji glLoadMatrixO, by zachować przekształcenia reprezentowane na stosie macierzy mo delowania używane do tworzenia sceny animacji. Własne macierze powinno się stosować jedynie w przypadku specjalizowanych zasto sowań. W pozostałych przypadkach należy zawsze korzystać z funkcji przekształceń udostępnianych przez OpenGL, ponieważ ich implementacja może korzystać ze wspar cia sprzętowego, do którego programista nie ma bezpośredniego dostępu.
140
Część II ♦ Korzystanie z OpenGL
Podsumowanie W rozdziale tym przedstawione zostały sposoby wykonywania przekształceń obiektów grafiki trójwymiarowej oraz określania widoku sceny za pomocą rzutowania. Omówio ne zostały macierze modelowania i rzutowania oraz działania wykonywane na tych ma cierzach za pomocą funkcji OpenGL i macierzy definiowanych przez programistę. Uzy skane w ten sposób zostały podstawowe narzędzia pozwalające tworzyć i animować obiekty trójwymiarowego świata gry.
Rozdział 6.
Kolory, łączenie kolorów i oświetlenie Świat pozbawiony kolorów byłby nudny i wywoływałby depresję. Podobnie grafika tworzona na ekranie komputerów jedynie z użyciem odcieni szarości byłaby monotonna i nieciekawa. Na szczęście OpenGL dysponuje środkami umożliwiającymi tworzenie barwnych światów gry. Kolor nie jest jedynym sposobem zwiększenia atrakcyjności tworzonej grafiki. OpenGL umożliwia manipulację oświetleniem obiektów oraz tworzenie wielu innych efektów. Rozdział ten rozpoczyna się omówieniem natury kolorów w świecie rzeczywistym i spo sobów ich reprezentacji w grafice komputerowej. Następnie przedstawione zostaną różne rodzaje oświetlenia obiektów. Rozdział kończy omówienie łączenia kolorów i przezro czystości. W rozdziale tym przedstawione zostaną: ♦ kolory w OpenGL; ♦ cienie; ♦ oświetlenia w OpenGL; ♦ źródła światła; ♦ łączenie kolorów i przezroczystość.
Czym są kolory? W tym miejscu trzeba przypomnieć sobie nieco wiadomości z fizyki. Światło składa się z fotonów, cząstek poruszających się w różnych kierunkach i drgających z różną czę stotliwością, dlatego też światło można interpretować zarówno jako cząstkę, jak i jako falę, co stanowi treść tak zwanego dualizmu korpuskularno-falowego. Rozpatrując światło jako falę można opisać ją za pomocą długości fali, co pokazuje rysunek 6.1.
142
Część II ♦ Korzystanie z OpenGL
Rysunek 6.1. Długość fali światła
Grzbiet
Widzialny zakres światła tworzą fale o długości od 390 nanometrów (nm) do 720 nm. W przedziale tym mieści się pełne spektrum tęczy: począwszy od fioletu, przez błękit, zieleń, żółcień, pomarańcz aż do czerwieni. Rysunek 6.2 przedstawia widzialne spek trum światła. Rysunek 6.2. Widzialne spektrum światła
B -2 £
III 2
co ~ o
li.
Czerwień Pomarańcz Żółcień Zieleń Błękit
CL
720 nm
Pokazane na nim kolory stanowią jedynie fragment zbioru milionów barw i odcieni, które rozróżnia ludzkie oko. Siatkówka oka składa się z milionów specjalnych komórek, których działanie przypomina zachowanie błony fotograficznej. Podrażnione światłem o określonej długości fali wysyłają impulsy do mózgu, w którym tworzony jest obraz. Istnieją trzy rodzaje takich komórek: reagujące na kolor zielony, czerwony i niebieski. W zależności od długości fali oglądanego światła wysyłają one do mózgu impulsy o róż nym natężeniu, co pozwala rozróżniać wiele odcieni kolorów.
Kolory w OpenGL Opisany model widzenia kolorów można rozszerzyć także o prezentację kolorów na ekra nie monitora. Kolorowe piksele powstają na nim na skutek świecenia z różną intensyw nością czerwonych, zielonych i niebieskich drobin fosforu. W grafice komputerowej in tensywność tych składowych opisuje się za pomocą wartości modelu RGB. Czasami do składowych tych dodaje się jeszcze jedną — współczynnik alfa, tworząc w ten sposób model RGBA (współczynnik alfa omówiony zostanie w dalszej części tego rozdziału przy przedstawieniu zasad łączenia kolorów). Kolory na ekranie można także opisywać za pomocą modelu indeksowego, w którym każdemu pikselowi na ekranie przyporząd kowany jest indeks koloru w tablicy zwanej mapą kolorów. Z każdym z indeksów moż na związać odpowiednią wartość składowej czerwonej, zielonej i niebieskiej.
Rozdział 6. ♦ Kolory, łączenie kolorów i ośw ietlenie
143
Głębia koloru Głębia koloru określa maksymalną liczbę kolorów, które może posiadać piksel i zależy od rozmiaru bufora koloru. Rozmiar ten może wynosić 4, 8, 16 i więcej bitów. 8-bitowy bufor koloru może przechowywać informację o jednym z 28, czyli 256 kolorów. Do stępne obecnie karty graficzne dysponują zwykle buforem kolorów o rozmiarze 8, 16, 24 lub 32 bitów. Dostępne liczby kolorów dla poszczególnych rozmiarów bufora koloru przedstawia tabela 6.1. Tabela 6.1. Najczęściej stosowane głębie kolorów Głębia kolorów
Charakterystyka
8
256 kolorów dostępnych w palecie indeksowanej od 0 do 255
16
65 536 kolorów, składowe modelu RGB opisane są przez (odpowiednio) 5, 6 i 5 bitów; czasami wykorzystywane jest tylko 15 bitów bufora koloru i wtedy każda ze składowych modelu RGB opisywana jest za pomocą 5 bitów
24
16 777 216 kolorów, każda ze składowych modelu RGB opisana za pomocą 8 bitów
32
Tak samo jak w przypadku głębi 24-bitowej, ale poprawiona efektywność przez zastosowanie architektury 32-bitowej
Sześcian kolorów OpenGL opisuje kolory za pomocą intensywności ich składowej czerwonej, zielonej i nie bieskiej. Intensywność poszczególnych składowych może przyjmować wartości z prze działu od 0 do 1. Rysunek 6.3 prezentuje za pomocą sześcianu kolorów to, w jaki sposób kombinacje różnej intensywności poszczególnych składowych tworzą spektrum kolo rów. Na przykład wartości R = 1.0, G = 0.0, B = 0.0 pozwalają uzyskać czerwień o naj większej intensywności. Wartości R = 1.0, G = 1.0, B = 0.0 opisują natomiast największe natężenie koloru żółtego. Jeśli wszystkie składowe posiadają wartość 0.0, to otrzymuje się kolor czarny, a jeśli wartość 1.0, to kolor biały. Jeśli wszystkie składowe mają taką samą wartość, to otrzymuje się odcień szarości, którego intensywność zależy od tego, w jakim miejscu przedziału od 0.0 do 1.0 znajduje się ta wartość. Sześcian kolorów może być pomocny w określeniu składowych żądanego koloru. Osie układu reprezentują składową czerwoną, zieloną i niebieską. Oddalenie się od danej osi zwiększa udział pozostałych składowych w tworzonym kolorze.
Model RGBA w OpenGL Specyfikując kolor w modelu RGBA przekazuje się funkcji glColor*() wartości skła dowej czerwonej, zielonej i niebieskiej. Funkcja glColor*() posiada kilkanaście wersji różniących się przyrostkami dodawanymi do jej nazwy. Do najczęściej używanych wersji należą: v o id v o id v o id v o id
g l C o l o r 3 f ( G L f l o a t r , G L f l o a t g, G L f l o a t b ) : g l C o l o r 4 f ( G L f l o a t r , G L f l o a t g, G L f l o a t b, G L f l o a t a ); g lC o lo r 3 f v ( c o n s t G L f lo a t *v); g lC o lo r 4 f v ( c o n s t G L f lo a t *v);
144
Część II ♦ Korzystanie z OpenGL
Rysunek 6.3. Sześcian kolorów
(R, G, B) = wartości z przedziału od 0 do 1 oznaczające intensywność składowych koloru
Zieleń
( 1.0, 0.0, 0.0)
Pierwsza z funkcji wybiera bieżący kolor na podstawie wartości składowej czerwonej reprezentowanej przez parametr r, zielonej g i niebieskiej b. Druga z nich posiada do datkowy parametr a reprezentujący wartość współczynnika alfa określającą sposób łą czenia kolorów (omówiona zostanie w dalszej części tego rozdziału). Wartości wszyst kich parametrów obu funkcji muszą należeć do przedziału od 0.0 do 1.0. Wartość 0.0 odpowiada oczywiście najmniejszej intensywności danej składowej, a wartość 1.0 naj większej. Kolejna para funkcji odpowiada już omówionej parze (z tą różnicą, że para metry umieszczane są w tablicy, której wskaźnik przekazywany jest funkcji). Taki spo sób przekazywania parametrów jest często używany w OpenGL i będzie pojawiać się podczas omawiania kolejnych funkcji. Aby wybrać kolor zielony za pomocą pierwszej z omówionych funkcji, należy wywołać ją w poniższy sposób: v o id g lC o l o r 3 f ( 0 . 0 f , l. O f , O .O f);
Aby wybrać kolor żółty, należy wywołać funkcję następująco: v o id g lC o lo r 3 f ( 1 . 0 f , l. O f , O .O f);
Jak łatwo zauważyć, podając wartości parametrów funkcji glColor*() można korzystać z pomocy w postaci sześcianu kolorów. I tak na przykład nadając wszystkim parame trom funkcji wartość 0.5 uzyskuje się kolor szary: v o id g lC o lo r 3 f ( 0 . 5 f . 0 .5 f , 0 . 5 f ) ;
Model kolorów RGBA będzie jeszcze wykorzystywany jako podstawowy model kolo rów tworzący grafikę przy użyciu OpenGL. Warto jednak zapoznać się także z modelem indeksowanym.
Model indeksowany w OpenGL W czasach systemu operacyjnego DOS programiści zmuszeni byli do tworzenia palety kolorów w postaci tablicy opisującej kolory używane w programie. Analogia z paletą malarza polegała na tym, że choć dostępna była ograniczona liczba kolorów, to możliwe było jednak tworzenie nowych poprzez łączenie już istniejących. Palety kolorów uży wane są nadal w programach korzystających z 8-bitowej głębi koloru.
Rozdział 6 . ♦ Kolory, łączenie kolorów i ośw ietlenie
145
W modelu indeksowanym OpenGL tworzy mapą kolorów, której rozmiar określa liczbę jednocześnie dostępnych kolorów. Sposób działania tej mapy ilustruje rysunek 6.4. Rozmiar mapy kolorów jest zawsze potęgą liczby 2 i najczęściej wynosi 256 bądź 4096 (w zależności od możliwości sprzętowych). W modelu indeksowanym wielu pikselom przypisany jest kolor opisywany przez pozycję mapy o danym indeksie. Jeśli pozycja ta zostanie zmodyfikowana, to automatycznie zmieni się kolor wszystkich tych pikseli. Rysunek 6.4. Przykład mapy kolorów
Indeks
Intensywność R G B
3 4 5
0 1 3 5 7 8
6
9
7
10 20 21 22 30 • • • 255
0 t a
8 9 to 11 • • ♦ 255
0 1 3 4
6 7 8 11 19 22 23 40
• • • 255
0 1 3 6 8 9
10 15 20 25 30 35 • • • 255
Aby wybrać określoną pozycję mapy, używa się funkcji gl Index*( ). Jej najczęściej wy korzystywane wersje posiadają następujące prototypy: v o id g lI n d e x f( G L flo a t c); v o id g lI n d e x fv ( c o n s t G L f lo a t * c);
Obie wersje funkcji wybierają pozycję mapy o indeksie c. Druga z nich pobiera jednak wartość indeksu z tablicy, której wskaźnik został jej przekazany. Tablica ta zawiera tyl ko jedną wartość typu G L flo a t reprezentującą wartość indeksu. Za pomocą funkcji g lC le a rIn d e x ( ) można wypełnić cały bufor kolorów pozycją mapy o podanym indeksie: v o i d g l C l e a r I n d e x ( G L f l o a t c in d e x );
Domyślnie bufor kolorów wypełniany jest pozycją mapy o indeksie 0.0.
Cieniowanie Dotychczas przy rysowaniu podstawowych elementów grafiki OpenGL wypełnienie stanowił jednolity, pojedynczy kolor. A co stanie się, jeśli podczas definiowania kolej nych wierzchołków takiego elementu wybierze się różne kolory? Na pytanie to można odpowiedzieć posługując się przykładem odcinka łączącego dwa wierzchołki o różnych kolorach. Dla uproszczenia należy przyjąć, że pierwszy z nich jest w kolorze czarnym, a drugi jest biały. Jaki będzie kolor łączącego je odcinka? Można to ustalić posługując się modelem cieniowania.
146
Część II ♦ Korzystanie z OpenGL
Cieniowanie może być płaskie lub gładkie. Cieniowanie płaskie wykonywane jest za po mocą pojedynczego koloru. Zwykle jest to kolor ostatniego wierzchołka (jedynie w przy padku trybu rysowania wielokątów o dowolnej liczbie wierzchołków wybieranego za pomocą stałej GL POLYGONjest to kolor pierwszego z wierzchołków). Cieniowanie gładkie zwane też cieniowaniem Gouraud stosuje natomiast interpolację koloru pikseli odcinka. Jeśli dla wybranego przed chwilą odcinka zastosuje się cieniowanie płaskie, to będzie on miał kolor biały, ponieważ ostatni z wierzchołków odcinka ma taki kolor. Natomiast w przypadku cieniowania gładkiego kolor pikseli będzie zmieniać się począwszy od koloru czarnego pierwszego wierzchołka poprzez coraz jaśniejsze odcienie szarości aż do koloru białego drugiego z wierzchołków. Efekt ten pokazuje rysunek 6.5. Rysunek 6.5. . . . . Gładkie cieniowanie odcinka łączącego wierzchołek w kolorze czarnym z wierzchołkiem w kolorze białym
■ m
m m m m m
Pierwszy wierzchołek
................ i,i
= 3 6 0 .Of) angle = O.Of;
/ / zwiększa l i c z n i k kąta obrotu
/ / wykonuje p r z e k s z ta łc e n ia g l T r a n s l a t e f ( 0 . O f . O.Of, - 3 . O f); g lR o t a t e f ( a n g le , l . O f , O.Of, O.Of); g lR o t a t e f ( a n g le , O.Of, l . O f , O.Of); g lR o t a t e f ( a n g le , O.Of. O.Of, l . O f ) ; D raw C ube(0.0f. O.Of, O.Of);
/ / ry s u je p rze k sz tałco n y sześcian
g lF lu s h O ; SwapBuffers(g_HDC);
/ / przełą cza bufory
} Funkcja RenderO wywoływana jest w celu utworzenia kolejnej klatki animacji. Jej kod nie zawiera żadnych nowych elementów, które wymagałyby szerszego omówienia. Funk cja Render () przesuwa za każdym razem bieżący układ współrzędnych do punktu (0, 0, -3), w którym zostaje on obrócony o ten sam kąt względem wszystkich osi, a następnie rysuje sześcian. Oświetlenie sześcianu wyznaczane jest automatycznie przez OpenGL i nie wymaga jakiejkolwiek interwencji ze strony programisty. W przykładzie tym nie jest używana utworzona wcześniej funkcja CalculateNormal (), ponieważ „ręczne” wyznaczenie normalnych w przypadku sześcianu nie przedstawia trudności. Podobne rozwiązanie stosowane jest często w przypadku ładowania trójwy miarowych modeli z plików. Niektóre formaty takich plików zawierają już gotowe dane określające normalne do wszystkich wielokątów modelu. W pozostałych przypadkach normalne muszą być wyznaczone podczas działania programu. Rysunek 6.10 ilustruje działanie omówionego programu.
158
Część II ♦ Korzystanie z OpenGL
Rysunek 6.10. Animacja obracającego się i oświetlonego sześcianu
Tworzenie źródeł światła Jak pokazano w ostatnim przykładzie, dla źródła światła można zdefiniować szereg właściwości, takich jak kolor, położenie czy kierunek. Stosuje się w tym celu funkcję g l L ig h t * ( ). Poniższa wersja będzie tu najczęściej przez używana: vo id glLightfv(G Lenum light, GLenum pname, TYPE *param);
Funkcja ta posiada trzy parametry. Pierwszy z nich podaje źródło światła, którego wła ściwości są określane, drugi nazwę właściwości, a trzeci jej nową wartość. Jak już wspomniano, parametr light może przyjmować wartości GL_LIGHTO, GL_LIGHT1 aż do GL LIGHT7, co sprawia, że wybierane jest w ten sposób jedno z ośmiu dostępnych źródeł światła. Parametr pname określający właściwość źródła światła może być jedną z warto ści przedstawionych w tabeli 6.2. Tabela 6.2. Właściwości źródła światła Właściwość
Znaczenie
GL_AMBIENT
Intensywność światła otoczenia
GL_DIFFUSE
Intensywność światła rozproszonego
GL_SPECULAR
Intensywność światła odbicia Pozycja źródła światła (x, y, z, w) Wektor kierunku strumienia światła (x, y, z)
GL_POSITION GL_SPOT_DI RECTION GL_S POT_EX PONENT GL_SPOT_CUTOFF GL_CONSTANT_ATTENUATION
Koncentracja strumienia światła Kąt rozwarcia strumienia światła
GL_LINEAR_ATTENUATION
Stała wartość tłumienia Liniowa wartość tłumienia
GL_QUADRATIC_ATTENUATION
Kwadratowa wartość tłumienia
Rozdział 6 . ♦ Kolory, łączenie kolorów i ośw ietlenie
159
Dzięki zastosowaniu funkcji g l L ig h t f v ( ) ostatni parametr będzie zawsze tablicą warto ści typu f 1oat. Na przykład światło otoczenia określić można w następujący sposób: f l o a t a m b ie n t L ig h t[ ] = { l . O f , l . O f , l . O f , l . O f }; / / ś w ia t ło b i a ł e , duża intensywność gl L i g h t f v ( LIGHTO, GL_AMBIENT, a m b ie n t L ig h t) ; / / o k re ś la ś w ia t ło otoczenia
Trzeba pamiętać o tym, że określając właściwość światła otoczenia dla danego źródła światła podaje się intensywność jego składowych w modelu RGB A, którą źródło dodaje do oświetlenia sceny. Powyższe dwa wiersze kodu informują więc OpenGL, że źródło światła GL LIGHTO dodaje do oświetlenia sceny światło otoczenia w kolorze białym. Domyślnie scena nie posiada światła otoczenia, czyli jego wartość wynosi (0.0, 0.0, 0.0, 1.0). Znaczenie parametru GL DI FFUSE jest nieco inne. Oznacza on kolor światła emitowa nego przez źródło. Domyślną wartością właściwości GL_DIFFUSE jest (1 .0 , 1.0, 1.0, 1 .0 ), ale tylko dla źródła światła GL LIGHTO. Dla pozostałych źródeł wynosi ona (0 .0 , 0 . 0 , 0 . 0 , 0 . 0 ).
Właściwość GL SPECULAR pozwala określić kolor światła odbicia. W praktyce często na daje się jej taką samą wartość jak właściwości GL DI FFUSE określającej kolor światła rozproszonego. W ten sposób uzyskuje się realistyczny efekt, ponieważ w świecie rze czywistym obiekty często sprawiają wrażenie, że mają taki sam kolor w świetle odbi tym, jak i rozproszonym. Domyślna wartość właściwości GL SPECULAR wynosi (1.0, 1.0, 1.0,1.0) dla źródła światła GL_LIGHT0 oraz (0.0, 0.0, 0.0, 0.0) dla pozostałych źródeł. Jak już wspomniano, podczas definiowania składowych w modelu RGB A ostatnia wartość prezentuje współczynnik alfa. Nie trzeba się nią jednak zajmować aż do mo mentu omówienia zagadnienia łączenia kolorów.
Położenie źródła światła Sposób umieszczania źródła światła omówiony został już przy okazji ostatniego pro gramu. Teraz należy jednak przyjrzeć się temu zagadnieniu dokładniej. Położenie źródła światła definiuje się za pomocą wektora (x, y, z, w) właściwości GL POSITION. Jak już poinformowano wcześniej, jeśli wartość w jest równa 0.0, to (.x, y, z) definiuje wektor określający kierunek, z którego pada światło. Tym samym definiowane jest kierunkowe źródło światła, czyli takie, którego wszystkie promienie są równoległe tak, jakby znaj dowało się ono nieskończenie daleko. Najlepszym przykładem kierunkowego źródła światła jest oczywiście Słońce. Na niewielkim obszarze Ziemi jego promienie są prak tycznie równoległe do siebie. Kierunkowe źródło światła może zostać zdefiniowane na przykład w poniższy sposób f l o a t 1 i g h t P o s i t i o n [ ] = { O.Of, O.Of, l . O f , O.Of }; gl Lightfv(GL_LIGH T0, GL_P0SITI0N, 1i g h t P o s i t i o n ) ;
Powyższy fragment kodu definiuje kierunkowe źródło światła, którego promienie pa dają z kierunku dodatniej części osi z, czyli w domyślnym kierunku orientacji kamery. Jeśli wartość w będzie różna od 0.0, to wektor (x, y, z) zdefiniuje pozycyjne źródło światła. Źródło takie znajduje się w punkcie o współrzędnych (x, y, z) i promieniuje we wszystkich kierunkach. Przykładem takiego źródła światła w świecie rzeczywistym jest
160
Część II ♦ Korzystanie z OpenGL
żarówka. Poniższy fragment kodu definiuje pozycyjne źródło światła znajdujące się w po czątku układu współrzędnych. f l o a t 1i g h t P o s i t i o n [ ] = { O.Of. O.Of. O.Of. l . O f }; glLightfv(G L_LIGHTO. GL_POSITION. 1i g h t P o s i t i o n ) ;
Tłumienie Tłumienie charakteryzuje spadek intensywności światła w miarę oddalania się od jego źródła. Efekt ten najlepiej można zaobserwować na przykładzie latami znajdującej się na zamglonej ulicy. Taki sam efekt można uzyskać w tworzonym przez programistę świecie gry, ale tylko dla pozycyjnych źródeł światła. Tłumienie nie ma sensu w przy padku kierunkowych źródeł światła, ponieważ znajdują się one w nieskończonej odle głości od oświetlanych obiektów. OpenGL wyznacza tłumienie światła mnożąc jego intensywność przez współczynnik tłumienia. Tabela 6.3 prezentuje domyślne wartości właściwości źródła związanych z tłumieniem. Tabela 6.3. Właściwości źródła światła określające jego tłumienie Właściwość
Wartość domyślna
GL_CONSTANT_ATTENUATION
1.0
GL_LINEAR_ATTENUATION
0.0
GL_QUADRATIC_ATTENUATION
0.0
Wartości tych właściwości można zmienić za pomocą funkcji gl Li ght (): glLightfv(GL_LIGHTO. GL_ATTENUATION. 4 . 0 f ) ; glLightfv(G L_LIGHTO, GL_LINEAR_ATTENUATION, l. Of ) ; g lL i g h t f v ( GL_LIGHTO, GL_QUADRATIC_ATTENUATION, 0. 25f) ;
Tłumienie ma wpływ na światło w całym świecie tworzonym przez OpenGL z wyjąt kiem światła emisji oraz globalnego światła otoczenia. Stosowanie tłumienia może nie co spowolnić tworzenie grafiki, ponieważ wymaga dodatkowych obliczeń.
Strumień światła Jeśli promieniowanie pozycyjnego źródła światła, zostanie zawężone tylko do określo nego kierunku, to uzyskany zostanie w efekcie strumień światła. Strumień światła two rzy się w ten sam sposób co pozycyjne źródło światła (dodając jedynie kilka specyficz nych parametrów, takich jak kąt rozwarcia strumienia światła, kierunek strumienia światła i jego zogniskowanie). Przyglądając się strumieniowi światła w ciemnościach zauważyć można, że tworzy on stożek świetlny w kierunku, w którym biegnie. Stożek ten definiuje się w OpenGL po dając wartość kąta, jaki tworzy powierzchnia stożka z jego osią. Kąt ten stanowi war tość właściwości GL SPOT CUTOFF, co pokazuje rysunek 6.11.
Rozdział 6 . ♦ Kolory, łączenie kolorów i ośw ietlenie
161
Rysunek 6.11. Właściwość G L S P O T C UTOFF definiuje kąt pomiędzy powierzchnią i osią stożka strumienia światła
Jeśli właściwości GL SPOT CUTOFF nadana zostanie wartość 180°, to światło rozchodzić się będzie we wszystkich kierunkach i uzyskany zostanie efekt identyczny jak w przypadku zwykłego pozycyjnego źródła światła. Oprócz specjalnej wartości 180° OpenGL do puszcza jedynie wartości właściwości GL SPOT CUTOFF z przedziału od 0 do 90°. Stru mień światła o kącie rozwarcia stożka wynoszącym 30° trzeba zdefiniować w następu jący sposób: glL ig htfv(G L_LIG H TO . GL_SP0T_CUT0FF, 1 5 .O f);
/ / stożek o kącie rozwarcia 30 stopni
Dla strumienia światła trzeba podać także jego kierunek. Określa go właściwość GL_ SP0T DIRECTI0N, która jest wektorem postaci (x, y , z). Domyślną wartością właściwości GL_SP0T_DIRECTION jest wektor (0 .0 , 0.0, -1. Ci) wskazujący ujemną część osi z. Inny kie runek strumienia światła można ustalić za pomocą funkcji gl Li g h tfv (): f l o a t s p o t l i g h t D i r e c t i o n [ ] = { 0 .0 , - 1 .0 , 0.0 } ; gl Lightfv(GL_LIGH T0, GL_SP0T_DIRECTI0N, s p o t l i g h t D i r e c t i o n ) ;
W tym przypadku strumień światła będzie skierowany w ujemnym kierunku osi y. Ostatnią z właściwości strumienia światła jest jego zogniskowanie, które określa kon centrację światła na osi stożka. Oddalając się od osi stożka w kierunku jego powierzchni światło staje się coraz bardziej stłumione i osiąga zerową intensywność na jego po wierzchni. Zwiększając wartość właściwości GL_SP0T_EXP0NENT można otrzymać bar dziej skoncentrowany strumień światła. Poniższy wiersz kodu nadaje tej właściwości wartość 10.0: gl Li ghtfv(GL_LIGHT0. GL_SPOT_EXPONENT, 1 0 .O f);
Poniżej przedstawiony został fragment programu, który kieruje strumień światła na ob racającą się kulę: ¡11111 Zmienne g lo b a ln e f l o a t angle = O.Of;
/ / bieżący ką t obro tu k u l i
////// flo a t flo a t flo a t
/ / ś w ia t ło otoczenia / / ś w ia t ło rozproszone / / ś w ia t ło o d b ic ia
Zmienne o p is u ją c e o ś w ie t le n ie a m b ie n t L ig h t[ ] = { 0 . 5 f , 0 . 5 f , 0 . 5 f , l . O f } ; d if f u s e L ig h t [ ] = { 0 .5 f, 0 .5 f, 0 .5 f, l. O f }; s p e c u la rL ig h t[] = { l. O f, l.O f, l.O f. l. O f };
/ / pozycja ź ró d ła s tru m ie n ia ś w ia t ła f l o a t s p o t l i g h t P o s i t i o n [ ] = { 6 . 0 f , 0 . 5 f , O.Of, l . O f }; f l o a t s p o t l i g h t D i r e c t i o n [ ] = { - l . O f , O.Of, - l . O f } ; / / k ie runek stru m ie n ia ś w ia tła
162
Część II ♦ Korzystanie z OpenGL
I I I I 11 Zmienne o p is u ją ce m a te ria l f l o a t matAmbient[] = { l . O f , l . O f , l . O f , l . O f } flo a t m a tD iff[] = { l.O f. l.O f, l.O f, l.O f} ; f l o a t m a tS p e c ula r[] = { l . O f , l . O f . l . O f , l . O f
// // //
m a te ria ł w ś w ie t le otoczenia m a t e ria ł w ś w ie t le rozproszonym m a te ria ! w ś w ie t le o d b ic ia
// In itia liz e / / o p is : i n i c j u j e OpenGL void I n i t i a l i z e ( ) g l C l e a r C o lo r ( 0 . O f. O.Of, O.Of, O.Of);
/ / k o lo r czarny
glShadeModel(GL_SMOOTH); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glFrontFace(GL_CCW);
// // // // //
aktywuje c ie n io w an ie g ła d k ie oraz usuwanie niewidocznych powierzchni wyłącza o b lic z e n ie d la t y ln y c h s tr o n w ie lo kątów t y l n e = uporządkowanie w ie rzchołków w ie lo k ą ta przeciwne do k ie runku ruchu wskazówek zegara
glEnable(GL_LIGHTING);
/ / aktywuje wyznaczanie o ś w ie t le n ia
/ / K o n fig u ru je ź ró d ło ś w ia t ła LIGHTO / / ś w ia t ło oto czen ia glLightfv(G L_LIGHTO, GL_AMBIENT, a m b ien tL ig ht) / / ś w ia t ło rozproszone g l L i g h t f v (GL_LIGHTO. GL_DIFFUSE, a m b ie n tLig h t) glLightfv(GL_LIGHTO, GL_POSITION, s p o t l i g h t P o s i t i o n ) ; / / pozycja ź ró d ła ś w ia t ła g lL i ghtf(GL_LIGHT0. GL_SP0T_CUT0FF, 4 0 .O f); / / stożek o k ącie rozw arcia 80 stopni g lL i ghtf(GL_LIGHT0. GL_SPOT_EXPONENT, 3 0 .Of); g l L i ghtfv(GL_LIGHTO, GL_SPOT_DIRECTION, s p o t l i g h t D i r e c t i o n ) ; / / Włącza ź ró d ło ś w ia tła glEnable(GL_LIGHT0); glEnable(GL_COLOR_MATERIAL); glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); glMaterialfv(GL_FRONT, GL_SPECULAR, m atS p e c u la r); glM ateria lf(GL_FR 0NT, GLJHININESS. 1 0 .O f); / / Render / / o p is : r y s u je scenę void R ender!) glCl e a r (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); / / opróżnia b ufo ry ekranu i g łę b i / / r e s e tu je macierz modelowania g lL o a d ld e n tity O ; angle = angle + O . l f ; / / zwiększa l i c z n i k kąta obrotu i f (angle >= 3 6 0 .Of) angle = O.Of; / / wykonuje p r z e k s z ta łc e n ia / / ( p rz e s u n ię c ie o 5 jednostek w t y ł i o b ró t dookoła osi y) g l T r a n s l a t e f ( 0 . 0 f , O.Of, - 5 . O f); g lR o ta t e f( a n g le , O.Of, l . O f , O.Of); g lC o lo r 3 f( 0 .0 f, 0 .5 f, 0 .5 f) ; a u x S o lid S p h e re (1 .0 ); g lF lu s h O ;
SwapBuffers(g_HDC);
/ / przełą cza bu fo ry
Rozdział 6 . ♦ Kolory, łączenie kolorów i ośw ietlenie
163
Działanie programu ilustruje rysunek 6.12. Do narysowania kuli użyta została funkcja a u xS o lidS phereO z biblioteki pomocniczej GLAUX. Funkcji tej przekazany został żą dany promień kuli. Aby móc korzystać z funkcji biblioteki GLAUX, należy umieścić plik GLAUXLIB wśród bibliotek dołączanych przez kompilator. Sposób postępowania podany został w rozdziale 1. Rysunek 6.12. Strumień światła padający na wirującą kulę
W powyższym fragmencie programu znajduje się kilka wierszy kodu, które nie zostały jeszcze omówione. Dotyczą one definiowania materiałów, do omówienia którego teraz można przejść.
Definiowanie materiałów Pokazano dotąd kilka przykładów definiowania właściwości materiałów tworzących obiekty, ale nie zostały jeszcze omówione służące do tego funkcje. Definiowanie mate riału przypomina definiowanie źródła światła, ale oczywiście odbywa się za pomocą in nych funkcji: void g lM a terialf(G Le num vo id g lM aterialf(G Lenum
face, GLenum pname, TYPE param): face, GLenum pname, TYPE *param):
Funkcje te definiują właściwości materiałów uwzględniane podczas obliczeń oświetle nia. Parametr face pozwala określić, którą stronę wielokąta tworzy dany materiał. Pa rametr ten może mieć jedną z wartości GL_FR0NT, GL_BACK lub GL_FRONT_AND_BACK. War tość GL FRONT ogranicza zastosowanie materiału tylko do przedniej strony wielokąta, a GL_BACK tylko do tylnej. Wartość GL_FRONT_AND_BACK powoduje użyciemateriałudla obu stron wielokąta. Kolejny parametr, pname, określa właściwość materiału,którama być zmodyfikowana. Może on przyjmować jedną z wartości wymienionych w tabeli 6.4. Ostatni z parametrów, param, w zależności od wersji funkcji jest albo tablicą (dla gl Ma t e r i a l f v ) , albo skalarem (dla g lM a te ri a lf ) .
164
Część II ♦ Korzystanie z OpenGL
Tabela 6.4. Właściwości materiałów Właściwość
Znaczenie
GL_AMBIENT
K o lo r przy oświetleniu światłem otoczenia
GL_DIFFUSE
K o lo r przy oświetleniu światłem rozproszonym
GL_AMBIENT_AND_DIFFUSE
K o lo r przy oświetleniu światłem otoczenia i światłem rozproszonym
GL_SPECULAR
K o lo r przy oświetleniu światłem odbicia
GLSHININESS p GLJMISSION
W spółczynnik odbicia K o lo r światła emisji
Aby określić kolor materiału dla przedniej i tylnej strony wielokąta widziany w świetle otoczenia jako czerwony, należy wykorzystać następujący fragment kodu: f l o a t r e d [ ] = { l . O f . O.Of. O.Of. l . O f }; g lM a te ri a1fv(GL_FRONT_AND_BACK. GL_AMBIENT, re d );
Aby określić kolor materiału dla przedniej strony wielokąta widziany w świetle otocze nia i świetle rozproszonym jako biały, należy wykorzystać poniższy fragment kodu: f l o a t w h i t e [ ] = { l . O f , l . O f , l . O f , l . O f }; g lM a te ri alfv(GL_FR0NT, GL_AMBIENT_AND_DIFFUSE, w h it e ) ;
Trzeba pamiętać, że zdefiniowany materiał będzie określać wygląd wszystkich tworzo nych wielokątów aż do momentu kolejnego wywołania funkcji gl Materi al *(). Inny sposób określania właściwości materiałów polega na śledzeniu kolorów. Pozwala on określać właściwości materiałów za pomocą funkcji glColor*(). Śledzenie kolorów musi najpierw zostać włączone poprzez wywołanie funkcji glEnableO z parametrem GL_C0L0R_MATERIAL. Następnie za pomocą funkcji glColorMaterial () należy podać, które z właściwości materiałów będą określane przez wywołania funkcji glColor*(). Poniż szy fragment kodu powoduje, że kolor przedniej strony wielokątów widziany w świetle rozproszonym będzie ustalany na drodze śledzenia kolorów: glEnable(GL_COLOR_MATERIAL); / / włącza ś led ze nie kolorów glColorMaterial(GL_FR0NT, GL_DIFFUSE); / / strona p rze dn ia , ś w ia t ło rozproszone g l C o l o r 3 f ( 1 . 0 f , O.Of, O.Of); / / k o lo r czerwony glBegin(GL_TRIANGLES); / / t u t a j r y s u je t r ó j k ą t y g lE n d O ;
Jak widać, śledzenie kolorów jest wyjątkowo łatwe w konfiguracji i użyciu. Poprzedni program przykładowy wykorzystuje śledzenie kolorów do określenie właściwości mate riału kuli. Definiując różne właściwości materiałów można uzyskać różne efekty zwią zane z ich oświetleniem, które omówione zostaną niebawem.
Modele oświetlenia Z tworzeniem oświetlenia sceny związane jest jeszcze jedno istotne zagadnienie, które należy >mówić: wybór modelu oświetlenia. Model oświetlenia w OpenGL składa się z czterech komponentów mających wpływ na wygląd sceny:
Rozdział 6. ♦ Kolory, łączenie kolorów i ośw ietlenie
165
♦ intensywności światła otoczenia sceny; ♦ położenia obserwatora (lokalne lub w nieskończoności), które ma wpływ na wyznaczanie kąta odbicia; ♦ oświetlenia jedno- lub dwustronnego; ♦ oddzielnego lub wspólnego traktowania koloru odbicia z kolorami widzianymi w świetle otoczenia i świetle rozproszonym. Model oświetlenia definiuje się za pomocą jednej z następujących wersji funkcji gl Li ghtModel*(): void glLightM odel[ i f ] (GLenum pname, TYPE param): void glLightM odel[ i f ]v(GLenum pname, TYPE *param):
Pierwszy z parametrów tych funkcji, pname, określa definiowaną właściwość modelu oświetlenia. Drugi (w zależności od wersji funkcji) może być pojedynczą wartością typu flo at lub tablicą takich wartości. Parametr pname może przyjmować wartości przedsta wione w tabeli 6.5. Tabela 6.5. Właściwości modelu oświetlenia Właściwość
Znaczenie
GL_LIGHT_MODEL_AMBIENT
Intensywność światła otaczającego sceny w modelu R G B A; domyślnie wartość (0.2, 0.2, 0.2,1.0)
GL_LIGHT_MODEL_LOCAL_VIEWER
Obserwator lokalny lub w nieskończoności; domyślnie wartość GL FALSE (obserwator w nieskończoności)
GL_LIGHT_MODEL_TWO_SIDE
Oświetlenie jedno- lub dwustronne; domyślnie wartość GL FALSE (oświetlenie jednostronne)
GLJ_ IGHT_M0DEL_C0L0R_C0NTR0L
Oddzielne traktowanie koloru w świetle odbicia od kolorów w świetle otoczenia i świetle rozproszonym; domyślnie wartość GL SINGLE COLOR (łączne traktowanie)
Pierwszą z właściwości podanych w tabeli 6.5 jest GL_LIGHT_MODEL_AMBIENT. Jak poka zano już wcześniej, światło otoczenia może pochodzić od zdefiniowanych źródeł świa tła. Właściwość GL_LIGHT_MODEL_AMBIENT modelu oświetlenia umożliwia dodanie do sceny światła otoczenia, które nie jest związane z żadnym źródłem światła. Światło to nazywa się globalnym światłem otoczenia. Poniżej zaprezentowany został fragment kodu, który dodaje do sceny globalne światło otoczenia o średniej intensywności: f l o a t am bie ntL ig h tM o de l[ ] = { 0 . 5 , 0 .5 , 0 .5, 1 . 0 } ; / / ś rednia intensywność gl LightModel(GL_LIGHT_MODEL_AMBIENT, ambientLig htM odel) ;
Gdy dla obiektów znajdujących się na scenie OpenGL wyznacza także światło odbicia, to brany jest pod uwagę punkt obserwacji. Właściwość GL_LIGHT_MODEL_LOCAL_VIEWER pozwala określić, czy obserwator efektów odbicia jest lokalny, czy też znajduje się w nieskończonej odległości od obiektów. Lokalne punkty obserwacji zwiększają realizm tworzonej sceny, ale zmniejszają przy tym efektywność jej tworzenia, ponieważ dla każ dego wierzchołka musi zostać wyznaczony kierunek obserwacji. Domyślnie jest więc wykorzystywany nieskończenie odległy punkt obserwacji (wartość GL FALSE). Można jed nak zmienić go na lokalny punkt obserwacji w następujący sposób: g lL ig h tM o d e li ( GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
166
Część II ♦ Korzystanie z OpenGL
Kolejną z właściwości modelu oświetlenia jest GL_LIGHT_MODEL_TWO_SIDE. Decyduje ona o tym, czy OpenGL oblicza oświetlenie tylnych stron wielokątów. Gdyby na przykład został wykonany przekrój oświetlonego sześcianu, to okazałoby się, że wewnętrzne strony jego ścian nie są prawidłowo oświetlone. Aby to zmienić, wystarczy nadać wła ściwości GL_LIGHT_M0DEL_TW0_SIDE wartość GL_TRUE: g lL i ghtModeli (GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
Dzięki temu OpenGL wyznaczy dla tylnych stron wielokątów normalne jako wektory przeciwne do podanych przez programistę dla stron przednich i prawidłowo obliczy oświetlenie tylnych stron wielokątów. Oczywiście spowoduje to spowolnienie tworze nia grafiki. Do wyznaczania oświetlenia tylko dla jednej strony wielokątów można zaw sze powrócić nadając właściwości GL_LIGHT_MODEL_TWO_SIDE wartość GL_FALSE. Ostatnią z właściwości modelu oświetlenia jest właściwość GL_LIGHT_M0DEL_C0L0R_C0NTR0L. Ma ona znaczenie, gdy oświetlane są obiekty pokryte teksturą. W przypadku zastoso wania tekstur standardowy sposób tworzenia efektów odbicia nie sprawdza się. Dlatego też OpenGL tworzy dla wszystkich wierzchołków podwójny opis kolorów: dodatkowy w świetle odbicia i podstawowy dla pozostałych rodzajów światła. Dla tekstury wyko rzystywany jest najpierw kolor podstawowy, a następnie dodawany jest do niego kolor dodatkowy. Pozwala to uzyskać lepiej widoczne efekty odbicia na teksturach. Poniższy wiersz kodu informuje OpenGL, że powinien stosować podwójny opis kolorów: g lL ig h tM o d e li (GL_LIGHT_M0DEL_C0L0R_C0NTR0L. GL_SEPARATE_SPECULAR_COLOR);
Aby OpenGL tworzył łączny opis kolorów dla wszystkich rodzajów światła, trzeba nadać właściwości GL_LIGHT_M0DEL_C0L0R_C0NTR0L wartość GL_SINGLE_C0L0R. Właści wość GL_LIGHT_M0DEL_C0L0R_C0NTR0L powinno się modyfikować jedynie, gdy używana jest tekstura. W przeciwnym razie nie ma ona znaczenia.
Efekty odbicia Ostatni z przykładowych programów pokazał, w jaki sposób można uzyskać efekt oświe tlenia obracającej się kuli za pomocą strumienia światła. Przedstawionych teraz zostanie kilka innych interesujących efektów związanych ze światłem odbicia i właściwościami materiałów. Obserwując działanie wspomnianego programu, a nawet oglądając rysunek zamieszczony w książce można zauważyć połysk oświetlanej powierzchni kuli. Powstaje on, gdy kąt padania światła jest mały i polega na odbiciu prawie całego światła. Chociaż strumień światła utworzony przez program nie posiada składowej odbicia, to można ją łatwo dodać. Podobnie jak w przypadku innych rodzajów światła określa się ją za pomocą funkcji gl Li g h tf v ( ) : f l o a t s p e c u la r L ig h t [ ] = { l . O f , l . O f . l . O f , l . O f }; / / ś w ia t ło o d b ic ia f l o a t 1ig h t P o s itio n C ] = { O.Of. O.Of, O.Of, l . O f }; / / p o łoż e n ie ź ró d ła glEnable(GL_LIGHTING);
/ / włącza o b lic z e n ia o ś w ie t le n ia
/ / k o n fig u r u je i włącza ź ró d ło GL_LIGHT0 glL ig h tfv(G L_L IG H T0 . GL_SPECULAR, s p e c u la r L ig h t ) ; glLightfv(GL_LIGHTO, GL_P0SITI0N, 1i g h t P o s i t i o n ) ; glE nable(GLLIGHTO);
Rozdział 6 . ♦ Kolory, łączenie kolorów i ośw ietlenie
167
Oczywiście do źródła GL LIGHTO można dodać także światło otoczenia i światło rozpro szone. Jednak tymczasem należy poprzestać na zdefiniowaniu tylko światła odbicia w kolorze białym o dużej intensywności, przypominającego światło słoneczne. Aby uzyskać efekt odbicia, trzeba zdefiniować także odpowiednio stopień odbicia dla da nego materiału. Program oświetlający kulę zastosował w tym celu poniższy fragment kodu: f l o a t m a tS p e c u la r[] = { l . O f , l . O f , l . O f , l . O f } ;
/ / m a te ria ł w ś w ie t le o d b ic ia
/ / m a t e r ia ł o słabym połysku glMaterialfv(GL_FRONT, GL_SPECULAR, m atS p e c u la r); glM aterialf(GL_FR0NT, GL_SHININESS, 1 0 .O f);
Wartości (1.0, 1.0, 1.0, 1.0) umieszczone w tablicy matSpecular oznaczają, że każda powierzchnia zdefiniowana po wywołaniu funkcji glM aterialfv() będzie całkowicie odbijać światło odbicia. Ostatni z wierszy tego fragmentu kodu nadaje wartość właściwości GL SHININESS, która definiuje skupienie odbitego światła. Właściwość ta może przyjmować wartości z prze działu od 1.0 do 128.0 oraz wartość 0.0 oznaczającą, że światło odbicia nie jest skupione. Jeśli nadana jej zostanie wartość 128.0, to tworzone powierzchnie będą wyraźnie poły skujące.
Ruchome źródła światła Źródła światła można przemieszczać i obracać tak samo jak każdy inny obiekt OpenGL. Gdy wywołuje się funkcję glLight*() w celu zdefiniowania położenia źródła światła lub kierunku, z którego pada światło, to przekazana jej informacja przekształcana jest za pomocą macierzy modelowania. W przykładach programów przedstawionych dotąd w tym rozdziale wszystkie źródła światła były statyczne, ponieważ ich położenie zostało zdefi niowane, zanim wykonane zostały jakiekolwiek przekształcenia. Źródło światła można przemieszczać w trójwymiarowej przestrzeni tak samo jak każdy inny obiekt. Wystarczy podać jego nowe położenie po wykonaniu przesunięcia lub ob rotu. Kolejny przykład programu porusza źródło światła wokół sześcianu i pozwala użytkownikowi modyfikować wygląd sceny. Należy przyjrzeć się jego kodowi: / / / / / / P l i k i nagłówkowe # in c lu d e # in c lu d e < g l / g l . h > # in c lu d e < g l / g l u . h > # in c lu d e < g l/ g la u x . h > / / / / / / Zmienne g lo b a ln e f l o a t angle = O.Of; HDC g_HDC; bool fu llS c r e e n = f a l s e ; bool ke yP re s s e d [2 5 6 ];
// // // //
standardowy p l i k nagłówkowy Windows standardowy p l i k nagłówkowy OpenGL p l i k nagłówkowy b i b l i o t e k i GLU fu n k c je pomocnicze OpenGL
// // // // //
bieżący k ą t obrotu ź ró d ła ś w ia t ła g lo b a ln y k o n te k st urządzenia t r u e = t r y b pełnoekranowy; f a l s e = okienkowy zawiera wartość t r u e dla k la w isz y, k tó r e z o s t a ły n a c iś n ię t e przez użytkownika
/ / Pozycje ź ró d e ł ś w ia t ła f l o a t 1 ig h tP o s itio n R E ] = { O.Of, O.Of, 7 5 .Of, l . O f } f l o a t 1 i ghtPosi t i onGEU = { O.Of, O.Of. 7 5 .Of, l . O f } f l o a t 1 i g h t P o s i t i o n B [ ] = { O.Of, O.Of. 7 5 .Of, l . O f }
168
Część II ♦ Korzystanie z OpenGL
/ / Intensywność składowej czerwonej, / / d la ś w ia t ła rozproszonego f l o a t d i f f u s e L i g h t R [ ] = { l . O f . O.Of, f l o a t d if f u s e L i g h t G [ ] = { O.Of, l . O f , f l o a t d i f f u s e L i g h t B [ ] = { O.Of, O.Of.
z ie lo n e j i n i e b ie s k i e j O.Of. l . O f O.Of, l . O f l.O f. l.O f
/ / Intensywność składowej czerwonej, z ie lo n e j / / d la ś w ia t ła o d b ic ia f l o a t s pe cula rl_ ig htR [] ={ l . O f , O.Of. O.Of. f l o a t s p e c u la r L ig h tG [] ={ O.Of. l . O f , O.Of, f l o a t s p e c u la r L ig h tB [] ={ O.Of, O.Of. l . O f ,
}; }; };
i n ie b ie s k i e j l . O f }; l . O f }; l . O f };
I I Kierunek s tru m ie n ia ś w ia tła f l o a t s p o t D i r e c t i o n [ ] = { O.Of. O.Of, - l . O f };
/ / Globalne w ła ściw ości o ś w ie tle n ia f l o a t d i f f u s e L i g h t [ ] = { 0 . 5 f , 0 . 5 f , 0 . 5 f , l . O f }; f l o a t s p e c u la r L ig h t[ ] = { l . O f . l . O f . l . O f . l . O f }; f l o a t 1 i ghtPosi t i on[ ] = { O.Of, O.Of. 100.Of. l . O f }; f l o a t obje ctX Rot; f l o a t obje ctY Rot; f l o a t o b je ctZR o t;
/ / o b rót sześcianu / / o b rót sześcianu / / o b ró t sześcianu
f l o a t redXRot; f l o a t redYRot; i n t c u rr e n tC o lo r
wokół osi x wokół osi y wokół osi z
/ / o b ró t ź ró d ła ś w ia t ła czerwonego wokół / / o b ró t ź ró d ła ś w ia t ła czerwonego wokół =1;
bool spotEnabled = tr u e ;
osi x osi y
/ / bieżący k o lo r ź ró d ła ś w ia t ła : / / 1 = czerwony, 2 = z ie lo n y , 3 = n ie b ie s k i / / strumień ś w ia tła t r u e = włączony; f a l s e = wyłączony
// In itia liz e / / o p is : i n i c j u j e OpenGL void I n i t i a l i z e O glShadeModel(GL_SMOOTH); g l Enable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glFrontFace(GL_CCW);
// // // // //
glEnable(GL_LIGHTING);
/ / aktywuje wyznaczanie o ś w ie t le n ia
aktywuje cie n io w anie g ła d k ie oraz usuwanie niewidocznych powierzchni wyłącza o b lic z e n ie dla t y ln y c h s tr o n w ie lo ką tó w t y l n e = uporządkowanie wie rzchołków w ie lo k ą ta przeciwne do k ie runku ruchu wskazówek zegara
/ / LIGHTO r e p re z e n tu je strumień ś w ia t ła , / / którego ź ró d ło zna jd u je s ię w punkcie (0 .0 , 0.0, 100 . 0 ) / / i skierowane j e s t w k ie runku ujemnej części osi g lL i ghtfv(GL_LIGHT0. GL_DIFFUSE, d i f f u s e L i g h t ) ; g lLig htfv(G L_LIG H T0, GL_SPECULAR, s p e c u la r L ig h t ) ; g lL i ghtfv(GL_LIGHT0, GL_POSITION, 1 i g h tP o s it i on); glL ig h tf(G L _ LIG H T0 , GL_SPOT_CUTOFF, 4 0 .O f); glL ig h tf(G L _ LIG H T0 . GL_SPOT_EXPONENT, 8 0 .O f); / / LIGHT1 re p re z e n tu je poruszające s ię ź ró d ło ś w ia t ła . / / Początkowo ma ono k o lo r czerwony, g l L i ghtfv(GL_LIGHT1. GL_DIFFUSE. d if f u s e L i g h t R ) ; g lL igh tfv (G L _L IG H T l, GL_SPECULAR. s p e c u la r L ig h tR ) ; g lL ig htfv(G L_LIG H T1. GL_POSITION, 1 ig h t P o s i t i o n R ) ;
Rozdział 6 . ♦ Kolory, łączenie kolorów i ośw ietlenie
/ / włącza ź ró d ła ś w ia t ła glEnable(GL_LIGHTO); glE nable(GL_LIGHTl); / / włącza ś le d z e n ie kolorów glEnable(GL_COLOR_MATERIAL); g lC o lo r M a te r ia l (GLJRONT, GL_AMBI ENT_AND_DI FFUSE); / / e f e k t s iln e g o o d b ic ia g lM aterialfv(G L_FR 0N T, GLJPECULAR. s p e c u la r L ig h t ) ; g lM a t e r ia l i (GLJRONT. GL_SHININESS, 128); / / czarny k o lo r t ł a g l C l e a r C o l o r ( 0 . 0 f , O.Of. O.Of. O.Of);
} / / Render / / o p is ; ry s u je scenę v o id RenderO
{
/ / o p ró żn ia b u fo ry ekranu i g łę b i gl Cl e a r (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); / / r e s e t u je macierz modelowania g l L o a d I d e n t it y O ; / / p rz e s u n ię c ie do punktu (0. 0, -150) g l T r a n s l a t e f ( 0 . 0 f . O.Of. - 1 5 0 .O f); g lP u s h M a t r ix O ; / / o b ró t względem osi x i y g lR o ta te f( r e d Y R o t, O.Of. l . O f . O.Of); g lR o ta te f( r e d X R o t, l . O f . O.Of, O.Of); / / umieszcza ź ró d ło ś w ia t ła gl Li g h t f v (G LLIG HT1, GLJOSITION, 1 i ghtPosi t i onR); s w itc h ( c u r r e n tC o lo r )
{
case 1:
{
/ / ś w ia t ło czerwone
gl L ig h tfv (G L _ L IG H T l, GL_DIFFUSE, d i f f u s e L i g h t R ) ; gl Li g h t f v ( G L J IG H T l, GLJOSITION. 1 i ghtPosi t i onR); gl Li g h t f v (GLJ IG H T 1 , GLJPECULAR, specul a rL i g h tR ) ; / / przesuwa ź ró d ło ś w ia t ła gl T r a n s l a t e f (1 i ghtPosi t i o n R [0 ], l i g h t P o s i t i o n R [ l ] , 1i g h t P o s it io n R [ 2 ] ); g l C o l o r 3 f ( 1 . 0 f , O.Of, O.Of); break;
i
case 2:
{
/ / ś w ia t ło z ie lo n e
gl Li g h t f v ( GL_LIGHT1. GL_DIFFUSE, d i f f u s e L i g h t G ) ; gl Li g h t f v ( G L J IG H T l, GLJOSITION, 1 i ghtPosi t i onG); gl Li g h tfv(G L_LIG H Tl, GLJPECULAR, specul a rLi g h tG ); / / przesuwa ź ró d ło ś w ia t ła g l T r a n s l a t e f O i g h t P o s i t i o n G [ 0 ] , l i g h t P o s i t i o n G [ l ] , 1ig h t P o s i t i o n G [ 2 ] );
169
170
Część II ♦ Korzystanie z OpenGL
g l C o l o r 3 f ( 0 . 0 f , l . O f , O.Of); break;
} case 3:
/ / ś w ia t ło n ie b ie s k ie
{ g lL ig h tfv (G L _ L IG H T l. GL_DIFFUSE, d i f f u s e L i g h t B ) ; g lL ig h tfv (G L_L IG H T l. GL_POSITION, 1 ig h t P o s i t i o n B ) ; gl L ig h tfv (G L _ L IG H T l, GL_SPECULAR, s p e c u la r L ig h tB ) ; / / przesuwa ź ró d ło ś w ia t ła g l T r a n s l a t e f (1 i ghtPosi t i onB[03. 1i g h t P o s i t i o n B [ l ] . 1 i g h t P o s i t i o n B [ 2 ] ); g l C o l o r 3 f ( 0 . 0 f . O.Of, l . O f ) ; break;
} } / / przechowuje a tr y b u ty o ś w ie t le n ia gl P u s h A ttri b ( GL_LIGHTING_BIT); glD isable(GL_LIGHTING); / / wyłącza o ś w ie t le n ie podczas rysowania k u l i / / r e p re z e n tu ją c e j poruszające s ię ź ró d ło ś w ia t ła a u x S o lid S p h e re ( 2 .5 f ) ; glEnable(GL_LIGHTING); g lP o p A ttrib O ; / / odtwarza a tr y b u ty o ś w ie t le n ia g lP o p M a t r ix ( ) ; / / Rysuje obracają cy s ię sześcian g lP u s h M a t r ix ( ) ; g lC o lo r3 f(1 .0 f, l.O f, l.O f) ; g lR o ta te f( o b je c tX R o t, l . O f , O.Of, O.Of); g lR o ta te f( o b je c tY R o t, O.Of, l . O f , O.Of); g lR o ta t e f( o b je c tZ R o t, O.Of, O.Of, l . O f ) ; a u x S o lid C u b e (7 0 .0 f); g lP o p M a t r ix ( ) ; g l F lu s h O ; SwapBuffers(g_HDC);
/ / p rzełącza b u fo ry
/ / zwiększa l i c z n i k i obro tu objectX Rot += O . O lf ; objectY Rot += O. 0 2 f ; obje ctZR ot += O .O lf; redXRot += 0 . 3 f ; redYRot += O . l f ;
} Kod używanej dotąd wersji procedury okienkowej WndProcO wzbogacony został tym razem o następujące wiersze: case WM_KEYDOWN: keyPressed[wParam] = t r u e ; r e tu r n 0; break;
/ / k la w is z n a c iś n ię t y
case r i_KEYUP: / / k la w is z zwolniony key^ ^ssed[wParam] = f a l s e ; re tu n 0; b re a u ;
Rozdział 6 . ♦ Kolory, łączenie kolorów i ośw ietlenie
171
Poniżej przedstawiona jest pętla przetwarzania komunikatów stanowiąca część funkcji W inM ainO : w h ile ( ! done)
{
PeekMessage(&msg, hwnd, NULL, NULL, PM_REM0VE); i f (msg.message == WM_QUIT)
{
done = t r u e ;
/ / nadszedł komunikat WM_QUIT? / / j e ś l i ta k . t o a p lik a c j a kończy d z ia ła n ie
} e ls e
{
i f (keyPressed[VK_ESCAPE]) done = t r u e ; e ls e
{ i f ( k e y P r e s s e d [ 'R ']) c u r r e n tC o lo r = 1; i f ( k e y P r e s s e d [ 'G '] ) c u r r e n tC o lo r = 2; i f ( k e y P r e s s e d [ 'B ']) c u r r e n tC o lo r = 3; i f ( ( k e y P r e s s e d [ 'S '] ) && (spotEnable d))
{
spotEnabled = f a l s e ; gl Di sable(GL_LIGHTO);
} e ls e i f ( ( k e y P r e s s e d [ 'S '] ) && ( ! spotE nabled))
{
spotEnabled = t r u e ; glE nable(G LLIG H TO );
} R e n d e rO ; / / tłumaczy komunikat i umieszcza go w k o le jc e TranslateMessage(&msg); DispatchMessage(&msg);
} } } / / konie c p ę t l i w h ile
Zaprezentowany kod nie został zoptymalizowany pod kątem efektywności wykonania. Wystarczy zmienić jedynie kilka wierszy programu, aby znacznie przyspieszyć jego działanie. Zadanie to pozostawione zostanie jako ćwiczenie do wykonania. Konieczny mi modyfikacjami będą na pewno zmiany, które należy wprowadzić wewnątrz instruk cji s w itc h funkcji RenderO . Określenie koloru nie musi być wykonywane w niej za każdym razem, gdy rysowana jest cena. Aby natomiast uzyskać wrażenie ruchu źródła światła, jego pozycja musi być zmieniana za każdym razem za pomocą odpowiednich przekształceń, co ilustruje poniższy fragment kodu: / / o b r ó t względem osi x i y g lR o ta te f( r e d Y R o t, O.Of, l . O f , O.Of); g lR o ta te f( r e d X R o t, l . O f , O.Of, O.Of);
172
Część II ♦ Korzystanie z OpenGL
// umieszcza źródło światła glLightfv(GL LIGHT1, GL_P0SITI0N, 1ightPositionR);
Gdyby źródło to tworzyło strumień światła, to konieczne byłoby także określenie jego kierunku za pomocą właściwości GL SPOT DIRECTION. Ponieważ jednak w tym przykła dzie używa się zwykłego, pozycyjnego źródła światła, to wystarczy jedynie określić jego położenie za pomocą właściwości GL POSITION. Podczas rysowania kuli reprezentującej poruszające się źródło światła należy skorzystać z funkcji glPushAttribO. Nie trzeba tu szczegółowo omawiać jej działania. Podobnie jak dla przekształceń modelowania, rzutowania i odwzorowań tekstur, także i dla two rzenia grafiki OpenGL posiada odpowiedni stos pozwalający przechować bieżący stan maszyny OpenGL. Przekazując funkcji glPushAttribO parametr GL LIGHTING BIT uzy skuje się przechowanie na tym stosie wszystkich parametrów związanych z oświetle niem tworzonej sceny. Dzięki temu można wyłączyć na chwilę oświetlenie i narysować kulę bez jakichkolwiek efektów świetlnych. Następnie włącza się oświetlenie i przy wraca jego charakterystykę pobierając zachowane na stosie atrybuty za pomocą funkcji glPopAttribO. Program ten pokazuje także sposób obsługi poleceń wydawanych przez użytkownika za pomocą klawiatury. Definiuje on w tym celu tablicę wartości logicznych o rozmiarze 255 odpowiadającym liczbie wszystkich kodów ASCII, które można wprowadzić za pomocą klawiatury. Jeśli użytkownik naciśnie klawisz A , to kod obsługi komunikatu WMJCEYDOWN umieszczony w funkcji WndProc() nada elementowi keyPressed[65] wartość true, ponieważ 65 jest kodem ASCII litery A. Rozwiązanie to umożliwia sprawdzenie wewnątrz pętli komunikatów o tym, które klawi sze zostały naciśnięte i wykonanie odpowiedniego kodu. Omawiany program pozwala w ten sposób użytkownikowi zmieniać kolor światła emitowanego przez poruszające się źródło. Naciskając klawisz S można wyłączyć strumień światła padający na sześcian, a za pomocą klawiszy R, G i B zmieniać kolor światła wysyłanego przez poruszające się źródło na czerwony, zielony lub niebieski. Rysunek 6.13 ilustruje działanie programu. Rysunek 6.13. Poruszające się źródło światła oświetla obracający się sześcian
Rozdział 6. ♦ Kolory, łączenie kolorów i ośw ietlenie
173
Efektem często stosowanym we współczesnych grach są flesze oświetlające obiekty, na które spogląda obserwator. Ich implementacja polega na umieszczeniu źródła światła na stałej pozycji względem obserwatora. W tym celu pozycję tę należy określić w układzie współrzędnych obserwatora. Najpierw należy załadować macierz jednostkową do ma cierzy modelowania, a następnie umieścić źródło światła w początku układu współrzęd nych. Jeśli nie zostanie określona przy tym orientacja tego źródła, to uzyskany będzie efekt latami oświetlającej scenę z pozycji kamery. Lepszy efekt uzyska się kierując źró dło światła wzdłuż ujemnej części osi z. Ponieważ pozycja tego źródła jest stała, to wy starczy zdefiniować ją raz podczas inicjacji grafiki OpenGL. Dzięki temu podczas two rzenia kolejnych scen nie trzeba już więcej zajmować się tworzeniem efektu flesza.
Łączenie kolorów Łączenie kolorów pozwala uzyskać efekt przezroczystości. Dzięki temu grafika OpenGL może symulować obiekty świata rzeczywistego, przez które można widzieć inne obiekty — na przykład szkło lub wodę. Przy łączeniu kolorów znajduje zastosowanie wartość współczynnika alfa, który poja wiał się wiele razy przy omawianiu funkcji OpenGL. Łączenie kolorów polega na utwo rzeniu nowego kolom na podstawie kolorów obiektu przesłanianego i przesłaniającego. Wykonywane jest zwykle na składowych modelu RGB przy wykorzystaniu współczyn nika alfa reprezentującego przesłanianie. Im mniejsza jego wartość, tym większe będzie wrażenie przezroczystości obiektu przesłaniającego. W dalszej części rozdziału obiekt przesłaniający będzie nazywany źródłem, a obiekt przesłaniany celem. Aby korzystać z łączenia kolorów w OpenGL, należy je uaktywnić przekazując funkcji gl Enabl e ( ) parametr GL_BLEND. Następnie wywołuje się fUnkcję gl BI endFunc( ), której trzeba przekazać definicję funkcji łączenia źródła z celem. Domyślne funkcje łączenia odpowia dają wywołaniu fimkcji gl BI endFunc( ) z parametrem GL ONE dla źródła i GL ZERO dla celu. Tabela 6.6 przedstawia dostępne funkcje łączenia źródła, a tabela 6.7 funkcję łączenia celu. Tabela 6.6. Funkcje łączenia źródła Funkcja
Opis
GL_ZER0
Kolorem źródła jest (0, 0, 0, 0)
GL_0NE
W ykorzystuje bieżący kolor źródła
GL_DST_C0L0R
M noży kolor źródła przez kolor celu
GL_0NE_MINUS_DST_C0L0R
M noży kolor źródła przez dopełnienie kolom celu, czyli przez [(1, 1, 1, 1) — kolor celu]
gl_ src_ alpha
M noży kolor źródła przez wartość alfa źródła
GL_ONE_MINUS_SRC_ALPHA
M noży kolor źródła przez dopełnienie wartości alfa źródła, czyli przez (1 — wartość alfa źródła)
GL_DST_ALPHA
M noży kolor źródła przez wartość alfa celu
GL_ONE_MINUS_DST_ALPHA
M noży kolor źródła przez dopełnienie wartości alfa celu, czyli przez wartość alfa celu)
(1 — GL_SRC_ALPHA_SATURATE
M noży kolor źródła przez m niejszą z wartości: alfa źródła lub dopełnienie alfa celu
174
Część II ♦ Korzystanie z OpenGL
Tabela 6.7. Funkcje łączenia celu Funkcja
Opis
GL_ZER0
Kolorem celu jest (0, 0, 0, 0)
GL_0NE
Wykorzystuje bieżący kolor celu
GL_SRC_C0L0R
M noży kolor celu przez kolor źródła
GL_0NE_MINUS_SRC_C0L0R
M noży kolor celu przez dopełnienie koloru źródła, czyli przez [(1, 1, 1, 1) — kolor źródła]
gl _ src_ alpha
M noży kolor celu przez wartość alfa źródła
GL_0NE_MI NUS_SRC_AL PHA
M noży kolor celu przez dopełnienie wartości alfa źródła, czyli przez (1 — wartość alfa źródła)
gl _ dst _ alpha
M noży kolor celu przez wartość alfa celu
GL_0NE_MI NUS_DST_AL PHA
M noży kolor celu przez dopełnienie wartości alfa celu, czyli przez (1 — wartość alfa celu)
GL_SRC_ALPHA_SATURATE
M noży kolor celu przez m niejszą z wartości: alfa źródła lub dopełnienia alfa celu
W praktyce aplikacje używają niewielu z wymienionych w tabelach funkcji. Dla uzy skania efektu przezroczystości stosuje się funkcję GL SRC ALPHA dla źródła i funkcję GL_ONE_MINUS_SRC_ALPHA dla celu. Z pozostałymi funkcjami można poeksperymentować w celu stworzenia innych efektów.
Przezroczystość Efekt przezroczystości uzyskuje się stosując kombinację funkcji źródła GL SRC ALPHA z funkcją celu GL ONE MINUS SRC ALPHA. Poniższy fragment kodu konfiguruje sposób łą czenia kolorów tak, by można było uzyskać efekt przezroczystości: glEnable(GL_BLEND); glBIendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Przy tak określonych funkcjach łączenia kolor źródła nakładany jest w stopniu określo nym przez współczynnik alfa na kolor pikseli celu. Teraz należy przyjrzeć się przykładowi zastosowania przezroczystości. Poniższy frag ment kodu wyświetla dwa nakładające się wielokąty dla wartości współczynnika alfa równej 0.6. Jak widać, kolejność nakładania wielokątów ma znaczenie dla końcowego efektu: bool l e f t F i r s t = t r u e ;
/ / t r u e = r y s u je n a jp ie r w lewy w ie lo k ą t / / f a l s e = r y s u je n a jp ie r w prawy w ie lo k ą t
/ / In itia liz e / / o p is : i n i c j u j e OpenGL void I n i t i a l i z e ( )
{ g l E n a b l e(GL_BLEND);
/ / aktywuje łą c z e n ie kolorów
/ / wybiera fu n k c je dające e fe k t p rz e z ro cz ys to ś c i glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Rozdział 6 . ♦ Kolory, łączenie kolorów i ośw ietlenie
glShadeModel(GL_SM00TH);
// wybiera model cieniowania gładkiego
// tło w kolorze czarnym glClearColor(0.0f, O.Of, O.Of, O.Of);
} // DrawLeftPoly() // opis: rysuje lewy wielokąt void DrawLeftPolyO
{ glColor4f(0.8f, 0.9f. 0.7f, 0.6); // wartość współczynnika alfa = 0.6 gl Begin(GL_QUADS); glVertex3f(-10.0f. -10.Of, O.Of); glVertex3f(0.0f, -10.Of, O.Of); glVertex3f(0.0f, 10.Of, O.Of); glVertex3f(-10.0f, 10.Of, O.Of); glEndO ;
} // DrawRightPolyO // opis: rysuje prawy wielokąt void DrawRightPolyO
{ glColor4f(0.0f, 0.5f, 0.5f, 0.6); // wartość współczynnika alfa = 0.6 glBegin(GL_QUADS); glVertex3f(0.0f, -10.Of, O.Of); glVertex3f(10.0f, -10.Of, O.Of); glVertex3f(10.0f, 10.Of, O.Of); glVertex3f(0.0f, 10.Of, O.Of); glEndO ;
} // Render // opis: rysuje scenę void Render()
{ // opróżnia bufor ekranu i głębi gl Cl ear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadldentityO; i f (angle >= 359.9f) angle = O.Of; angle +=0.1;
// zwiększa kąt obrotu
glTranslatef(0.0f, O.Of, -40.Of); // obraca i rysuje wielokąt przesłaniany (cel) • glPushMatrixO; glRotatef(angle, O.Of, O.Of, l.Of); i f (le ftF irst) DrawLeftPoly(); else DrawRightPolyO; glPopMatrixO;
175
176
Część II ♦ Korzystanie z OpenGL
/ / obraca i ry s u je w ie lo k ą t p rz e s ła n ia ją c y (ź ró d ło ) g lP u s h M a trix (); g lR o ta te f(a n g le , O.Of, O.Of, - l. O f ) ; i f ( le ft F ir s t ) D ra w R ig h tP o ly(); e ls e D ra w L e ftP o ly (); g lP o p M a trix (); g lF lu s h O ; SwapBuffers(g_FIDC);
/ / p rze łą cza b u fo ry
Działanie powyższego fragmentu kodu ilustruje rysunek 6.14. Przykład ten pokazuje, że łączenie kolorów w przypadku obiektów znajdujących się w takiej samej odległości od obserwatora jest stosunkowo proste. Rysunek 6.14. Łączenie kolorów dwóch wielokątów
Jednak podczas tworzenia prawdziwie trójwymiarowej grafiki zadanie łączenia kolorów nieco się komplikuje. Najważniejsza różnica polega na konieczności włączenia testowania głębi za pomocą funkcji gł Enabl e ( ), której przekazuje się wartość GL_DEPTH_TEST: glEnable(GL_DEPTH_TEST);
Testowanie głębi jest konieczne, by obiekty znajdujące się bliżej przesłaniały obiekty położone dalej od obserwatora. Bufor głębi służy do śledzenia odległości pomiędzy ob serwatorem i obiektem, do którego należy dany piksel na ekranie. Jeśli pojawi się inny obiekt znajdujący się bliżej obserwatora, to kolor piksela ulegnie zmianie, a w buforze głębi zostanie umieszczona nowa wartość opisująca odległość obiektu, do którego nale ży teraz piksel. Takie rozwiązanie pozwala maszynie OpenGL ukrywać dalsze obiekty za nieprzezroczystymi obiektami, które znajdują się bliżej. Aby właściwie wykonać operację połączenia kolorów, trzeba włączać i wyłączać bufor głębi podczas rysowania sceny. Najpierw rysuje się wszystkie nieprzezroczyste obiekty przy włączonym buforze głębi. Następnie przełącza się bufor głębi za pomocą funkcji
Rozdział 6 . ♦ Kolory, łączenie kolorów i ośw ietlenie
177
g l D epthM ask( ) w tryb, w którym możliwy jest tylko odczyt bufora. W ten sposób można zapobiec modyfikacji zawartości bufora głębi uzyskanej podczas rysowania nieprzezro czystych obiektów. Można teraz rysować obiekty przezroczyste bez obaw, że zmodyfi kuje to dotychczasową zawartość bufora. Podczas rysowania obiektów przezroczystych nadal ich odległość od obserwatora porównywana jest z zawartością bufora głębi. Dzię ki temu obiekty przezroczyste, które znajdują się za obiektami nieprzezroczystymi nie są rysowane. Jeśli natomiast obiekty przezroczyste znajdą się przed obiektami nieprze zroczystymi, to wykonywane będzie łączenie kolorów. Aby ustawić bufor głębi w tryb, w którym możliwy będzie tylko jego odczyt, należy przekazać funkcji glDepthM askO wartość GL FALSE. Aby przywrócić normalny tryb działania bufora, należy przekazać funkcji glDepthM askO wartość GL_TRUE.
Poniższy program stanowi ilustrację omówionego sposobu działania. Rysunek 6.15 pre zentuje efekt działania programu, który wyświetla dwie kule — przezroczystą i nieprze zroczystą— krążące wokół jeszcze jednej, mniejszej kuli. Ta ostatnia kula reprezentuje położenie pozycyjnego źródła światła, które odbijane jest przez krążące kule. Rysunek 6.15. Efekt przezroczystości kuli uzyskany przy zastosowaniu funkcji glDepthMaskO
•Łączenie koloiów. przykład dtugi: przezroczysta kula
f lo a t 1 ig h t P o s itio n [ ] = { O.Of, O.Of, l. O f . O.Of }; / / kie ru n e k g lo b alnego o ś w ie tle n ia f l o a t d if fu s e L ig h t [3 f l o a t d iffu s e M a t[] =
= { l . Of, l . Of, l . Of, l . Of }; { l . Of , l . Of. l . Of, l . Of } ;
/ / ś w ia tło rozproszone / / m a te ria ł w ś w ie tle rozproszonym
f l o a t bal 1D iff u s e f ] = { 0 .5 f. 0 .5 f. O.Of, l . Of } ; f l o a t bał 1S p e c u la r[ ] = { l . Of , l . Of . l . Of. l . Of } ; f lo a t bal 1P o s it io n [] = { O.Of, O.Of, O.Of, l . Of } ;
/ / ś w ia tło rozproszone ź ró d ła - k u li / / ś w ia tło o d b ic ia ź ró d ła - k u li / / p o ło ż e n ie ź ró d ła - k u li
/ / In itia liz e / / o p is : in ic ju j e OpenGL v o id I n i t i a l i z e O
{ / / w łącza o ś w ie tle n ie , b u fo r g łę b i oraz ukryw anie ty ln y c h s tro n w ie lo ką tó w gl Enabl e(GL_LIGFITING); gl Enable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE);
178
Część II ♦ Korzystanie z OpenGL
/ / o k re ś la w ła ściw o ści glo b a ln e g o ś w ia tła rozproszonego gll_ightfv(GL_LIG HTO , GL_DIFFUSE, d if f u s e L ig h t ) ; glLightfv(G L_LIG H TO . GL_P0SITI0N, 1ig h t P o s itio n ) ; glEnable(GL_LIGHTO); / / o k re ś la w ła ściw o ści ś w ia tła ź r ó d ła - k u li g lL ig h tfv (G L _ L IG H T l, GL_DIFFUSE, b a lI D if fu s e ) ; g lL ig h tfv (G L _ L IG H T l, GL_SPECULAR, bal 1S p e c u la r); glE nable(G L_LIG H Tl); / / o k re ś la wygląd m a te ria łu o biektów w ś w ie tle rozproszonym glM aterialfv(G L_FR O N T, GL_DIFFUSE, d iffu s e M a t); / / włącza ś le d z e n ie kolorów glEnable(GL_COLOR_MATERIAL); / / w ybiera model cie n io w a n ia g ła d k ie g o glShadeModel(GL_SM00TH); / / t ł o w k o lo rz e czarnym g lC le a r C o lo r ( 0 .0 f, O.Of, O .Of, O .O f);
} / / Render / / o p is : ry s u je scenę v o id Render()
{
/ / o p ró żn ia b u fo r ekranu i b u fo r g łę b i glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n tity O ; / / o b ie k u le krążą z tą samą p rę d ko ścią i f (a n g le >= 3 5 9 .9 f) angle = O.Of; angle += 0 .1 ; / / p rz e s u n ię c ie do t y łu o 15 je d n o ste k g lT r a n s la te f ( 0 . 0 f, O.Of, - 1 5 .O f); / / o k re ś la po zycję c e n tr a ln e j k u li re p re z e n tu ją c e j ź ró d ło ś w ia tła g lL ig h tf v (GL LIGHT1, GL_P0SITI0N, bal 1 P o s it io n ) ; / / ry s u je c e n tra ln ą k u lę g lP u s h M a trix O ; g lC o lo r 3 f( 1 .0 f, l. O f , O .O f); g lT r a n s la te f(b a l 1P o s it io n [0 ], bal 1P o s it i o n [ l] , bal 1P o s itio n [2 3 ); a u x S o lid S p h e re (0 .5 f); g lP o p M a trix O ; / / ry s u je n ie p rz e z ro c z y s tą k u lę g lP u s h M a trix O ; g lR o ta te f(a n g le , O.Of, l. O f , O .O f); g lT r a n s la te f ( 0 . 0 f. O.Of, 6 . O f); g lC o lo r 4 f( 1 .0 f, 0 .2 f, 0 .2 f, l. O f ) ; a u x S o lid S p h e re (2 .0 f); g lP o p M a trix O ;
Rozdział 6 . ♦ Kolory, łączenie kolorów i ośw ietlenie
179
/ / aktyw u je łą c z e n ie kolorów glEnable(GL_BLEND); / / w łącza tr y b "ty lk o -d o -o d c z y tu " bu fo ra g łę b i glDepthMask(GL_FALSE); / / w ybiera fu n k c je łą c z e n ia by uzyskać e fe k t p rz e z ro c z y s to ś c i glBlendFunc(GL_SRC_ALPHA, GL_0NE); / / ry s u je p rz e z ro c z y s tą k u lę g lP u s h M a trix (); g lR o ta te f(a n g le , O.Of, l. O f , O .O f); g lT r a n s la te f(O .O f, O.Of, - 6 . O f); g lC o lo r 4 f( 0 .0 f, 0 .5 f, 0 .5 f, 0 .3 f ) ; a u x S o lid S p h e re (2 .O f); g lP o p M a tr ix () ; / / p rze łą c z a b u fo r g łę b i z powrotem do normalnego try b y pracy glDepthMask(GL_TRUE); / / w yłącza łą c z e n ie kolorów g lD i sable(GL_BLEND); g lF lu s h O ; SwapBuffers(g_HDC);
/ / p rze łą cza b u fo ry
} Jak łatwo zauważyć program rysuje wszystkie nieprzezroczyste obiekty, zanim włączy łączenie kolorów. Następnie przełącza bufor w tryb „tylko-do-odczytu” i rysuje prze zroczyste obiekty. Program ten stosuje także kilka efektów związanych z oświetleniem, aby pokazać ich wpływ na łączenie kolorów i efekt przezroczystości. Jako że wartość współczynnika alfa dla przezroczystej kuli wynosi 0 . 3 , przenika ją więk sza część światła. Gdyby nie źródło światła w postaci centralnej kuli, to przezroczysta kula byłaby bardzo ciemna. Trzeba przypomnieć tu o tym, że im bardziej współczynnik alfa zbliża się do wartości 1.0, tym mniej przezroczysty staje się obiekt. Gdy zmniejszy się wartość współczynnika alfa, obiekt zyska na przezroczystości. Wartość 0 . 0 opisuje obiekt doskonale przezroczysty, a wartość 1 . 0 obiekt zupełnie nieprzezroczysty.
Podsumowanie Światło posiada jednocześnie naturę cząstki i fali. Siatkówka oka odbiera światło jako kombinację składowej czerwonej, zielonej i niebieskiej. Podobnie drobiny luminoforu na ekranie monitora emitują światło w kolorze czerwonym, zielonym i niebieskim. Głębia kolom określa liczbę kolorów, które są do dyspozycji. Zależy ona od rozmiaru bufora koloru. 8-bitowy bufor koloru umożliwia rozróżnienie 28, czyli 256 kolorów. Obecnie dostępne karty grafiki umożliwiają zwykle pracę z 8-, 16-, 24- lub 32-bitowym buforem koloru.
180
Część II ♦ Korzystanie z OpenGL
OpenGL definiuje kolory za pomocą intensywności składowej czerwonej, zielonej i nie bieskiej. Intensywność ta może przyjmować wartości z przedziału od 0.0 (brak składo wej) do 1.0 (pełna intensywność). Cieniowanie może być płaskie lub gładkie. Płaskie cieniowanie polega na wypełnieniu wielokąta jednym kolorem, który najczęściej jest kolorem ostatniego z wierzchołków. Bardziej realistyczny efekt oferuje cieniowanie gładkie zwane też cieniowaniem Gouraud, które interpoluje kolor wypełnienia pomiędzy wierzchołkami wielokąta. OpenGL modeluje oświetlenie za pomocą czterech komponentów: światła otoczenia, światła rozproszonego, światła odbicia i światła emisji. OpenGL wyznacza kolor mate riału na podstawie stopnia odbicia składowej czerwonej, zielonej i niebieskiej światła. Normalne używane są przez OpenGL do wyznaczania oświetlenia płaszczyzny. Ze wzglę du na efektywność obliczeń maszynie OpenGL przekazuje się normalne jednostkowe. OpenGL Programming Guide wyróżnia cztery etapy tworzenia oświetlenia w OpenGL.
1 . Wyznaczenie wektorów normalnych we wszystkich wierzchołkach każdego obiektu. Normalne te określają orientację obiektów względem źródeł światła. 2 . Utworzenie, wybranie i określenie pozycji źródła światła.
3. Utworzenie i wybranie modelu oświetlenia. Model ten definiuje światło
otoczenia oraz położenie obserwatora uwzględniane w obliczeniach oświetlenia. 4. Zdefiniowanie właściwości materiałów obiektów znajdujących się na scenie.
Źródła światła można przesuwać i obracać w przestrzeni tak samo jak inne obiekty OpenGL. Informacja, którą przekazuje się funkcji g lL ig h t * ( ) definiując położenie źródła lub kie runek światła, jest następnie przekształcana za pomocą bieżącej macierzy modelowania. Łączenie kolorów pozwala uzyskać efekt przezroczystości. Efekt ten otrzymuje się two rząc kombinację funkcji łączenia źródła GL SRC ALPHA z funkcją łączenia celu GL ONE MINUS_SRC_ALPHA.
Za pomocą funkcji gl DepthMask() przełącza się bufor głębi w tryb „tylko-do-odczytu”, aby rysować przezroczyste obiekty.
Rozdział 7.
Mapy bitowe i obrazy w OpenGL W rozdziale tym na chwilę będzie trzeba porzucić trójwymiarowy świat, aby zapoznać się z grafiką rastrową stosującą dwuwymiarowe tablice pikseli. Trzeba bowiem zapo znać się z funkcjami OpenGL działającymi na mapach bitowych i obrazach. Przedsta wione więc zostaną sposoby odczytu i zapisu plików graficznych w dwóch formatach: stosowanym w systemie Windows formacie BMP (.bmp) oraz formacie Targa (. tga). W rozdziale tym omówione zostaną: ♦ mapy bitowe w OpenGL; ♦ mapy bitowe systemu Windows; ♦ pliki graficzne formatu Targa.
Mapy bitowe w OpenGL OpenGL definiuje mapą bitową jako dwuwymiarową tablicę pikseli, w której każdy piksel opisany jest za pomocą pojedynczego bitu. Mapy bitowe wykorzystywane są ja ko maski podczas rysowania prostokątnych obszarów ekranu oraz w celu definiowania znaków czcionek ekranowych. Można przyjąć na przykład, że zdefiniowana została mapa bitowa o rozmiarach 16x16 pokazana na rysunku 7.1. Podczas jej rysowania pik sel uzyska bieżący kolor określony wcześniej za pomocą funkcji g lC o lo r 3 f( ) pod wa runkiem, że odpowiadająca mu pozycja mapy posiada wartość 1. W przypadku wartości 0 piksel pozostawiany jest bez zmian. Czcionki ekranowe omówione zostaną dokładniej w rozdziale 11. Teraz wystarczy je dynie pokazanie tego, w jaki sposób stosując funkcje gl B itm a p *( ) i glR a s te rP o s *( ) można wyświetlić pojedynczy znak czcionki.
182
Część II ♦ Korzystanie z OpenGL
Rysunek 7.1. Mapa bitowa o rozm iarach 16x16
1
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
0
1
0
0
0
0
0
0
0
0
0
0
0
0
1
0
0
0
1
0
0
0
0
0
0
0
0
0
0
1
0
0
0
0
0
1
0
0
0
0
0
0
0
0
1
0
0
0
0
0
0
0
1
0
0
0
0
0
0
1
0
0
0
0
0
0
0
0
0
1
0
0
0
0
1
0
0
0
0
0
0
0
0
0
0
0
1
0
0
1
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
0
0
0
0
0
0
0
0
0
0
0
0
0
1
0
0
1
0
0
0
0
0
0
0
0
0
0
0
1
0
0
0
0
1
0
0
0
0
0
0
0
0
0
1
0
0
0
0
0
0
1
0
0
0
0
0
0
0
1
0
0
0
0
0
0
0
0
1
0
0
0
0
0
1
0
0
0
0
0
0
0
0
0
0
1
0
0
0
1
0
0
0
0
0
0
0
0
0
0
0
0
1
0
1
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
Umieszczanie map bitowych Funkcja gl R a ste rP o s*( ) pozwala określić położenie rysowanej mapy bitowej na ekranie. Przekazywane funkcji współrzędne wyznaczają położenie lewego, dolnego narożnika mapy bitowej. Poprzez przekazanie funkcji g lR a s te rP o s *( ) współrzędnych (30, 10) można umieścić dolny, lewy narożnik rysowanej mapy bitowej w punkcie o współrzęd nych (30, 10). Funkcja ta posiada następujące wersje: vo id g lR a s te rP o s [2 3 4 ][ s i d f ] (TYPE x, TYPE y. TYPE z. TYPE w) ; vo id g lR a s te rP o s [2 3 4 ][s id f]v (T Y P E *coords);
Dla tego przykładu wywołanie jej będzie mieć postać: glR asterP os2 i (30. 10);
Jeśli dla grafiki dwuwymiarowej nie zostanie wyspecyfikowana macierz modelowania i macierz rzutowania, to współrzędne przekazywane funkcji gl R a s te rP o s *( ) zamieniane będą na współrzędne ekranowe. Okno grafiki dwuwymiarowej definiuje się podobnie jak w przypadku grafiki trójwymiarowej. Zamiast użycia funkcji g lu P r o je c tio n ( ) sto suje się jednak funkcje g l0 r t h o ( ) lub g lu 0 rth o 2 D ( ). Poniżej przedstawiony został przy kład definicji okna grafiki za pomocą funkcji g lO rth o O , gdzie zmienna w id th określa szerokość okna, a zmienna h e ig h t jego wysokość: g lV ie w p o rt(0 . 0, w id th , h e ig h t); / / o k re ś la nowe rozm iary okna glMatrixMode(GL_PR0JECTI0N); / / w ybiera m acierz rzutow ania g lL o a d ld e n tity O ; / / re s e tu je m acierz rzutow ania / / d e fin u je okno g r a f ik i dwuwymiarowej g l0 r th o ( 0 .0 f, w id th - 1 .0 , 0 .0 , h e ig h t - 1 .0 , -1 .0 , 1 .0 ); glMatrixMode(GL_MODELVIEW); g lL o a d ld e n tity O ;
/ / w ybiera m acierz modelowania / / re s e tu je m acierz modelowania
Przekazując funkcji glGetBooleanv() wartość GL_CURRENT_RASTER_POSITION_VALID moż na dow;udziec się, czy wybrana została dozwolona pozycja rysowania mapy bitowej. Jeśli funkcja glGetBooleanv() zwróci wartość fal se, to będzie znaczyć, że wybrana po zycja nie jest dozwolona.
Rozdział 7. ♦ M apy bitow e i obrazy w OpenGL
183
Rysowanie mapy bitowej Po wybraniu pozycji mapę bitową rysuje się za pomocą funkcji gl Bi tm ap( ) zdefiniowanej następująco: v o id g lB itm a p (G L s iz e i width, G Lsizei height, G L flo a t xOrigin, G L flo a t yOrigin, G L flo a t xlncrement, G L flo a t ylncrement, const GLubyte *bitmap );
Funkcja ta rysuje mapę o podanej szerokości i wysokości w punkcie o współrzędnych (xOri gin, yOrigin) w bieżącym układzie współrzędnych rastra. Parametry xlncrement i ylncre ment określają wartości, o które zwiększone zostaną bieżące współrzędne rastra po naryso waniu mapy bitowej. Znaczenie parametrów funkcji gl Bi tm ap( ) ilustruje rysunek 7.2. Rysunek 7.2. Znaczenie parametrów funkcji glBitmapO
Bieżąca pozycja rastra
(xOrigin, yOrigin)
W adą m ap bitowych w OpenGL je s t to, że nie mogą one być ani obracane, ani po większane. Operacje te w OpenGL można wykonywać natom iast w przypadku map pikseli oraz obrazów. Przedstawione one zostaną w dalszej części rozdziału.
Przykład zastosowania mapy bitowej Omówiony teraz zostanie przykład programu, który tworząc kolejne ramki grafiki ry sować będzie mapę bitową o rozmiarach 16x16 w 50 losowo wybranych pozycjach. Mapa ta definiować będzie literę A za pomocą poniższej tablicy: unsigned char le t t e r A [ ] = { 0xC0, 0x03, 0xC0, 0x03. 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xDF, 0xFB, 0x7F. 0xFE, 0x60, 0x06, 0x30, 0x0C, 0x30, 0x0C, 0x18, 0x18, 0x18, 0x18, 0x0C, 0x30, 0x0C, 0x30, 0x07, 0xE0. 0x07, 0xE0
}:
184
Część II ♦ Korzystanie z OpenGL
Wygląd zdefiniowanej mapy bitowej pokazuje rysunek 7.3. Należy zwrócić uwagę na to, że mapę bitową definiuje się w tablicy „do góry nogami” w stosunku do sposobu, w jaki zostanie ona przedstawiona na ekranie. Rysunek 7.3.
o 1
Definicja mapy bitowej reprezentującej literę A
2
3
4 5
0 0
0
0
0
0
1 0
0
0
0
2 0
0
0
0
6 7
8 9 10 11 12 13 14 15
3 0
0
0
0
4 0
0
0
1
5 0
0
0
1
6 0
0
1
1
1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 0 0 0 1 1 0 0 0 0 1 1 0 0 1 1 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 1
7 0
0
1
1
0
0
0
0
0
0
0
0
1
1
0
0
8 0
1
1
0
0
0
0
0
0
0
0
0
0
1
1
0 0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
9 0
1
1
1
1
1
1
1
1
1
1
1
1
1
1
10 1
1
0
1
1
1
1
1
1
1
1
1
1
0
1
1
11 1
1
0
0
0
0
0
0
0
0
0
0
0
0
1
1
12 1
1
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1 1 1
1
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
0
0
0
0
0
0
0
0
0
0
0
0
1
1
13 14 15
Grafika będzie jak zwykle tworzona za pomocą funkcji Render (), która tym razem zo stanie zdefiniowana następująco: v o id RenderO
{
/ / op ró żn ia b u fo ry ekranu i g łę b i glC le a r ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); / / w ybiera wyrównanie do pełnych b a jtó w gl Pi x e lStorei(GL_UNPACK_ALIGNMENT. 1 ); / / w ybiera k o lo r b ia ły g lC o lo r 3 f( 1 .0 f. l. O f , l. O f ) ;
/ / ry s u je 50 razy mapę b ito w ą 16 x 16 l i t e r y A / / w losowo wybranych pozycjach okna 800 x 600 f o r ( in t numA = 0; numA < 70; numA++)
{ g lR aste rP o s2 i ( ra n d ( ) % 800, randO % 600); g lB itm a p (16. 16, 0 .0 , 0 .0 , 0 .0 , 0 .0 , le t te r A ) ;
} g lF lu s h O ; SwapBuffers(g_HDC); / / p rze łą cza b u fo ry
} Przed rozpoczęciem rysowania funkcja RenderO wybiera kolor biały jako bieżący. Po zycja, na której rysowana jest mapa bitowa za pomocą funkcji g lB itm a p O , odpowiada pozycji określonej za pomocą funkcji glR asterP os2i O . Po narysowaniu kolejnej mapy bitowej funkcja g lB itm a p O nie zmienia bieżącej pozycji rastra, ponieważ jej parametry xlncrem ent i y Increm ent mają wartość 0. Okno grafiki dwuwymiarowej definiuje się za pomocą funkcj i gl O rt ho ():
Rozdział 7. ♦ M apy bitow e i obrazy w OpenGL
185
g l V ie w p o rt(0, 0, w id th , h e ig h t); / / o k re ś la nowe wymiary okna g r a f ik i glMatrixMode(GL_PROJECTION); / / w ybiera m acierz rzutow ania g lL o a d ld e n ti t y ( ) ; / / i re s e tu je ją / / rzu tow an ie o rto g ra fic z n e g lO r th o ( 0 . O f, w id th - 1 .0 , 0 .0 , h e ig h t - 1 .0 , -1 .0 , 1 .0 ); glMatrixMode(GL_MODELVIEW); g lL o a d ld e n t ity O ;
/ / w ybiera m acierz modelowania / / i re s e tu je ją
Wynik działania tego prostego przykładu zastosowania mapy bitowej ilustruje rysunek 7.4. Rysunek 7.4. Przykład zastosowania funkcji glBitmapQ
Wykorzystanie obrazów graficznych Przy tworzeniu grafiki rastrowej w OpenGL częściej korzysta się z obrazów graficznych niż z map bitowych. Od tych ostatnich różni je przede wszystkim bogatszy opis pikseli za pomocą wartości modelu kolorów RGB. Jako że w OpenGL można manipulować pojedynczymi pikselami obrazów graficznych, często stosuje się w ich przypadku także nazwę mapy pikseli. W rozdziale tym przed stawiony będzie sposób wyświetlania map pikseli na ekranie, natomiast w rozdziale 8. omówione zostaną możliwości ich wykorzystania do tworzenia tekstur pokrywających wielokąty grafiki trójwymiarowej.
Rysowanie obrazów graficznych Przy założeniu, że już załadowana została do pamięci mapa pikseli, można użyć funkcji g lD ra w P ix e ls (), aby wyświetlić ją w określonym miejscu okna grafiki. Podobnie jak w przypadku funkcji g lB itm a p ( ) położenie mapy pikseli określa się za pomocą funkcji gl R a s te rP o s *( ). Funkcja gl DrawPi xel s () zdefiniowana jest w następujący sposób:
186
Część II ♦ Korzystanie z OpenGL
void glD raw Pixels(GLsizei wi d t h , GLsizei h ei g h t , GLenum format , GLenum type, const GLvoid * p i x e l s );
Funkcji tej przekazuje się (kolejno) następujące parametry: szerokość i wysokość obra zu graficznego, format i typ pikseli, dane opisujące piksele. Dopuszczalne formaty pik seli przedstawia tabela 7.1. Najczęściej używa się formatu GL_RGB, który określa dla każdego piksela wartość składowej czerwonej, zielonej i niebieskiej. Tabela 7.1. Formaty pikseli Format pikseli
Opis
GL_ALPHA
Piksel opisany przez współczynnik alfa
GLBGR
Piksel opisany przez składową niebieską zieloną i czerwoną
GLJ3GRA
Piksel opisany przez składową niebieską zie lo n ą czerwoną i w spółczynnik alfa
GL_BLUE
Piksel opisany przez składową niebieską
GL_C0L0R_INDEX
Piksel opisany przez indeksowy model kolorów
GL_GREEN
Piksel opisany przez składową zieloną
GL_RED
Piksel opisany przez składową czerwoną
GL_RGB
Piksel opisany przez składową czerwoną zieloną i niebieską
GL_RGBA
Piksel opisany przez składową czerwoną zie lo n ą niebieską i w spółczynnik alfa
Typy pikseli prezentuje tabela piksela.
7.2.
Typ piksela definiuje typ danych stosowany do opisu
Tabela 7.2. Typy p ikseli Typ pikseli
Opis
GL_BITMAP
Pojedynczy bit (0 lu b i)
GL_BYTE
Liczba całkowita ze znakiem, 8 bitów
GLUNSIGNEDBYTE
Liczba całkowita bez znaku, 8 bitów
GL_SH0RT
Liczba całkowita ze znakiem, 16 bitów
GL_UNSIGNED_SHORT
Liczba całkowita bez znaku, 16 bitów
GL_INT
Liczba całkowita ze znakiem, 32 bity
GL_UNSIGNED_INT
Liczba całkowita bez znaku, 32 bity
Poniżej zaprezentowany został fragment kodu rysujący obraz graficzny za pomocą funkcji g lD ra w P ix e ls () na pozycji o współrzędnych (300, 300) zdefiniowany w bufo rze wskazywanym przez zmienną i mageData: unsigned char *imageData; in t imageWidth, imageHeight; glR asterP os2i(300. 300); glDrawPixels(imageWidth, imageHeight, GL_RGB, GL_UNSIGNED BYTE,
imageData);
Rozdział 7. ♦ M apy bitow e i obrazy w OpenGL
187
Odczytywanie obrazu z ekranu Często w programach zachodzi konieczność odczytania bieżącej zawartości ekranu i zapamiętania jej na dysku bądź przetworzenia w celu uzyskania efektów specjalnych. W OpenGL zadanie to wykonuje się za pomocą funkcji g lR ea dP ixelsO o następującym prototypie: void gl ReadPi xe*ls(GLint x. GLint y , GLsizei wi dt h, GLsizei h ei g h t . GLenum format, GLenum type, GLvoid * p i x e l s ) :
Parametry tej funkcji mają takie samo znaczenie jak w przypadku funkcji gl DrawPi xel s (). Uzupełnione zostały o parę współrzędnych (x, y) określających lewy, dolny narożnik fragmentu ekranu, który zostanie zapamiętany w buforze pixels. Parametry width i he ight określają rozmiary tego fragmentu. Parametry określające format i typ pikseli mo gą przyjmować te same wartości co w przypadku funkcji gl DrawPi x e ls ( ) (wymienione w tabelach 7.1 i 7.2). Poniższy fragment kodu prezentuje sposób umieszczenia w buforze zawartości górnej połowy okna grafiki za pomocą funkcji gl ReadPi xel s O : void *imageData; in t screenWidth, screenHeight; glReadPix e ls(0. screenHeight/2. screenWidth, screenHeight/2, GL_RGB, GL_UNSIGNED_BYTE, imageData);
Kopiowanie danych ekranu OpenGL umożliwia także kopiowanie pikseli pomiędzy różnymi obszarami ekranu za pomocą funkcji gl CopyPi xel s () zdefiniowanej następująco: glCopyPixels(G Lint x, GLint y , GLsizei wi dt h, GLsizei h e i g h t , GLenum b u f f e r );
Funkcja ta kopiuje fragment ekranu w kształcie prostokąta o szerokości width i wysoko ści height, którego lewy, dolny wierzchołek posiada współrzędne (x, y) i umieszcza go na bieżącej pozycji rastra. Parametr buffer może przyjmować jedną z wartości przed stawionych w tabeli 7.3. Tabela 7.3. Rodzaje bufom dla funkcji glCopyPixelsQ Wartość parametru buffer
Opis
GL_C0LQR
Kopiuje zawartość bufora kolorów
GL_DEPTH
Kopiuje zawartość bufora głębi
GL_STENCIL
Kopiuje zawartość bufora powielania
Jednym z typowych zastosowań funkcji gl CopyPi xel s ( ) w grach jest wprowadzenie szkła powiększającego lub celownika optycznego broni. Kopiując pewien obszar ekranu i powiększając go za pomocą funkcji gl Pi xel Zoom() uzyskać można właśnie taki efekt.
188
Część II ♦ Korzystanie z OpenGL
Powiększanie, pomniejszanie i tworzenie odbić OpenGL umożliwia powiększanie i pomniejszanie fragmentów obrazu oraz tworzenie ich odbić. Służy do tego funkcja gl Pi xel Zoom() posiadająca następujący prototyp: void glPixelZoom (GLfloat xZoom, G Lfloat yZoom);
Domyślna wartość obu parametrów wynosi 1.0, co oznacza normalny obraz. Wartości z przedziału od 0.0 do 1 . 0 powodują pomniejszenie obrazu, a większe od 1 . 0 jego po większenie. Jeśli dodatkowo wartość taka będzie ujemna, to spowoduje ona odbicie ob razu względem bieżącej pozycji rastra. Poniżej zaprezentowanych zostało kilka przy kładów użycia funkcji gl Pi xel Zoom(): glPixelZoom (-1.0f, -l.O f); // tworzy odbicie w pionie i w poziomie glPixelZoom (0.5f, 0 .5 f); // zmniejsza rozmiary obrazu o połowę glPixelZoom (5.0f. 5 .Of); // powiększa obraz 5-krotnie w każdym z wymiarów
Upakowanie danych mapy pikseli Po uruchomieniu tego samego programu tworzącego grafikę OpenGL na różnych kom puterach można zaobserwować różną szybkość jego działania. Może to być spowodo wane szeregiem różnych czynników. Jednym z nich jest różna prędkość kopiowania da nych w zależności od ich wyrównania do granic słów 2-, 4- i 8-bajtowych. Wyrównanie danych opisujących piksele można kontrolować za pomocą funkcji gl P ix e lS to re i () zdefiniowanej następująco: void g lP ix e lS to re i(GLenum pname, TYPE param):
W przypadku aktualnych zastosowań parametr pname będzie przyjmować wartość GL_ PACK_ALIGNMENT lub GL_UNPACK_ALIGNMENT, natomiast parametr param wartości 1, 2, 4 lub 8. Podając GL PACK ALIGNMENT jako wartość parametru pname określa się sposób, w jaki dane opisujące piksele wyrównywane są podczas umieszczania ich w buforze. Nato miast parametr GL UNPACK ALIGNMENT pozwala określić wyrównanie danych niezbędne do ich właściwego odczytania. Domyślną wartością parametru param jest w obu przy padkach wartość 4. Poniższy przykład informuje OpenGL, że dane umieszczone zostały w kolejnych bajtach: g lP ix e lS to re i(GL_UNPACK_ALIGNMENT, 1);
Mapy bitowe systemu Windows Jako że opanowana już została umiejętność wykonywania podstawowych operacji na obrazach graficznych, można pokusić się o próbę zastosowania ich do prawdziwych da nych. Jako pierwszy omówiony zostanie format plików graficznych BMP używany w sys temie Windows.
Rozdział 7. ♦ M apy bitow e i obrazy w OpenGL
189
Wielką zaletą plików BMP jest to, że mogą być łatwo tworzone, edytowane i przeglą dane przez każdego użytkownika systemów rodziny Windows. Ich wadą jest natomiast brak kompresji, który sprawia, że posiadać mogą całkiem spore rozmiary. Jednak brak kompresji oznacza także, że operacje odczytu i zapisu takich plików są bardzo proste.
Format plików BMP Jak pokazane zostało na rysunku 7.5, w strukturze pliku BMP można wyróżnić trzy czę ści: nagłówek pliku, nagłówek mapy bitowej oraz właściwe dane mapy bitowej. Rysunek 7.5. Struktura p liku B M P
Nagłówek pliku
Nagłówek obrazu
(Dane palety)
Dane obrazu
Do pobrania nagłówka pliku BMP wykorzystać można strukturę BITMAPFILEHEADER zde finiowaną jak poniżej: typedef s tru c t tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bf Reserved!.; WORD bfReserved2; DWORD b fO ffB its; } BITMAPFILEHEADER;
Po wczytaniu nagłówka pliku BMP należy koniecznie sprawdzić, czypole bfType struktury BITMAPFILEHEADER posiada wartość 0x4D42. Wtensposób weryfikuje się to, czy dany plik rzeczywiście jest plikiem formatu BMP. Następna część pliku BMP zawiera informacje dotyczące mapy bitowej. Informacje te umieszczone są w jednej lub dwóch strukturach w zależności od tego, czy mapa bitowa opisuje piksele za pomocą 8-bitów i posiada paletę kolorów. Ponieważ w tym przykła dzie nie jest konieczne korzystanie z plików BMP dysponujących własną paletą, wystar czy jedynie przeczytać dane umieszczone w strukturze BITMAPINFOHEADER: typedef s tru ct tagBITMAPINFOHEADER{ DWORD b iS ize ; LONG b iWidth; LONG b iHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression;
190
Część II ♦ Korzystanie z OpenGL
DWORD biSizelmage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrlm portant; } BITMAPINFOHEADER;
Zawartość tej struktury jest właściwie oczywista. Kłopotów może przysporzyć jedynie pole biCompression. Chociaż używane tu pliki BMP nie stosują kompresji, to istnieją jednak wersje formatu BMP, które mogą stosować kompresję za pomocą algorytmu RLE. Omówienie tego algorytmu wykracza poza tematykę tej książki, więc można ograniczyć się jedynie do sprawdzenia, czy pole biCompression posiada wartość BI_RGB, co oznacza brak kompresji. Ostatnią część struktury pliku BMP stanowią właściwe dane obrazu graficznego. Dane te opisują kolejne piksele obrazu w formacie 1-, 4-, 8-, 16- lub 24-bitowym.
Ładowanie plików BMP Aby załadować zawartość pliku BMP do pamięci, należy odczytać po kolei wszystkie trzy jego części. Po odczytaniu struktury BITMAPINFOHEADER można wyznaczyć wielkość bufora pamięci potrzebnego do przechowania obrazu zapisanego w pliku. Po wczytaniu danych do bufora należy dla wszystkich pikseli zamienić miejscami wartości składowej czerwonej ze składową niebieską, aby OpenGL właściwie wyświetlał kolory. Poniżej przedstawiony został przykład implementacji funkcji ładującej plik BMP stosujący 24bitowy opis pikseli: unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER *bitmapInfoHeader)
{
FILE * fi 1e P tr ; BITMAPFILEHEADERbitmapFileHeader; unsigned char*bitmaplmage; intimageldx = 0; unsigned chartempRGB;
// // // // //
wskaźnik p lik u nagłówek p lik u bufor obrazu lic z n ik bajtów obrazu zmienna zamiany składowych
// otwiera p lik w try b ie "read binary" f ile P t r = fopen(filename, "rb"); i f ( f ile P t r == NULL) return NULL; // wczytuje nagłówek p lik u fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER). 1. f ile P t r ) ; // sprawdza, czy rzeczyw iście je s t to p lik BMP i f (bitmapFileHeader.bfType != BITMAP_ID)
{
f c lo s e ( f ile P t r ) ; return NULL;
} // wczytuje nagłówek obrazu zapisanego w p lik u fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER). 1. f ile P t r ) ;
Rozdział 7. ♦ M apy bitow e i obrazy w OpenGL
1 91
// ustawia wskaźnik p lik u na początku danych opisujących obraz fs e e k ( file P tr , bitm apFileH eader.bfO ffBits, SEEK_SET); // p rzyd zie la pamięć na bufor obrazu bitmaplmage = (unsigned char*)malloc(bitmapInfoHeader->biSizeImage); // sprawdza, czy pamięć została przydzielona i f ( ! bitmaplmage)
{ free(bitmaplmage); f c lo s e ( f ile P t r ) ; return NULL;
} // wczytuje dane obrazu fread(bitmaplmage. 1, bitmapInfoHeader->biSizeImage, f ile P t r ) ; // sprawdza, czy operacja powiodła się i f (bitmaplmage == NULL)
{ f c lo s e ( f ile P t r ) ; return NULL;
} // zamienia składowe R i B, aby uzyskać format RGB używany przez OpenGL fo r (imageldx = 0; imageldx < bitmapInfoHeader->biSizeImage; imageIdx+=3)
{
tempRGB = bitmaplmage[imageldx]; bitmaplmage[imageldx] = bitmaplmage[imageldx + 2]; bitmaplmage[imageldx + 2] = tempRGB;
} // zamyka p lik i zwraca wskaźnik bufora zawierającego obraz f c lo s e ( f ile P t r ) ; return bitmaplmage;
} Jak pokazuje powyższy przykład załadowanie obrazu graficznego z pliku BMP nie jest skomplikowane. Poniżej zaprezentowany został sposób wykorzystania funkcji LoadB itm a p F ile O : BITMAPINFOHEADER bitmapInfoHeader; unsigned char* bitmapData;
// nagłówek obrazu // bufor obrazu
bitmapData = LoadBitm apFileCtest.bm p", &bitmapInfoHeader); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // określa sposób wyrównania danych g lra s te rP o s 2 i(100,100); // określa bieżącą pozycję rastra gl DrawPixels(bitmapInfoHeader.biW idth, bitmapInfoHeader.b iH eig h t, GL_RGB, GL_UNSIGNED_BYTE, bitmaplmage); // wyświetla obraz
Zapis obrazu w pliku BMP A co w przypadku, kiedy obraz uzyskany za pomocą funkcji gl Readpi xel s () będzie trzeba zapisać w pliku BMP1 W takim przypadku należy indywidualnie wypełnić struk tury BITMAPFILEHEADER i BITMAPINFOHEADER.
192
Część II ♦ Korzystanie z OpenGL
Poniżej prezentujemy przykład implementacji funkcji zapisu obrazu graficznego o sze rokości i wysokości określonych przez parametry w id th i h e ig h t : in t W riteBitm apFile(char ^filename, in t width, in t height, unsigned char *imageData)
{ FILE * f ile P tr ; BITMAPFILEHEADER bitmapFileHeader; BITMAPINFOHEADER bitmapInfoHeader; in t imageldx; unsigned char tempRGB;
// // // // //
wskaźnik p lik u nagłówek p lik u nagłówek obrazu indeks obrazu zmienna zamiany składowych
// otwiera p lik do zapisu w try b ie "w ritin g binary" f ile P t r = fopen(filename, "wb"); i f ( ! f ile P t r ) return 0; // d e fin iu je nagłówek p lik u bitm apFileHeader.bfSize = sizeof(BITMAPFILEHEADER); bitmapFileHeader.bfType = 0x4D42; bitmapFileHeader.bfReservedl = 0; bitmapFileHeader.bfReserved2 = 0; bitm apFileHeader.bfO ffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); // d e fin iu je nagłówek obrazu bitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER); bitmapInfoHeader.biPlanes = 1; bitmapInfoHeader.biBitCount = 24; // 24-bity bitmapInfoHeader.biCompression = BIRG B; // bez kompresji // szerokość * wysokość * (bajty opisu RGB) bitmapInfoHeader.biSizelmage = width * abs(height) * 3; bitmapInfoHeader.b iX P e lsPerMeter = 0; bitmapInfoHeader.biYPelsPerMeter = 0; bitmapInfoHeader. bi Cl rllsed = 0; bitmapInfoHeader.biClrlmportant = 0; bitmapInfoHeader.biWidth = width; // szerokość bitmapInfoHeader.biHeight = height; // wysokość // zamienia składowe z formatu RGB na GBR fo r (imageldx = 0; imageldx < bitmapInfoHeader.biSizelmage; imageIdx+=3)
{
tempRGB = imageData[imageIdx]; imageData[imageIdx] = imageData[imageIdx + 2]; imageData[imageIdx + 2] = tempRGB;
} // zapisuje nagłówek p lik u fwrite(&bitmapFileHeader, 1. sizeof(BITMAPFILEHEADER), f ile P t r ) ; // zapisuje nagłówek obrazu fwrite(&bitmapInfoHeader, 1, sizeof(BITMAPINFOHEADER), f ile P t r ) ; // zapisuje dane obrazu fwrite(imageData, 1, bitmapInfoHeader.biSizelmage, f ile P t r ) ; // zamyka p lik f c lo s e ( f ile P t r ) ; return 1;
Rozdział 7. ♦ M apy bitow e i obrazy w OpenGL
193
Jeśli funkcja ta będzie zastosowana razem z funkcją gl ReadPixel s (), to uzyskana zosta nie możliwość zapisu zawartości okna grafiki w pliku BMP. Poniżej przedstawiony zo stał kod funkcji implementującej takie rozwiązanie: u n s ig n e d c h a r * im a g e D a ta ; v o id S a v e S c re e n s h o t ( i n t w in W id th , i n t w in H e ig h t)
{ i m a g e D a t a = m ai 1o c ( 8 0 0 * 6 0 0 ^ 3 ) ; / / p r z y d z i e l a p a m i ę ć b u f o r a im a g e D a t a m e m se t(im a g e D a ta , 0, 8 0 0 * 6 0 0 * 3 ) ; / / o p r ó ż n ia b u f o r / / w c z y t u j e o b r a z okna do b u fo r a g l R e a d P i x e l s ( 0 . 0 , 7 9 9 , 5 9 9 , G L_ R G B , G L_ U N S IG N E D _ B Y T E , i m a g e D a t a ) ; / / z a p i s u j e b u f o r do p l i k u W r i t e B it m a p F ile C w r it e o u t . b m p " , 800, 600, (u n s ig n e d c h a r * ) im a g e D a t a ) ; // z w a ln ia b u fo r f r e e ( i m a g e D a t a );
}
Pliki graficzne Targa Kolejnym omówionym formatem plików graficznych będzie format Targa. Format ten nie jest szczególnie skomplikowany. Jego podstawową zaletą w stosunku do formatu BMP jest zastosowanie kanału alfa, który umożliwia wykorzystanie plików Targa do realizacji odwzorowań tekstur.
Format plików Targa Struktura plików Targa podzielona została na dwie części: nagłówek i dane. Nagłówek składa się z 12 pól opisanych przez poniższą strukturę: t y p e d e f s t r u c t tagTARGAFILEHEADER u n s ig n e d c h a r im a g e ID L e n g th ; u n s ig n e d c h a r co lo rM a p T y p e ; u n s ig n e d c h a r im ag e T yp e C od e; short short short short
in t in t in t in t
c o lo rM a p O rig in ; co lo rM a p L e n g th ; co lo rM a p E n try S iz e ; im a g e X O rig in ;
s h o r t i n t im a g e Y O rig in ; s h o r t i n t im a g e W id th ; s h o r t i n t im a g e H e ig h t; u n s ig n e d c h a r b it C o u n t ; u n s ig n e d c h a r im a g e D e s c r ip to r ; } TARGAFILEHEADER;
/ / / / / / / / / / / / / / / / / /
l i c z b a znaków w p o lu i d e n t y f i k a c j i 0 ozna cza b ra k p o la id e n t y f i k a c j i t y p m apy k o l o r ó w , z a w s z e w a r t o ś ć 0 w a r t o ś ć 2 = RGB b e z k o m p r e s j i w a rto ś ć 3 = o b ra z bez k o m p re s ji, o d c ie n ie s z a ro ś c i p o c z ą t e k mapy k o l o r ó w , z a w s z e w a r t o ś ć 0 d ł u g o ś ć mapy k o l o r ó w , z a w s z e w a r t o ś ć 0 r o z m i a r e l e m e n t ó w mapy k o l o r ó w , z a w s z e w a r t o ś ć 0 w s p ó łrz ę d n a x lew eg o , d o ln e g o n a r o ż n ik a o b r a z u . zaw sze w a rto ść 0 w s p ó łrz ę d n a y lew ego , d o ln e g o n a r o ż n ik a o b ra z u , zaw sze w a rto ść 0 sze ro k o ść obrazu w p ik s e la c h n a jp ie rw b a j t m n ie j z n a c z ą c y wysokość obrazu w p ik s e la c h n a jp ie rw b a j t m n ie j z n a c z ą c y l i c z b a b i t ó w : 1 6 , 2 4 l u b 32 w a r t o ś ć 0 x 00 = 24 b i t y , w a r t o ś ć 0 x 0 8 = 3 2 - b i t y
194
Część II ♦ Korzystanie z OpenGL
Dane obrazu w formacie Targa zaczynają się zaraz za nagłówkiem pliku. Pierwszym po lem nagłówka, którego zawartością trzeba się zainteresować, jest imageTypeCode. Określa ono typ pliku Targa. Niektóre z typów plików Targa prezentuje tabela 7.4. W podanych tu przykładach wykorzystane zostaną jedynie typy reprezentowane przez wartości 2 i 3. Tabela 7.4. Typy plików Targa Kod
Opis
2
K olorowy obraz RGB bez kompresji
3
Obraz biało-czarny bez kompresji
10
Skompresowany obraz RGB (algorytm RLE)
11
Skompresowany obraz biało-czarny
Ostatnie cztery pola nagłówka pliku Targa pozwalają ustalić rozmiary bufora potrzeb nego do wczytania obrazu zapisanego w pliku oraz sposób jego odczytu. Podobnie jak w przypadku plików BMP format Targa zawiera opis pikseli obrazu w formacie BGR lub BGRA (odpowiednio) dla opisu 24- i 32-bitowego.
Ładowanie zawartości pliku Targa Trzeba przyjrzeć się zatem bliżej sposobowi, w jaki należy załadować zawartość pliku Targa. Poniższą strukturę definiuje się tak, aby można było przechować w niej wszystkie istotne informacje o obrazie zawartym w pliku tego typu: typedef s tru c t
{
u n sig n e d c h a r short in t short in t u n sig n e d c h a r u n s ig n e d c h a r } TGAFILE;
im ag e Typ e C od e; im a g e W id th ; im a g e H e ig h t; b itC o u n t; * im a g eD ata;
Poniżej przedstawiamy przykład implementacji funkcji ładującej obraz graficzny zapi sany w pliku Targa: i n t L o a d T G A F ile ( c h a r * f ile n a m e , TGAFILE * t g a F ile )
{
FILE * f ile P t r ; u n s ig n e d c h a r u ch arBad ; s h o rt in t s in tB a d ; lo n g im a g e S iz e ; i n t co lo rM o d e ; lo n g im a g e ld x ; u n sig n e d c h a r c o lo rS w a p ; / / o tw ie ra p l i k f i l e P t r = fo p e n (file n a m e , i f ( 1f i l e P t r ) r e t u r n 0;
// // // // // //
n ie u ż y w a n e dane ty p u u n s ig n e d c h a r n ie w y k o rz y s ty w a n e dane ty p u s h o r t i n t r o z m i a r o b r a z u TGA 4 = RGBA l u b 3 = RGB lic z n ik zm ie n na z a m ia n y s k ła d o w y c h k o l o r u
"rb");
/ / w c z y t u j e dwa p i e r w s z e n i e u ż y w a n e b a j t y fr e a d ( & u c h a r B a d . s i z e o f ( u n s i g n e d c h a r ) , 1, f i l e P t r ) ; fr e a d ( & u c h a r B a d , s i z e o f ( u n s i g n e d c h a r ) , 1, f i l e P t r ) ;
Rozdział 7 . ♦ M apy bitow e i obrazy w OpenGL
// w czytu je typ p lik u f r e a d ( & t g a F ile - > im a g e T y p e C o d e , s i z e o f ( u n s ig n e d c h a r ) , 1. f i l e P t r ) ; / / używa s i ę j e d y n i e t y p u 2 ( k o lo r o w y ) l u b 3 ( b i a ł o - c z a r n y ) i f ( ( t g a F i l e - > i m a g e T y p e C o d e != 2 ) && ( t g a F i l e - > i m a g e T y p e C o d e != 3 ) )
{ fc lo s e ( file P tr) ; r e t u r n 0;
} / / w c z y t u j e k o l e j n y c h 13 n i e u ż y w a n y c h b a j t ó w f r e a d ( & s in t B a d , s i z e o f ( s h o r t i n t ) , 1. f i l e P t r ) ; f r e a d ( & s in t B a d , s i z e o f ( s h o r t i n t ) , 1, f i l e P t r ) ; fr e a d (& u c h a r B a d . s iz e o f ( u n s ig n e d c h a r ) . 1. f i l e P t r ) ; f r e a d ( & s in t B a d , s i z e o f ( s h o r t i n t ) . 1. f i l e P t r ) ; f r e a d ( & s in t B a d , s i z e o f ( s h o r t i n t ) . 1, f i l e P t r ) ; / / w c z y tu je ro z m ia ry o b ra zu f r e a d ( & t g a F ile - > im a g e W id t h , s i z e o f ( s h o r t i n t ) . 1, f i l e P t r ) ; f r e a d ( & t g a F i l e - > i m a g e H e i g h t , s i z e o f ( s h o r t i n t ) . 1, f i l e P t r ) ; / / w c z y tu je g ł ę b ię k o lo ró w f r e a d ( & t g a F i l e - > b i t C o u n t , s i z e o f ( u n s ig n e d c h a r ) . 1. f i l e P t r ) ; / / w c z y t u je 1 b a j t n ie u żyw a n yc h danych f r e a d ( & u c h a r B a d , s i z e o f ( u n s ig n e d c h a r ) , 1. f i l e P t r ) ; / / t r y b k o l o r ó w , 3 = B GR, 4 = BGRA c o lo r M o d e = t g a F i l e - > b i t C o u n t / 8; im a g e S iz e = tg a F ile - > im a g e W id th * tg a F ile - > im a g e H e ig h t * c o lo rM o d e ; / / p r z y d z i e l a p a m i ę ć na b u f o r o b r a z u tg a F ile - > im a g e D a ta = (u n s ig n e d c h a r * ) m a llo c ( s iz e o f (u n s ig n e d c h a r ) * im a g e S iz e ) ; / / w c z y tu je dane obrazu fr e a d ( t g a F ile - > im a g e D a t a , s i z e o f (u n s ig n e d c h a r ) , im a g e S iz e . f i l e P t r ) ; / / z m i e n i a o p i s k o l o r ó w z BGR na RGB / / a b y z o s t a ł y p r a w i d ł o w o z i n t e r p r e t o w a n e p r z e z O pe nG L f o r ( i m a g e l d x = 0; i m a g e l d x < i m a g e S i z e ; i m a g e l d x += c o l o r M o d e )
{
co lo rS w a p = tg a F ile -> im a g e D a ta [im a g e Id x ]; tg a F ile -> im a g e D a ta [im a g e Id x ] = tg a F ile -> im a g e D a ta [im a g e Id x + 2]; tg a F ile - > im a g e D a ta [ im a g e I d x + 2] = co lo rS w a p ;
} / / zamyka p l i k fc lo s e ( file P tr ) ; r e t u r n 1;
} Funkcję tę stosować można w poniższy sposób: T G A F I L E *m yTGA; myTGA = ( T G A F I L E * ) m a l l o c ( s i z e o f ( T G A F I L E ) ) ; L o a d T G A F ile C 't e s t . t g a " , m yTGA);
195
196
Część II ♦ Korzystanie z OpenGL
A następnie — podobnie jak w przypadku pliku BMP — wyświetla się załadowany obraz za pomocą funkcji gl DrawPi xel s (): g l P i x e l S t o r e i ( G L _ U N P A C K _ A L IG N M E N T , 4 ) ; g lR a s t e r P o s 2 i(2 00,20 0); g l D r a w P i x e l s ( m y T G A - > i m a g e W i d t h , m y T G A - > i m a g e H e i g h t , G L _ R G B , G L _ U N S IG N E D _ B Y T E , m y T G A -> im a g e D a ta );
Zapis obrazów w plikach Targa Zapis informacji w pliku Targa jest równie prosty jak jej odczyt. Wystarczy jedynie wyspecyfikować typ pliku, głębię kolorów i tryb ich opisu. W pozostałych polach na główka pliku należy umieścić wartość 0. Zapisując obraz OpenGL w pliku Targa trzeba pamiętać o zmianie modelu kolorów z RGB(A) na BGR(A). Należy przyjrzeć się zatem przykładowej implementacji funkcji zapisu obrazu w pliku Targa: i n t W r it e T G A F ile ( c h a r * f ile n a m e , s h o r t i n t w id th , s h o r t i n t h e ig h t , u n s ig n e d c h a r* im age D ata) u n sig n e d c h a r b y te S k ip ; short in t s h o rtS k ip u n s ig n e d c h a r im age Type in t c o lo rM o d e u n sig n e d c h a r co lo rS w a p i n t im a g e ld x ; u n sig n e d c h a r b itD e p th ; lo n g im a g e S iz e ; FILE * f i l e P t r ;
/ / zm ie n n e do w y p e ł n i a n i a n ie u ż y w a n y c h p ó l / / ty p za p isy w a n e g o o b ra z u
/ / o t w ie r a p l i k do z a p is u f i l e P t r = f o p e n ( f i le ñ a m e , "w b "); i f ( ¡file P tr)
{
fc lo s e (file P tr); r e t u r n 0;
im a g e T y p e = 2 b i t D e p t h = 24 c o lo rM o d e = 3
/ / RGB b e z k o m p r e s j i / / 2 4 -b ito w a g łę b ia k o lo ró w / / t r y b RGB
b y t e S k i p = 0; s h o r t S k i p = 0; / / z a p i s u j e dwa b a j t y n i e u ż y w a n y c h d a n y c h f w r i t e ( & b y t e S k i p , s i z e o f ( u n s i g n e d c h a r ) , 1, f i l e P t r ) ; f w r i t e ( & b y t e S k i p , s i z e o f ( u n s i g n e d c h a r ) , 1, f i l e P t r ) ; / / z a p i s u j e im ag e T yp e f w r i t e ( & i m a g e T y p e , s i z e o f ( u n s i g n e d c h a r ) , 1. f i l e P t r ) ; fw rite (& s h o rtS k ip , fw rite (& s h o rtS k ip , fw rite (& b y te S k ip . fw rite C & s h o rtS k ip , fw n te (& s h o rtS k ip ,
s i z e o f ( s h o r t i n t ) , 1, s i z e o f ( s h o r t i n t ) , 1, s iz e o f(u n s ig n e d c h a r ) , s i z e o f ( s h o r t i n t ) , 1, s i z e o f ( s h o r t i n t ) , 1.
file P tr) ; file P tr); 1, f i l e P t r ) ; file P tr); file P tr) ;
Rozdział 7. ♦ M apy bitow e i obrazy w OpenGL
197
// z a p is u je ro z m ia ry obrazu f w r i t e ( & w i d t h , s i z e o f ( s h o r t i n t ) , 1. f i l e P t r ) ; f w r i t e ( & h e i g h t . s i z e o f ( s h o r t i n t ) , 1, f i l e P t r ) ; f w r i t e C & b i t D e p t h , s i z e o f ( u n s i g n e d c h a r ) , 1, f i l e P t r ) ; / / z a p i s u j e je d e n b a j t n ie u ż y w a n y c h d an ych f w r i t e ( & b y t e S k i p , s i z e o f ( u n s i g n e d c h a r ) , 1, f i l e P t r ) ; / / o b lic z a ro z m ia r o b razu im a g e S iz e = w id th * h e ig h t * co lo rM o d e ; / / z m i e n i a o p i s k o l o r ó w z RGB na BGR f o r ( i m a g e l d x = 0; i m a g e l d x < i m a g e S i z e ; i m a g e l d x += c o l o r M o d e )
{ c o lo rS w a p = im a g e D a ta [im a g e Id x ]; im a g e D a ta [im a g e Id x ] = im a g e D a ta [im a g e Id x + 2 ]; im a g e D a ta [im a g e Id x + 2] = co lo rS w a p ;
} // z a p is u je dane obrazu fw r it e ( im a g e D a t a , s iz e o f ( u n s ig n e d c h a r ) , im a g e S iz e , f i l e P t r ) ; / / zam yka p l i k fc lo s e ( file P tr) ; r e t u r n 1;
} Podobnie jak w przypadku plików BMP funkcję tę można stosować w połączeniu z funk cją gl ReadPi xel s () do zapisu zawartości okna grafiki OpenGL w pliku Targa: v o id S a v e S c re e n s h o tO
{ i m a g e D a t a = m a i l o c ( 8 0 0 * 6 0 0 * 3 ) ; / / p r z y d z i e l a p a m i ę ć b u f o r o w i im a g e D a t a m e m s e t ( i m a g e D a t a , 0, 8 0 0 * 6 0 0 * 3 ) ; / / o p r ó ż n i a z a w a r t o ś ć b u f o r a im a g e D a t a / / w c z y tu je dane o b razu w o k n ie g r a f ik i g l R e a d P i x e l s ( 0 , 0 , 7 9 9 , 5 9 9 , G L _ R G B , G L _ U N S IG N E D _ B Y T E , i m a g e D a t a ) ; // z a p is u je obraz w p lik u W r it e T G A F i l e f 'w r i t e o u t . t g a " , 800, 600, (u n s ig n e d c h a r * ) im a g e D a t a ) ; / / z w a l n i a p a m ię ć b u fo r a f r e e ( i m a g e D a t a );
}
Podsumowanie Mapy bitowe w OpenGL definiowane są za pomocą dwuwymiarowych tablic, w któ rych każdy piksel opisany jest za pomocą pojedynczego bitu. Mapy bitowe wykorzy stywane są jako maski prostokątnych fragmentów ekranu oraz do reprezentacji znaków ekranowych. Funkcja g lR a s te rP o s *( ) pozwala określić położenie rysowanej mapy bitowej lub obra zu na ekranie za pomocą współrzędnych lewego, dolnego narożnika.
198
Część II ♦ Korzystanie z OpenGL
Po określeniu pozycji mapę bitową rysuje się za pomocą funkcji gl B itm apO . Po załadowaniu obrazu do bufora pamięci można wyświetlić go stosując funkcję gl D raw P ixelsO . Pozycję wyświetlanego obrazu określa się za pomocą funkcji g lR a s te rP o s*(). Zawartość ekranu można odczytać korzystając z funkcji gl ReadPi xel s (), która następnie powinna zostać przetworzona w celu uzyskania efektów specjalnych lub zapisana w pliku. Funkcja gl Copy Pi xel s () umożliwia przenoszenie fragmentów obrazu na ekranie. OpenGL pozwala powiększać i zmniejszać mapy bitowe, a także tworzyć ich odbicia. Służy do tego funkcja gl P ixe l Zoom(). Formaty Windows BMP i Truevision Targa umożliwiają łatwy zapis i odczyt obrazów z plików.
Rozdział 8.
Odwzorowania tekstur Żaden z omówionych dotychczas elementów grafiki OpenGL nie podnosi realizmu two rzonej grafiki w takim stopniu jak tekstury. Zamiast tworzyć obiekty wirtualnego świata z kolorowych wielokątów, które udawać będą jedynie kształt prawdziwych obiektów, można pokryć je dodatkowo teksturami, które pozwalają uzyskać obraz obiektów przy pominający realizmem fotografię. Omówione więc zostanie pojęcie tekstury i metody jej implementacji w celu podniesienia realizmu tworzonej grafiki. W rozdziale tym przedstawione zostaną: ♦ podstawy odwzorowań tekstur; ♦ obiekty tekstur; ♦ powielanie i nakładanie tekstur; ♦ mipmapy i poziomy szczegółowości; ♦ dwa przykłady zastosowań tekstur.
Odwzorowania tekstur Odwzorowania tekstur pozwalają pokrywać wielokąt za pomocą realistycznych obrazów. Na przykład prostokąt można pokryć obrazkiem przedstawiającym okładkę tej książki i uzy skać w ten sposób reprezentację okładki w trójwymiarowym świecie. Podobnie kulę moż na pokryć mapą powierzchni Ziemi uzyskując w ten sposób trójwymiarową reprezenta cję naszej planety. Tekstury są powszechnie wykorzystywane w grafice trójwymiarowej. Zwłaszcza w grach umożliwiają uzyskanie tak pożądanego realizmu tworzonych scen. Mapy tekstur są dwuwymiarowymi tablicami zawierającymi elementy nazywane tekselami. Mimo że reprezentują zawsze obszar prostokąta, to mogą być odwzorowywane na dowolnych trójwymiarowych obiektach, takich jak kule, walce i tym podobne. Zwykle stosowane są tekstury dwuwymiarowe, ale używa się także tekstur jedno- i trój wymiarowych. Tekstura dwuwymiarowa posiada szerokość i wysokość, co pokazano na rysunku 8.1. Tekstury wymiarowe są charakteryzowane jedynie przez szerokość, po nieważ ich wysokość wynosi zawsze jeden piksel. Tekstury trójwymiarowe posiadają sze rokość, wysokość oraz głębokość i czasami nazywane są teksturami bryłowymi. W bie żącym rozdziale przedstawiane będą głównie tekstury dwuwymiarowe.
200
Część II ♦ Korzystanie z OpenGL
Rysunek 8.1. Różnice pomiędzy teksturami jedno-, dwui trójwymiarowymi
Szerokość Szerokość Szerokość w aa— a ^
W ySO kO ŚĆ = 1
Tekstura jednowymiarowa
c
Tekstura trójwymiarowa
Po wykonaniu odwzorowania tekstury na wielokąt będzie ona przekształcana razem z wie lokątem. W przypadku wspomnianej już na przykład reprezentacji Ziemi tekstura będzie obracać się razem z kulą, na której została umieszczona stwarzając w ten sposób wraże nie obrotu Ziemi. Także w przypadku, gdy do jej animacji dodany zostanie kolejny obrót i przesunięcie na orbicie wokół innej kuli reprezentującej Słońce, tekstura pozostanie na powierzchni kuli. Odwzorowanie tekstury można wyobrazić sobie jako zmianę sposobu wyglądu powierzchni obiektu lub wielokąta. Niezależnie od wykonywanych przekształ ceń pokrywać będzie ona zawsze jego powierzchnię. Przed omówieniem szczegółów należy przyjrzeć się najpierw przykładowi zastosowania tekstury.
Przykład zastosowania tekstury W przykładzie tym wykorzystana zostanie znana z poprzednich rozdziałów animacja obracającego się sześcianu, ale tym razem będzie on pokryty teksturą o wzorze szachow nicy przedstawioną na rysunku 8.2 . Rysunek 8.2. Tekstura szachownicy
Ponieważ tekstura ta umieszczona została w pliku BMP, to do jej załadowania wykorzystać można funkcję LoadBitmapFi 1e ( ) zaimplementowaną w poprzednim rozdziale. Tekstury w OpenGL muszą posiadać zawsze rozmiary będące potęgą liczby 2, na przykład 64x64 lub 256x256. W tym przykładzie wykorzystana zostanie tekstura o rozmiarach 64x64. Pierwszy fragment kodu tego przykładu przedstawia pliki nagłówkowe i zmienne globalne: / / / / / / D e fin ic je # d e fin e B I T M A P J D 0x4D42 ////// P # in c lu d e # in c lu d e # in c lu d e # in c lu d e # in c lu d e
l i k i n ag łów kow e < w in d o w s . h > < s td io .h > < s td lib .h > < g l/g l.h > < g l/ g lu .h >
/ / / / / / Z m ie n n e g l o b a l n e HDC g_HDC; bool f u ll Screen = fa ls e ; bool k e y P re sse d [2 5 6 ]; f l o a t a n g le = O .O f;
/ / i d e n t y f i k a t o r f o r m a t u BMP
/ / s t a n d a r d o w y p l i k n a g ł ó w k o w y W in d o w s / / p l i k nagłów kow y o p e r a c j i w e j ś c i a i w y j ś c ia / / s ta n d a r d o w y p l i k n ag łó w k o w y OpenGL / / f u n k c j e p o m o c n i c z e O pe nG L
// g lo b a ln y k o n te k s t u rz ą d z e n ia // t r u e = t r y b p e łn o e kra n o w y; / / f a l s e = t r y b o kie n k o w y // t a b lic a p r z y c iś n ię ć k la w is z y // kąt obrotu
Rozdział 8 . ♦ Odwzorowania te ks tu r
I I II II Opis te k stu ry BITMAPINFOHEADERbitmapInfoHeader; unsigned char*bitmapData; unsigned in t texture;
201
// nagłówek p lik u // dane te kstu ry // obiekt tekstury
Większość zaprezentowanych deklaracji jest już znana z poprzednich przykładów. Je dyną nowością jest zmienna te x tu re . Używana będzie jako identyfikator tekstury po jej załadowaniu do pamięci. Kolejnym fragmentem kodu jest funkcja L oad B itm a p F ile O , której działanie omówione zostało w poprzednim rozdziale. Parametrem funkcji LoadBi tmapFi 1e ( ) jest nazwa pliku oraz zmienna typu BITMAPINFOHEADER. Funkcja zwraca wskaźnik do bufora, w którym umieszczono dane obrazu wczytane z pliku. Przechowywane są one jako ciąg bajtów, czyli wartości typu unsigned char. unsigned char *LoadBitm apFile(char ^filename, BITMAPINFOHEADER *bitmapInfoHeader)
{ FILE * f i l e P t r ; BITMAPFILEHEADERbitmapFileHeader; unsigned char*bitmaplmage; intim ageldx = 0; unsigned chartempRGB;
// wskaźnik pozycji p lik u // nagłówek p lik u // dane obrazu // lic z n ik p ik s e li // zmienna zamiany składowych
// otw iera p lik w t ry b ie "read b in a ry " f i l e P t r = f open(filename, "r b ") ; i f ( f i l e P t r == NULL) return NULL; // wczytuje nagłówek p lik u fread(&bitm apFileHeader, sizeof(BITMAPFILEHEADER), 1, f ile P t r ) ; // sprawdza, czy je st to p lik formatu BMP i f (bitm apFileHeader.bfType != BITMAP_ID)
{ f c lo s e (f ile P t r ); return NULL;
} // wczytuje nagłówek obrazu fread(bitm apInfoHeader, sizeof(BITMAPINFOHEADER), 1. f ile P t r ) ; // ustawia wskaźnik pozycji p lik u na początku danych obrazu f s e e k ( f ile P t r , b itm apFileH ead er.bfO ffBits, SEEK_SET); // p rz y d zie la pamięć buforowi obrazu bitmaplmage = (unsigned ch ar*)m alloc(bitm apInfoH eader->biSizeIm age ); // sprawdza, czy udało s ię p rz y d z ie lić pamięć i f ( ! bitmaplmage)
{ free(bitm aplm age); fc lo s e (f ile P t r ); return NULL;
// wczytuje dane obrazu fread(bitmaplmage, 1, bitm apInfoHeader->biSizeIm age, f ile P t r ) ;
202
Część II ♦ Korzystanie z OpenGL
// sprawdza, czy dane z o sta ły wczytane i f (bitmaplmage == NULL)
{ f c lo s e (f ile P t r ); return NULL;
} // zamienia miejscami składowe R i B fo r (im ageldx = 0; imageldx < bitm apInfoHeader->biSizeIm age; imageIdx+=3)
{ tempRGB = bitm aplm age[im ageldx]; bitmaplmage[imageldx] = bitmaplmage[imageldx + 2]; bitmaplmage[imageldx + 2] = tempRGB;
} // zamyka p lik i zwraca wskaźnik bufora zawierającego wczytany obraz f c lo s e (f ile P t r ) ; return bitmaplmage;
} Kolejna funkcja rysuje sześcian. Od poprzednich wersji tej funkcji odróżnia ją to, że dla każdego z wierzchołków sześcianu definiuje współrzędne tekstury. Współrzędne tekstur omówione zostaną wkrótce, teraz należy przyjrzeć się implementacji funkcji DrawTextureCubeO: void DrawTextureCube(float xPos, flo a t yPos, f lo a t zPos)
{ g lP u s h M a t r ix O ; g lT ra n sla te f(x P o s, yPos, zPos); glBegin(GL_QUADS); // górna ściana glT e x C o o rd 2 f(0 .0 f, O.Of); g lV e rte x 3 f(-0 .5 f. 0.5f. 0 .5 f); glTexC oord2f(1.0f. O.Of); g lV e r t e x 3 f (0 .5 f , 0.5f, 0 .5 f); glT e x C o o rd 2 f(1 .0 f, l. O f ); g lV e r t e x 3 f (0 .5 f , 0.5f, -0 .5 f); g lT e x C o o rd 2 f(0 .0 f, l. O f ); g lV e r t e x 3 f (- 0 .5 f , 0.5f, -0 .5 f); g lE n d O ; g l Be gin (GL_QUADS); glTexC oord2f(0.0f, glT e x C o o rd 2 f(1 .0 f, glT e x C o o rd 2 f(1 .0 f, glT e x C o o rd 2 f(0 .0 f, g lE n d O ;
// O.Of); O.Of); l. O f ); l. O f );
przednia ściana g lV e r t e x 3 f (0 .5 f , -0 .5 f, 0 .5 f); g lV e r t e x 3 f (0 .5 f . 0.5f, 0 .5 f); g lV e r t e x 3 f (- 0 .5 f , 0.5f, 0 .5 f); g lV e r t e x 3 f (- 0 .5 f . -0 .5 f. 0 .5 f);
g l Be gin (GL_QUADS); // prawa ściana glTexC oord 2f(0.0f, O.Of); g lV e r t e x 3 f (0 .5 f . 0.5f, -0 .5 f); glT e x C o o rd 2 f(1 .0 f, O.Of); g lV e r t e x 3 f (0 .5 f , 0.5f. 0 .5 f); glT e x C o o rd 2 f(1 .0 f, l. O f ); g lV e r t e x 3 f (0 .5 f . -0 .5 f. 0 .5 f); glT e x C o o rd 2 f(0 .0 f. l. O f ); g lV e r t e x 3 f (0 .5 f . -0 .5 f. -0 .5 f); g lE n d O ; g l Be gin (GL_QUADS); glT e x C o o rd 2 f(0 .0 f, glT e x C o o rd 2 f(1 .0 f, glT e x C o o rd 2 f(1 .0 f, glT e x C o o rd 2 f(0 .0 f, g lE n d O ;
// O.Of); O.Of); l. O f ); l. O f );
lewa ściana g lV e r t e x 3 f (- 0 .5 f , g lV e r t e x 3 f (- 0 .5 f . g lV e r t e x 3 f (- 0 .5 f , g lV e r t e x 3 f (- 0 .5 f ,
-0 .5 f, 0 .5 f); 0.5f, 0 .5 f); 0.5f, -0 .5 f); -0 .5 f, -0 .5 f) ;
Rozdział 8 . ♦ Odwzorowania te k s tu r
203
g l B e gin (GL_QUADS); // dolna ściana glT exC oord 2f(0.0f, O.Of); g lV e r t e x 3 f (0 .5 f . -0 .5 f, 0 .5 f); glT exC oord 2f(1.0f, O.Of); g lV e r t e x 3 f (- 0 .5 f , -0 .5 f, 0 .5 f); glT exC oord2f(1.0f, l. O f ); g lV e rte x 3 f(-0 .5 f. -0 .5 f, -0 .5 f); g lT e x C o o rd 2 f(0 .0 f, l. O f ); g lV e r t e x 3 f (0 .5 f , -0 .5 f, -0 .5 f); g lE n d O ; gl B e gin (GL_QUADS); glTexC oord2fU ).0f, glT exC oord 2f(1.0f, glT exC oord 2f(1.0f, g lT e x C o o rd 2 f(0 .0 f, g lE n d O ;
// O.Of); O.Of); l. O f ); l. O f );
tyln a ściana g lV e r t e x 3 f (0 .5 f . g lV e r t e x 3 f (0 .5 f . g lV e rte x 3 f(-0 .5 f. g lV e rte x 3 f(-0 .5 f.
0.5f, -0 .5 f); -0 .5 f, -0 .5 f); -0 .5 f. -0 .5 f); 0.5f. -0 .5 f);
g lP o p M a t r ix O ;
} Zadaniem następnej funkcji jest inicjalizacja grafiki OpenGL oraz obsługi tekstur. Poni żej przedstawiony został pełen kod źródłowy funkcji In itia l iz e (): void I n i t i a l i z e ! )
{ g lC le a rC o lo r(O .O f, O.Of, O.Of, O.Of); // t ło w czarnym kolorze glShadeModel(GL_SMOOTH); gl Enable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glFrontFace(GL_CCW );
// // // // //
cieniow anie gła d kie usuwanie ukrytych powierzchni brak obliczeń dla niewidocznych stron wielokątów niewidoczne strony posiadają porządek wierzchołków przeciwny do kierunku ruchu wskazówek zegara
gl Enable(GL_TEXTURE_2D); // włącza te kstu ry dwuwymiarowe // ładuje obraz te k stu ry bitmapData = LoadBitm apFileC checker.bm p", &bitmapInfoHeader); glG e nT e xtu re s(l, &texture); glBindTexture(GL_TEXTURE_2D, texture );
// tworzy obiekt te kstu ry // aktywuje obiekt te kstu ry
glTexParam eteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FI LTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, G L JE A R E S T ); // tworzy obraz te k stu ry glTexImage2D(GL_TEXTURE_2D, O, GL_RGB, bitm apInfoHeader.biWidth, bitm apInfoH eader.biH eight, O, GL_RGB, GL_UNSIGNED_BYTE, bitmapData);
} Pierwszym wierszem kodu funkcji In itia liz e !), na który należy zwrócić uwagę, jest wiersz zawierający wywołanie funkcji gl Enable!) z parametrem GL TEXTURE 2D. Infor muje się w ten sposób maszynę OpenGL, że tworzone wielokąty będą pokrywane wzo rem tekstury. Stosowanie tekstur można wyłączyć wywołując funkcję gl Di sabl e ! ) z pa rametrem GL_TEXTURE_2D. Po załadowaniu obrazu tekstury z pliku BMP stosuje się funkcję glGenTextures!), aby nadać nazwę obiektowi tekstury. Funkcja glBindTexture!) wiąże obiekt tekstury z bie żącą teksturą, która będzie wykorzystywana podczas rysowania wielokątów. Aby sko rzystać w programie z wielu różnych tekstur, trzeba przy zmianie bieżącej tekstury za każdym razem wywoływać funkcję gl Bi ndTexture!).
204
Część II ♦ Korzystanie z OpenGL
Funkcja glTexP aram eteri () określa sposób filtrowania tekstury. Filtrowanie tekstur zo stanie omówione w dalszej części bieżącego rozdziału. Wzór tekstury reprezentowany za pomocą wczytanego wcześniej obrazu określa się za pomocą funkcji glTexIm age2D ( ). Za pomocą jej parametrów można określić także roz miary, typ, położenie i format danych. Funkcja R enderO będzie jak zwykle tworzyć kolejną klatkę animacji sześcianu wy konując odpowiednie przekształcenia i wywołując funkcję rysowania sześcianu DrawTextureC ubeO : void RenderO
{ // opróżnia bufor ekranu i bufor głębi glC l e a r(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n t it y O ; g lT ra n s la te f(O .O f, O.Of, - 3 . Of); g lR o ta te f(a n g le , l.O f. O.Of. O.Of); glR o ta te f(a n g le . O.Of, l.O f, O.Of); g lR o ta te f(a n g le , O.Of, O.Of, l. O f );
// wykonuje p rze k szta łce n ia // odsuwa sześcia n o 3 je dnostki i obraca go
DrawTextureCube(O.Of, O.Of, O.Of);
// rysu je sześcia n pokryty te kstu rą
i f (angle >= 360.Of) angle = O.Of; a n g le + = 0 .2 f; g lF lu s h O ; SwapBuffers(g_FIDC);
// przełącza bufory
I to wszystko! Po umieszczeniu przedstawionych fragmentów kodu w wykorzystywa nym dotąd szkielecie aplikacji systemu Windows uzyska się działającą animację sześcia nu pokrytego teksturą. Pokazuje ją rysunek 8.3. Rysunek 8.3. Animacja sześcianu pokrytego teksturą
Rozdział 8 . ♦ Odwzorowania te ks tu r
205
Po zapoznaniu się ze sposobem stosowania tekstur można przejść do bardziej szczegó łowego omówienia działania tekstur OpenGL.
Mapy tekstur Po załadowaniu z pliku obrazu tekstury należy zadeklarować go w OpenGL jako mapę tekstury. W zależności od liczby wymiarów tekstury używa się w tym celu innej funkcji. Dla mapy tekstury dwuwymiarowej będzie to funkcja g lT e x Im a g e 2 D ( ). Dla map tekstur jednowymiarowych użyć trzeba funkcji g lT e x I m a g e lD ( ), a dla map tekstur trójwymia rowych funkcji g lT e x Im a g e 3 D ( ). W kolejnych podrozdziałach omówiony zostanie sposób użycia tych funkcji.
Tekstury dwuwymiarowe Aby zdefiniować teksturę dwuwymiarową użyć można funkcji niowanej następująco:
g lT e x Im a g e 2 D ( )
zdefi
v o id glTexImage2D(GLenum target, G Lint le ve l, G Lint internalFormat, G Lsizei width. G Lsizei height, G L in t border, GLenum format, GLenum type, const GLvoid* texels );
Parametr target może przyjmować wartość GL_TEXTURE_2D lub GL_PR0XY_TEXTURE_2D. W aktualnych zastosowaniach będzie posiadać on zawsze wartość GL TEXTURE 2D. Pa rametr level określa rozdzielczość mapy tekstury. Ponieważ tekstury o różnych rozdzielczościach omówione zostaną później, to na razie parametrowi temu trzeba nadawać wartość 0, co oznacza tylko jedną rozdzielczość. Parametr i nternal Frame opisuje format tekseli, które przekazywane będą funkcji. Może przyjmować on wartości z przedziału od 1 do 4 bądź jednej z 32 stałych zdefiniowanych przez OpenGL. Zamiast przedstawiać wszystkie te stałe wymienić wystarczy tylko te, które będą najbardziej przydatne: G L_LU M INANCE, GL_LUMINANCE_ALPHA, GL_RGB i Gl_RGBA. Parametry width i height definiują szerokość i wysokość mapy tekstury. Ich wartości muszą być potęgą liczby 2. Parametr border określa, czy dookoła tekstury znajduje się ramka. Przyjmuje wtedy wartość 1, natomiast wartość 0 oznacza brak ramki. Parametr format definiuje format danych obrazu tekstury. Może on przyjmować jedną z następujących wartości: G L_C0L0R_IN DEX, GL_RGB, GL_RGBA, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_LUM I NANCE lub GL_LUMINANCE_ALPHA. Parametr type definiuje typ danych obrazu tekstury. Może on przyjmować jedną z na stępujących wartości: GL_BYTE, GL_UNSIGNED_BYTE, GLJHORT, GL_UNSIGNED_SHORT, G L J N T , G L_UNSIG NED_INT, G L J L O A T lub GL_BITMAP. Ostatni z parametrów funkcji, texels, jest wskaźnikiem właściwych danych obrazu tek stury, które zostały wygenerowane za pomocą pewnego algorytmu lub załadowane z pliku.
206
Część II ♦ Korzystanie z OpenGL
Można przypuścić, że załadowany został obraz formatu RGB A o szerokości tex tu reWidth i wysokości textureHeight do bufora wskazywanego przez zmienną te x tureData. Funkcję glTexImage2D() wywoływać się będzie wtedy w następujący sposób: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, te x tu re W id th , te x tu re H e ig h t, 0, GL_RGBA, GL_UNSIGNED_BYTE, te x tu re D a ta ):
Po wywołaniu funkcji glTexImage2D() tekstura jest gotowa do użycia.
Tekstury jednowymiarowe Tekstury jednowymiarowe odpowiadają teksturom dwuwymiarowym, których wyso kość jest równa 1. Tekstury jednowymiarowe wykorzystywane są często do tworzenia kolorowych obwódek bądź przez algorytmy cieniowania, które wymagają tworzenia zbyt dużej liczby wielokątów. Teksturę jednowymiarową tworzy się za pomocą funkcji glTexImagelD() posiadającej następujący prototyp: vo id glTexImagelD(GLenum t a r g e t , G L in t le v e l, G L in t in t e r n a lF o r m a t , G Lsizei w id th , G L in t b o rd e r. GLenum format, GLenum type, co n st GLvoid* t e x e l s );
Wszystkie parametry funkcji glTexImagelD() są takie same jak w przypadku funkcji gl TexImage2D(). Jedyna różnica polega na braku parametru reprezentującego wysokość tekstury. Tablica texels jest w przypadku funkcji glTexImagelD() jednowymiarowa, a nie dwuwymiarowa jak dla funkcji glTexImage2D(). Parametr target posiadać będzie w analizowanych zastosowaniach zawsze wartość GL_TEXTURE_1D. Poniżej zaprezentowany został krótki fragment kodu ilustrujący sposób wykorzystania funkcji glTexImagelD(): unsigned char im age D a ta [1 2 8 ]; ęlTexImagelD(GL_TEXTURE_lD. 0, GL_RGBA, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, im ageData);
Tekstury trójwymiarowe Tekstury trójwymiarowe pozwalają uzyskać niezwykłe efekty, jednak ich zastosowanie pochłania ogromne ilości pamięci nawet dla najmniejszych tekstur. Dlatego też dotych czas tekstury trójwymiarowe wykorzystywane są szerzej jedynie w zastosowaniach me dycznych, takich jak na przykład rezonans magnetyczny. Powszechne zastosowanie tekstur trójwymiarowych w grach jest jeszcze kwestią przyszłości. Do tworzenia tekstur trójwymiarowych używa się funkcji glTexImage3D() o następują cym prototypie: g1TexImage3D(GLenum ta r g e t , G Lint le v e l, G L in t in t e r n a lF o r m a t , G Lsizei w id th , G Lsizei h e ig h t, G Lsizei depth, G Lint border, GLenum format, GLenum type, const GLvoid* t e x e ls );
Jej parametry są takie same jak w przypadku funkcji glTexImagelD() i glTex!mage2D(). Jedyna różnica polega na dodaniu parametru depth reprezentującego trzeci wymiar tek stury. Oczywiście tym razem parametr texels będzie wskazywał tablicę trójwymiarową.
Rozdział 8 . ♦ Odwzorowania tekstu r
207
Poniżej przedstawiony został krótki fragment kodu ilustrujący sposób korzystania z funk cji glTexImage3D() w programach: unsigned char im a g e D a ta [1 6 ][1 6 ][1 6 ][3 ]; glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, 16, 16, 16, 0, GL RGB, GL_UNSIGNED_BYTE, imageData);
Obiekty tekstur Obiekty tekstur służą do przechowywania gotowych do użycia tekstur. Umożliwiają za ładowanie do pamięci wielu obrazów tekstur, a następnie odwoływanie się do nich pod czas rysowania sceny. Rozwiązanie takie zwiększa efektywność tworzenia grafiki, ponie waż tekstury nie muszą być ładowane za każdym razem, gdy konieczne jest ich użycie.
Tworzenie nazwy tekstury Zanim użyty zostanie obiekt tekstury, trzeba najpierw utworzyć jego nazwę. Nazwy tekstur są liczbami dodatnimi całkowitymi różnymi od zera. Użycie funkcji g lG e n T e x tu res() do tworzenia nazw pozwala zachować pewność, że istniejąca już nazwa nie zo stanie zduplikowana: v o id g lG enT e xtu re s(G L size i n, G Luint *textureNames) ;
Parametr n określa liczbę nazw tekstur, które powinny zostać utworzone i umieszczone w tablicy textureNames. Na przykład poniższy fragment kodu utworzy trzy nowe nazwy tekstur: unsigned i n t te x tu re N a m e s [3 ]; g lG e n T e xtu re s(3 , textureN am es);
Tworzenie i stosowanie obiektów tekstur Po utworzeniu nazwy tekstury należy związać ją z danymi opisującymi teksturę. Służy do tego funkcja gl BindTexture(): v o id glB indTexture(G Lenum target, G Luint textureName) ;
Pierwsze wywołanie tej funkcji powoduje utworzenie nowego obiektu tekstury o domyśl nych właściwościach. Właściwości te można zmienić wywołując odpowiednie funkcje OpenGL związane z teksturami. Parametr target funkcji glBindTexture() może przyj mować wartości GL_TEXTURE_1D, GL_TEXTURE_2D lub GL_TEXTURE_3D. Po związaniu obiektu tekstury z danymi można ponownie użyć funkcji glBindTextureC) do zmiany właściwości obiektu tekstury. Można założyć, że w programie utworzono wiele obiektów tekstu i powiązano je z danymi tekstur. Przy tworzeniu grafiki infor muje się maszynę OpenGL za pomocą funkcji gl BindTexture(), której tekstury należy użyć. Poniższy fragment kodu informuje OpenGL, że należy użyć drugiej ze zdefinio wanych tekstur:
208
Część II ♦ Korzystanie z OpenGL
unsigned in t te x tu re N a m e s [3 ]; glG en T extu re s(3 . te xtu re N a m e s); glBindTexture(GL_TEXTURE_2D. te x tu re N a m e s [l]); / / kod o k re ś la ją c y dane i w ła ściw o ści te k s tu ry glBindTexture(GL_TEXTURE_2D, textureN am es[ 1 ] ) ; / / ry s u je o b ie k ty
Filtrowanie tekstur Ponieważ obraz tekstury ulega naturalnemu zniekształceniu podczas wykonywania przekształceń na wielokątach, to w efekcie pojedynczy piksel może reprezentować na ekranie wiele tekseli mapy tekstury. Filtrowanie tekstur umożliwia określenie sposobu, w jaki OpenGL wyznacza w takim przypadku reprezentację tekseli. Termin powiększenie stosowany w odniesieniu do filtrowania tekstur oznacza sytuację, w której piksel reprezentuje jedynie część pojedynczego teksela. Natomiast pomniejsze nie oznacza, że pojedynczy piksel reprezentuje wiele tekseli. Sposób, w jaki OpenGL ob sługuje obie sytuacje ustala się korzystając z funkcji glTexP aram eteri (): vo id g lT e x P a ra m e te ri(GLenum target, GLenum pname, G L in t param):
Parametr pa ram reprezentuje rodzaj używanych tekstur i może przyjmować wartości GL_TEXTURE_1D, GL_TEXTURE_2D lub GL_TEXTURE_3D. Parametr pname pozwala określić, czy definiuje się obsługę powiększania czy pomniejszania i może przyjmować wartości od powiednio GL_TEXTURE_MAG_FI LTER lub GL_TEXTURE_MI N_FI LTER. Tabela 8.1 prezentuje możliwe wartości parametru param. Tabela 8.1. Wartości określające sposób filtrowania tekstur Wartość
Opis
GLJEAREST
Używa teksela położonego najbliżej środka rysowanego piksela
GL_LINEAR
Stosuje liniową interpolację (średnią ważoną) czterech tekseli położonych najbliżej środka rysowanego piksela
GL_ NEAREST_MIPMAP_NEAREST
Używa obrazu o najbardziej zbliżonej rozdzielczości do rozdzielczości wielokąta oraz filtr GL NEAREST
GL_ NEAREST_MI PMAP_LINEAR
Używa obrazu o najbardziej zbliżonej rozdzielczości do rozdzielczości wielokąta oraz filtr GL_LINEAR
GL_ LI NEAR_MI PMAP_N EAREST
Stosuje liniową interpolację dwóch mipmap o najbardziej zbliżonej rozdzielczości do rozdzielczości wielokąta oraz używa filtr GL NEAREST
GL_ LI NEAR_MI PMAP_LI NEAR
Stosuje liniową interpolację dwóch mipmap o najbardziej zbliżonej rozdzielczości do rozdzielczości wielokąta oraz używa filtr GL_LINEAR
Filtry wykorzystujące mipmapy można stosować jedynie dla parametru GL_TEXTURE_MIN FI LTER. Mipmapy oraz poziomy szczegółowości omówione zostaną wkrótce.
Rozdział 8 . ♦ Odwzorowania te ks tu r
209
Poniżej przedstawiony został przykład konfiguracji filtrów powiększania i pomniejsza nia za pomocą funkcji glTexP aram eteri O : g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FI LTER, GL_LINEAR); g lT e x P a ra m e te ri(GL_TEXTURE_2D. GL_TEXTURE_MI N_FILTER, GL_LINEAR);
Funkcje tekstur Podobnie jak w przypadku łączenia kolorów OpenGL pozwala także definiować sposób zachowania kolorów tekstury za pomocą funkcji tekstur. Dla każdej tekstury można wybrać jedną z czterech dostępnych funkcji wywołując funkcję glTexEnvi O : v o id glTexE nvi (GLenum target, GLenum pname, G L in t param)-,
Parametr target musi posiadać wartość GL_TEXTURE_ENV, a parametr pname wartość GL_ TEXTURE ENV, która informuje maszynę OpenGL, że określany jest sposób łączenia kolo rów tekstury z pikselami istniejącymi w buforze ekranu. Parametr param może przyj mować jedną z wartości podanych w tabeli 8.2 . * Tabela 8.2. Funkcje tekstur Funkcja
Opis
GL_BLEND
Kolor tekstury mnożony jest przez kolor piksela i łączony ze stałym kolorem
GL_DECAL
Kolor tekstury zastępuje kolor piksela
GL_MODULATE
Kolor tekstury mnożony jest przez kolor piksela
Domyślną funkcją jest GLMODULATE. Poniższe wywołanie funkcji glG etErw i () zastępuje ją funkcją GL_DECAL: glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
Współrzędne tekstury Tworząc scenę należy określić współrzędne tekstury dla wszystkich wierzchołków. Współ rzędne tekstury pozwalają ustalić położenie tekseli na rysowanym obiekcie. Współrzędne (0, 0) określają lewy, dolny narożnik tekstury, a współrzędne ( 1 , 1 ) prawy, górny na rożnik. Podczas rysowania wielokąta trzeba określić współrzędne tekstury dla każdego z jego wierzchołków. W przypadku tekstur dwuwymiarowych mają one postać (s, /), gdzie 5 i t przyjmują wartości z przedziału od 0 do 1. Rysunek 8.4 pokazuje współrzędne tekstury dla wszystkich wierzchołków wielokąta. Współrzędne te muszą być zawsze określone przed narysowaniem danego wierzchołka.
210
Część II ♦ Korzystanie z OpenGL
Rysunek 8.4. Współrzędne tekstury dla wierzchołków wielokąta
(0.0, 1.0)
(1.0, 1.0)
( 0 .0 , 0 . 0 )
( 1 . 0 , 0 .0 )
Sposób interpolacji współrzędnych tekstury przez OpenGL przedstawiony zostanie na przykładzie hipotetycznego wierzchołka umieszczonego dokładnie w środku wielokąta. Ponieważ współrzędne lewego, dolnego wierzchołka wynoszą (0, 0), a prawego, górnego (1, 1), to współrzędne hipotetycznego wierzchołka OpenGL określi jako (0.5, 0.5). OpenGL wyznacza automatycznie współrzędne tekstury wewnątrz wielokąta, a progra mista musi określić je jedynie dla wierzchołków. Współrzędne tekstury określa się za pomocą funkcji glTexC oord2f( ). Posiada ona szereg wersji, ale w analizowanych zastosowaniach przydatne będąjedynie: v o id g lT e x C o o rd 2 f(flo a t s, f l o a t d): v o id g lT e x C o o rd 2 fv (flo a t *coords):
Funkcje te pozwalają określić bieżące współrzędne tekstury. Przy tworzeniu wierzchołka za pomocą funkcji gl V e rte x *( ) otrzymuje on współrzędne tekstury ustalone za pomocą funkcji glT exC o ord2f( ). W ostatnim z przykładów programów funkcja glT exC oord2f( ) użyta została w następu jący sposób: gl Begi n(GL_QUADS); g lT e x C o o rd 2 f(0 .0 f. g lT e x C o o rd 2 f(1 .0 f. g lT e x C o o r d 2 f(l,0 f, g lT e x C o o rd 2 f(0 ,0 f, g lE n d O ;
O .O f); O .O f): l. O f ) ; l. O f ) ;
g lV e r t e x 3 f ( - 0 . 5 f. 0 .5 f, g lV e r te x 3 f( 0 ,5 f, 0 .5 f, g lV e r te x 3 f( 0 .5 f. 0 .5 f, g lV e r te x 3 f( - 0 .5 f, 0 .5 f,
0 .5 f ) ; 0 .5 f); -0 .5 f); -0 .5 f);
// // // //
lew y, d o ln y prawy, d o ln y prawy, górny lewy górny
Choć zwykle współrzędne tekstury przyjmują wartości z przedziału od 0 do 1, to mogą także otrzymywać wartości spoza tego przedziału. W rezultacie otrzymuje się efekty po wtarzania lub rozciągania tekstury. Należy przyjrzeć się im bliżej.
Powtarzanie i rozciąganie tekstur Jeśli współrzędne tekstury będą posiadać wartości spoza przedziału od 0 do 1, to OpenGL może powtarzać lub rozciągać teksturę. Powtarzanie tekstury określane jest czasami także mianem kafelkowania. Jeśli współrzędne tekstury będą posiadać wartość (2 .0 , 2.0), to tekstura zostanie powtórzona czterokrotnie, co pokazuje rysunek 8.5. Jeśli współrzędne tekstury będą posiadać wartość ( 1 . 0, 2 . 0), to tekstura zostanie powtórzona dwukrotnie tylko w kierunku t.
Rozdział 8 . ♦ Odwzorowania te ks tu r
211
Rysunek 8.5. Powtórzenie tekstury
Rozciąganie tekstury polega na nadaniu współrzędnym tekstury większym od 1.0 war tości 1.0, a współrzędnym mniejszym od 0.0 wartości 0.0. Rysunek 8.6 pokazuje wynik rozciągnięcia tekstury o współrzędnych (2.0,2.0). W rezultacie obraz tekstury pojawia się w lewym, dolnym narożniku wielokąta. Rysunek 8 .6 . Rozciąganie tekstury
Powtarzanie i rozciąganie tekstur wykonuje się w OpenGL za pomocą funkcji glTexPara m e te ri O , która została omówiona przy okazji filtrowania tekstur. Tym razem jednak parametrowi ta r g e t nadana została wartość GL_TEXTURE_WRAP_S i GL_TEXTURE_WRAP_T w celu wyboru współrzędnych 5 i t. Dla danej współrzędnej określa się operację powta rzania lub rozciągania tekstury za pomocą wartości GL_REPEAT, GL_CLAMP lub GL_CLAMP_ T0_EDGE (wartość GL_CLAMP_TO_EDGE stosowana jest jedynie w przypadku, gdy tekstura posiada ramkę, a programista chce, aby OpenGL zignorował ją podczas rozciągania tekstury).
212
Część II ♦ Korzystanie z OpenGL
Poniżej zaprezentowany został fragment kodu informującego OpenGL, aby powtórzył teksturę w obu kierunkach. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
OpenGL może równocześnie powtarzać teksturę w jednym kierunku i rozciągać ją w dru gim kierunku. Oto fragment kodu, który rozciąga teksturę w kierunku s i powtarza w kie runku t: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
Mipmapy i poziomy szczegółowości Podczas rysowania obiektów pokrytych teksturami można zauważyć pewne obszary, w których pojawiają się nieoczekiwane wzory, zwłaszcza przy oddalaniu się lub zbliżaniu obiektów. Są one rezultatem filtrowania zastosowanego przez OpenGL w celu dopaso wania tekstury do rozmiarów obiektu. Niepożądane efekty można wyeliminować za po mocą mipmap, które pozwalają kontrolować poziom szczegółowości tekstury (patrz: ry sunek 8.7). Rysunek 8.7. Mipmapy pozwalają kontrolować poziom szczegółowości tekstury 128x128
OpenGL dobiera odpowiednią mipmapę na podstawie bieżącej rozdzielczości obiektu. Gdy obiekt znajduje się bliżej obserwatora, stosowana jest mipmapa zawierająca więcej szczegółów. Gdy obiekt oddala się od obserwatora wybierane są mipmapy o coraz mniej szym poziomie szczegółowości. Mipmapy definiuje się w ten sam sposób co tekstury, czyli za pomocą funkcji glTexImage2D(). Jedyna różnica polega na określeniu stopnia szczegółowości mipmapy za pomo cą parametru 1evel. Definiując zwykłą teksturę parametrowi 1evel nadaje się wartość 0. W przypadku mipmap wartość 0 oznacza mipmapę o największym poziomie szczegó łowości. Wartość 1 oznacza rozdzielczość dwukrotnie mniejszą niż dla poziomu 0, war tość 2 czterokrotnie mniejszą i tak dalej. Mipmapy muszą posiadać rozmiary będące potęgą liczby 2 (począwszy od największej mipmapy aż do mipmapy o rozmiarach 1 x 1 ). Jeśli na przykład mipmapa o największej rozdzielczości posiada rozmiary 64x64, to trzeba utworzyć także mipmapy 32x32, 16x16, 8x 8, 4x4, 2x2 i 1x1. Definiując mipmapę 64x64 za pomocą funkcji glTexIm age2D() parametrowi le v e l nadaje się wartość 0, a definiując mipmapę 32x32 wartość 1 i tak dalej. Ilustruje to poniższy fragment kodu:
Rozdział 8 . ♦ Odwzorowania te k s tu r
glTexP aram eteri (GL_TEXTURE_2D, glTexP aram eteri (GL_TEXTURE_2D, glTexImage2D(GL _TEXTURE_2D, 0, glTexImage2D(GL _TEXTURE_2D. glTexImage2D(GL _TEXTURE_2D, glTexImage2D(GL _TEXTURE_2D. glTexImage2D(GL _TEXTURE_2D, glTexImage2D(GL _TEXTURE_2D. glTexImage2D(GL TEXTURE 2D,
213
GL_TEXTURE MAGJILTER, GL_LINEAR); GL_TEXTURE MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); GL_RGB, 64, 64, 0, GL_RGB, GL_UNSIGNED_BYTE, texImageO) GL_RGB, 32, 32. 0, GL_RGB, GL_UNSIGNED_BYTE, te xlm ag e l) GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, texlm age2) GL_RGB, 8 , 8, 0. GL_RGB, GL_UNSIGNED_BYTE, texlm age3) GL_RGB, 4, 4, 0, GL_RGB, GL_UNSIGNED_BYTE. texlm age4) GL_RGB, 2 , 2, 0, GL_RGB, GL_UNSIGNED_BYTE, texlm age5) GL RGB, 1 . 1 , 0 . GL_RGB, GL_UNSIGNED_BYTE, texlm age6)
Mipmapy można stosować jedynie pod warunkiem, że wybrany został odpowiedni filtr pomniejszania (prezentuje je tabela 8.3). Tabela 8.3. Filtry wykorzystujące mipmapy Filtr GL
Opis NEAREST MIPMAP NEAREST
Używa obrazu o najbardziej zbliżonej rozdzielczości do rozdzielczości wielokąta oraz filtr GL NEAREST
GL NEAREST MIPMAP LINEAR
Używa obrazu o najbardziej zbliżonej rozdzielczości do rozdzielczości wielokąta oraz filtr GL_LINEAR
GL LINEAR MIPMAP NEAREST
Stosuje liniową interpolację dwóch mipmap o najbardziej zbliżonej rozdzielczości do rozdzielczości wielokąta oraz używa filtra GL NEAREST
GL LINEAR MIPMAP LINEAR
Stosuje liniową interpolację dwóch mipmap o najbardziej zbliżonej rozdzielczości do rozdzielczości wielokąta oraz używa filtra GL_LINEAR.
Automatyczne tworzenie mipmap Biblioteka GLU udostępnia zestaw funkcji pozwalający zautomatyzować tworzenie mi pmap. W przypadku tekstur dwuwymiarowych wiele wywołań funkcji glTexImage2D() można zastąpić pojedynczym wywołaniem funkcji gluBui 1d2dMi pmaps() o następują cym prototypie: i n t gluBuild2DMipmaps(GLenum target, G L in t internalFormat, G L in t width, G Lint height, GLenum format, GLenum type, v o id *texels);
Funkcja ta tworzy sekwencję mipmap, a sama wywołuje funkcję glTexImage2D(). Trzeba przekazać jej jedynie mipmapę o największej rozdzielczości, a pozostałe zostaną wyge nerowane automatycznie. Poprzedni przykład po zastosowaniu funkcji gl uBui 1d2DM1 pmaps () będzie wyglądał na stępująco: g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FI LTER, GL_LINEAR); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MI N_FI LTER, GL_NEAREST_MI PMAP_LI NEAR); glBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, 64, 64, GL_RGB, GL_UNSIGNED_BYTE, texImageO);
Animacja powiewającej flagi W podrozdziale tym omówiony zostanie krok po kroku przykład programu wyświetla jącego animację powiewającej flagi Stanów Zjednoczonych.
214
Część II ♦ Korzystanie z OpenGL
Objaśnienia W przykładzie tym wykorzystana zostanie tekstura reprezentująca flagę Stanów Zjed noczonych. Tekstura ta posiada rozmiary 128x64 i zapisana jest w pliku BMP stosują cym 24-bitowy opis kolorów. Zostanie ona nałożona na zbiór czworokątów tworzących prostokątna strukturę o wymiarach 35x20 czworokątów. Takie rozmiary pozwolą uzy skać realistyczny wizerunek flagi. Aby stworzyć efekt powiewania flagi, trzeba będzie zmieniać współrzędne tekstury dla wszystkich wierzchołków tworzących strukturę flagi pokazaną na rysunku 8.8. Rysunek 8.8. Tekstura flagi pokrywa siatką czworokątów o wymiarach 35x20
W jaki sposób uzyskać efekt powiewania? Wykorzystać należy funkcję trygonometrycz ną sin() do zainicjowania pozycji wierzchołków na powiewającej fladze. Tworząc ko lejne klatki, należy przemieszczać powstałą „falę” wzdłuż flagi. Flagę trzeba umieścić na płaszczyźnie xy, wobec czego zmiany współrzędnych wierz chołków będą dotyczyć jedynie współrzędnej z. Rysunek 8.9 ilustruje działanie efektu powiewania. Rysunek 8.9. Flaga powiewająca wzdłuż osi z
Implementacja Zamiast prezentować w tym miejscu kompletny, obszerny kod źródłowy programu omó wione zostaną krok po kroku jego najważniejsze fragmenty. # d e fin e BITMAP_ID 0x4D42 / / / / / / P lik i nagłówkowe fin c lu d e # in c lu d e < s td io .h > # in c lu d e < s td lib .h > # in c lu d e
/ / id e n t y f ik a t o r form atu BMP
/ / standardowy p lik nagłówkowy Windows / / p lik nagłówkowy o p e ra c ji w e jś c ia i w y jś c ia
Rozdział 8 . ♦ Odwzorowania te k s tu r
# in c lu d e < g l/g l.h > # in c lu d e < g l/g lu .h > / / / / / / Zmienne g lo b a ln e HDC g_HDC; bool f u l l Screen = fa ls e ; bool k e yP re s s e d [2 5 6 ]; / / / / / / Opis te k s tu r y BITMAPINFOHEADER b itm apInfoH eader; unsigned c h a r* bitm apD ata; unsigned i n t te x tu re ; / / / / / / Opis f la g i f l o a t f la g P o in t s [ 3 6 ][ 2 0 ][ 3 ]; f l o a t w rapV alue;
215
/ / standardowy p lik nagłówkowy OpenGL / / fu n k c je pomocnicze OpenGL
// // // //
g lo b a ln y k o n te k st urządzenia tru e = tr y b pełnoekranowy; fa ls e = tr y b okienkowy ta b lic a p rz y c iś n ię ć k la w is z y
/ / nagłówek p lik u / / dane te k s tu ry / / o b ie k t te k s tu r y / / fla g a 36x20 (w punktach) / / przenosi fa lę z końca na początek fla g i
Powyższy fragment prezentuje wykorzystywane pliki nagłówkowe i zmienne globalne. Większość z nich znanych jest już z poprzednich przykładów. Dodatkowo dołączony został plik nagłówkowy mathh ze względu na użycie funkcji sin(). Dane flagi można przechowywać w dwuwymiarowej tablicy punktów o rozmiarach 36x20. Punkty te tworzyć będą 35 czworokątów w kierunku osi x i 19 czworokątów w kierunku osi y. Każdy z nich otrzyma odpowiednie współrzędne tekstury, aby flaga była wyświetlana we właściwy sposób. Zmiennej wrapValue należy używać do przeniesienia danych opisujących falę na fladze z końca na początek flagi. Dzięki temu w trakcie trwania animacji nie będzie konieczne wielokrotne obliczanie danych fali. Kolejny fragment kodu prezentuje funkcję Initial izeFl ag(), która inicjuje punkty flagi za pomocą funkcji si n (): v o id I n i t i a l i z e F la g ()
{ in t x ld x ; / / lic z n ik w p ła s zc z yź n ie x i n t y ld x ; / / lic z n ik w p ła s zc z yź n ie y f l o a t sinTemp; / / Przegląda w s z y s tk ie punkty f la g i w p ę tli / / i wyznacza w a rto ść fu n k c ji s in d la w spółrzędnej z. / / W spółrzędne x i y o trzym u ją w artość odpowiedniego lic z n ik a p ę t l i , f o r ( x ld x = 0; x ld x < 36; xldx+ + )
{ fo r ( y ld x = 0; y ld x < 20; yld x+ + )
{ fla g P o in t s [ x I d x ] [y I d x ] [0 ] = ( f lo a t ) x ld x ; fla g P o in ts [ x l d x ] [ y l d x ] [1 ] = ( f lo a t ) y ld x ; sinTemp = ( ( f lo a t ) x ld x * 2 0 . 0 f ) / 3 6 0 .Of) * 2 .Of * P I; f la g P o in t s [ x I d x ] [y I d x ] [2 ] = ( flo a t)s in ( s in T e m p ) ;
216
Część II ♦ Korzystanie z OpenGL
Sposób działania tej funkcji jest bardzo prosty. Przegląda ona tablicę punktów tworzących flagę i wyznacza wartość funkcji s i n ( ) dla współrzędnej jc każdego punktu. Kształt po wiewającej flagi można zmienić modyfikując sposób wyliczania wartości zmiennej sinTemp. Po wykonaniu funkcji uzyskuje się zbiór punktów wyglądający jak na rysunku 8.10. Rysunek 8.10. Wykonanie funkcji InitializeFlag() powoduje ułożenie punktów we wzór fali rozchodzącej się wzdłuż osi x
Funkcja Initial i ze() służy jak zwykle do zainicjowania grafiki OpenGL: v o id I n i t i a l i z e O
{
g lC le a r C o lo r ( 0 .0 f. O.Of, O.Of, O .O f); / / t ł o w czarnym k o lo rz e glEnable(GL_DEPTH_TEST); / / usuwanie u k ry ty c h p ow ierzchni glEnable(GL_CULL_FACE); / / brak o b lic z e ń d la niew idocznych s tro n w ie lo k ą tó w glFrontFace(GL_CCW ); / / niew idoczne s tro n y p o s ia d a ją porządek w ie rzch o łkó w / / przeciw ny do k ie ru n ku ruchu wskazówek zegara g l E nable(GL_TEXTURE_2D);
/ / w łącza te k s tu ry dwuwymiarowe
/ / ła d u je obraz te k s tu ry bitmapData = L o a d B itm a p F ile C u s fla g .b m p ", & bitm a p In fo F le ad e r); g lG e n T e x tu re s (l, & te x tu re ); glBindTexture(GL_TEXTURE_2D, te x tu r e ) ;
/ / tw o rzy o b ie k t te k s tu r y It a ktyw uje o b ie k t te k s tu r y
g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FI LTER, GL_NEAREST); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GLJIEAREST); / / tw o rzy obraz te k s tu ry glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, b itm a p In fo H e a d e r.b iW id th , b itm a p In fo H e a d e r.b iH e ig h t, 0, GL_RGB, GL_UNSIGNED_BYTE, b itm a p D ata ); I n i t i a l i z e F la g ();
} Funkcja ta wygląda znajomo. Do załadowania obrazu tekstury z pliku BMP wykorzy stuje się funkcję LoadBitmapFi l e ( ), która została zaimplementowana w poprzednim roz dziale.
Rozdział 8 . ♦ Odwzorowania te k s tu r
217
Po załadowaniu zawartości pliku BMP tworzy się obiekt tekstury za pomocą funkcji gl G enTextures( ) i aktywuje się go wywołując funkcję g lB in d T e x tu re ( ). Dane opisujące tek sturę dostarczyć można obiektowi tekstury za pośrednictwem funkcji glTexIm age2D( ). Kolejna funkcja, DrawFl a g ( ), tworzy obraz flagi dla pojedynczej klatki animacji: v o id DrawFlagO
{ i n t x ld x ; in t y ld x ; f l o a t te x L e ft; f l o a t texB ottom f l o a t texTop; f l o a t te x R ig h t;
// // // // // //
g lP u s h M a trix (); glBindTexture(GL_TEXTURE_2D, te x tu r e ) ; glBegin(GL_QUADS);
/ / w ybiera o b ie k t te k s tu ry / / ry s u je czw orokąty
lic z n ik w k ie ru n k u osi x lic z n ik w k ie ru n ku o si y lewa współrzędna te k s tu ry dolna w spółrzędna te k s tu ry górna współrzędna te k s tu ry prawa w spółrzędna te k s tu ry
/ / przegląda punkty w p ę t l i z w yją tkie m dwóch o s ta tn ic h w każdym k ie ru n k u , / / ponieważ używane są je d y n ie do rysowania o s ta tn ic h czworokątów fo r ( x ld x = 0; x ld x < 36; xldx+ + ) / / k ie ru n e k osi x
{
f o r ( y ld x = 0; y ld x < 18; y ld x+ + )
{
/ / kie ru n e k osi y
/ / wyznacza w spółrzędne te k s tu ry d la bieżącego czworokąta te x L e ft = flo a t ( x ld x ) / 3 5 .Of; / / lewa w spółrzędna te k s tu ry texB ottom = f lo a t ( y ld x ) / 1 8 .Of; / / dolna w spółrzędna te k s tu ry te x R ig h t = f lo a t( x I d x + l) / 3 5 .Of; / / prawa w spółrzędna te k s tu ry texTop = flo a t ( y I d x + l) / 1 8 .Of; / / górna w spółrzędna te k s tu ry / / lewy d o ln y w ie rz c h o łe k glT exC oord2f( te x L e ft, te x B o tto m ); g lV e rte x 3 f( fla g P o in t s [ x I d x ] [ y I d x ] [ 0 ] , f 1a g P o in ts [ x ld x ] [ y l d x ] [13, f 1a g P o in ts [ x ld x ] [ y l d x ] [2 ] ); / / prawy d o ln y w ie rz c h o łe k glT exC oord2f( te x R ig h t, texB ottom ); gl V e rte x 3 f( fla g P o in ts [ x ld x + l ] [ y ld x ] [0 ] , f l a g P o in t s [ x l d x + l] [ y l d x ] [ l] , fla g P o in t s [ x I d x + l] [y Id x ][ 2 ] ); / / prawy górny w ie rz c h o łe k g lT e xC o ord 2 f( te x R ig h t, texTop ); g lV e rte x 3 f( f la g P o in t s [ x ld x + l] [ y ld x + l] [ 0 ] , f 1a g P o in ts [x ld x + l][y Id x + 1 ][1 ], f la g P o in t s [ x I d x + l] [y Id x + l][ 2 ] ); / / lewy górny w ie rz c h o łe k g lT exC oord2f( te x L e ft, texTop ); gl V e rte x 3 f( f l a g P o in ts [ x ld x ] [ y ld x + l ] [ 0 ] , fla g P o in ts [ x ld x ] [ y l d x + l ] [ l ] , fla g P o in t s [ x I d x ] [y I d x + l][ 2 ] );
} }
g ł End( ) ;
/* Udaje ruch f l a g i: w każdym w ie rszu przesuwa w spółrzędne z o jeden punkt w prawo.
*/
218
Część II ♦ Korzystanie z OpenGL
fo r ( y ld x = 0; y ld x < 19; yld x+ + )
/ / p ę tla w p ła s z c z y ź n ie y
{ / / zapam iętuje w spółrzędną z skra jn e g o , prawego punktu f la g i wrapValue = f la g P o in t s [ 3 5 ][ y I d x ] [2 ]; fo r ( x ld x = 35; x ld x >= 0; x l d x - - ) / / p ę tla w p ła s z c z y ź n ie y
{ / / bie żą cy punkt o trzym u je w spółrzędną z poprzedniego punktu fla g P o i n t s [ x ld x ] [ y ld x ] [2 ] = f l a g P o in t s [ x I d x - l] [ y I d x ] [ 2 ] ;
} / / s k ra jn y , lewy punkt o trzym u je w artość przechowaną w wrapValue f 1agPoi n t s [ 0 ] [ y ld x ] [2 ] = wrapValue;
} g lP o p M a trix ();
} Komentarze w kodzie funkcji DrawFlagO pomagają zrozumieć jej działanie. Pierwszą operacją wykonywaną przez funkcję jest wybranie tekstury flagi jako bieżącej tekstury, która używana będzie podczas tworzenia grafiki. Następnie za pomocą wartości GL QUADS informuje się maszynę OpenGL, że będą rysowane czworokąty. Rysowanie flagi rozpoczyna się od jej lewego, dolnego narożnika i przegląda wierz chołki w taki sposób, by możliwe było narysowanie kolejnego czworokąta. Zanim jed nak narysowany zostanie czworokąt, trzeba najpierw wyznaczyć współrzędne tekstury dla jego wierzchołków w odniesieniu do całości flagi tak, by została ona prawidłowo pokryta teksturą. Współrzędne tekstury oblicza się dzieląc współrzędne jc i y danego wierzchołka przez liczbę punktów flagi wzdłuż odpowiedniej osi. Po wyznaczeniu współrzędnych tekstury można narysować czworokąt za pomocą funkcji g lT e x C o o rd 2 f( ) i g l V e r te x 3 f( ). Wierzchołki czworokąta definiuje się w porządku prze ciwnym do kierunku ruchu wskazówek zegara. Po narysowaniu całej flagi trzeba zmodyfikować jej dane, aby uzyskać efekt fali bie gnącej wzdłuż flagi. W tym celu przegląda się punkty flagi wierszami i przesuwa współ rzędne z każdego punktu do jego sąsiada po prawej. Najpierw zapamiętuje się jednak w zmiennej wrapValue wartość współrzędnej z dla skrajnego punktu po prawej stronie flagi. Wartość tę otrzymuje następnie skrajny punkt po lewej stronie flagi, co pozwala uzyskać efekt powtarzającej się fali. Funkcja Render O jest bardzo podobna do wersji, które używane były w poprzednich programach. Wywołuje ona funkcję DrawFlagO dla każdej klatki animacji po uprzednim przesunięciu układu współrzędnych tak, aby flaga była lepiej widoczna: v oid RenderO
{ / / o p ró żn ia b u fo ry ekranu i g łę b i glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n tity O ; g lT r a n s la te f ( - 1 5 .0 f. -1 0 .Of, -5 0 .O f); D ra w F la g O ;// ry s u je fla g ę
/ / wykonuje p rz e s u n ię c ie
Rozdział 8 . ♦ Odwzorowania te k s tu r
g lF lu s h O ; SwapBuffers(g_HDC);
219
/ / p rze łą cza b u fo ry
} Poniżej zaprezentowana została pozostała część kodu aplikacji, którego zrozumienie nie powinno przedstawiać większych trudności: / / fu n k c ja o k re ś la ją c a fo rm a t p ik s e li v o id S e tu p P ix e lFormat(HDC hDC)
{ in t n P ixe lF o rm a t;
/ / indeks form atu p ik s e li
s t a t ic PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR),
1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0. 0, 0, 0, 16, 0. 0, PFD_MAIN PLANE, 0.
0. 0. 0 };
/ / / / / / / / / / / / / / / / / /
rozm iar s tr u k tu r y domyślna w ersja g r a fik a w oknie g ra fik a OpenGL podwójne buforow anie tr y b kolorów RGBA 3 2 -b ito w y o p is kolorów n ie s p e c y fik u je b itó w kolorów bez b u fo ru a lfa n ie s p e c y fik u je b itu p rze s u n ię c ia bez bufo ra a kum ulacji ig n o ru je b it y aku m u la cji 1 6 - b it b u fo r z bez bufo ra p o w ie la n ia bez buforów pomocniczych główna płaszczyzna rysowania zarezerwowane ig n o ru je maski warstw
n P ixelF orm a t = ChoosePixelFormat(hDC. & p fd ); / / w ybiera odpowiedni form at p ik s e li / / o k re ś la fo rm a t p ik s e li d la danego ko n te kstu urządzenia S etP ixelForm at(hD C , nP ixe lF orm a t, & p fd );
} / / procedura okienkowa LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM 1Pa ram) s t a t ic HGLRC hRC; s t a t ic HDC hDC; i n t w id th , h e ig h t;
/ / ko n te k st tw o rze n ia g r a f ik i / / ko n te k st urządzenia / / szerokość i wysokość okna
sw itch(m essage)
{ case WM CREATE; hDC = GetDC(hwnd); g_HDC = hDC; S etu p P ixe lF o rm a t(h D C );
/ / okno je s t tworzone / / pob ie ra k o n te k s t urządzenia d la okna / / w yw ołuje fu n k c ję o k re ś la ją c ą form at p ik s e li
/ / tw o rzy k o n te k s t tw o rze n ia g r a f ik i i czyni go bieżącym hRC = w glC re a te C o n te xt(h D C ); wglMakeCurrent(hDC. hRC);
220
Część II ♦ Korzystanie z OpenGL
re tu rn 0; break; case WM_CL0SE:
/ / okno je s t zamykane
/ / deaktyw uje bieżący k o n te k s t tw o rze n ia g r a f ik i i usuwa go wglMakeCurrent(hDC, NULL); w glD e le te C o n te xt(h R C ); / / wstawia kom unikat WM_QUIT do k o le jk i PostQ uitM essage(O ); re tu rn 0; break; case W MJIZE: h e ig h t = HIW0RD(1Param); w id th = L0W0RD(1Param); i f (h e ig h t= = 0 )
/ / p o biera nowe rozm iary okna
/ / zapobiega d z ie le n iu przez 0
{ h e ig h t - l;
} g l V ie w p o rt(0, 0. w id th , h e ig h t); / / nadaje nowe wym iary oknu OpenGL glMatrixMode(GL_PR0JECTI0N); / / w ybiera m acierz rzutow ania g lL o a d ld e n tity O ; / / re s e tu je m acierz rzutow ania / / wyznacza p ro p o rc je obrazu gluPerspect i v e (5 4 .O f , (G L flo at) width /(GLflo a t ) h e i g h t ,1 .O f ,1000.O f ) ;
glMatrixMode(GL_MODELVIEW); g lL o a d ld e n tity O ;
/ / w ybiera m acierz modelowania / / re s e tu je m acierz modelowania
re tu rn 0; break; case WM_KEYDOWN: keyPressed[wParam] = tr u e ; re tu rn 0; break;
/ / użytkow nik n a c isn ą ł kla w isz?
case WM_KEYUP: keyPressed[wParam] = fa ls e ; re tu rn 0; break; d e fa u lt: break;
} re tu rn (DefWindowProc(hwnd, message, wParam, IP a ra m ));
} / / p unkt, w którym rozpoczyna s ię wykonywanie programu i n t WINAPI WinMain(HINSTANCE h ln s ta n c e , HINSTANCE h P re vIn sta n ce , LPSTR IpCmdLine, in t nShowCmd)
Rozdział 8 . ♦ Odwzorowania tekstu r
WNDCLASSEX windowClass; HWND hwnd; MSG msg; bool done; DWORD dw ExStyle; DWORD dw S tyle; RECT windowRect;
/ / kla sa okna / / uchwyt okna i i komunikat i i znacznik zakończenia a p lik a c ji i i rozszerzony s ty l okna i i s ty l okna
/ / zmienne pomocnicze in t w id th = 800; i n t h e ig h t = 600; i n t b it s = 32; w indowR ect. l e f t = ( lo n g )0; w indowR ect. rig h t= ( lo n g )w id th ; wi ndowRect. to p = (1 ong)0; wi ndowRect. b o tto m = (1ong) hei g h t;
i i s tru k tu ra o k re ś la ją c a rozm iary okna
/ / d e f in ic ja k la s y okna w indow C lass,cbS ize= s i zeof(WNDCLASSEX); w in d o w C la s s.s ty le = CS_HREDRAW | CS_VREDRAW; wi ndowCla s s .1pfnWndProc= WndProc; w indow C lass.cbC lsE xtra= 0; windowClass.cbW ndExtra= 0; w ind ow C la ss.h ln sta n ce = h ln s ta n c e ; w indow C lass,hIcon= LoadIcon(NULL, IDI_APPLICATION); w indow C lass.hC ursor= LoadCursor(NULL, IDC_ARR0W); w indowC lass.hbrßackground= NULL; windowCla s s .lpszMenuName= NULL; wi ndowCla s s .1 pszClassName= "M ojaK lasa” ; windowClass.hIconSm= LoadIcon(NULL, IDI_WINL0G0);
// // // //
domyślna ikona domyślny k u rs o r bez t ł a bez menu
/ / logo Windows
/ / r e je s t r u je k la s ę okna i f ( ! R egisterC lassE x(& w indow C lass)) re tu rn 0; ‘ i f (fu llS c re e n )
/ / tr y b pełnoekranowy?
{ DEVMODE d m S creenS ettings; / / tr y b urządzenia m em set(&dm ScreenSettings, 0 , s i z e o f(d m S c re e n S e ttin g s )); dm S creenSettings.dm Size = size o f(d m S c re e n S e ttin g s ); dm ScreenSettings.dm PelsW idth = w id th ; / / szerokość ekranu dm S creenSettings.dm PelsH eight = h e ig h t; / / wysokość ekranu dm S creenS ettings.dm B itsP erP el = b it s ; / / b itó w na p ik s e l dm ScreenSetti n g s. dmFi elds=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
i f (C hangeD isplayS ettings(& dm S creenS ettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
{ / / p rz e łą c z e n ie try b u n ie pow iodło s ię , z powrotem tr y b okienkowy MessageBox(NULL, "D is p la y mode f a ile d " , NULL, MB_0K); fullScreen=FA LSE ;
} } i f ( fu llS c re e n )
i i tr y b pełnoekranowy?
221
222
Część II ♦ Korzystanie z OpenGL
dwExStyle=WS_EX_APPWINDOW; dwStyle=WS_POPUP; ShowCursor(FALSE);
/ / rozszerzony s ty l okna / / s ty l okna / / ukrywa k u rs o r myszy
i e ls e
{ dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; / / d e fin ic ja k la s y okna dwStyle=WS_OVERLAPPEDWINDOW; / / s ty l okna
} AdjustW indowRectEx(&windowRect, dw S tyle, FALSE, dw E xS tyle); / / k o ry g u je ro zm ia r okna / / tw orzy okno hwnd = CreateWindowEx(NULL, / / rozszerzony s ty l okna "M ojaK lasa". / / nazwa k la sy "T e kstu ry, p rzykła d d ru g i: powiewająca f l a g a " , / / nazwa a p lik a c ji dw Style | WS_CLIPCHILDREN | WS_CLI PSIBLINGS, 0, 0, / / w spółrzędne x ,y w in d o w R e c t.rig h t - windowRect.1e f t , w indowR ect.bottom - w indow R ect.top, / / szerokość, wysokość NULL. / / uchwyt okna nadrzędnego NULL, / / uchwyt menu h ln s ta n c e , / / in s ta n c ja a p lik a c ji NULL); / / bez dodatkowych parametrów / / sprawdza, czy u tw o rze n ie okna n ie pow iodło s ię / / (wtedy w artość hwnd równa NULL) i f ( ! hwnd) re tu rn 0; ShowWindow(hwnd, SW_SH0W); / / w yś w ie tla okno UpdateWindow(hwnd); / / a k tu a liz u je okno done = f a l s e ; / / in ic ju je zmienną warunku p ę t li I n i t i a l i z e O ; / / in ic ju je OpenGL / / p ę tla p rze tw a rza n ia komunikatów w h ile (!do n e )
{ PeekMessage(&msg, hwnd. NULL, NULL, PM_REM0VE); i f (msg.message == WM_QUIT)
{
done = tr u e ;
/ / a p lik a c ja o trzym a ła kom unikat WM_QUIT? / / j e ś l i ta k , to kończy d z ia ła n ie
} e ls e
{ i f (keyPressed[VK_ESCAPE]) done = tr u e ; e ls e
{
R ender(); TranslateM essage(&m sg); Di spatchMessage(&msg);
}
/ / tłum aczy kom unikat i w ysyła do systemu
Rozdział 8. ♦ Odwzorowania te ks tu r
223
} fre e (b itm a p D a ta ); i f ( fu llS c re e n )
{ C h a ng eD isp la yS e ttin g s(N U L L ,0 ); ShowCursor(TRUE);
/ / przywraca p u lp it / / i w skaźnik myszy
} } Rezultat działania programu pokazuje rysunek 8.11. Rysunek 8.11. Animacja powiewającej flagi
Ukształtowanie terenu Kolejny program tworzyć będzie grafikę naśladującą naturalne ukształtowanie pagór kowatego terenu za pomocą siatki różnych wysokości. Przedstawiona tutaj metoda jest tylko jednym ze sposobów imitacji naturalnego terenu. Istnieje szereg innych metod, które umożliwiają uzyskanie bardziej efektywnego lub re alistycznego efektu. W tym przykładzie zastosowany zostanie także prosty sposób ruchu kamery, który umożliwi obserwację terenu z różnych stron. Teren pokryty zostanie teksturą trawy. Inna tekstura wykorzystana zostanie do uzyskania realistycznego efektu powierzchni wody wypełniającej zagłębienia terenu.
Objaśnienia W celu reprezentacji ukształtowania terenu utworzona zostanie regularna siatka wierz chołków w równych odstępach, dla których określi się różną wysokość terenu.
224
Część II ♦ Korzystanie z OpenGL
Wysokość każdego z wierzchołków będzie określana na podstawie mapy bitowej o roz miarach 32x32. Mapa ta będzie zawierać jednakowe wartości składowych kolorów dla poszczególnych bitów (odcienie szarości). W formacie 24-bitowym każda z wartości składowych opisana jest za pomocą 8-bitów, wobec czego będą one reprezentować wy sokości od 0 do 255. Po załadowaniu tej mapy dysponuje się zbiorem punktów reprezentujących wysokość terenu w pewnych odstępach. Odstępy pomiędzy wierzchołkami ustala się posługując się skalą mapy. Skalowanie pozwala zwiększać lub zmniejszać rozmiary obiektów w spo sób określony przez współczynnik skalowania. Koncepcję tę wykorzystać można w tym przykładzie zwiększając odstęp pomiędzy wierzchołkami za pomocą współczynnika skali mapy. Ustalając współrzędne wierzchołka odpowiadającego węzłowi siatki mnoży się indeks węzła siatki przez współczynnik skali mapy. Na przykład współrzędną x wierzchołka zdefiniuje się jako iloczyn jego indeksu na osi x siatki i współczynnika skali mapy. Ponieważ mapa terenu ma postać siatki wysokości, to w programie reprezentować bę dzie ją można za pomocą dwuwymiarowej tablicy współrzędnych wierzchołków. Siatka ta leżeć będzie w płaszczyźnie xz, a wysokość terenu mierzona będzie wzdłuż osi y. Po załadowaniu siatki uzyskany zostanie obraz terenu przedstawiony na rysunku 8.12. Rysunek 8.12. Zbiór punktów siatki
Mapę ukształtowania terenu rysować można tworząc łańcuchy trójkątów GL_TRIANGLE_ STRIP rzędami wzdłuż osi z. W tym celu trzeba jednak tworzyć kolejne wierzchołki w spe cjalnym porządku. Rozpoczynając tworzenie kolejnego łańcucha należy posuwać się wzdłuż osi x podając kolejne wierzchołki według wzoru przypominającego literę Z, co ilustruje rysunek 8.13. Aby uzyskać właściwe odwzorowanie tekstury, każde kolejne cztery wierz chołki będą przekazywane maszynie OpenGL. W ten sam sposób należy postępować też w odniesieniu do kolejnych wierszy siatki.
Rozdział 8 . ♦ Odwzorowania te ks tu r
Rysunek 8.13. Porządek tworzenia wierzchołków pojedynczego wiersza siatki
225
Wierzchołek 1
Wierzchołek 3
Rysunek 8.14 przedstawia efekt rysowania wierzchołków za pomocą wzoru przypomi nającego literę Z zastosowanego do zbioru punktów pokazanych na rysunku 8.12. Rysunek 8.14. Mapa terenu uzyskana po narysowaniu łańcuchów trójkątów
Jeśli trójkąty wypełniony zostanie odcieniami szarości określonymi przez wartości skła dowych kolorów dla poszczególnych wierzchołków mapy oraz modelem cieniowania gładkiego, to uzyskany zostanie obraz terenu przedstawiony na rysunku 8.15. Jaśniejsze odcienie szarości reprezentują obszary położone wyżej, a ciemniejsze niżej. Efekt ten wzbogacony następnie zostanie teksturą dla podniesienia realizmu. Tekstura zostanie zastosowana do każdego kolejnego zestawu czterech wierzchołków z osobna. Sposób definicji współrzędnych tekstury dla poszczególnych wierzchołków przedstawia rysunek 8.16. W wyniku pokrycia czworokątów teksturą przypominającą murawę uzyskuje się reali styczny obraz terenu przedstawiony na rysunku 8.17. Wygląda całkiem dobrze, prawda?
226
Część II ♦ Korzystanie z OpenGL
Rysunek 8.15. Mapa terenu uzyskana na podstawie odcieni szarości poszczególnych wierzchołków
' ,,, '"A'*;.' s /'V
/ < **Ars
Rysunek 8.16. Współrzędne tekstury wierzchołków mapy
/y
Wierzchołek 1 glTexCoord2f(1.0, 0.0)
Wierzchołek 3 glTexCoord2f(1.0, 1.0)
Rysunek 8.17. Mapa terenu uzyskana przez zastosowanie tekstury
Uzyskany obraz terenu wzbogacony zostanie dodatkowo o efekt wody. W tym celu należy zdefiniować poziom morza jako wysokość terenu, poniżej której będzie on zalany wodą. Na poziomie morza trzeba umieścić pojedynczy prostokąt wypełniony przezroczystym
Rozdział 8 . ♦ Odwzorowania te k s tu r
227
kolorem niebieskim reprezentującym powierzchnię wody. Dla uzyskania bardziej reali stycznego wyglądu można pokryć ją odpowiednią teksturą. Zmieniając cyklicznie w ko lejnych ujęciach wysokość poziomu wody łatwo jest uzyskać jeszcze jeden efekt przy pominający zalewanie brzegu przez fale. Uzyskany w ten sposób obraz terenu prezentuje rysunek 8.18.
Mimo wysiłku włożonego w uatrakcyjnienie obrazu terenu jego statyczna obserwacja szybko się znudzi. Dlatego też umożliwić należy poruszanie kamerą za pomocą myszy. Ruchy myszą w lewo i prawo powodować będą okrążanie terenu przez kamerę, a ruchy w górę i w dół zbliżanie się kamery do terenu i jej oddalanie. Niezależnie od zmiany poło żenia kamera będzie zawsze wycelowana w ten sam punkt, który określony będzie w ko dzie programu. Położenie kamery zmieniać można obsługując komunikaty WM_M0USEM0VE w funkcji okienkowej WndProcC). Dla każdego komunikatu trzeba ustalić wielkość przesu nięcia myszy i na jego podstawie wyznaczyć nowe położenie kamery korzystając z funk cji trygonometrycznych.
Implementacja Kod programu omówiony zostanie fragmentami. Pierwszy z nich definiuje stałe i dekla ruje zmienne: / / / / / / D e fin ic je # d e fin e BITMAPJD 0x4D42 # d e fin e MAP_X32 # d e fin e MAP_Z32 # d e fin e MAP_SCALE20.0f # d e fin e P I3 .14159 / / / / / / P lik i nagłówkowe # in c lu d e # in c lu d e < s td io .h > # in c lu d e < s td lib .h > # in c lu d e
// // // //
id e n ty fik a to r form atu BMP rozm iar mapy w zdłuż o si x rozm iar mapy w zdłuż o si y ska la mapy
/ / standardowy p lik nagłówkowy Windows
228
Część II ♦ Korzystanie z OpenGL
# in c lu d e < g l/g l.h > # in c lu d e < g l/g 1 u .h > / / / / / / Zmienne g lo b a ln e HDC g_HDC; bool f u l l Screen = fa ls e ;
/ / standardowy p lik nagłówkowy OpenGL / / fu n k c je pomocnicze OpenGL
bool ke yP re sse d [2 5 6 ];
/ / g lo b a ln y k o n te k st urządzenia / / tru e = tr y b pełnoekranowy; // fa ls e = tr y b okienkowy / / ta b lic a p rz y c iś n ię ć k la w is z y
f lo a t angle = O.Of; f lo a t radian s = O.Of; f lo a t w ate rH e ig h t = 1 54.Of; bool w a te rD ir = tr u e ;
// // // //
/ / . / / / / Zmienne myszy i kamery in t mouseX, mouseY; f lo a t cameraX, cameraY, cameraZ: f lo a t lookX, lookY, lookZ ;
/ / współrzędne myszy / / współrzędne kamery / / punkt wycelowania kamery
I I I I it Opis te k s tu ry BITMAPINFOHEADERbitmapInfoHeader; BITMAPINF0HEADER1andlnfo; BITMAPINFOHEADERwaterlnfo;‘
11 pomocniczy nagłówek obrazu / / nagłówek obrazu te k s tu r y lądu 11 nagłówek obrazu te k s tu r y wody
unsigned unsigned unsigned unsigned unsigned
// // // // //
char*im ageD ata; c h a r* la n d T e xtu re ; ch a r*w a te rT e x tu re ; in tla n d ; in tw a te r;
/ / / / / / Opis te re n u f lo a t te rra in [M A P X][MAP Z ] [ 3 ] ;
k ą t kamery k ą t kamery w radianach poziom morza anim acja fa lo w a n ia wody; // tru e = poziom w górę, fa ls e = poziom w dół
dane mapy dane te k s tu ry lądu dane te k s tu ry wody o b ie k t te k s tu ry lądu o b ie k t te k s tu ry wody
/ / wysokość te re n u w punktach s ia t k i (0-255)
Tym razem pierwszy fragment kodu jest nieco dłuższy. Blok definicji stałych określa rozmiary mapy wzdłuż osi x i z oraz jej skalę. Definiuje także wartość liczby n wyko rzystywaną podczas obliczania położenia kamery. Zadanie większości z zadeklarowanych zmiennych globalnych jest oczywiste. Zmienna w a t e r H e ig h t definiuje poziom morza, na którym rysuje się czworokąt reprezentujący powierzchnię wody. Zmienna w a t e r D ir określa kierunek ruchu poziomu wody, która może podnosić się lub opadać. Kolejny blok zmiennych służy do przechowania infor macji o położeniu myszy oraz pozycji i orientacji kamery. Zmienna im a g e D a ta wskazy wać będzie dane mapy terenu o rozmiarach 32x32 załadowane z pliku BMP. Zmienne la n d T e x t u r e i w a t e r T e x t u r e wskazywać będą dane tekstur reprezentujących powierzch nię lądu i wody. Aby efektywnie korzystać z tych tekstur, trzeba utworzyć dla nich osobne obiekty tekstur, których nazwy przechowywane będą za pomocą zmiennych la n d i w a te r . Ostatnią zmienną globalną zadeklarowaną w powyższym fragmencie kodu jest tablica przechowująca opis terenu w postaci współrzędnych wierzchołków dla poszczególnych punktów siatki. Rozmiary terenu określone są przez stałe MAP X i MAR Z.
Rozdział 8 . ♦ Odwzorowania te k s tu r
229
Kod programu zawiera także znaną z poprzednich przykładów implementację funkcji LoadBitmapFi l e ( ) służącą do załadowania zawartości pliku BMP stosującego 24-bitowy opis kolorów. Funkcja ta nie będzie tutaj omawiana. Kolejny blok kodu stanowi funkcja I n i t i a l iz e T e r r a in ( ): v o id I n i t i a l iz e T e r r a in ( )
{ / / o b lic z a w spółrzędne w ierzchołków / / d la w s z y s tk ic h punktów s ia tk i f o r ( i n t z = 0; z < MAP_Z; z++)
{ f o r ( i n t x = 0; x < MAP_X; x++)
{ t e r r a i n [ x ] [ z ] [ 0 ] = f l o a t (x)*MAP_SCALE; t e r r a i n [ x ] [ z ] [ l ] = (flo a t)im a g e D a ta [(z *M A P _ Z + x )*3 ]; t e r r a i n [ x ] [ z ] [2 ] = -float(z)*M AP_SCALE;
} } } Funkcja ta wyznacza współrzędne wierzchołków dla wszystkich punktów siatki terenu. Wyznaczenie współrzędnych x i z polega na pomnożeniu indeksu punktu siatki przez współczynnik skali mapy. Wartość zmiennej z jest ujemna, ponieważ teren leży wzdłuż ujemnej części osi z. Oczywiście wybór położenia terenu może być zupełnie inny, ale wtedy należy pamiętać także o zmianie sposobu ruchu kamery i jej orientacji. Sposób wyznaczenia współrzędnej y jest najciekawszym fragmentem kodu funkcji Ini t i a l iz e T e r r a in ( ). Polega on na pobraniu wartości składowej koloru dla odpowiedniego
piksela mapy bitowej opisującej teren. Nieco kłopotu może sprawić jedynie znalezienie piksela odpowiadającego bieżącemu punktowi siatki. Korzysta się w tym celu z nastę pującego równania: (z*MAP_z+x)*3
Analizując to równanie należy pamiętać, że dane mapy bitowej znajdują się w jednowy miarowym buforze i zawierają takie same wartości składowej czerwonej, zielonej i nie bieskiej dla każdego piksela. Kolejna funkcja, L o a d T e xtu re s( ), służy do załadowania obrazów tekstur i utworzenia obiektów tekstur: bool LoadTexturesO
{ / / ła d u je obraz te k s tu r y lądu la n d T e x tu re = L o a d B itm a p F ile ("green.bm p” . &1and I n f o ) ; i f ( ¡la n d T e x tu re ) re tu rn fa ls e ; / / ła d u je obraz te k s tu r y wody w a te rT e x tu re = L o a d B itm a p F ile ("w a te r.b m p ", & w a te rIn fo ); i f ( ¡w a te rT e xtu re ) re tu rn fa ls e ;
230
Część II ♦ Korzystanie z OpenGL
/ / tw orzy mipmapy lądu g lG e n T e x tu re s (l, & la n d ); glBindTexture(GL_TEXTURE_2D, la n d ) ; g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER. GL_NEAREST); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MI N_FI LTER, GL_NEAREST); gluBuild2DMipmaps(GL_TEXTURE_2D. GL_RGB, la n d ln fo .b iW id th , 1a n d ln fo .b iH e ig h t, GL_RGB, GL_UNSIGNED_BYTE, la n d T e x tu re ); / / tw o rzy mipmapy wody g lG e n T e x tu re s (l, & w a te r); glBindTexture(GL_TEXTURE_2D, w a te r ) ; g lT e x P a ra m e te ri(GL_TEXTURE_2D. GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexP aram eteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GLJEAREST); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GLREPEAT); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, w a te rln fo .b iW id th , w a te r ln fo .b iH e ig h t. GL_RGB, GL_UNSIGNED_BYTE, w a te rT e x tu re ); re tu rn tr u e ;
} Po załadowaniu obrazów tekstur za pomocą funkcji LoadBitmapFi 1e ( ) najpierw trzeba utworzyć obiekt tekstury lądu i określić sposób filtrowania tekstury. Następnie tworzy się automatycznie mipmapy korzystając z funkcji gl uBui 1d2DMi pmaps(). Podobnie po stępuje się w przypadku tekstury wody, ale dodatkowo definiuje się dla niej właściwo ści powtarzania w obu kierunkach, z których korzystać można przy rysowaniu wody: v o id RenderO
{ radian s =
flo a t( P I * ( a n g le - 9 0 . 0 f ) /1 8 0 . 0 f ) ;
/ / wyznacza p o ło ż e n ie kamery cameraX = lookX + s in (ra d ia n s)*m o u se Y ; / / mnożenie cameraZ = lookZ + cos(radians)*m ouseY ; / / powoduje cameraY = lookY + mouseY / 2 .Of;
przez mouseY z b liż e n ie bądź o d d a le n ie kamery
/ / w ycelow uje kamerę w środek te re n u lookX = ( MAP_X*MAP_SCAL E) / 2 . 0 f ; lookY = 1 5 0 .Of; lookZ = - ( MAP_Z*MAP_SCAL E) /2 .0 f ; / / o próżnia b u fo ry ekranu i g łę b i g lC l e a r(GL_C0L0R_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n t it y d ; / / umieszcza kamerę g lu L o o k A t(cameraX, cameraY, cameraZ, lookX, lookY, lookZ , 0 .0 , 1 .0 , 0 .0 ); / / w ybiera te k s tu rę lądu glBindTexture(GL_TEXTURE_2D, la n d ) ; / / przegląda rzędami w s z y s tk ie punkty s ia t k i, / / ry s u ją c d la każdego pojedynczy łańcuch tró jk ą tó w w zdłuż o si z. fo r ( in t z = 0; z < MAP_Z-1; z++)
{
glBegin(GL_TRIANGLE_STRIP); fo r ( in t x = 0; x < MAP_X-1; x++)
Rozdział 8 . ♦ Odwzorowania te ks tu r
i
231
/ / dla każdego wierzchołka: obliczamy składowe odcienia szarości / / wyznaczamy współrzędne te k s tu ry i rysujem y go.
/* w ie rz c h o łk i rysowane są w n a s tę p u ją c e j k o le jn o ś c i: 0
---> 1
/ / 2
1/
---> 3
*/ / / w ie rz c h o łe k 0 g l C o lo r 3 f ( t e r r a in [ x ] [ z ] [ l] / 2 5 5 . 0 f , t e r r a i n [ x ] [ z ] [ l ] / 2 5 5 . 0 f . te r r a in [x ][ z ][ l]/ 2 5 5 .0 f ) ; g lT e x C o o rd 2 f(0 .0 f. O .O f); g lV e r t e x 3 f ( t e r r a in [ x ] [ z ] [ 0 ] . t e r r a i n [ x ] [ z ] [ 1 ] . t e r r a i n [ x ] [ z ] [ 2 ] ) : / / w ie rz c h o łe k 1 g lT e x C o o r d 2 f(l,0 f, O .O f); g lC o lo r 3 f ( t e r r a i n [ x + l] [ z ] [ l] / 2 5 5 . 0 f . t e r r a in [ x + l] [ z ] [ 1 J / 2 5 5 . O f. t e r r a i n [ x + l][ z ] [1 J / 2 5 5 . O f); g lV e r t e x 3 f ( t e r r a in [ x + l] [ z ] [ 0 ] . t e r r a i n [ x + l ] [ z ] [ l ] , t e r r a i n [ x + l ] [ z ] [ 2 ] ) ; / / w ie rz c h o łe k 2 g lT e x C o o rd 2 f(0 .0 f, l. O f ) ; g l Col o r 3 f ( t e r r a in [ x ] [ z + l] [ l ] / 2 5 5 . 0 f , t e r r a i n [ x ] [ z + l] [ l] / 2 5 5 . 0 f , t e r r a in [x ][ z + l] [l] /2 5 5 . 0 f) ; g lV e r t e x 3 f ( t e r r a in [ x ] [ z + l] [ 0 ] , t e r r a i n [ x ] [ z + l ] [ l ] , t e r r a i n [ x ] [ z + l ] [ 2 ] ) ; //w ie rz c h o łe k 3 g lT e x C o o rd 2 f(1 .0 f, l. O f ) ; g l C o l o r 3 f ( t e r r a in [ x + l] [ z + l] [ l] / 2 5 5 . 0 f , t e r r a in [ x + l] [ z + l ] [ l] / 2 5 5 . 0 f . t e r r a in [ x + l ] [ z + l ] [ l ] / 2 5 5 . 0 f ); g l V e r t e x 3 f ( t e r r a in [ x + l] [ z + l] [ 0 ] , t e r r a i n [ x + l ] [ z + l ] [ l ] , t e r r a i n [ x + l ] [ z + l ] [ 2 ] ) ;
} g lE n d O ;
} / / w łącza łą c z e n ie kolorów glEnable(GL_BLEND); / / p rze łą c z a b u fo r g łę b i w tr y b "ty lk o -d o -o d c z y tu " glDepthMask(GL_FALSE); / / o k re ś la fu n k c je łą c z e n ia d la e fe k tu p rz e jrz y s to ś c i glBlendFunc(GL_SRC_ALPHA, GL_0NE); gl Col o r 4 f ( 0 . 5 f , 0 .5 f, l. O f , 0 .7 f ) ; / / k o lo r n ie b ie s k i, p rze z ro cz ys ty glBindTexture(GL_TEXTURE_2D, w a te r); / / w ybiera te k s tu rę wody / / ry s u je p o w ie rzch n ię wody ja k o jeden duży czworokąt glBegin(GL_QUADS); g lT e x C o o rd 2 f(0 .0 f, O .O f); / / lew y. dolny w ie rzch o łe k g lV e r t e x 3 f ( t e r r a in [ 0 ] [ 0 ] [ 0 ] , w a te rH e ig h t, t e r r a in [ 0 ] [ 0 ] [ 2 ] ) ;
232
Część II ♦ Korzystanie z OpenGL
g lT e x C o o rd 2 f(1 0 .O f, O.Of); / / prawy, doln y w ie rz c h o łe k g l V e r t e x 3 f ( t e r r a i n [ M A P _ X - l ] [ 0 ] [ 0 ] , w a te r H e ig h t, t e r r a i n [ M A P _ X - l ] [ 0 ] [ 2 ] ) ; glT e x C o ord 2 f(1 0 .O f, 1 0 .O f); / / prawy, górny w ie rz c h o łe k g lV e rte x 3 f(te rra in [M A P _ X -l][M A P _ Z -l][0 ], w aterHeight. te r r a in [M A P _ X - l][ M A P _ Z - l] [2 ] ) ; g lT e x C o o r d 2 f(0 .0 f, 1 0 .O f); / / lewy, górny w ie rz c h o łe k g lV e r t e x 3 f ( t e r r a in [ 0 ] [ M A P _ Z - l] [ 0 ] . w a t e r H e ig h t , t e r r a i n [ 0 ] [ M A P _ Z - l ] [ 2 ] ) ; g lE n d O ; / / przywraca zwykły t r y b pracy bufora g łę b i glDepthMask(GLJRUE); / / wyłącza łą c z e n ie kolorów glDisable(GL_BLEND); / / tw orzy animację pow ierzchni wody i f (w aterH eight > 155.Of) w a te r D ir = f a l s e ; e ls e i f (waterH eig ht < 154.Of) w a te r D ir = t r u e ; i f (w a te rD ir ) w aterHeight += O .O lf; e ls e w aterH eight -= O .O lf; g lF I u s h ( ) ; SwapBuffers(g_HDC);
/ / przełą cza b ufo ry
} Funkcja Render() rozpoczyna swoje działanie od wyznaczenia kąta obrotu kamery wy rażonego w radianach. Korzystając z tej wartości oblicza się nowe położenia kamery — współrzędną x za pomocą funkcji sin() tego kąta, a współrzędną z za pomocą funkcji cos O. Następnie oblicza się współrzędne środka terenu, w który wycelowana będzie kamera. Kamerę umieszcza się i wycelowuje za pomocą funkcji gl uLookAt (). Przed rysowaniem terenu trzeba poinformować maszynę OpenGL wywołując funkcję glBindTexture(), że będzie wykorzystywana tekstura lądu. Dla każdego rzędu punktów siatki wzdłuż osi z rysuje się łańcuch trójkątów. Dla każdego wierzchołka określa się składowe odcienia szarości (dzieląc wysokość terenu przez 255) oraz współrzędne tek stury. W celu wyznaczenia kolejności tworzenia wierzchołków trzeba posłużyć się omó wionym już wzorem przypominającym literę Z. Ostatni fragment funkcji RenderO rysuje powierzchnię wody. Można rozpoznać w nim kod służący do uzyskania efektu przezroczystości, który omówiony został w rozdziale 7. Dodatkowo za pomocą funkcji glBindTexture() tworzy on teksturę powierzchni wody. Wierzchołki czworokąta reprezentującego powierzchnię wody zdefiniowane są w po rządku przeciwnym do kierunku ruchu wskazówek zegara. Po narysowaniu czworokąta wyłączany jest efekt przezroczystości i wykonywany kod animacji powierzchni wody. Funkcja inicjacji OpenGL i danych programu wygląda tym razem następująco:
Rozdział 8. ♦ Odwzorowania te ks tu r
233
void I n i t i a l i z e ( ) g lC le a r C o lo r ( 0 .O f . O.Of. O.Of. O.Of);
/ / t ł o w k o lo rz e czarnym
glShadeModel(GL_SM00TH); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glFrontFace(GL_CCW);
// // // // ii
cie nio w an ie g ła d k ie usuwanie uk ry ty c h powierzchni brak o b lic z e ń d la niewidocznych s tro n wielokątów niewidoczne stro n y posiadają porządek wierzchołków przeciwny do kie runku ruchu wskazówek zegara
glEnable(GL_TEXTURE_2D);
i i włącza te k s tu r y dwuwymiarowe
imageData = LoadBitmapFi1e ( " t e r r a in 2 . b m p " , & bitm apInfoH eader); i i i n i c j u j e dane terenu i ła d u je t e k s tu r y I n i t i a l i z e T e r r a i n ( ); L o a dTextu re s();
Po zainicjowaniu odpowiednich parametrów maszyny OpenGL funkcja I n i t i a l i z e O ładuje dane terenu za pomocą funkcji L o a d B i t m a p F i l e ( ). Następnie wywołuje funkcje służące do inicjacji danych terenu i załadowania tekstur. Ze względu na możliwość poruszania kamerą za pomocą myszy trzeba wprowadzić modyfikacje w kodzie funkcji okienkowej: LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam. LPARAM 1Param) s t a t i c HGLRC hRC; s t a t i c HDC hDC; i n t w id th , h e ig h t; i n t oldMouseX. oldMouseY;
// ii ii ii
kon te kst tw orzenia g r a f i k i kon te kst urządzenia szerokość i wysokość okna współrzędne poprzedniego położenia myszy
switch(message)
{
case WM_CREATE: hDC = GetDC(hwnd); g_HDC = hDC; SetupPixelF ormat(hD C );
i i okno j e s t tworzone i i pobiera ko n tekst urządzenia d la okna i i wywołuje fu n k c ję o k re ś la ją c ą format p ik s e li
i i tw orzy k on te k st tw orzenia g r a f i k i i czyni go bieżącym hRC = wglCreateContext(hD C ); wglMakeCurrent(hDC, hRC); r e tu r n 0; break; case WM_CL0SE:
/ / okno j e s t zamykane
/ / deaktywuje bieżący ko n te kst tw orzenia g r a f i k i i usuwa go wglMakeCurrent(hDC, NULL); w glD ele te C o n te xt(hR C ); / / wstawia komunikat WM_QUIT do k o l e j k i P ostQuitM essage(0); r e tu r n 0; break;
234
Część II ♦ Korzystanie z OpenGL
case WM_SIZE: h e ig ht = HIW0RD(1Param); w id th = LOWORDO Param); i f (height= =0)
{
/ / pobiera nowe rozmiary okna / / unika d z ie le n ie przez 0
h e ig h t = l;
} g l V ie w p o rt(0. 0, w id th , h e ig h t ) ; / / nadaje nowe wymiary oknu OpenGL glMatrixMode(GL_PROJECTION); / / wybiera macierz rzutowania g lL o a d ld e n tity O ; / / re s e tu je macierz rzutowania / / wyznacza p ro p o rc je obrazu g lu P e r s p e c t iv e ( 5 4 . 0 f , ( G L f lo a t ) w i d t h / ( G L f l o a t ) h e i g h t , 1 . 0 f .1 00 0 .O f ) ; glMatrixMode(GL_MODELVIEW); g lL o a d ld e n tity O ;
/ / wybiera macierz modelowania / / re s e t u je macierz modelowania
r e tu r n 0; break; case WM_KEYDOWN: keyPressed[wParam] = t r u e ; r e tu r n 0; break;
/ / użytkownik n a c isn ą ł klaw isz?
case WM_KEYUP: keyPressed[wParam] = f a l s e ; re tu r n 0; break; case WM_M0USEM0VE: / / zapamiętuje współrzędne myszy oldMouseX = mouseX; oldMouseY = mouseY; / / pobiera nowe współrzędne myszy mouseX = LOWORDO Param); mouseY = HIWORDO Param); / / o g ra n icz e n ia ruchu kamery i f (mouseY < 200) mouseY = 200; i f (mouseY > 450) mouseY = 450; i f ((mouseX - oldMouseX) > 0) / / mysz p rze s un ię ta w prawo angle += 3 . Of; e ls e i f ((mouseX - oldMouseX) < 0) / / mysz p rze s u n ięta w lewo angle -= 3 . Of; r e tu r n 0; break; d e fa u lt: b re a k ;
} r e tu r n (DefWindowProc(hwnd, message. wParam, IParam));
}
Rozdział 8 . ♦ Odwzorowania te k s tu r
235
Modyfikacja polega na dodaniu obsługi komunikatu WM_M0USEM0VE. Należy zapamiętać poprzednie współrzędne myszy, aby ustalić, w jakim kierunku została przesunięta. Na stępnie pobiera się nowe współrzędne myszy korzystając z wartości parametru 1 Param. Aby kontrolować sposób obserwacji grafiki przez użytkownika, wprowadza się ograni czenia ruchu kamery. Na podstawie ruchu myszy zwiększa się lub zmniejsza kąt obrotu kamery. I to wszystko! Przykład ten pokazuje, jak łatwo można uzyskać zaawansowaną grafikę OpenGL, jeśli tylko dokona się właściwej analizy i rozkładu problemu.
Podsumowanie Odwzorowania tekstur polegają na umieszczeniu obrazów na powierzchniach wieloką tów i służą do zwiększenia realizmu tworzonej grafiki. Po załadowaniu obrazu tekstury z pliku należy zdefiniować go jako dane tekstury OpenGL. W zależności od liczby wymiarów tekstury używa się w tym celu funkcji glTexIm ag e lD (), glTexIm age2D ( ) lub glTexIm age3D (). Obiekty tekstur ułatwiają korzystanie z tekstur OpenGL. Pozwalają załadować wiele tekstur i używać ich podczas tworzenia grafiki. Podczas tworzenia obiektów pokrytych teksturami można zaobserwować na ich po wierzchni niepożądane efekty związane z koniecznością dopasowania rozmiarów tek stur do zmieniających się rozmiarów obiektów. Efekty te można wyeliminować kon trolując poziom szczegółowości tekstur za pomocą mipmap.
236
Część II ♦Korzystanie z OpenGL
Rozdział 9.
Zaawansowane odwzorowania tekstur W rodziale tym przedstawione zostaną zaawansowane techniki tworzenia tekstur, które pozwolą kolejny raz na zwiększenie realizmu tworzonej grafiki. Omówione zostaną na stępujące zagadnienia: ♦ tekstury wielokrotne; ♦ mapy otoczenia; ♦ macierz tekstur; ♦ mapy oświetlenia; ♦ wieloprzebiegowe tworzenie tekstur wielokrotnych. Zastosowanie każdego z wymienionych efektów powoduje, że grafika trójwymiarowa nabiera jeszcze doskonalszego wyglądu.
Tekstury wielokrotne Dotychczas wykorzystywana była pojedyncza tekstura dla każdego wielokąta. OpenGL umożliwia jednak pokrycie wielokąta sekwencją tekstur, czyli tworzenie tekstur wielo krotnych. Możliwość ta stanowi obecnie jedynie rozszerzenie specyfikacji OpenGL. Roz szerzenia interfejsu programowego OpenGL zatwierdzane są przez radę ARB {Archi tectural Review Board) nadzorującą rozwój specyfikacji OpenGL. Tekstury wielokrotne stanowią opcjonalne rozszerzenie OpenGL i nie są dostępne w każdej implementacji. Tekstury wielokrotne tworzone są za pomocą sekwencji jednostek tekstury. Jednostki tekstury omówione zostaną szczegółowo w dalszej części rozdziału. Obecnie wystarczy jedynie informacja dotycząca tego, że każda jednostka tekstury reprezentuje pojedynczą teksturę, a także to, że podczas tworzenia tekstury wielokrotnej każda jednostka tekstury przekazuje wynik jej zastosowania kolejnej jednostce tekstury aż do utworzenia końcowej tekstury wielokrotnej. Rysunek 9.1 ilustruje proces tworzenia tekstur wielokrotnych.
238
Część II ♦ Korzystanie z OpenGL
Rysunek 9.1. Tekstura wielokrotna powstaje na skutek nałożenia na wielokąt więcej niż jednej tekstury za pomocą jednostek tekstur
W procesie tworzenia tekstury wielokrotnej można wyróżnić cztery główne etapy.
1 . Sprawdzenie, czy implementacja OpenGL udostępnia możliwość tworzenia tekstur wielokrotnych. 2 . Uzyskanie wskaźnika funkcji rozszerzenia.
3. Tworzenie jednostki tekstury. 4. Określenie współrzędnych tekstury.
Teraz należy przyjrzeć się bliżej każdemu z wymienionych etapów.
Sprawdzanie dostępności tekstur wielokrotnych Przed rozpoczęciem stosowania tekstur wielokrotnych trzeba najpierw sprawdzić, czy udostępnia je używana implementacja OpenGL. Tekstury wielokrotne są rozszerzeniem specyfikacji OpenGL zaaprobowanym przez radę ARB. Listę takich rozszerzeń udo stępnianych przez konkretną implementację można uzyskać wywołując funkcję g lG e tS t r in g ( ) z parametrem GL EXTENSIONS. Lista ta posiada postać łańcucha znaków, w któ rym nazwy kolejnych rozszerzeń rozdzielone są znakami spacji. Jeśli w łańcuchu tym znaleziona zostanie nazwa "G L_A R B jnultite x tu re " , to oznacza to dostępność tekstur wie lokrotnych. W celu sprawdzenia dostępności konkretnego rozszerzenia OpenGL można skorzystać z dostępnej w bibliotece GLU funkcji g lu C h e ckE xte n sio n ( ) o następującym prototypie: GLboolean gluCheckExtension(char *extName, const GLubyte * e x t S t r i n g ) ;
Funkcja ta zwraca wartość tru e , jeśli nazwa rozszerzenia extName znajduje się w łańcuchu e x t S t r in g lub wartość fa l se w przeciwnym razie. Łańcuch zwrócony przez funkcję g lG e tS trln g ( ) można także przeszukać indywidualnie. Oto przykład implementacji odpowiedniej funkcji:
Rozdział 9. ♦ Zaaw ansow ane odwzorowania te ks tu r
239
bool In S tr ( c h a r *s e a rc h S tr, char * s t r )
{ char *e nd O fS tr; i n t id x = 0;
/ / wskaźnik o s ta tn ie g o znaku łańcucha
endOfStr = s t r + s t r l e n ( s t r ) ;
/ / o s ta t n i znak łańcucha
/ / p ę tla wykonywana aż do o s ią g n ię c ia końca łańcucha w h ile ( s t r < endOfStr)
{
/ / odnajd uje poło że nie kole jne g o znaku s p a cji id x = s t r c s p n ( s t r , " " ) ; / / sprawdza s t r i wskazuje poszukiwaną nazwę i f ( ( s t r l e n ( s e a r c h S t r ) == id x ) && ( s trn c m p (s e a rc h S tr, s t r , id x ) == 0))
{ r e tu r n t r u e ;
} / / n ie j e s t t o poszukiwana nazwa, przesuwamy wskaźnik do k o le jn e j nazwy s t r += ( i d x + 1);
}
r e tu r n f a l s e ;
} Funkcja ta zwraca wartość t r u e , jeśli łańcuch s t r zawiera łańcuch s e a r c h S t r przy zało żeniu, że s t r składa się z podłańcuchów oddzielonych znakami spacji. Aby sprawdzić, czy dana implementacja umożliwia stosowanie tekstur wielokrotnych, można skorzystać ostatecznie z poniższego fragmentu kodu: char * e x te n s io n S t r ;
/ / l i s t a dostępnych rozszerzeń
/ / po bie ra l i s t ę dostępnych rozszerzeń e x te n s io n S tr = (char*)glGetString(GL_EXTENSIONS); i f ( InStr("GL_ARB_multit e x t u r e " , e x te n s io n S t r) )
{
/ / t e k s t u r y w ie lo k ro t n e są dostępne
} Po sprawdzeniu dostępności tekstur wielokrotnych kolejnym krokiem będzie uzyskanie wskaźników funkcji rozszerzeń.
Dostęp do funkcji rozszerzeń Aby korzystać z rozszerzeń OpenGL na platformie Microsoft Windows musimy uzy skać dostęp do funkcji rozszerzeń. Funkcja wglGetProcAddress() zwraca wskaźnik żądanej funkcji rozszerzenia. Dla tekstur wielokrotnych dostępnych jest sześć funkcji rozszerzeń: ♦ glMultiTexCoordifARB (gdzie 7 =1 ..4) — funkcje te określają współrzędne tekstur wielokrotnych; ♦ gl Acti veTextureARB — wybiera bieżącą jednostkę tekstury; ♦ glCl i entActi veTextureARB — wybiera jednostkę tekstury, na której wykonywane będą operacje.
240
Część II ♦ Korzystanie z OpenGL
Poniższy fragment kodu służy do uzyskania dostępu do wymienionych funkcji rozszerzeń: PFNGLMULTITEXC00RD2FARBPR0C glMultiTexCoord2fARB = NULL; PFNGLACTIVETEXTUREARBPROC g l ActiveTextureARB = NULL; PFNGLCLIENTACTIVETEXTUREARBPROC glC l i entActiveTextureARB = NULL; glMultiTexCoord2fARB = (PFNGLMULTITEXC00RD2FARBPR0C) wglG etP rocA ddress("glM ultiT exCoord2fA RB "); g l A c t i veTextureARB = (PFNGLACTIVETEXTUREARBPROC) wglGetProcAddress(" g lA c tiv e T e x tu re A R B "); glC l i e n t A c t i veTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) w glG etP rocAddressCglC l i e n tA c ti veTextureARB");
Po wykonaniu tego kodu można tworzyć tekstury wielokrotne za pomocą funkcji i g l A c t i v e T e x t u r e A R B ( ).
g l Mul -
tiT e x C o o rd 2 fA R B ()
Tworzenie jednostek tekstury Tworzenie tekstur wielokrotnych odbywa się poprzez nakładanie wielu jednostek tek stury. Jednostka tekstury składa się z obrazu tekstury, parametrów filtrowania, stosu macierzy tekstury, a ponadto posiada zdolność automatycznego tworzenia współrzęd nych tekstury. Aby skonfigurować parametry jednostki tekstury, trzeba najpierw wybrać bieżącą jed nostkę tekstury za pomocą funkcji g l A c t i v e T e x t u r e A R B ( ) zdefiniowanej w następujący sposób: void glActiveTextureARB(GLenum t e x i l n i t ) ;
Po wykonaniu tej funkcji wszystkie wywołania funkcji
g l T e x I m a g e * ( ), g l T e x P a r a m e t e r * ( ),
g l T e x E n v * ( ) , g l T e x G e n * ( ) i g l B i n d T e x t u r e O dotyczyć będą jednostki tekstury określo nej przez parametr texilnit. Parametr texilnit może posiadać wartość GL_TEXTURE7_ARB,
gdzie 7 jest liczbą całkowitą z przedziału od O do wartości o jeden mniejszej od dopusz czalnej liczby jednostek tekstur. Na przykład GL TEXTUREO ARB oznacza pierwszą z do stępnych jednostek tekstur. Liczbę jednostek tekstur dopuszczalną dla danej implemen tacji OpenGL uzyskać można wykonując poniższy fragment kodu: i n t maxTextU nits; / / maksymalna lic z b a jednostek t e k s t u r glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB. &maxTextUnits);
Jeśli w wyniku wywołania funkcji g l G e t I n t e g e r v ( ) uzyskana zostanie wartość 1, ozna czać to będzie, że dana implementacja nie umożliwia tworzenia tekstur wielokrotnych. Jednostki tekstur można konfigurować za pośrednictwem obiektów tekstur. Informacja, którą zawiera obiekt tekstury, jest automatycznie przechowywana z jednostką tekstury. Aby korzystać z jednostek tekstur, wystarczy więc — podobnie jak w przypadku zwy kłych tekstur — utworzyć obiekt tekstury, a następnie aktywować jednostkę tekstury za pomocą funkcji g l A c t i v e T e x t u r e A R B ( ). Następnie należy związać obiekty tekstur z od powiadającymi im jednostkami tekstur. Ilustruje to następujący fragment kodu: / / o b ie k ty t e k s t u r in t te x 0 b je c ts [2 ]; glG enTextures(2, te x O b je c ts ) ; / / tworzy dwa o b ie k ty t e k s t u r
Rozdział 9 . ♦ Zaaw ansow ane odwzorowania te k s tu r
241
/ / tw o rzy pierw szą te k s tu rę glBindTexture(GL_TEXTURE_2D, te x 0 b je c ts [0 ] ) ; g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MI N_FI LTER, GL_LINEAR); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); g lT e x E n v i(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); g lu B u i 1d2DMipmaps(GL_TEXTURE_2D, GL_RGB, 64, 64, GL_RGB, GL_UNSIGNED_BYTE, texO ne); / / tw o rzy drugą te k s tu rę glBindTexture(GL_TEXTURE_2D, t e x O b je c t s [ l] ) ; g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MI N_FILTER, GL_LINEAR); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); g lT e x E n v i(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); gl uBui 1d2DMipmaps(GL_TEXTURE_2D, GL_RGB, 64, 64, GL_RGB, GL_UNSIGNED_BYTE, texTw o); / / tw o rzy je d n o s tk i te k s tu r za pomocą obiektów te k s tu r glActiveTextureARB(GL_TEXTUREO_ARB); gl Enabl e(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, s m ile T e x -> te x ID ); g lT e x E n v i(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); g1A ct i veT e xtu reARB( GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, c h e c k e rT e x -> te x ID ); g lT e x E n v i(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
Powyższy fragment kodu tworzy najpierw dwa obiekty tekstur, a następnie kreuje za ich pomocą dwie jednostki tekstur używane do tworzenia tekstur wielokrotnych.
Określanie współrzędnych tekstury Teraz, gdy wiadomo już, w jaki sposób tworzyć jednostki tekstur, należy zapoznać się ze sposobem ich użycia. Jako że pojedynczy wielokąt będzie więcej niż jedną teksturą, trzeba zdefiniować dla niego także więcej niż jeden zestaw współrzędnych tekstury. Dla każdej jednostki tekstury należy więc dysponować osobnym zbiorem współrzędnych tekstury i zastosować je dla każdego wierzchołka z osobna. Aby określić współrzędne tekstury dwuwymiarowej można zastosować funkcję glMultiTexCoord2fARB() zdefinio waną w następujący sposób: v o id glMultiTexCoord2fARB(GLenum texUnit. f l o a t coords );
Inne wersje tej funkcji umożliwiają określenie współrzędnych tekstu jedno-, trój- oraz cztero wy miarowych także za pomocą wartości innych typów niż float. Na przykład funkcja g!MultiTexCoord3dARB() pozwala określić współrzędne tekstury trójwymiaro wej za pomocą wartości typu double. Korzystając z tych funkcji należy podać najpierw jednostkę tekstury, a następnie jej współrzędne. Oto przykład: gl Mul t i TexCoord2fARB(GL_TEXTURE0_ARB, O.Of, O .O f);
Powyższe wywołanie funkcji nadaje bieżącemu wierzchołkowi współrzędne (0.0, 0.0) pierwszej jednostki tekstur. Podobnie jak w przypadku funkcji glTexCoord2f() trzeba
242
Część II ♦ Korzystanie z OpenGL
określić najpierw współrzędne wszystkich używanych jednostek tekstur, a dopiero po tem zdefiniować wierzchołek. Ilustruje to poniższy fragment kodu: glB egi n(GL_QUADS); / / lew y, d o ln y w ie rz c h o łe k glMultiTexCoord2fARB(GL_TEXTURE0_ARB, O.Of, O .O f); glMultiTexCoord2fARB(GL_TEXTUREl_ARB. O.Of, O .O f); g lV e r te x 3 f( - 5 .0 f, - 5 .0 f , - 5 . 0 f ) ; / / prawy, d o ln y w ie rz c h o łe k glMultiTexCoord2fARB(GL_TEXTURE0_ARB, l. O f , O .O f); glMultiTexCoord2fARB(GL_TEXTUREl_ARB. l. O f, O .O f); g lV e r te x 3 f( 5 .0 f, - 5 .0 f, - 5 . 0 f ) ; / / prawy, górny w ie rz c h o łe k glMultiTexCoord2fARB(GL_TEXTURE0_ARB, l. O f, l. O f ) ; glMultiTexCoord2fARB(GL_TEXTUREl_ARB, l. O f, l. O f ) ; g lV e r t e x 3 f ( 5 . 0 f, 5 .0 f, - 5 . 0 f ) ; / / lew y. górny w ie rz c h o łe k glMultiTexCoord2fARB(GL_TEXTURE0_ARB. O.Of, l. O f ) ; glMultiTexCoord2fARB(GL_TEXTUREl_ARB, O.Of, l. O f ) ; g lV e r t e x 3 f ( - 5 . 0 f. 5 .0 f, - 5 . 0 f ) ; g lE n d O ;
Fragment ten rysuje kwadrat pokryty dwiema teksturami. Dla każdego wierzchołka de finiuje współrzędne obu tekstur. Należy pamiętać, że przy stosowaniu tekstur wielokrotnych funkcja glTexCoord2f () po zwala na zdefiniowanie współrzędnych tekstury tylko dla pierwszej jednostki tekstury. W takim przypadku wywołanie funkcji glTexCoord2f() jest więc równoważne wywoła niu glMultiTexCoord2f(GL_TEXTUREO_ARB, . .. ) .
Przykład zastosowania tekstur wielokrotnych Teraz analizie zostanie poddany kompletny przykład programu, który pokrywa sześcian dwiema teksturami pokazanymi na rysunku 9.2. Rysunek 9.2. Dwie tekstury, którymi pokryty zostanie sześcian
A oto pierwszy fragment kodu programu: / / / / / / D e fin ic je # d e fin e BITMAPJD 0x4D42 # d e fin e PI 3.14195 I l i ! U In clu d e s # in c iu d e # in c lu d e < s td io .n > # in c lu d e < s t d lib . h> # in c lu d e
/ / id e n ty fik a to r form atu BMP
/ / standardowy p lik nagłówkowy Windows
Rozdział 9. ♦ Zaaw ansow ane odwzorowania te ks tu r
# in c lu d e < g l/g l.h > # in c lu d e < g l/g lu .h > # in c lu d e " g le x t.h "
2 43
/ / standardowy p lik nagłówkowy OpenGL / / p lik nagłówkowy b ib lio t e k i GLU I I p lik nagłówkowy rozszerzeń OpenGL
U U I I Typy ty p e d e f s tr u c t
// // // //
szerokość te k s tu ry wysokość te k s tu ry o b ie k t te k s tu ry dane te k s tu ry
bool keyP re sse d [2 5 6 ]; f lo a t angle = O.Of; f lo a t ra d ia n s = O.Of;
// // // // // //
g lo b a ln y ko n te k st urządzenia tru e = tr y b pełnoekranowy fa ls e = tr y b okienkowy ta b lic a p rz y c iś n ię ć k la w isz y k ą t o b ro tu ką t o b ro tu kamery w radianach
/ / / / / / Zmienne myszy i kamery in t mouseX. mouseY; f lo a t cameraX, cameraY, cameraZ; f l o a t lo o k X ( lookY, lookZ;
/ / w spółrzędne myszy / / współrzędne kamery I I punkt wycelowania kamery
i n t w id th ; i n t h e ig h t; unsigned i n t te x ID ; unsigned char *d a ta ; } te x tu r e _ t; / / / / / / Zmienne g lo b a ln e HDC g_HDC; bool f u l l Screen = fa ls e ;
/ / / / / / Zmienne te k s tu r te x tu re _ t *s m ile T e x ; te x tu re _ t *checkerT ex; / / / / / / Zmienne związane z te k s tu ra m i w ie lo k ro tn ym i PFNGLMULTITEXC00RD2FARBPR0C glM ultiTexC oord2fAR B = NULL; PFNGLACTIVETEXTUREARBPROC glA ctiveT extureA R B = NULL; PFNGLCLIENTACTIVETEXTUREARBPROC g lC lie n tA c tiv e T e x tu re A R B = NULL; i n t m axTextureU nits = 0 ; / / dopuszczalna lic z b a je d n o ste k te k s tu r
Plik nagłówkowy glext.h należy dołączać wtedy, gdy korzysta się z jakichkolwiek roz szerzeń OpenGL zatwierdzonych przez ARB. Plik ten zawiera definicje wszystkich roz szerzeń. Jego kopia umieszczona została także na dysku CD. Powyższy fragment kodu prezentuje także sposób wykorzystania struktur do przecho wywania informacji o teksturze i jej danych. Typ te x tu re _ t umożliwia zapamiętanie obiektu tekstury, jej szerokości i wysokości oraz danych. Upraszcza to proces ładowania i stosowania tekstur w OpenGL. Omawiany program będzie umożliwiać sterowanie kamerą OpenGL w podobny sposób do programu prezentującego rzeźbę terenu, który przedstawiony został w rozdziale 8. Gdy mysz będzie poruszać się poziomo, sześcian będzie obracany wokół osi y. Nato miast przy wykonywaniu pionowych ruchów myszą, można spowodować oddalanie się bądź przybliżanie kamery do sześcianu. Pierwszą z funkcji, którą definiuje program, jest omówiona już wcześniej funkcja In S tr ( ). Zwraca ona wartość tru e , jeśli łańcuch s e a rch S tr zostanie odnaleziony wewnątrz łań cucha s t r , który zawiera wiele nazw oddzielonych znakami spacji.
244
Część II ♦ Korzystanie z OpenGL
bool In S tr(c h a r *s e a rc h S tr, char * s t r )
{ char * e n d O fS tr;// wskaźnik o s ta tn ie g o znaku łańcucha in t id x = 0; endO fStr = s t r + s t r l e n ( s t r ) ; / / o s ta tn i znak łańcucha / / p ę tla wykonywana aż do o s ią g n ię c ia końca łańcucha w h ile ( s t r < endO fS tr)
{ / / od n a jd u je p o ło ż e n ie ko le jn e g o znaku s p a c ji id x = s tr c s p n ( s tr . " " ) ; / / sprawdza, czy s t r wskazuje poszukiwaną nazwę i f ( ( s trle n (s e a rc h S tr ) == id x ) && (s trn c m p (s e a rc h S tr, s t r , id x ) == 0 ))
{ re tu rn tr u e ;
} / / n ie je s t to poszukiwana nazwa, przesuwamy wskaźnik do k o le jn e j nazwy s t r += ( id x + 1 );
}
re tu rn fa ls e ;
} W celu umożliwienia korzystania z tekstur wielokrotnych program definiuje funkcję InitMul tiT e x ( ), która sprawdza, czy dostępne jest rozszerzenie o nazwie "GL_ARB_mult i te x tu re ", a ponadto pozyskuje wskaźniki funkcji rozszerzeń. Funkcja In itM u ltiT e x ( ) zwraca war tość fa l se, jeśli tworzenie tekstur wielokrotnych nie jest dostępne. bool In itM u lt iT e x ( )
{
char * e x te n s io n S tr ;// l i s t a dostępnych rozszerzeń e x te n s io n S tr = (char*)glG etString(G L_EXTEN SIO N S); i f (e x te n s io n S tr == NULL) re tu rn fa ls e ; i f (In S tr("G L _ A R B _ m u ltite x tu re ", e x te n s io n S tr))
{ / / zwraca dopuszczalną lic z b ę je d n o ste k te k s tu r glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, & m a xT e xtu re U n its); / / pobie ra adresy fu n k c ji rozszerzeń glM ultiTexC oord2fAR B = ( PFNGLMULTITEXC00RD2FARBPR0C) w glG etP rocA ddress(Mg lM u ltiT e x C o o rd 2 fA R B "); g lA c ti veTextureARB = (PFNGLACTIVETEXTUREARBPROC) w g lG e tP ro cA d d re ssC 'g lA ctive T e xtu re A R B "); g lC l ie n t A c t iveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) w glG etProcAddress( "g l Cl i e n tA c ti veTextureAR B"); re tu rn tr u e ;
} e ls e re tu rn fa ls e ;
Rozdział 9. ♦ Zaaw ansow ane odwzorowania te ks tu r
245
Kolejna funkcja, L o a d T e x tu re F ile ( ), ładuje pojedynczą teksturę i umieszcza jej opis w strukturze typu te x tu re t i zwraca wskaźnik tej struktury. Parametrem funkcji LoadT e x tu r e F ile ( ) jest nazwa pliku. W celu załadowania jego zawartości wywołuje ona funkcję LoadBitmapFi 1e ( ) i umieszcza informacje o załadowanym obrazie w strukturze typu te x tu r e t . Funkcja LoadTextureFi l e ( ) tworzy także obiekt tekstury. t e x t u r e jt *L o a d T e x tu re F ile (c h a r ^ file n a m e )
{
BITMAPINFOHEADER te x ln fo ; te x tu re _ t * th is T e x tu r e ; / / p rz y d z ie la pamięć s tru k tu rz e typ u t e x t u r e jt th is T e x tu re = ( t e x t u r e jt* ) m a llo c ( s iz e o f ( t e x tu r e _ t) ); i f ( th is T e x tu re == NULL) re tu rn NULL; / / ła d u je obraz te k s tu r y i sprawdza poprawne wykonanie t e j o p e ra c ji th is T e x tu re -> d a ta = L o a d B itm a p F ile (file n a m e , & te x In fo ); i f (th is T e x tu re -> d a ta == NULL)
{
f r e e ( th is T e x tu r e ) ; re tu rn NULL;
} / / umieszcza in fo rm a c je o szerokości i wysokości te k s tu ry th is T e x tu re -> w id th = t e x ln f o . b iW id th ; th is T e x tu re -> h e ig h t = t e x ln f o . b iH e ig h t; / / tw o rzy o b ie k t te k s tu ry g lG e n T e x tu re s G . & th is T e x tu re -> te x ID ); re tu rn th is T e x tu re ;
} Jako że opanowana już została umiejętność ładowania tekstury, można zapoznać się z ko lejną funkcją, która może załadować wszystkie tekstury używane w programie i skonfi gurować je tak, by możliwe było ich wykorzystanie do tworzenia tekstur wielokrotnych. Funkcja LoadAł 1T e x tu re s () ładuje wszystkie tekstury, określa ich parametry, tworzy mipmapy i wiąże obiekty tekstur z jednostkami tekstur. bool LoadAlIT e x tu re s O
{ / / ła d u je pierw szą te k s tu rę sm ileTex = L o a d T e x tu re F ile C 's m ile .b m p "): i f (sm ileT e x == NULL) re tu rn fa ls e ; / / ła d u je drugą te k s tu rę checkerTex = L o a d T e x tu re F ile C 'c h e ss . b m p " ) ; i f (checkerTex == NULL) r e tu rn fa ls e ; / / tw o rzy mipmapy z filtro w a n ie m lin io w ym d la p ie rw s z e j te k s tu ry glBindTexture(GL_TEXTURE_2D, s m ile T e x -> te x ID ); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FI LTER, G LLINEAR ); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MI N_FILTER. GL_LINEAR);
246
Część II ♦ Korzystanie z OpenGL
g lT e x E n v i( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE. GL_REPLACE); gluBuild2DMipmaps(GL_TEXTURE_2D. GL_RGB, sm ile T e x-> w id th , s m ile T e x -> h e ig h t, GL_RGB, GLJJNSIGNEDJ3YTE. s m ile T e x -> d a ta ); / / tw o rzy mipmapy z filtro w a n ie m lin io w ym d la d r u g ie j te k s tu r y glBindTexture(GL_TEXTURE_2D. c h e ck e rT e x-> te x ID ); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FI LTER, G LLIN E AR ); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); g lT e x P a ra m e te ri(GL_TEXTURE_2D. GL_TEXTURE_WRAP_S, GL_REPEAT); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GLREPEAT); g lT e x E n v i(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, ch e cke rT e x-> w id th , c h e ck e rT e x-> h e ig h t, GL_RGB. GL_UNSIGNED_BYTE, c h e ck e rT e x-> d a ta ); / / w ybiera pierw szą je d n o s tk ę te k s tu ry g 1A ct i veT extu reARB(GL_TEXTUREO_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, s m ile T e x -> te xID ); / / wiąże je d n o stkę z pierwszą te k s tu rą / / w ybiera drugą je d n o s tk ę te k s tu ry glActiveTextureARB(GL_TEXTUREl_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, ch e ck e rT e x-> te x IO ); / / w iąże je d n o s tk ę z drugą te k s tu rą re tu rn tr u e ;
} Funkcja In itia l iz e () inicjuje dane programu: v o id I n i t i a l i z e O
{ g lC le a r C o lo r ( 0 .0 f. O.Of,
O.Of. O .O f); / / t ł o w czarnym k o lo rz e
glShadeModel(GL_SM00TH); / / c ie n io w a n ie g ła d k ie glEnable(GL_DEPTH_TEST); / / usuwanie u k ry ty c h p o w ie rzch n i glEnable(GL_CULL_FACE); / / brak o b lic z e ń d la niew idocznych s tro n w ie lo k ą tó w glFrontFace(GL_CCW ); / / niew idoczne s tro n y p o s ia d a ją porządek w ie rzch o łkó w / / przeciw ny do k ie ru n ku ruchu wskazówek zegara glEnable(GL_TEXTURE_2D); / / w łącza te k s tu ry dwuwymiarowe In itM u lt iT e x O ; L o a d A llT e x tu re s O ;
} Sześcian narysować można za pomocą rozbudowanej wersji funkcji DrawCubeO znanej z poprzednich przykładów. Będzie ona korzystać z funkcji glMultiTexCoord2f() w miej sce funkcji glTexCoord2f() w celu zdefiniowania współrzędnych jednostek tekstur dla każdego wierzchołka sześcianu: vo id DrawCube(f lo a t xPos, f l o a t yPos, f lo a t zPos)
{ g lP u s h M a trix O ; g lT ra n s la te f(x P o s , yPos, zPos); glBegin(GL_QUADS); g lN o rm a !3 f(0 .0 f, l. O f , O .O f);
/ / górna ściana
Rozdział 9. ♦ Zaaw ansow ane odwzorowania te ks tu r
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, l. O f, O .O f); glMultiTexCoord2fARB(GL_TEXTUREl_ARB, l. O f , O .O f); g !V e r te x 3 f( 0 .5 f, 0 .5 f, 0 .5 f ) ; gl M u lti TexCoord2fARB(GL_TEXTURE0_ARB, l. O f, l. O f ) ; g l M u lti TexCoord2fARB(GL_TEXTUREl_ARB, l. O f, l. O f ) ; g lV e r te x 3 f( 0 .5 f, 0 .5 f, - 0 . 5 f ) ; g1Mu11iTexCoord2fARB(GL_TEXTUREO_ARB, O.Of, l. O f ) ; g 1Mu11iTexCoord2fARB( GL_TEXTURE1_ARB, O.Of, l. O f ) ; g lV e r te x 3 f( - 0 .5 f, 0 .5 f, - 0 . 5 f ) ; g l M u lti TexCoord2fARB(GL_TEXTURE0_ARB, O.Of, O .O f); g l M u lti TexCoord2fARB(GL_TEXTUREl_ARB, O.Of, O .O f); g lV e r te x 3 f( - 0 .5 f, 0 .5 f, 0 .5 f ) ; g lE n d O ; glBegin(GL_QUADS); g lN o rm a l3 f(0 .0 f, O.Of, l. O f ) ; / / p rzednia ściana g l M u lti TexCoord2fARB(GL_TEXTURE0_ARB, l. O f , l. O f ) ; g 1Mu11iTexCoord2fARB( GL_TEXTURE1_ARB, l. O f, l. O f ) ; g lV e r t e x 3 f ( 0 . 5 f, 0 .5 f, 0 .5 f ) ; g l M u lti TexCoord2fARB(GL_TEXTURE0_ARB, O.Of, l. O f ) ; g 1Mu11iTexCoord2fARB(GL_TEXTURE1_ARB, O.Of, l. O f ) ; g lV e r te x 3 f( - 0 .5 f, 0 .5 f, 0 .5 f ) ; glMultiTexCoord2fARB(GL_TEXTURE0_ARB, O.Of, O .O f); gl M u lti TexCoord2fARB(GL_TEXTUREl_ARB, O.Of. O .O f); g lV e r t e x 3 f ( - 0 . 5 f, - 0 .5 f , 0 .5 f ) ; g l Mul tiTexCoord2fARB(GL_TEXTURE0_ARB, l. O f. O .O f); g l MultiTexCoord2fARB(GL_TEXTUREl_ARB, l. O f , O .O f); g lV e r t e x 3 f ( 0 . 5 f, - 0 .5 f , 0 .5 f ) ; g lE n d O ; gl Begi n(GL_QUADS); g lN o rm a l3 f( 1 . O f. O.Of, O .O f); / / prawa ściana gl Mul t i TexCoord2fARB(GL_TEXTURE0_ARB, O.Of, l . O f ) ; gl M u lti TexCoord2fARB(GL_TEXTUREl_ARB, O.Of, l. O f ) ; g lV e r t e x 3 f ( 0 . 5 f. 0 .5 f, 0 .5 f ) ; glMultiTexCoord2fARB(GL_TEXTURE0_ARB, O.Of, O .O f); g1Mu11iTexCoord2fARB(GL_TEXTURE1_ARB, O.Of, O .O f); g lV e r te x 3 f( 0 .5 f, - 0 .5 f , 0 .5 f ) ; glMultiTexCoord2fARB(GL_TEXTURE0_ARB, l. O f , O .O f); glMultiTexCoord2fARB(GL_TEXTUREl_ARB, l. O f . O .O f); g lV e r t e x 3 f ( 0 . 5 f, - 0 .5 f , - 0 . 5 f ) ; g 1Mu11iTexCoord2fARB( GL_TEXTUREO_ARB, l. O f, l. O f ) ; glMultiTexCoord2fARB(GL_TEXTUREl_ARB, l. O f , l. O f ) ; g lV e r t e x 3 f ( 0 . 5 f, 0 .5 f, - 0 . 5 f ) ; g lE n d O ; glBegin(GL_QUADS); g lN o rm a !3 f(- 1 .0 f, O.Of, O .O f); / / lewa ściana
2 47
248
Część II ♦ Korzystanie z OpenGL
glMultiTexCoord2fARB(GLJEXTURE0_ARB, l. O f . l. O f ) ; g 1MultiTexCoord2fARB(GL_TEXTURE1_ARB. l. O f. l. O f ) ; g lV e r te x 3 f( - 0 .5 f, 0 .5 f. 0 .5 f ) ; g 1Mu 11 iT exCoord2fARB(GL_TEXTUREO_ARB, O.Of, l. O f ) ; g 1Mu 11 iT exCoord2fARB(GLJTEXTURE1_ARB, O.Of. l. O f ) ; g lV e r te x 3 f( - 0 .5 f, 0 .5 f. - 0 . 5 f ) ; g 1Mu 11 iTexCoord2fARB(GL_TEXTUREO_ARB, O.Of. O .O f); glMultiTexCoord2fARB(GL_TEXTUREl_ARB, O.Of, O .O f); g lV e r t e x 3 f ( - 0 . 5 f, - 0 .5 f. - 0 . 5 f ) ; g 1Mu 11 iT exCoord2fARB(GL_TEXTUREO_ARB, l. O f . O .O f); g 1Mu 11 iT exCoord2fARB( GL_TEXTURE1_ARB. l. O f , O .O f); g lV e r te x 3 f( - 0 .5 f. - 0 .5 f . 0 .5 f ) ; g lE n d O ; glBegin(GL_QUADS); g lN o rm a l3 f(0 .0 f. - l. O f . O .O f); / / dolna ściana glMultiTexCoord2fARB(GL_TEXTURE0_ARB, l. O f . O .O f); g l M u lt iTexCoord2fARB(GL_TEXTUREl_ARB, l. O f, O .O f); g lV e r t e x 3 f ( - 0 . 5 f, - 0 .5 f , 0 .5 f ) ; glMultiTexCoord2fARB(GL_TEXTURE0_ARB, l. O f. l. O f ) ; glMultiTexCoord2fARB(GL_TEXTUREljARB, l. O f . l. O f ) ; g lV e r te x 3 f( - 0 .5 f, - 0 .5 f , - 0 . 5 f ) ; g 1Mu11 iT exCoord2fARB(GL_TEXTUREO_ARB, O.Of. l. O f ) ; glMultiTexCoord2fARB(GL_TEXTUREl_ARB, O.Of, l. O f ) ; g lV e r t e x 3 f ( 0 . 5 f, - 0 .5 f . - 0 . 5 f ) ; glMultiTexCoord2fARB(GL_TEXTURE0_ARB, O.Of, O .O f); glMultiTexCoord2fARB(GL_TEXTUREl_ARB, O.Of, O .O f); g lV e r te x 3 f( 0 .5 f, - 0 .5 f , 0 .5 f ) ; g lE n d O ; glBegin(GL_QUADS); g lN o rm a l3 f(0 .0 f, O.Of, - l. O f ) ; / / ty ln a ściana g 1Mu11 i TexCoo rd2fARB(GL_TEXTUREO_ARB, O.Of, O .O f); g 1Mu11 i TexCoo rd2fARB( GL_TEXTURE1_ARB, O.Of, O .O f); g lV e r t e x 3 f ( 0 . 5 f, - 0 .5 f , - 0 . 5 f ) ; g lM u lti TexCoord2fARB(GL_TEXTURE0_ARB, l. O f . O .O f); g 1Mu 11 i TexCoo rd2 fARB( GL _TEXTURE1_ARB. l. O f . O .O f); g lV e r te x 3 f( - 0 .5 f. - 0 .5 f , - 0 . 5 f ) : glMultiTexCoord2fARB(GL_TEXTURE0_ARB. l. O f, l. O f ) ; glMultiTexCoord2fARB(GL_TEXTUREl_ARB, l. O f . l. O f ) ; g lV e r t e x 3 f ( - 0 . 5 f, 0 .5 f, - 0 . 5 f ) ; glMultiTexCoord2fARB(GL_TEXTURE0_ARB. O.Of, l. O f ) ; g 1Mu 11 iT exCoord2fARB(GL_TEXTURE1ARB, O.Of, l. O f ) ; g lV e r t e x 3 f ( 0 . 5 f, 0 .5 f, - 0 . 5 f ) ; g lE n d O ; g lP o p M a trix O ;
Rozdział 9. ♦ Zaaw ansow ane odwzorowania te ks tu r
249
Większość kodu funkcji Render O znana jest z poprzednich przykładów. Określa ona po łożenie kamery i rysuje sześcian. v o id RenderO
{ ra d ia n s =
f lo a t( P I * ( a n g le - 9 0 . 0 f ) /1 8 0 . 0 f ) ;
/ / wyznacza p o ło ż e n ie kamery cameraX = lookX + s in (ra d ia n s)*m o u se Y ; cameraZ = lookZ + cos(radians)*m ouseY ; cameraY = lookY + mouseY / 2 . Of; / / w ycelow uje kamerę w punkt (0 ,0 ,0 ) lookX = 0 .O f; lookY = O.Of; lookZ = O.Of; / / o p ró żn ia b u fo ry ekranu i g łę b i gl Clear(GL_COLOR_BUFFER_BIT j GL_DEPTH_BUFFER_BIT); g ]L o a d Id e n tity ( ); / / umieszcza kamerę gluLookAt(cam eraX. cameraY, cameraZ, lookX, lookY, lookZ, 0 .0 , 1 .0 , 0 .0 ); / / s k a lu je sześcian do rozm iarów 15x15x15 gl S c a le f(1 5 .0 f. 1 5 .Of, 1 5 .O f); / / ry s u je sześcian o środku w punkcie (0 ,0 ,0 ) D raw C ube(0.0f, O.Of, O .O f); SwapBuffers(g_HDC);
/ / p rze łą cza b u fo ry
} Po umieszczeniu omówionych funkcji w szkielecie standardowej aplikacji systemu Win dows uzyskany zostanie program, którego działanie ilustruje rysunek 9.3. Rysunek 9.3. Przykład zastosowania tekstury wielokrotnej
250
Część II ♦ Korzystanie z OpenGL
Tekstury wielokrotne mogą być źródłem doskonałych efektów i dlatego z pewnością warto z nimi poeksperymentować trochę więcej. Pora jednak na omówienie kolejnego doskonałego narzędzia tworzenia zaawansowanych efektów graficznych: odwzorowania otoczenia.
Odwzorowanie otoczenia Odwzorowanie otoczenia polega na stworzeniu obiektu, którego powierzchnia wydaje się odbiciem jego otoczenia. Na przykład doskonale wypolerowana srebrna kula odbijać będzie na swojej powierzchni elementy otaczającego ją świata. Właśnie taki efekt można uzyskać stosując odwzorowanie otoczenia. W tym celu nie trzeba jednak wcale tworzyć obrazu odbicia otoczenia na powierzchni efektu. W OpenGL wystarczy jedynie utwo rzyć teksturę reprezentującą otoczenie obiektu, a OpenGL automatycznie wygeneruje odpowiednie współrzędne tekstury. Zmiana położenia obiektu wymaga wyznaczenia nowych współrzędnych tekstury, by można było uzyskać efekt odbicia zmieniającego się razem z poruszającym się obiektem. Najlepszy efekt zastosowania odwzorowania otoczenia uzyskać można używając tekstur utworzonych za pomocą „rybiego oka”. Zastosowanie takich tekstur nie jest absolutnie konieczne, ale znacznie zwiększa realizm odwzorowania otoczenia. Współrzędne tek stury można też obliczać indywidualnie, jednak zmniejsza to zwykle efektywność two rzenia grafiki. Aby skorzystać w OpenGL z możliwości odwzorowania otoczenia, niezbędny jest po niższy fragment kodu: glTexGenf(GL_S, GL_TEXTURE_GEN_MODE. GL_SPHERE_MAP); glTexGenf(GL_T, GL_TEXTURE_GEN_MODE. GL_SPHERE_MAP); glEnable(GL_TEXTURE_GEN_S); g lE n a b le ( GL_TEXTURE_GEN_T); / / n a stę p n ie w ybiera te k s tu rę o to c z e n ia i ry s u je o b ie k t
I to wszystko! Teraz należy przeanalizować przykład zastosowania możliwości odwzo rowania otoczenia.
Torus na niebie Omówiony teraz zostanie przykład zastosowania możliwości odwzorowania otoczenia w programie, który rysować będzie torus pokryty teksturą otoczenia. Rysunek 9.4 przed stawia końcowy efekt działania programu. Teksturę otoczenia utworzyć można na podstawie tekstury nieba, do której zastosowany zostanie efekt rybiego oka za pomocą dowolnego edytora obrazów dysponującego taką możliwością. Rysunek 9.5 przedstawia otrzymaną teksturę otoczenia.
Rozdział 9 . ♦ Zaaw ansow ane odwzorowania te k s tu r
251
Torus pokryty teksturą otoczenia
Rysunek 9.5. Tekstura otoczenia uzyskana na podstawie tekstury nieba
Teraz należy przyjrzeć się kodowi programu. Poniżej przedstawione zostały jego frag menty związane z tworzeniem tekstur i rysowaniem grafiki: ty p e d e f s tr u c t
{
i n t w id th ; i n t h e ig h t; unsigned i n t te x ID ; unsigned char *d a ta ; } te x tu r e _ t;
// // // //
szerokość te k s tu ry wysokość te k s tu ry o b ie k t te k s tu ry dane te k s tu ry
f l o a t a ngle = 0 . O f; te x tu r e _ t *envTex; t e x t u r e jt *skyTex;
/ / k ą t o b ro tu to ru s a / / te k s tu ra o to cze n ia / / te k s tu ra nieba
bool L o a d A llT e x tu re s ()
{
/ / ła d u je obraz te k s tu r y o to c z e n ia envTex = L o a d T e x tu re F ile C sk y -s p h e re .b m p ") ; i f (envTex == NULL) r e tu rn fa ls e ; skyTex = L o a d T e x tu re F ile C 's k y .b m p "); i f (skyTex == NULL) re tu rn fa ls e ;
252
Część II ♦ Korzystanie z OpenGL
// tworzy mipmapy z filtrow aniem liniowym dla te k stu ry otoczenia glBindTexture(GL_TEXTURE_2D. en vT ex->texID ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL L IN EA R ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T. GL_REPEAT); g lu B u i1d2DMipmaps(GL_TEXTURE_2D, GL_RGB, envTex->width, envT ex->height, GL^RGB, GL_UNSIGNED_BYTE, envTex->data); // tworzy mipmapy z filtrow aniem liniowym dla te kstu ry nieba glBindTexture(GL_TEXTURE_2D, skyT e x-> te xID ); glTexParameteri(GL_TEXTURE 2D, GL_TEXTURE_MAG_FILTER, GL LINEAR); glTexParameteri(GL_TEXTURE~2D, GL_TEXTURE_MIN_FILTER, GL~LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S. GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); gl uBui 1d2DMi pmaps(GL_TEXTURE_2D, GL_RGB, skyTex->width, skyT e x-> h e ign t, GL_RGB, GL_UNSIGNED_BYTE, skyT ex->d ata ); return true;
void RenderO r
// zwiększa kąt obrotu i f (angle > 360.Of) angle = O.Of; angle = angle + 0.2f; // opróżnia Dufory ekranu i głębi glC l e a r(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d Id e n t it y (); glBindTexture(GL_TEXTURE_2D( skyT e x-> te xID ); g l B e gin(GL_QUADS); glTexC oord2f(0 .O f, O.Of); g lV e r te x 3 f(-2 0 0 .0 f, -2 00 .Of. -1 20 .Of); glTexC oord2f(1 .O f, O.Of); g lV e rte x 3 f(2 0 0 .0f, -2 0 0 .Of. -1 20 .Of); glTexC oord2f(1 .Of, 1 .O f ) ; g lV e r te x 3 f(2 0 0 .0 f, 200.Of, -1 20 .Of); glT e x C o o rd 2 f(0 .0 f. l.O f); g lV e r te x 3 f(-2 0 0 .0 f, 200.Of, -1 20 .Of); glEnd C): // odsuwa obiekt i obraca go względem w szystkich osi układu współrzędnych g lT r a n s la t e f ( 0 . 0 f , O.Of, -1 00 .Of); g lR o ta te f(a n g le , l.O f, O.Of. O.Of); g lR o ta te f(a n g le . O.Of. l.O f, O.Of); g lR o ta te f(a n g le , O.Of. O.Of, l.O f); // k onfigu ru je odwzorowanie otoczenia glTexGenf(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glTexGenf(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE MAP); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T);
Rozdział 9 . ♦ Zaaw ansow ane odwzorowania te ks tu r
253
// wybiera te kstu rę otoczenia glBindTexture(GL_TEXTURE_2D, en vT ex->texID ); // rysu je to ru s o promieniu wewnętrznym równym 10 jednostek // i promieniu zewnętrznym 20 jednostek a u x S o lid T o ru s (1 0 .0 f, 20 .Of); g lF lu s h O ; Sw apBuffers(g_HDC);// przełącza bufory
} Jak łatwo zauważyć, stosowanie odwzorowania otoczenia w OpenGL jest niezwykle proste, o ile pozwoli się maszynie OpenGL automatycznie wyznaczać współrzędne tek stury. Współrzędne te można obliczać indywidualnie, aby uzyskać inne efekty specjalne, ale zagadnienie to nie będzie tutaj omawiane.
Macierze tekstur W rozdziale 5. omówione zostały sposoby wykonywania przekształceń wierzchołków, takich jak przesunięcie, obrót i skalowanie za pomocą macierzy modelowania. Przed stawiona została także koncepcja stosu macierzy umożliwiająca hierarchiczne tworzenie grafiki. Takie same możliwości w przypadku tekstur OpenGL udostępnia za pomocą macierzy tekstur i stosu macierzy tekstur. Na przykład funkcję glTranslatef () można zastosować do przesunięcia tekstury na odpowiednią powierzchnię. Podobnie funkcję glRotatefO można wykorzystać do obrotu układu współrzędnych tekstury i uzyskać w efekcie obrót tekstury. Gra „American McGee’s Alice” firmy Electronic Arts and Rogue Entertain ment jest najlepszym przykładem niezwykłych efektów, jakie można osiągnąć poprzez manipulacje macierzą tekstur. Wykonywanie operacji na macierzach tekstur jest bardzo łatwe. Stosuje się w tym celu udostępniane przez OpenGL funkcje glMultMatrix(), glPushMatnx(), glPopMatrix() oraz funkcje przekształceń. Najpierw jednak trzeba poinformować maszynę OpenGL, że wykonywane będą operacje na macierzy tekstur: glMatrixMode(GL_TEXTURE);
Od tego momentu na macierzy tekstur i stosie macierzy tekstur można wykonywać do wolne operacje. Po ich zakończeniu należy polecić maszynie OpenGL powrót do ma cierzy modelowania, aby zamiast tekstur przekształcać obiekty. Poniżej zaprezentowany został fragment kodu ilustrujący sposób wykonywania obrotu tekstury: // opróżnia bufory ekranu i głębi gl Cl e a r(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n t it y O ; // wybiera tryb operacji na macierzy te kstu r glMatrixMode(GL_TEXTURE); g lL o a d ! d e n t ity ();
254
Część II ♦ Korzystanie z OpenGL
g lR o ta t e f( a n g le , O.Of, O.Of, l . O f ) ; / / obraca te k s tu r ę / / przywraca t r y b o p e r a c ji na macierzy modelowania g1Mat r i xMode( GL_M0DELVI EW); glBindTexture(GL_TEXTURE_2D. t e x I D ) ; / / wybiera te k s tu r ę / / ry s u je czworokąt p o k ry ty te k s tu r ą glBegin(GL_QUADS); g lT e x C o o r d 2 f (0 . 0 f, O.Of); g l V e r t e x 3 f ( - 2 0 . 0 f , - 2 0 . Of. - 4 0 . Of); g lT e x C o o r d 2 f ( 1 . 0 f, O.Of); g lV e r t e x 3 f ( 2 0 . 0 f , - 2 0 . Of. - 4 0 . O f); g lT e x C o o r d 2 f (1 . 0 f, l . O f ) ; g lV e r t e x 3 f ( 2 0 . 0 f , 2 0 .Of. - 4 0 . O f); g lT e x C o o r d 2 f (0 . 0 f, l . O f ) ; g l V e r t e x 3 f ( - 2 0 .0 f . 2 0 .Of. - 4 0 . O f); gl EndO ;
Kod ten wybiera najpierw macierz tekstury jako bieżącą macierz. Następnie ładuje do niej macierz jednostkową, zanim zastosuje do bieżącej macierzy funkcję obrotu g l R o t a t e f ( ) . Na skutek jej użycia tekstura zostaje obrócona o pewien kąt względem osi z. Po wykonaniu obrotu wybiera macierz modelowania jako bieżącą macierz i rysuje czworo kąt pokryty obróconą teksturą. Proste, prawda? Gdyby po czworokącie zostały narysowane kolejne obiekty, to także zostałyby one po kryte obróconą teksturą. Rozwiązanie tego problemu zaprezentowane zostało poniżej: / / opróżnia b u fo ry ekranu i g łę b i gl Cl ear(GL_COLOR_BUFFER_BIT | Gl_DEPTH_BUFFER_BIT); g lL o a d ld e n tity O ; / / wybiera t r y b o p e r a c ji na macierzy t e k s t u r glMatrixMode(GL_TEXTURE); g l L o a d l d e n t i t y i ); g lR o ta t e f( a n g le , O.Of. O.Of, l . O f ) ; / / obraca te k s tu r ę / / przywraca t r y b o p e r a c ji na macierzy modelowania g1MatrixMode( GL_M0DELVI EW); glBindTexture(GL_TEXTURE_2D, t e x I D ) ; / / wybiera t e k s tu r ę / / r y s u je czworokąt p o k ry ty te k s tu r ą g1Beg i n (GL _QUADS); g lT e x C o o r d 2 f (0 . 0 f, O.Of); g l V e r t e x 3 f ( - 2 0 . O f, - 2 0 . Of. - 4 0 . Of); g lT e x C o o rd 2 f(1 .0 f. O.Of); g l V e r t e x 3 f ( 2 0 . 0 f , - 2 0 . Of, - 4 0 . O f); g lT e x C o o r d 2 f ( l. 0 f . l . O f ) ; g lV e r t e x 3 f ( 2 0 . 0 f , 2 0 .Of, - 4 0 . O f); g lT e x C o o r d 2 f (0 . 0 f, l . O f ) ; g l V e r t e x 3 f ( - 2 0 . 0 f , 2 0 .Of. - 4 0 . O f); glE n d ( ) ; / / re s e t u je macierz t e k s t u r d la następnych obiektów glMatrixMode(GL_TEXTURE); g lL o a d ld e n t it y O ; g 1Mat rixMode( GL_M0DELVI EW); / / ry s u je następne o b ie k ty
Rozdział 9. ♦ Zaaw ansow ane odwzorowania te k s tu r
255
Wystarczy jedynie załadować macierz jednostkową do macierzy tekstur, aby wykonane przekształcenia tekstury nie miały wpływu na sposób rysowania tekstur na kolejnych obiektach. Warto wypróbować różne przekształcenia tekstur i uzyskać nowe, ciekawe efekty, które można będzie zastosować w grach.
Mapy oświetlenia Zadaniem map oświetlenia jest symulacja statycznego oświetlenia powierzchni. Mapa oświetlenia jest teksturą, która opisuje sposób oświetlenia powierzchni. Mapy oświetle nia znajdują coraz powszechniejsze zastosowanie dzięki wprowadzeniu sprzętowej ob sługi tekstur wielokrotnych. Choć mapy oświetlenia stosowane są głównie w celu uzy skania efektu statycznego oświetlenia, można je wykorzystać także do tworzenia cieni. Rysunek 9.6 przedstawia mapę oświetlenia symulującą efekt strumienia światłą padają cego na powierzchnię pod kątem prostym. Jeśli pokryty nią zostanie wielokąt, to uzyskany zostanie efekt oświetlenia strumieniem światła prostopadłym do jego powierzchni. Rysunek 9.6. Przykład mapy oświetlenia
Mapy oświetlenia są teksturami wykorzystującymi jedynie odcienie szarości. Aby uzy skać efekt oświetlenia, mapę taką należy połączyć z teksturą pokrywającą wielokąt.
Stosowanie map oświetlenia Mapę oświetlenia stosuje się na przykład do uzyskania efektu oświetlenia sześcianu po krytego teksturą szachownicy. Trzeba nałożyć ją na każdą ze ścian sześcianu, by uzy skać efekt pokazany na rysunku 9.7. Rysunek 9.7. Zastosowanie mapy oświetlenia
256
Część II ♦ Korzystanie z OpenGL
Ładując mapę oświetlenia z pliku należy zadbać o to, by jej piksele opisane były za po mocą odcieni szarości. Poniżej zamieszczona została zmodyfikowana wersja fiinkcji LoadBitmapFi l e ( ) ładującej mapę oświetlenia z 24-bitowego pliku BMP jako obraz w od cieniach szarości: unsigned char *LoadG rayBitm ap(char *file n a m e . BITMAPINFOHEADER *bitm apInfoH eader)
{ FILE * f i l e P t r ; BITMAPFILEHEADERbitmapFileHeader; unsigned char*bitm aplm age; in tim a g e ld x = 0; unsigned chartempRGB;
// // // // //
unsigned char in t
/ / obraz w o d c ie n ia ch sza ro śc i / / lic z n ik p ik s e li w o d cie n ia ch s za ro ści
*graylm age; g ra y ld x ;
wskaźnik p o z y c ji p lik u nagłówek p lik u dane obrazu lic z n ik p ik s e li zmienna zamiany składowych
/ / o tw ie ra p lik w tr y b ie "read b in a r y ” f i l e P t r = fo p e n (file n a m e , " r b " ) ; i f ( f i l e P t r == NULL) re tu rn NULL; / / w cz y tu je nagłówek p lik u fre a d (& b itm a p F ile H e a d e r. sizeof(BITMAPFILEHEADER), 1, f i l e P t r ) ; / / sprawdza, czy je s t to p lik form atu BMP i f (b itm a p F ile H e a d e r.b fT yp e != BITMAP_ID)
{
f c lo s e ( f i l e P t r ) ; re tu rn NULL;
i / / w cz y tu je nagłówek obrazu fre a d (b itm a p In fo H e a d e r, sizeof(BITMAPINFOHEADER), 1, f i l e P t r ) ; / / ustaw ia wskaźnik p o z y c ji p lik u na początku danych obrazu fs e e k ( f ile P t r , b itm a p F ile H e a d e r.b fO ffB its , SEEK_SET); / / p rz y d z ie la pamięć b uforow i obrazu bitmaplmage = (unsigned c h a r*)m a llo c (b itm a p In fo H e a d e r-> b iS iz e Im a g e );
/ / p rz y d z ie la pamięć potrzebną do przechowania obrazu / / w o d c ie n ia ch s z a ro ś c i, / / tr z y razy m niejszą n iż ro zm ia r obrazu RGB graylm age = (unsigned ch a r*)m a llo c (b itm a p In fo H e a d e r-> b iS ize Im a g e / 3 ); / / sprawdza, czy udało s ię p r z y d z ie lić pamięć i f ( ! bitm aplm age)
{ fre e (b itm a p lm a g e ); fc lo s e ( file P tr ) ; re tu rn NULL;
} / / w cz y tu je dane obrazu fre a d(b itm a p lm a g e . 1, bitm ap In fo H e a d e r->b iS ize Im a g e , f i l e P t r ) ;
Rozdział 9 . ♦ Zaaw ansow ane odwzorowania te k s tu r
257
/ / sprawdza, czy dane z o s ta ły wczytane i f (bitm aplm age == NULL)
{ fc lo s e ( f ile P tr ) ; re tu rn NULL;
g ra y ld x = 0; f o r (im a g e ld x = 0; im ageldx < bitm apInfoH eader->biS izeIm age; imageIdx+=3)
{
g ra y lm a g e [g ra y ld x ] = b itm a p lm a g e [im a g e ld x ]; g ra yld x + + ;
fre e (b itm a p lm a g e ); / / zamyka p lik i zwraca w skaźnik b u fo ra zaw ierającego wczytany obraz fc lo s e ( f ile P tr ) ; r e tu rn graylm age;
Po załadowaniu mapy oświetlenia można posługiwać się nią jak zwykłą teksturą z jedną różnicą: format jej trzeba zawsze definiować jako GL_LUMINANCE, a niejako GLRGB. A oto przykład: / / mapa o ś w ie tle n ia ja k o mipmapa g lu B u i 1d2DMipmaps(GL_TEXTURE_2D, GL_LUMINANCE, w id th , h e ig h t, GL_LUMINANCE, GL_UNSIGNED_BYTE, 1ig h tm a p D a ta ); / / lu b pojedyncza te k s tu ra glTexImage2D (GL_TEXTURE_2D, 0, GL_LUMINANCE, w id th , h e ig h t. 0, GL LUMINANCE, GL_UNSIGNED_BYTE, 1ig h tm a p D a ta );
Aby zastosować mapę oświetlenia, należy posłużyć się mechanizmem tekstur wielo krotnych i włączyć tryb nakładania tekstury GL_MODULATE. Trzeba zatem przyjrzeć się naj istotniejszym fragmentom kodu tego programu: ty p e d e f s tr u c t
{
i n t w id th ; i n t h e ig h t; unsigned i n t te x ID ; unsigned char *d a ta ; } te x tu r e _ t;
/ / te k s tu r y te x tu r e _ t *checkerT ex; te x tu re _ t * 1 ightm apTex;
// // // //
szerokość te k s tu ry wysokość te k s tu ry o b ie k t te k s tu ry dane te k s tu ry
/ / mapa o ś w ie tle n ia / / te k s tu ra szachownicy
/ / fu n k c je tw o rze n ia te k s tu r w ie lo k ro tn y c h PFNGLMULTITEXC00RD2FARBPR0C glM ultiTexC oord2fAR B = NULL; PFNGLACTIV ETE XTUREARBPROC glA ctiveT extureA R B = NULL; PFNGLCLIENTACTIVETEXTUREARBPROC g lC lie n tA c tiv e T e x tu re A R B = NULL; i n t m axTextureU nits = 0; bool In itM u ltiT e x O
{
char * e x te n s io n S tr ;// l i s t a dostępnych rozszerzeń
258
Część II ♦ Korzystanie z OpenGL
e x te n s io n S tr = (char*)glG etString(G L_EXTEN SIO N S); i f (e x te n s io n S tr == NULL) re tu rn fa ls e ; i f ( s tr s tr ( e x te n s io n S tr , "G L _ A R B _ m u ltite xtu re "))
{ / / pobie ra dopuszczalną lic z b ę je d n o ste k te k s tu r glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, & m a xT e xtu re U n its); / / pobie ra adresy fu n k c ji tw o rze n ia te k s tu r w ie lo k ro tn y c h g lM u lti TexCoord2fARB = ( PFNGLMULTITEXC00RD2FARBPR0C) w g lG etP rocA ddress("glM ultiT exC oord2fA R B "); glA ctiveT extureA R B = (PFNGLACTIVETEXTUREARBPROC) w g lG e tP ro cA d d re s s("g lA c ti veTextureAR B"); g lC lie n tA c tiv e T e x tu re A R B = ( PFNGLCLIENTACTIVETEXTUREARBPROC) w glG etProcAddress( "g lC l i e n tA c ti veTextureARB" re tu rn tr u e ;
} e ls e re tu rn fa ls e ;
Funkcja Ini tMul tiT ex() sprawdza dostępność mechanizmu tworzenia tekstur wielokrot nych dla danej implementacji OpenGL. Tym razem wykorzystuje ona funkcję strstrO , która zwraca wartość NULL, jeśli drugi z jej parametrów nie jest podłańcuchem pierwszego. Kolejną z funkcji jest omówiona wcześniej funkcja ładowania mapy oświetlenia z pliku: unsigned char *LoadG rayBitm ap(char * file n a m e , BITMAPINFOHEADER *b itm a p In fo H e a d e r)
{ FILE * f i l e P t r ; BITMAPFILEHEADERbitmapFileHeader; unsigned char*bitm aplm age; in tim a g e ld x = 0; unsigned chartempRGB;
// // // // //
unsigned char in t
/ / obraz w o d c ie n ia ch s za ro śc i / / lic z n ik p ik s e li w o d c ie n ia ch sza ro ści
*graylm age; g ra y ld x ;
w skaźnik p o z y c ji p lik u nagłówek p lik u dane obrazu lic z n ik p ik s e li zmienna zamiany składowych
/ / o tw ie ra p lik w t r y b ie "read b in a ry " f i l e P t r = fo p e n (file n a m e , " r b " ) ; i f ( f i l e P t r == NULL) re tu rn NULL; / / w cz y tu je nagłówek p lik u fread(& b.itm apFileH eader, sizeof(BITMAPFILEHEADER), 1, f i l e P t r ) ; / / sprawdza, czy je s t to p lik form atu BMP i f (bitm a p F ile H e a d e r.b fT yp e != BITMAP_ID)
{ fc lo s e ( file P tr ) ; re tu rn NULL;
Rozdział 9 . ♦ Zaaw ansow ane odwzorowania te ks tu r
259
/ / w c z y tu je nagłówek obrazu fre a d (b itm a p In fo H e a d e r, sizeof(BITMAPINFOHEADER), 1, f i l e P t r ) ; / / ustaw ia w skaźnik p o z y c ji p lik u na początku danych obrazu f s e e k ( f ile P t r , b itm a p F ile H e a d e r.b fO ffB its , SEEK_SET); / / p rz y d z ie la pamięć b uforow i obrazu bitmaplmage = (unsigned c h a r*)m a llo c (b itm a p In fo H e a d e r-> b iS iz e Im a g e );
/ / p rz y d z ie la pamięć potrzebną do przechowania obrazu / / w o d c ie n ia ch s z a ro ś c i, / / tr z y razy m n ie jszą n iż ro zm ia r obrazu RGB graylm age = (unsigned c h a r*)m a llo c (b itm a p In fo H e a d e r-> b iS ize Im a g e / 3 ); / / sprawdza, czy udało s ię p r z y d z ie lić pamięć i f ( ! bitm aplm age)
{ fre e (b itm a p lm a g e ); fc lo s e ( f ile P tr ) ; re tu rn NULL;
} / / w c z y tu je dane obrazu fre a d (b itm a p lm a g e , 1. b itm a p InfoH eader->biS izeIm age. f i l e P t r ) ; / / sprawdza, czy dane z o s ta ły wczytane i f (bitm aplm age == NULL)
{ fc lo s e ( f ile P tr ) ; re tu rn NULL;
} g ra y ld x = 0; f o r (im a g e ld x = 0; im ageldx < b itm apInfoH eader->biS izeIm age; imageIdx+=3)
{ g ra y lm a g e [g ra y ld x ] = b itm a p lm a g e [im a g e ld x ]; g ra yld x + + ;
} fre e (b itm a p lm a g e ); / / zamyka p lik i zwraca w skaźnik b u fo ra zaw ierającego wczytany obraz fc lo s e ( f ile P tr ) ; re tu rn graylm age;
} Następna funkcja, LoadLightmapC ), przypomina funkcję LoadT extureF i l e ( ) z poprzed nich przykładów, ale korzysta z usług funkcji ładowania mapy oświetlenia LoadGrayB itm apO : te x tu re _ t *L o a d T e x tu re F ile (c h a r *file n a m e )
{ BITMAPINFOHEADER te x In fo ; te x tu re _ t * th is T e x tu r e ;
260
Część II ♦ Korzystanie z OpenGL
/ / p rz y d z ie la pamięć s tru k tu rz e typ u te x tu re _ t th is T e x tu re = ( te x tu r e _ t* ) m a llo c ( s iz e o f( te x tu r e _ t) ); i f (th is T e x tu re == NULL) re tu rn NULL; / / ła d u je obraz te k s tu ry i sprawdza poprawne wykonanie t e j o p e ra c ji th is T e x tu re -> d a ta = LoadGrayBitm apCfilename, & te x In fo ); i f (th is T e x tu re -> d a ta == NULL)
{ fr e e ( th is T e x tu r e ) ; re tu rn NULL;
} / / umieszcza in fo rm a c je o szerokości i wysokości te k s tu ry th is T e x tu re -> w id th = te x In fo .b iW id th ; th is T e x tu re -> h e ig h t = te x In fo .b iH e ig h t; / / tw orzy o b ie k t te k s tu ry g lG e n T e x tu re s (lf & th is T e x tu re -> te x ID ); re tu rn th is T e x tu re ;
} Również funkcja LoadAl 1Textures () , która prezentowana jest poniżej, została zmodyfi kowana pod kątem użycia mapy oświetlenia, dla której — w odróżnieniu od zwykłych tekstur — należy zastosować wartości GL_LUMINANCE i GL_MODULATE: boo! L o a d A llT e x tu re s ()
{ / / ła d u je te k s tu rę szachownicy checkerTex = L o a d T e xtu re F ile C ch e ss.b m p ” ) ; i f (checkerTex == NULL) re tu rn fa ls e ; / / ła d u je mapę o ś w ie tle n ia lightm apTex = LoadLightm apC lm ap.bm p"); i f (lightm a p T e x == NULL) re tu rn fa ls e ; / / tw orzy te k s tu rę szachownicy glBindTexture(GL_TEXTURE_2D, c h e ck e rT e x-> te x ID ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LI NEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); gl uBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB( ch eckerT ex-> w idth, c h e c k e rT e x -> h e ig h t, GL_RGB, GL_UNSIGNED_BYTE, c h e ck e rT e x-> d a ta ); / / tw orzy mapę o ś w ie tle n ia glBindTexture(GL_TEXTURE_2D. 1ig h tm a p T e x -> te x ID ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GLTEXTUREMIN FILTER, GL_LINEAR); glTexEnvi (GL JEXTUREJNV, GL_TEXTURE_ENV_MODE, GL_MODULATE); g lu B u i1d2DMipmaps(GL_TEXTURE_2D. GL LUMINANCE, 1ig n tm a p T e x-> w id th , 1ig h tm a p T e x -> h e ig h t, GLLUMINANCE, GL_UNSIGNED_8YTE, 1ig h tm a p T e x-> d a ta );
Rozdział 9 . ♦ Zaaw ansow ane odw zorow ania te k s tu r
261
/ / w ybiera pierw szą je d n o s tk ę te k s tu ry g lA c ti veTextureARB(GL_TEXTUREO_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, c h e ck e rT e x-> te x ID ); / / w iąże te k s tu rę szachownicy / / w ybiera drugą je d n o s tk ę te k s tu ry g lA c ti veTextureARB(GL_TEXTUREl_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 1ig h tm a p T e x -> te x ID ); / / w iąże mapę o ś w ie tle n ia re tu rn tr u e ;
} Pozostała część kodu jest identyczna z pierwszym przykładem programu tworzenia tek stur wielokrotnych.
Wieloprzebiegowe tekstury wielokrotne Brak obsługi mechanizmu tekstur wielokrotnych przez konkretną implementację OpenGL nie oznacza, że nie można uzyskać takich efektów innym sposobem. Rozwiązanie polega na symulacji tekstur wielokrotnych za pomocą doboru odpowiednich funkcji łączenia i tworzeniu końcowego efektu w wielu przebiegach rysowania grafiki. Metoda ta jest zwykle wolniejsza od tekstur wielokrotnych, które coraz częściej obsługiwane są sprzę towo, ale umożliwia uzyskanie podobnych efektów. Wieloprzebiegowe tworzenie sceny polega na odpowiedniej zmianie zawartości bufora głębi i trybów łączenia kolorów w kolejnych przebiegach. Aby uzyskać taki efekt jak w przypadku tekstur wielokrotnych, należy wykonać poniższy fragment kodu: / / p ie rw szy p rze b ie g rysowania glBindTexture(GL_TEXTURE_2D, t e x l ) ; D raw TexturedC ube(O .O f. O.Of, O .O f): / / d ru g i p rze b ie g rysowania gl Enabl e(GL_BLEND); glDepthMask(GL_FALSE); glDepthFunc(GL_EQUAL);
/ / w łącza łą c z e n ie kolorów / / wyłącza za p is do b u fo ra g łę b i
glBIendFunc(GL_ZERO, GL_SRC_C0L0R); glBindTexture(GL_TEXTURE_2D, c h e c k e rT e x -> te x ID ); D raw TexturedC ube(O .O f, O.Of, O .O f): / / przyw raca poprzedni stan maszyny OpenGL glDepthMask(GL_TRUE); glDepthFunc(GL_LESS); glD isable(G L_BLEN D );
Poprzez zmianę sposobu łączenia kolorów uzyskać można efekt tekstury wielokrotnej. Poniższe fragmenty kodu pokazują, w jaki sposób można uzyskać w wielu przebiegach taki sam efekt jak w przypadku pierwszego przykładu tekstur wielokrotnych:
262
Część II ♦ Korzystanie z OpenGL
bool LoadAllTextures O
{ // ładuje obraz pierwszej tekstury smileTex = LoadTextureFile ( "smile.bmp"); i f (smileTex == NULL) return false; // ładuje obraz drugiej tekstury checkerTex = LoadTextureFi 1eCchess. bmp"); i f (checkerTex == NULL) return false; // tworzy pierwszą teksturę g lBi ndTexture(GL_TEXTURE_2D. smi1eTex->texID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, sm11eTex->width, smileTex->height. GL_RGB, GL_UNSIGNED_BYTE, smileTex->data); // tworzy drugą teksturę glBindTexture(GL_TEXTURE_2D, checkerTex->texID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T. GL_REPEAT); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE. GL_MODULATE); gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, checkerTex->width, checkerTex->height, GL_RGB, GL_UNSIGNED_BYTE, checkerTex->data); return true;
} void DrawTexturedCube(float xPos. float yPos, float zPos)
{ glPushMatrixO; glTranslatef(xPos. yPos, zPos); glBegin(GL_QUADS); glNorma!3f(0.0f, l.Of. O.Of);
// górna ściana
glTexCoord2f(1.0f, O.Of); glVertex3f(0.5f, 0.5f. 0.5f); glTexCoord2f(l.Of. l.Of); glVertex3f(0.5f, 0.5f. -0.5f); glTexCoord2f(0.0f. l.Of); glVertex3f(-0.5f. 0.5f. -0.5f); glTexCoord2f(0.0f, O.Of); glVertex3f(-0.5f. 0.5f. 0.5f); glEndO ; glBegin(GL_QUADS); glNormal3f(O.Of. O.Of, l.Of); glTexCoord2f(1.0f, l.Of); glVertex3f(0.5f, 0.5f, 0.5f);
// przednia ściana
Rozdział 9 . ♦ Zaaw ansow ane odwzorowania te ks tu r
g lT e x C o o rd 2 f(0 .0 f, l. O f ) ; g lV e r t e x 3 f ( - 0 . 5 f. 0 .5 f. 0 .5 f ) ; g lT e x C o o rd 2 f(0 .0 f, O .O f); g lV e r t e x 3 f ( - 0 . 5 f, - 0 .5 f , 0 .5 f ) ; g lT e x C o o rd 2 f(1 .0 f, O .O f); g lV e r t e x 3 f ( 0 . 5 f, - 0 .5 f , 0 .5 f ) ; g lE n d O ; glBegin(GL_QUADS); g lN o rm a l3 f( 1 .O f. O .Of, O .O f);
/ / prawa ściana
g lT e x C o o rd 2 f(0 .0 f, l. O f ) ; g lV e r t e x 3 f ( 0 . 5 f, 0 .5 f, 0 .5 f ) ; g lT e x C o o rd 2 f(0 .0 f, O .O f); g lV e r te x 3 f( 0 .5 f, - 0 .5 f , 0 .5 f ) ; g lT e x C o o rd 2 f(1 .0 f, O .O f); g lV e r te x 3 f( 0 .5 f, - 0 .5 f , - 0 . 5 f ) ; g lT e x C o o rd 2 f(1 .0 f, l. O f ) ; g lV e r t e x 3 f ( 0 . 5 f, 0 .5 f , - 0 . 5 f ) ; g lE n d O ; glBegin(GL_QUADS); g lN o rm a !3 f(-1 .0 f, O.Of, O .O f);
/ / lewa ściana
g lT e x C o o rd 2 f(1 .0 f, l. O f ) ; g lV e r t e x 3 f ( - 0 . 5 f, 0 .5 f, 0 .5 f ) ; g lT e x C o o rd 2 f(0 .0 f, l. O f ) ; g lV e r t e x 3 f ( - 0 . 5 f, 0 .5 f, - 0 . 5 f ) ; g lT e x C o o rd 2 f(0 .0 f, O .O f); g lV e r t e x 3 f ( - 0 . 5 f, - 0 .5 f , - 0 . 5 f ) ; g lT e x C o o rd 2 f(1 .0 f, O .O f); g lV e r t e x 3 f ( - 0 . 5 f, - 0 .5 f , 0 . 5 f ) ; g lE n d O ; glBegin(GL_QUADS); g lN o rm a l3 f(0 .0 f, - l. O f , O .O f);
/ / dolna ściana
g !T e x C o o rd 2 f(1 .0 f, O .O f); g lV e r t e x 3 f ( - 0 . 5 f, - 0 .5 f , 0 .5 f ) ; g lT e x C o o rd 2 f(1 .0 f, l. O f ) ; g lV e r te x 3 f( - 0 .5 f. - 0 .5 f , - 0 . 5 f ) ; g lT e x C o o rd 2 f(0 .0 f, l. O f ) ; g lV e r te x 3 f( 0 .5 f, - 0 .5 f , - 0 . 5 f ) ; g lT e x C o o rd 2 f(0 .0 f, O .O f); g lV e r te x 3 f( 0 .5 f. - 0 .5 f, 0 .5 f ) ; g lE n d O ; glBegin(GL_QUADS); g lN o rm a !3 f(0 .0 f, O.Of. - l. O f ) ;
//
ty ln a ściana
263
264
Część II ♦ Korzystanie z OpenGL
g lT e x C o o rd 2 f(0 .0 f, O .O f); g lV e r te x 3 f( 0 .5 f. - 0 .5 f , - 0 . 5 f ) ; g lT e x C o o rd 2 f(1 .0 f. O .O f); g lV e r t e x 3 f ( - 0 . 5 f, - 0 .5 f. - 0 . 5 f ) ; g lT e x C o o rd 2 f(1 .0 f, l. O f ) ; g lV e r te x 3 f( - 0 .5 f, 0 .5 f, - 0 . 5 f ) ; g lT e x C o o rd 2 f(0 .0 f, l. O f ) ; g lV e r te x 3 f( 0 .5 f, 0 .5 f, - 0 . 5 f ) ; g lE n d O ; g lP o p M a trix O ;
} v o id RenderO
{ radia ns =
f lo a t( P I * ( a n g le - 9 0 . 0 f ) /1 8 0 . 0 f ) ;
/ / wyznacza p o ło ż e n ie kamery cameraX = lookX + s in (ra d ia n s)*m o u se Y ; cameraZ = lookZ + cos(radians)*m ouseY ; cameraY = lookY + mouseY / 2 . Of; / / w ycelow uje kamerę w punkt (0 ,0 ,0 ) lookX = O.Of; lookY = O.Of; lookZ = O.Of; / / op ró żn ia b u fo ry ekranu i g łę b i glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n tity O ; / / umieszcza kamerę glu!_ookAt(cameraX, cameraY, cameraZ, lookX, lookY, lo o kZ , 0 .0 , 1 .0 , 0 .0 ) ; / / s k a lu je sześcian do rozm iarów 15x15x15 g lS c a le f(1 5 .O f, 1 5 .Of, 1 5 .O f); / / pierw szy p rze b ie g rysowania glBindTexture(GL_TEXTURE_2D, s m ile T e x -> te x ID ); D raw TexturedC ube(0.0f, O.Of, O .O f); / / d ru g i p rze b ie g rysowania glFnable(GL_BLEND); / / w łącza łą c z e n ie kolorów glDepthMask(GL_FALSE); / / wyłącza z a p is do b u fo ra g łę b i glDepthFunc(GL_EQUAL); glBIendFunc(GL_ZER0. GL_SRC_C0L0R); glBindTexture(GL_TEXTURE_2D, c h e c k e rT e x -> te x ID ); D raw TexturedC ube(0.0f, O.Of, O .O f); / / przyw raca poprzedni stan maszyny OpenGL glDepthMask(GL_TRUE); glDepthFunc(GL_LESS); g lD i sable(GL_BLEND); g lF lu s h ( ) ; SwapBuffers(g_HDC); / / p rze łą c z a b u fo ry
Rozdział 9. ♦ Zaaw ansow ane odwzorowania te ks tu r
265
Efekt uzyskany w wyniku wieloprzebiegowego tworzenia tekstur nie będzie się różnić od efektów uzyskanych za pomocą tekstur wielokrotnych. Zwykle jednak tworzony bę dzie wolniej, ponieważ mechanizm tekstur wielokrotnych korzysta z możliwości ich sprzętowej obsługi. Jednak tworzenie tekstur wielokrotnych w wielu przebiegach po zwala na dodatkowe eksperymentowanie z różnymi efektami uzyskiwanymi przez zmianę funkcji łączenia kolorów.
Podsumowanie OpenGL umożliwia pokrycie powierzchni wielokąta sekwencją tekstur tworzącą teksturą wielokrotną. W procesie tworzenia tekstur wielokrotnych wyróżnić można cztery etapy: sprawdzenie dostępności tekstur wielokrotnych, uzyskanie wskaźników funkcji rozszerzenia, stwo rzenie jednostki tekstury i określenie współrzędnych tekstur. Odwzorowanie otoczenia pozwala rysować obiekty odbijające otaczający je świat na po dobieństwo wypolerowanej srebrnej kuli. Stos macierzy tekstur umożliwia wykonywanie przesunięć, obrotów i skalowań tekstur. Przekształcenia tekstur umożliwiają uzyskanie ciekawych efektów. Mapy oświetlenia służą do symulacji statycznego oświetlenia obiektu za pomocą odpo wiedniej tekstury reprezentującej światło padające na daną powierzchnię.
266
Część II ♦Korzystanie z OpenGL
Rozdział 10.
Listy wyświetlania i tablice wierzchołków W przypadku wielu aplikacji graficznych i praktycznie wszystkich gier szczególne zna czenie ma utrzymanie stałego tempa tworzenia kolejnych klatek animacji. Olbrzymi po stęp, jaki dokonał się w zakresie sprzętowych układów graficznych, nie zmusza już pro gramistów do optymalizacji każdego wiersza tworzonego kodu. Nie oznacza to jednak, że programiści zostali zwolnieni z obowiązku tworzenia efektywnego kodu. Efektywne wykorzystanie sprzętowych układów grafiki możliwe jest przez umiejętne zastosowanie następujących mechanizmów udostępnianych przez OpenGL: ♦
li s t w y ś w i e t l a n i a — umożliwiają one prekompilację często używanych zestawów poleceń OpenGL, a dzięki temu efektywniejsze ich wykonanie;
♦
t a b l i c w i e r z c h o ł k ó w — pozwalają efektywniej przechowywać dane 0 wierzchołkach i szybciej wykonywać operacje zmiany współrzędnych 1kolorów wierzchołków.
Listy wyświetlania W programach przykładowych zaprezentowanych w poprzednich rozdziałach często powtarzały się fragmenty kodu OpenGL konfigurujące maszynę stanów w ten sam spo sób. Koncepcja list wyświetlania polega na tym, że fragmenty takie są wstępnie prze. twarzane, dzięki czemu później mogą zostać wykonane dużo mniejszym kosztem. Zaletą list wyświetlania jest łatwość użycia. Jedyna trudność polega na ustaleniu, czy w danym przypadku spowodują one rzeczywiście szybsze wykonanie kodu. W praktyce okazuje się też, że końcowy rezultat może zależeć od konkretnej implementacji OpenGL. Nigdy jednak zastosowanie list wyświetlania nie powinno spowodować pogorszenia efektywności działania programu. Sposób tworzenia i stosowania list wyświetlania przedstawiony zostanie na przykładzie programu, który rysować będzie zbiór piramid za pomocą czterech trójkątów (podstawa ostrosłupa piramidy nie będzie rysowana, ponieważ nie będzie widoczna). Pojedynczą piramidę można narysować wywołując funkcję, która narysuje wieniec trójkątów two rzących ściany ostrosłupa:
268
Część II ♦ Korzystanie z OpenGL
void DrawPyramid()
{
glB egi n ( GL_TRIANGLE_FAN); g lV e rte x 3 f(0 .0 , 1 .0 , 0 .0 ); g lV e rte x 3 f(- 1 .0 , 0 .0 , 1 .0 ); g lV e rte x 3 f(1 .0 , 0 .0 , 1 .0 ); g lV e r t e x 3 f ( l.0, 0 .0 , - 1 .0 ) ; g lV e rte x 3 f(- 1 .0 , 1 .0 , - 1 .0 ) ; g lE n d ();
} Ponieważ funkcja ta będzie wywoływana wielokrotnie, to jest kandydatem do zastoso wania list wyświetlania. W rzeczywistości użycie list wyświetlania nie przyniesie istot nej poprawy efektywności działania tej funkcji, ponieważ nie wykonuje ona złożonych operacji. W tym przypadku jednak służy ona do zilustrowania zastosowania list wy świetlania na prostym przykładzie.
Tworzenie listy wyświetlania Zanim umieszczony zostanie pierwszy element na liście wyświetlania, trzeba najpierw uzyskać jej nazwę podobnie jak w przypadku obiektów tekstur omówionych w roz dziale 8. Nazwę taką można pobrać za pomocą funkcji g l G en L i s t s (): G Luint g lG e n L is ts (G L s iz e i range):
Parametr range umożliwia określenie liczby list wyświetlania, które mają być utworzone. Funkcja g l G e n L i s t s () zwraca dodatnią liczbę całkowitą będącą identyfikatorem pierw szej z utworzonych list. Po zwiększeniu go o jeden uzyskać można identyfikator kolej nej listy i tak dalej. Identyfikatory te stosuje się do poinformowania maszyny OpenGL, z której listy należy skorzystać. Należy jeszcze sprawdzić, czy wartość zwrócona przez funkcję g l G en Li s t s O nie jest równa 0. Wartość 0 nie jest identyfikatorem listy, lecz informuje o tym, że utworzenie list wyświetlania nie powiodło się. Przyczyną takiej sytuacji może być na przykład brak cią głej przestrzeni identyfikatorów o wymaganej przez programistę wielkości. W dowolnym momencie można także sprawdzić, czy dany identyfikator określa dostępną listę wyświe tlania. W tym celu należy wywołać funkcję g l I s L i s t ( ) o następującym prototypie: GLboolean g lIs L is t( G L u in t listName);
Funkcja ta zwraca wartość GL TRUE, jeśli wartość listName reprezentuje identyfikator dostępnej listy wyświetlania i wartość GL FALSE w przeciwnym razie.
Umieszczanie poleceń na liście wyświetlania Po uzyskaniu identyfikatora listy wyświetlanie możemy już umieszczać na niej polecenia. Odbywa się to w sposób przypominający użycie pary funkcji g l B e g i n ( ) i g l E n d ( ) otacza jących wywołania funkcji tworzących podstawowe elementy grafiki. Najpierw wywołu jemy funkcję, która określa listę wyświetlania, na której chcemy umieszczać polecenia. Po umieszczeniu poleceń wywołujemy kolejną funkcję, która kończy proces tworzenia listy. Funkcje te, o nazwach g l NewLi s t ( ) i g l End Li s t ( ) zdefiniowane są następująco:
Rozdział 1 0 . ♦ Listy w yśw ietlania i ta b lice w ierzchołków
269
vo id g lN e w L is t(G L u in t listName, GLenum mode); v o id g lE n d L is tO ;
Parametr 7 istName jest identyfikatorem listy, na której umieszcza się polecenia. Może to być identyfikator nowej listy, która właśnie została utworzona za pomocą funkcji glGenL i s t s ( ) lub listy, która była używana już w innych celach. W tym drugim przypadku po nowne użycie listy powoduje usunięcie jej dotychczasowej zawartości i umieszczenie nowych poleceń. Parametr mode określa tryb kompilacji poleceń umieszczanych na liście i może przyjmować wartość GL_COMPILE lub GL_COMPILE_AND_EXECUTE. Pierwsza z wymie nionych wartości powoduje jedynie kompilację poleceń, a druga dodatkowo wykonuje je. W większości przypadków umieszczając polecenia na liście wystarczy jedynie zaini cjować listę i wobec tego najczęściej używa się wartości GL COMPILE parametru mode. Mimo że w bloku kodu ograniczonym wywołaniami funkcji gl NewLi s t () i gl EndLi s t () można wywoływać dowolne funkcje OpenGL, to jednak nie wszystkie mogą zostać skompilowane i umieszczone na liście wyświetlania. Funkcje te zamiast tego zostają natychmiast wykonane. Należą do nich: g lC o lo rP o in te r g lD e le te L is ts g lD is a b le C li e n tS ta te glE d g e F la g P o in te r g lE n a b le C lie n tS ta te glF e e d b a ckB u ffe r g lF i n i sh g lF lu s h glG enLi s ts gl In d e x P o in te r gl In te rle a v e d A rra y s g lIs E n a b le d g lI s L i s t gl Normal P o in te r gl PopCl i e n tA tt r i b g l P ix e l S tore gl PushCli e n tA tt r i b g lR e a d P ixe ls glRenderMode g lS e le c tB u ffe r glT e xC o ord P o in te r g l V e rte x P o in te r
Natychmiast wykonane zostaną także wszystkie polecenia glGetO oraz wywołanie funk cji g lT e xIm a g e ( ), jeśli tworzy ono teksturę proxy (jeśli nie używa się tekstur proxy, to można bezpiecznie umieścić polecenie g lT exIm age( ) na liście wyświetlania, chociaż istnieją efektywniejsze polecenia obsługi tekstu, które omówione zostaną wkrótce).
Wykonywanie list wyświetlania Po utworzeniu listy wyświetlania i umieszczeniu na niej poleceń można zastosować listę w dowolnym miejscu programu, w którym zwykle wykonywane są te polecenia. W tym celu należy wywołać funkcję v o id g lC al 1L is t(G L u in t listName);
270
Część II ♦ Korzystanie z OpenGL
Wykonuje ona polecenia znajdujące się na liście wyświetlania o identyfikatorze 7IstName. Możliwe jest także wykonanie wielu list wyświetlania za pomocą pojedynczego wy wołania przedstawionej poniżej funkcji: void glCal 1L i s t s (GLsizei
num,
GLenum
type,
const GLvoid
*lists),
Parametr num określa liczbę list wyświetlania, które mają być wykonane, a parametr l i st s wskazuje tablicę ich identyfikatorów. Choć wartość zwracana przez funkcję g l G en Li s t s () jest typu t i n s i g n e d i n t i typ ten używany jest także przez inne funkcje zwią zane z listami wyświetlania, to jednak w praktyce można rzutować go na inny typ, który w danej sytuacji jest wygodniejszy. Właśnie dlatego parametr l i st s wskazuje typ v o i d , a parametr type umożliwia określenie typu danych znajdujących się w tablicy. Dozwo lone typy prezentuje tabela 10. 1 . Tabela 10.1. Wartości parametru type funkcji glCallListsQ Wartość
Typ
GL_BYTE
Liczba całkowita ze znakiem reprezentowana za pom ocąjednego bajta
GL_UNSIGNED_BYTE
Liczba całkowita bez znaku reprezentowana za pom ocąjednego bajta
GL_SH0RT
Liczba całkowita ze znakiem reprezentowana za pom ocą dwóch bajtów
GL_UNSIGNED_SHORT
Liczba całkowita bez znaku reprezentowana za pom ocą dwóch bajtów
GLJNT
Liczba całkowita ze znakiem reprezentowana za pom ocą czterech bajtów
GLJJNSIGNEDJNT
Liczba całkowita bez znaku reprezentowana za pom ocą czterech bajtów
GL_FL0AT
Liczba zmiennoprzecinkowa reprezentowana za pom ocą czterech bajtów
GL_2_BYTES
Obszar wskazywany przez parametr 7i sts traktowany jest jak tablica bajtów, których każda kolejna para reprezentuje identyfikator listy; wartość tego identyfikatora uzyskiwana jest przez pomnożenie wartości pierwszego bajtu pary (bez znaku) przez wartość 2 8 i dodanie do niej wartości drugiego bajtu pary (również bez znaku)
GL_3_BYTES
Obszar wskazywany przez parametr li sts traktowany jest jak tablica bajtów, których każda kolejna trójka reprezentuje identyfikator listy; wartość tego identyfikatora uzyskiwana jest przez zsum owanie wartości pierw szego bajtu trójki (bez znaku) pom nożonego przez wartość 2 16 z wartością drugiego bajtu trójki (bez znaku) pom nożoną przez wartość 28 i z wartością trzeciego bajtu trójki (również bez znaku)
GL_4_BYTES
Obszar wskazywany przez parametr 7i sts traktowany jest jak tablica bajtów, których każda kolejna czwórka reprezentuje identyfikator listy; wartość tego identyfikatora uzyskiwana jest przez zsum owanie wartości pierw szego bajtu czwórki (bez znaku) pom nożonego przez wartość 232 z wartością drugiego bajtu czwórki (bez znaku) pom nożoną przez wartość 2 16 z wartością trzeciego bajtu czwórki (bez znaku) pom nożoną przez wartość 2 8 z wartością czwartego bajtu czwórki (również bez znaku)
Funkcja g l C a l i L i s t s O wykonuje po kolei listy wyświetlenia, których identyfikatory znajdują się w tablicy l i s t s (począwszy od indeksu 0, a skończywszy na n u m - 1 ) . Jeśli któryś z elementów tablicy nie jest identyfikatorem dostępnej listy wyświetlania, to jest po prostu ignorowany.
Rozdział 1 0 . ♦ Listy w yśw ietlania i ta b lice wierzchołków
271
Może zdarzyć się (zwłaszcza w przypadku używania list wyświetlania do tworzenia tekstów), że wykonywanie list, których nazwy znajdują się w tablicy będziemy trzeba rozpocząć od pewnej wartości jej indeksu. Wartość tę można określić za pomocą funkcji v o id g lL istN a m e (G L u in t o ffse t );
Wykonywanie list rozpocznie się wtedy od identyfikatora o indeksie of f set i zakończy na identyfikatorze o indeksie o ffs e t + n um -1. Domyślnie wartość of f set wynosi 0. Na leży pamiętać, że parametr of f set opisuje stan maszyny OpenGL i gdy zmieniona zo stanie jego wartość, to pozostanie ona aktualna aż do następnego wywołania funkcji gl ListN am et ). Bieżącą wartość parametru offset można odczytać za pomocą funkcji gl Get () przekazując jej wartość GL LIST BASE.
Uwagi dotyczące list wyświetlania Należy zwrócić uwagę na kilka istotnych aspektów korzystania z list wyświetlania. Po lecenia gl Cal ILI sts C) lub gl Cal 1L is t () można umieszczać na listach wyświetlania. Aby zapobiec możliwości wystąpienia nieskończonej rekursji wzajemnych wywołań dwóch list wyświetlania, polecenia umieszczone na liście wyświetlania wykonywanej przez funkcję g l C a li Li s t () nie stająsię częścią nowej listy wyświetlania. Listy wyświetlania mogą zawierać polecenia zmieniające stan maszyny OpenGL. Stan maszyny OpenGL nie jest automatycznie zachowywany przed wykonaniem listy wyświe tlania i przywracany po wykonaniu listy. Dlatego trzeba pamiętać o zapamiętywaniu i od twarzaniu stanu maszyny OpenGL za pomocą funkcji gl P u s h M a t r i x () i gl P o p M a trix( ) o r a z g l P u s h A t t r i b O i gl P o p A trrib ( ).
Usuwanie list wyświetlania Utworzenie listy wyświetlania wymaga przydzielenia przez OpenGL obszaru pamięci, w którym przechowywane są polecenia. Po zakończeniu używania listy wyświetlania należy ją usunąć, aby maszyna OpenGL mogła zwolnić zajmowaną przez listę pamięć. Listę wyświetlania usuwa się za pomocą funkcji gl Del e te L i s ts (): v o id g lD e le te L i sts(G Lu i n t listname, G Lsizei range):
Wywołanie tej funkcji spowoduje zwolnienie pamięci zajmowanej przez listy o identy fikatorach z przedziału od 7istName do 1 i stName + range - 1. Jeśli którykolwiek z tych identyfikatorów odnosi się do nieistniejącej listy, to zostanie po prostu zignorowany. Gdy parametr range będzie miał wartość 0, to wywołanie funkcji nie spowoduje żadnej akcji, a jeśli wartość ujemną, to wystąpi błąd. Jako że opanowana została już umiejętność tworzenia, wypełniania, wykonywania i usu wania listy wyświetlania, można zastosować ją we wspomnianym już programie rysują cym zbiór piramid. Najpierw utworzona zostanie oczywiście lista wyświetlania: G Luint p y ra m id L is t; p y ra m id L is t = g l GenLi s ts ( 1 );
272
Część II ♦ Korzystanie z OpenGL
Następnie trzeba będzie wypełnić ją poleceniami. W tym celu należy zmienić postać za prezentowanej wcześniej funkcji D r a w P y r a m i d ( ). Ponieważ polecenia tworzenia wierz chołków umieszczone zostaną na liście, to funkcja ta będzie wywoływana tylko jeden raz na początku programu i wobec tego należy zmienić jej nazwę na bardziej odpowiednią I n i t i a l i z e P y r a m i d ( ). Wewnątrz tej samej funkcji utworzona zostanie lista wyświetla nia. Parametrem funkcji I n i t i a l i z e P y r a m i c K ) będzie referencja identyfikatora listy: void In itia liz e P y ra m id (G L u in t S p yra m id L ist)
{
p y ra m id L is t = g lG e n L is ts ( l) ; g lN e w L is t(p y ra m id L is t, GL_COMPILE); glBegin(GL_TRIANGLE_FAN); g lV e rte x 3 f(0 .0, 1 .0 , 0 .0 ); g lV e rte x 3 f(- 1 .0 , 0 .0 , 1 .0 ); g lV e r t e x 3 f ( l.0, 0 .0 , 1 .0 ); g lV e r t e x 3 f ( l.0, 0 .0 , - 1 .0 ) ; g lV e rte x 3 f(- 1 .0 , 1 .0 , - 1 .0 ) ; gl E n d O ; g lE n d L is tO ;
} Odtąd aby narysować piramidę, trzeba będzie wykonać najpierw odpowiednie przekształ cenia, a następnie wywołać funkcję g l C a ll L is t( p y r a m id L is t) ;
Po zakończeniu korzystania z listy wyświetlania (w tym przypadku prawdopodobnie związane to będzie z zakończeniem pracy programu), trzeba usunąć listę w następujący sposób: gl Del e te L i s t s ( py rami dLi s t , 1 );
Chociaż w przypadku omówionego programu zastosowanie listy wyświetlania nie spo woduje istotnej poprawy efektywności jego wykonania, to jednak stanowi on dobrą ilu strację sposobu posługiwania się listami wyświetlania.
Listy wyświetlania i tekstury Na listach wyświetlania można umieszczać dowolne funkcje OpenGL związane z tek sturami. Może to stanowić zachętę do umieszczenia na liście wyświetlania poleceń zwią zanych z tworzeniem i konfiguracją tekstury. Rozwiązanie takie byłoby z pewnością optymalne, gdyby nie istniały obiekty tekstur. Obiekty tekstur nie tylko ułatwiają zasto sowanie tekstur w programie, ale także optymalizują ich użycie dając w efekcie popra wę efektywności większą niż można by uzyskać stosując w tym celu listy wyświetlania. Dlatego też najlepiej jest tworzyć tekstury w prezentowany dotąd sposób — przez wią zanie ich z obiektami tekstur. Natomiast wywołania funkcji g l B i n d T e x t u r e ( ), g l T e x C o o r d ( ) i nawet g ! T e x E n v ( ) można później umieszczać na listach wyświetlania, ponieważ funkcje te związane są z zastosowaniem tekstur, a nie ich tworzeniem.
Rozdział 1 0 . ♦ Listy w yśw ietlania i ta b lice w ierzchołków
273
Przykład: animacja robota z użyciem list wyświetlania Zastosowanie list wyświetlania zademonstrowane zostanie teraz na przykładzie nowej wersji programu animacji robota przedstawionego w rozdziale 5. Zamiast tworząc grafikę kolejnej klatki animacji rysować poszczególne elementy robota, można umieścić je na osobnych listach wyświetlania. Na listach tych umieścić można także te z przekształceń, które nie są związane z ruchem robota. Nowa wersja programu tworzy najpierw listy wyświetlania elementów robota, a następnie wykonuje je tworząc w ten sposób kolejne klatki animacji robota. Poniżej zaprezentowany został kod funkcji stosującej listy wy świetlania. Należy zwrócić uwagę na to, że w przykładzie tym stosuje się zagnieżdżone listy wyświetlania. Jedna z list wyświetla sześcian i wykorzystywana jest przez listy wyświetlania poszczególnych elementów robota, które najpierw wykonują odpowiednie przekształcenia przesunięcia i skalowania. vo id I n i t i a l i z e L i s t s ()
{ / / tw o rzy 5 l i s t g_cube = glG enLi s t s ( 5 ); glN ew List(g _ cu b e , GL COMPILE); glBegin(GL_POLYGON); g lV e r te x 3 f( 0 .O f. 0 Of, 0 . O f) ; g lV e r t e x 3 f ( 0 . 0 f, 0 Of, - 1 . O f ) ; g lV e r t e x 3 f ( - 1 . 0 f. O.Of, - 1 .O f) ; g lV e r te x 3 f( - 1 .0 f, O.Of, O .O f); g lV e r t e x 3 f ( 0 . 0 f, 0 Of, O .O f); g lV e r t e x 3 f ( - 1 . 0 f, O.Of. O .O f); g lV e r t e x 3 f ( - 1 . 0 f. - l. O f , O .O f); g lV e r te x 3 f( 0 .O f. l. O f , O .O f); g lV e r t e x 3 f ( 0 . 0 f, 0 Of, O .O f); g lV e r t e x 3 f ( 0 . 0 f, l. O f , O .O f); g lV e r t e x 3 f ( 0 . 0 f. l. O f , - l. O f ) ; g lV e r t e x 3 f ( 0 . 0 f, 0 Of, - l. O f ) ; g lV e r t e x 3 f ( - 1 . 0 f, O.Of, O .O f); g lV e r te x 3 f( - 1 .O f, O.Of, - l. O f ) ; g lV e r t e x 3 f ( - 1 . 0 f, - l. O f , - l. O f ) ; g lV e r te x 3 f( - 1 .0 f, - l. O f , O .O f); g lV e r t e x 3 f ( 0 . 0 f, 0 .O f, O .O f); g lV e r t e x 3 f ( 0 . 0 f, l. O f , - l. O f ) ; g lV e r t e x 3 f ( - l.O f, - l. O f , - l. O f ) ; g lV e r t e x 3 f ( - 1 . 0 f. - l. O f , O .O f); g lV e r te x 3 f( 0 .O f, 0 .O f, O .O f); g lV e r t e x 3 f ( - 1 . 0 f, O.Of, - l. O f ) ; g lV e r te x 3 f( - 1 .0 f, - l. O f , - l . O f ) ; g lV e r t e x 3 f ( 0 . 0 f, l. O f, - l. O f ) ; g lE n d O ; g lE n d L is tO ;
/ / górna ściana
/ / p rzednia ściana
/ / prawa ściana
/ / lewa ściana
/ / dolna ściana
/ / t y ln ia ściana
g_head = g_cube + 1; g lN e w L ist(g _ cu b e , GL_COMPILE); g lP u s h M a trix O ; g lC o lo r 3 f( 1 .0 f, l. O f , l . O f ) ; / / k o lo r b ia ły g lT r a n s la te C l.O f, 2 . Of, O .O f); g l S c a le f( 2 . O f, 2 . Of, 2 .O f); / / głowa robota je s t sześcianem 2x2x2 g lC a l 1Li s t(g _ c u b e );
274
Część II ♦ Korzystanie z OpenGL
g lP o p M a trix (); g lE n d L is tO ; g jto rs o = g_cube + 2; g lN e w L is t(g _ to rs o , GL_COMPILE); g lP u s h M a trix (); g lC o lo r 3 f( 0 .0 f, O.Of, l. O f ) ; / / k o lo r n ie b ie s k i g lT r a n s la t e d . 5 f , O.Of, O .O f); g lS c a le f ( 3 . 0 f, 5 .0 f, 2 . 0 f ) ; / / korpus robota je s t prostopadłościanem 3x5x2 g lC a llL is t( g _ c u b e ) ; g lP o p M a trix (); g lE n d L is tO ; g_arm = g_cube + 3; glN ew List(g_arm , GL_COMPILE); g lP u s h M a tn x (); g lC o lo r 3 f( l. O f . O.Of, O .O f); g lS c a le f d . O f . 4 .0 f , l. O f ) ; glC al 1L i s t(g _ c u b e ); g lP o p M a trix (); glE ndLi s t ( ) ;
/ / k o lo r czerwony / / ramię robota je s t prostopadłościanem 1x4x1
g je g = g_cube + 4; g lN e w L is t(g _ le g , GL_COMPILE); g lP u s h M a trix (); g lC o lo r 3 f( l. O f , l. O f . l. O f ) ; / / k o lo r ż ó łty g lS c a le f d .O f , 5 . O f, l. O f ) ; / / noga robota je s t prostopadłościanem 1x5x1 g l Cal 1Li s t(g _ c u b e ); g lP o p M a trix (); g lE n d L is tO ; } / / kon ie c fu n k c ji I n i t ia liz e L is t s O
Ponieważ program ten jest bardzo prosty, to zastosowanie list wyświetlania nie popra wia znacząco efektywności jego działania. Na niektórych maszynach udało się jednak zaobserwować wzrost szybkości tworzenia klatek animacji o 5 do 10%, ale na szyb szych komputerach efektywność działania obu wersji programu była już praktycznie taka sama.
Tablice wierzchołków W przykładzie programu rysującego piramidy zastosowanie funkcji OpenGL ograni czało się do tworzenia wierzchołków. W praktyce często zdarza się, że programy tworzące grafikę trójwymiarową przetwarzają dużą liczbę wierzchołków lub związanych z nimi da nych. Programy zaprezentowane dotąd w tej książce tworzyły dość proste obiekty i dlatego wierzchołki tych obiektów mogły być definiowane bezpośrednio w kodzie programu. Jednak typowa gra korzysta zwykle z modeli składających się z setek, a nawet tysięcy wielokątów. Oczywiście tworzenie tak skomplikowanych modeli za pomocą definicji wierzchołków umieszczonych bezpośrednio w kodzie jest wyjątkowo niepraktyczne. Zamiast tego stosuje się zwykle jedno z przedstawionych niżej rozwiązań.
Rozdział 1 0 . ♦ Listy w yśw ietlania i tab lice w ierzchołków
275
♦ Proceduralne tworzenie modelu. Niektóre z tworzonych obiektów można opisać za pomocą równań lub tworzyć ich losowe wartości właściwości w trakcie działania programu. Dobrym przykładem takich obiektów są fraktale. Ich dane tworzone są poprzez wywołanie procedury, która tworzy za każdym razem takie same wartości. ♦ Załadowanie modelu z pliku. Obecnie dostępnych jest wiele doskonałych pakietów umożliwiających wizualne tworzenie modeli i zapisanie danych opisujących model w pliku. Dane te mogą być następnie czytane przez program. Rozwiązanie takie oferuje zdecydowanie największą elastyczność tak z punktu widzenia tworzenia modeli, jak i zarządzania ich danymi. Ładowanie modeli omówione zostanie szerzej w dalszej części książki. Niezależnie od tego, które z powyższych rozwiązań zostanie wybrane, to z pewnością przy tworzeniu kolejnej klatki grafiki nie będzie trzeba tworzyć całego modelu od nowa. Dane dostarczone przez jedno z tych rozwiązań będzie można umieścić w buforze i prze twarzać. Na tym właśnie opiera się koncepcja tablic wierzchołków. W procesie tworzenia modelu można wyróżnić przedstawione niżej etapy:
1 . Tworzenie danych modelu na drodze proceduralnej bądź przez pobranie ich z pliku. 2 . Dane modelu umieszcza się w tablicy lub zbiorze tablic (na przykład współrzędne
wierzchołków można umieścić w jednej tablicy, dane opisujące ich kolor w innej, a składowe normalnych w jeszcze innej i tak dalej). 3 . Przy tworzeniu grafiki OpenGL pobiera się kolejne elementy tablicy (lub tablic) i wywołuje dla każdego z nich odpowiednie funkcje OpenGL. Alternatywnie można też korzystać z pewnych podzbiorów danych umieszczonych w tablicach. Do realizacji opisanego sposobu działania w praktyce wystarczy znajomość programo wania pętli w języku C i przedstawionych dotąd poleceń języka OpenGL. Jednak po nieważ rozwiązanie takie stosowane jest powszechnie, to OpenGL posiada wbudowaną obsługę tablic wierzchołków, która optymalizuje efektywność ich wykorzystania.
Obsługa tablic wierzchołków w OpenGL Podobnie jak w przypadku innych mechanizmów udostępnianych przez OpenGL przed skorzystaniem z obsługi tablic wierzchołków trzeba najpierw ją aktywować. Choć można spodziewać się w tym przypadku kolejnego zastosowania funkcji gl Enable(), to jednak OpenGL dostarcza osobnej pary funkcji umożliwiających zarządzanie obsługą tablic wierzchołków: v o id g l E n a b le d ientState(G Lenum a r ra y ) ; v o id g l D is a b le d ientState(G Lenum a r ra y ) ;
Parametr array jest znacznikiem określającym rodzaje tablic, których obsługę włącza się (lub wyłącza). Dlatego też dla każdego rodzaju danych związanych z opisem wierz chołków (na przykład współrzędne, kolor, normalne) należy utworzyć osobne tablice i osobno aktywować obsługę wykorzystywanych typów tablic. Wartości znacznika array dla różnych typów tablic prezentuje tabela 10.2 .
276
Część II ♦ Korzystanie z OpenGL
Tabela 10.2. Znaczniki typów tablic Znacznik
Znaczenie
GL_C0L0R_ARRAY
Włącza obsługę tablic opisujących kolor każdego wierzchołka
GL_EDGE_ELAG_ARRAY
Włącza obsługę tablic zawierających znaczniki krawędzi dla każdego wierzchołka
GL_INDEX_ARRAY
Włącza obsługę tablic zawierających indeksy koloru dla każdego wierzchołka
GL_NORMAL_ARRAY
Włącza obsługę tablic zawierających składowe normalnej każdego wierzchołka
GL_TEXTURE_COORD_ARRAY
Włącza obsługę tablic zawierających współrzędne tekstury dla każdego wierzchołka
GL_VERTEX_ARRAY
Włącza obsługę tablic zawierających współrzędne każdego wierzchołka
Dokumentacja OpenGL określa wszystkie typy tablic wspólnym mianem tablic wierzchołków, ponieważ wszystkie zawierają dane związane z wierzchołkami. Jest to nieco mylące, ponieważ jedna z tych tablic opisuje współrzędne wierzchołków i w skrócie także nazywana jest tablicą wierzchołków. Nazwy znaczników określających typ tablicy zachowują na szczęście podobieństwo do nazw funkcji OpenGL, które wywoływać można za pomocą danych umieszczonych w konkretnej tablicy (na przykład gl V e rte x () dla tablicy wierzchołków, g lC o lo rO dla tablicy kolorów, glTexC oordO dla współrzęd nych tekstury i tak dalej).
Stosowanie tablic wierzchołków Kolejny etap po aktywowaniu wykorzystywanych typów tablic polega na dostarczeniu maszynie OpenGL tablic wypełnionych danymi. Zadaniem programisty jest utworzenie odpowiednich tablic i wypełnienie ich danymi (utworzonymi na drodze proceduralnej lub wczytanymi z pliku), a następnie powiadomienie maszyny OpenGL o istnieniu tych tablic. W tym celu trzeba wywoływać różne funkcje w zależności od typu wykorzysty wanych tablic. Przyjrzyjmy się im zatem bliżej. Funkcja gl Col orPoi n te r () definiuje tablicę kolorów i posiada następujący prototyp: void g lC o lo r P o in t e r ( G L in t s i z e , GLenum t y p e . GLsizei s t r i d e , const GLvoid * a r r a y ) :
Parametr s iz e określa liczbę składowych opisujących kolor wierzchołka i posiada war tość 3 lub 4. Parametr type reprezentuje typ elementów tablicy czyli typ, za pomocą którego reprezentowane są składowe kolorów. Może przyjmować on jedną z wartości GL_BYTE, GL_UNSIGNED_BYTE, GL_SH0RT, GL_UNSIGNED_SHORT, GLJNT, GLJJNSIGNEDJNT, GL FLOAT lub GL DOUBLE. Parametr s t r i d e określa liczbę bajtów, które oddzielają opis
kolorów kolejnych wierzchołków. Jeśli pomiędzy bajtami opisu kolorów kolejnych wierzchołków nie występują dodatkowe bajty, to wartość parametru s t r i d e wynosi 0. Oznacza to, że parametr array jest wskaźnikiem tablicy kolorów (a dokładniej rzecz biorąc pierwszego elementu tablicy kolorów). Funkcja gl E d ge F la gP ointer( ) definiuje tablicę znaczników krawędzi. Poniżej zaprezen towany został jej prototyp: void glE d g e Fla g P o in ter(G Lsize i s t r i d e , const GLboolean * a r r a y ) ;
Rozdział 1 0 . ♦ Listy w yśw ietlania i tab lice wierzchołków
277
Znaczniki krawędzi wykorzystywane są podczas wyświetlania samych krawędzi wielo kątów. Tablica znaczników krawędzi pozwala określić, które z nich mająbyć rysowane. Podobnie jak w przypadku poprzedniej funkcji parametr s t r i d e określa liczbę bajtów oddzielających kolejne pozycje tablicy wartości logicznych wskazywanej przez para metr array. Funkcja g l I n d e x P o i n t e r ( ) definiuje tablicę indeksów kolorów dla wierzchołków w przy padku, gdy wykorzystywany jest tryb wyświetlamia stosujący paletę kolorów. Prototyp funkcji zdefiniowany jest następująco: v o id g l In d e x P o in te r (GLenum type, G Lsizei stride, const GLvoid *array):
Parametr type reprezentuje typ elementów tablicy indeksów i może przyjmować warto ści GL_SHORT, G L _ IN T lub GL_DOUBLE. Parametry s t r i d e i a r r a y posiadają takie samo zna czenie jak w przypadku poprzednich funkcji. Funkcja g l N o rm a l P o i n t e r ( ) definiuje tablicę wektorów normalnych dla poszczególnych wierzchołków i posiada następujący prototyp: v o id g l Normal P o in te r (GLenum type, G Lsizei stride, const GLvoid *array):
Każdy wektor normalny opisany jest za pomocą trzech składowych, których typ opisuje parametr type. Może on przyjmować wartości GL_BYTE, GL_SHORT, G L_IN T, GL_FLOAT lub GL DOUBLE. Parametry s t r i d e i a r r a y posiadają takie samo znaczenie jak w przypadku poprzednich funkcji. Funkcja g l T e x C o o r d P o i n t e r ( ) definiuje tablicę współrzędnych tekstury dla wszystkich wierzchołków. Jej prototyp zdefiniowany jest następująco: g lT e x C o o rd P o in te r(G L in t size, GLenum type, G Lsizei stride, const GLvoid *array):
Parametr s i z e określa liczbę współrzędnych tekstury i może przyjmować wartości 1, 2, 3 łub 4. Parametr type określa typ danych reprezentujących współrzędne tekstury i mo że przyjmować wartości GL_SHORT, G L_ IN T, GL_ELOAT lub GL_DOUBLE. Parametry s t r i d e i a r r a y posiadają takie samo znaczenie jak w przypadku poprzednich funkcji. Funkcja g l V e r t e x P o i n t e r ( ) definiuje tablicę współrzędnych wierzchołków. Funkcja ta zdefiniowana jest następująco: vo id g lV e rte x P o in te r (G L in t size, GLenum type, G Lsizei stride, const GLvoid *arrav):
Parametr s i z e określa liczbę współrzędnych wierzchołka i może przyjmować wartości 2, 3 lub 4. Parametr type określa typ danych reprezentujących współrzędne wierzchoł ków i może przyjmować wartości GL_SHORT, G L_IN T, GL_FLOAT lub GL_DOUBLE. Parametry s t r i d e i a r r a y posiadają takie samo znaczenie jak w przypadku poprzednich funkcji. Po zdefiniowaniu różnych typów tablic wierzchołków można skorzystać z zawartych w nich danych za pomocą odpowiednich funkcji OpenGL. A teraz należy przejść do szczegółowego omówienia tych funkcji.
278
Część II ♦ Korzystanie z OpenGL
W danym m omencie można posiadać tylko jedną tablicę danego typu. Oznacza to, że jeśli tablice wierzchołków programista zam ierza wykorzystać w celu reprezentacji wie lu modelów, to albo musi umieścić razem opisujące je dane w jednym i tym samy zestawie tablic, albo przełączać osobne zestawy tablic dla poszczególnych obiektów za pomocą funkcji g l* P o in t e r ( ). Chociaż pierwsza m etoda je s t nieco szybsza, po nieważ nie wymaga zmiany stanu maszyny OpenGL, to jednak druga m etoda oferuje lepsze zarządzanie danymi.
glDrawArrays() Wywołanie tej funkcji powoduje, że OpenGL korzysta ze wszystkich aktywnych typów tablic wierzchołków i tworzy podstawowe elementy grafiki na podstawie zawartych w nich danych. Aby zrozumieć dokładnie, w jaki sposób działa ta funkcja, należy przyj rzeć się jej prototypowi: v o id glDrawArrays(GLenum mode, G L in t f i r s t . G Lsizei count):
Parametr mode ma takie samo znaczenie jak parametr funkcji g lB e g in O : określa typ podstawowych elementów grafiki tworzonych na podstawie danych wierzchołków. Może przyjmować wartości GL_P0INTS, GL_LINE_STRIP, GL_LINE_L00P, GLJJNES, GL_TRIANGLE_ STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP, GL_QUADS lub GL_P0LYG0N. Para metr f i r s t określa indeks elementu tablicy, od którego OpenGL powinien rozpocząć działanie, a parametr count specyfikuje zakres przetwarzanych elementów tablicy. Na leży zwrócić uwagę, że po wywołaniu funkcji gl D raw A rrays( ) bieżący stan maszyny związany z typem przetwarzanych tablic nie jest zdefiniowany. Na przykład przetwa rzanie przez funkcję gl D raw A rrays( ) tablicy normalnych powoduje, że bieżąca normal na nie jest zdefiniowana.
glDrawElements() Funkcja ta jest bardzo podobna do funkcji gl D raw A rrays( ), ale posiada nieco większe możliwości. Za pomocą funkcji gl D raw A rrays( ) można przejrzeć ciągły zakres ele mentów zgodnie z ich uporządkowaniem w tablicy. Funkcja gl DrawEl em ents( ) pozwala natomiast określić dowolny porządek przetwarzania wierzchołków. Jej prototyp wyglą da następująco: glDrawElements(GLenum mode, G Lsizei count, GLenum type, const GLvoid * ind ices):
Parametr mode posiada takie samo znaczenie jak dla funkcji g lD ra w A rra y s ( ), podobnie parametr count. Parametr type określa typ elementów wskazywanych przez parametr indices i może przyjmować jedną z wartości GLJJNSIGNEDJ3YTE, GL_UNSIGNED_SHORT lub GL UNSIGNED INT. Parametr indices reprezentuje tablicę zawierającą wierzchołki, które mają być narysowane. Aby zrozumieć, na czym polega zaleta tej funkcji należy zaznaczyć, że pozwala ona nie tylko przetwarzać wierzchołki w dowolnym porządku, ale także przetwarzać ten sam wierzchołek wielokrotnie. W przypadku obiektów używanych w grach większość wierz chołków należy do więcej niż jednego wielokąta. Przechowując opis wierzchołka nale żącego do wielu wielokątów tylko jeden raz można zaoszczędzić sporo pamięci. Dodat kowo dobre implementacje OpenGL wykonują operacje na takim wierzchołku tylko raz,
Rozdział 1 0 . ♦ Listy w yśw ietlania i ta b lice w ierzchołków
279
co zwiększa w oczywisty sposób efektywność wykonywania programu. Więcej infor macji na ten temat zawiera podrozdział „Blokowanie tablic” wierzchołków znajdujący się w dalszej części bieżącego rozdziału.
glDraw RangeElem ents() Funkcję tę wprowadzono dopiero w wersji 1.2 specyfikacji OpenGL, nie jest więc ona dostępna w starszych implementacjach. Używamy jej podobnie do funkcji g lD ra w E le mentsO. Różnica polega na ograniczeniu zakresu przetwarzanych elementów tablicy wierzchołków. Na przykład rysując obiekt, który zdefiniowany jest przez pierwszych sto wierzchołków tablicy składającej się z tysiąca elementów, można poinformować 0 tym OpenGL właśnie za pomocą funkcji gl DrawRangeElements(). Może to spowodo wać optymalizację procesu tworzenia grafiki, jeśli OpenGL przeniesie dane stu wierz chołków do pamięci o szybszym dostępie. Prototyp funkcji glDrawRangeElementsi) wy gląda następująco: v o id glDrawRangeElements(GLenum mode. G Luint start. G Luint end. G Lsizei count, GLenum type, const GLvoid * indices);
Parametry mode, type i count posiadają takie samo znaczenie jak w przypadku funkcji gl DrawEl ements (). Parametry sta rt i end określają dolną i górną granicę przetwarzanego zakresu indeksów tablicy wskazywanej przez indices.
glArrayElem ent() Funkcja ta stanowi najmniej efektywny sposób przetwarzania tablic wierzchołków. Nie działa ona w odniesieniu do pewnego zakresu wierzchołków, lecz do pojedynczego elementu tablicy wierzchołków. Funkcja gl Array Element () posiada następujący prototyp: v o id g lA rra y E le m e n t(G L in t index):
Parametr index określa oczywiście wartość indeksu wierzchołka, który ma być przetwa rzany. Dla lepszego zrozumienia sposobów korzystania z tablic wierzchołków należy podsumo wać uzyskane dotąd informacje. Tablice wierzchołków trzeba najpierw wypełnić danymi, które tworzy się za pomocą pewnego algorytmu lub wczytuje z pliku. Dane te opisują zbiór wierzchołków tworzących model pewnego obiektu. Informacja dotycząca każdego wierzchołka może opisywać jego współrzędne, kolor, współrzędne tekstury, znacznik kra wędzi i wektor normalny. Każda z tych informacji umieszczona jest w tablicy innego typu. Jeśli korzysta się z tablicy danego typu, to trzeba aktywować jej przetwarzanie w OpenGL. Poszczególne tablice wybiera się następnie za pomocą funkcji gl *Poi nter (). Aby skorzystać z danych umieszczonych w tablicach wierzchołków i narysować obiekt, należy wywołać jedną z omówionych wyżej funkcji. Dla każdego wierzchołka OpenGL pobierze dane związane z opisem danego wierzchołka i wywoła dla nich odpowiednią funkcję. Na przykład dla danych opisujących kolor wierzchołka będzie to funkcja gl Co lor (), dla danych reprezentujących składowe wektora normalnego funkcja gl Norma ! O 1tak dalej. Oczywiście działanie OpenGL nie polega w tych przypadkach na wywołaniu wymienionych funkcji (wtedy można by zrobić to samodzielnie i w ogóle pominąć kon cepcję tablic wierzchołków), ale efekt jest taki sam.
280
Część II ♦ Korzystanie z OpenGL
Tablice wierzchołków i tekstury wielokrotne Stosowanie tekstur wielokrotnych omówionych w rozdziale 9. w połączeniu z tablicami wierzchołków wymaga pewnych dodatkowych zabiegów. Każda jednostka tekstury po siada własny zbiór stanów i dlatego tablice wierzchołków muszą być aktywowane dla każdej jednostki tekstur osobno. Każda z jednostek tekstur dysponuje też własnym wskaźnikiem tablicy wierzchołków przechowującej współrzędne tekstury. Implementacje umożliwiające stosowanie tekstur wielokrotnych przyjmują, że domyśl nie aktywna jest pierwsza jednostka tekstury. Wywołania funkcji glT exC oordP ointerO oraz gl E n abled ie n tS ta te ( ) i g l D is a b le d ie n tS ta te ( ) z parametrem GL_TEXTURE_COORD_ ARRAY mają wpływ jedynie na aktywną jednostkę tekstury. Aby więc użyć tablic wierz chołków także w przypadku pozostałych jednostek tekstur, trzeba aktywować je za po mocą poniższej funkcji: vo id g lC lien tA ctiva te T e xtu re A R B (en u m texture ):
Parametr texture określa jednostkę tekstury, która jest aktywowana i ma postać GL_ TEXTURE 7_ARB, gdzie 7 jest wartością z przedziału od 0 do GL_MAX_TEXTURE_UNITS_ARB - 1.
Po aktywowaniu wybranej jednostki tekstury można wywołać funkcję gl Enabled ie n tS ta te O lub gl D is a b le d i e n tS ta te O , aby włączyć lub wyłączyć dla niej mechanizm tablic wierzchołków oraz funkcję g lT e x C o o rd P o in te r( ) w celu zdefiniowania tablicy współ rzędnych tekstury. Domyślnie mechanizm tablic wierzchołków jest wyłączony dla wszyst kich jednostek tekstur. Poniższy fragment kodu przedstawia sposób aktywacji tablic wierzchołków dla pierwszych dwóch jednostek tekstur: / / aktyw uje ta b lic e w ie rzch o łkó w d la je d n o s tk i te k s tu ry o in d e k s ie 0 g l E na bled ientState(GL_TEXTURE_COORD_ARRAY); / / d e fin iu je ta b lic ę współrzędnych te k s tu ry d la je d n o s tk i te k s tu ry o in d e k s ie 0 g lT e x C o ord P o in te r(2 , GL_FL0AT, 0, (GLvoid * ) te x U n itO V e rtic e s ) ; / / w ybiera je d n o s tk ę te k s tu r y o in d e k s ie 1 glC lientActiveTextureARB(GL_TEXTUREl_ARB); / / a ktyw uje ta b lic e w ie rzch o łkó w d la je d n o s tk i te k s tu ry o in d e k s ie 1 gl E nab led ientState(GL_TEXTURE_COORD_ARRAY); / / d e fin iu je ta b lic ę współrzędnych te k s tu ry d la je d n o s tk i te k s tu ry o in d e k s ie 0 glT e x C o o rd P o in te r(2 . GL_FL0AT, 0. (GLvoid * ) te x U n itlV e r t ic e s ) ;
Po aktywowaniu i zdefiniowaniu tablicy wierzchołków dla każdej z używanych jedno stek tekstur można już korzystać z funkcji gl D raw A rrays( ) i glD raw E lem e nts( ) w nor malny sposób.
Blokowanie tablic wierzchołków Wiele implementacji OpenGL udostępnia rozszerzenie pozwalające zakładać i zdejmo wać blokady tablic wierzchołków. Zablokowanie tablic wierzchołków informuje ma szynę OpenGL, że do momentu zdjęcia blokady nie będą modyfikowane znajdujące się w nich dane. Dzięki temu OpenGL zamiast wielokrotnie wykonywać przekształcenia
Rozdział 1 0 . ♦ Listy w yśw ietlania i tab lice w ierzchołków
281
wierzchołków wykonuje je tylko raz, a wynik przechowuje w buforze. W rezultacie ta kiego działania można uzyskać znaczne przyspieszenie tworzenia grafiki (zwłaszcza, je śli zawiera ona wiele wspólnych wierzchołków lub wykonywane jest wiele przebiegów dla tych samych danych). Ponieważ informacja o wierzchołkach zostaje w wyniku za blokowania tablic skompilowana, to nazwą tego rozszerzenia jest GL_EXT_compiled_ v e rte x a rra y . Z rozszerzeniem tym związane są następujące funkcje: v o id glLockA rra ysE X T (G L in t f ir st, G Lsizei count): v o id g lU n lo c kA rra y sE X T O ;
Parametr f i r s t jest indeksem pierwszego wierzchołka, który ma być zablokowany, a parametr count określa liczbę blokowanych wierzchołków. Przykład kodu sprawdzającego dostępność i używającego mechanizmu blokowania ta blic zawiera kod programu, który omawiany jest w następnym podrozdziale.
Przykład: ukształtowanie terenu po raz drugi W rozdziale 8. omówiony został przykład programu prezentującego ukształtowanie te renu na podstawie mapy wysokości. Teraz zmodyfikowany zostanie jego kod tak, by korzystał z tablic wierzchołków. Kompletny kod nowej wersji programu znajduje się na dysku CD, a tutaj przedstawione są jedynie fragmenty związane z zastosowaniem tablic wierzchołków. Jak pokazują rysunki 10.1 i 10.2, jedyna różnica w działaniu obu wersji programu polega na szybkości tworzenia grafiki. Rysunek 10.1. Mapa ukształtowania terenu prezentowana przez program z rozdziału 8.
Na początku trzeba oczywiście zadeklarować wykorzystywane tablice: G Luint f lo a t flo a t flo a t
g_indexArray[MAP_X * MAP_Z * 6 ]; g_terrain[M AP _X * MAP_Z][ 3 ] ; g_colorArray[M AP_X * M AP_Z][3]; g_texcoordArray[MAP_X * MAP_Z][2];
// // // //
ta b lic a indeksów w ierzchołków mapa wysokości te re n u 256x256 ta b lic a kolorów ta b lic a współrzędnych te k s tu ry
282
Część II ♦ Korzystanie z OpenGL
Rysunek 10.2. Mapa ukształtowania terenu tworzona przez wersję programu stosującą tablice wierzchołków
Tablica g t e r r a in przechowywać będzie dane o położeniu wierzchołków, tablica g_ c o lo rA rra y dane o ich kolorze, tablica g_texcoordA rray współrzędne tekstury. Tablica g in d e xA rra y zawierać będzie indeksy wierzchołków i używana będzie przez funkcję glD raw E lem entsO .
Funkcja In i t i al i z e A rra y s ( ) umieszcza dane w każdej z wymienionych tablic i aktywuje mechanizm tablic wierzchołków: v o id I n i t i a l i ze A rra ysO
{ / / zmienne wskazujące bieżącą po zycję t a b lic y indeksów i n t in d e x = 0; i n t c u rre n tV e rte x ; / / przegląda w p ę t li w s z y s tk ie w ie rz c h o łk i mapy te re n u f o r ( i n t z = 0; z < MAP_Z; z++)
{
fo r ( in t x = 0; x < MAPX; x++)
{
/ / w ie rz c h o łk i uporządkowane są od le w e j do p ra w e j, z g óry w d ó ł c u rre n tV e rte x = z * MAP_X + x; / / umieszcza w a rto ś c i w ta b lic y kolorów g _ c o lo rA rra y [c u rre n tV e rte x ][0 ] = g _ c o lo rA rr a y [c u r re n tV e r te x ][l] = g _ c o lo rA rra y [c u rre n tV e rte x ][2 ] = g _ te r r a in [x + MAP_X * z ] [ l ] / 2 5 5 . 0 f ; / / umieszcza w a rto ś c i w t a b lic y w spółrzędnych te k s tu ry g _ te x c o o rd A rra y [c u rre n tV e rte x ][0 ] = ( f lo a t ) x; g _ te x c o o rd A rra y [c u rre n tV e rte x ][l] = ( f lo a t ) z;
i
}
f o r (z = 0; z < MAP_Z - 1; z++)
{
f o r ( i n t x = 0; x < MAP_X; x++)
Rozdział 1 0 . ♦ Listy w yśw ietlania i ta b lice w ierzchołków
283
{ c u rre n tV e rte x = z * MAP_X + x; g _ in d e x A rra y [in d e x + + ] = c u rre n tV e rte x + MAP_X; g _ in d e x A rra y [in d e x + + ] = c u rre n tV e rte x ;
} } // gl gl gl
aktyw u je ty p y używanych t a b lic E na b le d ie n tS ta te ( GL_VERTEX_ARRAY); E n a b le d ientState(GL_COLOR_ARRAY); E n a b le d ientState(GL_TEXTURE_COORD_ARRAY);
/ / d e fin iu je t a b lic e w ie rzch o łkó w g lV e rte x P o in te r(3 , GL_FLOAT, O, g _ te r r a in ) ; g lC o lo rP o in te r(3 , GL_FLOAT, O, g _ c o lo rA rra y ); g lT e x C o o rd P o in te r(2 , GL_FLOAT, O, g _ te x c o o rd A rra y ); } / / koniec fu n k c ji I n i t i a l iz e A rra y s ()
Funkcja In itial izeArrays() przygotowuje tablice wierzchołków do użycia. Jako że ana lizowany przykład korzystać będzie z rozszerzenia GL_EXT_compiled_vertex_array, trzeba sprawdzić, czy jest ono dostępne. Służy do tego poniższy fragment kodu, który po ustaleniu, że mechanizm blokowania tablic wierzchołków jest dostępny, pobiera wskaźniki funkcji gl EXTLockArrays() i gl EXTUnlockArrays(). / / sprawdza dostępność blokowania t a b lic w ierzchołków char * e x tl_ is t = (ch a r * ) glGetString(GL_EXTENSIONS); i f ( e x t L is t && s t r s t r ( e x t L is t , "G L_EX T_com piled_vertex_array"))
{ / / p o b ie ra w ska źn iki fu n k c ji rozszerzeń glLockArraysEXT = ( PFNGLLOCKARRAYSEXTPROC) w glG etP rocA ddress( " g lLockArraysEXT"); gllinlockA rra ysE X T = ( PFNGLUNLOCKARRAYSEXTPROC) w g lG e tP ro cA d d re ssC 'g lU n lo ckA rra ysE X T ");
} Stosowanie blokad tablic wierzchołków jest całkiem proste. Ponieważ w tym programie nie będzie zmieniany wyglądu terenu, po zainicjowaniu tablic można je od razu zablo kować: / / b lo k u je ta b lic e w ie rzch o łkó w , j e ś l i mechanizm blokow ania je s t dostępny i f (glLockA rraysE X T) glLockA rraysE X K O . MAP_X * MAP_Z);
Blokadę zwalnia się kończąc działanie programu: / / zw a ln ia blokadę t a b l ic , j e ś l i mechanizm blokowania je s t dostępny i f (g llin lo ckA rra ysE X T ) g lU n lo ckA rra ysE X T ();
Dane wyświetlane są za pomocą funkcji glDrawElements(). Ponieważ ze względu na efektywność tworzenia grafiki dane o wierzchołkach zapisane są tak, by tworzyły łań cuchy trójkątów, to koniecznych jest wiele wywołań funkcji glDrawElements(). Każde takie wywołanie przetwarza inny zakres elementów tablicy wierzchołków:
284
Część II ♦ Korzystanie z OpenGL
/ / ry s u je w p ę t li k o le jn e łańcuchy tró jk ą tó w fo r ( in t z = 0; z < MAP_Z-1; z++)
{ / / ry s u je tr ó jk ą t y łańcucha glDrawElements(GL_TRIANGLE_STRIP, MAP_X * 2, GLJJNSIGNEDJNT. & g_indexA rra y[z * MAP_X * 2 ] ) ;
} Stworzenie wersji programu używającej tablice wierzchołków zajęło niecałą godzinę. Nowa wersja działa o około 10 do 15% szybciej od dotychczasowej. W praktyce ozna cza to tworzenie od 5 do 10 dodatkowych klatek grafiki na sekundę. Jest to istotna po prawa działania uzyskana przy stosunkowo małym nakładzie pracy.
Podsumowanie W rozdziale tym przedstawione zostały dwa wydajne sposoby zwiększania efektywno ści grafiki OpenGL. Ponieważ ich możliwości pokrywają się częściowo, zasadne jest pytanie o to, którą z nich należy preferować. Jeśli program rysuje wiele wierzchołków, które nie zmieniają często swego stanu, to należy skorzystać raczej z tablic wierzchoł ków. Gdy stan grafiki ulega częstym zmianom, lepszym rozwiązaniem będą listy wy świetlania. Różnicę w efektywności obu metod można ustalić w konkretnym przypadku doświadczalnie. Niezależnie jednak od tego, która z metod zostanie wybrana zawsze uzyskuje się wzrost efektywności tworzenia grafiki (nawet, jeśli będzie on niewielki). Nabierając stopniowo doświadczenia w korzystaniu z obu mechanizmów będzie można tworzyć coraz bardziej efektywne rozwiązania.
Rozdział 11.
Wyświetlanie tekstów Każda aplikacja graficzna wyświetla pewne teksty na ekranie. Mogą to być na przykład pozycje menu, teksty komunikatów, napisy prezentowane przez wygaszacz ekranu bądź używane do uzyskania efektów specjalnych. W rozdziale tym przedstawione zostaną najczęściej stosowane techniki tworzenia tekstów w OpenGL. Obejmować one będą two rzenie i korzystanie z czcionek rastrowych, konturowych i pokrytych teksturą. W rozdziale tym omawiamy: ♦ czcionki rastrowe; ♦ czcionki konturowe; ♦ czcionki pokryte teksturą.
Czcionki rastrowe Czcionki rastrowe są najczęściej wykorzystywanym sposobem wyświetlania tekstów w OpenGL i pozwalają na uzyskanie najlepszego efektu. Jednocześnie tworzenie ich jest bardzo proste i odbywa się za pomocą funkcji w glU seFontB itm aps( ). Tworzy ona czcionki rastrowe na podstawie czcionek dostępnych w systemie operacyjnym. Aby zastosować czcionki rastrowe do wyświetlania tekstów, trzeba najpierw utworzyć 96 list wyświetlania, które rysować będą poszczególne znaki czcionki. W tym celu należy skorzystać z funkcji gl GenLi s ts () w następujący sposób: unsigned i n t base; base = g lG e n L is ts (9 6 );
Następnie tworzymy czcionkę systemu Windows korzystając z funkcji C reateFontO zdefiniowanej w następujący sposób: HFONT in t in t in t in t in t
C rea te F o n t( n H e ig h t, nW idth, nEscapement, n O rie n ta tio n , fn W e ig h t,
// // // // //
wysokość c z c io n k i śre d n ia szerokość znaku k ą t p o ch yle n ia k ą t o r ie n ta c ji względem l i n i i bazowej waga c z c io n k i
286
Część II ♦ Korzystanie z OpenGL
DWORD f d w lt a lic , DWORD fdw llnderl i ne. DWORD fd w S trik e O u t, DWORD fdwCharSet, DWORD fd w O u tp u tP re c is io n , DWORD fd w C lip P re c is io n , DWORD fd w Q u a lity . DWORD fd w P itch A n d F a m ily, LPCTSTR IpszFace
// // // // // // // // //
znacznik kursywy znacznik p o d k re ś le n ia znacznik w y tłu s zc ze n ia id e n ty fik a to r z b io ru znaków dokładność p re z e n ta c ji dokładność o b cin a n ia ja ko ść p re z e n ta c ji ro d zin a czcionek nazwa c z c io n k i
); Funkcja ta zwraca uchwyt czcionki systemu Windows. Dla czcionki tej należy wybrać kontekst urządzenia, który następnie przekazuje się jako parametr funkcji wglUseFontBi tm aps() : HFONT h F o n t;
/ / uchwyt c z c io n k i systemu Windows
/ / tw o rzy czcio n kę C o u rie r o w ie lk o ś c i 14 punktów hFont = C re a te F o n t(fo n tS iz e , 0. 0. 0. FW_B0LD, FALSE. FALSE, FALSE, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE | DEFAULT_PITCH, "C o u rie r” ); / / sprawdza, czy u tw o rze n ie c z c io n k i pow iodło s ię i f ( ! h F ont) re tu rn 0; / / w ybiera k o n te k s t urządzenia S e le c tO b je c t( g_HDC, h F ont); / / przygoto w u je czcio n kę do użycia w OpenGL wgll)seFontBitmaps(g_FIDC. 32, 96, base);
Powyższy fragment kodu tworzy listy wyświetlania dla wytłuszczonej czcionki Courier o wielkości 14 punktów. Wyświetlanie czcionki jest prostsze niż jej tworzenie. W tym celu należy wywołać funk cje gl Li s tB a s e () ig lC a llL is t s O w pokazany niżej sposób: char * s t r ; g lP u s h A ttri b(G L_LIST_BIT); g lL is tB a s e (base - 3 2 ); g lC a l1L is t s ( s t r ie n ( s t r ) , GL_UNSIGNED_BYTE, s t r ) ; g lP o p A ttr ib ( );
Funkcja g lB a s e L is tO definiuje najpierw bazową listę wyświetlania, a funkcja gl Cal 1 L is ts O wykonuje odpowiednie listy wyświetlania znaków tworzących przekazany jej łańcuch. Korzystając z zaprezentowanych fragmentów kodu można stworzyć zestaw funkcji uła twiających korzystanie z czcionek rastrowych w OpenGL. Używać ich można za pomocą prostego programu, którego efekt działania przedstawia rysunek 1 1 . 1 .
Rozdział 1 1 . ♦ W yśw ietlanie te k s tó w
Rysunek 11.1. Pogram wyświetlający tekst za pomocą czcionki rastrowej
287
• Wyświetlanie tekstu, przykład pierwszy: czcionka i
Czcionka rastrowa OpenGL!
W celu zapamiętania identyfikatora listy bazowej trzeba zadeklarować zmienną 1i stBase: unsigned i n t lis tB a s e ;
Funkcja C re ate B itm ap F o nt() utworzy czcionkę rastrową OpenGL korzystając z funkcji C reateFontC ) systemu Windows: unsigned i n t C reateB itm apF ont(char *fontName, in t fo n tS iz e )
{ HFONT hFont; unsigned i n t base;
/ / uchwyt c z c io n k i systemu Windows
base = g lG e n L is ts (9 6 );
/ / l i s t y w y ś w ie tla n ia d la 96 znaków
i f (stricm p (fo n tN a m e , "sym b o l") == 0)
{ hFont = C re a te F o n tC fo n tS ize , 0, 0. 0, FW_B0LD, FALSE, FALSE, FALSE, SYMBOL_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE | DEFAULT_PITCH, fontN am e);
} el se
{ hFont = C re a te F o n tC fo n tS ize , 0, 0, 0, FW_B0LD, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE | DEFAULT_PITCH, fontName);
} i f C! hFont) re tu rn 0; S electO bject(g_H D C , h F o n t); wglllseFontBitm aps(g_HDC, 32, 96, base); re tu rn base;
}
288
Część II ♦ Korzystanie z OpenGL
Funkcja CreateBitmapFont() tworzy najpierw listy wyświetlania dla 96 znaków czcionki. Następnie sprawdza, czy nazwą wybranej czcionki jest łańcuch "sym bolJeśli tak jest, wywołuje funkcję CreateFontO przekazując jej stałą SYMBOL_CHARSET jako wartość pa rametru fdwCharSet. W przeciwnym razie wybiera zestaw znaków ANSI_CHARSET. Po przygotowaniu czcionki rastrowej za pomocą funkcji wglUseBitmapFonts() funkcja CreateBitmapFont() zwraca identyfikator bazowej listy wyświetlania. Kolejna funkcja, Pr i ntStri ng(), wyświetla łańcuch znaków za pomocą bieżącej czcionki rastrowej na bieżącej pozycji rastra: v oid P rin tS trin g (u n s ig n e d in t base, char * s t r )
{ i f ((base = = 0 ) || ( s t r == NULL)) r e tu r n ; g lP u s h A ttri b(G L_LIST_BIT); g lL is tB a s e (b a s e - 3 2 ); g lC a ll Li s t s ( s t r 1en( s t r ), GL_UNSIGNED_BYTE, s t r ) ; g lP o p A ttr ib ( );
} Przed zakończeniem działania programu należy zwolnić wszystkie listy wyświetlania. Zadanie to wykonuje funkcja ClearFont(): vo id C le arF o n t(u n sig n e d in t base)
{ i f (base!= 0) g lD e le te L is ts (b a s e . 9 6 );
} Wywołuje ona funkcję glD eleteL ists() usuwając 96 list wyświetlania (począwszy od indeksu listy bazowej przekazanego za pośrednictwem parametru base). W tym przykładzie zastosowana zostanie czcionka Arial o wielkości 48 punktów. Wy boru czcionki dokonuje się wewnątrz funkcji Initial i z e ( ): vo id I n i t i a l i z e O
{ g lC le a r C o lo r ( 0 .0 f. O.Of, O.Of,O .O f); glShadeModel(GL_SM00TH); glEnable(GL__DEPTH_TEST);
//
t l o w k o lo rz e czarnym / / c ie n io w a n ie g ła d k ie / / usuwanie p rz e s ło n ię ty c h p o w ierzchni
lis tB a s e = C re a te B itm a p F o n t("A ria l", 4 8 ); / / tw o rzy czcio n kę ra stro w ą
} Ostatnia z funkcji, Render (), wyświetla tekst w środku okna grafiki: void RenderO
{ / / o p różn ia b u fo ry ekranu i g łę b i glC l e a r(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n tity O ; / / odsuwa p łaszczyznę napisu o je d n o s tk ę g lT r a n s la te f ( 0 . 0 f, O.Of, - l. O f ) ;
Rozdział 1 1 . ♦ W yśw ietlanie te k s tó w
289
/ / w yb iera k o lo r b ia ły g lC o lo r 3 f( 1 .0 f. l. O f . l. O f ) ; / / w yb ie ra p o zycję r a s tra g lR a s te r P o s 2 f( -0 .5 f, O .O f); / / w y ś w ie tla te k s t P rin tS tr in g O i stBase, "Czcionka rastrow a OpenGL!"); g lF Iu s h ( ) ; SwapBuffers(g_HDC);
/ / p rze łą cza b u fo ry
} Funkcja R ender( ) używa funkcji gl R asterPos2f () do określenia położenia tekstu w oknie i funkcji gl Col o r 2 f () do wyboru koloru tekstu. Ponieważ grafika tworzona jest w trybie perspektywy, to wykonywane przekształcenia mają wpływ na współrzędne rastra. Na skutek przesunięcia rastra o jednostkę wzdłuż osi z, współrzędne okna na osi x wynoszą od około - 0 . 6 do 0 . 6. Tekst wyświetlany więc będzie od punktu, którego współrzędna x ma wartość 0 .5, co w efekcie daje w przybliżeniu wycentrowane położenie tekstu. Na tym można zakończyć omówienie czcionek rastrowych. Następny podrozdział po święcony będzie czcionkom konturowym.
Czcionki konturowe Czcionki konturowe są podobne do omówionych czcionek rastrowych, jednak umożli wiają uzyskanie efektów trójwymiarowych. Nadając czcionkom konturowym pewną grubość uzyskać można trójwymiarowe czcionki, które można poddawać przekształce niom jak inne obiekty OpenGL. Aby używać czcionek konturowych, trzeba najpierw zadeklarować w programie tablicę 256 zmiennych typu GLYPHMETRICSFLOAT, które przechowują informacje o położeniu i orien tacji poszczególnych znaków. Typ GLYPHMETRICSFLOAT reprezentuje strukturę wykorzy stywaną do tworzenia tekstów w OpenGL. Zdefiniowano ją w następujący sposób: ty p e d e f s tr u c t _GLYPHMETRICSFLOAT { FLOAT gmfBlackBoxX; FLOAT gmfBlackBoxY; POINTFLOAT gm fptG lyphO ri g i n ; FLOAT gm fCel1IncX; FLOAT gmfCel 1In c Y ; } GLYPHMETRICSFLOAT;
Zmienne typu GLYPHMETRICSFLOAT przekazywać należy funkcji w glU seFontO utl i n e s ( ). Funkcja ta tworzy zestaw list wyświetlania (po jednej dla każdego znaku czcionki), któ re wykorzystuje się następnie do rysowania tekstu w oknie grafiki. Funkcja wglUseFon tO u tl i nes () zdefiniowana jest następująco: w glU seFontO utlinesA ( HDC iidc, i i k o n te k st urządzenia d la c z c io n k i DWORD f i r s t , i i pierw szy znak c z c io n k i, i i d la któ re g o należy utw orzyć l i s t ę w y ś w ie tla n ia BOOL
290
Część II ♦ Korzystanie z OpenGL
DWORD c o u n t,
// // DWORD lis tB a s e , // FLOAT d e v ia tio n , // FLOAT e x tru s io n , // in t fo rm a t. // LPGLYPHMETRICSFLOAT lpgm f / /
lic z b a znaków c z c io n k i, d la k tó ry c h należy utw orzyć l i s t y w y ś w ie tla n ia bazowa l i s t a w y ś w ie tla n ia dopuszczalne zbiorow e o d ch yle n ie od obrysów w a rto ść w k ie ru n ku osi z l i s t y w yś w ie tla n ia ry s u ją o d c in k i lu b w ie lo k ą ty adres b u fo ra , w którym umieszczone zostaną dane znaku
Tworzenie czcionek konturowych przypomina więc tworzenie czcionek rastrowych. Można zauważyć to porównując przedstawioną wcześniej funkcję tworzenia czcionek rastrowych CreateBi tmapFont () z funkcją tworzenia czcionek konturowych: unsigned in t C re a te O u tlin e F o n t(c h a r *fontName, in t fo n tS iz e , f lo a t dep th )
{
HFONT hFont; unsigned in t base;
/ / uchwyt c z c io n k i systemu Windows
base = g lG e n L is ts (2 5 6 );
/ / l i s t y w y ś w ie tla n ia d la 256 znaków
i f (stricm p (fo n tN a m e , "sym bol") == 0)
{
hFont = C re a te F o n t(fo n tS iz e , 0, 0, 0, FW_B0LD, FALSE, FALSE, FALSE, SYMB0L_CHARSET, 0UT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_D0NTCARE | DEFAULT_PITCH, fontN am e);
i e ls e
{
hFont = C re a te F o n t(fo n tS iz e , 0, 0, 0, FWJ30LD, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE | DEFAULT_PITCH, fontN am e);
} i f ( ! hFont) re tu rn 0; SelectObject(g_FIDC, h F o n t); wglUseFontOutlines(g_HDC, 0, 255, base, 0 .0 f, depth, WGL_F0NT_P0LYG0NS. gm f); re tu rn base;
} Można jednak zauważyć kilka różnic pojawiających się w kodach obydwu funkcji. Funkcja C reateO utl i n e F o n t( ) posiada dodatkowy parametr depth określający głębokość konturu czcionek wzdłuż osi z. Kolejną różnicą jest utworzenie 256 list dla wszystkich znaków kodu ASCII. Czcionkę konturową przygotowuje się do użycia za pomocą funk cji w g lU se F o n tO u tlin e sO . Wyświetlanie tekstu za pomocą czcionek konturowych odbywa się dokładnie w ten sam sposób jak za pomocą czcionek rastrowych: g lP u s h A ttri b(G L_LIST_BIT); g lL is tB a s e (b a s e ); gl Cal IL i s t s ( s t r l e n ( s t r ) . GL__UNSIGNED_BYTE, s t r ) ; g lP o p A ttri D ();
Rozdział 1 1 . ♦ W yśw ietlanie te k s tó w
291
Teraz omówionych zostanie kilka funkcji przykładowego programu, który rysować bę dzie obracający się tekst (jak robią to programy wygaszaczy ekranu). Program ten ko rzysta z omówionej wcześniej funkcji C re a te O u tlin e F o n t( ), dlatego nie trzeba teraz przed stawiać jej kodu ani kodu szkieletu aplikacji systemu Windows. Na początku należy zadeklarować następujące zmienne: HDC g_HDC; f l o a t angle = O.Of; unsigned i n t lis tB a s e ; GLYPHMETRICSFLOAT gmf[2 5 6 ];
/ / g lo b a ln y ko n te k st urządzenia / / pierw sza l i s t a w y ś w ie tla n ia / / in fo rm a c je o p o ło że n iu i o r ie n ta c ji znaków
Kolejna funkcja, Cl earFont (), działa tak samo jak funkcja Cl earFont () z poprzedniego przykładu, ale zwalnia 256 list wyświetlania: v o id C le a rF o n t(u n sig n e d in t base)
{ g lD e le te L is ts (b a s e , 256);
} W „ciele” funkcji P r in t S t r in g ( ) umieszczony tym razem został fragment kodu, który centruje wyświetlany tekst względem podanego punktu przestrzeni. Przegląda on w pętli znaki wyświetlanego tekstu sumując ich szerokość na podstawie opisu zawartego w struk turze GLYPHMETRICSFLOAT, a następnie przesuwa układ współrzędnych w ujemnym kie runku osi x o wartość równą połowie sumy szerokości znaków. v o id P rin tS trin g (u n s ig n e d in t base, char * s t r )
{
f l o a t le n g th = 0; i f ( ( s t r == NULL)) r e tu r n ; / / c e n tru je te k s t f o r (unsigned i n t lo o p = 0 ;lo o p < ( s trle n ( s tr )) ;lo o p + + )
{
le n g th + = g m f[s tr[lo o p ]].g m fC e llIncX;
}
g lT r a n s la te f(- le n g th /2 .O .O f,O .O f) ;
/ / o k re ś la długość te k s tu / / sumuje szerokość znaków / / przesuwa układ współrzędnych / / o połowę uzyskanej sumy
/ / ry s u je te k s t g lP u s h A ttrib (G L _ L IS T _ B IT ); g lL is tB a s e (b a s e ); g lC a llL is t s ( s t r le n ( s t r ) , GL_UNSIGNED_BYTE, s t r ) ; g lP o p A t tr ib O ;
} Aby uzyskać właściwy obraz czcionek, należy włączyć oświetlenie sceny. Wywołując funkcję gl Enabl e(GL_COLOR_MATERIAL) można przyjąć też dla uproszczenia, że kolor określa równocześnie materiał tekstu. Operacje te wykonuje funkcja I n i t i a l i z e ( ): v o id I n i t ia liz e O
{ g lC le a r C o lo r ( 0 .0 f, O.Of. O.Of, O .O f);
/ / t ł o w k o lo rz e cz^rny^
292
Część II ♦ Korzystanie z OpenGL
glShadeModel(GL_SM00TH); g l Enable(GL_DEPTH_TEST); glEnable(GL_LIGHTO); glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL);
// // // // //
c ie n io w an ie g ła d k ie usuwa p rz e s ło n ię te pow ierzchnie włącza ź ró d ło ś w ia tła lig h tO włącza o ś w ie tle n ie k o lo r ja k o m a te ria ł
lis tB a s e = C re a te O u tlin e F o n t("A ria l". 10, 0 .2 5 f);
/ / czcionka A r ia l, 10 p k t.
} Funkcja RenderO ilustruje największą zaletę czcionek konturowych, którą jest możli wość dokonywania ich przekształceń w trójwymiarowej przestrzeni. Czcionki konturo we traktuje ona tak samo jak każdy inny obiekt i wykonuje przekształcenie przesunięcia i trzy obroty względem osi układu współrzędnych: void RenderO
{
/ / o pró żn ia b u fo ry ekranu i g łę b i gl Cl e a r(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n tity O ; / / przesuwa układ o 10 je d n o ste k "w g łą b " ekranu / / i wykonuje o b ro ty względem o si układu g lT r a n s la te f ( 0 . 0 f. O .Of, - 1 5 .O f); g lR o ta te f( a n g le * 0 .9 f, l. O f , O.Of, O .O f); g lR o ta te f( a n g le * 1 .5 f, O.Of, l. O f . 0 ,0 f ) ; g lR o ta te f(a n g le . O.Of, O.Of, l. O f ) ; / / w ybiera k o lo r g lC o lo r 3 f( 0 .0 f, l. O f , l. O f ) ; / / w y ś w ie tla te k s t P rin tS trin g O is tB a s e , "Czcionka konturowa OpenGL!"); g l C olo r 3 f ( 1 . Of, O.Of, O .O f); g lT r a n s la t e f ( - 2 . 0 f , O.Of. - 2 . O f); P r in tS tr in g d is tB a s e , "D rugi t e k s t ! " ) ; angle += O . lf ; g lE lu s h O ; SwapBuffers(gJHDC);
/ / p rze łą cza b u fo ry
Po umieszczeniu omówionych funkcji w szkielecie aplikacji systemu Windows uzyskać można program, którego efekt działania pokazuje rysunek 1 1 .2 .
Czcionki pokryte teksturą Jako że czcionki konturowe składają się z wielokątów i traktowane są przez OpenGL jak zwykłe obiekty, ich wygląd wzbogacić można teksturami. Nie trzeba przy tym zaj mować się wyznaczaniem współrzędnych tekstur dla wierzchołków wielokątów two rzących czcionki, ponieważ OpenGL wykonuje to zadanie automatycznie.
Rozdział 1 1 . ♦ W yśw ietlanie te ks tó w
293
Zastosowanie czcionek pokrytych teksturą zaprezentowane zostanie na przykładzie pro gramu wyświetlającego tekst obracający się na ekranie. Dodatkowo program będzie po siadać możliwość oddalania i zbliżania tekstu za pomocą klawiszy A i Z. A oto jego kod źródłowy: ¡11111 D e fin ic je # d e fin e BITMAPJD 0x4D42
. / / / / / / P lik i nagłówkowe # in c lu d e # in c lu d e < s td io .h > # in c lu d e < s td lib .h > # in c lu d e # in c lu d e < g l/g l.h > # in c lu d e < g l/g lu .h > # in c lu d e < g l/g la u x .h >
/ / id e n ty fik a to r form atu BMP
/ / standardowy p lik nagłówkowy Windows
/ / standardowy p lik nagłówkowy OpenGL / / p lik nagłówkowy b ib lio t e k i GLU
I I H U Typy ty p e d e f s tr u c t
in t w id th ; in t h e ig h t; unsigned in t te x ID ; unsigned char *d a ta ; } te x tu r e _ t; / / / / / / Zmienne g lo b a ln e HDC g_HDC; bool f u l l Screen = fa ls e ; bool keyP re sse d [2 5 6 ]; f lo a t angle = O.Of; unsigned in t lis tB a s e ; GLYPHMETRICSFLOAT .gmf[2 5 6 ]; f l o a t zDepth = - 1 0 .Of;
// // // //
szerokość te k s tu ry wysokość te k s tu ry o b ie k t te k s tu ry dane te k s tu ry
// // // //
g lo b a ln y ko n te k st urządzenia tr u e = tr y b pełnoekranowy; fa ls e = tr y b okienkowy ta b lic a p rz y c iś n ię ć k la w isz y
// // // //
pierwsza l i s t a w y ś w ie tla n ia dane o p o ło że n iu i o r ie n ta c ji znaków wykorzystywane przez l i s t y w yś w ie tla n ia bieżąca pozycja na o si z
294
Część II ♦ Korzystanie z OpenGL
/ / / / / / Zmienne te k s tu ry te x tu re _ t * te x tu re ;
/* Kod fu n k c ji Lo a d B itm a p F ile O z o s ta ł p o m in ię ty
*/ te x tu re _ t *L o a d T e x tu re F ile (c h a r ^ file n a m e )
{ BITMAPINFOHEADER te x ln fo ; te x tu re _ t * th is T e x tu re ; / / p rz y d z ie la pamięć d la s tr u k tu r y o p is u ją c e j te k s tu rę th is T e x tu re = ( te x tu r e _ t* ) m a llo c ( s iz e o f ( t e x t u r e _ t ) ) ; i f (th is T e x tu re == NULL) re tu rn NULL; / / ła d u je dane te k s tu ry i sprawdza poprawność wykonania t e j o p e ra c ji th is T e x tu re -> d a ta = L o a d B itm a p F ile (file n a m e , & te x In fo ); i f (th is T e x tu re -> d a ta == NULL)
{ f r e e ( th is T e x tu r e ) ; re tu rn NULL;
} / / o k re ś la ro zm ia ry te k s tu ry th is T e x tu re -> w id th = t e x ln f o . b iW id th ; th is T e x tu re -> h e ig h t = t e x ln f o . b iH e ig h t; / / tw o rzy o b ie k t te k s tu ry g lG e n T e x tu re s (l, & th is T e x tu re -> te x ID ); re tu rn th is T e x tu re ;
} bool L o a d A llT e xtu re sO
{ / / ła d u je te k s tu rę wody te x tu re = L o a d T e x tu re F ile C 'w a te r.b m p "); i f (te x tu r e == NULL) re tu rn fa ls e ; / / k o n fig u ru je te k s tu rę glBindTexture(GL_TEXTURE_2D. te x tu r e - > te x ID ) ; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER. GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T. GL_REPEAT); gl u B u i1d2DMipmaps(GL_TEXTURE_2D, GL_RGB, te x tu re -> w id th , te x tu r e - > h e ig h t, GL_RGB, GL_UNSIGNED_BYTE. te x tu re - > d a ta ) ; / / OpenGL autom atycznie ge n e ru je w spółrzędne te k s tu ry d la znaków c z c io n k i g lT e xG eni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); g lT e xG eni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T);
Rozdział 1 1 . ♦ W yśw ietlanie te k s tó w
295
return true;
} unsigned in t CreateOutlineFont(char *fontName, in t fontSize. flo a t depth)
{
HFONT hFont: unsigned in t base;
// uchwyt czcionki systemu Windows
base = glG en Lists(25 6 );
// li s t y wyświetlania dla 256 znaków czcionki
i f (stricm p (fontName, "symbol") == 0)
{
hFont = CreateFont(fontSize, 0, 0, 0, FW_B0LD, FALSE, FALSE, FALSE, SYMBOL_CHARSET, OUTJT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE | DEFAULT_PITCH. fontName);
} e lse
{
hFont = CreateFont(fontSize, 0, 0, 0, FW_B0LD, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_TT_PRECIS. CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE | DEFAULT_PITCH, fontName);
} i f ( ! hFont) return 0; SelectObject(g_HDC, hFont); wglUseFontOutlines(g_HDC, 0, 255, base, 0 .0 f, depth, WGL_F0NT_P0LYG0NS, gmf); return base;
} void ClearFont(unsigned in t base)
{
glD e le teL ists(b ase, 256);
} void PrintStringCunsigned in t base, char *str)
{
f lo a t length = 0; i f ( ( s tr == NULL)) return; // centruje te kst fo r (unsigned in t loop= 0;loop< (strlen(str)) ;loop++)
{
length+“ g m f[s tr[lo o p ]].gm fCellIncX;
}
glT ra n s la te f(-1ength/2,0 .O f,0 .Of); // wyświetla te kst glPushAttrib(GL_LIST_BIT); g lL istB a se (b a se ); glCal 1L is t s C s t r le n ( s t r ) , GL_UNSIGNED_BYTE, s tr); g lP o p A ttrib O ;
// długość tekstu // sumuje szerokość znaków // przesuwa do środka tekstu
296
Część II ♦ Korzystanie z OpenGL
v o id CleantlpO
{ C le a r F o n t (lis t B a s e ); f re e (t e x t u r e );
i void I n i t i a l i z e O
{ g lC le a r C o lo r (0 . 0 f . O.Of, O .Of. O.Of);
// t ło w k olorze czarnym
glShadeModel(GL_SM00TH); gl Enable(GL_DEPTH_TEST); • gl Enable(GL_LIGHT0); glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); glEnable(GL_TEXTURE_2D);
// // // // // //
cieniow anie g ła d k ie usuwa p rz e sło n ię te powierzchnie włącza źród ło św iatła ligh tO włącza o św ie tle n ie k o lo r jako m ateriał włącza odwzorowania te k stu r
lis t B a s e = C re a te O u tlin e F o n tC 'A ria l”, 14, O . lf ); // ładuje czcionkę A ria l L o a d A llT e x tu re s O ; glBindTexture(GL_TEXTURE_2D, te x t u re -> te x ID );
} // Render // o p is: rysu je scenę void Render()
{ // opróżnia bufory ekranu i głębi g lC le a r (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n t it y O ; // z b liż a lub oddala napis // i obraca go względem o si układu g lT ra n s la te f(O .O f. O.Of, zDepth); g lR o t a t e f (a n g le * 0 .9 f . l.O f, O.Of. O.Of); g lR o t a t e f (a n g le * 1 .5 f , O.Of, l.O f, O.Of); glR o ta te f(a n g le , O.Of, O.Of, l.O f); // w yświetla napis P r in t S t r in g O is t B a s e , "TEKSTURA NA CZCIONCE!"); angle += O .lf; g lF lu s h O ; SwapBuffers(g_FIDC);
// przełącza bufory
} // pon iższy kod stanowi fragment procedury okienkowej WndProcO switch(m essage)
{ case WM_SIZE:
height = HIWORDC1Param); width - LOWORDdParam);
// o k re śla rozmiary okna
Rozdział 1 1 . ♦ W yśw ietlanie te k s tó w
i f (height==0)
297
/ / zapobiega d z ie le n iu przez 0
{ h e ig h t= l;
} g lV ie w p o rt(0, 0, width, h e igh t); gl Matri xMode(GL_PROJECTION); g lL o a d ld e n t it y O ;
/ / nadaje oknu g r a f ik i nowe ro zm ia ry / / w ybiera m acierz rzutow ania / / i re s e tu je ją
// wyznacza proporcje okna g lu P e r s p e c t iv e (5 4 .0 f, ( G L flo a t)w id th /(G L f1o a t) hei g h t. 1 . O f.1 0 0 0 .O f); glMatrixMode(GL_MODELVIEW); g lL o a d ld e n t it y O ;
/ / w ybiera m acierz modelowania / / i re s e tu je ją
return 0;
i”
/ / po n iższy kod stanow i fragm ent fu n k c ji WinMainO done = fa ls e ; In itia liz e O ; w h ile (¡d on e )
{
PeekMessage(&msg, hwnd, NULL. NULL. PM_REM0VE); i f (msg.message == WM_QUIT)
{
done = tr u e ;
/ / kom unikat WM_QUIT? / / j e ś l i ta k , to a p lik a c ja kończy d z ia ła n ie
} e ls e
{
i f (keyPressed[VK_ESCAPE]) done = tr u e ; e ls e
{ i f ( k e y P re s s e d ['A ']) zDepth += O . lf ; i f (k e y P re s s e d ['Z ']) zDepth -= O . lf ; R enderO ; TranslateM essage(& m sg); D ispatchM essage(&m sg);
/ / tłum aczy kom unikat i w ysyła do systemu
} } } C lea nU p O ; Jak ła t w o z a u w a ż y ć , z a s a d n ic z a r ó ż n ic a p o m ię d z y p ro g r a m e m s to s u ją c y m z w y k łe c z c io n k i k o n tu r o w e i p r o g r a m e m r y s u j ą c y m c z c i o n k i k o n tu r o w e p o k r y te te k s tu r ą p o le g a n a ty m ,
298
Część II ♦ Korzystanie z OpenGL
że w drugim z tych przypadków trzeba poinformować maszynę OpenGL o konieczności automatycznego wyznaczania współrzędnych tekstury. Służy temu poniższy fragment kodu: / / OpenGL autom atycznie ge n e ru je współrzędne te k s tu ry d la znaków c z c io n k i glTexGeni(GL_S, GL_TEXTURE_GEN_MODE. GL_OBJECT_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T);
Należy zwrócić uwagę na zastosowanie funkcji glTexGeni (). Po raz pierwszy zastosowa na została w przykładach omawiających odwzorowanie otoczenia. Teraz trzeba przyj rzeć się jej dokładniej. Pierwszy parametr funkcji glTexGeni() informuje maszynę OpenGL o tym, która ze współrzędnych tekstury będzie generowana automatycznie. Pa rametr ten może przyjmować wartości GL S lub GL_T w przypadku tekstur dwuwymia rowych. Drugi parametr funkcji glTexGeni () określa tryb odwzorowania tekstury używany pod czas automatycznej generacji współrzędnej. Może on przyjmować wartości przedsta wione w tabeli 11 . 1 . Tabela 11.1. Tryb odwzorowania tekstury Wartość
Opis
GL_EYE_LINEAR
Tekstura umieszczana jest w stałym miejscu ekranu. Obiekty, których obraz pojawia się w tym miejscu, są pokrywane teksturą.
GL_OBJECT_LINEAR
Tekstura umieszczana jest na obiekcie. Tekstura reprezentuje odwzorowanie otoczenia (mapę sferyczną).
GL_SPHERE_MAP
Tekst napisu można wyświetlić po związaniu tekstury z obiektem tekstury. W przykładzie tym do pokrycia czcionki użyta została tekstura imitująca powierzchnię wody, a uzyskany rezultat przedstawia rysunek 1 1 .3. Rysunek 11.3. Przykład czcionki konturowej pokrytej teksturą
Rozdział 1 1 . ♦ W yśw ietlanie te k s tó w
299
Podsumowanie Czcionki rastrowe stanowią najprostszy sposób wyświetlania tekstu w OpenGL. Czcionki rastrowe przygotowuje się do użycia za pomocą funkcji wglllseFontBitmaps(), która tworzy je na podstawie czcionek dostępnych w systemie Windows. W podobny sposób — przez wywołanie funkcji wglUseOutl ineFonts() — powstają czcionki konturowe. Odróżnia je jednak możliwość określenia ich grubości i przetwa rzania w taki sposób, w jaki przetwarza się każdy obiekt trójwymiarowy. Dzięki temu można je obracać, skalować i przesuwać w trójwymiarowej przestrzeni. Wywołanie funk cji wglUseOutl ineFontsO tworzy reprezentację czcionek konturowych, która wykorzy stuje wielokąty OpenGL. Ponieważ czcionki konturowe reprezentowane są za pomocą wielokątów i traktowane są przez OpenGL jak zwykłe obiekty trójwymiarowe, można wzbogacić ich wygląd po krywając je teksturami. Współrzędne tekstury dla czcionek są automatycznie generowane przez maszynę OpenGL.
300
Część II ♦Korzystanie z OpenGL
Rozdział 12.
Bufory OpenGL Bufory OpenGL wykorzystywane były w każdym z przedstawionych programów, ale jak dotąd nie zostały omówione szczegółowo. Praktycznie każdy program używał bufo rów koloru i głębi w celu podwójnego buforowania grafiki i usuwania przesłoniętych powierzchni. W bieżącym rozdziale przedstawione zostaną także inne zastosowania bu forów, a ponadto omówione zostaną bufory powielania i akumulacji. W rozdziale tym przedstawione będą: ♦ bufory OpenGL; ♦ zastosowania bufora kolorów; ♦ zastosowania bufora głębi; ♦ zastosowania bufora powielania; ♦ zastosowania bufora akumulacji.
Bufory OpenGL — wprowadzenie OpenGL udostępnia kilka różnych typów buforów. Wszystkie bufory używane są w pro cesie tworzenia obrazu na ekranie monitora. Obraz powstający na ekranie reprezento wany jest za pomocą dwuwymiarowej tablicy pikseli, czyli bufora, w którym przecho wywany jest opis kolorów każdego piksela. Bufor jest więc zbiorem danych opisujących piksele. Na przykład bufor kolorów OpenGL przechowuje dane o kolorach pikseli zapi sane zgodnie z modelem RGBA lub modelem indeksowym. Zawartość bufor obrazu powstaje na podstawie zawartości wszystkich buforów dostęp nych w systemie. W OpenGL bufor obrazu jest więc kombinacją bufora koloru, bufora głębi, bufora powielania i bufora akumulacji. Przy zmianie zawartości któregokolwiek z wymienionych buforów zmienia się w efekcie zawartość bufora obrazu.
Konfiguracja formatu pikseli Bufory OpenGL stosowane były od początku poznawania OpenGL, a mimo to nie docze kały się specjalnego omówienia. Większość przykładowych programów używała funk cji S e tupP ixel Format O w celu skonfigurowania kontekstu urządzenia dla okna grafiki
302
Część II ♦ Korzystanie z OpenGL
OpenGL. Kontekst ten określał stosowany format pikseli i tym samym format przecho wującego je bufora.. Wystarczy przypomnieć kod fimkcji S etupP ixel Format () : v o id S e tu p P ix e lFormat(HDC hDC) in t n P ix e lFormat ;
/ / indeks form atu p ik s e li
s t a t ic PIXELFORMATDESCRIPTOR pfd sizeof(PIXELFORMATDESCRIPTOR),
1. PFD_DRAW_TO_WINDÓW I PFD_SUPPORT_OPENGL PFD_DOUBLEBUFFER, PFD_TYPE_RGBA. 32.
O, 0. 0. 0. 0, 0, 0. 0, 0.
0. 0. 0, 0. 16. 0, 0.
PFD_MAIN_PLANE, 0. 0 . 0 . 0 };
/ / / / / / / / / / / / / / / / / /
rozm iar s tr u k tu r y domyślna w ersja g r a fik a w okn ie g r a fik a OpenGL podwójne buforow anie tr y b kolorów RGBA 3 2 -b ito w y o p is kolorów n ie s p e c y fik u je b itó w kolorów bez b u fo ra a lfa n ie s p e c y fik u je b itu p rz e s u n ię c ia bez b u fo ra a kum ulacji ig n o ru je b it y a ku m u la cji 1 6 -b ito w y b u fo r z bez bu fo ra p o w ie la n ia bez buforów pomocniczych główna płaszczyzna rysow ania zarezerwowane ig n o ru je maski warstw
/ / w ybiera n a jb a rd z ie j zgodny fo rm a t p ik s e li nP ixelF orm at = ChoosePixelFormatChDC. & p fd ); / / o k re ś la fo rm a t p ik s e li d la danego k o n te k stu urządzenia S etPixelForm at(hD C . n P ixe lF orm a t. & p fd );
W celu określenia formatu pikseli można posłużyć się strukturą PIXELFORMATDESCRIPTOR. Oto jej definicja: ty p e d e f s tr u c t tagPIXELFORMATDESCRIPTOR
{
WORD WORD DWORD BYTE BYTE BYTE BYTE BYTE BYTE BYTE BYTE BYTE BYTE BYTE BYTE BYTE BYTE BYTE BYTE
nS ize; nV e rsio n ; dwFlags; i P ixe l Type; c C o lo rB its ; cR edB its; cR e d S h ift; cG re e n B its; c G re e n S h ift; c B lu e B its ; c B lu e S h ift; c A lp h a B its ; c A lp h a S h ift; cAccum Bits; cAccumRedBits; cAccumGreenBits; cAccum BlueBits; cAccum AlphaBits; cD e p th B its;
/ / / / / / / / / / / / / / / / / / /
rozm iar s tr u k tu r y w e rsja s tr u k tu r y w ła ściw o ści b u fo ra p ik s e li ty p danych p ik s e li rozm iar b u fo ra k o lo ru lic z b a b itó w składow ej czerw onej p rz e s u n ię c ie b itó w składow ej czerw onej lic z b a b itó w składow ej z ie lo n e j p rze s u n ię c ie b itó w składow ej lic z b a b itó w składow ej n ie b ie s k ie j p rze s u n ię c ie b itó w składow ej lic z b a b itó w w spółczynnika a lfa p rz e s u n ię c ie b itó w w spółczynnika a lfa lic z b a b itó w b u fo ra a ku m u la cji lic z b a b itó w składow ej czerw onej w b. a ku m u la cji lic z b a b itó w składow ej z ie lo n e j w b. a ku m u la cji lic z b a b itó w składow ej n ie b ie s k ie j w b. a k u m u la cji lic z b a b itó w w spółczynnika a lfa w b. a ku m u la cji lic z b a b itó w b u fo ra g łę b i
Rozdział 1 2 . ♦ Bufory OpenGL
BYTE c S te n c ilB its ; BYTE c A u x B u ffe rs ; BYTE iLa ye rT yp e ; BYTE bReserved; DWORD dwLayerMask; DWORD dw VisibleM ask; DWORD dwDamageMask; } P IXELFORMATDESCRIPTOR;
/ / / / / / /
303
lic z b a b itó w bu fo ra p o w ie la n ia lic z b a buforów pomocniczych (MS OpenGL n ie używa) p ole n ie je s t ju ż używane, a w artość ignorowana lic z b a warstw g r a f ik i p ole n ie je s t ju ż używane, a w artość ignorowana k o lo r p rz e z ro c z y s ty lu b indeks k o lo ru warstwy pod spodem pole n ie je s t ju ż używane, a w artość ignorowana
Pola tej struktury inicjuje się wewnątrz funkcji SetupPixel Format (). Omówione teraz zo stanie znaczenie najważniejszych pól struktury. Pole dwFlags określa właściwości bufo ra pikseli. Tabela 12.1 prezentuje znaczniki używane do określenia wartości tego pola. Tabela 12.1. Znaczniki pola dwFlags Znacznik
Opis
PFD_D RAW_T0_WI NDOW
Bufor może być wykorzystywany do tworzenia grafiki w oknie
PFD_DRAWJO_BITMAP
Bufor może być wykorzystywany do tworzenia mapy bitowej w pamięci
PFD_SUPPORT_GDI
Bufor GDI
PFD_SUPPORT_OPENGL
Bufor OpenGL
PFD_DOUBLEBUFFER
Podwójne buforowanie
PFD_STEREO
Bufor stereoskopowy
PFD_DEPTH_DONTCARE
Żądany format pikseli może — ale nie musi — posiadać bufora głębi; znacznik wykorzystywany jedynie podczas wywoływania funkcji ChoosePixelForm at 0
PFD_DOUBLEBUFFER_DONTCARE
Żądany format pikseli może — ale nie musi — być podwójnie buforowany; znacznik wykorzystywany jedynie podczas wywoływania funkcji ChoosePixel Form at0
PFD_STEREO_DONTCARE
Żądany format pikseli może — ale nie musi — dysponować buforem stereoskopowym; znacznik wykorzystywany jedynie podczas wywoływania funkcji ChoosePixel Format ()
Pole i P ixel Type wykorzystywane jest do określenia typu danych opisujących piksele. Zwy kle posiada wartość PFD_TYPE_RGBA. Dopuszczalne wartości pola prezentuje tabela 12.2. Tabela 12.2. Wartości pola iPixelType Wartość
Opis
PFD TYPE RGBA
Każdy piksel opisany jest za pomocą czterech składowych występujących w następującym porządku: składowa czerwona, składowa zielona, składowa niebieska i współczynnik alfa
PFD TYPE COLORINDEX
Każdy piksel opisany jest za pomocą indeksu koloru
Pola c C o lo rB its , cD epthB its, cAccumBits i c S te n c ilB its definiują rozmiary buforów ko loru, głębi, akumulacji i powielania. Funkcja SetupPi xel Format () nadaje polom cAccum B its i c S te n c ilB its wartości 0. Nadanie zerowego rozmiaru dowolnemu buforowi jest równoznaczne z jego wyłączeniem.
304
Część II ♦ Korzystanie z OpenGL
Pole iLayerType może przyjmować wartości PFD_MAI N_P LANE, PFD_OVERLAY_PLANE lub PFD_UNDERLAY_PLANE. Najczęściej nadaje się mu wartość PFD_MAIN_PLANE określającą główną warstwę rysowania grafiki. Po nadaniu wartości wszystkim polom struktury PIXELFORMATDESCRIPTOR należy przeka zać ją jako parametr funkcji ChoosePixel Format O , która próbuje dopasować najbardziej zgodny z formatów pikseli obsługiwanych przez kontekst urządzenia do opisu formatu zawartego w strukturze PI XELFORMATDESCRI PTOR. Funkcja ChoosePixel Format () zwraca indeks formatu pikseli, który następnie należy przekazać funkcji S e tP ix e l Format () wy bierającej format pikseli dla danego kontekstu urządzenia. Ilustruje to poniższy frag ment kodu: / / w y p e łn ie n ie s tr u k tu r y PIXELFORMATDESCRIPTOR / / w ybiera n a jb a rd z ie j zgodny fo rm a t p ik s e li n P ix e lFormat = C hooseP ixelFormat(hDC. & p fd ); / / o k re ś la fo rm a t p ik s e li d la danego k o n te k stu urządzenia S e tP ix e lFormat(hDC, n P ix e lFormat, & p fd );
} I tak opanowana została umiejętność konfigurowania buforów OpenGL, więc należy przyjrzeć się sposobom ich wykorzystania.
Opróżnianie buforów Zanim przedstawione zostanie szczegółowo zastosowanie każdego z buforów omówić trzeba najpierw sposoby ich opróżniania. OpenGL udostępnia dla każdego typu bufo ra osobną funkcję pozwalającą zdefiniować wartość, którą wypełniony zostanie bufor podczas opróżniania. Opróżniając bufor za pomocą funkcji gl Cl ear O powoduje się jego wypełnienie zdefiniowaną wcześniej wartością. Funkcje te posiadają następujące prototypy: v o id v o id vo id v o id
C lea rC o lo r(G L cla m p f red, GLclampf green, GLclampf b lu e , GLclampf a lp h a ); glClearDepth(G Lclam pd d e p th ); g lC le a r S te n c il(G L in t s ); g lC learA ccu m (G L flo a t red, G L flo a t green, G L flo a t b lu e . G L flo a t a lp h a );
Wymienione funkcje pozwalają określić wartość używaną podczas opróżniania buforów koloru, głębi, powielania i akumulacji. Zmienne typów GLclampf i GLclampd muszą posia dać wartość z przedziału od O. Odo 1.0. Domyślnie bufory wypełniane są wartością 0.0 (z wyjątkiem bufora głębi, który wypełniany jest wartością 1 . 0). W celu opróżnienia bufora należy wywołać funkcję glClearO zdefiniowaną w nastę pujący sposób: vo id g lC l e a r(GLbi t f i e ld mask);
Parametr mask stanowić może kombinację znaczników wymienionych w tabeli 12.3.
Rozdział 1 2 . ♦ Bufory OpenGL
305
Tabela 12.3. Znaczniki używane przez funkcją glClearQ Znacznik
Opis
GL_COLOR_BUFFER_BIT
Bufor koloru w modelu RGBA
GL_DEPTH_BUFFER_BIT
Bufor głębi
GL_STENCIL_BUFFER_BIT
Bufor powielania
GL_ACCUM_BUFFER_BIT
Bufor akumulacji
Bufor koloru Bufor koloru przechowuje opis koloru pikseli w modelu RGBA lub indeksowym. Naj częściej na ekranie rysuje się właśnie zawartość tego bufora. Umożliwia on dodatkowo zastosowanie mechanizmu podwójnego buforowania oraz uzyskanie obrazu stereosko powego.
Podwójne buforowanie Podwójne buforowanie wykorzystywane jest podczas animacji obiektów. Podczas gdy zawartość jednego bufora wyświetlana jest na ekranie, kolejna klatka animacji tworzona jest w drugim buforze. Po narysowaniu kompletnej sceny kolejnej klatki bufory są przełą czane, co pozwala zachować płynność animowanej grafiki. Tworzenie grafiki w buforze, którego zawartość nie jest wyświetlana, pozwala uniknąć migotania, które pojawia się podczas tworzenia grafiki w buforze, którego zawartość jest prezentowana na ekranie. Podwójne buforowanie dostępne jest tylko dla bufora kolorów. Mechanizmu tego nie można więc wykorzystywać w przypadku buforów głębi, akumulacji i powielania. Jeśli wybrany format pikseli umożliwia podwójne buforowanie, to OpenGL domyślnie tworzy grafikę w buforze, którego zawartość nie jest wyświetlana. Za pomocą funkcji gl D ra w B u ffe r( ) można określić bufor, w którym rysowana jest grafika. Funkcja ta zde finiowana jest następująco: v o id g1DrawBuffer(GLenum mode);
Parametr mode może przyjmować wartości przedstawione w tabeli 12.4. Tabela 12.4. Tryby rysowania grafiki określane za pomocą funkcji glDrawBufferQ dla podwójnego buforowania Tryb
Opis
GL_FRONT
Grafika tworzona jest w buforze koloru wyświetlanym na ekranie (domyślny sposób działania, gdy mechanizm podwójnego buforowania nie jest dostępny)
GL_BACK
Grafika tworzona jest w buforze koloru, którego zawartość nie jest wyświetlana na ekranie (domyślny sposób działania, gdy mechanizm podwójnego buforowania jest dostępny)
GL_FRONT_AND_BACK
Grafika tworzona jest w obu buforach koloru
GL_NONE
Grafika nie jest rysowana w żadnym z buforów koloru
306
Część II ♦ Korzystanie z OpenGL
W przypadku stosowania mechanizmu podwójnego buforowania domyślną wartością parametru jest GL BACK, a w przeciwnym przypadku GL_FR0NT. Za pomocą funkcji glReadBufferO można określić, z którego z buforów koloru pobie rany jest opis pikseli podczas wykonywania funkcji g lReadPixels (), glCopyPixelsO, gl CopyTexImage*() i gl CopyTexSubImage*(). Funkcja ta posiada następujący prototyp: v o id glReadBuffer(GLenum mode):
Parametr mode może przyjmować te same wartości co w przypadku funkcji glDrawBufferO.
Buforowanie stereoskopowe Buforowanie stereoskopowe wykorzystuje osobne bufory dla obrazu widzianego lewym i prawym okiem, co umożliwia uzyskanie pełnego wrażenia trójwymiarowości prezen towanych obiektów. Podobnie jak w przypadku podwójnego buforowania bufor, w któ rym tworzona jest grafika, określa się za pomocą funkcji glDrawBuffer(). Jej parametr może przyjmować wartości przedstawione w tabeli 12.5. Wartości tych nie należy łą czyć z wartościami przedstawionymi wcześniej w tabeli 12.4. Tabela 12.5. Tryby rysowania grafiki określane za pomocą funkcji glDrawBufferQ dla buforowania stereoskopowego Tryb
Opis
GL_LEFT
Grafika tworzona jest w obu buforach (wyświetlanym i nie w yświetlanym ) dla lew ego oka
GL_RIGHT
Grafika tworzona jest w obu buforach (wyświetlanym i nie w y św ietlanym ) dla prawego oka
GL_FRONT_LEFT
Grafika tworzona jest w wyświetlanym buforze dla lew ego oka
GL_FRONT_RIGHT
Grafika tworzona jest w wyświetlanym buforze dla prawego oka
GL_BACK_LEFT
Grafika tworzona jest dla lew ego oka w buforze, którego zawartość nie jest wyświetlana
GL_BACK_RIGHT
Grafika tworzona jest dla prawego oka w buforze, którego zawartość nie jest wyświetlana
Bufor głębi Typowym zastosowaniem bufora głąbi jest zapobieganie rysowaniu powierzchni obiek tów, która nie jest w danej scenie widoczna. Bufor głębi przechowuje dla każdego piksela odległość pomiędzy obserwatorem i obiektem, do reprezentacji którego należy dany piksel. Podczas rysowania obiektu, który przesłania obserwatorowi inny obiekt, modyfikowana jest zawartość bufora głębi w zależności od wybranej funkcji porów nania głębi.
Rozdział 1 2 . ♦ Bufory OpenGL
307
Funkcje porównania głębi Podczas rysowania grafiki OpenGL współrzędna z punktu, którego reprezentacją będzie dany piksel, porównywana jest zawsze z wartością współrzędnej z zapisaną dla danego piksela w buforze głębi. Sposób porównania określany jest za pomocą funkcji gl DepthF u n c ( ) o następującym prototypie: v o id glDepthFunc(GLenum func );
Parametr func może przyjmować wartości przedstawione w tabeli 12.6. Tabela 12.6. Funkcje porównania głąbi Funkcja
Opis
GL_NEVER
Rezultat porównania jest zawsze negatywny
GL_LESS
Rezultat porównania jest pozytywny, je śli nowa wartość współrzędnej z jest mniejsza od wartości współrzędnej z zapisanej w buforze (domyślna funkcja porównania głębi)
GLJQUAL
Rezultat porównania jest pozytywny, je śli nowa wartość współrzędnej z jest równa wartości współrzędnej z zapisanej w buforze
GL_LEQUAL
Rezultat porównania jest pozytywny, jeśli nowa wartość współrzędnej z jest mniejsza lub równa wartości współrzędnej z zapisanej w buforze
GL_GREATER
Rezultat porównania jest pozytywny, jeśli nowa wartość współrzędnej z jest większa od wartości współrzędnej z zapisanej w buforze
GL_NOTEQUAL
Rezultat porównania jest pozytywny, je śli nowa wartość współrzędnej z jest różna od wartości współrzędnej z zapisanej w buforze
GL_GEQUAL
Rezultat porównania jest pozytywny, je śli nowa wartość współrzędnej z jest większa lub równa wartości współrzędnej z zapisanej w buforze
GL_ALWAYS
Rezultat porównania jest zawsze pozytywny
Jeśli wynik porównania za pomocą wybranej funkcji jest pozytywny, to OpenGL umiesz cza piksel w buforze koloru. Domyślną funkcją porównania głębi jest GL LESS, która powoduje narysowanie piksela, jeśli współrzędna z punktu, który on reprezentuje na ekra nie, jest mniejsza od wartości współrzędnej z zapisanej w buforze głębi.
Zastosowania bufora głębi Najczęstszym zastosowaniem bufora głębi jest zapobieganie rysowaniu niewidocznych powierzchni. Bufor głębi można także wykorzystać w celu uzyskania przekroju po to, by zaprezentować wewnętrzną budowę złożonego obiektu (na przykład komputera czy silnika). Prezentację rozmaitych zastosowań bufora głębi należy rozpocząć od prostego programu, który umożliwiać będzie użytkownikowi zmianę funkcji porównania głębi z GL LESS na GL_ALWAYS i z powrotem poprzez naciśnięcie klawisza spacji. Program rozpocznie dzia łanie korzystając z funkcji GL_LESS, dzięki czemu tworzona przez niego scena będzie po siadać normalny wygląd, gdyż przesłonięte powierzchnie nie będą rysowane. Po naciśnię ciu klawisza spacji funkcja porównania głębi zostanie zmieniona na GL ALWAYS, a obiekty sceny widoczne będą w porządku rysowania (a nie przesłaniania). W tym przypadku —
308
Część II ♦ Korzystanie z OpenGL
jeśli na przykład sześcian przesłania kulę — aby uzyskać prawidłowy wygląd sceny, należy najpierw narysować kulę, a dopiero potem sześcian. Jeśli najpierw zostanie nary sowany sześcian, to kula zostanie narysowana tak, jakby znajdowała się przed sześcia nem, co oczywiście nie jest prawdą. W przypadku funkcji GL LESS nie trzeba zwracać uwagi na kolejność rysowania obiektów, ponieważ OpenGL automatycznie zapobiega rysowaniu przesłoniętych powierzchni korzystając z bufora głębi. Poniżej zaprezento wany został kod źródłowy omawianego programu. III III P l i k i nagłówkowe # include # include # include < gl/gl.h> # include < gl/glu.h> f in c lu d e < gl/glaux.h> / / / / / / Zmienne g lob aln e f lo a t angle = O.Of; HDC g_HDC; bool f u l l Screen = f a ls e ; bool keyPressed[256]; bool depthLess = tru e ;
// standardowy p lik nagłówkowy Windows // standardowy p lik nagłówkowy OpenGL // p lik nagłówkowy b ib lio t e k i GLU // fu n kcje pomocnicze OpenGL
// // // // //
b ie żą cy kąt obrotu g lo b a ln y kontekst urządzenia tru e = try b pełnoekranowy; f a ls e = try b okienkowy t a b lic a p r z y c iś n ię ć k la w is zy
// tru e : funkcja GL_LESS // f a ls e : funkcja GL_ALWAYS
// I n it ia liz e / / o p is : in ic j u j e OpenGL void I n i t i a l i z e O
{
glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glEnable(GL_LIGHTING); glEnable(GL_LIGHTO); glEnable(GL_COLOR_MATERIAL); glShadeModel(GL_SMOOTH);
// // // // // // g lC le a rC o lo r(O.O f, O.Of, O.Of, O.Of); //
włącza bufor g łę b i wyłącza rysowanie ty ln y c h stro n w ielokątów włącza o ś w ie tle n ie włącza źró d ło ś w ia tła lig h tO k o lo r jako m a te ria ł c ien io w a n ie g ła d k ie t ł o w k o lo rze czarnym
// Render / / o p is : ry s u je scenę void RenderO
{ // opróżnia bufory ekranu i g łę b i glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n t it y O ; // w ybiera fu n kcję porównania g łę b i i f (depthLess) glDepthFunc(GL_LESS); e ls e glDepthFunc(GL_ALWAYS); angle += 0.3; // odsuwa scenę o 100 jednostek g lT r a n s la te f( O .O f, O.Of, -1 0 0 .Of);
// zw iększa kąt obrotu
Rozdział 1 2 . ♦ Bufory OpenGL
/ / n a jp ie rw ry s u je n ie p rz e z ro c z y s ty sześcian g lP u s h M a trix (); g lR o ta te f(a n g le , l. O f . O.Of, O .O f); g lR o ta te f( a n g le * 0 .5 f, O.Of, l. O f , O .O f); g lR o ta te f( a n g le * 1 .2 f, O.Of, O.Of, l. O f ) ; g lC o lo r 3 f( 0 .2 f, 0 .4 f, 0 .6 f ) ; a u x S o lid C u b e (3 0 .0 f); g lP o p M a tr ix () ; / / a n a s tę p n ie schowaną za nim n ie p rz e z ro c z y s tą k u lę g lP u s h M a trix (); g lT ra n s la te f(O .O f, O.Of, -5 0 .O f); g lR o ta te f(a n g le , O.Of, l. O f , O .O f); g lC o lo r 3 f( 1 . O f, l. O f , l. O f ) ; a u x S o lid S p h e re (3 0 .0 f); g lP o p M a tr ix () ; g lF lu s h O ; SwapBuffers(g_HDC);
/ / p rze łą cza b u fo ry
} / / fu n k c ja o k re ś la ją c a fo rm a t p ik s e li v o id S e tu p P ix e lFormat(HDC hDC)
{
i n t n P ix e lFormat; s t a t ic PIXELFORMATDESCRIPTOR pfd = sizeof(PIXELFORMATDESCRIPTOR),
1. PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 32,
O, O, O, O, O, O, O, O, O,
O, O, O, O, 16, O, O, PFD_MAIN_PLANE, O,
0. O, O };
/ / indeks form atu p ik s e li
/ ro zm ia r s tr u k tu r y / domyślna w ersja / g r a fik a w o kn ie / g r a fik a OpenGL / podwójne buforow anie / tr y b kolorów RGBA / 3 2 -b ito w y o p is kolorów / n i e s p e c y fik u je b itó w kolorów / bez b u fo ra a lfa / n ie s p e c y fik u je b it u p rze s u n ię c ia / bez b u fo ra akum ulacji / ig n o ru je b it y a ku m u la cji / 1 6 -b ito w y b u fo r z / bez b u fo ra p o w ie la n ia / bez buforów pomocniczych / główna płaszczyzna rysowania / zarezerwowane / ig n o ru je maski warstw
/ / w ybiera n a jb a rd z ie j zgodny fo rm a t p ik s e li n P ix e lFormat = C hooseP ixelFormat(hDC, & p fd ); / / o k re ś la fo rm a t p ik s e li d la danego k o n te k stu urządzenia S e tP ix e lFormat(hDC, n P ix e lF o rm a t, & p fd );
} / / procedura okienkowa LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
s t a t ic HGLRC hRC; / / k o n te k s t tw o rze n ia g r a f ik i s t a t ic HDC h D C ;// k o n te k s t urządzenia in t w id th , h e ig h t ; / / szerokość i wysokość okna
309
310
Część II ♦ Korzystanie z OpenGL
sw itch(m essage)
{ case WM_CREATE: hDC = GetDC(hwnd); g_HDC = hDC; S etupP ixelF orm at(hD C );
/ / okno je s t tw orzone / / pob ie ra k o n te k s t u rządzenia d la
okna
/ / w yw ołuje fu n k c ję o k re ś la ją c ą fo rm a t
/ / tw o rzy k o n te k s t tw o rze n ia g r a f ik i i czyn i go bieżącym hRC = w glC re a te C o n te xt(h D C ); wglMakeCurrent(hDC, hRC); re tu rn 0; break; case WM_CL0SE:
/ / okno je s t zamykane
/ / dezaktyw uje bie żą cy k o n te k s t tw o rze n ia g r a f ik i i usuwa go w glM akeCurrent(hDCf NULL); w g lD e le te C o n te x t( hRC); / / w stawia kom unikat WM_QUIT do k o le jk i P ostQ uitM essage(0); re tu rn 0; break; case WMSIZE: h e ig h t = HIW0RD(1 Param); w id th = L0W0RD(1Param); i f (h e ig h t= = 0 )
/ / po b ie ra nowe ro zm ia ry okna
/ / unika d z ie le n ia przez 0
t h e ig h t= l;
} gl V ie w p o rt(0 , 0, w id th , h e ig h t); glMatrixMode(GL_PROJECTION); g lL o a d ld e n tity O ;
/ / nadaje nowe wym iary oknu OpenGL / / w ybiera m acierz rzutow ania / / re s e tu je m acierz rzu to w a n ia
/ / wyznacza p ro p o rc je obrazu g lu P e r s p e c tiv e ( 5 4 .0 f,( G L flo a t) w id th /( G L flo a t) h e ig h t.l.O f,1 0 0 0 .0 f) ; glMatrixMode(GL_MODELVIEW); g lL o a d ld e n tity O ;
/ / w ybiera m acierz modelowania / / re s e tu je m acierz modelowania
re tu rn 0; break; case WM_KEYD0WN: keyPressedEwParam] = tr u e ; re tu rn 0; break; case WM_KEYUP: keyPressed[wParam] = fa ls e ; re tu rn 0; break;
/ / z o s ta ł n a c iś n ię ty k la w isz?
p ik s e li
Rozdział 1 2 . ♦ Bufory OpenGL
d e fa u lt: bre ak;
} r e tu rn (DefWindowProc(hwnd, message, wParam, lP a ra m ));
} / / p u n k t, w którym rozpoczyna s ię wykonywanie a p lik a c ji i n t WINAPI WinMain(HINSTANCE h ln s ta n c e , HINSTANCE hP re vIn sta n ce , LPSTR IpCmdLine, i n t nShowCmd)
{
WNDCLASSEX windowClass; HWND hwnd; MSG msg; bool done; DWORD dw ExStyle; DWORD d w S tyle; RECT wi ndowRect;
// // // // // //
kla sa okna uchwyt okna komunikat znacznik zakończenia a p lik a c ji rozszerzony s ty l okna s ty l okna
/ / zmienne pomocnicze i n t w id th = 800; i n t h e ig h t = 600; i n t b it s = 32; f u l l Screen = FALSE wi ndowRect.1e f t = (1 ong)0 ; w indow R ect. rig h t= ( lo n g )w id th ; wi ndowRect.to p = (1 ong)0; wi ndowRect. b o tto m = (1ong) hei g h t;
/ / s tru k tu ra o k re ś la ją c a ro zm ia ry okna
/ / d e f in ic ja k la s y okna w indow C lass.cbSize= sizeof(WNDCLASSEX); w indo w C la ss.s t y le= CS_HREDRAW | CS_VREDRAW; wi ndowClass.1 pfnWndProc= WndProc; w indow C lass.cbC lsE xtra= 0; windowClass.cbW ndExtra= 0; wi ndowClass. h ln sta n ce = h ln s ta n c e ; w indow C lass.hIcon= LoadIcon(NULL, IDI_APPLICATION); w indow C lass.hC ursor= LoadCursor(NULL, IDC_ARR0W); w indowC lass.hbrBackground= NULL; windowClass.lpszMenuName= NULL; wi ndowClass.1pszClassName= "M o ja K la sa "; windowClass.hIconSm= LoadIcon(NULL, IDI_WINL0G0);
// // // //
domyślna ikona domyślny k u rs o r bez t ł a bez menu
/ / logo Windows
/ / r e je s t r u je k la s ę okna i f ( !R egisterC lassE x(& w indow C lass)) re tu rn 0; i f (fu llS c re e n )
{
/ / tr y b pełnoekranowy?
DEVMODE dm S creenSettings; / / tr y b urządzenia memset(&dmScreenSetti ng s, 0 . si zeof(dm S creenS etti n g s )); dm S creenSettings.dm Size = s ize o f(d m S c re e n S e ttin g s ); dm ScreenSettings.dm PelsW idth = w id th ; / / szerokość ekranu dm S creenSettings.dm PelsH eight = h e ig h t; / / wysokość ekranu dm S creenS ettings.dm B itsP erP el = b it s ; / / lic z b a b itó w na p ik s e l dm ScreenSetti n g s .dmFi elds=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
311
312
Część II ♦ Korzystanie z OpenGL
i f (C hangeD isplayS ettings(& dm S creenS ettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
{ / / p rz e łą c z e n ie try b u n ie pow iodło s ię , z powrotem tr y b okienkowy MessageBox(NULL, "P rz e łą c z e n ie n ie pow iodło s ię . " , NULL, MB_0K); fullScreen=FALSE;
i f ( fu llS c re e n )
/ / tr y b pełnoekranowy?
{ dw ExStyle=WS_EX_APPWINDOW; dwStyle=WS_POPUP; ShowCursor(FALSE);
/ / rozszerzony s ty l okna / / s ty l okna / / ukrywa k u rs o r myszy
}
el se dwExSty1e=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; dwStyle=WS_OVERLAPPEDWINDOW;
/ / d e fin ic ja k la s y okna / / s ty l okna
} AdjustWindowRectEx(&windowRect, dw Style, FALSE, dw ExStyle);
/ / k o ryg u je rozm iar okna
/ / tw o rzy okno hwnd = CreateWindowEx( NULL, / / rozszerzony s ty l okna "M ojaK lasa", / / nazwa k la s y "B u fo r g łę b i, p rz y k ła d p ie rw szy: fu n k c je porównania g łę b i" , / / nazwa a p lik a c ji dw Style | WS_CLIPCHILDREN | WS_CLI PSIBLINGS. 0, 0, / / współrzędne x ,y w in d o w R e c t.rig h t - windowR ect.1 e f t , windowR ect. bottom - w indow R ect.top, / / szerokość, wysokość NULL, / / uchwyt okna nadrzędnego NULL, / / uchwyt menu h ln s ta n c e , / / in s ta n c ja a p lik a c ji NULL); / / bez dodatkowych parametrów / / sprawdza, czy u tw o rze n ie okna n ie pow iodło s ię (wtedy w a rto ść hwnd równa NULL) i f ( ! hwnd) re tu rn 0; ShowWindow(hwnd, SW_SH0W); UpdateWindow(hwnd);
/ / w yś w ie tla okno / / a k tu a liz u je okno
done = fa ls e ; In itia liz e O ;
/ / i n ic ju je zmienną warunku p ę t li / / i n ic ju je OpenGL
/ / p ę tla p rze tw a rz a n ia komunikatów w h ile (!do n e ) PeekMessage(&msg, hwnd, NULL, NULL, PMREMOVE); i f (msg.message == WM_QUIT)
/ / a p lik a c ja o trzym a ła kom unikat WM_QUIT?
( done = tr u e ;
} e ls e
/ / j e ś l i ta k , to kończy d z ia ła n ie
Rozdział 1 2 . ♦ Bufory OpenGL
313
{ i f (keyPressed[VK_ESCAPE]) done = true; e lse
{ i f (keyPressed[VK_SPACE]) depthLess = !depthLess; R e n d e rO ; TranslateM essage(& m sg); Di spatchMessage(&msg);
// tłumaczy komunikat i wysyła do systemu
} } } i f (fu llS c re e n )
{ C han g e D isp laySe ttin g s(N U L L ,0); ShowCursor(TRUE);
// przywraca p u lp it // i wskaźnik myszy
return msg.wParam;
Program ten stanowi ilustrację omówionego wcześniej przykładu sześcianu przesłania jącego kulę. Rysunek 12.1 przedstawia działanie programu w sytuacji, gdy używana jest funkcja porównania głębi GL LESS. Efekt zmiany funkcji porównania na GL ALWAYS pre zentuje rysunek 12 .2 . Rysunek 12.1. Przykład zastosowania funkcji GL LESS
Kolejnym przykładem zastosowania bufora głębi jest przekrój sceny. Rysując na przy kład silnik samochodu można także pokazać jego wewnętrzną konstrukcję. Wykorzy stuje się w tym celu bufor głębi i rysuje płaszczyznę przekroju. Płaszczyznę przekroju rysuje się w następujący sposób: trzeba wyłączyć tworzenie grafiki w buforze koloru za pomocą funkcji gl DrawBuffer(), a następnie narysować płaszczyznę przekroju i włączyć z powrotem rysowanie w buforze koloru wywołując ponownie funkcję glDrawBufferO. Ilustruje to poniższy fragment kodu:
314
Część II ♦ Korzystanie z OpenGL
glDrawBuffer(GL_NONE); / / wyłącza tw o rze n ie g r a f ik i w b u fo rze k o lo ru / / w tym m ie jscu ry s u je płaszczyznę p rz e k ro ju glDrawBuffer(GL_BACK); P rzy k ła d program u, k tó ry zap rezen tow an y zostanie p o n iże j, nie bę d zie ry s o w a ł s iln ik a sam ochodu, le cz stw o rzy g ra fikę z ło ż o n ą z k u li u m ieszczo n ej w e w n ą trz n ie p rz e z ro c z y stego sześcianu. R o zp o c zy n a ją c d zia ła n ie program p okaże u ż y tk o w n ik o w i je d y n ie sze ścian. P o w y b ra n iu k la w is z a sp a cji na jedn ej ze ścian sześcianu zostan ie n aryso w ana pła szc zy zn a p rzekro ju , która p o z w o li dostrzec ku lę u m ie szc zo n ą w e w n ą trz sześcianu.
/ / / / / / P lik i nagłówkowe # in c lu d e # in c lu d e # in c lu d e < g l/g l.h > # in c łu d e < g l/g lu .h > # in c lu d e < g l/g la u x .h > / / / / / / Zmienne g lo b a ln e flo a t angle = O.Of; HDC g_HDC; bool f u l l Screen = fa ls e ;
/ / standardowy p lik nagłówkowy Windows / / standardowy p lik nagłówkowy OpenGL / / p lik nagłówkowy b ib lio t e k i GLU / / fu n k c je pomocnicze OpenGL
bool k e yP re sse d [2 5 6 ];
// // // // //
bieżący k ą t o b ro tu g lo b a ln y k o n te k st urządzenia tru e = tr y b pełnoekranowy; fa ls e = tr y b okienkowy ta b lic a p rz y c iś n ię ć k la w is z y
bool c u ttin g P la n e = tr u e ;
/ / tr u e : rysowana je s t płaszczyzna p rz e k ro ju
/ / In itia liz e / / o p is : in ic ju je OpenGL vo id I n i t i a l i z e ! ) gl Enable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glEnable(GL_LIGHTING); glEnable(GL_LIGHTO); glEnable(GL_COLOR_MATERIAL); gl ShadeModel(GL_SM00TH); g lC le a rC o lo r(0 .O f, O.Of, O.Of. O .O f);
// // // //
włącza b u fo r g łę b i w yłącza rysow anie ty ln y c h s tro n w ie lo ką tó w włącza o ś w ie tle n ie w łącza ź ró d ło ś w ia tła lig h tO I I k o lo r ja k o m a te ria ł / / c ie n io w a n ie g ła d k ie / / t ł o w k o lo rz e czarnym
Rozdział 1 2 . ♦ Bufory OpenGL
/ / Render / / o p is : ry s u je scenę v o id Render()
{
/ / o p ró żn ia b u fo ry ekranu i g łę b i glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n t ity O ; angle + = 0 .5 ;
/ / zwiększa k ą t o b ro tu
/ / odsuwa scenę o 100 je d n o s te k g lT r a n s la t e f ( 0 . 0 f , O.Of, -1 0 0 .O f); / / n a jp ie rw ry s u je k u lę wewnątrz sześcianu g lP u s h M a trix O ; g lR o ta te f(a n g le , 0 .0 f, l. O f , O .O f); g lC o lo r 3 f( 1 .0 f, l. O f . O .O f); a u x S o lid S p h e re (5 .0 f); g lP o p M a trix O ; / / a n a stę p n ie n ie p rz e z ro c z y s ty sześcian g lP u s h M a trix O ; g lR o ta te f(a n g le . l. O f , O.Of, O .O f); g lR o ta te f( a n g le * 0 .5 f, O .Of, l. O f , O .O f); g lR o ta te f( a n g le * 1 .2 f. O.Of, O.Of. l. O f ) ; / / ry s u je płaszczyznę p rz e k ro ju / / na je d n e j ze ścia n sześcianu i f (c u ttin g P la n e )
{
glDrawBuffer(GL_NONE); glBegin(GL_QUADS); g lV e r te x 3 f( - 1 0 .0 f, - 1 0 .Of, 1 5 .I f ) ; g lV e rte x 3 f(1 0 .0 f, - 1 0 .Of, 1 5 .I f ) ; g lV e rte x 3 f(1 0 .0 f. 1 0 .O f. 1 5 .I f ) ; g lV e r t e x 3 f ( - 1 0 . 0 f, 1 0 .Of, 1 5 .I f ) ; g lE n d O ; glD raw Buffer(G L_BAC K);
} g lC o lo r 3 f( 0 .2 f, 0 .4 f, 0 . 6 f ) ; a u x S o lid C u b e (3 0 .0 f); g lP o p M a trix O ; g lF lu s h O ; SwapBuffers(g_HDC);
/ / p rze łą cza b u fo ry
v o id SetupPixelForm at(HDC hDC) / * . . . . */ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM IParam)
/* ... */
315
316
Część II ♦ Korzystanie z OpenGL
in t WINAPI WinMain(HINSTANCE h ln s ta n c e , HINSTANCE hP re vIn sta n ce , LPSTR lpCmdLine. in t nShowCmd)
{
/* ... */ / / p ę tla p rze tw a rza n ia komunikatów w h ile (!do n e )
{ PeekMessage(&msg, hwnd, NULL, NULL, PM_REM0VE); i f (msg.message == WM_QUIT)
/ / a p lik a c ja o trzym a ła kom unikat WM_QUIT?
{ done = tr u e ;
/ / je ś l i ta k , to kończy d z ia ła n ie
} el se
{ i f (keyPressed[VK_ESCAPE]) done = tr u e ; e ls e
{ i f (keyPressed[VK_SPACE]) c u ttin g P la n e = !c u ttin g P la n e ; R e n d e rO ; TranslateM essage(&m sg); Di spatchMessage(&msg);
/ / tłum aczy kom unikat i w ysyła do systemu
} } } i f ( fu llS c re e n )
{ C hangeD isplayS ettings(N U LL.O ); ShowCursor(TRUE);
/ / przywraca p u lp it / / i w skaźnik myszy
} re tu rn msg.wParam;
} Program rysuje najpierw kulę, ponieważ będzie ona widoczna na płaszczyźnie prze kroju. Następnie rysuje płaszczyznę przekroju, a dopiero potem sześcian. W ten sposób na jednej ze ścian sześcianu pojawia się otwór, przez który widoczna jest kula umiesz czona we wnętrzu sześcianu. Działanie programu ilustruje rysunek 12.3.
Bufor powielania Bufor powielania stosuje się — podobnie do bufora głębi — aby zablokować tworzenie fragmentów grafiki. Jednak bufor powielania umożliwia przy tym osiągnięcie dodatko wych efektów, dla których możliwości bufora głębi nie są wystarczające. Przykładem zastosowania bufora powielania może być okno budynku, które umożliwia obserwację znajdujących się za nim obiektów.
Rozdział 1 2 . ♦ Bufory OpenGL
317
Rysunek 12.3. Przykład zastosowania płaszczyzny przekroju
Aby korzystać z bufora powielania, trzeba nadać polu cStenci 1Bits struktury PIXELF0RMATDESCRIPTOR odpowiednią wartość, na przykład: p fd .c S e tn c ilB its = 16;
Powyższy wiersz kodu konfiguruje 16-bitowy bufor powielania OpenGL. Przed rozpo częciem jego używania trzeba jeszcze aktywować go za pomocą funkcji gl Enabl e (), któ rej należy przekazać wartość GL_STENCIL_TEST: gl Enabl e(GL_STENCIL J E S T );
Ale to jeszcze nie wszystko. W przypadku bufora powielania należy określić jeszcze sposób jego wykorzystania przez maszynę OpenGL. Jeśli to nie zostanie zrobione, bufor powielania nie będzie używany w procesie tworzenia grafiki. Sposób wykorzystania bu fora powielania określa się za pomocą funkcji gl Stencil Func() i glStenci 10p(). Funk cja gl Stenci 1Func() zdefiniowana jest następująco: v o id g lS te n c i1Func(GLenum fu n c , G L in t r e f, G Luint m ask).;
Jej parametrami są (kolejno): funkcja porównania, wartość referencji i maska powielania. Tabela 12.7 prezentuje dostępne funkcje porównania. Poniżej zaprezentowany został przykład wywołania funkcji glStenci 1Func(), w efekcie którego wybrana zostaje funkcja porównania dającą zawsze rezultat pozytywny, war tość referencji równą 1 i maskę równą 1 : g l S te n c ilFunc(GL_ALWAYS, 1, 1 );
Funkcja gl Stenci 10p() pozwala określić rodzaj operacji powielania. Posiada ona nastę pujący prototyp: v o id glStenci10p(GLenum f a i l , GLenum z f a i l , GLenum zpass);
Część II ♦ Korzystanie z OpenGL
318
Tabela 12.7. Funkcje porównania dla bufora powielania Funkcja
Opis
GLNEVER
Rezultat porównania jest zawsze negatywny
GL_LESS
Rezultat porównania jest pozytywny, jeśli nowa wartość współrzędnej z jest mniejsza od wartości współrzędnej z zapisanej w buforze (domyślna funkcja porównania głębi) Rezultat porównania jest pozytywny, jeśli nowa wartość współrzędnej z jest równa wartości współrzędnej z zapisanej w buforze
GL_EQUAL GL_LEQUAL
Rezultat porównania jest pozytywny, jeśli nowa wartość współrzędnej z jest mniejsza lub równa wartości współrzędnej z zapisanej w buforze
GL_GREATER
Rezultat porównania jest pozytywny, jeśli nowa wartość współrzędnej z jest większa od wartości współrzędnej z zapisanej w buforze Rezultat porównania jest pozytywny, jeśli nowa wartość współrzędnej z jest różna od wartości współrzędnej z zapisanej w buforze
GL_NOTEQUAL GL_GEQUAL GL_ALWAYS
Rezultat porównania jest pozytywny, jeśli nowa wartość współrzędnej z jest większa lub równa wartości współrzędnej z zapisanej w buforze Rezultat porównania jest zawsze pozytywny
Jej parametry określają kolejno: rodzaj akcji podejmowanej, gdy funkcja porównania dla bufora powielania da rezultat negatywny (fai 1), funkcja porównania dla bufora po wielania da rezultat pozytywny, ale negatywny dla bufora głębi (zfail), a także wtedy, gdy funkcja porównania da w obu przypadkach rezultat pozytywny (zpass). Parametry te mogą przyjmować wartości przedstawione w tabeli 12.8. Tabela 12.8. Operacje bufora powielania Operacja
Opis
GL_KEEP
GL_REPLACE
Zachowuje bieżącą wartość Nadaje wartość 0 Nadaje wartość referencji r e f określoną za pomocą funkcji g l S tenci 1F u n c()
GLJNCR
Zwiększa bieżącą wartość o 1
GL_DECR
Zmniejsza bieżącą wartość o 1 Zmienia wartości wszystkich bitów na przeciwne
GL_ZER0
GL_INVERT
Poniższe wywołanie informuje OpenGL, że za każdym razem, gdy tworzona jest grafika w buforze powielania, powinien wypełnić go wartością ref, która zdefiniowana została za pomocą funkcji gl Stenci 1Func(): gl S te n c i1Op(GL_REPLACE, GL_REPLACE, GL_REPLACE);
W kolejnym z omawianych przykładów programów wykorzystany zostanie poniższy fragment kodu do narysowania posadzki w buforze powielania: / / k o n fig u ru je b u fo r p o w ie la n ia - w yp e łn ia n ie w a rto ś c ią r e fe r e n c ji gl S te n c i1Op(GL_REPLACE. GL_REPLACE, GL_REPLACE); glStencilFunc(GL_ALWAYS, 1. 1 ); / / ry s u je posadzkę w b u fo rze p o w ie la n ia D ra w F lo o rO ;
Rozdział 1 2 . ♦ Bufory OpenGL
319
Posadzki tej nie można zobaczyć, ale stworzony został w ten sposób w buforze powie lania rodzaj otworu, który będzie obcinał fragmenty obiektów znajdujące się poza nim. Czas przeanalizować kolejny przykład.
Przykład zastosowania bufora powielania Przykład, który został omówiony w niniejszym podrozdziale, prezentuje sposób, w jaki można tworzyć odbicia i obcinać je za pomocą krawędzi odbijającego obiektu, którym będzie w tym przypadku posadzka. Rysunek 12.4 przedstawia efekt działania prezento wanego programu. Rysunek 12.4. Przykład zastosowania bufora powielania
Tworząc kolejną klatkę animacji program wyłącza najpierw testowanie głębi, później możliwość modyfikacji bufora koloru. Następnie włącza bufor powielania i tworzy w nim obraz podłogi. Po ponownym włączeniu bufora głębi i modyfikacji kolorów konfiguruje bufor powielania w taki sposób, że tworzenie grafiki możliwe jest tylko w przypadku, gdy bufor powielania zawiera wartość 1, czyli wartość referencji. Dzięki temu OpenGL nie będzie tworzyć grafiki poza obszarem wyznaczonym przez wartości 1 umieszczone w buforze powielania. W tym momencie może przystąpić do rysowania odbicia, które nie będzie pojawiać się poza obszarem posadzki. W tym celu skaluje układ współrzęd nych tak, by uzyskać jego przeciwną orientację, a później rysuje torus pod płaszczyzną posadzki. Po narysowaniu odbicia program wyłącza bufor powielania i tworzy pozostałą część sceny w zwykły sposób. Najpierw rysuje przezroczystą posadzkę, a następnie prawdzi wy torus. Przyjrzyjmy się zatem kodowi programu: / / / / / / D e fin ic je łd e f in e BITMAP_ID 0x4D42 # d e fin e PI 3.14195
/ / id e n t y f ik a t o r form atu BMP
320
Część II ♦ Korzystanie z OpenGL
/ / / / / / P lik i nagłówkowe # in c lu d e # in c lu d e < s td io .h > # in c lu d e < s td lib .h > # in c lu d e # in c lu d e < g l/g l.h > # in c lu d e < g l/g lu .h > # in c lu d e < g l/g la u x .h >
/ / standardowy p lik nagłówkowy Windows
/ / standardowy p lik nagłówkowy OpenGL / / p lik nagłówkowy b ib lio t e k i GLU / / fu n k c je pomocnicze OpenGL
/ / / / / / Typy ty p e d e f s tr u c t i n t w id th ; i n t h e ig h t; unsigned in t te x ID ; unsigned char *d a ta ; } te x tu re _ t; / / / / / / Zmienne g lo b a ln e HDC g_HDC; bool f u l l Screen = fa ls e ;
// // // //
szerokość te k s tu ry wysokość te k s tu ry o b ie k t te k s tu ry dane te k s tu ry
bool keyP ressed[256];
/ / g lo b a ln y k o n te k s t urządzenia / / tr u e = tr y b pełnoekranowy / / f a l s e = tr y b okienkowy / / ta b lic a p rz y c iś n ię ć k la w is z y
f l o a t o b je c tA n g le = O.Of; f l o a t angle = O.Of; f l o a t ra d ia n s = O.Of;
/ / k ą t o b ro tu o b ie k tu / / k ą t kamery / / k ą t kamery w radianach
/ / / / / / Zmienne kamery i myszy i n t mouseX, mouseY; f l o a t cameraX, cameraY, cameraZ f l o a t lookX, lookY, lookZ;
/ / w spółrzędne myszy / / w spółrzędne kamery / / w spółrzędne punktu wycelowania kamery
/ / / / / / Opis te k s tu ry te x tu re _ t *envTex; te x tu re _ t * flo o rT e x ;
/ / te k s tu ra o to cze n ia / / te k s tu ra posadzki
/ / w ie rz c h o łk i posadzki f l o a t flo o r D a ta [4 ][3 ] = { { -5 .0 , 0 .0 , 5.0 } , { 5 .0 , 0 .0 , 5.0 }, { 5 .0 , 0 .0 , -5 .0 } . { -5 .0 , 0 .0 , -5 .0 } }; / * Kod fu n k c ji L o a d B itm a p F ile () z o s ta ł p o m in ię ty * / / * Kod fu n k c ji Lo a d T e xtu re F il e ( ) z o s ta ł p o m in ię ty * / bool L o a d A lU e x tu re s ()
{ / / ła d u je te k s tu rę o to cze n ia envTex = L o a d T e x tu re F ile C w a te re n v .b m p "); i f (envTex == NULL) re tu rn fa ls e ; / / ła d u je te k s tu rę posadzki flo o rT e x = L o a d T e x tu re F ile C c h e s s .b m p "); i f (flo o rT e x == NULL) re tu rn fa ls e ; / / k o n fig u ru je te k s tu rę o to c z e n ia glBindTexture(GL_TEXTURE_2D, e n v T e x-> te x ID );
Rozdział 1 2 . ♦ Bufory OpenGL
321
g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FI LTER, GL_LINEAR); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); g lT e x E n v i(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE).; gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, envTex->w idth, e n vT e x-> h e ig h t, GL_RGB, GL_UNSIGNED_BYTEf e n vT e x-> d a ta ); / / k o n fig u ru je te k s tu rę posadzki glBindTexture(GL_TEXTURE_2D, flo o rT e x -> te x ID ); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, G LLIN E AR ); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MI N_FILTER, GL_LINEAR); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S. GL_REPEAT); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); g lT e x E n v i(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); gl u B u i1d2DMi pmaps(GL_TEXTURE_2D, GL_RGB, flo o rT e x -> w id th , flo o rT e x -> h e ig h t. GL_RGB, GL_UNSIGNED_BYTEf flo o rT e x -> d a ta ); re tu rn tr u e ;
v o id CleanUpO
{
fre e (e n v T e x ); fr e e ( flo o r T e x ) ;
} / / In itia liz e / / o p is : in i c ju je OpenGL v o id I n i t i a l i z e O
{
g lC le a r C o lo r ( 0 .0 f, O.Of. O.Of, O .O f);
/ / t ł o w k o lo rz e czarnym
glShadeModel(GL_SM00TH); glEnable(GL_CULL_FACE); glFrontFace(GL_CCW );
// // // // // //
glEnable(GL_TEXTURE_2D);
c ie n io w a n ie g ła d k ie wyłącza rysowanie ty ln y c h s tro n w ielokątów ty ln y c h = o uporządkowaniu w ierzchołków w kie ru n k u przeciwnym do k ie ru n ku ruchu wskazówek zegara w łącza te k s tu ry dwuwymiarowe
L o a d A llT e x tu re s O ;
} / / D raw FloorO / / o p is : ry s u je posadzkę (ja k o pojedynczy czw orokąt) v o id D raw FloorO
{
glBindTexture(GL_TEXTURE_2D, flo o rT e x -> te x ID ); g l B egin(GL_QUADS); g lT e x C o o rd 2 f(0 .0 , 0 .0 ) ; g lV e r te x 3 fv ( flo o r D a ta [0 ]) ; g lT e x C o o rd 2 f(0 .0 , 4 .0 ) ; g lV e r t e x 3 f v ( f lo o r D a ta [ l] ) ; g lT e x C o o rd 2 f(4 .0 , 4 .0 ) ; g lV e r te x 3 fv ( flo o r D a ta [2 ]) ; g lT e x C o o rd 2 f(4 .0 , 0 .0 ) ; g lV e r te x 3 fv ( flo o r D a ta [3 ]) ; g lE n d O ;
/ / DrawTorusO / / o p is : ry s u je to ru s p o k ry ty te k s tu rą
322
Część II ♦ Korzystanie z OpenGL
vo id DrawTorusO
{
g lP u s h M a trix O ; / / k o n fig u ru je odwzorowania o to c z e n ia glTexGenf(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glTexGenf(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glEnable(GL_TEXTURE_GEN_S); g l Enable(GL_TEXTURE_GEN_T); / / w iąże te k s tu rę o to c z e n ia glBindTexture(GL_TEXTURE_2D, e n v T e x-> te x ID ); / / przesuwa i obraca g lT r a n s la te f( 0 ,0 f, 4 . Of, O .O f); g lR o ta te f(o b je c tA n g le , l. O f , l. O f , O .O f); g lR o ta te f(o b je c tA n g le , O.Of, l. O f , O .O f); g lR o ta te f(o b je c tA n g le , O.Of, O.Of, l. O f ) ; / / ry s u je to ru s a u x S o lid T o ru s (1 .0 f, 2 . O f);
/ / wyłącza tw o rze n ie w spółrzędnych te k s tu ry g lD i sable(GL_TEXTURE_GEN_T); g lD i sable(GL_TEXTURE_GEN_S); g lP o p M a trix O ;
/ / Render / / o p is : ry s u je scenę v o id RenderO
{ o b je c tA n g le += 0 . 2 f ; / / zwiększa k ą t o b ro tu ra d ia n s = f lo a t ( P I * ( a n g le - 9 0 . 0 f ) /1 8 0 . 0 f ) ; / / wyznacza p o ło ż e n ie kamery cameraX = lookX + s in (ra d ia n s)*m o u se Y ; cameraZ = lookZ + cos(radians)*m ouseY ; cameraY = lookY + mouseY / 2 . Of; / / w ycelow uje kamerę w punkt (0 ,2 ,0 ) lookX = O.Of; lookY = 2 .Of; lookZ = O.Of; / / o p ró żn ia b u fo ry k o lo ru , g łę b i i p o w ie la n ia gl Cl e a r(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); g lL o a d ld e n tity O ; / / umieszcza kamerę g lu L o o k A t(cameraX, cameraY, cameraZ, lookX , lookY, lo o kZ , 0 .0 , 1 .0 , 0 .0 ) ; / / w yłącza b u fo r g łę b i g lD i sable(GL_DEPTH_TEST); / / z abran ia m o d y fik a c ji w s z y stkic h składowych k o lo ru glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
Rozdział 1 2 . ♦ Bufory OpenGL
3 23
/ / w łącza b u fo r p o w ie la n ia glEnable(GL_STENCIL_TEST); / / o k re ś la w a rto ść r e fe r e n c ji d la b u fo ra p o w ie la n ia g lS te n c i1Op(GL_REPLACE, GL_REPLACE, GL REPLACE); glStencilFunc(GL_ALW AYS, 1, 1 ); / / ry s u je posadzkę; nadaje w szystkim pikselom w b u fo rz e p o w ie la n ia w artość 1, / / k tó ra z o s ta ła zdefin io w a n a ja k o w a rto ść maski za pomocą fu n k c ji g lS te n c ilF u n c O D ra w F lo o rO ; / / w łącza m ożliw ość m o d y fik a c ji składowych k o lo ru g l ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); / / w łącza b u fo r g łę b i glEnable(GL_DEPTH_TEST); / / rysow anie możliwe ty lk o tam, g d zie b u fo r p o w ie la n ia zawiera 1 glStencilFunc(GL_EQUAL, 1 . 1 ) ; g l Stenci10p(GL_KEEP, GLJCEEP, GL_KEEP); / / ry s u je "o d b ic ie " g lP u s h M a trix (); / / tw o rzy o d b ic ie to ru s a g lS c a le f( 1 .0 , -1 .0 , 1 .0 ); / / za b ra n ia rysowania p rze d n ich s tro n w ie lo ką tó w g lC ul 1Face(GL_FRONT); / / ry s u je o d b ic ie to ru s a D raw T orusO ; / / ponownie z a b ra n ia rysowania ty ln y c h s tro n w ie lo ką tó w glC ul 1Face(GL_BACK); g lP o p M a tr ix () ; / / w yłącza b u fo r p o w ie la n ia gl Di sable(GL_STENCIL_TEST); / / ry s u je p rz e z ro c z y s tą podłogę glEnable(GL_BLEND); g l BI endFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); g lC o lo r 4 f( 1 .0 f, l. O f , l. O f , 0 .4 f ) ; D ra w F lo o rO ; gl Di sable(GL_BLEND); / / ry s u je "praw dziw y" to ru s D raw T orusO ; g lF lu s h O ; SwapBuffers(g_HDC);
} / / fu n k c ja o k re ś la ją c a fo rm a t p ik s e li v o id S e tu p P ix e lFormat(HDC hDC)
/ / p rze łą cza b u fo ry
324
Część II ♦ Korzystanie z OpenGL
i n t n P ix e lFormat;
/ / indeks form atu p ik s e li
s t a t ic PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), / / rozm iar s tr u k tu r y 1 , / / domyślna w e rsja PFD_DRAW_TO_WINDÓW | / / g r a fik a w o kn ie PFD_SUPPORT_OPENGL | / / g r a fik a OpenGL PFD_DOUBLEBUFFER, / / podwójne buforow anie PFD_TYPE_RGBA, / / tr y b kolorów RGBA 32, / / 3 2 -b ito w y o p is kolorów 0, 0, 0, 0, 0, 0, / / n ie s p e c y fik u je b itó w kolorów 0 / / bez b u fo ra a lfa 0 / / n ie s p e c y fik u je b it u p rz e s u n ię c ia 0 / / bez b u fo ra a kum ulacji 0, 0. 0, 0. / / ig n o ru je b it y a ku m u la cji 16, / / 1 6 -b ito w y b u fo r z 16, / / 1 6 -b ito w y b u fo r p o w ie la n ia 0, / / bez buforów pomocniczych PFD_MAIN_PLANE, / / główna płaszczyzna rysowania 0, / / zarezerwowane 0. 0. 0 }; / / ig n o ru je maski warstw / / w ybiera n a jb a rd z ie j zgodny fo rm a t p ik s e li nP ixelF orm at = ChoosePixelFormat(hDC, & p fd ); / / o k re ś la fo rm a t p ik s e li d la danego k o n te k stu urządzenia SetPixelForm at(hD C , n P ixe lF o rm a t, & p fd );
} / * Kod fu n k c ji WndProcO z o s ta ł p o m in ię ty * / / * Kod fu n k c ji WinMainO z o s ta ł p o m in ię ty * /
Bufor akumulacji Trzeba powiedzieć to otwarcie: przy pisaniu tej książki implementacja bufora akumulacji działała wolno. Tak długo jak nie będą dostępne sprzętowe rozwiązania obsługi bufora akumulacji, jego zastosowanie w grach nie będzie możliwe. Dlatego też bufor akumulacji nie będzie omawiany szczegółowo, a jedynie przedstawiona zostanie jego podstawowa funkcjonalność. Koncepcja bufora akumulacji polega na akumulacji wielu kolejnych obrazów tworzo nych w buforze koloru. Obraz zakumulowany w buforze akumulacji można następnie umieścić w buforze koloru i uzyskać w ten sposób wiele ciekawych efektów, takich jak na przykład rozmycie obrazu wskutek ruchu obiektów, antialiasing czy cienie. Z działaniem bufora akumulacji związana jest tylko jedna funkcja OpenGL: v o id glAccum(GLenum op, G L flo a t value);
Parametr op określa wykonywaną operację, a parametr va lue wartość używaną przez tę operację. Tabela 12.9 przedstawia dostępne operacje.
Rozdział 1 2 . ♦ Bufory OpenGL
328
Tabela 12.9. Operacje bufora akumulacji Operacja
Opis
GL_ACCUM
Do zawartości bufora akumulacji dodawane są wartości RGBA pobierane z bieżącego bufora
GL_L0AD
Tak jak w przypadku GL ACCUM, ale pobierane wartości zastępują wartości w buforze akumulacji
GL_ADD
Dodaje wartość value do wartości każdego piksela w buforze akumulacji Mnoży wartość każdego piksela w buforze akumulacji razy wartość value
GL_MULT GL_RETURN
Mnoży wartość każdego piksela w buforze akumulacji razy wartość value, a wynik umieszcza w buforze koloru
Aby uzyskać efekt rozmycia obiektu na skutek jego ruchu, należy umieścić kilka kolej nych obrazów reprezentujących „ogon” obiektu w buforze akumulacji za pomocą funk cji glAccum(): glAccum(GL_ACCUM, 0 .1 ) ;
W przypadku zastosowania bufora akumulacji w celu uzyskania efektu rozmycia para metr value traktowany jest jak współczynnik zaniku kolejnych obrazów. Za każdym ra zem, gdy wywołana zostanie powyższa funkcja, obraz umieszczany w buforze akumu lacji będzie słabszy od poprzedniego. Po umieszczeniu wszystkich obrazów w buforze akumulacji należy wywołać ponownie funkcję gl Accum( )(tym razem w celu wykonania operacji GL_RETURN): g 1Accum( GL_RETURN, 1 .0 );
Poniższy fragment kodu ilustruje sposób zastosowania obu operacji bufora akumulacji w celu utworzenia pojedynczego obrazu zawierającego efekt rozmycia obiektu na sku tek jego ruchu: i n t id x ; / / n a jp ie rw tw o rzy pełen obraz o b ie k tu D ra w O b je c tO ; / / k o n fig u ru je b u fo r a ku m u la cji ta k , by z a w ie ra ł 70% obrazu o b ie k tu glAccum(GL_L0AD, 0 .7 ) ; / / ry s u je w p ę t li c z te ry obrazy o b ie k tu tw orzące rozm yty "ogon " f o r ( id x = 1; id x < 5; idx++ )
{ //' obraca o b ie k t wokół o si y i ry s u je go g lR o ta te f(o b je c tA n g le - ( f lo a t ) i d x , 0 .0 , 1 .0 , 0 .0 ) ; D ra w O b je c tO ; / / umieszcza 20% narysowanego obrazu w b u fo rze a kum ulacji gl Accum(GL_ACCUM, 0 .2 ) ;
} / / umieszcza zaw artość b u fo ra a ku m u la cji w b u fo rz e k o lo ru glAccum( GL_RETURN, 1 .0 ) ;
326
Część II ♦ Korzystanie z OpenGL
Przykład ten kończy krótkie omówienie bufora akumulacji. Chociaż jest on bardzo przydatny do tworzenia ciekawych efektów graficznych, to jednak jego niska efektyw ność nie pozwala stosować go w grach. Ponieważ możliwości sprzętu graficznego rosną w zawrotnym tempie, być może wkrótce pojawi się także możliwość sprzętowej obsługi bufora akumulacji.
Podsumowanie Obszary ekranu reprezentować można za pomocą dwuwymiarowych tablic opisujących kolor pikseli, czyli buforów. W OpenGL bufory przechowują dla każdego piksela skła dowe koloru w modelu RGBA lub indeks koloru w modelu indeksowym. Bufor ekranu stanowi kompozycję wszystkich pozostałych buforów systemu. OpenGL wymaga określenia formatu pikseli za pomocą struktury PIXELFORMATDESCRIPTOR. OpenGL udostępnia osobną funkcję dla każdego typu bufora pozwalającą określić war tość, którą będzie wypełniany bufor podczas kasowania jego zawartości: g lC le a rC o lo r(), gl Cl earDepth () , gl Cl ea rS ten ci 1 () i gl Cl earAccum(). Podwójne buforowanie polega na zastosowaniu dwóch buforów podczas tworzenia klatek animacji. Gdy wyświetlana jest zawartość jednego bufora, w drugim tworzona jest ko lejna klatka animacji. Po jej ukończeniu bufory zostają przełączone. Rozwiązanie takie pozwala uzyskać efekt płynnej animacji i zapobiega migotaniu ekranu. Podwójne bufo rowanie dostępne jest jedynie w przypadku bufora koloru. Nie jest używane w przypadku buforów głębi, akumulacji i powielania. Buforowanie stereoskopowe korzysta z osobnych buforów dla lewego i prawego oka, co pozwala osiągnąć w ten sposób efekt pełnej trójwymiarowości obrazu. Bufor głąbi zapobiega rysowaniu przesłoniętych obiektów. Przechowuje on dla każdego piksela odległość pomiędzy obserwatorem i punktem obiektu reprezentowanym przez dany piksel. Podczas rysowania obiektu przesłaniającego inny obiekt zawartość bufora głębi modyfikowana jest na podstawie funkcji porównania głębi. Również bufor powielania zapobiega rysowaniu fragmentów tworzonej sceny. Umoż liwia jednak uzyskanie efektów, dla których bufor głębi jest niewystarczający. Przykła dem zastosowania bufora powielania mogą być okna budynków pozwalające obserwo wać obiekty znajdujące się na zewnątrz. Koncepcja bufora akumulacji polega na akumulacji wielu obrazów tworzonych w bufo rze koloru. Zakumulowany obraz można następnie przenieść do bufora koloru i uzyskać w ten sposób efekty, takie jak rozmycie obiektów na skutek ich ruchu, antialiasing czy cienie.
Rozdział 13.
Powierzchnie drugiego stopnia W rozdziale 4. omówione zostały szczegółowo podstawowe elementy grafiki, takie jak punkty czy trójkąty. Z elementów tych tworzy się większość obiektów grafiki trójwy miarowej. Czasami jednak wygodniej jest skorzystać z gotowych obiektów reprezentu jących typowe bryły — na przykład kule bądź walce. Oczywiście można stworzyć własną bibliotekę takich obiektów, ale zamiast ponownie wymyślać koło, w większości przy padków lepiej skorzystać z obiektów udostępnianych przez bibliotekę GLU. Biblioteka GLU umożliwia rysowanie różnych powierzchni drugiego stopnia, takich jak dyski, walce, stożki i kule. Zanim omówione zostaną sposoby tworzenia poszczególnych rodzajów powierzchni, najpierw przedstawić trzeba ogólne zasady posługiwania się nimi. W rozdziale tym przedstawione zostaną następujące zagadnienia: ♦ powierzchnie drugiego stopnia i sposób ich tworzenia; ♦ zmiana właściwości powierzchni drugiego stopnia posiadająca wpływ na ich wygląd; ♦ rodzaje powierzchni drugiego stopnia udostępniane przez bibliotekę GLU.
Powierzchnie drugiego stopnia w OpenGL Aby właściwie zrozumieć zasady posługiwania się powierzchniami drugiego stopnia w OpenGL, najlepiej jest przyjąć, ze termin ten odnosi się nie tyle do powierzchni w sen sie konkretnego obiektu graficznego, ale raczej do obiektu określającego sposób ryso wania tej powierzchni. Dlatego też zanim narysuje się jakąkolwiek powierzchnię dru giego stopnia, trzeba najpierw stworzyć odpowiedni obiekt. Obiekt powierzchni drugiego stopnia można stworzyć wywołując funkcję gl uNewQuadri c ( ): GLUguadricObj *g lu N e w Q u a d ric ();
328
Część II ♦ Korzystanie z OpenGL
Funkcja ta zwraca wskaźnik nowego obiektu powierzchni drugiego stopnia (bądź war tość NULL, jeśli obiekt nie został utworzony). Po utworzeniu obiektu można użyć go do rysowania wielu powierzchni. Za pomocą jed nego obiektu powierzchni drugiego stopnia można rysować dyski, walce, stożki i kule. W tym momencie można oczywiście zapytać, po co tworzyć więcej niż jeden obiekt powierzchni drugiego stopnia? I skoro wystarczy jeden taki obiekt do rysowania do wolnych powierzchni, to czy nie może być on tworzony automatycznie przez maszynę OpenGL? Rzeczywiście, w przypadku niektórych zastosowań wystarczający okaże się pojedynczy obiekt. Jednak trzeba pamiętać, że każdy obiekt powierzchni drugiego stopnia posiada pewien stan określający sposób rysowania powierzchni. Dysponując pojedynczym obiek tem można rysować albo wiele takich samych powierzchni, albo za każdym razem, gdy trzeba narysować inną powierzchnię, zmieniać stan obiektu. W tym drugim przypadku okaże się, że bardziej efektywnym rozwiązaniem jest posiadanie wielu obiektów o róż nych stanach niż częste zmiany stanu pojedynczego obiektu. Stan obiektu powierzchni drugiego stopnia określa: ♦ styl rysowanej powierzchni; ♦ jej normalne; ♦ orientację powierzchni; ♦ współrzędne tekstury.
Styl powierzchni Styl powierzchni określa, czy tworzące ją wielokąty będą wypełniane podczas rysowa nia, czy rysowane będą tylko ich krawędzie lub same wierzchołki. Dostępny jest także styl sylwetki, który podobny jest do stylu, w którym rysowane są krawędzie wieloką tów, ale różni się tym, że nie są rysowane krawędzie łączące wielokąty leżące w jednej płaszczyźnie. Styl powierzchni określać można za pomocą funkcji v o id gluQ uadricD raw S tyle(G LU quadricO bj *quadObj, GLenum style):
Parametr quadObj jest wskaźnikiem obiektu powierzchni, którego stan należy zmienić, a parametr s ty le może przyjmować wartość GLU_FILL, GLU_LINE, GLU_P0INT lub GLU_ SILHOUETTE. Domyślnym stylem rysowania powierzchni jest GLU_FILL.
Wektory normalne Stan obiektu powierzchni drugiego stopnia określa także sposób tworzenia wektorów normalnych do rysowanej powierzchni. Wektory te mogą być tworzone dla każdego wielokąta (w rezultacie otrzymuje się cieniowanie płaskie) bądź generowane dla każde go wierzchołka (cieniowanie gładkie). Sposób tworzenia wektorów normalnych określa się za pomocą funkcji v o id gluQ uadricNorm als(G LUquadricO bj *quadObj, GLenum normalMode) ;
Rozdział 1 3 . ♦ Powierzchnie drugiego stopnia
32S
Parametr quadObj jest wskaźnikiem obiektu powierzchni drugiego stopnia, a parametr norma IMode może przyjmować wartość GLU_N0NE, GLU_FLAT lub GLU_SM00TH.
Orientacja Precyzyjne wytłumaczenie znaczenia orientacji zależy w pewnym stopniu od rodzaju powierzchni drugiego stopnia. W ogólnym przypadku orientacja określa zwrot wektorów normalnych do powierzchni. Określa się ją za pomocą funkcji v o id g lu Q u a d ric O rie n ta tio n (G L U q u a d ricO b j *quadObj, GLenum orientation ) ;
Parametr orientation może przyjmować domyślną wartość GLU OUTSIDE lub wartość GLU_ INSIDE. Wartość GLU OUTSIDE sprawia, że wektory normalne skierowane są na zewnątrz po wierzchni, a GLU INSIDE do wewnątrz. O ile pojęcie wnętrza jest oczywiste w przypadku stożka, walca czy kuli, to w przypadku dysku wymaga ono dodatkowej definicji. Przez ze wnętrzną stronę dysku rozumieć należy stronę zwróconą w kierunku dodatniej części osi z.
Współrzędne tekstury Obiekt powierzchni drugiego stopnia określa też, czy dla rysowanej powierzchni two rzone są współrzędne tekstury. Dokładny sposób tworzenia współrzędnych tekstury za leży od rodzaju powierzchni. Tworzenie współrzędnych tekstury można kontrolować za pomocą funkcji v o id gluQ uadricTexture(G LU quadricO bj *quadObj, GLboolean useTextureCoords) ;
Jeśli parametr useTextureCoords posiada wartość GL TRUE, to współrzędne tekstury są tworzone. Domyślnie generowanie współrzędnych tekstury jest wyłączone.
Usuwanie obiektów powierzchni Obiekty powierzchni drugiego stopnia zajmują pamięć i dlatego należy je usuwać, gdy nie są już potrzebne. Służy do tego funkcja gl uDel eteQuadri c ( ): v o id gluD eleteQ uadric(G LU quadricO bj *quadObj):
Jej wywołanie usuwa obiekt wskazywany przez parametr quadObj i zwalnia zajmowaną przez niego pamięć. W ten sposób poznane zostały zasady tworzenia i usuwania obiektów powierzchni dru giego stopnia, a także możliwości zmiany ich stanu. Najwyższa więc pora, by wykorzy stać je do rysowania powierzchni.
Dyski Dyskiem, w sensie biblioteki GLU, jest płaskie koło, które może posiadać w środku otwór, co ilustruje rysunek 13.1. Dysk rysowany jest wokół środka układu współrzędnych w płasz czyźnie z = 0. Dowolne położenie można mu nadać za pomocą przesunięć i obrotów.
330
Część II ♦ Korzystanie z OpenGL
Rysunek 13.1. Dysk jako powierzchnia drugiego stopnia
Dysk można narysować wywołując poniższą funkcję: vo id gluD isk(G LU quadricO bj *quadObj, GLdouble innerRadius , GLdouble outerRadius , G L in t slic e s, G L in t loops):
Parametr quadObj jest wskaźnikiem obiektu powierzchni. Promień dysku definiuje pa rametr outerRadius. Jeśli wartość parametru innerRadius określającego wewnętrzny pro mień dysku jest większa od 0, to dysk posiadać będzie w środku otwór. Parametry slices i loops kontrolują liczbę wielokątów tworzących dysk. Parametr s 1i ces definiuje liczbę wielokątów wokół osi z, a parametr loops liczbę pierścieni wielokątów tworzących dysk. Im większa jest wartość obu parametrów, tym lepszy będzie wygląd dysku, ale i dłuższy okaże się czas jego rysowania. Trzeba więc znaleźć rozsądny kompromis pomiędzy ja kością i efektywnością. Aby dysk nie posiadał nadmiaru kantów, parametr slic e s po winien mieć wartość co najmniej 20. W większości przypadków wystarczy, że parametr loops posiadał będzie wartość 2. Należy ją zwiększyć, jeśli planowane jest stworzenie na powierzchni dysku efektu odbicia. Jeśli dla dysku tworzone są współrzędne tekstury, to ich wartości generowane są linio wo dookoła dysku, co ilustruje rysunek 13.2. Rysunek 13.2. Współrzędne tekstury dysku
A+y (0.5, 1.0)
(1.0, 0.5)
(0.0, 0.5)
h e ig h t = t e x ln f o . bi Hei g h t; / / tw o rzy o b ie k t te k s tu ry g lG e n T e x tu re s (l. & th is T e x tu re -> te x ID ); re tu rn th is T e x tu re ;
} / / L o a d A llT e x tu re s () / / o p is : ła d u je te k s tu r y używane w program ie bool L o a d A llT e x tu re s ()
{ / / ła d u je te k s tu rę szachownicy surfaceT e x = L o a d T e xtu re F il e ( "chess.bm p"); i f (su rfa ce T e x == NULL) re tu rn fa ls e ; / / k o n fig u ru je te k s tu rę szachownicy g!BindTexture(GL_TEXTURE_2D, s u rfa c e T e x -> te x ID ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER. GL_LINEAR); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE. GL_REPLACE); gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, s u rfa ce T e x -> w id th , s u rfa c e T e x -> h e ig h t, GL_RGB, GL_UNSIGNED_BYTE, s u rfa c e T e x -> d a ta ); re tu rn tr u e ;
} / / CleanUpO / / o p is : zw a ln ia o b ie k ty v o id CleanUpO
{
fre e (s u rfa c e T e x );
} / / In itia liz e / / o p is : in ic ju j e OpenGL v o id I n i t i a l i z e d
{ g lC le a r C o lo r ( 0 .0 f, O.Of. O.Of. O.Of) glShadeModel(GL_FLAT); gl Enable(GL_DEPTF1_TEST); gl Enable(GL_TEXTURE_2D);
// // // //
t ł o w k o lo rz e czarnym c ie n io w a n ie g ła d k ie usuwanie p rz e s ło n ię ty c h pow ierzchni włącza te k s tu ry dwuwymiarowe
L o a d A llT e x tu re s ();
/ / ła d u je te k s tu ry
347
348
Część II ♦ Korzystanie z OpenGL
/ / Render / / o p is : ry s u je scenę void RenderO
{
radians =
f lo a t ( P I * ( a n g le - 9 0 . 0 f ) /1 8 0 . 0 f ) ;
/ / wyznacza współrzędne kamery cameraX = lookX + (flo a t)s in (ra d ia n s )* m o u s e Y ; cameraZ = lookZ + (flo a t)c o s (ra d ia n s )*m o u s e Y ; cameraY = lookY + mouseY / 2 .Of + 3 0 .Of; / / punkt wycelowania kamery lookX = - 2 0 .Of; lookY = 2 0 .Of; lookZ = 0 .O f; / / o p różn ia b u fo ry ekranu i g łę b i g lC le a r(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BI T ) ; g l L o a d I d e n t i t y ( ); / / umieszcza kamerę gluLookAt(cam eraX, cameraY, cameraZ, lookX, lookY, lo o kZ , 0 .0 , 1 .0 , 0 .0 ) ; / / tw o rzy e w a lu a to r punktów k o n tro ln y c h pow ierzchni glMap2f(GL_MAP2_VERTEX_3, 0 .0 , 1 .0 , 3, 3, 0 .0 , 1 .0 , 9, 3, & c S u r fa c e [0 ][0 ][0 ]) ; / / tw o rzy e w a lu a to r współrzędnych te k s tu ry glMap2f(GL_MAP2_TEXTURE_C00RD_2, 0, 1, 2. 2, 0. 1, 4, 2, & s T e x C o o rd s [0 ][0 ][0 ]); / / aktyw uje utworzone e w a lu a to ry gl Enable(GL_MAP2_TEXTURE_C00RD_2); glEnable(GL_MAP2_VERTEX_3); .// tw orzy s ia tk ę pow ierzchni glM a p G rid 2 f(10. O.Of, l. O f , 10. O.Of, l. O f ) ; glEvalM esh2(G L_FILL, 0, 10. 0, 10); / / wyłącza te k s tu ry podczas rysowania punktów k o n tro ln y c h g lD i sable(GL_TEXTURE_2D); / / ry s u je punkty k o n tro ln e w k o lo rz e żó łtym i rozm iarze 4 g lP o in tS iz e ( 4 .0 ) ; g lC o lo r 3 f ( l.0, 1 .0 , 0 .0 ); glB egi n(GL_POINTS); fo r ( i n t i = 0 ; i < 3 ; i+ + ) fo r ( in t j = 0; j < 3; j+ + ) g lV e r t e x 3 f v ( & c S u r f a c e [ i] [j] [0 ] ) ; g lE n d O ; / / przywraca domyślne ro zm ia ry punktów i włącza te k s tu ry gl Poi n tS i z e ( 1 .0 ) ; gl Enable(GL_TEXTURE_2D); g lF lu s h O ; SwapBuffers(g_HDC);
/ / p rze łą cza b u fo ry
Rozdział 1 4 . ♦ Krzywe i powierzchnie
349
Szczególnie interesująca jest dla nas poniższa część kodu: / / tw o rzy e w a lu a to r punktów k o n tro ln y c h pow ierzchni glMap2f(GL_MAP2_VERTEX_3, 0 .0 , 1 .0 , 3, 3, 0 .0 , 1 .0 . 9, 3, & c S u rfa c e [0 ][0 ][0 ]) ; / / tw o rzy e w a lu a to r w spółrzędnych te k s tu ry glMap2f(GL_MAP2_TEXTURE_C00RD_2, 0, 1. 2, 2, 0, 1. 4, 2, & s T e x C o o rd s [0 ][0 ][0 ]); / / a ktyw uje utw orzone e w a lu a to ry g lE n a b le ( GL_MAP2_TEXTURE_C00RD_2); glEnable(GL_MAP2_VERTEX_3); / / tw o rzy s ia tk ę pow ierzchni g lM a p G rid 2 f(1 0 . O.Of, l. O f , 10, O.Of, l. O f ) ; glE valM esh2(G L_FILL, 0, 10. 0. 10);
Najpierw — za pomocą funkcji glMap2f() — tworzy on ewaluatory punktów kontrol nych powierzchni oraz współrzędnych tekstury. Współrzędne tekstury określone są za pomocą tablicy sTexCoords: f l o a t s T e x C o o rd s [2 ][2 ][2 ] = { { { 0 . 0 , 0 .0 } , {0 .0 , 1 .0 } } , { { 1 .0 , 0 .0 } , {1 .0 , 1 .0 } } } ;
Definiuje ona kwadrat o wierzchołkach w punktach (0, 0), (0, 1), (1, 0), (1, 1). Punkty te odpowiadają bezpośrednio narożnikom tekstury, ponieważ tak zostały skonfigurowane za pomocą funkcji glMap2f(). Za każdym razem, gdy przy rysowaniu powierzchni wy wołana zostanie funkcja gl EvalMesh2(), OpenGL automatycznie wyznaczy też współ rzędne tekstury. Rysunek 14.8 przedstawia tworzoną przez powyższy kod powierzchnię pokrytą teksturą. Rysunek 14.8. Powierzchnia pokryta teksturą
350
Część II ♦ Korzystanie z OpenGL
Powierzchnie B-sklejane Wraz ze wzrostem liczby punktów kontrolnych krzywej Beziera wzrasta także trudność związana z uzyskaniem ciągłej i gładkiej krzywej. Krzywe Beziera klasyfikowane są na podstawie liczby punktów kontrolnych. Na przykład krzywe posiadające trzy punkty kontrolne nazywane są krzywymi drugiego stopnia, a krzywe o czterech punktach kon trolnych krzywymi trzeciego stopnia. Krzywe posiadające pięć, sześć, siedem i więcej punktów kontrolnych zaczynają tracić swoją gładkość na skutek oddziaływania tak wielu punktów kontrolnych. Problem ten rozwiązują krzywe B-sklejane nazywane także w terminologii OpenGL krzywymi NURBS ( non-uniform rational B-splines). Krzywe B-sklejane zachowują się tak samo jak krzywe Beziera. Ich przewaga polega na tym, że skomplikowana krzywa jest dzielona na segmenty, które posiadają cztery punkty kontrolne i są odpowiednikiem krzywych Beziera trzeciego stopnia. Nie trzeba tutaj omawiać szczegółowo teorii krzywych B-sklejanych, gdyż przedsta wione zostaną funkcje, za pomocą których można je rysować. Węzeł krzywej B-sklejanej opisany jest za pomocą sekwencji wartości określających jego wpływ na segmenty krzywej. Jest to zasadniczy element, który odróżnia krzywe B-sklejane od zwykłych krzywych Beziera. Każdy punkt kontrolny krzywej B-sklejanej dysponuje dwoma węzłami, które mogą przybierać dowolne wartości z dziedzin parametrów u i v. Dla segmentu krzywej posia dającego cztery punkty kontrolne istnieć więc będzie osiem węzłów. Ilustruje to poniższy fragment programu: GLUnurbsObj *myNurb; f l o a t k n o ts [8 ] = { 0 .0 , 0 .0 , 0 .0 , 0 .0 , 1 .0 . 1 .0 , 1 .0 , 1.0 } ; flo a t n u r b [4 ][4 ][3 ]; vo id C leanllpO
{
gluD eleteN urbsR enderer(m yN urb);
} / / In itia liz e / / o p is : in ic ju je OpenGL v o id I n i t i a l i z e O g lC le a r C o lo r ( 0 .0 f. O.Of, O.Of, O .O f);
/ / t l o w k o lo rz e czarnym
g l E nable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_COLOR_MATERIAL); g l Enable(GL_AUT0_N0RMAL); gl Enabl e(GLJORMALIZE);
// // // // //
usuwanie p rz e s ło n ię ty c h pow ierzchni włącza o ś w ie tle n ie włącza ź ró d ło ś w ia tła lig h tO k o lo r ja k o m a te ria ł autom atyczne tw o rz e n ie wektorów normalnych
/ / k o n fig u ru je p o w ie rzch n ię o k s z ta łc ie wypukłym in t u, v;
Rozdział 1 4 . ♦ Krzywe i powierzchnie
fo r (u = 0; u < 4; u++)
{ f o r (v = 0; v < 4; v++)
{ n u r b [ u ] [ v ] [ 0 ] = 3 .0 * ( ( f lo a t ) u - 1 .5 ); n u r b [ u ] [ v ] [ l ] = 2 . 0 * ( ( f ! o a t ) v - 1 .5 ); i f ( (u — 1 II u — 2) && (v — 1 II v — 2 )) n u r b [ u ] [ v ] [2 ] = 3 .0 ; e ls e n u r b [ u ] [ v ] [ 2 ] = -1 .0 ;
} } / / in ic ju je o b ie k t krzyw e j B -s k le ja n e j myNurb = gluNewNurbsRendererO ; / / o k re ś la maksymalną długość w przypadku u życia w ie lo ką tó w gluN urbsP roperty(m yN urb, GLU_SAMPLING_TOLERANCE, 5 0 .0 ); / / ry s u je p o w ie rzch n ię za pomocą w ie lo ką tó w gluN urbsP roperty(m yN urb, GLU_DISPLAY_MODE, GLU_FILL);
} / / Render / / o p is : ry s u je scenę v o id RenderO
{ // opróżnia bufory ekranu i głębi glClear(GL_COLOR_BUFFER_BIT | GL DEPTH_BUFFER_BIT ); g lL o a d ld e n tity O ;
/ / przesuwa i obraca p o w ie rz c h n ię , aby b y ła le p ie j widoczna g lT r a n s la te f( 0 .0 , 0 .0 , -1 0 .0 ); g lR o ta te f(2 9 0 .0 , 1 .0 , 0 .0 , 0 .0 ) ; g lP u s h M a trix O ; g lR o ta te f(a n g le , 0 .0 , 0 .0 , 1 .0 );
/ / obraca p o w ierzchnię
g lC o lo r 3 f( 0 .2 , 0 .5 , 0 .8 ) ; / / rozpoczyna rysow anie pow ierzchni B -s k le ja n e j glu B e g in S u rfa ce (m yN u rb ); / / ry s u je p o w ie rzch n ię B -s k le ja n ą gluN urbsS urface(m yN urb, 8, k n o ts , 8, k n o ts , 4 *3 , 3, & n u r b [0 ][0 ][0 ], 4, 4. GL_MAP2_VERTEX_3); / / kończy rysow anie p ow ierzchni B -s k le ja n e j g luE ndS urface(m yN urb); / / ry s u je punkty k o n tro ln e g lP o in tS iz e ( ó .O ) ; g lC o lo r 3 f( 1 .0 . 1 .0 , 0 .0 ) ; g lB e gi n(G LPO IN TS); f o r ( i n t i = 0; i < 4; i+ + ) f o r ( i n t j = 0; j < 4; j+ + )
351
352
Część II ♦ Korzystanie z OpenGL
g lV e r t e x 3 f v ( & n u r b [ i] [ j] [ 0 ] ) ; g lE n d O ; g lP o in tS iz e ( l.O ) ; g lP o p M a trix O ; g lF lu s h O ; SwapBuffers(g_HDC);
//
p rze łą c z a b u fo ry
a n g le + = 0 .3 f;
//
zwiększa k ą t o b ro tu
} Jak pokazuje powyższy przykład, narysowanie powierzchni B-sklejanej nie wymaga zbyt długiego kodu. Biblioteka GLU udostępnia odpowiedni zestaw funkcji wygodniejszy nawet w użyciu niż przedstawione wcześniej funkcje rysowania powierzchni Beziera. Najpierw należy zadeklarować wskaźnik obiektu powierzchni B-sklejanej, którym bę dzie można się posługiwać podczas jej definiowania i rysowania: GLUnurbsObj *myNurb; / / o b ie k t pow ierzchni B -s k le ja n e j
Trzeba także zdefiniować węzły punktów kontrolnych oraz tablicę, w której umieszczone zostaną punkty kontrolne: f lo a t k n o ts [8 ] = { 0 .0 , 0 .0 , 0 .0 , 0 .0 , 1 .0 , 1 .0 , 1 .0 , 1.0 }; f lo a t n u r b [ 4 ] [ 4 ] [ 3 ] ;
Funkcja I n i t i a l i z e ( ) włącza oświetlenie i instruuje maszynę OpenGL, by wyznaczała wektory normalne. Punkty kontrolne powierzchni należy zadeklarować przed rozpoczę ciem jej definiowania: / / in ic ju je o b ie k t krzyw ej B -s k le ja n e j myNurb = gluN ewN urbsR endererO ; / / o k re ś la maksymalną długość w przypadku u życia w ie lo ką tó w gluN urbsP roperty(m yN urb, GLU_SAMPLING_TOLERANCE, 5 0 .0 ); / / ry s u je p o w ie rzch n ię za pomocą w ie lo ką tó w gluN urbsP roperty(m yN urb, GLU_DISPLAY_MODE, GLU_FILL);
Funkcja g l uNewNurbsRenderer() tworzy obiekt powierzchni B-sklejanej. Funkcję g l uN urbsP roperty () wykorzystuje się w celu określenia tolerancji próbkowania oraz sposobu rysowania powierzchni B-sklejanej. Parametr GLU_SAMPLING_TOLERANCE określa maksymalną długość w przypadku wielokątów. W tym przykładzie jest to 50 jednostek. Parametr GLU DISPLAY MODE określa sposób rysowania powierzchni B-sklejanej i może przyjmować jedną z trzech wartości: ♦ GLU FILL — powierzchnia będzie rysowana za pomocą wielokątów; ♦ GLU_0UTLINE_P0LYG0N; ♦ GLUOUTLINEPATCH.
Poniżej zaprezentowane zostały prototypy obu omówionych funkcji: GLUnurbsObj* gluN ew N urbsR enderer(void); v o id gluN utbsProperty(G LU nurbsO bj * n o b j, GLenum p ro p e rty , f l o a t v a lu e );
Rozdział 1 4 . ♦ Krzywe i powierzchnie
353
Aby narysować powierzchnię B-sklejaną wystarczą jedynie trzy wywołania funkcji: / / rozpoczyna rysow anie pow ierzchni B -s k le ja n e j g luB egin S u rfa ce (m yN u rb ); / / ry s u je p o w ie rzch n ię B -s k le ja n ą gluN urbsS urface(m yN urb, 8, k n o ts , 8, kn o ts , 4*3, 3, & n u r b [ 0 ][ 0 ] [0 ]. 4, 4, GL_MAP2_VERTEX_3); / / kończy rysow anie pow ierzchni B -s k le ja n e j gluE ndS urface(m yN urb);
Funkcja g l u B e g in S u rfa c e ( ) zapowiada rysowanie powierzchni B-sklejanej za pomocą przekazanego jej obiektu powierzchni. Funkcja ta posiada następujący prototyp: v o id gluBeginSurface(G LUnurbsO bj * n o b j) ;
Funkcja gl uN u rb sS u rfa ce ( ) wyznacza wierzchołki powierzchni niezbędne do jej prawi dłowego narysowania. Zdefiniowana jest w następujący sposób: v o id gluN urbsSurface(G LU nurbsO bj *n o bj , in t uknot_count, f lo a t *uknot, i n t vknot_count, f lo a t *vknot, in t u_stride. in t v_stride, f l o a t *ctlarray, i n t uorder , in t vorder, GLenum type):
Pierwszy z parametrów funkcji, n o b j , wskazuje obiekt powierzchni B-sklejanej. Para metry uknot_count i uknot określają liczbę węzłów i ich dane w kierunku u. Podobnie parametry vknot_count i vknot określają liczbę węzłów i ich dane w kierunku v. Para metry u _ s t r i d e i v _ s t r id e określają odległości pomiędzy punktami kontrolnymi w kie runku u i v. Dane punktów kontrolnych są przekazywane w tablicy wskazywanej przez parametr c t l array. Parametry uorder i vorder określają stopień wielomianu płaszczyzny w kierunkach u i v, który zwykle równy jest liczbie punktów kontrolnych w danym kie runku. Parametr type specyfikuje typ rysowanej powierzchni B-sklejanej. W tym przy kładzie przyjmuje on wartość GL_MAP2_VERTEX_3. Po wyznaczeniu wierzchołków powierzchni wywołanie funkcji g lu E n d S u rfa c e ( ) infor muje OpenGL, że proces rysowania powierzchni został zakończony. Funkcji tej przeka zuje się obiekt narysowanej powierzchni. Funkcja gl uE ndS urface( ) posiada więc nastę pujący prototyp: v o id gluEndSurface(GLUnurbsObj *nobj);
Kończąc działanie programu należy zawsze usunąć wykorzystywane obiekty powierzchni B-sklejanych. Służy do tego funkcja gl uDel e te N u r b s R e n d e r e r( ) zdefiniowana w przed stawiony poniżej sposób: gluD eleteN urbsR enderer(G LU nurbsO bj *n o b j) ;
W tym przykładzie wywołuje się ją w następujący sposób: gluD eleteN urbsR enderer(m yN urb);
Rezultat działania programu przedstawia rysunek 14.9. Na tym można zakończyć omó wienie podstaw korzystania z powierzchni B-sklejanych.
354
Część II ♦ Korzystanie z OpenGL
Rysunek 14.9. Przykład powierzchni B-sklejanej
Podsumowanie Krzywa może przyjmować dowolne kształty w przestrzeni. Podobnie jak odcinek pro stej posiada on początek i koniec oraz długość. Powierzchnia posiada dodatkowo szero kość i składa się z krzywych. Równania parametryczne wyrażają współrzędne x i y za pomocą funkcji pewnej zmien nej. W fizyce często wyraża się współrzędne punktu na płaszczyźnie w funkcji czasu. Przy tworzeniu grafiki trójwymiarowej można zastosować równania parametryczne do określenia współrzędnych cząstki w funkcji wirtualnego czasu. Punkty kontrolne określają kształt krzywej. W swoim działaniu przypominają magnesy przyciągające fragmenty krzywej. Pierwszy i ostatni punkt kontrolny określają zawsze początek i koniec krzywej. Pozostałe punkty kontrolne umożliwiają zmianę kształtu krzywej. Krzywe B-sklejane są podobne do krzywych Beziera. Krzywe B-sklejane składają się z segmentów, z których każdy posiada cztery punkty kontrolne. Podział na segmenty, które są w istocie krzywymi Beziera trzeciego stopnia, umożliwia uzyskanie gładkiej krzywej o skomplikowanych kształtach.
Rozdział 15.
Efekty specjalne W poprzednich rozdziałach omówiono szereg podstawowych technik graficznych umoż liwiających stworzenie wirtualnego świata gry. W bieżącym rozdziale przedstawionych zostanie kilka sposobów jego uatrakcyjnienia za pomocą efektów specjalnych. Efekty specjalne są dość rozległym zagadnieniem i dlatego trzeba ograniczyć się do omówienia jedynie najczęściej stosowanych ich rodzajów. W rozdziale tym przedsta wione zostanie: ♦ plakatowanie jako sposób zmniejszenia liczby wielokątów; ♦ tworzenie systemów cząstek w celu uzyskania wielu różnych efektów począwszy od fontann, a skończywszy na eksplozjach; ♦ zastosowanie mgły jako efektu specjalnego i sposobu pozwalającego na zmniejszenie złożoności sceny; ♦ tworzenie odbić na płaskich powierzchniach; ♦ kilka technik tworzenia cieni. Większość efektów specjalnych można osiągnąć na wiele sposobów różniących się efektywnością oraz jakością uzyskanego efektu. Ponieważ książka poświęcona jest pro gramowaniu gier, to w rozdziale tym skoncentrować się będzie trzeba na technikach umożliwiających uzyskanie odpowiedniej szybkości tworzenia efektów specjalnych.
Plakatowanie Plakatowanie nie jest szczególnie widowiskowym efektem specjalnym, ale stosowane bywa w połączeniu z systemami cząstek i dlatego należy omówić je jako pierwsze. Pod stawowym zadaniem plakatowania jest zapewnienie, że wielokąt będzie zawsze zwró cony w kierunku obserwatora. Zastosowanie plakatowania omówione zostanie na przy kładzie. Można założyć, że scena tworzonej gry zawiera drzewa, których model ładowany jest z pliku lub tworzony proceduralnie. Gdy jednak obserwator znajdzie się w bardzo dużej odległości od drzew, to aby przedstawić każde z nich, przetwarzać będzie trzeba nadal dziesiątki, jeśli nie setki wielokątów. Tymczasem ich reprezentację na ekranie stanowić
356
Część II ♦ Korzystanie z OpenGL
będzie tylko kilka pikseli. Rozwiązanie tego problemu polegać może na stosowaniu modeli, które wraz ze wzrostem odległości od obserwatora zawierać będą coraz mniej wielokątów. Inny często stosowany sposób polega na zastąpieniu trójwymiarowego modelu odległego obiektu za pomocą dwuwymiarowego obrazu umieszczonego w for mie tekstury na pojedynczym wielokącie. Gdy obserwator zbliży się do takiego obiektu, dwuwymiarowy obraz może z powrotem zostać zastąpiony trójwymiarowym modelem. Można założyć teraz, że obserwator spogląda w kierunku północnym na dwuwymiarowy obraz obiektu umieszczony na wielokącie ustawionym w płaszczyźnie wschód-zachód. Jeśli obserwator zacznie okrążać ten obiekt na przykład w kierunku wschodnim, to kąt obserwacji zacznie się zmniejszać, a obraz obiektu zwężać. Gdy obserwator osiągnie kierunek na wschód od obiektu, to jego obraz przestanie być widoczny. Sytuacja taka zdarza się w praktyce bardzo często, gdy używa się obiektów dwuwymia rowych w celu reprezentacji obiektów trójwymiarowych. Systemy cząstek, które omó wione zostaną wkrótce, wykorzystują pojedyncze czworokąty pokryte teksturą do repre zentacji trójwymiarowych cząstek. Również starsze gry tworzące trójwymiarową grafikę, jak na przykład Doom, Duke Nukem’ 3D czy Daggerfall, często korzystały z dwuwy miarowych obrazów w celu reprezentacji postaci gry. Aby zachować wrażenie ich trój wymiarowości stosuje się właśnie plakatowanie, które zapewnia, że wielokąt pokryty teksturą jest zawsze skierowany w stronę obserwatora. Teraz należy zastanowić się nad sposobem implementacji plakatowania. Istnieje wiele sposobów na uzyskanie tego efektu. Wybrany tu został jeden z prostszych, który wy maga tylko kilku wierszy kodu, a przede wszystkim najlepiej nadaje się do zastosowa nia w systemach cząstek, które wkrótce będą tworzone. Wielokąt plakatu umieścić na leży w taki sposób, aby jego wektor normalny posiadał kierunek zgodny z kierunkiem widzenia obserwatora, ale przeciwny zwrot. W tym celu wystarczy jedynie odwrócić prze kształcenia opisane przez macierz modelowania. Najpierw trzeba więc pobrać zawartość macierzy modelowania: GLfloat vie w M a tr ix[1 6 ]; g 1GetF1o a tv (GL_M0DELVIEW_MATRIX, viewMatrix);
Następnie powinno się wyznaczyć macierz odwrotną do macierzy modelowania. Jak się jednak okazuje, potrzebne informacje można uzyskać szybciej. Wystarczy zauważyć, że interesująca jest dla programisty jedynie część macierzy o wy miarach 3x3. Jako że macierz 3x3 powstała przez odrzucenie ostatniej kolumny i ostat niego wiersza macierzy modelowania, jest także macierzą ortogonalną, więc wyznacze nie jej macierzy odwrotnej sprowadza się do transpozycji. Po wykonaniu transpozycji dwa pierwsze wiersze reprezentować będą wektory ortogonalne do kierunku widzenia obserwatora zwrócone w górę i w prawo. Ponieważ wiersze macierzy odwrotnej są rów noważne z kolumnami macierzy przed operacją transpozycji, to operację tę można po minąć. Ilustruje to rysunek 15.1.
Rozdział 1 5 . ♦ E fekty specjalne
Rysunek 15.1. Transpozycja macierzy modelowania
mo ni4 ms _m i 2
mi ms m9 m i3
rii2 nri6 mio m i4
m3 " m7 mu mi5_
macierz M
wektor right -> wektor up ->
(m’o ’hh'4 " c ^ r ms mg ; m6 m io m2 _m3 m 7 m i 1
357
m i2 m i3 m i4 m i5 _
macierz M T (transpozycja m acierzy M)
Wspomniane wektory pobiera się więc bezpośrednio z macierzy modelowania: v e c to r3 _ t r ig h t( v ie w M a tr ix [0 ], v ie w M a tr ix [4 ], v ie w M a trix [ 8 ] ) ; v e c to r3 _ t u p ( v ie w M a tr ix [l], v ie w M a tr ix [5 ], v ie w M a tr ix [9 ]) ;
Typ v e c to r3 _ t jest prostą klasą wektorów dysponującą przeciążonymi operatorami dla większości operacji na wektorach i zdefiniowaną w pliku nagłówkowym vectorlib.h umieszczonym na płycie CD. Plakat będzie tworzony wokół określonego punktu przestrzeni. Punkt ten będzie środ kiem czworokąta pokrytego teksturą. Wierzchołki czworokąta wyznaczyć można ska lując odpowiednio oba wektory, a następnie dodając je. Ilustruje to poniższa formuła: newPoint = c e n te rP o in t + up * h e ig h tS c a le + r ig h t * w id th S ca le ;
Dla punktów leżących na lewo od środka czworokąta współczynnik w id th S ca le powi nien być ujemny, podobnie współczynnik h e ig h tS ca le dla punktów leżących poniżej środka. Jeśli rysowany czworokąt jest — tak jak w tym przykładzie — kwadratem, to wystarczy tylko jedna wartość współczynnika skalowania. Nadal jednak trzeba pamię tać, aby wartość współczynnika była ujemna dla punktów leżących na lewo lub poniżej środka wielokąta. Poniżej przedstawiony został fragment kodu tworzący kwadrat plakatu. Zmienna p o in t reprezentuje środek kwadratu, a size połowę długości jego boku: / / d o ln y , lewy w ie rz c h o łe k g lT e x C o o rd 2 f(0 .0 , 0 .0 ) ; g lV e rte x 3 fv ((point / / d o ln y , prawy w ie rz c h o łe k g lT e x C o o rd 2 f(1 .0 , 0 .0 ) ; g lV e rte x 3 fv ((point / / g órn y, prawy w ie rz c h o łe k g lT e x C o o rd 2 f(1 .0 , 1 .0 ) ; g lV e rte x 3 fv ((point / / g órn y, lewy w ie rz c h o łe k g lT e x C o o rd 2 f(0 .0 , 1 .0 ); g lV e rte x 3 fv ((point
+ ( r ig h t + up) * - S 7 ' z e ) . v ) ; + ( r ig h t - up) * S 7 ' z e ) . v ) ; + ( r ig h t + up) * s iz e ) . w): + (up - r ig h t) * s iz e ) . w):
Przykład: kaktusy na pustyni Ponieważ typowym przykładem zastosowania plakatowania jest teren porośnięty drze wami, można pokusić się o oryginalność i stworzyć scenę przedstawiającą kaktusy ro snące na pustyni, co pokazuje rysunek 15.2. Najważniejszy fragment tworzącego scenę programu to funkcja DrawCacti O : v o id D raw C actiO
{ / / in ic ju j e g e n e ra to r pseudolosowy s ra n d ( 1 0 0 ) ;
358
Część II ♦ Korzystanie z OpenGL
Rysunek 15.2. Kaktusy na pustyni jako przykład techniki plakatowania
I I p rze z ro cz ys ta część te k s tu ry n ie będzie rysowana glEnable(GL_BLEND); gl BlendFunc(GL_SRC_ALPHA. GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_ALPHA_TEST); glAlphaEunc(GL_GREATER, 0 );
/ / pob ie ra m acierz modelowania f lo a t m a t[1 6 ]; glGetFloatv(GL_MODELVIEW_MATRIX, m at); / / tw orzy w ektory o rto g o n a ln e v e c to r3 _ t r ig h t( m a t[0 ], m a t[4 ], m a t[8 ]); v e c to r3 _ t u p ( m a t[l], m a t[5 ], m a t[9 ]); / / w ybiera te k s tu rę kaktusa glBindTexture(GL__TEXTURE_2D, g_cactus) ; glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); / / ry s u je w s z y s tk ie kaktusy glBegin(GL_QUADS); fo r ( in t n = 0; n < NUM_CACTI; n++)
{
/ / losowa w ie lk o ś ć kaktusa f l o a t s iz e = 5 . Of + FRAND + 3 . O f; / / losowe p o ło ż e n ie na mapie v e c to r3 _ t pos(RAND_C00RD((MAP_X - 1) * MAP_SCALE), 0 .0 , - RAND_C00RD( ( MAP_Z - 1) * MAP_SCALE)); pos.y = G e tF le ig h t(p o s.x, p o s .z ) + s iz e - 0 .5 f; / / d o ln y , lewy w ie rz c h o łe k g lT e x C o o rd 2 f(0 .0 , 0 .0 ) ; g lV e rte x 3 fv ((p o s + ( r ig h t + up) * - s iz e ) . v ) ; / / d o ln y , prawy w ie rz c h o łe k g lT e x C o o rd 2 f(1 .0 , 0 .0 ); g lV e rte x 3 fv ((p o s + ( r ig h t - up) * s iz e ) . v ) ; / / górny, prawy w ie rz c h o łe k g lfe x C o o rd 2 f(1 .0 . 1 .0 ); g lV e rte x 3 fv ((p o s + ( r ig h t + up) * s iz e ) . v ) ;
Rozdział 1 5 . ♦ E fekty specjalne
359
/ / górny, lewy w ie rz c h o łe k g lT e x C o o rd 2 f(0 .0 , 1 .0 ); g lV e rte x 3 fv ((p o s + (up - r ig h t) * s iz e ) . v ) ;
} g lE n d O ; glD isable(G L_A LPH A); glD isable(G L_BLEN D ); } / / D raw C actiO
Program umożliwia poruszanie się po krajobrazie za pomocą klawiszy ze strzałkami. Przytrzymanie klawisza Shift powoduje zwiększenie prędkości poruszania się.
Zastosowania systemów cząstek Zanim będzie można przejść do omówienia szczegółów zastosowań systemów czą stek, trzeba najpierw zdefiniować to pojęcie. Przez system cząstek należy rozumieć zbiór pojedynczych elementów zwanych cząstkami. Każda z cząstek posiadać może wartości atrybutów odróżniające ją od innych cząstek. Do atrybutów tych należą na przykład prędkość, kolor, czas życia i tak dalej. Każda z cząstek działa autonomicz nie, niezależnie od innych cząstek. Cząstki danego systemu posiadają jednak pewien zbiór wspólnych atrybutów. Dzięki temu — mimo że działają niezależnie — tworzą jednak wspólnie jeden efekt. Najprostszy przykład zastosowania systemu cząstek to snop iskier, z których każda reprezentowana jest przez jedną cząstkę. Łącząc zasto sowanie systemu cząstek z możliwościami tekstur można tworzyć także realistyczne efekty przedstawiające ogień, dym, eksplozję, przepływ cieczy, opady atmosferyczne, opary, rozgwieżdżone niebo i wiele innych. Ze względu na samodzielną naturę cząstki stanowią wyjątkowo wdzięczny przedmiot zastosowania programowania obiektowego. Klasy języka C++ modelujące system czą stek można tworzyć na dwa sposoby. Pierwszy polega na stworzeniu klasy bazowej systemu cząstek, z której dziedziczyć będą one specjalizowane klasy reprezentujące różne rodzaje systemów cząstek (na przykład osobne klasy dla symulacji dymu, inne dla ognia i tak dalej). Drugi sposób polega na utworzeniu ogólnej klasy reprezentującej system cząstek. W zależności od zastosowania będzie można zmieniać zestaw atrybu tów tej klasy tak, by uzyskać pożądane właściwości systemu cząstek. Wybór sposobu implementacji systemu cząstek zależy od osobistych preferencji programisty. Tu wy brany został pierwszy sposób, ponieważ wydaje się bardziej uniwersalny i przejrzysty. Zanim wniknie się w szczegóły implementacji systemu cząstek, należy przyjrzeć się naj pierw jego elementom i ich atrybutom.
Cząstki Omówienie elementów systemu cząstek należy rozpocząć od najbardziej podstawowego elementu, czyli cząstki. Na początku trzeba zdecydować o tym, jakie atrybuty będą cha rakteryzować cząstkę. Mogą być wśród nich: ♦ położenie; ♦ prędkość;
360
Część II ♦ Korzystanie z OpenGL
♦ czas życia; ♦ rozmiar; ♦ masa; ♦ reprezentacja; ♦ kolor; ♦ przynależność. Cząstki mogą posiadać także inne atrybuty. Zaprezentowana tutaj lista powinna oka zać się wystarczająca w przypadku większości zastosowań.
Położenie Aby narysować cząstkę, trzeba znać jej położenie w przestrzeni. Położenie cząstki jest z pewnością jej atrybutem, a nie atrybutem systemu cząstek. Można także przechowy wać informację o poprzednich położeniach cząstki, jeśli zamierza się rysować jej ślad. Położenie cząstki zależy od jej prędkości. Chociaż niektóre z atrybutów wydają się przynależeć do cząstek, to będą jednak przechowywane przez system cząstek, ponieważ wszystkie cząstki danego systemu współdziałają tworząc jeden, wspólny efekt. Jeśli na przykład wszystkie cząstki da nego systemu będą posiadać taką sam ą m asę, to nie ma sensu tworzenie setek kopii tej samej wartości przechowywanych przez poszczególne cząstki. Wystarczy, że wartość tę będzie przechowywał system cząstek.
Prędkość W większości zastosowań cząstki poruszają się, dlatego też trzeba określić ich pręd kość. Najwygodniej przechować jest informację o prędkości razem z wektorem okre ślającym kierunek ruchu. Dane te posłużą do wyznaczenia nowego położenia cząstki. Prędkość cząstek może ulegać zmianie na skutek oddziaływania grawitacji bądź oporu powietrza. Wpływ tych czynników omówiony zostanie w dalszej części rozdziału. Ruch cząstki może także charakteryzować się pewnym przyspieszeniem. Zwykle jednak przyczyny zmiany prędkości cząstki będą miały charakter zewnętrzny.
Czas życia Cząstki emitowane są przez źródło i po pewnym czasie przestają istnieć. Dlatego też dla każdej cząstki trzeba przechowywać informację o tym, jak długo istnieje dana cząstka, ile jeszcze czasu może istnieć. Czas życia cząstki może mieć wpływ na inne atrybuty, takie jak jej rozmiar, kolor, które mogą zmieniać się z upływem czasu.
Rozdział 1 5 . ♦ Efekty specjalne
361
Rozmiar Rozmiar nie musi być koniecznie atrybutem poszczególnych cząstek. Jeśli rozmiar cząstki nie zmienia się w ciągu jej życia lub rozmiary cząstek emitowanych przez źródło nie różnią się, to nie ma takiej potrzeby. W praktyce jednak sytuacja, w której rozmiar cząstki zmienia się z czasem występuje dość często. W takim wypadku może okazać się także potrzebny dodatkowy atrybut określający szybkość zmiany rozmiaru cząstki.
Masa Podobne uwagi jak te, które są związane z rozmiarem cząstki, można przywołać w sto sunku do jej masy. Masa cząstki nie jest może zbyt precyzyjnym określeniem tego atry butu, ale w przybliżeniu oddaje jego znaczenie. Atrybut ten określa wpływ różnych czynników zewnętrznych na zachowania cząstki.
Reprezentacja Aby cząstki tworzyły pewien efekt wizualny, muszą posiadać swoją reprezentację na ekranie. Najczęściej używane są trzy sposoby prezentacji cząstek: ♦
p u n k t y — reprezentacja cząstek za pomocą punktów okazuje się wystarczająca w przypadku wielu efektów (zwłaszcza, gdy są one obserwowane z większej odległości);
♦
o d c i n k i — odcinki używane są do reprezentacji cząstek, gdy konieczne jest uzyskanie efektu śladu cząstki (odcinek łączy zwykle bieżącą pozycję cząstki z pozycją poprzednią);
♦
— sposób ten oferuje największe możliwości i dlatego stosowany jest najczęściej; cząstka reprezentowana jest za pomocą czworokąta pokrytego teksturą, zwykle o wartości współczynnika alfa mniejszej od 1 (przykładem zastosowania tej metody może być cząstka imitująca iskrę reprezentowana za pomocą czworokąta pokrytego teksturą wyobrażającą iskrę). c z w o r o k ą ty p o k r y te te k stu r ą
W większości przypadków wszystkie cząstki danego systemu posiadają taką samą repre zentację i, jeśli reprezentowane są za pomocą czworokątów, pokryte są taką samą tek sturą. Dlatego też najczęściej reprezentacja jest atrybutem systemu cząstek, a nie poje dynczych cząstek. W przypadku reprezentacji cząstek za pomocą czworokątów zwykle stosuje się plaka towanie.
Kolor Jeśli cząstki reprezentowane są za pomocą punktów lub odcinków, to trzeba określić także ich kolor. Także stosując reprezentację cząstek w postaci czworokątów pokrytych teksturą, w niektórych zastosowaniach warto połączyć teksturę z różnymi kolorami czą stek (na przykład snop różnokolorowych iskier). Kolor cząstek może zmieniać się z cza sem i wtedy konieczny jest dodatkowy atrybut opisujący szybkość tych zmian.
362
Część II ♦ Korzystanie z OpenGL
Przynależność Cząstka powinna znać swoją przynależność do określonego systemu cząstek, jeśli ko rzysta z metod klasy reprezentującej system cząstek.
Metody Oprócz atrybutów cząstki mogą posiadać metody. Jak zostanie to pokazane, wystarczy, że klasa cząstki posiada jedną metodę umożliwiającą zmianę atrybutów cząstki. Para metrem tej metody będzie okres czasu, który upłynął od jej poprzedniego wywołania. Pozostałe operacje na cząstkach (takie jak na przykład inicjacja cząstki lub jej usunięcie) wykonywane będą bezpośrednio przez system cząstek.
Systemy cząstek Każdy system cząstek dysponuje własnym zbiorem autonomicznych cząstek, które po siadają jednak pewne wspólne atrybuty. Zadaniem systemu jest tworzenie tych cząstek i nadanie im odpowiednich atrybutów w taki sposób, by uzyskać wymagany efekt. System cząstek określa zwykle: ♦ listę cząstek; ♦ swoje położenie; ♦ częstość emisji cząstek; ♦ oddziaływania cząstek; ♦ zakresy wartości atrybutów cząstek i ich wartości domyślne; ♦ własny, bieżący stan; ♦ sposób łączenia kolorów cząstek; ♦ reprezentację cząstek. Jeśli system cząstek porusza się, to zwykle posiadać będzie także wektor prędkości. Jest to lepsze rozwiązanie niż arbitralna zmiana położenia systemu cząstek przez aplikację.
Lista cząstek Aby system mógł zarządzać cząstkami, musi posiadać ich listę. Dla danego systemu należy także określić największą dozwoloną liczbę posiadanych cząstek.
Położenie System cząstek musi posiadać określone położenie w przestrzeni, aby możliwe było wy znaczenie początkowego położenia cząstek. Zwykle położenie systemu cząstek modelo wane jest za pomocą pojedynczego punktu w przestrzeni. Jednak nic nie stoi na przeszko dzie, aby system cząstek reprezentowany był na przykład za pomocą prostokąta, a cząstki emitowane były z losowo wybranych punktów tego prostokąta. Takie położenie systemu cząstek będzie przydatne w uzyskaniu efektu śniegu. Prostokąt systemu cząstek umieścić należy wtedy w określonym obszarze nieba, z którego następować będzie opad.
Rozdział 1 5 . ♦ E fekty specjalne
363
Częstość emisji Atrybut ten określa to, jak często system cząstek tworzy nowe cząstki. Jeśli system regu larnie emituje cząstki, to musi przechowywać informację dotyczącą czasu, który upłynął od momentu utworzenia poprzedniej cząstki i zerować go podczas emisji kolejnej cząstki. Częstość emisji powiązana je s t z m aksymalną liczbą cząstek systemu i czasem ich życia. Jeśli cząstki systemu żyją zbyt długo, to system może zbyt szybko osiągnąć m aksym alną liczbę cząstek. W tej sytuacji em isja kolejnej cząstki nie je s t możliwa przez pewien okres czasu, co powoduje skokowe działanie efektu. Jeśli nie było ono założone, to należy lepiej dobrać częstość emisji i długość życia cząstek bądź usu wać najdłużej istniejącą cząstkę, gdy zachodzi konieczność utworzenia nowej.
Nie wszystkie systemy cząstek muszą posiadać określoną częstość emisji. Na przy kład systemy cząstek używane do uzyskania efektu wybuchu emitują wszystkie cząstki za jednym razem.
Oddziaływania Jeśli cząstki będą oddziaływać z otoczeniem na różne sposoby, podniesie to realizm tworzonego przez nie efektu. Wektor reprezentujący oddziaływanie cząstek powinien stanowić raczej atrybut systemu cząstek niż być arbitralnie określany przez aplikację dla wszystkich systemów cząstek.
Atrybuty cząstek, zakresy ich wartości i wartości domyślne System cząstek nadaje wartości atrybutom cząstki w momencie jej tworzenia. Wartości atrybutów niektórych cząstek mogą zmieniać się w czasie ich istnienia. System cząstek przechowuje więc wartości domyślne atrybutów cząstek oraz zakresy ich wartości. Często tworząc cząstki trzeba będzie uzyskać pewne ich zróżnicowanie, aby wszystkie nie wyglądały dokładnie tak samo. W tym celu system cząstek powinien określać do puszczalne odchylenie wartości atrybutu od wartości domyślnej. Przy tworzeniu cząstki pomnoży się wtedy wartość tego odchylenia przez wartość losową z przedziału od - 1 do 1 , a wynik doda do domyślnej wartości atrybutu. Tworząc efekty specjalne nie trzeba niewolniczo modelować rzeczywistego świata. Wystarczy, że efekt będzie prezentował się realistycznie. Dlatego też podobnie jak atrybut masy posiada jedynie umowne znaczenie i nie reprezentuje masy rzeczywi stej cząstki, tak i oddziaływania nie muszą reprezentować wiernie swoich odpowied ników w świecie rzeczywistym. Gdy na przykład modeluje się grawitację, wartość przyspieszenia nie musi wynosić 9 ,8 m /s 2. Ważne, by wpływ przyciągania ziem skie go na cząstki był wystarczająco realistyczny. Rozwijając przykład grawitacji można by zwiększyć przyciąganie ziem skie dla kuli stalowej, a zmniejszyć dla piórka i uzyskać w ten sposób efekt szybszego opadania kuli zgodny z rzeczywistym doświadczeniem. Oczywiście wiadomo, że siła przyciągania nie zależy od rodzaju m ateriału, z którego wykonany je s t obiekt. Symulując oddziaływanie grawitacji połączono w jedną całość dodatkowe czynniki, takie jak na przykład opór powietrza. Jeśli uzyskany model do brze symuluje rzeczywisty efekt i je s t w dodatku prosty, to należy go zastosować.
Część II ♦ Korzystanie z OpenGL
364
Niektóre z domyślnych wartości atrybutów cząstek przechowywanych przez system czą stek nie muszą przekładać się wprost na wartości atrybutów poszczególnych cząstek. System cząstek może na przykład przechowywać wektor prędkości cząstek, a każda z cząstek jedynie odchylenie swojej prędkości od domyślnej wartości.
Stan bieżący System cząstek może zmieniać swoje działanie z upływem czasu. W systemach cząstek, które będą tworzone w tej książce, zmiana ta polegać będzie na wyłączeniu lub włącze niu emisji cząstek. Istnieje wiele sytuacji, w których będzie trzeba wyłączyć emisję czą stek przez system. Typowym przykładem są systemy cząstek symulujące efekt wybu chu, które po krótkiej chwili przestają emitować cząstki. Inne systemy cząstek będą wyłączane najczęściej w sytuacji, gdy znajdą się poza obszarem widzenie obserwatora. Wyłączając system cząstek trzeba zdecydować także o losie wyemitowanych przez nie go cząstek, które jeszcze istnieją. Cząstki te można natychmiast usunąć bądź zezwolić im, by kontynuowały swoje istnienie. Stan systemu cząstek będzie można przechowy wać za pomocą odpowiedniej zmiennej.
Łączenie kolorów Większość systemów cząstek korzysta z możliwości łączenia kolorów. Sposób zasto sowania łączenia kolorów zależy od konkretnego systemu.
Reprezentacja W większości przypadków wszystkie cząstki danego systemu będą reprezentowane w ten sam sposób, czyli za pomocą punktów, odcinków bądź czworokątów pokrytych teksturą. Informacje o sposobie reprezentacji cząstek przechowywać będzie system cząstek. Jeśli na przykład cząstki reprezentowane będą za pomocą czworokątów, to system cząstek będzie przechowywać nazwę tekstury. W tym miejscu trzeba podjąć ważną decyzję związaną z projektem klas reprezentują cych cząstki. Można zdecydować się na utworzenie ogólnej klasy cząstek, która umoż liwiać będzie tworzenie cząstek reprezentowanych za pomocą punktów, odcinków bądź czworokątów. Można też utworzyć bazową klasę cząstek, na podstawie której będzie można tworzyć wyspecjalizowane klasy cząstek różniące się sposobem ich reprezentacji. Wybór jednej z tych możliwości zależy od preferencji i potrzeb progra misty (na przykład od tego, czy zam ierza w ogóle korzystać ze wszystkich sposobów reprezentacji, czy może tylko z jednego). W przykładach prezentowanych w tej książ ce używana będzie bazowa klasa cząstek, a konkretne klasy cząstek tworzone będą za pomocą dziedziczenia. Rozwiązanie takie je s t bardziej uniwersalne, gdyż umożli wia tworzenie dodatkowych, alternatywnych sposobów reprezentacji cząstek.
Metody System cząstek będą wykonywać różne operacje dostępne w postaci metod klasy sys temu cząstek.
Rozdział 1 5 . ♦ Efekty specjalne
365
♦ Inicjacja. Aby system cząstek stworzył pożądany efekt wizualny, trzeba go odpowiednio skonfigurować, czyli nadać jego atrybutom właściwe wartości. Ponieważ atrybutów tych może być sporo, to zwykle zamiast przekazywać ich wartości metodzie inicjacji osobno, można umieścić je w pojedynczej strukturze. ♦ Aktualizacja. Metoda ta, wywoływana w regularnych odstępach czasu, będzie aktualizować cząstki danego systemu oraz decydować, czy należy wyemitować kolejne cząstki. ♦ Rysowanie. Metoda ta rysować będzie wszystkie istniejące cząstki systemu korzystając z atrybutów poszczególnych cząstek i atrybutów określonych przez system. System cząstek określa też sposób graficznej reprezentacji cząstek. ♦ Ruch. Jeśli będzie trzeba zmienić położenie systemu cząstek, to potrzebna będzie do tego odpowiednia metoda. W takim przypadku przydatna może okazać się także metoda zwracająca bieżące położenie systemu cząstek. ♦ Zmiana stanu. Metoda ta zmieniać będzie stan systemu cząstek w opisanych wcześniej sytuacjach. Pomocna może okazać się także metoda pozwalająca określić bieżący stan systemu cząstek. ♦ Oddziaływania. Oddziaływania mające wpływ na zachowanie cząstek systemu mogą zmieniać się w czasie i wtedy niezbędna okaże się metoda umożliwiająca zmianę odpowiedniego atrybutu systemu. Także cząstki mogą korzystać z osobnej metody w celu pobrania informacji o oddziaływaniach. Zestaw metod może oczywiście zmieniać się w zależności od konkretnych potrzeb i za stosowań.
Menedżer systemów cząstek Systemy cząstek zarządzają autonomicznymi cząstkami — emitują je, aktualizują i usu wają. Jeśli używa się wielu systemów cząstek, co w praktyce zdarza się bardzo często, to celowe jest rozszerzenie tej struktury o kolejny poziom — menedżera systemów cząstek. Zadanie menedżera systemów cząstek może polegać na zmianie położenia systemów cząstek, modyfikacji oddziaływań na cząstki systemów na skutek zmiany pewnych glo balnych czynników (na przykład wiatru), a także na tworzeniu i usuwaniu systemów cząstek na skutek zdarzeń zachodzących w grze bądź znalezienia się poza obszarem wi dzianym przez obserwatora. Działanie menedżer systemów cząstek zależy w znacznym stopniu od specyfiki aplika cji i dlatego nie będzie analizowana tutaj jego implementacja.
Implementacja Przeanalizowano dotąd zagadnienia związane z projektowaniem systemu cząstek. Są one niezwykle ważne, jeśli zamierza się uzyskać uniwersalne systemy cząstek o dużych możliwościach zastosowań. Pozwalają one stworzyć zestaw bazowych klas implemen tujących wspólne cechy wszystkich systemów cząstek. Klas tych nie będzie się wyko rzystywać do tworzenia efektów wizualnych w grach. Posłużą one do stworzenia no wych klas implementujących specyficzne efekty.
366
Część II ♦ Korzystanie z OpenGL
Klasa reprezentująca pojedyncze cząstki nie będzie posiadać metod, dlatego można zde finiować ją za pomocą zwykłej struktury: s tr u c t p a r t i c l e j t
{ v e c to r3 _ t v e c to r3 _ t v e c to r3 _ t v e c to r3 _ t
m_pos; m_prevPos; m _ v e lo c ity ; m _ a c ce le ra tio n ;
// // // //
bieżące p o ło ż e n ie c z ą s tk i poprzednie p o ło że n ie c z ą s tk i k ie ru n e k i prędkość ruchu p rzy s p ie s z e n ie
f lo a t
m_energy;
/ / czas is tn ie n ia c z ą s tk i
f lo a t f lo a t
m_size; m _sizeD elta;
/ / ro zm ia r c z ą s tk i / / zmiana rozm iaru c z ą s tk i
f lo a t f lo a t
m_weight; m _w eightD elta;
/ / wpływ g r a w ita c ji na ruch c z ą s tk i / / zmiana wpływu g r a w ita c ji
f lo a t f lo a t
m _ c o lo r[4 ]; m _ c o lo rD e lta [4 ];
/ / bie żą cy k o lo r c z ą s tk i / / zmiana k o lo ru c z ą s tk i
Jak łatwo zauważyć, struktura ta nie zawiera wielu omówionych tu atrybutów. Ponie waż będzie ona klasą bazową, to zawiera jedynie te atrybuty, które używane są w więk szości zastosowań systemów cząstek. Oczywiście można by umieścić w strukturze wszystkie atrybuty, jakie kiedykolwiek będą wykorzystywane, a wtedy nie trzeba było by tworzyć nowych klas cząstek. Jednak w praktyce okazałoby się, że w różnych zasto sowaniach wiele z tych atrybutów jest niewykorzystywanych. Jeśli pomnoży się niewy korzystane pola struktury przez liczbę systemów i ich cząstek, to uzyska się całkiem sporą ilość zmarnowanej pamięci. Ponieważ cząstki reprezentuje zwykła struktura, to nie posiadają one własnych metod. I nie ma takiej potrzeby. Atrybuty cząstek będą inicjowane przez system cząstek. Zmia na atrybutów cząstek związana z upływem czasu może być wykonywana przez metodę cząstek, ale rozwiązanie takie będzie mało efektywne. Narzutu związanego z wywoły waniem takiej metody dla setek czy tysięcy cząstek można uniknąć, jeśli system cząstek będzie dokonywał bezpośredniej modyfikacji atrybutów cząstek. A oto implementacja systemu cząstek: c la s s C P article S yste m r
i p u b lic : C P a rtic le S y s te m (in t m a x P a rtic le s . v e c to r3 _ t o r ig in ) ; / / fu n k c je a b s tra k c y jn e v ir t u a l v o id U p d a te (flo a t elapsedTim e) v ir t u a l v o id RenderO v ir t u a l in t
E m it( in t n u m P a rtic le s );
v ir t u a l v o id v ir t u a l v o id
I n i t i a l ize S yste m (); K illS y s te m ();
= 0; = 0;
Rozdział 1 5 . ♦ Efekty specjalne
367
p ro te c te d : v ir t u a l v o id I n i t i a l i z e P a r tic le ( in t in d e x) = 0; p a r ti c l e _ t * m _ p a r tic le l_ is t; / / l i s t a cząstek należących do systemu in t m jn a x P a rtic le s ; / / maksymalna lic z b a cząstek in t m _num Particles; / / lic z b a is tn ie ją c y c h cząstek v e c to r3 _ t m _ o rig in ; / / p o ło ż e n ie systemu cząstek f lo a t
m_accumulatedTime;
v e c to r3 _ t
m _force;
//
okres czasu od wyemitowania p o p rze d n ie j c z ą s tk i
/ / o d d zia ływ a n ia (g ra w ita c ja , w ia tr , e tc .)
/ / P a r tic le s .c p p C P a rtic le S y s te m :: K o n s tru k to r I n ic ju je a try b u ty systemu.
*****************************************************************************j C P a rtic le S y s te m ::C P a rti c le S y s te m (in t m a x P a rtic le s , v e c to r3 _ t o r ig in )
{ m _m axP articles = m a x P a rtic le s ; m _ o rig in = o r ig in ; m _ p a rtic le l_ is t = NULL; } / / C P a rtic le S y s te m :: K o n s tru k to r
C P a rtic le S y s te m :: E m it() Tworzy nowe c z ą s tk i. Ich lic z b a o kre ślo n a je s t przez param etr metody. A try b u ty nowych czą ste k uzyskują domyślną w artość zmodyfikowaną o losową w ie lk o ś ć o d c h y łk i. Je d yn ie w a rto ś c i początkowe a try b u tó w p o s ia d a ją losową o d ch yłkę . W artości końcowe są zawsze ta k ie same, co może wymagać zmiany. *******************************************************^******************** / i n t C P a rtic le S y s te m :: E m it( in t n u m P a rtic le s)
{ / / tw o rzy n u m P a rticle s nowych cząstek ( je ś l i je s t m ie jsc e ) w h ile (n u m P a rticle s && (m _num Particles < m _ m a xP a rticle s))
{ / / in ic j u je bieżącą c zą stkę i zwiększa lic z n ik cząstek In itia liz e P a rtic le (m _ n u m P a r tic le s + + ) ; --n u m P a rtic le s ;
} r e tu rn n u m P a rtic le s ; } / / C P a rtic le S y s te m :: Emit
C P arti c l eSystem : : I n i t i a liz e S y s te m () P rz y d z ie la pamięć d la maksymalnej lic z b y cząstek systemu v o id C P a rtic le S y s te m :: I n i t i a l i zeSystemO
{ / / j e ś l i metodę wywołano w c e lu ponownej i n ic j a c j i systemu i f (m _ p a rtic le l_ is t)
368
Część II ♦ Korzystanie z OpenGL
{
del e te [] m _ p a rtic le l_ ist; m _ p a r t i c l e L i s t = N U LL;
} / / p r z y d z i e l a p a m ię ć m a k sy m a ln e j l i c z b i e c z ą s t e k m _ p a r t i c l e l _ i s t = new p a r t i c l e _ t [ m _ m a x P a r t i c l e s ] ; // ze ru je lic z b ę cząstek / / i c z a s , k t ó r y u p ł y n ą ł od u t w o r z e n i a p o p r z e d n i e j c z ą s t k i m j i u m P a r t i c l e s = 0; m _ a cc u m u la te d T im e = O .O f; } // C P a rtic le S y s te m ::I n itia liz e S y s te m
/***************************************************************************** C P a r t ic le S y s t e m : :Ki 11S y stem C ) Z a t r z y m u j e e m i s j ę c z ą s t e k . J e ś l i p a r a m e t r m e t o d y ma w a r t o ś ć t r u e , t o w s z y s t k ie i s t n i e j ą c e c z ą s t k i są usuwane. ic i c i ^ ^ ^ i c ^ ^ ^ i c ^ i c ^ i c - ^ c 'k 'k - k - k - k 'k ' k ' k - k ' k 'k - k 'k ' k ' k - k ' k 'k 'k 'k 'k 'k 'k ' k ' k ' k ' k ' k ' k ' k ' k - k 'k - k ' k ' k ' k ' k ' k 'k 'k 'k 'k 'k 'k ' k ' k - k - k 'k 'k 'k i f ' k ' k ' k ' k ' k ' k ' k 'k i c j
v o id C P a r t i c l e S y s t e m : :Ki 11Sy stem C )
{
i f (m _ p a rtic le l_ is t)
{ d e le te [] m _ p a rt ic le L is t; m _ p a r t i c l e l _ i s t = N U LL;
} m j i u m P a r t i c l e s = 0; } / / C P a r t i c l e S y s t e m : : K i 11 S y s t e m
Zaprezentowana klasa jest abstrakcyjna, nie może być więc bezpośrednio użyta do two rzenia systemów cząstek. Stanowi jednak szkielet, który można rozszerzyć na drodze dziedziczenia i stworzyć nowe klasy systemów cząstek. Klasa C P articleS ystem C ) po siada metody umożliwiające inicjację lub usunięcie systemu cząstek, które zarządzają przydziałem pamięci dla maksymalnej liczby cząstek. Cząstki te umieszczane są w ta blicy, której elementy o indeksach od 0 do (m jiu m P a rtic le s - 1) reprezentują cząstki aktywne, a elementy o indeksach od m jiu m P a rtic le s do (m jn a x P a rtic le s - 1) wyko rzystywane są przez metodę EmitC ) podczas tworzenia nowych cząstek.
Tworzenie efektów za pomocą systemów cząstek Klasę bazową systemu cząstek wykorzystuje się do tworzenia specjalizowanych klas pochodnych używanych już bezpośrednio w celu uzyskania wizualnych efektów spe cjalnych. Osiągnięcie realistycznie wyglądających efektów wymaga właściwego doboru wartości atrybutów systemu cząstek i sposobu jego działania. Nie istnieje gotowa re cepta działania, a osiągnięcie zadowalającego rezultatu zawsze wymaga pewnego na kładu pracy, w tym doboru niektórych atrybutów metodą prób i błędów. Poniżej znaj duje się kilka ogólnych wskazówek, które mogą okazać się pomocne podczas tworzenia systemów cząstek.
Rozdział 1 5 . ♦ Efekty specjalne
369
♦ Analiza efektu. Większość atrybutów systemu cząstek musi reprezentować w pewien sposób właściwości rzeczywistych zjawisk. Dlatego też nawet pobieżna analiza pozwala rozpocząć implementację odpowiedniego systemu cząstek. Na przykład słup dymu wznosi się zwykle pionowo i może być odchylany przez wiatr. Oznacza to, że początkowy wektor prędkości powinien być skierowany pionowo w górę, a wektor oddziaływania powinien być do niego prostopadły. Ponieważ wznosząc się dym staje się coraz rzadszy należy wziąć także pod uwagę zmianę rozmiaru i koloru cząstek z upływem czasu. ♦ Zastosowanie praw fizyki. Omawiając zachowanie cząstek brano dotychczas pod uwagę najbardziej podstawowe zasady fizyki. Jeśli cząstki mają wiernie symulować zachowanie się ich rzeczywistych odpowiedników, to należy zastosować bardziej złożone modele, co wiąże się z większym nakładem pracy oraz wolniejszym działaniem systemu cząstek. Właściwy model fizyczny systemu cząstek to jednak dopiero połowa sukcesu. Równie doskonały musi być sposób jego wizualnej prezentacji. ♦ Korzystanie z doświadczeń innych. W sieci Internet można znaleźć szereg przykładów implementacji systemów cząstek. Dla wielu z nich udostępniony został także kod źródłowy. Przy małej ilości czasu przeznaczonej na opracowanie charakterystyki systemu cząstek od podstaw można wzorować się na efektach uzyskanych przez innych. W dodatku A podano kilka adresów stron internetowych zawierających przykłady zastosowania systemów cząstek w celu uzyskania różnych efektów. ♦ Eksperyment. Na płycie CD dołączonej do tej książki umieszczony został program „Particie Sim” autorstwa Briana Tischlera (używający Direct3D według projektu Richa Bensona). Umożliwia on obserwację (na bieżąco!) wpływu zmian atrybutów systemu cząstek na tworzony efekt wizualny.
Przykład: śnieżyca Omówienie systemów cząstek nie byłoby kompletne bez przykładu programu demon strującego ich zastosowanie. Pełen kod źródłowy takiego programu umieszczony został na płycie CD. Kilku systemów cząstek używa także program omawiany w rozdziale 21. Przykład, który omówiony zostanie w tym podrozdziale, stosuje system cząstek w celu uzyskania efektu śnieżycy pokazanego na rysunku 15.3. Klasa tego systemu cząstek, CSnowstorm, reprezentuje płatki śniegu za pomocą czworokątów pokrytych teksturą i emi towanych z pewnego obszaru nieba. Poniżej przedstawiona została implementacja klasy CSnowstorm, a pełen kod programu znajduje się na dysku CD. const const const const
vector3_t vector3_t float float
SNOWFLAKE_VELOCITY (O.Of, -3.0f. O.Of); VELOCITY_VARIATION (0.2f. 0.5f. 0.2f); SNOWFLAKE_SIZE = 0.02f; SNOWFLAKES PER SEC = 2000;
370
Część II ♦ Korzystanie z OpenGL
Rysunek 15.3. Śnieżyca nad terenem pokrytym mgłą
I s tr u k tu ry danych c la s s CSnowstorm : p u b lic C P a rticleS y ste m
{ p u b lic : CSnowstormCint m a x P a rtic le s, v ecto r3 _ t o r ig in , f lo a t h e ig h t, f lo a t w id th , f lo a t depth) vo id vo id
U p d a te (flo a t elapsedT im e); RenderO;
void vo id
I n i t i a l ize S y s te m O ; K i l l System !);
p ro tected : void I n i t i a liz e P a r t ic le ( in t index); f lo a t m_height; f lo a t m_width; f lo a t m_depth; G Luint
m _texture;
// te k s tu ra p ła tk a śniegu
// snowstorm.cpp
CSnowstorm::K on stru kto r Nie wykonuje żadnych d z ia ła ń . CSnowstorm::CSnowstormCint n u m P article s, vecto r3 _ t o r ig in , f lo a t h e ig h t, f lo a t w idth, f lo a t depth)
: m _ h e ig h t(h e ig h t), m _ w id th (w id th ), m_depth( d e p th ) , C P arti cleSystemC numPa r t i c l e s , o r i g i n )
{ } // CSnowstorm::K on stru ktor
Rozdział 1 5 . ♦ E fekty specjalne
371
CSnowstorm:: I n i t ia liz e P a r t ic l e O O kreśla początkowe w a rto ś c i a try b u tó w cząstek v o id CSnowstorm:: I n i t i a l i z e P a r t ic l e ( in t in d e x)
{ / / umieszcza c zą stkę w losowo m _ p a rtic le l_ is t[in d e x ].m _ p o s .y m _ p a r tic le L is t[ in d e x ] .m_pos.x m _ p a rtic le L is t[in d e x ].m _ p o s .z
wybranym punkcie s tr e fy e m is ji = m _height; = m _ o rig in .x + FRAND * m _width; = m _ o rig in .z + FRAND * m_depth;
/ / o k re ś la ro zm ia r c z ą s tk i m _ p a rtic le L is t[in d e x ].m _ s iz e = SNOWFLAKESIZE; / / i nadaje j e j p rę d ko ści losowe o d ch yle n ie m _ p a r tic le L is t[in d e x ].m _ v e lo c ity .x = SNOWFLAKE_VELOCITY.x + FRAND * VELOCITY_VARIATION.x; m _ p a rtic l e L is t [ in d e x ] .m _ v e lo c ity .y = SNOWFLAKE_VELOCITY.y + FRAND * VELOCITY_VARIATION.y; m _ p a r tic le L is t[in d e x ].m _ v e lo c ity .z = SNOWFLAKE_VELOCITY.z + FRAND * VELOCITYVARIATION. z ; } / / CSnowstorm:: I n i t i a l iz e P a r tic le
CSnowstorm:: Update A k tu a liz u je a try b u ty c zą s te k , usuwa je i tw o rzy nowe v o id C Snow storm ::U pdate(f l o a t elapsedTim e)
{ fo r ( i n t i = 0; i < m jiu m P a rtic le s ; )
{ / / a k tu a liz u je p o ło ż e n ie c z ą s tk i na podstaw ie j e j prędkości i czasu, k tó ry u p łyn ą ł m _ p a r tic le L is t[i].m _ p o s = m _ p a rtic le L is t[i].m _ p o s + m _ p a r tic le L is t[i].m _ v e lo c ity * elapsedTim e; / / j e ś l i czą stka z n a la z ła s ię w p ła s zc z yź n ie g ru n tu , usuwa ją i f ( m _ p a r t ic le L is t [ i] .m_pos.y Acqui r e ( ); i f (m_pMouse) m_pMouse->Acqui r e ( ); } / / C Inp u tS yste m ::A cq u ire A l 1 ()
/* C InputS ystem : : Unacqui r e A l1 () Zw alnia w s z y s tk ie u rzą d ze n ia . v o id C In p u tS yste m :: UnacquireAl 1 ()
{ i f (m_pKeyboard) m_pKeyboard->Unacqui r e ( ); i f (m_pMouse) m_pMouse->Unacqui r e ( ); } / / C In p u tS yste m ::U n a cq u ire A l1 ()
413
414
Część III ♦ Tworzymy grę
Klasy reprezentujące klawiaturę i mysz obudowują odpowiednie obiekty urządzeń Direct lnput. Ich inicjacja odbywa się w konstruktorach i obejmuje tworzenie obiektów urzą dzeń, określenie ich formatu danych i poziomu współpracy oraz zajęcie urządzenia. Każda z klas przechowuje też ostatnio pobieraną informację o stanie urządzenia. Dane te aktu alizowane są za pomocą metody U pdateO , którą wywołuje system wejścia. Metodę Update () można też wywoływać indywidualnie, jeśli korzysta się bezpośrednio z obiek tów urządzeń. Obiekt klawiatury pozwala sprawdzać stan wszystkich klawiszy, a obiekt myszy dostarcza informacji o względnych zmianach jej położenia i położenia kółka myszy oraz o stanie jej przycisków (do czterech). Poniżej przedstawiona została implementacja klas klawiatury i myszy: c la s s CKeyboard
{
p u b lic : CKeyboard( LPDIRECTINPUT8 pDI, HWND hwnd); -C K eyboard(); bool bool
KeyDown(int key) { re tu rn (m _keys[key] & 0x80) ? tru e : fa ls e ; } K eyU p(int key) { re tu rn (m _keys[key] & 0x80) ? fa ls e : tr u e ; }
bool
U pdateO ;
v o id
C le a rO { ZeroMemory(m_keys, 256 * s iz e o f( c h a r ) ); }
bool bool
A c q u ir e !); U n a c q u ire !);
p r iv a te : LPDIRECTINPUTDEVICE8 m_pDIDev; in t m _keys[256];
}: / ■ k 'k 'k 'k 'k 'k -k 'k -k -k -k 'k -k 'k -k -k -k 'k 'k -k -k -k 'k 'k -k -k 'k -k -k -k -k 'k -k 'k -k 'k ii'k -k 'k 'k 'k -k 'k 'k -k 'k 'k -k 'k 'k 'k 'k -k -k -k -k 'k -k -k -k 'k 'k -k -k -k 'k -k ic -k 'k -k 'k 'k 'k -k 'k
CKeyboard: C o n s tru c to r I n ic ju je urządzenie D ir e c tln p u t CKeyboard::CKeyboard(LPDIRECTINPUT8 pDI, HWND hwnd)
{ i f (FAILED(pDI->CreateDevice!GUID_SysKeyboard, &m_pDIDev„ NULL)))
{ / / obsługa błędów
} i f (FAILED(m_pDIDev->SetDataFormat(&c_dfDIKeyboard)))
{
/ / obsługa błędów
} i f (FAILED(m_pDIDev->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE)))
{
/ / obsługa błędów
} i f (FAILED(m _pDIDev->Acquire() ) )
{ // obsługa błędów
Rozdział 1 6 . ♦ D irectX : D irectlnput
C le a r ! ) ; / / CKeyboard: C o n s tru c to r
/* C K eyboard:: D e s tru c to r Zw alnia u rzą d ze n ie D ir e c tln p u t CKeyboa r d : : -CKeyboa r d ()
{ i f (m j)D ID ev)
{ m_pDIDev->Unacqui r e ( ); m_pDIDev->Release();
}
} / / C Keyboard:: D e s tru c to r
/ * * * * * * * * * * * * jc***jci(i(***itic***-k-k-k-k-k'k-k*-k'k-k'k-k'k'k-k'k'k*'k-k-k'k-k-k'k'k-k'k-k'k'k'k'k'k'k'k'k'k'k-k'k*-k'k'k'k'k*'k
C Keyboard:: UpdateO O dpytuje k la w ia tu rę i przechowuje in fo rm a c ję o j e j s ta n ie . bool C Keyboard:: Update O
{ i f (FAILED (m _pD ID ev->G etD eviceS tate(sizeof(m _keys). ( LPV0ID)m_keys) ) )
{ i f (FAILED (m _pD ID ev->AcquireO ))
{ re tu rn fa ls e :
} i f (FAILED (m _pD ID ev->G etD eviceS tate(sizeof(m _keys), (LPVOID)m_keys)))
{ r e tu rn fa ls e :
} }
r e tu rn tr u e : } / / C K eyboard:: UpdateO
C K eyboard:A c q u ire O Zajm uje k la w ia tu rę .
i'****************************************'}'***********************************j bool C Keyboard:A c q u ir e O
{
C le a r O ; re tu rn ( !F A ILE D (m _pD ID ev->A cquire())); } / / C Keyboard:A c q u ir e O
CKeyboard:: Unacqui re O Zw alnia k la w ia tu rę . • k 'k 'k 'k 'k 'k -k 'k -k -k -k 'k -k -k 'k -k -k -k -k -k -k 'k 'k -k -k -k -k 'k -k 'k -k 'k -k -k -k -k 'k -k -k -k -k -k -k -k -k -k -k -k -k -k -k -k -k -k -k -k -k -k -k 'k -k 'k -k -k 'k 'k 'k 'k 'k 'k 'k 'k 'k 'k 'k 'k if J
415
416
Część III ♦ Tworzymy grę
bool C Keyboard:: U n a cq u ire 0
{ C le a rO ; re tu rn ( ! FAILED(m_pDIDev->Unacquire() ) ) ; } / / CKeyboard: :lln a c q u ire () c la s s CMouse
{
p u b lic : CMouse(LPDIRECTINPUT8 pDI, HWND hwnd, bool is E x c lu s iv e = tr u e ) ; ~CMouse(); bool B uttonD ow ndnt b u tto n ) { re tu rn (m _ s ta te .rg b B u tto n s [b u tto n ] & 0x80) ? tru e : fa ls e ; } bool B u tto n U p d n t b u tto n ) { re tu rn (m _ s ta te .rg b B u tto n s [b u tto n ] & 0x80) ? fa ls e : tr u e ; } in t GetWheelMovement() { re tu rn m _ s ta te .lZ ; } v o id G etM ovem entdnt &dx, in t &dy) { dx = m _ s ta te .lX ; dy = m _ s ta te .lY ; } bool
U p d a te d ;
bool bool
A c q u ir e d ; U n a c q u ire d ;
p r iv a te : LPDIRECTINPUTDEVICE8 DIMOUSESTATE
m_pDIDev; m _state;
}: /***************************************************************************** C M o u s e :C o n stru c to r I n ic ju je urządzenie D ir e c tln p u t. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * j
CMouse::CMouse(LPDIRECTINPUT8 pDI, HWND hwnd, bool is E x c lu s iv e )
{ i f (FAILED(pDI->CreateDevice(GUID_SysMouse. &m_pDIDev, NULL)))
{ / / obsługa błędów
} i f (FAILED(m_pDIDev->SetDataFormat(&c_dfDIMouse)))
{ / / obsługa błędów
} DWORD fla g s ; i f (is E x c lu s iv e ) fla g s = DISCL_FOREGROUND | DISCLJXCLUSIVE | DISCLJOWINKEY; e ls e fla g s = DISCLJOREGROUND | DISCL_NONEXCLUSIVE;
i f (FAILED(m _pDIDev->SetCooperativeLevel(hwnd, f la g s ) ) )
{ // obsługa błędów
Rozdział 1 6 . ♦ D irectX : D irectlnput
i f (FAILED(m_pDIDev->Acquire()))
{
// obsługa błędów
} i f (FAILED(m_pDIDev->GetDeviceState(sizeof(DIMOUSESTATE), &m_state)))
{
// obsługa błędów
}
} // CMouse::Constructor
z***************************************************************************** CMouse::Destructor Zwalnia urządzenie Directlnput
*****************************************************************************/ CMouse::~CMouse()
{
i f (m_pDIDev)
{
m_pDIDev->Unacqui re(): m_pDIDev->Release();
}
} // CMouse::Destructor
CMouse: -.UpdateO Odpytuje mysz i przechowuje informacje o jej stanie.
*****************************************************************************/ bool CMouse::UpdateO i f (FAILED(m_pDIDev->GetDeviceState(sizeof(DIMOUSESTATE), &m_state)))
{
i f (FAILED(m_pDIDev->Acquire()))
{
return false;
i f (FAILED(m_pDIDev->GetDeviceState(sizeof(DIMOUSESTATE), &m_state)))
{
return false;
} } return true; } // CMouse::UpdateO
Z***************************************************************************** CMouse::Acquire() Zajmuje mysz. ii****************************************************************************/
417
418
Część III ♦ Tworzymy grę
bool CMouse::A cq uireO
{ return ( ! FAILED(m_pDIDev->Acqui r e ( )) ) ; } // CMouse::Acquire
CMouse::UnacquireO Zwalnia mysz bool CMouse: : Unacqui r e ( )
{ return ( ! FAILED(m_pDIDev->Unacqui r e ( )) ); } // CMouse::Unacquire ()
Przykład zastosowania systemu wejścia Zastosowanie podsystemu wejścia zilustrowane zostanie przykładem prostego progra mu, którego efekt działania przedstawia rysunek 16.1. Program śledzi ruch myszy i wy świetla jej wskaźnik na ekranie. Naciśnięcie przycisku myszy lub ruch jej kółka powo dują zmianę kształtu kursora myszy. Program monitoruje także stan klawiszy funkcyjnych klawiatury i wyświetla nazwę ostatnio naciśniętego klawisza funkcyjnego na środku ekranu. Rysunek 16.1. Przykład zastosowania systemu wejścia
Pełen kod źródłowy przykładowego programu znajduje się na dysku CD. Tutaj zamiesz czony został jedynie kod funkcji P ro c e s s In p u t( ). Należy zwrócić uwagę, że aby zmie nić kształt kursora dokładnie raz przy każdym naciśnięciu przycisku myszy, nie wystar czy jedynie wywołanie metody ButtonDow n( ). Ponieważ w każdej sekundzie tworzy się grafikę co najmniej kilkanaście razy, a przycisk myszy jest naciśnięty nieco dłużej niż ułamek sekundy, to metoda ButtonDownO zwraca wartość tru e więcej niż jeden raz dla pojedynczego naciśnięcia przycisku. Należy więc obsłużyć moment naciśnięcia przyci sku, a następnie nie wykonywać żadnych działań do momentu jego zwolnienia.
Rozdział 1 6 . ♦ D irectX: D irectlnput
419
vo id P rocessIn p u t O
{ s t a t ic bool le ftB u tton D o w n = fa ls e ; s t a t ic bool rightB uttonD ow n = fa ls e ; / / zm ienia k u rs o r myszy na skutek n a c iś n ię c ia j e j p rz y c is k u lu b o brotu kółka i f (g _ inp u t.B u tto n D o w n (O )) leftB u tton D o w n = tr u e ; i f (g _ in p u t.B u tto n D o w n (l)) r ig h t ButtonDown = tr u e ; i f ( g _ in p u t.GetMouseWheelMovement O < 0 || (le ftB u tto n D o w n && g _ in p u t.B u tto n U p (O )))
{ leftB u tton D o w n = fa ls e ; g _ te x tu re ln d e x --; i f (g _ te x tu re ln d e x < 0) g _ te x tu re ln d e x = NUM_TEXTURES - 1;
} i f (g_input.GetMouseWheelMovement O > 0 || (rightButtonDown && g _ in p u t.B u tton U p (l)))
{ rightB uttonD ow n = fa ls e ; g _ te x tu re ln d e x + + ; i f (g _ te x tu re ln d e x == NUM_TEXTURES) g _ te x tu re Index = 0;
} / / a k tu a liz u je p o ło ż e n ie myszy i n t dx, dy; g_input.GetM ouseM ovem ent(dx, d y ) ; / / zapobiega opuszczeniu ekranu przez k u rs o r g_mouseX += dx; i f (gjnouseX >= g_screenW idth) g_mouseX = g_screenW idth - 1; i f (gjnouseX < 0) g_mouseX = 0; g_mouseY -= dy; i f (g_mouseY >= g_screenH eight) g_mouseY = g_screenH eight - 1; i f (gjnouseY < 0) g_mouseY = 0; // sprawdza, czy n a c iśn ię ty zo sta ł jeden z klaw iszy funkcyjnych i f (g_input.KeyDow n(DIK_Fl)) strc p y (g _ la stK e y , " F I " ) ; i f (g_input.KeyDown(DIK_F2)) strc p y (g _ la stK e y , "F 2 "); i f (g_input.KeyDown(DIK_F3)) strc p y (g _ la stK e y , "F 3 "); i f (g_input.KeyDown(DIK_F4)) strc p y (g _ la stK e y , "F 4 "); i f (g _ in p u t .KeyDown(DIK_F5)) strc p y (g _ la stK e y , "F 5 "); i f (g_input.KeyDown(DIK_F6)) strc p y (g _ la stK e y . "F 6 "); i f (g_input.KeyDown(DIK_F7))
420
Część III ♦ Tworzymy grę
s trc p y (g _ la s tK e y , "F 7 "); i f ( g jn p u t .KeyDown(DIK_F8)) s trc p y (g _ la s tK e y . "F 8 "); i f (g_input.KeyDown(DIK_F9)) s trc p y (g _ la s tK e y , "F 9 "); i f (g jn p u t.K e yD o w n (D IK _ F 1 0)) s trc p y (g J a s tK e y , "F 1 0 "); i f (g jn p u t.K e y D o w n (D IK _ F ll)) s trc p y (g _ la s tK e y , " F i l " ) ; i f (gjnpu t.K e yD o w n (D IK _ F 1 2)) s trc p y (g J a s tK e y , "F 1 2 "); / / sprawdza, czy n a c iś n ię ty k la w is z zakończenia ekranu i f (gjnput.KeyDown(DIK_ESCAPE)) P ostQ uitM essage(0); } / / P ro ce ssln p u t O
Podsumowanie W systemie Windows dostępnych jest kilka sposobów pobierania informacji wejściowej wprowadzanej przez użytkownika. Komunikaty systemu Windows i interfejs progra mowy Win32 stanowią najprostsze rozwiązanie, którego efektywność nie jest jednak wystarczająca w przypadku gier. Directlnput stanowi alternatywne rozwiązanie, którego zaletą jest duża szybkość. W rozdziale przedstawiono jedynie część możliwości Direc tlnput, jednak w stopniu wystarczającym do zastosowania w grach.
Rozdział 17.
Zastosowania DirectX Audio W czasach systemu operacyjnego DOS tworzenie podsystemu dźwiękowego gry uważane było za szczególnie trudne zadanie ze względu na ogromną liczbę różnych kart dźwię kowych dostępnych na rynku. Sytuacji tej nie poprawiło wprowadzenie systemu Win dows, który choć przesłaniał te różnice sprzętowe, to jednak nie nadawał się na docelową platformę dla tworzonych gier ze względu na niską efektywność. Obecnie DirectX Au dio oferuje programistom nie tylko doskonałą efektywność i obsługę praktycznie każdej karty dźwiękowej, ale przede wszystkim bogatą funkcjonalność pozwalającą zrealizować najbardziej zaawansowane zadania. W rozdziale tym pokazane zostanie to, w jaki spo sób wykorzystać DirectX Audio do stworzenie podsystemu dźwiękowego gry. W rozdziale tym przedstawione zostaną: ♦ podstawowe informacje na temat dźwięku; ♦ podstawy DirectX Audio; ♦ ładowanie i odtwarzanie dźwięku za pomocą DirectMusic; ♦ ścieżki dźwiękowe DirectX Audio; ♦ przestrzenne efekty dźwiękowe DirectX Audio.
Dźwięk Fizyka mówi, że dźwięk jest falą mechaniczną emitowaną przez źródło i rozchodzącą się w pewnym kierunku, co ilustruje rysunek 17.1. W przypadku atmosfery ziemskiej zasady te określają molekuły gazów. Natomiast obserwując wybuch w przestrzeni kosmicznej nie można by usłyszeć żadnych efektów dźwiękowych, ponieważ dźwięk nie może roz chodzić się w próżni. Fala dźwiękowa może natomiast przemieszczać się w innych ośrodkach, na przykład wodzie i to z prędkością większą niż w powietrzu.
422
Część III ♦ Tworzymy grę
Rysunek 17.1. Dźwięk jest falą mechaniczną emitowaną przez źródło
Źródło dźwięku
Molekuły gazu sprężają się i rozprężają
Fala dźwiękowa \
!
/
V
W
\
Mówiąc, że dźwięk jest falą mechaniczną opisuje się w rzeczywistości ruch cząstek ośrod ka, w którym rozchodzi się dźwięk. Aby się o przy tym przekonać, można na przykład zbliżyć dłoń do głośnika basów zestawu muzycznego odtwarzającego głośno muzykę. Poczuje się falę powietrze tworzoną przez drgającą membranę głośnika. Energia mem brany przekazywana jest otaczającym ją cząstkom powietrza, które następnie przekazują ją kolejnym cząstkom i tak dalej aż do momentu, gdy drgania te dotrą do ucha i mózg przetworzy je na dźwięk. Dźwięk rozprzestrzenia się więc w powietrzu dzięki zderzeniom molekuł, które w ten sposób przekazują sobie energię mechaniczną. Taki sposób rozchodzenia się fali dźwię kowej sprawia, że prędkość dźwięku jest stosunkowo niewielka (zwłaszcza, jeśli po równa się ją z prędkością światła). Na przykład obserwując wystrzał działa z odległości 1000 metrów ujrzy się natychmiast towarzyszący mu błysk i dym, ale huk wystrzału usłyszy się ponad dwie sekundy później. Dźwięk rozchodzi się bowiem w powietrzu z prędkością około 344 metrów na sekundę. Falę dźwiękową charakteryzują: ♦ amplituda — amplitudę można przedstawić jako objętość ośrodka przemieszczanego przez falę dźwiękową; dysponując potężnymi głośnikami zyskuje się możliwość przemieszczania znacznych mas powietrza i uzyskania dużej amplitudy fali (na wykresie fali pokazanym na rysunku 17.2 amplituda jest reprezentowana przez wysokość grzbietu fali); ♦ częstotliwość — częstotliwość jest liczbą pełnych okresów drgań wykonywanych przez źródło fali w jednostce czasu (okres fali reprezentowany jest na jej wykresie przez odległość pomiędzy dwoma grzbietami fali, co pokazuje rysunek 17.2); jednostką częstotliwości jest herc (Hz); częstotliwość dźwięku odbiera się jako jego wysokość (na przykład głośnik basu emituje dźwięki o niższej częstotliwości niż głośnik wysokotonowy).
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
Rysunek 17.2. Wykres fali
4 23
Okres
Amplituda
Dźwięk i komputery Oczywiście komputery nie przechowują ani nie tworzą dźwięku jako mechanicznych fal rozchodzących się w ośrodku złożonym z molekuł. Nie jest to po prostu technologicznie możliwe. Komputery mogą natomiast przechowywać i odtwarzać dźwięk zapisany cyfrowo, a także uzyskiwany na drodze syntezy. Źródłem dźwięku zapisanego w postaci cyfrowej jest zwy kle mikrofon. Dźwięk zapisany cyfrowo używany jest w grach przede wszystkim do uzy skania efektów dźwiękowych, takich jak eksplozje, odgłosy ruchu czy mowa. Natomiast dźwięk tworzony przez syntezator stanowi najczęściej podkład muzyczny gry.
Cyfrowy zapis dźwięku Aby zapisać dźwięk w postaci cyfrowej, trzeba skorzystać z przetwornika analogowocyfrowego, który zamienia analogowy sygnał mikrofonu na cyfrowe wartości odpowia dające zmianom amplitudy dźwięku w regularnych odstępach czasu. Rysunek 17.3 pre zentuje ten proces. Rysunek 17.3. Cyfrowy zapis dźwięku
Fale dźwiękowe Mikrofon
Przetwornik analogowocyfrowy
1100010100110000111111 0110101101011111100100 Strumień bitów
Dysponując cyfrowym zapisem dźwięku można go odtworzyć za pomocą przetwornika cyfrowo-analogowego, który wytwarza analogowy sygnał wysyłany do głośników. Cyfrowy zapis dźwięku charakteryzują następujące parametry: ♦ częstotliwość próbkowania — określa ona liczbę próbek dźwięku zapisywanych w jednostce czasu i musi być co najmniej dwa razy większa od częstotliwości dźwięku (na przykład zapisując dźwięk o częstotliwości 10 000 Hz trzeba próbkować go z częstotliwością co najmniej 20 000 Hz);
424
Część III ♦ Tworzymy grę
♦
r o z d z i e l c z o ś ć p r ó b k o w a n i a a m p l i t u d y — rozdzielczość ta określa liczbę rozróżnianych wartości amplitudy (jeśli dysponuje się 8-bitowym przetwornikiem analogowo-cyfrowym, to można zapisać jedynie 256 różnych wartości amplitudy; dobrą jakość dźwięku zapewniają przetworniki 16-bitowe umożliwiające rozróżnienie 65 536 wartości amplitudy).
Dźwięk zapisany w postaci cyfrowej używany jest w grach głównie w celu uzyskania krótkich efektów dźwiękowych. Możliwe jest również odtwarzanie ścieżek muzycznych zapisanych cyfrowo, ale w tym przypadku zasadniczy problem stanowią duże rozmiary plików dźwiękowych. Dopiero wprowadzenie technologii MP3 sprawiło, że zastosowa nie muzyki zapisanej cyfrowo w grach stało się o wiele bardziej realne. Jest to o tyle istotne, że muzyka zapisana cyfrowo posiada zdecydowanie lepszą jakość od efektów muzycznych tworzonych na drodze syntezy.
Synteza dźwięku Ponieważ falę można opisać za pomocą równania matematycznego, to cyfrowy dźwięk można tworzyć także za pomocą odpowiednich algorytmów, a nie tylko odtwarzać gotowy, wcześniej zapisany w postaci cyfrowej. Na przykład wiedząc, że nucie A odpowiada dźwięk o częstotliwości 440 Hz wystarczy sprawić, aby syntezator dźwięku wygenerował falę o takiej częstotliwości i w rezultacie otrzyma się ton odpowiadający nucie A. Kiedy komputery zaczęły tworzyć dźwięki zachwycano się melodiami tworzonymi za pomocą generowanych po kolei dźwięków o różnej częstotliwości. Takie możliwości jednak szybko okazały się niewystarczające i w rezultacie powstały syntezatory wielo kanałowe, które mogły generować jednocześnie wiele różnych tonów. Uzyskany efekt był już dużo lepszy, ale ciągle charakteryzował się zbyt syntetycznym, nienaturalnym brzmieniem. Przyczyną tego problemu jest to, że tony emitowane przez tradycyjne in strumenty składają się z wielu tak zwanych częstotliwości harmonicznych, a nie poje dynczej częstotliwości emitowanej przez syntezator. W odpowiedzi skonstruowano więc syntezatory z modulacją fazy (FM), które mają moż liwość zmiany amplitudy i częstotliwości dźwięku w danym kanale i tym samym moż liwość tworzenia naturalniej brzmiących dźwięków przez przesunięcie ich fazy i wpro wadzenie wielu częstotliwości harmonicznych. Obecnie stosowane są jeszcze dwie inne metody syntezy dźwięku wykorzystujące: — metoda ta stanowi połączenie syntezy dźwięku i jego cyfrowego zapisu; tablica wzorców dźwięku zawiera próbki naturalnych dźwięków przetworzone przez procesor sygnałowy (DSP), na ich podstawie procesor DSP potrafi wygenerować dźwięk o wybranej amplitudzie i częstotliwości, a uzyskany w ten sposób efekt jest zbliżony jakością do zapisanego cyfrowo dźwięku i wymaga jedynie przechowywania próbek dźwięku;
♦
t a b lic e w z o r c ó w d ź w ię k u
♦
o b w i e d n i ę d ź w i ę k u — synteza dźwięku wykorzystująca informacje o jego obwiedni umożliwia stworzenie przez procesor sygnałowy modelu dźwięku wybranego instrumentu; model ten jest na tyle wierny, że wygenerowane na jego podstawie dźwięki są praktycznie nie do odróżnienia od dźwięków prawdziwego instrumentu.
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
425
♦ Chociaż każda z tych metod umożliwia uzyskanie niezwykłych efektów, to jednak tworząc z ich pomocą podkład muzyczny trzeba przyjąć dość duże ograniczenie związane z formatem plików MIDI {Musical Instrument Digital Interface). Pliki MIDI umożliwiają zapis kompozycji muzycznych za pomocą instrumentów i dźwięków w osobnych kanałach. Dysponując na przykład czterema kanałami można za pomocą pliku MIDI stworzyć kwartet (na przykład fortepian, flet, gitara i saksofon), w którym każdy instrument będzie grał własną sekwencję nut w osobnym kanale. Problem z plikami MIDI polega na tym, że odtwarzając je na różnych komputerach można uzyskać różne efekty. Przyczyną jest to, że pliki MIDI zawierają jedynie zapis nutowy pozostawiając kwestie tworzenia dźwięku konkretnemu sprzętowi. Zaletą takiego rozwiązania jest natomiast niewielki rozmiar plików MIDI. Zapis kilkunastu minut muzyki w pliku MIDI zajmuje jedynie kilkaset kilobajtów, podczas gdy zapis cyfrowy tego samego fragmentu liczony jest w dziesiątkach megabajtów.
Czym jest DirectX Audio? DirectX Audio jest komponentem DirectX odpowiedzialnym za tworzenie ścieżki dźwię kowej z wykorzystaniem istniejących możliwości sprzętowych. W poprzednich wer sjach DirectX odtwarzaniu dźwięków służyły komponenty DirectSound i DirectMusic. W wersji 8. DirectX połączono je w komponent DirectX Audio. DirectSound służy do cyfrowego zapisu dźwięku i jego odtwarzania, a DirectMusic stanowi zasadniczy mecha nizm tworzenia dowolnych dźwięków. W wersji 8. DirectX komponenty DirectSound i DirectMusic stanowią interfejsy DirectX Audio. Z ich pomocą można: ♦ ładować i odtwarzać dźwięki z plików w formatach MIDI, WA V lub w formacie DirectMusic Producer; ♦ odtwarzać jednocześnie dźwięk z wielu źródeł; ♦ precyzyjnie planować w czasie zdarzenia związane z tworzeniem dźwięków; ♦ używać dźwięków ładowalnych DLS (downloadable sounds), dzięki którym efekty dźwiękowe będą brzmieć tak samo na różnych komputerach; ♦ stosować przestrzenne efekty dźwiękowe; ♦ stosować zmiany rozdzielczości, pogłos, zniekształcenia i inne efekty; ♦ zapisywać dane MIDI lub przesyłać je pomiędzy portami; ♦ zapisywać pliki WA V na podstawie dźwięku uzyskanego za pośrednictwem mikrofonu lub innych źródeł; ♦ odtwarzać muzykę, której brzmienie ulega z każdym powtórzeniem nieznacznym zmianom; ♦ tworzyć dynamicznie połączenia istniejących fragmentów muzyki.
426
Część III ♦ Tworzymy grę
Powyższa lista prezentuje jedynie ułamek bogatych możliwości DirectX Audio. Zanim z nich zacznie się korzystać, trzeba najpierw zapoznać się z podstawowymi informa cjami dotyczącymi DirectX Audio.
Charakterystyka DirectX Audio Zanim zacznie się korzystać z możliwości DirectX Audio, trzeba poznać najpierw pod stawowe funkcje: ♦ obiekty ładujące; ♦ segmenty i stany segmentów; ♦ wykonanie; ♦ komunikaty; ♦ kanały wykonania; ♦ syntezator DLS; ♦ instrumenty i ładowanie ich dźwięków; ♦ ścieżki dźwiękowe i bufory. Należy przyjrzeć się bliżej każdemu z nich.
Obiekty ładujące Obiekty te służą do ładowania innych obiektów (w tym także plików segmentowych DirectMusic, kolekcji DLS, plików MIDI, plików WAV mono- i stereofonicznych). Dlatego też obiekty ładujące tworzy się zwykle jako pierwsze spośród obiektów DirectX Audio.
Segmenty i stany segmentów Przez segment rozumieć należy obiekt obudowujący dane reprezentujące pewien dźwięk lub fragment muzyki odtwarzany jako całość. Dane te mogą być zapisane w formacie MIDI, WA V lub reprezentować fragment muzyki tworzony podczas wykonania programu lub kolekcję informacji pliku segmentowego DirectMusic Producer. Segment może być odtwarzany jako segment zasadniczy lub segment wtórny. W danym momencie może być odtwarzany tylko jeden segment zasadniczy, ale w tym sam ym czasie może być odtwarzanych wiele segmentów wtórnych, które zwykle rep rezen tu ją krótkie motywy muzyczne lub efekty dźwiękowe. Segmenty mogą zawierać różne rodzaje danych, takie jak dźwięk zapisany cyfrowo, in formacje o zmianie akordów i instrumentów oraz o zmianie tempa. Mogą tak że p rze chowywać informacje o ścieżkach dźwięku (które omówione zostaną w k ró tc e), za po mocą których są odtwarzane (w tym także przy użyciu efektów specjalnych).
Rozdział 1 7 . ♦ Zastosow ania D irec tX Audio
427
Wykonanie Obiekt wykonania obsługuje przepływ danych ze źródła do syntezatora. Określa on chronometraż wykonania, przyporządkowanie kanałów do ścieżek dźwiękowych, trasowanie komunikatów, sposób zarządzania narzędziami, powiadomienia i wiele innych. Zwykle wystarcza pojedynczy obiekt wykonania.
Komunikaty Dane, których przepływ obsługuje obiekt wykonania, mają postać komunikatów. Ko munikat może zawierać informację o pojedynczym tonie, dźwięku, zmianie kontrolera lub nawet tekst piosenki wyświetlany podczas jej odtwarzania. Zwykle nie trzeba bez pośrednio obsługiwać komunikatów w programie, ponieważ są one generowane pod czas odtwarzania segmentu. Jeśli zajdzie taka potrzeba, to można wstawić komunikat do wykonania bądź przejmować komunikaty generowane automatycznie. Komunikaty można także stosować do powiadamiania (na przykład o tym, że osiągnięty został pewien punkt wykonania).
Kanały wykonania Kanał wykonania łączy partię ze ścieżką dźwięku. Partia może reprezentować kanał MIDI, partię w segmencie DirectMusic Producer lub dźwięk. Każdy odtwarzany dźwięk składa się z jednej lub więcej partii, które zwykle reprezentują instrumenty muzyczne. Kanały wykonania przypominają kanały MIDI, jednak ich liczba jest praktycznie nie ograniczona (zwykle może istnieć do 16 kanałów MIDI).
Syntezator DSL Jeśli dane opisujące dźwięk nie określają jeszcze bezpośrednio jego częstotliwości, jak jest to na przykład w przypadku zapisu nuty w pliku MIDI, to zanim zostaną przekazane do karty dźwiękowej muszą zostać przetworzone przez syntezator. Większość synteza torów wyposażona jest w implementację ładowania dźwięków DLS Level 2. W przy padku braku sprzętowego syntezatora dźwięk syntezowany jest wyłącznie na drodze programowej. Syntezator DSL generuje dźwięk na podstawie wzorca, co umożliwia osią gnięcie nawet bardzo złożonych brzmień dowolnych dźwięków.
Instrumenty i ładowanie dźwięków Aby naśladować prawdziwy instrument, syntezator musi posiadać opis jego brzmienia. Opis ten ma postać próbek dźwięku i danych dotyczących ich artykulacji. Tworzą one kolekcje dźwięków łado walnych DLS, które należy załadować do syntezatora. Zwykle brzmienia poszczególnych nut dla danego instrumentu tworzone są na podsta wie jednej próbki, dla której zmieniana jest odpowiednio częstotliwość. DLS Level 2 umożliwia już reprezentowanie każdej nuty za pomocą osobnej próbki lub kombinacji próbek. Nawet prędkość odtwarzania nut może powodować wybór różnych próbek.
428
Część III ♦ Tworzymy grę
Ścieżki dźwięku i bufory Ścieżka dźwięku wyznacza drogę jaką przebywa segment DirectMusic od wykonania do syntezatora, następnie do buforów DirectSound, gdzie tworzone są efekty dźwiękowe i do zasadniczego bufora, gdzie następuje miksowanie ostatecznego dźwięku. Można utworzyć wiele ścieżek dźwięku i odtwarzać z ich pomocą segmenty. Na przy kład dla odtwarzania plików MIDI z efektem pogłosu stworzy się jedną ścieżką, a inną ścieżkę dla odtwarzania plików WA V z przestrzennym efektem dźwięku. Ścieżkę dźwięku można sobie wyobrażać jako łańcuch obiektów, które przetwarzają dane dźwięku.
Przepływ danych dźwięku DirectX Audio używa się najczęściej w celu załadowania muzyki lub efektów dźwię kowych z plików MIDI, WA V lub plików segmentów DirectMusic Producer. Po zała dowaniu dane te zostają obudowane w obiekty segmentów, z których każdy reprezentuje dane z pojedynczego źródła. Obiekt wykonania może następnie odtwarzać jeden z tych segmentów jako segment zasadniczy oraz dowolną liczbę segmentów wtórnych. Segment zawiera jeden lub więcej śladów. Każdy ślad zawiera synchronizowane dane opisujące nuty i zmiany tempa. Podczas odtwarzania śladu przez obiekt wykonania po wstają komunikaty oznaczone etykietą czasu. Obiekt wykonania rozsyła komunikaty do narzędzi pogrupowanych w następujące grafy: ♦ graf narzędzi segmentów — akceptuje komunikaty pochodzące z wybranych segmentów; ♦ graf narzędzi ścieżki dźwięku — akceptuje komunikaty pochodzące ze wszystkich segmentów ścieżki dźwięku; ♦ graf narzędzi wykonania — akceptuje komunikaty pochodzące ze wszystkich segmentów. Narzędzia mogą modyfikować, przekazywać, usuwać bądź wysyłać nowe komunikaty. Na końcu komunikaty trafiają do narzędzia wyjścia, które przetwarza dane na format MIDI i przekazuje je do syntezatora. Syntezator tworzy dźwięki i przekazuje je do uj ścia, które zarządza ich dystrybucją poprzez magistrale do jednego z trzech typów bufo rów DirectSound: ♦ buforów ujścia — są to bufory wtórne, które przekształcają dane na format bufora zasadniczego i umożliwiają zmianę natężenia dźwięku, jego położenia w przestrzeni i innych właściwości; bufory te mogą przekazywać swoje dane do modułów efektów, takich jak na przykład pogłos, zniekształcenie czy echo; uzyskany rezultat przekazywany jest albo wprost do bufora zasadniczego albo do jednego lub więcej buforów miksowania; ♦ buforów miksowania — bufory te otrzymują dane z innych buforów, stosują odpowiednie efekty i miksują uzyskany rezultat; globalne efekty wykonywane są najczęściej właśnie za pomocą buforów miksowania; ♦ bufora zasadniczego — wykonuje on ostateczne miksowanie wszystkich danych i wysyła rezultat do kanałów wyjścia.
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
429
Rysunek 17.4 ilustruje przepływ danych od plików do głośników. Rysunek 17.4.
lZ j
MIDI
Przepływ danych dźwięku w DirectX Audio
S® " »
__
Dźwięk, w postaci cyfrowej
r~r\
— \ Segment szablonu
Styl
Mapa akordów
L 1J
1
Segment
Ślad
Ślad
Kompozytor
ś la d . . .
Narzędzia mogą usuwać komunikaty
Narzędzie wyjsci.
tf
■
Komunikaty MIDI
SyntezatorDSŁ
| Bufory wtórne (dowolna liczba, mogą tworzyć łańcuch)
WW B p r] Bufor zasadniczy : Kanały wyjścia : (jeden lub więcej)
Ładowanie i odtwarzanie dźwięku w DirectMusic DirectMusic jest komponentem DirectX bazującym na modelu COM. W praktyce oznacza to, że DirectMusic nie posiada funkcji pomocniczych tworzących obiekty COM i wobec tego trzeba tworzyć je indywidualnie korzystając z wywołań funkcji biblioteki COM. Ko rzystanie z DirectMusic nie wymaga dołączenia do programu wykonywalnego dodatkowej biblioteki, a jedynie włączenia plików nagłówkowych dmusicc.h i dmusici.h do programu źródłowego. Proces odtworzenia dźwięku za pomocą DirectMusic składa się z sześciu podstawowych etapów.
1 . Inicjowanie COM. Ponieważ nie istnieją funkcje pomocnicze tworzące obiekty DirectMusic, trzeba samodzielnie zainicjować COM za pomocą funkcji C o ln it ia liz e O .
2 . Tworzenie i inicjowanie obiektu wykonania. Za pomocą funkcji C o C re a te In s ta n c e ( ) tworzy się pojedynczy obiekt wykonania i uzyskuje interfejs klasy ID i rectM usicPerform ance8. Następnie wywołuje się metodę ID ire ctM usicP e rfo rm an ce8 : :In itA u d io ( ) wcelu określenia domyślnej
ścieżki dźwięku.
430
Część III ♦ Tworzymy grę
3. Tworzenie obiektu ładującego. Ponownie trzeba skorzystać z funkcji C o C reate Instance( ), tym razem w celu uzyskania interfejsu ID i rectM usicLoader8. 4. Ładowanie segmentu. Wywołuje się metodę ID i re c tM u s ic L o a d e r8 : : SetSearchDi r e c t o r y ( ) w celu określenia położenia plików danych. Za pomocą metody ID irectM usciLoader8: :G e tO b je ct() ładuje się segment z pliku iuzyskuje jego interfejs ID i rectMusicSegment8. 5. Ładuje się brzmienia instrumentów. Za pomocą metody ID irectM usicS egm ent8:D ow nload() ładuje się dane DLS. 6. Odtwarzanie segmentu. Wskaźnik segmentu przekazuje się metodzie ID i rectM usi cPerform ance8: : PIaySegmentEx().
Teraz można już przejść do szczegółowego omówienia każdego z tych etapów.
Inicjacja COM Jest to z pewnością najprostszy etap w procesie odtwarzania dźwięków. Funkcję C olni t i a l iz e O należy wywołać na początku programu (przed wywołaniem innych funkcji COM): / / in ic ja c ja COM C o In itia liz e (N U L L ) ;
Tworzenie i inicjacja obiektu wykonania Interfejs obiektu jest zasadniczym interfejsem komponentu DirectX Audio. Aby go utwo rzyć, należy wywołać funkcję C o C re a te In sta n ce ( ) przekazując jej identyfikator inter fejsu wykonania, identyfikator klasy obiektu oraz zmienną, za pomocą której zostanie zwrócony wskaźnik interfejsu. Najpierw trzeba jednak zdefiniować zmienną typu IDi r e c t Musi cPerformance8: ID irectM usicP e rfo rm a n ce 8 * dmusicPerformance = NULL;
/ / o b ie k t wykonania
Następnie tworzy się obiekt wykonania: / / tworzymy wykonanie C oC reateInstance(C LS ID _ID irectM usicP erform ance, NULL. CLSCTX_INPROC, IID _ ID ire ctM u sicP e rfo rm a n ce 8 , (vo id **)& d m u sicP e rfo rm a n ce );
Teraz można wywoływać już metody interfejsu wykonania. Jako pierwszą należy wy wołać metodę IDi re ctM u sicP e rfo rm a n ce 8 : : In itA u d io O , która inicjuje wykonanie oraz umożliwia (opcjonalnie) określenie domyślnej ścieżki dźwięku. Metoda ta zdefiniowana jest następująco: HRESULT In itA u d io ( ID ire c tM u s ic ** ppDirectMusic. ID ire c tS o u n d ** ppDirectSound, HWND httnd, DWORD dwDefaultPathType, DWORD dwPChannelCount, DWORD dwFlags, DMUS_AUDIO PARAMS pParams);
I I wskaźnik in t e r f e js u o b ie k tu D ire c tM u sic / / w skaźnik in t e r f e js u o b ie k tu D irectSound / / uchwyt okna / / ty p dom yślnej ś c ie ż k i dźwięku I I lic z b a kanałów wykonania d la ś c ie ż k i dźwięku I I z n a cz n ik i w ła ściw o ści syn te za to ra / / param etry s yn te za to ra
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
431
A oto przykład wywołania metody Ini tAudi o (): dm usicPerform ance->InitAudio( NULL. NULL. NULL. DMUS_APATH_SHARED_STEREOPLUSREVERB,
64. DMUS_AUDIOF_ALL, NULL);
// // // // // // //
in t e r fe js ID ire ctM u sic (zbędny) in t e r fe js ID irectSound (zbędny) uchwyt okna domyślna ścieżka lic z b a kanałów wykonania właściw ość syntezatora domyślne parametry syntezatora
Powyższe wywołanie metody InitAudioO skonfiguruje interfejsy DirectMusic i DirectSound tak, by korzystały z 64 kanałów wykonania przydzielonych domyślnej ścieżce dźwięku oraz wszystkich możliwości syntezatora.
Tworzenie obiektu ładującego Interfejs IDirectMusicLoader8 można uzyskać za pomocą funkcji CoCreateInstance(), ale najpierw trzeba zdefiniować odpowiednią zmienną: ID ire ctM u sicL o a d e r8 * dmusicLoader = NULL;
// obiekt ładujący
// tworzy obiekt ładujący CoCreateInstance(CLSID_IDirectM usicPerform ance, NULL. CLSCTX_INPROC, IID _ ID ire ctM u sicL o a d e r8 , (void**)& dm usicLoader);
Załadowanie segmentu Zanim załaduje się segment, trzeba poinformować najpierw obiekt ładujący, gdzie może odnaleźć pliki zawierające dane dźwięku. Najlepiej zdefiniować w tym celu domyślny katalog takich plików, choć istnieje także możliwość określania pełnej ścieżki dostępu za każdym razem, gdy ładowany będzie plik. Domyślny katalog plików dźwiękowych konfiguruje się za pomocą metody IDi rectMusi cLoader8: :SetSearchDirectory() zdefi niowanej w następujący sposób: HRESULT Se tSearch D irectory( REFGUID r g u i d C l a s s , I I referencja id e n ty fik a to ra k la sy obiektu, // którego dotyczy to wywołanie WCHAR* pw szPa th, I I ścieżka dostępu do katalogu BOOL f C l e a r ); // j e ś l i wartość TRUE, to usuwa w sze lkie informacje // o obiektach przed ustaleniem śc ie ż k i dostępu
A oto przykład użycia tej metody: WCHAR searchPath[MAX_PATH];
// ścieżka dostępu do plików dźwiękowych
// konwersja CHAR na WCHAR Mu 11 i By teT oWi deCha r (CP_ACP. 0, "C iW m u sic", -1, searchPath, MAX_PATH); // k o n figu ru je ście żkę dostępu dm usicLoader->SetSearchD irectory(G U ID _DirectM usicAU Types, searchPath. FALSE);
432
Część III ♦ Tworzymy grę
Teraz można już załadować segment z pliku korzystając ż jednej z metod: ID ire ctM u si cLoader8: : LoadObjectFromFi le ( ) lub ID irectM usicLoader8: :G e tO b je c t(). Zwykle sto suje się metodę LoadObjectFromFi l e ( ), która zdefiniowana jest jak poniżej: HRESULT LoadO bjectFrom File( REFGUID rguidClassID, I I REFIID iidlnterfacelD, / / WCHAR *pwzFi lePath , // v o id **ppObject ) ; //
u n ik a ln y id e n ty fik a to r k la s y o b ie k tu u n ik a ln y id e n t y f ik a t o r in t e r f e js u nazwa p lik u zaw ierającego o b ie k t w skaźnik wymaganego in t e r f e js u o b ie k tu
Przy korzystaniu z tej metody należy przekazać jej nazwę pliku zawierającego ładowany segment oraz obiekt segmentu. Obiekt segmentu definiuje się w poniższy sposób: ID irectM usicS egm ent8* dmusicSegment = NULL; / / o b ie k t segmentu
A oto fragment kodu ładujący segment z pliku: / / nazwa p lik u zaw ierającego segment WCHAR filename[MAX_PATH] = L "te s ts o u n d .w a v"; / / ła d u je segment z p lik u i f ( FAILED( dmusicLoader->LoadObjectFrom Fi 1e ( CLSID_DirectMusicSegment. IID _ ID i rectM usi cSegment8, file n a m e . ( void**)& dm usicSegm ent) ) )
{ MessageBox(hwnd, "N ie zn a le zio n o p lik u a u d io !", " B łą d !" , MB_0K); re tu rn 0;
}
Ładowanie instrumentów Zanim przystąpi się do odtworzenia załadowanego segmentu, trzeba najpierw załado wać jeszcze dane odpowiednich instrumentów do syntezatora. Wywołuje się w tym celu metodę ID i rectM usicS egm ent8: : Download () o poniższym prototypie: HRESULT Download( Iunknown *pAudioPath );
I I wskaźnik in t e r f e js u ¡unknown o b ie k tu wykonania
/ / lu b ś c ie ż k i dźwięku, do k tó r e j t r a f i ą ładowane
dane
Aby załadować segment do wykonania, należy posłużyć się poniższym wywołaniem metody Downl o a d ( ): dmusicSegment->Download(dmusicPerformance); / / ła d u je segment do wykonania
Odtwarzanie segmentu Zawartość pliku dźwiękowego odtwarza się poprzez przekazanie segmentu metodzie ID i re ctM u sicP e rfo rm a n ce 8 ; : PlaySegmentEx( ) o następującym prototypie: HRESULT PIaySegmentEx( IUnknown* pSource ; // WCHAR pwzSegmentName. / / IUnknown* pTransition, I I DWORD dwFlags, //
wskaźnik in t e r f e js u IUnknown odtwarzanego o b ie k tu zawsze w artość NULL (n ie używany w D ire c tX 8) szablonu segmentu używany podczas p rz e jś c ia do segmentu zn a cz n ik i o k re ś la ją c e zachowanie metody
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
433
_ in t 6 4 i64StartTime. I I czas rozpoczęcia odtw arzania ID ire c tM u s ic S e g m e n tS ta te ** ppSegmentState. I I stan segmentu IUnknown* pFrom, I I o b ie k t, któ re g o odtw arzanie należy zatrzym ać, zanim IUnknown* pAudioPath I I ścieżka dźwięku używana do odtw arzania (dom yślnie NULL)
); Metoda ta pozwala określić precyzyjnie sposób odtwarzania segmentu. Aby odtworzyć go natychmiast korzystając z domyślnej ścieżki dźwięku, wystarczy nadać wszystkim parametrom metody, oprócz pierwszego, wartość NULL lub 0: dm usicPerformance->PlaySegmentEx( / / odtw arzany segment dmSeg, NULL, / / param etr n ie je s t wykorzystywany / / p r z e jś c ie pomiędzy segmentami NULL, / / z n a cz n ik i 0. / / czas rozpoczęcia (w a rto ść 0 oznacza n a tych m ia st) 0. NULL. / / stan segmentu / / zatrzymywany o b ie k t NULL, NULL); / / domyślna ście żka dźwięku
Po wywołaniu tej metody program będzie kontynuował swoje wykonanie, a załadowany plik dźwiękowy będzie odtwarzany aż do momentu jego zatrzymania.
Zatrzymanie odtwarzania segmentu Odtwarzanie segmentu można zatrzymać za pomocą metody IDirectMusicPerformance 8: : Stopi) o następującym prototypie: HRESULT S to p i ID irectM usicS egm ent* pSegment, I I zatrzymywany segment ID irectM u sicS e g m e n tS ta te * pSegmentState, / / stan zatrzymywanego segmentu MUSIC_TIME mtTime, / / czas zatrzym ania segmentu DWORD dwFlags I I kie d y powinno n a s tą p ić zatrzym anie segmentu
); Z metody tej można skorzystać na przykład w taki sposób: dm usicPerform ance->Stop( dmusicSegment, / / zatrzymywany segment (w a rto ść NULL oznacza w s z y stkie segmenty) NULL, / / stan zatrzymywanego segmentu 0, / / czas zatrzym ania segmentu (0 oznacza n a tych m ia st) 0 ); / / k ie d y zatrzym ać segment (0 oznacza n a tych m ia st)
Odtwarzanie segmentu można zatrzymać także za pomocą metody IDi rectMusicPerformance8 : :StopEx() zdefiniowanej jak poniżej: HRESULT StopEx( IUnknown *pO bjectToS top in t6 4 i64StopTim e, DWORD dwFlags
/ / in t e r f e js zatrzymywanego o b ie k tu / / czas zatrzym ania / / k ie d y zatrzym ać odtw arzanie
): Metoda ta umożliwia zatrzymanie odtwarzania segmentu lub ścieżki dźwięku. Będzie ona używana w następujący sposób:
434
Część III ♦ Tworzymy grę
dm usicPerform ance->StopEx( dmusicSegment. / / zatrzymywany in t e r f e js 0, / / czas zatrzym ania 0 ); / / kie d y zatrzym ać o dtw arzanie
Kontrola odtwarzania segmentu Za pomocą metody IDi rectMusicPerformance:: IsPlaying() można sprawdzić, czy od twarzany jest segment bądź stan segmentu. Metoda ta zdefiniowana jest następująco: HRESULT Is P la y in g ( ID irectM usicS egm ent* pSegment, ID i rectM usicS egm entS tate* p S e tS ta te
): Aby sprawdzić, czy dany segment jest odtwarzany, należy przekazać jego wskaźnik metodzie IsPlaying() jako wartość parametru pSegment, a parametrowi pSetState nadać wartość NULL: i f (dm usicP erform ance->IsP laying(dm usicS egm ent, NULL) == S_0K)
{ / / segment je s t nadal odtwarzany
} e ls e
{ / / segment n ie je s t ju ż odtwarzany
}
Określanie liczby odtworzeń segmentu Liczbę odtworzeń segmentu można określić za pomocą metody IDi rectMusicSegment8:: SetRepeatsO zdefiniowanej następująco: HRESULT SetRepeats( DWORD dwRepeats I I lic z b a odtworzeń segmentu
); Metoda określa liczbę odtworzeń segmentu. Domyślnie dotyczy to całego segmentu, ale za pomocą metody IDi rectMusicSegment8: :SetLoopPoints() można zdefiniować odtwa rzany fragment segmentu. Jeśli parametr dwRepeats otrzyma wartość DMUS_SEG_REPEAT_ INFINITE, to segment będzie odtwarzany w sposób ciągły, aż do momentu, gdy jawnie zatrzymane zostanie jego odtwarzanie. Jeśli natomiast parametr dwRepeats otrzyma war tość 0, to zostanie odtworzony tylko raz. Metoda IDi rectMusicSegment8: :SetLoopPoints() pozwala określić początek i koniec odtwarzanego fragmentu segmentu i zdefiniowana jest w następujący sposób: HRESULT SetLoopPointsC MUSIC_TIME m tS ta rt, I I początek odtwarzanego fragm entu MUSIC_TIME mtEnd I I koniec odtwarzanego fragm entu
); Po jej wywołaniu segment zostanie odtworzony za pierwszym razem od początku aż do punktu mtEnd, a następne odtworzenia będą rozpoczynać się od punktu mtStart i kończyć
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
435
w punkcie mtEnd. Takich odtworzeń będzie tyle, ile zostało określone za pomocą meto dy IDirectMusicSegment8 ::SetRepeats().
Kończenie odtwarzania dźwięku Gdy zakończone zostanie już odtwarzanie dźwięku i będzie można zakończyć wykony wanie aplikacji, należy zwolnić obiekty COM. Oczywiście najpierw trzeba zakończyć odtwarzanie segmentów za pomocą metody IDi rectMusicPerformance8: :Stop(). Na stępnie można zakończyć pracę obiektu wykonania posługując się metodą IDirectMusicPerformance8: :CloseDown() zdefiniowaną jak poniżej: HRESULT CloseDownO;
Wywołuje się ją w następujący sposób: dm usicPerform ance->C loseD ow n();
Teraz można już zwolnić wszystkie wykorzystywane interfejsy, w tym interfejsy obiektu wykonania, obiektu ładującego i segmentu: dm usicLoader->R elease(); dm usicP erfo rm a n ce -> R e le a se (); dm usicSegm ent->R elease();
Korzystanie z modelu COM kończy wywołanie funkcji CoUnitial iz e (): C o U n itia liz e ( );
Prosty przykład Poniższy przykład ilustruje zastosowanie omówionych dotąd sposobów ładowania pli ków dźwiękowych i ich odtwarzania. Program ten ładuje plik MIDI i odtwarza go pod czas rysowania prostej animacji za pomocą OpenGL. Zrozumienie jego działania nie powinno sprawić kłopotu — tekst źródłowy programu został opatrzony odpowiednimi komentarzami. # d e fin e WIN32_LEAN_AND_MEAN # d e fin e INITGUID / / / / / / P lik i nagłówkowe # in c lu d e # in c lu d e # in c lu d e < d m u sici.h > # in c lu d e < s td io .h > # in c lu d e < s td lib .h > # in c lu d e # in c lu d e < g l/g l.h > # in c lu d e < g l/g lu .h > / / / / / / Zmienne g lo b a ln e HDC g_HDC; bool f u l l Screen = fa ls e ;
/ / "odchudza" a p lik a c ję Windows / / używa id e n ty fik a to ró w GUID w D ire ctM u sic
/ / standardowy p lik nagłówkowy Windows / / p l i k i nagłówkowe D ire c tM u sic
/ / standardowy p lik nagłówkowy OpenGL / / p lik nagłówkowy b ib lio t e k i GLU
/ / g lo b a ln y k o n te k st urządzenia / / tru e = tr y b pełnoekranowy; / / fa ls e = tr y b okienkowy
436
Część III ♦ Tworzymy grę
bool k e yP re sse d [2 5 6 ]; f lo a t angle = O.Of; unsigned in t lis tB a s e ; GLYPHMETRICSFLOAT g m f[2 5 6 ];
/ / ta b lic a p rz y c iś n ię ć k la w is z y
/ / początkowa li s t a w y ś w ie tla n ia / / dane o o r ie n ta c ji i p o ło ż e n iu znaków / / używane przez l i s t y w y ś w ie tla n ia
/ / / / / / Zmienne D ire c tM u sic ID irectM u sicL o a d e r8 *dmusicl_oader = NULL; ID i rectM usicP erform anće8 *dm usicPerform ance ID irectM usicSegm ent8 *dmusicSegment = NULL;
NULL;
/ / o b ie k t ła d u ją c y / / o b ie k t wykonania / / segment
/******************************************************* * In t e r fe js y D ire c tM u sic
*******************************************************i I I In itD ir e c tX A u d io ( )
/ / o p is : in ic ju je komponent D ire c tX Audio bool InitD irectXAudio(H W N D hwnd)
{ char pathStr[MAX_PATH]; / / ścieżka dostępu p lik ó w dźwiękowych WCHAR wcharStr[MAX_PATH]; / / tw o rzy o b ie k t ła d u ją c y i f (FAILED (C oC reateInstance(C LS ID _D irectM usicLoader, NULL, CLSCTXJNPROC, IID _ ID ire ctM u s icL o a d e r8 , (v o id **)& d m u s ic L o a d e r)))
{ MessageBox(hwnd, "N ie można utw orzyć o b ie k tu ID ire c tM u s ic L o a d e r8 !". " B łą d !" , MB_0K); re tu rn fa ls e ;
} / / tw orzy o b ie k t wykonania i f (FAILED (C oC reateInstance(C LS ID _D irectM usicP erform ance, NULL. CLSCTX_INPROC, IID _ ID i rectM usicP erform ance8, (vo id **)& d m u sicP e rfo rm a n ce )))
{ MessageBox(hwnd, "N ie można utw orzyć o b ie k tu ID ire c tM u s ic P e rfo rm a n c e 8 !", " B łą d !" , MB_0K); re tu rn fa ls e ;
} / / in ic ju je o b ie k t wykonania za pomocą dom yślnej ś c ie ż k i dźwięku dm usicP erform ance->InitA udio(N U LL, NULL, hwnd, DMUS_APATH_SHARED_STEREOPLUSREVERB, 64, DMUS_AUDIOF_ALL, NULL); / / pob ie ra b ie żą cy k a ta lo g G etC urrentD i r e c to ry(MAX_PATH, p a th S tr ) ; / / zam ienia je g o nazwę na Unicode MultiByteToWideChar(CP_ACP, 0, p a th S tr, -1 , w ch a rS tr, MAX_PATH); / / o k re ś la ście żkę dostępu do p lik ó w dźwiękowych dm usicLoader->S etS earchD irectory(G U ID _D irectM usicA H Types, w ch a rS tr, FALSE); re tu rn tr u e ;
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
/ / LoadSegmentO / / o p is : ła d u je segment z p lik u bool LoadSegment(HWND hwnd, char ^ file n a m e )
{
WCHAR wcharStr[MAX_PATH]; / / zam ienia nazwę p lik u na Unicode MultiByteToWideChar(CP_ACP, 0, file n a m e , -1 , w ch a rS tr, MAX_PATH); / / ła d u je segment z p lik u i f ( FAILED( dmusicLoader->LoadObjectFrom Fi 1e ( CLSID_DirectMusicSegment. IID _ID ire ctM u sicS e g m e n t8, w ch a rS tr, (vo id **)& d m u sicS e g m e n t)))
{ MessageBox(hwnd, "N ie z n a le zio n o p lik u a u d io !", " B łą d !" , MB_0K); re tu rn fa ls e ;
} / / ła d u je in s tru m e n ty do syn te za to ra dm usicSegm ent->Download(dm usicPerform ance); re tu rn tr u e ;
} / / PlaySegmentO / / o p is : rozpoczyna o d tw a rza n ie segmentu vo id P layS egm ent(ID irectM usicP erform ance8* dmPerf, ID irectM usicS egm ent8* dmSeg)
{
/ / odtw arza segment od następnego ta k tu dmPerf->PlaySegmentEx(dmSeg, NULL, NULL, 0, 0. NULL, NULL, NULL);
} / / StopSegmentO / / o p is : z a trzym u je o d tw a rza n ie segmentu v o id S topS egm ent(ID irectM usicP erform ance8* dmPerf. ID irectM usicS egm ent8* dmSeg)
{
/ / z a trzy m u je od tw a rza n ie segmentu dmSeg dmPerf->StopEx(dmSeg, 0, 0 );
} / / CloseDownO / / o p is : kończy wykonanie v o id C loseD ow n(ID irectM usicP erform ance8* dmPerf)
{ / / z a trzy m u je od tw a rza n ie dmPerf->Stop(NULL, NULL, 0, 0 ); / / kończy pracę D ire c tM u sic dmPerf->CloseDown( ) ;
} ^ 'k -k -k 'k 'k i^ 'k 'k -k -k -k 'k -k -k 'k 'k -k 'k 'k 'k 'k 'k 'k ir-k 'k 'k 'k 'k -k -k 'k 'k 'k 'k -k 'k 'k -k -k -k 'k -k -k -k -k 'k -k -k -k -k -k -k -k
^ In t e r f e js y OpenGL
437
438
Część III ♦ Tworzymy grę
/ / C re a te O u tlin e F o n tO / / o p is : tw o rzy czcio n kę obrysową za pomocą fu n k c ji C reateF ontO unsigned i n t C re a te O u tlin e F o n t(c h a r *fontName, i n t fo n tS iz e , f l o a t depth)
{ HFONT hFont; unsigned i n t base;
/ / uchwyt c z c io n k i systemu Windows
base = g lG e n L is ts (2 5 6 );
/ / l i s t y w yś w ie tla n ia d la 96 znaków
i f (stricm p (fo n tN a m e , "sym bol") == 0)
{ hFont = C re a te F o n t(fo n tS iz e , 0, 0. 0, FW_B0LD, FALSE, FALSE, FALSE, SYMBOL_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALI ASED_QUALITY, FF_DONTCARE | DEFAULT_PITCH, fontN am e);
} el se
{ hFont = C re a te F o n t(fo n tS iz e . 0, 0, 0, FW_BOLD. FALSE, FALSE. FALSE. ANSI_CHARSET, OUTJT_PRECIS. CLIP_DEFAULT_PRECIS. ANTIAL I ASED_QUALITY, FF_DONTCARE | DEFAULT_PITCH, fontN am e);
} i f ( ! hFont) re tu rn 0; SelectO bject(g_HD C , h F o n t); w glllseFontO utlines(g_H D C , 0, 255, base, 0 .0 f, dep th , WGL_F0NT_P0LYG0NS, gm f); re tu rn base;
} / / C le a rF o n tO / / o p is : usuwa l i s t y w y ś w ie tla n ia c z c io n k i vo id C learFontC unsigned in t base)
{ g lD e le te L is ts C b a s e , 256);
} / / P r in t S t r in g ( ) / / o p is : w yś w ie tla te k s t wskazywany przez s t r / / za pomocą c z c io n k i o początkow ej l iś c ie w yś w ie tla n ia base v o id P rin tS trin g (u n s ig n e d in t base, char * s t r )
{ f lo a t le n g th = 0; i f ( ( s t r == NULL)) re tu rn ; I I c e n tru je te k s t fo r (unsigned in t lo o p = 0 ;lo o p < ( s tr le n ( s tr ) ) ;lo o p + + ) // długość te k s tu w znakach
{ le n g th + = g m f[s tr [lo o p ]].g m fC e llIncX;
/ / zwiększa długość te k s tu w p ik s e la c h / / o szerokość znaku
} g lT r a n s la te fC - le n g th /2 .0 .O f, 0 . O f);
/ / c e n tru je te k s t
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
439
/ / ry s u je te k s t g lP u s h A ttri b(G L_LIST_BIT); g lL is tB a s e (b a s e ); g lC a llL is t s ( s t r le n ( s t r ) , GL_UNSIGNED_BYTE, s t r ) ; g lP o p A t tr ib O ;
} / / CleanUpO / / o p is : zw a ln ia zasoby a p lik a c ji v o id CleanUpO
{
C le a rF o n tO is tB a s e ) ; dmusi cLoa d er-> R e le a se (); dm usicP erform ance->R elease(); dm usicSegm ent->R elease():
} / / In itia liz e / / o p is : in ic j u je OpenGL v o id I n i t i a l i z e O g lC le a r C o lo r ( 0 .0 f, O.Of, O .Of, O .O f); / / t ł o w k o lo rz e czarnym glShadeModel(GL_SM00TH); gl E nable(GL_DEPTH_TEST); glE nable(G L L IG H T O ); gl Enable(GL_LIGHTING): gl Enable(GL_COLOR_MATERIAL);
// // // // //
cie n io w a n ie g ła d k ie usuwanie p rz e s ło n ię ty c h pow ierzchni włącza ź ró d ło ś w ia tła lig h tO włącza o ś w ie tle n ie k o lo r ja k o m a te ria ł
1is tB a s e = C re a te O u tlin e F o n t("A ria l", 10, 0 .2 5 f) ;
/ / ła d u je czcionkę A ria l 10 p k t.
/ / Render / / o p is : ry s u je scenę v o id RenderO
{
/ / o p ró żn ia b u fo ry ekranu i g łę b i gl Cl ear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n t ity O ; / / odsuwa o 10 je d n o s te k i obraca wokół w sz y stkic h o si g lT r a n s la te f ( 0 . 0 f, O.Of, - 1 5 .O f); g !R o ta te f( a n g le * 0 .9 f, l. O f , O.Of, O .O f); g lR o ta te f( a n g le * 1 .5 f. O.Of, l. O f , O .O f); g lR o ta te f(a n g le , O.Of, O.Of, l. O f ) ; / / k o lo r niebieskaw y g lC o lo r 3 f( 0 .3 f, 0 .4 f, 0 .8 f ) ; / / w y ś w ie tla te k s t P rin tS trin g O is tB a s e . "D ire c tX A u d io !" ); / / k o lo r ż ó łto - z ie lo n y g lC o lo r 3 f( 0 .6 f, 0 .8 f, 0 .5 f ) ; / / w y ś w ie tla te k s t g lP u s h M a trix (); g lT r a n s la t e f ( - 3 . 0 f , - l. O f , O .O f);
440
Część III ♦ Tworzymy grę
P rin tS trin g O is tB a s e , "O - O d tw a rza n ie "); g lP o p M a trix (); g lP u s h M a trix (); g lT r a n s la t e f ( - 3 . 0 f , - 2 . O f, O .O f); P rin tS trin g C Iis tB a s e , "Z - Z a trz y m a n ie "); glPopM atrix();
gl PushMatri x ( ); g lT r a n s la t e f ( - 3 . 0 f , - 3 .0 f , O .O f); P rin tS trin g O is tB a s e , "ESC - K o n ie c "); g lP o p M a trix (); angle += 0 .4 f; SwapBuffers(g_HDC);
/ / p rze łą cza b u fo ry
/ / fu n k c ja o k re ś la ją c a fo rm a t p ik s e li v o id S e tu p P ix e lFormat(HDC hDC)
{ in t nP ixe lF orm a t;
I I indeks form atu p ik s e li
s t a t ic PIXELFORMATDESCRIPTOR pfd sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 32, O , O , O , O , 0,0. 0. 0, 0, 0, 0, 0, 0. 16, 0. 0, PFD_MAIN_PLANE, 0. 0, 0, 0 } ;
= // // // // // // // II // // // // // // II // // //
{ rozm iar s tr u k tu r y domyślna w e rsja g r a fik a w o kn ie g r a fik a OpenGL podwójne buforow anie tr y b kolorów RGBA 3 2 -b ito w y o p is kolorów n ie s p e c y fik u je b itó w kolorów bez b u fo ru a lfa n ie s p e c y fik u je b itu p rz e s u n ię c ia bez b u fo ra a kum ulacji ig n o ru je b it y a ku m u la cji 1 6 - b it b u fo r z bez bu fo ra p o w ie la n ia bez buforów pomocniczych główna płaszczyzna rysowania zarezerwowane ig n o ru je maski warstw
/ / w ybiera n a jb a rd z ie j zgodny fo rm a t p ik s e li nP ixelF orm at = ChoosePixelFormat(hDC, & p fd ); / / o k re ś la form at p ik s e li d la danego k o n te kstu urządzenia S etPixelForm at(hD C , nP ixe lF orm a t, & p fd );
} / / procedura okienkowa LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM 1Param)
{ s t a t ic HGLRC hRC; s t a t ic HDC hDC; in t w id th , h e ig h t;
/ / k o n te k st tw o rze n ia g r a f ik i / / k o n te k st urządzenia / / szerokość i wysokość okna
sw itch(m essage)
{ case WM_CREATE:
/ / okno je s t tworzone
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
hDC = GetDC(hwnd); g_HDC = hDC; S etupP ixelForm at(hD C );
/ / po b ie ra k o n te k st urządzenia d la okna / / w yw ołuje fu n k c ję o k re ś la ją c ą form at p ik s e li
/ / tw o rzy k o n te k s t tw o rze n ia g r a f ik i i czyn i go bieżącym hRC = w glC re a te C o n te xt(h D C ); wglMakeCurrent(hDC, hRC); re tu rn 0; brea k; case WM_CL0SE:
/ / okno je s t zamykane
/ / deaktyw uje bie żą cy k o n te k s t tw o rze n ia g r a f ik i i usuwa go wglMakeCurrent(hDC, NULL); w g lD e le te C o n te x t( hRC); / / w stawia kom unikat WM_QUIT do k o le jk i P ostQ u itM e ssa g e (0 ); re tu rn 0; b reak; case WM_SIZE: h e ig h t = HIWORDOParam); w id th = LOWORD(lParam); i f (h e ig h t= = 0 )
/ / p o biera nowe rozm iary okna
/ / unika d z ie le n ia przez 0
{ h e ig h t= l;
} g lV ie w p o rt(0 , 0. w id th , h e ig h t); / / nadaje nowe wymiary oknu OpenGL glMatrixMode(GL_PROJECTION); / / w ybiera m acierz rzutow ania g lL o a d ld e n tity O ; / / re s e tu je m acierz rzutow ania / / wyznacza p ro p o rc je obrazu g lu P e rs p e c tiv e (5 4 .0 f. ( G L flo a t) w id th /( G L flo a t) h e ig h t.l.O f,1000.O f) ; glMatrixMode(GL_MODELVIEW); g lL o a d ld e n tity O ;
/ / w ybiera m acierz modelowania / / re s e tu je m acierz modelowania
re tu rn 0; b reak; case WM_KEYDOWN: keyPressed[wParam] = tr u e ; re tu rn 0; break; case WM_KEYUP: keyPressed[wParam] = fa ls e ; re tu rn 0; break; d e fa u lt: break;
/ / użytkow nik n a c isn ą ł kla w isz?
441
442
Część III ♦ Tworzymy grę
re tu rn (DefWindowProc(hwnd, message, wParam, 1Param ));
/ / pun kt, w którym rozpoczyna s ię wykonywanie a p lik a c ji in t WINAPI WinMain(HINSTANCE h ln s ta n c e , HINSTANCE h P re vIn sta n ce , LPSTR IpCmdLine, in t nShowCmd) WNDCLASSEX windowClass; HWND hwnd; MSG msg; bool done; DWORD dwExStyle; DWORD dw S tyle; RECT wi ndowRect;
// // // // // //
k la sa okna uchwyt okna kom unikat znacznik zakończenia a p lik a c ji rozszerzony s ty l okna s ty l okna
/ / zmienne pomocnicze in t w id th = 800; i n t h e ig h t = 600; i n t b it s = 16; / / f u l l Screen = TRUE; windowR ect.1e f t = ( 1on g )0; windowRect. r ig h t= ( lo n g )w id th ; wi ndowRect. to p = (1 ong)0; wi ndowRect. bottom =(1ong) hei g h t;
/ / s tru k tu ra o k re ś la ją c a ro zm ia ry okna
/ / d e fin ic ja k la s y okna w indow C lass.cbSize= sizeof(WNDCLASSEX); w in d o w C la s s.s ty le = CS_HREDRAW | CS_VREDRAW; wi ndowClass.1pfnWndProc= WndProc; w indow C lass.cbC lsE xtra= 0; windowClass.cbW ndExtra= 0; w indow C lass.hlnstance= h ln s ta n c e ; w indow C lass.hIcon= LoadlconCNULL, IDI_APPLICATION); w indowC lass.hC ursor= LoadCursor(NULL, IDC_ARR0W); windowClass.hbrBackground= NULL; windowClass.lpszMenuName= NULL; windowClass.lpszClassName= "M o ja K la sa "; windowClass.h!conSm= LoadIcon(NULL, ID I WINLOGO);
// // // //
domyślna ikona domyślny k u rs o r bez t ł a bez menu
/ / logo Windows
/ / r e je s t r u je k la sę okna i f ( ! R egisterC lassE x(& w indow C lass)) re tu rn 0; i f (fu llS c re e n )
/ / tr y b pełnoekranowy?
{ DEVMODE dm S creenSettings; / / tr y b urządzenia memset( &dmScreenSetti ngs, 0 , size o f(d m S cre e n S e tti ngs) ) ; dm ScreenSettings.dm Size = s iz e o f(d m S c re e n S e ttin g s ); dm ScreenSettings.dm PelsW idth = w id th ; / / szerokość ekranu dm S creenSettings.dm PelsH eight = h e ig h t; / / wysokość ekranu dm S creenSettings.dm BitsPerPel = b it s ; / / b itó w na p ik s e l dm ScreenSetti n g s .dmFi elds=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
// i f (C hangeD isplayS ettings(& dm S creenS ettings, CDS_FULLSCREEN) != DISP CHANGE SUCCESSFUL)
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
443
/ / p rz e łą c z e n ie try b u n ie pow iodło s ię , z powrotem tr y b okienkowy MessageBox(NULL, "D is p la y mode f a i l e d " , NULL, MB_0K); fullScreen=FA LSE ;
/ / tr y b pełnoekranowy?
i f ( fu llS c re e n )
{
/ / rozszerzony s ty l okna / / s ty l okna / / ukrywa k u rs o r myszy
dwExStyle=WS_EX_APPWINDÓW ; dwStyle=WS_POPUP; ShowCursor(FALSE);
} e ls e dwExSty1e=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; dwSty 1e=WS_OVERLAPPEDWINDOW;
/ / rozszerzony s ty l okna / / s ty l okna
AdjustWindowRectEx(&windowRect, dw Style, FALSE, dw ExStyle); / / tw o rzy okno hwnd = CreateWindowEx( NULL, "M o ja K la s a ", "D ire c tX A udio, p rz y k ła d p ie rw szy: o d tw a rza n ie ", dw S tyle | WS CLIPCHILDREN | WS CLIPSIBLINGS, 0, 0, w indow R ect.r ig h t - windowRect.1 e f t , windowR ect.bottom - w indow R ect.top, NULL, NULL, h ln s ta n c e , NULL);
/ / ko ryg u je rozm iar okna
/ / rozszerzony s ty l okna / / nazwa k la s y / / nazwa a p lik a c ji / / w spółrzędne x ,y // // // // //
szerokość, wysokość uchwyt okna nadrzędnego uchwyt menu in s ta n c ja a p lik a c ji bez dodatkowych parametrów
/ / sprawdza, czy u tw o rze n ie okna n ie pow iodło s ię (wtedy w artość hwnd równa NULL) i f ( ! hwnd) re tu rn 0; / / in ic ju je COM i f ( FAILED(Colni t i a li z e (NULL) ) ) re tu rn 0;
/ / je ś l i in ic ja c ja COM n ie pow iodła s ię , / / program kończy d z ia ła n ie
/ / in ic ju j e D ire c tX Audio i f ( ! In itD ire c tX A u d io (h w n d )) re tu rn 0; / / ła d u je segment i f ( ! LoadSegment(hwnd, "ca n y o n .m id ")) re tu rn 0; / / odtw arza segment PlaySegm ent(dm usicPerform ance, dm usicSegment); ShowWindow(hwnd, SW_SH0W); UpdateWindow(hwnd);
/ / w yś w ie tla okno / / a k tu a liz u je okno
444
Część III ♦ Tworzymy grę
done = fa ls e ; In itia liz e O ;
/ / i n ic ju je zmienną warunku p ę t li / / in ic ju je OpenGL
/ / p ę tla p rze tw a rz a n ia komunikatów w h ile (!do n e )
{ PeekMessage(&msg. hwnd, NULL, NULL. PM_REM0VE); i f (msg.message == WM_QUIT) done - tr u e ;
/ / a p lik a c ja o trzym a ła kom unikat WM_QUIT? / / j e ś l i ta k . to kończy d z ia ła n ie
} e ls e
{ / / odtw arza segment i f (ke y P re s s e d [' 0 ' ] || k e yP re sse d [’ o ' ] )
{ i f (dm usicP erform ance->IsP laying(dm usicS egm entf NULL) != S_0K) PlaySegment(dmusicPerformance, dmusicSegment);
} / / z a trzym u je o d tw arzanie i f (k e y P re s s e d ['Z '] || k e y P re s s e d ['z '] )
{ i f (dm usicP erform ance->IsPlaying(dm usicS egm ent, NULL) == S_0K) StopSegment(dmusicPerformance. dmusicSegment);
} / / kończy d z ia ła n ie programu i f (keyPressed[VK_ESCAPE]) done = tr u e ; e ls e
{ R enderO ;
/ / ry s u je g r a fik ę
TranslateM essage(& m sg); DispatchM essage(&msg);
/ / tłum aczy kom unikat i w ysyła do systemu
} } } / / kończy wykonanie CloseDown(dm usicPerform ance); / / zw alnia zasoby CleanUp(); / / kończy pracę COM C o U n in itia liz e O ; i f ( fu llS c re e n )
{ C han ge D isp la yS e ttin g s(N U L L ,0 ); ShowCursor(TRUE);
} re tu rn msg.wParam;
/ / przywraca p u lp it / / i wskaźnik myszy
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
445
Program ten wyświetla obracający się tekst pokazany na rysunku 17.5 odtwarzając w tle plik canyon.mid. Naciśnięcie klawisza Z powoduje zatrzymanie jego odtwarzania, a klawisza O wznowienie odtwarzania. Program kończy pracę po naciśnięciu klawi sza Esc. Rysunek 17.5. Przykład zastosowania DirectX Audio
Zastosowania ścieżek dźwięku Ścieżki dźwięku zarządzają przepływem danych dźwięku poprzez różne obiekty od momentu załadowania tych danych z pliku aż do ich wysłania do głośników. Ścieżka dźwięku obejmować może obiekt wykonania, segment, graf narzędzi, syntezator i bufory DirectSound. Jak już pokazano, jeśli nie korzysta się z przestrzennych efektów dźwię kowych i odtwarza na przykład pliki MIDI, można skorzystać z domyślnej ścieżki dźwię ku. Aby wykorzystać pełne możliwości DirectX Audio, trzeba stosować własne ścieżki dźwięku. Ścieżkę dźwięku można skonfigurować na cztery różne sposoby: ♦ utworzyć jedną lub więcej standardowych ścieżek dźwięku za pomocą metody ID i rectM u sicP e rfo rm a n ce 8 ::C re a te S ta n d a rd A u d io P a th ();
♦ utworzyć domyślną, standardową ścieżkę dźwięku za pomocą metody ID ire c tM u s ic P e rfo rm a n c e 8 :: In itA u d io O ;
♦ uzyskać konfigurację ścieżki dźwięku z pliku stworzonego przez DirectMusic Producer i przekazać obiekt konfiguracji metodzie ID ire ctM u s ic P e rfo rm a n c e 8 :: C re a te A u d io P a th ();
♦ za pomocą DirectMusic utworzyć nową ścieżkę dźwięku ze ścieżki dźwięku odtwarzanego segmentu.
446
Część III ♦ Tworzymy grę
Posługując się metodami ID i rectM usicLoader8: :G e tO b je c t() lub ID ire ctM usicLoader8 : :L o a dO bjectF ro m F ile () można załadować obiekt konfiguracji ścieżki dźwięku w taki sam sposób, jak każdy inny obiekt DirectX Audio. Konfigurację ścieżki dźwięku dla danego segmentu pobiera się za pomocą metody ID i rectM usicSegm ent8: :G etAudioPathC o n fig O .
Konfiguracje ścieżek dźwięku nie posiadają własnego interfejsu, a tym samym własnych metod. Dlatego też obiektu konfiguracji nie można zmodyfikować, a jedynie przekazać metodzie ID irectM u sicP e rfo rm a nce 8: :C re a te A u d io P a th ().
Domyślna ścieżka dźwięku Domyślna ścieżka dźwięku używana jest podczas odtwarzania segmentu za pomocą metody ID ire ctM usicP erfo rm an ce8 : : PIaySegment() lub ID ire ctM usicP erform ance8::P laySegmentEx(). Aby utworzyć nową ścieżkę dźwięku i uczynić ją ścieżką domyślną, trzeba określić typ standardowej ścieżki dźwięku za pomocą parametru dwDefaultType metody ID ire c tM u s ic P e rfo rm a n c e 8 :: In itA u d io O .
Domyślną ścieżkę dźwięku określa się wywołując metodę ID ire c tM u s ic P e rfo rm a n c e 8 :: SetDefaul tA udi o P a th ( ) zdefiniowaną jak poniżej: HRESULT S etD e fa u ltA u d io P a th ( ID ire ctM u sicA u d io P a th *pAudioPath
I I in t e r f e js dom yślnej ś c ie ż k i dźwięku
); Domyślną ścieżkę dźwięku pobiera się za pomocą metody ID i rectM usicP erform ance8: :G etDefaul tA u di oPath () zdefiniowanej następująco: HRESULT G etD e fa u ltA u d io P a th ( ID ire ctM u sicA u d io P a th **ppAudioPath
I I domyślna ście żka dźwięku
);
Standardowe ścieżki dźwięku Pomijając przypadki, w których korzysta się wyłącznie ze ścieżek tworzonych na pod stawie obiektów konfiguracji ścieżek dźwięku, zwykle w programie tworzy się jedną lub więcej standardowych ścieżek dźwięku za pomocą metody ID ire ctM u sicP e rfo rm a n c e 8 ::C re a te S ta n d a rd A u d io P a th (): HRESULT C re a te S ta n d a rd A u d i o P a th ( DWORD dwType, DWORD dwPChannelCount, BOOL fActive, ID ire ctM u sicA u d io P a th **ppNewPath
/ / ty p ś c ie ż k i I I lic z b a kanałów wykonania
/ / tru e = ścieżka aktywna zaraz po u tw o rze n iu I I adres w skaźnika ś c ie ż k i dźwięku
); Standardową ścieżkę dźwięku można także utworzyć korzystając z parametru dwDefaultPathType metody ID irectM usicP erform ance8: :In itA u d o (). Parametr dwType metody C reateS ta ndardA u dio P ath( ) identyfikuje typ tworzonej ścieżki dźwięku. Tabela 17.1 przedstawia typy ścieżek dźwięku i używane przez nie bufory. Niektóre z tych buforów mogą być współużytkowane przez wiele ścieżek dźwięku.
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
447
Tabela 17.1. Typy standardowych ścieżek dźwięku Typ ścieżki dźwięku
Standardowe bufory
Współużytkowanie buforów
DMUS_APATH_DYNAMIC_3D
Efekt przestrzenny
nie
DMUS_APATH_DYNAMIC_MONO
Monofoniczny
nie
DMUS_APATH_DYNAMIC_STERE0
Stereofoniczny
nie
DMUS_APATH_SHARED_STEREOPLUSREVERB
Stereofoniczny, pogłos
tak, tak
Poniższy fragment kodu tworzy ścieżkę dźwięku umożliwiającą realizację efektu prze strzennego: ID ire ctM u sicA u d io P a th 8 *dmusic3DAudioPath = NULL; / / in t e r f e js ś c ie ż k i dźwięku i f (FAILED(dmusicPerformance->CreateStandardAudioPath(DMUS_APATH_DYNAMIC_3D, 128, TRUE. &dm usic3DAudioPath)))
{ / / ścieżka dźwięku z o s ta ła utworzona
} e ls e / / ścieżka dźwięku n ie z o s ta ła utworzona
Odtwarzanie za pomocą ścieżki dźwięku Dotychczas segment odtwarzany był za pomocą domyślnej ścieżki dźwięku i wobec te go parametrowi pAudioPath metody ID irectM usicP erform ance8: :PlaySegmentEx() trzeba było nadać wartość NULL. Aby skorzystać z innej ścieżki dźwięku, można nadal używać metody ID i rectM usicP erform a nce8: : PIaySegmentEx(), ale ścieżkę dźwięku należy okre ślić za pomocą jednego z poniższych sposobów: ♦ dostarczając wskaźnik obiektu interfejsu ścieżki dźwiękowej jako wartość parametru pAudioPath; ♦ umieszczając znacznik DMUS_SEGF_USE_AUDIOPATH jako element parametru dwFlags, co spowoduje, że segment utworzy ścieżkę dźwięku na podstawie konfiguracji zawartej w obiekcie segmentu. Tabela 17.2 przedstawia dopuszczalne wartości znaczników przekazywanych za pośred nictwem parametru dwFlags funkcji PlaySegmentEx(). Aby odtworzyć segment za pomocą ścieżki dźwiękowej (nie korzystając przy tym ze znaczników), należy wywołać metodę PlaySegmentEx( ) w następujący sposób: dmusicPerformance->PlaySegmentEx(dmusicSegm ent, NULL, NULL, 0, 0, NULL, dm usic3D A udioPath);
Dotąd pomijana była kwestia odtwarzania efektów dźwiękowych. Odtwarza się je jako segmenty wtórne za pomocą znacznika DMUS_SEGF_SECONDARY: dmusicPerformance->PlaySegmentEx(drnusicSegment. NULL, NULL, DMUS_SEGF_DEFAULT | DMUS_SEGF_SECONDARY, 0, NULL, dm usic3D A udioPath);
448
Część III ♦ Tworzymy grę
Tabela 17.2. Znaczniki metody PlaySegmentEx() Znacznik
Znaczenie
DMUS_SEGF_REFTIME
Parametry czasowe podawane są jako wartości względne
DMUSSEGFSECONDARY
Segment wtórny
DMUS_SEGF_QUEUE
Umieszcza segment na końcu kolejki segmentów zasadniczych (dotyczy tylko segmentów zasadniczych)
DMUS_SEGF_CONTROL
Odtwarza jako segment kontrolny (dotyczy tylko segmentów wtórnych)
DMUS_SEGF_AFTERPREPARETIME
Odtwarza po czasie przygotowania
DMUS_SEGF_GRID
Odtwarza na granicy siatki
DMUS_SEGF_BEAT
Odtwarza na granicy akordu
DMUS_SEGF_MEASURE
Odtwarza na granicy miary
DMUS_SEGF_DEFAULT
Używa domyślnej granicy segmentu
DMUS__SEGF_NOINVALIDATE
Nowy segment nie powoduje przerwania odtwarzania segmentu bieżącego
DMUS_SEGF_ALIGN
Początek segmentu może zostać wyrównany do granicy, która już minęła
DMUS_SEGF_VALID_START_BEAT
Odtwarzanie może rozpocząć się od dowolnego akordu
DMUS_SEGF_VALID_START GRID
Odtwarzanie może rozpocząć się od dowolnego punktu siatki
DMUS_SEGF_VALID_START_TICK
Odtwarzanie może rozpocząć się w dowolnym czasie
DMUS_SEGF_AUTOTRANSITION
Tworzy i odtwarza segment przejściowy na podstawie szablonu
DMUS_SEGF_AFTERQUEUETIME
Odtwarza po czasie kolejkowania (domyślnie dla wszystkich segmentów zasadniczych)
DMUS_SEGF_AFTERLATENCYTIME
Odtwarza po czasie opóźnienia (domyślnie dla wszystkich segmentów)
DMUS_SEGF__SEGMENTEND
Odtwarza po segmencie, który odtwarzany jest od podanego czasu rozpoczęcia odtwarzania (inne segmenty znajdujące się w kolejce są usuwane)
DMUS_SEGF_MARKER
Odtwarza po kolejnym markerze segmentu zasadniczego
DMUS_SEGF_TIMESIG_ALWAYS
Wyrównuje czas rozpoczęcia odtwarzania do bieżącej sygnatury czasu (nawet w przypadku, gdy nie istnieje segment zasadniczy)
DMUS_SEGF_USE_AUDIOPATH
Korzysta ze ścieżki dźwięku zawartej w segmencie (aby segment ten został prawidłowo odtworzony, musi być aktywne automatyczne ładowanie instrumentów)
DMUS_SEGF_VALID_START_MEASURE
Odtwarzanie może rozpocząć się od początku miary
W przypadku odtwarzania segmentów wtórnych nie trzeba określać ścieżki dźwięku. W powyższym wywołaniu wartość ostatniego parametru mogłaby więc równie dobrze wynosić NULL. Głośność kanałów wykonania podczas odtwarzania dźwięku za pomocą ścieżki dźwię ku kontroluje się za pomocą metody ID i rectM usicA udioP ath8: :SetVol ume() zdefinio wanej w następujący sposób:
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
449
HRESULT SetVolume( long 1Volume, II // // DWORD dwDuration I I
o k re ś la tłu m ie n ie wyrażone w setkach decybeli p rzyjm ując w a rto ści z p rz e d z ia łu od -9600 do 0, gdzie 0 oznacza pełną głośność o k re ś la czas w m ilisekundach, w którym odbywa s ię zmiana g łośności ! ! w artość 0 oznacza najw iększą sprawność zmiany głośności
); Aby uzyskać pełną głośność dźwięku w ciągu pół sekundy, metodę SetVolumeO naieży wywołać jak poniżej : dm usic3DAudioPath->SetVolum e(0, 500) ;
Głośność określana za pomocą metody SetVolumeO nie jest głośnością globalną lecz raczej głośnością kanałów wykonania używanych podczas odtwarzania za pomocą ścieżki dźwięku.
Pobieranie obiektów należących do ścieżek dźwięku W praktyce zdarza się, że trzeba nieraz uzyskać interfejs jednego z obiektów należących do ścieżki dźwięku. Na przykład w sytuacji, gdy należy zmienić właściwości przestrzen nego efektu. Aby pobrać obiekt ze ścieżki dźwięku, należy wywołać metodę IDirectMusicAudioPath8: : GetObject InPath ( ) zdefiniowaną jak poniżej: HRESULT G e tO b je ctIn P a th ( DWORD dwPChannel, II // DWORD dwStage, // DWORD dwBuffer, II // REFGUID guidObject, I I DWORD dwlndex. II REGUID iid lnterface . / / vo id **ppObject II
lic z b a przeszukiwanych kanałów wykonania w artość DMUS_PCHANNEL_ALL oznacza w szystkie etap ś cie ż k i indeks b u fo ra , gdy etap ś c ie ż k i równy je s t DM0 lu b gdy je s t to b u fo r m iksujący id e n ty fik a to r kla sy indeks o b ie k tu na liś c ie dopasowania; 0 d la pierwszego o b ie ktu id e n ty fik a to r in te r fe js u , na p rzykła d IID _ID irectM usicG raph adres zm iennej, k tó ra zawierać będzie wskaźnik in te r fe js u
); Poniższy fragment kodu ilustruje sposób dostępu do bufora standardowej ścieżki dźwięku: ID i rectM usicA udioP ath *dm usicA udioP ath; ID i re ctS oun d B u ffe r8 *dm usicS oundB uff; / / tw o rzy standardową ście żkę dźwięku za w ie ra ją cą b u fo r pogłosu / / ścieżka ta n ie je s t aktywna dmusicPerformance->CreateStandardAudioPath(DMUSIC_APATH_DYNAMIC_3D, 64, FALSE, &dm usicAudioPath); / / p o b ie ra w skaźnik o b ie k tu b ufora dmusicAudioPath->GetObjectInPath(DMUS_PCHANNEL_ALL, DMUS_PATH_BUFFER, 0, GUID_NULL, 0, IID ID ire c tS o u n d B u ffe r8 , (v o id **)& dm usicS oundB uff);
450
Część III ♦ Tworzymy grę
Dźwięk przestrzenny Efekt przestrzennego dźwięku można łatwo zastosować do bufora DirectSound, który pobrać należy ze ścieżki dźwięku. Trójwymiarowość dźwięku ma szczególne znaczenie w grach, w przypadku których może zrobić na użytkowniku nawet większe wrażenie niż najdoskonalsza grafika.
Współrzędne przestrzeni dźwięku Zanim zacznie się korzystać z efektu dźwięku przestrzennego, trzeba najpierw poznać sposób określania współrzędnych przestrzeni dźwięku stosowany w DirectX Audio. Położenie, prędkość i orientacja źródła dźwięku definiowane są w kartezjańskim ukła dzie współrzędnych na osiach x, y i z. Osie te są położone w taki sam sposób względem obserwatora jak osie układu współrzędnych używanego przez OpenGL z jednym wyjąt kiem: oś z posiada przeciwny zwrot. Inaczej mówiąc, ujemna część osi z zamiast znaj dować się „za” płaszczyzną ekranu, znajduje się tym razem „przed” ekranem. Dodatnia część osi z będzie wobec tego znajdować się „za” ekranem. Położenie źródła dźwięku określane jest w metrach. Można to zmienić stosując współ czynnik odległości określający liczbę metrów przypadających na jednostkę odległości zdefiniowaną przez aplikację. Prędkość mierzona jest w jednostkach aplikacji na sekundę. Orientacja źródła dźwięku określana jest w jednostkach aplikacji względem orientacji świata. Na przykład jeśli świat zorientowany jest w kierunku ujemnej części osi z (co odpowiada kierunkowi południowemu w DirectX Audio), a orientacja odbiorcy dźwięku określona jest przez wektor ( 1 , 0, 0), to będzie on zorientowany w kierunku zachodnim. Wektory używane przez DirectX Audio pochodzą z komponentu DirectX odpowie dzialnego za tworzenie grafiki trójwymiarowej, czyli Direct3D. Wektor zdefiniowany jest w tym przypadku w następujący sposób: ty p e d e f s tr u c t { f lo a t x ; f lo a t y: f lo a t z; } D3DVECT0R;
Percepcja położenia źródła dźwięku Sposób, w jaki odbierany jest dźwięk pochodzący od źródła znajdującego się w pewnym punkcie przestrzeni, zależy od wielu czynników (nie wyłączając wzroku)! Do czynni ków związanych bezpośrednio z dźwiękiem należą: ♦ ogólna głośność — gdy źródło dźwięku oddala się od obserwatora, to natężenie dźwięku maleje; ♦ różnica natężenia dźwięku dobiegającego do obu uszu — dźwięk pochodzący ze źródła znajdującego się po prawej stronie obserwatora jest odbierany jako głośniejszy prawym uchem niż lewym;
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
451
♦ różnica czasu, w którym dźwięk dociera do obu uszu — dźwięk pochodzący ze źródła znajdującego się po prawej stronie obserwatora dociera nieco wcześniej do ucha prawego niż do ucha lewego (różnica ta jest rzędu pojedynczych milisekund); ♦ tłumienie — kształt i orientacja uszu powodują że dźwięki docierające do nas z tyłu są bardziej tłumione niż dźwięki docierające z przodu; również dźwięk pochodzący ze źródła znajdującego się po prawej stronie obserwatora dociera bardziej stłumiony do ucha lewego niż do prawego. Wymienione powyżej czynniki nie wyczerpują tematu, ale posiadają zasadnicze zna czenie dla odbioru położenia źródła dźwięku i zostały zaimplementowane w DirectX Audio.
Bufor efektu przestrzennego w DirectSound Każde źródło dźwięku reprezentowane jest przez interfejs IDirectSound3DBuffer8. Źródło takie musi być ze swej natury monofoniczne, czyli jego dźwięk będzie docierać do odbiorcy jednym kanałem. Przy próbie uzyskania efektu przestrzennego dla źródła stereofonicznego uzyskany zostanie błąd. Aby stworzyć przestrzenne źródło dźwięku, trzeba najpierw uzyskać interfejs IDirectSound3DBuffer8. Jak już pokazano to omawiając ścieżki dźwięku, interfejs taki można pobrać za pomocą metody IDi rectMusicPerformance8 : :GetObjectInPath() w następu jący sposób: ID ire c tM u s ic A u d io P a th *dm usicAudioPath; ID i rectSound3D Buffer8 '*dmusic3DSoundBuff; / / tw o rzy ście ż k ę dźwięku z buforem e fe k tu przestrzennego / / (o p e ra c ja zbędna, j e ś l i ju ż ta k ą posiadamy) i f (FAILED(dmusicPerformance->CreateStandardAudioPath(DMUS_APATH_DYNAMIC_3D. 64, TRUE, &dm usic3DAudioPath))) re tu rn 0; / / p o b ie ra b u fo r e fe k tu przestrzennego i f ( FAILED( dm usic3D A udioPath->G etO bjectInP ath(0, DMUS_PATH_BUFFER, 0, GUID_NULL. 0, IID _ ID ire ctS o u n d 3 D B u ffe r, (v o id **)& d s 3 D B u ffe r))) re tu rn 0;
Parametry przestrzennego źródła dźwięku Parametry te można konfigurować pojedynczo lub wsadowo. W pierwszym przypadku trzeba skorzystać z odpowiedniej metody interfejsu IDirectSound3DBuffer8, które przed stawia tabela 17.3. Wszystkie te metody opisane są szczegółowo w dokumentacji DirectX 8.
452
Część III ♦ Tworzymy grę
Tabela 17.3. Metody określania pojedynczych parametrów przestrzennego źródła dźwięku Kategoria parametru
Metody
Odległość
GetMaxDistance G etM inD istance SetMaxDistance S etM inD istance
Tryb działania
GetMode SetMode G e tP o s itio n
Pozycja
S e tP o s itio n
Stożek rozchodzenia się dźwięku
GetConeAngles G etC oneO rientation GetConeOutsideVolume SetConeAngles S etC o n e O rie n ta tio n SetConeOutsi deVol ume
Prędkość ruchu źródła
G e tV e lo c ity S e tV e lo c ity
Zwykle jednak trzeba nadawać lub pobierać wartość wszystkich parametrów za jednym razem. Służą temu metody ID i re ctS o u n d 3 D B u ffe r8 :: SetAl 1 Parameters O i ID ire c tS o u n d 3 D B u ffe r8 :: Get A ll Parameters O . Metoda SetAl 1 Parameters O posiada następującą definicję: HRESULT S e tA l1Parameters( LPCDS3DBUFFER pcDs3DBuffer DWORD dwApply
/ / s tru k tu ra DS3DBUFFER o p isu ją ca c h a ra k te ry s ty k ę ź ró d ła / / o k re ś la , kiedy zastosować nowe parametry / / zwykle w artość DS3D_IMMEDIATE (n a tych m ia st)
): Jak łatwo zauważyć, aby korzystać z tej metody, trzeba utworzyć i wypełnić strukturę typu DS3DBUFFER. Zdefiniowano ją w następujący sposób: ty p e d e f s tr u c t _DS3DBUFFER
{ DWORD dwSize ; D3DVECT0R vP osition ; D3DVECT0R i/Velocity: DWORD dwInsideConeAngle; DWORD dwOutsideConeAngle: D3DVECT0R vConeOrientation: LONG IConeOutsideVolume; D3DVALUE fl Mi nDistance; D3DVALUE flMaxDistance: DWORD dwMode; } DS3DBUFFER, *LPDS3DBUFFER;
// // // // // // // // // //
rozm iar s tr u k tu r y DS3DBUFFER p o ło ż e n ie ź ró d ła dźwięku prędkość ź ró d ła dźwięku k ą t ro zw arcia wewnętrznego stożka dźwięku k ą t ro zw a rcia zewnętrznego stożka dźwięku o r ie n ta c ja stożków dźwięku głośność na zewnątrz stożka dźwięku m inim alna o d le g ło ś ć maksymalna o d le g ło ś ć tr y b d z ia ła n ia
ty p e d e f const DS3DBUFFER *LPCDS3BUFFER;
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
453
Pole dwMode struktury DS3DBUFFER może przyjmować następujące wartości: ♦ DS3DM0DEDISABLE — efekt przestrzenny nie jest wykorzystywany; ♦ DS3DM0DE HEADRELATIVE — parametry źródła dźwięku określone są względem obserwatora (jeśli położenie obserwatora zmienia się, to bezwzględne wartości parametrów źródła dźwięku są aktualizowane tak, by pozostawały one stałe względem obserwatora); ♦ DS3DM0DEN0RMAL — zwykły, domyślny tryb pracy. Teraz należy przyjrzeć się po kolei polom struktury DS3DBUFFER.
Odległość minimalna i maksymalna Przez odległość minimalną należy rozumieć odległość źródła od obserwatora, przy której słyszy on maksymalne natężenie dźwięku. Gdy obserwator zbliża się do źródła dźwięku, jego natężenie wzrasta. W połowie drogi będzie dwukrotnie większe niż na początku, ale po osiągnięciu odległości minimalnej przestanie dalej wzrastać. Odległość minimalna jest szczególnie przydatna, gdy trzeba uzyskać różnicę głośności dwóch różnych źródeł dźwięku. Najlepiej będzie rozważyć to na przykładzie silnika od rzutowca i agregatu klimatyzacji. Chociaż pierwsze z tych źródeł generuje dźwięk o dużo większym natężeniu, to w praktyce oba będzie trzeba zapisać z tym samym poziomem głośności. Aby uzyskać realistyczny efekt podczas ich odtwarzania, należy skonfiguro wać minimalną odległość dla źródła dźwięku reprezentującego odrzutowiec tak, by wy nosiła 100 metrów, a dla klimatyzacji tylko 1 metr. Tak więc w przypadku, gdy obser wator znajdzie się w odległości 200 metrów od odrzutowca natężenie dźwięku zmaleje dwukrotnie. Podobnie w przypadku odległości 2 metrów od agregatu klimatyzacji. Do myślna wartością parametru odległości minimalnej jest DS3D_DEFAULTMINDISTANCE i wy nosi on 1 jednostkę (czyli 1 metr przy domyślnej wartości współczynnika odległości). Jeśli nie zostanie zmieniona odległość minimalna, to maksymalne natężenie dźwięku bę dzie w odległości 1 metra od źródła, dwukrotnie mniejsze w odległości 2 metrów, czte rokrotnie w odległości 4 metrów i tak dalej. Odległość maksymalna określa natomiast odległość od źródła dźwięku, po przekrocze niu której natężenie dźwięku przestaje maleć. Domyślną wartością tego parametru jest DS3D_DEFAULTMAXDISTANCE (wynosi miliard metrów). W praktyce oznacza to, że głośność dźwięku wyznaczana jest nawet przy odległościach, z których w rzeczywistości nie jest on słyszalny. Różnica pomiędzy odległością maksymalną i minimalną ma wpływ na efekt tłumienia dźwięku. Jeśli jest ona duża to natężenie dźwięku nie zmienia się tak gwałtownie, jak w przypadku małej różnicy.
Tryb działania Dostępne są trzy tryby działania przestrzennego efektu dźwięku: ♦ zwykły — w trybie tym parametry źródła dźwięku określająjego bezwzględne położenie i charakterystykę;
454
Część III ♦ Tworzymy grę
♦ względem obserwatora — zmiana położenia i orientacji obserwatora powoduje automatyczną zmianę parametrów źródła dźwięku. ♦ wyłączony - efekt przestrzennego źródła dźwięku nie jest wykorzystywany.
Położenie i prędkość W zależności od trybu działania efektu przestrzennego położenie źródła dźwięku repre zentowane jest za pomocą współrzędnych świata bądź względem obserwatora. Prędkość źródła dźwięku wykorzystywana jest do wyznaczenia efektu Dopplera.
Stożki dźwięku Stożki dźwięku pozwalają zdefiniować kierunkową głośność dźwięku. Zwykłe źródło emituje dźwięk o takiej samej amplitudzie we wszystkich kierunkach. Stożek dźwięku składa się z części zewnętrznej i wewnętrznej, co ilustruje rysunek 17.6. Wewnątrz stożka wewnętrznego natężenie dźwięku jest takie samo jak w przypadku, gdy nie używa się stożków. Na zewnątrz stożka zewnętrznego dźwięk podlega tłumieniu, które można konfigurować. Wartość natężenia dźwięku na zewnątrz tego stożka jest ujemna i wyra żona w setkach decybeli, ponieważ reprezentuje tłumienie względem domyślnej wartości głośności równej 0. Rysunek
1 7 .6 .
Stożek dźwięku
źródło dźwięku
Pomiędzy obydwoma stożkami znajduje się strefa przejściowa, w której natężenie dźwięku maleje ze wzrostem kąta rozwarcia stożka. Jednym z zastosowań stożków dźwięku jest symulacja dźwięku dobiegającego z pomiesz czenia poprzez otwarte drzwi. Źródło dźwięku należy umieścić w pomieszczeniu i zo rientować je w kierunku drzwi w taki sposób, że wewnętrzny stożek będzie wypełniał szerokość otworu drzwi, a stożek zewnętrzny będzie nieco większy. Obserwator prze chodząc koło otworu drzwiowego usłyszy dobiegający z pomieszczenia dźwięk, którego natężenie będzie wygasać po minięciu drzwi.
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
455
Odbiorca efektu dźwięku przestrzennego Efekt dźwięku przestrzennego nie zależy wyłącznie od parametrów jego źródła, ale także od położenia, orientacji i prędkości odbiorcy dźwięku (obserwatora). Domyślnie odbiorca dźwięku znajduje się w środku układu współrzędnych, nie porusza się i spogląda w kie runku dodatniej części osi z. Zmieniając jego położenie, orientację i prędkość można odpo wiednio dopasować efekt dźwięku przestrzennego korzystając z efektu Dopplera i zmie niając głośność dźwięku wraz ze zmianą odległości od jego źródła. W przeciwieństwie do źródeł dźwięku, których może być wiele, istnieje tylko jeden odbiorca dźwięku. Orientacja odbiorcy zdefiniowana jest za pomocą dwóch wektorów: wskazującego kie runek „do przodu” i „w górę”. Jeśli wektory te nie będą tworzyć odpowiedniego kąta, to DirectX Audio zmodyfikuje składowe wektora wskazującego kierunek „do przodu”. Aby pobrać obiekt odbiorcy dźwięku korzysta się z metody IDirectMusicAudioPath8:: GetObjectInPath() w podobny sposób jak podczas pobierania obiektu bufora: ID i rectSound3DLi s te n e r8 *dmusi c L is te n e r ; / / p o b ie ra o b ie k t o d b io rc y dźwięku ze ś c ie ż k i dźwięku i f ( FAILED( dmusi c3DAudi oP a th -> G e tO b je ctIn P a th ( DMUS_PCHANNEL_ALL. DMUS_PATH_PRIMARY_BUFFER, 0, GUID_NULL, 0, IID _ ID i rectSound3DLi s te n e r, ( v o id **)& d m u s ic L is te n e r))) re tu rn 0;
Parametry pobranego obiektu określa się w podobny sposób jak parametry źródła dźwięku. Można konfigurować je pojedynczo za pomocą metod wymienionych w tabeli 17.4 lub skorzystać z metod IDi rectSound3DListener8::SetAl 1ParametersO i IDirectSound3DL isten er8 :: Get Al 1Pa rametersO. Tabela 17.4. Metody interfejsu IDirectSound3DListener8 Kategoria
Metody
Współczynnik odległości
G e tD istan ce F a cto r S etD ista n ce F a cto r
Współczynnik efektu Dopplera
G etD opplerFactor S etD opplerF actor
Orientacja
G e tO rie n ta tio n
Położenie
G e tP o s itio n
Współczynnik tłumienia
G e tR o llo ffF a c to r
Prędkość
G e tV e lo c ity
S e tO rie n ta tio n S e tP o s itio n S e tR o llo ffF a c to r
S e tV e lo c ity
456
Część III ♦ Tworzymy grę
Jak pokazuje poniższa definicja metody Set Al 1P aram eters( ) parametry odbiorcy dźwięku określa się przy użyciu struktury typu DS3DLISTENER: HRESULT S e tA llP a ra m e te rs( DS3DLISTENER pcListener, I I param etry o d b io rcy dźwięku DWORD dwApply / / kie d y na le ży je zastosować
Struktura DS3DLISTENER zdefiniowana jest następująco: ty p e d e f s tr u c t
{
DWORD dwSize ; // D3DVECT0R vPosition; II D3DVECT0R vVelocity ; // D3DVECT0R vOrientFront; II D3DVECT0R i/OrientTop: II D3DVALUE flDistanceFactor ; // D3DVALUE flRolloffFactor-, II D3DVALUE flDopplerFactor ; // } DS3DLISTENER, *LPDS3DLISTENER;
rozm iar s tr u k tu r y DS3DLISTENER w b a jta c h p o ło że n ie o d b io rcy dźwięku prędkość o d b io rcy dźwięku w ektor wskazujący k ie ru n e k "do przodu" w ektor wskazujący k ie ru n e k "wgórę" w spółczynnik o d le g ło ś c i w spółczynnik tłu m ie n ia w spółczynnik e fe k tu D opplera
ty p e d e f const DS3DLISTENER *LPCDS3DLISTENER;
Aby określić wartości parametrów, trzeba nadać je polom struktury DS3DLISTENER i wy wołać metodę ID i rectS ound3D Listener8: :S e tA llP a ra m e te rs ().
Przykład zastosowania efektu przestrzennego Zamieszczony poniżej program ilustruje sposób użycia efektu przestrzennego w DirectX Audio. Jego część graficzna rysuje tekst w ten sam sposób co w poprzednim przykła dzie. Tym razem jednak tekst ten nie obraca się, lecz oddala się i zbliża do obserwatora wydając przy tym dźwięk „kliknięcia”. Zmieniając położenie źródła dźwięku wyposa żonego w bufor IDirectSound3DBuffer wraz z położeniem tekstu uzyskuje się efekt malejącej głośności dźwięku, gdy tekst oddala się od obserwatora, a rosnącej, gdy się zbliża i stłumionej po jego minięciu. Odbiorcę dźwięku należy umieścić w tym samym punkcie, co obserwatora. Wyświetlany tekst zmienia się w zależności od tego, czy zbli ża się, czy oddala od obserwatora. Działanie programu ilustruje rysunek 17.7. Poniżej zamieszczony został pełen kod źródłowy programu opatrzony komentarzami ułatwiają cymi jego zrozumienie. # d e fin e WIN32_LEAN_AND_MEAN # d e fin e INITGUID I I I I U PI i k i nagłówkowe # in c lu d e # in c lu d e # in c lu d e < d m u sici.h > # in c lu d e # in c lu d e < cg u id .h > # in c lu d e < s td io .h > # in c lu d e < s td lib .h > # in c lu d e # in c lu d e < g l/g l.h > # in c lu d e < g l/g lu .h >
/ / "odchudza" a p lik a c ję Windows / / używa id e n ty fik a to ró w GUID w D ire c tM u sic
/ / standardowy p lik nagłówkowy Windows / / p l i k i nagłówkowe D ire c tM u sic / / zawiera d e fin ic ję typ u D3DVECT0R / / zaw iera d e fin ic ję GUID_NULL
/ / standardowy p lik nagłówkowy OpenGL / / p lik nagłówkowy b ib lio t e k i GLU
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
Rysunek 17.7. Przykład zastosowania efektu przestrzennego
Efekt przestrzenny!
Ruch w kierunku obserwatora! ESC - Koniec
/ / / / / / Zmienne g lo b a ln e HOC g_HDC; bool f u l l Screen = fa ls e ; bool ke yP re s s e d [2 5 6 ]; f l o a t angle = O.Of; unsigned in t lis tB a s e ; GLYPHMETRICSFLOAT gm f[2 5 6 ];
// // // //
g lo b a ln y k o n te k st urządzenia tr u e = tr y b pełnoekranowy; fa ls e = tr y b okienkowy ta b lic a p rz y c iś n ię ć k la w is z y
/ / początkowa li s t a w y ś w ie tla n ia / / dane o o r ie n ta c ji i p o ło że n iu znaków / / używane przez l i s t y w y ś w ie tla n ia
¡11111 Zmienne D ire c tM u sic
ID ire c tM u s ic L o a d e r8 *dmusicl_oader = NULL; ID irectM usicP e rfo rm a n ce 8 *dm usicPerform ance = NULL; ID irectM usicSegm ent8 *dmusicSegment = NULL; ID ire c tM u s ic A u d io P a th *dmusic3DAudioPath = NULL; ID ire ctS o u nd 3 D B u ffe r *d s3 D B u ffe r = NULL; ID ire c tS o u n d 3 D L is te n e r *d s3 D L iste n e r = NULL; DS3DBUFFER dsB ufferP aram s; DS3DLISTENER dsListenerP aram s;
*
// // // // // //
o b ie k t ła d u ją c y o b ie k t wykonania segment ścieżka dźwięku b u fo r e fe k tu przestrzennego od b io rca dźwięku
/ / param etry bufo ra e fe k tu przestrzennego / / param etry o d b io rcy dźwięku
In t e r fe js y D ire c tM u sic
*******************************************************i I I In itD ire c tX A u d io O
/ / o p is : i n i c ju je komponent D ire c tX Audio bool InitD irectXA udio(H W N D hwnd)
{
char pathStr[MAX_PATH]; / / ścieżka dostępu p lik ó w dźwiękowych WCHAR wcharStr[MAX_PATH]; / / tw o rzy o b ie k t ła d u ją c y i f (FAILED (C oC reateInstance(C LS ID _D irectM usicLoader, NULL, CLSCTX_INPROC, IID ID ire c tM u s ic L o a d e r8 , ( v o id **)& d m u sicL oa d e r) ) )
457
458
Część III ♦ Tworzymy grę
{ MessageBox(hwnd, "N ie można utw orzyć o b ie k tu ID ire c tM u s ic L o a d e r8 !", " B łą d !" , MB_0K); re tu rn fa ls e ;
} / / tw o rzy o b ie k t wykonania i f (FAILED (C oC reateInstance(C LSID _D irectM usicPerform ance, NULL, CLSCTX_INPROC, IID _ ID i rectM usicP erform ance8, ( voi d * * ) &dmus i cPerform ance) ) )
{
MessageBox(hwnd. "N ie można utw orzyć o b ie k tu ID ire c tM u s ic P e rfo rm a n c e 8 !" , " B łą d !" , MB_0K); re tu rn fa ls e ;
} / / in ic ju j e D ire c tM u sic i D irectSound i f (FA ILE D (dm usicP erform ance->InitAudio(N U LL, NULL, NULL, DMUS_APATH_DYNAMIC_STEREO, 64, DMUS_AUDIOF_ALL, NULL)))
{ MessageBox(hwnd, "N ie można z a in ic jo w a ć D ire c tM u sic i D ire c tS o u n d !", " B łą d !" , MB_0K); re tu rn fa ls e ;
} / / tw orzy standardową ście żke za w ie ra ją cą b u fo r e fe k tu prze strze n n e go i f (FAILED(dmusicPerformance->CreateStandardAudioPath(DMUS_APATH_DYNAMIC_3D, 64, TRUE, &dmus i c3DAudi oP ath) ) )
{ MessageBox(hwnd, "N ie można utw orzyć standardow ej ś c ie ż k i d ź w ię k u !", "B łą d ", MB_0K); re tu rn fa ls e ;
} / / p o biera b u fo r e fe k tu p rzestrzennego ze ś c ie ż k i dźwięku i f (FAILED (dm usic3D AudioP ath->G et0bjectInPath(0, DMUS_PATH_BUFFER, 0, GUID_NULL, 0, IID _ ID ire ctS o u n d 3 D B u ffe r, (v o id ** )& d s 3 D B u ffe r)))
{ MessageBox(hwnd, "N ie można pobrać b u fo ra ze ś c ie ż k i d ź w ię k u !". "B łą d ", MB_0K); re tu rn fa ls e ;
} / / pob ie ra param etry b u fo ra e fe k tu przestrzennego dsBufferParam s.dw Size = sizeof(DS3DBUFFER); ds3 D B uffe r-> G e tA l!P a ra m e te rs(& d sB u ffe rP a ra m s); / / nadaje nowe w a rto śc i parametrom b u fo ra e fe k tu p rzestrzennego dsBufferParams.dwMode = DS3DM0DE_HEADRELATIVE; / / tr y b względem o d b io rc y d s3 D B u ffe r-> S e tA l1P aram eters(&dsBufferParam s, DS3D_IMMEDIATE); / / pob ie ra o b ie k t o d b io rcy ze ś c ie ż k i dźwięku i f (FAILED (dm usic3D AudioP ath->G et0bjectInPath(0, DMUS_PATH_PRIMARY_BUFFER, 0, GUIDJIULL, 0, IID _ ID irectS ound3D Li s te n e r , ( voi d**)&ds3DLi s te n e r ) ))
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
MessageBox(hwnd, "N ie można pobrać o b ie k tu o d b io rc y !" "B łą d ". MB_0K); re tu rn fa ls e ;
/ / pob ie ra param etry o d b io rc y dsListenerP aram s.dw S ize = sizeof(DS3DLISTENER); d s 3 D L iste n e r-> G e tA llP a ra m e te rs(& d sL iste n e rP a ra m s); / / o k re ś la p o ło ż e n ie o d b io rc y d sL iste n e rP a ra m s. v P o s itio n .x = O.Of; d s L is te n e rP a ra m s .v P o s itio n .y = O.Of; d sL iste n e rP a ra m s. v P o s itio n .z = O.Of; d s 3 D L is te n e r-> S e tA l1Param eters(&dsListenerParam s, DS3D IMMEDIATE); / / pob ie ra b ie żą cy k a ta lo g G etC urrentD i rectory(MAX_PATH, p a th S tr ) ; / / zam ienia je g o nazwę na Unicode MultiByteToWideChar(CP_ACP. 0, p a th S tr,
-1. w ch a rS tr, MAX PATH);
/ / o k re ś la ście żkę dostępu do p lik ó w dźwiękowych d m usicLoader->S etS earchD irectory(G U ID _D irectM usicA llT ypes, w ch a rS tr, FALSE); re tu rn tr u e ;
/ / LoadSegmentO / / o p is : ła d u je segment z p lik u bool LoadSegment(HWND hwnd, char *file n a m e )
{
WCHAR wcharStr[MAX PATH]; / / zam ienia nazwę p lik u na Unicode MultiByteToWideChar(CP_ACP, 0, file n a m e , -1. w ch a rS tr, MAX PATH); / / ła d u je segment z p lik u i f (FAILED (dm usicLoader->LoadO bjectFrom File(C LSID _DirectM usicSegm ent, IID ID ir e c tM u s i cSegment8, w ch a rS tr, ( voi d * * ) &dmus i cSegment) ) )
{
MessageBox(hwnd, "N ie z n a le zio n o p lik u a u d io !", " B łą d !" , MB_0K); re tu rn fa ls e ;
} / / k o n fig u ru je nieskończoną lic z b ę powtórzeń odtwarzanego segmentu dmusicSegment->SetRepeats(DMUS_SEG_REPEAT_INFINITE); / / ła d u je in s tru m e n ty do ś c ie ż k i dmusi cSegment->Download(dmusi c3DAudi oP ath); re tu rn tr u e ;
459
460
Część III ♦ Tworzymy grę
/ / PlaySegmentO / / o p is : odtw arza segment v o id P layS egm ent(ID irectM usicP erform ance8* dmPerf, ID irectM usicS egm ent8* dmSeg)
{ / / odtwarza segment od następnego ta k tu dmPerf->P1aySegmentEx(dmSeg, NULL. NULL. DMUS_SEGF_DEFAULT, 0. NULL, NULL, dm usic3DAudioPath);
} / / StopSegmentO / / o p is : zatrzym u je odtw a rza n ie segmentu v o id S topS egm ent(ID irectM usicP erform ance8* dmPerf, ID irectM usicS egm ent8* dmSeg)
{ / / z a trzym u je o dtw arzanie dmSeg dmPerf->StopEx(dmSeg, 0, 0 );
} / / CloseDownO / / o p is : kończy wykonanie v o id CloseD ow nCID irectM usicPerform ance8* dmPerf)
{ / / z a trzym u je o dtw arzanie dmPerf->Stop(NULL, NULL, 0, 0 ); / / kończy pracę D ire c tM u sic dm Perf->C loseDow n();
} / / Set3DSoundParams() / / o p is : k o n fig u ru je param etry b u fo ra e fe k tu przestrzennego vo id Set3DSoundParams(f lo a t d o p p le r, f lo a t r o l l o f f , f l o a t m in D is t, f l o a t m axD ist)
{ / / param etry e fe k tu Dopplera i tłu m ie n ia d s L is te n e rP a ra m s .flD o p p le rF a c to r = d o p p le r; d s L is te n e rP a ra m s .flR o llo ffF a c to r = r o l l o f f ; i f (d s3 D L iste n e r) d s 3 D L is te n e r-> S e tA l1P aram eters(& dsListenerP aram s, DS3D_IMMEDIATE); / / o d le g ło ś ć m inim alna i maksymalna d s B u ffe rP a ra m s .flM in D is ta n c e = m in D is t; dsB ufferP aram s. flM a x D istance = m axD ist; i f (ds3D B u ffe r) d s3D B uffer-> S etA llP aram eters(& dsB ufferP aram s, DS3D_IMMEDIATE);
} / / Set3DSoundPos() / / o p is : a k tu a liz u je p o ło ż e n ie ź ró d ła dźwięku (a k c e p tu je w spółrzędne OpenGL) v o id Set3D SoundPos(ID irectSound3D Buffer* d s B u ff, f lo a t x, f l o a t y , f l o a t z)
{ / / używa -z ponieważ oś z a przeciw ny zw rot w D ire c tX w stosunku do OpenGL i f (d s B u ff ! ” NULL)
{ d s B u ff-> S e tP o s itio n (x , y , -z , DS3DIHMEDIATE);
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
461
/ 'k'k'k'k-k'k'k'k-k'k'k'k'k'klt'k'k'k'k'k'k-k'kic'kirk-k'k'k'k'k'k'k-kick'k'k'kirkiir'k-k-k-k-k'k'k'k'kick
*
In t e r fe js y OpenGL
I I C re a te O u tlin e F o n tO
/ / o p is : tw o rzy czcio n kę obrysową za pomocą fu n k c ji C reateF ontO unsigned i n t C re a te O u tlin e F o n t(c h a r *fontName, in t fo n tS iz e , f l o a t depth)
{
HFONT hFont; unsigned i n t base;
/ / uchwyt c z c io n k i systemu Windows
base = g lG e n L is ts (2 5 6 );
/ / l i s t y w y ś w ie tla n ia d la 96 znaków
• i f (stricm p (fo n tN a m e , "sym bol") == 0)
{
hFont = C re a te F o n t(fo n tS iz e , 0, 0, 0. FW_B0LD, FALSE, FALSE, FALSE. SYMBOL_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE | DEFAULT_PITCH, fontN am e);
}
e ls e
{
hFont = C re a te F o n t(fo n tS iz e , 0, 0, 0, FW_B0LD, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE | DEFAULT_PITCH, fontN am e);
} i f ( !hF ont) re tu rn 0; SelectObject(g_FHDC, h F o n t); wglUseFontO utlines(g_HD C , 0, 255, base, 0 .0 f, d epth, WGL_FONT_POLYGONS, gm f); re tu rn base;
} / / C le a rF o n tO / / o p is : usuwa l i s t y w y ś w ie tla n ia c z c io n k i v o id C le arF o n t(u n sig n e d i n t base)
{
g lD e le te L is ts (base, 256);
} / / P r in tS tr in g O / / o p is : w y ś w ie tla te k s t wskazywany przez s t r / / z a pomocą c z c io n k i o początkow ej l iś c ie w y ś w ie tla n ia base v o id P rin tS trin g (u n s ig n e d i n t base, char * s t r )
{
f l o a t le n g th = 0; i f ( ( s t r == NULL)) re tu rn ; / / c e n tru je te k s t f o r (unsigned in t lo o p = 0 ;lo o p < ( s tr le n ( s tr ) ) ;lo o p + + ) // długość te k s tu w znakach
{
le n g th + = g m f[s tr[lo o p ]].g m fC e llIn c X ;
/ / zwiększa długość te k s tu w p ik s e la c h / / o szerokość znaku
462
Część III ♦ Tworzymy grę
g lT r a n s la te f ( - le n g th / 2 ,0 . 0 f, 0 .0 f ) ;
/ / c e n tru je te k s t
/ / ry s u je te k s t g lP u sh A ttrib (G L _ L IS T _ B IT ); g lL is tB a s e (b a s e ); g lC a l1L is t s ( s t r l e n ( s t r ) , GL_UNSIGNED_BYTE, s t r ) ; g lP o p A ttr ib O ;
} / / CleanUpO / / o p is : zw a ln ia zasoby a p lik a c ji vo id CleanUpO
{ C le arF ontO is tB a s e ) ; dm usic3D A udioP ath->R elease(); dm usicLoader->R elease(): dm usicP erform ance-> R elease(); dm usicSegm ent->R elease();
} / / In itia liz e / / o p is : in ic ju je OpenGL v o id I n i t i a l i z e O
{ g lC le a rC o lo r(0 .0 f, O.Of. O.Of. O .O f);
/ / t ł o w k o lo rz e czarnym
glShadeModel(GL_SM00TH); glEnable(GL_DEPTH_TEST); glE nable(G LLIG H TO ); glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL);
// // // // //
c ie n io w a n ie g ła d k ie usuwanie p rz e s ło n ię ty c h p ow ierzchni włącza ź ró d ło ś w ia tła lig h tO włącza o ś w ie tle n ie k o lo r ja k o m a te ria ł
lis tB a s e = C re a te O u tlin e F o n t("A ria l", 10, 0 .2 5 f) ;
/ / ła d u je czcio n kę A r ia l 10 p k t.
} / / Render / / o p is : ry s u je scenę v o id RenderO
{ s t a t ic f lo a t zpos = O.Of; s t a t ic bool z D ir = fa ls e ;
/ / p o ło ż e n ie na o si z / / kie ru n e k ruchu fa ls e = ujemny, tr u e = d odatni
/ / na przemian odsuwa i z b liż a te k s t i f (z D ir) zpos += 0 .0 8 f; el se zpos -= 0 . 0 8 f; i f (zpos z D ir = i f (zpos zDi r =
> 3 0 .Of) fa ls e ; < -3 0 .Of) tr u e ;
/ / opró żn ia b u fo ry ekranu i g łę b i gl Cl e a r(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); g lL o a d ld e n tity O ;
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
g lP u s h M a trix (); g lC o lo r 3 f( 0 .5 f, 0 .5 f. 0 . 5 f ) ; g lT r a n s la te f(O .O f. 3 .0 f. -1 0 .O f); P r in tS tr in g d is tB a s e , "E fe k t p rz e s trz e n n y !"); g lP o p M a tr ix () ; / / przesuwa te k s t w zdłuż o si z g lT r a n s la t e f ( 0 . 0 f , O .Of. zp o s); / / a k tu a liz u je p o ło ż e n ie ź ró d ła dźwięku Set3DSoundPos(ds3DBuffer, 0 .0 , 0 .0 , zp o s); / / k o lo r niebieskaw y g lC o lo r 3 f( 0 .3 f, 0 .4 f, 0 .8 f ) ; / / w y ś w ie tla te k s t g lP u s h M a trix (); g lT r a n s la te f ( 0 . 0 f. - l. O f , O .O f); i f ( z D ir) P r in tS tr in g d is tB a s e , "Ruch w k ie ru n k u o b s e rw a to ra !" ) ; e ls e P r in tS tr in g d is tB a s e , "Ucieczka od o b s e rw a to ra !" ) ; g lP o p M a tr ix () ; / / k o lo r ż ó łto - z ie lo n y g lC o lo r 3 f( 0 .6 f, 0 .8 f, 0 .5 f ) ; / / w y ś w ie tla te k s t g lP u s h M a trix (); g lT r a n s la te f ( 0 . 0 f, - 3 .0 f , O .O f); P r in tS tr in g d is tB a s e . "ESC - K o n ie c "); g lP o p M a tr ix () ; SwapBuffers(g_HDC);
/ / p rze łą cza b u fo ry
} / / fu n k c ja o k re ś la ją c a fo rm a t p ik s e li v o id SetupPixelForm at(HDC hDC)
{ i n t n P ix e lFormat;
/ / indeks form atu p ik s e li
s t a t ic PIXELFORMATDESCRIPTOR pfd = ( sizeof(PIXELFORMATDESCRIPTOR), / / rozm iar s tr u k tu r y / / domyślna w e rsja 1, / / g r a fik a w o kn ie PFD_DRAW_TO_WINDÓW | / / g r a fik a OpenGL PFD_SUPPORT_OPENGL | / / podwójne buforow anie PFD_DOUBLEBUFFER, / / tr y b kolorów RGBA PFD_TYPE_RGBA, / / 32-b ito w y o p is kolorów 32. / / n ie s p e c y fik u je b itó w kolorów 0, 0, 0, 0, 0, 0, / / bez b u fo ru a lfa 0. / / n ie s p e c y fik u je b itu p rze s u n ię c ia 0, / / bez bu fo ra akum ulacji 0, / / ig n o ru je b it y akum ulacji 0, 0, 0, 0. / / 1 6 - b it b u fo r z 16, / / bez b u fo ra p o w ie la n ia 0,
463
464
Część III ♦ Tworzymy grę
0. PFD_MAIN_PLANE, 0, 0, 0, 0 } ;
// // // //
bez buforów pomocniczych główna płaszczyzna rysowania zarezerwowane ig n o ru je maski warstw
/ / w ybiera n a jb a rd z ie j zgodny fo rm a t p ik s e li nP ixelF orm at = ChoosePixelFormat(hDC, & p fd ); / / o k re ś la fo rm a t p ik s e li d la danego ko n te kstu urządzenia S etPixelForm at(hD C , n P ixe lF o rm a t, & p fd ); } / / procedura okienkowa LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM 1 Param)
{ s t a t ic HGLRC hRC; s t a t ic HDC hDC; in t w id th , h e ig h t;
/ / k o n te k s t tw o rze n ia g r a f ik i / / k o n te k s t urządzenia / / szerokość i wysokość okna
sw itch(m essage)
{ case WM_CREATE: hDC = GetDC(hwnd); g_HDC = hDC; S etupP ixelForm at(hD C );
/ / okno je s t tw orzone //
po b ie ra k o n te k st urządzenia d la okna
/ / w yw ołuje fu n k c ję o k re ś la ją c ą fo rm a t p ik s e li
/ / tw o rzy k o n te k s t tw o rze n ia g r a f ik i i czyni go bieżącym hRC = w g lC reateC ontext(hD C ); wglMakeCurrent(hDC, hRC); re tu rn 0; break; case WM_CL0SE:
/ / okno je s t zamykane
/ / deaktyw uje bieżący k o n te k s t tw o rze n ia g r a f ik i i usuwa go wglMakeCurrent(hDC, NULL); w glD e le te C o n te xt(h R C ); / / wstawia kom unikat WM_QUIT do k o le jk i PostQ uitM essage(O ); re tu rn 0; break; case WM_SIZE: h e ig h t = HIWORDOParam); w id th = LOWORDO Param); i f (h e ig h t= = 0 )
/ / p o biera nowe ro zm ia ry okna
/ / unika d z ie le n ia przez 0
{ h e ig h t= l;
} g l V ie w p o rt(0 , 0, w id th , h e ig h t); / / nadaje nowe wym iary oknu OpenGL glMatrixMode(GL_PROJECTION); / / w ybiera m acierz rzutow ania g lL o a d ld e n tity O ; / / re s e tu je m acierz rzutow ania
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
465
/ / wyznacza p ro p o rc je obrazu g lu P e rs p e c tiv e (5 4 .0 f, (G L flo a t)w id th /(G L f1o a t) h e ig h t, 1 .O f.1000.O f); glMatrixMode(GL_MODELVIEW); g lL o a d ld e n tity O ;
/ / w ybiera m acierz modelowania / / re s e tu je m acierz modelowania
re tu rn 0; break; case WM_KEYDOWN: keyPressed[wParam] = tr u e ; re tu rn 0; break;
/ / użytkow nik n a c isn ą ł kla w isz?
case WM_KEYUP: keyPressed[wParam] = fa ls e ; re tu rn 0; break; d e fa u lt: break;
} re tu rn (DefWindowProc(hwnd, message, wParam, 1Param));
/ / p u n k t, w którym rozpoczyna s ię wykonywanie a p lik a c ji in t WINAPI WinMain( HINSTANCE h ln s ta n c e , HINSTANCE hP re vIn sta n ce , LPSTR IpCmdLine, in t nShowCmd)
{
WNDCLASSEX windowClass; HWND hwnd; MSG msg; bool done; DWORD dw ExStyle; DWORD d w S tyle ; RECT wi ndowRect;
// // // // // //
kla sa okna uchwyt okna kom unikat znacznik zakończenia a p lik a c ji rozszerzony s ty l okna s ty l okna
/ / zmienne pomocnicze i n t w id th = 800; in t h e ig h t = 600; in t b it s = 16; / / f u l l Screen = TRUE; w in d o w R e c t.le ft= (lo n g )0; windowR ect. r ig h t= ( lo n g )w id th ; wi ndowRect. to p = (1ong)0; wi ndowRect. bottom =(1ong) hei g h t;
/ / s tru k tu ra o k re ś la ją c a rozm iary okna
/ / d e fin ic ja k la s y okna = sizeof(WNDCLASSEX); windowClass cbSize = CS_HREDRAW | CS_VREDRAW; w indowClass. s ty le = WndProc; w indow C lass. IpfnWndProc = 0; i windowClass. cb C ls E xtra w indow C lass. cbWndExtra = 0; = h ln s ta n c e ; windowClass. h ln s ta n c e = LoadIcon(NULL, IDI_APPLICATION); w indow C lass.hlcon
/ / domyślna ikona
466
Część III ♦ Tworzymy grę
w indow C lass.hC ursor = LoadCursor(NULL, IDC_ARR0W); windowClass.hbrBackground = NULL; w indow C lass. IpszMenuName = NULL; windowClass.lpszClassName = "M o ja K la sa "; windowClass.hlconSm = LoadIcon(NULL, IDI_WINL0G0);
/ / domyślny k u rs o r / / bez t ł a / / bez menu / / logo Windows
/ / r e je s t r u je k la sę okna i f ( ! R egisterC lassEx(& w indow C la ss)) re tu rn 0; i f ( fu llS c re e n )
/ / tr y b pełnoekranowy?
DEVMODE dm S creenSettings; / / tr y b urządzenia memset( &dm ScreenSettings, 0 , s i z e o f( dm ScreenSetti ngs) ) ; dm ScreenSettings.dm Size = s iz e o f(d m S c re e n S e ttin g s ); dm ScreenSettings.dm PelsW idth = w id th ; / / szerokość ekranu dm S creenSettings.dm PelsH eight = h e ig h t; / / wysokość ekranu dm S creenSettings.dm BitsPerPel = b it s ; / / b itó w na p ik s e l dm ScreenSetti n g s.dmFi elds=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
// i f (C hangeD isplayS ettings(& dm S creenS ettings, CDS_FULLSCREEN) != DISP CHANGE SUCCESSFUL) / / p rze łą c z e n ie try b u n ie pow iodło s ię , z powrotem tr y b okienkowy MessageBox(NULL, "D is p la y mode f a i le d " , NULL, MB_0K); fullScreen=FALSE;
f ( fu llS c re e n ) dwExSty1e=WS_EX_APPWINDOW; dwStyle=WS_POPUP; ShowCursor(FALSE);
/ / tr y b pełnoekranowy? / / rozszerzony s ty l okna / / s ty l okna / / ukrywa k u rs o r myszy
e ls e dwExSty1e=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; / / rozszerzony s ty l okna dwStyle=WS_OVERLAPPEDWINDOW; / / s ty l okna
/ / k o ry g u je rozm iar okna AdjustW indowRectEx(&windowRect, dw S tyle, FALSE, d w E xS tyle); / / tw o rzy okno hwnd = CreateWindowEx( NULL, "M ojaK lasa", "D ire c tX Audio, p rzykła d d ru g i: dźwięk i e fe k t dw Style | WS_CLI PCHILDREN |WS_CLIPSIBLINGS, 0,0, windowR ect. r ig h t - windowRect.1 e f t , windowR ect. bottom - w indow R ect.top, N U LL,// uchwyt okna nadrzędnego NULL, h ln s ta n c e , NULL);
/ / rozszerzony s ty l okna / / nazwa k la s y p rze s trz e n n y", / / nazwa a p lik a c ji / / w spółrzędne x ,y / / szerokość, wysokość / / uchwyt menu / / in s ta n c ja a p lik a c ji / / bez dodatkowych parametrów
Rozdział 1 7 . ♦ Zastosow ania D irectX Audio
467
/ / sprawdza, czy u tw o rze n ie okna n ie pow iodło s ię (wtedy w artość hwnd równa NULL) i f ( ! hwnd) re tu rn 0; / / in ic ju je COM i f (F A IL E D C C o ln itia lizeCNULL) ) ) re tu rn 0 ; / / j e ś l i in ic ja c ja COM n ie pow iodła s ię , / / program kończy d z ia ła n ie / / in ic ju je D ire c tX Audio i f ( ! In itD ire c tX A u d io (h w n d )) re tu rn 0; / / ła d u je segment i f ( !LoadSegment(hwnd, " s ta r t.w a v " ) ) re tu rn 0; / / k o n fig u ru je param etry e fe k tu przestrzennego Set3DSoundParams(0.0, O . lf , l. O f , 1 0 0 .O f); / / odtw arza segment PlaySegm ent(dm usicPerform ance, dmusicSegment); ShowWindow(hwnd, SW_SH0W); UpdateW indow(hwnd);
/ / w yś w ie tla okno / / a k tu a liz u je okno
done = fa ls e ; In itia liz e O ;
/ / in ic ju je zmienną warunku p ę t li / / in ic ju je OpenGL
•
/ / p ę tla p rze tw a rz a n ia komunikatów w h ile ( !done)
{ PeekMessage(&msg, hwnd, NULL, NULL, PM_REM0VE); i f (msg.message == WM_QUIT)
/ / a p lik a c ja otrzym a ła kom unikat WM_QUIT?
{ done = tr u e ;
/ / j e ś l i ta k , to kończy d z ia ła n ie
} e ls e
{ / / kończy d z ia ła n ie programu i f (keyPressed[VK_ESCAPE]) done = tr u e ; e ls e
{ R enderO ;
/ / ry s u je g r a fik ę
TranslateM essage(& m sg); Di spatchMessage(&msg);
/ / tłum aczy kom unikat i w ysyła do systemu
/ / kończy wykonanie CloseD ow n(dm usicPerform ance); / / zw a ln ia zasoby C leanU pO ;
468
Część III ♦ Tworzymy grę
/ / kończy pracę COM C o U n in itia liz e O ; i f (fu lIS c re e n )
{ C hangeD isplayS ettings(N U LL.O ); ShowCursor(TRUE);
/ / przywraca p u lp it / / i wskaźnik myszy
} re tu rn ntsg.wParam;
}
Podsumowanie Na tym można zakończyć omówienie możliwości DirectX Audio. W rozdziale tym nie zostały przedstawione niektóre możliwości DirectX Audio, takie jak dynamiczne kom ponowanie muzyki za pomocą DirectMusic oraz zastosowania efektów pogłosu, echa, chóru i zniekształceń dźwięku. Pominięte zostało także zagadnienie odtwarzania dźwięku w formacie MP3. Kompletny przykład implementacji odtwarzacza MP3 zawiera doku mentacja DirectShow. Komputery mogą przechowywać i odtwarzać dźwięk zapisany cyfrowo oraz uzyskiwany na drodze syntezy. Źródłem dźwięku zapisanego w postaci cyfrowej jest zwykle mikro fon. Dźwięk zapisany cyfrowo używany jest w grach przede wszystkim do uzyskania efektów dźwiękowych, takich jak eksplozje, odgłosy ruchu czy mowa. Natomiast dźwięk tworzony przez syntezator stanowi najczęściej podkład muzyczny gry. Komponent DirectX Audio umożliwia tworzenie dynamicznych ścieżek dźwiękowych wykorzystujących w pełni możliwości sprzętowe, w tym także efekt przestrzenny. Odtwarzanie dźwięku za pomocą DirectMusic wymaga zainicjowania modelu COM, utworzenia i zainicjowania obiektu wykonania, utworzenia obiektu ładującego, załado wania segmentu oraz instrumentów i odtworzenia segmentu. Ścieżki dźwięku zarządzają przepływem danych dźwięku poprzez różne obiekty na dro dze od pliku do kanałów wyjściowych. Ścieżka dźwięku może zawierać obiekt wyko nania, segment, grafy narzędzi, syntezator i bufory DirectSound. Efekt przestrzenny można łatwo uzyskać pobierając bufor DirectSound ścieżki dźwięku. Zastosowanie efektu przestrzennego jest jednym z ważniejszych sposobów podnoszenia realizmu gry.
Rozdział 18.
Modele trójwymiarowe Praktycznie każda współczesna gra przechowuje opis modeli trójwymiarowych postaci i innych obiektów w plikach o określonym formacie. Większość z tych gier umożliwia także użytkownikowi modyfikację wyglądu istniejących i tworzenie nowych postaci. W rozdziale tym przedstawione zostaną popularne formaty plików modeli trójwymia rowych MD2, które zostały zastosowane w grze „Quake 2”. Przedstawiony będzie także sposób ładowania takich modeli, wyświetlania ich i animowania we własnych grach. W rozdziale tym omówione zostaną następujące zagadnienia: ♦ formaty plików modeli trójwymiarowych; ♦ format MD2; ♦ ładowanie tekstur w formacie PCX.
Formaty plików modeli trójwymiarowych Umieszczanie definicji modeli trójwymiarowych bezpośrednio w kodzie źródłowym pro gramu nie jest dobrym rozwiązaniem. Ręczne wpisywanie danych modelu jest praco chłonne i łatwo przy tym o pomyłkę. Modyfikowanie modelu postaci lub dodanie nowej postaci wymaga za każdym razem ingerencji w kod źródłowy programu. Dlatego też le piej jest tworzyć modele trójwymiarowe za pomocą wyspecjalizowanych narzędzi (lub w najprostszym przypadku umieścić dane modelu w pliku tekstowym), następnie zapi sać je w pliku o określonym formacie, który będzie odczytywać później grę. Większość dostępnych na rynku gier korzysta z własnych, specjalizowanych formatów plików do przechowywania modeli obiektów. Proces ich powstawania ilustruje rysunek 18.1. Firmy produkujące gry zatrudniają własnych grafików, którzy tworzą modele po staci występujących w grach za pomocą takich narzędzi jak 2D Studio Max firmy Discreet, Lightwave firmy NewTek, trueSpace firmy Caligari czy Maya firmy Alias/Wavefront. Modele te następnie zapisywane są w specjalizowanym formacie używanym przez grę.
470
Część III ♦ Tworzymy grę
Rysunek 18.1. Obiekty gry ładowane są z plików modeli przygotowanych za pomocą odpowiednich narządzi
Książka ta nie zajmuje się problematyką tworzenia modeli. Bardziej interesujące jest ła dowanie, wyświetlanie i animacja gotowych modeli zapisanych w plikach. Formaty tych plików mogą być bardzo proste i sprowadzać się do opisu wierzchołków modelu w pliku tekstowym, a także bardzo skomplikowane i przyjmować postać plików binarnych za wierających informacje o wierzchołkach, teksturach i innych właściwościach modelu. Czas więc zapoznać się z formatem M D2 stosowanym w grze „Quake 2” firmy id Soft ware. Format ten posiada duże możliwości i jednocześnie jego użycie nie jest skompli kowane. Można go polecić jako wzór każdemu programiście, który zamierza opracować własny format modeli.
Format MD2 Strukturę plików formatu M D2 tworzą dwie części: nagłówek i właściwe dane. Nagłówek zawiera podstawowe informacje o rozmiarach modelu, takie jak na przykład liczba wierz chołków i trójkątów tworzących model oraz położenie opisujących je danych w pliku. Poniżej przedstawiona została struktura nagłówka: ty p e d e f s tr u c t
{ in t id e n t; i n t v e rs io n : in t s k in w id th : in t s k in h e ig h t; in t frames i z e ; in t numSkins: in t numXYZ: in t numST; in t num Tris: i n t numGLcmds; i n t numFrames; in t o ffs e tS k in s : in t o ffs e tS T : in t o ff s e tT r is : in t offse tF ra m e s ; in t offsetG Lcm ds: in t o ffs e tE n d : } m odelHeader_t;
// // // // // // // // // // // // // // // // //
"IDP2" oznacza fo rm a t MD2 obecnie w e rsja 8 szerokość te k s tu ry wysokość te k s tu ry ro zm ia r k la tk i ( w b a jta c h ) lic z b a te k s tu r lic z b a punktów lic z b a w spółrzędnych te k s tu ry lic z b a tró jk ą tó w lic z b a komend OpenGL c a łk o w ita lic z b a k la te k p o ło że n ie (w p lik u ) nazw te k s tu r (każda po 64 b a jty ) p o ło że n ie (w p lik u ) współrzędnych te k s tu ry p o ło że n ie (w p lik u ) s ia tk i tró jk ą tó w p o ło ż e n ie (w p lik u ) danych k la t k i (punktów) ty p używanych poleceń OpenGL koniec p lik u
Teraz należy przyjrzeć się bliżej polom tej struktury. Pierwsze z nich jest identyfikato rem formatu pliku. Ładując model z pliku należy więc sprawdzić, czy pole ident posia da wartość IDP2. Jeśli tak, to można kontynuować ładowanie modelu. W przeciwnym razie należy utworzyć komunikat o błędzie i poinformować użytkownika, że dane mo delu znajdują się w pliku nieznanego formatu.
Rozdział 1 8 . ♦ M odele trójw ym iarow e
471
Pola s k in w id th i s k in h e ig h t określają szerokość i wysokość tekstur, którymi będzie po krywany model. Pozwalają także wyznaczyć rozmiar tekstury w bajtach. Pole frames i ze informuje o rozmiarze klatki wyrażonym w bajtach. Klatki używane przez format MD2 reprezentują model w określonym momencie ruchu i stosowane są w celu uzyskania płynnego efektu animacji postaci. Animacje modeli formatu MD2 omówione zostaną wkrótce dokładniej, a na razie wystarczy zapamiętać, że pole frames i ze pozwala ustalić wielkość pamięci potrzebnej do przechowania danych klatki. Pole numSkins określa liczbę tekstur dostępnych dla danego modelu. Zwykle modele postaci w grze „Quake 2” przygotowywane są z zestawem tekstur, z których wybierać. może użytkownik. Pole to informuje o tym, ile tekstur zostanie wymienionych w pliku począwszy od pozycji o ffs e tS k in s . Chociaż sam pomysł wart jest uwagi, to jednak ła dując modele MD2 nie należy korzystać z informacji zawartej w polu numSkins , po nieważ nazwy tekstu zawierają ścieżki plików przydatne jedynie po zainstalowaniu gry „Quake 2”. Pole numXYZ specyfikuje całkowitą liczbę wierzchołków modelu, która stanowi sumę wierz chołków dla poszczególnych klatek. Gdy na przykład definicja modelu zawierać będzie 100 klatek po 100 wierzchołków każda, to pole numXYZ będzie miało wartość 10 000. Kolejne pole nagłówka, numST, zawiera liczbę współrzędnych tekstury, co pozwala okre ślić wielkość pamięci potrzebnej do przechowania współrzędnych tekstury. Pole num Tris specyfikuje liczbę trójkątów, z których składa się model. Im większa licz ba trójkątów, tym bardziej szczegółowy model, ale i mniejsza efektywność tworzenia grafiki. Wartość ta musi więc być wynikiem kompromisu pomiędzy szczegółowością grafiki, a minimalną wydajnością systemu. Pole numGLcmds określa liczbę komend OpenGL przechowywanych w pliku począwszy od pozycji offsetG Lcm ds. Przez komendy należy rozumieć tutaj liczby całkowite okre ślające sposób rysowania modelu. Na przykład dane pierwszych dziewięciu wierzchoł ków modelu mogą zostać wykorzystane do rysowania łańcucha trójkątów, jeśli komendą OpenGL będzie GL TRIANGLE STRIP. Kolejna z wartości umieszczonych w buforze o f f setGLcmds może reprezentować komendę GL TRIANGLE FAN, wobec czego kolejnych 20 wierzchołków będzie tworzyć wieniec trójkątów. Zastosowanie takiego sposobu two rzenia grafiki nie jest obligatoryjne, ale w pewnych przypadkach bardzo przydatne. Ostatnim z pól nagłówka określających rozmiary danych jest pole numFrames. Specyfi kuje ono liczbę klatek animacji, którymi dysponuje model. Są to tak zwane kluczowe ramki animacji, które określają wygląd modelu w określonych momentach animacji, co ilustruje rysunek 18.2. Rysunek 18.2. Kluczowe klatki animacji
472
Część III ♦ Tworzymy grę
Zastosowanie kluczowych klatek animacji pozwala istotnie zmniejszyć rozmiary pliku modelu, ponieważ nie musi on wtedy zawierać wszystkich klatek animacji. Aby uzy skać efekt płynnej animacji, trzeba jednak samodzielnie obliczyć kolejne pozycje mo delu pomiędzy kluczowymi klatkami animacji. Sposób wyznaczania tych pozycji omó wimy wkrótce. Ostatni blok pół nagłówka zawiera pola określające położenie w pliku różnych danych opisujących model. Pierwsze z nich, o ffs e tS k in s , wskazuje fragment pliku zawierający nazwy tekstur. Każda z nich zajmuje 64 bajty. Jeśli więc w pliku umieszczonych jest pięć tekstur, to pole o ffs e tS k in s wskazuje fragment pliku o rozmiarze 64 * 5 bajtów, czyli 320 bajtów. Pole o ffs e tS T określa położenie danych opisujących współrzędne tekstury. Ładując dane modelu trzeba odczytać numST współrzędnych tekstury. Pole o f f s e tT r is wskazuje dane siatki trójkątów tworzących model, a pole o ffse tF ra m e s położenie danych wierzchołków dla wszystkich klatek animacji. Ostatnie dwa pola, offsetGLcmds i o ffse tE n d , wskazują (odpowiednio) położenie komend OpenGL i końca pliku modelu.
Implementacja formatu MD2 Znając strukturę pliku w formacie MD2 można przystąpić do jego implementacji. Na początku trzeba utworzyć reprezentację podstawowego elementu opisu każdego modelu: wektora. W rzeczywistości przy tworzeniu gry dysponuje się już na tym etapie odpo wiednią reprezentacją wektora, ale tym razem przedmiotem zainteresowania jest stwo rzenie jedynie przykładu ilustrującego posługiwanie się formatem MD2. Poniższy fragment kodu definiuje typ v e c to r_ t wraz z kilkoma niezbędnymi operacjami na wektorach: // p o jed yn czy w ektor typ e d ef s tru c t
{ f lo a t p o in t [3]; } ve ctor_t; / / o d e jm o w a n ie w e k to ró w v e c t o r _ t o p e r a t o r - ( v e c t o r _ t a , v e c t o r _ t b)
{ v e c t o r _ t c; c .point[0] = a.point[0] - b .p oint[03; c . p o in t [ l ] = a .p o in t [ l] - b .p o in tf l] ; c .poi n t[2] = a .poi n t[2] - b .poi n t[2]; re tu rn c;
} / / m n o że n ie w e kto ró w v e c t o r _ t o p e r a t o r * ( f l o a t f , v e c t o r _ t b)
{ v e c t o r _ t c;
Rozdział 1 8 . ♦ M odele trójw ym iarow e
473
c . p o i n t [0 ] = f * b .poi n t [03; c . p o i n t [ l ] = f * b .poi n t [1]; c .poi n t [23 = f * b .poi n t [2]; return c;
i // d z ie le n ie wektorów vector_t operat or /(vector_t a, vector_t b)
{ vector_t c; c .poi n t [0] c .poi ntC l] c.point[2]
= a .poi n t [0] / b .poi n t [0]; = a .poi n t [1] / b .poi n t C l ] ; = a .poi n t [2] / b .poi n t [2];
return c;
} // dodawanie wektorów vector_t operat or +(vector_t a, vector_t b)
{ vector_t c; c . p o i n t [0 ] c .poi n t [1] c.point[2]
= a .poi n t [0] + b .poi n t [0]; = a .poi n t [1] + b .poi n t [1]; = a .poi n t [2] + b.p o in t[2]:
return c;
} Następnie należy zdefiniować strukturę do przechowywania pojedynczej współrzędnej tekstury oraz strukturę, która przechowywać będzie wartość indeksu współrzędnej tek stury w tablicy współrzędnych tekstur: // współrzędna t ekstury typedef s tr u c t .
{ f l o a t s; f lo a t t; } texCoord_t;
// współrzędna s // współrzędna t
// indeks współrzędnej tekstury typedef s tr u c t
{ short s; short t; } stlndex_t;
Kolejna z definiowanych struktur umożliwiać będzie przechowanie informacji o poje dynczym punkcie klatki animacji. Będzie ona zawierać wartość indeksu tablicy wekto rów normalnych oświetlenia. Indeks ten nie będzie na razie wykorzystywany, ale musi być uwzględniony, aby poprawnie załadować model zapisany w formacie MD2.
474
Część III ♦ Tworzymy grę
/ / pojedynczy punkt k la t k i a n im a c ji ty p e d e f s tr u c t
{ unsigned char v [ 3 ] ; unsigned char normal Index; } fram eP oin t j t ;
/ / in fo rm a c ja o punkcie / / p o le to n ie będzie używane
Struktura f ramePoi n t_ t będzie używana jako elementu struktury przechowującej infor macje o pojedynczej klatce animacji: / / in fo rm a c ja o p o je d yn cze j k la tc e a n im a cji ty p e d e f s tr u c t f lo a t s c a le [ 3 ]; f lo a t t r a n s ia t e [ 3 ] ; char name[16]; fra m e P o in tjt f p [ l ] ; frame t ;
// // // //
skalow anie w ierzchołków k la t k i p rz e s u n ię c ie w ie rzch o łkó w k la t k i nazwa modelu początek l i s t y w ie rzch o łkó w k la t k i
Ładując model z pliku w formacie MD2 trzeba wyznaczyć położenie danych opisują cych wierzchołki klatki korzystając z informacji zawartej w polach scale i translate struktury frame t . Pole fp określa natomiast położenie bufora zawierającego opis wszyst kich wierzchołków. Ponieważ siatka modelu w formacie MD2 zbudowana jest z samych trójkątów, logicznym rozwiązaniem będzie utworzenie listy trójkątów modelu. Dysponując taką listą można następnie określić strukturę modelu za pomocą listy indeksów trójkątów. Poniższa struktura przechowywać będzie indeksy trzech wierzchołków trójkąta (meshIndex) oraz indeksy trzech współrzędnych tekstury pokrywającej trójkąt ( s tIndex): I I dane o p is u ją c e pojedynczy t r ó jk ą t ty p e d e f s tr u c t
unsigned s h o rt m e s h ln d e x [3 ]; I l indeksy w ierzchołków unsigned s h o rt s tln d e x [3 ]; I l indeksy w spółrzędnych te k s tu ry mesh t ;
Wszystkie zdefiniowane dotąd struktury łączy się w jedną całość za pomocą struktury definiującej model: / / dane modelu ty p e d e f s tr u c t in t numFrames; in t numPoints; in t num Triangles; in t numST; in t fram eS ize; in t te x W id th , te x H e ig h t; in t currentF ram e; in t nextFrame; f lo a t in t e r p o l; m eshjt * t r i Index; te x C o o rd jt * s t ; v e c to rj t * p o in tl_ is t; te x t u r e jt *modelTex; model Data t ;
/ / / / / / / / / / / / /
lic z b a k la te k lic z b a punktów lic z b a tró jk ą tó w lic z b a te k s tu r ro zm ia r k la tk i w b a jta c h szerokość i wysokość te k s tu ry bieżąca k la tk a a n im a cji k o le jn a k la tk a a n im a cji w spółczynnik in t e r p o la c ji li s t a tró jk ą tó w li s t a w spółrzędnych te k s tu ry li s t a w ierzchołków dane te k s tu ry
Rozdział 1 8 . ♦ M odele trójw ym iarow e
475
Struktura ta zawiera kilka pól odpowiadających bezpośrednio polom nagłówka pliku w formacie MD2, takim jak na przykład liczba klatek, wierzchołków, współrzędnych tek stury i trójkątów oraz rozmiar klatki w bajtach. Przechowuje także wartości wykorzy stywane w procesie animacji, takie jak identyfikator bieżącej klatki kluczowej, identyfi kator kolejnej klatki kluczowej oraz sposób interpolacji pomiędzy tymi klatkami. Ostatnie pola struktury model Data t są wskaźnikami list trójkątów, współrzędnych tek stury, wierzchołków oraz danych bieżącej tekstury modelu. Dane tekstury przechowywane są za pomocą struktury te x tu re _ t zdefiniowanej jak poniżej: enum texT ype s_ t
{
PCX, BMP, TGA
ty p e d e f s tr u c t
{
texT yp es_t te x tu re T y p e ;
/ / ty p p lik u te k s tu ry
in t w id th ; in t h e ig h t; long in t sca le d W id th ; long i n t sca le d H e ig h t;
/ / szerokość te k s tu ry / / wysokość te k s tu ry
unsigned i n t te x ID ; unsigned char *d a ta ; unsigned char * p a le tte ; } te x tu r e _ t;
/ / id e n t y f ik a t o r o b ie k tu te k s tu ry / / dane te k s tu ry / / p a le ta te k s tu ry ( j e ś l i is t n ie je )
Rysunek 18.3 ilustruje ogólną architekturę reprezentacji modelu MD2. Rysunek 18.3.
Struktury reprezentujące model MD2
Rysunek 18.4 pokazuje sposób korzystania ze struktury model Data t w celu uzyskania współrzędnych wierzchołków i współrzędnych tekstury. Dysponując strukturą model D ata_t posiada się: ł
listę trójkątów wskazywaną przez pole t r i Index;
♦ listę współrzędnych wierzchołków wskazywaną przez pole p o in tL is t; ♦ listę współrzędnych tekstury wskazywaną przez pole s t .
476
Część III ♦ Tworzymy grę
modelData_t wielkość klatki w bajtach liczba tekstur liczba wierzchołków liczba trójkątów liczba klatek dane siatki trójkątów dane wierzchołków (wszystkich klatek) współrzędne tekstury (wszystkich klatek) dane tekstury
Trójkąt (trilndex[index])
Współrzędne wierzchołków (pointList)
meshlndex[0] (pointl) meshlndex[1] (point2)
Lista trójkątów (*trilndex)
meshlndex[2] (point3)
point[0] = 0 .0 point[1] = 0 .0 point[2] = 0 .0
100
i vector_t 2 '
101
! vector_t I— fi
102
; vector_t
point[0] = 1 0 .0 i ■point[1] = 0 .0 | point[2] = 0 .0 poinłtO] = 5 .0 p o in t[lj = 5 .0 point[2] = 0 .0
103
trilndex[36] trilndex[37]
Współrzędne tekstury (st)
trilndex[38]
stlndex[0] (pointl) stlndex[1] (point2) stlndex[2] (point3)
99 --------------------
'
s = 0 12 t = 0 .4 5
100 |texCoord_tp| 101
texCoordji— »“
s = 0 .2 0 t =0 .4 0
102 tex C o o rd Jk j 103
s =0 .1 4 t =0 .4 3
Rysunek 18.4. Sposób korzystania z reprezentacji modelu MD2
Przeglądając listę trójkątów można uzyskać dla każdego trójkąta wartości indeksów współrzędnych trzech wierzchołków i ich współrzędnych tekstury. Indeksy te umożli wiają odszukanie współrzędnych wierzchołków na liście p o i n t L i s t i współrzędnych tek stury na liście s t . Informacje te umożliwiają narysowanie bieżącego trójkąta. Rysując w ten sposób wszystkie trójkąty z listy t r i I n d e x uzyskuje się pełen model. Ponieważ znany już jest sposób korzystania ze struktur reprezentujących model, można zająć się teraz implementacją działających na nich funkcji.
Ładowanie modelu MD2 Oczywiście pierwszą operacją, którą trzeba zaimplementować, aby móc korzystać z modelu MD2, jest jego załadowanie z pliku. Poniżej zaprezentowany został kod funkcji L o a d M D 2 M o d e l ( ) , której przekazane zostały nazwy plików zawierających model i teksturę. Funkcja LoadMD2M odel () utworzy struktury reprezentujące model: m odelData_t *LoadMD2Model(char *file n a m e , char *textureN am e) FILE * f i l e P t r ; in t file L e n ; char * b u ffe r ;
/ / wskaźnik p lik u / / długość p lik u / / b u fo r p lik u
Rozdział 1 8 . ♦ M odele trójw ym iarow e
modelData_t *m o d e l; modelHeader_t *m odelHeader; te x t u re _ t *m d2Texture;
/ / model / / nagłówek modelu / / te k s tu ra modelu
s tln d e x _ t * s t P t r ; fram e_t *fra m e ; v e c to r_ t * p o in t L is t P t r ; mesh_t * t r i Index, *b u fIn d e x P tr; in t i , j ;
// // // // //
477
dane te k s tu ry dane k la tk i zmienna indeksu zmienne indeksu zmienne indeksu
Większość z pokazanych wyżej zmiennych lokalnych służy do przechowania danych, zanim zostaną umieszczone w strukturze m odel zwracanej przez funkcję. Następnie funkcja otwiera plik i ustala jego długość, co pozwala przydzielić wystar czający obszar pamięci do załadowania całej zawartości pliku: / / o tw ie ra p lik modelu f i l e P t r = fo p e n (file n a m e , " r b " ); i f ( f i l e P t r == NULL) re tu rn NULL; / / u s ta la długość p lik u fs e e k ( f ile P t r . 0, SEEKJND); file L e n = f t e l l ( f i l e P t r ) ; f s e e k ( f ile P t r , 0, SEEK_SET); / / w c z y tu je c a ły p lik do b u fo ra b u ffe r = ( c h a r * ) m a llo c (file L e n + 1 ); fr e a d ( b u ffe r , s iz e o f( c h a r ) , file L e n , f i l e P t r ) ;
Wyodrębnienie w buforze fragmentu reprezentującego nagłówek pliku pozwala określić atrybuty modelu. Funkcja przydziela talcże pamięć na dane modelu: / / w yodrębnia nagłówek modelHeader = (m o d e lH e a d e rjt*)b u ffe r; / / p rz y d z ie la pamięć na dane modelu model = (m o d e lD a ta _ t*)m a l1o c ( s iz e o f(m o d e lD a ta _ t)); i f (model == NULL) re tu rn NULL;
Korzystając z informacji zawartych w nagłówku funkcja wyznacza wielkość obszaru pamięci potrzebnego do przechowania informacji o wierzchołkach modelu. W tym celu mnoży liczbę wierzchołków modelu o liczbę klatek animacji. Pole p o in t L is t będzie wska zywać obszar pamięci, w którym zostanie umieszczona informacja o wszystkich wierz chołkach modelu. Po przydzieleniu pamięci na listę wierzchołków zapamiętywana jest liczba wierzchoł ków, klatek i wielkość klatki: / / p rz y d z ie la pamięć d la w sz y stkic h w ierzchołków w sz y stkic h k la te k a n im a cji m o d e l-> p o in tL is t = (ve cto r_ t*)m a llo c(s ize o f(v e c to r_ t)*m o d e lH e a d e r-> n u m X Y Z * modelHeader->numFrames); / / przechowuje podstawowe dane o modelu m odel->num Points = modelHeader->numXYZ; / / lic z b a w ierzchołków model->numFrames = modelHeader->numFrames; / / lic z b a k la te k m odel->fram eS ize = m odelH eader->fram esize; / / w ie lk o ść k la tk i
478
Część III ♦ Tworzymy grę
Następnie funkcja LoadMD2Model () ładuje wierzchołki modelu do pamięci przeglądając w pętli kolejne klatki animacji, wyznaczając położenie danych wierzchołków i określając położenie każdego wierzchołka korzystając przy tym z wartości współczynników ska lowania i przesunięcia dla każdej klatki: / / przegląda k la tk i a n im a cji f o r ( j = 0; j < modelHeader->numFrames; j+ + )
{
/ / początek o p isu punktów danej k la tk i frame = (fra m e _ t*)& b u ffe r[m o d e lH e a d e r-> o ffse tF ra m e s + m odelH eader->fram esize * j ] ; / / wyznacza p o ło ż e n ie o p isu k o le jn y c h punktów p o in t L is t P t r = (vector_t*)& m odel->pointList[m odelH eader->num X Y Z * j ] ; f o r ( i = 0; i < modelHeader->numXYZ; i+ + )
{ p o in t L is t P t r [ i] . p o in t[0 ] = fra m e -> s c a le [0 ] * fr a m e - > fp [i] . v [0 ] + fra m e -> tra n s la te [0 ]; poi n tL i s t P t r [ i ] . poi n t [1 ] = fra m e -> s c a le [l] * fr a m e - > fp [i] . v [ l ] + fr a m e - > tr a n s la te [l]; poi n tL i s t P t r [ i ] . poi n t [2 ] = fra m e -> s c a le [2 ] * fr a m e - > fp [i] . v [2 ] + fr a m e -> tra n s la te [2 ];
} } Po załadowaniu współrzędnych wierzchołków wszystkich klatek funkcja ładuje teksturę modelu do bufora md2Texture. Po sprawdzeniu, czy plik zawierający teksturę został od naleziony, a jego zawartość poprawnie załadowana, funkcja umieszcza w polu model Tex adres bufora md2Texture. / / ła d u je te k s tu rę modelu md2Texture = L o a d T exture(textureN am e); i f (md2Texture != NULL)
{ / / k o n fig u ru je te k s tu rę OpenGL SetupM D2Texture(m d2Texture); model->modelTex = md2Texture;
}
e ls e re tu rn NULL;
Następnie przydziela pamięć niezbędną do przechowania współrzędnych tekstury i za pamiętuje ich liczbę: / / p rz y d z ie la pamięć na współrzędne te k s tu ry m odel-> st = (te x C o o rd _ t*)m a llo c(s ize o f(te x C o o rd _ t)*m o d e lH e a d e r-> n u m S T ); / / zapam iętuje lic z b ę współrzędnych te k s tu ry model->numST = modelHeader->numST;
Współrzędne tekstury pobierane są w pętli i umieszczane w strukturach modelu: / / w skaźnik b u fo ra w spółrzędnych te k s tu ry s tP tr = (s tIn d e x _ t*)& b u ffe r[m o d e lH e a d e r-> o ffs e tS T ]; / / za pam iętuje współrzędne te k s tu ry fo r ( i = 0 ; i < modelHeader->numST; i+ + )
Rozdział 1 8 . ♦ M odele trójw ym iarow e
479
{ m o d e l- > s t[i].s = ( f l o a t ) s t P t r [ i ] . s / (fło a t)m d 2 T e x tu re -> w id th ; m o d e l- > s t[i] . t = ( f l o a t ) s t P t r [ i ] . t / (flo a t)m d 2 T e x tu re -> h e ig h t;
} Kolejną operacją jest przydzielenie obszaru pamięci na dane trójkątów, zapamiętanie ich całkowitej liczby i zainicjowanie tymczasowego wskaźnika bufora trójkątów: / / p rz y d z ie la pamięć l iś c ie tró jk ą tó w t r i Index = (m e s h _ t*)m a llo c (s iz e o f(m e s h _ t) * m odelH eader->num Tris); / / z a p am iętu je lic z b ę tró jk ą tó w m odel->num Triangles = m odelHeader->numTris; m o d e l- > tr iIndex = t r i Index; / / tymczasowy w skaźnik b u fo ra tró jk ą tó w b u fln d e x P tr = (m e s h _ t*)& b u ffe r[m o d e lH e a d e r-> o ffs e tT ris ];
Po przydzieleniu pamięci funkcja ładuje do niej dane trójkątów z bufora pliku przeglą dając w pętli trójkąty wszystkich klatek animacji i umieszczając indeksy ich wierzchoł ków i indeksy współrzędnych tekstury w strukturach modelu: / / w yp e łn ia l i s t ę tró jk ą tó w f o r ( j = 0; j < model->numFrames; j+ + )
{ / / d la w s z y s tk ic h tró jk ą tó w danej k la tk i fo r ( i = 0; i < model Fleader->numTris; i+ + )
{
tr iIn d e x [i].m e s h ln d e x [0 ] tr iI n d e x [ i]. m e s h ln d e x [ l] tr iIn d e x [i].m e s h ln d e x [2 ] t r iI n d e x [ i] . s t ln d e x [ 0 ] = tr iln d e x [i].s tln d e x [l] = t r iI n d e x [ i] . s t ln d e x [ 2 ] =
= b u fIn d e x P tr[i].m e s h ln d e x [0 ]; = b u fIn d e x P tr [i].m e s h ln d e x [l]; = b u fIn d e x P tr[i],m e s h ln d e x [2 ]; b u fIn d e x P tr [i] .s t ln d e x [ 0 ]; b u f I n d e x P t r [ i] . s t I n d e x [ l] ; b u fIn d e x P tr[ i ] . s tln d e x [2 ];
} } W ten sposób funkcja LoadMD2Model () załadowała wszystkie dane modelu. Może teraz zamknąć plik i zwolnić bufor, w którym umieściła jego zawartość, a także zainicjować zmienne modelu wykorzystywane podczas animacji: / / zamyka p lik i zw a ln ia je g o b u fo r fc lo s e ( f ile P tr ) ; fr e e ( b u f f e r ); / / in ic ju je zmienne a n im a cji m odel->currentFram e = 0 ; m o de l-> n e xtFrame = 1; m o d e l-> in te rp o l = 0 . 0 ; re tu rn m odel;
} W jaki sposób można używać tej funkcji w opracowywanych właśnie programach? Trzeba najpierw zadeklarować wskaźnik modelu: m odelD ata_t *myModel;
480
Część III ♦ Tworzymy grę
Zakładając, że model znajduje się w pliku o nazwie mymodelmd2, jego tekstura w pliku mymodel.pcx, a oba pliki znajdują się w katalogu bieżącym, wystarczy w celu załadowa nia modelu wywołać funkcję LoadMD2Model () w następujący sposób: myModel = LoadMD2Model("mymodel.md2", "m ym odel.pcx");
W ten sposób model zostaje załadowany i jest gotowy do użycia.
Wyświetlanie modelu MD2 Po załadowaniu modelu można wykorzystać go w różny sposób. Na razie analizie będzie poddane jedynie jego wyświetlenie, a animacja zostanie pozostawiona na później. Wyświetlenie modelu wymaga wybrania konkretnej klatki animacji, a następnie przej rzenia w pętli wszystkich jej trójkątów i wykorzystania zawartych w nich indeksów wierzchołków. Zadanie to wykonuje funkcja Di spl ayMD2(): v o id DisplayMD2(modelData_t *m o d e l. in t frameNum)
{ v e c to r_ t * p o in tl_ is t; in t i;
/ / l i s t a w ie rzch o łkó w danej k la t k i / / zmienna indeksu
/ / in ic ju je wskaźnik l i s t y w ierzchołków p o in tL is t = & m odel->pointl_ist[m odel ->numPoints * frameNum]; / / w ybiera te k s tu rę glBindTexture(GL_TEXTURE_2D, m odel-> m odelT ex-> texID ); / / w y ś w ie tla model za pomocą tró jk ą tó w w ypełnionych jednym kolorem glBegin(GL_TRIANGLES); f o r ( i = 0; i < model->num Trian g le s; i+ + )
{ g lV e r te x 3 fv ( p o in tL is t[m o d e l- > tr iIn d e x [i].m e s h ln d e x [0 ]].p o in t) ; gl V e r te x 3 fv ( p o in tL is t[m o d e l- > tr iIn d e x [i].m e s h ln d e x [2 ]].p o in t) ; g lV e r te x 3 fv ( p o in tL is t[m o d e l- > tr iIn d e x [i] .m e s h ln d e x [l]].p o in t) ;
} glEndO;
} Parametrami tej funkcji jest model i identyfikator tej jego klatki, która ma być naryso wana. Korzystając z parametru frameNum funkcja wyznacza położenie wierzchołków da nej klatki na liście wierzchołków. Powyższa wersja funkcji DisplayMD2() wyświetla model jedynie za pomocą trójkątów wypełnionych pojedynczym kolorem. W pętli prze gląda kolejne trójkąty i na podstawie zawartych w nich indeksów wierzchołków pobiera odpowiednie wierzchołki i rysuje je. Poniższe wywołanie funkcji DisplayMD2() powo duje narysowanie pierwszej klatki modelu myModel: DisplayMD2(myModel, 0 );
Rysunek 18.5 ilustruje efekt jej wykonania dla modelu jednej z postaci gry Quake 2.
Rozdział 1 8 . ♦ M odele trójw ym iarow e
481
Rysunek 18.5. Efekt wywołania pierwszej wersji funkcji DisplayMD2()
Oczywiście rezultat pokazany na rysunku 18.5 nie jest zadawalający. Aby uzyskać bar dziej realistyczny wygląd modelu, należy wzbogacić go o oświetlenie. W tym celu na leży wyznaczyć wektor normalny do każdego z trójkątów modelu. Wykorzystać można do tego funkcję Cal cul ateNormal () zdefiniowaną jak poniżej: v o id C a lcu la te N o rm a l( f l o a t * p l, f l o a t *p2, f lo a t *p3 )
{ f l o a t a[ 3 ] . b [ 3 ] , r e s u lt [ 3 ] ; f l o a t le n g th ; a[0] = pl[0] - p2[0]; a[1] = p l[l] - p 2 [l]; a[2] = p l[2] - p2[2]; b[0] bCl] b[2]
= p l[0 ] - p 3 [ 0 ] ; = p l[l] - p 3 [l]; = p l [2] - p 3 [ 2 ] ;
r e s u lt [ 0 ] = a [1 ] * b [2 ] - b [ l ] * a[2 ] ; r e s u l t [ l ] = b [0 ] * a [2 ] - a [0 ] * b [2 ]; r e s u lt [21 = a [0 ] * b [ l ] - b [0 ] * a [ l ] ; / / wyznacza długość w ektora normalnego le n g th = ( f lo a t ) s q r t ( r e s u lt [ 0 ] * r e s u lt [ 0 ] + r e s u l t [ l ] * r e s u l t [ l ] + r e s u l t [ 2 ] * r e s u lt [ 2 ] ) ; / / i n o rm a liz u je go g l Normal3 f ( r e s u lt [ 0 ] / le n g t h . r e s u lt [ l ] / l e n g t h . r e s u lt [ 2 ] / le n g t h ) ;
} Funkcję tę wykorzystać można także w kolejnej wersji funkcji Di spl ayMD2() do wyzna czenia wektora normalnego do trójkąta przed narysowaniem jego wierzchołków: g l B e g in (GL JTRIANGLES); f o r ( i = 0; i < m odel->num Tria ngles; i+ + )
482
Część III ♦ Tworzymy grę
{ CalculateNormal(poi ntList[model->triIndex[i].meshlndex[0]] . poi n t. poi ntLi st[model->triIndex[i] .meshlndex[2]].poi n t. pointList[model->triIndex[i] .meshlndex[l]].point); glVertex3fv(pointList[model->triIndex[i] .meshlndex[0]].point); g!Vertex3fv(pointList[model->trilndex[i].meshlndex[2]].point); glVertex3fv(pointList[model->trilndex[i].meshlndex[l]].point);
} glEndO ;
Aby jeszcze podnieść jego realizm, w kolejnej wersji funkcji D is p la y M D 2 ( ) wzbogacony zostanie o teksturę.
Pokrywanie modelu teksturą Model MD2 pokrywa się teksturą w taki sam sposób jak każdy inny obiekt. Najpierw ładuje się teksturę, konfiguruje ją w OpenGL i wybiera obiekt tekstury przed określe niem współrzędnych tekstury dla każdego z rysowanych wierzchołków. Współrzędne tekstury można pobrać wykorzystując w tym celu indeksy zawarte w trójkątach modelu (podobnie jak w przypadku współrzędnych wierzchołków). Większość modeli dostarczanych wraz z grami posiada tekstury umieszczone w plikach formatu PCX. Nie trzeba tutaj omawiać szczegółowo formatu plików PCX9 ale można przedstawić fragmenty kodu służące ładowaniu ich zawartości jako tekstur OpenGL. Przy tworzeniu własnych modeli można korzystać oczywiście z innych formatów pli ków zawierających tekstury. Mogą to być na przykład pliki formatów BMP lub TGA, które zostały omówione szczegółowo we wcześniejszych rozdziałach. Jeśli założy się, że tekstura została już załadowana z pliku i jej dane umieszczone są w strukturach reprezentujących model, to aby z niej skorzystać, trzeba zmodyfikować
Rozdział 1 8 . ♦ M odele trójw ym iarow e
483
funkcję D i s p l ayM D 2(). Należy wybrać obiekt tekstury i zdefiniować współrzędne tekstury dla rysowanych wierzchołków: v o id DisplayMD2(modelData_t *model, in t frameNum)
{ v e c to r_ t * p o in t lis t ; in t i ;
// l i s t a w ierzchołków danej k la t k i // zmienna indeksu
/ / i n ic j u j e w skaźnik l i s t y w ierzchołków p o in t L is t = &m odel->pointList[model->num Points * frameNum]; / / w ybiera te k s tu rę glBindTexture(GL_TEXTURE_2D, model->modelTex->texID); / / w yśw ie tla model pokryty te k s tu rą i o św ie tlon y glBegin(GL_TRIANGLES); f o r ( i = 0 ; i < model ->numTri angles; i++)
{ Cal cul ateNorm al(poi n tL is t[ m o d e l- > triI n d e x [ i] .m eshlndex[0]] . poi n t . poi n tL i s t[ m o d e l-> triIn d e x [ i] .m eshlndex[2]] . poi n t . p o in t L is t [ m o d e l- > t riln d e x [ i] .m e s h ln d e x [ l] ] .p o in t ) ; / / d e f in iu je współrzędne te k s tu ry i ry s u je w ie rzc h o łk i g lT e xC o o rd 2 f(m o d el-> st[m o d e l-> triIn d e x[i] . s t ln d e x [ 0 ] ] .s . m o d e l-> st[m o d e l-> triIn d e x [i] . s t ln d e x [ 0 ] ] . t ) ; g lV e rte x 3 fv (p o in tL is t[ m o d e l-> triIn d e x [ i] .m e s h ln d e x [ 0 ] ].p o in t) ; glT e xC o o rd 2 f(m o d el-> st[m o d e l-> triIn d e x[i] . s t ln d e x [ 2 ] ] . s . m o d e l-> st[m o d e l-> triIn d ex [i] . s tIn d e x [2 ]] . t ); gl V e r te x 3 fv ( p o in tL is t[ m o d e l- > triI n d e x [ i] .m eshlndex[2]]. p o in t ) ; glT e xC o o rd 2 f(m o d el-> st[m o d e l-> triIn d e x[i] . s t ln d e x [ l] ] . s , m o d e l-> st[m o d e l-> triIn d ex [i] . s t ln d e x [ l] ] . t ) ; gl V e rte x 3 fv (p o in tL i s t[ m o d e l-> triIn d e x [ i] .m e sh ln d e x [l]]. p o in t ) ;
} gl E n d O ;
} Jak łatwo zauważyć, podobnie jak było w przypadku współrzędnych wierzchołków, także i teraz współrzędne tekstury uzyskuje się za pomocą wartości indeksów przechowanych w strukturach opisujących trójkąty (patrz: rysunek 18.4). Rezultat uzyskany w wyniku wywołania nowej wersji funkcji D is p la y M D 2 () prezentuje rysunek 18.7. W ten sposób uzyskany został kompletny wizerunek modelu, pokryty teksturą i oświe tlony. Ma on jednak dość poważną wadę: jest statyczny. Aby tchnąć w niego życie, trzeba wykorzystać metodą interpolacji kluczowych klatek animacji.
Animacja modelu Przy definiowaniu struktury model Data t reprezentującej model umieszczone w niej zo stały pola currentFrame, nextFrame i in ter poi, które wykorzystane zostaną podczas animacji modelu. Animacja modelu z pewnością jest najważniejszym ze środków pod noszących realizm postaci.
484
Część III ♦ Tworzymy grę
Rysunek 18.7. Model MD2 pokryty teksturą
W podrozdziale tym omówiona zostanie metoda wykorzystująca interpolację klatek. Ponieważ model w formacie MD2 zawiera jedynie kluczowe klatki animacji, to aby uzyskać efekt płynnej animacji, konieczna jest interpolacja wyglądu modelu pomiędzy tymi klatkami. Metoda ta znana jest także pod nazw\morfingu. Załóżmy, że model zawiera klatki animacji pocisku, który przemieszcza się z punktu (x0, yO, zO) do (xl, yl, zl). Twórca modelu mógł nawet umieścić w nim tylko dwie kluczowe klatki animacji: jedną przedstawiającą pocisk w punkcie (x0, yO, zO) i drugą w punkcie (xl, yl, zl). Kolejnym zadaniem będzie więc wyznaczenie kilku położeń pocisku na drodze z punktu (x0, yO, zO) do (xl, yl, zl) w taki sposób, aby można było uzyskać wrażenie płynnej animacji. Sposób wyznaczania tych położeń prezentuje rysunek 18.8. Rysunek 18.8. Zastosowanie interpolacji do wyznaczenia klatek animacji pomiędzy klatkami kluczowymi
H bieżąca klatka kluczowa 0%
75%
_ D następna klatka kluczowa 100 %
Metoda interpolacji klatek polega na wyznaczeniu położeń wierzchołków klatki pośred niej na podstawie bieżącej i docelowej klatki kluczowej oraz współczynnika określają cego w procentach położenie klatki pośredniej na drodze pomiędzy klatkami kluczo wymi. Wszystkie informacje potrzebne do wykonania tej operacji umieszczone są już w strukturze model D ata_t i zapisane w polach currentFram e, nextFrame i in te r p o l. Pola currentFram e i nextFrame określają klatki kluczowe, pomiędzy którymi dokonuje się interpolacji. Pole in te rp o l określa w procentach położenie klatki pośredniej na dro dze pomiędzy tymi klatkami. Jeśli na przykład wierzchołek bieżącej klatki kluczowej posiada współrzędną x równą 0. 0, pole i n te rp o l posiada wartość 0 . 5, a ten sam wierz chołek modelu w kolejnej klatce kluczowej posiada współrzędnąx równą 10 . 0, to jego współrzędna x w klatce pośredniej uzyskana na drodze interpolacji wynosić będzie 5 . 0.
Rozdział 1 8 . ♦ M odele trójw ym iarow e
485
Aby wyznaczyć współrzędne wierzchołków w metodzie interpolacji klatek, można po służyć się więc następującym wzorem: Xi + in te rp o la te P e rc e n ta g e * (Xf - Xi)
Xi reprezentuje w nim położenie wierzchołka w bieżącej klatce kluczowej, a Xf w kolej nej takiej klatce. Parametr in te rp o la te P e rc e n ta g e określa położenie rysowanej klatki pośredniej pomiędzy obiema klatkami kluczowymi. Rysunek 18.9 ilustruje znaczenie wszystkich parametrów tego wzoru.
y
Rysunek 18.9. Interpolacja położenia
interpolatePercentage = procent odległości od Vi do
wierzchołka
Początkowe położenie wierzchołka in te rp o l >= 1 .0 )
{
m o d e l-> in te rp o l = O.Of; / / z e ru je w spółczynnik in te r p o la c ji m odel->currentFram e++; / / zwiększa numer b ie ż ą ce j k la tk i i f (m odel->currentFram e >= model->numFrames) m odel->currentFram e = 0; model->nextFrame = m odel->currentFram e + 1; i f (m odel->nextFram e >= model->numFrames) model->nextFrame = 0;
} //' l i s t a w ie rzch o łkó w b ie ż ą c e j k la tk i p o in t L is t = & m o d e l-> p o in tL ist[m o d e l-> n u m P o in ts*m o d e l-> cu rre n tF ra m e ]; / / l i s t a w ie rzch o łkó w następnej k la tk i n e x tP o in tL is t = & m o d e l-> p o in tL ist[m o d e l-> n u m P o in ts*m o d e l-> n e xtF ram e ]; / / k o n fig u ru je te k s tu rę i rozpoczyna rysow anie tró jk ą tó w gl BindTexture(GL_TEXTURE_2D. m odel-> m odelTex-> texID ); glBegin(GL_TRIANGLES); f o r ( i = 0 ; i < m odel->num Tria n g le s; i+ + )
486
Część III ♦ Tworzymy grę
{ // xl yl zl x2 y2 z2
współrzędne pierwszego w ie rz c h o łka tr ó jk ą ta w obu kluczowych k la tk a c h = poi n tL i s t[m o d e l- > tr iIn d e x [i] . m e sh ln d e x[0 ]] . poi n t [0 ] ; = p o in tL is t [m o d e l- > tr iI n d e x [ i]. m e s h In d e x [0 ] ]. p o in t[ l]; = p o in tL is t[m o d e l- > tr iIn d e x [i].m e s h In d e x [0 ]].p o in t[2 ]; = n e x tP o in tL is t[m o d e l- > tr iIn d e x [i].m e s h In d e x [0 ]].p o in t[0 ]; = n e x tP o in tL is t[m o d e l- > tr iIn d e x [i].m e s h In d e x [0 ]].p o in t[l]; = n e x tP o in tL is t[m o d e l- > tr iIn d e x [i].m e s h ln d e x [0 ]]. poi n t [ 2 ] ;
/ / x i + percentage * ( x f - x i) / / in te rp o lo w a n e w spółrzędne pierwszego w ie rz c h o łka v e r t e x [ 0 ] . p o in t[0 ] = x l + m o d e l-> in te rp o l * (x2 - x l ) ; v e r t e x [ 0 ]. p o in t[1 ] = y l + m o d e l-> in te rp o l * (y2 - y l ) ; v e r t e x [ 0 ] . poi n t [2 ] = z l + m o d e l-> in te rp o l * (z2 - z l ) ; // xl yl zl x2 y2 z2
w spółrzędne dru g ie g o w ie rz c h o łka tr ó jk ą ta = p o in t L is t [m o d e l- > tr iI n d e x [ i] .m e s h ln d e x [2 ]]. poi n t [0 ] ; = p o in tL is t [m o d e l- > tr iI n d e x [ i]. m e s h In d e x [2 ] ]. p o in t[ l]; = poi n tL i s t[m o d e l- > t r iIn d e x [i] .m e s h ln d e x [2 ]]. poi n t [ 2 ] ; = n e x tP o in tL is t [m o d e l- > tr iIn d e x [i].m e s h ln d e x [2 ]].p o in t[ 0 ] ; = n e x tP o in tL is t[m o d e l- > tr iIn d e x [i],m e s h ln d e x [2 ]]. poi n t [ 1 ] ; = n e x tP o in tL is t[m o d e l- > tr iIn d e x [i].m e s h In d e x [2 ]].p o in t[2 ];
/ / in te rp o lo w a n e w spółrzędne d rugiego w ie rz c h o łka v e r t e x [ 2 ]. p o in t[0 ] = x l + m o d e l-> in te rp o l * (x2 - x l ) ; v e r t e x [ 2 ] . p o in t [ l] = y l + m o d e l-> in te rp o l * (y2 - y l ) ; v e r t e x [ 2 ] . p o in t[2 ] = z l + m o d e l-> in te rp o l * (z2 - z l ) ; // xl yl zl x2 y2 z2
w spółrzędne trz e c ie g o w ie rz c h o łka tr ó jk ą ta = p o in tL is t [m o d e l- > tr iI n d e x [ i]. m e s h In d e x [l] ]. p o in t[ 0 ]; = p o in t L is t [m o d e l- > tr iI n d e x [ i] . m e s h ln d e x [ l] ] . p o in t [ l] ; = p o in tL is t [m o d e l- > tr iI n d e x [ i]. m e s h In d e x [l] ]. p o in t[ 2 ]; = n e x tP o in tL is t[m o d e l- > tr iIn d e x [i].m e s h In d e x [l]].p o in t[0 ]; = n e x tP o in tL is t [m o d e l- > tr iI n d e x [ i], m e s h ln d e x [ l]]. poi n t [ 1 ] ; = n e x tP o in t L is t [m o d e l- > tr iI n d e x [ i]. m e s h ln d e x [ l]] .p o in t [2 ];
/ / in te rp o lo w a n e w spółrzędne trz e c ie g o w ie rz c h o łka v e r t e x [ l ] . p o in t[0 ] = x l + m o d e l-> in te rp o l * (x2 - x l ) ; v e r t e x [ l ] . p o i n t [ l ] = y l + m o d e l-> in te rp o l * (y2 - y l ) ; v e r t e x [ l] . p o in t [ 2 ] = z l + m o d e l-> in te rp o l * (z2 - z l ) ; / / w e kto r normalny do tr ó jk ą ta C a lc u la te N o rm a l(v e rte x [0 ].p o in t. v e r t e x [2 ].p o in t, v e r t e x [ l] . p o in t ) ; / / ry s u je t r ó jk ą t p o k ry ty te k s tu rą g lT e x C o o rd 2 f(m o d e l-> s t[m o d e l-> triIn d e x [i] . s t ln d e x [ 0 ] ] . s , m o d e l-> s t[m o d e l-> triIn d e x [i] . s t ln d e x [ 0 ] ] . t ) ; g lV e r t e x 3 f v ( v e r t e x [ 0 ]. p o in t) ; g lT e x C o o rd 2 f(m o d e l-> s t[m o d e l-> triIn d e x [i] .s t ln d e x [ 2 ]] .s , m o d e l- > s t [m o d e l- > tr iln d e x [i].s t ln d e x [ 2 ]] .t ) ; g lV e r t e x 3 f v ( v e r t e x [ 2 ]. p o in t) ; g lT e x C o o rd 2 f(m o d e l-> s t[m o d e l-> triIn d e x [i] . s t ln d e x [ l] ] . s , m o d e l-> s t[m o d e l-> triIn d e x [i] . s t l n d e x [ l ] ] . t ) ; gl V e r t e x 3 f v ( v e r t e x [ l]. p o in t) ;
}
g lE n d O ; m o d e l-> in te rp o l += 0 .0 5 f;
/ / zwiększa w spółczynnik in t e r p o la c ji
Rozdział 1 8 . ♦ M odele trójw ym iarow e
487
Funkcja D is p la y M D 2 In te rp o la te () interpoluje współrzędne kolejnych trzech wierzchoł ków modelu, a następnie rysuje je jako trójkąt pokryty teksturą. Operacja ta wykony wana jest dla wszystkich wierzchołków modelu. Funkcja ta zwiększa stopniowo współ czynnik interpolacji, a po osiągnięciu wartości 1.0 dokonuje zmiany klatek kluczowych, względem których wykonuje interpolację. Chociaż zaprezentowana wyżej funkcja stanowi dobrą ilustrację metody interpolacji klatek, to jednak nie nadaje się do zastosowania w grze. Podstawową jej wadą jest brak możliwości wskazania klatki, od której rozpoczyna się animacja oraz określenia liczby klatek pośrednich. Dlatego też utworzona zostanie jej zmodyfikowana wersja poprzez dodanie parametrów pozwalających określić pierwszą i ostatnią klatkę animacji oraz wartość, o którą zwiększać się będzie współczynnik interpolacji: v o id D isp layM D 2 In te rp o la te (m o d e lD a ta _ t *m o d e l. in t s ta rtF ra m e , in t endFrame, f l o a t p e rce n t)
{ v e c to r_ t * p o in t L is t ; v e c to r_ t * n e x tP o in tL is t; in t i ; f lo a t x l, y l , z l; f lo a t x2, y2 . z2;
/ / w ie rz c h o łk i b ie ż ą c e j k la tk i / / w ie rz c h o łk i następnej k la tk i / / zmienna indeksu / / w spółrzędne w ie rz c h o łka b ie ż ą c e j k la tk i / / w spółrzędne w ie rz c h o łka następnej k la tk i
v e c to r_ t v e r te x [3 ];
/ / zmienna pomocnicza
i f (model == NULL) r e tu rn ; i f ( (sta rtF ra m e > currentF ram e) ) currentF ram e = s ta rtF ra m e ; / / w e ry fik a c ja w a rto ś c i parametrów i f ( (s ta rtF ra m e < 0) || (endFrame < 0) r e tu rn ; / / w e ry fik a c ja w a rto ś c i parametrów i f ( (s ta rtF ra m e >= model->numFrames) || (endFrame >= model->numFrames) r e tu rn ; / / po o s ią g n ię c iu k o le jn e j k la t k i kluczow ej / / n ależy zm ie n ić bieżącą i następną k la tk ę kluczową i f (m o d e l-> in te rp o l >= 1 .0 )
{ m o d e l-> in te rp o l = O.Of; / / z e ru je w spółczynnik in te r p o la c ji m odel->currentFram e++; / / zwiększa numer b ie ż ą ce j k la tk i i f (m odel->currentFram e >= endFrame) m odel->currentFram e = s ta rtF ra m e ; model->nextFrame = m odel->currentFram e + 1; i f (m odel->nextFram e >= endFrame) model->nextFrame = sta rtF ra m e ;
/ / l i s t a w ie rzch o łkó w b ie ż ą c e j k la tk i p o in t L is t = & m o d e l-> p o in tL ist[m o d e l-> n u m P o in ts*m o d e l-> cu rre n tF ra m e ];
488
Część III ♦ Tworzymy grę
/ / li s t a w ie rzch o łkó w następnej k la tk i n e x tP o in tL is t = & m o d e l-> p o in tL ist[m o d e l-> n u m P o in ts*m o d e l-> n e *tF ra m e ]; / / k o n fig u ru je te k s tu rę i rozpoczyna rysow anie tró jk ą tó w glBindTexture(GL_TEXTURE_2D, m odel-> m odelTex-> texID ); glBegin(GL_TRIANGLES); fo r ( i = 0 ; i < m odel->num Trian g le s; i ++)
{ / / wyznacza in te rp o lo w a n e w spółrzędne i ry s u je tr ó jk ą t y
} g lE n d O ; m o d e l-> in te rp o l += p e r c e n t;// zwiększa w spółczynnik in t e r p o la c ji
} Tę wersję funkcji Di spl ayM D2Interpol a t e ( ) można wywołać na przykład w następujący sposób: D ispla yM D 2 in te rp o la te (m yM o d e l, 0, 40, 0 .0 0 5 );
Nowa wersja funkcji D is p la y M D 2 In te rp o la te ( ) umożliwia nie tylko określenie począt kowej i końcowej klatki animacji, ale także sposobu, w jaki wzrasta współczynnik in terpolacji. Możliwość ta okazuje się szczególnie przydatna, gdy zachodzi potrzeba okre ślenia liczby klatek animacji, by zmieściła się ona w określonym przedziale czasu (na przykład w ciągu 1 sekundy). Chociaż ilustracja w książce nie oddaje ruchu animowanej postaci, to jednak rysunek 18.10 przedstawia efekt użycia nowej wersji funkcji D is p la y M D 2 In te rp o la te ( ). Rysunek 18.10. Model animowany za pomocą funkcji DisplayMD2Interpolate()
Klasa CMD2Model I tak możliwe już jest załadowanie, wyświetlenie, a nawet animowanie modelu* w for macie MD2. Dostępny jest także odpowiedni zestaw struktur reprezentujących taki model.
Rozdział 1 8 . ♦ M odele trójw ym iarow e
489
Jednak dołączanie opracowanych dotąd struktur danych i funkcji do kodu przygotowy wanej gry jest nieco kłopotliwe. Wygodniejszym rozwiązaniem będzie stworzenie klasy języka C++ reprezentującej model w formacie MD2. Klasa ta umożliwiać będzie łatwe rozszerzanie reprezentacji modelu, co nie było możliwe w przypadku reprezentacji modelu przedstawionej w po przednim podrozdziale. A oto definicja klasy CMD2Model, która wykorzystuje opracowane wcześniej struktury: c la s s CMD2Model p r iv a te : in t numFrames; in t num V ertices; in t num T riangles; in t numST; in t fram eS ize; in t curre n tF ra m e ; in t nextFrame; f l o a t in t e r p o l; mesh_t * t r i Index; texC oord _ t * s t ; v e c to r_ t * v e r te x L is t; te x tu re t *modelTex;
/ lic z b a k la te k a n im a cji / lic z b a w ierzchołków modelu lic z b a tró jk ą tó w modelu lic z b a te k s tu r modelu ro zm ia r k la tk i w b a jta c h bieżąca k la tk a a n im a cji k o le jn a k la tk a a n im a cji / w spółczynnik in te r p o la c ji l i s t a tró jk ą tó w li s t a w spółrzędnych te k s tu ry li s t a w ierzchołków / dane te k s tu ry
v o id S e tu p S k in (te x tu re _ t * th is T e x tu r e ) ; p u b lic : CMD2Model( ) ; ~CMD2Model();
/ / k o n s tru k to r / / d e s tru k to r
/ / ła d u je model i je g o te k s tu rę i n t LoadCchar *m o d e lF ile , char * s k in F ile ) ; / / ła d u je ty lk o model in t LoadM odel(char *m o d e lF ile ); / / ła d u je ty lk o te k s tu rę in t LoadS kin(char * s k in F ile ) ; / / o k re ś la te k s tu rę modelu i n t S e tT e x tu re (te x tu re _ t * t e x tu r e ) ; / / anim uje model metodą in t e r p o la c ji k la te k kluczowych i n t A n im a te (in t sta rtF ra m e , i n t endFrame, f lo a t p e rc e n t); / / w y ś w ie tla wybraną k la tk ę modelu i n t R enderFram e(int keyFrame); / / zw a ln ia pamięć wykorzystywaną przez model in t U nloadO ;
490
Część III ♦ Tworzymy grę
Klasa CMD2Model hermetyzuje opracowaną dotąd funkcjonalność modelu. Składowe o do stępie prywatnym są na tym etapie już dobrze znane. Metoda S e tu p S k in ( ) stanowi od powiednik wykorzystywanej poprzednio funkcji SetupM D2Texture(). Składowe o dostępie publicznym to kilka metod, których funkcjonalność odpowiada w zasadzie funkcjom, które zaimplementowane zostały w poprzednim podrozdziale. Na przykład metoda L o a d ( ) ładuje kompletny model wraz z teksturą tak, by był on gotowy do użycia. Metody LoadS kinO , LoadModeK) i S e tT e x tu re () dokonują jedynie częścio wej inicjacji modelu. Metoda LoadSkinO ładuje jedynie teksturę modelu z podanego pliku, a metoda LoadModel () model pozbawiony tekstury. Metoda S e tT e x tu re ( ) wybiera załadowaną wcześniej teksturę dla danego modelu. Metoda A n im ateO rysuje animowany model posługując się klatkami kluczowymi od s tartF ra m e do endFrame i metodą interpolacji tych klatek. Metoda RenderFrame( ) rysuje pojedynczą, nieruchomą klatkę. Ostatnia z metod, UnLoadO, zwalnia pamięć wykorzy stywaną przez model. Hermetyzacja modelu za pomocą klasy języka C++ nie zmieniła więc jego funkcjonalności, lecz ułatwiła wykorzystanie go w programach. Ponieważ omówiony już został sposób działania każdej z funkcji modelu MD2, nie bę dzie omawiana ponownie implementacja odpowiadających im metod w wersji obiekto wej. Warto jedynie zwrócić uwagę na niewielkie zmiany, których wymagały opracowane wcześniej funkcje po to, aby mogły stać się metodami klasy CMD2Model. A oto ich imple mentacja: CMD2Model: :CMD2Model() num Vertices = 0; / w ie rz c h o łk i num Triangles = 0; / t r ó jk ą t y numFrames = 0; / k la tk i numST = 0; / w spółrzędne te k s tu ry fram eS ize = 0; / rozm iar k la t k i currentF ram e = 0; / bieżąca k la tk a nextFrame = 1; / następna k la tk a in te rp o l = 0 .0 ; / w spółczynnik in te r p o la c ji t r i Index = NULL; / indeksy tró jk ą tó w s t = NULL; / indeksy w spółrzędnych te k s tu ry v e rte x L is t = NULL; / l i s t a w ierzchołków model Tex = NULL; / te k s tu ra model S ta te = MODELJDLE;
CMD2Model: :~CMD2Model()
{ } vo id CMD2Model: :S e tu p S k in (te x tu re _ t * th is T e x tu re )
{ / / k o n fig u ru je te k s tu rę modelu MD2 g lG e n T e x tu re s (l, & th is T e x tu re -> te x ID ); g lB i ndTexture(GL_TEXTURE_2D, t h i s T e x tu re -> te x ID ); g lT e x P a ra m e te ri(GL_TEXTURE_2D.GL_TEXTURE_WRAP_S.GL_CLAMP); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP); g lT e x P a ra m e te ri( GL_TE XTURE_2D, GL_TE XTURE_MI N_FI LTE R. GL_LINEAR); gl TexPa rameteri(GL_TEXTURE_2D. GLJEXTURE_MAG_FI LTER,GL_LINEAR);
Rozdział 1 8 . ♦ M odele trójw ym iarow e
s w itc h (th is T e x tu re -> te x tu re T y p e )
{
case BMP: gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, th is T e x tu re -> w id th . th is T e x tu re -> h e ig h t, GL_RGB, GL_UNSIGNED_BYTE, th is T e x tu re -> d a ta ); break; case PCX: gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, th is T e x tu re -> w id th , th i sT e xtu re-> h e i g h t. GL_RGBA, GL_UNSIGNED_BYTE, th is T e x tu re -> d a ta ); break; case TGA: break; d e fa u lt: break;
} } i n t CMD2Model: :Load(char *m o d e lF ile , char * s k in F ile ) FILE * f i l e P t r ; in t file L e n ; char * b u ff e r ;
/ / wskaźnik p lik u / / długość p lik u / / b u fo r p lik u
modelHeader_t *modelHeader;
/ / nagłówek modelu
s tln d e x _ t * s tP tr ; fram e_t *fra m e ; v e c to r_ t * v e r te x L is tP tr ; mesh_t *b u fIn d e x P tr; in t i , j ;
// // // // //
dane te k s tu ry dane k la tk i zmienna indeksu zmienna indeksu zmienne indeksu
/ / o tw ie ra p lik modelu f i l e P t r = fo p e n (m o d e lF ile , " r b " ) ; i f ( f i l e P t r == NULL) re tu rn FALSE; / / u s ta la długość p lik u fs e e k ( f ile P t r , 0, SEEK_END); file L e n = f t e l l ( f i l e P t r ) ; fs e e k ( f ile P t r , 0, SEEK_SET); / / w c z y tu je c a ły p lik do bufo ra b u ffe r = new char [file L e n + 1 ]; fr e a d ( b u ffe r , s iz e o f( c h a r ) , file L e n , f i l e P t r ) ; / / w yodrębnia nagłówek model Header = (m o d e lH e a d e r_ t*)b u ffe r; v e r te x L is t = new v e c to r_ t [modelHeader->numXYZ * modelHeader->numFrames]; num Vertices = modelHeader->numXYZ; numFrames = modelHeader->numFrames; fram eS ize = m odelH eader->fram esize;
491
492
Część III ♦ Tworzymy grę
f o r ( j = 0; j < numFrames; j+ + )
{ frame = (fra m e _ t*)& b u ffe r[m o d e lH e a d e r-> o ffse tF ra m e s + fram eS ize * j ] ; v e r te x L is tP tr = (v e c to r_ t*)& v e rte x L is t[n u m V e rtic e s * j ] ; fo r ( i = 0 ; i < num V ertices; i+ + )
{ v e r t e x L i s t P t r [ i ] . p o in t[0 ] = fra m e -> s c a le [0 ] * fr a m e - > fp [i] . v [0 ] + fr a m e -> tra n s la te [0 ]; v e r t e x L i s t P t r [ i ] . poi n t [1 ] = fra m e -> s c a le [l] * fram e- > fp [ i ] . v [1 ] + fr a m e - > tr a n s la te [l]; v e rte x L i s t P t r [ i ] . poi n t [2 ] = fra m e -> s c a le [2 ] * fr a m e - > fp [i] . v [2 ] + fr a m e - > tr a n s la te [2 ];
} } modelTex = L o a d T e x tu re (s k in F ile ); i f (modelTex != NULL) S etupS kin(m odelTex); el se re tu rn FALSE; numST = modelHeader->numST; s t = new texC oord_t [numST]; s tP tr = (s tIn d e x _ t*)& b u ffe r[m o d e lH e a d e r-> o ffs e tS T ]; fo r ( i = 0 ; i < numST; i+ + )
{ s t [ i ] . s = ( f 1o a t) s t P t r [ i ] . s / (flo a t)m o d e lT e x -> w id th ; s t [ i ] . t = ( f l o a t ) s t P t r [ i ] . t / (flo a t)m o d e lT e x -> h e ig h t;
} num Triangles = m odelHeader->numTris; t r iI n d e x = new mesh_t [n u m T ria n g le s ]; / / tymczasowy wskaźnik b u fo ra tró jk ą tó w b u fln d e x P tr = (m e s h _ t*)& b u ffe r[m o d e lH e a d e r-> o ffs e tT ris ]; / / w yp ełn ia l i s t ę tró jk ą tó w fo r ( j = 0; j < numFrames; j+ + )
{ / / d la w s z y stkic h tró jk ą tó w danej k la tk i f o r ( i = 0; i < num Triangles; i+ + )
{ tr iln d e x [i].m e s h ln d e x [0 ] t r iln d e x [i]. m e s h ln d e x [ l] t r i I n d e x [ i ] .m eshlndex[2] t r i I n d e x [ i ] .s tln d e x [0 ] = t r i I n d e x [ i ] .s t ln d e x [ l] = t r i I n d e x [ i ] ,s tln d e x [2 ] =
= b u fIn d e x P tr [i] .m e sh ln d e x [0 ]; = b u fIn d e x P tr [i] .m e s h ln d e x [l]; = b u fIn d e x P tr [i] .m e sh ln d e x [2 ]; b u fI n d e x P tr [ i]. s t ln d e x [ 0 ]; b u f I n d e x P t r [ i] . s t I n d e x [ l] ; b u fI n d e x P tr [i].s tIn d e x [2 ];
} / / zamyka p lik i zw a ln ia je g o b u fo r fc lo s e ( file P tr ) ; fre e (b u ffe r);
Rozdział 1 8 . ♦ M odele trójw ym iarow e
currentF ram e = 0; nextFrame = 1; in te r p o l = 0 .0 ; re tu rn TRUE;
i n t CMD2Model: :LoadM odel(char *m o d e lF ile ) FILE * f i l e P t r ; in t file L e n ; char * b u ffe r ;
/ / w skaźnik p lik u / / długość p lik u / / b u fo r p lik u
modelHeader_t *m odelHeader;
/ / nagłówek modelu
s tln d e x _ t * s tP tr ; fram e_t *fra m e ; v e c to r_ t * v e r te x L is tP tr ; mesh_t * t r i Index, * b u fIn d e x P tr; in t i , j ;
// // // // //
dane te k s tu ry dane k la tk i zmienna indeksu zmienne indeksu zmienne indeksu
/ / o tw ie ra p lik modelu f i l e P t r = fo p e n (m o d e lF ile , " r b " ) ; i f ( f i l e P t r — NULL) re tu rn FALSE; / / u s ta la długość p lik u fs e e k ( f ile P t r , 0, SEEK_END); file L e n = f t e l l ( f i l e P t r ) ; fs e e k ( f ile P t r , 0, SEEK_SET); / / w c z y tu je c a ły p lik do b u fo ra b u ffe r = new char [file L e n + 1 ]; fr e a d ( b u ffe r , s iz e o f( c h a r ) , file L e n , f i l e P t r ) ; / / w yodrębnia nagłówek model Header = (m o d e lH e a d e r_ t*)b u ffe r; / / p rz y d z ie la pamięć na l i s t ę w ierzchołków v e r te x L is t = new v e c to r_ t [modelHeader->numXYZ * modelHeader->numFrames]; num V ertices = modelHeader->numXYZ; numFrames = modelHeader->numFrames; fram eS ize = m odelH eader->fram esize; f o r ( j = 0; j < numFrames; j+ + )
{ frame = ( fra m e _ t*)& b u ffe r[m o d e lH e a d e r-> o ffse tF ra m e s + fram eSize * j ] ; v e r te x L is tP tr = (v e c to r_ t* )& v e rte x L is t[n u m V e rtic e s * j ] ; fo r ( i = 0 ; i < num V ertices; i+ + )
{ v e r t e x L is t P t r [ i] . poi n t [0 ] = fra m e -> s c a le [0 ] * fr a m e - > fp [ i]. v [ 0 ] + fra m e -> tra n s la te [0 ]; v e r t e x L is t P t r [ i] . poi n t [1 ] = fra m e -> s c a le [l] * fr a m e - > fp [i] . v [ l ] + fr a m e - > tr a n s la te [l];
493
494
Część III ♦ Tworzymy grę
v e r t e x l_ is t P t r [ i] .p o in t[2 ] = fra m e -> s c a le [2 ] * fr a m e - > fp [i] . v [2 ] + fr a m e -> tra n s la te [2 ];
} } numST = modelHeader->numST; s t = new texC oord_t [numST]; s tP tr = (s tIn d e x _ t*)& b u ffe r[m o d e lH e a d e r-> o ffs e tS T ]; f o r ( i = 0 ; i < numST; i+ + )
{ s t [ i ] . s = 0 .0 ; s t [ i ] . t = 0 .0 ;
} num Triangles = modelHeader->numTris; t r i Index = new mesh_t [n u m T ria n g le s ]; / / tymczasowy wskaźnik b u fo ra tró jk ą tó w b u fln d e x P tr = (m e s h _ t*)& b u ffe r[m o d e lH e a d e r-> o ffs e tT ris ]; / / w ypełn ia l i s t ę tró jk ą tó w f o r ( j = 0; j < numFrames; j+ + )
{ / / d la w sz y stkic h tró jk ą tó w danej k la tk i f o r ( i = 0; i < num Triangles; i+ + )
{ t r i I n d e x [ i ] .m eshlndex[0] t r i I n d e x [ i ] ,m e s h ln d e x [l] t r i I n d e x [ i ] ,m eshlndex[2] t r iln d e x [ i ] . s t ln d e x [ 0 ] = t r i I n d e x [ i ] ,s t ln d e x [ l] = t r i I n d e x [ i ] .s tln d e x [2 ] =
= b u fIn d e x P tr [i] .m e sh ln d e x [0 ]; = b u fIn d e x P tr [i] .m e s h ln d e x [l]; = b u fIn d e x P tr [i] .m e sh ln d e x [2 ]; b u fI n d e x P tr [i].s tIn d e x [0 ]; b u fIn d e x P tr [i] . s t ln d e x [ l] ; b u fI n d e x P tr [ i], s t ln d e x [ 2 ];
} } / / zamyka p lik f c lo s e ( f i l e P t r ) ; model Tex = NULL; currentFram e = 0; nextFrame = 1; in te rp o l = 0 . 0 ; re tu rn 0;
} in t CMD2Model: :L oadS kin(char * s k in F ile )
{
in t i ; modelTex = L o a d T e x tu re (s k in F ile ); i f (modelTex != NULL) SetupSkin(model T e x ); el se re tu rn -1 ;
Rozdział 1 8 . ♦ M odele trójw ym iarow e
fo r ( i = 0 ; i < numST; i+ + )
{ s t [ i ] . s /= (flo a t)m o d e lT e x -> w id th ; s t [ i ] . t /= (flo a t)m o d e lT e x -> h e ig h t;
re tu rn 0;
} i n t CMD2Model: :S e tT e x tu re (te x tu re _ t * te x tu re )
{ in t i ; i f ( te x tu r e != NULL)
{
fre e (m o d e lT e x ); model Tex = te x tu re ;
} e is e re tu rn -1 ; S etupS kin (m o d e lT e x); f o r ( i = 0 ; i < numST; i+ + )
{ s t [ i ] . s /= (flo a t)m o d e lT e x -> w id th ; s t [ i ] . t /= (flo a t)m o d e lT e x -> h e ig h t;
} r e tu rn 0;
i n t CMD2Model: :A n im a te (in t s ta rtF ra m e , i n t endFrame, f l o a t p e rce n t)
{ v e c to r_ t * v L is t ; v e c to r_ t * n e x tV L is t; in t i ; f l o a t x l. y l , z l; f l o a t x2, y2. z2;
// // // // //
w ie rz c h o łk i b ie ż ą c e j k la tk i w ie rz c h o łk i następnej k la tk i zmienna indeksu w spółrzędne w ie rz c h o łka b ie ż ą c e j k la tk i w spółrzędne w ie rz c h o łka następnej k la tk i
v e c to r_ t v e r te x [3 ]; i f ((s ta rtF ra m e > c u rre n tF ra m e )) currentF ram e = s ta rtF ra m e ; i f ((s ta rtF ra m e < 0) || (endFrame < 0 )) r e tu rn -1 ; i f ((s ta rtF ra m e >= numFrames) || (endFrame >= numFrames)) re tu rn -1 ; i f ( in te r p o l >= 1 .0 )
{ in te rp o l = O .Of; currentFram e++; i f (curre n tF ra m e >= endFrame) currentF ram e = s ta rtF ra m e ;
495
496
Część III ♦ Tworzymy grę
nextFrame = currentFram e + 1; i f (nextFrame >= endFrame) nextFrame = s ta rtF ra m e ;
} v L is t = & v e rte x L is t[n u m V e rtic e s *c u rre n tF ra m e ]; n e x tV L is t = & ve rte x L is t[n u m V e rtic e s *n e x tF ra m e ]; glBindTexture(GL_TEXTURE_2D, m o d e lT ex-> te xID ); glBegin(GL_TRIANGLES); fo r ( i = 0; i < num Triangles; i+ + )
{ // xl yl zl x2 y2 z2
w spółrzędne pierwszego w ie rz c h o łka tr ó jk ą ta w obu kluczowych k la tk a c h = v L is t [t r iIn d e x [i] .m e s h I n d e x [ 0 ]] .p o in t [0 ]; = v L is t [ t r iI n d e x [ i] . m e s h I n d e x [ 0 ] ] . p o in t [ l] ; = v L is t [t r iIn d e x [i] .m e s h I n d e x [ 0 ]] .p o in t [2 ]; = n e x tV L is t [tr iIn d e x [i].m e s h ln d e x [0 ]]. poi n t [ 0 ] ; = n e x tV L is t[tr iIn d e x [i].m e s h ln d e x [0 ]]. poi n t [ 1 ] ; = n e x tV L is t [tr iIn d e x [i].m e s h ln d e x [0 ]]. poi n t [ 2 ] ;
/ / in te rp o lo w a n e współrzędne pierwszego w ie rz c h o łka v e r t e x [ 0 ] . poi n t [0 ] = x l + in te rp o l * (x2 - x l ) ; v e r t e x [ 0 ] . p o in t [ l] = y l + in te rp o l * (y2 - y l ) ; v e r t e x [ 0 ], p o in t[ 2 ] = z l + in te rp o l * (z2 - z l ) ; // xl yl zl x2 y2 z2
w spółrzędne dru g ie g o w ie rz c h o łka tr ó jk ą ta = vLi s t [ t r iI n d e x [ i U.m e sh ln d e x[2 ]] . poi n t [ 0 ] ; = vLi s t [ t r i I n d e x [ i ] . m e sh ln d e x[2 ]] . poi n t [ l ] : = v L is t [t r iIn d e x [i] .m e s h I n d e x [ 2 ]] .p o in t [2 ]; = n e x tV L is t[tr iIn d e x [i].m e s h In d e x [2 ]].p o in t[0 ]; = n e x t V L is t [t r iIn d e x [i] .m e s h I n d e x [ 2 ] ]. p o in t[ l] ; = n e x tV L is t[tr iIn d e x [i].m e s h In d e x [2 ]].p o in t[2 ];
/ / in te rp o lo w a n e w spółrzędne dru g ie g o v e r t e x [ 2 ]. p o in t[ 0 ] = x l +in te rp o l * v e r t e x [ 2 ] . p o in t [ l] = y l +in te rp o l * v e r t e x [ 2 ] . poi n t [2 ] = z l +in te rp o l * // xl yl zl x2 y2 z2
w ie rz c h o łka (x2 - x l ) ; (y2 - y l ) ; (z2 - z l ) ;
w spółrzędne trz e c ie g o w ie rz c h o łka tr ó jk ą ta = vLi s t [ t r i I n d e x [ i ] . m e s h In d e x [l]] . poi n t [ 0 ] ; = v L is t [ t r iI n d e x [ i] . m e s h I n d e x [ l] ] . p o in t [ l] ; = vLi s t [ t r i I n d e x [ i ] .m e s h ln d e x [l]]. poi n t [ 2 ] ; = n e x t V L is t [ t r iIn d e x [i] .m e s h I n d e x [ l]] .p o in t [0 ] ; = n e x t V L is t [ t r iI n d e x [ i] . m e s h I n d e x [ l] ] . p o in t [ l] ; = nextVLi s t [ t r i I n d e x [ i ] . m eshIndex[1 ] ] . poi n t [2D;
/ / in te rp o lo w a n e w spółrzędne trz e c ie g o w ie rz c h o łka v e r t e x [ l] . p o in t [ 0 ] = x l +in te rp o l * (x2 - x l ) ; v e r t e x [ l ] . p o i n t [ l ] = y l +in te rp o l * (y2 - y l ) ; v e r t e x [ l] . p o in t [ 2 ] = z l +in te rp o l * (z2 - z l ) ; / / w e kto r normalny do tr ó jk ą ta C a lc u la te N o rm a l(v e rte x [0 ].p o in t, v e r t e x [ 2 ]. p o in t, v e r t e x [ l] . p o in t ) ;
Rozdział 1 8 . ♦ M odele trójw ym iarow e
/ / ry s u je t r ó jk ą t p o k ry ty te k s tu rą g lT e x C o o r d 2 f ( s t[ tr iI n d e x [ i]. s t ln d e x [0 ]] .s , s t[tr iln d e x [i] .s tln d e x [0 ] ]. t) ; g lV e r te x 3 f v ( v e r t e x [ 0 ]. p o in t) ; g lT e x C o o rd 2 f( s t C t r iI n d e x [ i] . s tIn d e x [2 ]] . s , s t C t r iI n d e x [ i] . s t ln d e x [ 2 ] ] . t ) ; g lV e r t e x 3 f v ( v e r t e x [ 2 ]. p o in t) ; g lT e x C o o r d 2 f ( s t [ t r iI n d e x [ i] . s t ln d e x [ l] ] . s , s t[tr iln d e x [i].s tln d e x [l]].t) ; g lV e r t e x 3 f v ( v e r t e x [ l] . p o in t ) ;
} g lE n d O ; in te rp o l += p e rc e n t;
/ / zwiększa w spółczynnik in t e r p o la c ji
re tu rn 0;
} i n t CMD2Model: :R enderFram e(int keyFrame)
{ v e c to r_ t * v l_ is t; in t i ; / / w skaźnik w y ś w ie tla n e j k la t k i v L is t = & v e rte x L is t[n u m V e rtic e s * keyFrame]; / / w ybiera te k s tu rę glBindTexture(GL_TEXTURE_2D, m o d e lT ex-> te xID ); / / w y ś w ie tla k la tk ę modelu glBegin(GL_TRIANGLES); f o r d = 0; i < num Triangles; i+ + )
{ C a lcu la te N o rm a l( vLi s t [ t r i I n d e x [ i ] .m e s h ln d e x [0 ]].p o in t, vLi s t E t r iI n d e x [ i] ,m e s h ln d e x [2 ]].p o in t. vLi s t [ t r i I n d e x [ i ] ,m e s h ln d e x [l]].p o in t) ; g lT e x C o o rd 2 f( s t [ t r i I n d e x [ i ] . s tIn d e x [0 ]] . s , s t [ t r i I n d e x [ i ] . s t ln d e x [ 0 ] ] .t ) ; g lV e r t e x 3 f v ( v L is t [ t r iI n d e x [ i] .m e s h ln d e x [0 ]].p o in t); g lT e x C o o rd 2 f( s t [ t r i I n d e x [ i ] . s t ln d e x [ 2 ] ] . s , s t [ t r i I n d e x [ i ] . s tIn d e x [2 ]] . t ) ; gl V e r t e x 3 f v ( v L is t [ t r iI n d e x [ i] ,m e s h ln d e x [2 ]].p o in t); g lT e x C o o r d 2 f( s t[tr iIn d e x [i] . s t l n d e x [ l ] ] . s . s t[ tr iIn d e x [i], s t ln d e x [ l]] .t ) ; g lV e rte x 3 fv ( v L is tC tr iIn d e x C i] .m e s h In d e x C l]].p o in t);
} g lE n d O ; r e tu rn 0;
} i n t CMD2Model: :U n lo a d ()
497
498
Część III ♦ Tworzymy grę
i f ( t r i Index != NULL) f r e e ( t r i In d e x ); i f ( v e r te x L is t != NULL) fr e e ( v e r t e x L is t) ; i f ( s t != NULL) fr e e ( s t) ; re tu rn 0;
} Obiektową wersję modelu MD2 można łatwo wykorzystać w tworzonych programach. Aby na przykład załadować model, wyświetlić jego animację, a następnie zwolnić pa mięć zajmowaną przez model, można posłużyć się następującymi wierszami kodu: CMD2Model *myModel; / / in s ta n c ja modelu myModel = new CMD2Model; / / tw o rzy o b ie k t myModel->Load("mymodel.md2", "m ym o d e l.p cx"); myModel->Animate(0, 40, p e rce n ta g e ); myModel->UnLoad(); d e le te myModel;
Sterowanie animacją modelu Mimo że opanowana została umiejętność odtwarzania animacji modelu, gry wymagają ponadto dostosowania sposobu animacji postaci do zmieniającej się sytuacji. Zwykle więc animowany obiekt musi posiadać odpowiedni stan określający sposób animacji. Na przykład stan bezczynności oznacza, że model nie wykonuje żadnych akcji. Inne ty powe stany modeli postaci występujących w grze będą reprezentować chód, bieg, skoki, kucanie i agonię. Stany, w których może znajdować się model, zależą od rodzaju gry, dla której został on opracowany. Tabela 18.1 przedstawia klatki modelu w formacie MD2 odpowiadające różnym jego stanom. Jeśli na przykład model dysponować ma czterema stanami reprezentującymi bezczyn ność, bieg, skok i kucanie, to można utworzyć dla nich następujący typ wyliczenia: enum m o d e lS ta te jt
{
MODEL_IDLE, M0DEL_CR0UCH, M0DEL_RUN, MODEL_JUMP
// // // //
bezczynność kucanie bieg skok
}: Następnie definicję klasy CMD2Model rozszerzyć należy o dwie metody służące zmianie i pobraniu bieżącego stanu modelu:
Rozdział 1 8 . ♦ M odele trójw ym iarow e
499
Tabela 18.1. Stany animacji modelu MD2 Numer klatki
Stan animacji modelu
0 -3 9
Bezczynność
4 0 -4 6
Bieg
4 7 -6 0
Postrzał
6 1 -6 6
Postrzał w plecy
6 7 -7 3
Skok
7 4 -9 5
Bezczynność
9 6 -1 1 2
Postrzał i upadek
11 3 -122
Bezczynność
123-135
Bezczynność
1 3 6 -154
Kucanie
155-161
Czołganie
1 6 2 -169
Bezczynność w pozycji przykucniętej
1 7 0 -177
Agonia na kolanach
17 8 -185
Upadek na plecy i agonia
18 6 -1 9 0
Upadek na brzuch i agonia
1 9 1 -198
Osunięcie się i agonia / / zm ienia stan a n im a c ji modelu in t S e tS ta te ( m ode lS ta te _ t S ta te ); / / p o b ie ra stan a n im a c ji modelu m o d e lS ta te jt G e tS ta te O ;
Także składowe o dostępie prywatnym trzeba uzupełnić o zmienną, która przechowywać będzie bieżący stan animacji modelu: m o d e lS ta te jt model S ta te ;
/ / bieżący stan a n im a cji modelu
Najczęściej stan animacji modelu ulegać będzie zmianie na skutek akcji wykonywanych przez użytkownika, na przykład za pomocą klawiatury. Najprostszym rozwiązaniem bę dzie więc zmiana stanu animacji w odpowiedzi na naciśnięcie przez użytkownika okre ślonego klawisza. Dla uproszczenia można założyć, że funkcja W in M a in O będzie prze chowywać nowy stan animacji za pomocą poniższej zmiennej globalnej: m o d e lS ta te jt model S ta te ; / / g lo b a ln a zmienna stanu
A oto zmodyfikowana pętla przetwarzania komunikatów funkcji Wi nMai n ( ) : / / p ę tla p rze tw a rz a n ia komunikatów w h ile ( !done)
{ PeekMessage(&msg, hwnd. NULL, NULL, PM_REM0VE); i f (msg.message == WM_QUIT)
/ / a p lik a c ja odebrała komunikat WM_QUIT?
{ done = tr u e ;
/ / j e ś l i ta k , to a p lik a c ja kończy d z ia ła n ie
500
Część III ♦ Tworzymy grę
} e ls e
{ i f (keyPressed[VK_ESCAPEj) done = tr u e ; e ls e
{ i f (keyPressed[VK_UP]) model S ta te = MODEL RUN; e ls e i f (keyPressed[VK_CONTROL]) model S ta te - MODEL_CROUCH; e ls e i f (keyPressed[VK_SHIFT]) model S ta te = MODELJUMP; e ls e model S ta te = MODEL IDLE; R en d e r!); TranslateM essage(&m sg); DispatchM essage(&msg);
/ / tłum aczy i w ysyła kom unikat
} } } Zmiana stanu dokonana przez funkcję WinMain() wykorzystywana będzie przez funkcję Render (), która wyświetlać będzie odpowiednią animację modelu: v o id RenderO
{ f lo a t p e rce n t; p e rcent = 0 .0 7 f; g lP u s h M a trix O ; g lR o ta te f( 9 0 .O f, - l. O f , O.Of. O .O f); g lC o lo r3 f(1 .0 , 1 .0 , 1 .0 ); / / zm ienia bieżący stan a n im a cji modelu m yM ode l-> S etS tate(m odelS tate); g u n M od e l-> S e tS tate (m o d e lS ta te ); / / w y ś w ie tla anim ację odpowiednią do stanu modelu / / UWAGA: p oniższy sposób n ie je s t w zorcow y!!! s w itc h (myModel -> G e tS ta te O )
{ case MODELJDLE: m yModel->Animate(0, 39, p e rc e n t); gunModel->Anim ate(0, 39, p e rc e n t); break; case M0DEL_RUN: myModel->Animate(40, 46, p e rc e n t); gunModel->Animate(40, 46, p e rc e n t); break; case M0DEL_CR0UCH: myModel->Animate(136, 154, p e rc e n t); gunModel->Animate(136, 154, p e rc e n t); break;
Rozdział 1 8 . ♦ M odele trójw ym iarow e
501
case MODEL_JUMP: myModel->Animate(67, 73, p e rc e n t); gunM odel->Anim ate(67, 73, p e rc e n t); b reak; d e f a u lt : b re a k ;
} g lP o p M a trix O ;
} I to wszystko! Gdy użytkownik nie naciśnie żadnego klawisza, to model znajdować się będzie w stanie bezczynności. Wybranie klawisza oznaczonego strzałką w górę spowo duje wyświetlenie animacji biegu, a klawiszy Ctrl i Shift (odpowiednio) animacji kuca nia i skoku. Na tym można zakończyć omówienie formatu MD2. Stanowi on dobry przykład pod stawowego formatu modeli trójwymiarowych, ale nie musi być najwłaściwszym for matem we wszystkich zastosowaniach. Dlatego warto poeksperymentować z własnymi formatami, a także zapoznać się z formatami modeli stosowanymi w innych grach.
Ładowanie plików PCX Ponieważ większość modeli używanych w grze Quake 2 posiada tekstury zapisane w plikach formatu PCX, poniżej zaprezentowany został kod funkcji umożliwiającej za ładowanie tekstury z takiego pliku. Nie będzie tutaj omawiana szczegółowo implemen tacja tej funkcji, gdyż analizę jej kodu pozostawiamy czytelnikowi. / / LoadPCXFileO / / o p is : ła d u je zaw artość p lik u PCX unsigned char *LoadP C X File(char ^ file n a m e , PCXHEADER *pcxHeader)
{ in t id x = 0; in t c; in t i ; in t numRepeat; FILE * f i l e P t r ; i n t w id th ; in t h e ig h t; unsigned char * p ix e lD a ta ; unsigned char * p a le tte D a ta ;
/ / lic z n ik indeksu / / p o biera znak z p lik u / / lic z n ik indeksu // // // // //
wskaźnik p lik u szerokość obrazka PCX wysokość obrazka PCX dane obrazka PCX dane p a le ty PCX
/ / o tw ie ra p lik PCX f i l e P t r = fo p e n (file n a m e , " r b " ) ; i f ( f i l e P t r == NULL) re tu rn NULL; / / p o b ie ra pie rw szy znak z p lik u , k tó ry pow inien mieć w artość 10 c = g e tc ( file P tr ) ; i f (c != 10)
{ fc lo s e ( f ile P tr ) ; re tu rn NULL;
}
502
Część III ♦ Tworzymy grę
/ / pobie ra następny znak, k tó ry pow inien mieć w artość 5 c = g e t c ( f il e P t r ) ; i f (c != 5)
{
f c l o s e ( f ile P t r ) ; re tu rn NULL;
} / / ustaw ia wskaźnik p lik u na je g o początek r e w in d ( f ile P t r ) ; / / pom ija pierw sze 4 znaki fg e t c ( f ile P t r ) ; fg e t c ( f ile P t r ) ; fg e t c ( f ile P t r ) ; fg e t c ( f ile P t r ) ; / / pob iera współrzędną x le w e j krawędzi obrazka PCX pcxHeader->xMin = f g e t c ( f i l e P t r ) ; / / m niej znaczące słowo pcxHeader->xMin |= f g e t c ( f ile P t r ) « 8; / / b a rd z ie j znaczące słowo / / p ob iera w spółrzędną y d o ln e j krawędzi obrazka PCX pcxHeader->yMin = f g e t c ( f i l e P t r ) ; / / m n ie j znaczące słowo pcxHeader->yMin |= f g e t c ( f ile P t r ) « 8; / / b a rd z ie j znaczące słowo / / p obiera współrzędną x prawej krawędzi obrazka PCX pcxHeader->xMax = f g e t c ( f i l e P t r ) ; / / m niej znaczące słowo pcxHeader->xMax |= f g e t c ( f ile P t r ) « 8; / / b a rd z ie j znaczące słowo / / pob iera współrzędną y g ó rn e j krawędzi obrazka PCX pcxHeader->yMax = f g e t c ( f i l e P t r ) ; / / m niej znaczące słowo pcxHeader->yMax |= f g e t c ( f ile P t r ) « 8; / / b a rd z ie j znaczące słowo / / o b lic z a szerokość i wysokość obrazka PCX w id th = pcxHeader->xMax - pcxHeader->xMin + 1; h e ig h t = pcxHeader->yMax - pcxHeader->yMin + 1; / / p rz y d z ie la pamięć na dane obrazka PCX p ix e lD a ta = (unsigned c h a r* ) m a llo c ( w id th * h e ig h t) ; / / ustaw ia w skaźnik p lik u na 128. b a jt , g d zie zaczynają s ię dane obrazka f s e e k ( f ile P t r , 128, SEEK_SET); / / dekoduje i przechowuje p ik s e le obrazka w h ile ( id x < ( w id th * h e ig h t))
{ c = g e t c ( f i le P t r ) ; i f (c > Oxbf)
{ numRepeat = 0 x 3 f & c; c = g e t c ( f ile P t r ) ; fo r ( i = 0 ; i < numRepeat; i+ + )
{ p ix e l D a ta [id x + + ] = c;
Rozdział 1 8 . ♦ M odele trójw ym iarow e
503
e ls e p ix e l D ata[ id x + + ] = c; f f lu s h ( s t d o u t ) ;
} / / p rz y d z ie la pamięć na p a le tę obrazka PCX p a le tte D a ta = (unsigned c h a r* )m a llo c (7 6 8 ); / / p a le ta zajm uje 769 końcowych b a jtó w p lik u PCX f s e e k ( f ile P t r , -769. SEEKJND); / / w e ry fik u je p a le tę ; pierw szy znak pow inien mieć w artość 12 c = g e t c ( f ile P t r ) ; i f (c != 12)
{
fc lo s e ( f ile P tr ) ; re tu rn NULL;
} / / w c z y tu je p a le tę f o r ( i = 0; i < 768; i+ + )
{ c = g e tc ( file P tr ) ; p a le tte D a ta [i] = c;
} / / zamyka p lik i umieszcza wskaźnik p a le ty w nagłówku fc lo s e ( f ile P tr ) ; p c x H e a d e r-> p a le tte = p a le tte D a ta ; / / zwraca w skaźnik do danych obrazka re tu rn p ix e l Data;
}
Podsumowanie Format pliku MD2 składa się z dwóch części: nagłówka i właściwych danych modelu. Nagłówek zawiera podstawowe informacje dotyczące rozmiarów modelu, takie jak na przykład liczba wierzchołków i trójkątów, a także informacje o położeniu danych opi sujących model w pliku. Klatki kluczowe reprezentują wygląd animowanego modelu w pewnych, stałych odstę pach czasu. Interpolacja klatek kluczowych pozwala wyznaczyć klatki pośrednie niezbędne do uzy skania efektu płynnej animacji. Modele w formacie MD2 przechowują jedynie kluczo we klatki animacji, co pozwala zmniejszyć rozmiar plików. Jedną z metod sterowania animacją modelu, z pewnością nie najdoskonalszą, stanowią stany animacji modelu. Zmiana stanu animacji modelu w odpowiedzi na akcję użyt kownika lub inne zdarzenie pozwala uzyskać bardziej realistyczne modele postaci.
504
Część III ♦Tworzymy grę
Rozdział 19.
Modelowanie fizycznych właściwości świata O atrakcyjności współczesnych gier decyduje przede wszystkim niezwykle realistyczne przedstawienie wirtualnego świata gry. Nawet najdoskonalsza grafika nie zrobi na gra czu odpowiedniego wrażenia, jeśli prezentowane przez nią obiekty nie będą zachowy wać się w wystarczająco realistyczny sposób. Czasy, w których projektanci tworzyli gry podporządkowując ich sposób działania skromnym możliwościom sprzętowym, minęły bezpowrotnie. Obecnie obiekty prezentowane w grach muszą zachowywać się zgodnie z doświadczeniami ze świata rzeczywistego. W rozdziale tym przedstawione więc zostaną sposoby symulacji rzeczywistego świata wykorzystujące modele fizyczne oparte na zasadach dynamiki Newtona oraz metody wykrywania zderzeń.
Powtórka z fizyki Przed omówieniem sposobów symulacji właściwości rzeczywistego świata, trzeba naj pierw upewnić się, że posiada się solidną wiedzę z zakresu podstaw fizyki. Nie trzeba tu przeprowadzić pełnego kursu podstaw fizyki, a jedynie ograniczyć się można do omó wienia podstawowych zagadnień, których zrozumienie niezbędne jest do tworzenia re alistycznych światów gier.
Czas Mimo że pojęcia czasu nie można łatwo zdefiniować, wszyscy dobrze wiedzą, czym jest czas. Działanie cywilizacji opiera się na wykorzystaniu kalendarza i zegarów. Używa się ich, aby odmierzać czas w sekundach, minutach, godzinach, dniach i latach. Różne dzie dziny nauki i techniki wymagają bardziej dokładnych pomiarów czasu w milisekundach ( 10-3), mikrosekundach (KT6) i nanosekundach ( 10-9). Fizyka używa pojęcia czasu dla określenia zmiany położenia obiektów w przestrzeni. Na przykład mówiąc, że obiekt porusza się z prędkością 40 kilometrów na godziną ma się na myśli to, że w okresie jednej godziny pokonuje on dystans czterdziestu kilometrów.
506
Część III ♦ Tworzymy grę
Czas można stosunkowo łatwo symulować w tworzonych grach. Można na przykład zde finiować wirtualny czas gry, którego jedna sekunda równa jest okresowi potrzebnemu na stworzenie jednej klatki obrazu. Aby jednak rozwiązanie takie właściwie modelowało upływ fizycznego czasu, niezbędne jest zachowanie stałej prędkości tworzenia klatek. Można założyć na przykład, że początkowo tworzy się 30 klatek na sekundę i stała prędkość pewnego obiektu w wirtualnym świecie wynosi 30 metrów na sekundę. Jeśli następnie prędkość tworzenia klatek spadnie do 20 na sekundę, to również odpowiednio zwolni swój ruch wspomniany obiekt, mimo że nie zmieniono jego prędkości. Innym rozwiązaniem problemu symulacji czasu w grach będzie zastosowanie czasu rze czywistego. Pozwala ono na doskonalsze modelowanie rzeczywistego świata, gdyż nie posiada opisanych ograniczeń poprzedniego rozwiązania. Prędkość ruchu obiektów nie będzie tu zależeć już od prędkości tworzenia klatek obrazu. Ponieważ celem twórców gier jest osiągnięcie możliwie najdoskonalszego poziomu realizmu wirtualnego świata, to w tworzonych programach będzie się korzystać z czasu rzeczywistego.
Odległość, przemieszczenie i położenie Odległość i przemieszczenie, choć pozornie wydają się oznaczać to samo, stanowią jed nak dwie różne wielkości o różnej definicji i znaczeniu. Odległość jest wielkością ska larną definiującą długość odcinka łączącego punkty, pomiędzy którymi przemieścił się obiekt. Przemieszczenie jest natomiast wektorem definiującym zmianę położenia obiektu. Aby lepiej zrozumieć różnice pomiędzy tymi pojęciami, należy posłużyć się przykładem. Można przyjąć, że obiekt porusza się po drodze w kształcie prostokąta pokazanej na ry sunku 19.1. Ruch swój rozpoczyna w punkcie (0, 0) i udaje się na północ do punktu (0, 5), następnie na wschód — do punktu (10, 5), po czym w kierunku południowym — przez punkt ( 10, 0) — powraca do punktu wyjścia poruszając się na zachód. Rysunek 19.1. Obiekt obiegający obwód prostokąta pokonuje pewną odległość, ale nie przemieszcza się
(0, 5)
10
(io,5)
i
5
(0,0)
5
10
(10,0)
Obiekt ten przebywa następującą odległość: o d le g ło ś ć = o d le g ło ś ć w kie ru n k u północnym + o d le g ło ś ć w k ie ru n k u wschodnim + o d le g ło ś ć w k ie ru n k u południowym + o d le g ło ś ć w k ie ru n k u zachodnim o d le g ło ś ć = 5 + 1 0 + 5 + 1 0 o d le g ło ś ć = 30
Jeśli jednak przemieszczenie obiektu wyrazi się za pomocą wektorów, to okaże się, że nie zostało ono dokonane:
Rozdział 1 9 . ♦ M odelow anie fizycznych w łaściw ości św iata
507
przem ie szcze n ie = w e kto r w k ie ru n k u północnym + w ektor w k ie ru n ku wschodnim + w e kto r w k ie ru n k u południowym + w e kto r w kie ru n k u zachodnim p rzem ieszczenie = (0 ,5 ) + (1 0 ,0 ) + ( 0 ,- 5 ) + (-1 0 ,0 ) p rzem ieszczenie = ( 0 + 10 + 0 -1 0 . 5 + 0 - 5 + 0) p rzem ieszczenie = (0 ,0 )
Mimo że obiekt przebył pewną odległość, to jednak nie przemieścił się. Aby określić odległość przebytą przez obiekt lub wyznaczyć jego przemieszczenie, trzeba posiadać informację o położeniu obiektu. W przestrzeni położenie obiektu opisują trzy współrzędne (x, y, z), a na płaszczyźnie tylko dwie (x , y). Dla wielu obiektów, na przy kład piłki, wystarczy jedynie informacja o położeniu ich geometrycznego środka. Sytu acja komplikuje się w przypadku obiektów o nieregularnych kształtach, takich jak na przykład kij do bejsbola. Spoglądając na rysunek 19.2 łatwo zauważyć, że masa kija jest istotnie większa na jed nym z jego końców. Zamiast więc reprezentować położenie takiego kija za pomocą poło żenia jego środka geometrycznego, należy posłużyć się w tym celu jego środkiem masy, co ilustruje rysunek 19.3. Rysunek 19.2. Geometryczny środek kija
e
------------- ---------
—
)
Większa część masy
Mniejsza część masy
Środek
Rysunek 19.3. Położenie środka masy kija może reprezentować położenie całego obiektu
M asa = 8 jednostek
Masa = 20 jednostek
Ś ro d e k
Posługując się środkiem masy obiektu uzyskuje się w wielu przypadkach doskonalszą reprezentację jego fizycznych właściwości. Nie znaczy to jednak, że zawsze trzeba uży wać w grach środka masy obiektów. Jest to wskazane jedynie wtedy, gdy przewiduje się dość zaawansowane odwzorowanie fizyki rzeczywistego świata. W jaki sposób można wyznaczyć środek masy obiektu? Ponieważ obiekty wirtualnego świata konstruowane są za pomocą wierzchołków, to z każdym wierzchołkiem można związać pewną wagę określającą wielkość masy dla tego wierzchołka. Aby wyznaczyć środek masy, sumuje się najpierw iloczyny współrzędnych wierzchołków i wagi wierz chołków, a następnie dzieli uzyskane sumy przez sumę wag wszystkich wierzchołków. Metodę tę ilustruje poniższy fragment kodu:
508
Część fll ♦ Tworzym y grę
flo a t f lo a t f lo a t in t i f lo a t
c e n te rO fM a ss[3 ]; object[NUM _VERTICES][3]; objectMass[NUM_VERTICES]; ;
// // // //
( x , y . z) w ie rz c h o łk i o b ie k tu wagi w ierzchołków zmienna indeksu
to ta lM a s s = 0 .0 ;
centerO fM ass[0 ] = 0 . 0 ; c e n te r0 fM a s s [l] = 0 .0 ; c e n te r0 fM a s s [2 ] = 0 .0 ; / / wyznacza sumę wag f o r d = 0; i < NUM_VERTICES; i+ + ) to ta lM a s s += o b je c tM a s s [i]; / / wyznacza sumy ilo czyn ó w wag i poszczególnych współrzędnych f o r d = 0; i < NUM_VERTICES; i+ + )
{ centerO fM ass[0] c e n te rO fM a s s [l] c e n te r0 fM a s s [2 ]
+= o b je c t [ i] [ 0 ] * o b je c tM a s s [i] ; += o b j e c t [ i ] [ l ] * o b je c tM a s s [i]; += o b je c t [ i] [ 2 ] * o b je c tM a s s [i];
} / / d z ie li uzyskane c enterO fM ass[0] /= c e n te rO fM a s s [l] /= c e n te r0 fM a s s [2 ] /=
sumy przez sumę wag w s z y stkic h w ie rzch o łkó w to ta lM a s s ; to ta lM a s s ; to ta lM a s s ;
/ / środek masy opisany je s t za pomocą współrzędnych (x , y , z ): / / (ce n te rO fM a s s [0 ], c e n te r0 fM a s s [l], c e n te r0 fM a s s [2 ])
Jak już wspomniano, korzystanie ze środka masy obiektów nie jest obowiązkowe i ma sens jedynie wtedy, gdy trzeba dodatkowo zwiększyć realizm odwzorowania rzeczywi stego świata. Dla uproszczenia w omawianych przykładach wykorzystywane będą więc geometryczne środki obiektów.
Prędkość Prędkość określa odległość przebytą w jednostce czasu. Rozróżnia się dwa rodzaje pręd kości: ♦ chwilową; ♦ średnią. Prędkość chwilowa określa prędkość obiektu w danym momencie. Na przykład spoglą dając na prędkościomierz samochodu jadącego z prędkością 20 kilometrów na godzinę, można powiedzieć, że tyle właśnie wynosi jego prędkość chwilowa. Prędkość średnią wyznacza się w pewnym okresie czasu. W tym celu dzieli się odle głość przebytą przez obiekt przez czas, którego potrzebował on na przebycie tej drogi. Jeśli drogę oznaczy się przez s, a czas przez t, to średnią prędkość v wyznaczyć będzie można za pomocą następującej zależności: V = AS / At = (Sk - Sp) / (tk - tp)
Rozdział 1 9 . ♦ M odelow anie fizycznych w łaściw ości św iata
509
Jeśli więc obiekt przebył drogę 10 metrów w czasie jednej sekundy, to jego prędkość wynosić będzie: v
=
as
/
At
=
10 / 1 = 10 m/s
W jaki sposób wykorzystać prędkość obiektów w grach? Niezależnie od tego, jak skomplikowane obliczenia fizyczne wykonuje się, wszystko sprowadza się do wyzna czenia położenia obiektu w kolejnej klatce. Znając prędkość obiektu wyznacza się je na podstawie wzoru: Xk
=
Xp
+
V *A t
co oznacza, że nowe p o ło ż e n ie = poprzednie p o ło że n ie + (prędkość * zmiana czasu)
Obiekt rozpoczynający swój ruch w położeniu XP z prędkością v znajdzie się po czasie A t w położeniu Xk. Sposób wyznaczenia nowego położenia obiektu ilustruje szczegółowo rysunek 19.4. Rysunek 19.4. Wyznaczanie nowego położenia obiektu poruszającego się ze stałą prędkością
tim e internal = 1 second (czas = 1 seku nda)
Przyspieszenie Omawiając ruch obiektów zakładano dotąd, że poruszają się one ze stałą prędkością. W świecie rzeczywistym ruch taki praktycznie nie występuje. Przez przyspieszenie obiektu należy rozumieć zmianę jego prędkości w czasie. Pojęcie przyspieszenia ilustrują wykresy widoczne na rysunku 19.5. Pierwszy z nich opisuje ruch obiektu z zerowym przyspieszeniem, ponieważ prędkość ruchu nie zmienia się w czasie. Kolejny z wykresów reprezentuje ruch ze stałym przyspieszeniem, którego wartość reprezentuje nachylenie wykresu. Ostatni z wykresów prezentuje ruch obiektu, którego przyspieszenie ciągle wzrasta. Wartość przyspieszenia w każdym punkcie tego wykresu reprezentuje nachylenie stycznej do krzywej wykresu. Przyspieszenie definiuje się jako wielkość zmiany prędkości w pewnym okresie czasu: a =
AV
/
At
=
(V k
-
Vp)
/
(tk
-
tp )
510
Część III ♦ Tworzymy grę
Rysunek 19.5. Wykresy zmiany prędkości obiektu w czasie dla różnych przypadków przyspieszenia
Prędkość w funkcji czasu
a =0
t
Jednostką przyspieszenia jest metr na sekundę kwadrat (m/s2). Jeśli obiekt rozpoczyna ruch z prędkością v P, a jego stałe przyspieszenie wynosi a, to po czasie A t prędkość obiektu Vk wynosić będzie: Vk = vP + a *A t
co znaczy, że nowa prędkość = poprzednia prędkość + (p rz y s p ie s z e n ie * zmiana czasu)
Nową wartość prędkości można podstawić do równania opisującego nowe położenie obiektu. W ten sposób uzyskać można podstawowy model ruchu obiektów, który można użyć w grze. Zanim to jednak nastąpi, trzeba poznać jeszcze kilka innych zagadnień.
Siła Siła stanowi jedno z najważniejszych pojęć w fizyce. Siła nadaje przyspieszenie obiek tom. Rzucając piłkę oddziałuje się na nią siłą, co pokazuje rysunek 19.6. Rysunek 19.6. Piłka uzyskuje przyspieszenia na skutek oddziaływania siły
Działanie sił opisują trzy zasady dynamiki Newtona stanowiące podstawę klasycznej fizyki.
Rozdział 1 9 . ♦ M odelow anie fizycznych w łaściw ości św iata
511
Pierwsza zasada dynamiki Pierwsza zasada dynamiki brzmi następująco: „każde ciało znajduje się w ruchu jedno stajnym, jeżeli nie oddziałuje na nie żadna siła”. Innymi słowy — obiekt pozostaje w sta nie spoczynku lub porusza się ze stałą prędkością tak długo, jak długo nie oddziałuje na niego żadna siła. Zasada ta nosi także nazwę zasady bezwładności. Gdy umieści się poruszającą się piłkę w przestrzeni kosmicznej, czyli w idealnej próżni, to poruszać się ona będzie w nieskończoność ze stałą prędkością. W rzeczywistym świę cie piłka taka zatrzyma się po pewnym czasie na skutek tarcia, oporu powietrza bądź oddziaływania grawitacji.
Druga zasada dynamiki Druga zasada dynamiki mówi, że „związek pomiędzy siłą, przyspieszeniem obiektu i jego masą wyraża się zależnością F = m * a”. Równanie F = m * a informuje w istocie o tym, że na skutek działania siły zmienia się prędkość ruchu ciała. Masa jest wielkością skalarną, a przyspieszenie wektorem, wobec czego także siła jest wektorem, którego zwrot i kierunek są takie same jak zwrot i kierunek wektora przy spieszenia. Jednostką siły jest kg*m/s2, czyli newton (N).
Trzecia zasada dynamiki Trzecia zasada dynamiki mówi, że „każdej akcji odpowiada reakcja o takiej samej wiel kości i kierunku, lecz przeciwnym zwrocie”. Potwierdzenie tej zasady można łatwo za obserwować w otaczającym świecie. Na przykład próbując wyważyć drzwi wywiera się na nie nacisk, a drzwi oddziałują z tą samą siłą. Podobnie jest, gdy wysiada się z łódki — następuje wtedy wzajemne oddziaływanie pasażera i łódki, na skutek czego oddala się ona od brzegu.
Pęd Pędem obiektu nazywa się iloczyn jego prędkości i masy: moment = masa * prędkość
lub p = m* v
Ponieważ równania opisujące siłę i pęd zawierają masę, to można je ze sobą powiązać. Z równania pędu otrzymuje się to, że masa wyraża się przez poniższą zależność: m= p / v
Podstawiając tę zależność do równania opisującego siłę otrzymuje się: F = m* a F = (p / v) * a F = (p * a) / v
512
Część III ♦ Tworzymy grę
Po zastąpieniu przyspieszenia a przez w czasie: F
=
AP
/
av
/
At,
okaże się, że siła równa jest zmianie pędu
At
Zmiana siły oddziałującej na obiekt zmienia więc także jego pęd. Można założyć na przykład, że piłka o masie 1 kilograma porusza się z prędkością 3 metrów na sekundę. Pęd jej jest więc równy: p = m * v = 3 * l = 3lchiIdN ode == t h i s ) ; e ls e re tu rn fa ls e ;
} / / czy węzeł stanowi koniec l i s t y węzłów s io strza n y ch ? bool Is L a s tC h ild ( )
{ i f (parentNode) re tu rn (parentNode->childN ode->prevNode == t h i s ) ; e ls e re tu rn fa ls e ;
} / / umieszcza węzeł na l iś c i e c y k lic z n e j danego węzła nadrzędnego vo id AttachTo(CNode *newParent)
{ / / je ś l i węzeł należy ju ż do in n e j l i s t y , to usuwa go z n ie j i f (parentNode) D e ta ch (); parentNode = newParent; i f (parentN ode->childN ode)
{ prevNode = parentN ode->childNode->prevN ode; nextNode = parentN ode->childN ode; parentN ode->childNode->prevN ode->nextN ode = t h is ; parentN ode->childNode->prevN ode = t h is ;
}
e ls e
{ parentN ode->childN ode = t h is ;
/ / pierw szy węzeł = węzeł podrzędny
} } / / dodaje węzeł podrzędny v o id A tta c h (CNode *new C hild)
{ / / j e ś l i węzeł należy ju ż in n e j l i s t y , to usuwa go z n ie j i f (new C h ild -> H a sP a re n t()) n e w C h ild -> D e ta c h (); newChild->parentNode = t h is ; i f (child N o d e )
{ newChild->prevNode = childN ode->prevN ode; newChild->nextNode = childN ode; childN ode->prevN ode->nextN ode = newChiId; childN ode->prevN ode = newChiId;
} e ls e childN ode = newChiId;
Rozdział 2 0 . ♦ Tworzenie szkieletu gry
/ / usuwa węzeł z l i s t y v o id D etach()
{
/ / j e ś l i węzeł te n je s t pierwszym węzłem l i s t y , / / to k o le jn y węzeł t e j l i s t y s ta je s ię węzłem podrzędnym i f (parentNode && parentN ode->childN ode == t h is )
{ i f (nextNode != t h i s ) parentN ode->childN ode = nextNode; e ls e parentN ode->childN ode = NULL; / / brak węzła podrzędnego
} / / usuwa p o łą c ze n ia prevNode->nextNode = nextNode; nextNode->prevNode = prevNode; / / węzeł n ie na le ży ju ż do l i s t y prevNode = t h is ; nextNode = t h i s ;
} / / z lic z a w ęzły i n t CountNodesO
{ i f (ch ild N o d e ) re tu rn childN ode->C ountN odes() + 1; e ls e re tu rn 1;
} / / k o n s tru k to r CNodeO / / tw o rzy węzeł
{
parentNode = childN ode = NULL; prevNode = nextNode = t h is ;
} / / k o n s tru k to r CNode(CNode *node)
{
/ / tw o rzy węzeł i p rzy łą c za go do param etru k o n s tru k to ra parentNode = childN ode = NULL; prevNode = nextNode = t h is ; A tta c h T o (n o d e );
} / / d e s tru k to r v ir t u a l -CNodeO
{
D e ta ch O ;
/ / odłącza węzeł
w h ile (ch ild N o d e )
/ / usuwa w ęzły podrzędne
{ }
d e le te c h ild N o de ;
565
566
Część III ♦ Tworzymy grę
Klasa CNode posiada metody umożliwiające łączenie i odłączanie węzłów, sprawdzanie tego, czy dany węzeł posiada węzeł nadrzędny lub podrzędny i fak dalej. Już na tym etapie łatwo ocenić przydatność klasy CNode, ale swoją użyteczność ujawnia ona w pełni dopiero jako klasa pochodna CObject.
Zarządzanie obiektami: klasa CObject Klasa CObject reprezentuje najbardziej podstawowy byt obsługiwany przez szkielet gry i jest klasą pochodną klasy CNode. Dzięki temu obiekty klasy CObject można ze sobą łą czyć w hierarchię, którą łatwiej jest zarządzać. Rysunek 20.5 przedstawia przykładową hierarchię obiektów hipotetycznej gry. Jej ko rzeń reprezentuje cały świat gry. Na liście jego węzłów podrzędnych umieszczone zo stały pomieszczenia gry. W każdym z tych pomieszczeń może znajdować się dowolna liczba skarbów, potworów, graczy i innych obiektów. Poszczególne postacie gry mogą być też wyposażone w różną broń, która tworzy kolejny poziom drzewa. Rysunek 20.5. Przykładowa hierarchia obiektów hipotetycznej gry
Prawdziwą potęgę klasa CObject ujawnia jednak dopiero przy rysowaniu lub przemiesz czaniu obiektów, a także wykrywaniu ich zderzenia. Przed omówieniem tych operacji należy jednak przedstawić definicję klasy CObject umieszczoną w pliku object.h: c la s s CObject : p u b lic CNode
{ p ro te c te d : / / wyznacza p o ło ż e n ie i prędkość o b ie k tu v ir t u a l v o id O n A nim ate(scalar_t d e lta T im e )
{ p o s itio n += v e lo c ity * d e lta T im e ; v e lo c ity += a c c e le ra tio n * d e lta T im e ;
Rozdział 2 0 . ♦ Tworzenie szkieletu gry
/ / ry s u je o b ie k t d la danego p o ło że n ia kamery v ir t u a l v o id OnDraw(CCamera *camera) {} / / wykrywa zd e rze n ia v ir t u a l v o id O n C o llis io n (C O b je c t * c o llis io n O b je c t) { } / / o b s łu g u je zd e rze n ia v ir t u a l v o id OnPrepareO
{ P ro c e s s C o llis io n s (F in d R o o tO ); / / wykrywa zderzenia / / począwszy od o b ie k tu w k o rz e n iu drzewa
p u b lic : CVector p o s itio n ; CVector v e lo c it y ; CVector a c c e le ra tio n ; s c a la r_ t s iz e ;
// // // //
p o ło ż e n ie o b ie k tu prędkość o b ie k tu p rz y s p ie s z e n ie promień s fe ry ograniczeń
C O bjectO { isDead = fa ls e ; } -C O b je ctO { } v ir t u a l v o id Load O {} v ir t u a l v o id U nloadO {} / / ry s u je o b ie k t v o id Draw(CCamera *camera)
{ / / umieszcza m acierz modelowania na s to s ie g lP u s h M a trix O ; OnDraw(camera); / / ry s u je o b ie k t i f (H a s C h ild O ) / / ry s u je o b ie k ty podrzędne ( (C O b je ct*)ch ild N o d e )-> D ra w (ca m e ra ); g lP o p M a trix (); / / ry s u je o b ie k ty s io s trz a n e i f (H asP arentO && ! Is L a s tC h ild O ) ( (C O bject*)nextN ode)-> D raw (cam era);
/ / anim uje o b ie k t v o id A n im a te (s c a la r_ t d e lta T im e )
{ O n A n im a te (d e lta T im e );
/ / anim uje dany o b ie k t
/ / anim uje o b ie k ty podrzędne i f (H a s C h ild O ) ( (C O b je c t*)c h ild N o d e )-> A n im a te (d e lta T im e ); / / anim uje o b ie k ty s io s trz a n e i f (H asParentO && ! Is L a s tC h ild O ) ( (C O b je ct*)n e x tN o d e )-> A n im a te (d e lta T im e ); i f (isD ead) d e le te t h is ;
567
568
Część III ♦ Tworzymy grę
/ / wykrywa zd erzenia v o id P ro c e ss C o lli s i ons(C O bject * o b j)
{ / / je ś l i s fe ra o graniczeń tego o b ie k tu k o lid u je ze s fe rą o b ie k tu o b j / / i o b ie k t o b j n ie je s t tym samym obiektem i f ( (( o b j- > p o s itio n - p o s itio n ) .L e n g th O s iz e + s iz e ) ) && (o b j != ( ( C O b je c t* ) th is ) ) )
{ OnCol1is io n ( o b j) ;
/ / o b s łu g u je
z d e rze n ie z obiektem o b j
/ / wykrywa zd e rze n ia o b ie k tu o b j z ob ie kta m i podrzędnymi i f (H a s C h ild O ) ( (C O b je c t*)c h ild N o d e )-> P ro c e s s C o llis io n s (o b j); / / wykrywa zd e rze n ia o b ie k tu o b j z obiektam i s io strza n y m i i f (H asParentO && ! IsL a stC h i 1d ( ) ) ( ( C O b je c t*)nextN ode)->P rocessC ol1 i s io n s ( o b j);
} / / j e ś l i o b ie k t o b j posiada o b ie k ty podrzędne, n a le ży spraw dzić z d e rze n ia z nim i i f (o b j-> H a s C h ild ()) P rocessC ol1is io n s ((C O b je c t* )(o b j-> c h ild N o d e )); / / j e ś l i o b ie k t o b j posiada o b ie k ty s io s trz a n e , należy spraw dzić zd e rze n ia z nim i i f (o b j-> H a sP a re n t() && ! o b j-> Is L a s tC h i1d ( )) P rocessC ol1is io n s ((C O b je c t*)(o b j-> n e x tN o d e ));
} / / przyg o to w u je o b ie k t v o id PrepareO
{ O nPrepareO ;
/ / p rzygotow uje dany o b ie k t
i f (H a s C h ild O ) / / i je g o o b ie k ty podrzędne ( (C O b je c t*)c h ild N o d e )-> P re p a re (); i f (H asParentO && ! IsL a stC h i 1d ( ) ) ( (C O b je c t*)nextN ode)-> P re p a re ();
/ / oraz s io s trz a n e
} / / z n a jd u je korzeń drzewa l i s t c y k lic z n y c h CObject *F in d R o o t()
{ / / j e ś l i dany o b ie k t posiada węzeł nadrzędny. / / to zwraca je g o korzeń i f (parentNode) re tu rn ((C O b je c t*)p a re n tN o d e )-> F in d R o o t(); re tu rn t h i s ;
} }; W dostępnej publicznie części klasy umieszczone zostały metody P re p a re ( ), Ani mate (), ProcessCol 1 i si ons ( ) i Draw() . Każda z nich wykonuje odpowiednie operacje na danym obiekcie, a następnie na jego obiektach podrzędnych. Dobrym tego przykładem jest kod źródłowy metody C O b je c t: :Ani mate O:
Rozdział 2 0 . ♦ Tw orzenie szkieletu gry
569
/ / anim uje o b ie k t v o id A n im a te (s c a la r t d e lta T im e ) / / anim uje dany o b ie k t
O n A n im a te (d e lta T im e );
/ / anim uje o b ie k ty podrzędne i f (H a s C h ild O ) ( (C O b je c t*)c h ild N o d e )-> A n im a te (d e lta T im e ); / / anim uje o b ie k ty s io s trz a n e i f (H asP arentO && ! IsL a stC h i 1d ( )) ( ( CObj e c t*)n e x tN o d e ) ->Ani mate( d e lta T ime); i f (isD ead) d e le te t h i s ;
Każdy z obiektów klasy CObject i jej pochodnych posiada zestaw własnych metod wir tualnych o dostępie chronionym i nazwach O n *( ), takich jak O nPrepare( ), OnAnim ate(), O n C o llis io n O i OnDrawO. Za każdym razem, gdy na rzecz obiektu wywoływana jest jedna z metod o dostępie publicznym (jak na przykład A nim ateO ), to odpowiednia me toda O n*( ) umożliwia wykonanie specjalizowanego kodu dla danej klasy obiektu. W przy padku przedstawionej wyżej metody A n im a te ( ) najpierw wywoływana jest metoda OnAn im a te ( ), która wykonuje rzeczywistą animację tego obiektu, a następnie rekurencyjnie wywoływana jest metoda Anim ateO dla jego obiektów podrzędnych i siostrzanych. Ostatni blok kodu w metodzie Anim ateO pozwala usunąć obiekt z hierarchii, jeśli za chodzi taka konieczność. Rysunek 20.6 ilustruje wywołanie metody A m m ate ( ) na rzecz obiektu znajdującego się w korzeniu drzewa hipotetycznej hierarchii. Rysunek 20.6. Przykład działania metody Animate() wywołanej dla korzenia drzewa
AnimateQ
-
1
Świat gry
AnimateQ - 2 Pokój 1
Pokój 3
Pokój 2
AnimateO - 3,
AnimateO - 4
AnimateQ - 9
Miecz
nimateQ -1 0
Zbroja
Metodę A n im a te ( ) stosuje się zwykle po to, aby wyznaczyć położenie obiektu w kolej nej klatce animacji, a metodę P re pa re O , aby zmienić stan obiektu. Zderzenia obiektu wykrywa metoda ProcessCol l is io n s O , a metoda DrawO służy do narysowania repre zentacji graficznej obiektu.
570
Część III ♦ Tworzymy grę
Ponieważ metody O nPrepare( ), O nAnim ate( ) i OnDraw() są wirtualne, to każda klasa po chodna klasy CObject musi dostarczyć własnej ich implementacji. W ten sposób tworząc nowe klasy pochodne klasy CObject można uzyskać prosty i efektywny sposób rozbudo wy szkieletu gry o nowe obiekty. Należy też zwrócić uwagę na to, że metoda D raw ( ) ujmuje wywołania metody D raw ( ) dla obiektów podrzędnych w blok pary wywołań funkcji gl P u sh M a trix( ) i gl PopMatri x ( ), których zastosowanie omówione zostało w rozdziale 5. Wspomniano tam o możliwości tworzenia hierarchii obiektów, w których współrzędne obiektów podrzędnych określane są względem obiektów nadrzędnych. Na przykład głowa robota może być obiektem podrzędnym jego korpusu i posiadać współrzędne (O. O, 5 . 0, 0 . 0) względem niego. Można teraz założyć, że rysuje się samochód i jego cztery koła. Koła dołączyć należy jako obiekty podrzędne do nadwozia samochodu. Wystarczy więc określić ich współ rzędne w stosunku do reszty samochodu — na przykład: lewe, przednie koło ( 2 . 0, 0. 0, 2 . 0), prawe, przednie koło (- 2 . 0, 0. 0, 2 . 0) i tak dalej — a hierarchia obiektów klasy CObject sama zadba o ich właściwe narysowanie. Rozwiązanie takie ilustruje poniższy fragment kodu: CObject *body; CObject * t i r e l , * t ir e 2 , * t ir e 3 , * t ir e 4 ; tir e l- > A tta c h T o ( b o d y ) ; / / lu b : b o d y -> A tta c h ( tir e i) t i re 2 -> A tta c h T o (b o d y ); tire 3 -> A tta c h T o (b o d y ); tire 4 -> A tta c h T o (b o d y ); body->D raw (. . . ) ;
Kod ten utworzy hierarchię obiektów pokazaną na rysunku 20.7. Rysunek 20.7. Hierarchia obiektów reprezentująca samochód i jego koła
Trzon szkieletu SimpEngine Zasadniczą część szkieletu stanowi klasa COGLWindows, która tworzy okno grafiki OpenGL i obsługuje komunikaty wysyłane do aplikacji przez system Windows. Dla każdego z ob sługiwanych komunikatów klasa COGLWi ndow definiuje osobną metodę wirtualną. Na przy kład komunikatowi WM SIZE odpowiada metoda wirtualna O nSizeO . Metody te wywo ływane są przez procedurę okienkową WndProcOGL, gdy otrzyma ona odpowiedni rodzaj komunikatu. A oto definicja klasy COGLWi ndow:
Rozdział 2 0 . ♦ Tworzenie szkieletu gry
571
c la s s COGLWindow p ro te c te d : HWNDhWnd; HDChDC; HPALETTEhPa1e t t e ; HGLRChGLRC;
// // // //
uchwyt okna k o n te k st urządzenia p a le ta k o n te k st tw o rze n ia g r a f ik i
p r iv a te : / / procedura okienkowa fr ie n d LRESULT API ENTRY WndProcOGL(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM 1Param); v o id S e tu p P ix e lF o rm a t!); v o id S e tu p P a le tte !);
/ / k o n fig u ru je fo rm a t p ik s e li / / k o n fig u ru je p a le tę
/ / metody o b s łu g i komunikatów systemu Windows bool C re a te !); / / WM_CREATE v o id D e s tro y !); / / WM_DESTROY v o id PaletteChanged!WPARAM wParam); / / WM_PAL ETT ECHANGED BOOL Q ueryN ew P alette!); / / WM_QUERYNEWPALETTE v o id P a in t ! ); / / WM_PAINT v o id S iz e ! ) ; / / WM_SIZE v o id MouseMove(int x, in t y ) ; / / WM_MOUSEMOVE in t GetMouseX!LPARAM IParam ); in t GetMouseY!LPARAM IParam );
/ / zwracają w spółrzędne myszy
f lo a t GetNormalizedPosX!LPARAM IParam); f l o a t GetNormalizedPosY!LPARAM IParam );
/ / zwracają znormalizowane współrzędne myszy / / z p rz e d z ia łu od -1 .0 do 1.0
in t iP re v W id th ; i n t iP re v H e ig h t; v o id B e g in F u llS c re e n ü n t w. in t h. in t b ) ; v o id E nd F u lIS cre e n !); p u b li c : in t w id th ; in t h e ig h t; i n t ce n te rX ; in t ce n te rY ; in t b it s ; i n t asp e ct; i n t mouseX; in t mouseY; bool fu lls c r e e n ; f l o a t m ou se S e n sitiv i t y ;
/ / rozm iary okna / / w spółrzędne środka okna / / lic z b a b itó w na p ik s e l / / w spółczynnik p ro p o rc ji okna
/ / tr y b pełnoekranowy? / / czu ło ść myszy
bool useD Input; / / w artość tr u e , je ś l i ko rz y sta z d in p u t CInputSystem *in p u tS yste m ; / / system w e jśc ia p ro te c te d : v ir t u a l v ir t u a l v ir t u a l v ir t u a l v ir t u a l
bool bool v o id v o id v o id
O nC reate!) { re tu rn tr u e ; } OnClose!) { re tu rn tr u e ; } O nS ize!) { } OnMouseDownL!float x. f l o a t y ) { } OnMouseDownR!float x, f lo a t y ) { }
572
Część III ♦ Tworzymy grę
v ir t u a l v ir t u a l v ir t u a l v ir t u a l v ir t u a l v ir t u a l v ir t u a l v ir t u a l v ir t u a l v ir t u a l v ir t u a l v ir t u a l
v o id OnMouseUpLO { } v o id OnMousellpRO { } v o id OnMouseMove(int x, in t y , in t c e n te rX , i n t ce n te rY ) { } v o id OnMouseMove(int d e lta X , in t d e lta Y ) { } v o id OnMouseDragL(int x. i n t y , in t dx, i n t dy) {} v o id OnMouseDragR(int x, in t y , in t dx, in t dy) {} v o id OnCommand(WORD w N otifyC ode, WORD wID, HWND hW ndC trl) { } v o id OnContextMenu(HWND hWnd, in t x, in t y ) { } v o id O nK eytlp(int n V irtK e y ) { } v o id OnInitMenu(HMENU hMenu) { } v o id OnKeyDown(int n V irtK e y ) { } v o id OnChar(char c) { }
p u b lic : COGLWindowO { } COGLWindow(const char *szName, bool fs c re e n , in t w, i n t h, i n t b ); v ir t u a l -COGLWindowO; / / poniższa metoda musi być wywołana, zanim użyta z o s ta n ie ja k a k o lw ie k inna s t a t ic bool RegisterWindowCHINSTANCE h ln s t) ;
}:
System wejścia Klasa COGLWindow przechowuje także referencję obiektu klasy CInputSystem, który re prezentuje podsystem wejścia wykorzystujący interfejs Directlnput do odczytu bieżącego stanu klawiatury, myszy i manipulatora. Klasa systemu wejścia będzie zawierać metody pozwalające sprawdzić, czy klawisz został naciśnięty lub zwolniony, jak zmieniła swoją pozycją mysz, a także to, jaki jest stan jej przycisków, o ile przesunął się drążek mani pulatora i to, czy użytkownik skorzystał z jego przycisków. Obiektem klasy C InputSys tem będzie zarządzać wyłącznie klasa COGLWindows (tworzyć go i usuwać). Poniżej przed stawiona została definicja klasy CInputSystem: c la s s CInputSystem
{ p u b lic : CInputSystemC) { m_pKeyboard = NULL; m_pMouse = NULL; m _pJoystick = NULL; } ~C InputS ystem () {} bool I n i t i a l izeCHWND hwnd, HINSTANCE applnstance, bool is E x c lu s iv e , DWORD fla g s = 0 ); bool ShutdownO; v o id v o id
A c q u ire A l1 ( ); U n a cq u ire A l1 0 ;
CKeyboard CMouse C Jo y s tic k
*G etK eyboard() { re tu rn m_pKeyboard; } *GetMouseO { re tu rn m_pMouse; } * G e tJ o y s tic k () { re tu rn m _pJoystick; }
bool
U pdateO ;
/ / a k tu a liz u je stan urządzeń
•
bool bool
KeyDown(char key) { re tu rn (m_pKeyboard &&. m _pKeyboard->KeyDown(key)); } KeyUpCchar key) { re tu rn (m_pKeyboard && m_pKeyboard->KeyUp(key)); } . ...
bool bool
ButtonDownCint b u tto n ) { re tu rn (m_pMouse && m_pM ouse->ButtonDown(button)); } B u tto n U p (in t b u tto n ) { re tu rn (m_pMouse && m _pM ouse->B uttonU p(button)); }
Rozdział 2 0 . ♦ Tworzenie szkieletu gry
v o id
573
GetMouseMovement(int &dx, in t &dy) { i f (m_pMouse) m_pMouse->GetMovement(dx, d y ); }
p r iv a te : CKeyboard CMouse C J o y s tic k
*m_pKeyboard; *m_pMouse; *m _ p J o y s tic k ;
/ / o b ie k t / / o b ie k t / / o b ie k t
k la w ia tu ry myszy m a n ip u la to ra
LPDIRECTINPUT8 m_pDI;
}:
Klasa CEngine Klasa CEngine jest klasą pochodną klasy COGLWindow i dostarcza implementację pętli przetwarzania komunikatów systemu Windows, główną pętlę gry oraz metodę obsługi informacji wejścia otrzymywanych z podsystemu wejścia. A oto definicja klasy CEngi ne: c la s s CEngine : p u b lic COGLWindow
{ p r iv a te : p ro te c te d : CHiResTimer * t im e r ; / / lic z n ik czasu o dużej ro z d z ie lc z o ś c i v ir t u a l v o id G am eC ycle(float d e lta T im e ); / / główna p ę tla g ry v ir t u a l v o id OnPrepareO { }
/ / k o n fig u ru je OpenGL
/ / k la s y pochodne muszą d o sta rcza ć własną im plem entacje t e j metody v ir t u a l CCamera *OnGetCamera() { re tu rn NULL; } / / k la s y pochodne muszą d o sta rcza ć własną im plem entacje t e j metody v ir t u a l CWorld *OnGetW orld() { re tu rn NULL; } v ir t u a l v o id C h e c k In p u t(flo a t d e lta T im e ); / / pozyskuje dane w ejściow e p u b lic : C Engine() { } C Engine(const char *szName, bool fs c re e n , in t w. in t h. in t b) : COGLWindow(szName, fs c re e n , w, h, b) {} -C E n gine() {} LRESULT E nterM essageLoopO ;
}; Metoda EnterMessageLoopO zawiera typową pętlę przetwarzania komunikatów systemu Windows i posługuje się licznikiem czasu klasy CHiResTimer. Poniżej zaprezentowany został kod metody C E n g in e : : EnterMessageLoopO: LRESULT C E n g in e :: EnterMessageLoop()
{ MSG msg; / / odebrany kom unikat tim e r = new CHiResTimer; / / lic z n ik czasu t i m e r - > I n it ( );
/ / i n ic ju je lic z n ik czasu
574
Część III ♦ Tworzymy grę
fo r ( ; ; )
{ / / wykonuje pojedynczy c yk l g ry GameCycl e (tim e r-> G e tE la p se c lS e c o n d s (l)); w h ile (PeekMessage (&msg, NULL, 0. 0, PM_N0REM0VE))
{ / / sprawdza obecność komunikatów i f (¡GetMessage (&msg, NULL, 0, 0 ))
{ d e le te tim e r; re tu rn msg.wParam;
/ / usuwa lic z n ik / / p rze ka zu je ste ro w a n ie do systemu
} TranslateM essage (&msg); DispatchMessage (&msg);
} ) d e le te tim e r;
/ / usuwa lic z n ik
re tu rn msg.wParam;
/ / p rze ka zu je ste ro w a n ie do systemu
\
j
W każdym przebiegu pętli metoda EnterM essageLoop( ) wykonuje pojedynczy cykl gry i sprawdza, czy aplikacja otrzymała komunikat o zakończeniu pracy. Metoda cyklu gry, GameCycl e ( ), otrzymuje jako parametr czas, który upłynął od poprzedniego cyklu. Czas ten wykorzystywany jest do przeprowadzenia obliczeń z wykorzystaniem zasad fizyki oraz wykrywania zderzeń.
Cykl gry Na typowy cykl gry składają się następujące etapy: ♦ pobranie informacji wejściowej; ♦ zmiana położenia gracza; ♦ zastosowanie sztucznej inteligencji; ♦ obliczenia z zastosowaniem zasad fizyki; ♦ odtwarzanie dźwięku; ♦ narysowanie grafiki. W przypadku szkieletu SimpEngine cykl gry nie będzie aż tak przejrzysty ze względu na istniejącą hierarchię obiektów i konieczność wywołania metod P re p a re ( ), A m m a te ( ) i D raw( ) klasy CObject. Ilustruje to poniższy kod metody CEngine:GameCycl e ( ): v oid CEngine: : GameCycle(f l o a t d e lta T im e )
{ CCamera *camera = OnGetCamera(); / / p o biera kamerę CWorld *w o rld = OnGetW orld(); / / i korzeń h ie r a r c h ii
/ / sprawdza in fo rm a c ję w e jśc ia C heckln p u tC d e lta T im e );
Rozdział 2 0 . ♦ Tw orzenie szkieletu gry
575
/ / k o n fig u ru je OpenGL O nP repare i); / / p rzygo to w u je o b ie k ty i o b s łu g u je zd erzenia w o rld -> P re p a re (); / / zm ienia p o ło ż e n ie i o r ie n ta c ję kamery cam e ra -> A n im a te (d e lta T im e ); / / zm ienia p o ło ż e n ie i o r ie n ta c ję obiektów w o rld -> A n im a te (d e lta T im e ); / / ry s u je o b ie k ty w o rld-> D ra w (ca m e ra ); / / p rze łą c z a b u fo ry Sw apB uffers(hD C );
} Etapy związane z obsługą kamery i obiektów gry omówione zostaną wkrótce. Teraz wystarczy, jeśli zapamiętana zostanie kolejność etapów tworzących cykl gry.
Obsługa wejścia Jak łatwo zauważyć analizując kod metody GameCycl e ( ), stan urządzeń wejścia określany jest za pomocą metody Check In p u t (), która używa obiektu klasy CInputSystem zarzą dzanego przez klasę COGLWindow. Zamieszczony poniżej kod metody Check Input () spraw dza, czy nastąpiło zdarzenie związane z urządzeniami wejścia (na przykład naciśnięcie klawisza Esc) i wywołuje odpowiednią metodę jego obsługi: v o id C E n g in e ::C h e c k In p u t(flo a t d e lta T im e )
{ s t a t ic f l o a t b u tto n D e lta = O.Of; / / okres czasu, k tó ry musi up łyn ą ć, zanim może być / / w yk ryte następne n a c iś n ię c ie p rz y c is k u myszy i n t mouseDeltaX, mouseDeltaY;
/ / zmiana p o ło że n ia myszy
/ / zm niejsza czas do następnego wykrywalnego n a c iś n ię c ia p rz y c is k u myszy b u tto n D e lta -= d e lta T im e ; i f (b u tto n D e lta < O.Of) b u tto n D e lta = O.Of; / / a k tu a liz u je urządzenia inputS yste m -> U p d a te (); / / p o b ie ra in fo rm a c je o ruchu myszy inputSystem->GetMouseMovement(mouseDeltaX, m ouseD eltaY); OnMouseMove(mouseDeltaX, m ouseD eltaY); / / n a c iś n ię c ie k la w isz a W i f ( i nputSystem ->KeyD ow n(D IKJI)) OnKeyDown(VKJJP); / / n a c iś n ię c ie k la w isz a S i f (inputSystem ->KeyDown(DIK_S)) OnKeyDown(VK_DOWN);
576
Część III ♦ Tworzymy grę
/ / n a c iś n ię c ie kla w isza A i f (inputSystem ->KeyDown(DIK_A)) OnKeyDown(VK_LEFT); / / n a c iś n ię c ie kla w isza D i f (inputSystem ->KeyDown(DIK_D)) OnKeyDown(VK_RIGHT); / / n a c iś n ię c ie kla w isza i f ( inputSystem->KeyDown(DIK_ADD)) OnKeyDown(VK_ADD); / / n a c iś n ię c ie kla w isza i f ( i nputSystem->KeyDown(DIK_SUBTRACT)) OnKeyDown(VK_SUBTRACT); / / n a c iś n ię c ie kla w isza Esc i f ( inputSystem->KeyDown(DIK_ESCAPE)) 0nKeyDown(VK_ESCAPE); / / n a c iś n ię c ie lewego p rz y c is k u myszy i f (inputS ystem ->B uttonD ow n(0))
{ / / je ś l i n a c iś n ię c ie może być ju ż ro zró żn io n e i f (b u tto n D e lta == O.Of)
{ OnMouseDownL(O.O); b u tto n D e lta = 0 .5 f;
/ / pół sekundy do następnego n a c iś n ię c ia
} } } Metoda ta wywołuje najpierw metodę CInputSystem: :U pdate() w celu aktualizacji in formacji o stanie urządzeń wejściowych używanych przez aplikację (na przykład kla wiatury i myszy). Następnie pobiera informację o zmianie położenia myszy i przekazuje ją metodzie obsługi OnMouseMove(). Sprawdza też, czy zostały naciśnięte wybrane klawi sze przekazując odpowiednie stałe DIK_ metodzie CInputSystem: :KeyDown(). A także — czy wybrany został jeden lub więcej przycisków myszy za pomocą metody C InputS ys tem: : ButtonDown( ). W przypadku wykrycia naciśnięcia jednego z interesujących klawi szy wywołuje metodę OnKeyDown() przekazując jej jako parametr wirtualny kod klawisza. Gdy naciśnięty został lewy przycisk myszy, wywołuje metodę OnMouseButton( ).
Klasa CSimpEngine Klasa CSimpEngine stanowi klasę pochodną klasy CEngine i reprezentuje kluczową część szkieletu SimpEngine. Pokazana poniżej definicja klasy pokazuje, że zawiera ona me todę obsługi wejścia OnKeyDown(), która może być modyfikowana w zależności od po trzeb tworzonej aplikacji. Przechowuje także obiekty klas CCamera i CWorld reprezentu jące odpowiednio kamerę i świat gry. c la s s CSimpEngine : p u b lic CEngine
{
p r iv a te : CCamera *gameCamera; / / kamera CWorld *gameWorld; / / ś w ia t g ry p ro te c te d : CCamera *OnGetCamera() { re tu rn gameCamera; } CWorld *O nG etW orld() { re tu rn gameWorld; }
Rozdział 2 0 . ♦ Tw orzenie szkieletu gry
v o id v o id v o id v o id v o id
577
O nP repare(); OnMouseDownL(f l o a t x, f l o a t y ) ; OnMouseMove(int d e lta X . i n t d e lta Y ); OnMouseMove(int x, i n t y . i n t ce n te rX , in t c e n te rY ); OnKeyDown(int n V irtK e y );
p u b li c : CSimpEngineO
{ gameCamera = new CCamera; gameWorld = new CWorld;
} C Sim pEngine(const char *szName, bool fs c re e n . in t w, i n t h, in t b) : CEngine(szName, fs c re e n , w, h, b)
{ gameCamera = new CCamera; gameWorld = new CWorld(gameCamera); gameCamera->centerX = ce n te rX ; gameCamera->centerY = ce n te rY ; gam eW orld->S etS creen(w ,h);
} -CSim pEngineO
{
d e le te gameWorld; d e le te gameCamera; gameWorld = NULL; gameCamera = NULL;
} }: Obiekty reprezentujące kamerę i świat gry pojawiały się już wcześniej podczas oma wiania szkieletu SimpEngine. Najwyższa więc pora, aby przyjrzeć im się bliżej.
Kamera Klasa CCamera wykorzystywana jest przez szkielet gry do określenia sposobu widzenia świata gry przez jej użytkownika. Zdefiniowana jest w następujący sposób: c la s s CCamera
{ p r iv a te : / / poniższe składowe o k re ś la ją p o ło ż e n ie i o r ie n ta c je kamery / / i wykorzystywane są przez metody MoveTo i LookTo CVector in it P o s it io n , fin a lP o s itio n ; CVector in itL o o k A t, fin a lL o o k A t; CVector lo o k A tV e l; CVector lo o k A tA c c e l; v o id U pdateLookA tO ; vo id UpdateMoveTo();
/ / prędkość o r ie n ta c ji kamery / / p rzy s p ie sz e n ie o r ie n ta c ji kamery
578
Część III ♦ Tworzymy grę
publi c : CVector CVector CVector CVector
p o sitio n ; v e lo c ity ; a cce leration; lookAt;
// // // //
położenie kamery prędkość kamery p rzysp ie sze n ie kamery wektor o rie n ta c ji
// wektory kierunków: w górę, na przód i w prawo CVector up; CVector forward; CVector rig h t; // kąty pochylenia kamery względem osi y i x flo a t yaw; flo a t p itch; in t screenWidth, scre en H e ight; in t centerX, centerY; CCamera(); CCamera(int width, in t height) {} CCamera(CVector *lo o k ); CCameraCCVector *pos, CVector *lo o k ); -CCameraC); void LookAt(CObject *o b je c t); // skierow anie kamery na obiekt void LookAtNowCCObject *o b je c t); // natychmiastowe skierow anie kamery na obiekt void MoveToCCObject *o b je c t); // z b liż e n ie kamery na obiekt void MoveToNowCCObject *o b je c t); // natychmiastowe z b liż e n ie kamery na obiekt // natychmiastowa zmiana położenia kamery // do punktu o podanych współrzędnych void MoveToNow(scalar_t x, s c a la r jt y , s c a la r jt z); void RotateYaw (scalar_t ra d ian s); void R o ta te P itch C sc a la rjt ra d ian s); void R o ta te R o ll(sc a la r_ t ra d ian s);
// obrót kamery względem o si y // obrót kamery względem osi x // obrót kamery względem o si z
// równania ruchu kamery void Anim ateCscalar t deltaTime);
Posługiwanie się obiektem kamery jest bardzo proste, ponieważ odbywa się na podsta wie danych odbieranych przez system wejścia. Najważniejszą metodą klasy CCamer a jest metoda C C a m e r a : : A n i m a t e ( ) , która działa podobnie do metody C O b j e c t : : A m m a t e C ), gdyż służy do przemieszczania i orientowania kamery. Ruch kamery opisany jest za pomocą prędkości i przyspieszenia, natomiast jej orientacja za pomocą kątów pochyle nia względem osi y i osi x. Wektor l o o k A t definiuje punkt, w który skierowana jest ka mera. Jeśli kamera będzie skierowana wzdłuż ujemnej części osi z, to wektor l o o k A t będzie równy ( 0 .0 , 0 .0 , - 1 .0 ) . Poniżej zaprezentowany został kod źródłowy metody C C a m e r a : : A n i m a t e ( ): void CCamera::A n im a te (sca larjt deltaTime)
{ i f ((yaw >= 360. Of) || yaw = O.Of;
(yaw 6 0 .Of) 6 0 .Of; < - 6 0 .Of) - 6 0 .Of;
579
11 g ra n ic e p o ch yle n ia kamery względem osi x
f l o a t cosYaw = (scalar_t)cos(D E G 2R A D (yaw )); f l o a t sinYaw = (scalar_t)sin(D E G 2R A D (yaw )); f l o a t s in P itc h = (s c a la r_ t)s in (D E G 2 R A D (p itc h )); f l o a t speed = v e lo c it y .z * d e lta T im e ; f l o a t strafeS peed = v e lo c it y .x * d e lta T im e ;
/ / prędkość naprzód lu b do t y lu / / prędkość w lewo lu b prawo
i f (speed > 15.0) speed = 1 5 .0 ; i f (stra fe S p e e d > 15.0) strafeS peed = 15.0 ; i f (speed < -1 5 .0 ) speed = -1 5 .0 ; i f (stra fe S p e e d < -1 5 .0 ) strafeS peed = -1 5 .0 ;
/ / o g ra n ic z e n ie prędkości / / naprzód
i f ( v e lo c ity .L e n g th ( ) > 0 .0 ) a c c e le ra tio n = - v e lo c it y * 1 .5 f;
/ / wpływ ta r c ia
//w
prawo
/ / do t y lu / / w lewo
v e lo c it y += a c c e le ra tio n *d e lta T im e ; / / o b lic z a nowe p o ło ż e n ie kamery / / na podstaw ie p rędkości ruchu w zdłuż o si z i o si x p o s it io n . x += float(cos(DEG2RAD(yaw + 9 0 .0 )))*s tra fe S p e e d ; p o s it io n . z += float(sin(D EG 2R AD(yaw + 9 0 .0 )))*s tra fe S p e e d ; p o s it io n . x += f l o a t (cosYaw)*speed; p o s it io n . z += flo a t(s in Y a w )*s p e e d ; / / wyznacza lo o k A t.x = lo o k A t.y = lo o k A t.z =
nowy w e kto r o r ie n ta c ji kamery f lo a t ( p o s it io n . x + cosYaw); f l o a t ( p o s it io n . y + s in P itc h ) ; f l o a t ( p o s it io n . z + sinYaw );
/ / używa fu n k c ji gluLookA t do um ieszczenia kamery w nowym p o ło że n iu /./ i j e j z o rie n to w a n ia g lu L o o k A t(p o s itio n .x , p o s itio n .y , p o s itio n .z, lo o k A t.x , lo o k A t.y , lo o k A t.z , 0 . 0 , 1 . 0 , 0 . 0 );
} Kończąc swoje działanie metoda Anim ateO określa nową pozycję i orientację kamery za pomocą funkcji g l uLookAt (). Jeśli weźmie się pod uwagę cykl gry, można zauważyć, że kamera jest pierwszym obiektem, dla którego wykonywana jest metoda A nim ateO . Powodem tego jest właśnie wywołanie funkcji gl uLookAt (). Zanim narysowane zostaną obiekty świata gry, musi najpierw zostać skonfigurowana kamera, aby OpenGL mógł ustalić prawidłowy sposób prezentacji grafiki.
580
Część III ♦ Tworzymy grę
Świat gry Klasa CWorld definiuje świat gry prezentowany przez szkielet SimpEngine. Jak już po kazano, metoda CEngine: :GameCycle() korzysta z klasy CWorld, aby wywołać metody P re p a re (), A n im a te ( ) i D raw ( ) dla wszystkich obiektów świata gry. Zwykle obiekt klasy CWorld reprezentuje określony poziom gry. Jeśli gra dysponuje wieloma poziomami, to obiekty klasy CWorld można dodatkowo umieścić w pewnym kontenerze i w ten sposób rozbudować hierarchię obiektów o kolejny szczebel. Klasa CWorld przechowuje także obiekt podsystemu dźwięku należący do klasy CAudioSystem, którego zadaniem jest za ładowanie i odtwarzanie odpowiednich dźwięków i podkładu muzycznego. A oto defi nicja klasy CWorld: c la s s CWorld
{ p ro te c te d : vo id O n A n im a te (flo a t d e lta T im e ); vo id OnDraw(CCamera *cam era); vo id O nP repareO ; p u b li c : HWND hwnd; C T errain ^ te r r a in ; CCamera *camera; CAudioSystem *audioSystem ;
/ / te re n / / kamera / / podsystem dźwięku
C W orldO ; CWorld(CCamera * c ) ; -C W orld O ; v o id LoadW orldO ; v o id U nloadW orld();
/ / ła d u je o b ie k ty św ia ta g ry / / usuwa o b ie k ty św ia ta g ry
/ / s to s u je zasady f iz y k i d la w sz y stkic h obiektów św ia ta g ry v o id A n im a te (flo a t d e lta T im e ); / / ry s u je w s z y s tk ie o b ie k ty św iata g ry v o id Draw(CCamera *cam era); v oid P repareO { O nPrepareO ; }
}: Na zasadzie przykładu umieszczona została w definicji klasy CWorld referencja obiektu klasy C T e rrain reprezentującego teren, po którynl porusza się gracz i często stanowi ko rzeń hierarchii obiektów.
Obsługa modeli Szkielet SimpEngine uzupełniony zostanie jeszcze o możliwość dodawania modeli w for macie MD2 za pomocą obiektów klasy CMD2Model. Klasa ta będzie klasą pochodną kla sy CObject, ponieważ modele MD2 należy traktować tak samo jak inne obiekty świata.
Rozdział 2 0 . ♦ Tworzenie szkieletu gry
581
Ponieważ pełen kod klasy CMD2Model przedstawiony został w rozdziale 18., nie będzie tutaj powtórzony. Należy pamiętać jedynie, aby zmienić wiersz c la s s CMD2Model
na c la s s CMD2Model : p u b lic CObject
W ten sposób klasa CMD2Model zostanie włączona do hierarchii dziedziczenia klasy ba zowej CObject. Klasa CMD2Model stanowi jedynie punkt wyjściowy, ponieważ szkielet gry będzie wyko rzystywać raczej obiekty klasy C E n tity . Klasa ta będzie reprezentować animowane mo dele MD2 i stanowić klasę pochodną klasy CObject. Klasa C E n tity przechowuje dodatkowo informację o orientacji modelu (obrót względem osi y), dźwięki, które może on wydawać w postaci obiektów CAudio (klasę tę omówimy wkrótce), bieżącą klatkę początkową i końcową animacji oraz prędkość animacji. Klasa C E n tity posiada następującą definicję: c la s s C E n tity : p u b lic CMD2Model
{
p ro te c te d : v o id O n A n im a te (flo a t d e lta T im e ); v o id OnDraw(CCamera *cam era); vo id O n C o llis io n (C O b je c t * c o l1is io n O b je c t) ; v o id O nP repare(); p u b lic : f lo a t d ir e c tio n ; CAudio *e n tity S o u n d ;
/ / o rie n ta c ja modelu (k ą t obrotu względem osi y w radianach) / / dźw ięki wydawane przez model / / w obecnej w e rs ji ty lk o jeden dźwięk
C E n tity ( ); - C E n tit y O ; i n t s ta te S ta r t, stateE nd; f l o a t d e lta T ; f l o a t animSpeed;
/ / k la tk a początkowa i końcowa a n im a cji / / używana podczas in te r p o la c ji k la te k / / tempo a n im a cji
vo id LoadAudio(CAudioSystem *audioSystem , char ^ file n a m e , bool is3DSound);
}:
System dźwięku Zadaniem klasy CAudioSystem jest tworzenie, odtwarzanie i zarządzanie dźwiękami i mu zyką za pomocą interfejsu DirectX Audio i obiektów klasy CAudio reprezentujących poje dyncze efekty dźwiękowe lub motywy muzyczne. Definicja klasy CAudi oSystem przedstawia się następująco:
582
Część III ♦ Tworzymy grę
c la s s CAudio
{ p r iv a te : ID irectM usicSegm ent8 *dmusicSegment;
/ / segment
/ / B ufor e fe k tu przestrzennego ID i rectSound3D Buffer *d s 3 D B u ffe r; bool is3DSound;
/ / w artość tr u e , j e ś l i używany je s t e fe k t p rze s trz e n n y
p ro te c te d : p u b lic : CAudioO { dmusicSegment = NULL; ds3D B uffer = NULL; is3DSound = fa ls e ; } ~ C A udio()
{ i f (dmusicSegment != NULL)
{ dm usicSegm ent->Release(); dmusicSegment - NULL;
} i f (ds3D B uffer != NULL)
{ d s 3D B u ffe r-> R e le a se (); ds3D B uffer = NULL;
} } v o id SetSegm ent(IDirectM usicSegm ent8 *seg) { dmusicSegment = seg; } ID irectM usicSegm ent8 *GetSegment() { re tu rn dmusicSegment; } v o id S et3 D B u ffe r(ID ire ctS o u n d 3 D B u ffe r * d s B u ff); ID irectS o u nd 3 D B u ffe r *G e t3 D B u ffe r() { re tu rn ds3D B uffer; } bool Is3DSound() { re tu rn is3DSound; } v oid Set3DSound(bool b) { is3DSound = b; } v o id Set3DParams(f lo a t m inD istance, f lo a t maxDis ta n c e ); v oid S et3 D P o s(flo a t x. f l o a t y , f l o a t z );
}: Klasa CAudio korzysta zarówno z interfejsu IDirectM usicSegm ent8, jak i ID ire c tS o und3DBuffer8. Jeśli jej obiekt reprezentuje tylko podkład muzyczny, to wystarczy jedynie załadować odpowiedni segment, a bufor efektu przestrzennego nie jest wykorzystywany. Klasa CAudioSystem zarządza obiektami klasy CAudio. Oto jej definicja: c la s s CAudioSystem
{ p r iv a te : ID i rectM usicLoader8 *dm usicLoader; ID i rectM usi cPerformance8 *dmusi cPerform ance; ID ire ctM u sicA u d io P a th 8 *dmusic3DAudioPath; ID i rectS ound3D Listener8 *d s 3 D L is te n e r; DS3DLISTENER dsListenerP aram s;
// // // //
o b ie k t ła d u ją c y o b ie k t wykonania ścieżka dźwięku o b ie k t słuchacza
/ / w ła ściw o ści słuchacza
Rozdział 2 0 . ♦ Tw orzenie szkieletu gry
583
p u b lic : C AudioSystem O ; -C A udioS ystem O ; bool InitD irectXA udio(H W N D hwnd); ID i rectSound3D Buffer8 *C re a te 3 D B u ffe r(); CAudio *C re a te (c h a r ^ file n a m e , bool is3DSound); ID irectM usicS egm ent8 *C reateSegm ent(char ^ file n a m e , bool is3DSound); v o id P lay(C Audio *a u d io , DWORD numRepeats); v o id Stop(CAudio * a u d io ); v o id PlayS egm ent(ID irectM usicSegm ent8 *dmSeg, bool is3DSound, DWORD numRepeats); v o id S topSegm ent(ID irectM usicSegm ent8 *dmSeg); v o id ShutdownO; v o id S e tL is te n e rP o s C flo a t cameraX, f lo a t cameraY, f lo a t cameraZ); v o id S e tL is te n e rR o l1o f f ( f l o a t r o l l o f f ) ; v o id S e tL is te n e r O r ie n ta tio n (flo a t forw ardX , f lo a t forw ardY , f l o a t forw ardZ , f l o a t topX , f l o a t topY , f l o a t topZ)
{ d s 3 D L is te n e r-> S e tO rie n ta tio n (fo rw a rd X , forw ardY , -fo rw a rd Z , topX , topY, topZ, DS3DJMMEDIATE);
} ID ire ctM u sicP e rfo rm a n ce 8 *G etP erform ance() { re tu rn dmusicPerformance; }
System cząstek Ostatnią z omawianych klas szkieletu SimpEngine jest klasa C P articleS ystem reprezen tująca system cząstek. W praktyce będą tworzone różne jej klasy pochodne po to, aby można było uzyskać efekty specjalne takie jak wybuchy, dym, ogień czy opady atmos feryczne. Sposoby korzystania z systemów cząstek omówione zostały w rozdziale 15. i dlatego nie będą omówione tutaj szczegółowo zastosowania klasy C P articleS ystem . Należy jednak przypomnieć, że najpierw należy utworzyć klasę pochodną klasy CPar tic le S y s te m , która będzie reprezentować wybrany rodzaj efektu specjalnego. Następnie trzeba dostarczyć implementację jej metod U pdateO , RenderO i I n it ia liz e P a r t ic le O , które opisywać będą zachowanie się cząstek tworzących efekt. Korzystając z systemu cząstek będzie można wywoływać jego metodę UpdateO z metody OnAnimateO odpo wiedniego obiektu oraz metodę R ender( ) z odpowiedniej metody OnDraw().
Podsumowanie W rozdziale tym przedstawiony został przykład szkieletu SimpEngine, który wykorzy stany zostanie w następnym rozdziale do stworzenia gry. Klasa CNode jest klasą bazową klasy CObject, którą wykorzystuje się do tworzenia hie rarchii obiektów wykorzystywanej przez szkielet SimpEngine.
584
Część III ♦ Tworzymy grę
Zasadniczą część szkieletu SimpEngine stanowi klasa COGLWindow, która tworzy okno grafiki OpenGL i obsługuje komunikaty wysyłane do tego okna przez system Windows. Dla każdego z obsługiwanych komunikatów klasa COGLWi ndow definiuje osobną, wirtu alną metodę obsługi. Klasa COGLWindow przechowuje także obiekt klasy ClnputSystem reprezentujący podsystem wejścia, który korzystając z interfejsu Directłnput pobiera in formacje o stanie klawiatury, myszy i manipulatora. System wejścia posiada metody umożliwiające sprawdzenie tego, czy poszczególne klawisze zostały naciśnięte, o ile przemieściła się mysz, jak zmienił się stan manipulatora. Klasa CEngi ne stanowi klasę pochodną klasy COGLWindow i zawiera metody obsługujące główną pętlę przetwarzania komunikatów systemu Windows, cykl gry oraz informacje wejściowe uzyskane za po średnictwem systemu wejścia. Na typowy cykl gry składają się etapy związane z pobraniem informacji wejściowej, zmianą położenia gracza, zastosowaniem sztucznej inteligencji, wyznaczeniem równań ruchu obiektów na podstawie praw fizyki, odtworzeniem dźwięków i narysowaniem grafiki. Cykl gry zastosowany w szkielecie SimpEngine nie stanowi dokładnego odbicia wymienionych etapów, ponieważ musi wywoływać metody PrepareC ), AnimateC) i Draw() dla wszystkich obiektów hierarchii klasy CObject. Sposób użycia urządzeń wejścia przez gracza (naciśnięcie klawisza, przesunięcie my szy, naciśnięcie przycisku manipulatora i tak dalej) pozwala określić metoda CEngi n e :: ChecklnputO, która wykorzystuje w tym celu obiekt klasy ClnputSystem zdefiniowany .w klasie COGLWindow. Klasa CSimpEngine stanowi klasę pochodną klasy CEngi ne i repre zentuje część szkieletu, która zyskuje możliwość sterowania po uruchomieniu gry. Klasa CCamera definiuje sposób widzenia świata gry przez gracza i tym samym sposób two rzenia jego graficznej reprezentacji. Klasa CWorld definiuje świat gry, którego obiekty ob sługuje szkielet SimpEngine. Metoda CEngi ne: :GameCycle() wykorzystuje klasę CWorld, aby wywołać metody PrepareC), AnimateC ) i DrawC) dla wszystkich obiektów świata. Dzięki temu, że klasa CMD2Model jest także klasą pochodną klasy CObject, możliwe staje się włączenie modeli w formacie MD2 do hierarchii obiektów gry. Klasa CEntity jest klasą pochodną klasy CMD2Model i hermetyzuje funkcjonalność pozwalającą korzystać z modeli MD2 w ten sam sposób co z innych obiektów gry. Klasa CAudioSystem zarządza tworzeniem i odtwarzaniem efektów dźwiękowych i frag mentów muzycznych reprezentowanych przez obiekty klasy CAudio. Klasy pochodne klasy CParticleSystem umożliwiają tworzenie dowolnych efektów specjalnych wyko rzystujących systemy cząstek.
Rozdział 21.
Piszemy grę: „Czas zabijania” Uwieńczeniem przedstawionych w tej książce zagadnień będzie napisanie własnej gry. Jej ukończenie zająć może około tygodnia przy zastosowaniu szkieletu SimpEngine przedstawionego w rozdziale 20. Gra wykorzystuje efekty eksplozji uzyskiwane przez wykorzystanie systemów cząstek, animowane modele w formacie MD2, wykrywanie zderzeń za pomocą sfer ograniczeń, sterowanie kamerą, efekty dźwiękowe, sztuczną inteligencję (w podstawowym zakresie) oraz tworzenie terenu i rysowanie jego repre zentacji graficznej. Gra jest bardzo prosta — głównie dlatego, że na jej opracowanie po święcono tak niewiele czasu. Na zakończenie lektury książki wskazana jest więc jej rozbudowa lub nawet stworzenie zupełnie nowej, lepszej gry! W rozdziale tym omówione zostaną następujące zagadnienia: ♦ wstępny projekt gry „Czas zabijania”; ♦ tworzenie świata gry, a zwłaszcza jej terenu; ♦ postacie gry i ich sztuczna inteligencja; ♦ symulacje rakiet i efekty wybuchów; ♦ graficzny interfejs użytkownika; ♦ korzystanie z gry; ♦ kompilacja i utworzenie programu wykonywalnego.
Wstępny projekt Trzeba przyznać, że jako autorzy na początku nie mieliśmy pojęcia, jak dokładnie bę dzie wyglądać gra omówiona w tym rozdziale. Chcieliśmy, aby gracz poruszał się po losowo generowanym terenie i mógł widowiskowo niszczyć wrogów. Żadnej treści. Żadnej fabuły. Żadnych nagród. Z czasem wpadliśmy na pomysł, aby ustanowić dla gracza limit czasu, w którym powi nien wyeliminować wszystkich przeciwników. W ten sposób określony został cel po stawiony przed graczem. Zdecydowaliśmy także, że przeciwnicy nie będą strzelać do
586
Część III ♦ Tworzymy grę
gracza. Łatwe zadanie? Nie całkiem, ponieważ wrogowie będą uciekać przed graczem, a nie dążyć do konfrontacji jak w przypadku większości gier. Jeśli gracz wyeliminuje wszystkich przeciwników w określonym czasie, to zostanie zwycięzcą, a w przeciwnym razie przegra. W ten sposób ustaliliśmy zasady gry „Czas zabijania” i rozpoczęliśmy pracę nad implementacją gry.
Świat gry Pierwszym elementem gry, który został opracowany był generowany losowo teren gry. Reprezentuje go klasa C T errain będąca klasą pochodną klasy CObject. Teren zdefinio wany jest za pomocą szeregu wartości z przedziału od 0 do 1, które następnie mnożone są przez odpowiedni współczynnik w zależności od szerokości terenu. Aby uzyskać bar dziej stromą rzeźbę terenu, można zwiększyć wartość tego współczynnika, a żeby wy gładzić jego rzeźbę, należy zmniejszyć współczynnik. Poniżej przedstawiona została defi nicja klasy C Terrain: c la s s C T errain : p u b lic CObject
{ private: in t width; float float float float float void void void void
terrainM ul; heightMul; scanDepth; textureM ul;
// // // // // //
k w a d r a t o w y t e r e n o w y m i a r a c h w i d t h na w i d t h n a j l e p i e j t a k i c h , ż e 2^n = w i d t h w spółczyn nik skalowania rozmiarów terenu współczynnik skalowania wysokości terenu głębokość widocznej części terenu współczynnik skalowania te k stu ry terenu
RangedRandomCf l o a t v l , f l o a t v2); Normali z e T e r r a in ( f lo a t f i e l d [ ] , i n t s iz e ); F ilte rH e ig h tB a nd (flo a t *band,int s t r id e , in t c o u n t,flo a t f i l t e r ) ; F i l t e r H e i g h t F i e l d ( f 1o a t f i e l d [ ] , i n t s i z e , f l o a t f i l t e r ) ; M a k e T e rra in P la sm a (flo a t f i e l d [ ] . i n t s i z e , f l o a t rough);
protected: // t e r e n n i e p orusza s i ę , równania ruchu zbędne v o i d O n A n i m a t e ( s c a l a r _ t d e l t a T i m e ) {} v o i d On Dr a w C C C a me r a * c a m e r a ) ; void OnCol1i s i o n (CObject * c o l 1i s i o n O b j e c t ) ; public: f l o a t *heightMap; CTexture t e r r a in T e x [ 5 ] ; flo a t fogColor[4];
// rysowanie terenu // zderzenia terenu
/ / d y n a m i c z n a mapa w y s o k o ś c i // w iele te k stu r terenu / / k o l o r mgły lu b n ie b a
C T e r r a i n i ); C T e rra in (in t width, flo a t rFactor); - C T e r r a i n O { d e le t e [] heightMap; } v o i d L o a d O {} v o i d U n l o a d O {} v o i d B u i 1d T e r r a i n ( i n t w i d t h , f l o a t r F a c t o r ) ; / / g e n e r u j e t e r e n
Rozdział 2 1 . ♦ Piszemy grę: „Czas zabijania”
587
f l o a t G etW idthO { re tu rn w id th ; } f l o a t G etM ulO { re tu rn te rra in M u l; } f l o a t G etScanDepth() { re tu rn scanDepth; }
/ / zwraca szerokość te re n u / / zwraca w spółczynnik skalow ania / / zwraca g łę b ie skanowania
f l o a t G etHeightCdouble x, double z );
/ / wysokość te re n u w punkcie
(x , z)
}: Jak łatwo zauważyć, obiekt klasy C T errain wymaga jedynie wykrywania zderzeń i pra widłowego tworzenia reprezentującej go grafiki. Metoda C T errain: :0nD raw( ) rysuje te ren korzystając ze zmiennej składowej scanDepth, która pozwala jej ustalić, jaki obszar terenu jest widoczny (w dal). Parametrem tej metody jest obiekt klasy CCamera, który z kolei pozwala określić, jaki wycinek terenu jest widoczny dla gracza. A oto implemen tacja metody C T e rra in ::0 n D ra w (): v o id C T e rra in : :0nDraw(CCamera *camera)
{ i n t z, x;
/ / zmienne indeksu
g l E nable(GL_DEPTH_TEST);
/ / włącza b u fo r g łę b i
g l Fogi(GL_F0G_M0DE, GL_LINEAR); gl Fogfv(GL_F0G_C0L0R, fo g C o lo r); glFogf(GL_FOG_START, scanDepth * 0 .2 f ) ; glFogf(GL_FOG_END, scanDepth * 2 .5 ) ; glHint(GL_FOG_HINT, GL_FASTEST); glEnable(GL_FOG);
/ / włącza mgłę
/ / k o n fig u ru je i włącza łą c z e n ie kolorów glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); g l E nable(GL_ALPFIA_TEST); glAlphaFunc(GL_GREATER,0 .0 ) ; glDisable(GL_ALPHA_TEST); / / w łącza obsługę te k s tu r i w ybiera podstawową te k s tu rę g l E nable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, te r r a in T e x [0 ].te x ID ) ; g lT e x E n v i(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); g lT e x P a ra m e te ri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); g lC o lo r 3 f ( l.0, 1 .0 , 1 .0 ); / / ry s u je łańcuchy tró jk ą tó w posuwając s ię w zdłuż o si z f o r (z = ( in t) ( c a m e r a - > p o s itio n .z / te rra in M u l - scanD epth), z=z p o s itio n .z / te rra in M u l + scanDepth) && z < w id th -1 ; z++)
{
g l B egin (GL_TRIANGLE_STRIP); f o r (x = ( in t) ( c a m e r a - > p o s itio n .x / te rra in M u l - scanD epth), x=x p o s itio n .x / te rra in M u l + scanDepth) && x < w id th -1 ; x++)
{ g lT e x C o o rd 2 f(te xtu re M u l * x, te x tu re M u l * z ); g lV e r t e x 3 f ( ( f lo a t) x * t e r r a in M u l, (flo a t)h e ig h tM a p [x + z * w id th ]* h e ig h tM u l, ( flo a t ) z * t e r r a in M u l);
588
Część III ♦ Tworzymy grę
g lT e x C o o rd 2 f(te xtu re M u l * ( x + l) , te xtu re M u l * 'z ) ; g lV e rte x 3 f( ( f 1o a t) ( x + l) * te r r a in M u l, ( flo a t)h e ig h tM a p [x + l + z * w id th ]* h e ig h tM u l, ( flo a t ) z * t e r r a in M u l); g lT e x C o o rd 2 f(te xtu re M u l * x, te xtu re M u l * ( z + D ) ; g lV e r t e x 3 f ( ( f lo a t) x * t e r r a in M u l, (flo a t)h e ig h tM a p [x + (z + l) * w id th ]* h e ig h tM u l, ( flo a t ) ( z + l) * t e r r a in M u l); g lT e xC o o rd 2 f(te xtu re M u l * ( x + l) , te xtu re M u l * (z + 1 )); g lV e rte x 3 f( ( f 1o a t) ( x + l) * t e r r a i nM ul, (flo a t)h e ig h tM a p [x + l + ( z + l) * w id th ]* h e ig h tM u l, ( flo a t ) ( z + l) * t e r r a in M u l);
} g lE n d O ;
} } Należy zwrócić uwagę na to, że na wstępie metoda C T e rra in : :0nDraw() konfiguruje mgłę o liniowej gęstości i umiarkowanej intensywności, a także włącza łączenie kolo rów, dzięki czemu efekt znikania terenu we mgle jest doskonalszy. W ten sposób mgła ukrywa przed graczem tę część terenu, która nie jest rysowana, o czym decyduje war tość zmiennej scanDepth. Metoda CTerrain::GetHelght() pozwala ustalić wysokość terenu w dowolnym jego punk cie, nawet pomiędzy wierzchołkami. W tym celu znajdowane są cztery wierzchołki naj bliższe danego punktu (x, z) i wysokość terenu wyznaczana jest na drodze interpolacji wysokości w tych czterech wierzchołkach. Metoda CTerrain: : GetHeight() wykorzy stywana jest przez metody wszystkich obiektów świata gry podczas wykrywania zde rzeń z terenem.
Przeciwnicy „Czas zabijania” dysponuje dwoma rodzajami przeciwników — Ogro i Sod. Nazwy te pochodzą od wykorzystanych tu modeli w formacie MD2. Przeciwników można równie łatwo wyeliminować, ale Ogro jest wolniejszy i mniej „inteligentny” od Soda. Dla reprezentacji tych postaci utworzyć trzeba najpierw klasę CEnemy jako klasę po chodną klasy C E n tity . Oto jej definicja: c la s s CEnemy : p u b lic C E n tity
{
p r iv a te : p ro te c te d : f lo a t d is tF ro m P la y e r f lo a t runSpeed; A IS ta te _ t a iS ta te ;
/ / o d le g ło ś ć p rze c iw n ika od gracza / / prędkość u c ie c z k i / / stan p rze ciw n ika
Rozdział 2 1 . ♦ Piszemy grę: „Czas zabijania”
589
/ / metodę tę zastępujem y im plem entując nowy ro d za j p rze ciw n ika V ir tu a l v o id OnProcessAIO {} / / obsługa zderzeń z innymi obiektam i v o id O n C o llis io n (C O b je c t * c o llis io n O b je c t) ; p u b lic : C Player * p la y e r; CEnemy()
{
/ / p rze c iw n ik wchodzi do g ry żywy isDead = fa l se; v e lo c it y = C V e cto r(0 .0 , 0 .0 , 0 .0 ) ; / / w e kto r ruchu p rze ciw n ika / / prędkość u c ie c z k i runSpeed = v e lo c it y . z; / / stan bezczynności SetState(MODELJDLE); / / zwrócony na północ d ir e c tio n = O.Of; / / gracz je szcze n ie is t n ie je p la y e r = NULL;
} -CEnemyO { } v o id P rocessA IO { O nP rocessA IO ; } / / sztuczna in te lig e n c ja v o id S e tP la ye r(C P la ye r *p ) { p la y e r = p; } / / re fe re n c ja do o b ie k tu gracza
}: Klasa CEnemy posiada dwie metody wirtualne, OnProcessAI( ) i O n C o llis io n ( ), które nale ży zastąpić tworząc własne klasy przeciwników. Metoda OnProcessAIO określa sposób „myślenia” przeciwnika. Stan działania przeciwnika przechowuje zmienna ai S ta te o do stępie chronionym.
Sztuczna inteligencja przeciwnika Sposób działania przeciwnika określony jest przez maszynę stanów. Stany tej maszyny zdefiniowane są za pomocą typu wyliczeniowego A IS ta te t : enum A IS ta te _ t
{
AIJJNCARING, AI_SCARED, AI_DEAD
/ / p rz e c iw n ik n ie je s t zaniepokojony / / p rz e c iw n ik je s t p rze stra szo n y i ucieka / / p rz e c iw n ik je s t martwy
}: „Czas zabijania” wyróżnia tylko kilka podstawowych stanów, w których może znajdo wać się przeciwnik: jeśli jeszcze żyje, to albo nie jest zaniepokojony, albo został prze straszony i ucieka przed graczem. Gdy przeciwnik nie został jeszcze przestraszony, to pozostaje bez ruchu lub porusza się w dowolnym kierunku. Tabela 21.1 przedstawia związek pomiędzy stanami przeciwnika i stanami modelu MD2 zastosowany w tej grze. Jak łatwo zauważyć, stanowi AI JJNCARING reprezentującemu nieprzestraszonego przeciw nika odpowiadają dwa stany modelu: MODEL IDLE lub MODEL RUN. To, w którym z nich w danym momencie znajduje się przeciwnik, zależy wyłączenie od jego sztucznej inte ligencji zaimplementowanej przez metodę wirtualną OnProcessAI ( ).
590
Tabela
Część III ♦ Tworzymy grę
21.1. Stany przeciwnika
Stan przeciwnika Al UNCARING
Stan modelu MODELJDLE MODEL RUN
AI_SCARED
MODEL_RUN
Al DEAD
MODEL DIE
Ogro Stwór ten porusza się powoli i ucieka przed graczem w mało inteligentny sposób. Gdy pozostawiony zostanie w spokoju (stan AI UNCARING), to prawdopodobieństwo, że bę dzie się poruszał wynosi tylko 25%. W pozostałych przypadkach pozostanie bezczynny (75%). Gdy gracz znajdzie się od niego w odległości mniejszej niż 100 jednostek, spło szy się (stan AI SCARED) i będzie uciekał pod kątem 45° (w jedną lub drugą stronę). Ilu struje to rysunek 21.1. Rysunek 21.1. Ogro ucieka przed graczem pod kątem 45°
Jeśli podczas ucieczki Ogro zderzy się z innym przedstawicielem swojego gatunku lub z Sodem, to powróci do stanu AI UNCARING i przestanie się poruszać. Ogro będzie także zderzać się z terenem, po którym się porusza. W takiej sytuacji będzie trzeba pobrać wysokość terenu w punkcie, w którym znalazł się Ogro, i „podnieść” jego pozycję na tę wysokość. Należy także sprawdzać współrzędne x i z pozycji Ogra, aby nie „uciekł” poza granice terenu. Jeśli Ogro zostanie trafiony rakietą, przejdzie do stanu AI DEAD. Poniżej przedstawiona została implementacja metody sztucznej inteligencji Ogra: COgroEnemy::OnProcessAI(): vo id COgroEnemy: :O nProcessAI() / / o b lic z a o d le g ło ś ć gracza CVector d i f f = p la y e r-> p o s itio n - p o s itio n ;
Rozdział 2 1 . ♦ Piszemy grę: „Czas zabijania”
591
i f ( a iS ta te != AI_DEAD)
{ / / j e ś l i gracz z n a jd u je s ię d o s ta te c z n ie b lis k o , rozpoczyna ucieczkę d is tF ro m P la y e r = s q r t ( d i f f . x * d i f f . x + d i f f . y * d i f f . y + d i f f . z * d i f f . z ) ; i f (d is tF ro m P la y e r < 100.0) a i S ta te = AI_SCARED; e ls e ai S ta te = AI_UNCARING;
} } Metoda COgroEnemy : :OnPrepare( ) zmienia stan modelu MD2 na podstawie stanu, w któ rym znajduje się Ogro: v o id COgroEnemy: :O nP repare()
{
f l o a t d irT o P la y e r;
/ / k ą t pomiędzy wektorami ruchu Ogra i gracza
C Vector d i f f ; / / w e kto r od Ogra do gracza d i f f . x = p o s itio n .x - p la y e r- > p o s itio n .x ; d i f f . z = p o s it io n . z - p la y e r- > p o s itio n .z ; d i f f .N orm ali z e ( ) ; / / z n a jd u je k ą t pomiędzy wektoram i ruchu Ogra i gracza / / w o d n ie s ie n iu do ujemnej części o si z d irT o P la y e r = R A D 2 D E G (d iff.A n g le (C V e cto r(0 ,0 ,- 1 ) ) ) ; / / in ic ju je g e n e ra to r lic z b pseudolosowych s ra n d ((u n s ig n e d in t)tim e (N U L L )); / / w yw ołuje metodę s z tu c z n e j in t e lig e n c ji P rocessA IO ; / / zm ienia m odelS tate na podstaw ie A Is ta te s w itc h (a iS ta te )
{ case AI_SCARED: / / o k re ś la k ie ru n e k u c ie c z k i Ogra d ir e c tio n = (d irT o P la y e r - 90) + ( (ra n d ()% 9 0 )-4 5 ); m odelS tate = M0DEL_RUN; v e lo c it y = C V e c to r(0 .0 . 0 .0 , 1 5 .0 ); break; case AIJJNCARING: d ir e c tio n = flo a t( r a n d O % 360); / / zwrócony w dowolnym k ie ru n k u i f ((ra n d O % 4) != 0) / / bezczynny 7b% czasu
{ m odelS tate = MODEL_IDLE; v e lo c ity = C V ector(0 .0 , 0 .0 , 0 .0 );
}
e ls e
{
v e lo c it y = C V e c to r(0 .0 , 0 .0 , 1 5 .0 ); m odelS tate = M0DEL_RUN;‘
} break; case AI_DEAD: m odelS tate = MODELDIE;
592
Część III ♦ Tworzymy grę
v e lo c ity = CVectorCO.O, 0.0, 0.0); i f (nextFrame == sta te Start) // usuwa Ogra ze świata gry { // po zakończeniu animacji jego agonii // usuwa Ogra isDead = true;
}
break; default: break;
} // wywołuje metodę dla stanów modelu MD2 CEntity::OnPrepare();
}
Sod Przeciwnik ten przypomina w zachowaniu Ogra, ale porusza się szybciej i nieco „inteli gentniej” — zaczyna uciekać już wtedy, gdy gracz znajdzie się w odległości 125 jedno stek w kierunkach pokazanych na rysunku 21.2. Kod implementujący jego zachowanie jest praktycznie identyczny z kodem Ogra, a jedyny wyjątek stanowi obliczenie kąta ucieczki. Nie będzie więc tutaj przedstawiony. Rysunek 21.2. Sod ucieka przed graczem pod kątem 60°
Rakiety i eksplozje Gracz może odpalać w kierunku przeciwników rakiety posługując się lewym przyci skiem myszy. Rakiety reprezentowane są przez obiekty klasy CRocket, która ładuje mo del rakiety w formacie MD2, rysuje go i wyznacza trajektorie jego ruchu. Rakiety poru szają się z prędkością 120 jednostek na sekundę. Klasa CRocket jest oczywiście klasą pochodną klasy CEntity. Poniżej przedstawiono jej definicję:
Rozdział 2 1 . ♦ Piszemy grę: „Czas zabijania”
593
c la s s CRocket : p u b lic C E n tity
{ p r iv a te : v oid S etu p E x p lo s io n T e x tu re O ;
/ / k o n fig u ru je te k s tu rę OpenGL
p ro te c te d : void O nA nim ate(scalar_t d e lta T im e ); / / wyznacza t r a je k to r ię ra k ie ty v oid O nC o llisio n (C O b je ct * c o llis io n O b je c t) ; / / wykrywa j e j zderzenie I I ry s u je ra k ie tę v o id OnDraw(CCamera *cam era); v o id OnPrepareO ; p u b lic : f lo a t d is ta n c e T ra v e l; CVector fo rw a rd ; bool is E x p lo s io n ; CTexture *e xp lo sio n T e x; CExplosion ^e x p lo s io n ;
/ / o d le g ło ś ć, k tó rą przebyła ra k ie ta / / kierunek ruchu ra k ie ty / / w artość tru e , gdy ra k ie ta wybuchła / / te k s tu ra wybuchu / / wybuch symulowany przez system cząstek
CRocket( ) ; -CR ocket0 ; vo id LoadO ; vo id U nloadO ;
} Uderzając w obiekt rakieta eksploduje. Efekt wybuchu tworzony jest przez obiekt klasy C Explosion, która jest klasą pochodną klasy C P articleS ystem . Zdarzenie to powoduje zmianę wartości składowej is E x p lo s io n klasy CRocket. Klasa C Explosion rysując wy buch wykorzystuje teksturę explosionT ex określoną przez klasę CRocket. Rysunek 21.3 przedstawia efekt wybuchu rakiety. Rysunek 21.3. Wybuch rakiety. Uwagą zwracają czworokąty pokryte teksturą, do których zastosowano łączenie kolorów
594
Część III ♦ Tworzymy grę
Interfejs użytkownika gry Klasa CGUI reprezentuje graficzny interfejs użytkownika gry „Czas zabijania”. Przed stawia on graczowi upływający czas i liczbę przeciwników, którzy pozostali do wyeli minowania. Wyświetla napisy: „Wygrałeś!” i „Przegrałeś!”, a także celownik na środku okna programu. A oto definicja klasy CGUI: c la s s CGUI
{ p rivate: // czas pozostający graczowi w bieżącej rozgrywce in t minutesLeft, secondsLeft. m illise con d sLe ft: in t enemiesLeft; // liczba przeciwników do wyeliminowania CFont *fon t: CFont ^c ro ssh a ir; CFont *endText;
// czcionka wykorzystywana do napisów // czcionka celownika // czcionka tekstu informującego o wygranej
public: C G U IO ; -CGUI O ; void void void void
SetCurrentTim e(float tim eLeft); SetEnem iesLeft(int eLeft); DrawO; Anim ate(float deltaTime);
//zmienia czas pozostający graczowi // zmienia lic zb ę przeciwników do wyeliminowania // rysuje in t e r fe js użytkownika // nieużywana
void DrawWinnerO; void DrawLoserO;
Bardzo prosta, prawda? Warto jeszcze sprawdzić, w jaki sposób klasa CGUI jest wyko rzystywana w programie zapoznając się z implementacją klasy CWorld umieszczoną w pliku world.cpp.
Korzystanie z gry Po załadowaniu programu zegar rozpoczyna odmierzanie limitu czasu, a gracz musi rozpocząć poszukiwanie przeciwników. Liczba przeciwników pozostających do wy eliminowania prezentowana jest w prawym górnym rogu ekranu. Za każdym razem, gdy uda się graczowi ustrzelić Ogra lub Soda, liczba ta będzie zmniejszać się. Gracz zostanie zwycięzcą, jeśli zdoła zniszczyć wszystkich przeciwników przed upływem zadanego czasu. Sposób sterowanie grą przedstawia tabela 21.2.
Rozdział 2 1 . ♦ Piszemy grę: „Czas zabijania”
595
Tabela 21.2. Sterowanie grą Klawisz lub przycisk
Funkcja
W
Ruch do przodu
S
Ruch do tyłu
A
Ruch w lewo
D
Ruch w prawo
Lew y przycisk myszy
Odpalenie rakiety
Ruch myszy
Spojrzenie w wybranym kierunku
K la w isz + (w bloku numerycznym)
Zwiększa czułość myszy
K la w isz - (w bloku numerycznym)
Zmniejsza czułość myszy
Esc
Koniec
Kompilacja gry Aby skompilować kod źródłowy gry i utworzyć plik wykonywalny, niezbędne są nastę pujące biblioteki: ♦ opengl32Jib — podstawowa biblioteka OpenGL; ♦ glu32.lib — biblioteka GLU; ♦ dxguid.lib — biblioteka DirectX GUID; ♦ winmmJib — biblioteka multimediów systemu Windows; ♦ dinputS.lib — biblioteka Directlnput. Używając kompilatora Microsoft Visual C++ należy rozwinąć menu Project, wybrać pozycję Setting, następnie zakładkę Link i wpisać podane wyżej nazwy plików bibliotek w wierszu Object/Libr ary Modules. Rysunek 21.4 przedstawia scenę z gry „Czas zabijania”. Rysunek 21.4. „ Czas zabijania ” — Ogro po lewej i Sod po praw ej
Czas: 4:53.00 Wrogowi«: 16
596
Częęć III ♦ Tworzymy grę
Podsumowanie W rozdziale tym przedstawione zostały tylko podstawowe informacje dotyczące gry „Czas zabijania”, które pozwolą rozpocząć pracę z jej pełnym kodem. Kod ten umiesz czony został na dysku CD dołączonym do książki. Omówiony został sposób tworzenia świata gry, jej postaci i ich sztucznej inteligencji, rakiet i wybuchów, prosty interfejs użytkownika, sposób korzystania z gry i kompilacji jej kodu. Z pewnością zaprezentowana gra może zostać istotnie ulepszona i rozbudowana. Przy kładowe modyfikacje mogą polegać na dodaniu warkoczy dymu do lecących rakiet, wy dawaniu dźwięków przez przeciwników, wprowadzeniu radaru do wykrywania Ogrów i Sodów, szybszej grafiki i nowych obiektów świata gry. Możliwości są nieograniczone i z pewnością czytelnicy będą potrafili zaskoczyć autorów niejednym pomysłem. Dysk CD dołączony do książki zawiera o wiele więcej materiału niż tylko kody źródłowe programów omawianych w książce. Specjalne podziękowania należą się Jeffowi „NeHe” Molofee za wybranie i umieszczenie na dysku CD ciekawych programów demonstra cyjnych wykorzystujących OpenGL. Na podziękowania takie zasłużył także Bas Kuenen, przede wszystkim za pomysły i pomoc w zaprojektowaniu szkieletu SimpEngine.
Dodatki
Dodatek A
Zasoby sieci Internet Istnieje wiele ciekawych zagadnień związanych z programowaniem gier, które chcieli śmy przedstawić w tej książce, lecz uniemożliwiły nam to ramy pojedynczego tomu. Na szczęście w sieci Internet dostępnych jest wiele informacji nadających się do wykorzy stania podczas tworzenia gier. Poniżej przedstawiamy wybór tych, które mogą okazać się najbardziej przydatne. W sieci Internet stworzyliśmy też specjalną stronę poświęconą niniejszej książce. Znaj duje się ona pod adresem http://glbook.gamedev.net i zawiera erratę, odpowiedzi na naj częściej zadawane pytania i inne dodatkowe informacje.
Programowanie gier W sieci Internet istnieją setki stron poświęconych temu zagadnieniu. Najlepsze z nich podajemy poniżej. Strony te dotyczą programowania gier w ogóle, a nie programowania z wykorzystaniem OpenGL.
GameDev.net
http://www.gamedev.net Wiodąca strona dla programistów gier o różnym zaawansowaniu. Zawiera ponad 1000 artykułów dotyczących wszystkich aspektów programowania gier, a w tym zastosowań OpenGL, DirectX, sieci komputerowych, dźwięku, grafiki, sztucznej inteligencji i wielu innych. Oprócz tego znaleźć tam można nowości z branży, przykłady kodów źródło wych, słownik terminologii związanej z programowaniem gier oraz największe i na jaktywniejsze forum gromadzące programistów gier z całego świata. Całość okraszona j est ciekawą oprawą graficzną.
600
Dodatki
Wyszukiwarka informacji związanych z programowaniem gier
http://www.gdse.com Serwis GDSE (Game Development Search Engine) umożliwia przeszukiwanie setek stron poświęconych programowaniu gier za pomocą specjalizowanej wyszukiwarki. Dzięki niemu można łatwo dotrzeć do interesującej odwiedzającego stronę informacji rozpro szonej na dziesiątkach stron w Internecie.
flipCode
http://www.flipcode.com Strona ta publikuje nowości oraz artykuły z zakresu programowania gier komputero wych. Szczególnie interesujące są artykuły uznanych programistów, w których dzielą się z odbiorcami swoimi osiągnięciami i wiedzą.
Gamasutra
http://www.gamasutra.com Strona ta jest własnością firmy Gama Network, która wydaje także Game Developer Magazine oraz organizuje konferencję Game Developers Conférence. Na stronie tej znaj dują się materiały publikowane wcześniej we wspomnianym czasopiśmie, prezentowane na konferencjach, a także oryginalne, dotąd niepublikowane teksty.
OpenGL Programiści OpenGL tworzą w Internecie aktywną społeczność. Już pierwszy próba wyszukania jakiejkolwiek informacji na ten temat przyniesie w efekcie dziesiątki — jeśli nie setki — stron poświęconych programowaniu w OpenGL. Poniżej przedstawiamy najbardziej interesujące.
NeHe Productions
http://nehe.gamedev.net NeHe Productions autorstwa Jeffa Molofee jest jedną z czołowych stron poświęconych technologii OpenGL. Obok ponad 40 oryginalnych kursów OpenGL zawiera także im ponujący zestaw programów demonstrujących możliwości OpenGL. Autor ciągle wzbo gaca także kolekcję referencji do innych stron poświęconych OpenGL.
D odatek A ♦ Zasoby sieci Internet
601
OpenGL.org
http://www.opengl.org OpenGL.org jest oficjalną stroną rady ARB. Regularnie publikuje ona wiadomości do tyczące rozwoju specyfikacji oraz zawiera szereg przydatnych informacji.
Inne strony poświęcone OpenGL http://reality.sgi. com/mjk/tips/
zaawansowane techniki OpenGL
http://glvelocity.gamedevnet. net
glVelocity
http://romka.demonews. com
Romka Graphics
http://nate.scuzzy. net/
strona Nate’a Millera
http://www.gamedev. net/opengl
OpenGL Journal
http://real ity. sgi. cim/blythe/s ig99/
notatki z kursu Siggraph 499
DirectX W sieci Internet istnieje także wiele stron poświęconych DirectX. Kilka, naszym zda niem najbardziej przydatnych, wymieniamy niżej.
DirectX Developer Center
http://msdn.microsoft.com/directx Tutaj możemy załadować najnowszą wersję pakietu DirectX SDK, poznać nowości i za poznać się z ważniejszymi wydarzeniami związanymi z DirectX, a także przeczytać arty kuły, których autorami są członkowie zespołu pracującego nad DirectX.
Lista mailingowa DirectX
http://discuss.microsoft.com/archives/directxdev.html Najczęściej zadawane pytania: http://m sdn.microsoft.com/library/techart/dxfaq2.htm Archiwum i uczestnictwo:
Lista prowadzona jest przez firmę Microsoft i stanowi jedno z najlepszych źródeł in formacji o DirectX. Zabierają tu głos członkowie zespołu DirectX, programiści wiodą cych producentów sprzętu oraz uznani programiści gier. Przed zarejestrowaniem się — a zwłaszcza przez zabraniem głosu w dyskusji — zalecamy zapoznanie się z archiwami listy i najczęściej zadawanymi pytaniami.
602
D odatki
Inne zasoby W Internecie znajdują się także omówienia zagadnień zbyt wyspecjalizowanych lub za awansowanych, by mogły znaleźć się w książkach. Poniżej prezentujemy kilka stron, które uzupełniają materiał przedstawiony w naszej książce.
ParticleSystems.com
http://particlesystems,com Strona poświęcona systemom cząstek. Zawiera doskonały interfejs programowy Particie Systems API.
Real-Time Rendering
http://www.realtimerendering.com Oficjalna strona doskonałej książki „Real-Time Rendering”. Zawiera także inne artykuły jej autorów oraz zbiór odnośników do interesujących informacji, które można znaleźć w Sieci. Skupia ona dużo wartościowych informacji, których eksploracji warto poświęcić więcej czasu.
Informacja techniczna dla programistów
htip://www.nvidia.com/developer.nsf Intel: http://cedar.intelcom /cgi-bin/ids.dll/main.jsp ATI: http://www.ati.com /na/pages/resource_centre/dev_rel/devrelhtm
NVIDIA:
Producenci sprzętu, tacy jak NVIDIA, Intel czy ATI, posiadają strony zawierające za awansowane informacje związane z tworzeniem oprogramowania graficznego wyko rzystującego ich produkty.
Artykuły dotyczące efektu mgły
http://www.gamedev.net/opengl/volfog.html http://ww.gamedev.net/reference/articles/article677.asp http://ww.gamedev.net/reference/articles/article672.asp W rozdziale 15. obiecaliśmy, że wspomnimy o artykułach poświęconych efektowi mgły objętościowej, więc dotrzymujemy danego słowa.
Dodatek B
Dysk CD Dysk CD dołączony do tej książki zawiera kody źródłowe i pliki wykonywalne progra mów omówionych w książce, a także wybór programów narzędziowych, demonstracyj nych i gier OpenGL.
Struktura plików na dysku CD Na dysku CD znajduje się pięć głównych katalogów zawierających: ♦ kod źródłowy — teksty źródłowe wszystkich przykładów programów omawianych w kolejnych rozdziałach tej książki; ♦ gry — dodatkowe przykłady gier opracowane i dostarczone przez utalentowanych autorów; ♦ programy demonstracyjne OpenGL — przykłady zastosowań OpenGL; ♦ programy i biblioteki — graficzne, multimedialne i narzędziowe; ♦ pakiety SDK — pakiety niezbędne do tworzenia aplikacji OpenGL i DirectX.
Wymagania sprzętowe Poniżej przedstawiamy minimalne oraz zalecane parametry systemu używanego do prze glądania dysku CD: ♦ napęd CD-ROM, DVD, CD-R lub CD-RW; ♦ przeglądarka stron internetowych: Internet Explorer 4.0 lub nowsza wersja, Netscape Navigator w wersji 4.0 lub nowszej; ♦ pamięć: minimum 32 MB, zalecane 64 MB lub 128 MB; ♦ karta grafiki: szybka karta ze sprzętową obsługą OpenGL, na przykład NVIDIA GeForce czy Voodoo3 lub lepsza; ♦ procesor: przynajmniej Pentium pracujący z częstotliwością 450 MHz (im szybszy, tym lepiej).
604
D odatki
Instalacja Po włożeniu dysku CD powinno się ukazać automatycznie menu dysku, jeśli korzystamy z systemu Windows 9x/Me/XP i jest włączona opcja automatycznego uruchamiania dys ku. W przeciwnym razie należy ręcznie wybrać w folderze Mój Komputer napęd, w któ rym znajduje się dysk CD (najczęściej D:) i kliknąć dwukrotnie plik start.exe.
Typowe problemy i ich rozwiązywanie Niezależnie od tego, ile wysiłku zostanie włożone w przetestowanie oprogramowania, i tak zawsze trzeba liczyć się z tym, że pojawią się jakieś problemy. Przed umieszczeniem na dysku wszystkie programy zostały sprawdzone przez aktualną wersję skanera antywirusowego. Dysk CD przeszedł pomyślnie tę weryfikację i przyję liśmy, że jest wolny od tego rodzaju zagrożeń. Wszystkie pliki umieszczone na dysku CD posiadają atrybut „tylko-do-odczytu”. Po ich przeniesieniu na dysk twardy należy pamiętać o usunięciu tego atrybutu, ponieważ w prze ciwnym razie kompilacja programów nie będzie możliwa. Większość przykładów umieszczonych na dysku CD zakłada znajomość języka C++ oraz umiejętność radzenia sobie ze zwykłymi problemami, które napotyka każdy progra mista. Typowym problemem gnębiącym początkujących programistów jest ustawiczne zapominanie o konieczności dołączania odpowiednich bibliotek w celu uzyskania wy konywalnego programu. Gwarantujemy, że wszystkie programy zamieszczone na dysku kompilują się poprawnie i dają w efekcie poprawne programy wykonywalne, gdy dołą czone zostaną odpowiednie biblioteki. Jeśli podczas kompilacji pojawią się jakieś pro blemy, może to wiązać się jedynie z nieodpowiednią wersją kompilatora. Mimo że programy demonstracyjne zostały także przetestowane, to nie można wyklu czyć, że niektóre z nich w szczególnych warunkach mogą doprowadzić nawet do zawie szenia się systemu operacyjnego. W takim przypadku należy uruchomić system od nowa i nie uaktywniać więcej programu, który doprowadził do jego zawieszenia. Zawieszenie się systemu operacyjnego nie powoduje jego uszkodzenia. Najczęstszym problemem, który można napotkać przy uruchamianiu wersji demonstra cyjnych programów graficznych, jest pojawienie się komunikatu: „Failed to create ren dering context”. Oznacza on niemożność utworzenia właściwego kontekstu tworzenia grafiki. W takim przypadku pomaga zwykle zmiana konfiguracji pulpitu prowadząca do tego, by używał on 16-bitowego kodowania kolorów. Jeśli działanie niektórych programów demonstracyjnych wydaje się zbyt wolne, to warto sprawdzić, czy karta graficzna rzeczywiście posiada sprzętową obsługę OpenGL i czy nie jest dostępna nowsza wersja jej sterownika. Miłej eksploracji dysku CD!
Skorowidz A AdjustWindowRectEx(), 69 akumulacja obrazów, 324 alfa, 173 ‘ algorytm cieniowania, 206 metody bryły cieni, 383 RLE, 190 animacje, 471 modele MD2, 471 powiewająca flaga, 213 sterowanie modelem, 498 ANSI CHARSET, 288 antialiasing, 38, 101 odcinków, 103 wielokątów, 106 aplikacje, 40 multimedialne, 36 OpenGL, 60, 67 pełnoekranowe OpenGL, 67 sieciowe, 30, 37 Windows, 41, 52, 570 ARB, 30, 38, 237 Architectural Review Board, 237 architektura DirectX, 35 gry, 27, 28 OpenGL, 31 otwarta, 29 szkieletu SimpEngine, 561 ASCII, 172, 290 automatyczne tworzenie mipmap, 213 AVI, 36
B BeginPaint(), 44 bezpośredni dostęp do danych, 408 BGRA, 194 B IR G B , 190 biblioteki dinput8.1ib, 398 Directlnput, 595 DirectX, 21 DLL, 398 dxguid.lib, 398 GLAUX, 163 GLU, 30, 31,327 GLUT, 33 graficzne, 19 multimediów, 595 bieżąca macierz przekształceń, 114 BITMAPFILEHEADER, 189, 191 BITMAPINFOHEADER, 189, 190, 191, 201 blokowanie tablic wierzchołków, 280 błędy GL_STACK_OVERFLOW, 124 GLSTACKUNDERFLOW , 124 BMP, 181, 188, 189, 541 wczytywanie plików, 190 zapis obrazu, 191 bryła cienia, 383 widoku, 8 6 , 118 bufor, 301, 326 akumulacji, 38, 301, 305, 324, 326 DirectSound, 450 efektu przestrzennego, 451 GDI, 303 geometrii, 38
606
D odatki
bufor głębi, 58, 177, 180, 187, 261, 301, 305, 306, 307, 326, 376, 381 IDirectSound3DBuffer, 456 koloru, 143, 179. 187, 301,305 konfiguracja formatu pikseli, 301 obrazu, 301 OpenGL, 303 opróżnianie, 304 powielania, 187, 301, 305, 316, 318, 319, 326, 377, 381 przełączanie, 6 6 stereoskopowy. 303 Z niezależny od sprzętu, 38 zerowanie, 6 6 buforowanie, 59 danych, 409 podwójne, 65, 305 stereoskopowe. 306, 326
c c_dfDIJoystick2, 406 c_dfDIKeyboard, 406 c_dfDIMouse, 406 CalcBoundingBox(), 534 CALLBACK, 42, 129, 403 CAudio. 581. 582 CAudioSystem, 580, 581, 582 CCamera, 576, 577 Animate( ), 578 CD HREDRAW, 45 CEnemy, 588 OnCollision(), 589 OnProcessAI(), 589 CEngine, 573 EnterMessageLoop(), 573 GameCycle(), 574, 580 CEntity, 581, 588 CGUI, 594 ChangeDisplaySettings(), 6 8 CHiResTimer, 518, 520, 553, 573 ChoosePixelFormat(), 59, 64, 302, 304 ciągłość, 339 krzywizny, 339 pozycyjna, 339 styczna, 339 cienie, 379, 384 bryła, 383 bufor głębi, 381 bufor powielania, 381 kuli, 379 macierz rzutowania, 380 ograniczanie obszaru, 381
rzutowanie, 380, 382 statyczne, 379 wiele zacienianych powierzchni, 382 wiele źródeł światła, 382 cieniowanie, 145, 180, 206 gładkie, 146 Gouraud, 146 płaskie, 146 CInputSystem, 411, 572, 575 AcquireAll(), 413 ButtonDown(), 576 Initialize(), 412 Shutdown(), 412 UnacquireAll(), 413 Update(), 413, 576 CKeyboard, 414 Acquire(), 415 Constructor, 414 Unacquire(), 415 Update(), 415 ClearColor(), 304 ClearFont(), 288, 291 CloseDown(), 435 CMD2Model, 488, 490, 498, 580, 584 Animate(), 490, 495 GetState(), 499 Load(), 490, 491 LoadModel(), 490, 493 LoadSkin(), 490, 494 RenderFrame(), 497 SetState(), 499 SetTexture(), 490, 495 SetupMD2Texture(), 490 SetupSkin(), 490 Unload(), 497 CMouse, 416 Acquire(), 418 Unacquire(), 418 Update(), 417 CNode, 562, 563 Attach(), 564 AttachTo(), 564 CountNodes(), 565 Detach(), 565 IsFirstChild(), 564 IsLastChild(), 564 CObject, 530, 531, 540. 544, 566, 569, 574, 584 Animate(), 567, 568, 569 Draw(), 567, 568 OnAnimate(), 566, 569, 570 OnCollision(), 567, 569 OnDraw(), 567, 569 OnPrepare(), 567, 569, 570 Prepare(), 568, 569 ProcessCollisions(), 568, 569
Skorowidz
CoCreateInstance(), 429, 430 COGLWindow, 562, 570, 575, 584 COgroEnemy OnPrepare(), 591 OnProcessAI(), 590 CoInitialize(), 429 COM, 38, 397, 468 Common Object Model, 397 CParticleSystem, 366, 583, 584, 593 Emit(), 367 InitializeSystem(), 367 KillSystem(), 368 CPlane, 527 DistanceToPlane(), 529, 535, 536 PointOnPlane(), 529, 536 RayIntersection(), 529 CPlayer, 550 Animate(), 551 Move(), 551 CPuck, 544 Animate(), 545, 547, 549 Draw(), 545 CreateAudioPath(), 446 CreateBitmapFont(). 287, 288, 290 CreateDevice(), 400 CreateFont(), 285, 287 CreateOutlineFont(), 290, 291 CreateStandardAudioPath(), 445, 446 CreateWindow(), 48 CreateWindowEx(), 48, 49 CRocket, 592 CS CLASSDC, 45 CS DBLCLKS, 45 CS GLOBALCLASS, 45 CS NOCLOSE, 45 CS OWNDC, 45 CS PARENTDC, 45 CS_SAVEB1TS, 45 CS_VREDRAW, 45 CSimpEngine, 576 CSnowstorm, 369 lnitializeParticle(), 371 InitializeSystem(), 372 KillSystem(), 373 Konstruktor, 370 Render(), 372 Update, 371 CTable, 539, 540 Dravv(), 542 Load(), 540, 541,542 SetupTexture(), 540, 541, 542 CTerrain, 586 GetHeightO, 588 OnDraw(), 587
607
CTexture, 540 LoadTexture(), 540 CVector, 521 CrossProduct(), 524 DotProduct(), 524 Length(), 525 Normalize(), 525 Reflection(), 538 UnitVector(), 525 CWorld, 576, 580, 594 cykl gry, 574, 584 czas, 505, 517 cząstki, 359 czcionki Arial, 288 konturowe, 289 pokryte teksturą, 292 przekształcenia w przestrzeni trójwymiarowej. 292 rastrowe, 285 systemu Windows, 285 trójwymiarowe, 289 czworokąty, 108
D D3DX, 34 DEVMODE, 67, 6 8 DI OK, 399 DI8 DEVCLASS_ALL, 403 DI8 DEVCLASS_DEVICE, 403 DI8 DEVCLASS_GAMECTRL, 403 DI8 DEVCLASS_KEYBOARD, 403 DI8 DEVCLASS_POINTER, 403 DI8DEVTYPE_1 STPERSON, 401 DI8 DEVTYPE_DEVICE, 401 DI8 DEVTYPE_DRIVING, 401 DI8 DEVTYPE_FLIGHT, 401 DI8 DEVTYPE_GAMEPAD, 401 DI8 DEVTYPE_JOYSTICK, 401 DI8 DEVTYPE_KEYBOARD, 402 DI8 DEVTYPE_MOUSE, 402, 403 DI8 DEVTYPE_SCREENPOINTER, 402. 403 DI 8 DEVTYPE_SUPPLEMENTAL, 402 DIDATAFORMAT, 406 DIDCALIAS, 404 DIDC ATTACHED, 404 DIDC FORCEFEEDBACK, 404 DIDC POLLEDDATAFORMAT, 404 DIDC_POLLEDDEVICE, 404 DIDEVCAPS, 404 DIDEVICEINSTANCE, 403 DIDEVICEOBJECTDATA, 409 DIDEVICEOBJECTINSTANCE, 405 DIDFT_ABSAXIS, 405
608
DIDFTALL, 405 DIDFTAXIS, 405 DIDFTBUTTON, 405 DIDFTNODATA, 405 DIDFTOUTPUT, 405 DIDFTPOV, 405 DIDFTPSHBUTTON, 405 DIDFTRELAXIS, 405 DIDFTTGLBUTTON, 405 DIEDFLALLDEVICES, 403 DIEDFLATTACHEDONLY, 403 DIEDFLFORCEFEEDBACK, 403 DIENUMCONTINUE, 403 DIENUMSTOP, 403 DIEnumDeviceObjectsCallback(), 405 DIERRINVALIDPARAM, 408 DIERR NOTINITIALIZED, 408 DIERROTHERAPPHASPRIO, 408 dinput.h, 398 dinput8.1ib, 398, 595 DIPROPBUFFERSIZE, 407 DIPROPHEADER. 407 Direct Audio, 36 Direct3D, 34, 36, 398 DirectDraw, 34, 36 Directlnput, 34, 36, 37, 391, 397, 572 bezpośredni dostęp do danych, 408 buforowanie danych, 409 DI_OK, 399 DirectlnputDevice, 398 dodawanie urządzeń, 399 format danych urządzenia, 405 inicjacja interfejsu, 397 klawiatura, 400 kończenie pracy z urządzeniem, 409 modyfikacja właściwości urządzenia, 407 mysz, 400 obiekty DirectlnputDevice, 399 odpytywanie urządzeń, 409 odwzorowania akcji, 410 pobieranie danych wejściowych, 408 poziom współpracy, 407 sprawdzanie możliwości urządzenia, 404 tworzenie obiektów DirectlnputDevice, 398 tworzenie urządzeń, 400 urządzenia, 401 urządzenia wejściowe, 391 wartości zwracane przez funkcje, 398 wyliczenia obiektów', 405 wyliczenia urządzeń, 400 zajmowanie urządzenia, 408 znaczniki możliwości urządzenia, 404 znaczniki poziomu współpracy, 407 znaczniki typu obiektów, 405 znaczniki wyliczenia urządzeń, 403
D odatki
DIRECTINPUT_VERSION, 398 DirectInput8 Create(), 398, 399 DirectInputDevice, 398 DirectMusic, 34, 425, 428, 468 DirectPlay, 34, 36 DirectSetup, 37 DirectShow, 34, 36, 468 DirectSound, 34, 425, 445 brzmienia instrumentów, 430 bufor efektu przestrzennego, 451 bufor miksowania, 428 bufor ujścia, 428 bufor zasadniczy, 428 inicjacja COM, 429, 430 inicjacja obiektu wykonania, 429, 430 kontrola odtwarzania, 434 kończenie odtwarzania, 435 liczba odtworzeń segmentu, 434 ładowanie dźwięku, 429 ładowanie instrumentów, 432 ładowanie segmentu, 430, 431 odległość maksymalna, 453 odległość minimalna, 453 odtwarzanie dźwięku, 429 odtwarzanie segmentu, 430, 432 parametry przestrzennego źródła, 451 tworzenie obiektu ładującego, 430, 431 tworzenie obiektu wykonania, 429, 430 zatrzymanie odtwarzania, 433 DirectX, 21, 25, 28, 30, 33, 37, 398, 601 architektura, 35 Directlnput, 36, 391 DirectPlay, 36 DirectSetup, 37 DirectShow, 36 DirectX Audio, 36 DirectX Graphics, 36 HAL, 35 HEL, 35 historia, 34 lista mailingowa, 601 DirectX 8 , 397, 451 DirectX 8.0 SDK, 29 DirectX Audio, 36, 37, 421, 425, 426, 445, 446, 450. 468 bufory, 428 DirectMusic, 425 DirectSound, 425 efekt przestrzenny, 456 graf narzędzi segmentów, 428 graf narzędzi ścieżki dźwięku, 428 graf narzędzi wykonania, 428 instrumenty, 427 kanały wykonania, 427 komunikaty, 427
Skorowidz
ładowanie dźwięków, 427 obiekty ładujące, 426 przepływ danych dźwięku, 428 segmenty, 426 stany segmentów, 426 syntezator DSL, 427 ścieżki dźwięku, 428 wykonanie, 427 DirectX Developer Center, 601 DirectX Graphics, 36 DirectX GUID, 595 DirectX Media Objects, 36 DirectX SDK, 406 DISCLBACKGROUND, 407 DISCLEXCLUSIVE, 407 DISCL FOREGROUND, 407 DISCLNONEXCLUSIVE, 407 DISCLNOWINKEY, 407 DispatchMessage(), 50, 51 DisplayMD2(), 480 DisplayMD2Interpolate(), 485, 487, 488 DisplayScene(), 116 DLL, 398 DLS, 36 DLS Level 2, 427 długość wektora, 72 DMO, 36 DMUS_APATH_DYNAMIC_3D, 447 DMUS_APATH_DYNAMIC_MONO, 447 D M U SA PATH D YN A M ICSTEREO, 447 DMU S A P ATH_SH ARED_ STEREOPLUSREVERB, 447 DMUS_SEG_REPEAT_INFINITE, 434 DMUS_SEGF_AFTERLATENCYTIME, 448 DM U SSEGFA FTERPREP ARETIME, 448 DMUS SEGF AFTERQUEUETIME, 448 DMUS SEGF ALIGN, 448 DMUSSEGFAUTOTRANSITION, 448 D M U SSE G FB EA T, 448 DMUS SEGF CONTROL, 448 DMUS SEGF DEFAULT, 448 DMUS SEGF GRID, 448 D M U S S E G F M ARKER, 448 DM USSEGFJM E ASURE, 448 DMUS_SEGF_NOINVALIDATE, 448 DMUS_SEGF_QUEUE, 448 DMUS_SEGF_REFTIME, 448 DMUS_SEGF_SECONDARY, 448 DMUS_SEGF_SEGMENTEND, 448 DMUS_SEGF_TIMESIG_ALWAYS, 448 DMUS_SEGF_USE_AUDIOPATH, 447, 448 DMUS_SEGF_VALID_START_BEAT, 448 DMUS SEGF VALID STARTGRID, 448
609
DMUS_SEGF_VALID START MEASURE, 448 DMUS_SEGF_VALID_START_TICK, 448 Doom, 26 Doom 3, 20 DOS, 34, 41 dostępność tekstur wielokrotnych, 238 downloadable sounds, 425 DrawCube(), 120, 155 DrawSurface(), 378 DrawTextureCube(), 202 druga zasada dynamiki, 511 drzewa, 355 DS3D_DEFAULTMAXDISTANCE, 453 DS3D DEFAULTMINDISTANCE, 453 DS3DBUFFER, 452, 453 DS3DLISTENER, 456 DS3DMODE DISABLE, 453 DS3DMODE_HEADRELATIVE. 453 DS3DMODE NORMAL, 453 DSL, 427 DSP, 424 dualizm korpuskularno-falowy, 141 dwuwymiarowe tablice, 138 dxguid.lib, 398, 595 dynamika Newtona, 510 dyski, 329 współrzędne tekstury, 330 wycinki, 331 dziedziczenie, 530 dźwięki, 36, 421 amplituda, 422 cyfrowy zapis, 423 częstotliwość, 422 częstotliwość próbkowania, 423 DLS, 36, 425 efekt Dopplera, 454 głośność, 450 komputery, 423 kończenie odtwarzania, 435 MIDI, 425 odbiorca, 455 odtwarzanie, 468 percepcja położenia źródła, 450 położenie źródła, 450 procesor sygnałowy, 424 przepływ danych, 428 przestrzenne, 450 rozdzielczość próbkowania amplitudy, 424 synteza, 424 ścieżki, 445 tłumienie, 451 współrzędne przestrzeni, 450 źródło, 423
610
D odatki
E efekty cienie, 379 Dopplera, 454 dźwiękowe, 27, 581 dźwięku przestrzennego, 450 flesza, 173 latarni, 173 mgła, 373, 375 odbicia, 166, 375 oświetlenia, 255 plakatowanie, 355 przezroczystości, 173, 174 specjalne, 187, 355 system cząstek, 359 tekstury wielokrotnej, 261 wybuchu, 593 ekran, 301 kopiowanie danych, 187 eksplozje, 592 EndPaint(), 44 EnumDevices(), 400, 403, 404 EnumObjects(), 405 ewaluatory, 339
F FAILED, 399 fclose(), 190, 191 filtrowanie, 212, 230 GL LINEAR, 208 GL_LINEAR_MIPMAP_LINEAR, 208 GL_LINEAR_MIPMAP_NEAREST, 208 GL NEAREST, 208 GL_NEAREST_MIPMAP_LINEAR, 208 GL_NEAREST_MIPMAP_NEAREST, 208 mipmapy, 213 tekstur, 204, 208 FindBoundingSphereRadius(), 532 flesze, 173 fopen(), 190 format pikseli, 58, 301 fov, 135 fread(), 190, 191 free(), 191 fseek(), 191 funkcje AdjustWindowRectEx(), 69 BeginPaint(), 44 CalcBoundingBox(), 534 ChangeDisplaySettings(), 6 8 ChoosePixelFormat(), 59, 64, 302, 304 ClearColor(), 304
ClearFont(), 288, 291 CoCreateInstance(), 429, 430 CoInitialize(), 429 CreateBitmapFont(), 287, 288, 290 CreateFont(), 285, 287 CreateOutlineFont(), 290, 291 CreateWindow(), 48 CreateWindowEx(), 48, 49 DirectInput8 Create(), 398, 399 DispatchMessage(), 50, 51 DisplayMD2(), 480 DisplayMD2Interpolate(), 485, 487, 488 DisplayScene(), 116 DrawCube(), 120, 155 DrawSurface(), 378 DrawTextureCube(), 2 0 2 EndPaint(), 44 EnumDevices(), 400, 403 fclose(), 190, 191 FindBoundingSphereRadius(), 532 fopen(), 190 fread(), 190, 191 free(), 191 fseek(), 191 fwrite(), 192 GetAsyncKeyState(), 394, 396 GetDC(), 54, 65 GetElapsedSeconds(), 520 GetFPS(), 519, 520 GetMessage(), 51 glAccum(), 324, 325 glActiveTextureARB(), 239, 240 glArrayElement(), 279 glBaseList(), 286 glBegin(), 99, 100, 101, 102 glBindTexture(), 203, 207, 217, 232, 240, 272 glBitmap(), 183, 184, 185 glBlendFunc(), 173 glBuild2DMipmaps(), 213 glCallList(), 100, 269, 271, 272 glCallLists(), 100, 270, 271, 286 glClear(), 6 6 , 116, 128, 162,304 glClearAccum(), 304, 326 glClearColor(), 128, 326, 340 glClearDepth(), 304 glClearIndex(), 145 glClearStencil(), 304, 326 glClientActivateTextureARB(), 239, 280 glColor(), 100 glColor2f(), 289 glColor3f(), 116, 143, 144, 164, 170, 181 glColor3fv(), 143 glColor4f(), 143 glColor4fv(), 143
Skorowidz
glColorMask(), 377 glColorMaterial(), 164 glColorPointer(), 276 glCopyPixels(), 187, 198, 306 glCullFaceO, 105 glDeleteLists(), 271,272, 288 glDepthFunc(), 307 glDepthMask(), 177, 179, 180, 377 glDisable(), 101, 103, 170 glDisableClientState(), 275, 280 glDrawArrays(), 278, 280 glDrawBuffer(), 306, 313, 314 glDrawElements(), 278, 280, 282, 283 glDrawPixels(), 185, 186, 187, 196, 198 glDrawRangeElements(), 279 glEdgeFlag(), 100, 106 glEdgeFlagPointer(), 276 glEnable(), 101, 103, 128, 152, 164, 173, 176, 178, 275,317,374 glEnableClientState(), 275, 280 glEnd(), 100, 101 glEndList(), 268, 269 glEvalCoord(), 100 glEvalCoordlf(), 342 glEvalMeshl(), 343 glEvalMesh2(), 345, 349 glEvalPoint(), 100 glEXTLockArrays(), 283 glEXTUnlockArrays(), 283 glFlush(), 116, 162 glFrontFace(), 106, 155, 162 glFrustum(), 134, 135 glGenLists(), 268, 270, 271, 285 glGenTextures(), 203, 207 glGet(), 93, 94, 98, 99, 101, 105, 110, 269 glGetBooleanv(), 182 glGetFloatv(), 102, 356 glGetIntegerv(), 240 glGetString(), 238 gllndex(), 1 0 0 gllndexf()„ 145 gllndexfv(), 145 glIndexPointer(), 277 glIsEnabled(), 98, 99, 101, 110 glLightfv(), 155, 158, 159, 160, 161, 162, 166, 168, 172, 178 glLightModel(), 165 glLightModeli(), 165, 166 glLineStipple(), 103 glLineWidth(), 103 glListBase(), 286 glListName(), 271 glLoadIdentity(), 114, 116, 119, 128, 130, 133, 138, 182
611
glLoadMatrix(), 138, 139 glLockArraysEXT(), 281 glMaplf(), 341,342, 344 glMap2f(), 344, 345, 346, 349 glMapGridlf(), 342, 343,345 glMapGrid2(), 345 glMaterial(), 100 glMaterialf(), 163 glMaterialfv(), 155, 164, 167 glMateriali(), 169 glMatrixMode(), 119, 123, 130, 133, 182, 253 glMultiTexCoord2f(), 242, 246 glMultiTexCoord2fARB(), 240, 241 glMultiTexCoord3dARB(), 241 glMultiTexCoordifARB(), 239 glMultMatrix(), 139, 253 glMultMatrixf(), 378 glNewList(), 268, 269 glNormal(), 100, 279 glNormal3f(), 151, 152 glNormalPointer(), 277 glOrtho(), 134, 182 glPixelStorei(), 188, 196 glPixelZoom(), 187, 188, 198 glPointSize(), 101 glPolygonMode(), 104 glPolygonStipple(), 107 glPopAtrrib(), 271 glPopMatrix(), 124, 128, 132, 253, 271, 570 glPushAttrib(), 170, 172, 271 glPushMatrix(), 124, 132, 169, 178, 253, 271, 378, 570 glQuadricTexture(), 334 glRasterPos(), 198 glRasterPos2f(), 289 glRasterPos2i(), 184, 186 glReadBuffer(), 306 glReadPixels(), 187, 191, 193, 198, 306 glRotated(), 120 glRotatef(), 116, 120, 121, 171,253,254 glScale(), 123, 376, 378 glScaled(), 122 glScalef(), 122 glSet(), 98 glShadeModel(), 146, 162, 168, 340 glStencilFunc(), 317, 318, 377 glStencilOpO, 317, 377 glTexCoord(), 100, 272 glTexCoord2f(), 210, 218, 241 glTexCoord2fv(), 210 glTexCoordPointer(), 277, 280 glTexEnv(), 240, 272 glTexEnvi(), 209, 241 glTexGen(), 240
612
funkcje glTexGeni(), 298 glTexImage(), 240, 269 glTexImagelD(), 205, 206, 235 glTexImage2D(), 204, 205, 206*212, 213, 217, 235 glTexImage3D(), 205, 206, 207, 235 glTexParamęter(), 240 glTexParameteri(), 209, 211, 212, 241 glTranslate(), 154 glTranslated(), 120 glTranslatef(), 116, 120, 169, 178 gluBeginSurface(), 353 gluBuild2DMipmaps(), 213, 230, 241 gluCheckExtension(), 238 gluCylinder(), 331 gluDeleteNurbsRenderer(), 353 gluDeleteQuadric(), 329, 335 gluDisk(), 330, 331 gluEndSurface(), 353 gluLookAt(), 114, 116, 232, 579 gluNewNurbsRenderer(), 352 gluNewQuadric(), 327, 334 glUnlockArraysEXT(), 281 gluNurbsProperty(), 352 gluNurbsSurface(), 353 gluNutbsPropertyO, 352 gluOrtho2D(), 134, 182 gluPartialDisk(), 331 gluPerspective(), 135 gluProjection(), 182 gluQuadricDrawStyle(), 328, 334 gluQuadricNormals(), 328 gluQuadricTexture(), 329 gluSphere(), 332 glVertex(), 100, 101, 134 glVertex3f(), 100, 218, 268, 342 glVertexPointer(), 277 glViewport(), 6 6 , 135, 182 graficzne, 56 InitMultiTex(), 244 InStr(), 243 konwencja wywołania, 42 LoadBitmapFile(), 190, 191, 200, 201, 216, 230, 233, 256 LoadCursor(), 47 LoadGrayBitmap(), 259 LoadIcon(), 46 LoadLightmap(), 259 LoadMD2Model(), 476, 478 LoadTextureFile(), 245, 259 LoadTGAFile(), 194 LockFPS(), 519, 520 main(), 42
D odatki
MatrixAdd(), 78 MatrixMult(), 79 PeekMessage(), 50, 51, 55, 6 6 , 70 PlaneView(), 116 PointInPolygon(), 538 PositionLights(), 376 PostQuitMessage(), 44 PrintString(), 288, 291 QueryPerformanceCounter(), 517 QueryPerformanceFrequency(), 517 rastra(), 184 RegisterClassEx(), 48 Render(), 184 rozszerzeń, 239 ScalarMatrixMult(), 78 SetPixelFormat(), 59, 64, 129, 302, 304 SetShadowMatrix(), 385 SetTextColor(), 55 SetupPixelFormat(), 64, 65, 301, 303 ShowCursor(), 6 8 , 222, 559 ShowWindow(), 50, 2 2 2 sin(), 215 stanu, 93 SwapBuffers(), 6 6 , 157, 179, 184 tekstur, 209 TranslateMessage(), 50, 51, 55 trygonometryczne, 80 UpdateWindow(), 50 WGF, 55, 56 wglCreateContext(), 56, 57, 65 wglDeleteContext(), 56 wglGetProcAddress(), 239 wglMakeCurrent(), 57, 65 wglSwapFayerBuffers(), 59 wglUseBitmapFonts(), 288 wglUseFontBitmaps(), 285, 286 wglUseFontOutlines(), 289 wglUseOutlineFonts(), 299 WinMain(), 42, 45, 50, 55, 69, 130, 171 WndProc(), 54, 64, 227 WriteBitmapFile(), 192 WriteTGAFile(), 196 fwrite(), 192
G g_normalObject, 334 g_texturedObject, 334 g_wireframeObject, 334 GDI, 31, 303 GeForce, 19 GetAsyncKeyState(), 394, 396 GetAudioPathConfig(), 446 GetCapabilities(), 405
Skorowidz
GetDC(), 54, 65 GetDefaultAudioPath(), 446 GetDeviceData(), 409 GetDeviceState(), 408 GetElapsedSeconds(), 520 GetElapsedTime(), 521 GetFPS(), 519, 520 GetMessage(), 51 GetObject(), 446 GetObjectInPath(), 449 GL, 19 GL ACCUM, 325 G L A C C U M B U F F E R B IT , 305 GL ADD, 325 GL_ALPHA, 186, 205 GL ALWAYS. 307, 313, 314, 318 GLAM BIENT, 158, 164 G L A M B IEN T A N D D IFFU SE , 164 GL_ARB_multitexture, 238, 244 G L A U TO N O R M A L, 345 G LBA CK , 104, 105, 163, 305, 306 GL BACK LEFT, 306 GL BACK RIGHT, 306 G LB G R , 186 G LBG RA , 186 GL BITMAP, 186, 205 GL BLEND, 173, 209 GL BLUE, 186, 205 GL BYTE, 186, 205 G LCCW , 106, 155 GL CLAMP TO EDGE, 211 GLCOLOR, 187 GL COLOR ARRAY, 276 GL COLOR BUFFER BIT, 305 G L_COLOR_INDEX, 186, 205 GL COLOR MATER1AL, 164, 291 GL COMPILE, 269 GL_COMPILE_AND_EXECUTE, 269 GLCONSTANTATTENUATION, 158, 160 G L C U L L F A C E , 105, 155 GL_CURRENT_RASTER_POSITION_VALID, 182 G L C W , 106 GL DECAL, 209 GL DECR, 318 GLDEPTH, 187 GL DEPTH BUFFER BIT, 305 G L D E P T H T E S T , 155, 176 GLDIFFUSE, 158, 159, 164 GL DOUBLE, 277 G L D S T A L P H A , 173, 174 G L D S T C O L O R , 173 GL EDGE FLAG ARRAY, 276 GLEM ISSION. 164 GL_EQUAL, 307, 318
613
GL_EXP, 374 GL_EXP2, 374 GL_EXT_compiled_vertex_array, 281, 283 GL_EXTENSIONS, 238 GL_EYE LINEAR, 298 GL_FALSE, 106, 166, 177, 268 GL FILL, 105, 345 G LFLA T, 146 GL FLOAT, 205 GL FOG, 374 GL FOG COLOUR, 374 GL FOG DENSITY, 374 GL FOG END, 374 GL_FOG_INDEX, 374 GL FOG MODE, 374 GL FOG START, 374 GL FRONT, 105, 163, 305, 306 GL FRONT AND BACK. 104, 105, 163, 305 GL FRONT LEFT, 306 GL FRONT RIGHT, 306 GL_GEQUAL, 307, 318 GL_GREATER, 307, 318 GL GREEN, 186, 205 GLJNCR, 318 GL_INDEX_ARRAY, 276 GL INT, 186, 205, 277 GL_INVERT, 318 GLJCEEP, 318 GL LEFT, 306 GL_LEQUAL, 307, 318 GL_LESS, 307, 313, 318 G LLIG H TM O D ELA M BIEN T, 165 G LLIG H TM O D ELC O LO R C O N TR O L, 165, 166 G L_LI G H T M O D E L L O C AL_V IE WER, 165 G L L IG H T M O D E L T W O S ID E , 165, 166 GLLIGHT0, 158, 159, 167 GLLIGHT1, 158 GLLIGHT4, 155 GLLIGHT7, 158 GLLIGHTING, 155 G LLIG H TIN G BIT, 172 GL LINE, 105, 343,345 GL LINE LOOP, 99, 278 G LLIN ESM O O TH , 103 G L LIN E ST IPPL E, 103, 104 GL_LINE_STIPPLE_REPEAT, 104 GL LINE STRIP, 99, 278 G L LIN E W ID TH , 103 GL_LINE_WIDTH_GRANULARITY, 103 G L L IN E W ID T H R A N G E , 103 GL LINEAR, 208, 374 GLLINEARATTENUATION, 158. 160 GL LINEAR MIPMAP LINEAR. 208, 213
614
G LLIN EARM IPM A PNEA REST, 208, 213 GLLINES, 99, 102, 278 GL LIST BASE, 271 GL LOAD, 325 GLLUMINANCE, 205, 257, 260 GL LUMINANCE ALPHA, 205 GLM A P 1 C O L O R 4 , 342 GL_MAP1_INDEX, 342 G LM A P 1NORMAL, 342 GL MAP 1_TEXTURE_COORD_1, 342 GL MAP 1_TEXTURE_COORD_2, 342 G LM A P 1_TEXTURE_COORD_3, 342 GL MAP l_TEXTURE_COORD_4, 342 GL_M AP1_VERTEX_3, 341, 342 G LM A P 1_VERTEX_4, 342 GL_MAP2_TEXTURE_COORD_2, 346 GL_M AP2_VERTEX_3, 353 GL_MAX TEXTURE_UNITS_ARB, 280 GL_MODELVIEW, 119 GL MODULATE, 209, 257, 260 GL MULT, 325 GL NEAREST, 208 GL NEAREST MIPMAP LINEAR, 208, 213 GL_NEAREST_MIPMAP_NEAREST, 208, 213 GL_NEVER, 307, 318 GL NONE, 305 G LNO RM ALA RRA Y, 276 GL_NORMALIZE, 152, 157 GL_NOTEQUAL, 307, 318 GLOBJECTJLINEAR, 298 G LON E, 173, 174 G L O N E M IN U S D S T A L P H A , 173, 174 G L O N E M IN U S D S T C O L O R , 173 G L O N E M IN U S S R C A L P H A , 174, 180 G L O N E M IN U S S R C C O L O R , 174 GLPACKALIGNM ENT, 188 GLPOINT, 105,343,345 GL_POINT_SIZE, 101 G LPO INTSIZEG RA N ULA RITY , 102 G L PO IN T S IZ E R A N G E , 102 G LPO INTSM O OTH , 101 GL POINTS, 99. 100, 278 GL_POLYGON, 99, 109, 146, 278 G LPOLY GONMODE, 105 GL_POL YGON_SMOOTH, 106 G LPO LY GON_STIPPLE, 107 GLPOSITION, 158, 172 GLPROJECTION, 119 GL_PROXY_TEXTURE_2D, 205 GL_QUAD_STRIP, 99, 278 G L_Q UA DRATI C_ ATT EN UATI ON, 158, 160 GL_QUADS, 99, 108,218, 278 GL RED, 186, 205 GL REPEAT. 211
D odatki
GL REPLACE, 318 GLRESCALENORM ALIZE, 152 GL RETURN, 325 GL RGB, 186, 205, 257 GL RGBA, 186, 205 GL RIGHT, 306 GLSHININESS, 164, 167 GL SHORT, 186, 205, 277 G LSIN G LECO LO R, 166 GLSMOOTH, 146, 155 GLSPECULAR, 158, 159, 164 GL SPHERE MAP, 298 G L SPO TC U TO FF, 158, 160, 161 G LSPOTD IRECTIO N, 158, 172 GL_SPOT_EXPONENT, 158, 161 G L SR C A L PH A , 173, 174, 180 G LSR C A L PH A SA T U R A T E, 173, 174 G L SR C C O L O R , 174 GL_STACK_OVERFLOW, 124 GL_STACK_UNDERFLOW, 124 GLSTENCIL, 187 GL STENCIL BUFFER BIT, 305 GL STENCIL TEST, 317 GL_TEXTURE, 119 GL_TEXTURE_1 D, 206, 207, 208 GL_TEXTURE_2D, 203, 205, 207, 208 GL_TEXTURE_3D, 207, 208 GL_TEXTURE_COORD_ARRAY, 276, 280 GL_TEXTURE_ENV, 209 GL_TEXTURE_MAG_FILTER, 208 GL_TEXTURE_MIN_FILTER, 208 GL_TEXTURE_WRAP_S, 21 1 GL_TEXTURE_WRAP_T, 211 GL_TEXTURE0_ARB, 240 GL_TEXTUREi_ARB, 280 GLTRIANGLE, 108 GL TRIANGLE_FAN, 99, 108, 278, 471 GL TRIANGLE_STRIP, 99, 108, 224, 278, 471 GL TRIANGLES, 99, 107, 278 GL TRUE, 268 GLUNPACKALIGNM ENT, 188 GL UNSIGNED BYTE, 186, 205 GL UNSIGNED INT, 186, 205 GL UNSIGNED SHORT, 186, 205 GL_VERTEX_ARRAY, 276 GL ZERO, 173, 174, 318 glAccum(), 324, 325 glActiveTextureARB(), 239, 240 glArrayElement(), 279 GLAUX, 163 glaux.lib, 28 glBaseList(), 286 glBegin(), 99, 100, 101, 102 glBindTexture(). 203, 207, 217, 232, 240. 272
Skorowidz
glBitmap(), 183, 184, 185 glBlendFunc(), 173 glBuild2DMipmaps(), 213 glCallList(), 1 0 0 , 269, 271,272 glCallLists(), 1 0 0 , 270, 271, 286 GLclampd, 304 GLclampf, 304 glClear(), 6 6 , 116, 128, 162,304 glClearAccum(), 304, 326 glClearColor(), 128, 326, 340 glClearDepth(), 304 glClearIndex(), 145 glClearStencil(), 304, 326 glClientActivateTextureARB(), 239, 280 glColor(), 100 glColor2f(), 289 glColor3f(), 116, 143, 144, 164, 170, 181 glColor3fv(), 143 glColor4f(), 143 glColor4fv(), 143 glColorMask(), 377 glColorMaterial(), 164 glColorPointer(), 276 glCopyPixels(), 187, 198,306 glCullFace(), 105 glDeleteLists(), 271,272, 288 glDepthFunc(), 307 glDepthMask(), 177, 179, 180, 377 glDisable(), 101, 103, 170 glDisableClientState(), 275, 280 glDrawArrays(), 278, 280 glDrawBuffer(), 306, 313, 314 glDrawElements(), 278, 280, 282, 283 glDrawPixels(), 185, 186, 187, 196, 198 glDrawRangeElements(), 279 glEdgeFlag(), 1 0 0 , 106 glEdgeFlagPointer(), 276 glEnable(), 101, 103, 128, 152, 164, 173, 176, 178, 275, 317, 374 glEnableClientState(), 275, 280 glEnd(), 100, 101 glEndList(), 268, 269 glEvalCoord(), 100 glEvalCoordlf(), 342 glEvalMeshl(), 343 glEvalMesh2(), 345, 349 glEvalPoint(), 100 glEXTLockArrays(), 283 glEXTUnlockArrays(), 283 glFlush(), 116, 162 glFrontFace(), 106, 155, 162 glFrustumO, 134, 135 glGenLists(), 268, 270, 271, 285 glGenTextures(), 203, 207
615
glGet(), 93, 94, 98, 99, 1 0 1 , 105, 1 1 0 , 269 glGetBooleanv(), 182 glGetFloatv(), 102, 356 glGetIntegerv(), 240 glGetString(), 238 gllndex(), 1 0 0 gllndexf(), 145 gllndexfv(), 145 glIndexPointer(), 277 glIsEnabled(), 98, 99, 1 0 1 , 1 1 0 glLightfv(), 155, 158, 159, 160, 161, 162, 166, 168, 172, 178 glLightModel(), 165 glLightModeli(), 165, 166 glLineStipple(), 103 glLineWidth(), 103 glListBase(), 286 glListName(), 271 glLoadIdentity(), 114, 116, 119, 128, 130, 133, 138, 182, 2 2 0 glLoadMatrix(), 138, 139 glLockArraysEXT(), 281 glMaplf(), 341,342,344 glMap2f(), 344, 345, 346, 349 glMapGridlf(), 342, 343,345 glMapGrid2(), 345 glMaterial(), 100 glMaterialf(), 163 glMaterialfv(), 155, 164, 167 glMateriali(), 169 glMatrixMode(), 119, 123, 130, 133, 182, 220, 253 glMultiTexCoord2f(), 242, 246 glMultiTexCoord2fARB(), 240, 241 glMultiTexCoord3dARB(), 241 glMultiTexCoordifARB(), 239 glMultMatrix(), 139, 253 glMultMatrixf(), 378 glNewList(), 268, 269 glNormal(), 100, 279 glNormal3f(), 151, 152 glNormalPointer(), 277 globalne światło otoczenia, 165 glOrtho(), 134, 182 ' glPixelStorei(), 188, 196 glPixelZoom(), 187, 188, 198 glPointSize(), 101 glPolygonMode(), 104 glPolygonStipple(), 107 glPopAtrribO, 271 glPopMatrix(), 124, 128, 132, 253, 271, 570 glPushAttribO, 170, 172, 271 glPushMatrix(), 124, 132, 169, 178, 253, 271, 378, 570 glQuadricTexture(), 334
616
glRasterPos(), 198 glRasterPos2f(), 289 glRasterPos2i(), 182, 184, 186 glReadBuffer(), 306 glReadPixels(), 187, 191, 193, 198, 306 glRotated(), 120 glRotatef(), 116, 120, 121, 171,253,254 glScale(), 123, 376, 378 glScaled(), 122 glScaleff), 122 glSet(), 98 glShadeModelQ, 146, 162, 168,340 glStencilFuncQ, 317, 318, 377 glStencilOpO, 317, 377 glTexCoordQ, 100, 272 glTexCoord2f, 210 glTexCoord2f(), 210, 218, 241 glTexCoord2fv(), 210 glTexCoordPointer(), 277, 280 glTexEnv(), 240. 272 glTexEnvi(), 209, 241 glTexGen(), 240 glTexGeni(), 298 glTexImage(), 240, 269 glTexImagelD(), 205, 206, 235 glTexImage2D(), 204, 205, 206, 212, 213, 217, 235 glTexImage3D(), 205, 206, 207, 235 glTexParameter(), 240 glTexParameteri(), 209, 211, 212, 241 glTranslate(), 154 glTranslated(), 1 2 0 glTranslatef(), 116, 120, 169, 178 GLU, 30, 31, 115, 134,327, 595 GLUD ISPLAY M O D E, 352 GLU FILL, 328, 352 GLU FLAT, 329 GLU INSIDE, 329 GLU LINE, 328 GLUNONE, 329 G LUOUTLINEPATCH, 352 GLU OUTLINE POLYGON, 352 GLUOUTSIDE, 329 GLU POINT, 328 GLU SILHOUETTE, 328 GLU SMOOTH, 329 glu32.1ib, 28, 595 gluBeginSurface(), 353 gluBuild2DMipmaps(), 213, 230, 241 gluCheckExtension(), 238 gluCylinder(), 331 gluDeleteNurbsRenderer(), 353 gluDeleteQuadric(), 329, 335 gluDisk(), 330, 331 gluEndSurface(), 353
Dodatki
gluLookAt(), 114, 1*16, 232, 579 gluNewNurbsRenderer(), 352 gluNewQuadric(), 327, 334 glUnlockArraysEXT(), 281 gluNurbsProperty(), 352 gluNurbsSurface(), 353 gluNutbsPropertyO, 352 gluOrtho2D(), 134, 182 gluPartialDisk(), 331 gluPerspective(), 135 gluProjection(), 182 gluQuadricDrawStyle(), 328, 334 gluQuadricNormals(), 328 gluQuadricTexture(), 329 gluSphere(), 332 GLUT, 32 glVertex(), 100, 101, 134 glVertex3f(), 100, 210, 218, 268, 342 glVertexPointer(), 277 glViewport(), 6 6 , 135, 182, 220 GLYPHMETRICSFLOAT, 289, 291 głębia, 306 koloru, 143 porównywanie, 307 Gouraud, 146, 180 grafika, 25, 99 antialiasing, 1 0 1 cieniowanie, 145 czworokąty, 108 dwuwymiarowa, 134, 182 odcinki, 99, 102 OpenGL, 60, 70 potok tworzenia, 32 punkty, 99, 100 rastrowa, 181 trójkąty, 99, 107 wielokąty, 104 grafika trójwymiarowa, 19, 67, 71, 237 iloczyn wektorowy, 74 macierze, 75 obcinanie, 8 6 obroty, 80 odwzorowania tekstur, 8 8 przekształcenia, 79 przesunięcie, 80 punkty, 71 rzutowanie, 82 skal ary, 71 skalowanie, 82 światło, 8 6 wektory, 71 Graphics Device Interface, 31 graphics library, 19 grawitacja, 511
617
Skorowidz
gry, 26
architektura, 28 cykl, 574 czas zabijania, 585 Doom, 26 Doom 3, 20 hokej, 538 interakcja, 391 komputerowe, 19 podsystemy, 27 projekt, 585 Quake, 20, 26 Quake 2, 470 Quake 3, 30 sieciowe, 34 sterowanie, 391 Unreal Tournament, 26 Wolfenstein, 26 GUID_SysKeyboard, 400 GUID SysMouse, 400
H HAL. 35, 37Hardware Abstraction Layer, 35 Hardware Emulation Layer, 35 HCURSOR, 47 hDC, 64 HEL, 35, 37 hermetyzacja modelu, 490 HICON, 46 hierarchiczne tworzenie grafiki, 253 hlnst, 398 hlnstance, 42 HIWORD, 65 hokej, 538 gracz, 550 krążek, 544 lodowisko, 539 zderzenia, 544 hPrevInstance, 42 hwnd, 43
I ID C A PP STARTING, 47 IDCARROW , 47 IDC CROSS, 47 IDC HELP, 47 IDCJBEAM, 47 IDC NO, 47 IDC SIZEALL, 47 IDC SIZENESW, 47 IDC SIZENS, 47
IDCSIZEN W SE, 47 IDC SIZEWE, 47 IDC UPARROW, 47 IDC WAIT, 47 IDIAPPLICATION, 46 IDI ASTERISK, 46 IDI ERROR, 46 IDI_EXCLAMATION, 46 IDI_HAND, 46 IDIJNFORMATION, 46 IDI QUESTION, 46 IDI_WARNING, 46 IDI WINLOGO, 46 IDirectlnputDevice SetCooperativeLevel(), 407 SetProperty(), 407 IDirectInputDevice8 Acquire(), 408 CreateDevice(), 400 EnumDevices(), 400, 404 EnumObjects(), 405 GetCapabilities(), 404 GetDeviceDataQ, 409 GetDeviceState(), 408 Poll(), 409 ReleaseO, 410 Unacquire(), 409 IDirectMusicAudioPath8 GetObjectInPath(), 449, 455 SetVolume(), 448 IDirectMusicLoader8 , 430 GetObject(), 430, 432, 446 LoadObjectFromFileO, 432, 446 SetSearchDirectory(), 430, 431 IDirectMusicPerformance8 , 429, 430 CloseDown(), 435 CreateAudioPath(), 445, 446 CreateStandardAudioPath(), 445, 446 GetDefaultAudioPath(), 446 GetObjectInPath(), 451 InitAudioO, 429, 4 4 5 , 446 IsPlayingO, 434 PlaySegment(), 446 PlaySegmentEx(), 430, 432, 446, 447 SetDefaultAudioPath(), 446 StopO, 433, 435 IDirectMusicSegment8 , 430, 582 Download(), 430, 432 GetAudioPathConfigO, 446 SetLoopPoints(), 434 SetRepeats(), 434, 435 IDirectSound3DBuffer, 456 IDirectSound3DBuffer8, 451, 582 GetAllParameters(), 452 SetAllParameters(), 452
618
D odatki
IDirectSound3DListener8, 455 GetAllParameters(), 455 GetDistanceFactor(), 455 GetDopplerFactor(), 455 GetOrientation(), 455 GetPosition(), 455 GetRolloffFactor(), 455 GetVelocity(), 455 SetAllParametersQ, 455 SetDistanceFactor(), 455 SetDopplerFactor(), 455 SetOrientation(), 455 SetPosition(), 455 SetRolloffFactor(), 455 SetVelocity(), 455 ikony, 46 iloczyn wektorowy, 74 inicjowanie grafiki, 216 InitAudio(), 429, 446 InitMultiTex(), 244 instalacja, 37 InStr(), 243 int, 42 interaktywność, 391, 550 interfejs Directlnput, 398 DirectX, 33 GDI. 31 IDirectSound3DBuffer8, 451 IDirectSound3DListener8, 455 IRIS GL, 19 OpenGL, 19 użytkownika gry, 594 Internet, 26, 36 interpolacja. 484 IRIS GL, 19 IsPlayingO, 434
j jednostki tekstur, 237, 240 język C, 41 C++. 28,41,526 JOYERRNOERROR, 396 JOYSTICKID1, 396 JOYSTICKID2, 396
K kafelkowanie, 32, 210 kamera. 112, 243, 576.577 orientacja, 115 położenie, 114 stacjonarna, 115
kanał alfa, 193 wykonania, 427 karty dźwiękowe, 34, 36, 421 karty graficzne, 398 VGA, 34 katalogi, 46 kąt odbicia, 538 padania, 538 widzenia obserwatora, 135 kierunek widzenia obserwatora, 114 kinematyka, 526 klasy CAudioSystem, 580, 581 CCamera, 577 CEnemy, 588 CEngine, 573 CEntity, 581, 588 CGUI, 594 CHiResTimer, 518, 553 CInputSystem, 411, 572 CKeyboard, 414 CMD2Model, 488, 580 CMouse, 416 CNode, 562, 563 CObject, 530, 531,566, 569 COGLWindow, 562, 570 COgroEnemy, 590 CParticleSystem, 366, 583, 593 CPlane, 527 CPlayer, 550 CPuck, 544 CRocket, 592 CSimpEngine, 576 CSnowstorm, 369 CTable, 539 CTerrain, 586 CTexture, 540 CVector, 521 CWorld, 580 IDirectMusicPerformance8 , 429 okien, 44 klatki animacji, 157 klawiatura, 172, 394, 572 obsługa Win32, 394 klawisze, 51,392, 393,406 wirtualne kody, 395 kod ASCII, 290 SCII, 172 wirtualne klawisze, 395 kolejka zdarzeń, 40
619
Skorowidz
kolory, 55, 58, 141 alfa, 173 bufor, 143 głębia, 143 intensywność, 143 łączenie, 173 mapa, 142 model indeksowy, 142, 144 model RGB, 142 model RGB A, 142, 143, 144 model widzenia, 142 OpenGL, 142 paleta, 144 piksela, 59 składowe, 143 spektrum, 143 sześcian, 143 wierzchołków, 276 współczynnik alfa, 142 kompilacja, 595 kompilator, 28 kompresja BMP, 190 komunikacja, 36 komunikaty, 42 obsługa, 43 pętla przetwarzania, 50 urządzeń wejścia, 392 Windows, 391, 394 WM CHAR, 392 WM CLOSE, 44 WM CREATE, 57, 64 WM DESTROY, 57 WM KEYDOWN, 172, 392, 393 WM KEYUP, 392, 393 WM LBUTTONDOWN, 392 WM LBUTTONUP, 392 WM MBUTTONDOWN, 392 WM MBUTTONUP, 392 WMMOUSEMOVE, 393 W M M O U SE WHEEL, 393 WM PAINT, 44 WM_QUIT, 44, 55 WM RBUTTONDOWN, 392 WM RBUTTONUP, 393 WM SIZE, 65 WM TIMER, 517 kontekst grafiki, 56, 135 urządzenia, 54, 70 urządzenia hDC, 64 wybór, 65 kopiowanie danych ekranu, 187 krawędzie, 106 krzywe, 38, 337
Beziera, 339, 340, 341,342 B-sklejane, 350, 354 ciągłość, 338, 339 dwuwymiarowe, 343 ewaluatory, 339 kształt, 339 NURBS, 350 punkty kontrolne, 338, 340, 342 równania, 337, 338 siatka równoodległa, 342 kształty, 354 kule, 332 kursor myszy, 46, 47
L LARGE INTEGER, 517 licznik czasu, 517 systemu Windows, 517 Lightwave, 469 linie, 37 liniowa interpolacja, 208 Linux, 20 listy cykliczne, 563 listy wyświetlania, 267, 271 identyfikatory, 272 rekursja, 271 tekstury, 272 tworzenie, 267, 268 usuwanie, 271 wstawianie poleceń, 268 wykonywanie, 269 LoadBitmapFileO, 190, 191, 200, 233, 256 LoadCursor(), 47 LoadGrayBitmapO, 259 LoadIcon(), 46 LoadLightmapO, 259 LoadMD2Model(), 476, 478 LoadObjectFromFileQ, 432, 446 LoadTextureFile(), 245, 259 LoadTGAFile(), 194 LockFPS(), 519, 520 LONGLONG, 517 LOWORD, 65 LPARAM, 65 lpCmdLine, 42 LPDIRECTINPUT8 , 398, 399 LRESULT, 42 lustra, 378
201
, 216, 230,
620
D odatki
Ł ładowanie elementów macierzy, 138 ikon, 46 modelu z pliku, 275 plików BMP, 190 plików PCX, 501 plików Targa, 194 łączenie kolorów, 173, 176 wierzchołków, 38
M macierze, 32, 75, 111, 119 definiowane, 139 dodawanie, 76 jednostkowe, 75, 120 kolumnowe, 81 ładowanie elementów, 138 mnożenie, 76, 139 modelowania, 119, 120 obrotu, 80 odejmowanie, 76 odwrotne, 356 przekształceń, 138 przekształceń perspektywicznych, 84 rzutowania, 119, 133 rzutowania cieni, 380 stos, 119, 123 tekstur, 119,253 transpozycja, 357 zerowe, 75 main(), 42 manipulator, 36, 394, 572 obsługa Win32, 396 mapy kolorów, 142, 145 oświetlenia, 255, 265, 375 pikseli, 185 sferyczne, 298 tekstur, 205 terenu, 228 mapy bitowe, 45, 181, 183, 197 OpenGL, 181 pakowanie danych, 188 położenie, 182 rysowanie, 183 umieszczanie, 182 Windows, 188 masa obiektu, 507 maska powielania, 317
maszyna stanów, 93, 271 elementy grafiki, 99 parametry, 93 punkty, 1 0 0 zakończenie tworzenia grafiki, 1 0 0 zmiana parametrów, 98 materiały, 148, 155 definiowanie, 163 śledzenie kolorów, 164 MatrixAdd(), 78 MatrixMult(), 79 Maya, 469 MD2, 470, 472, 584 animacja modelu, 471, 483 ładowanie modelu, 476 nagłówek, 470 pokrywanie modelu teksturą, 482 siatka modelu, 474 stany animacji, 499 sterowanie animacją modelu, 498 struktury, 475 wyświetlanie modelu, 480 mechanika obiektów, 27 mechanizm rozszerzeń, 38 menedżer systemów cząstek, 365 metody wirtualne, 531 mgła, 370, 373, 602 gęstość, 374 GLJEXP, 374, 375 GL_EXP2, 374, 375 GL LINEAR, 374, 375 indeks koloru, 374 koniec, 374 objętościowa, 375 OpenGL, 374 tryb tworzenia, 374 właściwości, 374 Microsoft DDK, 38 Microsoft DirectX, 34 Microsoft Visual C++, 28 MIDI, 425, 428 miksowanie dźwięku, 34 mipmapy, 32, 208, 212, 235, 541 automatyczne tworzenie, 213 MMRESULT, 396 mmsystem.h, 396 model cieniowania, 145 obiektowy COM, 397 oświetlenia, 164 model kolorów, 59 indeksowy, 142 RGB, 142, 185 RGB A, 142, 144, 159
621
Skorowidz
modele. 123 ładowanie z pliku, 275 robota, 124 modele trójwymiarowe, 469 3D Studio Max, 469 animacja, 483 format MD2, 470 formaty plików, 469 Lightwave, 469 ładowanie plików PCX, 501 Maya, 469 pokrywanie teksturą, 482 sterowanie animacją, 498 tworzenie, 469 modelowanie, 1 1 2 cieni, 379 oświetlenia, 148 świata, 505 świata rzeczywistego, 363, 516 morfing, 484 MP3, 468 MPG, 36 MSDN, 55 Musical Instrument Digital Interface, 425 mysz, 392, 550, 572
N narzędzia, 28 non-uniform rational B-splines, 350 normalizacja, 152 wektora, 72 normalne, 148, 151 jednostkowe, 152 obliczanie, 149 powierzchni, 148 nShowCmd, 42 NULL, 42 NURBS, 32, 350
o obcinanie, 8 6 obiekty, 529 DMO, 36 dwuwymiarowe, 356 flesze, 173 ładujące, 426 materiały, 148, 163 odwzorowanie otoczenia, 250 powierzchni drugiego stopnia. 328 prostopadłościany ograniczeń, 533 przesłonięte. 326 ruch, 1 1 1
sfery ograniczeń, 532 tekstur, 200, 207 trójwymiarowe, 8 6 , 199 wykonania, 427 wyznaczanie oświetlenia, 155 obrazy dwuwymiarowe, 32 graficzne, 185 odbicia, 188 odczytywanie obrazu z ekranu, 187 pomniejszanie, 188 powiększanie, 188 stereoskopowe, 38 upakowanie danych mapy pikseli, 188 obroty, 64, 80, 117, 120 kierunek, 1 2 1 oś, 1 2 1 obserwator, 113 kąt widzenia, 135 kierunek widzenia, 114 obsługa komunikatów, 43 zdarzeń, 42 odbicia, 188, 375, 384 bufor głębi, 376 bufor powielania, 377 nieregularne, 378 płaszczyzna dowolnie zorientowana, 378 płaszczyzna odbicia, 376 przedmioty metalowe, 378 światła, 376 odbiorca dźwięku, 456 odcinki, 99, 102 antialiasing, 103 szerokość, 103 wzór linii, 103 odległość, 506 odpytywanie urządzeń, 409 odrysowanie okna, 44 odtwarzanie dźwięku, 429, 580 głośność kanałów, 448 ścieżki dźwięku, 447 odtwarzanie plików, 36 odwzorowanie akcji, 410 otoczenia, 250, 265 tekstur, 8 8 , 199, 235, 237 ogniskowa, 84 okna, 43 atrybuty klasy, 45 grafiki OpenGL, 57 klasa, 44 odrysowanie zawartości, 44 OpenGL, 6 6
622
Dodatki
okna podrzędne, 49 rejestracja klasy, 48 style, 45, 49 tworzenie, 48 tworzenie grafiki, 54 widoku, 135 wyskakujące, 49 OpenGL, 19, 21, 25, 28, 29, 30, 37, 55, 56, 250, 398, 595 aplikacje pełnoekranowe, 67 ARB, 30 architektura, 31 funkcje rozszerzeń, 239 historia, 30 jądro, 31 kolory, 142 macierze, 119 maszyna stanów, 93 okno grafiki, 57 przekształcenia, 1 1 2 rozszerzenia specyfikacji, 31 specyfikacja, 30 stany, 93 Windows, 39 zasoby Internetowe, 600 OpenGL 1.2,31 OpenGL Architecture Review Board, 30 OpenGL Utility Library, 30, 31 OpenGL Utility Toolkit, 32 opengl32.1ib, 28, 595 operacje graficzne niskiego poziomu, 30 opróżnianie buforów, 304 oświetlenie, 8 6 , 147, 255, 481 dwustronne, 38 efekt odbicia, 166 globalne światło otoczenia, 165 materiały, 148, 163 modele, 164, 165 modelowanie, 148 normalne, 148 obiekty teksturowane, 166 OpenGL, 147, 153 realny świat, 147 ruchome źródła światła, 167 światło, 154 źródła światła, 153, 158 otoczenie, 250
p pakiet GLUT, 32 paleta kolorów, 144 Particie Systems API, 602
68
, 176,
pasek przewijania, 49 PCX, 482, 541 PeekMessage(), 50, 51, 55, 6 6 , 70 pełnoekranowe aplikacje OpenGL, 67 perspektywa, 135 asymetryczna, 135 pęd, 511 pętla przetwarzania komunikatów, 50, 6 6 , 573 PFDDEPTHDONTCARE, 303 PFDDOUBLEBUFFER, 129, 219, 303 PFD DOUBLEBUFFER DONTCARE, 303 PFD D R A W T O B IT M A P, 303 PFD DRAW TO WINDOW, 129, 219, 303 PFD MAIN PLANE, 129, 219, 304 PFD_OVERLAYJPLANE, 304 PFD STEREO, 303 PFD STEREO DONTCARE, 303 PFD SUPPORT GDI, 59, 303 PFD SUPPORT OPENGL, 129, 219, 303 PFD_TYPE_COLORINDEX, 303 PFD TYPE RGBA, 129, 219, 303 PFD UNDERLAY PLANE, 304 picking, 38 pierwsza zasada dynamiki, 511 piksel, 56, 59, 181 format, 58, 186 konfiguracja formatu, 301 kopiowanie, 187 typy, 186 PIXELFORMATDESCRIPTOR, 57, 58, 64, 70. 302, 304, 317, 326 cColorBits, 59 dwFlags, 59 iPixelType, 59 nSize, 58 plakat, 357 plakatowanie, 355, 357 macierz modelowania, 356 PlaneView(), 116 PlaySegmentEx(), 446, 447, 448 pliki, 46 AVI, 36 BMP, 181, 189, 482 graficzne, 181 MIDI, 425, 435 MP3, 468 MPG, 36 multimedialne, 34, 36 nagłówkowe, 28 PCX, 482 Targa, 193 TG A, 181,482 WAV, 428
Skorowidz
Plug and Play, 34 płaszczyzna, 526 obcięcia, 1