Spis treści
O autorach...............................................................................................................21
Przedmowa ..............................................................................................................23
Wprowadzenie.......................................................................................................... 25
Dla kogo jest ta książka?............................................................................................................26
System wymagany dla OpenGL.................................................................................................26
Język...........................................................................................................................................26
Kompilatory ...............................................................................................................................27
Co znajdziesz w tej książce........................................................................................................27
Część I: Wprowadzenie do OpenGL....................................................................................28
Część II: Używanie OpenGL ...............................................................................................28
Część III: Tematy zaawansowane i efekty specjalne............................................................29
Część IV: OpenGL i... .........................................................................................................30
Dodatki..............................................................................................:..................................31
Płytka CD-ROM dołączona do książki.......................................................................................31
Do dzieła!...................................................................................................................................32
Część l
Wprowadzenie do OpenGL.................................................................. 33
Rozdział 1.
Co to jest OpenGL?.................................................................................................. 35
O OpenGL..................................................................................................................................36
Historia OpenGL..................................................................................................................36
Dalszy rozwój OpenGL........................................................................................................37
Jak działa OpenGL.....................................................................................................................37
OpenGL w Windows..................................................................................................................38
Architektura graficzna: oprogramowanie kontra sprzęt .......................................................38
Ograniczenia ogólnej implementacji....................................................................................40
Dalsze perspektywy OpenGL w Windows.................................................................................40
Rozdział 2.
Podstawy grafiki 3D................................................................................................. 41
Postrzeganie w trzech wymiarach ..............................................................................................41
2D + Perspektywa = 3D.......................................................................................................42
Usuwanie niewidocznych linii .............................................................................................43
Kolory i cieniowanie............................................................................................................43
Światła i cienie.....................................................................................................................44
OpenGL - księga eksperta
Układy współrzędnych ...............................................................................................................44
Dwuwymiarowe współrzędne kartezjańskie ........................................................................45
Obcinanie współrzędnych....................................................................................................46
Widoki, twoje okna na trójwymiarowy świat....................................................................... 46
Rysowanie prymitywów....................................................................................................... 47
Trójwymiarowe współrzędne kartezjańskie ......................................................................... 48
Rzuty, podstawa grafiki 3D........................................................................................................48
Rzuty równoległe ................................................................................................................. 49
Rzuty perspektywiczne........................................................................................................ 50
Podsumowanie ...........................................................................................................................50
Rozdział 3.
Nauka OpenGL z użyciem biblioteki AUX ................................................................... 51
OpenGL: API, nie język............................................................................................................. 5 1
Podział pracy w OpenGL..................................................................................................... 52
Typy danych OpenGL.......................................................................................................... 53
Konwencje nazw funkcji.. .................................................................................................... 55
Biblioteka AUX.......................................................................................................................... 56
Niezależność od platformy................................................................................................... 57
AUX = wejście/wyjście w prosty sposób............................................................................. 57
Analiza krótkiego programu OpenGL........................................................................................ 58
Część nagłówkowa............................................................................................................... 59
Ciało programu ....................................................................................................................60
Tryb wyświetlania: pojedynczy bufor.................................................................................. 60
Pozycjonowanie okna.. ........................................................................................................ .60
Tworzenie okna OpenGL..................................................................................................... 62
Czyszczenie okna (wypełnianie kolorem). ........................................................................ ...62
Samoczyszczenie okna.. ............................. ...................................................................... ....64
Opróżnienie zawartości kolejki............................................................................................ 64
Rysowanie kształtów za pomocą OpenGL .................................................................................65
Funkcja renderująca............................................................................................................. 66
Rysowanie prostokąta ..........................................................................................................66
Inicjowanie........................................................................................................................... 67
Skalowanie do rozmiarów okna .................................................................................................68
Ustawianie widoku i bryły obcinania................................................................................... 68
Definiowanie widoku........................................................................................................... 7 1
Definiowanie bryły obcinania.............................................................................................. 72
Aby kwadrat był kwadratem ................................................................................................73
Animacja przy użyciu biblioteki AUX... .................................................................................... 74
Podwójne buforowanie......................................................................................................... 77
W końcu trochę trzeciego wymiaru!..... ...................................................................................... 78
Podsumowanie ...........................................................................................................................79
Podręcznik.. ........................................................................................................................... .....79
aux!dleFunc..........................................................................................................................79
auxłnitDisplayMode...... .................................................................................................... ...80
aux!nitPosition ..................................................................................................................... 8 1
aiucReshapeFunc ............................................................................................................... ...85
auxSetOneColor. ............................................................................................................... ...85
auxSolidBox.. ....................................................................................................................... 86
Spis treści
auxSolidCube.... ............................................................................................................... ....87
AuxSolidCylinder ................................................................................................................ 87
auxSolidDodecahedron. ..................................................................................................... ..88
auxSolidSphere .................................................................................................................... 89
auxSolidTeapot .................................................................................................................... 89
auxSolidTetrahedron.. ..................................................................................................... .....90
auxWireCone ....................................................................................................................... 9 1
auxWireCube ..................................................................................................................... ..92
auxWire!cosahedron ............................................................................................................93
auxWireOctahedron ............................................................................................................. 94
auxWireSphere.....................................................................................................................94
auxWireTeapot.. ................................................................................................................ ...95
auxWireTetrahedron ............................................................................................................95
auxWireTorus. ...................................................................................................................... 95
glYiewport...........................................................................................................................97
glRect...................................................................................................................................98
Rozdział 4.
OpenGL for Windows: OpenGL + Win32 = Wiggle..................................................... 101
Rysowanie w oknach Windows................................................................................................ 102
Konteksty urządzeń GDI....................................................................................................102
Konteksty renderowania OpenGL......................................................................................l 05
Korzystanie z funkcji Wiggle................................................................................................... 106
Tworzenie i wybieranie kontekstu renderowania............................................................... 106
Rysowanie przy użyciu OpenGL........................................................................................ 106
Przygotowanie okna dla OpenGL.............................................................................................l 08
Style okien .........................................................................................................................108
Formaty pikseli ..................................................................................................................108
Powrót odbijającego się kwadratu............................................................................................ 111
Skalowanie do okna........................................................................................................... 114
Tyknięciatimera.................................................................................................................ll4
Światła, kamera, akcja!................................................................................................
15
Podsumowanie. Podręcznik........
ChoosePixelFormat... DescribePixelFormat.
16 16 16 18 GetPixelFormat .................................................................................................................. 122
SetPixelFormat.. ...................................................................................... ........................... 122
SwapBuffers.... ............................................................................................................... ....123
wglCreateContext...........................................................................................,...................124
wglDeleteContext.. ............................................................................................................. 125
wglGetCurrentContext.. ................................................................................................... ..125
wglGetCurrentDC .............................................................................................................. 126
wglGetProcAddress..... .................................................................................................. .....126
10______________________________________OpenGL - księga eksperta
wglMakeCurrent................................................................................................................ 127
wglShareLists..................................................................................................................... 128
wglUseFontBitmaps........................................................................................................... 129
wglUseFontOutlines...........................................................................................................130
Rozdział 5.
Błędy i inne komunikaty OpenGL............................................................................. 133
Gdy dobremu programowi przydarzają się złe przygody ........................................................134
Kim jestem i co potrafię? .........................................................................................................135
Rozszerzenia OpenGL........................................................................................................ 136
Udzielanie wskazówek za pomocą funkcji glHint................................................................... 137
Podsumowanie .........................................................................................................................138
Podręcznik................................................................................................................................ 138
glGetLastError...................................................................................................................138
glGetLastError................................................................................................................... 138
glGetString.........................................................................................................................139
glHint................................................................................................................................. 140
gluErrorString....................................................................................................................141
gluGetString.......................................................................................................................141
Część II
Używanie OpenGL............................................................................ 143
Rozdział 6.
Rysowanie w trzech wymiarach: linie, punkty i wielokaty.........................................145
Rysowanie punktów w przestrzeni trójwymiarowej................................................................ 146
Przygotowanie trójwymiarowej osnowy............................................................................ 146
Trójwymiarowy punkt: wierzchołek .................................................................................. 148
Narysujmy coś! ..................................................................................................................149
Rysowanie punktów...........................................................................................................149
Ustalanie rozmiaru punktu................................................................................................. 152
Rysowanie linii w trzech wymiarach........................................................................................ 155
Łamane i łamane zamknięte............................................................................................... 156
Przybliżanie krzywych odcinkami..................................................................................... 157
Ustalanie grubości linii...................................................................................................... 158
Linie przerywane................................................................................................................ 160
Rysowanie trójkątów w przestrzeni 3D.................................................................................... 162
Trójkąt: twój pierwszy wielokąt......................................................................................... 162
Kierunek............................................................................................................................. 163
Paski trójkątów...................................................................................................................164
Wachlarze trójkątów.......................................................................................................... 165
Budowanie jednolitych obiektów............................................................................................. 166
Ustawianie kolorów wielokątów........................................................................................ 169
Korzystanie z bufora głębokości........................................................................................ 169
Ukrywanie niewidocznych powierzchni............................................................................ 171
Tryby wielokątów.............................................................................................................. 173
Inne prymitywy ........................................................................................................................174
Czworokąty ........................................................................................................................174
Paski czworokątów.............................................................................................................l75
Ogólne wielokąty...............................................................................................................l75
Wypełnianie wielokątów....................................................................................................l75
Reguły konstruowania wielokątów.................................................................................... 179
Podział wielokąta............................................................................................................... 181
Podsumowanie .........................................................................................................................183
Spis treści
Podręcznik. ........................................................................................................................... ....l 84
glBegin.. ............................................................................................................................. 184
glCullFace............,.............................................................................................................185
glEdgeFlag. ..................................................................................................................... ...186
glEnd...... .......................................................................................................................... ..188
glFrontFace........................................................................,...............................................188
glGetPolygonStipple .......................................................................................................... 1 89
glLineStipple.. ................................................................................................................ ....190
glPolygonMode. ............................................................................................................... ..194
glPolygonStipple. .............................................................................................................. .195
glVertex................................................,.............................................................................196
Rozdział 7.
Manipulowanie przestrzenią 3D: transformacje współrzędnych .................................199
Czy to jest ten rozdział z matematyką? ....................................................................................200
Przekształcenia.........................................................................................................................200
Współrzędne obserwatora..................................................................................................201
Przekształcenia punktu obserwacji.. ............................................................................... ....202
Przekształcenia modelu...................................................................................................... 202
Dwoistość widoku modelu.................................................................................................204
Przekształcenia rzutowania................................................................................................205
Przekształcenia okna. ......................................................................................................... 206
Ach, te macierze........ .......................................................................................................... .....206
Co to jest macierz?... ..................................................................................................... .....206
Kolejka przekształceń ........................................................................................................207
Macierz widoku modelu.... ................................................................................................. 208
Macierz tożsamościowa ..................................................................................................... 2 1 1
Stosy macierzy ...................................................................................................................2 1 3
Atomowy przykład.............................................................................................................214
Rzutowania.. ............................................................................................................................. 21 6
Rzutowanie równoległe......................................................................................................217
Rzutowanie perspektywiczne.............................................................................................218
Przykład daleko-blisko.. ................................................................................................ .....220
Zaawansowane operacje na macierzach ...................................................................................223
Ładowanie macierzy ..........................................................................................................223
Tworzenie własnych przekształceń .................................................................................... 224
Inne przekształcenia.. .................................................................................................... .....224
Podsumowanie .........................................................................................................................224
Podręcznik. ............................................................................................................................ ...225
glFrustum...........................................................................................................................225
glLoadldentity... ............................................................................................................. ....226
glLoadMatrix ..................................................................................................................... 226
glMatrixMode . ................................................................................................................. ..227
glPopMatrix ....................................................................................................................... 228
glTranslate.... .................................................................................................................... ..23 1
gluLookAt...... .................................................................................................................. ..23 1
gluOrtho2D........................................................................................................................232
gluPerspective. ................................................................................................................... 233
12______________________________________OpenGL - księga eksperta
Rozdział 8.
Kolory i cieniowanie............................................................................................... 235
Czym jest kolor?.......................................................................................................................236
Światło jako fala.................................................................................................................236
Światło jako cząsteczka......................................................................................................237
Twój osobisty wykrywacz fotonów....................................................................................237
Komputer jako generator fotonów .....................................................................................238
Sprzęt grafiki kolorowej...........................................................................................................239
Tryby graficzne w komputerach osobistych............................................................................241
Rozdzielczości ekranu........................................................................................................241
Głębokość koloru...............................................................................................................241
Kolor 4-bitowy...................................................................................................................242
Kolor 8-bitowy................................................................................................................... 242
Kolor 24-bitowy.................................................................................................................242
Inne głębokości koloru.......................................................................................................242
Wybór koloru...........................................................................................................................243
Kostka kolorów..................................................................................................................243
Ustalanie koloru rysowania................................................................................................245
Cieniowanie .......................................................................................................................246
Ustawianie trybu cieniowania ............................................................................................248
Palety Windows........................................................................................................................249
Dopasowywanie kolorów.............,.....................................................................................249
Dithering............................................................................................................................250
Korzyści ze stosowania palety w trybie 8-bitowym ...........................................................251
Tworzenie palety......................................................................................................................253
Czy potrzebujesz palety?....................................................................................................254
Struktura palety..................................................................................................................254
Paleta 3-3-2........................................................................................................................255
Tworzenie i usuwanie palety..............................................................................................258
Pewne ograniczenia..................:.........................................................................................259
Tryb indeksu koloru .................................................................................................................259
Kiedy używać trybu indeksu koloru?.................................................................................259
Użycie trybu indeksu koloru ..............................................................................................260
Podsumowanie .........................................................................................................................262
Podręcznik................................................................................................................................ 263
glClearIndex.......................................................................................................................263
glColor...............................................................................................................................263
glColorMask......................................................................................................................265
gllndex...............................................................................................................................265
gl!ndexMask.......................................................................................................................266
glLogicOp..........................................................................................................................267
glShadeModel....................................................................................................................268
Rozdział 9.
Oświetlenie i źródła światła.................................................................................... 271
Światło w rzeczywistym świecie..............................................................................................272
Światło otaczające..............................................................................................................273
Światło rozproszone...........................................................................................................273
Światło odbłysków.............................................................................................................273
Złóżmy to razem ................................................................................................................274
Materiały w rzeczywistym świecie...........................................................................................275
Właściwości materiału .......................................................................................................275
Oświetlanie materiałów......................................................................................................275
Spis treści
Obliczanie efektów światła otaczającego... ................................................................... .....276
Efekty światła rozpraszającego i odbłysków......................................................................277
Umieszczenie światła w scenie... ............................................................................................. .277
Włączanie oświetlenia........................................................................................................ 277
Przygotowanie modelu oświetlenia.................................................................................... 278
Przygotowanie właściwości materiału ...............................................................................279
Używanie źródła światła...........................................................................................................28 1
Gdzie jest góra?.... ............................................................................................................. .282
Normalne do powierzchni. ............................................................................................ .....283
Określanie normalnej .........................................................................................................283
Normalne jednostkowe.. .................................................................................................... .285
Znajdowanie normalnej...................................................................................................... 286
Przygotowanie źródła światła............................................................................................. 288
Przygotowanie właściwości materiału ...............................................................................289
Rysowanie wielokątów ...................................................................................................... 289
Efekty oświetlenia.. ................................................................................................................ ..29 1
Odbłyski.............................. ............................................................................................... 291
Światło odbłysków..................... ....................................................................................... .29 1
Właściwości odbłysków materiału..................................................................................... 292
Stopień połyskliwości ........................................................................................................293
Uśrednianie normalnych ....................................................................................................294
Światła punktowe..................................................................................................................... 298
Tworzenie światła punktowego. .................................................................................... .....299
Rysowanie światła punktowego .................................................................................... .....30 1
Cienie .......................................................................................................................................302
Czym jest cień? .................................................................................................................. 303
Kod „zgniatający".............................................................................................................. 304
Przykład cienia................................................................................................................... 305
Oświetlenie i tryb indeksu koloru............................................................................................. 308
Podsumowanie .........................................................................................................................308
Podręcznik................................................................................................................................ 309
glLightModel ..................................................................................................................... 3 15
glMaterial.................................................................................................................... ....... 3 17
glNormal ............................................................................................................................ 3 18
Rozdział 10.
Modelowanie i kompozycja obiektów 3D ................................................................. 321
Określenie zadania ................................................................................................................... 32 1
Wybór rzutowania.............................................................................................................. 322
Wybór oświetlenia i właściwości materiału .......................................................................323
Wyświetlanie rezultatu....................................................................................................... 324
Konstruowanie modelu po kawałku .........................................................................................325
Główka............................................................................................................................... 325
Trzpień................................................................................................... ............................ 328
Gwint ................................................................................................................................. 332
Składanie modelu w całość ................................................................................................335
Test szybkości.......................................................................................................................... 336
14 _____________________________________ OpenGL - księga eksperta
Poprawianie wydajności........................................................................................................... 340
Tworzenie listy wyświetlania............................................................................................. 340
Podsumowanie .........................................................................................................................343
Podręcznik.. ........................................................................................................................... ...343
glDeleteLists ...................................................................................................................... 345
gllsList .............................................................................................................................. .347
glListBase .......................................................................................................................... 348
glNewList...........................................................................................................................349
Rozdział 11.
Grafika rastrowa ....................................................................................................351
Rysowanie bitmap....................................................................................................................351
Czcionki bitmapowe...........................................................................................................355
Budowanie prostej biblioteki czcionek ..............................................................................355
Pixmapy: bitmapy z kolorem.. .................................................................................................. 359
Rysowanie pixmap.............................................................................................................359
Remapowanie kolorów....................................................................................................... 360
Tablice odwzorowań kolorów.. .......................................................................................... 361
Skalowanie pixmapy. ......................................................................................................... 362
Wykrawanie obszarów........ .......................................................................................... .....362
Odczytywanie pixmap z ekranu .........................................................................................363
Kopiowanie pixmap ....................................................................................................... ....366
Przeglądarka plików bitmap.. ................................................................................................ ...366
Pliki bitmap w Windows... ............................................................................................ .....367
Odczyt plików .BMP.......................................................................................................... 368
Zapis pliku .BMP............................................................................................................... 370
Drukowanie bitmap............................................................................................................ 372
Wyświetlanie bitmapy........................................................................................................375
Podsumowanie......................................................................................................................... 377
Podręcznik................................................................................................................................ 377
glPixelMap. ........................................................................................................................ 379
glPixelStore. ....................................................................................................................... 380
glPixelTransfer.. ................................................................................................................. 382
glPixelZoom. ................................................................................................................... ...383
glReadPixels ...................................................................................................................... 384
Rozdział 12.
Mapowanie tekstur ................................................................................................387
Podstawy nakładania tekstur. ................................................................................................... 388
Definiowanie obrazów tekstur.................................................................................................. 389
Definiowanie tekstur ID.. .................................................................................................. 389
Definiowanie tekstur 2D ............................................................................................... .....391
Rysowanie wielokątów z nałożoną teksturą... ...................................................................... ...392
Mipmapy. ................................................................................................................................. 394
Program oglądania terenu....... .................................................................................................. 397
Definiowanie terenu.. ......................................................................................................... 397
Rysowanie terenu... ....................................................................................................... .....397
Rysowanie sceny................................................................................................................ 400
Spis treści _______________________________________________ 15
Automatyczne generowanie współrzędnych tekstur........ ............................................... ....401
Lot nad terenem .................................................................................................................402
Podsumowanie .........................................................................................................................402
Podręcznik................................................................................................................................429
glTexCoord. ...................................................................................................................... .429
glTexImagelD ................................................................................................................ ...43 1
glTex!mage2D ................................................................................................................... 432
glTexParameter.. ........................................................................................................... .....433
Rozdział 13.
Kwadryki: sfery, cylindry i dyski. ......................................................................... ....435
Tworzenie kwadryk..................................................................................................................436
Zmiana sposobu rysowania kwadryki ......................................................................................436
Rysowanie cylindrów............................................................................................................... 437
Rysowanie stożków............................................................................................................438
Teksturowanie cylindrów................................................................................................... 438
Rysowanie dysków................................................................................................................... 438
Teksturowanie dysku .........................................................................................................439
Rysowanie częściowych dysków .......................................................................................439
Rysowanie sfer.. ....................................................................................................................... 439
Teksturowanie sfer. ........................................................................................................... .440
Rysowanie ołówka................................................................................................................... 440
Podsumowanie .........................................................................................................................442
Podręcznik.... ............................................................................................................................ 45 1
gluCylinder ........................................................................................................................451
gluDeleteQuadric ............................................................................................................... 452
gluQuadricDrawStyle.......... ........................................................................................... ....454
gluQuadricTexture .............................................................................................................456
gluSphere ...................................................................................................................... .....456
Część III
Tematy zaawansowane i efekty specjalne......................................... 459
Rozdział 14.
Maszyna stanu OpenGL .......................................................................................... 461
Podstawowe zmienne stanu OpenGL....................................................................................... 46 1
Zachowywanie i odtwarzanie zmiennych stanu .......................................................................462
Stan rysowania.. ................................................................................................................. 464
Stan bufora głębokości. ...................................................................................................... 465
Stan bufora szablonu.......................................................................................................... 465
Stan oświetlenia ............................................................................................................ .....465
Stan teksturowania............. .............................................................................................. ..466
Stan pikseli...... ............................................................................................................... ....467
Podręcznik.............. ................................................................................................................. .468
glDisable/glEnable...... ................................................................................................. ....468
gllsEnabled ................................................................................................................... .....470
16 _________________________________________ OpenGL - księga eksperta
glPopAttrib... ................................................................................................................... ...470
glPushAttrib.. ..................................................................................................................... 470
Rozdział 15.
Bufory: nie tylko do animacji .................................................................................473
Czym są bufory?. ................................................................................................................. .....473
Konfigurowanie buforów................................................................................................... 474
Bufor koloru. ........................................................................................................................... .477
Podwójne buforowanie.. ................................................................................................. ....477
Buforowanie stereo ............................................................................................................478
Przerzucanie buforów.. ..................................................................................................... ..478
Bufor glębokości......................................................................................................................479
Porównywanie głębokości.. ............................................................................................ ....479
Wartości głębokości........................................................................................................... 481
Zastosowania bufora głębokości ........................................................................................481
Inne zastosowanie bufora głębokości................................................................................. 484
Wycinanie fragmentów sceny ............................................................................................485
Bufor szablonu .........................................................................................................................489
Korzystanie z bufora szablonu ...........................................................................................489
Funkcje bufora szablonu. ............................................................................................... ....489
Rysowanie w buforze szablonu.......................................................................................... 490
Bufor akumulacji........ .............................................................................................................. 494
Użycie bufora akumulacji w celu zasymulowania rozmycia ruchu.. ................................. 495
Użycie bufora akumulacji do antyaliasingu .......................................................................498
Podręcznik................................................................................................................................ 499
glAccum.............................................................................................................................499
glClearColor............................................. .......................................................................... 500
glClearDepth. .................................................................................................................. ...500
glDepthRange...... .............................................................................................................. .503
Rozdział 16.
Efekty specjalne: przezroczystość i mgła. ............................................................. ...505
Blending...................................................................................................................................505
Efekt przezroczystości przez łączenie kolorów.................................................................. 507
Łączenie kolorów przy antyaliasingu................................................................................. 51 1
Łączenie kolorów w programach rysunkowych.. .......................................................... .....51 1
Mgła......................................................................................................................................... 520
Rysowanie imbryków cieniowanych w zależności od głębokości ....................................521
Inne rodzaje mgły............................................................................................................... 524
Głębokość mgły .................................................................................................................524
Powrót do programu przeglądania terenu................................................................................. 525
Podsumowanie .........................................................................................................................530
Podręcznik................................................................................................................................ 530
glBlendFunc.. .................................................................................................................. ...530
............................................................................................................................„
Rozdział 17.
Krzywe i powierzchnie: co to jest NURBS?!! ............................................................ 533
Krzywe i powierzchnie............................................................................................................. 534
Reprezentacja parametryczna..... ....................................................................................... .534
Punkty kontrolne................................................................................................................ 53 5
Spis treści ______________________________________________ 17
Ciągłość............... .............................................................................................................. 536
Obliczenia. ............................................................................................................................ ...537
Dwu wymiarowa krzywa .................................................................................................... 537
Obliczanie krzywej............................................... .............................................................. 540
Powierzchnia trójwymiarowa.... ........................................................................................ .541
Oświetlenie i normalne ..........................................................................................544
NURBS .................................................................................................................................... 545
Od krzywych Beziera do krzywych B-sklejanych.. ...... ................... ...................... .....,,. 545
Węzły ,,,,,,,,,,,,,,,,,,,,,,,,,,„„,,,,,,,,,,,,.................,.,.............,....,,,, 546
Tworzenie powierzchni NURBS,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 546 Właściwości obiektów NURBS ,,...................,..,,.......,.......,.............,..,,..„.,,„„ 547
Definiowanie powierzchni ,,.,,...,..,...,..........„,,,,,,,,.....,,,..,,...,....,......,..,,,, 547
Wycinanie,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 549
Podsumowanie ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,551
Podręcznik,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 55 1
glEvalCoord,,,,,,,,,,,,,,,,,,,,„„,,,,,,,,,,,,,,,,,,,,,,„„,„„,,,,.551
glEvalMesh ...... .................................... ....................... ............................ ....... ..,,,, ,..,,552
glEvaIPoint.., ....... ...... ............................................................................................ ,,553
glGetMap .................... ........... ............................................................... ....... ...... ,....., .,.553
glMap,,„„,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,„,,,,,,,,,„„„,,„,,555 glMapGrid............... ,,,,„,,,...,...,,.,....,,,..,..,,,,... ........... ...... ......... ............. ,.,,558
gluBeginCurve. ......,,....,,,,,,...,,.,.....,,.,,......,,,.,,,,,,...,,, ...... ,,,.,.,,,..,,, 559
gluBeginSurface.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,560 gluBeginTrim.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,560 gluDeleteNurbsRenderer, ..,.., ,..,,,... ...,,.................,..,..,,....,.,,„,,,.,....,,,,, „.56 1
gluEndCurve,,,,,,,„„„,„„„,,,„,,,,,,,,,,,„,,,„,,,,,,„„,,„„562 gluEndSurface,., ...... ...... .,,,,,...,.,.,„.............,..,.,„..,,....................,,„,,,,,,.... .,563
gluEndTrim.,, ,,,.,,,,,...,,,,,...,,.,,,,,.,,,.,,,,,..,,,,,,,,,..,....,,,,,,,,,., 563
gluGetNurbsProperty ......,,.........,,,,.,,,,.,,..,,,,,,,,,,,,,,,..,.,..,,,,.,.,,,...,563
gluLoadSamplingMatrices .,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 564 gluNewNurbsRenderer.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,565 gluNurbsCallback...................... ........ ............. ,,,,,,,,„,,,,,,„,,„„.,„,,,,, 566
gluNurbsProperty ....,,,....,,,,....,„..,.,,,,„......,,...,.....,„.....,.„,.,„....,,...,,.,,..., 569
gluNurbsSurface.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,571 gluPwlCurve „,,,,,,,,,,,,,,,,,,,,,,„„,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 572
Rozdział 18.
Podział wielokątów.. .............................................................................................. .575
Złożone wielokąty,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 575 Rysowanie wielokątów wklęsłych.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,, 576 Rysowanie wielokątów złożonych ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,„,,,,,,,,.577 Funkcje zwrotne, ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,. 58 1 Podsumowanie ,,,,,,,,,,,,,,,,,,,,,,,.........,,,.........,..,,,............,..,,,.,.,,,,.,,, 582
Podręcznik,,, ,..,..,.,,...,.,,,...,,....,,,,.,,....,,,,..„...„.,,.„„, ,„,,,,„, ........,, 583
gluBeginPolygon..... ,.,.,....,,„. ...... ,..,.....,„,...... ...... ,...., ...... .............................. ...,583
gluDeleteTess......................... ............... ,„..,.....,,.......,,........,..„.......,„„......,,......,583
gluEndPolygon. ...... ...... ........................................................ ....... .,.,..,„..„.,....„„, ....... .583
gluNewTess.,,,,....„„,„......,..„......„.„.„...„..„......„„......,,,,.,,,,,,„,,,,.,„,,„„,584
gluNextContour.....,,„...... ........................ ,...., ,......„.„.....„„„..„„....„„..„„,„„.,..,.„., 584
gluTessCallback. ,„..„„„„...„„„.„....,„..................„„„„....„...........„...,..„...„„. ,,,„.., 585
gluTessVertex .„,,„,,,,,,,,,,,,,,,,,,,„„,,,„„„,„,,,„,„,„.,,,„, 585
18 ______________________________________ OpenGL - księga eksperta
Rozdział 19.
Grafika interaktywna .............................................................................................. 587
Selekcja. .............................................................................................................................. .....588
Nazywanie prymitywów ....................................................................................................588
Praca w trybie selekcji.. ................................................................................................. .....590
Bufor selekcji ..................................................................................................................... 590
Wybieranie. ....................................................................................................................... .592
Wybór hierarchiczny.......................................................................................................... 594
Sprzężenie zwrotne................................................................................................................... 598
Bufor sprzężenia zwrotnego. .............................................................................................. 598
Dane sprzężenia zwrotnego................................................................................................ 599
Znaczniki użytkownika ......................................................................................................600
Przykład.................................................................................................................................... 600
Nadawanie obiektom etykiet na potrzeby sprzężenia zwrotnego ......................................601
Krok 1: wybranie obiektu................................................................................................... 602
Krok 2: pobieranie informacji o narysowanych obiektach. ............................................... 603
Podsumowanie .........................................................................................................................605
Podręcznik..:................................ ............................................................................................. 605
gllnitNames. ................................................................................................................... ....607
glSelectBuffer ................................................................................................................... .612
gluPickMatrix..... .............................................................................................................. ..613
Rozdział 20.
OpenGL w Sieci: VRML ........................................................................................... 615
Na styku dwóch światów.... .................................................................................................. ....61 5
Dwuwymiarowa nawigacja................................................................................................ 61 6
Instalacja............................................................................................................................ 61 8
Tryb spacerowy.................................................................................................................. 61 8
Tryb oglądania .................................................................................................................. .620
Open Inventor i VRML . ........................................................................................................ ...622
Podsumowanie .........................................................................................................................622
Część IV
OpenGL i... ..................................................................................... 625
Rozdział 21.
OpenGL i MFC.................................... ............................................................... .....627
Wydzielenie kodu związanego z OpenGL. .............................................................................. .628
Zaczynamy od AppWizarda... ................................................................................................ ..629
Budowanie szkieletu .......................................................................................................... 629
Dodawanie bibliotek ..........................................................................................................630
Przygotowanie klasy widoku dla OpenGL...............................................................................631
Format pikseli i kontekst renderowania... .............................................................................. ...632
Usuwanie kontekstu renderowania.. ............................................................................... ....634
Obsługa zmiany rozmiaru okna. ............................................................................................... 634
Spis treści_______________________________________________19
Renderowanie sceny.................................................................................................................634
Unikanie niepotrzebnego czyszczenia okna.......................................................................635
Obsługa palety..........................................................................................................................635
Podsumowanie.........................................................................................................................639
Rozdział 22.
OpenGL i OWL........................................................................................................ 641
Wydzielenie kodu związanego z OpenGL................................................................................642
Zaczynamy od AppExperta......................................................................................................643
Budowanie szkieletu ..........................................................................................................643
Dodawanie nagłówków......................................................................................................645
Dodawanie procedur obsługi komunikatów.......................................................................645
Wypełnianie szkieletu aplikacji................................................................................................646
Przygotowanie klasy widoku dla OpenGL.........................................................................647
Format pikseli i kontekst renderowania....................................................................................647
Usuwanie kontekstu renderowania.....................................................................................649
Obsługa zmiany rozmiaru okna................................................................................................649
Renderowanie sceny.................................................................................................................650
Unikanie niepotrzebnego czyszczenia okna.......................................................................650
Niech się kręci....................................................................................................................651
Obsługa palety..........................................................................................................................652
Podsumowanie.........................................................................................................................656
Rozdział 23.
OpenGL w Visual Basic I 4GL.....................................................................657
Wymagany dostęp niskiego poziomu.......................................................................................657
Magia obiektów........................................................................................................................658
Pług and Play.....................................................................................................................659
Wydzielenie kodu OpenGL................................................................................................659
Działanie i sposób użycia kontrolki WaiteGL.OCX.................................................................659
Znaczniki OpenGL.............................................................................................................660
Instalacja i użycie kontrolki WaiteGL w Yisual Basicu 6.0.....................................................661
Instalowanie kontrolki........................................................................................................661
Przykład w Yisual Basicu ..................................................................................................662
Malowanie w oknie OpenGL .............................................................................................663
Trochę ruchu......................................................................................................................664
Wykorzystanie kontrolki OCX w Delphi 2.0 ..........................................................................665
Instalowanie kontrolki........................................................................................................665
Przykład w Delphi..............................................................................................................665
Malowanie w oknie OpenGL.............................................................................................667
Trochę ruchu......................................................................................................................667
Parę uwag na temat kodu źródłowego......................................................................................668
Podsumowanie .........................................................................................................................669
Rozdział 24.
Przyszłość OpenGL w Windows ............................................................................... 671
Wnioski....................................................................................................................................674
Dodatki........................................................................................... 677
Dodatek A
Poprawianie wydajności OpenGL w Windows.................................................... 679
Listy wyświetlania.............................................................................................................679
Operacje na macierzach .....................................................................................................680
20______________________________________OpenGL - księga eksperta
Operacje związane z oświetleniem.....................................................................................680
Konstruowanie obiektów....................................................................................................680
Inne rady ............................................................................................................................681
Dodatek B
Dalsza lektura........................................................................................................683
Książki na temat programowania Windows.......................................................................683
Książki i materiały na temat OpenGL................................................................................684
Książki komputerowe na temat programowania grafiki (w szczególności 3D)..................684
Serwery FTP i WWW związane z OpenGL.......................................................................684
Składnice VRML...............................................................................................................685
Dodatek C
OpenGL wersja 1.1.................................................................................................687
Dodatek D
Słownik..................................................................................................................689
Skorowidz.............................................................................................................. 695
>erta
.680
.680
.681
583
683
684
684
684
685
87
O autorach
Richard S. Wright jr pracuje dla Yisteon Corporation w Maitland na Florydzie, opraco-
89 wując aplikacje Windows dla służby zdrowia. Richard programowania uczył się
w ósmej klasie, w 1978 roku, przy papierowym terminalu. Gdy miał szesnaście lat, ro-
dzice pozwolili mu kupić komputer zamiast samochodu; niecały rok później sprzedał
swój pierwszy program. Gdy skończył szkołę średnią, jego pierwszą pracą było naucza
nie programowania w lokalnej prywatnej szkole. Studiował na Wydziale Elektrycznym
i Nauk Komputerowych Uniwersytetu w Louisville, ale na ostatnim roku, kiedy studia
rzucając się w wir kariery. Urodzony w Louisville w Kentucky, obecnie wraz z żoną
i trójką dzieci mieszka w słonecznym Lakę Marry na Florydzie. Gdy nie programuje
i nie ściga huraganów, zajmuje się po amatorsku astronomią, wyleguje się na plaży
i uczy w szkółce niedzielnej.
Michael Sweet pracuje dla Chesapeake Test Rangę w Patuxent River w stanie Maryland; jest także współwłaścicielem Easy Software Products, małej firmy programistycznej specjalizującej się w tworzeniu grafiki komputerowej na stacjach roboczych Silicon Graphics. Przy komputerze pierwszy raz zasiadł w wieku sześciu lat, zaś pierwszy program sprzedał, gdy miał lat dwanaście. Pod koniec studiów w SUNY Institute of Technology w Nowym Jorku Michael pracował jako konsultant do spraw grafiki komputerowej. Tuż po studiach przeniósł się do Marylandu. W wolnych chwilach uwielbia jeździć na rowerze, fotografować i grać na trąbce.
Przedmowa
Z powodu bardzo dużych wymagań co do sprzętu i mocy obliczeniowej, trójwymiarowa grafika komputerowa dotąd była dostępna jedynie na specjalizowanych stacjach roboczych, mimp że sama technologia jej tworzenia jest znana od dziesiątek lat. Dzisiejsze komputery osobiste stały się jednak na tyle wydajne, że z powodzeniem można ich używać do tworzenia trójwymiarowej grafiki. Obecnie przeciętny komputer jest wydajniejszy niż stacja graficzna sprzed kilku lat, kosztując jednocześnie dużo mniej.
OpenGL to standard, który powstał w celu umożliwienia przeniesienia grafiki komputerowej, tradycyjnie dostępnej jedynie na stacjach graficznych, do zwykłego komputera osobistego. Gdy tylko powstała ta technologia, uzyskała znaczne wsparcie ze strony Microsoftu.
Obecnie platforma Windows oferuje różnorodne aplikacje OpenGL, od przeglądarek VRML do programów CAD/CAM i pakietów do animacji. Jest także pierwszą platformą, na której zostanie zaimplementowana nowa wersja OpenGL, wersja 1.1.
Richard Wright już od dawna był popularyzatorem technologii Win32 i OpenGL. Jest aktywnym uczestnikiem grupy dyskusyjnej comp.graphics.api.opengl i pomaga innym w rozwiązywaniu problemów programistycznych. Sam regularnie wymieniam się z nim pomysłami i uwagami. Cieszę się, że w tej książce Richard zechciał podzielić się swoim latami zdobywanym doświadczeniem i jestem pewien, że przeczytanie jej z pewnością bardzo pomoże ci w tworzeniu własnych aplikacji OpenGL dla Windows.
Hock San Lee
OpenGL Development Manager Microsoft Corporation Czerwiec 1996
Wprowadzenie
Witamy w świecie grafiki OpenGL! Pierwszy raz o OpenGL usłyszałem w 1992 roku na Win32 Developers Conference w San Francisco. Windows NT 3.1 były dopiero we wczesnej wersji beta (czy też późnej alfa) i obecnych było wielu producentów, zapowiadających swoje wsparcie dla tej nowej, ekscytującej platformy. Między innymi obecna była także firma Silicon Graphics, Inc. (SGI). Prezentowała swoje nowe stacje graficzne i odtwarzała fragmenty popularnych filmów, w których zastosowano efekty specjalne tworzone na jej komputerach. NT działały na procesorach MIPS - obecnie posiadanych przez SGI -jednak głównym celem pokazu była promocja nowego, trójwymiarowego standardu graficznego o nazwie OpenGL. Był on oparty na własnym standardzie IRIS GL firmy SGI i wtedy stanowił zupełną nowość. Co ważne, także Microsoft już wtedy zapowiedział wsparcie dla OpenGL w swoich Windows NT.
Jednak na pierwsze bliższe zetknięcie się z OpenGL musiałem czekać aż do momentu ukazania się wersji beta systemu Windows NT 3.5. Zawarte w nich, oparte na OpenGL wygaszacze ekranu stanowiły jedynie wierzchołek góry lodowej, zaledwie zapowiadając to, co można osiągnąć za pomocą tej biblioteki graficznej. Jak wiele innych osób, przedzierałem się przez pliki pomocy Microsoftu, kupiłem także książkę „OpenGL Pro-gramming Guide" (zwaną obecnie po prostu Czerwoną Księgą). W tej książce starannie unikano zagadnień związanych z platformami i we wszystkich przykładach korzystano z pomocniczej biblioteki AUX (Auxilary), niezależnej od platformy biblioteki funkcji przeznaczonych do uruchamiania aplikacji OpenGL.
W tym czasie Czerwona Księga była jedyną pozycją związaną z nauką OpenGL. Mimo że zawierała dokładny opis funkcji OpenGL, brakowało jej dwóch ważnych elementów. Po pierwsze, nie była dla początkujących. Może takie były intencje autorów, lecz do lektury tej książki potrzebna była ogólna wiedza o grafice trójwymiarowej. Drugą wadą była jej niezależność od platformy. Jako programista Windows, potrzebowałem odpowiedzi na pewne ważne pytania, na przykład takie, jak wykorzystać pliki .BMP jako tekstury, jak w OpenGL stworzyć paletę przeznaczoną do wykorzystania na 8-bitowej karcie graficznej, czy jak korzystać z dorzuconych przez Microsoft funkcji „wiggle".
Niniejsza książka wypełnia te luki. Chciałem w niej zawrzeć wprowadzenie do grafiki trójwymiarowej i programowania w OpenGL, połączonych w jedną całość. Poza tym zaprezentowałem to w kontekście pojedynczego systemu operacyjnego, najpopularniejszego systemu wszechczasów, Microsoft Windows. Dodatkowo, na końcu każdego rozdziału zamieściłem podręcznik, zawierający syntetyczny opis omawianych funkcji.
26______________________________________OpenGL - księga eksperta
Dla kogo jest ta książka?
Ta pozycja jest przeznaczona dla szerokiego grona programistów OpenGL i Windows. Mogą korzystać z niej zarówno programiści Windows, chcący zająć się grafiką trójwymiarową i OpenGL, jak i doświadczeni twórcy trójwymiarowej grafiki w Windows, chcący dowiedzieć się czegoś więcej na temat zastosowań tej biblioteki. Ta książka jest cenną pozycją także dla doświadczonych programistów OpenGL, którzy już zdobyli praktykę w pracy ze stacjami graficznymi i chcą się zająć tworzeniem aplikacji przeznaczonych również dla platformy Microsoft Windows.
System wymagany dla OpenGL
OpenGL nie jest dostępne dla 16-bitowych wersji Windows (3.1, 3.11 itd.). Microsoft dołączył OpenGL do Windows NT 3.5, a także do Windows 95, poprzez oddzielnie rozprowadzane biblioteki DLL (Te biblioteki są dostępne na serwerze FTP i stronach WWW Microsoftu, możesz także je znaleźć na płytce CD.-ROM dołączonej do tej książki, w kartotece Windows 95).
W naszej książce nie są opisywane inne biblioteki OpenGL, przeznaczone dla 32- lub 16-bitowych systemów. Od strony programowej, OpenGL używane w Windows 95 nie różni się od swojego odpowiednika dla Windows NT. Pierwszy zestaw bibliotek DLL wydany przez Microsoft dla Windows NT obejmuje wszystkie funkcje OpenGL 1.0; są one dostępne w Windows NT 3.5 i 3.51. Do Windows NT 4.0 zostały dodane funkcje OpenGL 1.1, a w momencie gdy ta książka znajdzie się na rynku, powinny być już dostępne takie funkcje również dla Windows 95. Najświeższe informacje znajdziesz w pliku readme.txt na płytce CD-ROM dołączonej do książki.
Wszystkie przykłady zaprezentowane w książce powinny działać poprawnie już na szybkim komputerze 486 (tzn. „prawdziwym" 486, z wbudowanym koprocesorem matematycznym!) z co najmniej ośmioma megabajtami pamięci RAM. Nie są to duże wymagania; większość pakietów programistycznych wymaga obecnie lepszego sprzętu. Jeśli jesteś zainteresowany, cały kod w książce i na płytce został opracowany i działał poprawnie na komputerze Pentium 90 MHz z 32 MB RAM i 16/24-bitową kartą graficzną. Będziesz potrzebował karty graficznej mogącej wyświetlić co najmniej 256 kolorów (co najmniej karty 8-bitowej). Jeśli możesz pracować w trybie graficznym z większą ilością kolorów, tym lepiej. Najlepsze wyniki daje praca w trybach z 65 tysiącami lub szesnastoma milionami kolorów.
Język
Z wyjątkiem dwóch rozdziałów poświęconych pakietom klas C++, cały kod źródłowy został napisany w C. Wybór pomiędzy C a C++ może prowadzić niemal do krucjaty re-
Wprowadzenie
ligijnej pomiędzy dwoma zwalczającymi się obozami programistów. Można jednak założyć, że każdy kompetentny programista C++ swobodnie analizuje dobrze zorganizowany kod C, co nie zawsze jest prawdą w sytuacji odwrotnej. Istnieje popularna biblioteka C++ dla OpenGL, zwana Open Inventor; w związku z tym każda próba utworzenia własnej biblioteki klas pośredniczących C++ dla OpenGL byłaby jedynie powieleniem wykonanej już dobrej pracy, a poza tym wykraczałoby to poza ramy tej książki.
Gdy już mamy wybrany język, zajmijmy się potrzebnymi narzędziami.
Kompilatory
Cały kod źródłowy był oryginalnie opracowywany przy pomocy Microsoft Yisual C++ 4.0. (Tak, z jego pomocą można kompilować także programy C!) Przy każdym przykładzie znajdziesz pliki projektu Yisual C++.
Ponieważ wszystkie przykłady są napisane w C i nie korzystają z żadnych bibliotek specyficznych dla innych producentów, nie powinieneś napotkać żadnych problemów przy budowie projektów przy pomocy innych 32-bitowych kompilatorów. Zakładam, że znasz, którego używasz kompilator, i wiesz, jak dodawać do projektów biblioteki i pliki nagłówkowe.
Dla programistów wolących biblioteki klas C++, takie jak MFC czy OWL, dołączono rozdziały dotyczące właśnie tych bibliotek. Dodatkowo, wiele przykładów C zostało dołączonych także w wersji MFC (Yisual C++) oraz OWL (Borland C++). Te przykłady można znaleźć w kartotekach \MFC i \OWL na płytce CD-ROM. Pliki projektów do tych przykładów przygotowano także w wersji dla kompilatora Borlanda (za pomocą Borland C++ 5.0).
Uwagę poświęcono także użytkownikom narzędzi Borlanda: płytka CD-ROM zawiera specyficzną dla Borlanda wersję biblioteki AUX OpenGL. Ta biblioteka nie jest częścią oficjalnej specyfikacji OpenGL, ale zwykle, tak jak OpenGL, jest implementowana na różnych platformach. Z nieznanych powodów Borland dołączył pliki nagłówkowe tej biblioteki, ale nie samą bibliotekę, zaś wersja biblioteki AUX dostarczana z narzędziami Microsoftu nie jest zgodna z Borland C++. Dodatkowe uwagi na temat używania kompilatora Borland C++ z przykładami z tej książki znajdziesz w kartotece \Borland na płytce CD-ROM.
Co znajdziesz w tej książce
Cała książka została podzielona na cztery części. Część pierwsza stanowi wprowadzenie do OpenGL i podstaw jej używania w Microsoft Windows. W części drugiej poznamy podstawy programowania w OpenGL. Będzie mowa o prymitywach, widokach, modelowaniu transformacji, oświetleniu i mapowaniu tekstur. W części trzeciej zagłębimy się w bardziej zaawansowane tematy związane z biblioteką OpenGL - poznamy
28_________________________________________OpenGL - księga eksperta
maszynę stanu OpenGL, efekty specjalne, bardziej szczegółowo zajmiemy się buforami, zaawansowanym generowaniem powierzchni oraz pewnymi zagadnieniami grafiki interaktywnej. W części czwartej poznamy dodatkowe informacje związane z wykorzystaniem OpenGL w innych środowiskach programowania (MFC, OWL i Vi-sual Basic). Na koniec pokrótce omówimy przyszłość OpenGL w Windows.
Część I: Wprowadzenie do OpenGL
Rozdział 1. - Co to jest OpenGL?
W tym rozdziale przedstawimy na przykładach, czym jest OpenGL, skąd się wzięło i dokąd zmierza. Omówimy także ważniejsze różnice i zgodności OpenGL z systemem graficznym Microsoft Windows.
Rozdział 2. - Podstawy grafiki 3D
Ten rozdział jest przeznaczony dla nowicjuszy w dziedzinie trójwymiarowej grafiki. Wprowadzimy w nim podstawowe koncepcje i często stosowane wyrażenia.
Rozdział 3. - Nauka OpenGL z użyciem biblioteki AUX
W tym rozdziale zaczniesz pisać programy używające OpenGL. Początkującym ułatwiamy życie dzięki wykorzystaniu biblioteki AUX. Ta prosta biblioteka narzędziowa jest niezależna od platformy i systemu okien. Poza tym poznamy konwencje nazywania funkcji i zmiennych OpenGL, a także DLL-e i biblioteki zawierające poszczególne grupy funkcji OpenGL.
Rozdział 4. - OpenGL for Windows: OpenGL + Win32 = Wiggle
W tym rozdziale zaczniesz pisać prawdziwe programy Windows (posiadające pętlę komunikatów) używające OpenGL. Poznasz tzw. funkcje „wiggle" Microsoftu, spajające kod graficzny OpenGL z kontekstami urządzeń Windows. Powiemy także parę słów o tym, na jakie komunikaty Windows należy reagować i w jakim celu.
Rozdział 5. - Błędy i inne komunikaty OpenGL
Poznamy metody, jakich OpenGL używa do zgłaszania błędów; powiemy także o sposobach odczytywania numeru wersji i nazwy twórcy biblioteki.
Część II: Używanie OpenGL
Rozdział 6. - Rysowanie w trzech wymiarach: linie, punkty i wielokąty
Tutaj dowiesz się, jak z dwuwymiarowych prymitywów są składane trójwymiarowe obiekty. Zostaną omówione wszystkie prymitywy OpenGL, a także sposoby ukrywania niewidocznych powierzchni.
Wprowadzenie_____________________________________________29
Rozdział 7. - Manipulowanie przestrzenią 3D: transformacje współrzędnych
W tym rozdziale nauczysz się przesuwać obiekty i widok sceny. Dowiesz się, jak obracać, przesuwać i skalować obiekty. Opowiemy sobie o działaniach na macierzach, dzięki czemu będziesz mógł ich używać, nawet jeśli wcześniej nie miałeś o nich żadnego pojęcia.
Rozdział 8. - Kolory i cieniowanie
Tutaj dowiesz się, jak tchnąć życie w obiekty uzupełniając je o kolory. Gdy przeczytasz ten rozdział, cieniowanie obiektów z jednego koloru w inny będzie dla ciebie dziecinną zabawą. Zobaczysz także, dlaczego musisz konstruować paletę 3-3-2, gdy twój program działa na 256-kolorowej karcie graficznej.
Rozdział 9. - Oświetlenie i źródła światła
OpenGL obsługuje do ośmiu niezależnych źródeł światła w scenie. Nauczysz się używać tych źródeł, ustawiać ich parametry i właściwości, a także dowiesz się, jak współdziałają one z odbijającymi właściwościami materiałów przypisywanych obiektom.
Rozdział 10. - Modelowanie i kompozycja obiektów 3D
i
l W tym rozdziale zaczniemy tworzyć złożone trójwymiarowe obiekty, składając je z pro-
f stych, mniej skomplikowanych obiektów 3D. Poznamy także listy wyświetlania OpenGL,
l stanowiące metodę przyspieszania rysowania obiektów składających się z wielu części.
Rozdział 11. - Grafika rastrowa
i
i
j W tym rozdziale dowiesz się, jak w bibliotece OpenGL manipulować bitmapami. Dotyczy
[ to także odczytywania plików .BMP Windows i wyświetlania ich w scenach OpenGL.
Rozdział 12. - Mapowanie tekstur
j Mapowanie tekstur jest jedną z najużyteczniejszych właściwości każdej biblioteki 3D.
Nauczysz się „owijać" bitmapy wokół wielokątów, a także korzystać z automatycznej
generacji współrzędnych tekstury.
Rozdział 13. - Kwadryki: kule, cylindry i dyski
W tym rozdziale omówimy funkcje biblioteki glu (OpenGL Utility Library), przeznaczone do szybkiego tworzenia pewnych standardowych kształtów.
Część III: Tematy zaawansowane i efekty specjalne
Rozdział 14. - Maszyna stanu OpenGL
Wiele globalnych ustawień i parametrów OpenGL jest zarządzanych przez Maszynę stanu OpenGL. W tym rozdziale poznamy ten mechanizm, a także kilka uogólnionych funkcji przeznaczonych do ustawiania i odczytywania różnych parametrów.
30______________________________________OpenGL - księga eksperta
Rozdział 15. - Bufory: nie tylko do animacji
W tym rozdziale poznamy wiele dodatkowych szczegółów na temat buforów w OpenGL. Przekonasz się, że nie służą wyłącznie do przełączania ekranów.
Rozdział 16. - Efekty specjalne: przezroczystość i mgła
W tym rozdziale zajmiemy się kilkoma innymi efektami specjalnymi. Należą do nich przezroczystość kanału alfa oraz efekt mgły, stosowane do uzyskania wrażenia głębi.
Rozdział 17. - Krzywe i powierzchnie: co to jest NURBS?!!
W tym rozdziale omawiamy funkcje narzędziowe służące do generowania krzywych i powierzchni Beziera i NURBS. Możesz ich użyć przy tworzeniu złożonych kształtów małą ilością kodu.
Rozdział 18. - Podział wielokątów
Tutaj poznasz sposoby podziału złożonych lub niewypukłych wielokątów na mniejsze, wygodniejsze elementy.
Rozdział 19. - Grafika interaktywna
W tym rozdziale wyjaśnimy dwie cechy biblioteki OpenGL: selekcję i funkcje zwrotne. Dzięki tym dwum grupom funkcji użytkownik może operować obiektami sceny. Możesz także otrzymywać szczegółowe informacje na temat każdego obiektu w scenie.
Rozdział 20. - OpenGL w Sieci: VRML
W tym rozdziale poznamy język VRML (Yirtual Reality Modeling Language) oraz jego historię w OpenGL. Omówimy także klasy biblioteki Open Inventor oraz ich powiązania z OpenGL i VRML.
Część IV: OpenGL i...
Rozdział 21. - OpenGL i MFC
Ten rozdział jest przeznaczony dla programistów C++ używających biblioteki klas MFC Microsoftu. Pokażemy, jak używać OpenGL w aplikacjach MFC oraz jak dodać możliwości renderowania OpenGL do okien wyprowadzonych z klasy CWnd.
Rozdział 22. - OpenGL i OWL
Ten rozdział jest przeznaczony dla programistów C++ używających biblioteki klas OWL Borlanda. Pokażemy, jak dodać możliwości renderowania OpenGL do okien OWL wyprowadzonych z klasy TWindow.
>erta Wprowadzenie
Rozdział 23. - OpenGL w Visual Basic i 4GL
pen- W tym rozdziale poznamy kontrolkę OCX, zawierającą większość funkcji i poleceń
OpenGL. Dzięki niej można łatwo tworzyć programy OpenGL w języku Yisual Basic
(4.0 lub nowszym) albo w dowolnym 32-bitowym środowisku obsługującym biblioteki
OCX. Przedstawimy przykłady dla Yisual Basic 4.0 i Delphi 2.0.
nich
Rozdział 24. - Przyszłość OpenGL w Windows
W tym rozdziale zastanowimy się nad przyszłością grafiki 3D i OpenGL w Windows. Omówimy konsekwencje płynące z powstania DirectX API Microsoftu, do którego należą DirectDraw, Direct Sound, Direct Play, Direct Input oraz Direct 3D, zawierające
ys 3D API firmy Reality Labs.
tów
Dodatki
3ze Dodatek A - Polepszanie wydajności OpenGL w Windows
Znajdziesz tutaj kilka ogólnych rad dotyczących poprawy szybkości działania OpenGL w Windows NT i Windows 95.
:ne.
las lać
t Dodatek B - Przydatna literatura
W tym dodatku znajduje się lista dalszych książek i materiałów zawierających informacje związane z tematami omawianymi w tej książce.
Dodatek C - OpenGL wersja 1.1
W trakcie powstawania tej książki powstawało również OpenGL l. l. W treści książki nie zostały omówione nowe funkcje i elementy tej wersji biblioteki; omówiono je ogólnie dopiero w tym dodatku. Na płytce CD-ROM znajdują się najaktualniejsze i uzupełnione informacje o nowych funkcjach i możliwościach dodanych do Windows NT 4.0; znajdziesz na niej także parę przykładowych programów.
Dodatek D - Słownik
Słownik popularnych terminów związanych z OpenGL i grafiką trójwymiarową.
Płytka CD-ROM dołączona do książki
as Wraz z tą książką otrzymujesz płytkę CD-ROM, do granic możliwości wypełnioną
przykładami i innymi rzeczami związanymi z OpenGL. W głównym katalogu płytki,
w kartotece o nazwie Book, znajdziesz cały kod źródłowy zaprezentowany w książce.
Oprócz tego na płytce jest wiele przykładów demonstrujących koncepcje prezentowane
w każdym z rozdziałów, nie opisywanych w treści książki.
32______________________________________OpenGL - księga eksperta
Każdy rozdział posiada własną kartotekę wewnątrz kartoteki Book. W każdej kartotece rozdziału znajdują się dodatkowe kartoteki, dla każdego przykładu osobno. Na przykład, program odbijającego się prostokąta z rozdziału trzeciego znajduje się w kartotece X:\Book\R03\bounce (gdzie X oznacza literę twojego napędu CD-ROM).
Niektóre z kartotek rozdziałów zawierają podkartotekę o nazwie \Tank. Zawiera ona program symulacji czołgu i robota, rozwijany wraz z poznawaniem materiału zawartego z kolejnych rozdziałach. Choć ten program nie jest analizowany rozdział po rozdziale, w miarę poznawania kolejnych funkcji i elementów OpenGL, symulacja staje się coraz bardziej złożona. Szczegóły dotyczące tworzenia tego programu znajdziesz w pliku re-adme.txt w kartotece każdego rozdziału.
Niektóre z przykładowych programów z każdego rozdziału zostały przepisane także w C++ przy użyciu MFC lub OWL. Można je znaleźć w kartotekach X:\MFC\ lub X:\OWLV Także w tym przypadku, wewnątrz kartotek MFC i OWL znajdują się osobne kartoteki dla każdego rozdziału.
Dwie następne główne kartoteki na płytce to \Borland i \OpenGLl 1. Kartoteka \Borland zawiera specyficzną dla Borlanda wersję biblioteki AUX. Szczegóły dotyczące używania funkcji tej biblioteki znajdziesz w pliku readme.txt w tej kartotece. Kartoteka \OpenGLll zawiera dokument opisujący dodatkowe elementy biblioteki OpenGL 1.1, które Microsoft zawarł w systemie Windows NT 4.0. Ponadto znajdziesz tam kilka dodatkowych programów demonstrujących te nowe możliwości.
Pamiętaj, aby sprawdzić zawartość pliku readme.txt w głównej kartotece w celu poznania najbardziej aktualnych informacji o zawartości płytki CD-ROM. Ten plik zawiera także pełny listing wszystkich plików i programów zawartych na tej płytce.
Do dzieła!
Jeśli dopiero zaczynasz poznawać grafikę trójwymiarową lub bibliotekę OpenGL, szczerze ci zazdroszczę. Nic nie daje większej satysfakcji i zwykłej radości niż poznawanie nowej technologii lub narzędzia. Choć OpenGL ma korzenie w naukowym modelowaniu i symulacji, nie musisz być naukowcem, aby je opanować. Szczegółowe, krok po kroku opisywane przykłady zawarte w tej książce poprowadzą cię na nowy poziom umiejętności programowania.
Nauka OpenGL jest porównywalna z nauką SQL przy programowaniu baz danych. Zanim poznałem SQL, nawet nie wyobrażałem sobie, jaką potęgą może władać programista baz danych. Jeśli tylko po amatorsku zajmowałeś się trójwymiarową grafiką lub po prostu chciałbyś ją poznać, przekonasz się, jak wiele w tym względzie ma do zaoferowania biblioteka OpenGL!
RichardS. Wrightjr.
Część l
Wprowadzenie do OpenGL
Pierwsza część tej książki zawiera wprowadzenie do trójwymiarowej grafiki i programowania w OpenGL. Zaczniemy ją od krótkiego omówienia biblioteki OpenGL, jej pochodzenia, przeznaczenia oraz sposobu działania. Następnie, zanim przystąpimy do pisania kodu, pomówimy ogólnie o grafice 3D w komputerach, wyjaśnimy, jak i dlaczego widzimy w trzech wymiarach oraz jak określa się położenie i orientację obiektu w trójwymiarowej przestrzeni. W ten sposób zapoznasz się z podstawowymi zagadnieniami i wyrażeniami wykorzystywanymi w całej dalszej części książki.
W rozdziale trzecim rozpoczniesz tworzenie swoich pierwszych programów OpenGL. Poznasz wymagane biblioteki i pliki nagłówkowe, a także konwencje nazw bibliotek i funkcji OpenGL. Na początku poznamy bibliotekę AUX, stanowiącą zestaw narzędzi do nauki OpenGL niezależnie od platformy programowej i sprzętowej. Dopiero później, w rozdziale czwartym, przejdziemy do pisania programów używających OpenGL bezpośrednio w Windows 95 i Windows NT. Poznamy rozszerzenia interfejsu GDI w Windows, umożliwiające użycie OpenGL w Windows; opiszemy także sposoby ich wykorzystania. W rozdziale piątym uzyskasz konieczne informacje dotyczące obsługi i zgłaszania błędów. Dowiesz się, jak od pytać bibliotekę AUX o jej tożsamość i twórcę, a także jak poprawić jej wydajność. Posiadając tę wiedzę, będziesz dobrze przygotowany do opanowania wiedz^z części drugiej, w której przykłady będą znacznie bardziej skomplikowane!
(zdział 1.
o to jest OpenGL?
OpenGL można zdefiniować jako „programowy interfejs sprzętu graficznego". Jest to biblioteka przeznaczona do tworzenia trójwymiarowej grafiki, bardzo szybka i łatwo przenaszalna pomiędzy różnymi systemami. Używając OpenGL możesz tworzyć elegancką i interesującą grafikę 3D, o jakości nie ustępującej programom typu ray-trace. Największą zaletą korzystania z biblioteki OpenGL jest jej szybkość działania, o rzędy wielkości wyższa od programów typu ray-trace. W OpenGL zastosowano algorytmy opracowane i dopieszczone przez Silicon Graphics (SGI), niekwestionowanego lidera w dziedzinie animacji i grafiki komputerowej.
OpenGL jest przeznaczone do używania w komputerach wyposażonych w sprzęt zaprojektowany i zoptymalizowany pod kątem wyświetlania i operowania grafiką 3D. Jedynie programowe, „ogólne" implementacje OpenGL także są możliwe; należą do nich właśnie implementacje w Windows NT i Windows 95. Jednak ta sytuacja wkrótce może się zmienić, ponieważ coraz więcej producentów kart graficznych uzupełnia swoje produkty o akceleratory grafiki trójwymiarowej. Choć są one przeznaczone głównie do wykorzystania w grach, z tym ściśle wiąże się, rozwój coraz szybszych kart 2D, optymalizujących operacje takie jak rysowanie odcinków czy wypełnianie i manipulowanie bitmapami. Tak jak dziś już nikt nie wyobraża sobie zastosowania w nowym komputerze Windows zwykłej karty VGA, tak wkrótce standardem staną się karty graficzne z akceleratorem 3D.
Graficzne interfejsy API w Windows
Na początku było GDI (Graphics Device Interface), dzięki któremu możliwe było pisanie programów graficznych niezależnych od sprzętu -jednak za cenę szybkości. Twórcy kart graficznych zaczęli pisać zoptymalizowane sterowniki GDI, znacznie przyspieszające działanie tego interfejsu. Następnie, specjalnie z myślą o twórcach gier, Microsoft zaproponował bibliotekę WinG. WinG zawierała jedynie kilka funkcji pozwalających na szybkie wyświetlanie bitmap, ale i one nie były wystarczająco sprawne. Po pewnym czasie Microsoft opracował nowy
36 Część l * Wprowadzenie do OpenGL
interfejs, Direct Draw API, umożliwiający naprawdę niskopoziomowy dostęp do sprzętu. Direct Draw rozwinął się do całego zestawu interfejsów, zwanych łącznie pakietem DirectX, umożliwiającym bezpośredni dostęp do sprzętu, dzięki któremu pisanie gier stało się o wiele łatwiejsze, zaś same gry mogą działać szybciej. Po jakimś czasie częścią pakietu stał się również interfejs 3DDI, wspierający trójwymiarową grafikę w grach i innych aplikacjach multimedialnych. Na temat powiązań Windows z akceleratorami trzeciego wymiaru powiemy sobie nieco więcej w rozdziale 24.
OpenGL jest używane do różnych celów, od programów typu CAD, przez aplikacje architektoniczne aż do generowania komputerowych dinozaurów w najnowszych filmach. Wprowadzenie przemysłowego API 3D na masowy rynek systemów operacyjnych, takich jak Microsoft Windows, także obiecuje pewne ekscytujące możliwości. Gdy powszechna stanie się akceleracja sprzętowa i szybkie procesory, grafika 3D wkrótce stanie się typowym składnikiem każdej aplikacji i nie będzie już stanowić domeny gier i aplikacji naukowych.
Kto jeszcze pamięta czasy, kiedy arkusze kalkulacyjne mogły prezentować dane w postaci płaskiego, dwuwymiarowego wykresu? Jeśli sądzisz, że dodanie trzeciego wymiaru do zwykłej aplikacji jest ekstrawagancją, przypomnij sobie firmy, które pierwsze zastosowały ten pomysł w praktyce. Quattro Pro, jedna z pierwszych aplikacji pozwalających na łatwe tworzenie trójwymiarowych wykresów, niemal całkowicie zdominowała rynek arkuszy kalkulacyjnych. Obecnie płaska, dwuwymiarowa grafika nie może zapewnić sukcesu żadej poważnej aplikacji.
Nie chodzi o to, że OpenGL służy wyłącznie tworzeniu wykresów w aplikacjach biurowych. Zastosowań jest mnóstwo. Sukces lub porażka produktu często w ogromnym stopniu zależy od jego wyglądu, mimo że we wszystkich innych aspektach program nie ustępuje lub nawet wyprzedza konkurencję. A dobra grafika 3D może znacznie podnieść walory wizualne programu!
O OpenGL
Powiedzmy sobie teraz parę słów na temat pochodzeniu biblioteki OpenGL, jej twórcach oraz o jej zastosowaniach i perspektywach. Poznamy także podstawowe aspekty implementacji OpenGL.
Historia OpenGL
OpenGL jest względnie nowym standardem przemysłowym, opracowanym zaledwie kilka lat temu, jednak już powszechnie zaakceptowanym. Przodkiem OpenGL był własny język GL firmy Silicon Graphics. „IRIS GL" było interfejsem API grafiki 3D dla stacji roboczych IRIS tej samej firmy. Te komputery nie miały jedynie ogólnego prze-
Rozdział 1. * Co to jest OpenGL?__________________________________37
znaczenia, lecz posiadały specjalny sprzęt zoptymalizowany pod kątem wyświetlania rozbudowanej grafiki. Ten sprzęt pozwalał między innymi na superszybkie wykonywanie obliczeń macierzowych (stanowiących podstawę wszelkiej grafiki 3D), wspierał operacje na buforach głębokości i na kilka innych sposobów przyspieszał obliczenia graficzne. Jednak gdy SGI podjęło próbę przeniesienia biblioteki IRIS GL na inne platformy sprzętowe, pojawiły się problemy.
OpenGL jest rezultatem prac firmy SGI nad poprawą przenaszalności biblioteki IRIS GL. Nowy język oferuje moc biblioteki GL, lecz jest przy tym „otwarty", pozwalając na łatwą adaptację dla różnych platform sprzętowych i systemów operacyjnych. (SGI wciąż sprzedaje IRIS GL, jednak poza usuwaniem błędów nie rozbudowuje go o nowe funkcje i elementy).
W lipcu 1992 roku powstała wersja 1.0 specyfikacji OpenGL. Już pięć dni później, na pierwszej konferencji programistów Win32, SGI zaprezentowała bibliotekę OpenGL działającą na komputerze IRIS Indigi. Wstawki wideo z filmów takich jak Terminator 2 oraz wizualizacje aplikacji medycznych przyciągały żywe zainteresowanie publiczności. Już w tym czasie Microsoft i SGI współpracowały ze sobą, planując zamieszczenie OpenGL w przyszłych wersjach Windows NT.
Dalszy rozwój OpenGL
Otwarty standard nie może być w rzeczywistości „otwarty", jeśli kontroluje go tylko jedna firma. W związku z tym wszelkie rozszerzenia OpenGL są zatwierdzane przez OpenGL Architecture Review Board (ARB), której członkami założycielami są SGI, Digital Eąuipment Corporation, IBM, Intel i Microsoft. OpenGL ARB zbiera się dwa razy w roku.
Te spotkania są otwarte dla publiczności i niezrzeszone firmy także mogą brać udział w dyskusji (choć nie mogą głosować). O pozwolenie na udział w obradach trzeba ubiegać się o wiele... wcześniej, gdyż w celu zwiększenia produktywności spotkania odbywają się w niewielkim gronie. Członkowie ARB często uczestniczą w grupie dyskusyjnej comp.graphics.api.opengl. Można do niej wysyłać własne pytania i uwagi.
W grudniu 1995 roku ARB ratyfikowało końcową wersję specyfikacji wersji 1.1 biblioteki OpenGL. Wiele z rozszerzeń i zmian w stosunku do wersji l .0 dotyczy poprawienia wydajności; zostały one zebrane w Dodatku A.
Jak działa OpenGL
OpenGL jest językiem1 raczej proceduralnym niż opisowym. Zamiast opisywania samej sceny oraz jej wyglądu, programista opisuje kroki konieczne do osiągnięcia określonego
1 W treści książki można natrafić na terminy biblioteka OpenGL i język OpenGL. O ile termin biblioteka odnosi się do całego produktu w ogólności, o tyle o języku można mówić raczej w kontekście specyficznych wywołań funkcji zawartych w bibliotece, (przyp. tłumacza)
38_________________________________Część l •» Wprowadzenie do OpenGL
wyglądu lub efektu. Te „kroki" stanowią wywołania funkcji wysoce przenaszalnego interfejsu API, zawierającego około 120 poleceń i funkcji. Służą one rysowaniu prymitywów graficznych, takich jak punkty, odcinki i wielokąty w trzech wymiarach. Ponadto OpenGL obsługuje oświetlenie i cieniowanie, mapowanie tekstur, animację oraz dodatkowe efekty specjalne.
OpenGL nie zawiera żadnych funkcji przeznaczonych do zarządzania oknami, interakcji z użytkownikiem czy operacji wejścia/wyjścia. Każde systemowe środowisko (takie jak Microsoft Windows) posiada do tego celu własne funkcje i jest odpowiedzialne za zapewnienie sposobów przeniesienia rysunków OpenGL do okna lub na bitmapę.
OpenGL w Windows
OpenGL zadebiutował w Windows NT 3.51. Wkrótce po powstaniu Windows 95 pojawił się również zestaw bibliotek DLL umożliwiających korzystanie z OpenGL także w tym systemie. Ta książka odnosi się właśnie do tej ogólnej implementacji OpenGL. Ty, programista, zostaniesz w niej poprowadzony przez podstawy grafiki trójwymiarowej, a następnie dowiesz się, jak kompilować i łączyć programy OpenGL w Windows NT i Windows 95. Przechodząc dalej, omówimy funkcje dostarczone przez Microsoft - stanowiące spoiwo umożliwiające działanie OpenGL z interfejsem GDI Microsoftu. Od tego momentu zaczniesz poznawać pozostałe elementy OpenGL API, wszystko to w kontekście Microsoft Windows NT i Windows 95.
Architektura graficzna: oprogramowanie kontra sprzęt
Użycie OpenGL nie jest tym samym, co użycie GDI przy rysowaniu w oknie. W rzeczywistości, bieżący wybór pędzli, piór, czcionek i innych obiektów GDI nie ma żadnego wpływu na OpenGL. Tak jak GDI używa kontekstu urządzenia do sterowania rysowaniem w oknie, tak OpenGL używa tzw. kontekstu renderowania (tworzenia grafiki). Kontekst renderowania jest powiązany z kontekstem urządzenia, który z kolei jest powiązany z oknem - i voila - OpenGL renderuje do okna. Szczegóły mechaniki tego procesu zostaną opisane w rozdziale czwartym.
Jak już wspomnieliśmy, OpenGL jest przeznaczony do pracy w systemach z akceleracją sprzętową. Coraz częściej producenci kart graficznych uzupełniają je o obsługę tej biblioteki. Poprawnie napisana aplikacja OpenGL nie powinna zauważać różnic pomiędzy renderowaniem ze wsparciem ze strony sprzętu a renderowaniem czysto programowym. Jednak użytkownik z pewnością zauważy różnicę, polegającą na znacznym wzroście wydajności w przypadku zastosowania akceleratora.
Rysunek 1.1 ilustruje akcelerację sprzętową w Windows, włącznie ze zwykłą akceleracją GDI i akceleracją Direct Draw, a także akcelerację OpenGL. Po lewej stronie widzimy, jak aplikacje korzystają z normalnych wywołań GDI, kierowanych poprzez bibliotekę WINSRV.DLL do interfejsu Win32 Device Driver Interface. Interfejs Win32 DDI
39
ozdział 1. * Co to jest OpenGL?
następnie komunikuje się bezpośrednio ze sterownikiem karty graficznej i dopiero tam odbywa się sprzętowa akceleracja wywołań GDI.
Rysunek 1.1.
Schemat działania akceleracji sprzętowej \v Windows
Direct Draw jest zoptymalizowany pod kątem dostępu do sprzętu graficznego. Całkowicie pomija GDI i komunikuje się bezpośrednio z kartą graficzną, być może za pośrednictwem cienkiej warstwy HAL (Hardware Abstraction Layer - warstwy abstrakcji sprzętu) i oprogramowania emulującego funkcje nieobsługiwane przez sprzęt. Direct Draw jest zwykle używany w grach, umożliwiając bezpośrednie manipulacje w pamięci graficznej w celu osiągnięcia ultraszybkiej grafiki i animacji 2D.
Po prawej stronie rysunku 1.1 znajdują się wywołania OpenGL i innych API 3D, przekazywane poprzez interfejs sterownika grafiki trójwymiarowej (3D DDI). 3D DDI jest opracowany tak, aby umożliwić producentom sprzętu akcelerację OpenGL i innych trójwymiarowych API, takich jak Reality Labs API. (Dyskusja na temat OpenGL i API firmy Reality Labs została zawarta w rozdziale 24). Oprócz tego, dostawcy akceleratorów
40_________________________________Część l » Wprowadzenie do OpenGL
sprzętowych przeznaczonych specjalnie dla OpenGL (takich jak chipset GLINT) mogą instalować własne sterowniki klienta OpenGL, a także specjalizowane sterowniki interfejsu producenta.
Ograniczenia ogólnej implementacji
O ile nie zostanie wsparta przez sprzęt, ogólna implementacja OpenGL zastosowana przez Microsoft podlega pewnym ograniczeniom. Zalicza się do nich brak wsparcia drukowania grafiki OpenGL na monochromatycznych drukarkach lub drukarkach kolorowych o mniej niż czterech bitplanach kolorów (mniej niż szesnastu kolorach). Nie są także obsługiwane palety sprzętowe dla poszczególnych okien. Zamiast tego, Windows posiada pojedynczą paletę sprzętową, którą musi dzielić pomiędzy poszczególne aplikacje.
Na koniec, pewne elementy OpenGL nie zostały zaimplementowane; należą do nich obrazy stereoskopowe, pomocnicze bufory oraz plany bitowe alfa. Jednak te elementy mogą, lecz nie muszą, być implementowane przez sprzęt. Zanim zechcesz skorzystać z nich w swojej aplikacji, zawsze najpierw sprawdź ich dostępność (więcej na ten temat w rozdziale piątym).
Dalsze perspektywy OpenGL w Windows
Wprowadzenie OpenGL do rodziny systemów operacyjnych Windows otwiera całkiem nowe, ekscytujące możliwości. Gdy miliony komputerów osobistych będą mogły korzystać z OpenGL, Windows stanie się najpopularniejszą platformą dla aplikacji tego typu. Na początku będą to głównie aplikacji naukowe i inżynieryjne, przeznaczone do modelowania i wizualizacji, jednak z czasem z pewnością powstanie także ogromna liczba gier, programów rozrywkowych i aplikacji biurowych wykorzystujących trójwymiarową grafikę.
Nawet w przypadku producentów tworzących aplikacje OpenGL dla innych platform, implementacje dla Microsoft Windows mogą stać się znaczącą pozycją. Stacje robocze oparte na Windows stanowią atrakcyjną alternatywę dla drogich, specjalistycznych stacji graficznych, przy tym można na nich korzystać z przeogromnej oferty różnorodnych istniejących programów i aplikacji.
Rozdział 2.
Podstawy grafiki 3D
W tym rozdziale dowiesz się:
Jak oczy postrzegają trzeci wymiar;
W jaki sposób dwuwymiarowy obraz może udawać trzeci wymiar;
Jak współrzędne kartezjańskie określają położenie obiektu;
Jak widok wpływa na rozmiar obrazka;
W jaki sposób tworzy się trójwymiarowe obiekty z dwuwymiarowych prymitywów;
Jak pracować z rzutami równoległymi i perspektywicznymi.
Zanim zajmiemy się specyfiką OpenGL, poświęćmy nieco czasu poznaniu podstawowego słownictwa trójwymiarowej grafiki. W tym celu zaprezentujemy fundamentalne koncepcje grafiki 3D i układów współrzędnych. Dowiesz się także, w jaki sposób można tworzyć (pozornie) trójwymiarową grafikę na płaskim, dwuwymiarowym ekranie. Czytelnicy doświadczeni w grafice 3D lub po prostu palący się do napisania pierwszego programu OpenGL, mogą po prostu na razie pominąć ten rozdział.
Postrzeganie w trzech wymiarach
„Trójwymiarowa grafika komputerowa" to w rzeczywistości dwuwymiarowe obrazy na płaskim ekranie komputera, sprawiające wrażenie posiadania głębokości czy „trzeciego" wymiaru. Aby móc rzeczywiście oglądać obraz trójwymiarowy, musisz oglądać obiekt oboma oczami lub dostarczyć każdemu oku oddzielny, nieco inny obraz obiektu. Rzuć okiem na rysunek 2.1. Każde oko widzi dwuwymiarowy obraz będący jakby tymczasową fotografią na siatkówce (na tylnej części oka). Te dwa obrazy są nieco różne, gdyż każde oko patrzy na obiekt pod nieco innym kątem (właśnie w tym celu nasze oczy są od siebie oddalone). Dopiero mózg łączy te dwa nieco różne obrazy w jeden, trójwymiarowy obraz w mózgu.
42
Część l » Wprowadzenie do OpenGL
Rysunek 2.1.
Jak oczy „ widzą' trzeci wymiar
Drugi obraz na siatkówce
Pierwszy obraz na siatkówce
Na rysunku 2.1 kąt 9 pomiędzy obrazami staje się tym mniejszy, im bardziej obiekt oddala się od oczu. W związku z tym efekt trójwymiarowości zwiększa się wraz ze wzrostem kąta pomiędzy dwoma obrazami. Stereoskopy (te ręczne przeglądarki trójwymiarowych slajdów, którymi z pewnością bawiłeś się w dzieciństwie) i trójwymiarowe kina wykorzystują właśnie ten efekt, przekazując każdemu z oczu odmienny obraz, czy to przez oddzielne soczewki, czy też przez kolorowe okulary przepuszczające wybrane fragmenty obrazu. W tych obrazach zwykle stosuje się większy kąt pomiędzy oboma widokami, w celu osiągnięcia wyraźniejszego efektu trójwymiarowości.
Co się stanie gdy zakryjesz jedno oko? Być może myślisz, że dalej będziesz widział trójwymiarowo, ale spróbuj przeprowadzić taki oto eksperyment: Umieść szklankę lub jakiś inny obiekt na wyciągnięcie ręki, po lewej stronie od siebie. Prawą ręką zasłoń prawe oko i spróbuj sięgnąć po ten przedmiot. (Powinieneś użyć plastikowej szklanki!) Zwróć uwagę że tym razem masz dużo więcej trudności z sięgnięciem i trafieniem w szklankę. Teraz odkryj prawe oko i jeszcze raz spróbuj chwycić szklankę; przekonasz się, jaka jest różnica. Właśnie z tego powodu ludzie, którzy stracili jedno oko, często mają problemy z oceną odległości.
2D + Perspektywa = 3D
Powód, dla którego po zasłonięciu jednego oka świat nie staje się natychmiast płaski, wynika z faktu, że wiele z efektów świata trójwymiarowego obowiązuje także w świecie dwóch wymiarów. Najoczywistszym z nich jest to, że obiekty bliższe wydają się większe od obiektów położonych dalej. Ten efekt jest nazywany perspektywą. Zaś perspektywa plus zmiany w barwie, teksturze, oświetleniu, cieniowaniu i różnych intensy-wnościach koloru (w związku z oświetleniem) łącznie tworzą wrażenie trójwymiarowości.
Już sama perspektywa wystarczy do stworzenia wrażenia trzeciego wymiaru. Rysunek 2.2 przedstawia prosty szkieletowy sześcian. Nawet bez kolorowania i cieniowania, ten sześcian wydaje się obiektem trójwymiarowym. Jednak gdy będziesz patrzył na niego wystarczająco długo, przód i tył sześcianu nagle zamienią się miejscami. Dzieje się tak,
43
Rozdział 2. * Podstawy grafiki 3D
ponieważ mózgowi brakuje jakiejś dodatkowej płaszczyzny odniesienia, kontekstu określającego rzeczy wista postać obiektu.
Rysunek 2.2.
Ten prosty szkieletowy sześcian demonstruje efekt perspektywy
Usuwanie niewidocznych linii
Rysunek 2.2 zawiera jedynie tyle informacji, aby dać wrażenie trzech wymiarów, nie umożliwia jednak określenia, gdzie jest przód, a gdzie tył obiektu. Oglądając rzeczywisty obiekt, skąd wiesz, gdzie jest jego tył, a gdzie przód? To proste - tył jest przesłonięty przez przód. Gdyby sześcian z rysunku 2.2 był wypełniony, nie mógłbyś ujrzeć jego tylnego wierzchołka i w związku z tym nie mógłbyś pomylić go z wierzchołkiem przednim. Nawet jeśliby sześcian był zrobiony z drutu, części drutu z przodu przesłaniałaby części drutu z tyłu. Aby zasymulować to na dwuwymiarowym rysunku, należy usunąć linie zasłaniane przez powierzchnie przednich ścian sześcianu. Ta technika nazywa się usuwaniem niewidocznych linii i została zastosowana w przypadku kostki z rysunku 2.3.
Rysunek 2.3.
Ta sama kostka po usunięciu niewidocznych linii
Kolory i cieniowanie
Rysunek 2.3 wciąż nie daje wrażenia rzeczywistego obiektu. Ścianki kostki mają dokładnie ten sam kolor, co tło, zaś wszystko, co widać, to przednie ścianki. Rzeczywista kostka miałaby pewien kolor i teksturę; na przykład w drewnianej kostce widoczne byłyby kolor i tekstura drewna. W komputerze (lub na papierze), jeśli jedynie pokolorujemy kostkę i narysujemy ją w dwóch wymiarach, otrzymamy rysunek podobny do rysunku 2.4.
Wróciliśmy więc do obiektu wyglądającego na płaski i dopóki nie narysujemy krawędzi innym kolorem, w ogóle nie osiągniemy efektu trójwymiarowości. Aby odzyskać wrażenie trzech wymiarów (bez rysowania krawędzi w innym kolorze), każdą z trzech widocznych ścianek musimy zamalować innym kolorem lub tym samym kolorem o różnym natężeniu, co daje wrażenie oświetlenia obiektu. Na rysunku 2.5 widzimy obiekt, w którym ścianki zostały zamalowane różnymi kolorami lub odcieniami.
44
Część l 4 Wprowadzenie do OpenGL
Rysunek 2.4.
Pokolorowana kostka, ale bez cieniowania
Rysunek 2.5.
Kostka ze ściankami zamalowanymi różnymi kolorami
Światła i cienie
Musimy powiedzieć także parę słów o oświetleniu. Oświetlenie daje dwa ważne efekty w przypadku obiektów oglądanych w trzech wymiarach. Po pierwsze, powoduje, że powierzchnie o jednakowym kolorze, po oświetleniu z boku, wydają się mieć różne cieniowanie. Po drugie, obiekty nie przepuszczające światła (czyli większość wypełnionych obiektów) po oświetleniu rzucają cień (rysunek 2.6).
Rysunek 2.6.
Kostka oświetlona pojedynczym źródłem światła
Na nasz trójwymiarowy obiekt mają wpływ dwa źródła światła. Światło otoczenia, pozbawione kierunku, to po prostu jednolite oświetlenie mogące powodować efekt cieniowania na jednolitych obiektach; światło otoczenia powoduje, że bardziej odległe krawędzie wydają się ciemniejsze. Drugim źródłem światła może być na przykład lampa. Lampy w grafice trójwymiarowej są używane do zmiany cieniowania jednolitych obiektów oraz uzyskiwania efektów cienia.
Układy współrzędnych
Gdy wiesz już, jak oko może postrzegać trzy wymiary na dwuwymiarowej płaszczyźnie (ekranie komputera), zastanówmy się, jak narysować obiekty na tym ekranie. Gdy rysujesz punkty, linie czy inne kształty na ekranie, zwykle określasz ich położenie w odnie-
45
Rozdział 2. 4 Podstawy grafiki 3D
sięniu do wierszy i kolumn. Na przykład, na standardowej karcie VGA na ekranie mieści się 640 punktów w poziomie i 480 punktów w pionie. Aby narysować punkt na środku ekranu, nakazujesz narysowanie punktu o współrzędnych (320, 240) - czyli 320 punktów od lewej krawędzi i 240 punktów od górnej krawędzi.
W OpenGL, gdy tworzysz okno przeznaczone do rysowania, musisz określić także układ współrzędnych, którego chcesz użyć, oraz sposób odwzorowania współrzędnych tego układu na fizyczne piksele ekranu. Zanim zajmiemy się trzema wymiarami, zobaczmy, jak wygląda to w układzie dwóch wymiarów.
Dwuwymiarowe współrzędne kartezjańskie
Najpopularniejszym układem w dwuwymiarowych rysunkach jest kartezjański układ współrzędnych. Współrzędne kartezjańskie są określane przez współrzędne x i y. Współrzędna x określa położenie w poziomie, zaś współrzędna y - w pionie.
Początek kartezjańskiego układu współrzędnych znajduje się w punkcie x = O, y = 0. Współrzędne kartezjańskie są zapisywane jako pary, w nawiasach, w których współrzędna x jest zapisywana jako pierwsza, zaś współrzędna y jako druga, oddzielona przecinkiem. Na przykład początek układu można zapisać jako (O, 0). Rysunek 2.7 opisuje kartezjański układ współrzędnych w dwóch wymiarach. Linie x i y ze znacznikami są nazywane osiami i biegną od minus nieskończoności do plus nieskończoności. Zwróć uwagę, że na tym rysunku widać po prostu zwykły układ współrzędnych, z jakim nieraz zetknąłeś się w szkole. W Windows istnieje jednak możliwość zastosowania innych (a właściwie zmodyfikowanych) układów odwzorowania, w których jednostki i kierunki mogą być interpretowane inaczej. W dalszej części książki zobaczymy, jak na różne sposoby można odwzorować ten prawdziwy układ współrzędnych na różne układy współrzędnych okna.
Rysunek 2.7.
Kartezjański uklad współrzędnych
(-7,6)
..____ _j (5,4)
"' P«zqtek '
(0,0) !
l l l l l l l
i M i I-3.-2) •"--••
-y
Osie x i y są do siebie prostopadle (przecinają się pod kątem prostym) i razem tworzą płaszczyznę xy. Płaszczyzna jest, mówiąc w dużym uproszczeniu, płaską powierzchnią. W każdym układzie współrzędnych, para osi przecinających się pod kątem prostym tworzy płaszczyznę. W systemie zawierającym jedynie dwie osie, istnieje oczywiście tylko jedna płaszczyzna.
46
Część l * Wprowadzenie do OpenGL
Obcinanie współrzędnych
Okno jest mierzone fizycznie w pikselach. Zanim zaczniesz rysować w oknie punkty, odcinki i inne kształty, musisz najpierw poinformować OpenGL, jak ma zamieniać podane pary współrzędnych na fizyczne współrzędne okna. Odbywa się to poprzez określenie regionu przestrzeni kartezjańskiej zajmowanego przez okno; ten region nosi nazwę obszaru obcinania. W dwuwymiarowej przestrzeni obszar obcinania jest określony przez minimalne i maksymalne wartości współrzędnych x i y punktów należących do okna. Można spojrzeć na to także inaczej, określając położenie początku w stosunku do okna. Rysunek 2.8 przedstawia dwa przykłady obszarów obcinania.
Rysunek 2.8.
Dwa obszary obcinania
100
Obszar
roboczy
okna
150
(0,0)
W pierwszym przykładzie, po lewej stronie rysunku 2.8, współrzędne x w oknie należą do zakresu od O do +150 (od lewej do prawej), zaś współrzędne y należą do zakresu od O do 100 (od dołu do góry). Punkt na środku ekranu miałby współrzędne (75, 50). W drugim przykładzie widzimy obszar obcinania ze współrzędnymi x należącymi do zakresu od -75 do +75, zaś współrzędnymi y należącymi do zakresu od -50 do 50. W tym przykładzie punkt w środku ekranu pokrywa się z początkiem układu (O, 0). Istnieje także możliwość użycia funkcji OpenGL (lub zwykłych funkcji rysunkowych GDI Windows) w celu odwrócenia układu współrzędnych „do góry nogami" lub zamiany strony lewej z prawą. W rzeczywistości, domyślnym odwzorowaniem w oknach Windows jest oś pionowa biegnąca od góry do dołu okna. Choć jest to użyteczne podczas wypisywania tekstu od dołu do góry, takie odwzorowanie jest inne niż to, do którego przywykliśmy w szkole.
Widoki, twoje okna na trójwymiarowy świat
Rzadko wysokość i szerokość obszaru obcinania dokładnie odpowiadają szerokości i wysokości okna w pikselach. W związku z tym układ współrzędnych musi zostać odwzorowany z logicznych współrzędnych kartezjańskich na fizyczne współrzędne pikseli okna. To odwzorowanie jest określane przez tak zwany widok. Widok jest obszarem wewnątrz obszaru roboczego okna, który zostanie użyty do rysowania obszaru roboczego. Widok po prostu odwzorowuje obszar obcinania w określony obszar okna. Zwykle
47
Rozdział 2. * Podstawy grafiki 3D
widok jest zdefiniowany jako cale okno, ale nie jest to wymagane - na przykład możesz zechcieć rysować tylko w dolnej części okna.
Rysunek 2.9 przedstawia duże okno, 300 x 200 pikseli, z widokiem obejmującym cały obszar roboczy. Gdyby obszar obcinania dla tego okna zostałby ustawiony na od O do 150 w osi x i od O do 100 w osi y, to współrzędne logiczne byłyby odwzorowane na większe współrzędne ekranowe okna. Każde zwiększenie współrzędnej logicznej powodowałoby dwukrotne zwiększenie odpowiedniej współrzędnej (współrzędnej piksela) w oknie.
Rysunek 2.9.
Widok zdefiniowany jako dwukrotne powiększenie obszaru obcinania
Obszot roboczy okna 300x200 pikseli Widok = 300x200
Dla odróżnienia, na rysunku 2.10 pokazano widok odpowiadający obszarowi obcinania. Okno ma jednak w dalszym ciągu wymiary 300 x 200 pikseli, co powoduje, że obszar widoku zajmuje jedynie lewą dolną część okna.
Rysunek 2.10.
Widok o tych samych rozmiarach, co obszar obcinania
Możesz używać widoków do zmniejszania lub powiększania obrazów wewnątrz okna oraz do wyświetlania jedynie części obszaru obcinania, poprzez ustawienie widoku większego niż obszar roboczy okna.
Rysowanie prymitywów
Zarówno w dwóch, jak i w trzech wymiarach, gdy rysujesz obiekt, w rzeczywistości składasz go z serii mniejszych kształtów, zwanych prymitywami. Prymitywy to proste obiekty, takie jak punkty, odcinki i płaskie wielokąty składane w przestrzeni 3D w celu utworzenia trójwymiarowych obiektów. Na przykład, trójwymiarowa kostka z rysunku 2.5 składa się z sześciu dwuwymiarowych kwadratów, z których każdy jest umie-
48
Część l * Wprowadzenie do OpenGL
szczony na osobnej ściance. Każdy róg prostokąta (i każdego innego prymitywu) jest nazywany wierzchołkiem. Wierzchołki posiadają określone współrzędne w przestrzeni dwu- lub trójwymiarowej. O prymitywach występujących w OpenGL oraz sposobach ich wykorzystania dowiesz się w rozdziale 6.
Trójwymiarowe współrzędne kartezjańskie
Spróbujmy teraz uzupełnić nasz dwuwymiarowy układ współrzędnych o trzeci wymiar, dodając do niego głębokość. Rysunek 2.11 przedstawia układ współrzędnych kartezjań-skich z nową osią, osią z. Oś z jest prostopadła zarówno do osi x, jak i osi y. Reprezentuje linię biegnącą prostopadle ze środka ekranu w kierunku oglądającego. (Obróciliśmy nasz układ z rysunku 7.7 wokół osi y, a następnie wokół osi x. Gdybyśmy tego nie zrobili, oś z biegłaby prosto w naszym kierunku i nie moglibyśmy jej ujrzeć). Współrzędne w takiej trójwymiarowej przestrzeni określamy przy pomocy trzech współrzędnych: x, y i z. Na rysunku 2.11 dla jasności zaznaczono punkt (-4, 4, 4).
Rysunek 2.11.
Współrzędne kartezjańskie w trzech wymiarach
(-4,4,4)
•z
Rzuty, podstawa grafiki 3D
Wiesz już, jak przy pomocy współrzędnych kartezjańskich można określić położenie w przestrzeni 3D. Jednak bez względu na to, jak będziesz wytrzeszczał oczy, piksele na ekranie mają tylko dwa wymiary. Jak OpenGL tłumaczy współrzędne kartezjańskie na dwuwymiarowe współrzędne, które można przedstawić na ekranie? Krótka odpowiedź brzmi: dzięki trygonometrii i prostych manipulacjach macierzami. Prostych? Cóż, może nie do końca - moglibyśmy teraz poświęcić wiele stron i licznych czytelników, którzy nie lubią algebry liniowej, objaśniając te „proste" zagadnienia. Więcej informacji na ten temat znajdzie się jednak w rozdziale 7, zaś głębsze omówienie można znaleźć w dodatku B. Na szczęście, aby używać OpenGL do tworzenia grafiki, nie trzeba się znać na matematyce.
Wszystko, czego w rzeczywistości potrzeba do zrozumienia większości materiału w tej książce, to pojęcie rzutu. Współrzędne 3D są rzutowane na dwuwymiarową płaszczyznę (tło okna). Przypomina to rysowanie pisakiem na szybie konturów jakiegoś trójwymia-
49
Rozdział 2. * Podstawy grafiki 3D
rowego obiektu w oddali. Gdy usuniemy obiekt lub przeniesiemy szybę, w dalszym ciągu pozostają na niej kontury obiektu. Na rysunku 2.12 dom w tle jest rzutowany na płaski kawałek szkła. Określając rzutowanie, określasz bryle obcinania (pamiętasz obszary obcinania?), którą chcesz wyświetlić w oknie, a także sposób przekształcania jej współrzędnych.
Rysunek 2.12.
Trójwym iarowy obiekt rzutowany na płaską szybę (ekran)
Rzuty równoległe
W OpenGL zwykle będziesz miał do czynienia z dwoma głównymi rodzajami rzutów. Pierwszy rodzaj to rzuty równoległe. Tego typu rzutowanie określa się stosując prostokątną lub sześcienną bryłę rzutowania. Nic, co znajduje się poza tą bryłą, nie jest rzutowane. Co więcej, wszystkie obiekty o tych samych rozmiarach są rysowane w tych samych rozmiarach, bez względu na to, czy znajdują się dalej czy bliżej. Ten rodzaj rzutowania (przedstawiony na rysunku 2.13) jest często używany w projektowaniu architektonicznym i programach CAD (Computer Aided Design - projektowanie wspomagane komputerem).
Rysunek 2.13.
Bryła obcinania rzutu równoleglego
Górna
Dalsza
Prawa
Bliższa
Dolna
Bryłę obcinania rzutu równoległego określa się podając bliższą, dalszą, lewą, prawą, górną oraz dolną płaszczyznę obcinania. Obiekty znajdujące się wewnątrz takiej bryły obcinania są wyświetlane (z wzięciem pod uwagę ich orientacji) na dwuwymiarowym obrazie wyświetlanym na ekranie.
50
Część l * Wprowadzenie do OpenGL
Rzuty perspektywiczne
Drugim, popularniejszym typem rzutowania są rzuty perspektywiczne. To rzutowanie powoduje postanie efektu zmniejszenia rozmiarów bardziej odległych obiektów. Bryła widzenia (rysunek 2.14) przypomina piramidę ze ściętym wierzchołkiem. Obiekty będące bliżej płaszczyzny rzutowania mają rozmiary zbliżone do oryginalnych, podczas gdy obiekty znajdujące się dalej, wraz ze wzrostem oddalenia mają coraz mniejsze rozmiary. Ten rodzaj rzutowania odgrywa najważniejszą rolę w nadawaniu realizmu symulacjom i animacjom 3D.
Dalsza
Rysunek 2.14.
Bryła obcinania w rzutowaniu perspektywicznym
Górna
Prowa
Podsumowanie
W tym rozdziale zapoznaliśmy się z podstawami trójwymiarowej grafiki. Wiesz już, że aby zobaczyć prawdziwy trójwymiarowy widok, musisz widzieć dwa obrazy tego samego obiektu, nieco przesunięte względem siebie. Widziałeś także, jak tworzy się iluzję trójwymiarowości płaskich obiektów, przez dodanie perspektywy, usunięcie niewidocznych linii, pokolorowanie, wycieniowanie oraz oświetlenie. Zapoznałeś się z kartezjańskim układem dwu- i trójwymiarowych współrzędnych, poznałeś także dwie metody stosowane przez OpenGL do rzutowania trójwymiarowych scen na dwuwymiarową powierzchnię.
Celowo nie omawialiśmy szczegółów związanych z osiągnięciem tych efektów w OpenGL. W następnych rozdziałach powiemy, jak stosować te techniki oraz jak maksymalnie wykorzystać potęgę tej biblioteki. Na dołączonej do książce płytce CD-ROM znajdziesz odnoszący się do rozdziału drugiego program CUBE, demonstrujący koncepcje omawiane w pierwszej części tego rozdziału. Po uruchomieniu programu możesz wciskać spację przechodząc kolejno od siatkowego sześcianu do w pełni oświetlonego jednolitego klocka z cieniem. Być może w tym momencie nie rozumiesz kodu programu, ale nie jest to teraz potrzebne do zrozumienia samego zagadnienia. Zanim skończysz lekturę tej książki, będziesz umiał tworzyć takie przykłady samodzielnie i od zera.
Rozdział 3.
Nauka OpenGL
z użyciem biblioteki AUX
W tym rozdziale dowiesz się:
Jakie biblioteki i pliki nagłówkowe składają się na bibliotekę OpenGL;
Jak biblioteka AUX zapewnia podstawowe funkcje zarządzania oknami, dostępne dla prawie wszystkich platform;
Jak użyć OpenGL do stworzenia okna i rysowania w nim;
Jak użyć domyślnego układu współrzędnych OpenGL;
Jak tworzyć złożone kolory używając komponentów RGB (Red, Green i Blue);
Jak widok wpływa na rozmiary obrazu;
Jak przeskalować rysunek, aby dopasował się do rozmiarów okna;
Jak stworzyć prostą animację z użyciem podwójnego bufora;
Jak rysować predefiniowane obiekty.
Gdy zostałeś wprowadzony do OpenGL i znasz podstawy grafiki trójwymiarowej, nadszedł czas, aby samemu zająć się tworzeniem programów. Ten rozdział rozpoczynamy od omówienia używania OpenGL z kompilatorem; przy okazji poznasz także konwencje nazw zmiennych i funkcji. Jeśli pisałeś już jakieś aplikacje OpenGL, w pewnością wiele szczegółów odkryłeś już samodzielnie. Jeśli tak jest rzeczywiście, możesz pominąć pierwszą sekcję i przejść bezpośrednio do opisu biblioteki AUX.
OpenGL: API, nie język
OpenGL nie jest językiem programowania; jest to interfejs API (Application Program-ming Interface - interfejs programowania aplikacji). Gdy mówimy, że program jest na-
52_________________________________Część l « Wprowadzenie do OpenGL
pisany w OpenGL lub jest aplikacją OpenGL, rozumiemy przez to, że jest napisany w jakimś języku programowania (takim jak C czy C++) i wywołuje funkcje jednej lub kilku bibliotek OpenGL. Nie twierdzimy, że programy używają OpenGL wyłącznie do rysowania, gdyż można w nich łączyć wybrane elementy różnych pakietów graficznych. Można też używać OpenGL wyłącznie do specyficznych zadań, zaś grafiki specyficznej dla systemu (na przykład Windows GDI) do innych.
Jako API, biblioteka OpenGL jest zgodna z konwencją wywołań funkcji C. To oznacza, że programy w C mogą wprost wywoływać funkcje OpenGL, ponieważ albo sama funkcja jest napisana w C, albo posiada specjalną funkcję pośrednią C do kodu napisanego w asemblerze lub jakimś innym języku. W tej książce programy są pisane w C lub C++ i są przeznaczone do pracy w środowisku Windows NT lub Windows 95. Programy C++ mogą łatwo korzystać z funkcji C i interfejsu API, wymaga to jedynie wprowadzenia bardzo drobnych poprawek. Inne języki programowania -między innymi tak zwane 4GL-e (j?zyki czwartej generacji), na przykład Yisual Basic - mogące wywoływać funkcje w bibliotekach C, także mogą korzystać z OpenGL. Więcej szczegółów na ten temat znajduje się w rozdziale 23.
Wywoływanie funkcji C z C++
Z wyjątkiem rozdziałów dotyczących specyficznych bibliotek klas C++ lub języków czwartej generacji, wszystkie przykłady w rozdziałach zostały stworzone w C. Na dołączonej do książki płytce CD-ROM wiele przykładów zostało przepisanych w C++ i przygotowanych do użycia z dwoma popularnymi bibliotekami klas C++ (MFC i OWL). Możesz przejrzeć te programy, aby zobaczyć, jak zastosowano w nich makra preprocesora w celu zachowania w języku C większości kodu rysunkowego OpenGL.
Podział pracy w OpenGL
OpenGL API zostało podzielone na trzy osobne biblioteki; są one zebrane w tabeli 3.1.
Tabela 3.1.
Biblioteki i pliki nagłówkowe OpenGL
N——l————N——„•.HM* 5KS» E*-* ——
Auxiliary (pomocnicza) glaux.lib
|
glaux.h
|
aux
|
OpenGL lub gl opengl32.dll
|
gl.h
|
gl
|
Narzędziowa lub glu glu32.dll
|
glu.h
|
glu
|
Pierwsza z nich, opisywana w tym rozdziale, to biblioteka Auxiliary (pomocnicza), zwana w skrócie AUX (czasem nazywa się ją biblioteką „narzędziową"), glaux.lib. Deklaracje dla tej biblioteki są zawarte w pliku nagłówkowym glaux.h. Funkcje zawarte w bibliotece nie są w rzeczywistości częścią spe-
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX_______________________53
cyfikacji OpenGL, lecz raczej zestawem narzędzi zapewniających niezależną od platformy osnowę dla wywoływania funkcji OpenGL. Jeśli producent twojego kompilatora nie dostarczył tych plików, możesz je otrzymać jako część Win32 SDK Microsoftu. Nazwy wszystkich funkcji z tej biblioteki rozpoczynają się od przedrostka aux.
+ Funkcje rzeczywiście zdefiniowane w specyfikacji OpenGL, zatwierdzonej przez OpenGL Architecture Review Board, są zawarte w bibliotece opengl32.dll oraz jej pliku nagłówkowym, gl.h. Nazwy funkcji w tej bibliotece są poprzedzone przedrostkiem g/.
* Na koniec, istnieje także biblioteka narzędziowa OpenGL, glu32.dll, wraz z plikiem nagłówkowym glu.h. Ta biblioteka zawiera pomocnicze funkcje ułatwiające wykonywanie powszechnych operacji, takich jak rysowanie kuł, dysków czy cylindrów. Ta biblioteka jest napisana przy użyciu funkcji OpenGL, przez co jest gwarantowane, że będzie działała na wszystkich platformach obsługujących specyfikację OpenGL. Nazwy funkcji w tej bibliotece są poprzedzone przedrostkiem glu.
Gdy jako podstawy dla aplikacji używasz biblioteki AUX (a właśnie na tym skupimy się w tym rozdziale), masz do dyspozycji także wszystkie funkcje bibliotek opengI32.dll i glu32.dll. W ten sposób będziesz mógł poznać podstawy OpenGL, a także kilka poleceń z biblioteki gl.
Uwaga na temat bibliotek
Z pewnością zauważyłeś, że biblioteka AUX jest biblioteką statycznie łączoną z aplikacją. Pozostałe biblioteki OpenGL są jednak zaimple-mentowane jako biblioteki DLL. Biblioteki importowe potrzebne dla tych DLL-i to openg!32.lib oraz glu32.lib. Zwykle są dostarczane wraz z kompilatorem; można także otrzymać je wraz z Win32 SDK Microsoftu. Jeśli używasz Borland C++, powinieneś stworzyć własne biblioteki eksportowe, przy pomocy programu implib.exe Borlanda.
Typy danych OpenGL
Aby ułatwić przenoszenie kodu OpenGL pomiędzy różnymi platformami, OpenGL definiuje własne typy danych. Te typy odnoszą się do normalnych typów danych C, których także możesz używać, jeśli zajdzie taka potrzeba. Jednak różne kompilatory i środowiska rządzą się odmiennymi regułami co do rozmiaru i ułożenia zmiennych C w pamięci. Używając typów danych zdefiniowanych w OpenGL, możesz uniezależnić się od tego rodzaju różnic.
Tabela 3.2 zawiera listę typów danych OpenGL, odpowiadających im typów danych C w 32-bitowym środowisku Windows (Win32) oraz odpowiednie przyrostki dla nazw zmiennych. W tej książce każda zmienna posiada odpowiedni przyrostek; przekonasz się, że takie przyrostki posiada także wiele nazw funkcji OpenGL.
54
Część l « Wprowadzenie do OpenGL
Tabela 3.2.
Typy zmiennych OpenGL oraz odpowiadające im typy zmiennych w C
|
|||
Typ danych w OpenGL
|
Wewnętrzna reprezentacja
|
Zdefiniowane jako typ C
|
Przyrostek nazwy zmiennej w C
|
GLbyte
|
8-bitowa liczba całkowita
|
signed char
|
b
|
GLshort
|
16-bitowa liczba całkowita
|
short
|
s
|
GLint, GLsizei
|
32-bitowa liczba całkowita
|
long
|
1
|
GLfloat, GLclampf
|
32-bitowa liczba zmiennoprzecinkowa
|
float
|
f
|
GLdouble, GLclampd
|
64-bitowa liczba zmiennoprzecinkowa
|
double
|
ub
|
GLubyte, GLboolean
|
8-bitowa liczba całkowita bez znaku
|
unsigned char
|
us
|
GLushort
|
1 6-bitowa liczba całkowita bez znaku
|
unsigned short
|
ui
|
GLuint, GLenum, GLbitfield
|
32-bitowa liczba całkowita bez znaku
|
unsigned long
|
|
Wszystkie typy danych rozpoczynają się od GL, co oznacza zmienną OpenGL. Wie- !
kszość posiada także przyrostek określający odpowiedni typ danych C (byte, short, int, ;
float itd.). Niektóre posiadają także literę u, co oznacza typ bez znaku, na przykład uby- j
te oznacza unsigned byte. W pewnych przypadkach stosowana jest bardziej opisowa ;
nazwa, na przykład size do podania wartości długości czy głębokości. Na przykład :
GLsizei to zmienna OpenGL oznaczająca parametr rozmiaru, reprezentowany jako li
czba całkowita. Słowo c lamp jest używane przy składaniu kolorów i stanowi skrót od
color amplitudę (amplituda koloru). Ten typ danych występuje łącznie z przyrostkami/;
i d, oznaczającymi typy float i double. Zmienne GLboolean są używane do określania j
warunków typu Prawda i Fałsz (True i False), GLenum dla zmiennych wyliczeniowych, i
zaś GLbitfield dla zmiennych zawierających binarne pola bitowe. f
Wskaźniki i tablice nie są traktowane w żaden specjalny sposób. Tablica zmiennych ty-f
pu GLshort może zostać zadeklarowana po prostu jako: [
GLshort shorts[10];
zaś tablica dziesięciu wskaźników do zmiennych GLdouble jako [
GLdouble *doubles[10]; \
j
Pewne inne rodzaje wskaźników są używane dla powierzchni NURBS i kwadryk. Zaj-; mierny się nimi w dalszych rozdziałach książki.
55
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX
Konwencje nazw funkcji
Wszystkie funkcje OpenGL zostały nazwane zgodnie z określoną konwencją, informującą o bibliotece, z której pochodzi funkcja, oraz o rodzaju i typie jej argumentów. Nazwy wszystkich funkcji posiadają rdzeń, określający odpowiadające funkcji polecenie OpenGL. Na przykład rdzeniem nazwy funkcji glColor3f() jest Color. Przedrostek gl reprezentuje bibliotekę gl (tabela 3.1), zaś przyrostek 3f oznacza, że funkcja wymaga przekazania trzech argumentów zmiennoprzecinkowych (float). Wszystkie funkcje OpenGL mają następujący format:
<Przedrostek biblioteki><Rdzeń polecenia><Opcjonalnie liczba argumentów><Op-cjonalnie typ argumentów>
Elementy nazwy funkcji OpenGL ilustruje rysunek 3.1. Ta prosta funkcja z przyrostkiem 3f wymaga podania trzech argumentów zmiennoprzecinkowych. Inne odmiany wymagają podania trzech liczb całkowitych (glColor3i()), trzech liczb podwójnej precyzji (glColor3d()) itd. Ta konwencja dodawania na końcu nazwy funkcji ilości i typu argumentów bardzo ułatwia zapamiętanie listy argumentów danej funkcji, bez konieczności zaglądania do dokumentacji. Pewne wersje funkcji glColor wymagają podania czterech argumentów; dodatkowy argument oznacza składnik alfa.
Rysunek 3.1.
Elementy nazwy funkcji OpenGL
glColor3f(...)
nazwy Liczba Typ
argumentów argumentów
W podręczniku na końcu rozdziału „rodziny" funkcji są wymienione według przedrostków bibliotek i rdzeni nazw. Tak więc wszystkie wariacje funkcji glColor (glColor3f, glColor4f, glColor3i etc.) są opisane jako pojedyncza pozycja - glColor.
Czystość kodu
Wiele kompilatorów C/C++ zakłada, że literały zmiennoprzecinkowe są typu double, chyba że przy pomocy przyrostka jawnie określi się inny typ. Gdy jako argumentów zmiennoprzecinkowych użyjesz literałów i nie określisz, że te literały są typu float, a nie double, kompilator zgłosi ostrzegawczy komunikat, informując o możliwej utracie precyzji w wyniku konwersji. W miarę wzrostu objętości programu OpenGL, ilość ostrzeżeń zaczyna iść w setki i trudno jest wśród nich spostrzec rzeczywiste błędy składni. Możesz wyłączyć ostrzeżenia używając odpowiedniej opcji kompilatora -jednak nie zalecamy tego rozwiązania.
56 Część l « Wprowadzenie do OpenGL
Dużo lepiej jest od początku tworzyć czysty, przenośny kod. Pozbądź się więc ostrzeżeń o konwersji typów (w tym wypadku o konwersji na typ float) - zamiast wyłączać potencjalnie użyteczne ostrzeżenia.
Z drugiej strony, jeśli nie chcesz się zajmować jawnym oznaczaniem literałów jako liczb typu float, może cię skusić stosowanie tych wersji funkcji, które akceptują argumenty zmiennoprzecinkowe o podwójnej precyzji (typ double). Jednak OpenGL wewnętrznie korzysta z typu float, więc stosowanie funkcji innych niż funkcje z argumentami pojedynczej precyzji powoduje dodanie pewnego narzutu, związanego ze wstępną konwersją argumentów na typ float.
Biblioteka AUX
W pozostałej części rozdziału zajmiemy się biblioteką Auxiliary (AUX), znacznie ułatwiającą opanowanie OpenGL. Ta biblioteka została stworzona w celu umożliwienia nauki pisania programów OpenGL bez konieczności zawracania sobie głowy szczegółami dotyczącymi danego środowiska systemowego, czy to będzie UNIX, czy Windows czy cokolwiek innego. Przy użyciu biblioteki AUX nie pisze się „finalnego" kodu; używa się jej głównie we wstępnej fazie, do przetestowania swoich pomysłów. Brak podstawowych elementów graficznego interfejsu użytkownika uniemożliwia zastosowanie tej biblioteki przy tworzeniu bardziej użytecznych aplikacji.
Zestaw głównych funkcji biblioteki AUX jest dostępny w prawie wszystkich implementacjach OpenGL. Te funkcje obsługują tworzenie okien i manipulowanie nimi, a także wejściem ze strony użytkownika. Pozostałe funkcje rysują pewne kompletne obiekty 3D, w postaci szkieletowej lub jednolitej. Używając biblioteki AUX do stworzenia i obsługi okna oraz OpenGL w celu rysowania w tym oknie, można tworzyć programy tworzące nawet bardzo skomplikowane rysunki. Po przekompilowaniu możesz bez większych problemów przenosić te programy do innych środowisk.
Oprócz głównych funkcji, każde środowisko implementujące bibliotekę AUX implementuje także pewne funkcje pomocnicze, umożliwiające wykonywanie operacji specyficznych dla systemu, takich jak przełączanie buforów czy ładowanie obrazów. Im bardziej twój kod będzie się opierał na tych pomocniczych funkcjach, w tym mniejszym stopniu będzie przenośny. Z drugiej strony, dzięki pełnemu wykorzystaniu tych funkcji możesz tworzyć fantastyczne sceny, które zadziwią rodzinę i przyjaciół - bez konieczności poznawania wszystkich złożonych szczegółów programowania Windows.
Niestety, mało prawdopodobne jest, aby większość funkcji użytecznej aplikacji sprowadzała się wyłącznie do rysowania trójwymiarowych scen, nie możesz więc używać biblioteki AUX do wszystkiego. Jednak mimo wszystko, ta biblioteka jest niezastąpiona jeśli chodzi o naukę i opanowywanie OpenGL, a w przypadku pewnych programów, zanim stworzysz pełną aplikację, możesz użyć biblioteki AUX do dopieszczenia kodu zajmującego się samym rysowaniem.
Rozdział 3. 4 Nauka OpenGL z użyciem biblioteki AUX_______________________57
Niezależność od platformy
OpenGL to wydajne i wymyślne API przeznaczone do tworzenia trójwymiarowej grafiki, z ponad 300 poleceniami, umożliwiającymi określenie wszystkich elementów sceny, od ustawienia koloru i właściwości materiału, aż po przeprowadzanie obrotów i innych złożonych transformacji obiektów. Być może zdziwi cię to, że OpenGL nie posiada pojedynczej funkcji czy polecenia związanego z zarządzaniem oknem czy ekranem. Nie ma także funkcji przeznaczonych do odczytu klawiatury czy położenia myszki. Weź jednak pod uwagę, że jednym z głównych założeń twórców tego systemu była jego niezależność od platformy. Tworzenie i otwieranie okna przebiega zupełnie inaczej na różnych platformach. Nawet gdyby OpenGL zawierało polecenie otwierające okno, użyłbyś właśnie jego, czy raczej specjalizowanej funkcji wbudowanej w system operacyjny?
Kolejnym zagadnieniem związanym z platformą jest obsługa klawiatury i myszy. Jeśliby każde środowisko obsługiwało je w ten sam sposób, musielibyśmy martwić się tylko jednym środowiskiem i niepotrzebne byłoby już „otwarte" API. Ponieważ jednak każdy system operacyjny jest inny, niezależność OpenGL od platformy uzyskuje się kosztem braku pewnych funkcji związanych z urządzeniami wejściowymi i graficznym interfejsem użytkownika.
AUX = wejście/wyjście w prosty sposób
Biblioteka AUX początkowo została stworzona jako zestaw narzędzi przeznaczonych do nauki OpenGL bez konieczności zagłębiania się w szczegóły związane z systemem operacyjnym i interfejsem użytkownika. Aby to osiągnąć, AUX zawiera podstawowe funkcje do tworzenia okna oraz odczytywania zdarzeń wywoływanych przez klawiaturę i mysz. Wewnętrznie, biblioteka AUX wykorzystuje mechanizmy danego systemu operacyjnego, jednak funkcje udostępniane przez nią pozostają takie same na wszystkich platformach.
Biblioteka AUX zawiera tylko kilka funkcji przeznaczonych do obsługi okien, klawiatury i myszy, jednak i tak oszczędza ci znacznego kłopotu związanego z ich obsługą w czystym C/C++ lub przez API Windows. Biblioteka zawiera także funkcje przeznaczone do rysowania kilku stosunkowo prostych trójwymiarowych obiektów, takich jak okrąg, sześcian, torus czy nawet imbryk do herbaty. Przy bardzo małym wysiłku możesz użyć biblioteki AUX do wyświetlenia okna i wykonania w nim kilku operacji OpenGL. Choć biblioteka AUX w rzeczywistości nie stanowi części specyfikacji OpenGL, wygląda na to, że została zaimplementowana dla wszystkich platform, dla których zaimplementowano OpenGL. Windows także nie stanowi wyjątku, zaś kod źródłowy biblioteki AUX jest dostępny za darmo jako część Win32 SDK Microsoftu.
58
Część l * Wprowadzenie do OpenGL
Analiza krótkiego programu OpenGL
Aby lepiej zrozumieć bibliotekę AUX, rzućmy okiem na jeden z najkrótszych w świecie programów OpenGL, stworzony właśnie za pomocą tej biblioteki. Listing 3.1 prezentuje program shortest.c, zaś wynik jego działania został pokazany na rysunku 3.2.
Rysunek 3.2.
Wynik działania programu shortest.c
Listing 3.1. Najkrótszy w świecie program OpenGL
// shortest.c
// Najkrótszy w świecie program OpenGL
łfinclude <windows.h> // Standardowy nagłówek Windows wymagany we
wszystkich programach
tinclude <conio.h> // Funkcje I/O konsoli tinclude <gl\gl.h> // Funkcje OpenGL tinclude <gl\glaux.h> // Funkcje biblioteki AUX
void main(void)
// Funkcje AOX służące do przygotowania okna auxInitDisplayMode (AUX_SINGLE | AUX_RGBA) ; aux!nitPosition(100, 100,250,250) ; aux!nitWindow("Mój pierwszy program OpenGL");
// Funkcje OpenGL wykonujące coś w oknie glClearColor(O.Of, O.Of, l.Of, l.Of); glClear (GL_COLOR_BUFFER_BIT) ;
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX_______________________59_
glFlushO ;
// Zatrzymanie i oczekiwanie na wciśnięcie klawisza cprintf("Wciśnij jakiś klawisz, aby zamknąć okno\n"); getch();
Tryby konsoli
Aplikacja konsoli to program Win32 działający w oknie trybu tekstowego. Taki program bardzo przypomina program DOS-a uruchomiony w Windows NT lub Windows 95, z tym że jest w pełni 32-bitowa aplikacja i ma dostęp do całego API Win32. Programy konsoli nie są ograniczone do trybu tekstowego. W rzeczywistości mogą tworzyć własne okienka (w powyższym programie spróbuj użyć funkcji MessageBox() z wartością IMULL podaną w miejscu uchwytu okna), zaś aplikacje GUI (Graphics User Interface - graficzny interfejs użytkownika) mogą w razie potrzeby tworzyć okna konsoli. Biblioteka AUX umożliwia łatwe pisanie programów konsoli, zawierających funkcję main(), która tworzy pomocnicze okno GUI dla rysunku tworzonego w OpenGL.
Aby zbudować ten program, musisz ustawić opcje kompilatora i linkera tak, aby powstała aplikacja konsoli Win32. Musisz dołączyć bibliotekę AUX, glaux.lib, oraz bibliotekę importową OpenGL, openg!32.1ib. Szczegółowe instrukcje na temat kompilacji i łączenia programów znajdziesz w dokumentacji kompilatora.
Program shortest.c nie czyni zbyt wiele. Gdy uruchomisz go w linii poleceń, tworzy standardowe okno GUI z tytułem „Mój pierwszy program OpenGL" oraz czystym niebieskim tłem. Następnie w oknie konsoli wyświetla komunikat „Wciśnij jakiś klawisz, aby zamknąć okno". Okno GUI nie reaguje na klawiaturę ani mysz; to okno konsoli oczekuje na wciśnięcie jakiegoś klawisza w celu zakończenia działania (musisz w tym celu przełączyć się z powrotem do okna konsoli). Nie możesz także przesunąć okna OpenGL ani zmienić jego rozmiaru, zaś samo okno nawet się nie odrysowuje. Jeśli przesłonisz okno, a następnie je odsłonisz, przekonasz się, że obszar roboczy stał się czarny.
Ten prosty program zawiera trzy funkcje biblioteki AUX (poprzedzone przedrostkiem aux) oraz trzy „prawdziwe" funkcje OpenGL (poprzedzone przedrostkiem gl). Przejrzyjmy ten program linia po linii, a następnie przejdźmy do omówienia innych funkcji, poprawiających działanie naszego programu.
Część nagłówkowa
Są do niej włączone następujące pliki:
tfinclude <windows.h> #include <conio.h> łinclude <gl\gl.h> tinclude <gl\glaux.h>
60_________________________________Część l « Wprowadzenie do OpenGL
Te pliki nagłówkowe zawierają definicje wszystkich prototypów funkcji używanych przez program. Pliku windows.h wymagają wszystkie aplikacje GUI w Windows; choć nasz program jest programem konsoli, biblioteka AUX tworzy okno GUI, w którym odbywa się rysowanie. Plik conio.h zawiera deklaracje funkcji wejścia/wyjścia konsoli. Musieliśmy go dołączyć, gdyż korzystamy z funkcji cprintf() w celu wyświetlenia komunikatu oraz z funkcji getch() w celu oczekiwania na wciśnięcie klawisza kończącego działanie programu. Plik gl.h definiuje funkcje OpenGL poprzedzone przedrostkiem gl, zaś plik glaux.h zawiera wszystkie funkcje konieczne dla biblioteki AUX.
Ciało programu
Następnie mamy do czynienia z główną częścią programu:
void main(void) {
Programy konsoli napisane w C i C++ zawsze rozpoczynają działanie od funkcji main(). Jeśli jesteś doświadczonym programistą Windows, być może zastanawiasz się, gdzie w tym przykładzie występuje funkcja WinMain(). Nie ma jej, a to dlatego, że na początku uruchamiamy aplikację konsoli, nie musimy więc tworzyć okna z pętlą komunikatów. W przypadku Win32 istnieje możliwość tworzenia okien graficznych z poziomu aplikacji konsoli, tak jak możliwe jest tworzenie okien konsoli z poziomu aplikacji GUI. Tymi szczegółami zajmuje się sama biblioteka AUX (jak pamiętasz, właśnie taki był cel jej powstania).
Tryb wyświetlania: pojedynczy bufor
Następna linia kodu
auKlnitDisplayMode(AUX_SINGLE | AUX_RGBA);
informuje bibliotekę AUX o trybie wyświetlania, jaki ma zostać użyty podczas tworzenia okna. Zastosowane przez nas znaczniki nakazują użycie okna z pojedynczym buforem (AUX_SINGLE) oraz trybu kolorów RGBA (AUX_RGBA). Okno z pojedynczym buforem oznacza, że wszystkie polecenia rysowania są wykonywane bezpośrednio w wyświetlanym oknie. Alternatywę stanowi okno z podwójnym buforem, w którym polecenia rysowania są realizowane w niewidocznym buforze, który następnie jest szybko przerzucany do okna na ekranie. Ta technika jest stosowana najczęściej przy tworzeniu animacji, co zademonstrujemy w dalszej części rozdziału. Tryb koloru RGBA oznacza, że wartości kolorów są podawane jako oddzielne wartości barw składowych: czerwonej, zielonej i niebieskiej (ang. red, green i blue) - więcej informacji na ten temat znajdziesz w rozdziale 8.
Pozycjonowanie okna
Po ustawieniu trybu wyświetlania, musimy poinformować bibliotekę AUX o położeniu i rozmiarach okna. Służy do tego następna linia kodu:
61
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX
aux!nitPosition(100,100,250,250);
Parametry reprezentują położenie lewego górnego rogu okna oraz jego szerokość i wysokość. Powyższa linia nakazuje umieszczenie lewego górnego rogu okna w miejscu o współrzędnych (100, 100) oraz nadanie oknu szerokości 250 pikseli i wysokości także 250 pikseli. Przy standardowej rozdzielczości VGA (640 x 480), okno zajmie dość dużą część ekranu. Przy rozdzielczościach Super VGA (800 x 600 i wyższych) okno zajmie mniej miejsca, gdyż ilość pikseli pozostanie taka sama (250 x 250).
Oto prototyp tej funkcji:
auxInitPosition(GLint x, GLint y, GLsizei width, GLsizei height);
Typy danych GLint i GLsizei są zdefiniowane jako liczby całkowite (zostały one omówione w sekcji dotyczącej typów danych we wcześniejszej części rozdziału). Parametr x określa ilość pikseli liczonych od lewej krawędzi ekranu, zaś parametr y - ilość pikseli liczonych od górnej krawędzi. Właśnie tak Windows domyślnie konwertuje współrzędne ekranu na współrzędne fizyczne. Domyślna metoda liczenia współrzędnej x w OpenGL jest taka sama; jednak w przypadku współrzędnej y jest ona liczona od dolu do góry - przeciwnie do kierunku wzrostu tej współrzędnej w Windows. Spójrz na rysunki 3.3 oraz 3.4.
Rysunek 3.3.
Domyślne odwzorowanie •współrzędnych ekranu \v Windows
Współrzędne ektranu w Windows
Kierunek dodatni
• (100,100)
Kierunek dodatni
Rysunek 3.4.
Domyślne odwzorowanie współrzędnych okna w OpenGL
/
|
}dwzorowanie współrzędnych w oknie OpenGL Kierunek dodatni i
|
|
(100,100) Kierunek dodatni
|
|
|
62 __ __ ______Część l » Wprowadzenie do OpenGL
Uwaga na temat przenośności kodu
O ile Windows odwzorowuje współrzędne ekranu tak, jak pokazano na rysunku 3.3, o tyle system X Windows odwzorowuje współrzędne tak samo jak OpenGL na rysunku 3.4. Jeśli przenosisz program korzystający z biblioteki AUX z innego środowiska, być może będziesz musiał zmodyfikować wywołanie funkcji auxlnitPosition().
Tworzenie okna OpenGL
Ostatnie wywołanie funkcji z biblioteki AUX wiąże się z samym utworzeniem okna na ekranie. Kod
auxlnitwindow("Mój pierwszy program OpenGL");
tworzy okno oraz nadaje mu tytuł „Mój pierwszy program OpenGL". Oczywiście, pojedynczym argumentem tej funkcji jest właśnie tytuł okna. Gdybyśmy zatrzymali się w tym miejscu, program stworzyłby puste okno (domyślnie o czarnym tle) z podanym tytułem, a następnie zakończyłby działanie natychmiast zamykając okno OpenGL. Dodanie ostatniej instrukcji getch() zabezpieczy nas przed zamknięciem okna, jednak w dalszym ciągu w samym oknie nie dzieje się nic interesującego.
Czyszczenie okna (wypełnianie kolorem)
Trzy omówione dotąd linie kodu są związane z biblioteką AUX i wystarczają do zainicjowania i stworzenia okna, w którym będzie rysować OpenGL. Od tego momentu, wszystkie wywołania poleceń i funkcji OpenGL będą operować właśnie w tym oknie.
Następna linia kodu
glClearColor(O.Of, O.Of, l.Of, l.Of);
to twoje pierwsze wywołanie prawdziwej funkcji OpenGL. Ta funkcja ustala kolor używany podczas czyszczenia okna. Jej prototyp to:
void glClearColor (GLclampf red, GLclampf green, GLclampf blue, OGLclampf alpha) ;
GLclampf w większości implementacji OpenGL jest zdefiniowane jako liczba zmienno-przecinkowa typu float. W OpenGL pojedynczy kolor jest reprezentowany jako mieszanina czerwonej, zielonej i niebieskiej barwy składowej. Zakres każdego ze składników należy do przedziału od 0,0 do 1,0. Występuje tu podobieństwo do sposobu określania koloru w Windows, przy pomocy makra RGB tworzącego wartość COLORREF. (Szczegóły znajdziesz w każdej książce poświęconej programowaniu w Windows). Różnica polega na tym, że w Windows każdy komponent koloru należy do przedziału od O do 255, co daje w sumie 256 x 256 x 256 dostępnych kolorów - czyli ponad 16 milionów kolorów. W OpenGL wartością każdej barwy składowej może być dowolna poprawna wartość z przedziału od 0,0 do 1,0, co teoretycznie daje nieskończoną liczbę potencjalnych kolorów. W praktyce, OpenGL wewnętrznie reprezentuje kolory jako
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX
wartości 32-bitowe, czyli maksymalna ilość reprezentowalnych kolorów wynosi 4294967296 (w przypadku niektórych kart graficznych odpowiada to trybowi TrueCo-lor). W efekcie każda barwa składowa może zmieniać się z 0,0 na 1,0, z krokiem wynoszącym w przybliżeniu 0,00006.
Naturalnie zarówno Windows, jak i OpenGL konwertują otrzymaną wartość koloru na najbliższą wartość możliwą do zaprezentowania na zainstalowanym sprzęcie i ewentualnie w danej palecie. Bliżej zajmiemy się tym w rozdziale 8.
Tabela 3.3 zawiera listę niektórych częściej wykorzystywanych kolorów oraz odpowiadające im wartości poszczególnych barw. Te wartości mogą być użyte we wszystkich funkcjach OpenGL związanych z kolorem.
Tabela 3.3.
Stosunki barw składowych częściej używanych kolorów
Kolor wynikowy
|
Barwa czerwona
|
Barwa zielona
|
Barwa niebieska
|
Czarny
|
0,0
|
0,0
|
0,0
|
Czerwony
|
1,0
|
0,0
|
0,0
|
Zielony
|
0,0
|
1,0
|
0,0
|
Żółty
|
1,0
|
1,0
|
0,0
|
Niebieski
|
0,0
|
0,0
|
1,0
|
Fioletowy
|
1,0
|
0,0
|
1,0
|
Błękitny
|
0,0
|
1,0
|
1,0
|
Ciemny szary
|
0,25
|
0,25
|
0,25
|
Jasny szary
|
0,75
|
0,75
|
0,75
|
Brązowy
|
0,60
|
0,40
|
0,12
|
Pomarańczowy
|
0,98
|
0,625
|
0,12
|
Różowy
|
0,98
|
0,04
|
0,70
|
Purpura
|
0,60
|
0,40
|
0,70
|
Biały
|
1,0
|
1,0
|
1,0
|
Ostatnim argumentem funkcji flClearColor() jest składnik alfa. Składnik alfa jest używany przy przezroczystości oraz przy efektach specjalnych takich jak przejrzystość. Przejrzystość odnosi się do właściwości obiektu umożliwiającej światłu przechodzenie przez obiekt. Przypuśćmy, że prezentujesz kawałek czerwonego witrażu, lecz oświetlonego niebieskim światłem. Niebieskie światło wpłynie na wygląd czerwieni w szkle (czerwony + niebieski = fioletowy). Możesz użyć składnika alfa w celu uczynienia koloru niebieskiego półprzejrzystym, tak aby wyglądał na przykład jak warstwa wody, ukazująca znajdujące się pod nią obiekty. Na temat tego rodzaju efektów można powiedzieć o wiele więcej niż tylko o wartości alfa; w rozdziale 16 napiszemy przykładowy program demonstrujący te zagadnienia, a na razie pozostaw tę wartość jako 1.
64 Część l •» Wprowadzenie do OpenGL
Samoczyszczenie okna
Gdy poinformowaliśmy OpenGL o kolorze, jakiego ma użyć do czyszczenia, możemy użyć instrukcji wykonującej samoczyszczenie. Służy do tego linia
glClear(GL_COLOR_BUFFER_BIT);
Funkcja glClear() czyści dany bufor lub kombinację buforów. Bufor to miejsce, gdzie przechowywana jest informacja o obrazie. Składniki czerwony, zielony i niebieski posiadają wewnętrznie osobne bufory, jednak w całości traktujemy je jako pojedynczy bufor kolorów.
Bufory to bardzo użyteczny element OpenGL; szczegółowo omówimy je w rozdziale 15. Przed lekturą następnych kilku rozdziałów jedyne, co musimy zapamiętać o buforze kolorów, to informacja że jest w nim wewnętrznie przechowywany rysowany obraz, zaś wyczyszczenie bufora funkcją glClear() powoduje usunięcie rysunku z okna.
Opróżnienie zawartości kolejki
Oto wywołanie ostatniej z trzech funkcji OpenGL:
glFlushO ;
Ta linia powoduje wykonanie wszystkich niewykonanych dotąd poleceń OpenGL - w tym momencie mamy dwa takie polecenia: glClearColor() oraz glClear().
Wewnętrznie, OpenGL używa tzw. kanału renderowania, kolejno przetwarzającego polecenia. Funkcje i polecenia OpenGL często są umieszczane w kolejce i oczekują w niej do momentu, aż serwer OpenGL przetworzy kilka „żądań" naraz. To znacznie poprawia wydajność, szczególnie w przypadku konstruowania złożonych obiektów. Następuje przyspieszenie rysowania, gdyż dostęp do stosunkowo wolnego sprzętu graficznego w celu wykonania danego zestawu instrukcji rysunkowych odbywa się rzadziej. (Gdy opracowywano Win32, zastosowano tę samą koncepcję dla Windows GDI, w celu przyspieszenia operacji graficznych w Windows NT). W naszym krótkim programie funkcja glFlush() po prostu informuje OpenGL, aby przetworzył dostarczone dotąd polecenia graficzne, nie czekając na następne instrukcje rysunkowe.
Ostatni fragment kodu w tym przykładzie
// Zatrzymanie i oczekiwanie na wciśnięcie klawisza cprintf("Wcisnij jakiś klawisz aby zamknąć okno\n"); getch() ; >
wyświetla komunikat w oknie konsoli i zatrzymuje działanie programu do momentu naciśnięcia jakiegoś klawisza; w tym momencie program kończy działanie i następuje zamknięcie okna.
Program w obecnej wersji nie jest zbyt interesujący, jednak demonstruje podstawowy sposób utworzenia okna oraz wyczyszczenia go przy użyciu określonego koloru. Spró-
Rozdział 3. ł Nauka OpenGL z użyciem biblioteki AUX
bujmy go teraz rozbudować dodając kilka dodatkowych funkcji bibliotek AUX i OpenGL.
Rysowanie kształtów za pomocą OpenGL
Program shortest.c tworzył puste okno o błękitnym tle. Spróbujmy teraz coś w nim narysować. Ponadto chcemy, aby okno mogło zmieniać rozmiary i przemieszczać się, tak jak prawdziwe okno Windows. Przy okazji zrezygnujemy także z używania funkcji getch() w celu wychwycenia momentu zakończenia działania programu. Zmodyfikowany program przedstawiono na listingu 3.2.
listing 3.2. Bardziej przyjazny program OpenGL________________________________
// friendly.c
// Bardziej przyjazny program OpenGL
linclude <windows.h> // Standardowy nagłówek dla Windows
tfinclude <gl\gl.h> // Biblioteka OpenGL
tinclude <gl\glaux.h> // Biblioteka AUX
// Wywoływane przez bibliotekę AUX w celu narysowania sceny void CALLBACK RenderScene(void)
{
// Kolor tła: Niebieski
glClearColor(O.Of, O.Of, l.Of, l.Of);
// Wyczyszczenie okna glClear(GL_COLOR_BUFFER_BIT);
// Kolor rysowania czerwony
// RGB
glColor3f(l.Of, O.Of, O.Of);
// Narysowanie prostokąta wypełnionego bieżącym kolorem glRectf(lOO.Of, 150.Of, 150.Of, 100.Of);
glFlushO ;
void main(void) {
// Przygotowanie trybu rysowania i okna za pomocą biblioteki AUX auxInitDisplayMode(AUX_SINGLE | AUX_RGBA); aux!nitPosition(100,100,250,250); auxlnitWindow("Mój drugi program OpenGL");
// Przygotowanie funkcji wywoływanej w momencie, gdy konieczne // jest odświeżenie okna auxMainLoop(RenderScene);
66_________________________________Część l * Wprowadzenie do OpenGL
Pierwsza zmiana dotyczy plików nagłówkowych. Nie ma tu pliku conio.h, gdyż nie używamy już funkcji getch() ani cprintf().
Funkcja renderująca
W programie występuje nowa funkcja, RenderScene().
// Wywoływane przez bibliotekę AUX w celu narysowania sceny void CALLBACK RenderScene(void)
W tej funkcji umieściliśmy cały kod odpowiedzialny za rysowanie w oknie. Proces rysowania w OpenGL często jest nazywany renderowaniem, użyliśmy więc adekwatnej do tego nazwy funkcji. W następnych przykładach większość kodu rysunkowego znajdzie się właśnie w tej funkcji.
Zwróć uwagę na instrukcję CALLBACK w deklaracji funkcji. Jest ona wymagana, gdyż mamy zamiar poinformować bibliotekę AUX, aby wywoływała tę funkcję za każdym razem, gdy okno będzie wymagać odświeżenia. Funkcje zwrotne (ang. callback) to zwykłe funkcji, które bez naszego udziału wywołuje biblioteka AUX. O tym, jak to działa, powiemy sobie nieco później.
Rysowanie prostokąta
Poprzednim razem wszystkie działania programu sprowadzały się jedynie do wyczyszczenia ekranu. Teraz do kodu rysunkowego dodaliśmy dwie poniższe linie:
// Kolor rysowania czerwony
// RGB
glColor3f(l.Of, O.Of, O.Of);
// Narysowanie prostokąta wypełnionego bieżącym kolorem glRectf(lOO.Of, 150.Of, 150.Of, 100.Of);
Pierwsza linia ustawia kolor używany w następnych operacjach rysowania (linii i wypełnień); służy do tego funkcja glColor3f(). Druga linia, wywołująca funkcję glRectf(), służy do narysowania wypełnionego prostokąta.
Funkcja glColor3f() wybiera kolor w ten sam sposób, co glClearColor(), z tym że nie trzeba podawać wartości składnika alfa:
void glColor3f(GLfloat red, GLfloat green, GLfloat blue);
Funkcja glRectf() wymaga parametrów typu float, o czym informuje końcowa litera f nazwie funkcji. W tym wypadku w nazwie nie ujmuje się ilości argumentów, gdyż wszystkie odmiany tej funkcji wymagają podania czterech argumentów. Cztery argumenty funkcji glRectf():
void glRectf(GLfloat xl, GLfloat yl, GLfloat x2, GLfloat y2);
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX_______________________67
reprezentują dwie pary współrzędnych: (xl, yl) oraz (x2, y2). Pierwsza para reprezentuje lewy górny róg prostokąta, zaś druga para - róg prawy dolny. Jeśli chcesz sobie przypomnieć odwzorowanie współrzędnych w OpenGL, spójrz na rysunek 3.4.
Inicjowanie
Główna funkcja programu friendly.c rozpoczyna się tak samo jak w naszym pierwszym przykładzie:
void main(void) {
// Przygotowanie trybu rysowania i okna za pomocy biblioteki AUX auxInitDisplayMode(AUX_SINGLE | AUX_RGBA); auxInitPosition(100,100,250,250); aux!nitWindow("Mój drugi program OpenGL");
// Przygotowanie funkcji wywoływanej w razie konieczności // odświeżenia okna auxMainLoop(RenderScene); }
Jak wcześniej, trzy wywołania auxlnito* przygotowują i tworzą okno, w którym będzie się odbywać rysowanie. W ostatniej linii funkcji auxMainLoop() znajduje się nazwa funkcji, która będzie używana do rysowania, funkcji RenderScene(). Funkcja Main-Loop() biblioteki AUX po prostu podtrzymuje działanie programu do momentu, aż użytkownik zamknie okno. Pojedynczym argumentem funkcji jest wskaźnik do innej funkcji, która ma być wywoływana za każdym razem, gdy okno wymaga aktualizacji. Ta funkcja zwrotna zostanie wywołana podczas pierwszego wyświetlenia okna, gdy zostanie ono przesunięte lub przeskalowane, a także gdy zostanie odsłonięte przez inne okno.
// Wywoływane przez bibliotekę AUX w celu narysowania sceny void CALLBACK RenderScene(void)
{
// Kolor tła: Niebieski
glClearColor(O.Of, O.Of, l.Of, l.Of);
// Wyczyszczenie okna glClear(GL_COLOR_BUFFER_BIT);
// Kolor rysowania czerwony
// RGB
glColor3f(l.Of, O.Of, O.Of);
// Narysowanie prostokąta wypełnionego bieżącym kolorem glRectf(lOO.Of, 150.Of, 150.Of, 100.Of);
glFlushf) ; }
W tym momencie program wyświetli czerwony kwadrat na środku niebieskiego okna, gdyż tak właśnie na stałe ustawiliśmy położenie prostokąta. Gdy powiększysz okno, kwadrat pozostanie na swoim miejscu w stosunku do lewego dolnego rogu okna. Gdy zmniejszysz okno, kwadrat może nie zmieścić się w obszarze roboczym. Dzieje się tak, ponieważ, na skutek zmiany rozmiaru okna zmieniana się rozmiary obszaru rysowania, lecz kod rysunkowy w dalszym ciągu umieszcza prostokąt w tym samym miejscu
68_________________________________Część l * Wprowadzenie do OpenGL
o współrzędnych (100, 150, 150, 100). W oryginalnym oknie było to dokładnie na środku; w oknie większym te współrzędne wskazują lewy dolny róg okna (rysunek 3.5).
Rysunek 3.5.
250
Efekty zmiany rozmiaru okna
Skalowanie do rozmiarów okna
W praktycznie wszystkich środowiskach okienkowych użytkownik może w dowolnej chwili zmienić rozmiary okna. Gdy tak się dzieje, okno zwykle odpowiada odrysowaniem swej zawartości, biorąc pod uwagę nowy rozmiar. Czasem zechcesz po prostu obciąć obraz do nowych wymiarów lub wyświetlić cały obraz w większym oknie. Do naszych celów zwykle jednak zechcemy tak przeskalować rysunek, aby w pełni mieścił się w oknie, bez względu na rozmiar rysunku lub okna. Tak więc bardzo małe okno będzie zawierało pełny, lecz bardzo mały rysunek, zaś większe okno będzie zawierało podobny, lecz większy rysunek. Widać to w większości programów rysunkowych, gdy zmniejszasz okno, lecz nie powiększasz samego rysunku. Zmniejszenie okna zwykle nie zmienia rozmiaru rysunku, jednak powiększenie rysunku zwykle zwiększa rozmiar okna.
Ustawianie widoku i bryły obcinania
W rozdziale 2 omawialiśmy wpływ widoków i brył obcinania na zakres współrzędnych oraz skalowanie dwu- i trójwymiarowych rysunków w dwuwymiarowych oknach na ekranie komputera. Teraz zajmiemy się ustawieniami widoku i współrzędnymi bryły obcinania w OpenGL. Gdy tworzymy okno wywołaniem funkcji
auKlnitPosition(100,100,250,250);
biblioteka AUX domyślnie tworzy widok dokładnie dopasowany do rozmiarów okna (0,0, 250, 250). Bryła obcinania jest domyślnie ustawiana tak, aby wypełniała pierwszy kwadrant kartezjańskiej przestrzeni, z osiami x i y wyznaczającymi szerokość i wysokość okna. Oś z biegnie prostopadle do obserwatora, przez co obiekty rysowane na płaszczyźnie xy sprawiają wrażenie dwuwymiarowych. Graficznie ilustruje to rysunek 3.6.
69
Rozdział 3. + Nauka OpenGL z użyciem biblioteki AUX
Rysunek 3.6.
Widok i bryła obcinania dla program u friendly. c
(250,250,-!)
(250,250,1)
(100,150,0)
(150,100,0
Choć nasz rysunek składa się z płaskiego, dwuwymiarowego prostokąta, w rzeczywistości rysujemy w trójwymiarowej przestrzeni kartezjańskiej. Funkcja glRectf() rysuje prostokąt na płaszczyźnie xy o współrzędnej z = 0. Ponieważ patrzymy w dół osi z, widzimy płaski prostokąt.
Gdy tylko zmienia się rozmiar okna, widok i bryła obcinania muszą zostać przedefmio-wane odpowiednio do nowych rozmiarów. W przeciwnym razie zobaczymy efekt przedstawiony na rysunku 3.5, kiedy to odwzorowanie układu współrzędnych sceny na układ współrzędnych okna pozostaje takie samo, bez względu na rozmiar okna.
Ponieważ zmiany wymiarów okna są wykrywane i obsługiwane różnie w różnych środowiskach, biblioteka AUX dostarcza funkcji auxReshapeFunc(), która rejestruje funkcję zwrotną, wywoływaną przez bibliotekę AUX przy każdej zmianie wymiarów okna. Funkcja przekazywana w wywołaniu auxReshapeFunc() ma następujący prototyp:
void CALLBACK ChangeSize(GLsizei w, GLsizei h);
Nazwę ChangeSize (zmień rozmiar) wybraliśmy jako nazwę opisową i będziemy się jej trzymać także w następnych przykładach.
Funkcja ChangeSize() otrzymuje nową szerokość i wysokość okna za każdym razem, gdy zmienia się któryś z wymiarów okna. Możemy użyć tej informacji do zmodyfikowania odwzorowania naszego układu współrzędnych na układ współrzędnych ekranu, za pomocą dwóch funkcji OpenGL: glViewport() oraz glOrtho(). Listing 3.3 przedstawia nasz poprzedni przykład modyfikowany tak, aby brał pod uwagę zmianę rozmiaru okna. Na listingu została przedstawiona jedynie zmodyfikowana funkcja main() oraz nowa funkcja ChangeSize().
70_________________________________Część l •» Wprowadzenie do OpenGL
Listing 3.3. Skalowanie w OpenGL_______________________________________
// Scalę.c
// Skalowanie okna OpenGL
// Wywoływana przez bibliotekę AUX w momencie zmiany rozmiaru okna
void CALLBACK ChangeSize(GLsizei w, GLsizei h)
{
// Zabezpieczenie przed dzieleniem przez zero
if(h == 0) h = 1;
// Ustawienie widoku na wymiary okna glViewport(O, O, w, h);
// Wyzerowanie układu współrzędnych glLoadldentity();
// Ustalenie bryły obcinania (lewa, prawa, dolna, górna, bliższa, // dalsza) if (w <= h)
glOrtho (O.Of, 250.Of, O.Of, 250.0f*h/w, 1.0, -1.0); else
glOrtho (O.Of, 250.0f*w/h, O.Of, 250.Of, 1.0, -1.0);
}
void main(void) {
// Przygotowanie trybu rysowania i okna za pomocą biblioteki AUX
auxInitDisplayMode(AUX_SINGLE | AUX_RGBA);
aux!nitPosition(100,100,250,250);
auxlnitwindow("Skalowanie okna");
// Przygotowanie funkcji wywoływanej w momencie zmiany rozmiaru
// okna
auxReshapeFunc(ChangeSize);
// Przygotowanie funkcji wywoływanej w momencie konieczności // odświeżenia okna auxMainLoop(RenderScene);
Teraz, gdy zmienisz wymiary okna, prostokąt także zmieni swój rozmiar. Znacznie większe okno będzie zawierało dużo większy prostokąt, zaś o wiele mniejsze okno będzie zawierało znacznie mniejszy prostokąt. Jeśli wydłużysz okno w poziomie, prostokąt zostanie wyśrodkowany w pionie, daleko na lewo od środka. Jeśli wydłużysz okno w pionie, prostokąt zostanie wyśrodkowany w poziomie, bliżej dołu okna. Zwróć uwagę że prostokąt zawsze pozostaje kwadratem. Aby zobaczyć jak zmienia się rozmiar prostokąta wraz z rozmiarami okna, spójrz na rysunki 3.7a i 3.7b.
71
Rozdział 3. « Nauka OpenGL z użyciem biblioteki AUX
Rysunek 3.7a.
Obraz
przeskalowany do wymiarów okna
Rysunek 3.7b.
Prostokąt zmienia
rozmiar wraz
z wymiarami okna
Definiowanie widoku
Aby zrozumieć, jak definiuje się widok, przyjrzyjmy się bliżej funkcji ChangeSize(). Na początku wywołuje ona funkcję glViewport() z nową szerokością i wysokością okna. Funkcja glViewport() jest zdefiniowana jako
void glviewport(GLint x, GLint y, GLint width, GLint height);
Parametry x i y określają prawy dolny róg widoku wewnątrz okna, zaś parametry width i height określają szerokości i wysokość widoku w pikselach. Zwykle x i y przyjmują wartość O, jednak można użyć widoków do przedstawiania kilku rysunków w różnych obszarach okna. Widok definiuje obszar wewnątrz okna, określając współrzędne ekranowe, w zakresie których będzie mogło rysować OpenGL (rysunek 3.8). Następnie bieżąca bryła obcinania jest odwzorowywana na nowy widok. Jeśli określisz widok mniejszy niż współrzędne okna, rysunek także zostanie odpowiednio zmniejszony, tak jak przedstawiono na rysunku 3.8.
72
Część l * Wprowadzenie do OpenGL
Rysunek 3.8.
Odwzorowanie widoku na okno
GrViewport(0,0,125,125) 125——X
glVjewport(0,0,250,250)
11
N-
250
-H
Widok i okno mają te same rozmiary
250
-H
Widok stanowi połowę rozmiaru okna
Definiowanie bryły obcinania
Ostatnią czynnością w naszej funkcji ChangeSize() jest przedefiniowanie bryły obcinania, tak aby stosunek współrzędnych pozostał bez zmian. Stosunek współrzędnych (ang. aspect ratió) to stosunek ilości pikseli odpowiadających jednostce osi pionowej do ilości pikseli odpowiadających jednostce osi poziomej. Stosunek współrzędnych o wartości 1,0 oznacza, że jednostkom obu osi odpowiadają równe ilości pikseli (stosunek prostokątny). Stosunek o wartości 0,5 oznacza że na każde dwa piksele w osi poziomej przypada jeden piksel w osi pionowej.
Jeśli widokowi zostanie przypisany stosunek współrzędnych różny od 1,0 i widok zostanie odwzorowany w prostokątną bryłę obcinania, spowoduje to, że obrazy będą zniekształcone. Na przykład, widok dostosowany do wymiarów okna, lecz odwzorowany w prostokątną bryłę obcinania spowoduje, że obrazy będą wąskie i wysokie w wąskich i wysokich oknach, a niskie i szerokie w niskich i szerokich oknach. W naszym przypadku kwadrat tylko wtedy byłby kwadratowy, gdyby samo okno było kwadratowe.
W naszym przykładzie dotyczącym bryły obcinania stosujemy rzutowanie równoległe (patrz rozdział 2). Poleceniem OpenGL służącym do utworzenia takiego rzutowania jest glOrtho():
void glOrtho(GLdouble lewa, GLdouble prawa, GLdouble dolna, GLdouble ^gorna, GLdouble bliższa, GLdouble dalsza);
W trójwymiarowej przestrzeni kartezjańskiej, wartości lewa i prawa określają minimalną i maksymalną współrzędną wyświetlaną wzdłuż osi x; wartości dolna i górna odnoszą się do osi y. Wartości bliższa i dalsza określają zakres wyświetlanych współrzędnych osi z, na której wartości maleją w głąb ekranu (rysunek 3.9).
Tuż przed wywołaniem funkcji glOrtho() z pewnością zauważyłeś pojedyncze wywołanie funkcji glLoad!dentity(). Jest ono konieczne, gdyż OpenGL nie ustanawia nowej bryły obcinania, lecz modyfikuje bryłę już istniejącą. Mnoży macierz opisującą bieżącą bryłę obcinania przez macierz opisującą bryłę obcinania określoną przez argumenty wywołania funkcji glOrtho(). Jednak dyskusję na temat operacji macierzowych i przekształcania współrzędnych odłożymy aż do rozdziału 7. W tym momencie powinieneś
73
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX
jedynie wiedzieć, że funkcja glLoad!dentity() po prostu „zeruje" układ współrzędnych przed każdym zastosowaniem funkcji glOrtho(); następne wywołanie funkcji głOrtho() bez wyzerowania układu współrzędnych zmodyfikowałoby bryłę obcinania nawet do tego stopnia, że nasz prostokąt mógłby nawet nie ukazać się w oknie.
Rysunek 3.9.
Przestrzeń kartezjańska
Aby kwadrat był kwadratem
To, że nasz „kwadrat" pozostaje kwadratowy, jest zasługą poniższego kodu:
if (w <= h)
glOrtho (O.Of, 250.Of, O.Of, 250.0f*h/w, 1.0, -1.0);
else
glOrtho (O.Of, 250.0f*w/h, O.Of, 250.Of, 1.0, -1.0);
Rysunek 3.10.
Region obcinania dla trzech różnych kształtów okna
250
250
L
|
t -' S cni .: J
|
1 1 1 l i i 1 i _ _
|
"> 1 1
|
ł ,T
|
|
1 1 1 , 1 ' ._______-!
|
s C««l H
|
r« ————— 500 ————— H
|
250
Nasza bryła obcinania (widoczna część przestrzeni współrzędnych) jest modyfikowana tak, że jej lewa strona znajduje się zawsze w punkcie x = 0. Prawa strona rozciąga się o 250 jednostek w prawo, chyba że okno ma większą szerokość niż wysokość. W takim wypadku prawa strona jest mnożona przez stosunek współrzędnych okna. Dolna płaszczyzna zawsze znajduje się w punkcie y = O, zaś górna płaszczyzna jest przesunięta o 250 jednostek w górę, chyba że okno ma większą wysokość niż szerokość. W takim
74_________________________________Część l « Wprowadzenie do OpenGL
przypadku położenie górnej płaszczyzny jest mnożone przez stosunek współrzędnych okna. Dzięki temu zawsze pozostaje nam do dyspozycji region 250 x 250 jednostek, bez względu na kształt okna. Działanie tego mechanizmu przedstawia rysunek 3.10.
Animacja przy użyciu biblioteki AUX
Jak dotąd, omawialiśmy podstawy zastosowania biblioteki AUX przy tworzeniu okna. Omawialiśmy także polecenia OpenGL służące do rysowania. Często zależy nam jednak, aby obracać lub przemieszczać obiekty, tworząc efekt animacji. Jako punkt wyjścia weźmy poprzedni przykład, rysujący kwadrat, i sprawmy, aby ten kwadrat zaczął odbijać się od krawędzi okna. Mógłbyś stworzyć pętlę, która w nieskończoność zmieniałaby współrzędne obiektu i wywoływałaby funkcję RenderScene(), co powodowałoby, że kwadrat krążyłby wokół okna.
Jednak biblioteka AUX zawiera o wiele wygodniejszą funkcję ułatwiającą tworzenie prostych animowanych sekwencji. Ta funkcja, aux!dleFunc(), jako argumentu oczekuje nazwy funkcji, która ma być okresowo wywoływana w czasie bezczynności okna. Wywoływana funkcja ma następujący prototyp:
void CALLBACK IdleFunction(void);
Ta funkcja jest cały czas regularnie wywoływana przez bibliotekę AUX, chyba że okno jest akurat przemieszczane lub skalowane.
Gdy zmienimy zaszyte w program wartości określające położenie kwadratu na wartości przechowywane w zmiennych, a następnie stale będziemy je modyfikować wewnątrz funkcji IdleFunction(), kwadrat zacznie się poruszać w obrębie okna. Spójrzmy na przykład tego rodzaju animacji. Listing 3.4 stanowi zmodyfikowaną wersję listingu 3.3, tak że kwadrat zmienia położenie w obrębie okna, odbijając się od jego krawędzi. Musimy przy tym śledzić położenie i rozmiar kwadratu, a także brać pod uwagę wszelkie zmiany w rozmiarze okna.
Listing 3.4. Animowany odbijający się kwadrat____________________________________
// bounce.c
// Odbijający się kwadrat
#include <windows.h> // Standardowy nagłówek dla Windows
łinclude <gl\gl.h> // Biblioteka OpenGL
Sinclude <gl\glaux.h> // Biblioteka AUX
// Początkowa pozycja i wymiary kwadratu GLfloat xl = 100.Of; GLfloat yl = 150.Of; GLsizei rsize = 50;
Rozdziaf 3. * Nauka OpenGL z użyciem biblioteki AUX ______________________ 75
// Długość kroku w kierunku x i y
// (ilość pikseli, o które trzeba się za każdym razem przesunąć)
GLfloat xstep = l.Of;
GLfloat ystep = l.Of;
// Przechowanie informacji o zmianach rozmiarów okien GLfloat windowWidth; GLfloat windowHeight;
// Wywoływana przez bibliotekę AUX w momencie zmiany rozmiaru okna
void CALLBACK ChangeSize (GLsizei w, GLsizei h)
{
// Zabezpieczenie przed dzieleniem przez zero, gdy okno jest zbyt
// niskie (nie można tak zmniejszyć okna, aby miało zerową
// wysokość) .
if(h == 0) h = 1;
// Ustawienie widoku na wymiary okna glviewport (O, O, w, h);
// Wyzerowanie układu współrzędnych przed modyfikacją glLoadldentity ( ) ;
// Niech kwadrat będzie kwadratem, tym razem przechowujemy // obliczoną szerokość i wysokość do późniejszego użytku if (w <= h)
{
windowHeight = 250.0f*h/w;
windowWidth = 250. Of;
else
windowWidth = 250.0f*w/h; windowHeight = 250. Of;
// Ustalenie bryły obcinania
glOrtho (O.Of , windowWidth, O.Of, windowHeight, l.Of, -l.Of);
// Wywoływane przez bibliotekę AUX w celu narysowania sceny
void CALLBACK RenderScene (void)
{
// Kolor tła: Niebieski
glClearColor (O.Of, O.Of, l.Of, l.Of);
// Wyczyszczenie okna bieżącym kolorem tła glClear (GL_COLOR_BUFFER_BIT) ;
// Kolor rysowania czerwony; Narysowanie prostokąta // wypełnionego bieżącym kolorem glColorSf (l.Of, O.Of, O.Of); glRectf(xl, yl, xl+rsize, yl+rsize);
glFlush() ;
76 _________________________________ Część l * Wprowadzenie do OpenGL
// Wywoływana przez bibliotekę AUX w czasie wolnym (okno nie // jest przesuwane ani skalowane) void CftLLBACK IdleFunction (void) {
// Odwrócenie kierunku w momencie osiągnięcia lewej lub prawej
// krawędzi
if(xl > windowWidth-rsize || xl < 0) xstep = -xstep;
// Odwrócenie kierunku w momencie osiągnięcia górnej lub dolnej // krawędzi
if(yl > windowHeight-rsize l l yl < 0) ystep = -ystep;
// Sprawdzenie zakresu. Robimy to na wypadek zmniejszenia okna, // kiedy kwadrat mógłby się znaleźć poza bryłą obcinania if(xl > windowWidth-rsize)
xl = windowWidth-rsize-1;
if(yl > windowHeight-rsize)
yl = windowHeight-rsize-1;
// Samo przesunięcie kwadratu xl += xstep; yl += ystep;
// Narysowanie sceny z nowymi współrzędnymi RenderScene () ;
// Główna funkcja programu
void main(void)
{
// Przygotowanie trybu rysowania i okna za pomocą biblioteki AUX
aux!nitDisplayMode (AUX_SINGLE | AUX_RGBA) ;
aux!nitPosition(100, 100,250,250);
auxlnitwindow( "Prosta animacja 2D");
// Przygotowanie funkcji wywoływanej w momencie zmiany wymiarów // okna auxReshapeFunc(ChangeSize) ;
// Przygotowanie funkcji wywoływanej w momencie bezczynności okna aux!dleFunc (IdleFunction) ;
// Przygotowanie głównej pętli programu auxMainLoop (RenderScene) ;
Animacja tworzona przez ten przykład jest kiepska, nawet w bardzo szybkim komputerze. Ponieważ przed narysowaniem prostokąta jest czyszczona zawartość całego okna, cały czas kwadrat migocze i wyraźnie widać, że w rzeczywistości jest rysowany jako dwa trójkąty. Aby stworzyć płynniejszą animację, musimy wykorzystać mechanizm zwany podwójnym buforowaniem.
Rozdział 3. « Nauka OpenGL z użyciem biblioteki AUX __ __ 77
Podwójne buforowanie
Jedną z najważniejszych cech każdego pakietu graficznego jest obsługa podwójnego buforowania. Ta funkcja umożliwia tworzenie rysunku w niewidocznym buforze, a następnie błyskawiczne przerzucenie zawartości tego bufora do okna na ekranie.
Podwójne buforowanie służy dwu celom. Po pierwsze, rysowanie złożonych rysunków może trwać dość długo, a nie życzysz sobie, aby na ekranie był widoczny każdy etap tworzenia obrazu. Używając podwójnego bufora, możesz skomponować obraz i wyświetlić go dopiero wtedy, gdy będzie w całości gotowy. W ten sposób użytkownik nigdy nie widzi fragmentów sceny, lecz dopiero cały obraz po przerzuceniu go na ekran.
Drugim zastosowaniem podwójnego buforowania jest animacja. Każda klatka animacji jest rysowana w niewidocznym buforze, a następnie, gdy już jest gotowa, zostaje przerzucona na ekran. Biblioteka AUX zapewnia w tym celu podwójnie buforowane okna. Aby skorzystać z tej techniki i uzyskać w naszym programie znacznie płynniejszą animację, do pliku bounce.c musimy wprowadzić jedynie dwie drobne zmiany. Po pierwsze, zmienimy linię w funkcji main() inicjującą tryb wyświetlania, nakazując włączenie podwójnego buforowania:
auKlnitDisplayMode(AUX_DOUBLE | AUX_RGBA);
Spowoduje to, że całe rysowanie odbywać się będzie w niewidocznym buforze.
Następnie dodamy pojedynczą linię na końcu funkcji RenderScene():
auxSwapBuffers();
Funkcja auxSwapBuffers() powoduje przerzucenie na ekran zawartości niewidocznego bufora. (Pełny kod tego programu znajdziesz w kartotece BOUNCE2 na płytce CD-ROM). W wyniku otrzymujemy bardzo płynną animację czerwonego kwadratu obijającego się wewnątrz niebieskiego okna (rysunek 3.11).
Rysunek 3.11.
Odbijający się kwadrat
78_________________________________Część l * Wprowadzenie do OpenGL
W końcu trochę trzeciego wymiaru!
Jak dotąd wszystkie omawiane przykłady przedstawiały czerwony prostokąt na środku niebieskiego okna, ewentualnie przeskalowany lub odbijający się od krawędzi okna. W tym momencie sam możesz obijać się o ściany, niecierpliwie oczekując, że w końcu pojawi się coś trójwymiarowego. Nie czekaj dłużej!
Jak już wspomniano, cały czas rysowaliśmy obiekty w przestrzeni 3D, jednak rzut prostokąta był prostopadły do bryły obcinania. Gdybyśmy mogli po prostu obrócić bryłę obcinania w stosunku do obserwatora, być może ujrzelibyśmy jakieś elementy trzeciego wymiaru. W tym momencie jednak, aż do rozdziału 7, nie mamy zamiaru zagłębiać się w obroty i transformacje współrzędnych. A nawet gdybyśmy spróbowali tego teraz, kwadrat, nawet obrócony, nie jest zbyt ciekawy.
Aby ujrzeć głębokość, musimy narysować obiekt, który nie jest płaski. Biblioteka AUX zawiera prawie tuzin trójwymiarowych obiektów - od kuli aż po imbryk na herbatę - które można stworzyć jednym wywołaniem funkcji. Te funkcje mają postać auxSolidrra:() lub auxWirexxx;e(), gdzie xxxx oznacza, nazwę jednolitego lub szkieletowego obiektu, który ma zostać stworzony. Na przykład, poniższe polecenie rysuje szkieletowy imbryk do kawy o średnicy wynoszącej, w przybliżeniu, 50 jednostek:
auxWireTeapot(50.Of);
Jeśli byśmy zdefiniowali bryłę obcinania na zakres od -100 do 100 we wszystkich trzech osiach, otrzymalibyśmy imbryk do kawy przedstawiony na rysunku 3.12. Imbryk do kawy jest w tym momencie chyba najlepszym przykładem, ponieważ pozostałe obiekty oglądane w rzucie równoległym nadal sprawiają wrażenie płaskich. Program rysujący ten imbryk do herbaty znajduje się w osobnym podrozdziale na płytce CD-ROM, w pliku teapot.c.
Rysunek 3.12.
Szkieletowy imbryk do herbaty
Jeślibyś zamienił szkieletowy imbryk na imbryk jednolity poleceniem
auxWireTeapot(50.Of);
zobaczyłbyś jedynie sylwetkę imbryka. Aby ujrzeć wypukłość jednolicie pokolorowa-nego obiektu, musiałbyś zastosować cieniowanie i oświetlenie, wykorzystując do tego polecenia OpenGL, które poznasz dopiero w rozdziale 9 i następnych.
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX_______________________79
Aby lepiej poznać trójwymiarowe obiekty biblioteki AUX, zajrzyj do przykładów AUXWIRE i AUXSOLID na płytce CD-ROM, w podrozdziałach bieżącego rozdziału. W tych przykładach została wykorzystana funkcja glRotatef() (opisana w rozdziale 7), obracająca obiekty wokół wszystkich trzech osi bryły widzenia. Niektóre z obiektów wykorzystują bibliotekę narzędziową, więc pamiętaj, aby przy samodzielnym modyfikowaniu przykładów połączyć kod z biblioteką glu32.1ib.
Podsumowanie
W tym rozdziale zostaliśmy wprowadzeni do narzędziowej biblioteki AUX oraz poznaliśmy podstawy pisania programów korzystających z OpenGL. Użyliśmy biblioteki AUX do pokazania najprostszego sposobu utworzenia okna i rysowania w nim przy pomocy poleceń OpenGL. Nauczyłeś się używać biblioteki AUX do tworzenia okien, które mogą zmieniać rozmiary, a także prezentować w nich proste animacje. Oprócz tego znasz już proces używania OpenGL przy rysowaniu - komponowanie i wybieranie kolorów, czyszczenie ekranu, rysowanie prostokąta, a także ustawianie widoku oraz bryły obcinania tak, aby dopasować je do rozmiaru okna. Omówiliśmy także różne typy danych, jak również pliki nagłówkowe i biblioteki wymagane przy budowie programów OpenGL.
Biblioteka AUX zawiera jeszcze wiele innych funkcji obsługujących także klawiaturę i mysz. Implementacja biblioteki stworzona przez Microsoft zawiera specyficzne dla Windows funkcje umożliwiające dostęp do uchwytów okien i kontekstów urządzeń. Przejrzyj umieszczoną poniżej sekcję podręcznika, aby samemu odkryć pozostałe elementy i zastosowania biblioteki AUX. Przejrzyj także inne przykłady do rozdziału trzeciego, zamieszczonych na płytce CD-ROM dołączonej do książki.
Podręcznik
auxldleFunc
Przeznaczenie Ustawia funkcję zwrotną wywoływaną w czasie bezczynności.
Plik nagłówkowy <glaux.h>
Składnia void aux!dleFunc(AUXIDLEPROC func);
Opis Określa, że funkcja bezczynności func() będzie regularnie wywoływana
w momencie braku innej aktywności okna. Gdy program nie jest zajęty
renderowaniem sceny, funkcja zmienia pewne parametry używane przez
funkcje rysunkowe przy tworzeniu następnego obrazu.
80
Część l « Wprowadzenie do OpenGL
Parametry func
Zwracana wartość Przykład Patrz także
Prototypem tej funkcji jest
void CALLBACK IdleFunc(void);
Definiowana przez użytkownika funkcja regularnie wywoływana w czasie bezczynności programu. Przekazanie wartości NULL jako adresu funkcji zatrzymuje przetwarzanie czasu wolnego.
Brak.
Przykłady BOUNCE i BOUNCE2 do tego rozdziału.
auxSwapBuffers, auxMainLoop, auxReshapeFunc
auxlnitDisplayMode
Przeznaczenie Plik nagłówkowy Składnia Opis
Inicjuje tryb wyświetlania biblioteki AUX dla okna OpenGL.
<glaux.h>
void auxInitDisplayMode(GLbitfield mask);
To pierwsza funkcja, jaka musi być wywołana w programie korzystającym z biblioteki AUX. Służy do przygotowania okna OpenGL. Ta funkcja ustala charakterystykę okna używanego przez OpenGL w operacjach rysunkowych.
Parametry mask
Zwracana wartość Przykład Patrz także
GLbitfield: Maska lub kombinacja bitowa masek z tabeli 3.4. Te wartości masek mogą być łączone przy pomocy bitowego operatora OR. Na przykład, aby przygotować okno posiadające podwójny bufor i korzystające z indeksowanego trybu kolorów, wywołaj
aux!nitDisplayMode(AUX_DOUBLE | AUX_INDEX)
Brak.
Każdy przykładowy program w tym rozdziale.
aux!nitPosition, aux!nitWindow
Tabela 3.4.
Wartości masek dla różnych charakterystyk okna
Znaczenie
Wartość maski
AUX_SINGLE AUX_DOUBLE AUX_RGBA AUX_INDEX AUX DEPTH
Włącza tryb pracy z pojedynczym buforowaniem
Włącza tryb pracy z podwójnym buforowaniem
Włącza tryb RGBA
Włącza tryb koloru indeksowanego
Włącza 32-bitowy bufor głębokości
81
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX
Tabela 3.4.
Wartości masek dla różnych charakterystyk okna — ciąg dalszy
Wartość maski
Znaczenie
AUX_DEPTH16
AUX_STENCIL
AUX_ACCUM
AUX_ALPHA
AUX FIXED 332 PAL
Włącza 16-bitowy bufor głębokości
Włącza bufor szablonu
Włącza bufor akumulacji
Włącza bufor ALFA
Włącza dla okna stałą paletę 3-3-2
auxlnitPosition
Przeznaczenie Ustala pozycję okna przygotowanego przez aux!nitWindow().
Plik nagłówkowy <glaux.h>
Składnia Opis
Parametry x
y
width height
void aux!nitPosition(GLint x, GLint y, GLsizei width, GLsizei height);
Ta funkcja informuje bibliotekę AUX o miejscu, gdzie ma zostać otwarte główne okno graficzne.
GLint: Odległość lewego górnego rogu okna od lewej krawędzi ekranu, mierzona w pikselach.
GLint: Odległość lewego górnego rogu okna od górnej krawędzi ekranu, mierzona w pikselach.
GLsizei: Początkowa szerokość obszaru roboczego okna, mierzona w pikselach.
GLsizei: Początkowa wysokość obszaru roboczego okna, mierzona w pikselach.
Zwracana wartość Brak.
Przykład Każdy przykładowy program w tym rozdziale.
Patrz także aux!nitDisplayMode, aux!nitWindow
auxlnitWindow
Przeznaczenie Plik nagłówkowy Składnia
Inicjuje i wyświetla okno renderowania OpenGL.
<glaux.h>
void aux!nitWindow(BYTE *titleString);
82
Część l » Wprowadzenie do OpenGL
Opis
Ta funkcja otwiera okno, które będzie używane przez OpenGL przy operacjach graficznych.
Przed wywołaniem okna należy określić jego charakterystykę za pomocą funkcji aux!nitDisplayMode() oraz aux!nitPosition().
Parametry titleString
GLBYTE: Wskaźnik do łańcucha znaków, który zostanie użyty jako tytuł okna, wyświetlany na belce tytułowej.
Zwracana wartość Brak.
Przykład Każdy przykładowy program w tym rozdziale.
Patrz także aux!nitDisplayMode, aux!nitPosition
auxKeyFunc
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry key
function
Zwracana wartość Przykład
Patrz także
Wiąże funkcję zwrotną z wciśnięciem danego klawisza.
<glaux.h>
void auxKeyFunc(GLint key, void(*function(void));
Ustala funkcję zwrotną {function), która będzie wywoływana przez
bibliotekę AUX za każdym razem, gdy zostanie wciśnięty klawisz
o numerze key. Po przetworzeniu klawisza następuje odświeżenie okna.
GLint: Numer klawisza, jaki ma być powiązany z daną funkcją. Może to być jedna z wartości z tabeli 3.5.
Ta funkcja zwrotna ma następujący prototyp: void CALLBACK KeyFunc(void);
Ta funkcja jest wywoływana przez bibliotekę AUX za każdym razem, gdy zostanie wciśnięty dany klawisz. Gdy jako ten parametr zostanie przekazana wartość NULL, następuje wstrzymanie wywoływania funkcji zwrotnej.
Brak.
Dodatkowy przykładowy program KEYMOYE na płytce CD-ROM, w folderze tego rozdziału.
auxMouseFunc
83
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX
Tabela 3.5.
Definicje klawiszy w bibliotece A UX
Wartość
Opis
AUX_ESCAPE
AUX_SPACE
AUX_RETURN
AUX_LEFT
AUX_RJGHT
AUX_UP
AUX_DOWN
AUX_A do AUX_Z
AUX_a do AUX_z
AUX OdoAUX 9
Klawisz Esc Klawisz spacji Klawisz Enter Klawisz kursor w lewo Klawisz kursor w prawo Klawisz kursor w górę Klawisz kursor w dół Klawisze od A do Z (wielkie litery) Klawisze od a do z (małe litery) Klawisze numeryczne od O do 9
auxMainLoop
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Parametry func
Zwracana wartość Przykład Patrz także
Określa funkcję, która powinna zostać użyta do odświeżenia zawartości okna OpenGL,
<glaux.h>
void auxMainLoop(AUXMAINPROC func);
Ta funkcja jest używana do określenia funkcji, która ma być wywoływana za każdym razem, gdy okno OpenGL wymaga aktualizacji. Ta funkcja nie zwraca sterowania aż do momentu zamknięcia okna OpenGL.
Ta funkcja ma następujący prototyp void CALLBACK MainFunc(void);
Ta funkcja jest używana do aktualizacji zawartości okna i powinna zawierać instrukcje rysunkowe.
Brak.
Każdy przykładowy program w tym rozdziale.
aux!dleFunc, auxReshapeFunc
84
Część l » Wprowadzenie do OpenGL
auxMouseFunc
Przeznaczenie Plik nagłówkowy Składnia Opis
Wiąże funkcje zwrotną z kliknięciami przycisków myszy
<glaux.h>
void auxMouseFunc(int button, int modę, AUXMOUSEPROC func);
Służy do ustawienia funkcji/M«c jako funkcji wywoływanej w momencie kliknięcia lub zwolnienia przycisku myszy. Przycisk myszy jest określany przez jedną z podanych niżej wartości. Tryb wywoływania określa, czy funkcja ma być wywoływana w momencie, gdy przycisk jest wciskany lub zwalniany.
Parametry button
modę
func
Zwracana wartość Przykład Patrz także
int: Przycisk, z którym ma być powiązana funkcja zwrotna; może to być jedna z następujących wartości: AUX_LEFTBUTTON (lewy), AUX_MIDDLEBUTTON (środkowy) lub AUX_RIGHTBUTTON (prawy).
int: Tryb wywoływania funkcji zwrotnej. Może nim być AUX_MOUSEDOWN (funkcja jest wywoływana w momencie klinięcia przyciskiem myszy) lub AUX_MOUSEUP (funkcja jest wywoływana w momencie zwolnienia przycisku myszy).
Ta funkcja ma następujący prototyp
void CALLBACK MouseFunc(AUX_EVENTREC *event); Struktura event zawiera współrzędne wskaźnika myszy w momencie wystąpienia zdarzenia.
typedef struct _AUX_EVENTREC {
GLint event;
GLINT data [4]; } AUX_EVENTREC;
event: GLint: Określa zdarzenie, jakie nastąpiło (AUX_MOUSEUP lub AUX_MOUSEDOWN)
data[4]\ GLint: zawiera dane specyficzne dla zdarzenia data[AUX_MOUSEX] = pozioma współrzędna wskaźnika myszy data[AUX_MOUSEY] = pionowa współrzędna wskaźnika myszy data[MOUSE_STATUS] = przycisk myszy (z parametru button)
Brak.
Program MBOUNCE na płytce CD-ROM, w folderze tego rozdziału.
auxKeyFunc
85
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX
auxReshapeFunc
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Parametry func
Zwracana wartość Przykład Patrz także
Ustanawia funkcję, która będzie wywoływana w momencie, gdy zmieni się rozmiar okna.
<glaux.h>
void auxReshapeFunc(AUXRESHAPEPROC func);
Ta funkcja jest używana do określenia funkcji, która ma być wywoływana za każdym razem, gdy zmieni się rozmiar okna OpenGL. Zwykle w tej funkcji są modyfikowane ustawienia widoku i bryły obcinania tak, aby prawidłowo przeskalować obraz.
Ta funkcja ma następujący prototyp
void CALLBACK Reshape(GLsizei width, GLsizei height);
Funkcja otrzymuje nową szerokość i wysokość okna.
Brak.
Przykład SCALĘ w tym rozdziale.
aux!dleFunc, auxMainLoop
auxSetOneColor
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
index
red
green
blue Zwracana wartość
Ustala pojedynczy kolor w indeksowanej palecie kolorów
<glaux.h>
void auxSetOneColor(int index, float red, float green, float blue);
Ta funkcja jest używana w indeksowanych trybach kolorów. W takim trybie, zamiast określania koloru za pomocą wartości RGB, tworzona jest paleta kolorów. Kolory są przypisywane obiektom przez podanie indeksu w palecie. Ta funkcja ustala wartości RGB koloru reprezentowanego przez dany indeks palety.
int: Indeks w palecie kolorów.
float: Czerwona składowa żądanego koloru.
float: Zielona składowa żądanego koloru.
float: Niebieska składowa żądanego koloru.
Brak.
86_________________________________Część l * Wprowadzenie do OpenGL
Przykład Uzupełniający przykład COLORDX na płytce CD-ROM, w folderze tego
rozdziału. Zwróć uwagę, że ten przykład należy uruchomić
w indeksowanym trybie wyświetlania (dostępnym w większości
256-kolorowych kart, jednak przy nie więcej niż 8-bitach koloru).
Patrz także getColorMapSize, auxSetRGBMap
auxSolidBox________________________
Przeznaczenie Rysuje jednolity prostopadłościan.
Plik nagłówkowy <glaux.h>
Składnia void auxSolidBox(GLdouble width, GLdouble height, GLdouble depth);
Opis Rysuje jednolity prostopadłościan ze środkiem w centrum układu
współrzędnych (O, O, 0). Alternatywną formą tej funkcji jest
auxSolidCube. Używana głównie do demonstracji.
Parametry
width GLdouble: Szerokość prostopadłościanu.
height GLdouble: Wysokość prostopadłościanu.
depth GLdouble: Głębokość prostopadłościanu.
Zwracana wartość Brak.
Przykład Uzupełniający przykład AUXSOLID na płytce CD-ROM, w folderze tego
rozdziału. Umieszczony tam program zawiera przykłady tworzenia
wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
Patrz także auxWireBox, auxSolidCube
auxSolidCone_______________________
Przeznaczenie Rysuje jednolity stożek.
Plik nagłówkowy <glaux.h>
Składnia void auxSolidCone(GLdouble radius, GLdouble height);
Opis Rysuje jednolity stożek ze środkiem w centrum układu współrzędnych
(O, O, 0). Używana głównie do demonstracji.
Parametry
radius GLdouble: Promień podstawy stożka.
height GLdouble: Wysokość stożka.
Zwracana wartość Brak.
87
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX
Przykład
Patrz także
Uzupełniający przykład AUXSOLID na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefmiowanych jednolitych obiektów z biblioteki AUX.
auxWireCone
auxSolidCube
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
width
Zwracana wartość Przykład
Patrz także
Rysuje jednolity sześcian.
<glaux.h>
void auxSolidCube(GLdouble width);
Rysuje jednolity sześcian ze środkiem w centrum układu współrzędnych (O, O, 0). Alternatywna forma funkcji auxSolidBox Używana głównie do demonstracji.
GLdouble: Długość krawędzi sześcianu. Brak.
Uzupełniający przykład AUXSOLID na płytce CD-ROM, w folderze dotyczącym tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefmiowanych jednolitych obiektów z biblioteki AUX.
auxWireCube, auxSolidBox
AuxSolidCylinder
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
radius
height
Zwracana wartość Przykład
Patrz także
Rysuje jednolity cylinder.
<glaux.h>
void auxSolidCylinder(GLdouble radius, GLdouble height);
Rysuje jednolity cylinder ze środkiem w centrum układu współrzędnych (O, O, 0). Używana głównie do demonstracji.
GLdouble: Promień podstawy cylindra. GLdouble: Wysokość cylindra. Brak.
Uzupełniający przykład AUXSOLID na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefmiowanych jednolitych obiektów.z biblioteki AUX.
auxWireCylinder
88
Część l * Wprowadzenie do OpenGL
auxSolidDodecahedron
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
radius
Zwracana wartość Przykład
Patrz także
Rysuje jednolity dwunastościan.
<glaux.h>
void auxSolidDodecahedron(GLdouble radius);
Rysuje jednolity dwunastościan foremny ze środkiem w centrum układu współrzędnych (O, O, 0). Ścianki dwunastościanu są pięciokątami foremnymi. Używana głównie do demonstracji.
GLdouble: Promień dwunastościanu. Brak.
Uzupełniający przykład AUXSOLID na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxWireDodecahedron
auxSolidlcosahedron
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
radius
Zwracana wartość Przykład
Patrz także
Rysuje jednolity dwudziestościan.
<glaux.h>
void auxSolid!cosahedron(GLdouble radius);
Rysuje jednolity dwudziestościan foremny ze środkiem w centrum układu współrzędnych (O, O, 0). Ścianki dwudziestościanu są trójkątami równobocznymi. Używana głównie do demonstracji.
GLdouble: Promień dwudziestościanu. Brak.
Uzupełniający przykład AUXSOLID na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxWire!cosahedron
auxSolidOctahedron
Przeznaczenie Plik nagłówkowy Składnia
Rysuje jednolity ośmiościan.
<glaux.h>
void auxSolidOctahedron(GLdouble radius);
89
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX
Opis
Parametry
radius
Zwracana wartość Przykład
Patrz także
Rysuje jednolity ośmiościan foremny ze środkiem w centrum układu współrzędnych (O, O, 0). Ścianki ośmiościanu są trójkątami równobocznymi. Używana głównie demonstracji.
GLdouble: Promień ośmiościanu. Brak.
Uzupełniający przykład AUXSOLID na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxWireOctahedron
auxSolidSphere
Przeznaczenie Plik naglówkowy Składnia Opis
Parametry
radius
Zwracana wartość Przykład
Patrz także
Rysuje jednolitą kulę.
<glaux.h>
void auxSolidSphere(GLdouble radius);
Rysuje jednolitą kulę ze środkiem w centrum układu współrzędnych (O, O, 0). Używana głównie do demonstracji.
GLdouble: Promień kuli. Brak.
Uzupełniający przykład AUXSOLID na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxWireSphere
auxSolidTeapot
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
radius Zwracana wartość
Rysuje jednolity imbryk do herbaty.
<glaux.h>
void auxSolidTeapot(GLdouble radius);
Rysuje jednolity imbryk do herbaty ze środkiem w centrum układu współrzędnych (O, O, 0). Używana głównie demonstracji.
Gldouble: Promień imbryka (w przybliżeniu). Brak.
90
Część l * Wprowadzenie do OpenGL
Przykład Uzupełniający przykład AUXSOLID na płytce CD-ROM, w folderze tego
rozdziału. Umieszczony tam program zawiera przykłady tworzenia
wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
Patrz także auxWireTeapot
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
radius
Zwracana wartość Przykład
Patrz także
auxSolidTetrahedron
Rysuje jednolity czworościan foremny.
<glaux.h>
void auxSolidTetrahedron(GLdouble radius);
Rysuje jednolity czworościan foremny ze środkiem w układu współrzędnych (O, O, 0). Ścianki czworościanu są trójkątami równobocznymi. Używana głównie do demonstracji.
GLdouble: Promień ośmiościanu. Brak.
Uzupełniający przykład AUXSOLID na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxWireTetrahedron
Przeznaczenie Plik nagłówkowy Składnia Opis
auxSolidTorus
Rysuje jednolity torus (dętkę).
<glaux.h>
void auxSolidTorus(GLdouble innerRadius, GLdouble outerRadius);
Parametry innerRadius outerRadius
Zwracana wartość
Przykład
Patrz także
Rysuje jednolity torus ze środkiem w centrum układu współrzędnych (O, O, 0). Torus ma kształt dętki. Promień wewnętrzny to promień dętki, zaś promień zewnętrzny to promień koła. Używana głównie do demonstracji.
GLdouble: Wewnętrzny promień torusa. GLdouble: Zewnętrzny promień torusa. Brak.
Uzupełniający przykład AUXSOLID na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxWireTorus
91
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX
auxSwapBuffers
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Zwracana wartość Przykład Patrz także
Przerzuca rysunek z niewidocznego bufora do okna podczas rysowania z podwójnym buforowaniem.
<glaux.h>
void auxSwapBuffers(void);
Ta funkcja jest używana przy rysowaniu i animacji z wykorzystaniem podwójnego buforowania. Wywołanie funkcji powoduje przerzucenie sceny tworzonej w niewidocznym buforze do okna.
Brak.
Przykład BOUNCE2 w tym rozdziale.
aux!nitDisplayMode, aux!dleFunc
auxWireBox
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
width
height
depth
Zwracana wartość Przykład
Patrz także
Rysuje szkieletowy prostopadłościan.
<glaux.h>
void auxWireBox(GLdouble width, GLdouble height, GLdouble depth);
Rysuje szkieletowy prostopadłościan ze środkiem w centrum układu współrzędnych (O, O, 0). Alternatywną formą tej funkcji jest auxWireCube. Używana głównie do demonstracji.
GLdouble: Szerokość prostopadłościanu. GLdouble: Wysokość prostopadłościanu. GLdouble: Głębokość prostopadłościanu. Brak.
Uzupełniający przykład AUXWIRE na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxSolidBox, auxWireCube
auxWireCone
Przeznaczenie Rysuje szkieletowy stożek.
Plik nagłówkowy <glaux.h>
Składnia void auxWireCone(GLdouble radius, GLdouble height);
92
Część l * Wprowadzenie do OpenGL
Opis
Parametry
radius
height
Zwracana wartość Przykład
Patrz także
Rysuje szkieletowy stożek ze środkiem w centrum układu współrzędnych (O, O, 0). Używana głównie do demonstracji.
GLdouble: Promień podstawy stożka. GLdouble: Wysokość stożka. Brak.
Uzupełniający przykład AUXWIRE na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxSolidCone
auxWireCube
Przeznaczenie
Plik nagłówkowy Składnia
Opis
Parametry
width
Zwracana wartość Przykład
Patrz także
Rysuje szkieletowy sześcian.
<glaux.h>
void auxWireCube(GLdouble width);
Rysuje szkieletowy sześcian ze środkiem w centrum układu współrzędnych (O, O, 0). Alternatywna forma funkcji auxWireBox Używana głównie do demonstracji.
GLdouble: Długość krawędzi sześcianu. Brak.
Uzupełniający przykład AUXWIRE na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxSolidCube, auxWireBox
auxWireCylinder
Przeznaczenie Plik nagłówkowy Składnia Opis
Rysuje szkieletowy cylinder.
<glaux.h>
void auxWireCylinder(GLdouble radius, GLdouble height);
Rysuje szkieletowy cylinder ze środkiem w centrum układu współrzędnych (O, O, 0). Używana głównie do demonstracji.
93
Rozdział 3. + Nauka OpenGL z użyciem biblioteki AUX
Parametry
radius
height
Zwracana wartość Przykład
Patrz także
GLdouble: Promień podstawy cylindra. GLdouble: Wysokość cylindra. Brak.
Uzupełniający przykład AUXWIRE na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefmiowanych jednolitych obiektów z biblioteki AUX.
auxSolidCylinder
auxWireDodecahedron
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
radius
Zwracana wartość Przykład
Patrz także
Rysuje szkieletowy dwunastościan.
<glaux.h>
void auxWireDodecahedron(GLdouble radius);
Rysuje szkieletowy dwunastościan foremny ze środkiem w centrum układu współrzędnych (O, O, 0). Ścianki dwunastościanu są pięciokątami foremnymi. Używana głównie do demonstracji.
GLdouble: Promień dwunastościanu. Brak.
Uzupełniający przykład AUXWIRE na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefmiowanych jednolitych obiektów z biblioteki AUX.
auxSolidDodecahedron
auxWirelcosahedron
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
radius Zwracana wartość
Rysuje szkieletowy dwudziestościan.
<glaux.h>
void auxWire!cosahedron(GLdouble radius);
Rysuje szkieletowy dwudziestościan foremny ze środkiem w centrum układu współrzędnych (O, O, 0). Ścianki dwudziestościanu są trójkątami równobocznymi. Używana głównie do demonstracji.
GLdouble: Promień dwudziestościanu. Brak.
94
Część l * Wprowadzenie do OpenGL
Przykład Uzupełniający przykład AUXWIRE na płytce CD-ROM, w folderze tego
rozdziału. Umieszczony tam program zawiera przykłady tworzenia
wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
Patrz także auxSolid!cosahedron
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
radius
Zwracana wartość Przykład
Patrz także
auxWireOctahedron
Rysuje szkieletowy ośmiościan.
<glaux.h>
void auxWireOctahedron(GLdouble radius);
Rysuje szkieletowy ośmiościan foremny ze środkiem w centrum układu współrzędnych (O, O, 0). Ścianki ośmiościanu są trójkątami równobocznymi. Używana głównie do demonstracji.
GLdouble: Promień ośmiościanu. Brak.
Uzupełniający przykład AUXWIRE na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxSolidOctahedron
auxWireSphere
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
radius
Zwracana wartość Przykład
Patrz także
Rysuje szkieletową kulę.
<glaux.h>
void auxWireSphere(GLdouble radius);
Rysuje szkieletową kulę ze środkiem w centrum układu współrzędnych (O, O, 0). Używana głównie do demonstracji.
GLdouble: Promień kuli. Brak.
Uzupełniający przykład AUXWIRE na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxSolidSphere
95
Rozdział 3. » Nauka OpenGL z użyciem biblioteki AUX
auxWireTeapot
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
radius
Zwracana wartość Przykład
Patrz także
Rysuje szkieletowy imbryk do herbaty.
<glaux.h>
void auxWireTeapot(GLdouble radius);
Rysuje szkieletowy imbryk do herbaty ze środkiem w centrum układu współrzędnych (O, O, 0). Używana głównie do demonstracji.
GLdouble: Promień imbryka (w przybliżeniu). Brak.
Uzupełniający przykład AUXWIRE na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxSolidTeapot
auxWireTetrahedron
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
radius
Zwracana wartość Przykład
Patrz także
Rysuje szkieletowy czworościan foremny.
<glaux.h>
void auxWireTetrahedron(GLdouble radius);
Rysuje szkieletowy czworościan foremny ze środkiem w centrum układu współrzędnych (O, O, 0). Ścianki czworościanu są trójkątami równobocznymi. Używana głównie do demonstracji.
GLdouble: Promień ośmiościanu. Brak.
Uzupełniający przykład AUXWIRE na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxSolidTetrahedron
auxWireTorus
Przeznaczenie Rysuje szkieletowy torus (dętkę).
Plik nagłówkowy <glaux.h>
Składnia void auxWireTorus(GLdouble innerRadius, GLdouble outerRadius);
96
Część l * Wprowadzenie do OpenGL
Opis
Rysuje szkieletowy torus ze środkiem w centrum układu współrzędnych (O, O, 0). Torus ma kształt dętki. Promień wewnętrzny to promień dętki, zaś promień zewnętrzny to promień koła. Używana głównie do demonstracji.
Parametry
innerRadius
outerRadius Zwracana wartość Brak.
GLdouble: Wewnętrzny promień torusa. GLdouble: Zewnętrzny promień torusa.
Przykład
Patrz także
Uzupełniający przykład AUXWIRE na płytce CD-ROM, w folderze tego rozdziału. Umieszczony tam program zawiera przykłady tworzenia wszystkich predefiniowanych jednolitych obiektów z biblioteki AUX.
auxSolidTorus
glCIearColor
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry
red
green
blue
alpha
Zwracana wartość Przykład
Ustala wartości barw składowych i kanału alfa dla buforów kolorów.
void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
Ustala wartości używane podczas czyszczenia buforów dla barw czerwonej, zielonej i niebieskiej oraz dla składnika alfa. Podane wartości są obcinane do przedziału [O.Of, 1.0].
GLclampf: Czerwony składnik koloru wypełnienia.
GLclampf: Zielony składnik koloru wypełnienia.
GLclampf: Niebieski składnik koloru wypełnienia.
GLclampf: Składnik alfa koloru wypełnienia.
Brak.
Przykład SHORTEST w tym rozdziale.
glFIush
Przeznaczenie Plik nagłówkowy Składnia
Wykonuje funkcje i polecenia OpenGL oczekujące w kolejce.
void glFlush(void);
97
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX
Opis
Przykład
Polecenia OpenGL często są umieszczane w kolejce w celu późniejszego przetworzenia wszystkich naraz, co owocuje poprawą wydajności. Zależy to także od sprzętu, sterowników i samej implementacji OpenGL. Polecenie glFlush powoduje wykonanie wszystkich oczekujących w kolejce poleceń.
Wszystkie przykłady w tym rozdziale.
glOrtho
Przeznaczenie Plik nagłówkowy Składnia
Opis
Ustala lub modyfikuje zakres bryły obcinania..
void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
Ta funkcja określa równoległą bryłę obcinania. W rzutowaniu tego typu obiekty znajdujące się dalej od obserwatora nie wydają się mniejsze niż obiekty umieszczone bliżej (w przeciwieństwie do rzutowania perspektywicznego). Wyobraź sobie bryłę obcinania umieszczoną w przestrzeni kartezjańskiej; w takim przypadku lewa i prawa płaszczyzna wyznaczają minimalną i maksymalną wartość osi x, płaszczyzny dolna i górna wyznaczają minimalną i maksymalną wartość osi y, zaś płaszczyzny bliższa i dalsza wyznaczają minimalną i maksymalną wartość osi z.
Parametry
left
right
bottom
top
near
far
Zwracana wartość Przykład Patrz także
GLdouble: Współrzędna lewej płaszczyzny obcinania.
GLdouble: Współrzędna prawej płaszczyzny obcinania.
GLdouble: Współrzędna dolnej płaszczyzny obcinania.
GLdouble: Współrzędna górnej płaszczyzny obcinania.
GLdouble: Współrzędna bliższej płaszczyzny obcinania.
GLdouble: Współrzędna dalszej płaszczyzny obcinania.
Brak.
Przykład SCALĘ w tym rozdziale.
glYiewport
giyiewport
Przeznaczenie
Ustala obszar okna (widok), przeznaczony do wykorzystania przez OpenGL.
Plik nagłówkowy
98
Część l * Wprowadzenie do OpenGL
Składnia Opis
Parametry x
wldth
height
Zwracana wartość Przykład Patrz także
void glViewport(GLint x, GLint x, GLsizei width, GLsizei height);
Ustala region wewnątrz okna (widok), który ma zostać użyty do odwzorowania współrzędnych bryły obcinania na fizyczne współrzędne okna.
GLint: Odległość lewej krawędzi widoku od lewej krawędzi okna, mierzona w pikselach.
GLint: Odległość górnej krawędzi widoku od górnej krawędzi okna, mierzona w pikselach.
GLsizei: Szerokość widoku w pikselach.
GLsizei: Wysokość widoku w pikselach.
Brak.
Przykład SCALĘ w tym rozdziale.
glOrtho
gIRect
Przeznaczenie Plik nagłówkowy Odmiany
Opis
Parametry xl, y l
x2,y2 *vl
Rysuje płaski prostokąt.
<gl.h>
void gIRectd(GLdouble xl, GLdouble yl, GLdouble x2, GLdouble y2);
void glRectf(GLfloat xl, GLfloat yl, GLfloat x2, GLfloat y2);
void glRecti(GLint xl, GLint yl, GLint x2, GLint y2);
void glRects(GLshort xl, GLshort y l, GLshort x2, GLshort y2);
void glRectdv(const GLdouble *vl, const GLdouble *v2);
void glRectfv(const GLfloat *vl, const GLfloat *v2);
void glRectiv(const GLint *vl, const GLint *v2);
void glRectsv(const GLshort *vl, const GLshort *v2);
Ta funkcja stanowi efektywną metodę tworzenia prostokąta przez podanie współrzędnych dwóch przeciwległych wierzchołków. Prostokąt jest rysowany na płaszczyźnie xy o współrzędnej z = 0.
Określają lewy górny wierzchołek prostokąta. Określają prawy dolny wierzchołek prostokąta.
Tablica dwóch wartości określających lewy górny wierzchołek prostokąta. Może być opisany także jako v l [2].
Rozdział 3. * Nauka OpenGL z użyciem biblioteki AUX_______________________99
*v2 Tablica dwóch wartości określających prawy dolny wierzchołek
prostokąta. Może być opisany także jako v2[2].
Zwracana wartość Brak.
Przykład Przykład FRIENDLY w tym rozdziale.
Rozdział 4.
OpenGL for Windows: OpenGL + Win32 = Wiggle
W tym rozdziale:
Obsługa okna bez pośrednictwa biblioteki AUX: Używane funkcje
* Tworzenie i korzystanie z kontekstów * wglCreateContext,
renderowania wglDeleteContext,
wglMakeCurrent
* Żądanie i wybieranie formatu pikseli * ChoosePixelFormat,
SetPixelFormat
* Reagowanie na komunikaty okienkowe «• WM_PAINT, WM_CREATE,
WM_DESTROY, WM_SIZE
* Użycie podwójnego buforowania w Windows * SwapBuffers
OpenGL to czysto graficzne API, więc obsługa okien i interakcji z użytkownikiem spoczywa na barkach systemu operacyjnego. Aby zapewnić poprawną współpracę, każdy system operacyjny zwykle posiada odpowiednie rozszerzenia, „sklejające" OpenGL z własnymi funkcjami wejścia/wyjścia i zarządzania oknami. Te rozszerzenia to kod wiążący polecenia rysunkowe OpenGL z konkretnym oknem. Oprócz tego, potrzebne są także funkcje ustawiające tryb buforów, głębokość koloru oraz inne charakterystyki graficzne.
W przypadku Microsoft Windows kod łączący występuje w postaci sześciu nowych funkcji OpenGL (tzw. funkcji wiggle, gdyż ich nazwy zaczynają się od przedrostka wgl, a nie g/) oraz pięciu nowych funkcji Win32 dodanych do GDI w Windows NT i 95. W tym rozdziale omówimy właśnie te łączące funkcje, rezygnując jednocześnie z wyręczania się funkcjami biblioteki AUX.
W rozdziale 3 biblioteki AUX używaliśmy do nauki podstaw programowania OpenGL w C. Dowiedziałeś się, jak rysować parę dwu- i trójwymiarowych obiektów oraz jak
102________________________________Część l « Wprowadzenie do OpenGL
określać układ współrzędnych i sposób rzutowania, nie wgłębiając się w żadne szcze-goty programowana Windows. Nadszedł wJec czas, aby odejść od tego „bezokienko-wegp" podejścia do OpenGL i spróbować prawdziwej pracy w środowisku Windows. feśtt nie zadawafa cię pojedyncze okno, drak menu, brak możliwości drukowania, brak okien dialogowych oraz kilku innych elementów nowoczesnego interfejsu użytkownika, powinieneś nauczyć się używać OpenGL we własnych aplikacjach Win32.
Poczynając od tego rozdziału, będziemy budować w pełni funkcjonalne aplikacje Windows, potrafiące korzystać ze wszystkich elementów systemu operacyjnego. Dowiesz się, jaką charakterystykę musi posiadać okno Windows, aby mogło służyć do prezentowania grafiki tworzonej w OpenGL. Zapoznasz się z komunikatami okienkowymi, na jakie powinien reagować dobrze napisany program OpenGL, powiemy także parę słów na temat sposobów obsługi tych komunikatów. Koncepcje omawiane w tym rozdziale są wprowadzane stopniowo, w trakcie budowania modelowego programu OpenGL w C, który będzie stanowił punkt wyjścia dla wszystkich następnych przykładów.
Aż do teraz, do lektury tej książki nie była wymagana żadna wiedza na temat trójwymiarowej grafiki, wymagana była jedynie podstawowa umiejętność programowania w C. Jednak od tego momentu zakładamy, że znasz przynajmniej podstawy pisania programów dla Windows. (W przeciwnym wypadku musielibyśmy napisać książkę dwa razy grubszą, której większa część dotyczyłaby zagadnień programowania Windows niż programowania w OpenGL). Jeśli jesteś nowicjuszem w Windows lub przywykłeś do korzystania z któregoś z pakietów Application Framework (na przykład MFC czy OWL) i nie znasz się na procedurach okienkowych, przekazywaniu komunikatów itd., przed zagłębieniem się w dalszy tekst powinieneś zapoznać się z którąś z pozycji wymienionych w dodatku B, dotyczącym dalszej lektury związanej z tematem programowania w OpenGL.
Rysowanie w oknach Windows
Przy korzystaniu z biblioteki AUX mieliśmy do dyspozycji tylko jedno okno, więc OpenGL zawsze wiedział, w którym oknie ma rysować (bo gdzieżby indziej?). Jednak aplikacje Windows zwykle posiadają znacznie więcej okien. W rzeczywistości okna dialogowe, kontroIki i nawet menu na podstawowym poziomie są oknami. Skąd więc OpenGL, podczas wykonywania kodu rysunkowego, wie, gdzie ma rysować? Zanim spróbujemy odpowiedzieć na to pytanie, spójrzmy najpierw, jak normalnie przebiega rysowanie w oknie, bez korzystania z OpenGL.
Konteksty urządzeń GDI
Aby narysować coś w oknie bez użycia OpenGL, powinieneś użyć funkcji GDI (Graphics Device Interface) - graficznego interfejsu użytkownika. Każde okno posiada tzw. kontekst urządzenia, określający graficzny charakter okna, zaś każda funkcja GDI jako jednego z argumentów wymaga podania uchwytu kontekstu urządzenia, wskazującego
Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle_________________103
okno, w którym ma odbywać się rysowanie. Możesz korzystać z wielu kontekstów urządzeń, ale tylko po jednym dla każdego z okien.
Przykładowy program WINRECT na dołączonej do książki płytce CD-ROM tworzy standardowe okno z niebieskim tłem i czerwonym kwadratem w środku. Wynik działania tego programu, przedstawiony na rysunku 4.l, wygląda znajomo. To ten sam obraz, jaki otrzymaliśmy w naszym drugim programie OpenGL w rozdziale 3., programie friendly.c. Jednak w odróżnieniu od tamtego przykładu, program WINRECT został stworzony z wykorzystaniem wyłącznie Windows API. Kod tego programu praktycznie nie różni się od ogólnego schematu programów Windows. Występuje w nim funkcja WinMain, przeprowadzająca standardowe inicjowanie i zawierająca pętlę komunikatów, a także funkcja WndProc, obsługująca komunikaty skierowane do głównego okna.
Rysunek 4.1.
Odpowiednik programu friendly. c z rozdziału 3., napisany bez użycia OpenGL
Twoja znajomość programowania Windows powinna obejmować także szczegóły dotyczące tworzenia i wyświetlania okna, z tego programu omówimy więc jedynie kod odpowiedzialny za rysowanie tła i kwadratu.
Najpierw musimy utworzyć niebieski i czerwony pędzel dla tła i kwadratu. Uchwyty tych pędzli są zadeklarowane globalnie:
// Uchwyty pędzli GDI używanych do rysowania HBRUSH hBlueBrush,hRedBrush;
Same pędzle są tworzone w funkcji WinMain, przy użyciu makra RGB w celu utworzenia jednolitych pędzli, niebieskiego i czerwonego.
// Utworzenie niebieskiego i czerwonego pędzla do wypełniania // i rysowania
// Czerwony, zielony, niebieski
hBlueBrush = CreateSolidBrush(RGB( O, O, 255)); hRedBrush = CreateSolidBrush(RGB( 255, O, 0));
W momencie określania stylu okna wskazujemy także, że tło ma być malowane przy użyciu niebieskiego pędzla.
wc.hbrBackground = hBlueBrush; // Niebieski pędzel dla tła
Położenie i rozmiary okna (poprzednio określane przy pomocy funkcji aux!nitPosition) są ustalane w momencie tworzenia okna.
104 ________________________________ Część l * Wprowadzenie do OpenGL
// Utworzenie głównego okna aplikacji hWnd = CreateWindow(
IpszAppName,
IpszAppName,
WS_OVERLAPPEDWINDOW,
100, 100, // Położenie i wymiary
// okna 250, 250, NULL, NULL, hlnstance,
Na koniec, samo rysowanie zawartości okna odbywa się wewnątrz procedury obsługi komunikatu WM_PAINT wewnątrz funkcji WndProc.
case WM_PAINT: (
PAINTSTRUCT ps; HBRUSH hOldBrush;
// Rozpoczęcie rysowania BeginPaint (hWnd, &ps) ;
// Wybranie czerwonego pędzla
hOldBrush = SelectObject (ps .hdc,hRedBrush) ;
// Narysowanie prostokąta aktualnie wybranym pędzlem Rectangle (ps.hdc, 100, 100, 150, 150) ;
// Odłożenie pędzla SelectObject (ps .hdc, hOldBrush) ;
// Koniec rysowania EndPaint (hWnd, sps) ; } break;
Wywołanie funkcji BeginPaint() przygotowuje okno do rysowania oraz przypisuje polu hdc struktury PAINTSTRUCT uchwyt kontekstu urządzenia używanego do rysowania w tym oknie. Uchwyt kontekstu urządzenia jest przekazywany jako pierwszy parametr właściwie wszystkich funkcji GDI, identyfikując okno, do którego ma się odnosić działanie danej funkcji. Nasz kod następnie wybiera do operacji rysowania czerwony pędzel, po czym rysuje czerwony, wypełniony prostokąt o współrzędnych (100, 100, 150, 150). Po narysowaniu prostokąta pędzel jest odkładany, zaś funkcja EndPaint() zajmuje się wykonaniem końcowych porządków.
Zanim wyciągniesz wniosek, że OpenGL działa tak samo, przypomnij sobie, że GDI jest mechanizmem specyficznym dla Windows. Inne środowiska nie posiadają kontekstów urządzeń, uchwytów okien itp. Z drugiej strony, OpenGL został zaprojektowany tak, aby być całkowicie przenośny pomiędzy różnymi środowiskami i platformami sprzętowymi. Dodanie kontekstu urządzenia do funkcji OpenGL sprawiłoby, że kod OpenGL stałby się bezużyteczny w każdym środowisku innym niż Windows.
105
Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle
Konteksty renderowania OpenGL
W celu zapewnienia przenośności rdzennych funkcji OpenGL, każde środowisko musi posiadać jakiś sposób na wskazanie okna, w którym OpenGL będzie rysować. W Windows, środowisko OpenGL jest osadzone w czymś, co nosi nazwę kontekstu renderowania. Podobnie jak kontekst urządzenia zapamiętuje informacje o trybie rysowania i poleceniach GDI, tak kontekst renderowania zapamiętuje ustawienia i polecenia OpenGL.
W swojej aplikacji możesz posiadać więcej niż jeden kontekst renderowania - na przykład możesz korzystać z dwóch okien używających różnych trybów rysowania, rzutów itd. Jednak aby polecenia OpenGL „wiedziały" w którym oknie mają działać, w pojedynczym wątku w danym momencie tylko jeden kontekst renderowania może być bieżący. Gdy kontekst renderowania staje się bieżącym, jest wiązany także z kontekstem urządzenia, czyli konkretnym oknem. W tym momencie OpenGL wiej już, w którym oknie ma rysować. Tę koncepcję ilustruje rysunek 4.2, pokazując, jak polecenia OpenGL są pośrednio powiązane z oknem przez bieżący kontekst renderowania.
Rysunek 4.2.
W jaki sposób polecenia OpenGL odnajdują swoje okno
Rada dotycząca wydajności
Biblioteka OpenGL jest bezpieczna ze względu na wątki, co oznacza, że możesz korzystać jednocześnie z kilku wątków rysujących w swoich oknach lub na swoich bitmapach. W przypadku systemów wieloproce-sorowych może to powodować duży wzrost wydajności. Wątki mogą być użyteczne także w systemach jednoprocesorowych, na przykład kiedy jeden wątek zajmuje się rysowaniem, zaś drugi obsługą interakcji z użytkownikiem. Można także zastosować kilka wątków rysujących obiekty w ramach tego samego kontekstu renderowania. Na płytce CD-ROM dołączonej do książki, w folderze tego rozdziału, znajduje się program GLTHREAD, stanowiący przykład używania wątków w OpenGL.
106 ________________________________ Część l » Wprowadzenie do OpenGL
Korzystanie z funkcji Wiggle
Kontekst renderowania nie jest własną koncepcją OpenGL, lecz raczej dodatkiem do API Windows stworzonym w celu wsparcia OpenGL. W rzeczywistości, nowe funkcje wiggle zostały dodane do Win32 API specjalnie w celu umożliwienia OpenGL działania w oknach. Trzema najczęściej używanym funkcjami związanymi z kontekstem renderowania są
HGLRC wglCreateContext (HDC hDC); BOOL wglDeleteContext (HGLRC hrc); BOOL wglMakeCurrent(HDC hDC, HGLRC hrc);
Tworzenie i wybieranie kontekstu renderowania
Zwróć uwagę na nowy typ danych, HGLRC, reprezentujący uchwyt kontekstu renderowania. Funkcja wglCreateContext() otrzymuje uchwyt kontekstu urządzenia GDI i zwraca uchwyt kontekstu renderowania OpenGL. Podobnie jak kontekst urządzenia GDI, kontekst renderowania musi zostać zniszczony, gdy już nie będzie potrzebny. Służy do tego funkcja wglDeleteContext(), jako jedynego parametru oczekująca uchwytu kontekstu renderowania, który ma zostać zniszczony.
Gdy kontekst renderowania jest tworzony dla danego kontekstu urządzenia, mówi się, że jest odpowiedni do rysowania w tym kontekście urządzenia. Gdy kontekst renderowania staje się bieżący (w wyniku wywołania funkcji wglMakeCurrent()), nie jest konieczne podawanie kontekstu urządzenia użytego przy tworzeniu tego kontekstu renderowania. Jednak wskazany przy tym kontekst urządzenia musi mieć tę samą charakterystykę, co kontekst urządzenia wskazany podczas tworzenia kontekstu renderowania. Muszą zgadzać się liczba kolorów, definicje buforów itd., czyli te informacje, które określa się mianem formatu pikseli.
Aby kontekst renderowania mógł stać się bieżącym dla kontekstu urządzenia różnego od kontekstu urządzenia użytego przy tworzeniu kontekstu renderowania, oba konteksty urządzeń muszą mieć taki sam format pikseli. Możesz odłożyć bieżący kontekst renderowania albo przez wybranie innego kontekstu renderowania, albo przez wywołanie funkcji wglMakeCurrent z parametrem NULL w miejscu uchwytu kontekstu renderowania. (Wybór i ustawianie formatu pikseli zostanie omówione za chwilę).
Rysowanie przy użyciu OpenGL
Jeśli niezbyt często programowałeś z wykorzystaniem GDI, śledzenie zarówno kontekstu urządzenia, jak i kontekstu renderowania może wydawać się dość kłopotliwe, jednak gdy raz tego spróbujesz, przekonasz się, że to bardzo łatwe. W dawnych czasach programowania w 16-bitowych Windows, konieczne było pobieranie kontekstu urządzenia, szybkie wykorzystanie i jeszcze szybsze zwrócenie - gdyż cały system Windows miał do dyspozycji jedynie pięć kontekstów naraz. Teraz, w czasach 32-bitowych Windows te ograniczenia odeszły w niepamięć. Nie oznacza to, że możemy być nieuważni, lecz że występuje mniej ograniczeń w tworzeniu okien z prywatnym kontekstem
Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle _________________ 107
urządzenia (przy użyciu stylu okna WS_OWNDC) i pozostawaniu przy nim przez cały czas życia okna. Co więcej, ponieważ większość naszych przykładów będzie animowana, możemy uniknąć ciągłych (i kosztownych) wywołań funkcji GetDC() za każdym razem, gdy chcemy kontekst renderowania uczynić bieżącym. Kolejna oszczędność czasu polega na jednokrotnym uczynieniu kontekstu renderowania bieżącym i pozostawieniu go bieżącym przez cały czas działania programu. Jeśli tylko jedno okno w wątku korzysta z OpenGL, nigdy nie spowoduje to problemów, zaoszczędzi zaś nam konieczności regularnego wywoływania funkcji wglMakeCurrent.
Jedynie dwa komunikaty okienkowe wymagająjakiegokolwiek kodu związanego z tworzeniem i usuwaniem kontekstu renderowania: WM_CREATE oraz WM_DESTROY. Naturalnie, kontekst renderowania jest tworzony w odpowiedzi na komunikat WM_ CREATE, zaś niszczony w odpowiedzi na komunikat WM_DESTROY. Poniższa szkieletowa sekcja z procedury okna korzystającego z OpenGL ilustruje schemat tworzenia i usuwania kontekstu renderowania:
LRESULT CALLBACK WndProc (HWND hWnd, . . . { static HGLRC hRC; // Przechowuje kontekst renderowania pomiędzy
// wywołaniami
static HDC hDC; // Przechowuje kontekst urządzenia pomiędzy // wywołaniami
switch (message) {
case WM_CREATE: hDC = GetDC(hWnd) ;
hRC = wglCreateContext (hDC) ; wglMakeCurrent (hDC, hRC); break;
case WM_DESTROY:
wglMakeCurrent (hDC, NULL) ; wglDeleteContext (hRC) ;
PostQuitMessage (0) ; break;
Rysowanie w oknie w dalszym ciągu odbywa się w procedurze obsługi komunikatu WM_PAINT, z tym że tym razem zawiera polecenia rysunkowe OpenGL. W procedurze obsługi tego komunikatu możesz już zrezygnować z sekwencji BeginPaint/ EndPaint. (Te funkcje czyściły okno, ukrywały punkt wstawiania na czas rysowania oraz zatwierdzały obszar roboczy okna po rysowaniu). W OpenGL musisz jedynie zatwierdzić obszar roboczy okna, aby w dalszym ciągu móc otrzymywać komunikaty WM_PAINT. Oto szkielet procedury obsługi komunikatu WM_PAINT:
case WM_PAINT:
// Wywołanie kodu rysunkowego OpenGL RenderScene () ;
108________________________________Część l * Wprowadzenie do OpenGL
// Zatwierdzenie odmalowanego obszaru ValidateRect(hWnd,NULL); > ^ break;
Sztuczki programistyczne
W dalszym ciągu, po narysowaniu sceny przez OpenGL, możesz używać do rysowania poleceń GDI. Według dokumentacji Microsoftu, takie działanie jest w pełni obsługiwane, z wyjątkiem okien o podwójnym buforowaniu. Możesz jednak używać wywołań GDI także w oknach z podwójnym buforowaniem - jeśli tylko wywołujesz funkcje GDI już po przerzuceniu buforów. W rzeczywistości nie są obsługiwane wywołania GDI działające na tylnym buforze podwójnie buforowanych okien. Najlepiej unikać takich wywołań, gdyż głównym przeznaczeniem podwójnego buforowania jest zapobieganie migotaniu oraz płynna aktualizacja ekranu.
Przygotowanie okna dla OpenGL
W tym momencie może się niecierpliwisz chcąc szybko zabrać się za pisanie programu, w którym użyjesz poznanego kodu i funkcji z poprzedniego rozdziału, umieszczając je w procedurze obsługi komunikatu WM_PAINT. Jednak poczekaj jeszcze moment. Musisz poznać jeszcze dwa ważne wstępne kroki, zanim będziesz mógł skorzystać z kontekstu renderowania.
Style okien
Aby OpenGL mógł rysować w oknie, okno musi zostać utworzone z ustawionymi stylami WS_CLIPCHILDREN oraz WS_CLIPSIBLINGS, natomiast nie może zawierać stylu CS_PARENTDC. Powodem tego jest fakt, że kontekst renderowania nadaje się jedynie do rysowania w oknach, dla których został stworzony (określonych przez kontekst urządzenia w funkcji wglCreateContext) lub w oknach o dokładnie takim samym formacie pikseli. Style WS_CLIPCHILDREN oraz WS_CLIPSIBLINGS zabezpieczają funkcję rysunkową przed próbą aktualizacji wszelkich okien potomnych. Styl CS_PARENTDC (powodujący, że okno dziedziczy kontekst urządzenia swojego okna nadrzędnego) jest niedozwolony, ponieważ kontekst renderowania może być powiązany z tylko jednym kontekstem urządzenia i oknem. Jeśli te style nie zostaną określone, nie będziesz mógł ustawić formatu pikseli okna - ostatniego szczegółu, jakim musimy się zająć przed przejściem do naszego pierwszego programu OpenGL w Windows.
Formaty pikseli
Rysowanie w oknie przez OpenGL wymaga także ustawienia formatu pikseli. Podobnie jak kontekst renderowania, format pikseli nie jest w rzeczywistości częścią OpenGL ja-
— Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle_________________109
ko takiego. Stanowi raczej rozszerzenie Win32 API (a konkretnie GDI) przeznaczone do wsparcia funkcjonowania OpenGL. Format pikseli ustala właściwości kontekstu ren-derowania OpenGL, takie jak ilość kolorów i głębokość bufora, a także to, czy okno jest podwójnie buforowane. Zanim będziesz mógł użyć kontekstu urządzenia do stworzenia kontekstu renderowania, musisz ustawić dla tego kontekstu urządzenia format pikseli. Oto dwie funkcje, których będziesz potrzebował:
int ChoosePixelFormat(HDC hDC, PIXELFORMATDESCRIPTOR *ppfd)
BOOL SetPixelFormat(HDC hDC, int iPixelFormat, PIXELFORMATDESCRIPTOR
0*ppfd)
Ustawienie formatu pikseli przebiega w trzech krokach. Po pierwsze, wypełniasz strukturę PIXELFORMATDESCRIPTOR zgodnie z charakterystyką i zachowaniem okna, jakie chcesz osiągnąć (poznamy je za chwilę). Następnie przekazujesz tę strukturę funkcji ChoosePixelFormat. Funkcja ChoosePixelFormat zwraca indeks (będący liczbą całkowitą) formatu pikseli dostępnego dla wskazanego kontekstu urządzenia. Na koniec ten indeks jest przekazywany funkcji SetPixelFormat. Cała sekwencja wygląda mniej więcej tak:
PIXELFORMATDESCRIPTOR pixelFormat; int nFormat!ndex; HDC hDC;
// inicjowanie struktury pixelFormat;
nFormat!ndex = ChoosePixelFormat(hDC, spixelFormat); SetPixelFormat(hDC, nPixelFormat, spixelFormat);
Funkcja ChoosePixelFormat próbuje dopasować obsługiwany format pikseli do infor-
i macji otrzymanych w strukturze PIXELFORMATDESCRIPTOR. Zwracany indeks sta-
1 nowi identyfikator formatu pikseli. Na przykład, możesz zażądać formatu pikseli
! posiadającego 16 milionów kolorów, lecz sprzęt obsługuje jedynie 256 kolorów jedno
cześnie. W takim przypadku format pikseli będzie najbliższym z możliwych - w tym
; przypadku formatem pikseli z 256 kolorami. Ten indeks jest przekazywany funkcji
SetPixelFormat.
Dokładny opis struktury PIXELFORMATDESCRIPTOR znajdziesz w sekcji podręcznika na końcu rozdziału, w punkcie dotyczącym funkcji DescribePixelFormat. Listing 4.1 przedstawia funkcję z przykładowego programu GLRECT, w której następuje wypełnienie tej struktury, a następnie ustalenie formatu pikseli dla kontekstu urządzenia.
Listing 4.1. Funkcja ustalająca format pikseli dla danego kontekstu urządzenia_______________
// Wybranie formatu pikseli dla danego kontekstu urządzenia void SetDCPixelFormat(HDC hDC)
{
int nPixelFormat;
Static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIKELFORMATDESCRIPTOR), // Rozmiar tej struktury
l, // Wersja struktury
PFD_DRAW_TO_WINDOW | // Rysowanie w oknie (nie na
// bitmapie)
110________________________________Część l » Wprowadzenie do OpenGL
PFD_SUPPORT_OPENGL | // Obsługa wywołań OpenGL
// w tym oknie
PFD_DOUBLEBOFFER, // Tryb podwójnego buforowania
PFD_TYPE_RGBA, // Tryb kolorów RGBA
8, // Chcemy 8-bitowego koloru
0,0,0,0,0,0, // Nieużywane przy wybieraniu
// trybu
0,0, // Nieużywane przy wybieraniu
// trybu
0,0,0,0,0, // Nieużywane przy wybieraniu
// trybu
16, // Rozmiar bufora głębokości
O, // Nieużywane przy wybieraniu
// trybu
O, / // Nieużywane przy wybieraniu
// trybu
PFD_MAIN_PLANE, // Rysowanie na głównym planie
O, // Nieużywane przy wybieraniu
// trybu
0,0,0 }; // Nieużywane przy wybieraniu
// trybu
// Wybranie formatu pikseli najbardziej zbliżonego do wskazanego
// w pfd
nPixelFormat = ChoosePixelFormat(hDC, ipfd);
// Ustawienie formatu pikseli dla kontekstu urządzenia SetPixelFormat(hDC, nPixelFormat, Spfd);
Jak widać w tym przykładzie, nie wszystkich pól struktury PIXELFORMATDESCRIP-TOR dotyczy wybieranie formatu pikseli. Tabela 4. l zawiera listę pól ustawianych przez kod z listingu 4.1. Pozostałe pola mogą w tym momencie otrzymać wartość 0.
Tabela 4.1.
Pola struktury PKELFORMA TDSCRIPTOR używane podczas żądania formatu pikseli
Pole Opis
nSize Rozmiar struktury, ustawiany na sizeof(PIXELFORMATDESCRIPTOR)
nYersion Wersja tej struktury danych, ustawiana na l.
dwFlags Znaczniki określające właściwości bufora pikseli, ustawiane na
(PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL PFD_DOUBLEBUFFER). Wskazują one, że kontekst urządzenia nie jest kontekstem bitmapy (pamięciowym), do rysowania zostanie użyty OpenGL oraz że okno powinno być podwójnie buforowane.
iPixelType Typ danych pikseli. Informuje OpenGL, aby użyło trybu RGBA lub indeksowanego trybu kolorów. Aby użyć trybu RGBA, użyj stałej PFD_TYPE_RGBA.
cColorBits Ilość bitów na kolor, w tym wypadku 24 bity. Jeśli sprzęt nie obsługuje 24-bitowego
koloru, zostanie wybrana maksymalna obsługiwana ilość bitów na kolor.
cDepthBits Ilość bitów bufora głębokości (z-bufora). Ustawiane na 32 dla maksymalnej dokładności, jednak często wystarcza 16 bitów (zajrzy] do sekcji podręcznika).
iLayerType Rodzaj warstwy. W przypadku Microsoft Windows dozwolona jest jedynie stała PFD MAIN PLANE.
GL Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle_________________111
Powrót odbijającego się kwadratu
a
W końcu mamy wystarczającą ilość informacji, aby utworzyć okno Windows używające OpenGL bez podpierania się biblioteką AUX. Program pokazany na listingu 4.2 zawiera wymagany kod Windows oraz funkcje rysujące z przykładu BOUNCE2 z rozdziału 3. Jak widać z długości kodu programu, biblioteka AUX oszczędza nam wiele wysiłku.
Funkcje RenderScene, ChangeSize i IdleFunction praktycznie zostały żywcem przeniesione z przykładu z rozdziału trzeciego i w związku z tym nie będziemy ich tutaj omawiać. Te funkcje, razem z funkcją z listingu 4.1, tworzą przykładowy program GLRECT. Rysunek 4.3 przedstawia znajomy odbijający się kwadrat. Listing 4.2 przedstawia funkcję WinMain, tworzącą okno, oraz funkcję WndProc, obsługującą poszczególne komunikaty skierowane do okna.
Rysunek 4.3.
Odbijający się kwadrat w wersji dla Windows
Listing 4.2. Program animowanego kwadratu, bez użycia biblioteki A UX___________
// Punkt wejścia wszystkich programów Windows int APIENTRY WinMain( HINSTANCE hlnstance,
HINSTANCE hPrev!nstance, LPSTR IpCmdLine, int nCmdShow) {
MSG msg; // Struktura komunikatu
WNDCLASS we; // Struktura klasy okna
HWND hWnd; // Uchwyt głównego okna
// Określenie klasy okna
we.style = CS_HREDRAW l CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
we.hlnstance = hlnstance;
wc.hlcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC ARROW);
112________________________________Część l » Wprowadzenie do OpenGL
// W przypadku okna OpenGL nie potrzeba pędzla tła wc.hbrBackground = NULL;
wc.lpszMenuName = NULL; wc.lpszClassName = IpszAppName;
// Zarejestrowanie klasy okna if(RegisterClasst&wc) == 0) return FALSE;
// Utworzenie głównego okna aplikacji hWnd = CreateWindow(
IpszAppName,
IpszAppName,
// OpenGL wymaga WS_CLIPCHILDREN i WS_CLIPSIBLINGS
ws_overlappedwindow | ws_clipchildren | ows_clipsiblings,
// Położenie i wymiary okna
100, 100,
250, 250,
NULL,
NULL,
hlnstance,
NULL);
// Wyjście, jeśli nie powiodło się utworzenie okna ifthWnd == NULL) return FALSE;
// Wyświetlenie okna ShowWindow(hWnd,SW_SHOW); UpdateWindow(hWnd);
// Przetwarzanie komunikatów aż do momentu zakończenia
// działania aplikacji
while( GetMessage(smsg, NULL, O, 0))
{
TranslateMessage(smsgi;
DispatchMessage(imsg); }
return msg.wParam; }
// Procedura okna, która obsługuje wszystkie komunikaty skierowane do
// tego okna
LRESULT CALLBACK WndProc( HWND hWnd,
UINT message, WPARAM wParam, LPARAM IParam) {
static HGLRC hRC; // Stały kontekst renderowania static HDC hDC; // Prywatny kontekst urządzenia GDI
Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle_________________113
switch (message)
{
// Utworzenie okna, przygotowanie dla OpenGL
case WM_CREATE:
// Przechowanie kontekstu urządzenia
hDC = GetDC(hWnd);
// Wybranie formatu pikseli SetDCPixelFormat(hDC);
// Utworzenie kontekstu renderowania i uczynienie go
// bieżącym
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC, hRC);
// Utworzenie timera odpalanego co milisekundę
SetTimer(hWnd,101,l,NULL);
break;
// Okno jest niszczone, więc robimy porządki case WM_DESTROY:
// Usunięcie utworzonego timera
KillTimer(hWnd,101);
// Odłożenie bieżącego kontekstu renderowania i usunięcie
// go
wglMakeCurrent(hDC,NULL);
wglDeleteContext(hRC);
// Poinformowanie aplikacji o zakończeniu działania PostQuitMessage(0); break;
// Zmieniają się wymiary okna case WM_SIZE:
// Wywołanie naszej funkcji modyfikującej bryłę obcinania
// i widok
ChangeSize(LOWORD(IParam), HIWORD(IParam));
break;
// Timer, który przesuwa i odbija prostokąt, po prostuO // wywołujemynaszą poprzednią funkcję Onldle, a następnie // unieważniamy okno,aby zostało odrysowane case WMJTIMER:
{
IdleFunction();
InvalidateRect(hWnd,NULL,FALSE);
}
break;
// Funkcja rysująca. Ten komunikat jest wysyłany przez Windows // za każdym razem, gdy okno wymaga odświeżenia case WM_PAINT:
(
// Wywołanie kodu rysunkowego OpenGL
RenderScene();
// Wywołanie funkcji przerzucającej bufory SwapBuffers(hDC);
Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle_________________115
LION). Gdy funkcja WndProc otrzyma komunikat WM_TIMER, wykonywany jest poniższy kod:
case WMJTIMER: { IdleFunctionO ;
IiwalidateRect (hWnd, NULL, FALSE) ;
}
break;
Funkcja IdleFunction jest identyczna z funkcją z przykładu BOUNCE2, z jednym wyjątkiem: nie zawiera wywołania funkcji RenderScene(). Zamiast niej wywoływana jest funkcja InvalidateRect, powodująca wysłanie przez system komunikatu WM_PAINT do okna, czyli w efekcie odmalowanie jego zawartości.
Światła, kamera, akcja!
Wszystko już jest na swoim miejscu, nadszedł więc czas na prawdziwe działania. Kod OpenGL przeznaczony do rysowania sceny jest wywoływany z wnętrza procedury obsługi komunikatu WM_PAINT. Procedura wywołuje funkcję RenderScene (także zapożyczoną z przykładu BOUNCE2), przerzuca zawartość buforów oraz zatwierdza obszar roboczy okna (w celu zablokowania nadmiarowych komunikatów WM_PAINT).
case WM_PAINT: {
// Wywołanie kodu rysunkowego OpenGL RenderScene();
// Wywołanie funkcji przerzucającej bufory SwapBuffers(hDC);
// Zatwierdzenie odmalowanego obszaru
YalidateRect(hWnd,NULL);
}
break;
Występuje tu nowa funkcja GDI Windows, SwapBuffers. Ta funkcja pełni tę samą rolę, co auxSwapBuffers - w oknie z podwójnym buforowaniem przerzuca do okna zawartość niewidocznego (tylnego) bufora. Jednym parametrem funkcji jest kontekst urządzenia. Zwróć uwagę, że ten kontekst urządzenia musi posiadać format pikseli z ustawionym znacznikiem PFD_DOUBLEBUFFER; w przeciwnym razie działanie funkcji się nie powiedzie.
I już! W tym momencie posiadasz szkielet kodu, w który możesz wstawić własną, dowolną procedurę renderującą OpenGL. Będzie ona korzystała z prawdziwego okna Windows, z wszystkimi jego cechami (skalowaniem, przenoszeniem, zamykaniem itd.). Co więcej, możesz oczywiście użyć tego kodu do utworzenia okna OpenGL stanowiącego część rozbudowanej aplikacji, zawierającej inne okna, menu itd.
116 ____ ____ ____ ___ ____ Część l •» Wprowadzenie do OpenGL
Brakujący kod obsługi palety
Gdy porównasz zamieszczony w książce kod programu GLRECT z kodem programu na dołączonej do książki płytce CD-ROM, w tym drugim zauważysz procedury obsługi dwóch dodatkowych komunikatów. Te dwa komunikaty: WM_QUERYPALETTE i WM_PALETTECHANGED obsługują odwzorowanie palety Windows. Kolejna funkcja, GetOpenGL-Palette, tworzy dla nas paletę kolorów. Palety są złem koniecznym, jeśli mamy zamiar korzystać z kart graficznych potrafiących wyświetlić maksymalnie 256 kolorów. Bez tego kodu moglibyśmy nie otrzymać kolorów o które poprosilibyśmy funkcją glColor, zaś w przypadku pewnych kolorów, nie otrzymalibyśmy nawet ich przybliżenia. Palety i kolory w Windows stanowią ważny temat, którym zajmiemy się szczegółowo w rozdziale 8. To kolejne irytujące zagadnienie, którego roztrząsania starała się nam oszczędzić biblioteka AUX!
Podsumowanie
Po przeczytaniu tego rozdziału powinieneś zdawać sobie sprawę, o ilu uciążliwych szczegółach nie musimy pamiętać korzystając z biblioteki AUX. Poznałeś koncepcję kontekstów renderowania, wprowadzoną do Windows GDI po to, aby OpenGL wiedziało, w którym oknie ma wykonywać operacje graficzne. Wiesz już, jak wybór i ustawienie formatu pikseli przygotowuje kontekst urządzenia przed wykorzystaniem go do stworzenia kontekstu renderowania. Oprócz tego dowiedziałeś się, na które komunikaty okienkowe i w jaki sposób powinien odpowiadać program OpenGL w Windows.
W poniższej sekcji podręcznika znajdziesz usystematyzowane informacje o funkcjach opisywanych w tym rozdziale oraz dodatkowe informacje o funkcjach jeszcze nie omawianych, gdyż to wymagałoby poznania pewnych zagadnień i koncepcji, które jeszcze nie były poruszane. Przykłady zastosowania tych funkcji znajdziesz na płytce CD-ROM dołączonej do książki. Zachęcamy do samodzielnej analizy i modyfikowania tych przykładów.
Podręcznik
ChoosePixel Format
Przeznaczenie Wybiera format pikseli najbardziej zbliżony do formatu zażądanego
w strukturze PIXELFORMATDESCRIPTOR, dostępny dla danego
kontekstu urządzenia.
Plik nagłówkowy <wingdi.h>
117
Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle
Składnia Opis
int ChoosePixelFormat(HDC hDC, const PIXELFORMATDESCRIPTOR *ppfd);
Ta funkcja jest używana do wyznaczenia najlepszego formatu pikseli dostępnego dla danego kontekstu urządzenia. Wybór opiera się na charakterystyce przekazanej w strukturze PIXELFORMATDESCRIPTOR. Otrzymany indeks formatu jest przekazywany funkcji SetPixelFormat.
Parametry hDC
PPfd
Zwracana wartość
Przykład
HDC: Uchwyt kontekstu urządzenia, dla którego funkcja wyszukuje najbardziej dopasowany format pikseli.
PDCELFORMATDESCRIPTOR: Wskaźnik do struktury opisującej idealny żądany format pikseli. Niektóre pola struktury są zarezerwowane do wykorzystania w przyszłości. Pełny opis struktury PIXELFORMATDESCRIPTOR zostanie podany przy opisie funkcji DescribePixelFormat. Tutaj opiszemy jedynie te pola, które są wykorzystywane przy wyborze formatu pikseli:
nSize WORD: Rozmiar struktury, zwykle ustawiany na sizeof(PIXELFORMATDESCRIPTOR).
nYersion WORD: Numer wersji struktury, ustawiany na 1.
dwFlag DWORD: Zestaw znaczników określających właściwości bufora pikseli.
iPixelType BYTE: Tryb kolorów (RGBA lub indeksowany). cColorBits BYTE: Głębokość bufora kolorów. cAlphaBits BYTE: Głębokość bufora alfa. cAccumBits BYTE: Głębokość bufora akumulacji. cDepthBits BYTE: Głębokość bufora głębokości. cStencilBits BYTE: Głębokość bufora szablonu.
cAuxBuffers BYTE: Ilość buforów pomocniczych (nieobsługiwane w implementacji Microsoftu).
iLayerType BYTE: Rodzaj warstwy (nieobsługiwane w implementacji Microsoftu).
Indeks formatu pikseli najbardziej zbliżonego do przekazanego formatu lub wartość zero, jeśli nie powiodło się wyszukanie zbliżonego formatu.
Kod przykładu GLRECT w tym rozdziale demonstruje wybór formatu pikseli:
int nPixelFormat;
static PIKELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR),// Rozmiar tej
struktury
l, // Wersja struktury
118
Część l * Wprowadzenie do OpenGL
Patrz także
// Wybranie formatu pikseli najbardziej zbliżonego
// do wskazanego w pfd
nPixelFormat = ChoosePixelFormat(hDC, &pfd);
// Ustawienie formatu pikseli dla kontekstu urządzenia SetPixelFormat(hDC, nPixelFormat, &pfd);
DescribePixelFormat, GetPixelFormat, SetPixelFormat
DescribePixelFormat
Przeznaczenie Plik nagłówkowy Składnia
Opis
Zwraca szczegółowe informacje o formacie pikseli. <wingdi.h>
int DescribePixelFormat(HDC hDC, int iPixelFormat, UINT nBytes, LPPIXELFORMATDESCRIPTOR ppfd);
Ta funkcja wypełnia strukturę PIXELFORMATDESCRIPTOR informacjami o formacie pikseli określonym dla danego kontekstu urządzenia. Zwraca także maksymalny indeks formatu pikseli dla danego kontekstu urządzenia. Jeśli ppfd ma wartość NULL, funkcja i tak zwraca maksymalny numer indeksu formatu pikseli dla danego kontekstu urządzenia. Niektóre pola struktury PIXELFORMATDESCRIPTOR nie są obsługiwane w ogólnej implementacji OpenGL Microsoftu, mogą jednak być obsługiwane przez poszczególnych producentów sprzętu.
Parametry hDC
tPixelFormat nBytes
ppfd
HDC: Uchwyt kontekstu urządzenia zawierającego formaty pikseli. int: Indeks formatu pikseli, o którym chcemy otrzymać informacje.
UINT: Rozmiar struktury wskazywanej przez ppfd. Jeśli ten parametr ma wartość zero, dane nie będą kopiowane do bufora. Powinien być ustawiony na sizeof(PIXELFORMATDESCRIPTOR).
LPPIXELFORMATDESCRIPTOR: Wskaźnik do struktury PIXELFORMATDESCRIPTOR, która zostanie wypełniona szczegółowymi informacjami o danym formacie pikseli. Struktura PIXELFORMATDESCRIPTOR jest zdefiniowana następująco:
typedef struct tagPIKELFORMATDESCRIPTOR { // pfd WORD nSize; WORD nVersion; DWORD dwFlags; BYTE iPixelType; BYTE cColorBits; BYTE cRedBits; BYTE cRedShift; BYTE cGreenBits; BYTE cGreenShift;
Rozdział 4. » OpenGL for Windows: OpenGL + Win32 = Wiggle_________________119
BYTE CBlueBits; BYTE cBlueShift; BYTE cAlphaBits; BYTE cAlphaShift; BYTE cAccumBits; BYTE cAccumRedBits; BYTE cAccumGreenBits; BYTE cAccumBlueBits; BYTE cAccumAlphaBits; BYTE cDepthBits; BYTE cStencilBits; BYTE cAuxBuffers; BYTE iLayerType; BYTE bReserved; DWORD dwLayerMask; DWORD dwVisibleMask; DWORD dwDamageMask; } PIXELFORMATDESCRIPTOR;
nSize Zawiera rozmiar struktury. Zawsze powinno być ustawione na sizeof(PIXELFORMATDESCRIPTOR).
nYersion Zawiera numer wersji struktury. Zawsze powinno być ustawione na l.
dwFlags zawiera zestaw znaczników bitowych (tabela 4.2) opisujących właściwości formatu pikseli. Z pewnymi wyjątkami, te znaczniki nie wykluczają się wzajemnie.
iPixelType określa typ danych pikseli, a dokładniej, określa tryb wyboru koloru. Może to być jedna z wartości z tabeli 4.3.
cColorBits ilość bitów koloru używanych w buforze koloru,
z wyłączeniem bitów alfa w trybie RGBA. W trybie indeksowanym
określa ilość bitów indeksu koloru.
cRedBits określa ilość bitów czerwieni w buforze koloru w trybie RGBA.
cRedShift określa przesunięcie bitów czerwieni w buforze koloru w trybie RGBA.*
cGreenBits określa ilość bitów zieleni w buforze koloru w trybie RGBA.
cGreenShift określa przesunięcie bitów zieleni w buforze koloru w trybie RGBA.*
cBlueBits określa ilość bitów błękitu w buforze koloru w trybie RGBA.
cBlueShift określa przesunięcie bitów błękitu w buforze koloru w trybie RGBA.*
cAlphaBits określa ilość bitów kanału alfa w buforze koloru w trybie RGBA. Nieobsługiwane w implementacji Microsoftu.
cAlphaShift określa przesunięcie bitów kanału alfa w buforze koloru w trybie RGBA. Nieobsługiwane w implementacji Microsoftu.
cAccumBits to łączna głębokość (ilość bitów) bufora akumulacji. Patrz rozdział 15.
120
Część l « Wprowadzenie do OpenGL
cAccumRedBits to ilość bitów czerwieni w buforze akumulacji. cAccumGreenBits to ilość bitów zieleni w buforze akumulacji. cAccumBlueBits to ilość bitów błękitu w buforze akumulacji. cAccumAlphaBits to ilość bitów bufora alfa w buforze akumulacji. cDepthBits określa głębokość bufora głębokości. Patrz rozdział 15. cStencilBits określa głębokość bufora szablonu. Patrz rozdział 15.
cAuxBuffers określa ilość buforów pomocniczych. Nieobsługiwane w implementacji Microsoftu.
iLayerType określa rodzaj warstwy. Wartości dostępne dla tego pola zawiera tabela 4.4, lecz w implementacji Microsoftu dostępna jest jedynie wartość PFD_MAIN_PLANE.
bResenredJest zarezerwowane i nie powinno być modyfikowane.
dwLayerMask jest używane w powiązaniu z dwYisibleMask w celu sprawdzenia, czy jedna warstwa nachodzi na inną. W bieżącej implementacji Microsoftu warstwy nie są obsługiwane.
dwYisibleMask jest używane w powiązaniu z dwYisibleMask w celu sprawdzenia, czy jedna warstwa nachodzi na inną. W bieżącej implementacji Microsoftu warstwy nie są obsługiwane.
dwDamageMask wskazuje, czy więcej niż jeden format pikseli korzysta z tego samego bufora ramki. Jeśli iloczyn bitowy (AND) pól dwDamageMask dwóch formatów pikseli jest różny od zera, to korzystają one z tego samego bufora ramki.
*W rozdziale 8 wyjaśniamy, jak odnosi się to do urządzeń z paletą kolorów.
Zwracana wartość Maksymalna ilość formatów pikseli obsługiwanych przez dany kontekst urządzenia lub zero w przypadku błędu.
Przykład Kod przykładu GLRECT na płytce CD-ROM dołączonej do książki.
Sprawdzamy w nim, czy kontekst urządzenia wymaga zdefiniowania
palety:
PIXELFORMATDESCRIPTOR pfd; // Deskryptor formatu pikseli int nPixelFormat; // Indeks formatu pikseli
// Pobranie indeksu formatu pikseli oraz
// deskryptora formatu pikseli
nPixelFormat = GetPixelFormat(hDC);
DescribePixelFormat(hDC, nPixelFormat, sizeof(PIKELFORMATDESCRIPTOR) ,
121
Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle
// Czy ten format pikseli wymaga palety? Jeśli nie, po prostu nie // twórz palety i zwróć wartość NULL if(!(pfd.dwFlags i PFD_NEED_PALETTE)) return NULL;
// Stworzenie palety
Patrz także
ChoosePixelFormat, GetPixelFormat, SetPixelFormat
Tabela 4.2.
Znaczniki pola dwFlags struktury PIXELFORMATDESCR1PTOR
Opis
Znacznik
PFD_DRAW_TO_WINDOW
PFD_DRAW_TO_BITMAP PFD_SUPPORT_GDI
PFD_SUPPORT_OPENGL PFD_GENERIC_FORMAT
PFD_NEED_PALETTE PFD_NEED_SYSTEM_PALETTE
PFD_DOUBLEBUFFER PFD_STEREO
PFD_DOUBLE_BUFFER_DONTCARE PFD STEREO DONTCARE
Bufor jest używany do rysowania w oknie lub na powierzchni urządzenia, takiego jak drukarka.
Bufor jest używany do rysowania na bitmapie w pamięci.
Bufor obsługuje rysowanie GDI. Ten znacznik oraz znacznik PFD_DOUBLEBUFFER wykluczają się wzajemnie.
Bufor obsługuje rysowanie OpenGL.
Format pikseli jest ogólną implementacją (obsługiwaną przez emulację GDI). Jeśli ten znacznik nie jest ustawiony, format pikseli jest obsługiwany przez sprzęt lub sterownik urządzenia.
Format pikseli wymaga użycia palety logicznej.
Używane w szczególnych implementacjach obsługujących tylko jedną paletę sprzętową. Ten znacznik wymusza jednoznaczne odwzorowanie palety sprzętowej na paletę logiczną.
Ten format pikseli korzysta z podwójnego buforowania. Ten znacznik i znaczniki PFD_SUPPORT GDI wykluczają się wzajemnie.
Bufor jest stereoskopowy. Jest to analogia do przedniego i tylnego bufora w podwójnym buforowaniu, z tym że w tym przypadku mamy do czynienia z buforem lewym i prawym. Nieobsługiwane w implementacji OpenGL Microsoftu.
Podczas wyboru formatu pikseli pozwalamy na wybranie pojedynczego lub podwójnego buforowania, bez preferencji.
Podczas wyboru formatu pikseli pozwalamy na wybranie trybu stereoskopowego lub zwykłego, bez preferencji.
122________________________________Część l » Wprowadzenie do OpenGL
Tabela 4.3.
Znaczniki pola iPixelType
Znacznik Opis
PFD_TYPE_RGBA Tryb koloru RGBA. Kolor każdego piksela jest określany przez
podanie składowej czerwonej, zielonej, niebieskiej i alfa.
PFD_TYPE_COLORINDEX Indeksowany tryb koloru. Kolor każdego piksela jest określany
przez podanie indeksu w palecie (tablicy kolorów).
Tabela 4.4.
Znaczniki pola iLayerType
Znacznik Opis
PFD_MAIN_PLANE Warstwa jest głównym planem.
PFD_O YERLA Y_PL ANE Warstwa j est nakładką.
PFD_UNDERLAY_PLANE Warstwa jest podkładką.
GetPixel Format______________________
Przeznaczenie Zwraca indeks formatu pikseli aktualnie wybranego w danym kontekście
urządzenia.
Plik nagłówkowy <wingdi.h>
Składnia int GetPixelFormat(HDC hDC);
Opis Ta funkcja zwraca format pikseli wybrany w danym kontekście
urządzenia. Zwracana wartość jest indeksem formatu pikseli,
liczonym od l.
Parametry
hDC HDC: Uchwyt kontekstu urządzenia.
Zwracana wartość Indeks formatu pikseli wybranego w danym kontekście urządzenia lub wartość zero w przypadku błędu.
Przykład Spójrz na przykład dla funkcji DescribePixelFormat.
Patrz także DescribePixelFormat, ChoosePixelFormat, SetPixelFormat
Set Pixel Format______________________
Przeznaczenie Ustala format pikseli dla danego kontekstu urządzenia.
Plik nagłówkowy <wingdi.h>
Składnia BOOL SetPixelFormat(HDC hDC, int nPixelFormat, const
PIXELFORMATDESCR1PTOR *ppfd);
123
Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle
Opis
Ta ftmkcja ustawia format pikseli dla danego kontekstu urządzenia. Gdy format pikseli zostanie ustawiony dla danego kontekstu urządzenia, nie można go już modyfikować, Ta funkcja musi być wywołana przed utworzeniem kontekstu renderowania OpenGL dla tego urządzenia.
Parametry hDC
nPixelFormat PPfd
Zwracana wartość
Przykład Patrz także
HDC: Uchwyt kontekstu urządzenia, dla którego chcemy ustawić format pikseli.
int: Indeks formatu pikseli do ustawienia.
LPPDCELFORMATDESCRIPTOR: Wskaźnik do struktury PIXELFORMATDESCRIPTOR, zawierającej deskryptor logicznego formatu pikseli. Ta struktura jest używana wewnętrznie do zapisu specyfikacji logicznego formatu pikseli. Zawartość przekazywanej struktury nie wpływa na działanie operacji.
TRUE, jeśli podany format pikseli został ustawiony dla danego kontekstu urządzenia, a FALSE w przypadku błędu.
Spójrz na przykład do funkcji ChoosePixelFormat. DescribePixelFormat, GetPixelFormat, SetPixelFormat
SwapBuffers
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Szybko kopiuje zawartość tylnego bufora do przedniego bufora (widocznej powierzchni okna).
<wingdi.h>
BOOL SwapBuffers(HDC hDC);
Gdy jest wybrane podwójne buforowanie, okno posiada przedni bufor (widoczny) oraz tylny bufor (niewidoczny). Polecenia rysunkowe są wykonywane w tylnym buforze. Ta funkcja służy do skopiowania zawartości niewidocznego tylnego bufora do wyświetlanego przedniego bufora, w celu uzyskania płynnego rysowania scenki lub płynnej animacji. Zwróć uwagę, że zawartości bufora nie są wymieniane. Po zakończeniu tej operacji zawartość tylnego bufora jest niezdefiniowana.
Parametry hDC
HDC: Uchwyt kontekstu urządzenia okna, które posiada przedni i tylny bufor.
Zwracana wartość TRUE, jeśli bufory zostały przerzucone.
Przykład
Poniższy przykład przedstawia typowy kod procedury obsługi komunikatu WM_PAINT. Wywołujemy w nim kod renderowania i jeśli pracujemy z podwójnym buforowaniem, przerzucamy zawartość tylnego bufora. Ten kod występuje także w przykładzie GLRECT w tym rozdziale.
124
Część l « Wprowadzenie do OpenGL
// Funkcja rysująca. Ten komunikat jest wysyłany // przez Windows za każdym razem, gdy // okno wymaga odświeżenia case WM_PAINT:
// Wywołanie kodu rysunkowego OpenGL RenderScene ();
// Wywołanie funkcji przerzucającej bufory SwapBuffers(hDC);
// Zatwierdzenie odmalowanego obszaru ValidateRect(hWnd,NULL);
} break;
glDrawBuffer
Patrz także
wglCreateContext
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Tworzy kontekst renderowania odpowiedni do rysowania w podanym kontekście urządzenia.
<wingdi.h>
HGLRC wglCreateContext(HDC hDC);
Tworzy kontekst renderowania OpenGL odpowiedni dla danego kontekstu urządzenia Windows. Format pikseli kontekstu urządzenia powinien zostać wybrany przed tworzeniem kontekstu renderowania. Gdy aplikacja zakończy korzystanie z kontekstu renderowania, powinna wywołać funkcję wglDeleteContext.
Parametry hDC
Zwracana wartość Przykład
HDC: Uchwyt kontekstu urządzenia okna, w którym odbędzie się rysowanie poprzez nowy kontekst renderowania.
Uchwyt nowego kontekstu renderowania lub wartość NULL w przypadku błędu.
Poniższy przykład przedstawia początek procedury obsługi komunikatu WM_CREATE. Pobieramy w nim kontekst urządzenia dla bieżącego okna, wybieramy format pikseli, a następnie tworzymy kontekst renderowania i czynimy go bieżącym.
case WM_CREATE:
// Przechowanie kontekstu urządzenia hDC = GetDC(hWnd);
// Wybranie formatu pikseli
SetDCPixelFormat(hDC);
// Utworzenie kontekstu renderowania
// i uczynienie go bieżącym
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC, hRC);
125
Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle
Patrz także
wglDeleteContext, wglGetCurrentContext, wglMakeCurrent
wglDeleteContext
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
hglrc Zwracana wartość
Przykład
Usuwa niepotrzebny już kontekst renderowania.
<wingdi.h>
BOOL wglDeleteContext(HGLRC hglrc);
Usuwa kontekst renderowania OpenGL. Zwalniana jest pamięć i zasoby zajmowane przez ten kontekst.
HGLRC: Uchwyt kontekstu renderowania, który ma zostać usunięty.
TRUE, jeśli kontekst renderowania został usunięty, lub FALSE, jeśli wystąpił błąd. Błąd występuje na przykład wtedy, gdy w jednym wątku próbujemy usunąć bieżący kontekst renderowania innego wątku.
Poniższy przykład przedstawia początek procedury obsługi komunikatu WM_DESTROY. Zakładając, że kontekst renderowania został utworzony w momencie tworzenia okna, możemy w tym miejscu usunąć ten kontekst. Zanim usuniesz kontekst, musisz uczynić go niebieżącym.
case WM_DESTROY:
// Odłożenie bieżącego kontekstu renderowania // i usunięcie go wglMakeCurrent(hDC,NULL); wglDeleteContext(hRC);
// Poinformowanie aplikacji PostOuitMessage(0); break;
o zakończeniu działania
Patrz także
wglCreateContext, wglGetCurrentContext, wglMakeCurrent
wglGetCurrentContext
Przeznaczenie Plik nagłówkowy Składnia Opis
Zwracana wartość
Zwraca uchwyt bieżącego kontekstu renderowania należącego do wątku.
<wingdi.h>
HGLRC wglGetCurrentContext(void);
Każdy wątek aplikacji może mieć własny bieżący kontekst renderowania. Ta funkcja może zostać użyta do sprawdzenia, który kontekst renderowania jest kontekstem bieżącym dla wątku.
Jeśli wątek posiada bieżący kontekst renderowania, funkcja zwraca uchwyt tego kontekstu. W przeciwnym wypadku zwraca wartość NULL.
126
Część l •» Wprowadzenie do OpenGL
Przykład Patrz także
Zajrzyj do przykładowego programu GLTHREAD na płytce CD-ROM, w folderze tego rozdziału.
wglCreateContext, wglDeleteContext, wglGetCurrentDC, wglMakeCurrent
wglGetCurrentDC
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Zwracana wartość
Przykład
Patrz także
Zwraca uchwyt kontekstu urządzenia powiązanego z bieżącym kontekstem renderowania OpenGL.
<wingdi.h>
HGLRC wglGetCurrentDC(void);
Ta funkcja jest używana do pobrania kontekstu uchwytu urządzenia okna, z którym powiązany jest bieżący kontekst renderowania OpenGL. Zwykle stosuje się ją w celu połączenia rysunku OpenGL z rysunkiem tworzonym przy pomocy GDI.
Jeśli wątek posiada bieżący kontekst renderowania, funkcja zwraca uchwyt kontekstu urządzenia powiązanego z tym kontekstem renderowania. W przeciwnym wypadku zwraca wartość NULL.
Zajrzyj do przykładowego programu GLTHREAD na płytce CD-ROM, w folderze tego rozdziału.
wglGetCurrentContext
wglGetProcAddress
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Zwraca adres funkcji rozszerzenia OpenGL, której można użyć w danym kontekście renderowania.
<wingdi.h>
PROC wglGetProcAddress(LPCSTR IpszProc);
Funkcje rozszerzeń to funkcje, które albo nie stanowiąjeszcze standardu OpenGL, albo zostały wprowadzone do danej implementacji OpenGL, zwykle w celu uzupełnienia tej biblioteki o elementy specyficzne dla danej platformy. Wiele rozszerzeń jest obsługiwanych w więcej niż jednej implementacji. Aby użyć tych funkcji, musisz wywołać funkcję wglGetProcAddress podając dokładną nazwę funkcji rozszerzenia. W ten sposób możesz także sprawdzić obecność danego rozszerzenia. Otrzymany adres funkcji może być różny dla różnych formatów pikseli, więc nie przechowuj go i nie próbuj używać z różnymi kontekstami renderowania, chyba że będziesz pewien, iż ich formaty pikseli są identyczne. Możesz wywołać funkcję glString(GL_EXTENSION) w celu otrzymania oddzielonego spacjami łańcucha zawierającego wszelkie dostępne rozszerzenia (szczegóły znajdziesz w rozdziale 5).
127
Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle
Parametry IpszProc
LPCSTR: Nazwa funkcji rozszerzenia. Wielkość liter i nazwa muszą dokładnie odpowiadać wielkości liter i nazwie funkcji rozszerzenia.
Zwracana wartość Jeśli funkcja rozszerzenia nie istnieje, zwracana jest wartość NULL, w przeciwnym wypadku zwracany jest adres funkcji rozszerzenia.
Przykład Poniższy przykład przedstawia sposób pobrania adresu specyficznej dla
Windows funkcji rozszerzenia glAddSwapHintRectWIN. To rozszerzenie
umożliwia przyspieszenie przerzucania buforów przez poinformowanie
OpenGL, że tylko określone regiony okna wymagają odświeżenia.
// Sprawdzenie, czy dane rozszerzenie jest obsługiwane
char *szBuffer;
szBuffer = (char*)glString(GL_EXTENSION);
// Jeśli jest obsługiwane, pobierz adres funkcji
// i wywołaj ją
if(strcmp(szBuffer,"GL_WIN_swap_hint") == 0)
{
PROC pSwapHint;
pSwapHint = wglGetProcAddress("glAddSwapHintRectWIN");
// Wywołaj tę funkcje
pSwapHint(40.Of, 40.Of, 50.Of, 50.2f); ) else (
// Jeśli nie jest obsługiwana, obsłuż to
// w inny sposób...
glGetString
Patrz także
wglMakeCurrent
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Bieżący kontekst renderowania OpenGL czyni bieżącym dla danego wątku i wiąże go z podanym kontekstem urządzenia.
<wingdi.h>
BOOL wglMakeCurrent(HDC hDC, HGLRC hglrc);
Ta funkcja czyni podany kontekst renderowania bieżącym kontekstem renderowania dla aktualnego wątku. Kontekst renderowania jest wiązany ze wskazanym kontekstem urządzenia Windows. Kontekst urządzenia nie musi być kontekstem użytym w wywołaniu funkcji wglCreateContext przy tworzeniu kontekstu renderowania, jednak jego format pikseli musi być identyczny z formatem pikseli tamtego kontekstu urządzenia, zaś oba konteksty urządzeń muszą odnosić się do tego samego urządzenia fizycznego. Przed wybraniem nowego kontekstu renderowania, wszelkie oczekujące w kolejce polecenia graficzne zostaną zrzucone do poprzedniego kontekstu renderowania. Gdy jako uchwyt kontekstu
128
Część l * Wprowadzenie do OpenGL
renderowania zostanie podana wartość NULL, żaden kontekst nie będzie kontekstem bieżącym.
Parametry hDC
hglrc Zwracana wartość
Przykład Patrz także
HDC: Uchwyt kontekstu urządzenia, który ma zostać powiązany z nowym bieżącym kontekstem renderowania.
HGLRC: Uchwyt kontekstu renderowania, który ma stać się bieżącym kontekstem aktualnego wątku.
TRUE w przypadku powodzenia, a FALSE w przypadku błędu. Jeśli wystąpi błąd, bieżący wątek nie będzie miał bieżącego kontekstu renderowania.
Spójrz na przykład przy opisie funkcji wglCreateContext.
wglCreateContext, wglDeleteContext, wglGetCurrentContext, wglGetCurrentDC
wglShareLists
Przeznaczenie Plik nagłówkowy Składnia Opis
Umożliwia kilku kontekstom korzystanie ze wspólnych list wyświetlania.
<wingdi.h>
BOOL wglShareLists(HGLRC hRCl, HGLRC hRC2);
Lista wyświetlania to lista „prekompilowanych" poleceń i funkcji OpenGL (patrz rozdział 10). Dla każdego kontekstu renderowania alokowana jest pamięć na jego listy wyświetlania. Gdy lista wyświetlania zostanie utworzona w ramach danego kontekstu renderowania, tylko ten kontekst ma dostęp do jej pamięci. Ta funkcja umożliwia kilku kontekstom renderowania dostęp do wspólnej pamięci list wyświetlania. Jest to szczególnie użyteczne wtedy, gdy w kilku wątkach lub kontekstach renderowania korzysta się z dużych list wyświetlania, gdyż daje znaczne oszczędności pamięci. Ze wspólnej pamięci list wyświetlania może korzystać dowolna liczba kontekstów renderowania; pamięć nie zostanie zwolniona aż do momentu usunięcia ostatniego kontekstu renderowania z niej korzystającego. Podczas używania wspólnej pamięci list wyświetlania należy synchronizować tworzenie i wykorzystywania list wyświetlania.
Parametry hRCl
hRC2
HGLRC: Określa kontekst renderowania, z którym będzie dzielona pamięć list wyświetlania.
HGLRC: Określa kontekst renderowania, który będzie dzielił pamięć list wyświetlania z kontekstem hRCl. Aż do momentu wywołania tej funkcji nie należy tworzyć żadnych list wyświetlania dla kontekstu hRC2.
Zwracana wartość TRUE w przypadku powodzenia, a FALSE w przypadku błędu.
do
129
Opendtozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle
16 będzie Przykład
Patrz także
cym
Zajrzyj do kartoteki symulacji czołgu/robota (\TANK) na płytce CD-ROM, wewnątrz foldera rozdziału 10. Ten program korzysta z kilku okien w celu stworzenia jednocześnie kilku widoków tej samej sceny. Aby zaoszczędzić pamięć, wszystkie konteksty renderowania tych okien korzystają ze wspólnej pamięci list wyświetlania.
gllsList, glNewList, glCallLists, glListBase, glDeleteLists, glEndList, glGenLists
wglUseFontBitmaps
Przeznaczenie
Plik nagłówkowy Składnia
Opis
Tworzy zestaw list wyświetlania bitmap dla aktualnie wybranej czcionki GDI.
<wingdi.h>
BOOL wglUseFontBitmaps(HDC hDC, DWORD dwFirst, DWORD dwCount, DWORD dwListBase);
Ta funkcja pobiera czcionkę aktualnie wybraną w kontekście urządzenia hDC i tworzy listy wyświetlania bitmap dla dwCount znaków, poczynając od dwFirst. Listy wyświetlania są tworzone w bieżącym kontekście renderowania i są identyfikowane przez numery rozpoczynające się od numeru dwListBase. Zwykle używa się tej funkcji do rysowania tekstu w podwójnie buforowanych scenach OpenGL, gdyż Windows GDI nie działa w trybie podwójnego buforowania. Ta funkcja jest używana także do nadawania etykietek obiektom OpenGL na ekranie.
Parametry hDC
dwFirst
dwCount
dwListBase Zwracana wartość Przykład
HDC: Kontekst urządzenia Windows, z którego pobierana jest definicja czcionki. Używana czcionka może zostać zmieniona przez utworzenie nowej czcionki i wybranie jej w tym kontekście urządzenia.
DWORD: Wartość ASCII pierwszego znaku czcionki, używanej do budowania list wyświetlania.
DWORD: Liczba tworzonych bitmap znaków, począwszy od znaku dwFirst.
DWORD: Wartość bazowa identyfikatorów list wyświetlania; zostanie nadana liście wyświetlania dla pierwszego znaku.
TRUE jeśli listy wyświetlania mogą zostać utworzone; FALSE w przeciwnym wypadku.
Poniższy kod demonstruje tworzenie zestawu list wyświetlania dla zestawu znaków ASCII. Ten zestaw list jest następnie używany do wypisania tekstu „OpenGL" w bieżącej pozycji rastra.
//Utworzenie sylwetek znaków na podstawie czcionki // wybraną w kontekście urządzenia
130
Część l * Wprowadzenie do OpenGL
wglUseFontBitmaps(hDC, // kontekst urządzenia
O, // pierwszy znak 255, // liczba znaków 1000); // numer bazowy listy wyświetlania
"OpenGL") ,
// Wyrysowanie napisu glListBase(1000); glPushMatrix(); glCallLists (3, GL_UNSIGNED__BYTE,
glPopMatrix();
Patrz także
wglUseFontOutlines, gllsList, glNewList, glCallLists, glListBase, glDeleteLists, glEndList, glGenLists
WglUseFontOutlines
Przeznaczenie
Plik nagłówkowy Składnia
Opis
Tworzy zestaw list wyświetlania trójwymiarowych znaków dla aktualnie wybranej czcionki GDI.
<wingdi.h>
BOOL wglUseFontOutlines(HDC hDC, DWORD dwFirst, DWORD dwCount, DWORD dwListBase, FLOAT deviation, FLOAT extrusion, int format, LPGLYPHMETRICSFLOAT Ipgmf);
Ta funkcja pobiera czcionkę TrueType aktualnie wybraną w kontekście urządzenia hDC i tworzy listy wyświetlania trójwymiarowych sylwetek dla dwCount znaków, poczynając od znaku dwFirst. Listy wyświetlania są tworzone w bieżącym kontekście renderowania i są identyfikowane przez numery rozpoczynające się od numeru dwListBase. Sylwetka może być tworzona z segmentów linii lub wielokątów; decyduje o tym parametr format. Komórka znaku używana dla czcionki ma szerokość i wysokość jednej jednostki w poziomie i w pionie. Parametr extrusion wyznacza grubość czcionki w ujemnym kierunku osi z. Parametr deviation, o wartości O lub większej, określa odchylenie łuków od oryginalnych postaci znaków czcionki. Ta funkcja działa wyłącznie z czcionkami typu TrueType. Dodatkowe dane dotyczące znaków są przekazywane w tablicy /pgw/struktur GLYPHMETRICSFLOAT.
Parametry hDC
dwFirst dwCount
HDC: Kontekst urządzenia Windows, z którego pobierana jest definicja czcionki.
DWORD: Wartość ASCII pierwszego znaku czcionki, używanej do budowania list wyświetlania.
DWORD: Liczba tworzonych znaków, począwszy od znaku dwFirst.
131
Rozdział 4. * OpenGL for Windows: OpenGL + Win32 = Wiggle
dwListBase
devaition
extrmion format
Ipgmf
DWORD: Wartość bazowa identyfikatorów list wyświetlania; zostanie nadana liście wyświetlania dla pierwszego znaku.
FLOAT: Maksymalne odchylenie łuków znaków od łuków oryginalnych.
FLOAT: Grubość czcionki; rośnie w kierunku ujemnych wartości osi z.
int: Określa, czy znaki powinny być tworzone z segmentów linii czy wielokątów. Może być jedną z poniższych wartości:
WGL_FONT_LINES Znaki będą tworzone z segmentów linii. WGL_FONT_POLYGONS Znaki będą tworzone z wielokątów.
LPGLYPHMETRICSFLOAT: Adres tablicy struktur, w których znajdą się dane metryczne znaków. Każdy element tablicy jest wypełniany danymi odnoszącymi się do listy wyświetlania danego znaku. Każda struktura jest zdefiniowana następująco:
typedef struct _GLYPHMETRICSFLOAT { // gmf
FLOAT gmfBlackBoxX;
FLOAT gmfBlackBoxY;
POINTFLOAT gmfptGlyphOrigin;
FLOAT gmfCelllncK;
FLOAT gmfCelllncY; } GLYPHMETRICSFLOAT;
Składowe:
gmfBlackBoxX gmfBlackBoxY gmfptGfyphOrigin
gmfCallIncX gmfCalllncY
Zwracana wartość
Przykład
Szerokość najmniejszego prostokąta całkowicie opisującego znak. Wysokość najmniejszego prostokąta całkowicie opisującego znak.
Współrzędne x i y lewego górnego rogu prostokąta całkowicie opisującego znak. Struktura POINTFLOAT jest zdefiniowana następująco:
typedef struct _POINTFLOAT { // ptf
FLOAT x; // Współrzędna pozioma punktu
FLOAT y; // Współrzędna pionowa punktu } POINTFLOAT;
Odstęp w poziomie od początku komórki bieżącego znaku do początku komórki następnego znaku.
Odstęp w pionie od początku komórki bieżącego znaku do początku komórki następnego znaku.
TRUE, jeśli listy wyświetlania mogą zostać utworzone; FALSE w przeciwnym wypadku.
Poniższy kod można znaleźć albo w przykładowym programie MFCGL w rozdziale 21, albo w pliku glcode.c, w przykładowym programie OWLGL w rozdziale 22. Te przykłady ilustrują, jak czcionka zdefiniowana, strukturze LOGFONT jest tworzona i wybierana w kontekście urządzenia, a następnie jest używana do utworzenia list wyświetlania reprezentujących cały zestaw znaków ASCII tej czcionki.
hDC = (HDC)pData;
hFont = CreateFontlndirect(Slogfont);
132________________________________Część l * Wprowadzenie do OpenGL
SelectObjectfhDC, hFont);
// Otworzenie list wyświetlania dla znaków od O do 255,
// z grubością 0,3 oraz domyślnym odchyleniem.
// Numerowanie list wyświetlania zaczyna się
// od 1000 (może być dowolna liczba)
wglUseFontOutlines(hDC, O, 255, 1000, O.Of,
0.3f, WGL_FONT_POLYGONS, agmf);
DeleteObject(hFont);
Patrz także wglUseFontBitmaps, gllsList, glNewList, glCallLists, glListBase,
glDeleteLists, glEndList, glGenLists
Rozdział 5.
Błędy i inne komunikaty OpenGL
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
* Odczytać kod ostatniego błędu OpenGL * glGetError
+ Konwertować kod błędu na tekstowy opis problemu * glErrorString
+ Odczytać numer wersji oraz informacji o twórcy OpenGL * glGetString, gluGetString
+ Poprawić wydajność z wykorzystaniem elementów * glHint
zależnych od implementacji
W każdym projekcie chcemy, aby tworzona aplikacja była niezawodnym i dobrze działającym programem, poprawnie reagującym na polecenia użytkownika i posiadającym pewną elastyczność. Nie są tu wyjątkiem także programy graficzne korzystające z OpenGL. Nie mamy zamiaru zmienić tego rozdziału w kurs inżynierii oprogramowania i kontroli jakości, ale jeśli chcesz, aby twoje programy działały sprawnie, musisz brać pod uwagę także błędy i nieoczekiwane sytuacje. OpenGL dostarcza dwóch różnych metod zapewnienia poprawności kodu.
Pierwszy z mechanizmów kontrolnych OpenGL dotyczy wykrywania błędów. Gdy wystąpi błąd, musisz mieć jakiś sposób jego wychwycenia i wykrycia przyczyny. To jedyna możliwość zapewnienia, że obraz Stacji Kosmicznej Freedom nie zamieni się w obraz Stacji Kosmicznej Rozpuszczone Lody.
Drugi mechanizm OpenGL to proste rozwiązanie powszechnie występującego problemu - czegoś, czemu od czasu do czasu winien jest każdy programista, dobry i zły. Załóżmy, że wiesz, iż implementacja Microsoftu ogólnej wersji biblioteki OpenGL umożliwia rysowanie przy pomocy GDI w oknach z podwójnym buforowaniem, o ile tylko rysujesz w przednim buforze. Następnie kupujesz jedną z najnowszych kart z akceleratorem 3D, a jej producent dorzuca nowe sterowniki OpenGL. Co gorsza, przypuśćmy, że
134
Część l * Wprowadzenie do OpenGL
któryś z twoich klientów kupuje taką kartę. Czy twój kod będzie dalej działał? Czy też zniszczy rysunek i wyświetli psychodeliczną tęczę? Być może masz powody, aby korzystać z takich sztuczek optymalizacji; funkcja TextOut działa zdecydowanie szybciej niż funkcja wglUseFontBitmaps. (Oczywiście, jeśli masz taką super-hiper kartę graficzną, TextOut może nie być już najszybszym sposobem wyświetlania napisów). Prostym sposobem zabezpieczenia się przed tym rodzajem katastrof jest sprawdzenie wersji i producenta biblioteki OpenGL. Jeśli używana implementacja to ogólna implementacja Microsoftu, możesz oszukiwać do woli; w przeciwnym razie lepiej pozostań przy udokumentowanych metodach.
Podsumowując, jeśli chcesz skorzystać z możliwości specyficznych dla wersji i producenta biblioteki, powinieneś sprawdzać w programie, czy numer wersji i producent zgadzają się z tą wersją biblioteki, dla której tworzyłeś program. Nieco później omówimy wskazówki OpenGL, umożliwiające poinstruowanie systemu graficznego aby poświęcił uniwersalność na rzecz szybkości czy jakości obrazu. Właśnie taki jest zalecany sposób korzystania z optymalizacji specyficznych dla producenta karty.
Gdy dobremu programowi przydarzają się złe przygody
Wewnętrznie, OpenGL przechowuje zestaw sześciu znaczników stanu błędów. Każdy znacznik reprezentuje odmienny rodzaj błędu. Za każdym razem, gdy wystąpi błąd, ustawiany jest odpowiedni znacznik. Aby sprawdzić, czy któryś ze znaczników jest ustawiony, wywołaj funkcję glGetError:
GLenum glGetError(void);
Rysunek 5.1.
Okno dialogowe O programie opisujące biblioteki GL i CŁU, a także ostatnio wykryte błędy
Rozdziaf 5. » Błędy i inne komunikaty OpenGL__________________________135
Funkcja glGetError zwraca jedną z wartości wymienionych w tabeli 5.1, zlokalizowanej w sekcji podręcznika, przy opisie funkcji glGetError. Biblioteka GLU definiuje trzy własne błędy, ale te błędy są odwzorowywane dokładnie na dwa już istniejące znaczniki. Jeśli ustawiony jest więcej niż jeden z tych znaczników, glGetError w dalszym ciągu zwraca tylko jedną wartość. W momencie wywołania funkcji ta wartość jest zerowana, zaś kolejne wywołanie funkcji zwraca wartość kolejnego błędu lub stałą GL_ NO_ERROR. Zwykle funkcja glGetError jest więc wywoływana w pętli, aż do momentu otrzymania wartości GL_NO_ERROR.
Listing 5.1 stanowi fragment kodu z programu GLTELL, zawierający właśnie taką pętlę, odczytującą kody kolejnych błędów. Zwróć uwagę, że opis błędu jest przekazywany kontrolce w oknie dialogowym. Wynik działania programu GLTELL widzimy na rysunku 5.1.
Listing 5.1. Przykład kodu odczytującego wszystkie błędy______________________________
// Wyświetlenie ostatnich komunikatów błędu i = 0; do {
glError = glGetError ();
SetDlgItemText(hDlg,IDC_ERRORl+i,gluErrorString(glError));
i++; } while(i < 6 && glError != GL_NO_ERROR) ;
Możesz także użyć innej funkcji biblioteki GLU, gluErrorString, zwracającej opis kodu błędu:
const GLubyte* gluErrorString(GLenum errorCode);
Ta funkcja wymaga podania kodu błędu (otrzymanego od funkcji glGetError), zaś zwraca statyczny łańcuch opisujący błąd. Na przykład, kod błędu GL_ INVALID_ ENUM zwraca łańcuch
nieprawidłowe wyliczenie
Możesz być spokojny, że jeśli błąd zostanie spowodowany jakimś poleceniem lub funkcją OpenGL, to polecenie lub funkcja zostaną zignorowane. Być może OpenGL nie wykona wtedy tego, czego oczekujesz, ale w dalszym ciągu będzie działał. Jedynym wyjątkiem jest błąd GL_OUT_OF_MEMORY (lub GLU_OUT_OF_MEMORY, zresztą o tej samej wartości). Gdy wystąpi ten błąd, stan OpenGL jest niezdefiniowany - w rzeczywistości niezdefiniowany może być stan twojego programu! W przypadku tego błędu najlepiej jest posprzątać, na ile to możliwe, i zakończyć działanie programu.
Kim jestem i co potrafię?
Jak wspomniano we wprowadzeniu do tej sekcji, istnieją sytuacje, kiedy musisz wiedzieć, czy dane działanie jest dostępne w bieżącej implementacji. Jeśli wiesz, że pro-
136________________________________Część l * Wprowadzenie do OpenGL
gram działa z implementacją Microsoftu, zaś jej numer wersji jest taki sam jak numer wersji biblioteki, z którą testowałeś program, niczym niezwykłym nie będzie próba użycia jakichś sztuczek w celu poprawienia wydajności programu. Aby być pewnym, że wykorzystywane możliwości występują w komputerze, w którym działa twój program, musisz poznać sposób odpylania biblioteki OpenGL o nazwę producenta i numer wersji systemu graficznego. Zarówno biblioteka GL, jak i GLU potrafią zwrócić swój numer wersji oraz informacje o producencie.
W przypadku biblioteki GL możesz wywołać funkcję glGetString:
const GLubyte *glGetString(GLenum name);
Ta funkcja zwraca statyczny łańcuch opisujący wskazany aspekt biblioteki GL. Dozwolone parametry są podane przy opisie funkcji glGetString w sekcji podręcznika, łącznie z elementami biblioteki GL, do których się odnoszą.
Biblioteka GLU także posiada odpowiednią funkcję, gluGetString:
const GLubyte *gluGetString(GLenum name);
Ta funkcja zwraca statyczny łańcuch opisujący wskazany aspekt biblioteki GLU. Dozwolone parametry są podane przy opisie funkcji gluGetString w sekcji podręcznika, łącznie z elementami biblioteki GLU, do których się odnoszą.
Listing 5.2 przedstawia fragment kodu z przykładowego programu GLTELL, zmodyfikowanej wersji programu odbijającego się kwadratu. Tym razem uzupełniliśmy program o menu i okno dialogowe O programie. Okno O programie, pokazane na rysunku 5.1, wyświetla informacje o dostawcy i wersji obu bibliotek: GL oraz GLU. Dodatkowo umieściliśmy w kodzie błędną instrukcję, w celu utworzenia listy komunikatów błędów.
Listing 5.2. Przykład użycia funkcji glGetString i gluGetString_________________________
// glGetString demo
SetDlgItemText(hDlg,IDC_OPENGL_VENDOR,glGetString(GL_VENDOR)); SetDlgItemText(hDlg,IDC_OPENGL_RENDERER,glGetString(GL_RENDERER)); SetDlgItemText(hDlg, IDC_OPENGL_VERSION, glGetString(GL_VERSION)); SetDlgItemText(hDlg, IDC_OPENGL_EXTENSIONS, glGetString(GL_EXTENSIONS));
// gluGetString demo
SetDlgItemText(hDlg,IDC_GLU_VERSION,gluGetString(GLU_VERSION));
SetDlgItemText(hDlg,IDC_GLO_EXTENSIONS,gluGetString(GLU_EXTENSIONS));
Rozszerzenia OpenGL
Zwróć szczególną uwagę na znaczniki GL_EXTENSIONS oraz GLU_EXTENSIONS. Niektórzy producenci (łącznie z Microsoftem w ostatniej wersji OpenGL) oferują rozszerzenia OpenGL umożliwiające zastosowanie specyficznych optymalizacji lub pewnych popularnych rozszerzeń, które jeszcze nie stały się częścią standardu. Te rozszerzenia mogą znacznie poprawić wydajność twojej aplikacji. Jeśli jednak korzystasz z funkcji rozszerzeń, musisz sprawdzić, czy w systemie występują rozszerzenia
Rozdział 5. * Błędy i inne komunikaty OpenGL__________________________137
(używając GL_EXTENSIONS), a jeśli nie są obecne, musisz zaimplementować je w jakiś inny sposób.
Zwrócona lista rozszerzeń zawiera pozycje oddzielone spacjami. Musisz sam przetworzyć ten łańcuch w celu sprawdzenia obecności konkretnego rozszerzenia. Więcej informacji na temat rozszerzeń OpenGL znajdziesz w opisie funkcji wglGetProcAddress w rozdziale 4 lub w dokumentacji swojej implementacji OpenGL. Rozszerzenia w implementacji Microsoftu są omawiane i ilustrowane w dodatku A.
Udzielanie wskazówek za pomocą funkcji glHint
Wspomnieliśmy o wykorzystywaniu znanych anomalii w bibliotekach OpenGL. Możesz wykorzystywać także inne zachowanie specyficzne dla danego producenta. Możesz na przykład renderować obraz tak szybko, jak się da, w przypadku ogólnej implementacji, lecz przełączać się do trybu sprzętowego, jeśli tylko karta na to pozwala. Nawet bez korzystania z funkcji zależnych od producenta, możesz po prostu polecić bibliotece OpenGL, aby położyła większy nacisk na szybkość lub na jakość obrazu, czyli na przykład aby pomijając pewne szczegóły utworzyła obraz bardzo szybko lub przeciwnie, aby uwzględniła wszystkie detale bez względu na to, jak długo rysunek miałby być tworzony.
Funkcja glHint umożliwia wskazanie preferencji dotyczących jakości lub szybkości dla operacji różnych rodzajów. Funkcja jest zdefiniowana następująco:
void glHint(GLenum target, GLenum modę);
Parametr target umożliwia wskazanie rodzajów działania, które chcesz zmodyfikować. Te wartości, wymienione przy opisie funkcji glHint w podręczniku, dotyczą wskazówek dla mgły i ustawień antyaliasingu. Parametr modę informuje bibliotekę OpenGL, na czym bardziej ci zależy - na krótszym czasie rysowania czy na ładniejszym obrazie - lub że jest ci wszystko jedno. Przykładem może być rysowanie w małym oknie podglądu z mniejszą dokładnością w celu szybkiego otrzymania obrazu, przy pozostawieniu wyższej jakości i dokładności dla końcowego obrazu. Dozwolone wartości parametru modę są wymienione w opisie funkcji glHint w sekcji podręcznika.
W celu przeanalizowania tych ustawień dla różnych obrazów, zajrzyj do uzupełniającego przykładowego programu WINHINT na płytce CD-ROM, w folderze tego rozdziału.
Pamiętaj, że nie wszystkie implementacje korzystają z ustawień przekazanych w funkcji glHint, akceptując je bez zgłaszania błędu. Oznacza to, że twoja wersja OpenGL może ignorować pewne lub wszystkie wskazówki.
138
Część l * Wprowadzenie do OpenGL
Podsumowanie
Nawet w tym niedoskonałym świecie możemy przynajmniej wyłapywać błędy i próbować jakoś im zaradzić. Możemy także odczytywać informacje o producencie i numerze wersji biblioteki, dzięki czemu mamy możliwość korzystania ze specyficznych rozszerzeń lub wystrzegania się znanych błędów. W tym rozdziale dowiedziałeś się, jak zmagać się z tym problemami. Wiesz już, jak poinstruować OpenGL, aby poświęciło jakość obrazu na rzecz szybkości jego generowania, jednak efekt tych wskazówek także zależy od producenta i szczegółów implementacji biblioteki.
Podręcznik
gIGetError
Przeznaczenie
Zwraca informacje o ostatnim błędzie.
Składnia Opis
Plik nagłówkowy <gl.h>
GLenum glGetError(void)
OpenGL przechowuje pięć znaczników błędów, wymienionych w tabeli 5.1. Gdy wystąpi błąd, odpowiedni znacznik błędu pozostaje ustawiony aż do momentu wywołania funkcji gIGetError. Jeśli ustawionych zostanie kilka znaczników błędów jednocześnie, konieczne jest kilkakrotne wywoływanie funkcji gIGetError w celu wyzerowana kolejnych znaczników. Dobrym pomysłem jest wywoływanie tej funkcji w pętli, aż do momentu otrzymania wartości GLJNO_ERROR. Jeśli gIGetError będzie wywołana pomiędzy instrukcjami glBegin i glEnd, zostanie ustawiony znacznik błędu GL_INVALID_OPERATION.
Zwracana wartość
Jeden z kodów błędów z tabeli 5.1. We wszystkich przypadkach
z wyjątkiem GL_OUT_OF_MEMORY błędne polecenie jest ignorowane,
zaś stan zmiennych stanu OpenGL, buforów itd. nie zmienia się.
W przypadku błędu GL_OUT_OF_MEMORY, stan OpenGL jest
Przykład Patrz także
niezdefiniowany.
Patrz przykład GLTELL z listingu 5.1 glErrorString
glGetlastError
Przeznaczenie Zwraca informacje o ostatnim błędzie.
Plik nagłówkowy <gl.h>
139
Rozdział 5. * Błędy i inne komunikaty OpenGL
Składnia Opis
Zwracana wartość
Przykład Patrz także
GLenum glGetError(void)
OpenGL przechowuje sześć znaczników błędów, wymienionych w tabeli 5.1. Gdy wystąpi błąd, odpowiedni znacznik błędu pozostaje ustawiony aż do momentu wywołania funkcji glGetError. Jeśli ustawionych zostanie kilka znaczników błędów jednocześnie, konieczne jest kilkakrotne wywoływanie funkcji glGetError w celu wyzerowania kolejnych znaczników. Dobrym pomysłem jest wywoływanie tej funkcji w pętli, aż do momentu otrzymania wartości GL_NO_ERROR. Jeśli glGetError będzie wywołana pomiędzy instrukcjami glBegin i glEnd, zostanie ustawiony znacznik błędu GL_INVALID_OPERATION.
Jeden z kodów błędów z tabeli 5.1. We wszystkich przypadkach
z wyjątkiem GL_OUT_OF_MEMORY błędne polecenie jest ignorowane,
zaś stan zmiennych stanu OpenGL, buforów itd. nie zmienia się.
W przypadku błędu GL_OUT_OF_MEMORY, stan OpenGL jest
niezdefiniowany.
Patrz przykład GLTELL z listingu 5.1 glErrorString
Tabela 5.1.
Kody błędów zwracane przez funkcję glGetError
Wartość
Znaczenie
GL_N<D_ERROR
GLJNYALID ENUM
GLU_INVALID_ENUM
GL_rNVALID_VALUE
GLU_INVALID_VALUE
GL_INVALID_OPERATION
GL_STACK_OVERFLOW
GL_STACK_UNDERFLOW
GL_OUT_OF_MEMORY GLU OUT OF MEMORY
Nie wystąpił żaden błąd.
Podano niewłaściwą wartość argumentu typu wyliczeniowego.
Argument liczbowy przekroczył dozwolony zakres.
Próbowano wykonać operację niedozwoloną w bieżącym stanie. Próbowano wykonać polecenie, które powoduje przepełnienie stosu. Próbowano wykonać polecenie, które powoduje niedopełnienie stosu. Brak pamięci do wykonywania żądanej operacji.
gIGetString
Przeznaczenie Zwraca łańcuch opisujący wskazany aspekt implementacji OpenGL.
Plik nagłówkowy <gl.h>
Składnia const GLubyte *glGetString(GLenum name);
140
Część l * Wprowadzenie do OpenGL
Opis
Ta funkcja zwraca łańcuch opisujący wskazany aspekt danej implementacji OpenGL. Łańcuch jest zdefiniowany statycznie, więc zwracany adres nie może być modyfikowany.
Parametry
name
Zwracana wartość
Przykład Patrz także
GLenum: Identyfikuje aspekt implementacji OpenGL, o którym chcemy otrzymać informację. Może przyjmować jedną z poniższych wartości:
GL_VENDOR Zwraca nazwę firmy, która stworzyła daną implementację.
GL_RENDERER Zwraca nazwę renderera. Może ona się zmieniać
w zależności od konfiguracji sprzętowej. Nazwa „GDI Generic" określa
programową emulację OpenGL bez wsparcia ze strony sprzętu.
GL_VERSION Zwraca numer wersji danej implementacji.
GL_EXTENSIONS Zwraca listę obsługiwanych w tej wersji implementacji rozszerzeń. Każda pozycja listy jest oddzielona spacją.
Łańcuch znaków opisujący żądany aspekt lub wartość NULL w przypadku użycia niewłaściwego parametru.
Patrz przykład GLTELL z listingu 5.2 gluGetString
glHint
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Umożliwia programiście przekazanie wskazówek dotyczących działania biblioteki OpenGL.
void glHint(GLenum target, GLenum modę);
Pewne aspekty działania OpenGL mogą się nieco różnić w różnych implementacjach. Ta funkcja umożliwia zasugerowanie bibliotece OpenGL, aby poświęciła jakość obrazu na rzecz szybkości lub odwrotnie. Specyfikacja OpenGL nie wymaga, aby funkcja glHint dawała jakiś efekt, więc może być ignorowana w różnych implementacjach.
Parametry target
GLenum: Określa rodzaj działania, jakie ma być zmodyfikowane. Parametr może przyjmować jedną z poniższych wartości:
GL_FOG_HINT Wpływa na obliczenia związane z mgłą.
GL_LINE_SMOOTH_HINT Wpływa na rysowanie linii z antyaliasingiem.
GL_PERSPECTIVE_CORRECTION_HINT Wpływa na jakość koloru i interpolacji tekstur.
141
Rozdział 5. t Błędy i inne komunikaty OpenGL
modę
Zwracana wartość Przykład
GL_POINT_SMOOTH_HINT Wpływa na jakość punktów rysowanych z antyaliasingiem.
GL_POLYGON_SMOOTH_HINT Wpływa na jakość wielokątów rysowanych z antyaliasingiem.
GLenum: Określa pożądany rodzaj działania. Parametr może przyjmować jedną z poniższych wartości:
GL_FASTEST Żąda użycia najefektywniejszej i najszybszej metody. GL_NICEST Żąda użycia metody dającej najlepsząjakość obrazu. GL_DONT_CARE Brak preferencji co do używanej metody.
Brak
Poniższy kod został zaczerpnięty z uzupełniającego przykładu WINHINT na płytce CD-ROM, z foldera tego rozdziału. Informuje bibliotekę OpenGL, aby rysowała linie z antyaliasingiem tak szybko, jak to jest możliwe, nawet kosztem jakości obrazu.
gluErrorString
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry errorCode
Zwracana wartość Przykład Patrz także
Zwraca łańcuch opisujący dany kod błędu.
<glu.h>
const GLubyte *gluErrorString(GLenum errorCode);
Ta funkcja zwraca łańcuch opisujący wskazany kod błędu. Łańcuch jest zdefiniowany statycznie, więc zwracany adres nie może być modyfikowany. Zwracany łańcuch to łańcuch ANSI. Aby otrzymać łańcuch ANSI lub UNICODE w zależności od środowiska, użyj makra glErrorStringWIN.
GLenum: Kod błędu, którego opis chcemy uzyskać. Może być podana dowolna wartość z tabeli 5.1.
Łańcuch znaków opisujący kod błędu. Patrz przykład GLTELL z listingu 5.2 gIGetError
gluGetString
Przeznaczenie Plik nagłówkowy Składnia
Zwraca łańcuch zawierający dodatkowe informacje o bibliotece GLU.
<glu.h>
const GLubyte *gluGetString(GLenum name);
142
Część l * Wprowadzenie do OpenGL
Opis
Ta funkcja zwraca łańcuch zawierający numer wersji lub informacje o rozszerzeniach biblioteki GLU. Łańcuch jest zdefiniowany statycznie, więc zwracany adres nie może być modyfikowany.
Parametry
name
Zwracana wartość
Przykład Patrz także
GLenum: Identyfikuje aspekt biblioteki GLU, o którym chcemy otrzymać informację. Może przyjmować jedną z poniższych wartości:
GLU_VENDOR Zwraca nazwę firmy, która stworzyła daną implementację biblioteki GLU. Format tego łańcucha to:
<numer wersji><spacja>informacje o producencie>
GLU_EXTENSIONS Zwraca listę rozszerzeń obsługiwanych w tej wersji biblioteki GLU. Każda pozycja listy jest oddzielona spacją.
Łańcuch znaków opisujący żądany aspekt lub wartość NULL w przypadku użycia niewłaściwego parametru.
Patrz przykład GLTELL z listingu 5.2 glGetString
Część 2
Używanie OpenGL
Wygląda na to, że każdy kurs programowania rozpoczyna się od tego samego głupiego przykładowego programu, obliczającego, ile litrów benzyny na sto kilometrów spali się jadąc do określonego celu. Aby go stworzyć, trzeba nauczyć się korzystać z terminala, potem edytora, kompilatora, linkera, poznać strukturę programu, a następnie składnię języka. Niestety, zanim zaczniemy chodzić, musimy nauczyć się raczkować i OpenGL nie jest tu wyjątkiem.
W pierwszej części książki poznaliśmy OpenGL, pewne zagadnienia trójwymiarowej grafiki, a także format funkcji OpenGL. Następnie zaczęliśmy łączyć OpenGL z Windows API, budując programy Windows rysujące w oknach przy pomocy OpenGL. Nauczyliśmy się odczytywać kody błędów, interpretować je oraz nie korzystać z rozszerzeń, które nie są dostępne.
Teraz nadszedł czas, aby od raczkowania przejść do marszu na dwóch nogach. W rozdziale 6 omówimy
prymitywy graficzne w OpenGL. Będziesz używał tych klocków tworząc większe i bardziej złożone
obiekty. Następnie dowiesz się, co można zrobić z obiektem w trójwymiarowej przestrzeni: poznasz tran
slacje, obroty i inne przekształcenia, łącznie z przekształceniami układu współrzędnych. Uzbrojony w te
informacje będziesz mógł przejść do rozdziałów 8 i 9, w których zajmiemy się kolorami, cieniowaniem
i innymi fotorealistycznymi efektami. Pozostałe rozdziały dotyczą zaawansowanych narzędzi manipulo
wania obiektami, technik żonglowania obrazami i teksturami oraz kilku specjalnych trójwymiarowych
prymitywów. ^^
ar
Pamiętaj, aby śledzić rozwój programu symulacji czotagpito^-llrego tworzenie rozpoczynamy już od najbliższego rozdziału. Ten specjalny przykładowy program nie jest opisywany w książce i można go znaleźć tylko na płytce CD-ROM, jednak jego kod rozwija się, w miarę zdobywania wiadomości z kolejnych rozdziałów. Plik readme.txt opisuje kolejne etapy proc*M tworzenła (Stogramu. Czy ktoś ma już dosyć odbijających ąię kwadratów? Jeśjp^od razu przejdźmy dalej!
114________________________________Część l 4 Wprowadzenie do OpenGL
// Zatwierdzenie odmalowanego obszaru
ValidateRect(hWnd,NULL);
}
break;
default: // Domyślna obsługa wszystkich
// nieprzetwarzanych komunikatów return (DefWindowProc(hwnd, message, wParam, IParam));
return (OL);
Jeśli śledziłeś naszą wcześniejszą dyskusję, z pewnością zrozumienie kodu w wersji dla Windows nie sprawi ci problemu. Spójrzmy jednak na kilka miejsc, na które powinieneś zwrócić szczególną uwagę.
Skalowanie do okna
W naszym przykładzie z rozdziału trzeciego, opartym na bibliotece AUX, biblioteka AUX wywoływała zarejestrowaną funkcję ChangeSize za każdym razem, gdy zmieniały się wymiary okna. W naszym nowym przykładzie musimy w tym celu przechwy-tywać komunikat WM_SIZE, wysyłany przez Windows przy każdej zmianie wymiarów okna. W procedurze obsługi tego komunikatu możemy sami wywoływać funkcję ChangeSize, przekazując LOWORD z parametru IParam, który zawiera nową szerokość okna, oraz HIWORD z IParam, zawierającą nową wysokość okna.
// Zmieniają się wymiary okna case WM_SIZE:
// Wywołanie naszej funkcji modyfikującej bryłę obcinania
//i widok
ChangeSize (LOWORD (IParam) , HIWORD (IParam) ) ;
break;
łyknięcia timera
Biblioteka AUX regularnie wywoływała także naszą funkcję czasu wolnego, IdleFun-ction. Ta funkcja była wywoływana wtedy, gdy program nie miał nic lepszego do roboty (na przykład nie musiał odrysowywać zawartości okna). Możemy łatw.o zasymulować to działanie ustawiając timer Windows dla naszego okna. Poniższy kod:
// Utworzenie timera odpalanego co milisekundę SetTimer(hWnd,101,1,NULL);
wywoływany w momencie tworzenia okna, uaktywnia timer Windows dla okna. W wyniku tego, teoretycznie co milisekundę okno OpenGL otrzyma komunikat WMJTIMER. W praktyce komunikat będzie wysyłany tak często, jak często Windows będą w stanie go wysłać - w Windows 95 mniej więcej co 55 milisekund - i tylko wtedy, gdy w kolejce nie będą oczekiwać inne komunikaty. (Więcej informacji na temat timera znajdziesz w książce Programming Windows 98 i NT, wydanej przez Wydawnictwo HĘ-
Rozdział 6.
Rysowanie
w trzech wymiarach:
linie, punkty i wielokąty
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
* Rysować punkty, linie i kształty * glBegin/glEnd/glVertex
* Rysować kształty szkieletowe lub jednolite * glPolygonMode
* Ustalać wielkość rysowanych punktów * glPointSize
* Ustalać grubość rysowanych linii * glLineWidth
* Ukrywać niewidoczne powierzchnie * glCullFace
* Ustalać wzory linii przerywanych 4 glLineSample
4 Wypełniać wielokąty wzorem 4 glPolygonStipple
Jeśli kiedykolwiek uczyłeś się chemii (a nawet jeśli się nie uczyłeś), z pewnością wiesz, że wszystko dookoła składa się z atomów, zaś same atomy składają się jedynie z trzech rzeczy: protonów, neutronów i elektronów. Wszystkie materiały i substancje, z którymi kiedykolwiek miałeś kontakt - od kropel rosy po piasek na plaży - stanowią po prostu różne ułożenie tych trzech podstawowych klocków. Choć takie podejście jest nieco uproszczone z punktu widzenia każdego, kto skończył trzecią czy czwartą klasę, przedstawia jednak pewną generalną zasadę: przy pomocy prostych elementów składowych można tworzyć bardzo złożone i piękne struktury.
Analogia jest oczywista. Obiekty i sceny tworzone w OpenGL także składają się z małych, prostych kształtów, ułożonych i połączonych na różne i niepowtarzalne sposoby. W tym rozdziale poznamy właśnie te klocki, zwane prymitywami. Wszystkie prymity-
146_______________________________________Część II » Używanie OpenGL
wy w OpenGL są jedno- lub dwuwymiarowymi obiektami, poczynając od zwykłych punktów i linii, a kończąc na skomplikowanych wielokątach. W tym rozdziale nauczysz się wszystkiego, czego potrzeba, aby rysować trójwymiarowe obiekty złożone z tych prostszych kształtów.
Rysowanie punktów
w przestrzeni trójwymiarowej
Ucząc się cokolwiek rysować w systemie komputerowym, zwykle zaczynamy od pi-kseli. Piksel to najmniejszy element na monitorze komputera, który może przyjmować różne kolory. Taka jest właśnie grafika komputerowa w maksymalnym uproszczeniu: narysowanie punktu w pewnym miejscu ekranu, z nadaniem mu określonego koloru. Opierając się na tej prostej koncepcji, używając ulubionego języka programowania, możesz tworzyć linie, wielokąty, okręgi oraz wszelkie inne kształty i grafikę. Może nawet graficzny interfejs użytkownika...
Jednak w przypadku OpenGL rysowanie na ekranie komputera przebiega zupełnie inaczej. Nie zajmujesz się współrzędnymi fizycznymi i punktami na ekranie, ale raczej współrzędnymi w widocznym fragmencie przestrzeni trójwymiarowej. Do OpenGL należy zamiana trójwymiarowych obiektów na dwuwymiarowy obraz wyświetlany na ekranie.
W tym i następnym rozdziale poznamy większość podstawowych zagadnień OpenGL i innych pakietów grafiki trójwymiarowej. W następnym rozdziale zajmiemy się zagadnieniami przekształceń przestrzeni trójwymiarowej na dwuwymiarowy obraz na ekranie komputera oraz szczegółami dotyczącymi manipulowania obiektami (obracaniem, przesuwaniem i skalowaniem). Na razie zajmiemy się samym rysowaniem obiektów w trójwymiarowym układzie współrzędnych. Być może uznasz, że się nieco cofamy, ale jeśli najpierw dowiesz się jak rysować obiekty, a dopiero potem jak nimi manipulować, materiał w rozdziale siódmym stanie się ciekawszy i łatwiejszy do opanowania. Gdy porządnie opanujesz prymitywy graficzne i przekształcenia współrzędnych, będziesz mógł szybko opanować każdy język lub bibliotekę grafiki trójwymiarowej.
Przygotowanie trójwymiarowej osnowy
Rysunek 6. l przedstawia prostą bryłę widzenia, której będziemy używać w przykładach w tym rozdziale. Obszar obejmowany przez tę bryłę to przestrzeń kartezjańska rozciągająca się od wartości -100 do 100 we wszystkich trzech osiach, x, y i z. (Przestrzeń kartezjańska została omówiona w rozdziale 2). Potraktuj ten trójwymiarowy obszar jako osnowę, na której będzie opierać się działanie poleceń i funkcji OpenGL.
147
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokaty
Rysunek 6.1.
Kartezjańska bryła •widzenia o wymiarach 200x200x200
|
|
|
t
|
i
|
|
|
|
|
/
|
|
• +100 ———
|
|
/
|
|
/
|
|
|
|
/
|
|
|
|
|
|
XlOO
|
|
|
,
|
-1
|
00
|
/ +100
|
|
l
|
]0
|
Kierunek
|
|
/
|
/
|
inn
|
|
/
|
|
/
|
|
+ z
|
|
/
|
|
Przygotowujemy tę bryłę wywołaniem funkcji glOrtho(), podobnie jak to czyniliśmy w poprzednich rozdziałach. Listing 6.1 przedstawia kod funkcji ChangeSize(), wywoływanej w momencie zmiany rozmiaru okna (łącznie z początkowym wymiarowaniem okna). Ten kod nieco różni się od kodu, jaki poznaliśmy w poprzednich rozdziałach, gdyż można w nim natrafić na kilka nieznanych funkcji (glMatrixMode, glLoadEntity). Poświęcimy im więcej czasu w rozdziale 7, szczegółowo wyjaśniając ich działanie i przeznaczenie.
Listing 6.1. Kod ustalający bryłę widzenia z rysunku 6. l ___
// Zmiana bryły widzenia i widoku.
// Wywoływane w momencie zmiany wymiaru okna
void ChangeSize(GLsizei w, GLsizei h)
{
GLfloat nRange = 100.Of;
// Zabezpieczenie przed dzieleniem przez O if(h == 0) h = 1;
// Ustawienie widoku na wymiary okna glYiewport(O, O, w, h);
// Wyzerowanie stosu macierzy rzutowania glMatrixMode(GL_PROJECTION); glLoadldentity();
// Ustanowienie bryły obcinania
// (lewa, prawa, dolna, górna, bliższa, dalsza)
if (w <= h)
glOrtho (-nRange, nRange, -nRange*h/w, nRange*h/w, -nRange,
^nRange); else
glOrtho (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, ^nRange);
// Wyzerowanie stosu macierzy widoku modelu glMatrixMode(GL_MODELVIEW); glLoadldentity();
148
Część II » Używanie OpenGL
Dlaczego zaczynamy od tego?
Gdy spojrzysz na kod źródłowy w tym rozdziale, zauważysz kilka nowych wywołań wewnątrz funkcji RenderScene(): glRotate(), glPushMa-trix() i glPopMatrix(). Zostaną one omówione w rozdziale 7, lecz wprowadzamy je już teraz. Robimy tak, ponieważ implementują one kilka ważnych elementów, które chcemy wykorzystać tak szybko, jak tylko się da. Te funkcje umożliwiają rysowanie w przestrzeni 3D i pozwalają na łatwą wizualizację rysunków pod różnymi kątami. Wszystkie przykłady w tym rozdziale umożliwiają obracanie scenek w osiach x i y za pomocą klawiszy strzałek. Gdy spojrzysz na trójwymiarowy rysunek prosto z góry, tj. wzdłuż osi z, przekonasz się, że wygląda zupełnie płasko. Jednak gdy nieco obrócisz scenkę, natychmiast ujrzysz głębię obiektów.
Wiele jeszcze musimy powiedzieć o rysowaniu w trójwymiarowej przestrzeni, a w tym rozdziale chcemy się skupić tylko na tym. Dzięki temu, że w kolejnych przykładach zmienia się jedynie kod rysujący, możesz już teraz zacząć eksperymentować z rysowaniem w trzech wymiarach osiągać interesujące rezultaty. Gdy już to opanujesz, w następnym rozdziale dowiesz się, jak manipulować rysunkiem za pomocą innych funkcji.
Trójwymiarowy punkt: wierzchołek
Aby narysować punkt w trzech wymiarach, używamy funkcji OpenGL glVertex - bez wątpienia najczęściej używanej funkcji w OpenGL API. Jest to „najmniejszy wspólny dzielnik" wszystkich prymitywów OpenGL: pojedynczy punkt w przestrzeni. Funkcja glVertex może przyjmować od dwóch do czterech parametrów dowolnego typu liczbowego, od bajtów do liczb podwójnej precyzji, zgodnie z konwencją nazw omawianą w rozdziale 3.
Poniższa pojedyncza linia kodu nakazuje narysowanie punktu w naszym układzie współrzędnych, położonego 50 jednostek wzdłuż osi x, 50 jednostek wzdłuż osi y i zero jednostek wzdłuż osi z:
glVertex3f(50.Of, 50.Of, O.Of);
Ten punkt jest przedstawiony na rysunku 6.2. Wybraliśmy reprezentację współrzędnych w postaci liczb typu float, przy czym pozostaniemy także w pozostałej części książki. Podobnie, używana przez nas forma funkcji glVertex() wymaga podania trzech parametrów, określających współrzędną x, y oraz z.
Dwie inne formy funkcji glVertex wymagają podania dwóch i czterech parametrów. Moglibyśmy utworzyć ten sam punkt, co na rysunku 6.2, wywołując na przykład funkcję
glVertex2f(50.Of, 50.Of);
Ta forma funkcji glVertex wymaga podania jedynie dwóch parametrów, określających współrzędne x i y, przy założeniu, że współrzędna z ma zawsze wartość 0,0. Forma
149
Rozdział 6. + Rysowanie w trzech wymiarach: linie, punkty i wielokąty
funkcji glVertex wymagająca czterech parametrów, glVertex4f, używa czwartej wartości współrzędnej, w, używanej w celu skalowania, Więcej na jej temat powiemy sobie w rozdziale 7, którego większość poświęcimy przekształceniom współrzędnych.
Rysunek 6.2.
Punkt (50, 50, 0) określony funkcją glVertex3f(50.0f, SO.Of, O.Oj)
(50,50,0)
50 '
50
Narysujmy coś!
Teraz wiemy już, jak wskazać punkt w trójwymiarowej przestrzeni OpenGL. Co możemy z nim zrobić i co może zrobić z nim OpenGL? Czy wierzchołek jest punktem, który po prostu powinien zostać narysowany? Czy jest to koniec odcinka lub wierzchołek sześcianu? Geometryczna definicja wierzchołka to nie tylko punkt w przestrzeni, ale raczej miejsce, gdzie przecinaj ą się dwie linie lub krzywe. Na tym polegają prymitywy.
Prymityw jest po prostu interpretacją zestawu lub listy wierzchołków tworzących kształt rysowany na ekranie. W OpenGL występuje dziesięć prymitywów, od zwykłych punktów rysowanych w przestrzeni, po zamknięte wielokąty o dowolnej ilości krawędzi. OpenGL rozpoczyna interpretację listy wierzchołków tworzących prymityw w momencie napotkania instrukcji glBegin. Koniec interpretacji następuje w momencie napotkania instrukcji glEnd. Jest to zgodne z intuicją, prawda?
Rysowanie punktów
Zacznijmy od pierwszego i najprostszego z prymitywów: punktu. Spójrz na poniższy kod:
glBegin(GL_POINTS);
glVertex3f (0,0f, 0,0f, 0,0f); glVertex3f (50,Of, 50,Of, 50,Of), glEnd() ;
// wybranie punktów jako
// prymitywu
// wskazanie punktu
// wskazanie innego punktu
// koniec z rysowaniem punktów
L
Argument funkcji glBegin(), GL_POINTS, informuje OpenGL, że następujące po niej wierzchołki mają zostać zinterpretowane jako punkty do narysowania. Na naszej liście występują dwa wierzchołki, czyli dwa punkty, które należy narysować.
W tym momencie dochodzimy do ważnej roli pary funkcji glBegin i glEnd: pomiędzy tymi wywołaniami możesz umieścić długą listę prymitywów, jeśli tylko należą do tego
150_______________________________________Część II » Używanie OpenGL
samego typu. W ten sposób, w pojedynczej sekwencji glBegin/glEnd możesz zawrzeć dowolną liczbę prymitywów.
Następny segment kodu jest bardzo rozrzutny i wykonuje się dużo wolniej niż poprzedni przykład:
glBegin(GL_POINTS); // wybranie punktu jako prymitywu
glVertex3f(0,0f, 0,0f, 0,0f);
glEnd();
glBegin(GL_POINTS); // wybranie punktu jako prymitywu
glVertex3f(50,Of, 50,Of, 50,Of); glEnd();
Wcięcia w kodzie programu
Czy zwróciłeś uwagę na wcięcia używane przy zapisie wywołań funkcji glVertex()? Ta konwencja jest stosowana przez większość programistów OpenGL, gdyż znacznie ułatwia analizę kodu. Wcięcia nie są wymagane, jednak ułatwiają wyszukanie początków i końców list rysowanych prymitywów.
Nasz pierwszy przykład
Kod przedstawiony na listingu 6.2 rysuje punkty w przestrzeni 3D. Używa przy tym kilku prostych wyrażeń trygonometrycznych w celu utworzenia spirali punktów biegnących w górę osi z. Ten kod pochodzi z programu POINTS, zawartego na płytce CD-ROM w folderze tego rozdziału. Wszystkie przykładowe programy korzystają ze schematu, jaki stworzyliśmy w rozdziałach 4 i 5. Zwróć uwagę na funkcję SetupRC(), w której ustawiamy kolor rysowania na zielony.
Listing 6.2. Kod rysunkowy tworzący spiralę punktów________________________________
// Definiuje stałą o wartości zbliżonej do PI ttdefine GL_PI 3.1415f
// Ta funkcja odpowiada za inicjalizację kontekstu renderowania void SetupRCO
// Czarne tło
glClearColor(O.Of, O.Of, O.Of, l.Of );
// Kolor rysowania zielony glColorSf(O.Of, l.Of, O.Of); }
// Wywoływane w celu narysowania sceny void RenderScene(void)
GLfloat x,y,z,angle; // Zmienne dla współrzędnych i kątów
// Wyczyszczenie okna bieżącym kolorem tła glClear(GL_COLOR_BUFFER_BIT);
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty____________151
// Zachowanie stanu macierzy i wykonanie obrotu
glPushMatrix();
glRotatef(xRot, l.Of, O.Of, O.Of);
glRotatef(yRot, O.Of, l.Of, O.Of);
// Wywoływane tylko raz dla wszystkich punktów glBegin(GL_POINTS);
z = -50.Of;
for(angle = O.Of; angle <= (2.Of*GL_PI)*3.Of; angle += O.lf)
{
x = 50.0f*sin(angle);
y = 50.Of*cos(angle);
// Określenie punktu i przesunięcie się nieco w górę osi Z glVertex3f(x, y, z); z += 0.5 f; }
II Koniec rysowania punktów glEnd();
// Odtworzenie transformacji glPopMatrix();
// Zrzucenie poleceń graficznych glFlushO ;
W tym \ w innych przykładach w tym rozdziale interesuje nas przede wszystkim kod zawarty pomiędzy wywołaniami glBegin i glEnd. Ten kod oblicza współrzędne x i y dla kątów od O do 360° dla trzech obrotów. (W programie wyrażamy je w radianach, a nie w stopniach; jeśli nie znasz trygonometrii, musisz uwierzyć na słowo. Jeśli cię to interesuje, zajrzyj do ramki „Trygonometria radianów i stopni"). Za każdym razem, gdy rysowany jest punkt, zwiększana jest nieco wartość współrzędnej z. Po uruchomieniu programu widać jedynie zielony krąg punktów, a to dla tego, że początkowo patrzymy w dół osi z. Aby lepiej widzieć spiralę, za pomocą klawiszy kursora obróć scenę dookoła osi x i y. Ilustruje to rysunek 6.3.
Rysunek 6.3.
Efekt działania programu POINTS
152
Część II » Używanie OpenGL
Nie wszystko od razu
Przypominamy, nie zajmujemy się teraz funkcjami, których jeszcze nie omawialiśmy (glPushMatrix, glPopMatrix ani gIRotate). Te funkcje służą do obracania obrazu, tak abyś lepiej widział efekt trójwymiarowości spirali rysowanej w przestrzeni 3D. Szczegóły przekształceń współrzędnych omówimy w rozdziale 7. Gdybyśmy nie użyli tych funkcji w tym momencie, nie widziałbyś trzeciego wymiaru naszych rysunków i przykładowe programy nie byłyby interesujące. Także w pozostałych przykładach w tym rozdziale będziemy prezentować jedynie kod zawarty pomiędzy instrukcjami gIBegin i glEnd.
Trygonometria radianów i stopni
x=sin(oc) y=cos(ce)
Uy)
->• x
Rysunek w tej ramce przedstawia okrąg narysowany na płaszczyźnie xy. Odcinek biegnący z początku układu współrzędnych (O, O) do dowolnego punktu okręgu tworzy kąt a z osią ox. Dla każdego kąta a funkcje trygonometryczne cosinus i sinus określają wartości współrzędnych x i y punktu na okręgu. Zwiększając wartość zmiennej reprezentującej kąt, tak aby przeszła przez cały okrąg, możemy obliczyć położenie punktów na całym obwodzie koła. Zwróć uwagę, że funkcje C sin() i cos() wymagają podania wartości kąta wyrażonej w radianach, a nie w stopniach. Pełny kąt odpowiada 2*PI radianów, gdzie Pl to liczba niewymierna, wynosząca w przybliżeniu 3,1415 (ponieważ to jest liczba niewymierna, po przecinku występuje nieskończona liczba cyfr).
Ustalanie rozmiaru punktu
Gdy rysujesz pojedynczy punkt, rozmiar tego punktu to domyślnie jeden piksel. Możesz to zmienić przy pomocy funkcji glPointSize:
void glPointSize(GLfloat size);
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokaty____________153
Funkcja gIPointSize wymaga pojedynczego parametru, określającego przybliżoną średnicę (w pikselach) rysowanych punktów. Nie są dostępne wszystkie średnice punktów, powinieneś więc sprawdzić, które wartości są obsługiwane. Użyj poniższego kodu, aby sprawdzić zakres rozmiarów punktu oraz najmniejszy interwał pomiędzy tymi wartościami:
GLfloat sizes[2]; // Obsługiwany zakres wielkości punktów GLfloat step; // Obsługiwany przyrost wielkości punktów
// Pobranie zakresu obsługiwanych wielkości i przyrostu wielkości glGetFloatv(GL_POINT_SIZE_RANGE,sizes); glGetFloatv(GL_POINT_SIZE_GRANULARITY,Sstep);
Tablica sizes będzie zawierała dwa elementy, określające najmniejszą i największą wartość argumentu funkcji gIPointSize. Dodatkowo, zmienna step będzie zawierała najmniejszą dozwoloną wartość przyrostu wielkości punktu. Specyfikacja OpenGL wymaga, aby był obsługiwany przynajmniej jeden rozmiar punktu, 1,0. Implementacja OpenGL Microsoftu obsługuje wielkości punktów od 0,5 do 10,0 z przyrostem 0,125. Określenie wartości spoza zakresu nie będzie interpretowane jako błąd. Zamiast tego zostanie użyta najmniejsza lub największa obsługiwana wartość, najbardziej zbliżona do wartości podanej.
Zmienne stanu OpenGL
OpenGL posiada zmienne przechowujące wiele wewnętrznych stanów i ustawień. Ta kolekcja ustawień jest nazywana Maszyną stanu OpenGL. Istnieje możliwość odczytywania i ustawiania wartości zmiennych stanu. Do włączania i wyłączania opcji służą funkcje glEnable i glDisable, wartości zmiennych można ustawiać funkcją gISet, zaś odczytywać funkcją gIGet. Pełniejszy opis Maszyny stanu OpenGL znajdziesz w rozdziale 14.
Przyjrzyjmy się przykładowi korzystającemu z tej nowej funkcji. Kod przedstawiony na listingu 6.3 tworzy tę samą spiralę, co w pierwszym przykładzie, jednak tym razem rośnie rozmiar każdego kolejnego punktu, od najmniejszego do największego dozwolonego rozmiaru. Ten kod pochodzi z programu POINTSZ na płytce CD-ROM, z foldera tego rozdziału. Wynik działania programu widzimy na rysunku 6.4.
Listing 6.3. Kod programu POINTSZ tworzący spiralą coraz większych punktów_______________
// Definiuje stałą o wartości zbliżonej do PI #define GL_PI 3.1415f
// Wywoływane w celu narysowania sceny void RenderScene(void>
GLfloat x,y,z,angle; // Zmienne dla współrzędnych i kątów GLfloat sizes[2]; // Przechowuje obsługiwany zakres wielkości
// punktów GLfloat step; // Przechowuje obsługiwany przyrost wielkości
// punktów GLfloat curSize; // Storę current size
154
Część II * Używanie OpenGL
// Pobranie zakresu obsługiwanych wielkości i przyrostu wielkości glGetFloatv(GL_POINT_SIZE_RANGE,sizes); glGetFloatv(GL_POINT_SIZE_GRANULARITY,Sstep);
// Ustawienie początkowego rozmiaru punktów curSize = sizes [0];
// Ustawienie początkowej zmiennej Z z = -50.Of;
// Przejście całego okręgu trzy razy
forfangle = O.Of; angle <= (2.Of*3.1415f)*3.Of; angle += O.lf)
{
// Oblicza wartości z i y na okręgu
x = 50.0f*sin(angle);
y = 50.Of*cos(angle);
// Określenie rozmiaru punktu przed wybraniem prymitywu glPointSize(curSize);
// Rysowanie punktu glBegin(GL_POINTS);
glVertex3f(x, y, z);
glEnd();
// Zwiększenie współrzędnej Z i wielkości punktu z += 0.5f; curSize += step;
Rysunek 6.4.
Wynik działania programu POINTSZ
Ten przykład demonstruje kilka ważnych rzeczy. Początkujący powinni zwrócić uwagę, że funkcja glPointSize musi być wywoływana na zewnątrz pary glBegin/glEnd. Nie wszystkie funkcje OpenGL mogą być wywoływane wewnątrz tej pary. Ponieważ glPointSize wpływa na wszystkie rysowane po jej wywołaniu punkty, samo rysowanie
Rozdział 6. » Rysowanie w trzech wymiarach: linie, punkty i wielokąty_____________155
punktów odbywa się dopiero wewnątrz pary glBegin/glEnd. Kompletną listę funkcji, które można wywoływać wewnątrz tej pary, znajdziesz w sekcji podręcznika.
Najważniejszą rzeczą, jaką z pewnością dostrzegłeś w działaniu programu, jest to, że punkty o największych rozmiarach są po prostu kwadratami. Jest to domyślne działanie, jednak niepożądane w wielu aplikacjach. Poza tym zapewne zastanawiasz się, jak można zwiększyć wielkość punktu o wartości mniejsze od 1. Jeśli wartość 1.0 reprezentuje jeden piksel, jak narysować mniej niż jeden piksel lub, powiedzmy, 2,5 piksela?
Wynika to z tego, że rozmiar podany w funkcji glPointSize nie jest dokładnym rozmiarem punktu w pikselach, ale raczej przybliżoną średnicą okręgu opisującego wszystkie piksele użyte do narysowania punktu. Możesz namówić OpenGL, aby rysowało okrągłe punkty, tj. wypełnione okręgi, wywołując funkcję
glEnable(GL_POINT_SMOOTH);
Inne funkcje określają sposób wygładzania punktów i linii, jednak należy to już do szerokiego tematu związanego z antyaliasingiem (rozdział 16). Antyaliasing to technika używana do wygładzania ząbkowanych krawędzi i zaokrąglania wierzchołków. Wspominamy o tym jedynie, abyś mógł sam poeksperymentować i abyś nabrał apetytu na dalsze rozdziały!
Rysowanie linii w trzech wymiarach
Używany dotąd przez nas prymityw, GL_POINTS, był bardzo prosty; dla każdego wskazanego wierzchołka rysowany był punkt. Następnym logicznym krokiem jest określenie dwóch wierzchołków i narysowanie pomiędzy nimi odcinka. Właśnie do tego służy następny prymityw, GL_LINES. Poniższy krótki fragment kodu rysuje pojedynczy odcinek pomiędzy punktami (O, O, 0) a (50, 50, 50):
glBegin(GL_LINES);
glVertex3f(0.0, 0.0, 0.0);
glVertex3f(50.0, 50.0, 50.0); glEnd();
Zwróć uwagę że do określenia pojedynczego odcinka wymagane są dwa wierzchołki. Dla każdych dwóch wierzchołków jest rysowany jeden odcinek. Jeśli podamy nieparzystą ilość wierzchołków, ostatni wierzchołek zostanie po prostu zignorowany. Listing 6.4, z przykładowego programu LINES na płytce CD-ROM, rysuje bardziej złożony kształt, składający się z odcinków tworzących promienie okręgu. Wynik działania tego programu jest pokazany na rysunku 6.5.
Listing 6.4. Fragment kodu z programu LINES rysujący serię odcinków tworzących promienie okręgu
// Wywoływane tylko raz dla wszystkich punktów glBegin(GL_LINES);
z = O.Of;
forfangle = O.Of; angle <= GL PI*3.0f; angle += 0.5f)
156____________________________________Część II » Używanie OpenGL
// Górna połowa okręgu
x = 50.0f*sin(angle) ;
y = 50.0f*cos (angle) ;
glVertex3f (x, y, z); // początek odcinka
// Dolna połowa okręgu
x = 50.0f*sin(angle+3.1415f) ;
y = 50.0f*cos(angle+3.1415f) ;
glVertęx3f (x, y, z); // koniec odcinka
// Koniec rysowania glEndO ;
Rysunek 6.5.
Wynik działania programu LINES
Łamane i łamane zamknięte
Następne dwa prymitywy OpenGL, także oparte na odcinkach, umożliwiają podanie listy wierzchołków, które zostaną połączone odcinkami linii. Gdy zastosujesz prymityw GL_LINE_STRIP, zostanie utworzona łamana składająca się z segmentów łączących kolejne wierzchołki. Poniższy kod rysuje dwa segmenty łamanej, biegnącej przez trzy wierzchołki w płaszczyźnie xy. Wygląd łamanej został przedstawiony na rysunku 6.6.
glBegin(GL_LINES);
glVertex3f(0.0, 0.0, 0.0); // V0
glVertex3f(50.0, 50.0, 50.0); // VI
glVertex3f(50.0, 100.0, 0.0); // V2 glEnd();
Ostatni prymityw oparty na odcinkach to GL_LINE_LOOP. Ten prymityw jest podobny do prymitywu GL_LINE_STRIP, z tym że po narysowaniu ostatniego segmentu łamanej jest rysowany dodatkowy segment, zamykający całą łamaną. Używając tego prymitywu możemy łatwo rysować zamknięte kształty. Rysunek 6.7 przedstawia łamaną zamkniętą narysowaną na podstawie tych samych wierzchołków co na rysunku 6.6.
157
Rozdziaf 6. » Rysowanie w trzech wymiarach: linie, punkty i wielokąty
Rysunek 6.6.
Przykład prymitywu GL_LINE_STR1P rozpiętego na trzech •wierzchołkach
V2(50,100,0) V, (50,50,0)
V„(0,0,0)
Rysunek 6.7.
Łamana zamknięta rozpięta na tych samych
wierzchołkach, co łamana z rysunku 6.6
-*• x
Przybliżanie krzywych odcinkami
Program POINTS, przedstawiony wcześniej na rysunku 6.3, pokazywał, jak rysować punkty wzdłuż zadanej krzywej. Może kusi cię chęć zbliżania punktów do siebie (przez ustawienie mniejszego przyrostu kąta) w celu utworzenia gładkiej, jednolitej linii krzywej, zamiast serii punktów jedynie ją przybliżających. To całkowicie poprawna operacja, jednak może zajmować bardzo dużo czasu w przypadku większych i bardziej skomplikowanych krzywych, składających się z tysięcy punktów.
Lepszym sposobem przybliżenia krzywej jest użycie prymitywu GL_LINE_STRIP, łączącego kolejne punkty. Gdy punkty zbliżają się do siebie, krzywa staje się gładsza, i to bez konieczności określania jej wszystkich punktów. Listing 6.5 przedstawia kod z listingu 6.2, w którym prymityw GL_LINES zastąpiono prymitywem GL_LINE_ STRIPS. Wynik działania tego nowego programu, LSTRIPS, został pokazany na rysunku 6.8. Jak widać, przybliżenie krzywej jest całkiem dobre. Przekonasz się, że ta technika jest stosowana powszechnie w programach OpenGL.
Listing 6.S. Kod programu LSTRIPS, przybliżający krzywą za pomocą łamanej_________ __ __
// Wywoływane tylko raz dla wszystkich punktów glBegin(GL LINĘ STRIP);
z = -50.Of;
for(angle = O.Of; angle <= (2.Of*3.1415f)*3.Of; angle += O.lf)
158 Część II » Używanie OpenGL
x = 50. Of *sin (angle) ; y = 50 .Of*cos (angle) ;
// Określenie punktu i przesunięcie się nieco w górę osi Z glVertex3f (x, y, z); z += 0.5f;
// Koniec rysowania glEnd ( ) ;
Rysunek 6.8.
Program LSTR1PS, 2 krzywą przybliżoną za pomocą łamanej
Ustalanie grubości linii
Podobnie jak można ustawić różne wielkości punktów, można również ustawić różne grubości linii. Służy do tego funkcja glLineWidth:
void glLineWidth(GLfloat width);
Funkcja glLineWidth otrzymuje pojedynczy parametr, określający przybliżoną grubość rysowanych linii, w pikselach. Podobnie jak w przypadku wielkości punktów, nie wszystkie grubości linii są obsługiwane, więc zawsze powinieneś sprawdzić, jakie grubości są dostępne. Użyj poniższego kodu w celu odczytania zakresu grubości linii oraz najmniejszej wartości przyrostu ich grubości.
GLfloat sizes[2]; // Zakres szerokości linii GLfloat step; // Przyrost szerokości linii
// Pobranie dostępnych rozmiarów linii i najmniejszej wartości // przyrostu
glGetFloatv(GL_LINE_WIDTH_RANGE,sizes); glGetFloatv(GL_LINE_WIDTH_GRANULARITY,Sstep);
Tablica sizes będzie zawierać dwa elementy określające najmniejszą i największą dozwoloną wartość parametru funkcji glLine Width. Dodatkowo, zmienna step będzie za-
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty_____________159
wierała najmniejszą dozwoloną wartość przyrostu grubości linii. Specyfikacja OpenGL wymaga, aby była obsługiwana przynajmniej jedna grubość linii, 1,0. Implementacja OpenGL Microsoftu obsługuje grubości linii od 0,5 do 10,0 z przyrostem 0,125. Określenie wartości spoza zakresu nie będzie interpretowane jako błąd. Zamiast tego zostanie użyta najmniejsza lub największa obsługiwana wartość, najbardziej zbliżona do wartości podanej.
Listing 6.6 przedstawia kod wykorzystujący funkcję glLineWidth. Fragment kodu pochodzi z programu LINESW i rysuje dziesięć odcinków o różnych grubościach. Zaczyna od dołu okna, od punktu o współrzędnej -90 w osi y i przechodzi w górę w krokach po 20 jednostek. Każdy następny odcinek jest grubszy od poprzedniego o jeden piksel. Wynik działania programu ilustruje rysunek 6.9.
Rysunek 6.9.
Wynik działania funkcji glLine Width z programu LINESW
Listing 6.6. Rysowanie odcinków o różnych grubościach
II Wywoływane w celu narysowania sceny void RenderScene(void)
{
GLfloat y; // Zmienna dla zmieniającej się współrzędnej Y
GLfloat fSizes[2]; // Zakres szerokości linii
GLfloat fCurrSize; // Przechowuje bieżącą szerokość
// Pobranie dostępnych rozmiarów linii // i zapamiętanie najmniejszej wartości przyrostu glGetFloatv(GL_LINE_WIDTH_RANGE,fSizes); fCurrSize = fSizes[0];
// Przejście w osi Y po 20 punktów forty = -90.Of; y < 90.Of; y += 20.Of) (
// Ustawienie szerokości linii
glLineWidthffCurrSize);
160____________________________________Część II » Używanie OpenGL
// Narysowanie linii glBegin(GL_LINES);
glVertex2f(-80.Of, y);
glVertex2f(80.Of, y); glEnd();
// Zwiększenie szerokości linii fCurrSize += l.Of; }
Zwróć uwagę, że tym razem do określenia współrzędnych wierzchołków zamiast funkcji glVertex3f() użyliśmy funkcji glVertex2f(). Jak wspomniano, jest to tylko wygoda, gdyż rysujemy w płaszczyźnie xy, z wartością ustawioną na 0. Aby się przekonać, że linie wciąż są rysowane w trzech wymiarach, wciskając klawisze strzałek spróbuj obrócić scenkę dookoła. Zobaczysz, że odcinki są narysowane na tej samej płaszczyźnie.
Linie przerywane
Oprócz zmieniania szerokości linii możemy także rysować linie kropkowane lub przerywane. Aby rysować takie linie, musisz najpierw włączyć przerywanie, wywołując funkcję
glEnable(GL_LINE_ST1PPLE);
a następnie przy pomocy funkcji glLineStipple określić deseń używany przy rysowaniu linii:
void glLineStipple(GLint factor, GLushort pattern);
UWAGA:
Każda opcja włączana funkcją glEnable() może być wyłączona funkcją glDisable().
Rysunek 6.10. Wzór = OXOOFF = 255
Wzór przerywania
zastosowany do k A [ ^
utworzenia segmentu
Wzór linii = l t l i l l l t l l l l l l l l l
linia =
linii Binornie= 0000000011111111
Rozdział 6. » Rysowanie w trzech wymiarach: linie, punkty i wielokąty_____________161
Parametr pattern to 16-bitowa wartość określająca wzór używany podczas rysowania linii przerywanych. Każdy bit reprezentuje sekcję odcinka, która może być narysowana lub nie. Domyślnie, każdy bit odpowiada pojedynczemu pikselowi, lecz parametr factor służy jako mnożnik zwiększający szerokość wzoru. Na przykład ustawienie parametru factor na 5 powoduje, że każdy bit we wzorze reprezentuje pięć kolejnych pikseli linii, które albo wszystkie są włączone, albo wyłączone. Co więcej, bit O (najmniej znaczący) jest używany jako pierwszy. Przykładowy wzór linii ilustruje rysunek 6.10.
Dlaczego bity są odczytywane od tyłu?
Zastanawiasz się może, laczego bity we wzorcu linii są odczytywanie od najmniej znaczącego. Wewnętrznie, OpenGL dużo szybciej przesuwa bity w lewą stronę, za każdym razem, gdy chce pobrać kolejny bit maski. W przypadku wysokowydajnych aplikacji, konieczność wewnętrznego odwracania wzorca (łatwiejszego do opanowania przez człowieka) zajmowałaby zbyt dużo cennego czasu procesora.
Listing 6.7 przedstawia przykład użycia wzorca linii, składającego się z serii naprzemiennych zer i jedynek (0101010101010101). Ten program rysuje dziesięć odcinków, od dołu do góry okna. Każda linia korzysta z tego samego wzorca, 0x5555, jednak w każdej następnej linii wartość mnożnika wzorca zwiększa się o 1. Efekt poszerzających się wzorów wyraźnie widać na rysunku 6.11.
Rysunek 6.11.
Wynik działania
programu
LSTIPPLE
Listing 6.7. Kod z programu LSTIPPLE demonstrujący efekt zastosowania różnych mnożników wzorca linii
// Wywoływane w celu narysowania sceny void RenderScene(void)
0x5555;
GLfloat y;
GLint factor = 1;
GLushort pattern =
// Zmienna współrzędnej Y
// Współczynnik przerw
// Deseń przerw
162 ___ _____ _____ ________Część II » Używanie OpenGL
// Włączenie przerywania glEnable(GL_LINE_STIPPLE);
// Przechodzenie w osi Y po 20 jednostek forty = -90.Of; y < 90.Of; y += 20.Of)
{
// Wyzerowanie czynnika wypełnienia i desenia
glLineStipple(factor,pattern);
// Rysowanie linii glBegin(GL_LINES);
glVertex2f(-80.Of, y);
glVertex2f(80.Of, y) ; glEnd();
factor++;
Rysowanie trójkątów w przestrzeni 3D
Wiesz już, jak rysować punkty i odcinki, a nawet jak przy użyciu prymitywu GL_LI-NE_LOOP rysować zamknięte kształty. Za pomocą tych trzech prymitywów możesz tworzyć dowolne kształty w trójwymiarowej przestrzeni. Możesz na przykład narysować sześć kwadratów, układając je tak, aby tworzyły sześcian.
Z pewnością zauważyłeś jednak, że kształty tworzone z użyciem tych prymitywów nie są wypełnione żadnym kolorem - przecież rysowane są tylko linie. Aby narysować jednolitą powierzchnię, potrzebujemy czegoś więcej niż punkty i odcinki; potrzebujemy wielokątów. Wielokąt to zamknięty kształt, który może (ale nie musi) być wypełniony aktualnie wybranym kolorem; wielokąt stanowi podstawę tworzenia wszystkich jednolitych obiektów w OpenGL.
Trójkąt: twój pierwszy wielokąt
Najprostszym istniejącym wielokątem jest trójkąt, który, jak wiadomo, posiada trzy krawędzie. Do rysowania trójkątów służy prymityw GL_TRJANGLES; trójkąty są tworzone w oparciu o trzy wierzchołki. Poniższy kod rysuje dwa trójkąty, oparte na trzech wierzchołkach każdy, przedstawione na rysunku 6.12.
glBegin(GLJTRIANGLES);
glvertex2f(O.Of, O.Of); // V0 glVertex2f(25.Of, 25.Of); // VI glVertex2f(50.Of, O.Of); // V2
163
sowanie w trzech wymiarach: linie, punkty i wielokąty
// V3 //V4
// V5
tfese
• glVertex2f(-50.0f, O.Of);
glVertex2f(-75.Of, 50.Of); . glVertex2f(-25.Of, O.Of); glEnd () ;
12.
tmitywu ILEŚ
! Zwróć uwagę, że trójkąty zostaną wypełnione bieżącym kolorem rysowania. Jeśli w pewnym momencie nie określisz koloru rysowania, nie będziesz wiedział, jaki kolor zostanie użyty (nie ma domyślnego koloru rysowania).
Wybór najszybciej rysowanych prymitywów
Programista OpenGL najczęściej wybiera właśnie rysowanie trójkątów. Okazuje się, że przy odrobinie wysiłku każdy wielokąt da się złożyć z jednego lub większej ilości odpowiednio ułożonych trójkątów. Większość sprzętowych akceleratorów 3D została zoptymalizowania do rysowania trójkątów, a wiele programów testujących szybkość grafiki trójwymiarowej podaje wynik właśnie jako ilość trójkątów rysowanych w ciągu sekundy.
trunek
Ważna właściwość każdego wielokątnego prymitywu została zilustrowana na rysunku 6.12. Zwróć uwagę na kierunek strzałek przy liniach łączących wierzchołki trójkąta. Przy rysowaniu pierwszego trójkąta, linie są rysowane od wierzchołka V0 do wierzchołka VI, następnie do wierzchołka V2, a na koniec z powrotem do wierzchołka V0. Ta ścieżka została wyznaczona przez kolejność podawania wierzchołków i w tym przypadku, z twojego punktu widzenia, jest zgodna z ruchem wskazówek zegara. Również drugi trójkąt został utworzony w tym samym kierunku.
Połączenie położenia wierzchołków z kolejnością ich definiowania jest nazywane kierunkiem. W przypadku trójkątów z rysunku 6.12 mówimy, że mają kierunek zgodny z ruchem wskazówek zegara (ang. clockwise winding, CW), ponieważ ich wierzchołki są ułożone właśnie w takiej kolejności. Gdybyśmy w trójkącie z lewej strony zamienili miejscami wierzchołki V4 i V5, otrzymalibyśmy trójkąt o kierunku przeciwnym do ruchu wskazówek zegara (ang. counterclockwise winding, CCW), tak jak pokazano na rysunku 6.13.
164
Część II » Używanie OpenGL
Rysunek 6.13.
Dwa trójkąty o różnych kierunkach
Trójkqt o kierunku przeciwnym do ruchu Trójkąt o kierunku zgodnym z ruchem wskazówek zegara (Przednia strona) wskazówek zegara (Tylna strona)
OpenGL domyślnie zakłada, że wielokąty o kierunku przeciwnym do ruchu wskazówek zegara są skierowane przodem do patrzącego. Oznacza to, że trójkąt po lewej stronie rysunku 6.13 pokazuje nam swoją przednią stronę, zaś w przypadku trójkąta z prawej strony widzimy jego tylną stronę.
Jakie to ma znaczenie? Jak się wkrótce przekonasz, często zdarza się, że przedniej i tylnej stronie trójkąta chcemy nadać różne charakterystyki. Można na przykład całkowicie ukryć tył wielokąta lub też nadać mu inny kolor i właściwości odbijania światła (rozdział 9). Bardzo ważne jest więc zachowanie spójnych kierunków wielokątów w scenie, poprzez użycie wielokątów skierowanych przodem do tworzenia zewnętrznych powierzchni jednolitych obiektów. W następnych sekcjach, poświęconych jednolitym obiektom, zademonstrujemy to zagadnienie używając bardziej złożonych modeli.
Jeśli chcesz zmienić domyślne działanie OpenGL, możesz wywołać funkcję
glFrontFace(GL_CW);
Parametr GL_CW instruuje OpenGL, aby za odwrócone przodem były uważane trójkąty o kierunku zgodnym z ruchem wskazówek zegara. Aby powrócić do kierunku przeciwnego do ruchu wskazówek zegara dla trójkątów widzianych z przodu, użyj tej funkcji z parametrem GL_CCW.
Paski trójkątów
W przypadku wielu kształtów i powierzchni zdarzy ci się rysować kilka połączonych trójkątów. Używając prymitywu GL_TRIANGLE_STRIP, możesz zaoszczędzić mnóstwo czasu przy rysowaniu pasków trójkątów. Rysunek 6.14 przedstawia kolejność tworzenia paska trójkątów składającego się z trzech trójkątów rozpiętych na pięciu wierzchołkach V0 i V4. Jak widać, wierzchołki nie są przetwarzane w tej samej kolejności, w jakiej są podawane. Powodem takiego działania jest konieczność zachowania kierunku (przeciwnego do ruchu wskazówek zegara) każdego trójkąta.
165
Rozdział 6. + Rysowanie w trzech wymiarach: linie, punkty i wielokąty
Rysunek 6.14.
Przebieg tworzenia
paska trójkątów
GL TRIANGLE STRIP
(Na marginesie, przy omawianiu dalszych wielokątnych prymitywów nie będziemy już przedstawiać kodu demonstrującego określanie wierzchołków po instrukcji gIBegin. Z pewnością wiesz już, o co w tym chodzi. Później, gdy zajmiemy się rzeczywistym przykładowym programem, powrócimy do przykładów).
Za korzystaniem z paska trójkątów zamiast określania każdego trójkąta z osobna przemawiają dwa argumenty. Po pierwsze, po podaniu trzech pierwszych wierzchołków, dla każdego następnego trójkąta musimy podać tylko jeden wierzchołek. Gdy do narysowania jest wiele trójkątów, oszczędzamy dzięki temu wiele czasu i pamięci. Druga korzyść wynika z tego, że, jak wspomniano wcześniej, przy komponowaniu obiektów lub powierzchni dobrze jest korzystać właśnie z trójkątów, a nie z innych prymitywów.
Inna korzyść, płynąca z reprezentowania dużych płaskich powierzchni z kilku mniejszych trójkątów, występuje w momencie gdy scena zostaje oświetlona źródłem światła. Dzięki użyciu kilku trójkątów OpenGL może lepiej symulować efekty oświetlenia. Więcej na temat tej techniki powiemy sobie w rozdziale 9.
Wachlarze trójkątów
Rysunek 6.15.
Etapy tworzenia trójkątów w prymitywie GL TRIANGLE FAN
Oprócz tworzenia pasków trójkątów, możesz tworzyć także wachlarze trójkątów. Prymityw GL_TRIANGLE_FAN służy do tworzenia grupy trójkątów o jednym wspólnym punkcie. Rysunek 6.15 przedstawia wachlarz trzech trójkątów utworzony przez podanie czterech dodatkowych wierzchołków. Pierwszy wierzchołek, V0, wyznacza wspólny punkt wachlarza. Gdy pierwsze trzy wierzchołki zostaną użyte do narysowania pierwszego trójkąta, każdy następny trójkąt jest budowany w oparciu o wierzchołek wspólny (V0), następny wierzchołek oraz wierzchołek bezpośrednio go poprzedzający (Vn-l).
166____________________________________Część II » Używanie OpenGL
Zwróć uwagę, że trójkąty są tworzone w kierunku zgodnym z ruchem wskazówek zegara, a nie w przeciwnym.
Budowanie jednolitych obiektów
Tworzenie jednolitego obiektu z trójkątów (lub innych wielokątów) wymaga czegoś więcej niż tylko składania wierzchołków w trójwymiarowej przestrzeni. Przyjrzyjmy się przykładowemu programowi TRIANGLE, w którym wykorzystano dwa wachlarze trójkątów w celu utworzenia stożka w widocznym obszarze przestrzeni. Pierwszy wachlarz tworzy kształt stożka, w którym pierwszy wierzchołek pokrywa się z wierzchołkiem stożka, zaś pozostałe wierzchołki tworzą okrąg przesunięty w dół osi z. Drugi wachlarz formuje koło i leży dokładnie w płaszczyźnie xy, tworząc podstawę stożka.
Wynik działania programu TRIANGLE został pokazany na rysunku 6.16. W tym momencie spoglądamy dokładnie w dół osi z i widzimy jedynie koło utworzone przez wachlarz trójkątów. Poszczególne trójkąty zróżnicowano nadając im na przemian kolory czerwony i zielony.
Rysunek 6.16.
Początkowy wygląd
programu
TRIANGLE
Kod funkcji SetupRC i RenderScene został przedstawiony na listingu 6.8. (Nowe zmienne i znaczniki zostaną wyjaśnione za chwilę). Ten program demonstruje kilka aspektów komponowania trójwymiarowych obiektów. Zwróć uwagę na elementy menu Efekty; będziemy ich używać do włączania i wyłączania pewnych elementów rysunku 3D, dzięki czemu będziemy mogli lepiej poznać pewne zagadnienia związane z tworzeniem trójwymiarowych obiektów.
Listing 6.8. Fragmenty kodu z przykładowego programu TRIANGLE_____________________
// Ta funkcja odpowiada za inicjowanie kontekstu renderowania void SetupRC()
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty_____________167
// Czarne tło
glClearColor (O.Of, O.Of, O.Of, l.Of );
// Kolor rysowania zielony glColorSf (O.Of, l.Of, O.Of);
// Ustawienie trybu cieniowania na płaski glShadeModel (GL_FLAT) ;
// Wielokąty o kolejności krawędzi zgodnej z ruchem wskazówek // zegara są widoczne od frontu. Odwracamy to, // gdyż korzystamy z wachlarza trójkątów glFrontFace (GL_CW) ;
// Wywoływane w celu narysowania sceny
void RenderScene (void)
{
GLfloat x,y,angle; // Zmienne dla współrzędnych i kątów
int iPivot = 1; // Używane do uzyskania przemiennych kolorów
// Wyczyszczenie okna i bufora głębokości
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ;
// Włączenie usuwania niewidocznych trójkątów, // jeśli znacznik jest ustawiony if (bCull)
glEnable (GL_CULL_FACE) ; else
glDisable (GL_CULL_FACE) ;
// Włączenie bufora głębokości, jeśli znacznik jest ustawiony if (bDepth)
glEnable (GL_DEPTH_TEST) ; else
glDisable (GL_DEPTH_TEST) ;
// Rysowanie tylko siatki wielokąta, // jeśli znacznik jest ustawiony if (bOutline)
glPolygonMode (GL_BACK, GL_LINE) ; else
glPolygonMode (GL_BACK, GL_FILL) ;
// Zachowanie stanu macierzy i wykonanie obrotu
glPushMatrix();
glRotatef(xRot, l.Of, O.Of, O.Of);
glRotatef(yRot, O.Of, l.Of, O.Of);
// Początek wachlarza trójkątów glBegin(GL_TRIANGLE_FAN);
// Czubek stożka stanowi wspólny wierzchołek wachlarza; jest // przesunięty w celu otrzymania stożka, a nie koła glVertex3f {O.Of, O.Of, 75.Of);
168____________________________________Część II » Używanie OpenGL
// Przejście przez okrąg i wyznaczenie parzystych trójkątów
wachlarza for(angle = O.Of; angle < (2.Of*GL_PI); angle += (GL_PI/8.Of))
// Obliczenie pozycji x i y następnego wierzchołka x = 50.0f*sin(angle); y = 50.Of*cos(angle);
// Zmiana koloru z czerwonego na zielony if((iPivot %2) == 0)
glColorSf(O.Of, l.Of, O.Of); else
glColorSf(l.Of, O.Of, O.Of);
// Zmiana koloru dla następnego trójkąta iPivot++;
// Określenie następnego wierzchołka wachlarza trójkątów glVertex2f(x, y);
// Koniec rysowania stożka glEnd() ;
// Rozpoczęcie nowego wachlarza w celu zakrycia podstawy glBegin(GL_TRIANGLE_FAN);
// Środek wachlarza w środku układu
glVertex2f(O.Of, O.Of);
for(angle = O.Of; angle < (2.Of*GL_PI); angle += (GL_PI/8.Of))
// Obliczenie pozycji x i y następnego wierzchołka x = 50.0f*sin(angle); y = 50.Of*cos(angle);
// Zmiana koloru z czerwonego na zielony if((iPivot %2) == 0)
glColorSf(O.Of, l.Of, O.Of); else
glColor3f(l.Of, O.Of, O.Of);
// Zmiana koloru dla następnego trójkąta iPivot++;
// Określenie następnego wierzchołka wachlarza trójkątów glVertex2f(x, y) ;
// Koniec rysowania wachlarza podstawy glEnd() ;
// Odtworzenie transformacji glPopMatrix();
// Zrzucenie poleceń graficznych glFlusht); }
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty ______ 169
Ustawianie kolorów wielokątów
Jak dotąd, kolor rysowania ustawialiśmy tylko raz i rysowaliśmy jedynie pojedynczy kształt. Teraz, gdy mamy do czynienia z większą ilością wielokątów, rzeczy stają się bardziej interesujące. Chcemy użyć różnych kolorów, aby łatwiej rozróżniać poszczególne trójkąty. Kolory są określane dla wierzchołków, a nie dla wielokątów. O tym, czy wielokąt będzie posiadał jednolity kolor (kolor określony dla ostatniego wierzchołka tworzącego wielokąt), czy też kolor zmieniający się płynnie od wierzchołka do wierzchołka, decyduje model cieniowania wielokąta. Linia
glShadeModel(GL_FLAT);
instruuje OpenGL, aby wypełniało wielokąty jednolitym kolorem, zgodnym z kolorem aktualnym w momencie podawania ostatniego wierzchołka wielokąta. Właśnie dzięki temu możemy łatwo zmieniać kolor trójkąta z zielonego na czerwony, przed podaniem następnego wierzchołka w wachlarzu. Z drugiej strony, linia
glShadeMode(GL_SMOOTH);
nakazywałaby zastosowanie płynnego przejścia pomiędzy kolorami wierzchołków, poprzez interpolację kolorów pośrednich. Na temat kolorów i cieniowania porozmawiamy w rozdziale 8.
Korzystanie z bufora głębokości
Wciskając jeden z klawiszy kursora obróć stożek dookoła; nie wybieraj jeszcze żadnej pozycji z menu Efekty. Zauważysz coś dziwnego: stożek wydaje się odchylać w tył i w przód po 180°, zaś jego podstawa zawsze będzie skierowana w twoją stronę. Jaśniej przedstawia to rysunek 6.17.
Rysunek 6.17. _ f _+180
Obracający się
stożek wydaje się
jedynie odchylać
w przód i w tył
Dzieje się tak, ponieważ podstawa stożka jest rysowana już po narysowaniu jego ścian. Oznacza to, że bez względu na orientację stożka, jego podstawa jest rysowana na nim, co daje wrażenie odchylania się. Ten efekt nie ogranicza się jedynie do różnych stron i części obiektu. Jeśli jest rysowanych kilka obiektów i jeden z nich jest rysowany bliżej niż drugi (z punktu widzenia obserwatora), drugi rysowany obiekt zawsze przesłania obiekt narysowany jako pierwszy.
Możesz usunąć ten problem dzięki technice zwanej buforem głębokości, a OpenGL posiada specjalne funkcje, które zajmą się tym za ciebie. Idea jest prosta: gdy jest rysowa-
170____________________________________Część II » Używanie OpenGL
ny piksel przypisywana mu wartość wartość (zwana wartością z) określająca odległość punktu dla tego piksela od obserwatora. Później, gdy w tym samym miejscu ekranu ma zostać narysowany kolejny piksel, wartość z nowego piksela jest porównywana z wartością z piksela już narysowanego. Jeśli wartość z nowego piksela jest większa, oznacza to, że jego punkt jest bliżej obserwatora, więc stary piksel jest przesłaniany przez nowy. Jeśli wartość z nowego piksela jest mniejsza, to odpowiadający mu punkt musi znajdować się dalej od obserwatora i nie przysłania starego piksela. Ten mechanizm jest implementowany za pomocą wewnętrznego bufora głębokości, który szczegółowo omówimy w rozdziale 15.
Aby włączyć testowanie głębokości, po prostu wywołaj
glEnable(GL_DEPTH_TEST);
Na listingu 6.8 włączamy testowanie głębokości w momencie gdy zmienna bDepth ma wartość True wyłączamy, gdy ma wartość False.
// Włączenie bufora głębokości, jeśli znacznik jest ustawiony if(bDepth)
glEnable(GL_DEPTH_TEST); else
glDisable(GL_DEPTH_TEST);
Zmienna bDepth jest ustawiana w momencie wybrania w menu Efekty polecenia Bufor głębokości. Należy pamiętać, że bufor głębokości musi być wyzerowany za każdym razem gdy przystępujemy do narysowania sceny. Bufor głębokości jest podobny do bufora koloru, z tym że zamiast koloru przechowuje informacje o odległościach pikseli od obserwatora. Dzięki temu można wyznaczyć, czy dany piksel znajduje się bliżej lub dalej niż inny piksel.
// Wyczyszczenie okna i bufora głębokości
glClęar(GL_COLOR_BUFFER_BIT | GL_DEPTH__BUFFER_BIT) ;
Rysunek 6.18 przedstawia menu Efekty z włączonym buforem głębokości. Oprócz tego przedstawia stożek z podstawą poprawnie ukrytą za ściankami. Jak widać, bufor głębokości jest praktycznie nieodzowny jeśli chodzi o rysowanie jednolitych trójwymiarowych obiektów.
Rysunek 6.18.
W tym położeniu podstawajest poprawnie zasłonięta przez ścianę, stożka
171
Rozdział 6. » Rysowanie w trzech wymiarach: linie, punkty i wielokąty
Ukrywanie niewidocznych powierzchni
Widzimy, że jakość rysunku ogromnie się poprawia, gdy nie są rysowane powierzchnie zasłonięte przez inne powierzchnie. Jednak mimo to wydajność programu nie jest zbyt wysoka, gdyż wartość z każdego rysowanego piksela musi być porównana z wartością z poprzednio narysowanego piksela. Czasem jednak z góry wiadomo, że dana powierzchnia nigdy nie zostanie narysowana, więc po co jaw ogóle określać? Na przykład po to, aby nie rysować tylnych ścian powierzchni.
W naszym przykładzie stożek jest bryłą zamkniętą i nigdy nie widzimy jego wnętrza. OpenGL w rzeczywistości (wewnętrznie) rysuje tylne ściany z tyłu stożka, a następnie przednie strony wielokątów, skierowane w naszą stronę. Następnie, przez porównanie wartości w buforze głębokości, eliminuje ściany stożka znajdujące się dalej. Rysunki 6.19a i 6.19b przedstawiają stożek w pewnym położeniu z włączonym (a) i wyłączonym (b) buforem głębokości. Zwróć uwagę że czerwone i zielone trójkąty zamieniają się miejscami w momencie włączenia i wyłączenia bufora głębokości. Bez bufora głębokości, w dalszym ciągu widzimy strony trójkątów znajdujących się dalej.
Rysunek 6.19a.
Z włączonym buforem głębokości
Rysunek 6.19b.
Bez włączonego bufora głębokości
172____________________________________Część II » Używanie OpenGL
Wcześniej w tym rozdziale wyjaśnialiśmy, jak OpenGL korzysta z kierunku wielokąta w celu wyznaczenia jego przedniej i tylnej strony oraz że ważne jest, aby wielokąty tworzące powierzchnię obiektów posiadały ten sam, spójny kierunek. Właśnie ta spójność zapewnia, że OpenGL renderuje tylko przód, tylko tył lub obie strony wielokątów. Poprzez wyeliminowanie wielokątów skierowanych tyłem, możemy znacznie zredukować czas renderowania sceny. Mimo korzystania z bufora głębokości, OpenGL i tak wewnętrznie brałby pod uwagę takie wielokąty, chyba że jawnie nakażemy mu, aby tego nie robił.
Eliminacja wielokątów skierowanych tyłem jest nazywana usuwaniem niewidocznych powierzchni. Usuwanie niewidocznych powierzchni jest włączane lub wyłączane przy pomocy poniższego fragmentu kodu z listingu 6.8:
// Wielokąty o kolejności krawędzi zgodnej z ruchem wskazówek
// zegara
// są widoczne od frontu, odwracamy to gdyż
// korzystamy z wachlarza trójkątów
glFrontFace(GL_CW);
// Włączenie usuwania niewidocznych trójkątów // jeśli znacznik jest ustawiony if(bCull)
glEnable(GL_CULL_FACE); else
glDisable(GL_CULL_FACE);
Zwróć uwagę, że najpierw zmieniamy definicję wielokątów skierowanych przodem na wielokąty o kierunku zgodnym z ruchem wskazówek zegara (ponieważ tak właśnie są ułożone trójkąty w wachlarzach).
Rysunek 6.20.
Podstawa stożka została ukryta, gdyż przednia strona jej trójkątów jest skierowana do środka bryły
Rysunek 6.20 demonstruje efekt wycinania podstawy stożka w momencie włączenia usuwania niewidocznych powierzchni. Dzieje się tak, ponieważ nie przestrzegamy zasady wymagającej, aby wszystkie wielokąty na powierzchni obiektu miały ten sam kierunek. Wachlarz trójkątów tworzący podstawę stożka składa się z trójkątów o kierunku zgodnym z ruchem wskazówek zegara, podobnie jak wachlarz tworzący ściany stożka,
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty _____________ 173
jednak przednia strona wachlarza podstawy jest skierowana do wnętrza stożka. Wyjaśnia to rysunek 6.21.
Rysunek 6.21. „ j . .
' ,, , . . Przednia strono
Sposób złożenia
stożka z dwóch wachlarzy trójkątów
Przednia strona
trójkątów
Moglibyśmy rozwiązać ten problem zmieniając kierunek przedniej strony trójkątów funkcją
glFrontFace(GL_CCW);
wywoływaną tuż przed rysowaniem drugiego wachlarza trójkątów. Jednak w tym przykładzie chcemy, abyś zobaczył usuwanie tylnych powierzchni w działaniu, a także chcielibyśmy przygotować cię do następnej demonstracji wyginania wielokątów.
Tryby wielokątów
Wielokąty nie muszą być wypełnione bieżącym kolorem. Domyślnie, wielokąty są rysowane jednolitym kolorem, możesz jednak zmienić to zachowanie nakazując, by wielokąty były rysowane jedynie jako sylwetki lub nawet jako punkty (są wtedy rysowane jedynie wierzchołki). Funkcja glPolygonMode() umożliwia określenie, aby wielokąty były renderowane jako wypełnione, jako kontur lub jako punkty. Oprócz tego, tryb ren-derowania może być przypisany obu stronom wielokąta lub tylko stronie wybranej. Poniższy kod z listingu 6.8 przedstawia ustawianie trybu wielokąta na wypełniony lub siatkowy, w zależności od stanu zmiennej logicznej bOutline:
// Rysowanie tylko siatki wielokąta, jeśli znacznik jest ustawiony if(bOutline)
glPolygonMode(GL_BACK,6L_LINE); else
glPolygonMode(GL_BACK,GL_FILL);
Rysunek 6.22 pokazuje, że wszystkie wielokąty skierowane tyłem są rysowane jako siatka. (Aby stworzyć ten obrazek, musieliśmy wyłączyć usuwanie tylnych powierzchni, gdyż w przeciwnym razie zostałyby usunięte i nie byłoby widać siatki). Zwróć uwagę, że teraz podstawa stanowi siatkę i nie jest wypełniona, dzięki czemu możesz zajrzeć do wnętrza stożka, w którym wewnętrzne ściany także są rysowane jako siatkowe trójkąty.
174____________________________ Część II » Używanie ppenGL
Rysunek 6.22.
Użycie funkcji gIPolygonMode() w celu narysowania jednej strony trójkątów jako siatki
Inne prymitywy
Trójkąty są najczęściej wybieranymi prymitywami, gdyż większość kart graficznych zoptymalizowanych dla OpenGL najszybciej rysuje właśnie trójkąty; jednak nie są jedynymi dostępnymi obiektami. Niektóre karty potrafią rysować sprzętowo także inne kształty, zaś w programie często łatwiej jest użyć ogólnych prymitywów graficznych. Do pozostałych prymitywów OpenGL należą czworokąty oraz paski czworokątów, a także wielokąty o dowolnej liczbie krawędzi. Jeśli wiesz, że twój program będzie działał w komputerze z kartą, która obsługuje takie wielokąty sprzętowo, możesz pokusić się o zastosowanie ich w celu osiągnięcia lepszej wydajności.
Czworokąty
Następnym kształtem po trójkącie jest czworokąt. Do rysowania czworokątów służy prymityw GL_QUADS. Na rysunku 6.23 widzimy, że czworokąt jest rozpięty na czterech wierzchołkach. Zwróć uwagę, że ten czworokąt ma kierunek zgodny z ruchem wskazówek zegara.
Rysunek 6.23.
Przykład prymitywu GL_QUAD
175
Rozdział 6. « Rysowanie w trzech wymiarach: linie, punkty i wielokąty
Paski czworokątów
Tak jak w przypadku pasków trójkątów, możesz utworzyć także paski czworokątów; służy do tego prymityw GL_QUAD_STRIP. Rysunek 6.24 przedstawia przebieg powstawania paska czworokątów składającego się z dwóch czworokątów rozpiętych na sześciu wierzchołkach. Poszczególne czworokąty, podobnie jak czworokąt prymitywu GL_QUADS, mają kierunek zgodny z ruchem wskazówek zegara.
V,
Rysunek 6.24.
Przebieg
powstawania paska czworokątów GL_QUAD_STRIP
V, 2
V, V
Ogólne wielokąty
Ostatnim prymitywem OpenGL jest GL_POLYGON, który może zostać użyty do narysowania wielokąta o dowolnej liczbie krawędzi. Rysunek 6.25 przedstawia wielokąt składający się z pięciu wierzchołków. Wielokąty tworzone za pomocą GL_POLYGON mają kierunek zgodny z ruchem wskazówek zegara.
Rysunek 6.25.
Proces tworzenia
wielokąta
GL POLYGON
A co z prostokątami?
Wszystkie dziesięć prymitywów OpenGL jest używanych pomiędzy wywołaniami gIBegin/glEnd do rysowania ogólnych kształtów. Jeden z kształtów jest tak powszechny, że zamiast prymitywu posiada własną funkcję; tym kształtem jest prostokąt. Był to pierwszy kształt, który narysowaliśmy w rozdziale 3. Funkcja glRect() zapewnia łatwy i wygodny sposób rysowania prostokątów, bez konieczności odwoływania się do prymitywu GL_QUAD.
Wypełnianie wielokątów
Istnieją dwie metody wypełniania jednolitych wielokątów. Bardziej uniwersalną metodą jest mapowanie tekstur, w którym bitmapa jest nakładana na powierzchnię wielokąta (omawiamy to w rozdziale 11). Innym sposobem jest określenie desenia, tak jak to czy-
176
Część II * Używanie OpenGL
niliśmy w przypadku linii. Wzór desenia dla wielokąta nie jest niczym innym jak monochromatyczną bitmapą o rozmiarach 32x32, używaną do wypełnienia wielokąta.
Aby włączyć wypełnianie wielokąta, wywołaj funkcję
glEnable(GL_POLYGON_STIPPLE);
a następnie
glPolygonStipple(pBitmap);
gdzie pBitmap to wskaźnik do obszaru danych zawierających wzór desenia. Od tego momentu wszystkie wielokąty będą wypełniane wzorem określonym przez zawartość pamięci wskazywanej przez pBitmap (typu GLubyte *). Ten deseń jest podobny do używanego przy przerywanych liniach, z tym że bufor ma rozmiar wystarczający do pomieszczenia wzorca 32x32 bity. Oprócz tego bity są odczytywane od najbardziej do najmniej znaczącego, czyli przeciwnie niż w przypadku linii. Deseń ogniska, jakiego użyjemy do wypełniania wielokątów, został przedstawiony na rysunku 6.26.
4 2 l 8 4 Z l B 4 2 l 8 4 2 l 8 4 2 l 9 4 2 l 8 4 2 l 8 4 2 l
Rysunek 6.26.
Budowanie wzoru desenia wypełniania wielokątów
12864 32 16 8 4 2 11286432 16 8 4 2 1128 M 3216 8 4 2 11286432168 4 2 l
Y
0X80
0X1F
= 0X1F
OXCO
Przechowywanie pikseli
Jak dowiesz się w rozdziale 11, możesz modyfikować sposób interpretacji pikseli wzorca wypełnienia; służy do tego funkcja glPixelStofe(). Na razie pozostańmy jednak przy zwykłym wypełnianiu wielokątów.
177
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty
Aby skonstruować maskę reprezentującą ten wzór, przechowamy bitmapę wierszami, od dołu do góry. Na szczęście, inaczej niż w przypadku linii, dane są interpretowane tak, jak je zapisujemy, i najbardziej znaczący bajt jest odczytywany jako pierwszy. Każdy bajt może więc być zapisany od strony lewej do prawej i przechowany w tablicy wartości GLubyte na tyle dużej, aby pomieściła 32 wiersze po cztery bajty każdy.
Kod użyty do przechowania wzorca przedstawia listing 6.9. Każdy wiersz tablicy reprezentuje wiersz punktów z rysunku 6.26. Pierwszy wiersz w tablicy stanowi ostatni wiersz rysunku, i tak dalej, aż do ostatniego wiersza w tablicy, który odpowiada pierwszemu wierszowi na rysunku.
Sugestia: wróć tu później
Jeśli wciąż nie rozumiesz, jak ta bitmapa ogniska jest przechowywana i interpretowana, sugerujemy, abyś wrócił tu później, gdy już opanujesz materiał z rozdziału 11, „Grafika rastrowa".
Listing 6.9. Definicja maski przedstawiającej ognisko z rysunku 6.26
// Bitmapa ogniska GLubyte fire[] = {
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
OxcO,
|
0x00,
|
0x00,
|
0x01,
|
OxfO,
|
0x00,
|
0x00,
|
0x07,
|
OxfO,
|
OxOf,
|
0x00,
|
Oxlf,
|
OxeO,
|
Oxlf,
|
0x80,
|
Oxlf,
|
OxcO,
|
OxOf,
|
OxcO,
|
Ox3f,
|
0x80,
|
0x07,
|
OxeO,
|
Ox7e,
|
0x00,
|
0x03,
|
OxfO,
|
Oxff,
|
0x80,
|
0x03,
|
Oxf5,
|
Oxff,
|
OxeO,
|
0x07,
|
Oxfd,
|
Oxff,
|
Oxf8,
|
Oxlf,
|
Oxfc,
|
Oxff,
|
Oxe8,
|
Oxff,
|
Oxe3,
|
Oxbf,
|
0x70,
|
Oxde,
|
0x80,
|
Oxb7,
|
0x00,
|
0x71,
|
0x10,
|
Ox4a,
|
0x80,
|
0x03,
|
0x10,
|
Ox4e,
|
0x40,
|
0x02,
|
0x88,
|
Ox8c,
|
0x20,
|
0x05,
|
0x05,
|
0x04,
|
0x40,
|
0x02,
|
0x82,
|
0x14,
|
0x40,
|
0x02,
|
0x40,
|
0x10,
|
0x80,
|
0x02,
|
0x64,
|
Oxla,
|
0x80,
|
0x00,
|
0x92,
|
0x29,
|
0x00,
|
0x00,
|
OxbO,
|
0x48,
|
0x00,
|
0x00,
|
Oxc8,
|
0x90,
|
0x00,
|
0x00,
|
0x85,
|
0x10,
|
0x00,
|
0x00,
|
0x03,
|
0x00,
|
0x00,
|
0x00,
|
0x00,
|
0x10,
|
0x00 }
|
178____________________________________Część II » Używanie OpenGL
Abyśmy mogli skorzystać z tego wzorca, musimy najpierw włączyć wypełnianie wielo-kątów, a następnie wskazać ten wzór jako wzór wypełniania. Właśnie to wykonuje przykładowy program PSTIPPLE, a następnie rysuje ośmiokąt (kształt znaku Stop), używając wzorca wypełnienia. Interesujący nas kod znajduje się na listingu 6.10, zaś wynik działania programu został przedstawiony na rysunku 6.27.
Rysunek 6.27.
Wynik działania
programu
PSTIPPLE
Listing 6.10. Fragmenty programu PSTIPPLE rysującego \vielokat i wypełniającego go wzorcem
II Ta funkcja odpowiada za inicjowanie kontekstu renderowania
void SetupRCO
{
// Czarne tło
glClearColor(O.Of, O.Of, O.Of, l.Of );
// Kolor rysowania czerwony glColorSf(l.Of, O.Of, O.Of);
// Włączenie wypełniania wielokąta glEnable{GL_POLYGON_STIPPLE);
// Wskazanie desenia wypełniania glPolygonStipple(fire);
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
// Wyczyszczenie okna bieżącym kolorem tła
glClear(GL_COLOR_BUFFER_BIT);
// Zachowanie stanu macierzy i wykonanie obrotu
glPushMatrix();
glRotatef(xRot, l.Of, O.Of, O.Of);
glRotatef(yRot, O.Of, l.Of, O.Of);
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty_____________179
// Utworzenie kształtu znaku STOP
// Dla uproszczenia zostanie użyty standardowy wielokat
glBegin(GL_POLYGON);
glVertex2f(-20.Of, 50.Of);
glVertex2f(20.Of, 50.Of);
glVertex2f(50.Of, 20.Of);
glVertex2f(50.Of, -20.Of);
glVertex2f(20.Of, -50.Of);
glVertex2f(-20.Of, -50.Of);
glVertex2f(-50.Of, -20.Of);
glVertex2f(-50.Of, 20.Of); glEnd();
// Zrzucenie poleceń graficznych glFlushf) ;
Rysunek 6.28 przedstawia ośmiokąt nieco obrócony. Jak widać, wielokat jest w dalszym ciągu wypełniony, jednak deseń nie obraca się wraz z nim. Dzieje się tak, ponieważ wzorzec wypełnienia jest używany wyłącznie do wypełniania wielokątów na ekranie. Jeśli chcesz nałożyć na wielokat bitmapę imitującą jego powierzchnię, będziesz musiał zastosować mapowanie tekstur (rozdział 12).
Rysunek 6.28.
Wygląd wielokąta po obróceniu go wokół osi pionowej. Jak widać, wzór wypełnienia nie obraca się wraz z wielokątem
Reguły konstruowania wielokątów
Gdy przy tworzeniu złożonej powierzchni korzystasz z wielu wielokątów, musisz pamiętać o dwóch ważnych zasadach.
Po pierwsze, wszystkie wielokąty muszą być płaskie. Tzn. wszystkie wierzchołki wielokąta muszą leżeć na tej samej płaszczyźnie, jak przedstawiono na rysunku 6.29. Wielokat nie może być „wykręcony" ani „wygięty" w przestrzeni.
180
Część II » Używanie OpenGL
Rysunek 6.29.
Płaski i niepłaski wielokąt
Wielokąt piaski
Wielokąt niepłaski
Jest to więc jeszcze jeden powód, aby korzystać z trójkątów. Żadnego trójkąta nie da się „wykręcić" tak, aby któryś z wierzchołków nie spoczywał na innej płaszczyźnie niż pozostałe, ponieważ zgodnie z zasadami geometrii, właśnie trzy punkty wyznaczaj ą płaszczyznę. (Jeśli więc uda ci się narysować niewłaściwy trójkąt, oczywiście poza ustawieniem go w złym kierunku, być może czeka cię nagroda Nobla!)
Po drugie, krawędzie konstruowanego wielokąta, nie mogą się przecinać, zaś sam wielokąt musi być wielokątem wypukłym. „Wypukły" oznacza że, nie może posiadać żadnych wcięć. Bardziej oficjalna definicja wielokąta wypukłego wiąże się z rysowaniem przechodzącej przez niego linii. Jeśli jakakolwiek linia prosta przecina krawędzie wielokąta więcej niż dwa razy, taki wielokąt nie jest wypukły. Przykłady poprawnych i niepoprawnych wielokątów pokazuje rysunek 6.30.
Rysunek 6.30.
Dozwolone i niedozwolone wielokąty
Wielokąty dozwolone
Wielokąty niedozwolone
Skąd takie ograniczenia co do wielokątów?
Może zastanawiasz się, dlaczego OpenGL nakłada takie ograniczenia dotyczące konstruowania wielokątów. Operacje na wielokątach mogą być bardzo skomplikowane, zaś ograniczenia OpenGL wynikają z zastosowania bardzo szybkich algorytmów renderowania wielokątów. Prawdopodobnie te ograniczenia nie będą dla ciebie zbyt kłopotliwe, gdyż za pomocą dostępnych prymitywów możesz tworzyć dowolne kształty (w ostateczności składając je z prymitywów GLJJNES czy nawet GL.POINTS).
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty 181
Podział wielokąta
Choć OpenGL potrafi rysować jedynie wielokąty wypukłe, istnieje sposób tworzenia wielokątów niewypukłych — łącząc dwa lub więcej wielokątów wypukłych. Na przykład, weźmy czteroramienną gwiazdę przedstawioną na rysunku 6.31. Ten kształt z całą pewnością nie jest wypukły i w związku z tym narusza reguły OpenGL dotyczące konstruowania wielokątów. Jednak gwiazda po prawej stronie jest skonstruowana z sześciu trójkątów, które są zupełnie dozwolone.
Rysunek 6.31.
Niewypukła czteroramienną gwiazda złożona z sześciu trójkątów
Gdy wielokąty zostaną wypełnione, nie będzie widać żadnych krawędzi i cała figura będzie stanowić pojedynczy kształt na ekranie. Jeśli jednak użyjesz funkcji glPolygonMode w celu przełączenia się na rysowanie sylwetki, będzie widać szkielety trójkątów składających się na gwiazdę, co w pewnych przypadkach może być bardzo mylące.
OpenGL posiada w tym celu specjalny znacznik, zwany znacznikiem krawędzi. Ustawiając lub zerując ten znacznik podczas podawania listy wierzchołków, informujesz OpenGL, które linie mają zostać potraktowane jako zewnętrzne krawędzie figury (linie tworzące sylwetkę figury), a które jako linie wewnętrzne (które nie powinny być widoczne). Służy do tego funkcja glEdgeFlag(), wywoływana z pojedynczym parametrem, będącym wartością logiczną, włączającą lub wyłączającą znacznik krawędzi. Gdy znacznik zostanie ustawiony na wartość True, wszystkie następne wierzchołki będą definiowały krawędzie należące do sylwetki figury. Na listingu 6.11 widzimy zastosowanie tego w praktyce, w programie STAR z płytki CD-ROM.
Listing 6.11. Fragment programu STAR, wykorzystującego funkcję glEdgeFlag_______________
// Rysowanie trójkątów
glBegin(GL_TRIANGLES) ;
glEdgeFlag(bEdgeFlag); glVertex2f(-20.Of, O.Of); glEdgeFlag(TRUE); glVertex2f (20.Of, O.Of); glVertex2f(O.Of, 40.Of);
glVertex2f(-20.Of,O.Of); glVertex2f(-60.Of,-20.Of); glEdgeFlag(bEdgeFlag); glVertex2f(-20.Of,-40.Of); glEdgeFlag(TRUE);
182
Część II * Używanie OpenGL
glVertex2f(-20.Of,-40.Of); glVertex2f(O.Of, -80.Of); glEdgeFlag(bEdgeFlag); glVertex2f(20.Of, -40.Of); glEdgeFlag(TRUE);
glVertex2f(20.Of, -40.Of); glVertex2f(60.Of, -20.Of); glEdgeFlag(bEdgeFlag); glVertex2f(20.Of, O.Of); glEdgeFlag(TROE);
// Środkowy kwadrat jako dwa trójkąty glEdgeFlag(bEdgeFlag); glVertex2f(-20.Of, O.Of); glVertex2f(-20.Of,-40.Of); glVertex2f(20.Of, O.Of);
glVertex2f(-20.Of,-40.Of); glVertex2f(20.Of, -40.Of); glVertex2f(20.Of, O.Of); glEdgeFlag(TRUE);
// koniec rysowania glEnd();
Zmienna boolowska bEdgeFlag jest włączana i wyłączana poprzez polecenia w menu Krawędzie. Jeśli ten znacznik ma wartość True, wszystkie krawędzie są traktowane jako widoczne i pojawiają się, gdy figura jest wyświetlana w trybie GL_LINES. Na rysunkach 6.32a i 6.32b widzimy wynik działania programu STAR w trybie siatkowym, z wyświetlaniem i bez wyświetlania wewnętrznych krawędzi.
Rysunek 6.32a.
Program STAR z włączonymi wewnętrznymi krawędziami
183
Rozdział 6. « Rysowanie w trzech wymiarach: linie, punkty i wielokaty
Rysunek 6.32b.
Program STAR
z wyłączonymi wewnętrznymi krawędziami
Podsumowanie
W tym rozdziale zdobyliśmy mnóstwo informacji. Potrafisz już tworzyć własną trójwymiarową przestrzeń do renderowania, wiesz także, jak narysować każdy z prymitywów, od punktów i odcinków aż po złożone wielokaty. Dowiedziałeś się również, jak z dwuwymiarowych prymitywów można tworzyć powierzchnię trójwymiarowych obiektów.
Zachęcamy do eksperymentów z tym, czego nauczyłeś się w tym rozdziale. Użyj swojej wyobraźni i przed przejściem do dalszych rozdziałów utwórz jakieś trójwymiarowe obiekty. Będziesz miał wtedy własne przykłady, z którymi będziesz mógł eksperymentować, a jednocześnie, dzięki pewnej praktyce, łatwiej opanujesz dalszy materiał książki.
Czas na symulację czołgu/robota
Poczynając od tego rozdziału zaczynamy tworzenie symulatora czołgu/robota, który będzie stanowić uzupełniający przykład na płytce CD-ROM. Celem tej symulacji jest otrzymanie czołgu i robota, które mogą poruszać się po pewnej przestrzeni, widzianej właśnie z perspektywy czołgu lub robota. Symulator nie będzie omawiany w tekście książki, ale symulacja będzie stopniowo rozbudowywana za pomocą technik opisywanych w kolejnych rozdziałach. Możesz zacząć już teraz i poznać obiekty występujące w wirtualnym świecie czołgu i robota. Przyjrzyj się, jak te obiekty zostały skomponowane wyłącznie z prymitywów opisywanych w tym rozdziale.
Część II » Używanie
184
Tato
Prym
Sta
GL
Podręcznik
gIBegin_____
Przeznaczenie Poprzedza podawanie listy wierzchołków definiujących jeden lub więcej
prymitywów.
Składnia Opis
Plik nagłówkowy <gl.h>
void glBegin(GLenum modę);
Ta funkcja, w połączeniu z funkcją glEnd, jest przeznaczona do tworzenia prymitywów OpenGL. Wewnątrz pary wywołań glBegin/glEnd można podać wiele wierzchołków, definiujących serię prymitywów tego samego rodzaju. Oprócz definiowania wierzchołków, wewnątrz pary glBegin/glEnd można zmieniać różne opcje i ustawienia, wpływające na sposób tworzenia i rysowania prymitywów. Pomiędzy wywołaniami gIBegin i glEnd można wywoływać jedynie następujące funkcje: glVertex, glColor, gllndex, glNormal, glEvaICoord, glCallLists, glTextCoord, glEdgeFlag oraz glMaterial.
Parametry modę
GLenum: Ten parametr określa rodzaj konstruowanych prymitywów. Może być jedną z wartości z tabeli 6. l.
Zwracana wartość Brak.
Przykład
Patrz także
Przykłady użycia tej funkcji znajdziesz w każdym programie opisywanym w tym rozdziale. Poniższy kod przedstawia tworzenie pojedynczego punktu, położonego w środku układu współrzędnych:
gIBegin(GL_POINTS);
glVertex3f{0.0, 0.0, 0.0); // punkt w środku układu glEnd();
glEnd, glVertex
Tabela 6.1.
Prymitywy OpenGL obsługiwane przez funkcję glBegin()
Stała
Rodzaj prymitywu
GL_POINTS GL_LINES
GL LINĘ STRIP
Każdy wskazany wierzchołek określa pojedynczy punkt.
Podane wierzchołki definiują końce odcinków. Każde dwa wierzchołki definiują osobny odcinek. Jeśli zostanie podana nieparzysta ilość wierzchołków, ostatni wierzchołek jest ignorowany.
Podane wierzchołki definiują łamaną. Po podaniu pierwszego wierzchołka, każdy następny definiuje kolejny segment łamanej.
185
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokaty
Tabela 6.1.
Prymitywy OpenGL obsługiwane przez funkcją glBeginf) - ciąg dalszy
Rodzaj prymitywu
Stała
GL LINĘ LOOP
Działa podobnie jak GL_LINE_STRIP, z tym że po określeniu ostatniego wierzchołka rysowany jest dodatkowy segment, zamykający łamaną. Tego prymitywu zwykle używa się do tworzenia kształtów naruszających ograniczenia narzucane przez GL_POLYGON.
Podane wierzchołki określają wierzchołki trójkątów. Każde trzy wierzchołki definiują pojedynczy, osobny trójkąt. Jeśli ilość wierzchołków nie jest podzielna przez trzy, nadmiarowe wierzchołki są ignorowane.
Podane wierzchołki określają pasek trójkątów. Po podaniu pierwszych trzech wierzchołków, każdy następny wierzchołek, wraz z dwoma poprzednimi, tworzy kolejny trójkąt paska. Każda trójka wierzchołków (poza trzema pierwszymi) jest automatycznie aranżowana, tak aby zapewnić jednakowy kierunek wszystkich trójkątów paska.
Podane wierzchołki określają wachlarz trójkątów. Pierwszy wierzchołek wyznacza punkt wspólny wachlarza, zaś każdy wierzchołek, począwszy od czwartego, definiuje kolejny trójkąt wachlarza. W ten sposób można zdefiniować dowolną liczbę trójkątów.
Każdy zestaw czterech wierzchołków definiuje czworokąt. Jeśli ilość wierzchołków nie jest podzielna przez cztery, nadmiarowe wierzchołki są ignorowane.
Podane wierzchołki są używane do tworzenia paska czworokątów. Po określeniu pierwszego czworokąta, każda następna para wierzchołków wyznacza kolejny czworokąt, oparty na tych wierzchołkach i dwóch poprzednich. W odróżnieniu od GL_QUADS, każda para wierzchołków jest używana w odwrotnej kolejności, w celu zapewnienia jednakowego kierunku wszystkich czworokątów w pasku.
Podane wierzchołki są używane do tworzenia wielokąta wypukłego. Krawędzie wielokąta nie mogą się przecinać. Ostatni wierzchołek jest automatycznie łączony krawędzią z pierwszym wierzchołkiem, w celu zamknięcia wielokąta.
GL TRIANGLES
GL TR1ANGLE STRIP
GL TR1ANGLE FAN
GL_QUADS GL_QUAD_STRIP
GL POLYGON
glCulIFace
Przeznaczenie Plik nagłówkowy Składnia
Określa niewidoczną stronę wielokątów.
void glCullFace(GLenum modę);
186
Część II » Używanie OpenGL
Opis
Ta funkcja wyłącza oświetlenie, cieniowanie, obliczenia koloru i inne operacje dla przedniej lub tylnej strony wielokątów. Eliminuje niepotrzebne obliczenia, gdyż tylna strona wielokąta nigdy nie jest widoczna, bez względu na jego orientację. Samo usuwanie niewidocznych ścian jest włączane i wyłączane za pomocą funkcji glEnable i glDisable z parametrem GL_CULL_FACE. Do określania przedniej strony wielokątów służy funkcja glFrontFace(); kierunek wielokąta jest wyznaczany na podstawie określenia kolejności jego wierzchołków (zgodnie z ruchem wskazówek zegara lub przeciwnie do niego).
Parametry modę
GLenum: Określa, która strona wielokąta ma być niewidoczna (usunięta). Dozwolone wartości to GL_FRONT (przednia) oraz GL_BACK (tylna).
Zwracana wartość Brak.
Przykład
Poniższy przykład (z programu TRIANGLE w tym rozdziale) przedstawia wyłącznie rysowanie wielokątów zwróconych tyłem, pod warunkiem, że zmienna bCull ma wartość True:
// Wielokąty o kolejności krawędzi zgodnej z ruchem // wskazówek zegara są widoczne od frontu. Odwracamy // to gdyż korzystamy z wachlarza trójkątów glFrontFace(GL_CW);
Patrz także
// Włączenie usuwania niewidocznych trójkątów, // jeśli znacznik jest ustawiony if(bCull)
glEnable(GL_CDLL_FACE); else
glDisable(GL_CULL_FACE);
glFrontFace, glLightModel
glEdgeFlag
Przeznaczenie
Plik nagłówkowy Składnia
Opis
Określa krawędzie wielokąta jako wewnętrzne (niewidoczne) lub zewnętrzne (widoczne).
void glEdgeFlag(GLboolean flag);
void glEdgeFlagv(const GLboolean *flag);
Gdy dwa lub więcej wielokątów jest łączonych w większy region, zewnętrzne (widoczne) krawędzie definiują sylwetkę nowej figury. Ta funkcja służy do określenia krawędzi wewnętrznych, które zwykle nie powinny być widoczne. Funkcja odnosi się jedynie do trybów rysowania GL LINĘ i GL POINT.
r
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty_____________187
Parametry
flag GLboolean: Ustawia stan znacznika krawędzi. Wartość Tnie oznacza
krawędzie zewnętrzne, zaś wartość False - krawędzie wewnętrzne.
*flag const GLboolean *: Wskaźnik do wartości określającej stan znacznika
krawędzi.
Zwracana wartość Brak.
Przykład Poniższy przykład (z programu STAR w tym rozdziale) przedstawia
ustawienie znacznika krawędzi na False w odniesieniu do krawędzi
występujących wewnątrz figury. Figura (czteroramienna gwiazda) jest
rysowana jako wypełniona, jako sylwetka lub jako punkty w
wierzchołkach.
// Narysowanie jedynie zewnętrznych konturów
// wielokąta, jeśli jest ustawiony taki znacznik
if(iMode == MODE_LINE)
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
if{iMode == MODE_POINT)
glPolygonMode(GL_FRONT_AND_BACK,GL_POINT);
if(iMode == MODE_SOLID)
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
// Rysowanie trójkątów glBegin(GLJTRIANGLES);
glEdgeFlag(bEdgeFlag); glVertex2f(-20.Of, O.Of); glEdgeFlag(TRUE); glVertex2f(20.Of, O.Of); glVertex2f(O.Of, 40.Of);
glVertex2f(-20.Of,O.Of); glVertex2f(-60.Of,-20.0f); glEdgeFlag(bEdgeFlag); glVertex2f(-20.0f,-40.0f); glEdgeFlag(TRUE);
glVertex2f(-20.Of,-40.Of); glVertex2f(O.Of, -80.Of); glEdgeFlag(bEdgeFlag); glVertex2f(20.Of, -40.Of), glEdgeFlag(TRUE);
glVertex2f(20.Of, -40.Of), glVertex2f(60.Of, -20.Of); glEdgeFlag(bEdgeFlag); glVertex2f(20.Of, O.Of); glEdgeFlag(TRUE);
188
Część II » Używanie OpenGL
// Środkowy kwadrat jako dwa trójkąty glEdgeFlag(bEdgeFlag); glVertex2f(-20.Of, O.Of); glVertex2f(-20.Of,-40.Of); glVertex2f (20. Of, 0.0"f);
glVertex2f(-20.Of,-40.Of); glVertex2f(20.Of, -40.Of); glVertex2f(20.Of, O.Of); glEdgeFlag(TRUE);
// koniec rysowania glEndO ;
Patrz także
glBegin, glPolygonMode
glEnd
Przeznaczenie Plik nagłówkowy Składnia Opis
Zwracana wartość Przykład
Kończy listę wierzchołków definiujących prymitywy.
void glEnd();
Ta funkcja jest używana w połączeniu z funkcją glBegin w celu określenia wierzchołków prymitywów OpenGL. Wewnątrz pary wywołań glBegin/glEnd można podać wiele wierzchołków, definiujących serię prymitywów tego samego rodzaju. Oprócz definiowania wierzchołków, wewnątrz pary glBegin/glEnd można zmieniać różne opcje i ustawienia, wpływające na sposób tworzenia i rysowania prymitywów. Pomiędzy wywołaniami glBegin i glEnd można wywoływać jedynie następujące funkcje: glVertex, glColor, gllndex, glNormal, glEvalCoord, glCallLists, glTextCoord, glEdgeFlag oraz glMaterial.
Brak.
Przykłady użycia tej funkcji znajdziesz w każdym programie opisywanym w tym rozdziale. Poniższy kod przedstawia tworzenie pojedynczego punktu, położonego w środku układu współrzędnych:
Patrz także
glBegin(GL_POINTS);
glVertex3f(0.0, 0.0, glEndO ;
glBegin, glVertex
0.0); // punkt w środku układu
gl Front Face
Przeznaczenie Określa, która strona wielokąta ma być traktowana jako przednia.
Plik nagłówkowy <gl-h>
Składnia void glFrontFace(GLenum modę);
189
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty
Opis
Gdy scena zawiera obiekty zamknięte (nie widać ich wnętrza), obliczenia koloru i oświetlenia dla wewnętrznych stron obiektu nie są potrzebne. Funkcja glCullFace wyłącza te obliczenia dla przedniej lub tylnej strony wielokątów, zaś funkcja glFrontFace określa, która strona jest przednia, a która tylna. Kierunek wielokąta określa się na podstawie kolejności podawania jego wierzchołków (z punktu widzenia obserwatora) -w kierunku zgodnym lub przeciwnym do ruchu wskazówek zegara. Ta funkcja umożliwia określenie, która strona wielokąta jest przednia: strona zgodna z kierunkiem ruchu wskazówek zegara, czy też strona przeciwna do ruchu wskazówek.
Parametry modę
GLenum: Określa orientację przedniej strony wielokątów: zgodną z ruchem wskazówek (GL_CW) lub przeciwną do ruchu wskazówek zegara (GL_CCW).
Zwracana wartość Brak.
Przykład
Poniższy przykład (z programu TRIANGLE w tym rozdziale) przedstawia sposób wyłączenia rysowania trójkątów skierowanych tyłem do obserwatora. Przedtem konieczne jest wskazanie, która strona wielokątów ma być traktowana jako przednia.
// Wielokąty o kolejności krawędzi zgodnej // z ruchem wskazówek zegara są widoczne od frontu. // Odwracamy to, gdyż korzystamy // z wachlarza trójkątów glFrontFace(GL_CW);
Patrz także
// Włączenie usuwania niewidocznych trójkątów, // jeśli znacznik jest ustawiony if(bCull)
glEnable(GL_CULL_FACE); else
glDisable(GL_COLL_FACE);
glCullFace, glLightModel
glGetPolygonStipple
Przeznaczenie Plik nagłówkowy Składnia Opis
Zwraca bieżący wzór wypełniania wielokątów.
void glGetPolygonStipple(GLubyte *mask);
Ta funkcja zwraca maskę 32x32 bity, reprezentującą wzór wypełnienia wielokątów. Maska jest kopiowana do bufora w pamięci, wskazywanego przez wskaźnik *mask. Sposób upakowania pikseli jest określany przez ostatnie wywołanie funkcji glPixelStore.
190
Część II » Używanie OpenGL
Parametry modę
GLubyte: Wskaźnik do bufora, w którym ma zostać umieszczona maska bitów.
Zwracana wartość Brak. Przykład
Poniższy fragment kodu ilustruje pobieranie bieżącego wzoru wypełniania:
Glubyte mask[32*4]; // 4 bajty = 32 bity x 32 wiersze
Patrz także
glGetPolyginStipple(mask);
glPolygonStipple, glLineStipple, glPixelStore
gllineStipple
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Określa wzór wypełniania linii przerywanych rysowanych z użyciem prymitywów GLJLINES, GL_LINE_STIPPLE oraz GL_LINE_LOOP.
void glLineStipple(GLint factor, GLushort pattern);
Ta funkcja określa styl wypełnienia linii przerywanych. Wzór przerywania linii określa parametr pattern, zaś wielkość segmentów określa parametr factor. Domyślnie, każdy bit wzorca pattern odpowiada jednemu pikselowi rysowanej linii. Aby użyć linii przerywanych, musisz najpierw wywołać funkcję
glEnable (GL_LINE_STIPPLE) ;
Domyślnie, przerywanie linii jest wyłączone. Jeśli rysujesz kilka segmentów linii, wzór przerywania jest zerowany dla każdego segmentu, tj. dla każdego następnego segmentu wzorzec przerywania jest interpretowany od początku.
Parametry factor
pattern
GLint: Mnożnik określający, ile pikseli linii odpowiada pojedynczemu bitowi wzorca przerywania. Domyślną wartością jest l, zaś wartością maksymalną może być 255.
GLushort: Określa 16-bitowy wzorzec przerywania. Jako pierwszy interpretowany jest bit najmniej znaczący. Domyślny wzorzec zawiera same jedynki (rysowana jest ciągła linia).
Zwracana wartość Brak.
191
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty
Przykład
Poniższy fragment kodu (z programu LSTIPPLE w tym rozdziale) ilustruje użycie wzorca 0x5555 (0101010101010101), dającego linię kropkowaną. Dla każdej następnej rysowanej linii jest zwiększana wartość mnożnika wzorca, co powoduje, że fragmenty linii i odstępy stają się coraz dłuższe.
// Wywoływane w celu narysowania sceny void RenderScene(void)
GLfloat y; // Zmienna współrzędnej Y
GLint factor = 1; // Współczynnik przerw
GLushort pattern = 0x5555; // Deseń przerw
// Włączenie przerywania glEnable(GL_LINE_STIPPLE);
// Przechodzenie w osi Y po 20 jednostek for(y = -90.Of; y < 90.Of; y += 20.Of)
{
// Wyzerowanie czynnika wypełnienia i desenia
glLineStipple(factor,pattern);
// Rysowanie linii glBegin(GL_LINES);
glVertex2f(-80.Of, y);
glVertex2f(80.Of, y); glEnd();
factor++;
glPolygonStipple
Patrz także
gllineWidth
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Określa szerokość linii rysowanych przy użyciu prymitywów GL_LINES, GL_LINE_STIPPLE oraz GLLINELOOP.
void glLineWidth(GLfloat width);
Ta funkcja ustala szerokość (w pikselach) rysowanych linii. Bieżącą szerokość linii można pobrać za pomocą poniższego kodu:
GLfloat fSize; glGetFloatv(GL_LINE_WIDTH, ŁfSize) ;
192
Część II » Używanie OpenGL
Bieżąca szerokość linii zostanie wpisana do zmiennej fSize. Za pomocą poniższego kodu możesz wyznaczyć minimalną i maksymalną dostępną szerokość linii:
GLfloat fSizes[2]; glGetFloatv(GL_LINE_WIDTH_RANGE, fSizes) ;
W tym przypadku, minimalna grubość linii zostanie wpisana do fSizes[0], zaś szerokość maksymalna - do fSizes[l].
Na koniec, najmniejszy dopuszczalny przyrost szerokości linii może zostać odczytany przez poniższy fragment kodu:
GLfloat fStepSize; glGetFloatv(GL_LINE_WIDTH_GRANULARITY, SfStepSize);
Specyfikacja OpenGL wymaga, aby w każdej implementacji była dostępna co najmniej jedna szerokość linii, wynosząca l piksel. W ogólnej implementacji Microsoftu dostępny zakres obejmuje szerokości od 0,5 do 10,0 pikseli, z krokiem 0,125 piksela.
Parametry wldth
Zwracana wartość Przykład
GLfloat: Określa szerokość rysowanych linii. Domyślną wartością jest 1.0.
Brak.
Poniższy fragment kodu (z programu LINESW w tym rozdziale) demonstruje rysowanie linii o różnych grubościach.
void RenderScene(void)
GLfloat y; // Zmienna dla współrzędnej Y
GLfloat fSizes[2]; // Zakres szerokości linii
GLfloat fCurrSize; // Przechowuje bieżącą szerokość
// Pobranie dostępnych rozmiarów linii // i zapamiętanie najmniejszej wartości przyrostu glGetFloatv(GL_LINE_WIDTH_RANGE, fSizes) ; fCurrSize = fSizes[0];
// Przejście w osi Y po 20 punktów for(y = -90.Of; y < 90.Of; y += 20.Of) {
// Ustawienie szerokości linii
glLineWidth(fCurrSize) ;
// Narysowanie linii glBegin(GL_LINES);
glVertex2f(-80.Of, y);
glVertex2f(80.Of, y); glEnd();
193
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty
// Poszerzenie linii fCurrSize += l.Of;
glPointSize
Patrz także
gIPointSize
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Określa rozmiar punktów rysowanych przy użyciu prymitywu GL_POINTS.
void glPointSize(GLfloat size);
Ta funkcja ustala rozmiar (w pikselach) punktów rysowanych przy użyciu prymitywu GL_POINTS.
Bieżący rozmiar punktów można pobrać za pomocą poniższego kodu:
GLfloat fSize; glGetFloatv(GL_POINT_SIZE, SfSize) ;
Bieżący rozmiar punktów zostanie wpisany do zmiennej fSize. Za pomocą poniższego kodu możesz wyznaczyć minimalny i maksymalny dostępny rozmiar punktów:
GLfloat fSizes[2] ; glGetFloatv(GL_POINT_SIZE_RANGE, fSizes) ;
W tym przypadku minimalny rozmiar punktów zostanie wpisany do fSizes[0], zaś rozmiar maksymalny - do fSizesfl].
Na koniec, najmniejszy dopuszczalny przyrost rozmiaru punktów może zostać odczytany przez poniższy fragment kodu:
GLfloat fStepSize; glGetFloatv(GL_POINT_SIZE_GRANULARITY, SfStepSize) ;
Specyfikacja OpenGL wymaga, aby w każdej implementacji był dostępny co najmniej jeden rozmiar punktów, wynoszący l piksel. W ogólnej implementacji Microsoftu, dostępny zakres obejmuje rozmiary od 0,5 do 10,0 pikseli, z krokiem 0,125 piksela.
Parametry
size
GLfloat: Określa przybliżoną średnicę rysowanych punktów. Domyślną wartością jest 1,0.
194
Część II » Używanie OpenGL
Zwracana wartość Brak.
Przykład
Poniższy fragment kodu (z programu POINTSZ w tym rozdziale) demonstruje rysowanie punktów o różnych rozmiarach.
GLfloat x,y,z,angle; // Zmienne dla współrzędnych
//i kątów GLfloat sizes[2]; // Przechowuje obsługiwany
// zakres wielkości punktów GLfloat step; // Przechowuje obsługiwany
// przyrost wielkości punktów GLfloat curSize; // Przechowuje bieżący rozmiar
// Pobranie zakresu obsługiwanych wielkości // i przyrostu wielkości glGetFloatv(GL_POINT_SIZE_RANGE,sizes) ; glGetFloatv(GL_POINT_SIZE_GRANULARITY, Sstep) ;
// Ustawienie początkowego rozmiaru punktów curSize = sizes[0];
// Ustawienie początkowej zmiennej Z z = -50. Of;
// Przejście całego okręgu trzy razy
for(angle = O.Of; angle <= (2.0f*3 . 1415f ) *3.0f ; angle += O.lf)
(
// Oblicza wartości z i y na okręgu
x = 50. Of*sin (angle) ;
y = 50. Of*cos (angle) ;
// Określenie rozmiaru punktu przed // wybraniem prymitywu glPointSize (curSize) ;
// Rysowanie punktu glBegin(GL_POINTS) ;
glVertex3f (x, y, z); glEnd ( ) ;
// Zwiększenie współrzędnej Z i wielkości punktu z += 0.5f; curSize +•= step;
glLineWidth
Patrz także
glPolygonMode
Przeznaczenie Określa tryb rysowania wielokątów.
Plik nagłówkowy <gl.h>
Składnia void glPolygonMode(GLenum face, GLenum modę);
r
195
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty
Opis
Ta funkcja umożliwia zmianę sposobu rysowania wielokątów. Domyślnie, wielokąty są kolorowane lub cieniowane przy użyciu bieżącego koloru i właściwości materiału. Można jednak nakazać rysowanie jedynie sylwetki lub punktów odpowiadających wierzchołkom wielokąta. Co więcej, można określić tryb rysowania osobno dla przedniej, tylnej lub obu stron wielokąta.
Parametry face
modę
GLenum: Określa stronę wielokątów, do której będzie się odnosić dane wywołanie funkcji: GL_FRONT (przednia strona), GL_BACK (tylna strona) lub GL_FRONT_AND_BACK (obie strony).
GLenum: Określa nowy tryb rysowania. Domyślnym trybem jest GL_FILL, dający wypełnione wielokąty. GL_LINE powoduje rysowanie jedynie sylwetek wielokątów (zewnętrznych krawędzi), zaś GL_POINT powoduje rysowanie jedynie punktów w wierzchołkach wielokąta. O tym, czy krawędzie i wierzchołki wielokąta zostaną narysowane, decyduje wartość znacznika krawędzi, ustawianego funkcją glEdgeFlag.
Zwracana wartość Brak.
Przykład
Patrz także
Poniższy fragment kodu (z programu TRIANGLE w tym rozdziale) demonstruje rysowanie tylnych stron wielokątów w postaci siatki krawędzi, w zależności od wartości zmiennej bOutline.
// Rysowanie tylko siatki wielokąta, // jeśli znacznik jest ustawiony if(bOutline)
glPolygonMode(GL_BACK,GL_LINE); else
glPolygonMode(GL_BACK,GL_FILL);
glEdgeFlag, glLineStipple, glLineWidth, glPointSize, glPolygonStipple
glPolygonStipple
Przeznaczenie Plik nagłówkowy Składnia Opis
Określa wzór wypełniania wielokątów.
void glPolygonStipple(const GLubyte *mask);
Do wypełniania wielokątów można użyć wzorca bitowego o wymiarach 32x32 bity. Przedtem należy wywołać funkcję
glEnable(GL_POLYGON_STIPPLE). Jedynki we wzorcu są rysowane bieżącym kolorem wypełniania, zera nie są rysowane.
Parametry *mask
const GLubyte: Wskazuje na bufor o rozmiarze 32x32 bity, zawierający wzór wypełniania wielokąta. O sposobie upakowania bitów wzorca decyduje funkcja glPixelSize. Podczas interpretacji wzorca jako pierwszy odczytywany jest najbardziej znaczący bajt.
196
Część II » Używanie OpenGL
Zwracana wartość Brak.
Przykład
Poniższy fragment kodu (z programu PSTIPPLE w tym rozdziale) ilustruje użycie wzorca wypełniania wielokąta. Wypełniany wielokąt ma kształt ośmiokąta (znaku Stop).
// Włączenie wypełniania wielokąta glEnable(GL_POLYGON_STIPPLE);
// Wskazanie desenia wypełniania glPolygonStipple(fire);
// Wywoływane w celu narysowania sceny void RenderScene(void)
// Utworzenie kształtu znaku STOP
// Dla uproszczenia zostanie użyty standardowy wielokąt glBegin(GL_POLYGON);
glVertex2f(-20.Of, 50.Of);
glVertex2f(20.Of, 50.Of);
glVertex2f(50.Of, 20.Of);
glVertex2f(50.Of, -20.Of);
glvertex2f(20.Of, -50.Of);
glVertex2f(-20.Of, -50.Of);
glVertex2f(-50.Of, -20.Of);
glVertex2f(-50.Of, 20.Of); glEnd();
Patrz także
glLineStipple, glGetPolygonStipple, glPixelStore
glVertex
Przeznaczenie Plik nagłówkowy Składnia
Określa współrzędne wierzchołka w trójwymiarowej przestrzeni.
void glVertex2d(GLdouble x, GLdouble y);
void glVertex2f(GLfloat x, GLfloat y);
void glVertex2i(GLint x, GLint y);
void glVertex2s(GLshort x, GLshort y);
void glVertex3d(GLdouble x, GLdouble y, GLdouble z);
void glVertex3f(GLfloat x, GLfloat y, GLfloat z);
void glVertex3i(GLint x, GLint y, GLint z);
void glVertex3s(GLshort x, GLshort y, GLshort z);
197
Rozdział 6. * Rysowanie w trzech wymiarach: linie, punkty i wielokąty
Opis
Parametry
x,y,z
w
void glVertex4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w);
void glVertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w);
void glVertex4i(GLint x, GLint y, GLint z, GLint w);
void gIVertex4s(GLshort x, GLshort y, GLshort z, GLshort w);
void glVertex2dv(const GLdouble *v);
void glVertex2fv(const GLfloat *v);
void głVertex2iv(const GLint *v);
void glVertex2sv(const GLshort *v);
void glVertex3dv(const GLdouble *v);
void glVertex3fv(const GLfloat *v);
void glVertex3iv(const GLint *v);
void glVertex3sv(const GLshort *v);
void glVertex4dv(const GLdouble *v);
void glVertex4fv(const GLfloat *v);
void glVertex4iv(const GLint *v);
void glVertex4sv(const GLshort *v);
Ta funkcja służy do określenia współrzędnych wierzchołków dla punktów, linii i wielokątów, wskazanych w wywołaniu funkcji glBegin. Ta funkcja nie może być wywoływana poza parą funkcji glBegin/glEnd.
Współrzędne x, y oraz z wierzchołka. Gdy z nie jest podawane, domyślną wartością tej współrzędnej jest 0,0.
Współrzędna w wierzchołka. Ta współrzędna jest używana do skalowania i nakładania tekstury; jej domyślną wartością jest 1,0. Przy skalowaniu trzy pozostałe współrzędne są dzielone przez tę współrzędną.
Tablica wartości, zawierająca 2, 3 lub 4 wartości konieczne do zdefiniowania wierzchołka.
Zwracana wartość Brak.
Przykład
Patrz także
Ta funkcja występuje we wszystkich przykładach w tej książce (i praktycznie we wszystkich programach OpenGL). Poniższy kod przedstawia rysowanie pojedynczego punktu położonego w środku układu współrzędnych.
glBegin(GL_POINTS);
glVertex3f(0.0, 0.0, 0.0); glEnd();
glBegin, glEnd
Rozdział 7.
Manipulowanie przestrzenią 3D: transformacje współrzędnych
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
4 Ustawić swoją pozycję w scenie * gluLookAt/glTranslate/glRotate
* Ustawić obiekty w scenie * glTranslate/glRotate
* Skalować obiekty * glScale
* Włączyć rzutowanie perspektywiczne * gluPerspective
* Wykonywać obliczenia na macierzach 4 glLoadMatrix/glMultMatrix
W rozdziale szóstym nauczyłeś się rysować linie, punkty i inne kształty. Aby zamienić kolekcję prymitywów w spójną scenę, musisz je odpowiednio ułożyć względem siebie i względem obserwatora. W tym rozdziale zaczniemy przemieszczać kształty i obiekty w układzie współrzędnych. (W rzeczywistości nie przesuwamy obiektów, ale raczej przesuwamy układ współrzędnych, tak aby uzyskać pożądany widok). Możliwość umieszczania i orientowania obiektów w scenie jest podstawową sprawą dla każdego programisty trójwymiarowej grafiki. Jak się przekonasz, wygodnie jest opisać kształt obiektu umieszczonego w środku układu współrzędnych, a dopiero potem obrócić obiekt i przesunąć go we właściwe położenie.
200____________________________________Część II » Używanie OpenGL
Czy to jest ten rozdział z matematyką?
Tak, to właśnie ten rozdział. Możesz się jednak odprężyć - postaramy się, aby nie był aż tak straszny.
Kluczem do transformacji obiektów i układu współrzędnych są dwie macierze modelowania, przechowywane przez OpenGL. Aby zapoznać cię z tymi macierzami, ten rozdział stanowi pewien kompromis pomiędzy dwoma krańcowymi podejściami do grafiki komputerowej. Z jednej strony, moglibyśmy cię ostrzec: „przed przeczytaniem tego rozdziału przejrzyj podręcznik algebry liniowej". Z drugiej strony moglibyśmy spróbować cię oszukać, twierdząc, że „nauczysz się tworzyć trójwymiarową grafikę bez wszystkich tych skomplikowanych wzorów matematycznych". Nie popieramy jednak żadnej z tych dwóch szkół.
Rzeczywiście, możesz tworzyć wspaniałą trójwymiarową grafikę bez bliższej znajomości zaawansowanej matematyki, podobnie jak możesz każdego dnia prowadzić samochód nie wiedząc nic o mechanice i zasadzie działania silnika. Jest jednak lepiej jeśli wiesz przynajmniej, że od czasu do czasu trzeba wymienić olej, napełnić bak paliwem czy wymienić opony, gdy staną się łyse. Czyniąc to, stajesz się odpowiedzialnym (i bezpiecznym!) posiadaczem pojazdu. Jeśli chcesz być profesjonalnym programistą OpenGL, powinieneś coś wiedzieć o wykorzystywanej matematyce. Musisz opanować przynajmniej podstawy, tak aby wiedzieć, co możesz zrobić i jakie narzędzia będą do tego najodpowiedniejsze.
Zatem, mimo że nie potrafisz przemnożyć w myśli dwóch macierzy, powinieneś jednak wiedzieć, czym są macierze i jakie mają zastosowanie w trójwymiarowej magii OpenGL. Zanim jednak odkurzysz ten stary podręcznik algebry liniowej (czy jest ktoś, kto go nie ma?), pozbądź się obaw - OpenGL wszystkie obliczenia bierze na siebie. Potraktuj to jak użycie kalkulatora do podzielenia dużej liczby, której nie potrafiłbyś podzielić na papierze. Mimo że nie musisz robić tego samodzielnie, w dalszym ciągu musisz wiedzieć, co i po co dzielisz. Widzisz - możesz mieć ciastko i jednocześnie je jeść!
Przekształcenia
Przekształcenia umożliwiają rzutowanie trójwymiarowych współrzędnych na dwuwymiarowy ekran. Przekształcenia umożliwiają także obracanie obiektów, przesuwanie, a nawet ich rozciąganie, kurczenie czy zawijanie. Zamiast bezpośrednio modyfikować obiekt, przekształcenie modyfikuje układ współrzędnych. Gdy przekształcenie obróć układ współrzędnych, wszystkie rysowane obiekty będą obrócone. Po określeniu wierzchołków, a przed narysowaniem obiektów na ekranie, korzystamy z trzech rodzajów przekształceń: punktu obserwacji, modelu i rzutowania. W tej sekcji opiszemy właści wości każdego z tych przekształceń, zebranych w tabeli 7.1.
201
Rozdział 7. « Manipulowanie przestrzenią 3D: transformacje współrzędnych
Tabela 7.1.
Rodzaje przekształceń -w OpenGL
Przekształcenie
Zastosowanie
Punktu obserwacji Modelu
Widoku modelu Rzutowanie Widoku okna
Określa położenie obserwatora lub kamery Przesuwa obiekty w scenie Opisuje dwoistość przekształceń widoku i modelu Obcina i dostosowuje rozmiary bryły widzenia Skaluje końcowy obraz do wymiarów okna
Współrzędne obserwatora
Ważną koncepcją w tym rozdziale sąwspółrzędne obserwatora. Współrzędne obserwatora określają układ współrzędnych obserwatora, bez względu na wszelkie przekształcenia, które mogą zostać zastosowane - potraktuj je jako „bezwzględne" współrzędne ekranowe. Tak więc współrzędne obserwatora nie są rzeczywistymi współrzędnymi, lecz reprezentują raczej pewien ustalony układ współrzędnych, używany jako układ odniesienia. Wszystkie przekształcenia omawiane w tym rozdziale są opisywane właśnie pod kątem ich efektów względem układu współrzędnych obserwatora.
Rysunek 7.1 przedstawia układ współrzędnych obserwatora widziany z dwóch różnych miejsc. Z lewej strony (a) układ współrzędnych obserwatora jest przedstawiony tak, jak widzi go użytkownik (tzn. prostopadle do ekranu monitora). Po prawej stronie (b) układ współrzędnych obserwatora został nieco obrócony, aby lepiej było widać położenie osi z. Z punktu widzenia użytkownika, dodatnie wartości osi x i y biegną, odpowiednio, w prawo i w górę. Dodatnie wartości osi z biegną w kierunku patrzącego.
Rysunek 7.1.
Dwa widoki układu
współrzędnych
obserwatora
l +z Obserwator/V^
•y
(a)
•y
(b)
Gdy rysujesz trójwymiarowe obiekty w OpenGL, używasz układu współrzędnych karte-zjańskich. Gdyby nie zostały użyte żadne przekształcenia, ten układ byłby identyczny z układem współrzędnych obserwatora. Wszystkie przekształcenia zmieniają bieżący układ współrzędnych względem układu współrzędnych obserwatora. Właśnie w ten sposób są obracane i przesuwane obiekty sceny - przez przesunięcie i obrócenie układu
202____________________________________Część II » Używanie
współrzędnych względem układu współrzędnych obserwatora. Rysunek 7.2 przedstaw dwuwymiarowy przykład układu współrzędnych obróconego o 45 stopni względt układu współrzędnych obserwatora. Kwadrat narysowany w tym odwróconym układj współrzędnych także wydaje się obrócony.
Rysunek 7.2.
Układ współrzędnych y Przekształcony
współrzędnych obserwatora s^ ** yi układ
obrócony względem
ukladu
współrzędnych vy' Vl^- Przekształcony kwadrat «°delu
obserwatora ' ' ~
W tym rozdziale przestudiujemy metody służące do modyfikacji bieżącego układ współrzędnych przed narysowaniem obiektów. Możesz nawet zachować stan bieżąceg układu, wykonać pewne przekształcenia i narysować część obiektów, a następnie od tworzyć stan układu i zacząć przekształcać go w inny sposób. Poprzez odpowiedni do bór przekształceń możesz dowolnie umieszczać i obracać obiekty w scenie.
Przekształcenia punktu obserwacji
Przekształcenie punktu obserwacji jest pierwszym przekształceniem sceny. Jest używał ne do wyznaczenia położenia oka i kierunku patrzenia obserwatora (kamery). Domyśt nie, punktem obserwacji jest początek układu współrzędnych, zaś kierunek patrzeniif pokrywa się z osią z, w stronę malejących wartości („do" monitora). Ten punkt obser wacji jest przesuwany względem układu współrzędnych obserwatora, w celu umie szczenią „oka" w odpowiednim punkcie i skierowania go w odpowiednim kierunku Gdy punkt obserwacji pokrywa się z początkiem układu współrzędnych, wszystkie obiekty o dodatnich wartościach współrzędnej z znajdują się „za plecami" obserwatora, Przekształcenie punktu obserwacji umożliwia ustawienie punktu obserwacji w dowol nym położeniu i obrócenie go w dowolnym kierunku. Określenie przekształcenia punktu obserwacji odpowiada ustawieniu i skierowaniu kamery na scenę.
i
Podczas definiowania sceny, przekształcenie punktu obserwacji musi być określone jako! pierwsze, przed wszystkimi innymi przekształceniami, gdyż to przekształcenie przesuwa bieżący układ współrzędnych względem układu współrzędnych obserwatora. Wszystkie następne przekształcenia dotyczą tego nowo zmodyfikowanego układu. Później - gdy spróbujemy sami stosować przekształcenia - dokładniej zobaczysz, jak to działa.
Przekształcenia modelu
Przekształcenia modelu są używane do manipulowania modelem i jego poszczególnymi obiektami. W tym przekształceniu obiekty są przesuwane, modelowane i skalowane.
203
Rozdział 7. * Manipulowanie przestrzenią 3D: transformacje współrzędnych
Rysunek 7.3 ilustruje trzy przekształcenia modelu, zastosowane na obiektach. Rysunek 7.3a pokazuje przesunięcie (translację), w którym obiekt jest przesuwany o zadany wektor. Rysunek 7.3b przedstawia obrót (rotację), w której obiekt jest obracany wokół zadanej osi. Na koniec, rysunek 7.3c pokazuje efekt skalowania, w którym rozmiary obiektu są zwiększane lub zmniejszane o zadaną wartość. Skalowanie może być nierównomierne (obiekt może być skalowany o różne wartości w różnych kierunkach) i może być użyte do „ściskania" lub rozciągania obiektów.
Rysunek 7.3.
Przekształcenia modelu
y---y
Przesunięcia (a)
Końcowy wygląd sceny lub obiektu zależy głównie od kolejności zastosowanych przekształceń modelu. Jest to szczególnie istotne w przypadku przesunięć połączonych z obrotem. Rysunek 7.4a przedstawia przekształcenie kwadratu, w którym kwadrat jest najpierw obracany wokół osi z, a następnie przesuwany wzdłuż nowo przekształconej osi x. Na rysunku 7.4b ten sam kwadrat jest najpierw przesuwany wzdłuż osi x, a następnie obracany wokół osi z. Różnica w końcowym położeniu kwadratu wynika z tego, że każde przekształcenie odbywa się względem poprzednio zastosowanych przekształceń. Na rysunku 7.4a kwadrat jest najpierw obracany względem środka układu. Na rysunku 7.4b, po przesunięciu kwadratu, wykonywany jest obrót względem nowo przesuniętego środka układu.
204
Część II » Używanie OpenGL
Rysunek 7.4.
Przekształcenia
modelu:
obrót/przesunięcie
oraz
przesunięcie/obrót
Dwoistość widoku modelu
Przekształcenia punktu obserwacji i przekształcenia modelu są w rzeczywistości tym samym, jeśli chodzi o ich wpfyw na końcowy wygląd sceny. Rozróżnienie pomiędzy nimi jest stosowane wyłącznie dla wygody programisty. W rzeczywistości nie ma żadnej różnicy pomiędzy przesunięciem obiektu a przesunięciem w drugą stronę układu odniesienia-jak pokazano na rysunku 7.5, końcowy efekt jest taki sam. (Najwyraźniej widać to, gdy siedzimy w stojącym pociągu, zaś na sąsiednim peronie rusza pociąg; często wydaje się wtedy, że to nasz pociąg odjeżdża w przeciwnym kierunku). Termin „widok modelu" jest używany w celu wskazania, że możesz potraktować przekształcenie jako przekształcenie modelu lub jako przekształcenie punktu obserwacji, choć w rzeczywistości są one tym samym, czyli przekształceniem widoku modelu.
Rysunek 7.5.
Dwa sposoby patrzenia na przekształcenie punktu obserwacji
Przesunięcie układu współrzędnych (b)
Przesunięcie obserwatora (a)
205
Rozdział 7. « Manipulowanie przestrzenią 3D: transformacje współrzędnych
Tak więc przekształcenie punktu obserwacji jest właśnie przekształceniem modelu zastosowanym w odniesieniu pozornego obiektu (kamery lub oka) przed narysowaniem obiektów. Jak już wkrótce zobaczysz, każdy następny obiekt sceny podlega kolejnym przekształceniom. Początkowe przekształcenie zapewnia układ odniesienia, na którym opierają się wszystkie następne przekształcenia.
Przekształcenia rzutowania
Przekształcenia rzutowania są stosowane w końcowej scenie, z już zastosowanymi wszystkimi przekształceniami widoku modelu. Rzutowanie określa bryłę widzenia i definiuje płaszczyzny obcinania. Co więcej, przekształcenie rzutowania określa, jak ukończona scena (po zakończeniu modelowania) jest rzutowana na końcowy obraz na ekranie. W tym rozdziale poznasz dwa rodzaje rzutowania: równoległe {perspektywiczne.
W rzutowaniu równoległym wszystkie obiekty są rysowane na ekranie z zachowaniem ich oryginalnych rozmiarów. Takie rzutowanie jest typowe dla systemów CAD i dokumentacji technicznej, w których ważne jest zachowanie rzeczywistych proporcji obiektów.
Rzutowanie perspektywiczne przedstawia obiekty w taki sposób, w jaki widzimy je w rzeczywistym świecie. Cechą rzutowania perspektywicznego jest pozorne zmniejszanie się bardziej oddalonych obiektów; obiekty położone dalej wydają się mniejsze od takich samych obiektów położonych bliżej. W takim rzutowaniu linie równoległe nie zawsze są rysowane jako równoległe. Na przykład szyny toru kolejowego biegną równolegle, lecz w rzutowaniu perspektywicznym zbiegają się w pewnym odległym punkcie. Taki punkt nosi nazwę punktu zbiegu.
Zaletą rzutowania perspektywicznego jest to, że nie musisz sam decydować, które obiekty mają być mniejsze i jak małe powinny być odległe obiekty. Wszystko, co musisz zrobić, to zdefiniować scenę za pomocą przekształceń widoku modelu, a następnie zastosować przekształcenie perspektywiczne. OpenGL zajmie się całą magią.
Rysunek 7.6 przedstawia rzutowanie perspektywiczne i równoległe na przykładzie dwóch różnych scen.
Rysunek 7.6.
Przykłady
rzutowania
równoległego
i perspektywicznego
Perspektywiczne
Perspektywiczna
Równoległe
Ogólnie, rzutowanie równoległe powinno być stosowane podczas modelowania prostych obiektów, dla których nie ma znaczenia odległość od obserwatora. Rzutowanie perspektywiczne zwykle wygląda naturalnie wtedy, gdy stosunek rozmiaru obiektu do
206____________________________________Część II » Używanie OpenGL
jego odległości od obserwatora jest dość mały (oglądamy duże obiekty z dużej odległości). Tak więc samochód stojący na wystawie może być przedstawiony w rzucie równoległym, ale jeśli staniesz dokładnie przed maską i spojrzysz w kierunku samochodu, perspektywa zaczyna nabierać dużego znaczenia. Rzutowanie perspektywiczne jest używane przy renderowaniu scen składających się z wielu rozrzuconych obiektów, na przykład widoków architektonicznych czy widoków z lotu ptaka, lub w przypadku dużych obiektów, które w zależności od punktu obserwacji mogą się wydawać zniekształcone. W większości przypadków rzutowanie perspektywiczne jest najodpowiedniejsze.
Przekształcenia okna
Gdy wszystko zostanie wymodelowane i przekształcone, mamy dwuwymiarowy rzut sceny, który powinien zostać umieszczony w jakimś oknie na ekranie. To odwzorowanie obrazu na fizyczne współrzędne okna jest ostatnim z przekształceń i nosi nazwę przekształcenia okna. Okna (widoki) zostały omówione pokrótce w rozdziale trzecim, w którym rozciągaliśmy lub ściskaliśmy obraz tak, aby kwadratowy rysunek zmieścił się w prostokątnym oknie.
Ach, te macierze...
Uzbrojeni w podstawowy słownik definicji i przekształceń, jesteśmy gotowi do przeprowadzenia prostych operacji na macierzach. Zobaczmy, jak OpenGL przeprowadza te transformacje oraz jakie funkcje służą do osiągnięcia zamierzonego efektu.
Matematyka kryjąca się za operacjami na macierzach jest znacznie uproszczona dzięki stosowaniu tzw. zapisu macierzowego. Każde z omawianych przekształceń można osiągnąć przez przemnożenie macierzy zawierających współrzędne wierzchołków przez macierz opisującą dane przekształcenie. Tak więc wszystkie przekształcenia dostępne w OpenGL sprowadzaj ą się do mnożenia przez siebie dwóch lub więcej macierzy.
Co to jest macierz?
Macierz nie jest niczym innym jak zestawem liczb ułożonych w wierszach i kolumnach - czyli z punktu widzenia programisty jest po prostu dwuwymiarową tablicą. Macierz nie musi być kwadratowa, jednak we wszystkich wierszach musi być ta sama ilość elementów; także wszystkie kolumny muszą mieć jednakową wysokość, tj. zawierać jednakową ilość elementów. Przykłady macierzy pokazuje rysunek 7.7. (Te macierze nie reprezentują niczego szczególnego, a jedynie demonstrują własną strukturę). Zwróć uwagę, że macierz może składać się z pojedynczej kolumny elementów.
Rozdział 7. + Manipulowanie przestrzenią 3D: transformacje współrzędnych______ ___207
Rysunek 7.7.
Przykłady macierzy
1
|
4
|
7
|
|
0
|
42
|
2
|
5
|
8
|
|
1.5
|
0.877
|
_3
|
6
|
9_
|
|
_2
|
14
|
Naszym celem nie jest zagłębianie się w szczegóły matematyki i manipulowania macierzami. Jeśli chcesz dowiedzieć się czegoś więcej o manipulowaniu macierzami i samodzielnym kodowaniu pewnych specjalnych przekształceń, dobrym punktem wyjścia może być literatura podana w dodatku B.
Kolejka przekształceń
W celu zastosowania przekształceń opisywanych w tym rozdziale, będziemy modyfikować głównie dwie macierze: macierz widoku modelu oraz macierz rzutowania. Nie martw się, OpenGL zapewnia specjalne funkcje przeznaczone do modyfikowania tych macierzy jako całości. Poszczególne elementy tych macierzy będziesz modyfikował tylko wtedy, gdy zechcesz osiągnąć jakieś specjalne efekty.
Droga od współrzędnych wierzchołka do współrzędnych punktu na ekranie jest długa i żmudna. Proces przekształceń współrzędnych został przedstawiony na rysunku 7.8. Najpierw współrzędne wierzchołka są zamieniane na macierz 1x4, w której pierwsze trzy elementy odpowiadają współrzędnym x, y i z. Czwarty element to współczynnik skalowania, który możesz sam określić, stosując funkcje wierzchołków wymagające podania czterech argumentów. To jest właśnie współrzędna w, domyślnie przyjmująca wartość 1,0. Zwykle rzadko będziesz modyfikował tę wartość bezpośrednio, zamiast tego stosując jedną z funkcji skalowania macierzy widoku modelu.
Rysunek 7.8.
Kolejka
przekształceń
•wierzchołka
obcinania
Or WSf wie
|
X y. z w yginol otrzeć rzcho
|
ne ne ko
|
Macierz widoku modelu «
|
|
x y. w_ Jcszta tedne iserwo
|
|
Macierz rzutowania u
|
|
|
|
|
Prze rspólr ol
|
|
cone punkt qi
|
|
Dzielenie dla perspektywy
|
-*•
|
x/w y /w z/w
|
-
|
Znormalizowane współrzędne urządzenia
|
Przekształcenie
okna (także macierz)
Współrzędne okna
Następnie wierzchołek jest mnożony przez macierz widoku modelu, określającą współrzędne układu obserwatora. Współrzędne układu obserwatora są mnożone przez macierz rzutowania, a następnie obcinane przez współrzędne obcinania. Powoduje to wyeliminowanie wszystkich danych poza bryłą widzenia. Obcięte współrzędne są następnie
Część II «• Używanie <
208
dzielone przez współrzędną w, w celu otrzymania znormalizowanych współrzędnych! urządzenia (punkt jest rzutowany perspektywicznie). Wartość w może być zmodyfik wana przez przekształcenie rzutowania lub przekształcenie widoku modelu, w zależnoś-j ci od zastosowanych przekształceń. Oczywiście, OpenGL i wysokopoziomowe funkcje operacji na macierzach ukrywają wszystkie szczegóły związane z obliczeniami.
Na koniec, trójka współrzędnych jest odwzorowywana na dwuwymiarową płaszcz okna; służy do tego przekształcenie okna. To przekształcenie także jest reprezentov przez macierz, ale nie określasz jej ani nie modyfikujesz bezpośrednio. OpenGL twór ją sam w oparciu o wartości przekazane funkcji glYiewport.
Macierz widoku modelu
Macierz widoku modelu to macierz 4x4, reprezentująca przekształcony układ wsp rzędnych, używany do rozmieszczania i orientowania obiektów. Wierzchołki twór; prymitywy są używane jako jednokolumnowa macierz, przemnażana przez macierz i doku modelu w celu obliczenia nowych współrzędnych, przekształconych wzglę układu obserwatora.
Na rysunku 7.9 macierz zawierająca współrzędne pojedynczego wierzchołka jest mn żona przez macierz widoku modelu w celu otrzymania nowych współrzędnych w ukłj dzie obserwatora. Dane wierzchołka zawierają w rzeczywistości cztery elementy, w l rych dodatkowy element, wartość w, reprezentuje współczynnik skalowania. Ta wa jest domyślnie ustawiana na 1,0 i rzadko będziesz zmieniał j ą samodzielnie.
Rysunek 7.9.
Równanie macierzowe opisujące przekształcenie widoku modelu dotyczące pojedynczego wierzchołka
Przesunięcie
Spójrzmy na przykład modyfikujący macierz widoku modelu. Powiedzmy, że chcea narysować kostkę za pomocą funkcji auxWireCube() z biblioteki AUX. Wywołamy] prostu
auxWireCube(10.Of);
i mamy kostkę umieszczoną w środku układu współrzędnych, z krawędziami o długcj dziesięciu jednostek. Aby przed narysowaniem kostki przesunąć ją w górę osi y oj jednostek, musimy przemnożyć macierz widoku modelu przez macierz opisującą] sunięcie w górę osi y o 10 jednostek, a dopiero potem narysować kostkę. W szkiek wej formie, kod wyglądałby mniej więcej tak:
209
Rozdział 7. * Manipulowanie przestrzenią 3D: transformacje współrzędnych
// Konstruowanie macierzy przesunięcia o 10 jednostek w górę osi Y // Przemnożenie jej przez macierz widoku modelu
// Narysowanie kostki auxWireCube(lO.Of);
W rzeczywistości taką macierz konstruuje się bardzo prosto, jednak wymaga to sporo linii kodu. Na szczęście mamy do dyspozycji specjalną funkcję wyższego poziomu:
void glTranslatef(GLfloat x, GLfloat y, GLfloat z);
Parametry tej funkcji określają przesunięcie w osi x, y oraz z. W ten sposób tworzona jest odpowiednia macierz i wykonywane jest mnożenie. Teraz podany wyżej pseudokod może wyglądać następująco, zaś efekt jego działania jest przedstawiony na rysunku 7.10.
// Przesunięcie w górę osi Y o 10 jednostek glTranslatef(O.Of, 10.Of, O.Og);
// Narysowanie kostki auxWireCube(lO.Of);
Rysunek 7.10.
Kostka przesunięta o 10 jednostek w górę osi y
Obrót
Aby obrócić obiekt wokół jednej z trzech osi, musielibyśmy utworzyć macierz obrotu i przemnożyć ją przez macierz widoku modelu. Jednak także w tym przypadku mamy do dyspozycji specjalną funkcję:
glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
Ta funkcja obraca obiekt o kąt angle wokół linii wyznaczonej przez wektor [x, y, z]. Kierunek obrotu jest określany w stopniach w kierunku przeciwnym do ruchu wskazówek zegara. W najprostszym przypadku obrót następuje wokół jednej z osi, tak że musi zostać podana tylko jedna wartość.
Możesz także wykonywać obrót wokół dowolnej osi, przekazując wartości x, y i z określające wektor wyznaczający oś obrotu. Aby zobaczyć oś obrotu, możesz po prostu narysować linię biegnącą od środka układu do punktu o współrzędnych (x, y, z). Poniższy kod obraca kostkę o 45 stopni wokół zadanej osi, określonej wektorem [1,1, 1]; ilustruje to rysunek 7.11.
210
Część II • Używanie OpenGL
// Wykonanie przekształcenia glRotatef(45.Of, l.Of, l.Of, l.Of);
// Narysowanie kostki auxWireCube(10.0f);
Rysunek 7.11.
Kostka obrócona wokół zadanej osi
Skalowanie
Skalowanie powoduje zwiększenie lub zmniejszenie obiektu we wszystkich trzech osiach o zadane współczynniki. Funkcja
glScalef(GLfloat x, GLfloat y, GLfloat z); mnoży współrzędne x, y i z obiektu przez podane współczynniki.
Skalowanie nie musi być jednorodne. Możesz użyć go także do „ściśnięcia" lub rozciągnięcia obiektu. Poniższy kod tworzy prostopadłościan o większej szerokości dwa razy niż wysokości, a wysokości takiej samej jak kostki w poprzednich przykładach. Wynik widzimy na rysunku 7.12.
// Wykonanie skalowania glScalef (2.Of, l.Of, 2.0f);
// Narysowanie kostki auxWireCube(lO.Of);
Rysunek 7.12.
Niejednorodne skalowanie kostki
Rozdział 7. » Manipulowanie przestrzenią 3D: transformacje współrzędnych__________211
Macierz tożsamościowa
Z pewnością zastanawiasz się, czemu służyła wstępna dyskusja o macierzach. Czy nie moglibyśmy po prostu używać funkcji przesuwając obiekty we właściwe miejsca? Czy rzeczywiście powinno obchodzić nas to, że modyfikowana jest macierz widoku modelu?
Odpowiedź brzmi: i tak, i nie, ale tylko w przypadku rysowania w scenie pojedynczego obiektu. Powodem jest to, że efekty użycia tych funkcji się kumulują. Za każdym razem, gdy wywołujesz jedną z tych funkcji, tworzona jest odpowiednia macierz, która jest mnożona przez macierz widoku modelu. Otrzymana w wyniku macierz staje się nową macierzą widoku modelu, modyfikowaną przez następne przekształcenie, itd.
Przypuśćmy, że chcemy narysować dwie kule: jedną o 10 jednostek w kierunku osi y i jedną o 10 jednostek w kierunku osi x, tak jak pokazano na rysunku 7.13. Mógłbyś pokusić się o napisanie kodu wyglądającego mniej więcej tak:
// Przejście o 10 jednostek w kierunku osi y glTranslatef(O.Of, 10.Of, O.Of);
// Narysowanie pierwszej kuli auxSolidSphere(1.0f);
// Przejście o 10 jednostek w kierunku osi x glTranslatef(10.Of, O.Of, O.Of);
// Narysowanie drugiej kuli auxSolidSphere(1.0f);
Rysunek 7.13.
Dwie kule narysowane na osiach y ix
Weź jednak pod uwagę, że każde wywołanie funkcji glTranslate jest kumulowane w macierzy widoku modelu, więc drugie wywołanie przesunie kulę o dziesięć jednostek w dodatnim kierunku x względem poprzedniego przesunięcia w kierunku osi y. W ten sposób powstanie scena pokazana na rysunku 7.14.
212
Część II » Używanie OpenGL
Rysunek 7.14.
Wynik dwóch kolejnych przesunięć
10
Mógłbyś zastosować dodatkowe wywołanie funkcji glTranslate w celu wycofania się o 10 jednostek w dół osi y, ale w przypadku rozbudowanych scen kod stałby się bardzo skomplikowany i trudny do debuggowania. Prostszą metodą jest ustawienie macierzy widoku modelu w znany stan - w tym przypadku, ustalenie położenia na początku układu współrzędnych obserwatora.
Osiąga się to poprzez załadowanie do macierzy widoku modelu macierzy tożsamościowej. Macierz tożsamościowa określa że nie jest wykonywane żadne przekształcenie, w efekcie powodując że wszystkie podawane współrzędne są podawane w układzie współrzędnych obserwatora. Macierz tożsamościowa zawiera same zera, z wyjątkiem elementów występujących na przekątnej macierzy, które mają wartość 1. Gdy macierz tożsamościowa jest mnożona przez jakąkolwiek macierz wierzchołka, w wyniku otrzymujemy niezmienioną macierz tego wierzchołka. To równanie przedstawia rysunek 7.15.
8.0
|
|
1.0
|
0
|
0
|
0
|
4.5
|
|
0
|
1.0
|
0
|
0
|
-2.0
|
|
0
|
0
|
1.0
|
0
|
1.0
|
|
0
|
0
|
0
|
1.0
|
Rysunek 7.15.
Po przemnożeniu macierzy wierzchołka przez macierz tożsamościową otrzymujemy niezmienioną macierz •wierzchołka
8.0 4.5 -2.0 1.0
Jak już ustaliliśmy, szczegóły wykonywania operacji na macierzach wykraczają poza ramy tej książki. Wystarczy, że zapamiętasz, iż załadowanie macierzy tożsamościowej oznacza, że na wierzchołkach nie zostanie wykonane żadne przekształcenie. Po prostu w ten sposób przywracasz początkowy stan macierzy widoku modelu.
Poniższe dwie linie kodu ładują macierz tożsamościową do macierzy widoku modelu:
glMatrixMode(GL_MODELVIEW); glLoadldentity(};
Pierwsza linia określa, że będziemy się odwoływać do macierzy widoku modelu. Gdy ustawisz bieżącą macierz roboczą (macierz, do której odnoszą się funkcje operujące na macierzach), pozostaje ona bieżącą, aż sam wskażesz inną macierz. Druga linia ładuje
Rozdział 7. » Manipulowanie przestrzenią 3D: transformacje współrzędnych__________213
macierz tożsamościową do bieżącej macierzy (w tym przypadku do macierzy widoku modelu).
Poniższy kod tworzy scenę przedstawioną na rysunku 7.13:
// Uczynienie macierzy widoku modelu macierzą bieżącą i wyzerowanie
// jej
glMatrixMode(GL_MODEVIEW);
glLoadldentity() ;
// Przejście o 10 jednostek w górę osi y glTranslatef(O.Of, 10.Of, O.Of);
// Narysowanie pierwszej kuli auxSolidSphere(l.Of} ;
// Ponowne wyzerowanie macierzy widoku modelu glLoadldentity();
// Przejście o 10 jednostek w kierunku osi x glTranslatef(10.Of, O.Of, O.Of};
// Narysowanie drugiej kuli auxSolidSphere(l.Of);
Stosy macierzy
Nie zawsze zerowanie macierzy widoku modelu, przed umieszczeniem w scenie kolejnego obiektu, jest pożądane. Często wygodnie jest zachować bieżący stan macierzy, a następnie odtworzyć go po umieszczeniu innych obiektów. Jest to szczególnie użyteczne, gdy na początku przygotujesz macierz widoku modelu jako macierz widoku sceny (czyli punkt obserwacji nie znajduje się już na początku układu współrzędnych).
Aby to umożliwić, OpenGL tworzy stos macierzy dla macierzy widoku modelu oraz macierzy rzutowania. Stos macierzy działa tak, jak każdy stos programowy. Możesz umieścić bieżącą macierz na stosie w celu zachowania jej stanu, a potem odpowiednio przekształcić grupę następnych obiektów. Gdy zechcesz odtworzyć stan macierzy, po prostu zdejmiesz ją ze stosu. Ilustruje to rysunek 7.16.
Rysunek 7.16.
Działanie stosu macierzy
Stos macierzy
214 Część II » Używanie OpenGL
Stos macierzy tekstur
Stos macierzy tekstur jest jeszcze jednym stosem dostępnym dla programisty. Jest używany przy przekształceniach współrzędnych tekstur. Mapowanie i współrzędne tekstur zostaną omówione w rozdziale 12.
Stos może osiągnąć maksymalną wysokość, którą można odczytać za pomocą wywołań
glGet(GL_MAX_MODELVIEW_STACK_DEPTH);
lub
glGet(GL_MAX_PROJECTION_STACK_DEPTH) ;
Jeśli zostanie przekroczona maksymalna wysokość stosu, wystąpi błąd GL_STACK_ OYERFLOW; jeśli spróbujesz zdjąć macierz z pustego stosu, wystąpi błąd GL_ STACK_UNDERFLOW. Wysokość stosu jest zależna od implementacji. W przypadku programowej implementacji Microsoftu, maksymalna wysokość stosu wynosi 32 dla stosu macierzy widoku modelu, a 2 dla stosu macierzy rzutowania.
Atomowy przykład
Spróbujmy zastosować nabytą wiedzę w praktyce. W następnym przykładzie zbudujemy prosty, animowany model atomu. W środku atomu umieścimy pojedynczą kulę, reprezentującą jądro, dookoła którego będą poruszać się trzy elektrony. Użyjemy rzutowania równoległego, tak jak we wszystkich poprzednich przykładach w książce. (Inne interesujące rzutowania zostaną omówione w następnej sekcji, „Rzutowania").
Rysunek 7.17.
Działanie programu ATOM
Nasz program ATOM, korzystając z timera, przesuwa elektrony cztery razy w ciągu sekundy (czyli o wiele wolniej, niż poruszają się prawdziwe elektrony!). Za każdym razem, gdy wywoływana jest funkcja RenderScene, zwiększany jest kąt obrotu elektronu dookoła jądra. Ponadto każdy elektron krąży w innej płaszczyźnie. Funkcja RenderScene z tego
Rozdział 7. * Manipulowanie przestrzenią 3D: transformacje współrzędnych__________215
przykładu jest przedstawiona na listingu 7.1, zaś wynik działania programu widzimy na rysunku 7.17.
Listing 7.1. Funkcja rysunkowa z programu ATOM______________________________
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
// Kąt obrotu dookoła jądra
static float fElectl = O.Of;
// Wyczyszczenie okna bieżącym kolorem tła
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Wyzerowanie macierzy widoku modelu glMatrixMode(GL_MODELVIEW); glLoadldentity();
// Przekształcenie całej sceny do układu obserwatora // To jest początkowe przekształcenie widoku glTranslatef(O.Of, O.Of, -100.Of);
// Czerwone jądro glRGB(255, O, 0); auxSolidSphere(10.0f);
// Żółte elektrony glRGB(255,255,0);
// Orbita pierwszego elektronu
// Zachowanie przekształcenia widoku
glPushMatrix ();
// Obrót o kąt
glRotatef(fElectl, O.Of, l.Of, O.Of);
// Przesunięcie elektronu z początku układu na orbitę glTranslatef(90.Of, O.Of, O.Of);
// Rysowanie elektronu auxSolidSphere( 6. Of) ;
// Odtworzenie przekształcenia widoku glPopMatrix ();
// Orbita drugiego elektronu
glPushMatrix () ;
glRotatef(45.Of, O.Of, O.Of, l.Of);
glRotatef(fElectl, O.Of, l.Of, O.Of);
glTranslatef(-70.Of, O.Of, O.Of);
auxSolidSphere(6.0f);
glPopMatrix () ;
// Orbita trzeciego elektronu
glPushMatrix();
glRotatef(360.Of-45.0f,O.Of, O.Of, l.Of);
glRotatef(fElectl, O.Of, l.Of, O.Of);
glTranslatef(O.Of, O.Of, 60.Of);
auxSolidSphere(6.Of);
glPopMatrix();
216
Część II * Używanie OpenGL
// Zwiększenie ka_ta obrotu fElectl += 10.Of; if(fElectl > 360.Of) fElectl = O.Of;
// Zrzucenie poleceń graficznych glFlush() ;
Przeanalizujmy kod umieszczający jeden z elektronów, omawiając po kilka linii naraz. Pierwsza linia zachowuje bieżący stan macierzy widoku modelu, odkładając na stos bieżące przekształcenie:
// Orbita pierwszego elektronu
// Zachowanie przekształcenia widoku
glPushMatrix () ;
Następnie układ współrzędnych jest obracany dookoła osi y o kąt fElectl:
II Obrót o kąt
glRotatef(fElectl, O.Of, l.Of, O.Of);
Następnie umieszczamy elektron na orbicie, przesuwając obrócony układ współrzędnych:
// Przesunięcie elektronu z początku układu na orbitę glTranslatef(90.Of, O.Of, O.Of);
Potem następuje rysowanie elektronu (w postaci kuli), po czym jest odtwarzany stan macierzy widoku modelu, przez zdjęcie jej ze stosu macierzy:
// Rysowanie elektronu auxSolidSphere(6.0f);
// Odtworzenie przekształcenia widoku glPopMatrix();
Te operacje powtarzają się dla pozostałych elektronów.
Rzutowania
Do tej pory w przykładach używaliśmy macierzy widoku modelu do umieszczania punktu obserwacji oraz do rozmieszczania obiektów w scenie. Macierz rzutowania określa zaś rozmiar i kształt naszej bryły widzenia.
Jak dotąd, cały czas tworzyliśmy zwykłą, prostopadłościenną bryłę widzenia, za pomocą funkcji glOrtho określając położenie bliższej, dalszej, lewej, prawej, górnej oraz dolnej płaszczyzny obcinania. Gdy do macierzy rzutowania zostanie załadowana macierz tożsamościowa, bryła widzenia zajmuje przestrzeń od środka układu do punktu o współrzędnych równych l dla wszystkich trzech osi - macierz rzutowania nie realizuje zatem żadnego skalowania ani perspektywy. Jak się wkrótce przekonamy, nie jest to jedyna możliwość, jaką mamy do dyspozycji.
Rozdział 7. * Manipulowanie przestrzenią 3D: transformacje współrzędnych 217
Rzutowanie równoległe
W rzutowaniu równoległym, którego używaliśmy do tej pory najczęściej, bryła widzenia ma kształt prostopadłościenny, a zatem szerokość logiczna obiektu mierzona wzdłuż przeciwległych płaszczyzn (tylnej i przedniej, górnej i dolnej, lewej i prawej) jest identyczna. Daje to w efekcie rzut równoległy, odpowiedni dla zobrazowania obiektów, dla których chcemy uniknąć zniekształcenia perspektywą. Rzutowanie takie jest przydatne w programach CAD i rysunkach architektonicznych (w izometriach), w których chcemy zachować właściwe rozmiary i proporcje obiektów.
Rysunek 7.18 przedstawia wynik działania przykładowego programu ORTHO z płytki CD-ROM. W celu utworzenia tego wydrążonego pudełka użyliśmy rzutowania równoległego, tak jak we wszystkich poprzednich przykładach.
Rysunek 7.18.
Wydrążone pudełko przedstawione w rzucie równoległym
Rysunek 7.19 przedstawia ten obiekt lekko obrócony, abyś mógł się zorientować jak wygląda z boku.
Rysunek 7.19.
Dopiero po obróceniu obiektu możemy zorientować się co do jego długości
Na rysunku 7.20 patrzymy dokładnie w kierunku pudełka. Ponieważ w tym widoku pudełko nie zwęża się wraz ze wzrostem odległości, nie jest to obraz zbliżony do rzeczywistego. Aby uzyskać perspektywę, zastosujemy rzutowanie perspektywiczne.
218
Część II * Używanie OpenGL
Rysunek 7.20.
Wydrążone pudełko widziane prosto od frontu
Rzutowanie perspektywiczne
W rzutowaniu perspektywicznym otrzymujemy efekt zmniejszania się obiektów wraz ze wzrostem ich odległości od obserwatora. Szerokość tylnej płaszczyzny bryły widzenia jest różna od szerokości płaszczyzny przedniej. Tak więc obiekt położony przy przedniej części bryły widzenia jest rysowany jako większy niż takie same obiekty położone w dalszych częściach bryły widzenia.
Obrazem w naszym następnym przykładzie będzie geometryczny kształt zwany ostrosłupem widzenia. Ostrosłup widzenia stanowi część piramidy, widzianej od strony wierzchołka. Ostrosłup widzenia jest pokazany na rysunku 7.21; widzimy tam także położenie oka obserwatora.
Perspektywiczna bryła widzenia
Rysunek 7.21.
Rzutowanie perspektywiczne określone przez ostrosłup widzenia
Do definiowania ostrosłupa widzenia służy funkcja glFrustum. Jej parametrami są współrzędne oraz odległość pomiędzy przednią a tylną płaszczyzną obcinania. Jednak funkcja glFrustum nie jest zbyt intuicyjna, jeśli chodzi o uzyskanie pożądanego efektu. Łatwiej jest użyć funkcji gluPerspective, umożliwiającej bardziej intuicyjne określenie perspektywy:
void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, ^GLdouble zFar) ;
Parametrami tej funkcji są kąt pola widzenia w kierunku pionowym (fovy\ stosunek wysokości do szerokości bryły (aspect) oraz odległości do bliższej i dalszej płaszczyzny obcinania (zNear i zFar), przedstawione na rysunku 7.22. Stosunek wysokości do szero-
r
219
Rozdział 7. » Manipulowanie przestrzenią 3D: transformacje współrzędnych
kości jest obliczany przez podzielenie wysokości (h) przez szerokość (w) przedniej płaszczyzny obcinania.
Rysunek 7.22.
Ostrosłup widzenia definiowany funkcją gluPerspective
Listing 7.2 przedstawia zmodyfikowany program rzutowania równoległego z poprzedniego przykładu, w którym tym razem zastosowano rzutowanie perspektywiczne. Dzięki temu uzyskujemy bardziej realistyczny obraz wydrążonego pudełka, pokazany na rysunkach 7.23, 7.24 oraz 7.25. Jedyną znaczącą zmianą w kodzie przedstawionym na listingu 7.2 jest dodanie wywołania funkcji gluPerspective.
Rysunek 7.23.
Wydrążone pudełko vi rzucie perspektywicznym
Rysunek 7.24.
Pudełko "widziane z boku; wyraźnie widać efekt zbiegania się. krawędzi
220___________________ Część II » Używanie OpenGL
Rysunek 7.25.
Spoglądanie w głąb pudełka przy zastosowanym rzutowaniu perspektywicznym
Listing 7.2. Przygotowanie rzutowania perspektywicznego w przykładowym programie PERSPECT
// Zmiana bryły widzenia i widoku.
// Wywoływane w momencie zmiany wymiaru okna
void ChangeSize(GLsizei w, GLsizei h)
{
GLfloat fAspect;
// Zabezpieczenie przed dzieleniem przez O if(h == 0) h = 1;
// Ustawienie widoku na rozmiar okna glViewport(O, O, w, h);
fAspect = (GLfloat)w/(GLfloat)h;
// Wyzerowanie układu współrzędnych glMatrixMode(GL_PROJECTION); glLoadldentity();
// Przygotowanie rzutowania perspektywicznego gluPerspective(60.Of, fAspect, 1.0, 400.0);
glMatrixMode(GL_MODELVIEW); glLoadldentity();
Przykład daleko-blisko
Aby uzupełnić przykłady manipulowania przekształceniami widoku modelu i rzutowania, przygotowaliśmy program stanowiący model działania układu słonecznego. W celu uzyskania lepszego efektu włączyliśmy oświetlenie i cieniowanie, dzięki czemu lepiej widać efekt działania programu. O cieniowaniu i oświetleniu będziemy mówić w dwóch następnych rozdziałach.
221
Rozdział 7. * Manipulowanie przestrzenią 3D: transformacje współrzędnych
W naszym modelu Ziemia porusza się dookoła Słońca, zaś Księżyc obiega Ziemię, Źródło światła zostało umieszczone za plecami obserwatora, tak aby oświetlało Słońce. Następnie jest przesuwane na środek Słońca, tak aby oświetlało Ziemię i Księżyc od strony Słońca, dzięki czemu otrzymujemy fazy księżyca. To doskonały przykład, jak łatwo można uzyskać realistyczne efekty w OpenGL.
Listing 7.3 przedstawia kod przygotowujący rzutowanie oraz kod renderujący, utrzymujący system w ruchu. Timer, zdefiniowany w innym miejscu programu, cztery razy na sekundę unieważnia obszar okna, wymuszając odświeżenie jego zawartości. Spoglądając na rysunki 7.26 i 7.27, zwróć uwagę że Ziemia, gdy znajduje się bliżej nas, jest większa niż wtedy, gdy znajduje się po drugiej stronie Słońca.
Rysunek 7.26.
Układ słoneczny z Ziemią położoną dalej
Rysunek 7.27.
Układ słoneczny z Ziemią po dalszej
stronie
Listing 7.3. Fragmenty kodu przykładowego programu SOLAR
// Zmiana bryły widzenia i widoku. Wywoływane w momencie zmiany
// wymiaru okna
void ChangeSize(GLsizei w, GLsizei h)
222____________________________________Część Ił » Używanie OpenGL
{
GLfloat fAspect;
// Zabezpieczenie przed dzieleniem przez O if(h == 0) h « 1;
// Ustawienie widoku na rozmiar okna glViewport(O, O, w, h);
// Obliczenie stosunku długości boków okna fAspect = (GLfloat)w/(GLfloat)h;
// Wyzerowanie układu współrzędnych glMatrixMode(GL_PROJECTION); glLoadldentity();
// Kąt widzenia 45 stopni, bliższa i dalsza płaszczyzna 1.0 i 425 gluPerspective(45.Of, fAspect, 1.0, 425.0);
// Zerowanie macierzy widoku modelu glMatrixMode(GL_MODELVIEW); glLoadldentity();
// Wywoływane w celu narysowania sceny void RenderScene(void)
// Kąty obrotu Księżyca i Ziemi static float fMoonRot = O.Of; static float fEarthRot = O.Of;
// Wyczyszczenie okna bieżącym kolorem tła glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Zachowanie stanu macierzy i wykonanie obrotu glMatrixMode(GL_MODELVIEW); glPushMatrix();
// Umieszczenie źródła światła przed przekształceniem widoku glLightfv(GL_LIGHTO,GL_POSITION,lightPos) ;
// Przekształcenie całej sceny do układu obserwatora glTranslatef(O.Of, O.Of, -300.Of);
// Ustawienie koloru materiału na czerwony // Słońce
glRGB(255, 255, 0); auxSolidSphere(15.0f);
// Przesuwamy źródło światła po narysowaniu słońca! glLightfv(GL_LIGHTO,GL_POSITION,lightPos) ;
// Obrót układu współrzędnych glRotatef(fEarthRot, O.Of, l.Of, O.Of);
// Rysowanie Ziemi glRGB(0,0,255);
glTranslatef(105.Of,O.Of,O . Of) ; auxSolidSphere(15.0f);
Rozdział 7. » Manipulowanie przestrzenią 3D: transformacje współrzędnych__________223
// Obrót względem układu współrzędnych Ziemi i narysowanie // Księżyca glRGB(200,200,200);
glRotatef(fMoonRot, O.Of, l.Of, O.Of); glTranslatef(30.Of, O.Of, O.Of); fMoonRot+= 15.Of; if(fMoonRot > 360.Of) fMoonRot = O.Of;
auxSolidSphere(6.0f);
// Wyzerowanie stanu macierzy
glPopMatrix(); // Macierz widoku modelu
// Zwiększenie kąta obrotu wokół Słońca o 5 stopni fEarthRot += 5.0f; if(fEarthRot > 360.Of) fEarthRot = O.Of;
// Zrzucenie poleceń graficznych glFlushO ;
Zaawansowane operacje na macierzach
Przy tworzeniu przekształceń nie musisz korzystać z funkcji wysokiego poziomu. Zalecamy jednak korzystanie z nich, gdyż zwykle mają one konkretne przeznaczenie i są pod tym względem wysoce zoptymalizowane, podczas gdy funkcje niskiego poziomu mają zastosowanie bardziej ogólne. Dwie z funkcji wysokiego poziomu umożliwiają załadowanie własnej macierzy i umieszczenie jej na stosie macierzy widoku modelu lub rzutowania.
Ładowanie macierzy
Do macierzy rzutowania, widoku modelu lub tekstury możesz załadować dowolną macierz. W tym celu musisz najpierw zadeklarować tablicę szesnastu wartości, tworzącą macierz o wymiarach 4x4. Uczyń żądaną macierz bieżącą, a następnie wywołaj funkcję glLoadMatrix.
Macierz jest przechowywana w kolejności kolumn, co oznacza, że ka2da kolumna jest kolejno przeglądana od góry do dołu. Numery kolejnych elementów przedstawia rysunek 7.28. Poniższy kod pokazuje przebieg wypełniania tablicy macierzą tożsamościową, a następnie ładowanie tą tablicą macierzy widoku modelu. Przedstawiony kod stanowi odpowiednik funkcji wyższego poziomu, glLoadldentity.
224 Część II » Używanie OpenGL
04 03
Rysunek 7.28.
Kolejność „ „5 „9 Q
numerowania
elementów macierzy 2 ' ^O "14
03 07 On "15
// Odpowiednik, ale bardziej elastyczny glFloat m[] = { l.Of, O.Of, O.Of, O.Of,
O.Of, l.Of, O.Of, O.Of,
O.Of, O.Of, l.Of, O.Of,
O.Of, O.Of, O.Of, l.Of };
glMatrixMode(GL_MODELVIEW); glLoadMatrixf(m);
Tworzenie własnych przekształceń
Możesz załadować tablicę dowolnymi własnymi wartościami, a następnie przemnożyć ją przez jedną z trzech macierzy. Poniższy kod przedstawia macierz przekształcenia, przesuwającą współrzędne o 10 jednostek wzdłuż osi x. Ta macierz jest następnie mnożona przez macierz widoku modelu. Ten sam efekt możesz otrzymać wywołując funkcję glTranslatef.
// Definicja macierzy przesunięcia glFloat m[) = { l.Of, O.Of, O.Of, 10.Of,
O.Of, l.Of, O.Of, O.Of,
O.Of, O.Of, l.Of, O.Of,
O.Of, O.Of, O.Of, l.Of };
// Przemnożenie macierzy przesunięcia przez bieżącą macierz widoku // modelu. Otrzymana macierz stanie się nową macierzą widoku modelu. glMatrixMode(GL_MODELVIEW); glMultMatrixf(m);
Inne przekształcenia
Z powielania działania funkcji glLoadldentity czy glTranslatef nie płyną żadne szczególne korzyści. Rzeczywistym powodem korzystania z możliwości wypełnienia macierzy dowolnymi wartościami jest tworzenie złożonych przekształceń macierzowych. Jedno z takich przekształceń jest używane do rysowania cieni, zajmiemy się tym w rozdziale 9. Inne przekształcenia umożliwiają zawinięcie obiektu dookoła innego obiektu lub uzyskanie pewnych efektów soczewek. Informacje o tych zaawansowanych zastosowaniach znajdziesz w dodatku B.
Podsumowanie
W tym rozdziale poznałeś koncepcje mające podstawowe znaczenie dla tworzenia trójwymiarowych scen w OpenGL. Nawet jeśli nie potrafisz przemnożyć macierzy w myśli,
225
Rozdział 7. * Manipulowanie przestrzenią 3D: transformacje współrzędnych
wiesz już przynajmniej, czym jest macierz i jakie ma zastosowanie przy przekształceniach. Wiesz już także jak manipulować macierzami rzutowania i widoku modelu, co umożliwia rozmieszczenie i przedstawienie obiektów w scenie.
Na koniec, poznałeś także funkcje umożliwiające samodzielne tworzenie macierzy przekształceń, jeśli zajdzie taka potrzeba. Te funkcje umożliwiają załadowanie macierzy dowolnymi wartościami lub też załadowanie macierzy, a następnie przemnożenie jej przez inną macierz.
Program symulacji czołgu/robota w tym rozdziale umożliwia już poruszanie się w trójwymiarowym świecie i oglądanie rozmieszczonych w nim obiektów. Jeśli przeanalizujesz kod tego programu, lepiej poznasz sposób wykorzystania rzutowania perspektywicznego oraz użycia funkcji narzędziowej gluLookAt, ułatwiającej określenie przekształcenia punktu obserwacji. W tym momencie trójwymiarowy świat składa się jedynie ze szkieletowych brył, ale ta sytuacja już wkrótce się zmieni.
Podręcznik
glFrustum
Przeznaczenie Plik nagłówkowy Składnia
Opis
Mnoży bieżącą macierz przez macierz rzutowania perspektywicznego.
void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
Ta funkcja tworzy macierz rzutowania perspektywicznego, tworzącą rzut perspektywiczny. Zakłada się w nim, że oko jest położone w punkcie (O, O, 0), zaś far określa odległość do dalszej płaszczyzny obcinania, a near - bliższej. Ta funkcja może zakłócić precyzję działania bufora głębokości, jeśli stosunek odległości dalszej do bliższej (far/near) jest duży.
Parametry left, right bottom, top near,far
Zwracana wartość Przykład
GLdouble: Współrzędne lewej i prawej płaszczyzny obcinania. GLdouble: Współrzędne górnej i dolnej płaszczyzny obcinania.
GLdouble: Odległość do bliższej i dalszej płaszczyzny obcinania. Obie wartości muszą być dodatnie.
Brak
Poniższy kod przygotowuje macierz rzutowania perspektywicznego, definiującą bryłę widzenia od O do 100 jednostek w osi z. W osiach x i y bryła rozciąga się na 100 jednostek w każdym kierunku:
226
Część II » Używanie
PrzyU
Patrz także
glMatrixMode(GL_PROJECTION); glLoadldentity(); glFrustum(-100.0f,100.0f,-100.0f,100.0f,O.Of,100.0f);
glOrtho, glMatrixMode, glMultMatrix, glYiewport
glLoadldentity
Przeznaczenie Plik nagłówkowy Składnia Opis
Zwracana wartość Przykład
Patrz także
Zeruje bieżącą macierz do macierzy tożsamościowej.
void glLoad!dentity(void);
Funkcja zastępuje zawartość bieżącej macierzy przekształcenia zawartością macierzy tożsamościowej, czyli innymi słowy, zeruje układ współrzędnych do układu współrzędnych obserwatora.
Brak
Poniższy kod ilustruje zerowanie macierzy widoku modelu:
glMatrixMode(GL_MODELVIEW); glLoadldentity();
glLoadMatrix, glMatrixMode, glMultMatrix, glPushMatrix
Pa
J
P
glLoadMatrix
Przeznaczenie Plik nagłówkowy Składnia
Opis
Ładuje wskazaną macierz do bieżącej macierzy. <gl.h>
void glLoadMatrixd(const GLdouble *m); void glLoadMatrixf(const GLfloat *m);
Zastępuje zawartość bieżącej macierzy przekształcenia zawartością wskazanej tablicy. Czasem jednak bardziej efektywne może być użycie specjalnych funkcji przeznaczonych do przygotowywania przekształceń, takich jak glLoadldentity, glRotate, glTranslate czy glScale.
Parametry
kot
GLdouble lub GLfloat: Ta tablica reprezentuje macierz o rozmiarach 4x4, która zostanie załadowana do bieżącej macierzy przekształcenia. Macierz jest zapisana w formie tablicy szesnastu wartości, interpretowanych kolumnami, od lewej do prawej strony.
Zwracana wartość Brak
Rozdział 7. * Manipulowanie przestrzenią 3D: transformacje współrzędnych__________227
Przykład Poniższe dwa segmenty kodu są sobie równoważne. Oba fragmenty
ładują macierz tożsamościową do macierzy widoku modelu.
// Sposób bardziej efektywny glMatrixMode(GL_MODELVIEW); glLoadldentity();
// To samo, ale oferujące większą elastyczność glFloat m[] = { l.Of, O.Of, O.Of, O.Of,
O.Of, l.Of, O.Of, O.Of,
O.Of, O.Of, l.Of, O.Of,
O.Of, O.Of, O.Of, l.Of }; glMatrixMode(GL_MODELVIEW); glLoadMatrixf(m);
Patrz także glLoadldentity, glMatrixMode, glMultMatrix, glPushMatrix
glMatrixMode_______________________
Przeznaczenie Określa bieżącą macierz (rzutowania, widoku modelu lub tekstury).
Plik nagłówkowy <gl.h>
Składnia void glMatrixMode(GLenum modę);
Opis Ta funkcja jest używana do wybierania stosu macierzy, który będzie
wykorzystywany w następnych operacjach macierzowych.
Parametry
modę GLenum: Identyfikuje stos macierzy, który będzie używany w następnych
operacjach macierzowych. Dozwolone są wartości zebrane w tabeli 7.2.
Zwracana wartość Brak
Przykład Poniższe dwie standardowe linie kodu wybierają do działania macierz
widoku modelu, a następnie ładuj ą do niej macierz tożsamościową.
glMatrixMode(GL_MODELVIEW);
glLoadldentityO;
Patrz także glLoadMatrix, glPushMatrix
Tabela 7.2.
Dostępne wartości parametru funkcji glMatrixModeQ
Tryb (parametr modę) Stos macierzy
GL_MODELVIEW Operacje macierzowe będą się odnosiły do stosu macierzy widoku modelu
(używanego do rozmieszczania elementów w scenie).
GL_PROJECTION Operacje macierzowe będą się odnosiły do stosu macierzy rzutowania
(używanego do definiowania bryły obcinania).
GL_TEXTURE Operacje macierzowe będą się odnosiły do stosu macierzy mapowania
tekstury (używanego do manipulowania współrzędnymi tekstur).
228
Część II » Używanie OpenGL
g!MultMatrix
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry *m
Mnoży bieżącą macierz przez wskazaną macierz. <gl.h>
void glMultMatrixd(const GLdouble *m); void glMultMatrixf(const GLfloat *m);
Ta funkcja mnożybieżącą macierz przekształcenia przez wskazaną macierz. Otrzymana macierz jest umieszczana, jako macierz bieżąca, na szczycie stosu macierzy.
GLdouble lub GLfloat. Ta tablica reprezentuje macierz o rozmiarach 4x4, która zostanie przemnożona przez bieżącą macierz. Macierz jest zapisana w formie tablicy szesnastu wartości, interpretowanych kolumnami, od lewej do prawej strony.
Zwracana wartość Brak
Przykład
Patrz także
Poniższy kod tworzy macierz przesunięcia i mnożyjąprzez bieżącą macierz widoku modelu. Nowo utworzona macierz zastępuje macierz bieżącą. Przedstawione tu mnożenie macierzy może zostać zastąpione przez wywołanie funkcji glTranslatef(10.0f, O.Of, O.Of);.
// Definicja macierzy przesunięcia
glFloat m[] = { l.Of, O.Of, O.Of, 10.Of,
O.Of, l.Of, O.Of, O.Of,
O.Of, O.Of, l.Of, O.Of,
O.Of, O.Of, O.Of, l.Of };
// Przemnożenie macierzy przesunięcia przez
// bieżącą macierz widoku modelu.
// Otrzymana macierz stanie się nową macierzą
// widoku modelu.
glMatrixMode(GL_MODELVIEW);
glMultMatrixf(m);
glMatrixMode, glLoadldentity, glLoadMatrix, glPushMatrix
glPopMatrix
Zdejmuje bieżącą macierz ze stosu macierzy.
void glPopMatrix(void);
Przeznaczenie Plik nagłówkowy Składnia Opis
Ta funkcja służy do pobierania ze stosu ostatniej umieszczonej na nim macierzy. Najczęściej wykorzystuje się ją do przywrócenia poprzedniego stanu bieżącej macierzy przekształcenia, odłożonej wcześniej na stos za pomocą funkcji glPushMatrix.
Zwracana wartość Brak
Rozdział 7. •» Manipulowanie przestrzenią 3D: transformacje współrzędnych__________229
Przykład Poniższy kod pochodzi z programu ATOM w tym rozdziale. Ten
fragment, przez wywołanie funkcji glPushMatrix, zachowuje na stosie bieżący stan macierzy widoku modelu (ustawionej na środek atomu). Potem następuje obrót i przesunięcie układu współrzędnych w celu umieszczenia elektronu, reprezentowanego przez małą kulkę. Przed narysowaniem kolejnego elektronu przywracany jest poprzedni stan macierzy, przez wywołanie funkcji glPopMatrix.
// Orbita pierwszego elektronu
// Zachowanie przekształcenia widoku
glPushMatrix();
// Obrót o kąt
glRotatef(fElectl, O.Of, l.Of, O.Of);
// Przesunięcie elektronu z początku układu na orbitę glTranslatef(90.Of, O.Of, O.Of);
// Rysowanie elektronu auxSolidSphere(6.0f);
// Odtworzenie przekształcenia widoku glPopMatrix();
Patrz także glPushMatrix
glPushMatrix_______________________
Przeznaczenie Umieszcza na stosie macierzy bieżącą macierz przekształcenia.
Plik nagłówkowy <gl.h>
Składnia void glPushMatrix(void);
Opis Ta funkcja jest używana do umieszczenia bieżącej macierzy na szczycie
stosu macierzy. Najczęściej celem tej operacji jest chęć zachowania
bieżącego stanu macierzy, tak aby móc go później odtworzyć
wywołaniem funkcji glPopMatrix.
Zwracana wartość Brak
Przykład Zobacz glPopMatrix
Patrz także glPopMatrix
gIRotate
Przeznaczenie Mnoży bieżącą macierz przez macierz obrotu.
Plik nagłówkowy <gl.h>
Składnia void glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z);
void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
230
Część II » Używanie OpenGL
Opis
Ta funkcja mnoży bieżącą macierz przez macierz obrotu. Kąt obrotu określany jest w kierunku przeciwnym do ruchu wskazówek zegara, zaś sam obrót jest wykonywany wokół osi określanej przez wektor o początku w centrum układu współrzędnych i końcu w punkcie (x, y, z). Nowa obrócona macierz staje się bieżącą macierzą przekształcenia.
Parametry angle
x,y,z
GLdouble lub GLfloat: Kąt obrotu w stopniach. Kat mierzony jest w kierunku przeciwnym do ruchu wskazówek zegara.
GLdouble lub GLfloat: Współrzędne wektora określającego kierunek osi obrotu.
Zwracana wartość Brak
Przykład
Patrz także
Poniższy kod, pochodzący z programu SOLAR w tym rozdziale, umieszcza Ziemię na orbicie okołosłonecznej. Bieżąca macierz widoku modelu jest skierowana na środek Ziemi, po czym zostaje obrócona o kąt dla Księżyca, a następnie przesunięta poza Ziemię.
// Księżyc glRGB(200,200,200);
glRotatef(fMoonRot,O.Of, l.Of, O.Of); glTranslatef(30.Of, O.Of, O.Of); fMoonRot+= l5.Of; if(fMoonRot > 360.Of) fMoonRot = O.Of;
auxSolidSphere(6.0f) ;
glScale, glTranslate
gIScale
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry
x,y, z
Zwracana wartość Przykład
Mnoży bieżącą macierz przez macierz skalowania. <gl.h>
void glScaled(GLdouble x, GLdouble y, GLdouble z); void glScalef(GLfloat x, GLfloat y, GLfloat z);
Ta funkcja mnożybieżącą macierz przez macierz skalowania. Nowa macierz staje się bieżącą macierzą przekształcenia.
GLdouble lub GLfloat: Współczynniki skalowania dla osi x, y i z. Brak
Poniższy kod modyfikując macierz widoku modelu tworzy spłaszczone obiekty. Wierzchołki we wszystkich następnych prymitywach zostaną spłaszczone o połowę w kierunku pionowym.
231
Rozdział 7. * Manipulowanie przestrzenią 3D: transformacje współrzędnych
Patrz także
glMatrixMode(GL_MODELVIEW); glLoadldentity();
glScaled.Of, 0.5f, l.Of); glRotate, glTranslate
gITranslate
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry
x,y,z
Zwracana wartość Przykład
Patrz także
Mnoży bieżącą macierz przez macierz przesunięcia.
<gl.h>
void gITranslated(GLdouble x, GLdouble y, GLdouble z);
void glTranslatef(GLfloat x, GLfloat y, GLfloat z);
Ta funkcja mnożybieżącą macierz przez macierz przesunięcia. Nowa macierz staje się bieżącą macierzą przekształcenia.
GLdouble lub GLfloat: Współrzędne x, y i z wektora przesunięcia. Brak
Poniższy kod pochodzi z programu SOLAR w tym rozdziale. Umieszcza niebieską kulę (Ziemię) w odległości 105 jednostek w kierunku osi x od początku układu współrzędnych.
// Ziemia
glColor3f(O.Of, O.Of, l.Of); glTranslate(105.Of, O.Of, O.Of); auxSolidSphere(15.Of);
glRotate, glScale
glulookAt
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry eyex, eyey, eyez
Definiuje przekształcenie punktu obserwacji. <glu.h>
void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz);
Ta funkcja definiuje przekształcenie punktu obserwacji na podstawie pozycji oka, położenia środka sceny oraz wektora, który z punktu widzenia obserwatora jest skierowany pionowo do góry.
GLdouble: Współrzędne x, y i z punktu położenia oka.
232
Część II » Używanie OpenGL
centerx, centery, centerz
upx, upy, upz
Zwracana wartość Przykład
Patrz także
GLdouble: Współrzędne x, y i z punktu, na który chcemy patrzeć.
GLdouble: Współrzędne x, y i z wektora określającego „górę" z punktu widzenia obserwatora..
Brak
Poniższy kod pochodzi z przykładowego programu TANK. Przedstawia sposób zmiany przekształcenia punktu obserwacji za każdym razem, gdy czołg lub robot zmienia położenie.
// Wyzerowanie macierzy widoku modelu glMatrixMode(GL_MODELVIEW); glLoadldentity() ;
// Ustawienie przekształcenia punktu obserwacji // na podstawie położenia i pozycji gluLookAt(locX, locY, locZ, dirX, dirY, dirZ, O.Of, l.Of, O.Of);
locXdo locZ określają położenie czołgu lub robota (punkt widzenia obserwatora), zaś dirXdo dirZ określają punkt, w kierunku którego skierowany jest czołg lub robot. Ostatnie trzy wartości określają współrzędne wektora skierowanego ku górze, który w tym programie zawsze pokrywa się z kierunkiem osi y.
glFrustum, gluPerspective
gluOrtho2D
Przeznaczenie Plik nagłówkowy Składnia
Opis
Definiuje dwuwymiarowe rzutowanie równoległe. <glu.h>
void gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top);
Ta funkcja definiuje macierz dwuwymiarowego rzutowania równoległego. Taka macierz rzutowania stanowi odpowiednik wywołania funkcji glOrtho / parametrami near \far ustawionymi, odpowiednio, na O i 1.
Parametry
left, right
bottom, top Zwracana wartość
GLdouble: Określaj ą lewą i prawą płaszczyznę obcinania. GLdouble: Określaj ą dolną i górną płaszczyznę obcinania. Brak
233
Rozdział 7. * Manipulowanie przestrzenią 3D: transformacje współrzędnych
Przykład
Patrz także
Poniższy kod ustala dwuwymiarową bryłę widzenia umożliwiającą rysowanie w płaszczyźnie xy w zakresie od -100 do 100 jednostek w osiach x i y. Dodatnie wartości osi y biegną w górę, zaś dodatnie wartości osi x - w prawo.
gluOrtho2D(-100.0, 100.0, -100.0, 100.0);
glOrtho, gluPerspectiye
Przeznaczenie Plik nagłówkowy Składnia
Opis
gluPerspectiye
Definiuje macierz rzutowania perspektywicznego. <glu.h>
void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);
Ta funkcja tworzy macierz opisującą ostrosłup widzenia we współrzędnych sceny. Stosunek wysokości do szerokości (aspect) powinien odpowiadać stosunkowi długości krawędzi okna widoku (określonego funkcją glYiewport). Dzielenie w rzutowaniu perspektywicznym odbywa się na podstawie wartości kąta obserwacji w pionie (fovy) oraz odległości do bliższej (zNear) i dalszej (zFar) płaszczyzny obcinania.
Parametry fovy aspect
zNear, zFar
GLdouble: Szerokość kąta widzenia w pionie, wyrażona w stopniach.
GLdouble: Stosunek szerokości do wysokości ostrosłupa widzenia. Używany do wyznaczania kąta widzenia w poziomie.
GLdouble: Odległości obserwatora od bliższej i dalszej płaszczyzny obcinania. Te wartości są zawsze dodatnie.
Zwracana wartość Brak
Przykład
Poniższy kod pochodzi z przykładowego programu SOLAR. Tworzy rzutowanie perspektywiczne, które powoduje, że planety położone z drugiej strony Słońca są rysowane jako mniejsze niż planety położone bliżej patrzącego.
// Zmiana bryły widzenia i widoku.
// Wywoływane w momencie zmiany wymiaru okna
void ChangeSize(GLsizei w, GLsizei h)
{
GLfloat fAspect;
// Zabezpieczenie przed dzieleniem przez O if(h == 0) h = 1;
// Ustawienie widoku na wymiary okna glYiewport(O, O, w, h);
234____________________________________Część II » Używanie OpenGL
// Obliczenie stosunku długości boków okna fAspect = (GLfloat)w/(GLfloat)h;
// Wyzerowanie układu współrzędnych glMatrixMode(GL_PROJECTION); glLoadldentity();
// Kąt widzenia 45 stopni,
// bliższa i dalsza płaszczyzna 1.0 i 425
gluPerspective(45.0f, fAspect, 1.0, 425.0);
// Zerowanie macierzy widoku modelu glMatrixMode(GL_MODELVIEW); glLoadldentity(); }
Patrz także glFrustum, gluOrtho2D
Rozdział 8.
Kolory i cieniowanie
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
* Określić kolor przy użyciu barw składowych RGB * glColor
4 Ustawić model cieniowania * glShadeModel
* Stworzyć paletę 3-3-2 * CreatePalette
* Zastosować paletę * RealizePalette, SelectPalette,
UpdateColors
No, nareszcie powiemy coś o kolorze! To chyba najważniejszy aspekt każdej biblioteki graficznej - ważniejszy nawet niż możliwość tworzenia animacji. Przy tworzeniu aplikacji graficznych musisz mieć na względzie starą zasadę: w tym przypadku wygląd JEST wszystkim! Nie wierz, gdy ktoś twierdzi coś innego. Tak, oczywiście, wyposażenie, wydajność, cena i pewność działania także się liczą, ale powiedzmy sobie szczerze - w większości przypadków to właśnie wygląd decyduje o powodzeniu lub porażce produktu.
Jeśli chcesz poważnie zająć się tworzeniem aplikacji graficznych, nie możesz programować wyłącznie dla garstki intelektualistów myślących tak jak ty. Zwróć się do mas! Zastanów się: czarno-białe telewizory były tańsze w produkcji niż telewizory kolorowe. Podobnie czarno-białe kamery wideo, także były tańsze i łatwiejsze w użyciu. Ale rozejrzyj się dookoła i sam wyciągnij wnioski. Oczywiście, czarno-biały sprzęt odgrywa pewną rolę, ale jednak powszechny jest kolor. (A swoją drogą, szkoda, że pokolorowali te filmy z Shirley Tempie...).
236
Część II » Używanie OpenGL
Czym jest kolor?
Powiedzmy najpierw parę słów o samym kolorze. Jak powstaje kolor i jak go postrzegamy? Zrozumienie teorii koloru oraz tego, jak oko ludzkie postrzega kolorowe sceny, pomoże nam w zrozumieniu sposobów programowego określania kolorów. (Jeśli wiesz, jak powstają kolory i jak sieje postrzega, możesz spokojnie pominąć tę sekcję).
Światło jako fala
Kolor jest po prostą długością fali świetlnej widzialnej dla ludzkiego oka. Jeśli w szkole uczyłeś się fizyki, z pewnością pamiętasz, że światło może być traktowane zarówno jako fala, jak i cząsteczka. Może być przedstawiane jako fala poruszająca się w przestrzeni, podobnie jak fale na wodzie, lub jako strumień cząsteczek, na przykład jak krople deszczu padające na ziemię. Jeśli wydaje ci się to niezrozumiałe, wiesz już, dlaczego większość ludzi nie studiuje mechaniki kwantowej!
Światło odbijające się od prawie każdej powierzchni w rzeczywistości jest mieszaniną wielu różnych rodzajów światła. Te rodzaje światła są określane przez ich długości fal. Długość fali światła jest mierzona jako odległość pomiędzy szczytami kolejnych fal światła, jak ilustruje to rysunek 8.1.
Rysunek 8.1.
Sposób mierzenia dlugości fali świetlnej
Długości fal światła widzialnego należą do zakresu od 390 nanometrów (nanometr to l O"9 m, jedna miliardowa metra) dla fioletu, do 720 nanometrów dla czerwieni; ten zakres zwykle określa się jako spektrum. Z pewnością obiły ci się o uszy wyrażenia ultrafiolet i podczerwień; oznaczają one światło niewidzialne gołym okiem, o długościach fal leżących poza granicami spektrum. Spektrum, zawierające wszystkie barwy podstawowe, wygląda jak tęcza, pokazana schematycznie na rysunku 8.2.
Rysunek 8.2.
Spektrum światła widzialnego
UiBsiy .;•;; -Niebieski Zielony Żółty Pomarańczowy ' ^Czi§ii|lH
390 nm
720 nm
Rozdział 8. * Kolory i cieniowanie_________________________________237
Światło jako cząsteczka
- W porządku, panie Mądralo - możesz stwierdzić - Jeśli kolor jest długością fali światła i widzialne światło mieści się w tym „tęczowym" zakresie, to gdzie jest brąz mojego czekoladowego batonu, czerń moich butów czy choćby biel tej kartki?
Odpowiedź na to pytanie zaczniemy od stwierdzenia, że ani czerń, ani biel nie są kolorami. W rzeczywistości czerń to brak koloru, zaś biel to równomierne wymieszane wszystkie kolory naraz. Czyli białe przedmioty w równym stopniu odbijają wszystkie długości fal, zaś czarne obiekty pochłaniają wszystkie długości fal.
Brąz czekoladowego batonu i wiele innych widzialnych kolorów są rzeczywiście kolorami. Z punktu widzenia fizyka są one kolorami złożonymi. Składają się z różnych ilości „czystych" barw występujących w spektrum. Aby zrozumieć, jak to działa, pomyśl o świetle jak o cząsteczkach. Każdy obiekt oświetlony źródłem światła jest bombardowany „miliardami miliardów" fotonów, czyli maleńkich cząsteczek światła. Nawiązując do naszej „fizycznej" pogawędki, każdy z tych fotonów jest także falą, posiadającą długość, czyli kolor w spektrum.
Wszystkie obiekty fizyczne składają się z atomów. Odbijanie się fotonów od obiektu zależy od rodzaju atomów, ilości atomów każdego rodzaju czy od ułożenia atomów w obiekcie. Niektóre fotony zostaną odbite, a niektóre pochłonięte (pochłonięte fotony zwykle zmieniają się w ciepło), zaś każdy rodzaj materiału lub połączenia materiałów (na przykład w batonie czekoladowym) odbija różne długości fal światła niż inne materiały. Ilustruje to rysunek 8.3.
Rysunek 8.3.
Obiekt pochłania /ił^ Źródło światłu
niektóre fotony, Twoje oko
zaś odbija inne
^ f* .!<%»
C l «\\V'
X-^F v \\V\
Część fotonów zostaje pochłonięta część zaś odbiła
Fotony odbite
Baton czekoladowy
Strumień światła (fotonów)
Twój osobisty wykrywacz fotonów
Światło odbite od batonu czekoladowego, gdy trafia do twojego oka, jest interpretowane jako kolor. Do oka trafiają miliardy fotonów, skupiając się na jego tylnej powierzchni, siatkówce, działającej jak błona aparatu fotograficznego. Miliony komórek światłoczułych siatkówki są podrażniane uderzającymi fotonami i wysyłają impulsy nerwowe,
238____________________________________Część II » Używanie OpenGL
przez nerw wzrokowy przekazywane do mózgu, który interpretuje je jako światło i kolor. Im więcej fotonów trafia w komórkę światłoczułą, tym bardziej zostaje ona podrażniona. Ten poziom podrażnienia jest interpretowany przez mózg jako natężenie światła, co jest naturalne - im mocniejsze światło, tym więcej fotonów trafia w komórki siatkówki.
Oko posiada trzy rodzaje komórek światłoczułych. Wszystkie reagują na fotony, jednak każdy rodzaj reaguje na inny zakres długości fal. Jeden rodzaj jest pobudzany przez światło zbliżone do czerwonego, drugi przez światło zbliżone do zielonego, zaś trzeci przez światło zbliżone do niebieskiego. Tak więc światło zawierające głównie czerwień będzie bardziej pobudzać komórki reagujące na czerwień niż inne rodzaje komórek, zaś mózg zinterpretuje sygnały jako światło zawierające w większości czerwień. Mózg automatycznie dokonuje obliczeń - mieszanina różnych rodzajów światła o różnych inten-sywnościach zostanie zinterpretowana jako określony kolor. Tak więc jednakowe natężenie fal o wszystkich długościach daje światło białe, zaś brak jakiegokolwiek światła jest interpretowany jako czerń.
Jak widać, każdy „kolor" postrzegany przez oko jest w rzeczywistości złożony z fal świetlnych o różnych długościach i natężeniach, pochodzących z widzialnego spektrum. „Sprzęt" w twoim oku wykrywa strumień fotonów jako względne ilości światła czerwonego, zielonego i niebieskiego. Rysunek 8.4 przedstawia widzenie koloru brązowego, składającego się z 60 procent fotonów czerwieni, 40 procent fotonów zieleni i 10 procent fotonów światła niebieskiego.
Rysunek 8.4. Soczewki okna "Brązowe" światło
Jak brązowy „kolor" Siatkówka • / /
trafia do oka ^^,——J J R R G
• ••..••:•-, ^•••-fg* ^- •••
R G R G
8~<Y~V«-r
6 fotonów czerwieni, 4 Fotony zieleni i l foton niebieski
Komputer jako generator fotonów
Łatwo się domyślić, że gdy chcemy wygenerować kolor za pomocą komputera, robimy to przez osobne określenie intensywności czerwonej, zielonej i niebieskiej składowej światła. Tak się składa, że monitory komputerowe są skonstruowane tak, aby móc generować trzy rodzaje (długości fal) światła (wiesz już, które), z których każde może różnić się intensywnością. Z tyłu monitora znajduje się działo elektronowe, wystrzeliwujące strumień elektronów w kierunku tylnej powierzchni ekranu kineskopu. Ta powierzchnia zawiera trzy rodzaje fosforu, które w momencie uderzenia przez rozpędzony elektron emitują czerwone, zielone i niebieskie światło. W zależności od intensywności strumienia elektronów zmienia się intensywność emitowanego światła. Te trzy rodzaje fosforu są ułożone precyzyjnie blisko siebie, razem tworząc fizyczną kropkę na ekranie, co przedstawiono na rysunku 8.5.
Rozdział 8. * Kolory i cieniowanie________________________________239
Rysunek 8.5. D™*> elektronowe
Kolory powstające Ekran komputen
na monitorze
komputera
Poszczególne elementy' ekranu
Plamki czerwonego,zielonego i niebieskiego fosforu
Być może pamiętasz, że w rozdziale 3 mówiliśmy, iż OpenGL definiuje kolor za pomocą funkcji glColor, właśnie poprzez określenie intensywności barwy czerwonej, zielonej i niebieskiej. Tutaj omówimy bardziej szczegółowo dwa tryby kolorów obsługiwanych przez OpenGL.
* Tryb koloru RGBA był trybem, którego używaliśmy we wszystkich dotychczasowych przykładach. Przy rysowaniu w tym trybie można precyzyjnie określić kolor przez podanie intensywności jego trzech składowych (czerwonej, zielonej i niebieskiej - po angielsku Red, Green oraz Blue).
4- W trybie z indeksem koloru kolor określa się przez podanie indeksu do tablicy dostępnych kolorów, zwanej paletą. W ramach tej palety możesz precyzyjnie określić każdy z kolorów, podając intensywności jego trzech barw składowych.
Sprzęt grafiki kolorowej
Swojego czasu szczytowym osiągnięciem w dziedzinie sprzętu do grafiki komputerowej była karta graficzna Hercules. Ta karta umożliwiała wyświetlanie rastrowego obrazu o rozdzielczości 720 na 348 punktów, lecz tylko w dwóch kolorach. Każdy piksel (punkt obrazu) mógł znajdować się tylko w jednym z dwóch stanów: włączonym lub wyłączonym. W tamtych czasach każda bitmapowa grafika komputerowa w komputerze osobistym była czymś i nawet na monitorze monochromatycznym można było dało się tworzyć wspaniałe obrazy. Nawet mnie, nie chwaląc się, udało się stworzyć na studiach trójwymiarową grafikę na karcie Hercules.
Przed kartą Hercules istniała karta CGA, Color Graphics Adapter. Wprowadzona wraz z pierwszymi komputerami IBM PC, karta obsługiwała rozdzielczość 320 x 200 punktów i na ekranie można było wyświetlić do szesnastu kolorów jednocześnie. Była dostępna także wyższa rozdzielczość (640 x 200), z dwoma dostępnymi kolorami, ale to rozwiązanie nie było tak efektywne jak w przypadku karty Hercules (kolorowy monitor = dużo żywej gotówki). W porównaniu z dzisiejszymi standardami, karta CGA miała bardzo niewielkie możliwości, nie do porównania z możliwościami graficznymi domo-
240____________________________________Część II » Używanie OpenGL
wych komputerków Commodore 64 czy Atari, dostępnych wtedy już za 200 dolarów. Przy braku rozdzielczości odpowiednich dla aplikacji biurowych i nawet najprostszych pakietów do modelowania, CGA znajdowała zastosowanie głównie prostych gier i aplikacji, które zadowalały się kolorowanym tekstem. Ogólnie rzecz biorąc, trudno było znaleźć ekonomiczne uzasadnienie zakupu tej droższej karty.
Następny większy przełom w grafice PC nastąpił w momencie, gdy IBM opracował kartę EGA (Enhanced Graphics Adapter). Ta karta mogła wyświetlić więcej niż 25 wierszy tekstu w nowym trybie tekstowym, a także grafikę rastrową o rozdzielczości 640 x 350 pikseli, i to w szesnastu kolorach! Pewne techniczne poprawki eliminowały problem migotania, występujący w przypadku kart CGA, dzięki czemu umożliwiały tworzenie płynnych animacji. Od tego czasu nowe gry zręcznościowe, prawdziwe aplikacje graficzne, a nawet grafika 3D w komputerach PC stały się nie tylko dostępne, ale także uzasadnione. Stanowiło to ogromny postęp w porównaniu z kartą CGA, jednak grafika w komputerach osobistych wciąż jeszcze była w powijakach.
Ostatnim głównym standardem kart graficznych opracowanych przez IBM była karta VGA (co w rzeczywistości oznacza Yector Graphics Array, a nie, jak się powszechnie uważa, Video Graphics Adapter). Ta karta była znacznie szybsza niż karta EGA, mogła wyświetlać 16 kolorów w wyższej rozdzielczości 640 x 480 256 kolorów w niższej rozdzielczości (320 x 200). Te 256 kolorów było wybieranych z palety ponad 16 milionów dostępnych kolorów. W tym momencie otwarły się podwoje dla wszelkiego rodzaju grafiki komputerowej PC. Na ekranach pojawiła się prawie fotorealistyczna grafika. Ray tracery, trójwymiarowe gry, a nawet oprogramowanie do obróbki zdjęć masowo zaczęły pojawiać się na rynku programów do komputerów osobistych.
IBM posiadał także jeszcze inną, wysokowydajną kartę graficzną- 8514 - do swoich „stacji roboczych". Ta karta mogła wyświetlić 256 kolorów przy rozdzielczości 1024 x 768. IBM uznał, że ta karta będzie wykorzystywana jedynie przez aplikacje CAD i naukowe! Jednakże jedno z pewnością można powiedzieć o rynku konsumenta: zawsze chce więcej. Ta krótkowzroczność kosztowała firmę IBM utratę pozycji niedoścignionego lidera na rynku kart graficznych do komputerów osobistych. Inni producenci zaczęli tworzyć karty „Super-VGA", mogące wyświetlać coraz wyższe rozdzielczości, w coraz większych ilościach kolorów. Najpierw 800 x 600, potem 1024 x 768 i więcej, najpierw w 256 kolorach, potem w 32 tysiącach, a następnie w ponad 65 tysiącach kolorów. Obecne 24-bitowe karty graficzne potrafią wyświetlić 16 milionów kolorów w rozdzielczoś-ciach 1024 x 768 i wyższych. Tańsze karty graficzne PC obsługują pełny kolor w rozdzielczościach VGA lub w rozdzielczości 800 x 600 kart Super-VGA. Większość sprzedawanych obecnie komputerów osobistych może wyświetlić przynajmniej 65 tysięcy kolorów w rozdzielczości 1024 x 768.
Cała ta moc sprawia, że pojawiają się coraz ciekawsze możliwości — fotorealistyczna trójwymiarowa grafika to tylko jedna z nich. Gdy Microsoft zaimplementował OpenGL do systemu Windows, powstała możliwość tworzenia najwyższej jakości aplikacji graficznych do komputerów osobistych. Obecne komputery Pentium czy Pentium Pro wciąż znacznie ustępują pod względem wydajności nowoczesnym stacjom roboczym SGI. Jednak gdy komputer PC zostanie wyposażony w kartę akceleratora 3 D, uzyskasz wydajność, która jeszcze kilka lat temu była dostępna jedynie w przypadku stacji roboczej za 100 tysięcy dolarów! W najbliższej przyszłości typowy komputer domowy bę-
Rozdział 8. •» Kolory i cieniowanie_________________________________241
dzie miał możliwość przedstawienia wymyślnych symulacji, gier i innych aplikacji związanych z intensywnym tworzeniem grafiki.
Tryby graficzne
w komputerach osobistych
Microsoft Windows zrewolucjonizowały świat grafiki PC w dwóch aspektach. Po pierwsze, stary się powszechnie używanym graficznym środowiskiem operacyjnym, szybko zaadaptowanym przez rynek aplikacji biurowych i domowych. Po drugie, grafika PC stała się dużo łatwiej dostępna dla programistów. W Windows sprzęt został „uogólniony" przez sterowniki kart graficznych. Zamiast pisać instrukcje przeznaczone bezpośrednio dla sprzętu graficznego, dzisiejszy programista może korzystać z jednego, spójnego API, zaś samym porozumiewaniem się ze sprzętem zajmie się system Windows. Microsoft dostarcza zestawu podstawowych sterowników (stworzonych z udziałem producentów) do większości popularnych kart graficznych. Producenci sprzętu wraz ze swoimi kartami dostarczają specjalizowane sterowniki dla Windows, często także można pobrać nowe wersje sterowników przez Internet lub BBS.
Swego czasu Windows były rozprowadzane ze sterownikami do monochromatycznej karty Hercules, do kart CGA oraz EGA. Niczego więcej. Obecnie standardowa karta VGA stanowi absolutne wymagane minimum. Nowe, sprzedawane obecnie komputery PC są w stanie wyświetlić przynajmniej szesnaście kolorów w rozdzielczości 640 x 480 punktów, lecz zwykle oferują o wiele, wiele większe możliwości.
Rozdzielczości ekranu
Rozdzielczości ekranu w przypadku obecnych komputerów PC zmieniają się od 640 x 480 pikseli do 1280 x 1024 i większych. Jednak sama rozdzielczość obrazu nie jest najważniejszym czynnikiem podczas tworzenia aplikacji graficznych. Do większości zadań graficznych w zupełności wystarcza niższa rozdzielczość, 640 x 480 pikseli. Ważniejszy jest rozmiar okna, brany pod uwagę przy ustalaniu bryły obcinania i widoku (patrz rozdział 3). Skalując rozmiar rysunku do rozmiaru okna, możesz łatwo obsłużyć różne rozdzielczości i rozmiary okien, z jakimi masz do czynienia. Dobrze napisana aplikacja graficzna wyświetli podobny obraz bez względu na rozdzielczość ekranu. Użytkownik powinien automatycznie dostrzec więcej szczegółów w momencie przejścia do wyższej rozdzielczości.
Głębokość koloru
O ile wraz ze wzrostem rozdzielczości ekranu wzrasta ostrość i ilość widocznych szczegółów obrazu, o tyle wraz ze wzrostem dostępnej ilości kolorów zwiększa się przejrzystość rysunku. Obraz wyświetlony w komputerze potrafiącym wyświetlić miliony kolorów wygląda zdecydowanie lepiej niż wyświetlony w szesnastu kolorach. Z punktu widzenia
242____________________________________Część II » Używanie OpenGL
programisty, powinieneś brać pod uwagę tylko trzy głębokości kolorów: 4 bity, 8 bitów i 24 bity.
Kolor 4-bitowy
W najgorszym razie twój program może zostać uruchomiony w trybie graficznym umożliwiającym wyświetlenie jedynie 16 kolorów - nazywanym 4-bitowym, gdyż kolor każdego piksela jest opisany przez 4 bity. Te cztery bity reprezentują wartość od O do 15, stanowiącą indeks do zestawu szesnastu predefmiowanych kolorów. Mając do dyspozycji jedynie 16 kolorów, nie możesz uczynić zbyt wiele w celu poprawienia przejrzystości i ostrości rysowanego obrazu. Ogólnie akceptuje się, że większość poważnych aplikacji graficznych może ignorować tryb o 16 kolorach.
Kolor 8-bitowy
Kolor 8-bitowy umożliwia wyświetlenie na ekranie do 256 kolorów. Stanowi to znaczny krok naprzód, zaś w połączeniu z ditheringiem (roztrząsaniem, które omówimy w dalszej części rozdziału) umożliwia otrzymanie w wielu przypadkach satysfakcjonujących wyników. Każdy piksel jest opisany przez 8 bitów, przechowujących wartości od O do 255, stanowiące indeks do tablicy kolorów, zwanej paletą. Kolory w palecie mogą być wybierane z ponad 16 milionów dostępnych kolorów. Jeśli potrzebujesz 256 odcieni koloru czerwonego, sprzęt jest w stanie ci je zapewnić.
Każdy kolor w palecie jest określany przez podanie ośmiobitowych wartości, z osobna dla każdej barwy składowej koloru, tak więc intensywność każdej barwy składowej może wahać się w przedziale od O do 255. W efekcie kolorowi w palecie można przypisać dowolny z 16 milionów dostępnych kolorów. Przez uważne dobranie kolorów palety można zapewnić prawie fotorealistyczny obraz na ekranie komputera PC.
Kolor 24-bitowy
Obecnie obrazy najwyższej jakości tworzone są w trybie 24-bitowym. W tym trybie każdy piksel jest opisany przez pełne 24 bity, po osiem bitów dla intensywności każdej barwy składowej (8 + 8 + 8 = 24). Dzięki temu każdemu pikselowi na ekranie można przypisać jeden z ponad 16 milionów dostępnych kolorów. Największą wadą tego trybu jest ilość pamięci zajmowanej przez obraz (ponad 2 MB dla ekranu 1024 x 768). Oprócz tego, przenoszenie obszarów obrazu lub po prostu odświeżanie ekranu podczas animacji trwa znacznie dłużej. Na szczęście, obecne akcelerowane karty graficzne są zoptymalizowane dla operacji tego typu.
Inne głębokości koloru
W celu oszczędzenia pamięci lub poprawy wydajności wiele kart graficznych obsługuje także inne tryby koloru.
Rozdział 8. » Kolory i cieniowanie________________________________243
W celu poprawy wydajności, pewne karty obsługują 32-bitowe tryby koloru, czasem określane jako True Color. W rzeczywistości, w trybie 32-bitowym na ekranie nie można wyświetlić więcej kolorów niż w trybie 24-bitowym, lecz jedynie poprawia się wydajność programów, gdyż dane każdego piksela zostają ułożone na granicy 32 bitów. Niestety, powoduje to utratę ośmiu bitów (jednego bajtu) dla każdego piksela. W obecnych, 32-bitowych procesorach Intela, dostęp do adresów pamięci ułożonych na granicy 32 bitów odbywa się znacznie szybciej niż w innych przypadkach.
W celu bardziej efektywnego wykorzystania pamięci często stosuje się dwa inne popularne tryby. Pierwszy z nich to tryb 15-bitowy, w którym na każdą barwę składową przypada po pięć bitów. Każdy piksel może więc zostać wyświetlony w jednym z 32768 kolorów. W trybie 16-bitowym, jednej ze składowych przypada dodatkowy bit; zwykle jest to składowa zielona. Dzięki temu na ekranie można wyświetlić 65 536 dostępnych kolorów. Ten ostatni tryb, w przypadku reprodukcji obrazów fotograficznych, jest praktycznie tak samo efektywny jak tryb 24-bitowy. W przypadku większości obrazów fotograficznych trudno jest dostrzec różnicę pomiędzy 16- a 24-bitowym trybem, choć jeśli w obrazie występują większe gładko cieniowane płaszczyzny, w trybie 16-bitowym można zauważyć na nich pewne paski.
Z punktu widzenia programisty, kolor w trybach 15- i 16-bitowych jest określany tak samo, jak w trybach 24-bitowych - przez podanie intensywności trzech barw składowych. Sprzęt lub sterownik urządzenia pobierają tę 24-bitową wartość i przed ustawieniem koloru piksela konwertują ją do 15 lub 16 bitów.
Wybór koloru
Wiesz już, że OpenGL określa konkretny kolor przez oddzielne podanie intensywności składowych czerwonej, zielonej i niebieskiej. Wiesz także że sprzęt obsługiwany przez Windows może wyświetlić prawie wszystkie kombinacje lub tylko bardzo niewiele kombinacji koloru. Jak więc określa się pożądany kolor jako wartości intensywności składowych czerwonej, zielonej i niebieskiej? I jak Windows wypełnia żądania dotyczące kolorów, które nie są dostępne?
Kostka kolorów
Ponieważ kolor jest określany przez trzy dodatnie wartości poszczególnych składowych, możemy zamodelować dostępne kolory jako bryłę, którą nazywa się przestrzenią koloru RGB. Rysunek 8.6 pokazuje, że ta przestrzeń wygląda jak początek układu współrzędnych z osiami dla kolorów czerwonego, zielonego i niebieskiego. Współrzędne czerwona, zielona i niebieska odpowiadają osiom x, y oraz z. W początku układu (0,0,0) względne intensywności wszystkich trzech składowych wynoszą zero, więc w efekcie otrzymujemy czerń. Maksymalna głębokość koloru w komputerach PC to 24 bity, z 8 bitami na każdą barwę składową, możemy więc założyć, że wartość 255 na każdej z osi reprezentuje pełne nasycenie danego koloru. Otrzymujemy więc kostkę o długości boku wynoszącej 255. Wierzchołek położony dokładnie naprzeciw czerni, w której koń-
244
Część II » Używanie OpenGL
centracje wynosiły (O, O, 0), odpowiada bieli, ze względnymi koncentracjami składowych (255, 255, 255). Pełne nasycenie (255) wzdłuż każdej z osi odpowiada czystemu kolorowi czerwonemu, zielonemu lub niebieskiemu.
Zielony
Rysunek 8.6.
Przestrzeń kolorów RGB
Czerń (0,0,0)
• Czerwony
Niebieski
Ta „kostka kolorów" (rysunek 8.7) zawiera wszystkie dostępne kolory, na swojej powierzchni lub we wnętrzu. Na przykład, wszystkie dostępne odcienie szarości pomiędzy czernią i bielą występują na przekątnej łączącej wierzchołki (O, O, 0) i (255, 255, 255).
Rysunek 8.7.
Przestrzeń kolorów RGB
Żółć (255,255,0)
Cyjan (0,255,255)
Czerwony
Fiolet (255,0,255)
(0,0,255) Niebieski
Niebieski
Rysunek 8.8.
Wynikiem działania programu CCUBE jest kostka kolorów
Rozdział 8. * Kolory i cieniowanie_________________________________245
Rysunek 8.8 przedstawia gładko cieniowaną kostkę koloru, narysowaną przez przykładowy program CCUBE z tego rozdziału. Powierzchnia kostki zawiera przejścia kolorów od czerni w jednym rogu do bieli w przeciwnym. Czerwień, zieleń i błękit występują w swoich rogach, 255 jednostek od czerni. Dodatkowo, kolory żółty, cyjan i fiolet zajmują rogi odpowiadające kombinacjom trzech barw podstawowych. Ten program działa poprawnie nawet w trybie 16-kolorowym, zaś jak to się dzieje, dowiesz się w dalszej części rozdziału. W tym programie można także obracać kostkę w celu obejrzenia wszystkich ścian; służą do tego klawisze kursora.
Ustalanie koloru rysowania
Przyjrzyjmy się funkcji glColor(). Jej prototyp jest następujący:
void glColor<x><t>(red, green, blue, alpha);
W nazwie funkcji <x> reprezentuje ilość argumentów; może to być 3 dla trzech argumentów - red, green oraz blue — lub 4, jeżeli występuje dodatkowy argument alpha. (Składnik alpha określa przejrzystość koloru i zostanie omówiony szczegółowo w rozdziale 15). Na razie będziemy używać wersji funkcji z trzema argumentami.
<t> w nazwie funkcji określa typ używanych argumentów. Może to być b, d, f, i, s, ub, ui, us dla, odpowiednio, typów byte, double, float, integer, short, unsigned byte, unsig-ned integer oraz unsigned short. Inne wersje funkcji zawierają w nazwie jeszcze literę v; te wersje jako parametru wymagają tablicy zawierającej argumenty (v oznacza vectored — w tablicy). W sekcji podręcznika znajdziesz dokładniejszy opis parametrów funkcji glColor().
Większość programów OpenGL, które napotkasz, korzysta z funkcji glColorSf i określa intensywność każdej składowej jako 0,0 dla braku składowej i 1,0 dla j ej pełnej intensywności. Jeśli jednak przyzwyczaiłeś się do programowania w Windows, może być ci łatwiej korzystać z wersji glColorSub tej funkcji. Ta wersja wymaga podania trzech bajtów bez znaku, zawierających wartości od O do 255, określających intensywności składowej czerwonej, zielonej i niebieskiej. Użycie tej wersji funkcji przypomina użycie makra RGB z Windows:
glColor3ub(0, 255, 128) = RGB(0, 255, 128);
W rzeczywistości, dzięki temu może być ci łatwiej dostosować kolory OpenGL z istniejącymi kolorami RGB używanymi w programie do rysowania elementów, które nie są rysowane w OpenGL.
Pamiętaj, że makro RGB określa w Windows kolor, lecz nie powoduje zmiany bieżącego koloru rysowania, tak jak robi to funkcja glColor. W związku z tym makra RGB możesz używać w połączeniu z tworzeniem pędzli lub piór GDI.
246
Część II » Używanie OpenGL
Cieniowanie
W naszej pierwszej definicji funkcji glColor stwierdziliśmy, że ta funkcja ustala bieżący kolor rysowania, zaś wszystkie obiekty tworzone po tym poleceniu będą rysowane bieżącym kolorem. Przy omawianiu prymitywów OpenGL (rozdział 6) rozszerzyliśmy tę definicję następująco: funkcja glColor ustawia bieżący kolor rysowania, który jest używany do wszystkich wierzchołków rysowanych po wywołaniu tej funkcji. Jak dotąd, we wszystkich przykładach rysowaliśmy bryły szkieletowe lub bryły jednolite, w których każda ze ścian miała określony, jednolity kolor. Gdybyśmy jednak określili inny kolor dla każdego wierzchołka prymitywu (punktu, linii lub wielokąta), jaki kolor miałoby wnętrze?
Spróbujmy odpowiedzieć na to pytanie rozpoczynając od punktów. Punkt posiada tylko jeden wierzchołek, więc każdy określony kolor będzie kolorem tego punktu.
W przypadku odcinka mamy dwa wierzchołki, więc każdy z nich może mieć inny kolor. Kolor rysowanej linii zależy od modelu cieniowania. Cieniowanie jest zdefiniowane po prostu jako płynne przejście od jednego koloru do innego. Każde dwa punkty w przestrzeni kolorów RGB (rysunek 8.7) mogą zostać połączone linią prostą.
Płynne (ang. smooth) cieniowanie powoduje zmianę koloru odcinka w miarę przechodzenia przez bryłę kolorów z jednego punktu do drugiego. Na rysunku 8.9 przedstawiono kostkę kolorów z zaznaczonymi wierzchołkami dla czerni i bieli. Poniżej przedstawiono odcinek z dwoma wierzchołkami, czarnym i białym. Kolory użyte do narysowania odcinka odpowiadają kolorom leżącym wzdłuż odcinka łączącego wierzchołki kostki kolorów, od czerni do bieli. Otrzymujemy w wyniku odcinek zmieniający kolor od czarnego, poprzez coraz jaśniejsze odcienie szarości, aż do osiągnięcia na końcu bieli.
Rysunek 8.9.
Sposób cieniowania odcinka od czerni do bieli
Rozdział 8. * Kolory i cieniowanie________________________________247
Matematyka, na której oparte jest cieniowanie wymaga wyznaczenia równania linii łączącej dwa punkty w trójwymiarowej przestrzeni kolorów RGB. Następnie po prostu przechodzimy w pętli od jednego końca odcinka do drugiego, pobierając jego współrzędne w przestrzeni kolorów i przypisując je kolejnym pikselom odcinka rysowanego na ekranie. Odpowiednie algorytmy i sposoby skalowania odcinka w przestrzeni RGB do odcinka na ekranie są zawarte w wielu dobrych książkach o grafice komputerowej, ale na szczęście OpenGL wszystkie obliczenia wykonuje za nas!
W przypadku wielokątów zagadnienie cieniowania staje się trochę bardziej złożone. Na przykład trójkąt także może być reprezentowany jako płaszczyzna w przestrzeni kolorów. Rysunek 8.10 przedstawia trójkąt, w którym każdy wierzchołek jest w jednym z pełnych czystych kolorów, czerwieni, zieleni i błękitu. Kod wyświetlający ten trójkąt znajduje się na listingu 8.1, pochodzącym z przykładowego programu TRIANGLE na płytce CD-ROM.
Rysunek 8.10.
Trójkąt w przestrzeni kolorów RGB
Listing 8.1. Rysowanie płynnie cieniowanego trójkąta z wierzchołkami \v kolorach czerwonym, zielonym ________i niebieskim_____________________________________________
// Włączenie płynnego cieniowania glShadeModel(GL_SMOOTH);
// Rysowanie trójkąta
glBegin(GLJTRIANGLES);
// Czerwony górny wierzchołek
glColor3ub((GLubyte)255,(GLubyte)O,(GLubyte)0);
glVertex3f(O.Of,200.Of,O.Of);
// Zielony wierzchołek w prawym dolnym rogu glColor3ub((GLubyte)O, (GLubyte)255, (GLubyte)0) ;r glVertex3f(200.Of,-70.Of,O.Of);
// Niebieski wierzchołek w lewym dolnym rogu glColor3ub((GLubyte)O, (GLubyte)O, (GLubyte)255); glVertex3f(-200.Of, -70.Of, O.Of); glEnd();
248
Część II » Używanie OpenGL
Ustawianie trybu cieniowania
Pierwsza linia z listingu 8.1 służy do poinstruowania OpenGL, że powinno zostać użyte płynne cieniowanie - czyli modelu, który omawiamy. To jest także domyślny model cieniowania, ale dobrym pomysłem jest mimo to wywoływanie tej funkcji, w celu zapewnienia zamierzonego działania programu.
(Innym modelem cieniowania, który można włączyć za pomocą funkcji glShadeModel, jest model GL_FLAT, czyli płaskie cieniowanie. Płaskie cieniowanie oznacza, że wewnątrz prymitywów nie są wykonywane żadne obliczenia związane z cieniowaniem. Ogólnie, przy płaskim cieniowaniu kolor wnętrza prymitywu jest kolorem określonym przy definiowaniu jego ostatniego wierzchołka. Jedynym wyjątkiem jest prymityw GL_ POLYGON, w którym kolorem całego wielokąta jest kolor pierwszego wierzchołka).
Następnie kod z listingu 8.1 ustala kolor górnego wierzchołka na czystoczerwony, kolor prawego dolnego wierzchołka na zielony, zaś kolor ostatniego, lewego dolnego wierzchołka na niebieski. Ponieważ jest włączone płynne cieniowanie, wnętrze trójkąta jest rysowane w taki sposób, aby powstały płynne przejścia pomiędzy kolorami.
Wynik działania programu TR1ANGLE widzimy na rysunku 8.11. Obraz stanowi reprezentację płaszczyzny przedstawionej graficznie na rysunku 8.10.
Rysunek 8.11.
Wynik działania
programu
TR1ANGLE
Wielokąty, bardziej skomplikowane niż trójkąty, także mogą posiadać wierzchołki w różnych kolorach. W takich przypadkach wewnętrzne obliczenia stają się jeszcze bardziej skomplikowane. Na szczęście, w OpenGL nie musisz się tym martwić. Bez względu na to, jak skomplikowany będzie wielokąt, OpenGL z powodzeniem wymaluje jego wnętrze pomiędzy wierzchołkami.
Zwykle nie będziesz sam stosował tego typu cieniowania. Jest ono używane głównie w celu stworzenia efektów oświetlenia; także w tym przypadku OpenGL przychodzi z pomocą. Oświetleniem zajmiemy się w rozdziale 9.
249
Rozdział 8. * Kolory i cieniowanie
Palety Windows
Przykładowe programy TRIANGLE i CCUBE działają poprawnie bez względu na ilość dostępnych kolorów. Jeśli możesz zmienić głębokość koloru w swoim systemie, spróbuj uruchomić te programy przy różnych głębokościach koloru, poczynając od 16 kolorów, a kończąc na 16 milionach kolorów, jeśli karta to umożliwia. Zauważysz, że kolory użyte do płynnego przejścia zależą od głębokości koloru. Rysunki 8.12a i 8.12b przedstawiają wynik działania przykładowego programu TRIANGLE odpowiednio przy 16 i przy 16 milionach kolorów. Mimo że rysunki nie są wydrukowane w kolorze, widzimy, że cieniowanie drugiego trójkąta jest znacznie płynniejsze.
Rysunek 8.12a.
Wynik działania programu TRIANGLE przy 16 kolorach
Rysunek 8.12b.
Przy 16 milionach kolorów cieniowanie trójkąta jest znacznie płynniejsze
Dopasowywanie kolorów
Co się dzieje, gdy chcesz narysować piksel w konkretnym kolorze, określonym omawianymi przed chwilą wartościami RGB? Wewnętrznie Windows definiuje kolor używając makra RGB, czyli ośmiu bitów dla każdej z barw składowych: czerwonej, zielonej i niebieskiej; w OpenGL możesz odtworzyć to działanie używając funkcji glColorSub.
Jeśli karta graficzna pracuje akurat w trybie 24-bitowym, to każdy piksel jest rysowany dokładnie w kolorze określonym przez tę 24-bitową wartość (trzy intensywności po 8 bitów). W trybach 15- i 16-bitowych, Windows przekazuje 24-bitową wartość do sterownika urządzenia, który przed wyświetleniem piksela zamienia ją na wartość 15- lub 16-bitową. W 24-bitowym trybie kostka RGB mierzy 255 jednostek (czyli 8 bitów)
250____________________________________Część II » Używanie OpenGL
w każdym kierunku. W trybach 15- lub 16-bitowym kostka kolorów mierzy 32 jednostki (5 bitów) lub 64 jednostki (6 bitów) w danym kierunku. Sterownik urządzenia dopasowuje 24-bitową wartość koloru do najbliższej wartości koloru w 15- lub 16-bitowej kostce kolorów.
Rysunek 8.13 przedstawia sposób odwzorowania 8-bitowej składowej czerwieni na 5-bi-tową wartość czerwieni.
Rysunek 8.13.
Odwzorowanie średniointensywnej czerwieni z 8-bitowej wartości na wartość 5-bitową
Na samym końcu skali, w trybie 4-bitowym można wyświetlić tylko 16 kolorów. Te kolory są ustalone i nie można ich modyfikować. Wewnętrznie Windows w dalszym ciągu reprezentuje kolory jako 24-bitowych wartości RGB. Gdy określasz kolor rysowania, za pomocą makra RGB lub funkcji glColor3ub, Windows używa najbardziej zbliżonego z 16 dostępnych kolorów. Jeśli kolor jest przybliżony do wypełnienia, zostaje przybliżony przez tzw. dithering kilku dostępnych kolorów.
Dithering
Gdy mamy do dyspozycji jedynie szesnaście kolorów, tworzona grafika raczej nie będzie miała najwyższej jakości. Jedyną rzeczą, jaka może pomóc, jest tzw. dithering (roztrząsanie), stosowany w tym trybie przez GDI przy wypełnianiu wielokątów lub powierzchni. Roztrząsanie oznacza umieszczanie blisko siebie punktów w różnych kolorach, w celu wywołania iluzji innego koloru złożonego. Na przykład, jeśli obok siebie ułożysz w szachownicę punkty niebieskie i żółte, cały deseń będzie sprawiał wrażenie zielonego. Bez mieszania kolorów, zieleń wydawałaby się ziarnista. Zmieniając wzajemne proporcje żółtych i niebieskich punktów możesz zmieniać intensywność zieleni całego deseniu.
Windows używa roztrząsania w celu otrzymania kolorów niedostępnych w bieżącej palecie. W trybie 16-kolorowym, w przypadku bardziej złożonych scen, jakość obrazu jest zwykle bardzo kiepska. Rysunek 8.12 jasno ilustrował roztrząsanie w Windows; próbowaliśmy na nim odtworzyć trójkąt RGB w systemie posiadającym jedynie 16 kolorów. Ogólnie, Windows nie wykonuje roztrząsania w OpenGL.
OpenGL może używać własnego roztrząsania, które włącza się poleceniem
glEnable(GL_DITHER) ;
Rozdział 8. » Kolory i cieniowanie________________________________251
Może to czasem poprawić obraz w 8- i 15-bitowych trybach. Roztrząsanie w działaniu możesz sprawdzić uruchamiając przykładowy program DITHER na płytce CD-ROM. Ten program rysuje kostkę ze ściankami w różnych kolorach, umożliwiając włączenie lub wyłączenie roztrząsania poprzez polecenie menu. Gdy uruchomisz program w trybie 8-bitowym lub lepszym, roztrząsanie daje niewielki efekt, jednak w trybie 16-bitowym kostka rysowana przy użyciu renderowania wygląda zdecydowanie inaczej.
Korzyści ze stosowania palety w trybie 8-bitowym
W trybach 8-bitowych na ekranie można wyświetlić 256 kolorów, co świadczy o znacznej poprawie jakości grafiki komputerowej. Gdy Windows działa w trybie umożliwiającym wyświetlenie 256 kolorów, sensowne byłoby, aby te kolory były równomiernie rozmieszczone w przestrzeni kolorów RGB. Wtedy wszystkie aplikacje miałyby do dyspozycji względnie szeroki zakres kolorów, zaś podczas wybierania koloru zostałby wybrany najbliższy dostępny kolor. Niestety, w rzeczywistym świecie takie podejście nie jest zbyt praktyczne.
Ponieważ 256 kolorów palety może być wybranych z ponad 16 milionów dostępnych kolorów, aplikacja może znacznie poprawić jakość swojej grafiki poprzez uważne dobranie tych kolorów - i wiele aplikacji właśnie to robi. Na przykład, do stworzenia obrazu morza konieczne będą dodatkowe odcienie błękitu. Aplikacje CAD i modelowania modyfikują paletę tak, aby otrzymać płynne cieniowania powierzchni o określonym, jednolitym kolorze. Na przykład, do precyzyjnego przedstawienia przecięcia dwóch rur scena może wymagać zastosowania ponad dwustu odcieni szarości. Tak więc aplikacje w komputerach PC zwykle zmieniają paletę kolorów zgodnie ze swoimi potrzebami, dzięki czemu wiele obrazów i scen osiąga prawie fotorealistyczny wygląd. W przypadku 256-kolorowych bitmap, format .BMP w Windows posiada nawet własną paletę kolorów, składającą się z 256 pozycji zawierających 24-bitowe wartości koloru dla wszystkich elementów palety kolorów przechowywanego obrazka.
Aplikacja może utworzyć paletę za pomocą funkcji CreatePalette(),w wyniku czego otrzymuje uchwyt palety typu HPALETTE. Ta funkcja wymaga przekazania logicznej struktury palety (LOGPALETTE) zawierającej 256 pozycji, z których każda określa trzy 8-bitowe wartości dla barw składowych, czerwonej, zielonej i niebieskiej. Jednak zanim przejdziemy do tworzenia palety, zobaczmy, jak wielozadaniowe aplikacje mogą korzystać ze wspólnej, pojedynczej palety sprzętowej w 8-bitowym trybie koloru.
Udostępnianie palety
Wielozadaniowość w Windows umożliwia występowanie na ekranie kilku aplikacjom naraz. Sprzęt jednak obsługuje jednocześnie tylko 256 kolorów na ekranie, więc wszystkie aplikacje muszą korzystać z tej samej palety systemowej. Jeśli jedna z aplikacji zmieni paletę systemową, obrazy w innych oknach mogą zmienić kolory, dając w efekcie psychodeliczne tęcze. Aby rozdzielić paletę pomiędzy różne aplikacje, Windows rozsyła zestaw komunikatów. Aplikacje są informowane o tym, że któraś z aplikacji
252____________________________________Część II » Używanie OpenGL
zmodyfikowała systemową paletę oraz o tym, że ich okno znalazło się w ognisku wejściowym i w związku z tym mogą same zmodyfikować paletę systemową.
Gdy aplikacja znajdzie się w ognisku wejściowym, Windows wysyła do głównego okna aplikacji komunikat WM_QUERYNEWPALETTE. Ten komunikat po prostu pyta aplikację, czy chce ona zrealizować nową paletę. Realizacja palety oznacza, że aplikacja kopiuje pozycje swojej prywatnej palety do palety systemowej. W tym celu aplikacja musi najpierw wybrać paletę w kontekście urządzenia aktualizowanego okna, a następnie wywołać funkcję RealizePalette(). Listing 8.2 przedstawia kod procedury obsługi tego komunikatu; będziemy z niego korzystać we wszystkich następnych przykładach w książce.
Listing 8.2. Typowy kod rozdzielania palety w aplikacjach Windows______________________
// Uchwyt palety tworzonej dla 8-bitowych kart graficznych HPALETTE hPalette = NULL;
// Utworzenie palety o uchwycie hPalette
// Windows informuje aplikację o modyfikacji systemowej palety // Ten komunikat głównie odpytuje aplikację o nową paletę, case WM_QUERYNEWPALETTE:
// Jeśli paleta została utworzona, if(hPalette) { int nRet;
// Wybranie palety w bieżącym kontekście urządzenia SelectPalette(hDC, hPalette, FALSE);
// Odwzorowanie pozycji bieżącej palety na paletę // systemową. Zwracaną wartością jest ilość // zmodyfikowanych pozycji palety. nRet = RealizePalette(hDC);
// Przemalowanie, wymuszające nowe odwzorowanie palety // w bieżącym oknie InvalidateRect(hWnd,NULL,FALSE);
return nRet; } break;
// To okno może ustalać paletę, nawet jeśli akurat nie jest
// aktywne
case WM_PALETTECHANGED:
// Nie rób niczego, jeśli paleta nie istnieje lub jeśli // to okno jest oknem, które zmieniło paletę if((hPalette != NULL) && ((HWND)wParam != hWnd))
Rozdział 8. * Kolory i cieniowanie __ __ __ __ 253
i
// Wybranie palety w kontekście urządzenia SelectPalette(hDC,hPalette,FALSE);
// Odwzorowanie pozycji w paletę systemową RealizePalette(hDC);
// Przemapowanie bieżących kolorów do nowo // zrealizowanej palety UpdateColors(hDC); return 0;
break;
Kolejnym komunikatem wysyłanym przez Windows w celu zrealizowania palety jest WM_PALETTECHANGED. Ten komunikat jest wysyłany do okna, które może zrealizować swoją paletę, mimo to, że nie znajduje się w ognisku wejściowym. Podczas obsługi tego komunikatu musisz sprawdzać wartość parametru wParam. Jeśli wParam zawiera uchwyt bieżącego okna otrzymującego komunikat, oznacza to, że został już przetworzony komunikat WM_QUERYNEWPALETTE, więc paleta nie musi być realizowana ponownie.
Zwróć także uwagę, że na listingu 8.2 jest sprawdzana wartość zmiennej hPalette (czy nie zawiera wartości NULL) przed przystąpieniem do realizacji palety. Jeśli aplikacja nie działa w trybie 8-bitowym, nie trzeba realizować żadnej palety. Takie ułożenie kodu zapewnia, że aplikacja poradzi sobie zarówno w przypadku trybów korzystających z palety, jak i trybów nie używających palet.
Tworzenie palety
Niestety, zagadnienia związane z paletą są złem koniecznym, gdyż w dalszym ciągu aplikacje mogą zostać uruchomione w systemach działających w trybie 8-bitowym. Cóż masz więc zrobić, gdy twój program zostanie uruchomiony w systemie umożliwiającym wyświetlenie jedynie 256 kolorów?
W przypadku reprodukcji obrazów, zalecamy wybranie palety kolorów ściśle odzwierciedlającej oryginalne kolory obrazu. Jednak w przypadku ogólnych aplikacji OpenGL, przydatniejsze będzie wybranie palety kolorów zapewniającej najbardziej ogólne rozmieszczenie kolorów w przestrzeni RGB. Sztuczka polega na takim wybraniu kolorów, aby były one równomiernie rozmieszczone w kostce kolorów. Wtedy, gdy zostanie wskazany kolor nie występujący akurat w palecie, Windows wybierze kolor najbardziej zbliżony. Jak już wspomniano, takie rozwiązanie nie jest idealne, ale w przypadku scen renderowanych za pomocą OpenGL stanowi wszystko, co możemy zrobić. Dopóki w scenie nie występuje mapowanie tekstur zawierających szeroki zakres kolorów, otrzymane wyniki mogą być całkiem zadowalające.
254 Część II » Używanie OpenGL
Czy potrzebujesz palety?
Aby sprawdzić, czy twoja aplikacja potrzebuje palety, po ustaleniu formatu pikseli możesz wywołać funkcję DescribePixelFormat(). Sprawdź wartość pola dwFlags otrzymanej struktury PDCELFORMATDESCRIPTOR. Jeśli ustawiony jest bit PFD_NEED_PALETTE, powinieneś utworzyć paletę, która będzie używana przez aplikację. Kod tego testu jest zawarty w listingu 8.3.
Listing 8.3. Sprawdzenie, czy aplikacja wymaga palety_____________________________
PIKELFORMATDESCRIPTOR pfd; // Deskryptor formatu pikseli int nPixelFormat; // Indeks formatu pikseli
// Pobranie indeksu formatu pikseli oraz deskryptora formatu pikseli nPixelFormat = GetPixelFormat(hDC);
DescribePixelFormat(hDC, nPixelFormat, sizeof(PIKELFORMATDESCRIPTOR), spfd);
// Czy ten format pikseli wymaga palety? Jeśli nie, po prostu nie // twórz jej i zwróć wartość NULL if(!(pfd.dwFlags & PFD_NEED_PALETTE)) return NULL;
// Kod tworzenia palety
Struktura palety
Aby utworzyć paletę, musisz najpierw zaalokować pamięć dla struktury LOGPALET-TE. Ta struktura jest wypełniana informacjami opisującymi paletę, a następnie przekazywana funkcji Win32, CreatePalette(). Struktura LOGPALETTE jest zdefiniowana następująco:
typedef struct tagLOGPALETTE { // Igpl
WORD palYersion;
WORD palNumEntries;
PALETTEENTRY palPalEntry[1]; } LOGPALETTE;
Pierwsze dwie składowe to nagłówek palety, określający jej wersję (zawsze ustawiany na 0x300) oraz ilość pozycji palety (256 dla trybów 8-bitowych). Każda pozycja jest zdefiniowana jako struktura PALETTEENTRY, zawierająca składowe RGB dla pozycji koloru.
Poniższy kod alokuje pamięć dla palety logicznej:
LOGPALETTE *pPal; // Wskaźnik do obszaru pamięci dla palety logicznej
// Zaalokowanie pamięci na strukturę logicznej palety
Rozdział 8. » Kolory i cieniowanie_________________________________255
// i wszystkie jej pozycje
pPal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) +
nColors*sizeof(PALETTEENTRY));
nColors określa ilość kolorów w palecie, która do naszych celów zawsze wynosi 256.
Każda pozycja palety jest strukturą PALETTEENTRY, zdefiniowaną następująco:
typedef struct tagPALETTEENTRY { // pe
BYTE peRed;
BYTE peGreen;
BYTE peBlue;
BYTE peFlags; } PALETTEENTRY;
Składowe peRed, peGreen oraz peBlue określają 8-bitowe wartości reprezentujące względne intensywności poszczególnych barw składowych koloru. W ten sposób, każda z 256 pozycji palety zawiera 24-bitową definicję koloru. Składowa peFlag opisuje zaawansowane wykorzystanie pozycji palety. Do zastosowań OpenGL można ją ustawić po prostu na NULL.
Oprócz palety 3-3-2, Windows obsługuje także inne palety, na przykład przeznaczone do wyświetlenia dwustu odcieni szarości.
Paleta 3-3-2
A teraz parę sztuczek. Nasze 256 pozycji palety musi być nie tylko równomiernie rozprowadzone w obrębie kostki kolorów, ale musi także być ułożone w odpowiedniej kolejności. Właśnie ta kolejność umożliwi OpenGL wybranie z palety najbliższej wartości koloru. Jak pamiętamy, w 8-bitowych trybach mamy po trzy bity dla czerwieni i zieleni oraz dwa bity dla błękitu. Taką paletę zwykle określa się mianem palety 3-3-2. Tak więc nasza kostka kolorów RGB będzie mierzyła 8x8x4 jednostki, odpowiednio w osiach czerwonej, zielonej i niebieskiej.
Aby wyszukać w tej palecie żądany kolor, wartość 8-8-8 (24-bitowa wartość koloru) musi zostać przeskalowana do wartości 3-3-2. Ta ośmiobitowa wartość staje się indeksem do naszej palety. Intensywności czerwieni od O do 7 w palecie 3-3-2 muszą odpowiadać intensywnościom od O do 255 w palecie 8-8-8. Sposób połączenia składowych R, G i B w indeks palety ilustruje rysunek 8.14.
Rysunek 8.14. Intensywność Intensywność Intensywność
Upakowanie bitów błękitu zieleni czerwieni
palety 3-3-2 '—*—*——*——«——A——•>
7|t|5|4|3|l|l|Q|.
Gdy budujemy paletę, w pętli przechodzimy przez wartości od O do 255. Następnie de-komponujemy indeks do wartości czerwieni, zieleni i błękitu, reprezentowanych przez te wartości (w ramach palety 3-3-2). Każda składowa jest mnożona przez 255, po czym dzielona przez maksymalną reprezentowalną wartość, co daje efekt skokowego przejś-
256
Część II * Używanie OpenGL
cia od wartości O do 7 dla czerwieni i zieleni oraz od O do 3 dla błękitu. W celu zademonstrowania przebiegu obliczeń, tabela 8.1 przedstawia wartości niektórych pozycji palety.
Tabela 8.1.
Kilka przykładów wartości pozycji palety 3-3-2
Pozycja palety
|
Binarnie (B G R)
|
Składowa niebieska
|
Składowa zielona
|
Składowa czerwona
|
0
|
00 000 000
|
0
|
0
|
0
|
1
|
00000001
|
0
|
0
|
1*255/7
|
2
|
00 000 010
|
0
|
0
|
2*255/7
|
3
|
00000011
|
0
|
0
|
3*255/7
|
9
|
00001 001
|
0
|
1*255/7
|
1*255/7
|
10
|
00001 010
|
0
|
1*255/7
|
2*255/7
|
137
|
10001 001
|
2*255/3
|
1*255/7
|
1*255/7
|
138
|
10001 010
|
2*255/7
|
1*255/7
|
2*255/3
|
255
|
11 111 111
|
3*255/3
|
7*255/7
|
7*255/7
|
Budowanie palety
Niestety, w tym momencie OpenGL w Windows obsługuje w trybie RGBA jedynie paletę 3-3-2. Jest to określone w strukturze PIXELFORMATDESCRIPTOR zwracanej przez funkcję DescribePixelFormat(). Pola cRedBits, cGreenBits oraz cBlueBits tej struktury podają 3, 3 oraz 2 jako liczbę bitów reprezentujących poszczególne składowe. Co więcej, pola CRedShift, cGreenShift oraz cBlueShift określają, o ile bitów należy przesunąć każdą wartość w lewo (w tym przypadku o O, 3 i 6 bitów dla czerwieni, zieleni i błękitu). Te ustawienia wartości tworzą indeks pozycji palety (rysunek 8.14).
Kod z listingu 8.4 tworzy w miarę potrzeby paletę i zwraca jej uchwyt. Ta funkcja wykorzystuje pola ilości i przesunięcia bitów dla każdej składowej ze struktury PIXEL-FORMATDESCRIPTOR, tworząc paletę 3-3-2.
Listing 8.4. Funkcja tworząca paletę 3-3-2
II Jeśli trzeba, tworzy paletę 3-3-2 dla wskazanego kontekstu
// urządzenia
HPALETTE GetOpenGLPalette(HDC hDC)
HPALETTE hRetPal = NULL; PIXELFORMATDESCRIPTOR pfd; LOGPALETTE *pPal;
int nPixelFormat;
// Uchwyt tworzonej palety // Deskryptor formatu pikseli // Wskaźnik do obszaru pamięci //dla palety logicznej // Indeks formatu pikseli
Rozdział 8. * Kolory i cieniowanie_________________________________257
int nColors; // Ilość pozycji palety
int i; // Licznik
BYTE RedRange,GreenRange,BlueRange;
// Zakres dla każdej pozycji koloru (7, 7 i 3)
// Pobranie indeksu formatu pikseli oraz deskryptora formatu // pikseli
nPixelFormat = GetPixelFormat(hDC); DescribePixelFormat(hDC, nPixelFormat, sizeof(PIXELFORMATDESCRIPTOR) , Spfd);
// Czy ten format pikseli wymaga palety? Jeśli nie, po prostu nie // twórz jej i zwróć wartość NULL if(!(pfd.dwFlags S PFD_NEED_PALETTE)) return NULL;
// Ilość pozycji w palecie. 8 bitów oznacza 256 pozycji nColors = l « pfd.cColorBits;
// Zaalokowanie pamięci na strukturę logicznej palety
// i wszystkie jej pozycje
pPal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) +
nColors*sizeof(PALETTEENTRY));
// Wypełnienie nagłówka palety pPal->palVersion = 0x300; // Windows 3.0 pPal->palNumEntries = nColors; // rozmiar tabeli
// Budowanie maski wszystkich jedynek. Tworzy liczbę
// reprezentowaną przez x dolnych bitów ustawionych, gdzie
// x = pfd.cRedBits, pfd.cGreenBits oraz pfd.cBlueBits.
RedRange = (l « pfd.cRedBits) -1;
GreenRange « (l « pfd.cGreenBits) - 1;
BlueRange = (l « pfd.cBlueBits) -1;
// Przejście przez wszystkie pozycje palety for(i =0; i < nColors; i++)
// Wypełnienie 8-bitowych odpowiedników każdego komponentu pPal->palPalEntry [i] .peRed = (i » pfd.cRedShift) & RedRange; pPal->palPalEntry[i].peRed = (unsigned char)(
(double) pPal->palPalEntry[i].peRed * 255.0 / RedRange);
pPal->palPalEntry[i].peGreen = (i » pfd.cGreenShift) &
^GreenRange;
pPal->palPalEntry[i].peGreen = (unsigned char)(
(double)pPal->palPalEntry[i].peGreen * 255.0 /
^GreenRange) ;
pPal->palPalEntry[i].peBlue = (i » pfd.cBlueShift) &
^BlueRange;
pPal->palPalEntry[i].peBlue = (unsigned char)(
(double)pPal->palPalEntry[i].peBlue * 255.0 / BlueRange);
pPal->palPalEntry[i].peFlags = (unsigned char) NULL; }
258____________________________________Część II » Używanie OpenGL
// Otworzenie palety
hRetPal = CreatePalette(pPal);
// Wybranie i zrealizowanie palety dla bieżącego kontekstu // urządzenia
SelectPalette(hDC,hRetPal,FALSE) ; RealizePalette(hDC);
// Zwolnienie pamięci użytej przez strukturę palety logicznej free(pPal);
// Zwrócenie uchwytu nowej palety return hRetPal;
Tworzenie i usuwanie palety
Paleta powinna zostać utworzona i zrealizowana przed utworzeniem i uczynieniem bieżącym kontekstu renderowania. Funkcja z listingu 8.4 wymaga jedynie kontekstu urządzenia, już po ustaleniu formatu pikseli. Funkcja zwraca uchwyt palety, jeśli rzeczywiście jest taka potrzeba. Listing 8.5 przedstawia kolejność operacji wykonywanych podczas tworzenia i niszczenia okna. Jest on podobny do przedstawionego wcześniej kodu związanego z tworzeniem i niszczeniem kontekstu renderowania, lecz w tym przypadku bierzemy pod uwagę także ewentualną obecność palety.
Listing 8.5. Tworzenie i niszczenie palety_____________________________________
// Utworzenie okna, przygotowania dla OpenGL case WM_CREATE:
// Przechowanie kontekstu urządzenia
hDC = GetDC(hwnd);
// Wybranie formatu pikseli SetDCPixelFormat(hDC);
// Jeśli trzeba, utworzenie palety hPalette = GetOpenGLPalette(hDC);
// Utworzenie kontekstu renderowania i uczynienie go
// bieżącym
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC, hRC);
SetupRC();
break;
// Okno jest niszczone, więc robimy porządki case WM_DESTROY:
// Odłożenie bieżącego kontekstu renderowania i usunięcie
// go
wglMakeCurrent(hDC,NULL);
wglDeleteContext(hRC);
// Osunięcie palety, jeśli została utworzona if(hPalette != NULL)
DeleteObject(hPalette);
Rozdział 8. » Kolory i cieniowania________________________________259
// Poinformowanie aplikacji o zakończeniu działania
PostQuitMessage(0);
break;
Pewne ograniczenia
Nie wszystkie pozycje 256 - kolorowej palety zostaną odwzorowane do palety systemowej. Windows rezerwuje 20 pozycji dla statycznych kolorów systemowych, obejmujących 16 standardowych kolorów EGA/YGA. Zabezpiecza to przed zmianą kolorów komponentów okien (pasków tytułowych, przycisków etc.) przez aplikację zmieniającą paletę systemową. Gdy aplikacja realizuje swoją paletę, tych 20 kolorów pozostanie niezmienionych. Na szczęście, niektóre z tych kolorów występują lub ściśle odzwierciedlają kolory z palety 3-3-2. Te, które nie odzwierciedlają żądanych kolorów, są na tyle podobne, że, nie raczej zauważysz różnicy.
Tryb indeksu koloru
OpenGL obsługuje także alternatywny tryb indeksu koloru. W tym trybie kolor do operacji rysunkowych jest określany przez wskazanie indeksu do tablicy kolorów, a nie trójki wartości dla poszczególnych składowych.
Nie można korzystać jednocześnie z trybu indeksu i z trybu RGBA. Oznacza to, że jeśli używasz trybu indeksu na urządzeniu True Color (lub zbliżonym do True Color, na przykład w trybie 16-bitowym), nie będziesz miał dostępu do wszystkich możliwych kolorów. W niektórych implementacjach paleta indeksu koloru może zawierać do 4096 pozycji. Jednak implementacja Microsoftu obsługuj e jedynie 256 pozycji.
Możesz użyć trybu indeksu koloru w odwzorowaniach konturu, w których niektóre funkcje powierzchni zwracają indeks do palety. Ten tryb jest nieco szybszy niż RGBA i nie występują w nim ograniczenia palety 3-3-2. Na przykład, jeśli potrzebujesz dwustu odcieni szarości, możesz je uzyskać. Jednak w trybie indeksu kolorów nie są dostępne pewne efekty oświetlenia omawiane w następnym rozdziale.
Kiedy używać trybu indeksu koloru?
Tak naprawdę rzadko zachodzi konieczność korzystania z trybu indeksu koloru. Zwykle stosuje się go w celu lepszego zapanowania nad paletą. Możesz w nim tworzyć także animacje palety, ale tylko w przypadku urządzeń korzystających z palety (8-bitowych kart graficznych). Animacja palety występuje wtedy, gdy zmieniasz kolory pozycji palety, co powoduje zmiany koloru tych pikseli ekranu, którym przypisano daną pozycję palety. W ten sposób można uzyskać płynne zmiany kolorów dla pewnych efektów specjalnych.
260____________________________________Część II » Używanie OpenGL
Kolejny powód do używania trybu indeksu koloru mają aplikacje, które wykorzystują kolor do wskazania trzeciego wymiaru - na przykład stopnia wgłębienia pewnych obszarów obiektu. Możesz także użyć tego trybu przy tworzeniu obrazów, w których nie jest wymagana zorganizowana paleta. Na koniec, w przypadku trybów 8-bitowych, tryb indeksu koloru może być trochę szybszy, gdyż podczas zmiany palety często konieczne jest manipulowanie tylko jednym kanałem (a nie trzema czerwonym, zielonym i niebieskim).
Oprócz ograniczeń co do wyboru kolorów, w trybie indeksu kolorów nie można korzystać z pewnych innych efektów specjalnych OpenGL — włącznie z wieloma efektami oświetlenia i cieniowania, antyaliazingu czy alpha blendingu. Ogólnie lepiej więc korzystać z trybu RGBA.
Jak już wspomniano, największą korzyścią płynącą ze stosowania trybu indeksu koloru jest większa kontrola nad paletą w 8-bitowych trybach koloru. Paleta 3-3-2 ogranicza możliwości wyboru kolorów, więc gdy w trybie 8-bitowym potrzebujesz dwustu odcieni czerwieni w celu naprawdę gładkiego pocieniowania obiektu, nie masz szczęścia. Jednak w trybie indeksu koloru, pozycjom palety można przypisać dowolne wartości kolorów, od najciemniejszych do najjaśniejszych. Możesz przy tym podzielić paletę na dowolną ilość pasm. Przykładowy program INDEX wyświetla właśnie taki trójkąt, płynnie pocieniowany od czerni do czerwieni (rysunek 8.15). Takie cieniowanie w trybie 8-bitowym, przy użyciu palety 3-3-2, nie byłoby możliwe.
Rysunek 8.15.
Wynik działania programu IN D EX, przedstawiający dwieście odcieni czerwieni
Użycie trybu indeksu koloru
Aby włączyć tryb indeksu koloru, jedyne, co musisz zrobić, to przypisać wartości pola iPixelType struktury PKELFORMATDESCRIPTOR stałą PFD_TYPE_COLORJNDEX. Przedtem musisz jednak utworzyć paletę. W przypadku trybu indeksu koloru, paleta jest specyficzna dla aplikacji. Do przykładowego programu INDEX potrzebujemy palety składającej się jedynie z odcieni koloru czerwonego, tak aby móc uzyskać płynne cieniowanie także w trybie 8-bitowym. Kod użyty do utworzenia tej palety został przedstawiony na listingu 8.6.
Rozdział 8. » Kolory i cieniowanie_________________________________261
Listing 8.6. Kod tworzący paletą składającą się wyłącznie z odcieni koloru czerwonego___________
// Tworzy paletę z przejściem koloru od czarnego do jasnoczerwonego HPALETTE GetRedPalette(HDC hDC)
HPALETTE hRetPal = NULL; // Uchwyt tworzonej palety
LOGPALETTE *pPal; // Wskaźnik do pamięci dla palety
// logicznej
int i; // Licznik
// Zaalokowanie pamięci na strukturę logicznej palety
// i wszystkie jej pozycje
pPal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) +
256*sizeof(PALETTEENTRY));
// Wypełnienie nagłówka palety
pPal->palVersion = 0x300; // Windows 3.0 pPal->palNumEntries = 256; // rozmiar tabeli
// Przejście przez wszystkie pozycje palety i utworzenie kolejnych // odcieni koloru czerwonego, for(i =0; i < 256; i++)
// Intensywność czerwieni od O do 255 pPal->palPalEntry[i].peRed = i; pPal->palPalEntry[i].peGreen = 0; pPal->palPalEntry[i].peBlue = 0; pPal->palPalEntry[i].peFlags = (unsigned char) NULL;
// Utworzenie palety
hRetPal = CreatePalette (pPal);
// Wybranie i zrealizowanie palety dla bieżącego kontekstu
// urządzenia
SelectPalette(hDC,hRetPal,FALSE);
RealizePalette(hDC);
// Zwolnienie pamięci użytej przez strukturę palety logicznej free(pPal);
// Zwrócenie uchwytu nowej palety return hRetPal; }
Zwróć uwagę, że ten kod zawsze zwraca uchwyt palety, gdyż w ogóle nie sprawdzamy, czy wybrany format pikseli wymaga palety. Dzieje się tak, ponieważ trybu indeksu koloru możemy użyć także w trybach o większych ilościach kolorów. Wszystkie pozostałe części kodu związane z realizacją palety pozostają niezmienione.
Rysowanie trójkąta
Przejdźmy do fragmentu kodu nadającego najwyższemu wierzchołkowi trójkąta kolor o indeksie O, czyli najciemniejszy kolor palety (czerń). Kolor dwóch dolnych wierzchołków trójkąta jest ustawiony na indeks palety 255, najjaśniejszy odcień czerwieni. Przy
262____________________________________Część II » Używanie OpenGL
włączonym płynnym cieniowaniu, ten kod (listing 8.7) daje obraz trójkąta przedstawionego na rysunku 8.15.
Listing 8.7. Kod rysujący cieniowany trójkąt w programie INDEX_______________________
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
// Włączenie płynnego cieniowania
glShadeModel(GL_SMOOTH);
// Ustawienie koloru czyszczenia na pierwszą pozycję palety // (czerń) glClear!ndex(O.Of);
// Wyczyszczenie okna bieżącym kolorem tła glClear(GL_COLOR_BUFFER_BIT);
// Rysowanie trójkąta glBegin(GLJTRIANGLES);
// Najciemniejszy czerwony wierzchołek (czarny)
gllndexi(0);
glVertex3f(O.Of,200.Of,O.Of);
// Najjaśniejsze czerwone dolne wierzchołki gllndexi(255);
glVertex3f(200.Of,-70.Of,O.Of); glVertex3f(-200.Of, -70.Of, O.Of); glEnd();
// Zrzucenie poleceń graficznych glFlushO;
Podsumowanie
W tym rozdziale zajęliśmy się jednym z najważniejszych elementów każdego pakietu graficznego: kolorem. Wiesz już, jak określać kolor przez podanie barw składowych RGB, a także jak te składowe mają się do siebie w przestrzeni (kostce) kolorów RGB. Poznałeś zastosowanie funkcji glColor do kolorowania wierzchołków, wiesz więc, jak wpływa to na efekty cieniowania. Wyjaśniliśmy wybór kolorów OpenGL w trybach kolorów 4-, 8-, 16- i 24-bitowych. Zademonstrowaliśmy budowanie palety 3-3-2, używanej przez OpenGL w trybach 8-bitowych. Na koniec, rzuciliśmy okiem na tryb indeksu koloru oraz sposób jego wykorzystania w celu uzyskania większej kontroli nad paletą w 8-bitowych trybach kolorów.
Poprawne użycie kolorów i cieniowania jest nieodzownym elementem dobrej trójwymiarowej grafiki. W następnym rozdziale wyjaśnimy, jak OpenGL stosuje cieniowanie w celu uzyskania efektów oświetlenia. Dowiesz się, jak określać kolory materiałów i warunki oświetlenia, a także jak umożliwić OpenGL wybór kolorów przy rysowaniu.
263
Rozdział 8. + Kolory i cieniowanie
Podręcznik
glClearlndex
Przeznaczenie Plik nagłówkowy Składnia Opis
Ustawia numer koloru tła dla buforów indeksu koloru.
void glClearIndex(GLfloat color);
Ta funkcja określa numer (indeks) koloru, który w trybie indeksu koloru zostanie użyty do wymazania (wyczyszczenia) buforów koloru. Efektem ubocznym jest wymazanie zawartości okna i ustawienie koloru tła na kolor (indeks) określony parametrem color.
Parametry color
Zwracana wartość Przykład Patrz także
GLfloat: Wartość używana przy czyszczeniu buforów koloru funkcją glClear. Domyślną wartością jest 0.
Brak
Przykładowy program INDEX w tym rozdziale.
glClear, glGet
glColor
Przeznaczenie Plik nagłówkowy Składnia
Ustawia bieżący kolor w trybie koloru RGBA.
<gl.h>
void glCo!or3b(GLbyte red, GLbyte green, GLbyte blue);
void glColor3d(GLdouble red, GLdouble green, GLdouble blue);
void glColor3f(GLfloat red, GLfloat green, GLfloat blue);
void g!Co!or3i(GLint red, GLint green, GLint blue);
void glColor3s(GLshort red, GLshort green, GLshort blue);
void glColor3ub(GLubyte red, GLubyte green, GLubyte blue);
void glColor3ui(GLuint red, GLuint green, GLuint blue);
void glColor3us(GLushort red, GLushort green, GLushort blue);
void glColor4b(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha);
void gICołor4d(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha);
void glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); void glColor4i(GLint red, GLint green, GLint blue, GLint alpha);
void glColor4s(GLshort red, GLshort green, GLshort blue, GLshort alpha);
264
Część II » Używanie OpenGL
Opis
void glColor4ub(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha);
void glColor4ui(GLuint red, GLuint green, GLuint blue, GLuint alpha);
void g!Color4us(GLushort red, GLushort green, GLushort blue, GLushort alpha);
void glColor3bv(const GLbyte *v); void glColor3dv(const GLdouble *v); void glColor3fv(const GLfloat *v); void glColor3iv(const GLint *v); void glColor3sv(const GLshort *v); void glColor3ubv(const GLubyte *v); void glColor3uiv(const GLuint *v); void glColor3usv(const GLushort *v); void glColor4bv(const GLbyte *v); void glCo!or4dv(const GLdouble *v); void glColor4fv(const GLfloat *v); void gIColor4iv(const GLint *v); void glColor4sv(const GLshort *v); void glColor4ubv(const GLubyte *v); void glColor4uiv(const GLuint *v); void glColor4usv(const GLushort *v);
Ta funkcja określa bieżący kolor przez określenie wartości trzech barw składowych koloru. Niektóre funkcje akceptuj ą także składową alfa. Każdy komponent reprezentuje wartość intensywności barwy składowej od zera (0,0) do pełnej intensywności (1,0). Funkcje z przyrostkiem v wymagają podania wskaźnika do tablicy zawierającej poszczególne komponenty. Każdy element tej tablicy musi być tego samego typu. Gdy nie jest podawany komponent alfa, jego wartość domyślnie ustawia się na 1,0. Gdy używane są funkcje, które nie korzy stają z argumentów zmiennoprzecinkowych, zakres od zera do najwyższej wartości reprezentowanej przez dany typ jest odwzorowywany w zmiennoprzecinkowy zakres od 0,0 do l ,0.
Parametry red green blue alpha
Określa intensywność czerwonej barwy składowej. Określa intensywność zielonej barwy składowej. Określa intensywność niebieskiej barwy składowej.
Określa intensywność składowej alfa. Używany tylko w wersjach funkcji wymagających podania czterech argumentów.
Wskaźnik do tablicy zawierającej czerwoną, zieloną i niebieską wartość składowej, a być może także wartość alfa.
265
Rozdział 8. » Kolory i cieniowanie
Zwracana wartość Brak
Przykład
Patrz także
Poniższy kod pochodzi z przykładu CCUBE w tym rozdziale. Nadaje jednemu z wierzchołków kostki kolorów kolor biały.
// Przednia ściana glBegin(GL_POLYGON>;
// Biały
glColor3ub((GLubyte)255, (GLubyte)255, (GLubyte)255);
glVertex3f(50.Of, 50.Of, 50.Of);
gllndex
glColorMask
Przeznaczenie Plik nagłówkowy Składnia
Opis
Włącza lub wyłącza modyfikacje składowych koloru w buforach koloru.
void glColorMask(GLboolean bRed, GLboolean bGreen, GLboolean bBlue, GLboolean bAlpha);
Ta funkcja umożliwia określenie, które składowe koloru w buforze koloru będą mogły być modyfikowane (domyślnie wszystkie mogą być modyfikowane). Na przykład, ustawienie parametru bAlpha na GL_FALSE blokuje wszystkie zmiany w składowej alfa bufora koloru okna.
Parametry bRed bGreen bBlue bAlpha
Zwracana wartość
» ,
Przykład Patrz także
GLboolean: Określa, czy składowa czerwona może być modyfikowana. GLboolean: Określa, czy składowa zielona może być modyfikowana. GLboolean: Określa, czy składowa niebieska może być modyfikowana. GLboolean: Określa, czy składowa alfa może być modyfikowana. Brak
Przykładowy program MASK na płytce CD-ROM, w folderze tego rozdziału.
glColor, gllndex, gl!ndexMask, glDepthMask, glStencilMask
gllndex
Przeznaczenie Plik nagłówkowy Składnia
Ustala bieżący indeks koloru dla rysowanych wierzchołków. <gl.h>
void gl!ndexd(GLdouble c); void glIndexf(GLfloat c); void gl!ndexi(GLint c);
266
Część II » Używanie OpenGL
Opis
Parametry
Zwracana wartość Przykład
void glIndexs(GLshort c); void gllndexdv(const GLdouble *c); void gllndexfv(const GLfloat *c); void gilndexiv(const GLint *c); void gllndexsv(const GLshort *c);
Ta funkcja zmienia bieżący indeks koloru na indeks określony przez c. Wewnętrznie indeks koloru jest przechowywany jako liczba zmiennopozycyjna.
Nowy indeks koloru, który zostanie użyty w następnych poleceniach.
Wskaźnik do nowego indeksu koloru, który zostanie użyty w następnych poleceniach.
Brak
Przykładowy program INDEX w tym rozdziale rysuje gładko cieniowany trójkąt. Górny wierzchołek trójkąta otrzymuje kolor o indeksie O, ustawionym na czerń, zaś dolne wierzchołki otrzymują kolor o indeksie 255, ustawiony na jaskrawą czerwień.
(czarny)
// Rysowanie trójkąta glBegin(GL_TRIANGLES);
// Najciemniejszy czerwony wierzchołek
gllndexi(0);
glVertex3f(O.Of,200.0f,O.Of);
// Najjaśniejsze czerwone dolne wierzchołki gllndexi(255);
glVertex3f(200.0f,-70.0f,O.Of); glVertex3f(-200.Of, -70.Of, O.Of); glEnd();
glColor
Patrz także
gllndexMask
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Maskuje bity bufora indeksów kolorów, które w trybie indeksu kolorów mogą być modyfikowane.
void gl!ndexMask(GLuint mask);
Ta funkcja umożliwia zamaskowanie poszczególnych bitów w buforze indeksów kolorów. Gdy bit maski jest ustawiony, odpowiedni bit indeksu może być modyfikowany. Gdy bit maski jest wyzerowany, odpowiedni bit indeksu nie może być modyfikowany, czyli jest zabezpieczony przed zmianą w wyniku operacji rysunkowych. Ta funkcja ma zastosowanie jedynie w trybie indeksu koloru.
267
Rozdział 8. + Kolory i cieniowanie
Parametry mask
GLuint: Określa binarną maskę bitową, włączającą lub wyłączającą zapis poszczególnych bitów w buforze indeksów koloru.
Zwracana wartość Brak Przykład
Przykładowy program MASK na płytce CD-ROM, w folderze tego rozdziału.
gllndex, glDepthMask, glStencilMask
Patrz także
gILogicOp
Przeznaczenie Plik nagłówkowy Składnia Opis
Ustala logiczną operację na pikselach w trybie indeksu koloru.
void glLogicOp(GLenum opcode);
Logiczna operacja na pikselach określa sposób łączenia wartości pikseli. Gdy zamiast piksela jest wpisywana nowa wartość indeksu koloru (rysowany jest nowy punkt), jest on łączony logicznie z bieżącym indeksem koloru już istniejącego piksela. Aby włączyć operacje logiczne na kolorze pikseli, wywołaj funkcję glEnable(GL_LOGIC_OP). Aby je wyłączyć, wywołaj glDisable(GL_LOGIC_OP). Gdy są włączone logiczne operacje na indeksach kolorów pikseli, nowe wartości pikseli są łączone logicznie z wartościami indeksu pikseli już istniejących; samą zaś operację logiczną określa parametr opcode. Gdy operacje logiczne nie są włączone, efekt rysowania pikseli jest taki, jakby wybrana była operacja GL_COPY, czyli zwykłe rysowanie. Logiczne operacje na indeksach pikseli nie są obsługiwane w trybie koloru RGBA.
Parametry opcode
Zwracana wartość Przykład
Patrz także
GLenum: Określa rodzaj operacji logicznej wykonywanej na wartościach indeksów koloru pikseli. Dozwolone są wartości z tabeli 8.2. Ta tabela zawiera listę operacji logicznych oraz wzorów określających ich działanie, gdzie s reprezentuje wartość indeksu koloru piksela źródłowego (istniejącego), zaś d reprezentuj e wartość indeksu koloru piksela docelowego (rysowanego).
Brak
Przykładowy program FLASHER na płytce CD-ROM. W tym przykładzie zastosowano operację logiczną GL_XOR w celu stworzenia płynnej animacji bez podwójnego buforowania.
glGet, gllsEnabled, glEnable, glDisable
268
Część II » Używanie OpenGL
Tabela 8.2.
Operacje logiczne na pikselach
Opcode
|
Wynik
|
GL_CLEAR
|
0
|
GL_SET
|
1
|
GL_COPY
|
s
|
GL_COPY_INVERTED
|
!s
|
GL_NOOP
|
d
|
GLJNYERT
|
!d
|
GL_AND
|
s&d
|
GLJMAND
|
!(s & d)
|
GL_OR
|
s|d
|
GL_NOR
|
!(s | d)
|
GL_XOR
|
sAd
|
GL_EQUIW
|
!(s A d)
|
GL_AND_REVERSE
|
s&!d
|
GL_AND_INVERTED
|
!s&d
|
GL_OR_REVERSE
|
s|!d
|
GL_OR_INVERTED
|
!s|d
|
gIShadeModel
Przeznaczenie Plik nagłówkowy Składnia Opis
Ustala domyślny tryb cieniowania płaski lub gładki.
void glShadeModel(GLenum modę);
Prymitywy OpenGL są zawsze cieniowane, jednak model cieniowania może być płaski (GL_FLAT) lub gładki (GL_SMOOTH). W najprostszym scenariuszu, przed narysowaniem prymitywu jest ustawiany pojedynczy kolor funkcją glColor(). Taki prymityw ma jednolity, niezmienny kolor, bez względu na cieniowanie. Jeśli dla każdego wierzchołka zostanie określony inny kolor, to w zależności od trybu cieniowania zmienia się wygląd obiektu. Przy włączonym gładkim cieniowaniu, kolor wnętrza wielokątów jest interpolowany na podstawie koloru poszczególnych wierzchołków, czyli kolor zmienia się płynnie od jednego wierzchołka do innego. Zmiany kolorów przebiegają zgodnie ze zmianą koloru punktów odcinka łączącego dwa kolory w przestrzeni kolorów. Jeśli przy tym jest włączone oświetlenie, OpenGL dokonuje także innych obliczeń, mających na celu wyznaczenie poprawnego koloru
SL
Rozdział 8. * Kolory i cieniowanie_________________________________269
piksela (patrz rozdział 9). Przy płaskim cieniowaniu, cały prymityw jest rysowany w kolorze zdeterminowanym przez kolor przypisany ostatniemu wierzchołkowi prymitywu. Jednym wyjątkiem jest prymityw GL_POLYGON; w tym przypadku o kolorze wielokąta decyduje kolor określony dla pierwszego wierzchołka.
Parametry
modę GLenum: Określa tryb cieniowania, GL_FLAT (płaski) lub
GL_SMOOTH (gładki). Domyślnym trybem jest GL_SMOOTH.
Zwracana wartość Brak
Przykład Przykładowe programy TRIANGLE i CCUBE na płytce CD-ROM,
w folderach tego rozdziału.
Patrz także glColor, glLight, glLightModel
Rozdział 9.
Oświetlenie i źródła światła
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
4 Określić model oświetlenia 4 glLightMaterial
4 Określić parametry oświetlenia 4 glLight
4 Określić właściwości refleksyjne materiału * glColorMaterial, glMaterial
4 Używać normalnych do powierzchni 4 glNormal
W tym rozdziale omawiamy oświetlenie: w naszym mniemaniu najciekawszą rzecz w OpenGL. Bibliotekę OpenGL poznawałeś od początku - od sposobu tworzenia programów, przez składanie obiektów z prymitywów, a następnie manipulowanie nimi w przestrzeni 3D. W rozdziale ósmym dowiedziałeś się, jak nadawać obiektom kolory oraz jak uzyskać różne sposoby cieniowania. Wszystko to w kolejności i w porządku, ale powiedzmy sobie szczerze - każdy w miarę zdolny student, zaopatrzywszy się w dobrą książkę o grafice komputerowej, może osiągnąć to samo, wykorzystując choćby GDI Windows. Parafrazując znane stwierdzenie, „Gdzie jest mięso?".
Można powiedzieć, że zaczyna się właśnie tutaj. W większości pozostałej części książki nauka traci na znaczeniu, zaś zaczyna rządzić magia. Jak stwierdził Arthur C. Clark, każda odpowiednio zaawansowana technologia jest nieodróżnialna od magii. Oczywiście, w oświetleniu nie ma prawdziwej magii, jednak z pewnością często można odnieść takie wrażenie. (Jeśli chcesz zagłębić się w matematykę, zajrzyj do dodatku B).
Inną nazwą tego rozdziału mogłoby być „Dodawanie realizmu scenie". Widzisz, w rzeczywistym świecie nie liczy się tylko kolor obiektu, jaki zaczęliśmy określać w rozdziale 8. Oprócz posiadania koloru, obiekt może wydawać się lśniący lub matowy, a może nawet jarzyć się własnym światłem. Określony kolor obiektu zmienia się w jasnym lub ciemnym oświetleniu; różnicę sprawia nawet kolor światła oświetlającego
272____________________________________Część II » Używanie OpenGL
obiekt. Oświetlony obiekt, oglądany pod różnymi kątami, może mieć zupełnie inaczej pocieniowane ściany. Większość z pozostałych zagadnień w częściach II i III związanych jest z technikami umożliwiającymi nadanie scenie coraz więcej realizmu. Odłóż więc kalkulator (jeśli chcesz), załóż kapelusz maga i weź głęboki oddech... Zaczynamy pokaz magiczny!
Światło w rzeczywistym świecie
Rzeczywiste obiekty nie mają wyłącznie jednolitego lub pocieniowanego koloru, opartego jedynie na wartościach barw składowych RGB. Rysunek 9.1 przedstawia wynik działania programu JET z płytki CD-ROM. To prosty samolot, stworzony ręcznie z trójkątów, przy wykorzystaniu jedynie metod opisanych dotąd w książce. Jak zwykle, JET i inne programy w tym rozdziale umożliwiają obracanie modeli za pomocą klawiszy kursora w celu lepszego przedstawienia efektów.
Rysunek 9.1.
Prosty samolot zbudowany przez dobraniem odmiennego koloru dla każdego z trójkątów
Kolory dobrano po to, aby podkreślić trójwymiarową strukturę samolotu. Oprócz tego, że jest złożony z trójkątów, nasz samolot w niczym nie przypomina rzeczywistego obiektu. Przypuśćmy, że stworzyłeś model tego samolotu i pomalowałeś każdą powierzchnię pokazanym w oknie kolorem. Ten rzeczywisty model wciąż będzie lśniący lub matowy, w zależności od użytej farby, zaś kolor każdej z powierzchni będzie zmieniał się w zależności od kąta padania światła i kąta, pod którym na nią patrzysz.
OpenGL, jeśli chodzi o oświetlenie, doskonale sobie radzi z odzwierciedlaniem rzeczywistego świata. Dopóki obiekt nie emituje własnego źródła światła, w OpenGL jest iluminowany przez trzy różne rodzaje światła: otaczające (ang. ambient), rozproszone (ang. diffuse) oraz odbłysku1 (ang. specular).
1 W rzeczywistym świecie mamy oczywiście tylko jeden rodzaj światła. Podział na rodzaje światła w OpenGL wynika ze sposobu komputerowego modelowania oświetlenia. (Przy. tłum.)
Rozdział 9. * Oświetlenie i źródła światła 273
Światło otaczające
Światło otaczające to światło, które nie pochodzi z żadnego określonego kierunku. Ma swoje źródło, jednak promienie światła odbijają się po całym pomieszczeniu lub scenie i generalnie są pozbawione kierunku. Obiekty iluminowane światłem otaczającym są równomiernie oświetlone na wszystkich powierzchniach we wszystkich kierunkach. Wszystkie poprzednie przykłady w tej książce możesz uznać za oświetlone białym światłem otaczającym, gdyż obiekty były zawsze widoczne i równomiernie pokoloro-wane (lub cieniowane), bez względu na ich obrót i kąt patrzenia. Obiekt oświetlony przez światło otaczające przedstawia rysunek 9.2.
Rysunek 9.2.
Obiekt oświetlony wyłącznie światłem otaczającym
Światło rozproszone
Światło rozproszone pochodzi z konkretnego kierunku, lecz jest odbijane od powierzchni równomiernie. Nawet jeśli światło jest odbijane równomiernie, powierzchnia jest jaśniejsza, gdy światło pada na nią bezpośrednio, niż wtedy, gdy pada na nią pod większym kątem. Dobrym przykładem źródła światła rozproszonego jest oświetlenie jarzeniowe lub światło słoneczne padające w boczne okno w południe. Na rysunku 9.3 obiekt jest oświetlony źródłem światła rozproszonego.
Rysunek 9.3. Źródło światła rozproszonego
Obiekt oświetlony
wyłącznie światłem \\\\ V-7Światło |est rozproszone równomierne
rozproszonym ^^
Światło odbłysków
Podobnie jak światło rozproszone, światło odbłysków posiada kierunek, ale jest odbijane ostro i w jedną stronę. Bardziej pobłyskujące obiekty możemy poznać po jasnych, lśniących plamach światła na ich powierzchniach (na ekranie są rysowane z użyciem
274____________________________________Część II » Używania OpenGL
koloru odbłysków - ang. specular color). Obiekt oświetlony wyłącznie światłem odbły-sków został przedstawiony na rysunku 9.4.
Rysunek 9.4. Źródło świata odbłysków
Obiekt oświetlony /^^X~ ^wia*> iest wN™ ostro
wyłącznie światłem ^Ęj^jE^^ i w jednym kierunku
odbtysków
Złóżmy to razem
Żadne źródło światła nie jest złożone wyłącznie z jednego z tych trzech rodzajów światła. Składa się raczej z różnych intensywności każdego z rodzajów. Na przykład, czerwone światło lasera w laboratorium składa się prawie wyłącznie z czystego czerwonego światła odbłysku. Jednak cząsteczki dymu lub kurzu wirujące w pomieszczeniu rozpraszają promień, przez co widać go, jak biegnie przez pokój. To rozproszenie reprezentuje składową światła rozproszonego. Gdyby promień był jasny, a w pokoju nie byłoby żadnego innego źródła światła, zauważyłbyś, że przedmioty w pomieszczeniu przybrały czerwony odcień. Byłaby to właśnie niewielka składowa światła otoczenia.
W związku z tym mówimy, że źródło światła w scenie składa się z trzech składowych oświetlenia: otoczenia, rozpraszania i odbłysków. Podobnie jak inne kolory, każdy komponent światła jest definiowany przez wartość RGBA, określającą względne intensywności czerwieni, zieleni i błękitu tworzących ten komponent. (Aż do rozdziału 15 będziemy ignorować składową alfa). Na przykład, nasze czerwone światło lasera mogłoby zostać opisane za pomocą wartości z tabeli 9.1.
Tabela 9.1.
Kolor i dystrybucja światła dla źródła czerwonego światła laserowego
Czerwony Zielony Niebieski Alfa
Odbłysków
|
0,99
|
0,0
|
0,0
|
1,0
|
Rozproszone
|
0,10
|
0,0
|
0,0
|
1,0
|
Otaczające
|
0,05
|
0,0
|
0,0
|
1,0
|
Zauważ, że czerwony promień lasera nie zawiera światła zielonego ani niebieskiego. Zwróć także uwagę, że światło odbłysków, rozproszone i otoczenia, może przybierać wartości z zakresu od 0,0 do 1,0. Możesz zinterpretować tę tabelę jako określającą, że czerwone światło lasera w pewnych scenach będzie miało bardzo dużą składową odbłysków, małą składową światła rozproszonego i bardzo małą składową światła otaczającego. Gdziekolwiek skierujesz promień takiego światła, zobaczysz najprawdopodobniej czerwony punkt. Oprócz tego, z powodu warunków panujących w pomieszczeniu (dym,
Rozdział 9. » Oświetlenie i źródła światła___________________________275
kurz etc.), składowa światła rozproszonego umożliwi spostrzeżenie drogi promienia w powietrzu. Na koniec, składowa światła otaczającego - także wynikającego z obecności cząsteczek dymu i kurzu - skieruje troszkę światła na wszystkie obiekty w pomieszczeniu. Składowe otaczające i rozpraszania często są łączone, gdyż w naturze także bardzo często występują razem.
Materiały w rzeczywistym świecie
Światło jest jednak tylko jednym z elementów równania. W rzeczywistym świecie obiekty posiadają także własny kolor. W rozdziale 8 stwierdziliśmy, że kolor obiektu można zdefiniować jako długości fal odbijanego od niego światła. Niebieska piłka odbija większość niebieskich fotonów i pochłania inne. Zakładamy przy tym, że światło padające na piłkę zawiera takie niebieskie fotony, więc mogą one zostać dostrzeżone przez obserwatora. Ogólnie, większość scen w rzeczywistym świecie jest oświetlonych światłem białym, będącym równomierną mieszaniną wszystkich kolorów. (W świetle białym większość obiektów ukazuje swe właściwe, „naturalne" kolory.) Jednak nie zawsze tak jest; gdy niebieską piłkę umieścisz w ciemnym pokoju i oświetlisz ją jedynie żółtym światłem, obserwatorowi będzie wydawała się czarna, ponieważ całe światło zostanie pochłonięte, zaś nie będzie niebieskiego światła, które mogłoby zostać odbite.
Właściwości materiału
Gdy używamy oświetlenia, nie opisujemy wielokątów jako posiadających konkretny kolor, ale raczej jako zrobione z materiałów o określonych właściwościach refleksyjnych. Zamiast mówić, że wielokąt jest czerwony, mówimy, że jest zrobiony z materiału odbijającego głównie światło czerwone. Wciąż twierdzimy, że powierzchnia jest czerwona, ale tym razem musimy określić także właściwości refleksyjne materiału w odniesieniu do źródeł światła otaczającego, rozpraszającego i odbłysków. Materiał może być lśniący i doskonale odbijać światło odblasków, pochłaniając przy tym większość światła otaczającego i rozpraszającego. Odwrotnie, płaski pokolorowany obiekt może absorbować całe światło odbłysków i w żadnych warunkach nie wydawać się lśniącym. Kolejną właściwością, którą możemy określić, jest właściwość emisji obiektów emitujących własne światło, takich jak świetliki czy zegarki świecące w ciemności.
Oświetlanie materiałów
Dobranie światła i właściwości materiałów tak, aby uzyskać pożądany efekt, wymaga nieco praktyki. Nie ma tu kostek kolorów ani prostych reguł dających szybkie i proste odpowiedzi. Właśnie tu analiza ustępuje miejsca sztuce, a nauka magii. Na płytce CD-ROM, w folderze tego rozdziału, znajduje się program o nazwie MATLIGHT (od Materials and Lighting Studio). Ten program umożliwia zmianę na bieżąco właściwości materiału i światła w scenie składającej się z kilku prostych obiektów. Programu MATLIGHT możesz użyć w celu pobawienia się różnymi kombinacjami właściwości. Oprócz tego,
276____________________________________Część II » Używanie OpenGL
ponieważ został dołączony kod źródłowy, możesz zastąpić obiekty programu własnymi obiektami i dopracować ich właściwości przed umieszczeniem ich w swoich scenach.
Podczas rysowania obiektu OpenGL decyduje, jakiego koloru użyć dla każdego piksela sceny. Obiekt ma własne „refleksyjne" kolory, a źródło światła własne. Jak OpenGL wyznacza, których kolorów należy użyć? Zrozumienie tego nie jest trudne, pod warunkiem, że potrafisz mnożyć ułamki. (Widzisz, a nauczyciel mówił, że kiedyś ci się to przyda!)
Każdemu wierzchołkowi prymitywu jest przypisywana wartość koloru RGB obliczona poprzez przemnożenie efektu składowej otaczającej, rozpraszającej i odbłysków źródła światła przez właściwości koloru otaczającego, rozpraszającego i odbłysków materiału. W wyniku zastosowania gładkiego przejścia pomiędzy wierzchołkami osiąga się iluzję iluminacji obiektu!
Obliczanie efektów światła otaczającego
Po pierwsze, powinieneś odrzucić notację koloru i myśleć o nim wyłącznie w kategoriach intensywności składowej czerwonej, zielonej i niebieskiej. W przypadku źródła światła otaczającego o półintensywnych składowych czerwonej, zielonej i niebieskiej, otrzymałbyś wartość RGB dla tego źródła wynoszącą (0,5, 0,5, 0,5). Jeśli takie światło otaczające oświetla obiekt z właściwościami koloru otaczającego określonymi w wartościach RGB jako (.50, 1.0, .50), to wynikowym „kolorem" dla światła otaczającego byłoby
(0.50 * .50, 0.5 * 10.0, 0.50 * .50) = (0.25, 0.5, 0.25)
co odpowiada przemnożeniu przez siebie poszczególnych barw składowych światła otaczającego i koloru otaczającego materiału, tak jak na rysunku 9.5.
Rysunek 9.5.
Obliczanie koloru U q
dla składowej \lntensywnosc 5 \ Intensywność 5 \ Intensywność 5
otaczającej koloru
5x5 = 25 5x1=5 5x5 = 25
obiektu
Otaczający "kolor" materiału (.5,1,.5)
Tak więc komponenty koloru materiału określają po prostu procent odbijanego światła danego rodzaju. W naszym przykładzie intensywność składowej czerwonej światła otaczającego wynosiła 1/2, zaś właściwość materiału .5 dotycząca składowej czerwonej światła otaczającego określiła, że tylko połowa połowy intensywności została odbita. Połowa połowy to 1/4, czyli 0,25.
Rozdział 9. * Oświetlenie i źródła światła_____________________________277
Efekty światła rozpraszającego i odbłysków
W przypadku światła otaczającego było to dość proste. Światło rozpraszające także posiada intensywności RGB, w podobny sposób współgrające z właściwościami materiału. Jednak światło rozpraszające ma kierunek, więc intensywność na powierzchni obiektu zmienia się w zależności od kąta między tą powierzchnią a kierunkiem źródła światła. To samo odnosi się do intensywności powierzchni oświetlanych światłem odbłysków. Ogólny efekt jako wartość RGB jest obliczany tak samo jak w przypadku światła otaczającego, gdzie intensywność źródła światła (po zmodyfikowaniu według kąta padania) jest mnożona przez współczynnik odbicia światła dla materiału. Na koniec, wszystkie trzy wartości RGB są sumowane w celu otrzymania końcowego koloru obiektu. Jeśli którakolwiek z intensywności barw składowych przekracza wartość l ,0, jest obcinana do tej wartości (nie można zastosować większej intensywności niż pełna!).
Ogólnie, składowe otaczająca i rozpraszająca źródeł światła i materiałów są takie same i maj ą największy wpływ na określanie koloru obiektu. Światła odbłysków i właściwości materiałów dla odbłysków najczęściej są jasnoszare lub białe. Składowa odbłysków przede wszystkim zależy od kąta padania światła, zaś odbłyski na powierzchni obiektu zwykle są białe.
Umieszczenie światła w scenie
Być może wydaje ci się to zbyt dużą ilością teorii naraz. Zwolnijmy więc nieco i zacznijmy poznawać przykłady kodu OpenGL, potrzebne do włączenia oświetlenia; dzięki temu utrwalimy także to, czego się dotąd dowiedziałeś. Zademonstrujemy także pewne dodatkowe właściwości i wymagania dotyczące oświetlenia w OpenGL. Kilka następnych przykładów zostało zbudowanych na podstawie programu JET. Początkowa wersja nie zawierała żadnego kodu związanego z oświetleniem i po prostu rysowała trójkąty przy włączonym buforze głębokości. Jednak gdy skończymy, na metalicznej powierzchni obracanego samolotu będą przesuwać się jasne odblaski słońca.
Włączanie oświetlenia
Aby poinformować OpenGL, że ma zacząć obliczać oświetlenie, wywołaj funkcję glEnable() z parametrem GL_LIGHTING:
glEnable(GL_LIGHTING);
Ta pojedyncza instrukcja nakazuje OpenGL, aby przy obliczaniu koloru każdego wierzchołka w scenie brał pod uwagę właściwości materiału i parametry oświetlenia. Jednak bez określenia właściwości materiału lub źródła światła twój obiekt pozostanie ciemny i nieoświetlony, tak jak pokazano na rysunku 9.6. Spójrz do kodu każdego z przykładów opartych na programie JET, a przekonasz się, że w każdym z nich, tuż po przygotowaniu kontekstu renderowania, jest wywoływana funkcja SetupRC(). Właśnie tam odbywa się inicjowanie wszelkich parametrów oświetlenia.
278__________________ ___ Część II » Używanie OpenGL
Rysunek 9.6.
Program 2 włączonym oświetleniem, jednak bez zdefiniowania parametrów oświetlenia i właściwości materiałów
Przygotowanie modelu oświetlenia
Po włączeniu obliczania oświetlenia, pierwsze, co musisz zrobić, to przygotować model oświetlenia. Trzy parametry określające model oświetlenia są ustawiane za pomocą funkcji glLightModel().
Pierwszym parametrem oświetlenia, używanym w naszym następnym przykładzie, jest GL_LIGHT_MODEL_AMBIENT. Umożliwia określenie globalnego światła otaczającego, oświetlającego wszystkie obiekty ze wszystkich stron. Poniższy kod określa użycie jasnego, białego światła:
// Jasne białe światła = pełna intensywność wszystkich składowych RGB GLfloat ambientLightU = f l.Of, l.Of, l.Of, 1.0 } ;
// Włączenie światła glEnable(GL_LIGHTING);
// Ustawienie modelu oświetlenia tak aby korzystał ze światła
otoczenia
// określonego w ambientLight[] glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientLight);
Pokazana tu odmiana funkcji glModelLight, glModelLightfy, jako pierwszy parametr otrzymuje ustawiany lub modyfikowany parametr modelu oświetlenia, a jako drugi - tablicę wartości RGBA składających się na światło. Domyślne wartości RGBA globalnego światła otaczającego to (0,2, 0,2, 0,2, 1,0), czyli dość ciemne. Inne parametry modelu oświetlenia umożliwiają wskazanie, czy są oświetlane przednie, tylne czy obie strony wielokątów, a także kąty dla obliczania oświetlenia rozproszonego i odbłysków. Więcej informacji na temat tych parametrów znajdziesz w sekcji podręcznika.
Rozdział 9. » Oświetlenie i źródła światła_____________________________279
Przygotowanie właściwości materiału
Gdy mamy już źródło światła otaczającego, musimy tak ustawić właściwości materiałów, aby wielokąty odbijały światło i żebyśmy widzieli samolot. Spójrz na poniższy fragment kodu:
GLfloat gray[] = { 0.75f, 0.75f, 0.75f, l.Of };
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray);
glBegin(GL_TRIANGLES);
glVertex3f(-15.Of, O.Of, 30.Of);
glVertex3f(O.Of, 15.Of, 30.Of);
glVertex3f(O.Of, O.Of, -56.Of); glEnd();
Pierwszy parametr funkcji glMaterialfy określa, czy będą ustawiane właściwości przedniej (GL_FRONT), tylnej (GL_BACK) czy obu stron materiału (GL_FRONT_AND_ BACK). Drugi parametr informuje o ustawianej właściwości; w tym przypadku współczynniki odbicia światła rozpraszającego i otaczającego są ustawiane na taką samą wartość. Ostatnim parametrem jest tablica zawierająca wartości RGBA określające daną właściwość. Wszystkim prymitywom stworzonym po wywołaniu funkcji glMaterial będą nadawane ostatnio ustawione właściwości, aż do momentu innego wywołania funkcji glMaterial.
W większości sytuacji składowe rozpraszająca i otaczająca są takie same, a dopóki nie chcesz odbłysków (iskier, lśniących miejsc), nie musisz definiować ich właściwości. Jednak nawet mimo to dość żmudne byłoby przygotowywanie tablicy dla każdego koloru w naszym modelu i wywoływanie funkcji glMaterial() przed każdym wielokątem czy grupą wielokątów.
To prowadzi nas do drugiej i zalecanej metody ustalania właściwości materiałów, zwanej śledzeniem kolorów. W przypadku śledzenia kolorów informujesz OpenGL, aby ustawiało właściwości materiału w momencie wywoływania funkcji glColor. Aby włączyć śledzenie kolorów, wywołaj funkcję glEnab!e() z parametrem GL_COLOR_MA-TERIAL:
glEnable(GL_COLOR_MATERIAL);
Następnie funkcją glColorMaterial określ właściwości materiału, które będą ustawiane zgodnie z wartościami przekazanymi funkcji glColor
Na przykład, aby ustawić właściwości otaczającą i rozpraszającą dla przednich stron wielokątów tak, aby śledziły wywołania glColor, wywołaj
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
W tym momencie wcześniejszy kod ilustrujący ustawianie właściwości materiału uległby zmianie na obecny. To wygląda jak więcej kodu, jednak w rzeczywistości, przy wzroście liczby wielokątów, oszczędza konieczności pisania wielu linii kodu i wykonuje się trochę szybciej.
280____________________________________Część II » Używanie OpenGL
// Włącz śledzenie kolorów glEnable(GL_COLOR_MATERIAL) ;
// Kolory rozpraszania i otaczania dla przednich stron wielokątów // śledzą kolor ustawiany w glColor glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFOSE, gray);
glColor3f(0.75f, 0.75f, 0.75f);
glBegin(GL_TRIANGLES) ;
glvertex3f(-15.Of, O.Of, 30.Of);
glVertex3f(O.Of, 15.Of, 30.Of);
glVertex3f(O.Of, O.Of, -56.Of); glEnd();
Listing 9.1 zawiera kod, który dodaliśmy w funkcji SetupRC do naszego przykładu JET, ustawiający jasne światło otaczające oraz dobierający właściwości materiału tak, aby mógł odbijać światło i być widoczny. Oprócz tego zmieniliśmy kolory samolotu, tak że teraz każda sekcja, a nie każdy wielokąt, są rysowane w osobnych kolorach. Zwróć uwagę, że końcowy wynik (rysunek 9.7) nie różni się zbytnio od obrazka, dla którego jeszcze nie włączyliśmy oświetlenia. Jeśli jednak zredukujemy światło otaczające o połowę, otrzymamy obraz pokazany na rysunku 9.8. Osiągamy to przez ustawienie następujących wartości RGBA światła otaczającego:
GLfloat ąmbientLightn = { 0.5f, 0.5f, 0.5f, l.Of };
Rysunek 9.7.
Wynik działania ukończonego programu AMBIENT
Listing 9.1. Zmiana warunków oświetlenia otaczającego
// Ta funkcja odpowiada za inicjowanie kontekstu renderowania // Oprócz tego tworzy i inicjuje światła void SetupRC() {
// Wartości oświetlenia
// Jasne białe światło
GLfloat ambientLightl] = { l.Of, l.Of, l.Of, l.Of };
281
Rozdział 9. * Oświetlenie i źródła światła
glEnable(GL_DEPTH_TEST), glFrontFace(GL_CCW);
glEnable(GL_CULL_FACE);
// Oświetlanie glEnable(GL_LIGHTING);
// Usuwanie niewidocznych powierzchni
// Wielokąty o kierunku przeciwnym do
// ruchu wskazówek są widziane z
// przodu
// Nie pokazuj wnętrza obiektów
// Włączenie oświetlenia
// Ustawienie modelu oświetlenia tak aby korzystał ze światła // otoczenia
// określonego w ambientLight[] glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambientLight);
glEnable(GL_COLOR_MATERIAL);// Włączenie śledzenia koloru dla ^materiałów
// Kolory rozpraszania i otaczania dla przednich stron wielokątów // śledzą kolor ustawiany w glColor glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE);
// Jasnoniebieskie tło
glClearColor(O.Of, O.Of, 05.f,l.Of);
Rysunek 9.8.
Wynik działania programu AMBIENT ze źródłem światła przygaszonym o połowę
Wiemy więc już, jak można zredukować światło otaczające w scenie, w celu stworzenia ciemniejszego obrazu. Jest to użyteczne w symulacjach, w których stopniowo zapada mrok lub w których blokowane jest źródło światła kierunkowego, na przykład gdy obiekt znajdzie się w cieniu innego, większego obiektu.
Używanie źródła światła
Manipulowanie światłem otaczającym ma swoje zastosowania, jednak w większości aplikacji próbujących odtworzyć rzeczywisty świat trzeba określić jedno lub więcej
282____________________________________Część II » Używanie OpenGL
konkretnych źródeł światła. Oprócz intensywności i kolorów, te źródła posiadają także położenie i kierunek. Położenie źródeł światła może radykalnie wpłynąć na wygląd sceny.
OpenGL obsługuje do ośmiu niezależnych źródeł światła umieszczonych w dowolnym miejscu sceny lub poza bryłą widzenia. Możesz umieścić źródło światła w nieskończonej odległości, sprawiając, że jego promienie będą równoległe, lub ustawić je blisko, sprawiając, że jego promienie będą rozbiegały się dookoła. Możesz także określić światło punktowe, z zadanym stożkiem światła, jak również manipulować innymi charakterystykami światła.
Gdzie jest góra?
Gdy określasz źródło światła, informujesz OpenGL, gdzie ono jest i w jakim kierunku świeci. Często źródła światła będą świecić we wszystkich kierunkach lub ich promienie będą ograniczone. W przypadku każdego rysowanego obiektu promienie światła biegnące od źródła (każdego poza czystym światłem otaczającym) będą uderzać pod pewnym kątem w powierzchnię wielokątów obiektów. Oczywiście, w przypadku światła kierunkowego, niekoniecznie powierzchnie wszystkich wielokątów będą oświetlone. W celu zrealizowania efektu cieniowania pomiędzy wielokątami, OpenGL musi być w stanie obliczyć wspomniany kąt padania.
Na rysunku 9.9 wielokąt (kwadrat) został oświetlony promieniem światła z pewnego źródła. W momencie zetknięcia się z powierzchnią, promień tworzy z nią kąt A. Następnie światło odbija się pod kątem B w kierunku obserwatora (gdyż w przeciwnym razie by go nie zobaczył). Te kąty, w powiązaniu z omawianymi dotąd właściwościami oświetlenia i materiału, służą do obliczenia wynikowego koloru punktu w danym miejscu. Tak się składa (ze względów projektowych), że miejscami używanymi przez OpenGL są wierzchołki wielokątów. Obliczając wynikowy kolor w każdym z wierzchołków, a następnie przeprowadzając gładkie cieniowanie (opisywane w rozdziale 8), OpenGL tworzy iluzję oświetlenia. Magia!
Rysunek 9.9. H Źródło światła
Światło odbija się od ^\
obiektów pod ^-' vs^ 0bsMwator
określonym kątem ^\ »£""
Z punktu widzenia programisty, ukazuje to pewną niedogodność koncepcji. Każdy wielokąt jest tworzony jako zestaw wierzchołków, które są tylko punktami w przestrzeni. Na każdy wierzchołek pada pod pewnym kątem strumień światła. Jak więc ty lub OpenGL możecie obliczyć kąt pomiędzy punktem a linią (linią promienia światła)? Oczywiście, geometrycznie nie da się wyznaczyć kąta pomiędzy pojedynczym punktem a linią w przestrzeni 3D, gdyż istnieje nieskończona liczba rozwiązań. Z tego względu należy
Rozdział 9. * Oświetlenie i źródła światła_____________________________283
przypisać każdemu wierzchołkowi dodatkowe informacje, określające kierunek od wierzchołka w górę, czyli na zewnątrz od powierzchni prymitywu.
Normalne do powierzchni
Linia poprowadzona od wierzchołka w górę zaczyna się na jakiejś wyimaginowanej płaszczyźnie (lub wielokącie) i biegnie do niej pod kątem prostym. Ta linia jest nazywana wektorem normalnym. Słowo wektor może raczej kojarzyć ci się z rzucanymi od niechcenia terminami załogi Star Trek, ale w tym wypadku oznacza po prostu linię prostopadłą do danej powierzchni. Wektor jest linią wskazującą w pewnym kierunku, zaś słowo normalny to kolejne określenie, jakie jajogłowi wymyślili dla słowa prostopadły (oznaczającego przecinanie się pod kątem 90°). Tak jakby słowo prostopadły nie było okropne samo w sobie!
Tak więc, wektor normalny to linia wskazująca, prostopadła do powierzchni wielokąta. Rysunek 9.10 przedstawia przykłady wektorów normalnych w dwóch i trzech wymiarach.
Rysunek 9.10. ^ Wektor normalny
. ... '' Wektor normalny
; trójwymiarowy '
wektor normalny
90°
Dwuwymiarowy wektor normalny Trójwymiarowy wektor normalny
Być może zapytasz, dlaczego musimy określić wektor normalny dla każdego wierzchołka? Dlaczego nie moglibyśmy po prostu utworzyć pojedynczej normalnej dla całego wielokąta i użyć jej w odniesieniu do każdego wierzchołka? Moglibyśmy - i w pierwszych przykładach właśnie to będziemy robić. Istnieją jednak sytuacje, kiedy nie chcemy, aby wszystkie normalne w wierzchołkach były dokładnie prostopadłe do powierzchni wielokąta. Z pewnością zauważyłeś, że wiele powierzchni nie jest płaskich! Możesz przybliżać je płaskimi, wielokątnymi obszarami, ale skończy się to otrzymaniem poszarpanej lub wielościennej powierzchni. Nieco później omówimy technikę tworzenia iluzji gładkich krzywych za pomocą odcinków poprzez „wykręcanie" normalnych do powierzchni (jeszcze więcej magii!). Ale na wszystko przyjdzie czas.
Określanie normalnej
Aby zobaczyć, jak określamy normalną dla wierzchołka, spójrz na rysunek 9.11 - płaszczyznę unoszącą się ponad płaszczyzną \z w przestrzeni 3 D. Zwróć uwagę na linię biegnącą przez wierzchołek (l, l, 0), prostopadłą do płaszczyzny. Gdybyśmy wybrali dowolny punkt na tej linii, powiedzmy, że (1> 10, 0), odcinek od pierwszego punktu (l, l, 0) do drugiego punktu (l, 10, 0) byłby naszym wektorem normalnym. Drugi wybrany punkt w tym wypadku wskazuje, że kierunek naszego wierzchołka wskazuje
284
Część II » Używanie OpenGL
w górę osi y. Używa się tego także do wskazania przedniej i tylnej strony wielokątów, zakładając, że wektor biegnie w górę od przedniej strony wielokąta.
Wektor norn
Rysunek 9.11.
Wektor normalny biegnący prostopadle do płaszczyzny
/
(1,10,0)
przód
\|
A:
ty)
Jak widzisz, drugi punkt, określający koniec wektora normalnego, znajduje się o pewną liczbę jednostek w osiach x, y oraz z od wierzchołka. Zamiast określać dwa punkty dla każdego wektora normalnego, możemy odjąć wierzchołek od drugiego punktu wektora normalnego, aby otrzymać pojedynczą trójkę współrzędnych opisującą odległości x, y i z od wierzchołka. Dla naszego przykładu będzie to
(l, 10, 0) -
(l, l, 0)
(1-1, 10-1, 0) = (O, 9, 0)
Można na to spojrzeć jeszcze inaczej; jeśliby wierzchołek został przeniesiony do początku układu, punkt określony przez odjęcie dwóch oryginalnych punktów w dalszym ciągu wskazywałby kierunek na zewnątrz, pod kątem 90° do płaszczyzny. Taki nowo przetransformowany wektor normalny przedstawia rysunek 9.12.
Rysunek 9.12.
Nowo
przetransformowany wektor normalny
.'' (1,10,0)
Przesunięty wektoi
Ten wektor jest kierunkową wartością informującą OpenGL, w którym kierunku zwrócony jest wierzchołek (lub wielokąt). W następnym segmencie kodu widzimy, jak wektory normalne są określane dla jednego z trójkątów w przykładowym programie JET:
glBegin(GLJTRIANGLES);
glNormalSf(O.Of, -l.Of, O.Of); glVertex3f(O.Of, O.Of, 60.Of);
Rozdział 9. * Oświetlenie i źródła światła_____________________________285
glVertex3f(-15.Of, O.Of, 30.Of); glVertex3f(15.Of,O.Of,30.Of); glEnd();
Funkcja glNormalSf pobiera trójkę współrzędnych określającą wektor normalny wskazujący w kierunku prostopadłym do tego trójkąta. W tym przykładzie normalne wszystkich trzech wierzchołków mają ten sam kierunek, w stronę ujemnej części osi y. To bardzo prosty przykład, gdyż trójkąt leży płasko na płaszczyźnie xz i w rzeczywistości stanowi dolną część samolotu.
Perspektywa podawania normalnej dla każdego wierzchołka lub wielokąta w naszych scenach mogłaby wydawać się przerażająca, zwłaszcza że bardzo niewiele powierzchni leży dokładnie na jednej z głównych płaszczyzn. Nie bój się, wkrótce zaprezentujemy nadającą się do ponownego wykorzystania funkcję, którą możesz wywoływać w celu obliczenia normalnych dla swoich wierzchołków.
Kierunek wielokątów
Zwróć szczególną uwagę na kolejność wierzchołków w trójkącie samolotu. Jeśli oglądasz ten trójkąt z kierunku, w którym wskazuje wektor normalny, wierzchołki są ułożone dookoła trójkąta w kierunku przeciwnym do ruchu wskazówek zegara. Właśnie to nazywa się kierunkiem wielokąta. Domyślnie, przednia strona wielokąta jest zdefiniowana jako strona, od której wierzchołki wydają się ułożone w kierunku przeciwnym do ruchu wskazówek zegara.
Normalne jednostkowe
Aby OpenGL mógł realizować całą swoją magię, wszystkie normalne do powierzchni muszą zostać zamienione na normalne jednostkowe. Normalna jednostkowa to po prostu wektor normalny o długości 1. Normalna na rysunku 9.12 ma długość 9. Długość wektora oblicza się przez zsumowanie jego składowych podniesionych do kwadratu, a następnie wyciągnięcie z sumy pierwiastka kwadratowego. Po podzieleniu każdej składowej przez długość otrzymujemy wektor wskazujący dokładnie w tym samym kierunku, ale o długości jednej jednostki. W naszym przypadku otrzymamy wektor (O, l, 0). Nazywa się to normalizacją. Tak więc w celu obliczenia oświetlenia, wszystkie wektory normalne trzeba znormalizować. I jak tu nie mówić w żargonie!
Możesz poinformować OpenGL, aby automatycznie zamieniał wektory normalne na normalne jednostkowe; w tym celu możesz włączyć normalizację parametrem GL NOR-MALIZE:
glEnable(GL_NORMALIZE);
To jednak narzuca pewne ograniczenia co do wydajności. Lepiej jest samemu wcześniej obliczyć normalne, zamiast zmuszać OpenGL, aby to robił.
Mając dowolny wektor normalny w postaci trójki współrzędnych, za pomocą kodu z listingu 9.2 możesz łatwo znaleźć odpowiadający mu jednostkowy wektor normalny.
286
Część II » Używanie OpenGL
Listing 9.2. Funkcja redukująca wektory normalne do jednostkowych wektorów normalnych________
// Redukuje wektor normalny określony jako zestaw trzech współrzędnych // do normalnego wektora o długości l (normalizuje podany wektor). void ReduceToUnit(float vector[3]) {
float length;
// Obliczenie długości wektora
length = (float)sqrt((vector[0]*vector[0]) +
(vector[l]*vector[l]) +
(yector[2]*vector[2]));
// Zabezpieczenie przed wektorami, // zbyt bliska zeru if(length == O.Of) length = l.Of;
których długość może być
// Normalizacja wektora polega na podzieleniu każdej ze // współrzędnych przez długość wektora vector[0] /= length; vector[l] /= length; vector[2] /= length;
Znajdowanie normalnej
Rysunek 9.13 przedstawia kolejny wielokąt, który już nie leży po prostu na jednej z głównych płaszczyzn. Wektor normalny prostopadły do tej powierzchni nie jest już tak prosty do odgadnięcia, potrzebujemy więc jakiegoś prostego sposobu obliczania normalnych dla dowolnego wielokąta w przestrzeni 3D.
Rysunek 9.13.
Niebanalne
wyznaczanie
normalnej
Normalna
Możesz łatwo obliczyć wektor normalny dla każdego wielokąta składającego się z przynajmniej trzech wierzchołków leżących w tej samej płaszczyźnie (płaskiego wielokąta). Rysunek 9.14 przedstawia trzy punkty: Pl, P2 i P3, użyte do zdefiniowania dwóch wektorów: wektora VI z Pl do P2 oraz wektora V2 z P l do P3. Matematycznie rzecz biorąc, dwa wektory w przestrzeni 3 D definiuj ą płaszczyznę (leży w niej nasz wielokąt). Jeśli obliczysz iloczyn wektorowy tych dwóch wektorów (w zapisie matematycznym VI x V2), otrzymany w wyniku wektor będzie prostopadły do płaszczyzny (czyli będzie
Rozdział 9. * Oświetlenie i źródła światła____________________________287
normalną). Rysunek 9.15 pokazuje wektor V3 wyprowadzony jako wynik iloczynu wektorowego wektorów VI i V2.
Rysunek 9.14.
Dwa wektory zdefiniowane przez trzy punkty na płaszczyźnie
Rysunek 9.15.
Wektor normalny jako iloczyn •wektorowy dwóch wektorów
Nie martw się, jeśli jeszcze nie wiesz, jak obliczyć iloczyn wektorowy dwóch wektorów; wszystko, czego potrzebujesz, to fiinkcja z listingu 9.3. Aby jej użyć, przekaż jej tablicę zawierającą trzy dowolne wierzchołki swojego wielokąta (podane z zachowaniem kolejności przeciwnej do ruchu wskazówek zegara), a także tablicę, która po powrocie z funkcji będzie zawierać obliczony wektor normalny. Abyś mógł zobaczyć, jak działa ta funkcja, zostały zapewnione stałe wartości dla współrzędnych x, y i z.
Listing 9.3. Funkcja obliczająca wektor normalny dla trzech dowolnych wierzchołków wielokąta______
// Punkty pl, p2 i p3 są podane w kolejności przeciwnej
// do ruchu wskazówek zegara
void calcNormal(float v[3][3], float out[3])
{
float vl[3],v2[3];
static const int x = 0;
static const int y = 1;
static const int z = 2;
// Obliczenie dwóch wektorów na podstawie trzech punktów vl[x] = v[0][x] - v[l][x]; vi[y] = v[0][y] - v[l][y]; vl[z] = v[0] [z] - v[l] [z];
v2[x] = v[l] [x] - v[2] [x] ;
v2[y] = v[l][y] - v[2][y];
v2[z] = v[l][z] - v[2][z];
// Obliczenie iloczynu wektorowego dwóch wektorów w celu
// otrzymania wektora normalnego, który zostanie przechowany w
// out[]
out[x] = vl[y]*v2[z] - vl[z]*v2[y] ;
out[y] = vl[z]*v2[x] - vl[x]*v2[z];
outfz] = vl[x]*v2[y] - vl[y]*v2[x] ;
288____________________________________Część II » Używanie OpenGL
// Normalizowanie wektora (przeskalowanie długości do jedności) ReduceToUnit(out);
Przygotowanie źródła światła
Gdy znasz już wymagania co do przygotowania wielokątów tak, aby współdziałały ze źródłem światła, nadszedł czas na włączenie samych świateł! Listing 9.4 przedstawia funkcję SetupRC() z przykładowego programu LITJET. Część procesu przygotowawczego do tego programu tworzy źródło światła i umieszcza je z góry po lewej stronie, nieco poza obserwatorem. Źródło światła GL_LIGHTO posiada składowe otaczającą i rozpraszającą, ustawione zgodnie z zawartością tablic ambientLight[] oraz diffuseLight[]. W rezultacie otrzymujemy umiarkowane białe światło.
GLfloat ambientLightU = ( 0.3f, 0.3f, 0.3f, l.Of }; GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.7f, l.Of };
// Przygotowanie i włączenie światła O glLightfv(GL_LIGHTO,GL_AMBIENT,ambientLight); glLightfv(GL_LIGHTO,GL_DIFFUSE,diffuseLight);
Do pozycjonowania światła służy poniższy kod:
GLfloat lightPosM = { -50.f, 50.Of, 100.Of, l.Of } ;
glLightfv(GL_LIGHTO,GL_POSITION,lightPos);
W tym kodzie lightPosf] zawiera położenie światła. Ostatnią wartością w tej tablicy jest 1,0, czyli podane współrzędne oznaczają koordynaty źródła światła. Gdyby ostatnią wartością w tablicy było 0,0, oznaczałoby to że światło znajduje się w nieskończonej odległości, w kierunku wektora zawartego w tej tablicy. Zajmiemy się tym nieco później.
Na koniec, źródło światła GLJJGHTO jest włączane:
glEnable(GL_LIGHTO); Listing 9.4. Przygotowywanie oświetlenia i kontekstu renderowania w programie LITJET__________
// Ta funkcja odpowiada za inicjowanie kontekstu renderowania // Oprócz tego tworzy i inicjuje światła void SetupRCO (
// Wartości i współrzędne źródła światła
GLfloat ambientLightn = { 0.3f, 0.3f, 0.3f, l.Of );
GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.7f, l.Of };
GLfloat lightPosU = {-50 . f, 50 . Of, 100.Of, l.Of } ;
glEnable(GL_DEPTH_TEST); // Usuwanie niewidocznych powierzchni
glFrontFace(GL_CCW); // Wielokąty o kierunku przeciwnym do
// ruchu wskazówek są widziane z
// przodu
Rozdział 9. » Oświetlenie i źródła światła_____________________________289
glEnable(GL_CULL_FACE); // Nie pokazuj wnętrza obiektów
// Włączenie oświetlenia glEnable(GL_LIGHTING);
// Przygotowanie i włączenie światła O glLightfv(GL_LIGHTO,GL_AMBIENT,ambientLight); glLightfv(GL_LIGHTO,GL_DIFFUSE,diffuseLight); glLightfv(GL_LIGHTO,GL_POSITION,lightPos); glEnable(GL_LIGHTO);
// Włączenie śledzenia koloru materiału glEnable(GL_COLOR_MATERIAL);
// Ustawienie właściwości materiału glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
// Jasne niebieskie tło glClearColor(O.Of, O.Of, l.Of, l.Of );
Przygotowanie właściwości materiału
Zwróć uwagę, że w listingu 9.4 włączane jest śledzenie koloru, zaś śledzonymi składowymi są składowe otaczająca i rozpraszająca przednich części wielokątów. Właśnie to zdefiniowaliśmy w przykładowym programie AMBIENT:
// Włączenie śledzenia koloru materiału glEnable(GL_COLOR_MATERIAL);
// Ustawienie właściwości materiału glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
Rysowanie wielokątów
Kod renderowania z pierwszych dwóch przykładów JET teraz ulega znacznej zmianie, tak aby uwzględnić nowy model oświetlenia. Listing 9.5 pochodzi z funkcji RenderSce-ne() programu LITJET.
Listing 9.5. Przykład kodu ustalającego kolor i obliczającego normalne dla -wierzchołków wielokątów___
float normal[3J; // Miejsce na obliczona normalną do powierzchni
// Ustalenie koloru materiału glRGB(0, 255, 0); glBegin(GLJTRIANGLES);
glNormal3f(O.Of, -l.Of, O.Of);
glVertex3f(O.Of, O.Of, 60.Of);
glVertex3f(-15.Of, O.Of, 30.Of);
glVertex3f(15.Of,O.Of,30.Of); //glEndO;
290
Część II » Używanie OpenGL Rc
// Wierzchołki dla tego panelu float v[3][3] = {{ 15.Of, O.Of, 30.Of}, { O.Of, 15.Of, 30.Of}, { O.Of, O.Of, 60.Of}};
// Obliczenie normalnej dla płaszczyzny calcNormal(v,normal);
// Narysowanie trójkąta przy użyciu // normalnej do płaszczyzny //glBegin(GLJTRIANGLES);
glNormal3fv(normal);
glVertex3fv(v[0]);
glvertex3fv(v[l]);
glVertex3fv(v[2]); 7/glEndO ;
(dla wszystkich wierzchołków)
Zauważ, że obliczamy wektor normalny używając naszego kodu z listingu 9.3. Oprócz
tego właściwości materiałów śledzą teraz kolory ustawiane funkcją glColor (zastąpioną
naszym makrem glRGB). Zwróć także uwagę, że nie każdy trójkąt jest blokowany parą
funkcji glBegin()/glEnd(). Możesz raz określić, że rysujesz trójkąty, zaś każde trzy no
we wierzchołki będą tworzyły trójkąt, chyba że odwołasz to instrukcją glEnd(). W przy
padku bardzo dużych ilości wielokątów, może to znacznie poprawić wydajność, dzięki
wyeliminowaniu wielu niepotrzebnych wywołań funkcji. ,
Rysunek 9.16 przedstawia działanie programu LITJET. Obracając samolot za pomocą klawiszy kursorów, możesz oglądać efekt cieniowania powierzchni, oświetlanych światłem pod coraz to innymi kątami.
Rysunek 9.16.
Działanie programu LITJET
Rada dotycząca wydajności
Nąjoczywistszym sposobem poprawienia wydajności tego kodu byłoby wcześniejsze obliczenie wszystkich normalnych dla wierzchołków i przechowanie ich w celu użycia w funkcji RenderScene. Zanim jednak to wy-
Rozdział 9. + Oświetlenie i źródła światła 291
próbujesz, w rozdziale 10 poczytaj o listach wyświetlania. Listy wyświetlania umożliwiają przechowanie nie tylko obliczonych wartości dla wektorów normalnych, ale także danych dla samych wielokątów. Pamiętaj, przykłady w tym rozdziale mają na celu zademonstrowanie koncepcji, niekoniecznie muszą stanowić najefektywniejszy możliwy kod.
Efekty oświetlenia
Światło otaczające i rozpraszające w przykładzie LITJET wystarczy jedynie do zapewnienia iluzji oświetlenia. Powierzchnia samolotu jest cieniowana zgodnie z kątem padania światła. Gdy samolot się obraca, te kąty się zmieniają i dostrzegasz efekt oświetlenia, dzięki czemu możesz łatwo odgadnąć, skąd pada światło.
Pominęliśmy jednak światło odbłysków, a także właściwości odbłysków materiału samolotu. Choć występują efekty cieniowania, sama powierzchnia samolotu jest raczej płasko pokolorowana. Właściwości otaczająca i rozpraszająca materiału są wystarczające, jeśli modelujesz obiekty udające plastelinę, drewno, karton, szmaty czy inne niepo-łyskliwe materiały. Jednak w przypadku metalicznych powierzchni, takich jak powłoka samolotu, połysk jest najczęściej nieodzowny.
Odbłyski
Światło i właściwości materiału dotyczące odbłysków nadają obiektowi wymagany połysk. Ten połysk może rozjaśnić pewne części obiektu i tworzy plamy odbłysków w momencie, gdy światło pada pod kątem ostrym do kąta patrzenia na powierzchnię. Plama odbłysku występuje wtedy, gdy prawie całe światło padające na powierzchnię jest odbijane w kierunku obserwatora. Dobrym przykładem plam odbłysków mogą być odbły-ski słońca na wodzie.
Światło odbłysków
Dodanie składowej odbłysków do źródła światła jest bardzo łatwe. Poniższy program pokazuje przygotowanie źródła światła z programu LITJET, zmodyfikowane tak, aby uwzględnić składową odbłysków.
// Wartości i współrzędne źródła światła GLfloat ambientLigta [ ] = { 0.3f, 0.3f, 0.3f, l.Of }; GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.1 f, l.Of }; GLfloat specular[] = { l.Of, l.Of, l.Of, l.Of}; GLfloat lightPos[] = { O.Of, 150.Of, 150.Of, l.Of };
// Włączenie oświetlenia glEnable(GL LIGHTING);
292____________________________________Część II » Używanie OpenGL
// Przygotowanie i włączenie światła O glLightfv(GL_LIGHTO,GL_AMBIENT,ambientLight) ; glLightfv(GL_LIGHTO,GL_DIFFUSE,diffuseLight); glLightfv(GL_LIGHTO,GL_SPECULAR,specular) ; glLightfv(GL_LIGHTO,GL_POSITION,lightPos) ; glEnable(GL_LIGHTO);
Tablica specular[] definiuje bardzo jasne, białe światło dla składowej odbfysków światła. Naszym celem było wymodelowanie jasnego światła słonecznego. Linia
glLightfv(GL_LIGHTO,GL_SPECULAR, specular) ;
po prostu dodaje składową odbłysków do źródła światła GL_LIGHTO.
Gdyby były to jedyne zmiany w programie LITJET, nie zobaczylibyśmy żadnej różnicy w wyglądzie samolotu. Dzieje się tak, ponieważ nie zdefiniowaliśmy jeszcze właściwości odbłysków materiałów składających się na nasz samolot.
Właściwości odbłysków materiału
Dodanie właściwości odbłysków do materiału jest tak samo proste jak dodanie składowej odbłysków do źródła światła. Następny segment kodu przedstawia kod z programu LITJET, także zmodyfikowany w celu dodania składowej odbłysków do właściwości materiału.
// Wartości i współrzędne źródła światła
GLfloat specref[] = { l.Of, l.Of, l.Of, l.Of };
// Włączenie śledzenia koloru materiału glEnable(GL_COLOR_MATERIAL);
// Ustawienie właściwości materiału glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
// Od tego momentu wszystkie materiały mają pełną właściwość // odbłysków z ustawionym wysokim połyskiem glMaterialfv(GL_FRONT, GL_SPECULAR,specref); glMateriali(GL_FRONT,GL_SHININESS,128);
Jak wcześniej, włączyliśmy śledzenie koloru, tak że właściwości otaczająca i rozpraszająca materiału odpowiadają bieżącemu kolorowi ustawionemu funkcją glColor(). (Oczywiście nie chcemy, aby właściwość odbłysków była zgodna z kolorem ustawianym funkcjąglColor, gdyż określiliśmy ją oddzielnie i nie chcemy jej zmieniać).
Tym razem dodaliśmy tablicę specreff] zawierającą wartości RGBA dla właściwości odbłysków materiału. Ta tablica z samymi jedynkami daje w wyniku powierzchnię, odbijającą prawie całe padające w kierunku obserwatora światło. Linia
glMaterialfv(GL_FRONT, GL_SPECULAR, specref);
ustawia właściwości materiału dotyczące wszystkich następnych wielokątów, które dzięki temu nabierają połysku. Ponieważ nie wywołujemy już później funkcji glMate-
Rozdział 9. * Oświetlenie i źródła światła_____________________________293
rial z właściwością GL_SPECULAR, tę właściwość będą posiadały wszystkie materiały. Uczyniliśmy to celowo, aby wszystkie wielokąty samolotu wyglądały, jakby zostały uczynione z bardzo połyskliwego materiału.
To, co zrobiliśmy w tej funkcji przygotowawczej, jest bardzo ważne: określiliśmy, że właściwości otaczająca i rozpraszająca wszystkich następnych wielokątów (dopóki nie zmienimy ich wywołując funkcję glMaterial lub glColorMaterial) będą się zmieniać wraz ze zmianami bieżącego koloru, lecz właściwość odbłysków materiałów pozostanie taka sama.
Stopień połyskliwości
Jak już wspomnieliśmy, jasne światło połysków i wysoka wartość właściwości połysku materiału powodują rozjaśnienie kolorów obiektu. W naszym przykładzie, obecne, ekstremalnie jasne światło połysków (pełna intensywność) i właściwość połysku materiału (także pełna intensywność) powodują, że samolot staje się prawie całkowicie biały lub jasnoszary, z wyjątkiem tych powierzchni, które są odwrócone od źródła światła (w takim wypadku stają się czarne i nieoświetlone). Aby zmniejszyć ten efekt, po określeniu składowej odbłysków stosujemy następną linię kodu:
glMateriali(GL_FRONT,GL_SHININESS, 128) ;
Właściwość GL_SHININESS ustawia stopień połyskliwości materiału, określający, jak mała i skupiona będzie plama połysku. Wartość O określa nieskupioną plamę połysku, czyli w rzeczywistości równomierne rozjaśnienie kolorów wszystkich pikseli wielokąta. Jeśli ustawisz tę wartość, zredukujesz rozmiar plamy i zwiększysz stopień skupienia połysku, powodując powstanie lśniącej plamy. Im większa wartość, tym bardziej lśniąca staje się powierzchnia. We wszystkich implementacjach OpenGL wartość tego parametru należy do przedziału od l do 128.
Listing 9.6 przedstawia nowy kod funkcji SetupRC, pochodzący z programu SHINY-JET. Jest to jedyny fragment kodu, jaki uległ zmianie w stosunku do poprzedniego programu LITJET (oczywiście poza nazwą programu). W tym programie samolot wydaje się bardzo połyskliwy. Rysunek 9.17 przedstawia wynik działania tego programu, jednak aby móc w pełni docenić efekt, powinieneś uruchomić program i przytrzymać jeden z klawiszy kursora w celu obrócenia samolotu w promieniach słońca.
Listing 9.6. Funkcja SetupRC z programu SHINYJET, powodująca powstanie odbłysków na samolocie
// Ta funkcja odpowiada za inicjowanie kontekstu renderowania // Oprócz tego tworzy i inicjuje światła void SetupRC()
// Wartości i współrzędne źródła światła GLfloat ambientLight[] = { 0.3f, 0.3f, 0.3f, l.Of }; GLfloat diffuseLightU = ( 0.7f, 0.7f, 0.7f, l.Of }; GLfloat specular[] = { l.Of, l.Of, l.Of, l.Of}; GLfloat lightPos[] = { O.Of, 150.Of, 150.Of, l.Of }; GLfloat specref[] = { l.Of, l.Of, l.Of, l.Of };
294____________________________________Część II » Używanie OpenGL
glEnable(GL_DEPTH_TEST); // Usuwanie niewidocznych powierzchni glFrontFace(GL_CCW); // Wielokąty o kierunku przeciwnym do
// ruchu wskazówek są widziane z
// przodu glEnable(GL_CULL_FACE); // Nie pokazuj wnętrza obiektów
// Włączenie oświetlenia glEnable(GL_LIGHTING);
// Przygotowanie i włączenie światła O glLightfv(GL_LIGHTO,GL_AMBIENT,ambientLight); glLightfv(GL_LIGHTO,GL_DIFFUSE,diffuseLight); glLightfv(GL_LIGHTO,GL_SPECULAR,specular) ; glLightfv(GL_LIGHTO,GL_POSITION,lightPos); glEnable(GL_LIGHTO);
// Włączenie śledzenia koloru materiału glEnable(GL_COLOR_MATERIAL);
// Ustawienie właściwości materiału glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
// Od tego momentu wszystkie materiały mają pełną właściwość // odbłysków z ustawionym wysokim połyskiem glMaterialfv(GL_FRONT, GL_SPECULAR,specref); glMateriali(GL_FRONT,GL_SHININESS,128) ;
// Jasne niebieskie tło glClearColor(O.Of, O.Of, l.Of, l.Of ) ;
Rysunek 9.17.
Działanie programu SHINYJET
Uśrednianie normalnych
Wspomnieliśmy wcześniej, że przez „wykręcanie" normalnych można tworzyć gładkie krzywe powierzchnie za pomocą prostych linii. Ta technika, zwana uśrednianiem normalnych, daje interesującą iluzję optyczną. Załóżmy, że mamy powierzchnię taką jak pokazana na rysunku 9.18, ze zwykłymi normalnymi do powierzchni.
295
Rozdział 9. + Oświetlenie i źródła światła
Rysunek 9.18.
Powierzchnia łamana, ze zwykłymi normalnymi
Choć normalne zostały narysowane pomiędzy wierzchołkami, w rzeczywistości są określone dla każdego z nich. Jeśli weźmiesz pod uwagę, że każdy wierzchołek styka się także z inną płaszczyzną, możesz określić normalną tego wierzchołka jako średnią normalnych poszczególnych płaszczyzn. Wspólny wierzchołek dwóch stykających się płaszczyzn (ilustruje to rysunek 9.19), będzie miał inną normalną w momencie rysowania każdej z powierzchni. Jeśli weźmiemy średnią z tych dwóch normalnych i zastosujemy ją przy definiowaniu każdej z powierzchni, to gdy OpenGL je pocieniuje, połączenie ich obu będzie się wydawało mniej ostre.
Rysunek 9.19.
Uśrednianie normalnych sprawia, że ostre krawędzie wydają się mniej ostre
Listing 9.7 przedstawia funkcję renderującą tworzącą powierzchnię przedstawioną na rysunku 9.18. (Ten kod pochodzi z programu WAYEY na płytce CD-ROM). Powierzchnia jest tworzona przez przechodzenie od strony lewej do prawej na współrzędnej x i zmianę kierunku w osi y. Współrzędne z są stałe, gdzie -50 odpowiada przedniej krawędzi obiektu, zaś 50 - tylnej.
Listing 9.7. Funkcja RenderSceneprogramu WAVEY______________________________
// Wywoływane w celu narysowania sceny void RenderScene(void)
float normal[3]; float v[4][3]; float lastY; float nextY; float temp; float x;
// Miejsce na obliczane normalne
// Miejsce na współrzędne prostokąta
// Lewa strona prostokąta
// Prawa strona prostokąta
// Tymczasowe miejsce na zamianę zmiennych
// Miejsce na współrzędną X
// Stan menu określa, czy jest włączony tryb szkieletowy if(iState == WIRE)
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); else
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
296____________________________________Część II » Używanie OpenGL
// Stan menu określa, czy jest włączone gładkie cieniowanie iffiState == SMOOTH || iState == AVERAGE)
glshadeModel(GL_SMOOTH); else
glshadeModel(GL_FLAT);
// Wyczyszczenie okna kolorem tła glClear(GL_COLOR_BOFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Zachowanie stanu macierzy i wykonanie obrotów
glPushMatrix();
glRotatef(xRot, l.Of, O.Of, O.Of);
glRotatef(yRot, O.Of, l.Of, O.Of);
// Ustalenie niebieskiego koloru powierzchni glRGB(0,0,255) ;
// Inicjowanie kroków Y lastY = O.Of; nextY = 10.Of;
// Przejście przez współrzędne x od lewa do prawa budowanie // prostokątów o zmiennych pochyleniach w górę i w dół for(x = -60.Of; x < 60.Of; x+= 20.Of)
{
// Pierwszy wierzchołek
v[0][0] = x; // wsp. X dla lewej
v[0][1] = lastY;
v[0][2] = 50.Of; // wsp. Z dla tyłu
// Drugi wierzchołek
v[l][0] = x; // wsp. X dla lewej
v[l] [1] •= lastY;
v[l] [2] = -50.Of; // wsp. Z dla przodu
// Trzeci wierzchołek
v[2][0] = x + 20.Of; // wsp. X dla prawej
v[2][1] = nextY;
v[2] [2] = -50.Of; // wsp. Z dla przodu
// Czwarty wierzchołek
v[3][0] = x + 20.Of;// wsp. X dla prawej
v[3][1] = nextY;
v[3] [2] •= 50.Of; // wsp. Z dla tyłu
// Początek wielokąta glBegin(GL_POLYGON);
ifUState != AVERAGE)
{
// Obliczenie i ustawienie wektora normalnego,
// chyba że w menu wybrano uśrednianie
calcNormal(v,normal);
glNormal3fv(normal);
Rozdział 9. * Oświetlenie i źródła światła _____________________________ 297
else // Uśrednianie normalnych. Nieco oszukujemy, bo
// wiemy że normalne wskazują w górę lub w dół <
// Normalne wskazują prosto w górę if(nextY == 10)
glNorma!3f (O.Of, l.Of, O.Of); else
// Normalne wskazują prosto w dół
glNorma!3f (O.Of, -l.Of, O.Of); }
// Podanie dwóch lewych wierzchołków glVertex3fv(v[0] ) ; glVertex3fv(v[l] ) ,-
// To samo, ale normalna po drugiej stronie wskazuje // w przeciwnym kierunku iffiState == AVERAGE) ( if(nextY == 10)
glNormalSf (O.Of, -l.Of, O.Of); // Normalna wskazuje
// dół else
glNorma!3f (O.Of , l.Of, O.Of); // Normalna wskazuje
// górę }
// Określenie dwóch prawych wierzchołków glVertex3fv(v[2] ) ; glVertex3fv(v[3] ) ; glEnd ( ) ;
// Zamiana pozycji współrzędnych Y
temp = lastY;
lastY = nextY;
nextY = temp;
}
glPopMatrix() ;
// Zrzucenie poleceń rysunkowych glFlush ( ) ;
Program WAYEY posiada opcje menu umożliwiające rysowanie szkieletu, płaskie lub gładkie cieniowania bądź wreszcie uśrednianie normalnych. Rysunek 9.20 przedstawia powierzchnię łamaną przy włączonym płaskim cieniowaniu, zaś rysunek 9.2 1 - tę samą powierzchnię z uśrednionymi normalnymi. Można dostrzec, że na drugim rysunku wzdłuż całej powierzchni przebiegają gładkie fale.
298
Część II » Używanie OpenGL
Rysunek 9.20.
Powierzchnia łamana, ze zwykłymi normalnymi do powierzchni
Rysunek 9.21.
Ta sama
powierzchnia po uśrednieniu normalnych
Światła punktowe
Jak dotąd, pozycję światła określaliśmy następująco:
// Tablica zawierająca pozycję
GLfloat lightPosU = { O.Of, 150.Of, 150.Of, l.Of },
// Ustawienie pozycji światła glLightfv(GL_LIGHTO,GL_POSITION,lightPos);
Tablica HghtPosf] zawiera wartości x, y i z określające albo samo położenie źródła światła w scenie, albo kierunek, z którego światło biegnie. Ostatnia wartość, w tym przypadku 1,0, wskazuje, że światło występuje we wskazanym miejscu. Domyślnie,
Rozdział 9. * Oświetlenie i źródła światła_____________________________299
światło rozchodzi się równomiernie we wszystkich kierunkach -jednak można to zmienić w celu uzyskania efektu światła punktowego.
Aby źródło światła znalazło się w nieskończonej odległości, w kierunku wskazywanym przez wektor zawarty w tablicy lightPosf], w ostatnim elemencie tej tablicy powinieneś umieścić wartość 0,0. Kierunkowe źródło światła, bo tak się takie źródło nazywa, równomiernie oświetla powierzchnie obiektów, tzn. wszystkie promienie światła biegną równolegle. Z drugiej strony, w przypadku pozycyjnych źródeł światła promienie świetlne rozchodzą się ze sceny. Plamy odbłysków w programie SHINYJET byłyby nie do uzyskania z kierunkowym źródłem światła. Zamiast lśniących miejsc, cała powierzchnia stawałaby się biała w momencie skierowania jej wprost do światła (czyli gdyby światło padało na nią pod kątem 90°).
Tworzenie światła punktowego
Tworzenie światła punktowego nie różni się od tworzenia źródła światła kierunkowego. Kod z listingu 9.8 przedstawia funkcję SetupRC() z przykładowego programu SPOT. Ten program tworzy w środku okna niebieską kulę. Tworzone jest światło punktowe, które można obracać dookoła kuli za pomocą klawiszy kursora. W miarę jak światło punktowe porusza się dookoła kuli, na jej powierzchni pojawia się plama odbłysku.
Listing 9.8. Przygotowanie oświetlenia \v programie SPOT___________________________
// Wartości i współrzędne świateł GLfloat lightPos[] = { O.Of, O.Of, 75.Of, l.Of }; GLfloat specular[] = { l.Of, l.Of, l.Of, l.Of}; GLfloat specref[] = { l.Of, l.Of, l.Of, l.Of}; GLfloat ambientLight[] = { 0.5f, 0.5f, 0.5f, l.Of }; GLfloat spotDirN = { O.Of, O.Of, -l.Of };
// Ta funkcja odpowiada za inicjowanie kontekstu renderowania // Oprócz tego tworzy i inicjuje światła void SetupRC()
glEnable(GL_DEPTH_TEST); // Usuwanie niewidocznych powierzchni
glFrontFace(GL_CCW); // Wielokąty o kierunku przeciwnym do
// ruchu wskazówek są widziane
// z przodu
glEnable(GL_CULL_FACE); // Nie pokazuj wnętrza obiektów
// Włączenie oświetlenia glEnable(GLJLIGHTING);
// Przygotowanie i włączenie światła O
// Zastosowanie lekkiego światła otaczającego, aby obiekty były
// widoczne
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientLight);
// Światło składa się ze składowych rozpraszającej i odbłysków glLightfv(GL_LIGHTO,GL_DIFFUSE,ambientLight); glLightfv(GL_LIGHTO,GL_SPECULAR,specular); glLightfv(GL_LIGHTO,GL_POSITION,lightPos);
300 ____________________________________ Część II » Używanie OpenGL
// Włączenie efektu światła punktowego // Kąt wyłączenia wynosi 60 stopni glLightf (GL_LIGHTO, GL_SPOT_CUTOFF, 60. Of) ;
// Jasny świecący punkt
glLightf (GL_LIGHTO, GL_SPOT_EXPONENT, 100 . Of ) ;
// Włączenie tego światła glEnable (GL_LIGHTO) ;
// Włączenie śledzenia koloru glEnable (GL_COLOR_MATERIAL) ;
// Kolor otaczający i rozpraszający materiału śledzą funkcję
// glColor
glColorMaterial (GL_FRONT, GL_AMBIENT_AND_DIFFUSE) ;
// Od tego momentu wszystkie materiały mają pełną właściwość // odbłysków z ustawionym wysokim połyskiem glMaterialfv(GL_FRONT, GL_SPECULAR, specref ) ; glMateriali(GL_FRONT, GL_SHININESS, 128) ;
// Czarne tło
g!ClearColor(O.Of, O.Of, O.Of, l.Of ) ;
Za zmianę światła pozycyjnego w punktowe odpowiadaj ą poniższe linie:
// Włączenie efektu światła punktowego // Kąt rozwarcia wynosi 60 stopni glLightf (GL_LIGHTO, GL_SPOT_CUTOFF, 60 . Of ) ;
// Jasny świecący punkt
glLightf (GL_LlGHTO,GL_SPOT_EXPONENT,100.0f);
Wartość GL_SPOT_CUTOFF określa kąt rozwarcia stożka światła, emitowanego ze źródła światła punktowego. W przypadku normalnego światła pozycyjnego, ten kąt wynosi 180°, przez co światło nie jest ograniczone żadnym stożkiem. Światła punktowe emitują stożek światła, a obiekty poza tym stożkiem nie są oświetlane. Rysunek 9.22 pokazuje, jak kąt rozwarcia ma się do szerokości stożka.
Rysunek 9.22.
Kąt rozwarcia stożka światła punktowego
Światło punktowe
Rozdział 9. » Oświetlenie i źródła światła____________________________301
Rysowanie światła punktowego
Gdy umieścisz w scenie światło punktowe, musi ono skądś biec. To, że umieścisz w jakimś miejscu źródło światła punktowego, nie oznacza jeszcze, że zobaczysz tam jasny punkt. W przypadku naszego programu SPOT, w miejscu położenia źródła światła umieściliśmy czerwony stożek, wskazujący, skąd pochodzi światło. Wewnątrz podstawy stożka umieściliśmy żółtą kulę, imitującą bańkę żarówki. Kompletny kod służący do rysowania sceny został przedstawiony na listingu 9.9.
Zwróć szczególną uwagę na instrukcję
glPushAttrib(GL_LIGHTING_BIT);
Zaraz po niej wyłączamy oświetlenie i rysujemy jasną żółtą kulę. Następnie wywołujemy instrukcję
glPopAttribO ;
Pierwsza instrukcja zachowuje stan wszystkich zmiennych stanu oświetlenia. Możemy potem wyłączyć światło na czas rysowania żółtej żarówki, a następnie z powrotem włączyć system oświetlenia. Dokładny opis parametrów funkcji glPushAttrib i glPopAttrib znajdziesz w sekcji podręcznika w rozdziale 14. Przykładowy obraz z programu SPOT przedstawia rysunek 9.23.
Rysunek 9.23.
Przykład działania programu SPOT, demonstrującego światło punktowe
Listing 9.9. Funkcja renderująca programu SPOT, pokazująca sposób obracania światła punktowego
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
// Wyczyszczenie okna bieżącym kolorem tła
glClear(GL_COLOR_BUFFER_BIT l GL_DEPTH_BUFFER_BIT);
// Ustawienie koloru materiału i narysowanie kuli w środku glRGB(0, O, 255); auxSolidSphere(30.Of);
302____________________________________Część II » Używanie OpenGL
// Teraz umieszczamy światło
// Zachowanie przekształceń współrzędnych
glPushMatrix();
// Obrót układu współrzędnych
glRotatef(yRot, O.Of, l.Of, O.Of); glRotatef(xRot, l.Of, O.Of, O.Of);
// Określenie nowego położenia i kierunku w obróconych // współrzędnych
glLightfv(GL_LIGHTO,GL_POSITION,lightPos); glLightfv(GL_LIGHTO,GL_SPOT_DIRECTION,spotDir);
// Rysowanie czerwonego stożka imitującego źródło światła glRGB(255,0,0);
// Przemieszczenie początku w celu przesunięcia stożka // w miejsce źródła światła
glTranslatef(lightPos[0],lightPos[1],lightPos[2]); auxSolidCone(4.0f,6.0f);
// Narysowanie mniejszej, przesuniętej kuli imitującej żarówkę // Zachowanie zmiennych stanu oświetlenia glPushAttrib(GL_LIGHTING_BIT);
// Wyłączenie oświetlenia i narysowanie jasnej żółtej kuli glDisable(GL_LIGHTING); glRGB(255,255,0); auxSolidSphere(3.0f);
// Przywrócenie zmiennych stanu oświetlenia glPopAttribO ;
// Przywrócenie stanu macierzy glPopMatrix();
// Zrzucenie poleceń graficznych glFlushO ;
Cienie
Rozdział poświęcony oświetleniu aż prosi się o wspomnienie o cieniach. Uzupełnienie sceny o cień może znacznie poprawić jej realizm i efekt wizualny. Na rysunkach 9.24a i 9.24b widzimy dwa widoki oświetlonej kostki, z cieniem i bez cienia (ten przykładowy program pochodzi z rozdziału 2). Kostka z rysunku 9.24b z cieniem wygląda o wiele bardziej wiarygodnie.
303
Rozdział 9. * Oświetlenie i źródła światła
Rysunek 9.24a.
Oświetlona kostka bez cienia
Rysunek 9.24b.
Oświetlona kostka
z cieniem
Czym jest cień?
Koncepcyjnie, rysowanie cienia jest całkiem proste. Cień powstaje wtedy, gdy jakiś obiekt zasłania światło padające na inny obiekt. Obszar obiektu, na który pada cień w kształcie obiektu zasłaniającego, pozostaje ciemny. Możemy tworzyć cienie programowo, spłaszczając oryginalny obiekt na płaszczyznę powierzchni, na której ten obiekt spoczywa. Następnie spłaszczony obiekt jest rysowany na czarno lub w jakimś ciemnym kolorze, być może z zachowaniem pewnej przezroczystości (spójrz na przykład cienia z rozdziału 16). To spłaszczenie ilustruje rysunek 9.25.
Rysunek 9.25.
Spłaszczenie obiektu w celu otrzymania cienia
Proces spłaszczania obiektu na inną powierzchnię jest wynikiem z zastosowania jednej z tych zaawansowanych manipulacji macierzami, opisanych w rozdziale 7. Tutaj spróbujemy tak uprościć to zagadnienie, jak tylko się da.
304____________________________________Część II » Używanie OpenGL
Kod „zgniatający"
Chcemy „spłaszczyć" macierz rzutu widoku modelu, tak aby wszystkie rysowane przy jej użyciu obiekty stały się dwuwymiarowe. Bez względu na orientację obiektu, zostanie on spłaszczony do płaszczyzny, na którą pada cień. Drugim elementem jest odległość i kierunek źródła światła. Kierunek światła determinuje kształt cienia oraz wpływa na jego rozmiar. Jeśli kiedykolwiek widziałeś swój cień wczesnym rankiem lub tuż przed zachodem słońca, wiesz, jak długi może on być w zależności od położenia słońca.
Funkcja z listingu 9.10 pobiera trzy punkty leżące na płaszczyźnie, na którą ma paść cień, pozycję źródła światła oraz, na koniec, wskaźnik do macierzy przekształcenia, która ma zostać wypełniona. Nie zagłębiając się zbytnio w algebrę liniową, ta funkcja oblicza współczynniki równania płaszczyzny, na którą ma padać cień, i łącznie z położeniem światła wykorzystuje je do stworzenia macierzy przekształcenia. Jeśli przemnożysz tę macierz przez bieżącą macierz widoku modelu, wszystkie następne obiekty zostaną spłaszczone na tę płaszczyznę.
listing 9.10. Funkcja tworząca macierz przekształcenia dla cieni_______________________
// Na podstawie równania płaszczyzny i położenia światła tworzy // macierz rzutu cienia.
// Obliczona macierz jest umieszczona w tablicy destMat[][] void MakeShadowMatrix(GLfloat points[3][3], GLfloat lightPos[4],
GLfloat destMat[4][4]) (
GLfloat planeCoeff[4];
GLfloat dot;
// Znalezienie współczynników równania płaszczyzny
// Wyszukanie trzech pierwszych współczynników tak samo
// jak przy znajdowaniu normalnej
calcNormal(points,planeCoeff) ;
// Znalezienie ostatniego współczynnika przez zastępowanie wstecz planeCoeff[3] = - (
(planeCoeff[0]*points[2][0]) + (planeCoeff[1]*points[2][1]) +
(planeCoeff[2]*points[2][2]));
// Iloczyn skalarny płaszczyzny i położenia światła dot = planeCoeff[0] * lightPos[0] +
planeCoeff[1] * lightPostl] +
planeCoeff[2] * lightPos[2] +
planeCoeff[3] * lightPos[3];
// A teraz rzutowanie
// Pierwsza kolumna
destMat[0][0] = dot - lightPos[0] * planeCoeff[0];
destMat[1][0] = O.Of - lightPos[0] * planeCoeff[1];
destMat[2][0] = O.Of - lightPos[0] * planeCoeff[2];
destMat[3][0] = O.Of - lightPos[0] * planeCoeff[3];
305
Rozdział 9. * Oświetlenie i źródła światła
* planeCoeff[0] , planeCoeff[1] ;
* planeCoeff[2] ,
* planeCoeff[3],
// Druga kolumna
destMatlO][1] - O.Of - lightPos[l] destMat[1][1] = dot - lightPostl] v destMat[2][1] = O.Of - lightPostl] destMat[3][1] = O.Of - lightPos[l]
// Trzecia kolumna destMat[0][2] = O.Of destMat[1][2] = O.Of destMat[2][2] = dot -destMat[3][2] = O.Of
// Czwarta kolumna
O.Of O.Of O.Of dot -
destMatlO][3] destMat[1][3] destMat[2][3] destMat[3][3]
- lightPos[2] * planeCoeff[0],
- lightPos[2] * planeCoeff[1]; lightPos[2] * planeCoeff[2];
- lightPos[2] * planeCoeff[3],
- lightPos[3] * planeCoeff[0]j
- lightPos[3] * planeCoeff[1];
- lightPos[3] * planeCoeff[2]; lightPos[3] * planeCoeff[3];
Przykład cienia
Aby zademonstrować użycie funkcji z listingu 9.10, umieściliśmy nasz samolot w powietrzu, wysoko nad ziemią. Umieścimy źródło światła ponad nim i nieco z lewej strony. Gdy użyjesz klawiszy kursora w celu obrócenia samolotu, cień samolotu będzie odpowiadał jego spłaszczonej sylwetce na ziemi. Wynik działania programu SHADOW został przedstawiony na rysunku 9.26.
Rysunek 9.26.
Wynik działania programu SHADOW
Kod z listingu 9.11 pokazuje sposób przygotowania macierzy cienia na potrzeby tego przykładu. Zwróć uwagę, że tworzymy macierz raz i przechowujemy ją w zmiennej globalnej.
306 ____________________________________ Część II » Używanie OpenGL
. Przygotowanie macierzy rzutu cienia _______________________________
GLfloat lightPost] = { -75. Of, 150. Of, -50. Of, O.Of } ;
// Macierz przekształcenia do tworzenia cienia GLfloat shadowMat [ 4 ][ 4 ];
// Ta funkcja odpowiada za inicjowanie kontekstu renderowania // Oprócz tego tworzy i inicjuje światła void SetupRC ( ) {
// Dowolne trzy punkty na "ziemi" (kierunek przeciwny do // ruchu wskazówek)
GLfloat points[3] [31 = U -30. Of, -149. Of, -20. Of },
{ -30. Of, -149. Of, 20. Of }, { 40. Of, -149. Of, 20. Of }};
glEnable (GL_DEPTH_TEST) ; // Usuwanie niewidocznych powierzchni glFrontFace (GL_CCW) ; // Wielokąty o kierunku przeciwnym do
// ruchu wskazówek są widziane
// z przodu glEnable (GL_CULL_FACE) ; // Nie pokazuj wnętrza obiektów
// Włączenie oświetlenia glEnable (GL_LIGHTING) ;
// Kod przygotowujący oświetlenie itd.
II Jasne niebieskie tło
glClearColor (O.Of, O.Of, l.Of, l.Of ) ;
// Obliczenia macierzy rzutowania w celu narysowania cienia na
// "ziemi"
MakeShadowMatrix(points, lightPos, shadowMat);
Listing 9.12 przedstawia kod renderujący programu SHADOW. Najpierw rysujemy samolot tak jak zwykle, a następnie odtwarzamy macierz widoku modelu i mnożymy ją przez macierz cienia. W ten sposób otrzymujemy spłaszczoną macierz rzutowania. Następnie ponownie rysujemy samolot (zmodyfikowaliśmy kod, tak aby korzystał ze znacznika informującego funkcję DrawJet że powinna rysować samolot na czarno lub w kolorze). Po kolejnym odtworzeniu macierzy widoku modelu rysujemy małą żółtą kulę znajdującą się mniej więcej w miejscu zajmowanym przez źródło światła, po czym rysujemy poniżej samolotu płaszczyznę, imitującą „ziemię". Ten prostokąt leży na tej samej płaszczyźnie, na której rysowany jest cień samolotu.
Rozdział 9. * Oświetlenie i źródła światła____________________________307
Listing 9.12. Rysowanie samolotu i jego cienia________________________________
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
// Wyczyszczenie okna bieżącym kolorem tła glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Zachowanie stanu macierzy i wykonanie obrotu glPushMatrix O;
// Rysowanie samolotu w nowym położeniu,
// Przed obróceniem samolotu umieszczenie światła we właściwym
// miejscu
g!Lightfv(GL_LIGHTO,GL_POSITION,lightPos);
glRotatef(xRot, l.Of, O.Of, O.Of);
glRotatef(yRot, O.Of, l.Of, O.Of);
DrawJet(FALSE) ;
// Przywrócenie oryginalnego stanu macierzy glPopMatrix();
// Przygotowania do rysowania cienia na ziemi
// Wyłączenie oświetlenia i zachowanie macierzy rzutowania
glPushAttrib(GL_LIGHTING_BIT) ;
glDisable(GL_LIGHTING);
glPushMatrix();
// Przemnożenie przez macierz rzutu cienia glMultMatrixf((GLfloat *)shadowMat);
// Obrócenie samolotu w nowej, spłaszczonej przestrzeni glRotatef(xRot, l.Of, O.Of, O.Of); glRotatef(yRot, O.Of, l.Of, O.Of);
// Wartość TRUE oznacza rysowanie cienia DrawJet(TRUE);
// Przywrócenie macierzy przekształcenia glPopMatrix();
// Rysowanie źródła światła (słońca)
glPushMatrix();
glTranslatef(lightPos[0],lightPos[1], lightPos[2]);
glRGB(255,255,0);
auxSolidSphere(5.0f);
glPopMatrix();
// Rysowanie "ziemi", cieniujemy ją ręcznie do ciemnej zieleni // w celu stworzenia iluzji głębokości glBegin(GL_QUADS);
glRGB(0,128,0) ;
glVertex3f(400.Of, -150.Of, -200.Of);
glVertex3f(-400.Of, -150.Of, -200.Of);
glRGB(0,255,0) ;
glVertex3f(-400.Of, -150.Of, 200.Of);
glVertex3f(400.Of, -150.Of, 200.Of); glEnd();
308____________________________________Część II » Używanie OpenGL
// Przywrócenie zmiennych stanu oświetlenia glPopAttribO ;
// Zrzucenie poleceń graficznych glFlushO ;
Oświetlenie i tryb indeksu koloru
W rozdziale 8 poznałeś tryb indeksu koloru, w którym kolor określany jest jako indeks palety, a nie jako barwy składowe światła. Powoduje to oczywiste konsekwencje jeśli chodzi o oświetlenie. Większość funkcji oświetlenia oczekuje, że światła i właściwości materiału zostaną określone jako wartości RGBA.
W przypadku trybu indeksu koloru OpenGL korzysta z pewnych założeń, jednak w tym trybie światła mogą zawierać jedynie składową rozpraszającą i odbłysków. Materiały mogą posiadać właściwość lśnienia, otaczającą, rozpraszającą i odbłysków choć w pewnych przypadkach to wystarcza, ale często uzyskany efekt nie jest wart wysiłku.
Aby można było zastosować oświetlenie, paleta musi zawierać trzy pasma kolorów dla kolorów otaczających, rozpraszających i odbłysków. Aby osiągnąć satysfakcjonujące rezultaty, te pasma zwykle obejmują odcienie od czarnego, poprzez odcienie pojedynczego koloru, aż do białego. Istnieje możliwość zdefiniowania ich tak, aby stworzyć gładko cieniowany obiekt w jednym kolorze, ale ma to małe zastosowanie w praktycznych aplikacjach.
Ogólnie, większość publikacji dotyczących OpenGL zaleca unikanie trybu indeksu koloru w połączeniu z efektami oświetlenia. Jednak jeśli musisz go użyć, płytka CD-ROM zawiera dodatkowy przykład o nazwie ILIGHT, ilustrujący użycie trybu indeksu koloru w celu oświetlenia sceny zawierającej kilka obiektów. Jednak wszystkie te obiekty mają ten sam kolor!
Podsumowanie
W tym rozdziale zostałeś wprowadzony w pewne bardziej zaawansowane i „magiczne" dziedziny OpenGL. Dowiedziałeś się, jak określać jedno lub kilka źródeł światła oraz jak definiować ich charakterystykę w postaci składowych otaczającej, rozpraszającej i odbłysków. Wyjaśniliśmy także, jak odpowiednie właściwości materiału współgrają ze składowymi źródeł światła, a także zademonstrowaliśmy pewne efekty specjalne, takie jak tworzenie plam odbłysków czy wygładzanie ostrych krawędzi.
Oprócz tego zostało omówione położenie źródła światła, a także tworzenie i manipulowanie światłami punktowymi. Zaprezentowana w treści wysokopoziomowa funkcja do operowania macierzami bardzo ułatwi tworzenie cieni. Na koniec wyjaśniliśmy, dlacze-
309
Rozdział 9. » Oświetlenie i źródła światła
go powinieneś unikać trybu indeksu koloru przy tworzeniu efektów oświetlenia. Programy demonstracyjne w tym rozdziale są całkiem proste, ale więcej przykładów znajdziesz na płytce CD-ROM, w folderze tego rozdziału. Programy na płytce demonstrują wszystkie opisywane efekty, łącznie ze scenami zawierającymi więcej niż jedno źródło światła.
Podręcznik
glColorMaterial
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Sprawia, że kolory materiału śledzą bieżący kolor ustawiany funkcją glColor.
void glColorMaterial(Glenum face, GLenum modę);
Ta funkcja umożliwia ustawianie właściwości materiału bez konieczności bezpośredniego wywoływania funkcji glMaterial. Jeśli użyjesz tej funkcji, pewne właściwości materiału mogą być automatycznie ustawianie w momencie wywołania funkcji glColor. Domyślnie śledzenie koloru jest wyłączone; aby je włączyć, musisz wywołać funkcję glEnable(GL_COLOR_MATERIAL). Aby ponownie wyłączyć śledzenie koloru, wywołaj funkcję glDisable(GL_COLOR_MATERIAL).
Parametry face
modę
GLenum: Określa, która strona wielokąta powinna śledzić bieżący kolor. Dozwolone parametry to GL_FRONT (przednia), GL_BACK (tylna) lub GL_BACK_AND_FRONT (obie strony).
GLenum: Określa, która właściwość materiału ma zależeć od bieżącego koloru. Dozwolone parametry to GL_EMISSION, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR oraz GL_AMBIENT_AND_DIFFUSE.
Zwracana wartość Brak
Przykład
Patrz także
Poniższy kod z przykładowego programu AMBIENT włącza śledzenie koloru, a następnie ustawia właściwość otaczającą i rozpraszającą materiału tak, aby śledziły kolor ustawiany funkcją glColor
// Włączenie śledzenia koloru przez materiały glEnable(GL_COLOR_MATERIAL);
// Kolory rozpraszania i otaczania dla
// przednich stron wielokątów śledzą kolor
// ustawiany w glColor
glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE);
glColor, glMaterial, glLight, glLightModel
310
Część II » Używanie OpenGL
glCulIFace
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Określa, która strona wielokąta, przednia czy tylna, ma być usuwana z rysunku.
void glCullFace(GLenum modę);
Ta funkcja wyłącza obliczenia dotyczące oświetlenia, cieniowania i koloru przedniej lub tylnej strony wielokąta. Jeśli, na przykład, obiekt jest zamknięty i tylne ściany wielokątów nigdy nie będą widoczne, bez względu na przemieszczenie i obrót, wyłączenie rysowania tylnych ścian wyeliminuje niepotrzebne obliczenia w scenie. Usuwanie niewidocznych ścian jest włączane i wyłączane przez wywołanie funkcji glEnable i glDisable z parametrem GL_CULL_FACE. Przednia i tylna strona wielokąta jest definiowana funkcją glFrontFace oraz poprzez kolejność definiowania wierzchołków wielokąta (przeciwnie do ruchu wskazówek zegara lub zgodnie z nim).
Parametry modę
Zwracana wartość Przykład
Patrz także
GLenum: Określa, która strona wielokąta powinna być usunięta. Dozwolone parametry to GLJFRONT (przednia) lub GLJ3ACK (tylna).
Brak
Poniższy kod z przykładowego programu AMBIENT pokazuje sposób wyłączenia obliczeń dotyczący wnętrza samolotu. Oprócz tego konieczne jest wskazanie, która strona wielokątów jest na zewnątrz, przez określenie kierunku ułożenia ich wierzchołków.
// Wielokąty o kierunku przeciwnym do // ruchu wskazówek są widziane z przodu glFrontFace(GL_CCW);
// Nie pokazuj wnętrza obiektów glEnable(GL_CULL_FACE);
glFrontFace, glLightModel
glFrontFace
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Określa, która strona wielokąta jest traktowana jako przednia, a która jako tylna.
void glFrontFace(GLenum modę);
Gdy scena składa się z obiektów zamkniętych (nie można zobaczyć ich wnętrza), nie ma potrzeby obliczania koloru i światła dla wewnętrznych stron obiektu. Do wyłączenia tych obliczeń służy funkcja glCulIFace. Z kolei funkcja glFrontFace określa, która strona wielokąta ma być
311
Rozdział 9. * Oświetlenie i źródła światła
Parametry modę
traktowana jako przednia. Jeśli wierzchołki wielokąta są podawane
w takiej kolejności, że układają się w kierunku ruchu wskazówek zegara,
mówimy, że wielokąt ma kierunek zgodny z ruchem wskazówek zegara.
Jeśli wierzchołki układają się w kierunku przeciwnym, mówimy,
że wielokąt ma kierunek przeciwny do ruchu wskazówek zegara.
Ta funkcja umożliwia określenie, że przednią częścią wielokąta może być
strona zgodna z ruchem wskazówek zegara lub strona odwrotna.
GLenum: Określa, orientację przedniej strony wielokąta. Dozwolone parametry to GL_CW (zgodnie z ruchem wskazówek) lub GL_CCW (przeciwnie do ruchu wskazówek).
Zwracana wartość Brak
Przykład
Patrz także
Poniższy kod z przykładowego programu AMBIENT pokazuje sposób wyłączenia obliczeń dotyczących wnętrza samolotu. Oprócz tego konieczne jest wskazanie, która strona wielokątów jest na zewnątrz, przez określenie kierunku ułożenia ich wierzchołków.
// Wielokąty o kierunku przeciwnym do // ruchu wskazówek są widziane z przodu glFrontFace(GL_CCW);
// Nie pokazuj wnętrza obiektów glEnable(GL_CULL_FRCE);
glCullFace, glLightModel
gIGetMaterial
Przeznaczenie Plik nagłówkowy Składnia
Opis
Zwraca bieżące właściwości materiału.
<gl.h>
void glGetMaterialfv(GLenum face, GLenum pname, GLfloat *params);
void glGetMaterialiv(GLenum face, GLenum pname, GLint *params);
Ta funkcja jest używana do odczytywania bieżących właściwości materiału przednich lub tylnych ścian wielokątów. Zwracane wartości są umieszczane pod adresem wskazywanym przez params. W większości przypadków będzie to tablica czterech wartości zawierająca składowe RGBA odczytywanej właściwości.
Parametry face
GLenum: Określa stronę wielokąta, której właściwości materiału będą odczytywane. Dozwolone parametry to GL_FRONT (strona przednia) lub GL_BACK (strona tylna).
312
Część II » Używanie OpenGL
pname
face
GLenum: Określa właściwość materiału, którą chcemy odczytać. Dozwolone wartości to GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_EMISSION, GL_SHININESS oraz GL_COLOR_INDEXES.
GLint* lub GLfloat*: Tablica liczb zmiennopozycyjnych lub całkowitych reprezentująca zwracane wartości. Dla parametrów GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR i GL_EMISSION jest to czteroelementowa tablica zawierająca składowe RGBA danego parametru. Dla GL_SHININESS zwracana jest pojedyncza wartość określająca wartość połysku materiału.
GL_COLOR_INDEXES zwraca tablicę trzech elementów zawierających składowe otaczającą, rozpraszającą i odbłysków w formie indeksów kolorów. GL_COLOR_INDEXES jest używane jedynie w oświetleniu wykorzystującym tryb indeksu koloru.
Zwracana wartość Brak
Przykład
Poniższy kod ilustruje sposób odczytania i przechowania wszystkich bieżących właściwości materiału.
// Miejsce na wszystkie właściwości materiału GLfloat ambientMat[4], diffuseMat[4], specularMat[4],
emissionMat[4] ; GLfloat shine;
// Odczyt wszystkich właściwości materiału
Patrz także
glGetMaterialfv(GL_FRONT, glGetMaterialfv(GL_FRONT, glGetMaterialfv(GL_FRONT, glGetMaterialfv(GL_FRONT, glGetMaterialfv(GL_FRONT,
glMaterial
GL_AMBIENT, ambientMat); GL_DIFFUSE, diffuseMat); GL_SPECULAR, specularMat), GL_EMISSION, emissionMat), GL SHININESS, Sshine);
gIGetLight
Przeznaczenie Plik nagłówkowy Składnia
Opis
Zwraca informacje o bieżących parametrach światła.
<gl.h>
void glGetLightfv(GLenum light, GLenum pname, GLfloat *params);
void glGetLightiv(GLenum light, GLenum pname, GLint *params);
Ta funkcja jest używana do odczytywania bieżących właściwości jednego z ośmiu źródeł światła. Zwracane wartości są umieszczane pod adresem wskazywanym przez params. W większości przypadków będzie to tablica czterech wartości zawierająca składowe RGBA odczytywanej właściwości.
313
Rozdział 9. « Oświetlenie i źródła światła
Parametry ttght
pname
params
Zwracana wartość Przykład
GLenum: Określa źródło światła, którego parametry chcemy odczytać. Ta wartość należy do zakresu od O do GL_MAX_LIGHT (8 dla Windows NT i Windows 95). Stałe dla źródeł światła to GL_LIGHTO, GL_LIGHT1 ... GL_LIGHT7.
GLenum: Określa właściwość światła, którą chcemy odczytać. Dozwolone wartości to GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_POSITION, GL_SPOT_DIRECTION, GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION oraz GL_QUADRATIC_ATTENUATION.
GLint* lub GLfloat*: Tablica liczb zmiennopozycyjnych lub całkowitych reprezentująca zwracane wartości. Wartości zwracane są w formie tablicy czterech, trzech lub jednego elementu. Wartości zwracane dla każdej z właściwości zawiera tabela 9.2.
Brak
Poniższy kod ilustruje sposób odczytania i przechowania bieżących właściwości źródła światła.
// Miejsce na właściwości źródła światła
GLfloat ambientComp[4], diffuseComp[4], specularComp[4];
Patrz także
// Odczyt właściwości światła
glGetLightfv(GL_LIGHTO, GL_AMBIENT, ambientComp); glGetLightfv(GL_LIGHTO, GL_DIFFOSE, diffuseComp); glGetLightfv(GL_LIGHTO, GL_SPECOLAR, specularComp),
glLight
Tabela 9.2.
Dostępne parametry funkcji glGetLight
Właściwość
Zwracane wartości
GL_AMBIENT GLJ3IFFUSE GL_SPECULAR GL POSITION
GL SPOT DIRECTION
Cztery składowe RGBA. Cztery składowe RGBA. Cztery składowe RGBA.
Cztery elementy określające położenie źródła światła. Pierwsze trzy elementy określają położenie światła. Jeśli czwarty element ma wartość 1,0, światło rzeczywiście znajduje się we wskazanym miejscu. W przeciwnym razie źródło światła jest kierunkowe i promienie są równoległe.
Trzy elementy określające kierunek źródła światła punktowego. Wektor nie jest znormalizowany i jest podany we współrzędnych obserwatora.
314
Część II » Używanie OpenGL
Tabela 9.2.
Dostępne parametry funkcji glGetLight - ciąg dalszy
Zwracane wartości
Właściwość
GL_S POT_EXPONENT GL_SPOT_CUTOFF
GL_CONSTANT_ATTENUATION GL_LINEAR_ATTENUATION GL QUADRATIC ATTENUATION
Pojedyncza wartość określająca stopień skupienia światła.
Pojedyncza wartość określająca kąt rozwarcia stożka światła punktowego.
Pojedyncza wartość określająca stałe zanikanie światła. Pojedyncza wartość określająca liniowe zanikanie światła. Pojedyncza wartość określająca nieliniowe zanikanie światła.
gILight
Przeznaczenie Plik nagłówkowy Składnia
Opis
Ustawia parametry jednego z ośmiu dostępnych źródeł światła. <gl.h>
void glLightf(GLenum light, GLenum pname, GLfloat param); void glLighti(GLenum light, GLenum pname, GLint param); void glLightfv(GLenum light, GLenum pname, const GLfloat *params); void glLightiv(GLenum light, GLenum pname, const GLint *params);
Ta funkcja jest używana do ustawiania parametrów jednego z ośmiu źródeł światła. Pierwsze dwie odmiany funkcji wymagaj ą jedynie pojedynczego parametru służącego do ustawienia jednej z następujących właściwości:
GL_SPOT_EXPONENT,GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION oraz GL_QUADRATIC_ATTENUATION.
Dwie pozostałe odmiany wymagaj ą podania wskaźnika do tablicy kilku właściwości. W ten sposób określane są właściwości:
GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_POSITION, GL_SPOT_DIRECTION. Te odmiany funkcji mogą być użyte także do ustawienia parametrów składających się z pojedynczej wartości, przez umieszczenie w tablicy *params pojedynczego elementu.
Parametry light
pname
GLenum: Określa źródło światła, którego parametry chcemy zmodyfikować. Ta wartość należy do zakresu od O do GL_MAX_LIGHT (8 dla Windows NT i Windows 95). Stałe dla źródeł światła to GLJJGHTO, GL_LIGHT1 ... GL_LIGHT7.
GLenum: Określa właściwość światła, którą chcemy zmodyfikować. Dozwolone wartości i znaczenie parametrów zawiera tabela 9.2.
315
Rozdział 9. * Oświetlenie i źródła światła
param
params
GLint lub GLfloat: Określa wartość parametrów reprezentowanych przez pojedynczą wartość. Należą do nich parametry GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION oraz
GL_QUADRATIC_ATTENUATION. Znaczenie parametrów zawiera tabela 9.2.
GLint* lub GLfloat*: Tablica liczb zmiennopozycyjnych lub całkowitych zawierająca wartości ustawianych parametrów. Znaczenie parametrów podaje tabela 9.2.
Zwracana wartość Brak
Przykład
Poniższy kod z programu LITJET ilustruje sposób ustawienia źródła światła w lewym górnym rogu poza obserwatorem. Źródło światła podaje jedynie składowe otaczającą i rozpraszającą.
// Wartości i współrzędne źródła światła GLfloat ambientLight[] = { 0.3f, 0.3f, 0.3f, l.Of }; GLfloat diffuseLightU = { 0.7f, 0.7f, 0.11, l.Of }; GLfloat UghtPosN = {-50.f,50.Of, 100.Of, l.Of };
Patrz także
// Włączenie oświetlenia glEnable(GL_LIGHTING);
// Przygotowanie i włączenie światła O glLightfv(GL_LIGHTO,GL_AMBIENT,ambientLight) , glLightfv(GL_LIGHTO,GL_DIFFUSE, diffuseLight) , glLightfv(GL_LIGHTO,GL_POSITION,lightPos); glEnable(GL_LIGHTO);
glGetLight
gilightModel
Przeznaczenie Plik nagłówkowy Składnia
Opis
Ustawia parametry modelu oświetlenia używanego przez OpenGL. <gl.h>
void glLightModelf(GLenum pname, GLfloat param); void gILightModeli(GLenum pname, GLint param); void gILightModelfv(GLenum pname, const GLfloat *params); void glLightModeliv(GLenum pname, const GLint *params);
Ta funkcja jest używana do ustawiania parametrów modelu oświetlenia używanego przez OpenGL. Może być ustawiony dowolny z trzech parametrów modelu oświetlenia. GL_LIGHT_MODEL_AMBIENT jest używany do ustawienia domyślnego światła otaczającego dla sceny. Domyślnie to światło ma wartość RGBA (0,2, 0,2, 0,2, 1,0). Do ustawienia tego modelu mogą zostać użyte jedynie dwie ostatnie odmiany funkcji, gdyż umożliwiaj ą przekazanie tablicy zawierającej wartości RGBA. Parametr GL_LIGHT_MODEL_TWO_SIDE jest używany do
316
Część II » Używanie OpenGL
określenia, czy będą oświetlane obie strony wielokątów. Domyślnie oświetlana jest tylko przednia (wyznaczana przez kierunek wierzchołków) strona wielokątów, w wyniku zastosowania właściwości materiału przedniej strony, określonego funkcją glMaterial(). Na koniec, określenie parametru GL_LIGHT_MODEL_LOCAL_VIEWER modyfikuje obliczenia kąta odbicia dla odbłysków tak, że widok znajduje się w kierunku ujemnej osi z we współrzędnych obserwatora (patrz rozdział 6).
Parametry pname
param
params
GLenum: Określa parametr modelu oświetlenia. Dozwolone parametry to GL_LIGHT_MODEL_AMBIENT, GL_LIGHT_MODEL_LOCAL_VIEWER oraz GL_LIGHT_MODEL_TWO_SIDE.
Glint lub GLfloat: Dla GL_L1GHT_MODEL_LOCAL_VIEWER wartość 0,0 wskazuje, że kąty światła odbłysków przybierają kierunek widoku zgodny z ujemnym kierunkiem osi z. Każda inna wartość oznacza, że odbłyski są widziane z początku układu współrzędnych. Dla GLJLIGHT_MODEL_TWO_SIDE, wartość 0,0 wskazuje, że w obliczeniach oświetlenia będzie brana pod uwagę tylko przednia strona wielokątów. Każda inna wartość określa, że przy obliczeniach będą brane pod uwagę obie strony wielokątów. Ten parametr nie daje efektu w przypadku punktów, odcinków i bitmap.
GLint* lub GLfloat*: W przypadku GL_LIGHT_MODEL_TWO_SIDE i GL_LIGHT_MODEL_LOCAL_VIEWER jest to wskaźnik do tablicy liczb całkowitych lub zmiennopozycyjnych, w których jako wartość parametru używany jest tylko pierwszy element. W przypadku parametru GL_LIGHT_MODEL_AMBIENT wskazywana tablica zawiera cztery elementy, będące składowymi wartości RGBA.
Zwracana wartość Brak
Przykład
Poniższy kod z programu AMBIENT ilustruje sposób ustawienia globalnego źródła światła otaczającego na jasny, biały kolor.
// Jasne białe światło GLfloat ambientLight[]
{ l.Of, l.Of, l.Of, l.Of
// Usuwanie niewidocznych powierzchni
glEnable(GL_DEPTH_TEST);
// Wielokąty o kierunku przeciwnym do ruchu wskazówek są
// widziane z przodu
glFrontFace(GL_CCW);
// Nie pokazuj wnętrza obiektów
glEnable(GL_CULL_FACE);
// Oświetlanie glEnable(GL_LIGHTING);
// Włączenie oświetlenia
317
Rozdział 9. 4 Oświetlenie i źródła światła
Patrz także
// Ustawienie modelu oświetlenia tak, // aby korzystał ze światła otoczenia // określonego w ambientLight[] glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambientLight) ,
glLight, glMateriał
gIMaterial
Przeznaczenie Plik nagłówkowy Składnia
Opis
Ustawia parametry materiału używane w modelu oświetlenia. <gl.h>
void glMaterialf(GLenum face, GLenum pname, GLfloat param); void glMateriali(GLenum face, GLenum pname, GLint param);
void glMaterialfv(GLenum face, GLenum pname, const GLfloat *params);
void glMaterialiv(GLenum face, GLenum pname, const GLint *params);
Ta funkcja jest używana do ustawienia właściwości materiału wielokątów. Parametry GL_AMBIENT, GL_DIFFUSE oraz GL_SPECULAR wpływają na sposób odbijania odpowiednich składowych światła. GL_EMISSION jest używanew celu nadania materiałom takiego wyglądu, jakby emitowały własne światło. GL_SHININESS może zmieniać się w zakresie od O do 128, gdzie większe wartości powodująjaśniejsze i bardziej skupione plamy odbłysków na powierzchni materiału. Na koniec, GL_COLOR_INDEXES jest używany w celu określenia właściwości materiału w trybie indeksu koloru.
Parametry face
pname
param params
GLenum: Określa stronę wielokąta, do której będą się odnosić ustawiane właściwości materiału. Dozwolone parametry to GL_FRONT (strona przednia), GL_BACK (strona tylna) lub GL_FRONT_AND_BACK (przednia i tylna).
GLenum: Dla pierwszych dwóch odmian określa wartość parametru reprezentowanego przez pojedynczą wartość. Obecnie jedynym takim parametrem jest GL_SHININESS. Pozostałe dwie odmiany, wymagające przekazania tablicy wartości, mogą służyć do ustawienia następujących właściwości:
GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_EMISSION, GL_SHININESS, GL_AMBIENT_AND_DIFFUSE oraz GL_COLOR_INDEXES.
GLint lub GLfloat: Wartość parametru określanego przez pname (GL_SHININESS).
GLint* lub GLfloat*: Tablica liczb całkowitych lub zmiennopozycyjnych zawierająca wartości dla ustawianego parametru.
318
Część II » Używanie OpenGL
Zwracana wartość Przykład Patrz także
Brak
Spójrz na przykładowy program LITJET w tym rozdziale.
glGetMaterial, glColorMaterial, glLight, glLightModel
gINormal
Przeznaczenie
Plik nagłówkowy Składnia
Opis
Parametry nx ny nz
v
Zwracana wartość Przykład
Definiuje normalną do powierzchni dla następnego wierzchołka lub zestawu wierzchołków.
void glNormal3b(GLbyte nx, GLbyte ny, GLbyte nz);
void glNorma!3d(GLdouble nx, GLdouble ny, GLdouble nz);
void glNormal3f(GLfloat nx, GLfloat ny, GLfloat nz);
void glNormal3i(GLint nx, GLint ny, GLint nz);
void glNormal3s(GLshort nx, GLshort ny, GLshort nz);
void glNorma!3bv(const GLbyte *v);
void glNorma!3dv(const GLdouble *v);
void glNormal3fv(const GLfloat *v);
void glNorma!3iv(const GLint *v);
void gINormal3sv(const GLshort *v);
Wektor normalny jest skierowany w górę, prostopadły do powierzchni
wielokąta. Normalne są używane w obliczeniach oświetlenia
i cieniowania. Określenie wektora o długości l poprawia szybkość
renderowania. OpenGL automatycznie konwertuje normalne na normalne
jednostkowe w momencie włączenia parametru
glEnable(GLJNORMALIZE).
Określa współrzędną x wektora normalnego. Określa współrzędną y wektora normalnego. Określa współrzędną z wektora normalnego.
Wskazuje tablicę trzech wartości odpowiadających współrzędnym x, y i z wektora normalnego.
Brak
Poniższy kod z programu LITJET demonstruje ustawianie wektora normalnego przed rysowaniem wielokąta.
float normal[3]; // Wierzchołki dla tego panelu float v[3][3] = {{ 15.Of, O.Of, 30.Of}, ( O.Of, 15.Of, 30.Of},
Rozdział 9. » Oświetlenie l źródła światła____________________________319
{ O.Of, O.Of, 60.Of}};
// Obliczenie normalnej do płaszczyzny calcNormal(v,normal);
// Narysowanie trójkąta przy użyciu // tej samej normalnej do płaszczyzny // dla wszystkich wierzchołków glBegin(GLJTRIANGLES);
glNormal3fv(normal);
glVertex3fv(v[0]);
glVertex3fv(v[l]);
glVertex3fv(v[2]); glEnd();
Patrz także glTexCoord, glVertex
Rozdział 10.
Modelowanie
i kompozycja obiektów 3D
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
* Składać trójwymiarowe obiekty z wielokątów * glBegin/glEnd/glVertex
4 Optymalizować wyświetlanie obiektów za * glNewList/glEndList/glCallList
pomocą list wyświetlania
Jesteś już w pełni przygotowany, aby zacząć poważnie korzystać z OpenGL. W odróżnieniu od poprzednich rozdziałów, ten będzie dotyczył głównie zastosowania nabytej wiedzy w praktyce. Mamy zamiar wyznaczyć sobie pewne zadanie, po czym krok po kroku doprowadzić do logicznego końca: gotowego programu. Podczas tego procesu dowiesz się, jak dzielić obiekty w scenie na mniejsze części, łatwiejsze w tworzeniu i zarządzaniu. Złożymy skomplikowany obiekt z mniejszych, prostszych obiektów, które z kolei będą zbudowane wyłącznie z prymitywów OpenGL.
Oprócz tego, jakby mimochodem, dowiesz się, do czego służą list wyświetlania i jak ich używać. Jednym z najważniejszych powodów ich stosowania jest szybkość, więc jako premię spróbujemy pokazać ci sposoby testowania szybkości działania kodu.
Określenie zadania
Aby zademonstrować budowanie obiektu z prostszych elementów, użyjemy interesującego, choć niezbyt skomplikowanego przykładu, tworzącego model metalowej śruby (takiej, jakie służą na przykład do przykręcania dysków twardych). Choć nasza śrubka może nie być dostępna w żadnym sklepie z żelastwem, ma jednak pewne zalety. Po-
322_______________________________________Część II » Używanie OpenGL
winniśmy więc uczynić ją tak prostą, jak to tylko możliwe, starając się jednak niczego nie utracić z sensu naszego zadania.
Śruba będzie miała sześciokątną główkę oraz nagwintowany trzpień, podobnie jak wiele innych typowych stalowych śrubek. Ponieważ to tylko ćwiczenie, uprościmy gwint „owijając" go na powierzchni trzpienia, a nie wycinając go w nim.
Szkic modelu, jaki chcemy utworzyć, przedstawia rysunek 10.1. Zbudujemy trzy główne elementy tej śruby - główkę, trzpień oraz gwint - indywidualnie, a następnie złożymy je razem w końcowy obiekt.
Rysunek 10.1. / S^———G|6wka
Śruba, którą
postaramy się
• Trzpień
wymodelować
\v tym rozdziale ___
• Gwint
Wybór rzutowania
Zanim zaczniemy konstruowanie, najpierw potrzebujemy rzutowania, czyli płaszczyzny odniesienia dla przedstawianych obiektów. W przypadku tego przykładu najlepsze będzie rzutowanie równoległe. To typowe rzutowanie dla aplikacji typu C AD, w których obiekt jest dokładnie mierzony i modelowany. Ta śruba ma określoną wysokość, szerokość i ilość zwojów gwintu, a przy tym pozostaje stosunkowo mała. Użycie rzutowania perspektywicznego miałoby sens wtedy, gdybyśmy modelowali coś większego, na przykład krajobraz, w którym efekt perspektywy byłby bardziej widoczny.
Listing 10.1 stanowi kod tworzący bryłę widzenia. Tworzy rzutowanie równoległe oraz układ współrzędnych rozciągający się po 100 jednostek w osiach x i y. Wzdłuż osi z dodano dodatkowe 100 jednostek; umieścimy tam obserwatora.
Listing 10.1. Przygotowanie rzutowania równoległego dla przykładów w tym rozdziale_______ ____
// Zmiana bryły widzenia i widoku.
// Wywoływane w momencie zmiany wymiaru okna
void ChangeSize(GLsizei w, GLsizei h)
{
GLfloat nRange = 100.Of;
// Zabezpieczenie przed dzieleniem przez O if(h == 0) h = 1;
// Ustawienie widoku na rozmiary okna glViewport(O, O, w, h);
Rozdział 10. » Modelowanie i kompozycja obiektów 3D_____________________323
// Wyzerowanie stosu macierzy rzutowania glMatrixMode(GL_PROJECTION); glLoadldentity();
// Ustanowienie bryły obcinania
// (lewa, prawa, dolna, górna, bliższa, dalsza)
if (w <= h)
glOrtho (-nRange, nRange, -nRange*h/w, nRange*h/w, -nRange*2.0f,
nRange*2.0f) ; else
glOrtho (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange*2. Of,
nRange*2.0f);
glMatrixMode(GL_MODELVIEW) ; glLoadldentity{);
Wybór oświetlenia i właściwości materiału
Po wybraniu rzutowania, następnym krokiem jest wybranie modelu oświetlenia do naszego widoku śruby. Listing 10.2 zawiera kod służący do przygotowania kontekstu ren-derowania, łącznie z oświetleniem i właściwościami materiału. Musimy się upewnić, że światło otaczające będzie na tyle jasne, iż będziemy widzieć wszystkie cechy obiektu; dodamy także światło odbłysków, aby śruba wyglądała rzeczywiście jak przedmiot z metalu. Pojedyncze źródło światła zostanie umieszczone w lewym górnym rogu.
Listing 10.2. Przygotowanie kontekstu renderowania oraz parametrów oświetlenia_____________
// Ta funkcja odpowiada za inicjowanie kontekstu renderowania // Oprócz tego tworzy i inicjuje światła void SetupRC()
// Wartości i współrzędne źródła światła GLfloat ambientLight[] = (0.4f, 0.4f, 0.4f, l.Of }; GLfloat diffuseLightN = {0.7f, 0.7f, 0.7f, l.Of }; GLfloat specular[] = { 0.9f, 0.9f, 0.9f, l.Of}; GLfloat lightPos[] = ( -50.Of, 200.Of, 200.Of, l.Of ); GLfloat specreff] = { 0.6f, 0.6f, 0.6f, l.Of };
glEnable(GL_DEPTH_TEST); // Osuwanie niewidocznych powierzchni glEnable(GL_CULL_FACE); // Nie pokazuj wnętrza obiektów
// Włączenie oświetlenia glEnable(GL_LIGHTING);
// Przygotowanie i włączenie światła O glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambientLight); glLightfv(GL_LIGHTO,GL_AMBIENT,ambientLight); g!Lightfv(GL_LIGHTO,GL_DIFFUSE,diffuseLight); glLightfv(GL_LIGHTO,GL_SPECULAR,specular); <
324____________________________________Część II » Używanie OpenGL
// Umieszczenie i włączenie światła glLightfv(GL_LIGHTO,GL_POSITION,lightPos); glEnable(GL_LIGHTO);
// Włączenie śledzenia koloru materiału glEnable(GL_COLOR_MATERIAL);
// Ustawienie właściwości materiału glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
// Od tego momentu wszystkie materiały mają pełną właściwość // odbłysków z ustawionym wysokim połyskiem glMaterialfv(GL_FRONT, GL_SPECULAR,specref); glMateriali(GL_FRONT,GL_SHININESS, 64) ;
// Czarne tło
glClearColor(O.Of, O.Of, O.Of, l.Of );
Wyświetlanie rezultatu
Gdy określiliśmy widok, oświetlenie oraz parametry materiału, jedyne co pozostało, to wyrenderowanie sceny. Listing 10.3 przedstawia schemat kodu używanego do wyświetlania śruby i jej elementów. Występujące tu linie SomeFunc() to po prostu miejsca na wywołania funkcji indywidualnie renderujących główkę, trzpień i gwint śruby. Zachowujemy stan macierzy, wykonujemy obroty (za pomocą klawiszy kursora, podobnie jak w innych przykładach w tej książce) oraz wywołujemy funkcję renderującą dany element obiektu.
Listing 10.3. Renderowanie obiektu z uwzględnieniem obrotów________________________
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
// Wyczyszczenie okna bieżącym kolorem tła glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Zachowanie stanu macierzy glMatrixMode(GL_MODELVIEW); glPushMatrix();
// obrót wokół osi x i y glRotatef(xRot, l.Of, O.Of, O.Of); glRotatef(yRot, O.Of, l.Of, O.Of);
// Kod rysujący obiekt
SomeFuncO; // miejsce wywołania funkcji glPopMatrix();
// Zrzucenie poleceń graficznych glFlushO;
Rozdział 10. » Modelowanie i kompozycja obiektów 3D_____________________325
Konstruowanie modelu po kawałku
Każde zadanie programistyczne można rozbić na mniejsze, łatwiejsze do opanowania części. Te mniejsze części łatwiej jest opanować i zakodować, wprowadza to także pewne możliwości ponownego wykorzystania kodu. Trójwymiarowe modelowanie również nie jest wyjątkiem; stworzymy złożony obiekt z kilku mniejszych i mniej skomplikowanych obiektów.
Zdecydowaliśmy się podzielić śrubę na trzy części: główkę, trzpień i gwint. Takie podejście zdecydowanie ułatwia nam graficzne zaprojektowanie elementów, ale także daje nam trzy obiekty, które możemy wykorzystać w przyszłości. W bardziej skomplikowanych aplikacjach do modelowania, łatwość ponownego wykorzystania ma decydujące znaczenie. Na przykład w aplikacji typu C AD będziesz musiał modelować wiele różnych rodzajów śrub - o różnych długościach, grubościach i gęstości gwintu. Zamiast funkcji RenderHead(), renderującej główkę w naszym przykładzie, mógłbyś napisać funkcję pobierającą parametry określające ilość kątów, grubość i średnicę główki śruby.
Kolejną rzeczą jest fakt, że każdy element śruby modelujemy w układzie współrzędnych najwygodniejszym do opisania obiektu. Najczęściej obiekty są modelowane w początku układu współrzędnych, a dopiero później przemieszczane w odpowiednie miejsce. Później, gdy będziemy komponować ostateczny obiekt, będziemy przenosić i obracać poszczególne elementy, a nawet skalować je w razie potrzeby.
Główka
Główka naszej śruby ma kształt prostopadłościanu o sześciu ścianach oraz płaskich podstawach. Możemy skonstruować ten obiekt z dwóch sześciokątów reprezentujących dół i górę główki oraz serii sześciu czworokątów reprezentujących boczne ścianki. Moglibyśmy użyć prymitywów GL_QUAD i GL_POLYGON, aby rysować przy jak najmniejszej liczbie wierzchołków; jednak, jak już wcześniej wspomnieliśmy, jeśli to tylko możliwe, powinieneś używać trójkątów. W przypadku każdej karty akceleratora OpenGL (a także pewnych programowych implementacji) zwykle krócej trwa narysowanie dwóch trójkątów niż jednego czworokąta.
Rysunek 10.2 pokazuje, w jaki sposób główka naszej śruby zostanie skonstruowana z trójkątów. Użyjemy wachlarza sześciu trójkątów dla górnej i dolnej powierzchni, zaś każda z bocznych ścianek będzie składać się z dwóch trójkątów.
Rysunek 10.2.
Trójkąty składające się na główkę śruby
Tak więc na całą główkę śruby składają się 24 trójkąty: po 6 na górę i dół plus 12 na ścianki boczne. Funkcję renderującą główkę śruby zawiera listing 10.4. Z kolei rysunek 10.3 przedstawia wynik działania programu HEAD z płytki CD-ROM. Zwróć uwagę,
326
Część II » Używanie OpenGL
że kod nie zawiera żadnych funkcji, których dotąd nie omawialiśmy, lecz jest bardziej złożony niż jakikolwiek wcześniejszy przykład.
Rysunek 10.3.
Działanie programu HEAD
Listing 10.4. Renderowanie główki śruby
// Tworzy główkę śruby void RenderHead(void) {
float x,y,angle;
float height = 25.Of;
float diameter = 30.Of;
float normal[3],corners[4][3],
float step = (3.1415f/3.0f);
// Wyliczone pozycje
// Grubość główki
// Średnica główki
// Miejsce na wierzchołki
// i normalne
// step = 1/6 okręgu = sześciokąt
// Ustawienie koloru materiału dla główki śruby glColorSf(O.Of, O.Of, 0.7f);
// Wielokąty zgodne z ruchem wskazówek są widziane z przodu, // co ustalamy dla wachlarzy glFrontFace(GL_CW);
// Nowy wachlarz trójkątów na górną powierzchnię glBegin(GL_TRIANGLE_FAN);
// Wszystkie normalne góry śruby wskazują w górę osi z glNormal3f(O.Of, O.Of, l.Of);
// Środek wachlarza w początku układu współrzędnych glVertex3f(O.Of, O.Of, O.Of);
// Podział okręgu na sześć sekcji i rozpoczęcie podawania
// punktów tworzących wachlarz
for(angle = O.Of; angle < (2.0f*3.1415f); angle += step)
{
// Obliczenie pozycji x i y następnego wierzchołka
x = diameter*(float)sin(angle) ;
y = diameter*(float)cos(angle);
Rozdział 10. » Modelowanie i kompozycja obiektów 3D_____________________327
// Następny wierzchołek wachlarza
glVertex3f(x, y, O.Of);
}
// Ostatni wierzchołek zamyka wachlarz glVertex3f(O.Of, diameter, O.Of);
// Koniec rysowania wachlarza na górna część glEnd();
// Przejście do rysowania wachlarza na dolną część
// Wielokąty zgodne z ruchem wskazówek są widziane z przodu,
glFrontFace(GL_CCW);
// Nowy wachlarz trójkątów na dolną powierzchnię glBegin(GL_TRIANGLE_FAN);
// Wszystkie normalne góry śruby wskazują w dół osi z glNormalSf(O.Of, O.Of, -l.Of);
// Środek wachlarza w początku układu współrzędnych glVertex3f(O.Of, O.Of, -height);
// Podział okręgu na sześć sekcji i rozpoczęcie podawania
// punktów tworzących wachlarz
for(angle = O.Of; angle < (2.Of*3.1415f); angle += step)
(
// Obliczenie pozycji x i y następnego wierzchołka
x = diameter*(float)sin(angle);
y = diameter*(float)cos(angle);
// Następny wierzchołek wachlarza
glVertex3f(x, y, -height);
)
// Ostatni wierzchołek zamyka wachlarz glvertex3f(O.Of, diameter, -height);
// Koniec rysowania wachlarza na górną część
glEnd();
// Budowa bocznych ścian z trójkątów (po dwa). Każda ściana // będzie się składać z dwóch trójkątów tworzących czworokąt glBegin(GLJTRIANGLES);
// Rysowanie ścian
for(angle = O.Of; angle < (2.Of*3.1415f); angle += step)
{
// Obliczenie pozycji x i y następnego punktu sześciokąta
x = diameter*(float)sin(angle);
y = diameter*(float)cos(angle);
// Początek na dole główki corners[0][0] = x; corners[0][1] = y; corners[0][2] = -height;
328
Część II » Używanie OpenGL
// Przejście do góry
corners [1] [0] = x;
corners [1] [1] = y;
cornersfl] [2] = O.Of;
// Obliczenie następnego punktu sześciokąta x = diameter* (float) sin (angle+step) ; y = diameter* (float) cos (angle+step) ;
// Sprawdzenie, czy to koniec if (angle+step < 3.1415*2.0)
{
// Jeśli koniec, po prostu zamknięcie wachlarza
// w znanej współrzędnej
corners [2] [0] = x;
corners [2] [1] = y;
corners[2] [2] = O.Of;
corners [3] [0) = x;
corners [3] [1] = y;
corners [3] [2] = -height;
else
// Jeśli nie koniec, punkty na górnej i dolnej // części główki corners [2] [0] = O.Of; corners [2] [1] = diameter; corners[2] [2] = O.Of;
corners[3] [0] = O.Of;
corners [3] [1] = diameter;
corners [3] [2] = -height;
// Wektory normalne dla całej ścianki wskazują // ten sam kierunek calcNormal (corners, normal); glNormal3fv(normal) ;
// Każdy trójkąt określamy osobno jako leżący // obok następnego glVertex3fv(corners [0] ) glVertex3fv(corners [1] ) glVertex3fv(corners [2] )
glVertex3fv(corners [0] ) glVertex3fv(corners [2] ) glVertex3fv(corners [3] )
glEnd();
Trzpień
Trzpień śruby nie jest niczym innym jak cylindrem z dnem. Utworzymy cylinder rozkładając punkty na okręgu w płaszczyźnie xy, a następnie użyjemy dwóch wartości z w tych punktach, tworząc wielokąty przybliżające ścianki cylindra. Jednak także tym
Rozdział 10. » Modelowanie i kompozycja obiektów 3D_____________________329
razem ścianki będą składane wyłącznie z trójkątów. Kształt cylindra przedstawia rysunek 10.4.
Rysunek 10.4.
Trójkąty składające się na ścianki cylindra
Używając wachlarza trójkątów, utworzymy także dno cylindra. Zwróć uwagę że im mniejszy krok dookoła okręgu, tym mniejsze będą płaskie ścianki na ściankach cylindra i tym gładszy będzie się wydawał trzpień.
Kod wykorzystany do utworzenia tego cylindra został przedstawiony na listingu 10.5. Zwróć uwagę, że tym razem normalne wierzchołków nie są obliczane dla trójkątów przy użyciu wierzchołków trójkątów. Zwykle ustawialiśmy normalne wierzchołków równe normalnej dla całego trójkąta, jednak ponieważ tym razem modelujemy krzywą powierzchnię, normalne w każdym z wierzchołków będą normalnymi dla modelowanej krzywej.
Listing 10.5. Renderowanie trzpienia śruby___________________________________
// Tworzy trzpień śruby w postaci cylindra z zamkniętym dnem
void RenderShaft(void)
{
float x,y,angle; // Używane do obliczenia ścian
// cylindra
float height = 75.Of; // Wysokość cylindra
float diameter = 20.Of; // Średnica cylindra
float normal[3],corners[4][3]; // Miejsce na wierzchołki i
// normalne
float step = (3.1415f/50.0f); // Przybliża ściany cylindra 100
// płaskimi segmentami
// Ustawienie koloru materiału na trzpień śruby glColor3f(O.Of, O.Of, 0.7f);
// Wielokąty przeciwne do ruchu wskazówek są widziane z przodu, // (domyślnie dla trójkątów) glFrontFace(GL_CCW);
// Najpierw złożymy ścianę jako 100 czworokątów uformowanych // z przystających do siebie trójkątów glBegin(GL_TRIANGLES);
// Rysowanie ścianek
for(angle = O.Of; angle < (2.0f*3.1415f); angle += step)
{
// Obliczenie pozycji x i y następnego wierzchołka
x = diameter*(float)sin(angle) ;
y = diameter*(float)cos(angle) ;
330
Część II * Używanie OpenGL
// Pobranie współrzędnej tego punktu i wyciągnięcie // długości cylindra corners[0][0] = x; corners[0][1] = y; corners[0][2] = -height;
corners[1][0] = x;
corners[1][1] = y;
corners[l](2] = O.Of;
// Pobranie następnego punktu i zrobienie tego samego x = diameter*(float)sin(angle+step); y = diameter*(float)cos(angle+step);
// Gdy kończymy, użycie znanego punktu początkowego
// w celu zamknięcia powierzchni
if(angle+step < 3.1415*2.0) // Not Finished
{
corners[2][0] = x;
corners [2][1] = y;
corners[2][2] = O.Of;
corners[3][0] = x;
corners[3][1] = y;
corners[3][2] = -height;
else
// Kończymy przy użyciu punktu startowego
corners[2][0] = O.Of;
corners[2][1] = diameter;
corners[2][2] = O.Of;
corners[3][0] = O.Of;
corners[3][1] = diameter;
corners[3][2] = -height;
// Zamiast używać zwykłych normalnych do trójkątów, użyjemy
// takich, jakie by występowały, gdyby powierzchnia była
// rzeczywiście zakrzywiona. Ponieważ cylinder biegnie wzdłuż
// osi Z, normalne wybiegają z osi Z dokładnie w kierunku
// każdego wierzchołka. W związku z tym możemy użyć
// wierzchołka jako normalnej, pod warunkiem, że zredukujemy
// go do wektora jednostkowego
// Pierwszy trójkąt //////////////////////////////////////// // Wypełnienie wektora normalnego współrzędnymi punktu normal[0] = corners[0][0] normal[l] = corners[0][1] normal[2] = corners[0][2]
// Znormalizowanie wektora i wskazanie dla danego punktu ReduceToUnit(normal); glNormal3fv(normal); glVertex3fv(corners[0]);
// Pobranie wierzchołka, obliczenie normalnej,
// narysowanie go
normal[0] = corners[1][0] ;
normal[1] = corners[1][1];
T
Rozdziaf 10. * Modelowanie i kompozycja obiektów 3D_____________________331
normal[2] = corners[1][2]; ReduceToUnit(normal); glNorma!3fv(normal); glVertex3fv(corners[1]);
// Pobranie wierzchołka, obliczenie normalnej,
// narysowanie go
normal[0] = corners [2] [0];
normal[1] = corners[2][1];
normal[2] = corners[2][2];
ReduceToUnit(normal);
glNorma!3fv(normal);
glVertex3fv(corners[2]);
// Drugi trójkąt ////////////////////////////////////////
// Pobranie wierzchołka, obliczenie normalnej,
// narysowanie go
normal[0] = corners[2][0];
normal[1] - corners[2][1];
normal[2] = corners[2][2];
ReduceToUnit(normal);
glNorma!3fv(normal);
glVertex3fv(corners[2]);
// Pobranie wierzchołka, obliczenie normalnej,
// narysowanie go
normal[0] = corners[3][0];
normal[1] = corners[3][1];
normal[2] = corners[3][2];
ReduceToUnit(normal);
glNormal3fv(normal);
glVertex3fv(corners[3]);
// Pobranie wierzchołka, obliczenie normalnej,
// narysowanie go
normal[0] = corners[0][0];
normal[1] = corners[0][1];
normal[2] = corners[0][2];
ReduceToUnit(normal);
glNorma!3fv(normal);
glVertex3fv(corners[0]);
}
glEnd(); // Koniec ze ściankami cylindra
// Przejście do rysowania wachlarza na dolną część glBegin(GL_TRIANGLE_FAN);
// Normalne wskazują w dół osi Z glNorma!3f(O.Of, O.Of, -l.Of);
// Środek wachlarza w początku układu współrzędnych glVertex3f(O.Of, O.Of, -height);
332____________________________________Część II » Używanie OpenGL
// Obrót dookoła zgodnie z krokiem ścian cylindra
for(angle = O.Of; angle < (2.Of*3.1415f); angle += step)
// Obliczenie pozycji x i y następnego wierzchołka x = diameter*(float)sin(angle) ; y = diameter*(float)cos(angle);
// Następny wierzchołek wachlarza glVertex3f(x, y, -height);
// Ostatni wierzchołek zamyka wachlarz glVertex3f(O.Of, diameter, -height); glEnd(); }
Na szczęście, cylinder jest owinięty symetrycznie dookoła osi z. Tak więc normalna dla każdego wierzchołka może zostać obliczona przez znormalizowanie (zredukowanie do długości jednostkowej) samego wierzchołka. Wynik działania programu SHAFT przedstawia rysunek 10.5.
Rysunek 10.5.
Wynik działania programu SHAFT
Gwint
Gwint jest najbardziej skomplikowaną częścią naszej śruby. Jest złożony z dwóch płaszczyzn ułożonych w kształt V, owiniętych spiralnie wzdłuż cylindra. Jest tworzony jako dwa płaskie segmenty ułożone w V. Sposób konstruowania gwintu przedstawia rysunek 10.6, zaś kod OpenGL użyty do jego utworzenia zawiera listing 10.6.
Rysunek 10.6.
Sposób
konstruowania gwintu śruby
333
Rozdział 10. » Modelowanie i kompozycja obiektów 3D
Listing 10.6. Renderowanie gwintu śruby
// Tworzy gwint śruby void RenderThread(void) {
float x,y,z,angle;
float height = 15.0f;
float diameter = 20.Of;
float normal[3],corners[4][3],
float step = (3.1415f/32.0f); float revolutions = T.Of; float threadWidth = 2.0f; float threadThick = 3.0f; float zstep = .125f;
segment
// 360 stopni w radianach Idefine PI2 (2.Of*3.1415 f)
// Obliczone współrzędne i kąt
// Wysokość gwintu
// Średnica gwintu
// Miejsce na wierzchołki
//i normalne
// Jeden obrót
// Ilość obrotów dookoła trzpienia
// Szerokość gwintu
// Grubość gwintu
// Krok w kierunku osi Z za każdym
// razem, gdy rysowany jest nowy
// Ustawienie koloru materiału dla gwintu glColorSf(O.Of, O.Of, 0.4f);
-height+2;
// punkt początkowy prawie przy końcu
// Rysowanie ścian gwintu aż do osiągnięcia końca for(angle = O.Of; angle < PI2*revolutions; angle += step)
(
// Obliczenie pozycji x i y następnego wierzchołka
x = diameter*(float)sin(angle);
y = diameter*(float)cos(angle);
// Przechowanie następnego wierzchołka przy trzpieniu
corners[0][0] = x;
corners[0][1] = y;
corners[0][2] = z;
// Obliczenie pozycji w oddaleniu od trzpienia x = (diameter+threadWidth)*(float)sin(angle); y = (diameter+threadWidth)*(float)cos(angle);
corners[1][0] = x;
corners[1] [1] = y;
corners[1][2] = z;
// Obliczenie następnej pozycji w oddaleniu od trzpienia x = (diameter+threadWidth)*(float)sin(angle+step); y = (diameter+threadWidth)*(float)cos(angle+step);
corners[2][0] = x;
corners[2][1] = y;
corners[2][2] = z + zstep;
// Obliczenie następnej pozycji wzdłuż trzpienia x = (diameter)*(float)sin(angle+step); y = (diameter)*(float)cos(angle+step);
334____________________________________Część II » Używanie OpenGL
corners[3][0] = x;
corners[3][1] = y;
corners [3] [2] = z+ zstep;
// Używamy trójkątów, więc wybierzemy kierunek
// przeciwny do ruchu wskazówek
glFrontFace(GL_CCW);
glBegin(GL_TRIANGLES); // Początek górnej sekcji gwintu
// Obliczenie normalnych dla tego segmentu calcNormal(corners, normal); glNormal3fv(normąl);
// Dwa trójkąty pokrywające obszar glVertex3fv(corners[0]] glVertex3fv (corners[1]] glVertex3fv(corners[2])
glVertex3fv(corners[2J) glVertex3fv(corners[3]) glVertex3fv(corners[0])
glEnd() ;
// Przesunięcie krawędzi lekko w górę osi z // w celu utworzenia dolnej części gwintu corners[0][2] += threadThick; corners[3][2] += threadThick;
// Przeliczenie normalnej z powodu zmiany punktów,
// tym razem normalna wskazuje w przeciwnym kierunku, więc
// wystarczy ją odwrócić
calcNormal(corners, normal);
normal[0] = -normal[0];
normal[1] = -normal[1];
normal[2] = -normal[2];
// Przejście na kierunek zgodny z ruchem wskazówek dla dolnej // płaszczyzny gwintu glFrontFace(GL_CW);
// Rysowanie dwóch trójkątów glBegin(GL_TRIANGLES); glNormal3fv(normal);
glVertex3fv(corners[0]) glVertex3fv(corners[1]) glVertex3fv(corners[2])
glVertex3fv(corners[2]) glVertex3fv(corners[3]) glVertex3fv(corners[0])
glEnd();
// Przejście w górę osi Z z += zstep;
335
Rozdział 10. * Modelowanie i kompozycja obiektów 3D
Działanie programu THREAD zostało przedstawione na rysunku 10.7.
Rysunek 10.7.
Wynik działania programu THREAD
Składanie modelu w całość
Śruba jest składana przez narysowanie w odpowiednim położeniu wszystkich trzech części. Wszystkie części są odpowiednio przesuwane wzdłuż osi z. Trzpień i gwint są przesuwane o tę samą odległość, gdyż od początku zajmują to samo położenie. Wszystko, co musimy zrobić, to ułożyć części w odpowiednim położeniu, zaś bufor głębokości usunie za nas niepotrzebne fragmenty.
Manipulowaniem i renderowaniem trzech części śruby zajmuje się kod przedstawiony na listingu 10.7. Końcowy wynik działania programu BOLT przedstawia rysunek 10.8.
Rysunek 10.8.
Działanie programu BOLT
336_______________________________________Część II » Używanie OpenGL
Listing 10.7. Kod tworzący kompletną śrubą___________________________________
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
// Wyczyszczenie okna bieżącym kolorem tła
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Zachowanie macierzy przekształcenia i wykonanie obrotu glMatrixMode(GL_MODELVIEW);
// Obrót i przesunięcie, a następnie narysowanie główki śruby glPushMatrix();
glRotatef(xRot, l.Of, O.Of, O.Of);
glRotatef(yRot, O.Of, l.Of, O.Of);
glTranslatef(O.Of, O.Of, 55.Of);
RenderHeadO ; glPopMatrix();
// Zachowanie stanu macierzy, obrót, przesunięcie,
// narysowanie razem trzpienia i gwintu
glPushMatrix();
glRotatef(xRot, l.Of, O.Of, O.Of);
glRotatef(yRot, O.Of, l.Of, O.Of);
glTranslatef(O.Of, O.Of, 40.Of);
// Narysowanie trzpienia i gwintu RenderShaft(); RenderThreadO ;
glPopMatrix();
// Zrzucenie poleceń graficznych glFlushO ;
Test szybkości
Nasz ostatni program dość dobrze prezentuje metalową śrubę, jaką chcieliśmy wymodelować. Składając się z ponad 1700 trójkątów, jest to jak dotąd najbardziej skomplikowany obiekt w tej książce. Jednak trzeba przyznać, że ta ilość trójkątów jest niczym w porównaniu z liczbami wielokątów, z jakimi się zetkniesz komponując większe sceny i bardziej złożone obiekty. W rzeczywistości, wydajność najnowszych kart akceleratorów 3D jest mierzona w setkach tysięcy trójkątów na sekundę, i to już w przypadku tańszych modeli! Jednym z celów tego rozdziału było wprowadzenie list wyświetlania w celu przyspieszenia renderowania. Zanim jednak przejdziemy do porównań, potrzebujemy jakiegoś sposobu mierzenia szybkości - testu szybkości.
Gdy przechodzimy do tematu list wyświetlania, chcemy wiedzieć, czy występuje jakaś różnica, zamiast wierzyć wszystkiemu na słowo. Zmodyfikujmy więc nasz program BOLT. Zamiast obracać śrubę za pomocą klawiszy kursora, obracajmy ją cały czas,
Rozdział 10. » Modelowanie i kompozycja obiektów 3D_____________________337
choćby wyłącznie w osi y. Jak się zapewne domyślasz, w ten sposób zamienimy program w generator trójkątów, którego możemy łatwo użyć do zmierzenia wydajności. Zmienioną funkcję RenderScene() z programu SPINBOLT przedstawia listing 10.8.
Listing 10.8. Nowa funkcja RenderScene() obracająca śrubą dookoła osi y__________________
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
// Wyczyszczenie okna bieżącym kolorem tła
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Upewnienie się, że korzystamy z właściwej macierzy glMatrixMode(GL_MODELVIEW);
// Obrót i przesunięcie układu współrzędnych glRotatef(5.0f, O.Of, l.Of, O.Of);
// Przesunięcie i narysowanie główki śruby glTranslatef(O.Of, O.Of, 55.Of); RenderHead();
// Przesunięcie w tył i narysowanie trzpienia i gwintu glTranslatef(O.Of, O.Of, -15.Of); RenderShaft(); RenderThread();
// Ponowne cofnięcie w przygotowaniu do następnego razu glTranslatef(O.Of, O.Of, -40.Of);
// Zrzucenie poleceń graficznych glFlushO ;
Nowa funkcja renderowania nie zachowuje ani nie odtwarza stanu macierzy. Używamy funkcji glTranslate, aby ręcznie odtworzyć stan przesunięcia macierzy, jednak efekty funkcji glRotate się kumulują. To powoduje, że śruba obraca się dookoła swojej osi y o 5° za każdym razem, gdy jest renderowana.
Prosta technika animacji polega na stworzeniu timera, a gdy zostanie otrzymany komunikat WM_T1MER, unieważnieniu obszaru okna w celu odmalowania go. W ten sposób możemy w miarę potrzeby spowalniać lub przyspieszać animację. Naszym celem nie jest jednak prosta animacja, ale raczej poznanie tempa obrotów. Sensownym kryterium będzie czas, wymagany, aby śruba obróciła się o pełne 360 stopni.
Użycie komunikatów WM_TIMER nie jest najlepszym rozwiązaniem z dwóch powodów. Po pierwsze, nikt nie gwarantuje, że okno otrzyma wszystkie komunikaty WM_TI-MER (system operacyjny może być zbyt zajęty). Po drugie, gdy określisz przedziały czasu, jak mierzenie odstępów czasu będzie się miało do rzeczywistej wydajności?
To, czego rzeczywiście potrzebujemy, to odstęp czasu pomiędzy początkiem a zakończeniem renderowania. Mogłaby to być wartość zbyt mała do praktycznego użytku, możemy więc zmierzyć czas zajmowany przez pewną liczbę renderowań. Powtarzając
338____________________________________Część II » Używanie OpenGL
renderowanie sceny większą ilość razy i mierząc czas, jaki to zabierze, otrzymamy zupełnie poprawny test czasu.
Uwaga: To jest tylko przybliżenie!
Ten test czasu jest bardzo nieformalny i korzysta z metod naliczania czasu, które są tak niedokładne, że nie pozwalają na publikowania wyników. Używamy ich tylko w celu przedstawienia zysku w wydajności wynikłego z użycia list wyświetlania. Aby porównać prawdziwe programy (a także dwa zamieszczone tutaj), na czas trwania testu powinieneś przynajmniej zakończyć działanie wszystkich innych programów w systemie. Na szybkość renderowania ma wpływ wiele różnych czynników, ale jeśli warunki dla obu programów będą mniej więcej jednakowe, powinieneś zauważyć różnicę pomiędzy obiema wersjami.
Być może kusi cię zebranie po prostu garści wywołań funkcji RenderScene i zmierzenie, jak długo będą wykonywane. To działa, jednak zamknięcie aplikacji stawia się bardzo trudne, gdyż nie będzie miała szans obsługi żadnych innych komunikatów (na przykład WM_CLOSE). Najlepszym sposobem sprawienia, aby Windows wciąż odświeżały obszar roboczy okna, jest pominięcie zatwierdzenia obszaru po otrzymaniu komunikatu WM_PAINT. Jeśli obszar roboczy wciąż będzie nieważny, Windows w nieskończoność będą posyłały aplikacji komunikat WM_PAINT. W strumieniu tych komunikatów znajdują się być może także inne komunikaty, takie jak WM_CLOSE, które w dalszym ciągu będą mogły zostać przetworzone.
Nową procedurę obsługi komunikatu WM_PAINT w programie SPINBOLT przedstawia listing 10.9.
Listing 10.9. Obsługa komunikatu WM_PAINT w programie SPINBOLT___________________
// Miejsce na wartości do mierzenia czasu static unsigned long ulstart = OL; static unsigned long ulFinish = OL; static double dTime = 0.0;
// Miejsce na statystykę wydajności char cBuffer[80]; RECT cRect;
// Funkcja rysująca. Ten komunikat jest wysyłany przez Windows // za każdym razem, gdy okno wymaga odświeżenia case WM_PAINT:
{
// Zliczanie ilości renderowań
static iRenderCount = 0;
// Pobranie czasu na początku obrotu if(iRenderCount == 0)
ulstart » ulGetProfileTime();
Rozdział 10. » Modelowanie i kompozycja obiektów 3D_____________________339
// Wywołanie kodu rysunkowego OpenGL RenderScene();
// Wyświetlenie obrazka SwapBuffers(hDC);
// Zwiększenie licznika. Jeśli >= 71 -> koniec mierzenia // czasu iRenderCount++;
if(iRenderCount > 71) { iRenderCount = 0;
ulFinish = ulGetProfileTime();
// Przeliczenie czasu na sekundy dTime = ulFinish - ulStart; dTime /= 1000.0; }
// Wyświetlenie czasu (nie zapominajmy o kolorze tła)
sprintf(cBuffer,"%3.If sekund dla 360 stopni.",dTime);
GetClientRect(hWnd,icRect);
SetBkColor(hDC,RGB(O,O,255));
SetTextColor(hDC,RGB(255,255,0));
TextOut(hDC,0,cRect.bottom-20,cBuffer,strlen(cBuffer));
// Nie unieważniamy, wymuszając kolejne komunikaty
// WM_PAINT
}
break;
Procedura obsługi odczytuje bieżący czas systemowy i zlicza ilość wywołań funkcji renderującej. Po 71 razach odczytuje nowy czas, oblicza różnicę i wyświetla czas, jaki upłynął od ostatniego pomiaru. Pamiętaj, że nasza śruba obraca się za każdym razem o 5°, więc używając tej techniki naliczamy czas trwania obrotu o 360°.
Rysunek 10.9.
Działanie programu SPINBOLT
340
Część II » Używanie OpenGL
Funkcja ulGetProfileTime po prostu zwraca ilość tyknięć zegara systemowego i zamienia je na tysiączne części sekundy. (Możesz to sam sprawdzić w listingu, ale teraz nie jest to istotne dla naszej dyskusji). Działanie programu SPINBOLT zostało przedstawione na rysunku 10.9. Czas obrotu śruby dookoła własnej osi wynosił trzy sekundy (na komputerze Pentium Celeron 333 MHz z akceleratorem Matrox Millenium G200).
Poprawianie wydajności
Być może dostrzegłeś, gdzie tkwi problem z wydajnością w programie korzystającym z techniki WM_PA1NT. Podczas każdego rysowania śruby należy wykonać ogromną ilość obliczeń związanych z rysowaniem gwintu, trzpienia i główki. W tych obliczeniach mnóstwo razy odwołujemy się do kosztownych wywołań funkcji sin() i cos().
To, czego nam potrzeba, to sposób przechowania wszystkich obliczonych wierzchołków i normalnych, a następnie wykorzystanie ich bez konieczności każdorazowego przechodzenia przez całą tę trygonometrię. Oczywiście, OpenGL może nam w tym względzie dużo zaoferować, a mianowicie listy wyświetlania. Za pomocą listy wyświetlania możesz zarejestrować wywołania funkcji OpenGL (oraz ich wyniki), a następnie je odtworzyć. Listy wyświetlania są szybsze niż ponowne odtwarzanie pojedynczych wywołań OpenGL. Co więcej, wywołania zewnętrzne dla OpenGL, takie jak obliczanie funkcji trygonometrycznych i normalnych, nie są powtarzane, lecz są rejestrowane jedynie ich wyniki, przekazywane funkcjom OpenGL. Już z tego powinieneś się zorientować, dlaczego listy wyświetlania są tak dobrym pomysłem.
Ludzie i komputery
Dobrą regułą podczas tworzenia dowolnego oprogramowania jest zajęcie się poprawkami, które dają ponaddwudziestoprocentowy wzrost wydajności. Zostało ogólnie przyjęte, że ludzie w większości przypadków mają trudności z „wykryciem" wzrostu wydajności oprogramowania o mniej niż dwadzieścia procent. W przypadku OpenGL te 20% można często osiągnąć właśnie przez zastosowanie list wyświetlania tam, gdzie liczba wielokątów staje się znaczna. Tak więc dobrym pomysłem jest przyzwyczajenie się do używania list wyświetlania.
Tworzenie listy wyświetlania
Tworzenie listy wyświetlania jest bardzo proste. Tak jak obejmujesz prymitywy OpenGL wywołaniami funkcji glBegin/glEnd, podobnie listę wyświetlania obejmujesz wywołaniami glNewList/glEndList. Listę wyświetlania powinna identyfikować dostarczana przez ciebie wartość całkowita. Poniższy kod ilustruje typowy przebieg tworzenia listy wyświetlania:
Rozdział 10. » Modelowanie i kompozycja obiektów 3D_____________________341
glNewListd, GL_COMPILE); // Jakiś kod OpenGL glEndList();
Jako drugi parametr funkcji glNewList możesz podać GL_COMPILE lub GL_COM-PILE_AND_EXECUTE. Informuje on OpenGL o tym, czy polecenia OpenGL mają zostać skompilowane i przechowane, czy też skompilowane, wykonane i przechowane. Później, gdy chcesz odtworzyć listę wyświetlania, wywołaj po prostu
glCallList(l);
Podawany identyfikator jest tym samym identyfikatorem, co przekazany w odpowiednim wywołaniu funkcji glNewList.
Listing 10.10 zawiera fragment kodu z naszego nowego przykładu, SLSTBOLT, który do rysowania wirującej śruby wykorzystuje listy wyświetlania. Zwróć uwagę na możliwość zagnieżdżania list wyświetlania. Maksymalna ilość 64 poziomów zagnieżdżenia stanowi zabezpieczenie przed nieskończoną rekurencją. W tym kodzie tworzymy listę wyświetlania dla każdej części śruby, a następnie pojedynczą listę wykonującą wszystkie przekształcenia współrzędnych i wywołującą pozostałe listy w celu utworzenia kompletnej śruby.
Listing 10.10. Nowy kod wirującej śruby, tym razem korzystający z list wyświetlania____________
#define HEAD_LIST l łdefine SHAFT_LIST 2
#define THREAD_LIST 3 łdefine BOLT_LIST 4
// Ta funkcja odpowiada za inicjowanie kontekstu renderowania // Oprócz tego tworzy i inicjuje światła void SetupRCO
// Utworzenie listy wyświetlania dla główki śruby glNewList(HEAD_LIST,GL_COMPILE) ;
RenderHead(); glEndListO;
// Utworzenie listy wyświetlania dla trzpienia śruby glNewList(SHAFT_LIST,GL_COMPILE) ;
RenderShaft(); glEndListO;
// Utworzenie listy wyświetlania dla gwintu śruby glNewList(THREAD_LIST,GL_COMPILE) ;
RenderThread(); glEndList () ;
342 ____________________________________ Część II * Używanie OpenGL
// Utworzenie zagnieżdżonej listy wyświetlania dla całej śruby glNewList (BOLT_LIST, GL_COMPILE) ;
// Wyczyszczenie okna bieżącym kolorem tła glClear(GL_COLOR_BOFFER__BIT | GL_DEPTH_BUFFER_BIT) ;
// Zapewnienie, że korzystamy z właściwej macierzy glMatrixMode (GL_MODELVIEW) ;
// Obrót i przesunięcie układu współrzędnych // Zwróć uwagę, że się kumuluje glRotatef (5.0f, O.Of, l.Of, O.Of);
// Przesunięcie i narysowanie główki śruby glTranslatef (O.Of , O.Of, 55. Of); glCallList (HEAD_LIST) ;
// Przesunięcie w tył i narysowanie trzpienia i gwintu glTranslatef (O.Of, O.Of, -15. Of); glCallList (SHAFT_LIST) ; glCallList (THREAD_LIST) ;
// Ponowne cofnięcie w przygotowaniu do następnego razu glTranslatef (O.Of, O.Of, -40. Of);'
// Koniec listy dla śruby glEndList () ;
// Wywoływane do narysowania całej śruby
void RenderScene (void)
{
glCallList (BOLT_LIST) ;
// Zrzucenie poleceń graficznych glFlushO ;
Rysunek 10.10.
Wykorzystujemy listy wyświetlania -program SLSTBOLT
IŁ
343
Rozdział 10. * Modelowanie i kompozycja obiektów 3D
Jak widzisz, zdefiniowaliśmy kilka stałych identyfikujących poszczególne listy. Te stałe to po prostu numeryczne identyfikatory list. Rysunek 10.10 przedstawia działanie naszego nowego i poprawionego programu. Tym razem do wykonania całego obrotu potrzeba było jedynie 2,5 sekundy, czyli wynik o pół sekundy jest lepszy niż w poprzednim przykładzie. To może nie wydawać się dużo, ale poczekaj kilka rozdziałów i spróbuj ponownie z efektami specjalnymi, takimi jak mapowanie tekstur czy powierzchnie NURJBS. Jak już wspominaliśmy, 1700 trójkątów to naprawdę niewielki procent tego, z czego składają się większe i bardziej złożone sceny.
Symulator czołgu
Spróbuj uruchomić symulator czołgu z poprzedniego rozdziału i porównać go z symulatorem w tym rozdziale. Ta wersja, w dużym stopniu korzystająca z list wyświetlania, zawiera wiele tysięcy trójkątów i nie potrzebujesz żadnego programu do testowania wydajności, aby zauważyć różnicę w działaniu!
Podsumowanie
Wykorzystywaliśmy ten rozdział do pokazania, w jaki sposób tworzy się złożone trójwymiarowe obiekty, poczynając od użycia prymitywów OpenGL do budowania prostych trójwymiarowych klocków, a następnie składania ich w większe obiekty. Sama nauka interfejsu programowania jest łatwa, ale to właśnie doświadczenie w konstruowaniu trójwymiarowych scen i obiektów będzie cię wyróżniać spośród innych osób zajmujących się tym zagadnieniem. Gdy obiekt lub scena zostaną rozbite na mniejsze, potencjalnie ponownie wykorzystywalne części, możesz oszczędzić czas renderowania sceny tworząc listy wyświetlania. W sekcji podręcznika znajdziesz więcej funkcji przeznaczonych do wykorzystania, wyświetlania i zarządzania listami. Oprócz tego, na końcu rozdziału poznałeś prosty sposób mierzenia wydajności swojego programu, umożliwiający porównanie korzyści płynących z optymalizacji kodu.
Podręcznik
glCallList
Przeznaczenie Plik nagłówkowy Składnia Opis
Wykonuje listę wyświetlania
void glCallList(GLuint list);
Wykonuje listę wyświetlania identyfikowaną przez parametr list. Po wywołaniu tej funkcji maszyna stanu OpenGL nie jest odtwarzana, więc
344
Część II * Używanie OpenGL
dobrym pomysłem jest wywołanie wcześniej funkcji glPushMatrix, zaś później funkcji glPopMatrix. Wywołania glCallList mogą być zagnieżdżone. Funkcja glGet z argumentem GL_MAX_LIST_NESTING zwraca maksymalną ilość poziomów zagnieżdżenia. W przypadku Microsoft Windows jest nią 64.
Parametry
list GLuint: Identyfikuje listę wyświetlania, która ma zostać wykonana.
Zwracana wartość Brak
Przykład
Patrz także
Poniższy kod zachowuje stan macierzy przed wywołaniem listy wyświetlania. Po wywołaniu odtwarza stan macierzy.
// Zachowanie bieżącego stanu macierzy glPushMatrix();
// Narysowanie śruby zawierającej zagnieżdżone listy glCallList(BOLT_HEAD);
// Odtworzenie stanu macierzy glPopMatrix();
glCallLists, glDeleteLists, glGenLists, glNewList
glCallLists
Przeznaczenie Plik nagłówkowy Składnia Opis
Wywołuje listę list wyświetlania.
void glCallLists(GLsizei n, GLenum type, const GLvoid *lists);
Wywołuje kolejno listy wyświetlania wskazywane w tablicy *lists. Ta tablica może być prawie dowolnego typu. Rezultat jest zamieniany lub obcinany do najbliższej liczby całkowitej, która będzie stanowić indeks (identyfikator) konkretnej listy wyświetlania. Opcjonalnie, wartości z listy mogą zostać przesunięte o pewną wartość bazową, określoną wywołaniem funkcji glListBase.
Parametry n type
GLsizei: Ilość elementów tablicy list wyświetlania.
GLenum: Rodzaj elementów przechowywanych w liście *lists. Może być jedną z następujących wartości: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GLJNT, GLJJNSIGNEDJNT, GL_FLOAT, GL_2_BYTES, GL_3_BYTES lub GL_4_BYTES.
GLvoid: Tablica elementów typu określanego parametrem type. Zadeklarowanym typem danych jest void, co umożliwia użycie dowolnego z wymienionych powyżej typów.
345
jg OpenGl Rozdział 10. * Modelowanie i kompozycja obiektów 3D
Zwracana wartość Brak
Poniższy kod ilustruje wywołanie grupy list wyświetlania w pojedynczym wywołaniu funkcji:
// Miejsce na identyfikatory list wyświetlania int lists[50]; int i ;
// Stworzenie nazw list for(i = 0; i < 50; i++) lists[i] = i + 1;
// Zbudowanie pięćdziesięciu list wyświetlania:
// Pierwsza lista
glNewList(lists[0],GL_COMPILE);
glEnd ();
// Druga lista glNewList(listS[q],GL_COMPILE);
glEnd();
// I tak dalej.. .
Patrz także
// Wywołanie pięćdziesięciu list jedną funkcją glCallLists(50, GL_INT, lists;
glCallLists, glDeleteLists, glGenLists, glListBase, glNewList
glDeleteLists
Przeznaczenie Plik nagłówkowy Składnia Opis
Usuwa ciągły zakres list wyświetlania.
void glDeleteLists(GLuint list, GLsizei rangę);
Ta funkcja usuwa zakres list wyświetlania. Usuwanie rozpoczyna się od początkowej listy list i trwa do momentu usunięcia rangę list. Usuwanie nieużywanych list wyświetlania pozwala odzyskać znaczne ilości pamięci. Nieużywane listy wyświetlania z podanego zakresu są ignorowane i nie powodują błędów.
Parametry
list
rangę Zwracana wartość
GLuint: Identyfikator pierwszej usuwanej listy.
GLsizei: Ilość list wyświetlania, jakie mają zostać usunięte.
Brak
F
346
Część II » Używanie OpenGL
Przykład
Patrz także
Poniższa linia kodu ilustruje usuwanie wszystkich list wyświetlania o identyfikatorach pomiędzy l a 50:
glDeleteLists(l, 50);
glCallLists, glCallLists, glGenLists, gllsList, glNewList
glEndList
Przeznaczenie Plik nagłówkowy Składnia Opis
Zwracana wartość Przykład
Patrz także
Wyznacza koniec listy wyświetlania.
void glEndList(void);
Listy wyświetlania są tworzone przez wywołanie funkcji glNewList. Wszystkie następne polecenia OpenGL są kompilowane i umieszczane na liście wyświetlania. Funkcja glEndList kończy tworzenie listy wyświetlania.
Brak
Poniższy kod stanowi przykład tworzenia listy wyświetlania za pomocą funkcji glNewList i glEndList. Tworzona lista składa się z dwóch zagnieżdżonych w niej list.
// Początek listy wyświetlania glNewList (BOLT_LIST, GL_COMPILE) ;
// Lista wyświetlania zawiera dwie zdefiniowane // uprzednio listy wyświetlania glCallList (SHftFT_LIST) ; glCallList (THREAD_LIST) ;
// Koniec listy wyświetlania glEndList O ;
glCallList, glCallLists, glDeleteLists, glGenLists, gllsList
glGenLists
Przeznaczenie Plik nagłówkowy Składnia Opis
Generuje ciągły zakres pustych list wyświetlania.
GLuint glGenLists(GLsizei rangę);
Funkcja tworzy zakres pustych list wyświetlania. Ilość tworzonych list zależy od wartości argumentu rangę. Zwracaną wartością jest identyfikator pierwszej listy z zakresu. Celem tej funkcji jest zarezerwowanie zakresu identyfikatorów list do późniejszego użytku.
Parametry rangę
GLsizei: Ilość żądanych pustych list.
347
Rozdział 10. * Modelowanie i kompozycja obiektów 3D
Zwracana wartość
Przykład
Identyfikator pierwszej listy z zakresu. Zostają utworzone puste listy z zakresu od zwracanej wartości do range-l.
Poniższy kod alokuje tablicę 25 liczb całkowitych, które zostaną użyte do przechowania identyfikatorów 25 list wyświetlania. Każdemu elementowi tablicy musi zostać przypisany poprawny identyfikator listy, który może zostać użyty później.
int lists[25]; // miejsce na 25 list wyświetlania int first; // indeks pierwszego dostępnego
// identyfikatora listy wyświetlania int x; // Licznik pętli
Patrz także
// Odczyt pierwszego identyfikatora listy wyświetlania
first = glGenLists(25);
// Przypisanie każdemu elementowi tablicy poprawnego
// identyfikatora listy wyświetlania
for(x = 0; x < 25; x++)
lists[x] = first + x + 1;
glCallList, glCallLists, glDeleteLists, glNewList
gllsList
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
list Zwracana wartość
Przykład
Sprawdza, czy istnieje dana lista.
GLboolean gl!sList(GLuint list);
Ta funkcja jest używana do sprawdzania, czy istnieje lista wyświetlania o danym identyfikatorze. Możesz jej użyć do sprawdzenia listy przed jej zastosowaniem.
GLuint: Identyfikator potencjalnej listy wyświetlania.
GLJTRUE jeśli dana lista wyświetlania istnieje, lub wartość GL_FALSE w przeciwnym wypadku.
Poniższy kod przegląda tablicę, która powinna zawierać poprawne identyfikatory list. Jeśli lista o danym identyfikatorze istnieje, zostaje wywołana.
int lists[25]; // miejsce na 25 list wyświetlania int x; // Licznik pętli
Patrz także
for(x = 0; x < 25; x++) if(gllsList(lists[x])
glCallList(lists[x]);
glCallList, glCallLists, glDeleteLists, glGenLists, glNewList
348
Część II «• Używanie OpenGL
gllistBase
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Określa wartość bazową dodawaną do identyfikatorów list wywoływanych funkcją glCallLists.
void glListBase(GLuint base);
Funkcja glCallLists wywołuje serię list wyświetlania zawartych w tablicy. Funkcja glListBase ustawia wartość bazową, która zostaje dodana do każdego identyfikatora listy w tablicy. Domyślną wartością bazową jest zero. Możesz odczytać bieżącą wartość wywołując funkcję glGet(GL_LIST_BASE).
Parametry base
Zwracana wartość Przykład
GLuint: Wartość, która zostanie dodana do identyfikatora listy w tablicy list, wywoływanych funkcją glCallLists. Domyślnie ta wartość jest równa zeru.
Brak.
Poniższy kod tworzy 20 list wyświetlania ponumerowanych od O do 19. Tworzona jest tablica identyfikatorów list (listA), zawierająca liczby od O do 9. Następnie jest wywoływana funkcja glCallLists wykonująca wszystkie listy występujące w tablicy listA. Następnie, zamiast ładowania tablicy kojejnymi dziesięcioma liczbami, za pomocą funkcji glListBase zmieniana jest wartość bazowa. Po ponownym wywołaniu funkcji CallLists dla tablicy listA, zostaną wywołane listy określone przez elementy tablicy powiększone o wartość bazową (10).
int listA[10]; int i;
for(i = 0; i < 10; listA[i] = i;
// Budowanie list wyświetlania od l do 20 glNewListfl, GL_COMPILE);
glEndList();
// Druga lista glNewList(2, GL_COMPILE);
glEndList();
// I tak dalej...
// Wywołanie pierwszych dziesięciu list glCallLists(10, GL_INT, listA);
349
Rozdział 10. * Modelowanie i kompozycja obiektów 3D
przy użyciu
Patrz także
// Wywołanie następnych dziesięciu list,
// tej samej tablicy
glListBase(10);
glCallLists(10, GL_INT, listA);
glCallLists
glNewlist
Przeznaczenie Plik nagłówkowy Składnia Opis
Rozpoczyna tworzenie nowej listy odtwarzania.
void glNewList(GLuint list, GLenum modę);
Lista wyświetlania jest grupą poleceń OpenGL zarejestrowanych w celu późniejszego wywołania i wykonania. Możesz używać list wyświetlania do przyspieszania złożonych obliczeniowo rysunków lub wymagających danych odczytywanych z dysku. Funkcja glNewList rozpoczyna tworzenie listy, którą jednoznacznie identyfikuje pierwszy parametr. Identyfikatory list są używane w funkcjach glCallList i glCallLists w celu określenia listy, która ma być wywołana. Jeśli identyfikator nie jest unikatowy, poprzednia lista może zostać zastąpiona. W celu zarezerwowania zakresu identyfikatorów możesz użyć funkcji glGenLists. W celu sprawdzenia, czy istnieje lista o danym identyfikatorze, możesz użyć funkcji gllsList. Listy wyświetlania mogą być jedynie kompilowane albo kompilowane i wykonywane. Po wywołaniu polecenia glNewList, następnie polecenia OpenGL są rejestrowane i przechowywane w liście odtwarzania w kolejności ich wystąpienia, aż do momentu napotkania polecenia glEndList. Wykonywane, lecz nigdy nie są rejestrowane w samej liści są poleceniem: glGenLists, glDeleteLists, glFeedbackBuffer, glSelectBuffer, glRenderMode, glReadPixels, glPixelStore, glFlush, glFinish, gllsEnabled oraz glGet.
Parametry list
modę
GLuint: Numeryczny identyfikator listy wyświetlania. Jeśli istnieje już lista o takim identyfikatorze, zostanie zastąpiona przez nową listę.
GLenum: Listy wyświetlania mogą być kompilowane w celu późniejszego wykonania lub kompilowane i wykonywane jednocześnie. Jeśli chcesz jedynie skompilować listę, zastosuj parametr GL_COMPILE. Jeśli chcesz skompilować i jednocześnie wykonać listę wyświetlania, zastosuj parametr GL_COMPILE_AND_EXECUTE.
Zwracana wartość Brak.
Przykład
Poniższy kod stanowi przykład tworzenia listy wyświetlania za pomocą funkcji glNewList i glEndList. Tworzona lista składa się z dwóch zagnieżdżonych w niej list.
350
Część II » Używanie OpenGL
Patrz także
// Początek listy wyświetlania glNewList(BOLT_LIST, GL_COMPILE);
// Lista wyświetlania zawiera dwie zdefiniowane // uprzednio listy wyświetlania glCallList(SHAFT_LIST); glCallList(THREAD_LIST) ;
// Koniec listy wyświetlania glEndList();
glCallList, glCallLists, glDeleteLists, glGenLists, gllsList
Rozdział 11.
Grafika rastrowa
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
Rysować bitmapowe obrazki * glBitmap/glRasterPos
Używać bitmapowych czcionek * wglUseFontBitmaps/glGenLists/glCallLists
Rysować kolorowe obrazki * glDrawPixels
Odczytywać i kopiować kolorowe * glCopyPixels/glReadPixels
obrazki na ekran
Odczytywać i zapisywać pliki * LoadDIBitmap/SaveDIBitmap
bitmap Windows
Z pewnością słyszałeś wiele wypowiedzi o tym, że lepiej jest pracować z trójwymiarową grafiką niż z grafiką dwuwymiarową, tą z przed lat. O ile w większości przypadków jest to prawdą, to jednak te trójwymiarowe obrazki są rysowane przecież w dwóch wymiarach na ekranie monitora. Grafika rastrowa to dwuwymiarowe tablice kolorów, używane nie tylko do wyświetlania trójwymiarowych scen, ale także do drukowania obrazów na drukarkach rastrowych czy nawet taśmie filmowej!
Oprócz funkcji związanych z wektorami i wielokątami, jakie poznaliśmy dotychczas, OpenGL udostępnia także kilka funkcji przeznaczonych do wyświetlania trójwymiarowych bitmap i obrazów. Właśnie te funkcje będą przedmiotem tego rozdziału.
Rysowanie bitmap
Bitmapy w OpenGL to dwukolorowe obrazy, używane do szybkiego rysowania na ekranie znaków lub symboli (na przykład ikon). Różni się to od (błędnej) definicji stosowanej w Microsoft Windows, które do bitmap zaliczają także obrazki posiadające więcej kolorów. W OpenGL do rysowania bitmap służy pojedyncza funkcja, glBitmap. Gdy za
352
Część II * Używanie OpenGL
jej pomocą rysujesz bitmapę, pierwszy kolor (0) jest przezroczysty. Drugi (1) jest ustalany na podstawie bieżącego koloru i właściwości oświetlenia dla materiału.
Rysunek 11.1 przedstawia wyświetlone w OpenGL bitmapy, przedstawiające uśmiechnięte buźki. Kod (listing 11.1) służący do stworzenia takiego obrazu składa się z danych bitmapy, po których następuje wywołanie funkcji glBitmap.
Rysunek 11.1.
Przyklad bitmapy •w OpenGL
Listing 11.1. Rysowanie okna pełnego uśmiechniętych buzi
void
RepaintWindow(RECT *rect) /* Obszar roboczy */
/* buźka 16x16 */
int i;
static GLubyte sraileyt]
0x03, OxcO, O, O, /*
OxOf, OxfO, O, O, /*
*** ** ***
Oxle, 0x78, O, O, /*
0x39, Ox9c, O, O, /*
0x77, Oxee, O, O, /* *** ****** ***
Ox6f, Oxf6, O, O, /* ** ******** **
Oxff, Oxff, O, O, /* ****************
Oxff, Oxff, O, O, /* ****************
Oxff, Oxff, O, O, /* ****************
Oxff, Oxff, O, O, /* ****************
0x73, Oxce, O, O, /* *** **** ***
0x73, Oxce, O, O, /* *** **** ***
Ox3f, Oxfc, O, O, /* ************
Oxlf, Oxf8, O, O, /* **********
OxOf, OxfO, O, O, /* ********
0x03, OxcO, O, O /* ****
glViewport(O, O, rect->right, rect->bottom);
Rozdział 11. » Grafika rastrowa__________________________________353
glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadldentity();
g!0rtho(0.0, rect->right - 1.0, 0.0, rect->bottom - 1.0, -1.0, 1.0);
/*
* Ta bitmapa jest wyrównana do granicy 4 bajtów...
*/
glPixelTransferi(GL_UNPACK_ALIGNMENT, 4);
glColorSf (1.0, 0.0, 0.0); for (i =0; i < 100; i ++) {
glRasterPos2i(rand() % rect->right, rand() % rect->bottom);
glBitmap(16, 16, 8.0, 8.0, 0.0, 0.0, smiley); };
glFinish();
W tym przykładzie zdefiniowaliśmy bitmapę 16 x 16 pikseli, przedstawiającą uśmiechniętą buźkę. Bitmapa jest zapisana w tablicy 32 bajtów bez znaku, w której siódmy bit pierwszego bajtu odpowiada lewemu dolnemu rogowi bitmapy.
Parę słów na temat bitmap
Bitmapy OpenGL zwykle są zdefiniowane „do góry nogami". To znaczy, są zapisywane od dołu do góry (widać to zresztą na listingu). Aby zdefiniować bitmapę od góry do dołu, musisz zastosować ujemną wysokość. Ponadto, z powodu błędu w bibliotekach OpenGL Microsoftu, musisz wyrównać każdy wiersz bitmapy do granicy czterech bajtów. Przy prawidłowo funkcjonującej bibliotece OpenGL mógłbyś, do zmiany wyrównania bitmapy użyć funkcji glPixelStore, opisanej w dalszej części rozdziału.
Po zdefiniowaniu bitmapy, która ma zostać narysowana, musimy określić poprawną pozycję rastra, wywołując w tym celu funkcję glRasterPos:
glRasterPos2i(rand() % rect->right, rand() % rect->bottom);
W tym przykładzie rysujemy buźki w przypadkowych miejscach w obszarze roboczym okna, z przesunięciem bitmapy po osiem pikseli od lewej strony i od dołu. Pozycja rastra jest określana we współrzędnych modelu, podobnie jak położenia wierzchołków w funkcji glVertex. Oprócz ustawienia bieżącej pozycji rastra, funkcja glRasterPos ustawia także znacznik poprawnej pozycji rastra. Ta zmienna logiczna ma wartość True, gdy pozycja rastra leży wewnątrz bieżącego widoku, zaś wartość False w przeciwnym wypadku.
354
Część II » Używanie OpenGL
Uwaga na temat obcinania
Wielokąty i inne wektorowe prymitywy są częściowo rysowane nawet wtedy, gdy'wykraczają poza bieżący widok, i są obcinane tylko do granic widoku. Obcinanie bitmap przebiega nieco inaczej. Jeśli podana pozycja rastra leży poza bieżącym widokiem, bitmapa nie będzie rysowana.
Aby narysować bitmapę, wywołujemy funkcję glBitmap:
glBitmap(16, 16, 8.0, 8.0, 0.0, 0.0, smiley);
W tym przypadku rysujemy bitmapę 16 x 16, której środek leży w punkcie (8,0, 8,0) bitmapy. Po narysowaniu bitmapy, pozycja rastra jest przesuwana o (0,0, 0,0) pikseli.
Prototyp tej funkcji jest następujący:
glBitmap(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bits)
Parametry width i height określają szerokość i wysokość bitmapy. Parametr bits wskazuje bitmapę przeznaczoną do narysowania, wyrównaną do granicy 32 bitów. Parametry xorig i yorig zawierają położenie środka bitmapy. Po narysowaniu bitmapy, bieżąca pozycja rastra jest przesuwana o (xmove, ymove) pikseli, zaś znacznik poprawnej pozycji rastra pozostaje niezmieniony. Parametry xmove i ymove zwykle są używane przy rysowaniu bitmapowych czcionek (opisywanych w następnej sekcji) w celu przejścia do „komórki" następnego znaku.
Uwaga na temat bieżącej pozycji rastra
Jak już wspomniano, gdy bieżąca pozycja rastra leży poza widokiem, bitmapa nie zostanie narysowana. Ponieważ jednak po narysowaniu bitmapy znacznik pozycji rastra pozostaje niezmieniony, możesz użyć funkcji glBitmap do rozmieszczania i rysowania bitmap częściowo obciętych granicą bieżącego widoku. Na przykład, oto jak możesz narysować buźkę na lewej krawędzi bieżącego widoku:
glRasterPos2i(0, 0) ;
glBitmap(O, O, 0.0, 0.0, -4.0, 0.0, NULL);
glBitmap(16, 16, 8.0, 8.0, 0.0, 0.0, smiley);
Parametr NULL w pierwszym wywołaniu funkcji glBitmap określa, że nie ma żadnej bitmapy do narysowania. Po pierwszym wywołaniu glBitmap, bieżąca pozycja rastra zostanie przesunięta, 4 piksele w lewo (-4,0) przed narysowaniem rzeczywistej bitmapy w następnym wywołaniu. To rozwiązanie nadaje się także do rysowania pixmap, które zostaną opisane w dalszej części rozdziału.
Rozdział 11. » Grafika rastrowa__________________________________355
Czcionki bitmapowe
Jednym z bardzo ważnych zastosowań bitmap jest wyświetlanie łańcuchów znaków. W zwykłych warunkach musiałbyś zdefiniować bitmapę dla każdego znaku, a następnie rysowałbyś bitmapy konieczne do narysowania łańcucha. Na szczęście, biblioteka Win32 w Windows zawiera funkcję zwaną wglUseFontBitmaps, przeznaczoną do generowania bitmap na podstawie czcionek zainstalowanych w systemie.
Aby umożliwić użycie bitmap czcionek, OpenGL dostarcza trzech funkcji: glGenLists, glListBase oraz glCallList (opisanych w rozdziale 10). Funkcja glGenLists generuje ciągłą serię identyfikatorów list wyświetlania OpenGL, które będą odpowiadać bitmapom znaków utworzonych funkcją WglUseFontBitmaps.
GLuint base; HDC hdc;
base - glGenLists (96); wglUseFontBitmaps(hdc, 32, 96, base);
Ten kod tworzy 96 bitmap znaków z bieżącej czcionki, poczynając od znaku 32, kodu ASCII spacji. Zmienna base zawiera identyfikator listy wyświetlania pierwszego znaku - w tym przypadku znaku 32 (spacji w ASCII). Aby wyświetlić łańcuch znaków za pomocą tej bitmapy, użyj połączenia funkcji glListBase oraz glCallLists:
char *s;
glListBase(font - 32); glCallLists(strlen(s), GL_UNSIGNED_BYTE, s);
Funkcja glListBase ustawia wartość bazową identyfikatorów list wyświetlania. Funkcje glCallList oraz glCallLists dodadzą tę wartość do przekazywanych im identyfikatorów list wyświetlania, a w efekcie wybiorą zdefiniowaną właśnie czcionkę. Funkcja glCallLists wywołuje serię list wyświetlania, przekazywaną w postaci tablicy znaków (unsigned byte), zawierającej łańcuch do wypisania.
Budowanie prostej biblioteki czcionek
Funkcja wglUseFontBitmaps z pewnością upraszcza tworzenie czcionek, jednak wciąż pozostaje nam wiele pracy z wypisaniem łańcucha. Możemy jednak całkiem łatwo stworzyć użyteczną bibliotekę czcionek. W tym celu musimy posiadać funkcję do tworzenia czcionek (listing 11.2).
Listing 11.2. Początek funkcji FontCreateBitmaps________________________________
GLuint
FontCreateBitmaps(HDC hdc, /* We - Kontekst urządzenia */
char *typeface, /* We - Specyfikacja czcionki */ int height, /* We - Wysokość czcionki w
^pikselach */
356
Część II » Używanie OpenGL
GLuint H FONT
int weight, /* We - Waga czcionki (pogrubiona,
=>etc) */ DWORD italic) /* We - Tekst jest pochyły */
base; /* Bazowa lista wyświetlania dla
^czcionki */
font; /* Identyfikator czcionki
^Windows */
if ((base = glGenLists(96)) == 0) return (0);
Argument typeface to po prostu nazwa czcionki, taka jak Courier czy Helvetica, określająca styl znaków. Argumenty height, width oraz italic (wysokość, szerokość, pochylenie) są przekazywane bezpośrednio funkcji wglUseFontBitmaps i określają rozmiar i wygląd znaków.
Zanim utworzysz bitmapy znaków, musisz zdecydować się na wybór zestawu znaków. Zwykle używa się zestawu ANSI lub UNICODE. Zestaw znaków ANSI (ANSI_CHA-RSET) zawiera standardowy zestaw znaków 7-bitowego ASCII. W celu uzyskania znaków międzynarodowych i znaków diakrytycznych, użyj zamiast niego zestawu znaków UNICODE (UNICODE_CHARSET). Pewne czcionki zawierają zestawy znaków specjalnych. Na przykład czcionka Symbol zawiera litery alfabetu greckiego oraz wiele symboli naukowych.
W tej prostej implementacji użyjemy zestawu znaków ANSI_CHARSET dla zwykfych czcionek i zestawu SYMBOL_FONTSET dla czcionki Symbol. Spójrz na listing 11.3.
Listing 11.3. Kontynuacja funkcji FontCreateBitmaps_____________________________
if (stricmpttypeface, "symbol") == 0)
font = CreateFont(height, O, O, O, weight, italic, FALSE, FALSE, SYMBOL_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DRAFT_QUALITY, DEFAULT_PITCH, typeface);
else font
FALSE,
CreateFont(height, O, O, O, weight, italic, FALSE, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DRAFT_QUALITY, DEFAOLT_PITCH, typeface);
SelectObject(hdc, font); wglUseFontBitmaps(hdc, 32, 96, base); return (base);
Jeśli potrzebujesz znaków międzynarodowych, zmień „normalny" zestaw znaków na zestaw UNICODE_CHARSET i zdefiniuj 224 znaki (256 minus 32):
Rozdział 11. » Grafika rastrowa__________________________________357
else
font = CreateFont(height, O, O, O, weight, italic, FALSE, FALSE, UNICODE_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DRAFT_QUALITY, DEFAULT_PITCH, typeface);
SelectObject(hdc, font); wglOseFontBitmaps(hdc, 32, 224, base);
Aby uzupełnić funkcje biblioteki, będziemy potrzebowali funkcji przeznaczonej do usuwania czcionek (listing 11.4). Wywoływana w niej funkcja glDeleteLists po prostu usuwa podane listy wyświetlania, w tym przypadku nasze bitmapy czcionek. Tak jak w przypadku funkcji FontCreateBitmaps, aby umożliwić działanie z międzynarodowym zestawem znaków, powinieneś zmienić ilość list wyświetlania z 96 na 224.
Listing 11.4. Funkcja FontDelete_________________________________________
void
FontDelete(GLuint font) /* We - Czcionka do usunięcia */
{
if (font == 0) return;
glDeleteLists(font, 96);
Na koniec, aby ułatwić sobie rysowanie znaków, możesz stworzyć funkcję wypisywania łańcuchów i wypisywania sformatowanych łańcuchów. Funkcja FontPuts (listing 11.5) używa funkcji glPushAttrib oraz glPopAttrib w celu zachowania i odtworzenia bieżącej wartości bazowej identyfikatorów list wyświetlania. Jeślibyś o tym zapomniał, mógłbyś niechcący wpłynąć na inny kod rysunkowy, korzystający z list wyświetlania!
Listing 11.5. Funkcja FontPuts_________________________________________
void
FontPuts(GLuint font, /* We - Czcionka do użycia */
char *s) /* We - Łańcuch do wypisania */
if (font == 0) return;
if (s == NULL) return;
glPushAttrib(GL_LIST_BIT);
glListBase(font - 32);
glCallLists(strlen(s), GL_UNSIGNED_BYTE, s); glPopAttrib();
358
Część II * Używanie OpenGL
Uwaga na temat funkcji glCallLists i łańcuchów znaków
Ważne jest, aby pamiętać, że funkcja glCallLists i funkcje czcionek opisywane w tym rozdziale nie obsługują znaków kontrolnych, takich jak tabulator czy znak nowej linii. Jeśli w wyświetlanym łańcuchu znajdą się takie znaki, mogą zostać wywołane inne listy wyświetlania, co wpłynie na końcowy wygląd sceny. To działanie może być kontrolowane poprzez przetworzenie łańcucha wejściowego przed wywołaniem funkcji glCallLists. Znaki nowej linii i tabulatora mogą zostać zasymulowane za pomocą techniki korzystającej z funkcji gIBitmap, opisywanej w poprzedniej uwadze, a także poprzez wywołanie funkcji glGetlntegen/ (opisywanej w rozdziale 14).
Funkcja FontPrintf (listing 11.6) używa pliku nagłówkowego <stdarg.h> w celu zarządzania zmienną liczbą argumentów potrzebną do funkcji vsprintf, formatującą łańcuch, który ma zostać wypisany.
Listing 11.6. Funkcja FontPrintf _______________________________________
#define MAX_STRING 1024
void
FontPrintf(GLuint font, /* We - Czcionka do użycia */
// We - łańcuch formatowania w stylu funkcji printfO char * format,
...) /* We - Inne argumenty w miarę potrzeby */
{
va_list ap; /* Wskaźnik argumentów */
char s[MAX_STRING + 1]; /* Łańcuch wyjściowy */
if (format == NULL) return;
va_start(ap, format); // Przetwarzanie zmiennej liczby argumentów
vsprintf(s,format, ap) ; // Sformatowanie tekstu do łańcucha
// wyjściowego
va_end(ap); // Koniec przetwarzania zmiennej liczby
// argumentów
FontPuts(font, s);
Pełny kod funkcji FontCreateBitmaps, FontDelete, FontPuts oraz FontPrintf znajduje się w pliku FONT.C na płytce CD-ROM, w folderze do tego rozdziału. Prototypy funkcji zostały zdefiniowane w pliku FONT.H.
Rozdziaf 11. » Grafika rastrowa ________________________________ 359
Pixmapy: bitmapy z kolorem
Obrazy zawierające więcej niż dwa kolory często nazywane s\pixmapami (skrót odpi-xel maps - mapy pikseli) i najczęściej służą jako obrazy tła lub tekstury (patrz rozdział 12). W OpenGL pixmapy to zwykle obrazki z 8-bitowym indeksowanym kolorem lub 24-bitowe obrazki RGB.
Rysowanie pixmap
Do rysowania pixmap w OpenGL służy pojedyncza funkcja, glDrawPixels. Podobnie jak glBitmap, glDrawPixels wykorzystuje bieżącą pozycję rastra określającą położenie lewego dolnego rogu bitmapy. Nie określa się jednak środka bitmapy ani przesunięcia pozycji rastra.
BITMAPINFO *BitmapInfo; GLubyte *BitmapBits;
glRasterPos2 (xof fset, yof fset) ;
glDrawPixels (BitmapInfo->bmiHeader .biWidth, BitmapInfo->bmiHeader .biHeight, GL_RGB, GL_ONSIGNED_BYTE, BitmapBits);
Funkcja glDrawPixels akceptuje pięć argumentów:
glDrawPixels (GLsizei width, GLsizei height, GLenura format, GLenum type, GLvoid *pixels)
Parametr format określa przestrzeń kolorów pixmapy; dostępne formaty zostały zebrane w tabeli 11.1. Format GL_COLOR_INDEX określa, że każda wartość koloru w pixma-pie stanowi indeks do bieżącej logicznej palety kolorów Windows. Obrazki z indeksowanym kolorem często są używane do wyświetlania ikon. Format GL_LUMINANCE odwzorowuje każdą wartość koloru na skalę szarości na ekranie, gdzie wartość minimalna odpowiada zupełnej czerni, zaś wartość maksymalna odpowiada zupełnej bieli. Format GL_RGB określa, że każdy piksel w obrazie posiada własne intensywności barw składowych: czerwonej, zielonej i niebieskiej.
Tabela 11.1.
Formaty pikseli OpenGL
Format Opis
GL_COLOR_INDEX Piksele jako indeksy kolorów
GL_LUMINANCE Piksele w skali szarości
GL_RGB Piksele RGB
Parametr type funkcji glDrawPixels określa typ oraz zakres wartości kolorów w obrazie, tak jak opisano w tabeli 1 1.2.
360
Część II » Używanie OpenGL
Tabela 11.2.
Typy pikseli \v OpenGL
typ
GL_BYTE
GL_UNSIGNEDJBYTE GL BITMAP
Opis
8-bitowe wartości ze znakiem (od -128 do 127) 8-bitowe wartości bez znaku (od O do 255) Obraz bitmapy (od O do l)
Remapowanie kolorów
Gdy używasz kolorów w formacie GL_COLOR_INDEX, możesz przemapować kolory pixmapy lub bitmapy, używając w tym celu funkcji glPixelMap lub glPixelTransfer. Funkcja glPixelTransfer umożliwia określenie przeskalowania i przesunięcia indeksów kolorów i wartości RGB. Na przykład, poniższy kod rozjaśnia obraz RGB o 10 procent:
glPixelTransfer(GL_RED_SCALE, 1.1) ; glPixelTransfer(GL_GREEN_SCALE, 1.1) ; glPixelTransfer(GL_BLUE_SCALE, 1.1);
Podobnie, aby przesunąć indeksy kolorów bitmapy na pozycje palety, które dla nich zdefiniowałeś, użyj
glPixelTransfer(GL_INDEX_OFFSET, kolor_dla_bitmapy);
W przykładzie z „uśmiechniętą" bitmapą (listing 11.7) możemy użyć poniższego kodu do odwzorowania dwóch kolorów bitmapy na odpowiednie pozycje palety:
Listing 11.7. Funkcja rysująca w oknie uśmiechnięte buźki___________ ___ __
void
RepaintWindow(RECT *rect)
{ /
int i ;
static GLubyte smiley[]
{
0x03, OxcO, O, O, /* OxOf, OxfO, O, O, /* Oxle, 0x78, O, O, /* 0x39, Ox9c, O, O, /* 0x77, Oxee, O, O, /* Ox6f, Oxf6, O, O, /* Oxff, Oxff, O, O, /* ' Oxff, Oxff, O, O, /* ' Oxff, Oxff, O, O, /* ' Oxff, Oxff, O, O, /* ' 0x73, Oxce, O, O, /* 0x73, Oxce, O, O, /* Ox3f, Oxfc, O, O, /* Oxlf, Oxf8, O, O, /* OxOf, OxfO, O, O, /* 0x03, OxcO, O, O /*
/* We - Obszar roboczy okna */
/* Uśmiechnięta buźka 16x16 */
* ***
****
Rozdział 11. » Grafika rastrowa__________________________________361
glViewport(O, O, rect->right, rect->bottom);
glClear!ndex(0.0) ;
glClear(GL_COLOR_BUFFER_BIT) ;
glMatrixMode(GL_PROJECTION);
glLoadldentity();
glOrtho(0.0, rect->right - 1.0, 0.0, rect->bottom - 1.0, -1.0, 1.0);
/*
* Ta bitmapa jest wyrównana do granicy 4 bajtów...
*/
glPixelTransferi(GL_UNPACK_ALIGNMENT, 4); glPixelTransferi(GL_INDEX_OFFSET, 1);
for (i = 0; i < 100; i ++) {
glRasterPos2i(rand() % rect->right, rand() % rect->bottom);
glDrawPixels(16, 16, GL_COLOR_INDEX, GL_BITMAP, smiley); };
glFinishO;
Tablice odwzorowań kolorów
Czasami konieczne jest zastosowanie korekcji kolorów bardziej skomplikowanych niż zwykłe liniowe skalowanie i przesunięcie. Jednym z zastosowań jest korekcja gamma, w której intensywność każdej wartości koloru jest dostosowywana zgodnie z krzywą wykładniczą, kompensującą błąd odwzorowania kolorów przez monitor lub drukarkę (rysunek 11.2).
Rysunek 11.2.
Obraz bez korekcji gamma (po lewej) oraz z korekcją gamma l, 7 (po prawej)
362____________________________________Część II » Używanie OpenGL
Funkcja glPixelMap umożliwia przeprowadzenie tego przez zastosowanie tablicy przeglądania (LUT - lookup table);
GLfloat lut[256]; GLfloat gamma_value; int i;
gamma_value = 1.7; /* Dla monitorów wideo NTSC */ for (i = 0; i < 256; i++)
lut[i] = pow(i / 255.0, 1.0 / gamma_value;
glPixelTransfer(GL_MAP_COLOR, GL_TRUE); glPixelMap(GL_PIXEL_MAP_R_TO_R, 256, lut); glPixelMap(GL_PIXEL_MAP_G_TO_G, 256, lut); glPixelMap(GL_PIXEL_MAP_B_TO_B, 256, lut);
Skalowanie pixmapy
Poza dostosowywaniem kolorów pixmapy, możesz dopasować jej rozmiar, używając w tym celu funkcji glPixelZoom. Ta funkcja przyjmuje dwa parametry zmiennoprzecin-kowe, określające współczynniki skalowania obrazu w poziomie i w pionie:
glPixelZoom(l.O, 1.0); // Bez skalowania glPixelZoom(-l.O, 1.0); // Odbicie lustrzane w poziomie glPixelZoom(l.0, -2.0); // Odbicie lustrzane w pionie i podwojenie
// wysokości glPixelZoom(0.33, 0.33); // Zmniejszenie obrazka do 1/3 wielkości
Jak widać, funkcja glPixelZoom pozwala na skalowanie i odwracanie obrazka prawie dowolnie. Jednak aby osiągnąć efekty nieliniowe, takie jak efekt pofalowanej wody czy korekcję perspektywy, musisz użyć mapowania tekstury (rozdział 12).
Wykrawanie obszarów
Funkcja glPixelStore może zostać użyta do wydzielenia jedynie części obrazu. Na przykład, aby wyświetlić centralny obszar o rozmiarze 300 x 300 pikseli z obrazu o rozmiarze 540 x 480 pikseli, mógłbyś użyć poniższego kodu:
glPixelStore(GL_UNPACK_ROW_LENGTH, 640); glPixelStore(GL_UNPACK_SKIP_PIXELS, (640 - 300) /2); glPixelStore(GL_UNPACK_SKIP_ROWS, (480 - 300) /2); glDrawPixels(300, 300, GL_RGB, GL_ONSIGNED_BYTE, BitmapBits);
W tym przykładzie wartość GL_UNPACK_ROW_LENGTH określa szerokość oryginalnego obrazu w pikselach. Ustaw ją, gdy szerokość określona w funkcji glDrawPixels różni się od szerokości obrazu.
GL_UNPACK_SKIP_PIXELS określa ilość pikseli do pominięcia z lewej strony obrazu. Pomijamy pierwsze (640 - 300) / 2, czyli 170 pikseli z lewej strony, w celu wyświetlenia środka obrazu.
Rozdział 11. * Grafika rastrowa__________________________________363
GL_UNPACK_SKIP_ROWS jest podobne, z tym że określa ilość wierszy pikseli do pominięcia. Zwykle ta wartość reprezentuje ilość wierszy do pominięcia z dołu obrazu, ale możesz to zmienić określając w funkcji glPixelZoom ujemną wartość skalowania w pionie.
UWAGA:
Atrybuty GL_UNPACK_ROW_LENGTH, GL_UNPACK_SKIP_PIXELS oraz GL_UNPACK_SKIP_ROWS odnoszą się do oryginalnych rozmiarów pixmapy, nie do rozmiarów po przeskalowaniu!
Odczytywanie pixmap z ekranu
OpenGL posiada funkcję o nazwie glReadPixels, przeznaczoną do odczytywania obrazu z ekranu. Poza jej oczywistym zastosowaniem do zachowania utworzonego obrazu na dysku, ta funkcja może zostać użyta w celu osiągnięcia ciekawych efektów mapowania tekstury.
W odróżnieniu od glDrawPixels, funkcja glReadPixels ignoruje bieżącą pozycję rastra i wymaga podania współrzędnych (x, y) widoku dla lewego dolnego rogu obrazu, który ma zostać odczytany. Sposób odczytania widoku do bitmapy Windows, nadającej się do zapisania na dysku lub użycia jako tekstury, przedstawia listing 11.8.
Listing 11.8. Funkcja ReadDIBitmap_______________________________________
'ReadDIBitmap()' - Odczytuje bieżący widok OpenGL do 24-bitowej bitmapy RGB.
Przy sukcesie zwraca piksele bitmapy, a NULL w razie niepowodzenia i
void *
ReadDIBitmap(BITMAPINFO **info) /* Wy - Nagłówek bitmapy */
long i, j,
bitsize, /* Całkowity rozmiar bitmapy */ width; /* Wyrównana szerokość wiersza */
GLint viewport[4]; /* Bieżący widok */
void *bits; /* Bity RGB */
GLubyte *rgb, /* Zmienne pętli RGB */ temp; /* Zmienna tymczasowa */
/*
* Odczyt bieżącego widoku...
*/
glGet!ntegerv(GL_VIEWPORT, viewport);
/*
* Zaalokowanie pamięci na nagłówek
*/
364
Część II » Używanie OpenGL
if ((*info NULL)
(BITMAPINFO *)malloc(sizeof(BITMAPINFOHEADER)))
V
Za mało pamięci na nagłówek
return (NULL) ;
width = viewport[2] * 3;
/* Rzeczywista szerokość
width
(width + 3) & ~3;
^wiersza */ /* Wyrównanie do 4 bajtów */
bitsize = width * viewport [3] ; /* Rozmiar wyrównanej bitmapy */ if ( (bits = calloc (bitsize, D) == NULL)
/*
Brak pamięci na bitmapę
*/
free(*info) ; return (NULL) ;
* Odczytanie pikseli z bufora ramki
*/
glFinishO; /* Zrzut poleceń OpenGL */
glPixelStorei(GL_PACK_ALIGNMENT, 4); // Wymuszenie 4-bajtowego
// wyrównania
glPixelStorei(GL_PACK_ROW_LENGTH, 0); glPixelStorei(GL_PACK_SKIP_ROWS, 0); glPixelStorei(GL_PACK_SKIP_PIXELS, 0) ;
glReadPixels(O, O, viewport[2], viewport[3], GL_RGB, GL_UNSIGNED_BYTE, bits);
Zamiana czerwieni z niebieskim w bitmapie
for (i = 0; i < viewport[3]; i ++)
for (j = O, rgb = ((GLubyte *)bits) + i * width; j < viewport[2]; j ++, rgb += 3)
temp = rgb[0];
rgb[0] = rgb[2];
rgb[2] = temp;
Na koniec wypełnienie nagłówka bitmapy
(*info)->bmiHeader.biSize (*info)->bmiHeader.biwidth (*info)->bmiHeader.biHeight
sizeof(BITMAPINFOHEADER) yiewport[2]; viewport[3];
Rozdział 11. » Grafika rastrowa __________________________________ 365
(*info) ->bmiHeader .biPlanes = 1; (*info) ->bmiHeader .biBitCount = 24; (*info) ->bmiHeader .biCompression = BI_RGB; (*info) ->bmiHeader .biSizelmage = bitsize; (*info)->bmiHeader.biXPelsPerMeter = 2952; /* 75 DPI */ (*info)->bmiHeader.biYPelsPerMeter = 2952; /* 75 DPI */ (*info)->bmiHeader .biClrUsed = 0; (*inf o) ->bmiHeader .biClrlmportant = 0;
return (bits) ;
Pierwszą rzeczą, którą powinieneś zrobić, jest wyznaczenie rozmiarów bieżącego widoku, jak pokazano poniżej, za pomocą funkcji glGetlntegery. (Ta funkcja zostanie opisana w rozdziale 14). Powoduje to umieszczenie początku X, początku Y, rozmiaru X oraz rozmiaru Y w tablicy widoku, opisanej w tabeli 1 1.3.
/*
* Odczyt bieżącego widoku. . .
*/
glGet!ntegerv(GL_VIEWPORT, viewport) ;
Tabela 11.3.
Definicje tablicy widoku
Indeks Opis
O Początek widoku X (w pikselach)
1 Początek widoku Y (w pikselach)
2 Szerokość widoku (w pikselach)
3 Wysokość widoku (w pikselach)
Gdy masz już rozmiar widoku, możesz zaalokować pamięć dla pixmapy. Ważne jest, aby pamiętać, że bitmapy Windows (i domyślnie pixmapy OpenGL) muszą zaczynać każdą linię na granicy czterech bajtów. Aby to zapewnić, wykonujemy poniższą sztuczkę:
width = viewport[2] * 3; /* Rzeczywista szerokość wiersza */ width = (width +3) & ~3; /* Wyrównanie do 4 bajtów */
Musimy zaokrąglić wyliczoną rzeczywistą szerokość widoku (w tym przypadku 3 bajty dla każdego piksela szerokości) do najbliższej 32-bitowej (czyli 4-bajtowej) granicy. Tak więc całkowitym rozmiarem bitmapy staje się
bitsize = width * viewport [3] ; /* Rozmiar wyrównanej bitmapy */
Po zaalokowaniu pamięci dla pixmapy, wywołujemy funkcję glReadPixels w celu odczytania zawartości bieżącego widoku, po czym wypełniamy strukturę BITMAPINFO-HEADER wszystkimi potrzebnymi informacjami.
366____________________________________Część II » Używanie OpenGL
Kopiowanie pixmap
OpenGL udostępnia także funkcję przeznaczoną do kopiowania obszaru na ekranie w inne miejsce - na przykład w celu przewinięcia widoku lub zastosowania efektu szkła powiększającego:
int mousex, mousey;
glReadBuffer(GL_FRONT) ;
glDrawBuffer(GL_FRONT);
glPixelZoom(2.0, 2.0);
glRasterPos2i(O, 0) ;
glCopyPixels(mousex - 8, mousey - 8, 16, 16, GL_COLOR) ;
Funkcja glCopyPixels kopiuje piksele ze wskazanego miejsca do bieżącej pozycji rastra:
void glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, ^GLenum type)
Parametry x i y określają lewy dolny róg obszaru, który ma zostać skopiowany. Parametry width i height określają rozmiar kopiowanego obrazu. Piksele są kopiowane z podanej pozycji (x, y) w miejsce określone bieżącą pozycją rastra. Argument type określa, jakie wartości będą kopiowane. Dla większości aplikacji typem pikseli będzie wartość GL_COLOR, oznaczająca kopiowanie indeksów kolorów lub wartości RGB.
Skalowanie pikseli odnosi się do pikseli wyjściowych, a nie docelowych. W powyższym przykładzie, obraz 16 x 16 pikseli zostanie skopiowany do lewego dolnego rogu okna, po czym zostanie przeskalowany do rozmiarów 32 x 32 piksele. Przesunięcia i rozmiary podane w wywołaniu funkcji glPixelStore nie wpływają na działanie funkcji glCopyPixels; wpływają za to zmiany dokonane funkcjami glPixelTransfer i glPixelMap,
Przeglądarka plików bitmap
Gdy poznaliśmy już wszystkie dostępne funkcje związane z bitmapami, spróbujmy napisać program OpenGL będący przeglądarką plików .BMP w Windows. Zadania naszego programu są łatwe do opisania:
* Ładowanie dowolnego pliku .BMP Windows;
* Skalowanie obrazka do bieżącego rozmiaru okna;
* Dostarczenie prostych kontrolek do zmiany jasności obrazu i korekcji gamma;
* Pokazywanie powiększonego widoku obszaru pod wskaźnikiem myszy;
* Zapisywanie wyświetlanego obrazka na dysku;
* Wydrukowanie wyświetlanego obrazka.
Pełny kod tego programu znajduje się w pliku OGLYIEW.C na płytce CD-ROM, w folderze tego rozdziału.
367
mGL
Rozdział 11. » Grafika rastrowa
Pliki bitmap w Windows
mię kła
Zanim zaczniemy tworzyć kod, przyjrzyjmy się formatowi plików bitmap w Windows. Mimo swoich ograniczeń, pliki .BMP w Windows są prawdopodobnie najpowszechniej używanymi plikami graficznymi w komputerach osobistych, mogącymi przechowywać obrazy zawierające od 2 do ponad szesnastu milionów kolorów. Z kilkoma wyjątkami, w plikach .BMP nie stosuje się kompresji danych, dzięki czemu można je bardzo łatwo odczytywać i zapisywać w programach, także w programach OpenGL.
Plik .BMP jest podzielony na trzy lub cztery sekcje, w zależności od ilości występujących w nim kolorów (Rysunek 11.3). Wszystkie pliki .BMP rozpoczynają się od struktury BITMAPFILEHEADER, zawierającej sygnaturę (łańcuch „BM"), całkowity rozmiar pliku oraz offset do danych obrazu. Ta struktura jest zdefiniowana następująco:
typedef struct tagBITMAPFILEHEADER
WORD bfTyep; DWORD bfSize; WORD bfReservedl; WORD bfReserved2; DWORD bfOffBits; BITMAPFILEHEADER;
// "BM"
// Rozmiar pliku w bajtach
// Zarezerwowane, zawsze O
// Zarezerwowane, zawsze O
// Offset do danych obrazu, w bajtach
Rysunek 11.3.
Organizacja pliku .BMP
Plik bitffiopy RGB
Plik bitmapy i pdelq
BITMAPFILEHEADER
BITMAPFILEHEADER
BITMAPINFO
BITMAPINFO
Pozycje PALETTENTRY
Dane bitmapy/pxeli
Dane bitmapy/pxeli
Po nagłówku pliku następuje struktura BITMAPINFOHEADER, opisująca zawartość pliku:
typedef struct tagBITMAPINFOHEADER
DWORD biSize; LONG biWidth; LONG biheight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizelmage; LONG biKPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrlmportant; BITMAPINFOHEADER;
// Rozmiar struktury w bajtach
// Szerokość obrazka w pikselach
// Wysokość obrazka w pikselach
// Ilość planów koloru (zawsze 0)
// Ilość bitów koloru
// Rodzaj zastosowanej kompresji
// Rozmiar obrazka w bajtach
// Ilość pikseli na metr w poziomie
// Ilość pikseli na metr w pionie
// Ilość wykorzystanych kolorów
// Ilość kolorów znaczących
368____________________________________Część II » Używanie OpenGL
W przypadku obrazów z indeksowanymi kolorami (z paletą kolorów), po strukturze BITMAPINFOHEADER występuje paleta definiująca każdy kolor w obrazku. Zaraz potem następuj ą dane obrazu.
Odczyt plików .BMP
Ponieważ format pliku .BMP jest tak prosty, odczyt takich plików jest prawie banalny. Zaczynamy od otwarcia pliku i wczytania struktury BITMAPFILEHEADER.
if ((fp = fopen(filename, "rb")) == NULL) return (NULL);
freadlsheader, sizeof(BITMAPFILEHEADER), l, fp) ;
if (header.bfType != 'MB') /* Sprawdzenie sygnatury MB... */ { /*
* To nie jest plik bitmapy - zwróć NULL...
*/
fclose(fp); return (NULL); };
Jeśli nagłówek wygląda poprawnie, możemy odczytać strukturę BITMAPINFO, łącznie z definicją palety.
infosize = header.bfOffBits - sizeof(BITMAPFILEHEADER); fread(*info, l, infosize, fp);
Na koniec zaś odczytujemy dane bitmapy i zamykamy plik.
if ((bitsize = (*info)->bmiHeader.biSizelmage) == 0) bitsize = ((*info)->bmiHeader.biWidth *
(*info)->bmiHeader.biBitCount +7) / 8 * abs((*info)->bmiHeader.biHeight);
fread(bits, l, bitsize, fp); fclose(fp);
Listing l ł .9 zawiera pełny kod funkcji LoadDlBitmap, uzupełniony o sprawdzanie błędów. Listing 11.9. Funkcja LoadDIBitmap_____________________________________
void *
LoadDlBitmap(char *filename, /* We - Plik do wczytania */
BITMAPINFO **info) /* We - Informacje o bitmapie */ {
FILE *fp; /* Wskaźnik pliku */ void *bits; /* Bity bitmapy */ long bitsize, /* Rozmiar bitmapy */
infosize; /* Rozmiar nagłówka */ BITMAPFILEHEADER header; /* Nagłówek pliku */
Rozdział 11. » Grafika rastrowa_________________________________369
/*
* Próba otwarcia pliku w trybie binarnym
*/
if ((fp = fopenffilename, "rb")) -- NULL) return (NULL);
/*
* Odczyt nagłówka i informacji o bitmapie
*/
if (fread(Sheader, sizeof(BITMAPFILEHEADER), l, fp) < 1) { /*
* Nie powiodło się odczytanie nagłówka
*/
fclose(fp); return (NULL); };
if (header.bfType != 'MB') /* Sprawdzenie sygnatury MB... */ { /*
* To nie jest plik bitmapy - zwróć NULL... V
fclose(fp); return (NULL); };
infosize = header.bfOffBits - sizeof(BITMAPFILEHEADER); if «*info = (BITMAPINFO *)malloc(infosize)) == NULL) { /*
* Brak pamięci na nagłówek bitmapy
*/
fclose(fp); return (NULL); };
if (fread(*info, l, infosize, fp) < infosize) { /*
* Nie powiodło się odczytanie nagłówka bitmapy
*/
free(*info); fclose(fp); return (NULL); );
/*
* Gdy mamy wszystkie informacje, alokowanie pamięci na
* bitmapę i jej odczyt z dysku
*/
370
Część II * Używanie OpenGL
if ((bitsize = (*info)->bmiHeader.biSizelmage) == 0) bitsize = ((*info)->bmiHeader.biwidth *
(*info)->bmiHeader.biBitCount +7) / 8 * abs((*info)->bmiHeader.biHeight);
if ((bits = malloc(bitsize)) == NULL) { /*
* Brak pamięci
*/
free(*info); fclose(fp); return (NULL);
if (fread(bits, l, bitsize, fp) < bitsize) { /*
* Nie da się odczytać bitmapy - zwolnij pamięć
* i zwróć wartość NULL
*/
free(*info); free(bits); fclose(fp); return (NULL);
* OK, wszystko w porządku, zwróć wskaźnik do bitmapy
*/
fclose (fp); return (bits) ;
Zapis pliku .BMP
Aby zapisać plik .BMP, po prostu dodamy strukturę BITMAPFILEHEADER do bitmapy w pamięci i wszystko razem zapiszemy na dysk. Funkcję SaveDIBitmap zawiera listing 11.10.
Listing 11.10. Funkcja SaveDIBitmap__________ ________ ___
int
SaveDIBitmap(char *filename, /* We - Plik do zapisu */
BITMAPINFO *info, /* We - Informacje o bitmapie */ void *bits) /* We - Bity bitmapy */
FILE *fp;
long size,
infosize, bitsize;
BITMAPFILEHEADER header;
/* Wskaźnik pliku */
/* Rozmiar pliku */
/* Rozmiar nagłówka bitmapy */
/* Rozmiar danych bitmapy */
/* Nagłówek pliku */
l
Rozdział 11. » Grafika rastrowa_________________________________371
/*
* Próba otwarcia pliku do zapisu w trybie binarnym V
if ((fp = fopen(filename, "wb")) ™ NULL) return (-1);
if (info->bmiHeader.bisizelmage == 0) /* Wyznaczenie rozmiaru
<=>bitmapy */ bitsize = (info->bmiHeader.biWidth *
info->bmiHeader.biBitCount +7) / B * abs(info->bmiHeader.biHeight); else
bitsize = info->bmiHeader.bisizelmage;
infosize = sizeof(BITMAPINFOHEADER); switch (info->bmiHeader.biCompression) {
case BI_BITFIELDS :
infosize += 12; /* Dodanie trzech masek RGB doubleword */ if (info->bmiHeader.biClrUsed == 0) break; case BI_RGB :
if (info->bmiHeader.biBitCount > 8 &&
info->bmiHeader.biClrUsed == 0) break;
case BI_RLE8 : case BI_RLE4 :
if (info->bmiHeader.biClrUsed == 0)
infosize += (l « info->bmiHeader.biBitCount) * 4; else
infosize += info->bmiHeader.biClrUsed * 4; break; };
size = sizeof(BITMAPFILEHEADER) + infosize + bitsize;
/*
* Zapis nagłówka pliku, nagłówka bitmapy i danych bitmapy
*/
header.bfType = 'MB'; /* Nie przenośna... ech */
header.bfSize = size;
header.bfReservedl = 0;
header.bfReserved2 = 0;
header.bfOffBits = sizeof(BITMAPFILEHEADER) + infosize;
if (fwrite(Sheader, l, sizeof(BITMAPFILEHEADER), fp) <
sizeof(BITMAPFILEHEADER)) {
/*
* Nie powiodło się zapisanie nagłówka pliku V
fclose(fp); return (-1);
372___________________________________Część II » Używanie OpenGL
if (fwrite(info, l, infosize, fp) < infosize) ( /*
* Nie powiodło się zapisanie nagłówka bitmapy
*/
fclose(fp); return (-1) ; };
if (fwrite(bits, l, bitsize, fp) < bitsize) { /*
* Nie powiodło się zapisanie bitmapy
*/
fclose(fp); return (-1);
>;
/*
* OK, wszystko w porządku
*/
fclose(fp); return (0);
Drukowanie bitmap
Ponieważ Windows dostarcza kilku wygodnych funkcji do drukowania, dostępnych z wnętrza aplikacji, nie pozostaje nam nic innego jak wydrukować nasz obrazek wyświetlany w przeglądarce. W tym przykładowym programie będziemy korzystać ze standardowych usług druku GDI.
Pierwszą rzeczą, jaką musimy zrobić, jest wyświetlenie standardowego okna dialogowego Drukuj w Windows. Służy do tego poniższy fragment kodu:
memsetl&pd, O, sizeof(pd)); pd.lStructSize = sizeof(pd); pd.hwndOwner = owner; pd.Flags = PD_RETURNDC; pd.hlnstance = NULL; if (IPrintDlg(Spd)) return (0);
Jeśli funkcja PrintDlg zwróci wartość O, oznacza to, że użytkownik kliknął na przycisk Anuluj. W przeciwnym wypadku struktura PRINTDLG będzie zawierała uchwyt kontekstu urządzenia (HDC), którego będziemy mogli użyć do drukowania.
Teraz musimy rozpocząć zadanie drukowania.
di.cbSize - sizeof(DOCINFO); di.lpszDocName = "OpenGL Image"; di.lpszOutput = NULL; StartDoc(pd.hDC,
Rozdział 11. » Grafika rastrowa_________________________________373
Następnie musimy narysować bitmapę za pomocą funkcji StretchBlt, po czym zamknąć zadanie drukowania.
StretchBlt(pd.hDC, xoffset, yoffset, xsize, ysize, hdc, O, O, info->bmiHeader.biwidth, info c*->bmiHeader. biHeight, SRCCOPY);
EndPage(pd.hDC); EndDoc(pd.hDC);
Pierwsze cztery parametry funkcji StretchBlt obliczamy na podstawie rozmiaru drukowanej strony. Chcemy przeskalować obraz do rozmiarów strony, jednak z zachowaniem niezmiennego stosunku długości boków.
xsize = rect.right;
ysize = xsize * info->bmiHeader.biHeight / info->bmiHeader.biwidth;
if (ysize > rect.bottom)
{
ysize = rect.bottom;
xsize = ysize * info->bmiHeader.biwidth / info->bmiHeader.biHeight; };
Przesunięcia są obliczane jako połowa różnicy pomiędzy szerokościami a wysokościami:
xoffset = (rect.right - xsize) / 2; yoffset = (rect.bottom - ysize) / 2;
Zwykle wyświetliłbyś jeszcze dialog informujący o drukowaniu, ale w tym wypadku drukowanie w programie odbywa się tak szybko, że nie byłoby to użyteczne.
Pełny kod funkcji PrintDIBitmap został przedstawiony na listingu 11.11. Listing 11.11. Funkcja PrintDIBitmap____________________________
int
PrintDIBitmap(HWND owner, /* We - Okno nadrzędne */
BITMAPINFO *info, /* We - Nagłówek bitmapy */ void *bits) /* We - Dane bitmapy*/ {
PRINTDLG pd; /* Okno dialogu Drukuj */ long xsize, /* Rozmiar drukowanego obrazka */ ysize,
xoffset,/* Odstęp od krawędzi obrazka */ yoffset;
RECT rect; /* Prostokąt strony */ DOCINFO di; /* Informacje o dokumencie */ HDC hdc; /* Kontekst urządzenia dla bitmapy */ HBITMAP bitmap; /* Obraz bitmapy */ HBRUSH brush; /* Pędzel tła strony */ HCURSOR busy, /* Kursor zajętości */ oldcursor; /* Stary kursor */
/*
* Sprawdzenie zakresu
*/
if (info == NOLL || bits «= NULL) return (0);
374 Część II » Używanie OpenGL
/*
* Inicjowanie struktury PRINTDLG przed wyświetleniem standardowego
* okna dialogowego Drukuj w Windows
*/
memsetl&pd, O, sizeof (pd)); pd.lStructSize » sizeof (pd); pd.hwndOwner = owner; pd.Flags = PD_RETURNDC; pd.hlnstance = NULL; if ( IPrintDlg(spd) )
return (0); /* Użytkownik wybrał Anuluj... */
/*
* OK, użytkownik chce drukować, ustawmy więc kursor zajętości
* i zacznijmy drukowanie
*/
busy = LoadCursor (NULL, IDC_WAIT) ; oldcursor = SetCursor (busy) ;
SetMapModel pd.hDC, MM_TEXT) ; di.cbSize = sizeof (DOCINFO) ; di .IpszDocName = "OpenGL Image"; di.lpSzOutput = NULL;
StartDoc (pd.hDC, sdi) ; StartPage (pd.hDC) ;
/*
* Wyczyszczenie tła białym kolorem
*/
rect.top = 0;
rect.left - 0;
rect.right = GetDeviceCaps (pd.hDC, HORZRES);
rect.bottom = GetDeviceCaps (pd.hDC, YERTRES);
brush = CreateSolidBrush(OxOOffffff ) ;
FillRect (pd.hDC, Srect, brush);
/*
* Rozcia.gniecie bitmapy do rozmiarów kartki
*/
hdc = CreateCompatibleDC (pd.hDC) ;
bitmap = CreateDIBitmap (hdc, S (info->bmiHeader) , CBM_INIT, bits,
Oinfo,
DIB_RGB_COLORS) ; SelectObject (hdc, bitmap);
xsize = rect.right;
ysize = xsize * inf o->bmiHeader .biHeight / info->bmiHeader .biwidth;
if (ysize > rect.bottom)
{
ysize = rect.bottom;
xsize = ysize * info->bmiHeader .biwidth / info->bmiHeader. biHeight;
xoffset = (rect.right - xsize) / 2;
375
Rozdział 11. » Grafika rastrowa
yoffset = (rect.bottom - ysize) / 2;
StretchBlt(pd.hDC, xoffset, yoffset, xsize, ysize, hdc, O, O, info->bmiHeader.biWidth, info •*->bmiHeader. biHeight, SRCCOPY);
/*
* To wszystko. Koniec drukowanie i zwolnienie zaalokowanych zasobów.
*/
EndPage(pd.hDC); EndDoc(pd.hDC); DeleteDC(pd.hDC);
DeleteObject(bitmap); DeleteObject(brush); DeleteObject(busy); DeleteDC(hdc);
/*
* Odtworzenie kursora i powrót
*/
SetCursor(oldcursor); return (1);
Wyświetlanie bitmapy
Część OpenGL naszego przykładowego programu rozpoczyna się od wyświetlenia pliku .BMP. Podobnie jak większość programów OpenGL, także ten zaczyna się od ustalenia bieżącego widoku i przekształceń widoku.
glViewport (O, O, rect->right, rect->bottom) ;
glMatrixMode (GL_PROJECTION) ;
glLoadldentity () ;
glOrtho(0.0, rect->right - 1.0, 0.0, rect->bottom - 1.0, -1.0, 1.0);
glMatrixMode (GL_MODELVIEW) ;
Zaraz po tym rysujemy bitmapę. Skalujemy ją w celu dopasowania do okna, z zachowaniem stosunku boków 1:1. Poniższy kod powinien wyglądać bardzo znajomo — używałeś go w opisanej powyżej funkcji PrintDIBitmap:
xsize /
xsize = rect->right;
ysize = BitmapInfo->bmiHeader. biHeight Bitmaplnf o->bmiHeader . biWidth; if (ysize > rect->bottom)
ysize = rect->bottom;
}; xscale
xsize = BitmapInfo->bmiHeader .biWidth * ysize / Bitmaplnf o->bmiHeader . biHeight;
(float)xsize / (float)Bitmaplnfo->bmiHeader.biWidth;
376____________________________________Część II » Używanie OpenGL R
yscale = (float)ysize / (float)BitmapInfo->bmiHeader.biHeight;
xoffset = (rect->right - xsize) * 0.5; yoffset = (rect->bottom - ysize) * 0.5;
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixe!Zoom(xscale, yscale);
glRasterPos2i(xoffset, yoffset);
glDrawPixels(BitmapInfo->bmiHeader.biWidth, Bitmaplnfo->bmiHeader.biHeight, GL_RGB, GL_UNSIGNED_BYTE, BitmapBits);
Co ciekawe, funkcja Windows StretchBlt wyświetla bitmapy szybciej niż funkcja glDrawPi-xels. Oczywiście, StretchBlt nie uwzględnia działania funkcji glPixelMap i glPixelTransfer.
Pebiy kod funkcji RepaintWindow przedstawia listing 11.12. Listing 11.12. Funkcja RepaintWindow_____________________________________
void
RepaintWindow(RECT *rect) /* We - Obszar roboczy okna */
{
GLint xoffset, /* Przesunięcie obrazu w poziomie */
yoffset; /* Przesunięcie obrazu w pionie */ GLint xsize, /* Szerokość przeskalowanego obrazu */
ysize; /* Wysokość przeskalowanego obrazu */ GLfloat xscale, /* Skalowanie w poziomie */ yscale; /* Skalowanie w pionie */
/*
* Wyzerowanie widoku i wyczyszczenie okna białym tłem
*/
glViewport(O, O, rect->right, rect->bottom);
glMatrixMode(GL_PROJECTION);
glLoadldentity();
glOrtho(0.0, rect->right - 1.0, 0.0, rect->bottom - 1.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glClearColord.O, 1.0, 1.0, 1.0); glClear(GL_COLOR_BUFFER_BIT);
/*
* Jeśli załadowaliśmy obraz bitmapy, przeskalowanie go do rozmiarów
* okna
*/
if (BitmapBits != NOLL) {
xsize = rect->right; ysize = BitmapInfo->bmiHeader.biHeight * xsize /
Bitmaplnfo->bmiHeader.biWidth; if (ysize > rect->bottom) {
ysize = rect->bottom;
xsize = Bitmaplnfo->bmiHeader.biWidth * ysize /
Rozdział 11. » Grafika rastrowa_________________________________377
BitmapInfo->bmiHeader.biHeight; };
xscale = (float)xsize / (float)BitmapInfo->bmiHeader.biwidth; yscale - (float)ysize / (float)BitmapInfo->bmiHeader.biHeight;
xoffset = (rect->right - xsize) * 0.5; yoffset = (rect->bottom - ysize) * 0.5;
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glPixelZoom(xscale, yscale); glRasterPos2i(xoffset, yoffset); glDrawPixels(BitmapInfo->bmiHeader.biwidth, Bitmaplnfo->bmiHeader.biHeight, GL_RGB, GL_UNSIGNED_BYTE, BitmapBits); };
glFinishO ;
Podsumowanie
W tym rozdziale poznałeś większość zagadnień dotyczących funkcji OpenGL operujących na bitmapach. Poza prostymi zastosowaniami związanymi z wyświetlaniem bitma-powych znaków, bitmapy mogą być wykorzystywane jako pełnokolorowe obrazy do tworzenia tła okna lub tekstur (które zostaną omówione w następnym rozdziale). Funkcje OpenGL, takie jak glPixelMap, glPixelTransfer czy glPixelZoom pozwalają także na uzyskanie wielu specjalnych efektów.
Podręcznik
glCopyPixels
Przeznaczenie Kopiuje prostokątny blok pikseli w buforze ramki.
Plik nagłówkowy <gl.h>
Składnia void glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height,
GLenum type);
Opis Ta funkcja kopiuje dane pikseli ze wskazanego obszaru w buforze ramki
do obszaru wskazywanego przez bieżącą pozycję rastra. Jeśli bieżąca
pozycja rastra nie jest poprawna, żadne dane nie są kopiowane.
Na działanie funkcji glCopyPixels wpływają wywołania funkcji glPixelMap, glPixelTransfer oraz glPixelZoom.
378
Część II * Używanie OpenGL
Parametry x
width
height
type
Zwracana wartość Przykład Patrz także
GLint: Współrzędna pozioma lewego dolnego rogu kopiowanego obszaru.
GLint: Współrzędna pionowa lewego dolnego rogu kopiowanego obszaru.
GLsizei: Szerokość kopiowanego obszaru w pikselach. GLsizei: Wysokość kopiowanego obszaru w pikselach.
GLenum: Typ danych, które mają zostać skopiowane. Dostępne są poniższe wartości:
GL_COLOR Wartości bufora kolorów GL_STENCIL Wartości bufora szablonu GL_DEPTH Wartości bufora głębokości
Brak.
Przejrzyj kod programu OGLYIEW.C
glPixelMap, glPixelStore, glPixelTransfer, glPixelZoom
glDrawPixels
Przeznaczenie Plik nagłówkowy Składnia
Opis
Rysuje prostokątny blok pikseli w buforze ramki.
void glDrawPixels(GLsizei width, GLsizei height, GLenum formay, GLenum type, const GLvoid *pixels);
Ta funkcja kopiuje dane pikseli z pamięci do obszaru wskazywanego przez bieżącą pozycję rastra w buforze ramki. Do ustawiania pozycji rastra służy funkcja glRasterPos. Jeśli bieżąca pozycja rastra nie jest poprawna, żadne dane nie są kopiowane.
Poza argumentami/O/Twa/ i type, na kodowanie pikseli i przetwarzanie ich przed umieszczeniem w buforze ramki wpływa także kilka innych parametrów. Zajrzyj do opisu funkcji glPixelMap, glPixelStore, glPixelTransfer oraz glPixelZoom.
Parametry width height
GLsizei: Szerokość obrazu w pikselach. GLsizei: Wysokość obrazu w pikselach.
379
Rozdział 11. » Grafika rastrowa
format
type
pbcels
Zwracana wartość Znane błędy
Przykład Patrz także
GLenum: Przestrzeń kolorów rysowanych pikseli. Dostępne są poniższe
wartości:
GL_COLOR_INDEX Piksele indeksu koloru
GL_LUMINANCE Piksele skali szarości
GL_LUMINANCE_ALPHA Skala szarości + piksele alfa (dwa komponenty)
GL_RGB Piksele RGB (trzy komponenty)
GL_RGBA Piksele RGBA (cztery komponenty)
GL_RED Piksele czerwieni
GL_GREEN Piksele zieleni
GL_BLUE Piksele niebieskiego
GL_ALPHA Piksele kanału alfa
GL_STENCIL JNDEKS Wartości bufora szablonu
GL_DEPTH_COMPONENT Wartości bufora głębokości
GLenum: Typ danych, które mają zostać skopiowane. Dostępne są poniższe wartości:
GLJ3YTE 8-bitowe wartości ze znakiem (od -128 do 127) GL_UNSIGNED_BYTE 8-bitowe wartości bez znaku (od O do 255) GL_BITMAP Obraz bitmapy (od O do 1) GL_SHORT 16-bitowe wartości ze znakiem (od -32 768 do 32 767)
GL_UNSIGNED_SHORT 16-bitowe wartości bez znaku (od O do 65 535)
GLJNT 32-bitowe wartości ze znakiem (od -2 147 483 648 do 2 147 483 647)
GL_UNSIGNED_INT 32-bitowe wartości bez znaku (od O do 4 294 967 295)
GL_FLOAT 32-bitowe wartości zmiennoprzecinkowe (GLfloat)
GLvoid*: Wskaźnik do danych obrazka.
Brak.
Obecnie parametr GL_UNPACK_ALIGNMENT funkcji glPixelStore jest ignorowany przez funkcję glDrawPixels.
Przejrzyj kod programu OGLYIEW.C
glPixelMap, glPixelStore, glPixelTransfer, glPixelZoom
glPixelMap
Przeznaczenie Definiuje tablicę przeglądania dla funkcji operujących na pikselach.
Plik nagłówkowy <gl.h>
380
Część II «• Używanie OpenGL
Składnia
Opis
void glPixelMapfv(GLenum map, GLtnt mapsize, const GLfloat *values);
void glPixelMapuiv(GLenum map, GLint mapsize, const GLuint
*values);
void glPixeIMapusv(GLenum map, GLint mapsize, const GLushort
*values);
Ta funkcja przygotowuje tablicę przeglądania (lookup table - LUT) dla funkcji glReadPixels, glTexImagelD oraz glTexImage2D. Tablice przeglądania, czyli tzw. odwzorowania, są używane tylko wtedy, gdy w funkcji glPixelTransfer zostanie włączona odpowiednia opcja, GL_MAP_COLOR lub GL_MAP_STENCIL. Odwzorowania są stosowane przed rysowaniem i po odczycie wartości z bufora ramki.
Parametry map
mapslze
values
Zwracana wartość Przykład Patrz także
GLenum: Rodzaj definiowanego odwzorowania. Dostępne są poniższe wartości:
GL_PIXEL_MAP_I_TO_I Odwzorowanie do indeksów kolorów GL_PIXEL_MAP_S_TO_S Odwzorowanie do wartości szablonu
GL_PIXEL_MAP_I_TO_R Odwzorowanie z indeksów kolorów na wartości czerwieni
GL_PIXEL_MAP_I_TO_G Odwzorowanie z indeksów kolorów na wartości zieleni
GL_PIXEL_MAP_I_TO_B Odwzorowanie z indeksów kolorów na wartości niebieskiego
GL_PIXEL_MAP_I_TO_A Odwzorowanie z indeksów kolorów na wartości alfa
GL_PIXEL_MAP_R_TO_R Odwzorowanie do wartości czerwieni GL_PIXEL_MAP_G_TO_G Odwzorowanie do wartości zieleni GL_PIXEL_MAP_B_TO_B Odwzorowanie do wartości błękitu GL_PIXEL_MAP_A_TO_A Odwzorowanie do wartości alfa
GLint: Rozmiar tablicy przeglądania.
GLfloat*, GLuint*, GLushort*: Tablica przeglądania.
Brak.
Przejrzyj kod programu OGLYIEW.C
glCopyPixels, glDrawPixels, glPixelStore, glPixelTransfer, glReadPixels, glTex!magelD, glTex!mage2D
glPixelStore
Przeznaczenie Określa sposób zapisu i odczytu pikseli z pamięci.
Plik nagłówkowy <gl.h>
381
Składnia Opis
Parametry pname
Rozdział 11. * Grafika rastrowa
void gIPixeIStorei(GLenum pname, GLint param); void glPixelStoref(GLenum pname, GLfloat param);
Ta funkcja określa sposób zapisu pikseli do pamięci funkcją glReadPixels oraz sposób ich odczytu funkcjami glDrawPixels, glTex!magelD oraz glTex!mage2D. Nie wpływa na działanie funkcji glCopyPixels.
GLenum: Parametr do ustawienia. Dostępne wartości są następujące:
GL_PACK_SWAP_BYTES* GLJTRUE Jeśli True, bajty
wszystkich wielobajtowych wartości zostają zamienione w pamięci.
Jeśli True, lewe bity w bitmapach znajdują się w pikselu O, a nie 7.
Ustala szerokość obrazu w pikselach. Jeśli O, używany jest argument width.
Ustala ilość pikseli obrazu do pominięcia w poziomie.
Ustala ilość pikseli obrazu do pominięcia w pionie.
Ustala wyrównanie dla linii obrazu. Patrz sekcja Znane błędy poniżej.
Jeśli True, bajty wszystkich wielobajtowych wartości są zamieniane miejscami podczas odczytu.
Jeśli True, lewy piksel bitmap odczytywany jest z bitu O, a nie 7.
GL FALSE
GL PACK LSB FIRST
GL PACK RÓW LENGTH O
GL PACK SKIP PDCELS O
GL PACK SKIP ROWS
GL PACK ALIGNMENT 4
GL_UNPACK_SWAP_BYTES GLJTRUE
(GLJTRUE dla little-endian, GL_FALSE dla big-endian)
GL UNPACK LSB FIRST GL FALSE
382
Część II » Używanie OpenGL
Ustala szerokość obrazu w pikselach. Jeśli O, używany jest argument width.
Ustala ilość pikseli obrazu do pominięcia w poziomie.
Ustala ilość pikseli obrazu do pominięcia w pionie.
Ustala wyrównanie dla linii obrazu. Patrz sekcja Znane błędy poniżej.
GL UNPACK RÓW LENGTH O
GL UNPACK SKIP PIXELS O
GL UNPACK SKIP ROWS
GL UNPACK ALIGNMENT
param
Zwracana wartość Znane błędy
Przykład Patrz także
GLint, GLfloat: Wartość parametru. Brak.
W obecnej chwili parametry GL_PACK_ALIGNMENT i GL_UNPACK_ALIGNMENT są ignorowane przez funkcję glPixelStore.
Przejrzyj kod programu BITMAP.C
glDrawPixels, glReadPixels, glTexImagelD, glTex!mage2D
glPixelTransfer
Przeznaczenie
Plik nagłówkowy Składnia
Opis
Parametry pname
Ustala opcje i tryb przenoszenia pikseli dla funkcji glCopyPixels, glDrawPixels, glReadPixels, glTex!magelD oraz glTexImage2D.
void glPixelTransferi(GLenum pname, GLint param); void glPixelTransferf(GLenum pname, GLfloat param);
Ta funkcja ustala opcje i tryb przenoszenia pikseli dla funkcji glCopyPixels, glDrawPixels, glReadPixels, glTexImagelD oraz glTex!mage2D.
GLenum: Parametr do ustawienia. Dostępne są poniższe wartości:
GL_MAP_COLOR Gdy ustawiony na GLJTRUE, włącza pixmapy zdefiniowane funkcją glPixelMap dla indeksów kolorów i wartości RGBA.
GL_MAP_STENCIL Gdy ustawiony na GLJTRUE, włącza pixmapy zdefiniowane funkcją glPixelMap dla wartości szablonu.
383
nGL
Rozdział 11. * Grafika rastrowa
param
Zwracana wartość Przykład Patrz także
GL_INDEX_SHIFT Określa wielkość bitowego przesunięcia indeksów kolorów. Wartości dodatnie przesuwają indeksy w lewo, wartości dodatnie - w prawo.
GL_INDEX_OFFSET Określa wartość, jaka zostanie dodana do każdego indeksu koloru.
GL_RED_SCALE Określa zmiennoprzecinkowy współczynnik skalowania dla czerwonej składowej koloru.
GL_RED_BIAS Określa wartość dodawaną do każdej czerwonej składowej koloru.
GL_GREEN_SCALE Określa zmiennoprzecinkowy współczynnik skalowania dla zielonej składowej koloru.
GL_GREEN_BIAS Określa wartość dodawaną do każdej zielonej składowej koloru.
GL_BLUE_SCALE Określa zmiennoprzecinkowy współczynnik skalowania dla niebieskiej składowej koloru.
GL_BLUE_BIAS Określa wartość dodawaną do każdej niebieskiej składowej koloru.
GL_ALPHA_SCALE Określa zmiennoprzecinkowy współczynnik skalowania dla wartości alfa.
GL_ALPHA_BIAS Określa wartość dodawaną do każdej wartości alfa.
GL_DEPTH_SCALE Określa zmiennoprzecinkowy współczynnik skalowania dla wartości bufora głębokości.
GL_DEPTH_BIAS Określa wartość dodawaną do każdej wartości bufora głębokości.
GLint, GLfloat: Wartość parametru.
Brak.
Przejrzyj kod programu OGLYIEW.C
glCopyPixels, glDrawPixels, glPixelMap, glReadPixels, glTex!magelD, glTex!mage2D
glPixelZoom
Przeznaczenie Plik nagłówkowy Składnia Opis
Określa skalę przy rysowaniu obrazka.
void glPixelZoom(GLfloat xfactor, GLfloat yfactor);
Ta funkcja ustala współczynniki skalowania dla funkcji glCopyPixels, glDrawPixels, glReadPixels, glTex!magelD oraz glTex!mage2D.
W przypadku odczytu obrazu z pamięci lub bufora ramki, piksele są skalowane przy użyciu algorytmu „najbliższy sąsiad". W przypadku funkcji glCopyPixels i glDrawPixels, przeskalowane piksele są rysowane
384
Część II » Używanie OpenGL
w buforze ramki w miejscu określonym bieżącą pozycją rastra.
W przypadku funkcji glReadPixels, piksele są zapisywane do wskazanego bufora w pamięci. Podczas odczytu powiększonego obrazka, jego rozmiar powinieneś obliczyć następująco:
int nowa_wysokosc, nowa_szerokosc; int szerokość, wysokość;
nowa_szerokosc = xfactor * szerokość + 0.5; nowa_wysokosc = yfactor * wysokość + 0.5;
Parametry xfactor
yfactor
Zwracana wartość Przykład Patrz także
GLfloat: Współczynnik skalowania w poziomie (1,0 oznacza brak skalowania).
GLfloat: Współczynnik skalowania w pionie (1,0 oznacza brak skalowania).
Brak.
Przejrzyj kod programu OGLYIEW.C
glCopyPixels, glDrawPixels, glReadPixels, glTex!magelD, glTex!mage2D
Przeznaczenie Plik nagłówkowy Składnia
Opis
glReadPixels
Odczytuje blok pikseli z bufora ramki.
void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels);
Parametry x
y
width
height
format
Ta funkcja kopiuje dane pikseli z bufora ramki do pamięci. Poza argumentami format i type, na kodowanie pikseli i przetwarzanie ich przed umieszczeniem w pamięci wpływa także kilka innych parametrów. Zajrzy] do opisu funkcji glPixelMap, glPixelStore oraz glPixelTransfer.
GLint: Pozioma współrzędna lewego dolnego rogu obszaru obrazu. GLint: Pionowa współrzędna lewego dolnego rogu obszaru obrazu. GLsizei: Szerokość obrazu w pikselach. GLsizei: Wysokość obrazu w pikselach.
GLenum: Przestrzeń kolorów odczytywanych pikseli. Dostępne są poniższe wartości:
GL_COLOR_INDEX Piksele indeksu koloru GL LUMINANCE Piksele skali szarości
385
Rozdział 11. » Grafika rastrowa
type
pbcels
Zwracana wartość Znane błędy
Przykład Patrz także
GL_LUMINANCE_ALPHA Skala szarości + piksele alfa (dwa komponenty)
GL_RGB Piksele RGB (trzy komponenty)
GL_RGBA Piksele RGBA (cztery komponenty)
GL_RED Piksele czerwieni
GL_GREEN Piksele zieleni
GL_BLUE Piksele niebieskiego
GL_ALPHA Piksele kanału alfa
GL_STENCIL JNDEKS Wartości bufora szablonu
GL_DEPTH_COMPONENT Wartości bufora głębokości
GLenum: Typ danych, które mają zostać skopiowane. Dostępne są poniższe wartości:
GL_BYTE 8-bitowe wartości ze znakiem (od -128 do 127) GL_UNSIGNED_BYTE 8-bitowe wartości bez znaku (od O do 255) GL_BITMAP Obraz bitmapy (od O do l) GL_SHORT 16-bitowe wartości ze znakiem (od -32 768 do 32 767)
GL_UNSIGNED_SHORT 16-bitowe wartości bez znaku (od O do 65 535)
GLJNT 32-bitowe wartości ze znakiem (od -2 147 483 648 do 2 147 483 647)
GL_UNSIGNED_INT 32-bitowe wartości bez znaku (od O do 4 294 967 295)
GL_FLOAT 32-bitowe wartości zmiennoprzecinkowe (GLfloat)
GLvoid*: Wskaźnik do bufora na dane obrazu.
Brak.
Obecnie parametr GL_PACK_ALIGNMENT funkcji glPixelStore jest ignorowany przez funkcję glReadPixels.
Przejrzyj kod programu BITMAP.C glPixelMap, glPixelStore, glPixelTransfer
Rozdział 12.
Mapowanie tekstur
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
* Owijać obrazki na wielokątach * glTexImagelD/glTexImage2D
(mapowanie tekstur)
* Używać plików .BMP jako tekstur * TextureLoadBitmap/TextureLoadMipmap
* Używać automatycznej generacji * glTexGen
współrzędnych tekstur
Mapowanie tekstur stanowi chyba największy postęp grafiki komputerowej w ciągu ostatnich kilku lat. OpenGL posiada funkcje mapowania tekstur, umożliwiające nakładanie obrazów na wielokąty w scenie. O tym, jak te obrazy zostaną nałożone, decydujesz wyłącznie ty.
Mapowanie tekstur jest stosowane w grach, włącznie z grą DOOM, w celu uzyskania realistycznych obrazów pomieszczeń i potworów. W odróżnieniu od OpenGL, w tych grach tekstury nakładane są metodą tzw. raycastingu. Choć w przypadku standardowych kart 2D raycasting jest dużo szybszy niż techniki nakładania stosowane w OpenGL, ogranicza się jednak do płaskich powierzchni w dwuwymiarowej płaszczyźnie, tzn. przy jego użyciu nie można spoglądać w górę i w dół. Mapowanie tekstur w OpenGL nie ma tych ograniczeń, lecz na standardowych kartach graficznych działa dużo wolniej.
Dobrą wiadomością jest to, że nowe, stosunkowo tanie karty z akceleratorami 3D, sprzętowo wspierają OpenGL i nakładanie tekstur. Gdy karta sprzętowo wspiera mapowanie tekstur, procesor nie musi zajmować się obliczeniami związanymi z przygotowaniem i nakładaniem obrazów - wszystkim zajmuje się karta graficzna.
Przykłady w tym rozdziale działaj ą na wszystkich kartach graficznych zgodnych z Windows. Jeśli twoja karta obsługuje 16- lub 24-bitowe tryby graficzne, powinieneś używać właśnie ich. Poza lepszym wyglądem scen, okazuje się, że w trybach 16- i 24-bitowych nakładanie tekstur przebiega szybciej.
388____________________________________Część II » Używanie OpenGL
Podstawy nakładania tekstur
Nakładanie tekstur w OpenGL jest dość proste. Na początku możemy stwierdzić, że każda tekstura jest jakimś obrazkiem.
Tekstura ID (jednowymiarowa) jest obrazkiem nieposiadającym wysokości lub szerokości; tekstury ID to obrazki w postaci paska o szerokości lub wysokości jednego pi-ksela. Być może sądzisz, że takie tekstury nie są zbyt użyteczne, ale w rzeczywistości mogą zastąpić bardziej konwencjonalne techniki kolorowania i cieniowania, przy tym znacznie przyspieszając proces renderowania! Rysunek 12.1 przedstawia jednowymiarową teksturę „ROY-G-BIV" (Red, Orange, Yellow - Green - Blue, Indigo, Yiolet) użytą do wyświetlenia tęczy. Obrazek tej tekstury to pasek pikseli (wartości kolorów) pokrywający spektrum kolorów w tęczy. Odpowiadająca temu scena nie korzystająca z tekstury zawierałaby siedem razy więcej wielokątów i wymagałaby dużo więcej czasu do wyrenderowania.
Rysunek 12.1.
Tęcza stworzona za pomocą
jednowym iarowej tekstury
Tekstura 2D (dwuwymiarowa) to obrazek szerszy i wyższy niż jeden piksel; zwykle jest ładowana z pliku .BMP Windows. Tekstury 2D są powszechnie używane w celu zastąpienia powierzchni o skomplikowanej geometrii (mnóstwie wielokątów), budynków, drzew itd. Tekstury 2D są używane także w celu stworzenia realistycznego tła, na przykład chmur na niebie, pokazanych na rysunku 12.2.
Użyte przez nas jedno- i dwuwymiarowe tekstury składały się z wartości koloru RGB. Tekstury mogą być tworzone także z indeksów kolorów lub poziomów szarości (lumi-nancji), mogą także zawierać wartości alfa (przezroczystości). Te ostatnie są użyteczne przy definiowaniu naturalnych obiektów, takich jak drzewa, gdyż wartość alfa może być użyta do uczynienia drzewa widocznym, a jednocześnie umożliwić prześwitywanie tła. Nauczymy się tego w rozdziale 16.
Niektóre karty obsługują w OpenGL także tekstury trójwymiarowe (wolumetryczne). Tekstury wolumetryczne są używane w różnych aplikacjach „skanerów" trzeciego wymiaru. Niestety, mała tekstura 256 x 256 x 256 pikseli w skali szarości zajmuje całe 16 MB pamięci.
389
Rozdział 12. * Mapowanie tekstur
Rysunek 12.2.
Dwuwymiarowa tekstura imitująca niebo
Choć obecnie stanowi rozszerzenie, być może teksturowanie 3D będzie obowiązkowym elementem specyfikacji OpenGL 1.1.
Definiowanie obrazów tekstur
Oczywiście, zanim zaczniesz rysować wielokąty, musisz w jakiś sposób zdefiniować teksturę. Obrazy tekstur są przechowywane tak samo jak bitmapy (omawiane w rozdziale 11 .)•
Uwaga na temat obrazów tekstur
Standard OpenGL wymaga, aby rozmiary obrazów tekstur odpowiadały potęgom liczby 2. Obrazy tekstur mogą posiadać także jeden lub dwa piksele ramki dookoła krawędzi, określające kolor wielokątów poza obrazem tekstury.
Definiowanie tekstur ID
W OpenGL do definiowania jednowymiarowych tekstur służy pojedyncza funkcja: glTex!magelD. Funkcja wymaga przekazania ośmiu argumentów:
void glTexImagelD(GLenum target, GLint level, GLint components, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels)
Argument target określa, która tekstura powinna zostać zdefiniowana; musi nim być GL_TEXTURE_1D. Argument level wskazuje poziom szczegółów obrazu tekstury i zwykle wynosi 0. Inne wartości są używane przy tekturach typu mipmap (opisywanych w dalszej części rozdziału). Argument components określa ilość wartości koloru użytych dla każdego piksela. W przypadku tekstur z indeksami kolorów, tą wartością musi być l. Wartości 3 i 4 są używane, odpowiednio, dla obrazów tekstur RGB i RGB A.
390
Część II » Używanie OpenGL
Argumenty width i border określają rozmiar obrazu tekstury. Wartość border określa ilość pikseli ramki, których OpenGL może oczekiwać (i używać) i może mieć wartość O, l lub 2. Parametr width określa szerokość głównego obrazu tekstury (bez pikseli ramki) i musi stanowić potęgę liczby 2.
Argument format wskazuje rodzaj wartości koloru w teksturze - GL_COLOR_INDEX, GLJJUMINANCE, GL_RGB lub GL_RGBA.
Przykład definiowania jednowymiarowej tekstury znajdziesz na listingu 12.1; ten kod pochodzi z programu TEX1D na płytce CD-ROM.
Listing 12.1. Definiowanie jednowymiarowej tekstury____
void
LoadAllTextures(void)
{
static unsigned char roygbiv_image[8][3] =
{ Ox3f, 0x00, Ox3f t,
{ Ox7f, 0x00, Ox7f ;,
{ Oxbf, 0x00, Oxbf },
{ 0x00, 0x00, Oxff },
{ 0x00, Oxff, 0x00 },
{ Oxff, Oxff, 0x00 },
{ Oxff, Ox7f, 0x00 },
{ Oxff, 0x00, 0x00 }
Ciemny fiolet (dla 8 kolorów...) */
Fiolet */
Indy go */
Błękit */
Zieleń */
Żółć
*/
*/ /
Pomarańcz Czerwień
glNewList(RainbowTexture = glGenLists(1), GL_COMPILE);
glTexParameteri(GL_TEXTURE_1D,
glTexParameteri(GL_TEXTURE_1D,
glTex!magelD(GL_TEXTURE_lD, O,
GL_UNSIGNED_BYTE,
glEndList O;
GL_TEXTURE_MAG_FILTER, GL_LINEAR) , GL_TEXTURE_MIN_FILTER, GL_LINEAR) , 3, 8, O, GL_RGB, roygbiv_image);
Ten przykładowy kod tworzy listę wyświetlania zawierającą obraz tekstury oraz żądany filtr powiększania i pomniejszania, GL_LINEAR. Filtr pomniejszania jest używany, gdy rysowany wielokąt jest mniejszy niż obraz tekstury, w tym przypadku 8 pikseli. Filtr powiększania jest używany, gdy wielokąt jest większy niż obraz tekstury. Stosując filtr GL_LINEAR, informujemy OpenGL, że przed narysowaniem czegokolwiek na ekranie, wartości kolorów obrazu tekstury powinny zostać liniowo zinterpolowane. Inne filtry, których możesz użyć jako parametru GL_TEXTURE_MIN_FILTER, zostały zebrane w tabeli 12.1.
Filtr GL_NEAREST zamiast interpolować wartości kolorów pikseli, pobiera najbliższy piksel obrazu tekstury. Filtrowaniem tekstur zajmiemy się w dalszej części rozdziału.
Rozdział 12. » Mapowanie tekstur_________________________________393^
Tabela 12.1.
Filtry obrazu tekstury
Filtr______________________Opis___________________________
GL_NEAREST Filtrowanie „najbliższy sąsiad"
GLJJNEAR Liniowa interpolacja
GL_NEAREST_MIPMAP_NEAREST Filtr mipmapy „najbliższy sąsiad"
GL_NEAREST_MIPMAP_LINEAR Liniowo interpolowana mipmapa
GL_LINEAR_MIPMAP_NEAREST Liniowa interpolacja mipmap
GL_LINEAR_MIPMAP_LINEAR Liniowa interpolacja interpolowanych mipmap
Definiowanie tekstur 2D
W celu zdefiniowania dwuwymiarowej tekstury należy wywołać funkcję glTex!mage2D. Ta funkcja, oprócz argumentów przekazywanych funkcji glTex!magelD, wymaga dodatkowo podania argumentu height (wysokość):
void glTex!mage2D(GLenum target, GLint level, GLint components,
GLsizei width, GLsizei height, GLint border,
GLenum format, GLenum type, const GLvoid *pixels)
Podobnie jak w przypadku funkcji glTex!magelD, argumenty width i height muszą być potęgami liczby 2.
Listing 12.2 zawiera przykład ładowania obrazu tekstury zachmurzonego nieba Listing 12.2. Definiowanie tekstury 2D______________________________________
void
LoadAllTextures(void)
{
BITMAPINFO *info; /* Nagłówek bitmapy */
void *bits; /* Dane bitmapy */
GLubyte *rgb; /* Piksele RGB bitmapy */
/*
* Próba załadowania bitmapy i zamiany jej na RGB...
*/
bits = LoadDIBitmap("sky.bmp", Sinfo); if (bits == NULL) return;
rgb = ConvertRGB(info, bits);
if (rgb == NULL)
(
free(info) ;
free(bits) ;
return;
392
Część II » Używanie OpenGL
glNewList(SkyTexture = glGenLists(1), GL_COMPILE) ;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
/*
* Definiowanie obrazu tekstury 2D
*/
/* Wymuszenie wyrównania do 4 bajtów */ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0) ;
glTex!mage2D(GL_TEXTURE_2D, O, 3, info->bmiHeader.biwidth, info->bmiHeader.biHeight, O, GL_RGB, GL_UNSIGNED_BYTE, rgb); glEndList() ;
* Zwolnienie bitmapy i obrazów RGB,
* Zwrócenie wartości O (bez błędu)
*/
free(rgb); free(info); free(bits);
Uwaga na temat tekstur
Jak się przekonasz, we wszystkich przykładach w tym rozdziale do przechowywania obrazów tekstur wykorzystuje się listy wyświetlania. Listy wyświetlania przyspieszają działanie statycznych poleceń graficznych, co dotyczy także obrazów tekstur. Dodatkowo, w nadchodzącym API OpenGL 1.1 zoptymalizowano obsługę tekstur przechowywanych na listach wyświetlania przez umieszczenie ich, jeśli to możliwe, w pamięci karty graficznej.
Rysowanie wielokątów z nałożoną teksturą
Gdy już zdefiniujesz teksturę, musisz jeszcze włączyć teksturowanie. Aby włączyć teksturowanie ID, użyj poniższego kodu:
glDisable(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_1D);
glTexEnvi(GL_TEXTURE_ENW, GL_TEXTORE_ENW_MODE, GL_DECAL);
Rozdział 12. » Mapowanie tekstur_________________________________393
Wywołanie glEnable włącza teksturowanie ID. Jeśli zapomnisz włączyć teksturowanie, tekstura nie zostanie nałożona na żaden z wielokątów! Funkcja glTexEnvi ustawia teksturowanie w tryb „kafelków", co oznacza, że tekstury zostaną nałożone na wielokąty jak kafelki.
Inne tryby teksturowania zostały zebrane w tabeli 12.2.
Tabela 12.2.
Tryby teksturowania dla parametru GL_TEXTURE_ENV_MODE
Tryb Opis
GL_MODULATE Piksele tekstury „filtrują" kolory istniejących pikseli na ekranie.
GL_DECAL Piksele tekstury zastępują istniejące piksele na ekranie.
GL_BLEND Piksele tekstury „filtrują" kolory istniejących pikseli na ekranie, będąc przy tym
łączone ze stałym kolorem.
W trybie teksturowania GL_MODULATE bieżący kolor piksela tekstury (lub jego luminancja) są mnożone przez kolor piksela na ekranie. W przypadku tekstur z jednym komponentem (luminancji), powoduje to otrzymanie filtra jasności, zmieniającego jasność pikseli na ekranie w zależności od obrazu tekstury. W przypadku tekstur z trzema komponentami (RGB), możesz generować efekt „kolorowych soczewek".
W odróżnieniu od teksturowania GL_MODULATE, teksturowanie w trybie GL_BLEND umożliwia połączenie sceny ze stałym kolorem, na podstawie obrazu tekstury. Trybu GL_BLEND będziesz używał zwykle przy rysowaniu obiektów takich jak chmury; stałym kolorem będzie biel, zaś obrazem tekstury będzie chmura.
Gdy zdefiniujesz tryb teksturowania, możesz przejść do rysowania wielokątów. Listing 12.3 przedstawia sposób rysowania tęczy z rysunku 12.1.
Listing 12.3. Rysowanie tęczy za pomocą tekstury J D__________________
glEnable(GL_TEXTURE_1D); glCallList(RainbowTexture); glBegin(GL_QUAD_STRIP);
for (th = 0.0; th <= M_PI; th += (0.03125 * M_PI»
{
// Dolna krawędź tęczy
x = cos(th) * 50.0; y = sin(th) * 50.0; z = -50.0; glTexCoordlf(0.0); glVertęx3f(x, y, z);
// Górna krawędź tęczy
x = cos(th) * 55.0;
y = sin(th) * 55.0;
z = -50.0;
394
Część II * Używanie OpenGL
glTexCoordlf (1.0) ; glVertex3f (x, y, z);
glEnd ( ) ;
Aby umieścić teksturę na tęczy, wywołaliśmy funkcję glTexCoord. W przypadku tekstur ID, możesz wywołać jedną z funkcji: glTexCoordlf, glTexCoordld, glTexCoordłs lub glTexCoordli. Wartość 0,0 reprezentuje piksel po lewej stronie obrazu, zaś wartość 1,0 reprezentuje piksel po prawej stronie. Wartości spoza zakresu są interpretowane różnie, zależnie od wartości parametru GL_TEXTURE_WRAP_S. Jeśli ten parametr ma wartość GL_CLAMP (domyślną), współrzędne tekstury są ograniczone do zakresu od 0,0 do 1,0 włącznie. Gdy wielokąt wystaje poza obraz tekstury, jest rysowany w kolorze (kolorach) krawędzi obrazu tekstury (patrz rysunek 12.3) lub w kolorze pikseli ramki tekstury, jeśli jest zdefiniowana. Współrzędne tekstur tradycyjnie określa się jako S i T lub (s, t) zamiast X i Y.
Rysunek 12.3.
Tekstury GL CLAMP
Obszar połączenia
Jeśli natomiast użyjesz wartości GL_REPEAT, obraz tekstury zostanie powtórzony na powierzchni wielokąta. Współrzędne tekstury są używane modulo l ,0 - to znaczy, że tekstura powtarza się w regularnych odstępach. Teksturowania GL_REPEAT można użyć w celu zredukowania rozmiarów obrazów tekstur na powtarzalnych powierzchniach. Wyzwaniem przy tego rodzaju teksturach jest zapewnienie, by krawędzie jednego kafelka idealnie pokrywały się z krawędziami następnego.
Automatyczne generowanie współrzędnych tekstury
Generowanie współrzędnych tekstur może być bardzo nużącym zadaniem. W dalszej części rozdziału poznasz funkcję glTexGen, automatycznie generującą współrzędne tekstur.
Mipmapy
Dotychczas posługiwaliśmy się jedynie obrazami z pojedynczymi teksturami. To znaczy, przy każdym rysowaniu wielokąta posługiwaliśmy się pojedynczym jedno- lub dwuwymiarowym obrazem. W przypadku większości scen to w zupełności wystarcza, jednak w scenach animowanych często potrzebny jest zmienny poziom szczegółów,
395
Rozdział 12. » Mapowanie tekstur
w zależności od odległości wielokąta od obserwatora. Na przykład, podczas spacerowania po wirtualnym pokoju mógłbyś zastosować obraz w wysokiej rozdzielczości przy patrzeniu na niego z bliska i jedynie szkic obrazu, przy patrzeniu na niego z daleka.
OpenGL obsługuje tekstury zawierające wiele obrazów, zwanych mipmapami. Przy stosowaniu mipmapowania wybierany jest obraz tekstury najbliższy rozmiarowi wielokąta na ekranie. Ładowanie mipmap trwa nieco dłużej niż zwykłych tekstur, ale efekty wizualne robią wrażenie. Dodatkowo, mipmapy mogą poprawić wydajność renderowania, gdyż zmniejszają konieczność stosowania filtru GL_LINEAR.
Co oznacza „mip" w słowie mipmap?
Słowo „mip" to po łacinie „wiele". Tak więc „mipmapping" oznacza „wiele obrazów".
Mipmapy definiuje się przez określenie specyficznego parametru level dla każdego z obrazów tekstury. W przypadku naszej tekstury ROY-G-BIY z poprzedniego przykładu, mógłbyś użyć poniższego kodu:
static unsigned char roygbiv_imageO[16][3];
static unsigned char roygbiv_imagel[8][3];
static unsigned char roygbiv_image2[4][3];
static unsigned char roygbiv_image3[2][3];
static unsigned char roygbiv_image4[1][3];
glNewList(RainbowTexture = glGenLists(1), GL_COMPILE);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) ;
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTex!magelD(GL_TEXTURE_lD, O, 3, 16, O, GL_RGB, GL_UNSIGNED_BYTE,
roygbiv_imageO); glTexImagelD(GL_TEXTURE_lD, l, 3, 8, O, GL_RGB, GL_UNSIGNED_BYTE,
roygbiv_imagel); glTexImagelD(GL_TEXTURE_lD, 2, 3, 4, O, GL_RGB, GL_UNSIGNED_BYTE,
roygbiv_image2); glTexImagelD(GL_TEXTURE_lD, 3, 3, 2, O, GL_RGB, GL_UNSIGNED_BYTE,
roygbiv_image3); glTexImagelD(GL_TEXTURE_lD, 4, 3, l, O, GL_RGB, GL_UNSIGNED_BYTE,
roygbiv_image4); glEndList();
Poziom obrazu jest określany jako drugi parametr funkcji glTexImagelD(). Obraz o poziomie O to obraz podstawowy tekstury, o najwyższej rozdzielczości. Obraz o poziomie l ma połowę rozmiaru obrazu podstawowego, itd. Rysując wielokąty za pomocą bitmap, powinieneś użyć jednego z filtrów zmniejszania (GL_TEXTURE_MIN_FIL-TER) z tabeli 12.3.
396_______________________________________Część II » Używanie OpenGL
Tabela 12.3.
Filtry zmniejszania
Filtr___________________Opis______________________________
GL_NEAREST_MIPMAP_NEAREST Używa obrazu najbliższego pod względem rozmiaru do
wielokąta na ekranie. Przy teksturowaniu tego obrazu stosuje filtr GL_NEAREST.
GL_NEAREST_MIPMAP_L1NEAR Używa obrazu najbliższego pod względem rozmiaru do
wielokąta na ekranie. Przy teksturowaniu tego obrazu stosuje filtr GL_LINEAR.
GL_LINEAR_MIPMAP_NEAREST Liniowo interpoluje pomiędzy dwoma obrazami najbliższymi
pod względem rozmiaru do wielokąta na ekranie. Przy teksturowaniu tego obrazu stosuje filtr GL_NEAREST.
GL_LINEAR_MIPMAP_LINEAR Liniowo interpoluje pomiędzy dwoma obrazami najbliższymi
pod względem rozmiaru do wielokąta na ekranie. Przy teksturowaniu tego obrazu stosuje filtr GL_LINEAR.
Filtry GL_LINEAR_MIPMAP_NEAREST oraz GL_LINEAR_MIPMAP_LINEAR mogą być bardzo drogie w sensie obniżenia wydajności. Filtr GL_NEAREST_ MIP-MAP_NEAREST wydajnością odpowiada mniej więcej filtrowi GL_NEAREST, lecz zwykle daje dużo lepsze rezultaty. Obrazy z mipmapy są wybierane przez porównanie rozmiaru wielokąta na ekranie z rozmiarami obrazów w mipmapie.
Aby ułatwić ci nieco życie, biblioteka narzędziowa OpenGL (GLU32.LIB) zawiera dwie funkcje automatycznie generujące mipmapy na podstawie pojedynczej tekstury o wysokiej rozdzielczości. W poniższym kodzie, funkcje gluBuildlDMipmaps oraz gluBuild2-DMipmaps zajmuj ą miejsce funkcji glTex!magelD i glTex!mage2D:
// Tekstura ID
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER,
GL_NEAREST_MIPMAP_LINEAR);
gluBuildlDMipmaps(GL_TEXTURE1D, 3, 8, O, GL_RGB, GL_UNSIGNED_BYTE, roygbiv_image);
// Tekstura 2D
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTDRE_MIN_FILTER,
GL_NEAREST_MIPMAP_NEAREST); gluBuild2DMipmaps(GL_TEXTURE2D, 3, info->bmiHeader.biwidth,
info->bmiHeader.biHeight, O, GL_RGB,
GL_UNSIGNED_BYTE, rgb);
Ponieważ funkcje gluBuildlDMipmaps i gluBuildlDMipmaps tworzą obrazy z jednego, podstawowego obrazu, wygląd pewnych obrazów w mniejszych rozdzielczościach może nie być najlepszy. Przypomina to rysowanie znaków tekstu w różnych rozdzielczościach - przeskalowanie bitmapy nie zawsze daje dobrze wyglądające rezultaty! Gdy natrafisz na tego rodzaju problem, utwórz mipmapę ręcznie.
Rozdział 12. * Mapowanie tekstur_________________________________397
Program oglądania terenu
Naszym projektem w tym rozdziale będzie program oglądania terenu, korzystający z pewnych omawianych technik nakładania tekstur. W tym programie zamierzamy osiągnąć następujące cele:
* Oglądanie scen obrazujących teren;
* Interaktywną edycję terenu w trójwymiarowej scenie;
+ Przelot nad terenem;
* Wydruk bieżącej sceny;
* Zapisanie bieżącej sceny do pliku .BMP.
Kod całego programu został przedstawiony na końcu rozdziału, tuż przed sekcją podręcznika. Kopia programu znajduje się na płytce CD-ROM, w folderze tego rozdziału. Dwukrotnie kliknij na ikonę programu TEXSCENE.EXE i sam sprawdź, jak działa!
Definiowanie terenu
W celu zachowania prostoty, nasz teren zdefiniujemy jako siatkę punktów wysokościowych z dodatkową informacją o teksturze, taką jak „to jest woda" czy „to jest góra". Każdy punkt siatki będzie także posiadał odpowiednią normalną, w celu zachowania realistycznego oświetlenia.
#define TERRAIN_SIZE 21
int TerrainType[TERRAIN_SIZE][TERRAIN_SIZE];
GLfloat TerrainHeight[TERRAIN_SIZE][TERRAIN_SIZE];
GLfloat TerrainNormal[TERRAIN_SIZE][TERRAIN_SIZE][3];
Tablica TerrainType zawiera rodzaj terenu w każdym punkcie, czyli jeden z poniższych identyfikatorów kontrolek z pliku definicji zasobów:
IDC_GRASS Trawa
IDC_WATER Woda
IDCJTREES Drzewa
IDC_ROCKS Skały
IDC_MOUNTAINS Góry
Rysowanie terenu
Na kontrolki rysowania terenu składa się okno dialogowe z paskiem narzędzi, zawierającym pięć przycisków służących do wybierania rodzaju terenu. Aby narysować teren, po prostu kliknij i przeciągnij przycisk do głównego okna (rysunek 12.4).
398
Część II * Używanie OpenGL
Rysunek 12.4.
Okno edycji terenu
Serce interfejsu rysowania znajduje się w funkcji DrawTerrain. Używamy w niej mechanizmu selekcji OpenGL w celu wyznaczenia punktów terenu, leżących pod wskaźnikiem myszy. Zamiast rysować teren na ekranie, renderowanie selekcji rejestruje „trafienia" wewnątrz obszaru selekcji (w tym przypadku wskaźnika myszy) do wskazanego bufora. W funkcji DrawTerrain rejestrujemy położenie (x, y) terenu w buforze selekcji, tak jak w dziecięcej kolorowance, według numerków (rysunek 12.5). Selekcje w OpenGL zostaną szerzej omówione w rozdziale 19.
Rysunek 12.5.
Wybór komórki terenu
J.
Gdy znamy położenie (x, y) terenu, w funkcji draw_cell ustalamy wysokość i typ jego punktów (listing 12.4).
Listing 12.4. Funkcja draw_cell_______________
void
draw_cell (int x, /* We - Położenie terenu X */ int y) /* We - Położenie terenu Y */ { /*
* Sprawdzenie zakresu terenu
*/
X >= TERRAIN_SIZE | y >= TERRAIN_SIZE)
if (x < O
y < O
return;
399
Rozdział 12. * Mapowanie tekstur
if (TerrainType[y][x] == TerrainCurrent) return; /* Już jest poprawnego typu */
TerrainType[y][x] = TerrainCurrent;
Wymuszenie odświeżenia okna...
InvalidateRect(SceneWindow, NULL, TRUE);
Ustawienie wysokości "komórki" terenu. Dla wody wysokość jest stała, WATER_HEIGHT. Dla innych typów dodajemy losowe odchylenie w celu uzyskania realistycznego obrazu.
switch (TerrainCurrent) {
case IDC_WATER :
TerrainHeight [y] [x] = WATER_HEIGHT;
break; case IDC_GRASS :
TerrainHeight [y] [x] = GRASS_HEIGHT + O.lf * (rand() % 5);
break; case IDC_TREES :
TerrainHeight [y] [x] = TREES_HEIGHT + 0.1 * (randf) % 5) ;
break; case IDC_ROCKS :
TerrainHeight [y] [x] = ROCKS_HEIGHT + O.lf * (rand() % 5);
break; case IDC_MOUNTAINS :
TerrainHeight [y] [x] = MOUNTAINS_HEIGHT + 0.15f * (rand() % 5) ;
break;
Wysokość punktów terenu typu IDC_WATER (wody jest ustawiana po prostu na WA-TER_HEIGHT (wysokość wody, 0,0). Dla innych typów terenu dodajemy przypadkową wartość odchylenia, dzięki czemu teren wygląda bardziej realistycznie. Gdy wybrana komórka zostanie narysowana, na podstawie nowych wysokości, za pomocą funkcji UpdateNormals przeliczamy normalne. Każda normalna jest obliczana przy użyciu punktów powyżej i na prawo od bieżącego punktu, według poniższego wzoru:
N = normalna
H = wysokość bieżącego punktu Hu = wysokość punktu powyżej Hr = wysokość punktu na prawo
Nx = (Hr - H) / |N|
Ny = l / |N|
Nz = (Hu - H) / |N|
To po prostu uproszczone obliczanie iloczynu wektorowego dla przystających komórek siatki terenu. Gdy zostaną przeliczone wszystkie normalne, ekran jest odrysowywany.
400 Część II » Używanie OpenGL
Rysowanie sceny
Gdy zajęliśmy się już obowiązkową pracą, możemy skoncentrować się na wyświetlaniu terenu. Pamiętajmy, że oprócz wyświetlania ładnego teksturowanego obrazka, chcemy także polatać nad terenem. Aby to osiągnąć, musimy rysować teren bez teksturowania - głównie dlatego, że teksturowanie na zwykłym komputerze PC przebiega zbyt wolno jak na potrzeby animacji. Gdy użytkownik nie lata (i nie rysuje terenu), zastosujemy teksturowanie. Zadbamy o to za pomocą paru linii warunkowych i kilku parametrów oświetlenia.
Ponadto, ponieważ rysowanie teksturowanej sceny przebiega wolniej niż podczas przelotu nad sceną, spróbujemy w jakiś sposób wskazać użytkownikowi, że program cokolwiek robi. Dla uproszczenia, podczas teksturowania będziemy rysować po prostu w przednim buforze (widocznym), zaś podczas lotu lub rysowania - w tylnym (niewidocznym). W ten sposób, gdy program będzie aktualizował teksturowaną scenę, użytkownik będzie widział postępy w rysowaniu. Więcej informacji na temat buforów znajdziesz w rozdziale 15.
Funkcja RepaintWindow obsługuje odrysowywanie terenu. Rozpoczyna od wybrania przedniego lub tylnego bufora (jak wspominaliśmy powyżej). Następnie czyści bity koloru i głębokości:
glViewport(O, O, rect->right, rect->bottom); glClearColor(0.5, 0.5, 1.0, 1.0); glEnable(GL_DEPTH_TEST);
if (Moving || Drawing) {
glDisable(GL_TEXTURE_2D);
glDrawBuffer(GL_BACK) ; }
else {
glEnable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glDrawBuffer(GL_FRONT); };
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Następnie jest rysowane niebo. Ze względu na wydajność, niebo jest rysowane tylko wtedy, gdy użytkownik nie lata i nie rysuje terenu. Ponieważ tło jest czyszczone kolorem jasnoniebieskim, nie ma z tym większego problemu. Niebo ma kształt piramidy z nałożoną teksturą SKY.BMP. przedstawiającą przyjemne, lekko zachmurzone niebo.
Po narysowaniu nieba, funkcja RepaintWindow zaczyna rysować teren. Użyty algorytm jest bardzo prosty i polega na generowaniu pasków czworokątów (kwadratów) wzdłuż punktów terenu. Każdy pasek używa innej tekstury lub materiału, więc dla każdego z nich musimy użyć pary funkcji glBegin/glEnd. Graficzny opis tego algorytmu przedstawia rysunek 12.6.
Rozdział 12. » Mapowanie tekstur_________________________________401
Rysunek 12.6.
Algorytm rysowania terenu
Jak widać, algorytm nie śledzi terenu dokładnie, jednak jest szybki i prosty w implementacji. Przegląda teren od lewej do prawej strony, od góry do dołu, zaczynając nowy prymityw GL_QUAD_STRIP za każdym razem, gdy zmienia się typ terenu. Po drodze ustala normalną i współrzędną tekstury dla każdego punktu terenu.
Automatyczne generowanie współrzędnych tekstur
Generowanie wszystkich tych współrzędnych tekstur może być bardzo żmudne. Na szczęście, OpenGL ma dla nas rozwiązanie! W kodzie rysunkowym zawarliśmy wywołania funkcji glTexCoord2i:
glTexCoord2i(x * 2, y * 2);
dla każdego punktu terenu. Jednak zamiast robić to dla każdego punktu, możemy użyć funkcji glTexGen w celu zdefiniowania współrzędnych S i T oznaczających położenie X i Z w scenie (Y jest używane do określania wysokości). W celu wygenerowania współrzędnych dla naszego terenu, możemy zastosować poniższy kod:
static GLint s_vector[4] = { 2, O, O, O } static GLint t_vector[4] = { O, O, 2, O }
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glTexGeniv(GL_S, GL_OBJECT_PLANE, s_vector);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glTexGeniv(GL_T, GL_OBJECT_PLANE, t_vector);
Tryb mapowania GL_OBJECT_LINEAR odwzorowuje współrzędne tekstury ze współrzędnych obiektu:
coordinate = X * vector[0] + Y * vector[l] + Z * vector[2] + W * vector[3]
Tablica wektor jest określona za pomocą funkcji glTexGen:
void glTexGeniv(GLenum coord, GLenum pname, GLint *params)
gdzie parametr coord określa, która współrzędna tekstury ma zostać wygenerowana, GL_S czy GL_T, zaś parametr pname wskazuje definiowany wektor; w tym przypadku GL_OBJECT_PLANE. Na koniec, tablica params określa wektor planu obiektu, używany do obliczania współrzędnych tekstury.
402_______________________________________Część II » Używanie OpenGL
Poprzedni kod dla naszego terenu wygenerowałby następujące współrzędne:
S = 2 * X T = 2 * Z
Aby OpenGL mógł użyć wygenerowanych współrzędnych, musimy włączyć generowanie współrzędnych tekstury:
glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T);
Plik TEXSCENE.C zawiera wersję przeglądarki terenu korzystającą z wygenerowanych współrzędnych tekstury. Ta sama technika może zostać zastosowana w przypadku jednowymiarowych obrazów tekstur. Dla obrazu ID współrzędną S wygenerujesz najprawdopodobniej z wysokości (Y), a dla koloru terenu, na podstawie wysokości terenu. Generowanie współrzędnych tekstur trwa zwykle krócej niż określanie ich ręcznie w trybie natychmiastowym, jednak trwa nieco dłużej w przypadku użycia list wyświetlania.
Lot nad terenem
Gdy użytkownik przelatuje nad terenem, musimy regulować prędkości jego lotu biorąc pod uwagę szybkość odświeżania sceny. Zamiast starać się zachować stałą prędkość aktualizacji - która może się zmieniać w zależności od karty graficznej i procesora - będziemy mierzyć czas, jaki upłynął od ostatniej aktualizacji do chwili obecnej. Funkcja FlyTerrain mierzy czas w sekundach pomiędzy każdym wywołaniem, po czym przesuwa punkt obserwacji w przód z określoną szybkością, zależną od czasu, jaki upłynął.
Podsumowanie
W tym rozdziale nauczyłeś się nakładać obrazy tekstur na wielokąty i inne prymitywy. Teksturowanie może w znacznym stopniu podnieść realizm tworzonych scen, a właśnie to sprawia, że praca z grafiką komputerową jest taka pasjonująca.
Dzięki użyciu funkcji glTexParameter możemy w znacznym stopniu wpłynąć na jakość obrazów tekstur nakładanych na wielokąty. Dzięki użyciu mipmap możemy zastosować wiele poziomów szczegółów tekstury, z zachowaniem dużej precyzji i szybkości rende-rowania. Liniowa interpolacja tekstur może poprawić pewne rodzaje obrazów, na przykład takich jak obraz nieba używany w przykładach w tym rozdziale.
Funkcja glTextGen znacznie ułatwia generowanie współrzędnych tekstur, gdyż oszczędza nam żmudnych lub niepotrzebnych obliczeń. Poprzez zastąpienie dużej ilości warunkowych wywołań funkcji glTexCoord, automatyczne generowanie współrzędnych upraszcza także programy, które muszą wyświetlać zarówno teksturowane, jak i niete-ksturowane wielokąty.
403
Rozdział 12. » Mapowanie tekstur
W przypadku gier i innych interaktywnych, animowanych scen, możesz stworzyć wersję z teksturowaniem lub bez, przynajmniej do czasu aż sprzętowe akceleratory 3D staną się bardziej powszechne.
Na koniec, pozostaje nam listing 12.5, zawierający kompletny kod programu przeglądarki terenu, TEXSCENE.C.
Listing 12.5. Przeglądarka teksturowanego terenu______________________________
tinclude "texture.h"
#include "texscene.h"
#include <stdarg.h>
#include <math.h>
#ifndef M_PI
łdefine M_PI (double)3.14159265358979323846
ttendif /* !M PI */
Stałe. . .
*/
łdefine TERRAIN_SIZE tdefine TERRAIN_EDGE #define TERRAIN_SCALE
łdefine GRASS_HEIGHT ttdefine WATER_HEIGHT tdefine TREES_HEIGHT ttdefine ROCKS_HEIGHT tfdefine MOUNTAINS HEIGHT
21
((TERRAIN_SIZE - 1) / 2)
(500.0 / TERRAIN_EDGE)
0.0 0.0 0.0 0.5 1.0
*/
HWND HPALETTE HDC HGLRC
GLuint
Zmienne globalne...
SceneWindow; ScenePalette; SceneDC; SceneRC;
SkyTexture,
GrassTexture,
RocksTexture,
WaterTexture,
TreesTexture,
MountainsTexture;
Okno sceny */
Paleta kolorów (jeśli potrzebna) */
Kontekst rysowania */
Kontekst renderowania OpenGL */
/* Obraz tekstury nieba
|
|
/* Trawa. .
|
. */
|
/* Skała. .
|
. */
|
/* Woda. . .
|
*/
|
/* Drzewa.
|
. . */
|
/* Góry. . .
|
*/
|
HBITMAP GrassDownBitmap, /* GrassUpBitmap, /* GrassSelectBitmap, /* RocksDownBitmap, /* RocksUpBitmap, RocksSelectBitmap, WaterDownBitmap,
Obraz wciśniętego przycisku trawy */ Obraz zwolnionego przycisku trawy */ Obraz zaznaczonego przycisku trawy */ ... V
404
Część II » Używanie OpenGL
WaterUpBitmap, WaterSelectBitmap, TreesDownBitmap, TreesOpBitmap, TreesSelectBitmap, MountainsDownBitmap, MountainsUpBitmap, MountainsSelectBitmap;
HWND TerrainWindow; /* Dialog terenu */ int TerrainCurrent = IDC_WATER; int TerrainType[TERRAIN_SIZE][TERRAIN_SIZE]; GLfloat TerrainHeight[TERRAIN_SIZE][TERRAIN_SIZE]; GLfloat TerrainNormal[TERRAIN SIZE][TERRAIN SIZE][3]
double GLboolean
POINT
GLfloat
Position[3] = { 0.0, TERRAIN SCALĘ, 0.0
/* Pozycja obserwatora */ GLfloat Heading =0.0, Pitch =0.0, Roli = 0.0;
MoveTime; /*
Moving - GL_FALSE, /*
Drawing = GL_FALSE; /*
CenterMouseKY; /*
Ostatni czas aktualizacji */ GLJTRUE jeśli lecimy */ GL_TRUE jeśli rysujemy */ Początkowa pozycja myszy */
*/
void
void
LRESULT CALLBACK
UINT CALLBACK
void
void
void
void
void
void
void
void
double
Funkcje lokalne...
DisplayErrorMessage(char *, ...);
MakePalette(int);
SceneProc(HWND, UINT, WPARAM, LPARAM);
TerrainDlgProclHWND, UINT, WPARAM, LPARAM),
InitializeTerrain(void);
LoadAllTextures(void);
LoadAllBitmaps(HINSTANCE);
DrawTerrain(int, int);
FlyTerrain(int, int);
RepaintWindow(RECT *);
SaveBitmapFile(void);
PrintBitmap(void);
GetClock(void);
/*
* 'WinMainO '
*/
int APIENTRY
WinMain (HINSTANCE hlnst,
HINSTANCE hPrev!nstance, LPSTR IpCmdLine, int nCmdShow)
MSG msg;
WNDCLASS we;
POINT pos;
Rozdział 12. » Mapowanie tekstur_________________________________405
/*
* Inicjowanie terenu dla trawy V
InitializeTerrain();
we.style = 0;
wc.lpfnWndProc = (WNDPROC)SceneProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hlnstance = hlnst;
wc.hlcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = 0;
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
we.IpszClassName = "Textured Scenę";
if (RegisterClass(swe) == 0) {
DisplayErrorMessage("Nie mogę zarejestrować klasy okna!");
return (FALSE); };
Scenewindow = Createwindow("Textured Scenę", "Teksturowany teren",
ws_overlappedwindow | ws_clipchildren | ows_clipsiblings,
32, 32, 400, 300,
NULL, NULL, hlnst, NULL);
if (Scenewindow == NULL) {
DisplayErrorMessage("Nie udało się utworzyć okna!");
return (FALSE); };
ShowWindow(Scenewindow, nCmdShow); UpdateWindow(Scenewindow);
/*
* Ładowanie bitmap przycisków, utworzenie okna dialogowego
* edycji terenu
*/
LoadAllBitmaps(hlnst);
TerrainWindow = CreateDialog(hlnst, <=>MAKEINTRESOURCE (IDD_TERRAIN_DIALOG) ,
Scenewindow, (DLGPROC)TerrainDlgProc);
// Główna pętla komunikatów
while (TRUE)
{
while (!Moving ||
PeekMessagetsmsg, NULL, O, O, PM_NOREMOVE) == TRUE) if (GetMessaget&msg, NULL, O, 0))
406____________________________________Część II » Używanie OpenGL
{
TranslateMessage(&msg); DispatchMessage(&msg);
else
return (1);
/* * Jeśli trzeba, obsługa lotu...
GetCursorPos(ipos); FlyTerrain(pos.x, pos.y); };
return (msg.wParam); }
/*
* 'DisplayErrorMessage()' - Wyświetla okno komunikatu błędu.
void
DisplayErrorMessage(char *format,// Łańcuch formatowania w stylu
=>printf ()
...) // Pozostałe argumenty
va_list ap; /* Wskaźnik argumentu */ char s[1024]; /* Łańcuch wyjściowy */
if (format == NOLL) return;
va_start(ap, format); vsprintf(s, format, ap) ; va_end(ap) ;
MessageBeep(MB_ICONEXCLAMATION);
MessageBox(NULL, s, "Błąd", MB_OK | MB_ICONEXCLAMATION); }
/*
* 'MakePalette()' - Jeśli trzeba, tworzy paletę kolorów.
*/
void
MakePalette(int pf) /* We - ID formatu pikseli */
PIXELFORMATDESCRIPTOR pfd;
LOGPALETTE *pPal;
int nColors;
int i,
rmax,
gmax,
bmax;
Rozdział 12. * Mapowanie tekstur _________________________________ 407
/*
* Sprawdzenie, czy paleta jest potrzebna...
*/
DescribePixelFormat (SceneDC, pf, sizeof (PIXELFORMATDESCRIPTOR) ,
if ( ! (pfd.dwFlags & PFD_NEED_PALETTE) ) {
ScenePalette = NULL;
return;
/*
* Alokowanie pamięci dla palety.
*/
nColors = l « pfd.cColorBits;
pPal = (LOGPALETTE *) malloc (sizeof (LOGPALETTE) +
nColors * sizeof (PALETTEENTRY) );
pPal->palVersion = 0x300; pPal->palNumEntries = nColors;
/*
* Odczyt maksymalnych wartości barw składowych i budowanie nColors ^kolorów
*/
rmax = (l « pfd. cRedBits) - 1;
gmax = (l « pfd.cGreenBits) - 1;
bmax = (l « pfd.cBlueBits) - 1;
for (i = 0; i < nColors; i ++) {
pPal->palPalEntry[i] .peRed = 255 * ( (i » pfd.cRedShift) & rmax) /
rmax;
pPal->palPalEntry[i] .peGreen = 255 * ((i » pfd.cGreenShift) & t*gmax) /
gmax;
pPal->palPalEntry[i] .peBlue = 255 * ( (i » pfd.cBlueShift) & ^bmaK) /
bmax ;
pPal->palPalEntry [i] .peFlags = 0;
/*
* Uworzenie, wybranie i realizacja palety
*/
ScenePalette = CreatePalette (pPal) ; SelectPalette (SceneDC, ScenePalette, FALSE) ; RealizePalette (SceneDC) ;
free(pPal) ;
408____________________________________Część II » Używanie OpenGL
/*
* 'SceneProc()' - Obsługa komunikatów okna sceny.
*/
LRESULT CALLBACK SceneProc(HWND hWnd, OINT uMsg, WPARAM wParam, LPARAM IParam) (
int pf; /* ID formatu pikseli */
PIKELFORMATDESCRIPTOR pfd; /* informacje o formacie pikseli */
PAINTSTRUCT ps;
RECT rect;
switch (uMsg)
{
case WM_CREATE :
// Pobranie kontekstu urządzenia i renderowania, // przygotowanie obszaru klienta dla OpenGL
SceneDC = GetDC(hWnd);
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER; // Dla OpenGL
pfd.dwLayerMask = PFD_MAIN_PLANE; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 0; pfd.cDepthBits = 32; pfd.cStencilBits = 0; pfd.cAccumBits = 0;
pf = ChoosePixelFormat(SceneDC, Spfd); if (pf == 0)
DisplayErrorMessage("Program nie mógł znaleźć
^odpowiedniego formatu pikseli!"); else if (!SetPixelFormat(SceneDC, pf, Łpfd))
DisplayErrorMessage("Program nie mógł ustawić
•^odpowiedniego formatu pikseli!");
MakePalette(pf);
SceneRC = wglCreateContext(SceneDC); wglMakeCurrent(SceneDC, SceneRC);
// Ładowanie obrazów tekstur do list wyświetlania...
LoadAllTextures(); break;
Rozdział 12. * Mapowanie tekstur_________________________________409
case WM_SIZE : case WM_PAINT :
// Odrysowanie obszaru klienta...
BeginPaint (hWnd, sps);
GetClientRect(hWnd, Srect); RepaintWindow(srect);
EndPaint(hWnd, &ps); break;
case WM_COMMAND : /*
* Obsługa menu...
*/
switch (LOWORD(wParam)) {
case IDM_FILE_SAVEAS :
SaveBitmapFile();
break; case IDM_FILE_PRINT :
PrintBitmapt);
break; case IDM_FILE_EXIT :
DestroyWindow(SceneWindow);
break;
case IDM_WINDOW_TERRAIN : /*
* Włączenie lub wyłączenie okna terenu...
*/
if (GetMenuState(GetMenu(SceneWindow), IDM_WINDOW_TERRAIN,
MF_BYCOMMAND) S MF_CHECKED) {
CheckMenuItem(GetMenu(SceneWindow), OIDM_WINDOW_TERRAIN,
MF_BYCOMMAND | MF_UNCHECKED); ShowWindow(TerrainWindow, SW_HIDE); } else {
CheckMenuItem(GetMenu(SceneWindow), oidm_window_terrain,
mf_bycommand | mf_checked); ShowWindow(TerrainWindow, SW_SHOW); };
break; }; break;
case WM_QUIT : case WM_CLOSE : /*
* Zniszczenie okna, bitmap i wyjście...
*/
DestroyWindow(SceneWindow); DestroyWindow(TerrainWindow);
410____________________________________Część II » Używanie OpenGL
DeleteObject(GrassDownBitmap); DeleteObject(GrassSelectBitmap); DeleteObject(GrassUpBitmap); DeleteObject(WaterDownBitmap); DeleteObject(WaterSelectBitmap); DeleteObject(WaterUpBitmap); DeleteObject(RocksDownBitmap); DeleteObject(RocksSelectBitmap); DeleteObject(RocksUpBitmap); DeleteObject(TreesDownBitmap); DeleteObject(TreesSelectBitmap); DeleteObject(TreesDpBitmap); DeleteObject(MountainsDownBitmap); DeleteObject(MountainsSelectBitmap); DeleteObject(MountainsUpBitmap);
exit(0); break;
case WM_DESTROY : /*
* Zwolnienie kontekstu urządzenia, kontekstu
* renderowania i palety
*/
if (SceneRC)
wglDeleteContext(SceneRC);
if (SceneDC)
ReleaseDC(SceneWindow, SceneDC);
if (ScenePalette)
DeleteObject(ScenePalette);
PostQuitMessage(0); break;
case WM_QUERYNEWPALETTE : /*
* W razie potrzeby realizacja palety...
*/
if (ScenePalette) {
SelectPalette(SceneDC, ScenePalette, FALSE);
RealizePalette(SceneDC);
InvalidateRect(hWnd, NULL, FALSE); return (TRUE);
); break;
case WM_PALETTECHANGED: /*
* W razie potrzeby ponowne wybranie palety...
*/
Rozdział 12. * Mapowanie tekstur_________________________________411
if (ScenePalette && (HWND)wParara != hWnd) {
SelectPalette(SceneDC, ScenePalette, FALSE);
RealizePalette(SceneDC);
UpdateColors(SceneDC); }; break;
case WM_LBUTTONDOWN : /*
* Wciśnięto lewy przycisk myszy. Jeśli był otwarty dialog
* terenu, oznacza to początek rysowania.
* W przeciwnym razie ustawienie znacznika 'Moving' w celu
* wskazania, że lecimy
*/
SetCapture(SceneWindow);
if (IsWindowVisible(TerrainWindow)) (
DrawTerrain(LOWORD(IParam), HIWORD(IParam));
Drawing = GL_TRUE; }
else {
GetCursorPos(&CenterMouseXY);
Moving = GLJTRUE;
MoveTime = GetClockf); }; break;
case WM_MOUSEMOVE : /*
* Poruszył się wskaźnik myszy. Jeśli rysujemy teren,
* zróbmy to.
* Jeśli nie, ignorujemy to, bo lecimy w głównej pętli
*/
if (Drawing)
DrawTerrain(LOWORD(IParam), HIWORD(IParam)); break;
case WM_LBUTTONUP : /*
* Użytkownik zwolnił lewy przycisk myszy. Przestajemy
* rysować lub lecieć.
*/
Moving = GL_FALSE; Drawing = GL_FALSE; ReleaseCapture();
InvalidateRect(SceneWindow, NULL, TRUE); break;
412__________________________________ Część II » Używanie OpenGL
default : /*
* Standardowa obsługa wszystkich innych komunikatów
* procedury...
*/
return (DefWindowProc(hWnd, uMsg, wParam, IParam)); };
return (FALSE);
* 'TerrainDlgProc()' - Przetwarza komunikaty okna dialogowego terenu.
*/
UINT CALLBACK
TerrainDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM IParam) {
HDC hdc; /* Kontekst rysowania dla przycisków */
LPDRAWITEMSTRUCT Ipdis; /* Informacja o stanie przycisków */
UINT idCtl; /* Identyfikator przycisku */
switch (uMsg) {
case WM_DRAWITEM : /*
* Windows chce narysować przycisk. Sprawdź, który i narysuj go
*/
idCtl - (UINT)wParam;
Ipdis - (LPDRAWITEMSTROCT)IParam;
hdc = CreateCompatibleDC(lpdis->hDC) ;
switch (idCtl)
{
case IDC_WATER :
if (lpdis->itemState S ODS_SELECTED)
SelectObject(hdc, WaterDownBitmap); else if (TerrainCurrent == IDC_WATER)
SelectObject(hdc, WaterSelectBitmap); else
SelectObject(hdc, WaterUpBitmap); break; case IDC_GRASS :
if (lpdis->itemState & ODS_SELECTED)
SelectObject(hdc, GrassDownBitmap); else if (TerrainCurrent == IDC_GRASS)
SelectObject(hdc, GrassSelectBitmap); else
SelectObject(hdc, GrassUpBitmap); break;
Rozdział 12. » Mapowanie tekstur_________________________________413
case IDCJTREES :
if (lpdis->itemState S, ODS_SELECTED)
SelectObject(hdc, TreesDownBitmap); else if (TerrainCurrent == IDC_TREES)
SelectObject(hdc, TreesSelectBitmap); else
SelectObject(hdc, TreesUpBitmap); break; case IDC_ROCKS :
if (lpdis->itemState i ODS_SELECTED)
SelectObject(hdc, RocksDownBitmap); else if (TerrainCurrent == IDC_ROCKS)
SelectObject(hdc, RocksSelectBitmap); else
SelectObject(hdc, RocksUpBitmap); break; case IDC_MOUNTAINS :
if (lpdis->itemState & ODS__SELECTED)
SelectObject(hdc, MountainsDownBitmap); else if (TerrainCurrent == IDC_MOUNTAINS)
SelectObject(hdc, MountainsSelectBitmap); else
SelectObject(hdc, MountainsUpBitmap); break;
* Rozciągnięcie bitmapy na obszar przycisku...
*/
StretchBlt(lpdis->hDC, lpdis->rcltem.left,
lpdis->rcltem.top, lpdis->rcltem.right,
lpdis->rcltem.bottom,
hdc, O, O, 24, 24, SRCCOPY);
DeleteDC(hdc);
break;
case WM_CLOSE : /*
* Zamknięcie okna (ukrycie go) i wyłączenie znaczka w menu
*/
ShowWindow(TerrainWindow, SW_HIDE); CheckMenuItem(GetMenu(SceneWindow), IDM_WINDOW_TERRAIN,
MF_BYCOMMAND | MF_UNCHECKED); break;
case WM_COMMAND : /*
* Kliknięto na przycisk - wybierz nowy typ terenu.
*/
switch (LOWORD(wParam))
{
case IDC_GRASS :
case IDCJTREES :
case IDC ROCKS :
414 ____ _________________________ Część II » Używanie OpenGL
case IDC_WATER : case IDC_MOUNTAINS :
TerrainCurrent = LOWORD(wParam);
InvalidateRect(TerrainWindow, NULL, TRUE); UpdateWindow(TerrainWindow); return (TRUE);
}; break;
};
return (FALSE);
/*
* 'LoadAllBitmaps()' - Ładuje bitmapy przycisków.
*/
void
LoadAllBitmaps(HINSTANCE hinstance)
{
GrassDownBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOURCE(IDB_GRASS_DOWN)); GrassSelectBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOURCE(IDB_GRASS_SELECT)) GrassUpBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOURCE(IDB_GRASS_UP));
WaterDownBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOURCE(IDB_WATER_DOWN));
WaterSelectBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOORCE(IDB_WATER_SELECT))
WaterUpBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOURCE(IDB_WATER_UP));
RocksDownBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOORCE(IDB_ROCKS_DOWN));
RocksSelectBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOURCE(IDB_ROCKS_SELECT))
RocksDpBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOURCE(IDB_ROCKS_UP));
TreesDownBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOORCE(IDB_TREES_DOWN));
TreesSelectBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOURCE(IDB_TREES_SELECT))
TreesDpBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOURCE(IDB_TREES_UP));
MountainsDownBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOURCE(IDB_MOUNTAINS_DOWN)); MountainsSelectBitmap = LoadBitmap((HANDLE)hinstance,
MAKEINTRESOURCE(IDB_MOONTAINS_SELECT)); MountainsUpBitmap = LoadBitraap((HANDLE)hinstance,
MAKEINTRESOURCE(IDB_MOONTAINS_UP));
415
Rozdział 12. » Mapowanie tekstur
'LoadAllTextures()
Ładuje tekstury dla sceny.
void
LoadAllTextures(void)
{
glNewList(SkyTexture = glGenLists(1), GL_COMPILE);
glTexParameteri(GL_TEXTORE_2D, GL_TEXTORE_WRAP_S, GL_REPEAT) ; glTexParameteri(GL_TEXTURE_2D, GL_TEXTORE_WRAP_T, GL_REPEAT); TextureLoadBitmap("sky.bmp");
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glEndList();
glNewList(RocksTexture = glGenLists(1), GL_COMPILE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT) ;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) ;
TextureLoadMipmap("rock.bmp");
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) ;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTORE_MIN_FILTER,
OGL_NEAREST_MIPMAP_LINEAR); glEndList();
glNewList(GrassTexture = glGenLists(1), GL_COMPILE);
GL_TEXTURE_WRAP_S, GL_REPEAT); GL REPEAT);
glTexPararaeteri(GL_TEXTURE_2D
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _ TextureLoadMipmap("grass.bmp");
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) , glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, C>GL_NEAREST_MIPMAP_LINEAR) ; glEndList O;
glNewList(WaterTexture = glGenLists(1), GL_COMPILE);
GL_TEXTORE_WRAP_S, GL_REPEAT) ; GL TEXTDRE WRAP T, GL REPEAT);
glTexParameteri(GL_TEXTURE_2D, glTexParameteri(GL_TEXTURE_2D, TextureLoadMipmap("water.bmp");
GL TEXTURE MIN FILTER,
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) , glTexParameteri(GL_TEXTURE_2D, C*GL_NEAREST_MIPMAP_LINEAR) ; glEndListO;
glNewList(TreesTexture = glGenLists(1), GL_COMPILE); glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); TextureLoadMipmap("trees.bmp");
glTexParameteri(GL_TEXTURE_2D glTexParameteri(GL_TEXTORE_2D OGL_NEAREST_MIPMAP_LINEAR); glEndListO;
GL_TEXTDRE_MAG_FILTER, GL_NEAREST) ;
GL TEXTURE MIN FILTER,
glNewList(MountainsTexture = glGenLists(1), GL_COMPILE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) ;
TextureLoadMipmap("mountain.bmp");
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) ;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTORE_MIN_FILTER,
C>GL_NEAREST_MIPMAP_LINEAR) ; glEndList O;
416____________________________________Część II » Używanie OpenGL
/*
* 'UpdateNormals()' - Aktualizuje normalne punktów sceny...
*/
void
UpdateNormals(void)
{
int x, y; /* Współrzędne x i y terenu */ GLfloat (*n)[3], /* Bieżąca normalna terenu */ nx, ny, nz, /* Składowe normalnej */ d, /* Wielkość normalnej */ *height; /* Bieżąca wysokość terenu */
/*
* Przejście przez tablice terenu i odtworzenie normalnych
* na podstawie wysokości terenu
*/
n = TerrainNormal[0];
height = TerrainHeight[0];
for (y = 0; y < (TERRAIN_SIZE - 1) ; y ++, n ++, height ++)
{
for (x = 0; x < (TERRAIN_SIZE - 1); x ++, n ++, height ++)
{ /*
* Iloczyn wektorowy wektorów w górę i na prawo
* (uproszczone w tym specjalnym przypadku)
*/
nx = height[0] - height[1];
ny = -1.0;
nz = height[0] - height[TERRAIN_SIZE];
d = (float)-sqrt(nx * nx + ny * ny + nz * nz) ;
n[0][0] = nx / d; /* Normalizacja wektora */ n[0] [1] = ny / d; n[0][2] = nz / d; };
/*
* Iloczyn wektorowy wektorów w górę i na lewo
* (uproszczone w tym specjalnym przypadku) dla ostatniej
* kolumny siatki
*/
nx = height[0] - height[-l];
ny = -1.0;
nz = height[0] - height[TERRAIN_SIZE];
d = (float)-sqrt (nx * nx -t- ny * ny + nz * nz) ;
n[0][0] = nx / d; /* Normalizacja wektora */
n[0] [1] = ny / d;
n[0] [2] = nz / d;
Rozdział 12. * Mapowanie tekstur_________________________________417
/*
* Ustawienie górnego wiersza normalnych tak samo jak
* normalnych drugiego wiersza od góry.
*/
for (x = 0; x < TERRAIN_SIZE; x ++, n ++) {
n[0][0] = n[-TERRAIN_SIZE][0];
n[0][l] = n[-TERRAIN_SIZE][1];
n[0][2] = n[-TERRAIN SIZE][2];
/*
* 'InitializeTerrain()' - Inicjuje tablice terenu...
*/
void
InitializeTerrain(void)
{
int x, y; /* Położenie terenu (x,y) */
/*
* Wypełnienie terenu trawą.
*/
TerrainCurrent = IDC_WATER;
for (y = 0; y < TERRAIN_SIZE; y ++) for (x = 0; x < TERRAIN_SIZE; x ++) {
TerrainType [y] [x] = IDC_GRASS;
TerrainHeight[y] [x] = GRASS_HEIGHT + O.lf * (rand() % 5),
/*
* Aktualizacja normalnych
*/
UpdateNormals ( ) ;
/*
* 'draw_cell()' - Rysowanie (wypełnienie) pojedynczej komórki ^terenu...
*/
void
draw_cell(int x, /* We - Położenie terenu X */ int y) /* We - Położenie terenu Y */ { /*
* Sprawdzenie zakresu terenu
*/
418 ___________________________________ Część II » Używanie OpenGL
if (X < O | | X >= TERRAIN_SIZE | |
y < O || y >= TERRAIN_SIZE) return;
if (TerrainType [y] [x] == TerrainCurrent) return; /* Już jest poprawnego typu */
TerrainType [y] [x] = TerrainCurrent;
/*
* Wymuszenie odświeżenia okna...
*/
InvalidateRect (SceneWindow, MOLL, TRUE) ;
/*
* Ustawienie wysokości "komórki" terenu. Dla wody wysokość
* jest stała, WATER_HEIGHT. Dla innych typów dodajemy losowe
* odchylenie w celu uzyskania realistycznego obrazu.
*/
switch (TerrainCurrent) {
case IDCJSATER :
TerrainHeight [y] [x] = WATER_HEIGHT;
break; case IDC_GRASS :
TerrainHeight [y] [x] = GRASS_HEIGHT + O.lf * (randt) % 5);
break; case IDCJTREES :
TerrainHeight [y] [x] = TREES_HEIGHT + 0.1 * (rand() % 5);
break; case IDC_ROCKS :
TerrainHeight [y] [x] = ROCKS_HEIGHT + O.lf * (rand() % 5);
break; case IDC_MOUNTAINS :
TerrainHeight [y] [x] = MOUNTAINS_HEIGHT + 0.15f * (rand() % 5);
break;
/*
* ' DrawTerrain ( ) ' - Rysuje komórkę terenu na pozycji myszy
*/
void
DrawTerrain (int mousex, /* We - pozioma pozycja myszy */ int mousey) /* We - pionowa pozycja myszy */ {
int i,
count, /* Licznik selekcji */ x, y; /* Położenie terenu (x, y) */ GLfloat *height; /* Bieżąca wysokość */ GLuint bufferflOO]; /* Bufor selekcji */ GLint viewport[4]; /* Widok OpenGL */
Rozdział 12. » Mapowanie tekstur_________________________________419
/*
* Pobranie bieżącego widoku OpenGl i rozpoczęcia liczenia
* pionowej współrzędnej myszy od krawędzi widoku.
*/
glGet!ntegerv(GL_VIEWPORT, viewport); mousey = viewport[3] - l - mousey;
/*
* Umożliwienie wyboru w granicach 4 pikseli od pozycji myszy
*/
glSelectBuffer(100, buffer); glRenderMode(GL_SELECT);
gllnitNames();
glMatrixMode(GL_PROJECTION);
glLoadldentity();
gluPickMatrix((GLdouble)mousex, (GLdouble)mousey, 4.0, 4.0,
viewport);
gluPerspective(45.O, (float)viewport[2] / (float)viewport[3], 0.1, 1000.0);
glMatrixMode(GL_MODELVIEW); glPushMatrix(); /*
* Obrót/translacja bieżącego punktu obserwacji
*/
glRotatef(Roli, 0.0, 0.0, 1.0); glRotatef(Pitch, -1.0, 0.0, 0.0); glRotatef(Heading, 0.0, 1.0, 0.0); glTranslatef(-Position[0],
-Positionfl],
-Position[2]); glScalef(TERRAIN_SCALE, TERRAIN_SCALE, TERRAIN_SCALE);
Narysowanie terenu do bufora selekcji. Dzieje się to inaczej niż w funkcji RepaintWindowO , wiec możemy wybrać pojedyncze komórki, a nie całe paski jednego typu.
Wybrany bufor został umieszczony na stosie zarówno dla położeń X, jak i Y. /
height = TerrainHeight[0];
glPushName(0);
for (y =0; y < (TERRAIN_SIZE - 1); y ++, height ++)
glLoadName(y); glPushName(0);
for (x = 0; x < (TERRAIN_SIZE - 1); x ++, height ++)
glLoadName(x);
glBegin(GL_POLYGON);
glVertex3f((GLfloat)(x - TERRAIN_EDGE),
height[0],
(GLfloat)(y - TERRAIN_EDGE));
420____________________________________Część II » Używanie OpenGL
glVertex3f((GLfloat)(x - TERRAIN_EDGE), height[TERRAIN_SIZE], (GLfloat) (y - TERRAIN_EDGE + D);
glVertex3f((GLfloat)(x - TERRAIN_EDGE + 1),
height[1],
(GLfloat)(y - TERRAIN_EDGE)); glVertex3f((GLfloat)(x - TERRAIN_EDGE + 1),
height[TERRAIN_SIZE + 1],
(GLfloat)(y - TERRAIN_EDGE + l)); glEnd(); };
glPopName O;
};
glPopName(); glPopMatrix O; glFinishO ;
/*
* Odczyt trafień w buforze selekcji
*/
count = glRenderMode(GL_RENDER); for (i = 0; i < count; i += 3) {
if (buffer[i) == 0) continue;
/*
* Każde trafienie zawiera następujące parametry:
*
* O - ilość (2)
* l - Minimalna wartość Z
* 2 - Maksymalna wartość Z
* 3 - Położenie X w terenie
* 4 - Położenie Y w terenie
*/
x = buffer[i + 4];
y = buffer[i + 3];
i += buffer[i];
/*
* Wypełnienie 4 rogów wybranej komórki...
*/
draw_cell(x, y); draw_cell(x + l, y); draw_cell(x, y + 1); draw_cell(x +1, y + 1);
/*
* Aktualizacja normalnych terenu.
*/
UpdateNormals();
Rozdział 12. » Mapowanie tekstur_________________________________421
/*
* 'FlyTerrain()' - Lot na podstawie bieżącej pozycji myszy.
*/
void
FlyTerrain(int mousex, /* We - pozioma współrzędna myszy */ int mousey) /* We - pionowa współrzędna myszy */ {
RECT rect;
GLfloat movex, movey; /* Przemieszczenie myszy */ double curtime, /* Bieżący czas w sekundach */ distance; /* Dystans do przeniesienia */ GLfloat cheading, /* Cosinus kierunku */ sheading, /* Sinus kierunku */ cpitch, /* Cosinus pochylenia */ spitch; /* Sinus pochylenia */
/*
* Pobranie bieżącego czasu systemowego
*/
curtime = GetClockO;
distance = 10.0 * (curtime - MoveTime);
MoveTime = curtime;
/*
* Obliczenie odległości wskaźnika myszy od "środka" (kliknięcia)
*/
movex = 0.05 * (mousex - CenterMouseXY.x); movey = 0.05 * (mousey - CenterMouseKY.y);
/*
* Dostosowanie kierunku pochylenia do bieżącego położenia myszy
*/
Roli += movex;
Pitch += movey * cos(Roli * M_PI / 180.0);
Heading += movey * sin(Roli * M_PI / 180.0);
if (Heading < 0.0)
Heading += 360.0; else if (Heading >= 360.0)
Heading -= 360.0;
if (Pitch < -180.0)
Pitch += 360.0; else if (Pitch >= 180.0)
Pitch -« 360.0;
if (Roli < -180.0)
Roli += 360.0; else if (Roli >= 180.0)
Roli -= 360.0;
422
Część II » Używanie OpenGL
/*
* Przeniesienie w bieżącym kierunku
*/
cheading = cos(Heading * M_PI / 180.0); sheading = sin(Heading * M_PI / 180.0); cpitch = cos(Pitch * M_PI / 180.0); spitch = sin(Pitch * M_PI / 180.0);
Position[0] += distance * sheading * cpitch;
Position[2] -= distance * cheading * cpitch;
Position[l] += distance * spitch;
/*
Odrysowanie okna z nowymi współrzędnymi
GetClientRect(SceneWindow, srect); Repaintwindow(irect);
*/
'RepaintWindowO ' - Odrysowuje obszar roboczy okna sceny.
void Repaintwindow(RECT
"rect) /* We - Obszar roboczy okna */
0.8, -TERRAIN_EDGE ),
0.8, -TERRAIN_EDGE ),
0.8, TERRAIN_EDGE },
0.8, TERRAIN EDGE )
int i;
Położenie terenu (x,y) */ Poprzedni typ terenu */ Bieżący typ terenu */ Bieżąca wysokość terenu * Bieżąca normalna terenu *
int x, y; /*
int last_type; /*
int *type; /*
GLfloat *height, /*
(*n)[31; /*
static GLfloat sky_top[4][3] =
( /* Współrzędne nieba */
{ -TERRAIN_EDGE, TERRAIN_SIZE
{ TERRAIN_EDGE, TERRAIN_SIZE
{ TERRAIN_EDGE, TERRAIN_SIZE
{ -TERRAIN EDGE, TERRAIN SIZE
static GLfloat
sky_bottom[4][3]
{ -TERRAIN_EDGE, 0.0, -TERRAIN_EDGE },
( TERRAIN_EDGE, 0.0, -TERRAIN_EDGE },
{ TERRAIN_EDGE, 0.0, TERRAIN_EDGE ),
{ -TERRAIN EDGE, 0.0, TERRAIN EDGE }
static GLfloat
static GLfloat
static GLfloat
sunpos[4] = { 0.0, 1.0, 0.0, 0.0 }; suncolor[4] = { 64.0, 64.0, 64.0, 1.0 }; sunambient[4] = { 0.001, 0.001, 0.001, 1.0
Rozdział 12. » Mapowanie tekstur _________________________________ 423
/*
* Wyzerowanie widoku i wyczyszczenie okna na jasny błękit
*/
glViewport (O, O, rect->right, rect->bottom) ; glClearColor(0.5, 0.5, 1.0, 1.0); glEnable (GL_DEPTH_TEST) ;
if (Moving | | Drawing) { /*
* Bez tekstur podczas rysowania lub lotu; jest za wolne...
* Rysowanie tylnego bufora dla płynnej animacji
*/
glDisable (GL_TEXTURE_2D) ; glDrawBuf fer (GL_BACK) ;
else
Włączenie tekstur, gdy przestaniemy rysować lub latać W ten sposób tworzymy scenę, która można wydrukować lub zachować w pliku.
Ponieważ to trwa dłużej, rysujemy w przednim buforze, aby użytkownik widział przebieg rysowania. . .
glEnable (GL_TEXTURE_2D) ;
glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE , GL_DECAL) ;
glDrawBuffer (GL_FRONT) ;
glClear (GL_COLOR_BOFFER_BIT | GL_DEPTH_BUFFER_BIT) ;
/*
* Przygotowanie przekształcenia widoku dla bieżącego
* punktu obserwacji
*/
glMatrixMode (GL_PROJECTION) ; glLoadldentity ( ) ;
gluPerspective(45.0, (float) rect->right / (float) rect->bottom, 0.1, 1000.0);
glMatrixMode (GL_MODELVIEW) ; glPushMatrix() ;
glRotatef (Roli, 0.0, 0.0, 1.0);
glRotatef (Pitch, -1.0, 0.0, 0.0);
glRotatef (Heading, 0.0, 1.0, 0.0);
glTranslatef |-Position[0] ,
-Position[l] ,
-Position[2] ) ; glScalef (TERRAIN_SCALE, TERRAIN_SCALE, TERRAIN_SCALE) ;
424____________________________________Część II » Używanie OpenGL
if (!(Moving l| Drawing)) { /*
* Rysowanie nieba...
*/
g!Disable(GL_LIGHTING); glCallList(SkyTexture); glBegin(GL_QDAD_STRIP); for (i = 0; i < 4; i ++) {
glTexCoord2f((float)i, 0.0); glVertex3fv(sky_bottom[i]);
glTexCoord2f((float)i, 0.8); glVertex3fv(sky_top[i]); };
glTexCoord2f(4.0, 0.0); glVertex3fv(sky_bottom[0]);
glTexCoord2f(4.0, 0.8); glVertex3fv(sky_top[0]); glEnd();
glBegin(GL_TRIANGLE_FAN); glTexCoord2f(0.5, 1.0); glVertex3f(0.0, TERRAIN_SIZE, 0.0);
for (i = 0; i < 4; i ++) {
glTexCoord2f((float)i, 0.8);
glVertex3fv(sky_top[i]); };
glTexCoord2f(4.0, 0.8); glVertex3fv(sky_top[0]); glEnd(); };
/*
* Przygotowanie oświetlenia...
*/
glEnable(GL_LIGHTING);
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TROE);
glEnable(GL_LIGHTO);
glLightfv(GL_LIGHTO, GL_POSITION, sunpos); glLightfv(GL_LIGHTO, GL_DIFFUSE, suncolor); glLightfv(GL_LIGHTO, GL_AMBIENT, sunambient);
if (Moving l l Drawing)
glEnable(GL_COLOR_MATERIAL); else
glDisable(GL_COLOR_MATERIAL);
Rozdział 12. » Mapowanie tekstur_________________________________425
/*
* Teraz teren...
*/
type = TerrainType[0];
height = TerrainHeight[0];
n = TerrainNormal[0];
for (y = 0; y < (TERRAIN_SIZE - 1); y ++)
{
last_type = -1;
for (x • 0; x < TERRAIN_SIZE; x ++, type ++, height ++, n ++) {
if (last_type 1= *type)
{ /*
* Jeśli zmienia się typ terenu, zakończ istniejący pasek
* i wyzeruj parametry tekstury/koloru
*/
if (last_type != -1) glEnd();
switch (*type)
{
case IDCJłATER :
if (Moving || Drawing)
glColor3f(0.0, 0.0, 0.5); else
glcallList(WaterTexture); break; case IDC_GRASS :
if (Moving l l Drawing)
glColor3f(0.0, 0.5, 0.0); else
glCallList(GrassTexture); break; case IDC_ROCKS :
if (Moving || Drawing)
glColor3f(0.25, 0.25, 0.25); else
glCallList(RocksTexture); break; case IDC_TREES :
if (Moving l l Drawing)
glColorSf(0.0, 0.25, 0.0); else
glCallList(TreesTexture); break; case IDC_MOUNTAINS :
if (Moving || Drawing)
glColorSf(0.2, 0.1, 0.05); else
glCallList(MountainsTexture); break; };
glBegin(GL_QOAD_STRIP); if (last_type != -1)
426 ____________________________________ Część II » Używanie OpenGL
/*
* Zaczniemy od poprzedniego miejsca, aby nie było dziur
*/
glTexCoord2i (x * 2 - 2, y * 2);
glNorma!3fv(n[-l] ) ;
glVertex3f ( (GLfloat) (X - TERRAIN_EDGE - 1), heightt-1] , (GLfloat) (y - TERRAIN_EDGE) ) ;
glTexCoord2i(x * 2 - 2, y * 2 + 2);
glNorma!3fv(n[TERRAIN_SIZE - 1] ) ;
glVertex3f( (GLfloat) (x - TERRAIN_EDGE - 1), height[TERRAIN_SIZE - 1], (GLfloat) (y - TERRAIN_EDGE + l ) ) ;
};
last_type = *type; >;
glTexCoord2i(x * 2, y * 2);
glNormal3fv(n[0] ) ;
glvertex3f( (GLfloat) (x - TERRAIN_EDGE) ,
height[0] ,
(GLfloat) (y - TERRAIN_EDGE) ) ; glTexCoord2i(x * 2, y * 2 + 2) ; glNormal3fv(n[TERRAIN_SIZE] ) ; glVertex3f( (GLfloat) (x - TERRAIN_EDGE) ,
height [TERRAIN_SIZE] ,
(GLfloat) (y - TERRAIN_EDGE + D);
};
glEnd ( ) ; }; glPopMatrix () ;
/*
* Gdy lecimy lub rysujemy, używamy podwójnego buforowania.
* Jeśli trzeba, przerzuć bufory
*/
glFinish ( ) ;
if (Moving l l Drawing) SwapBuf fers (SceneDC) ;
/*
* 'SaveBitmapFile () ' - Zapis wyświetlanej sceny na dysku.
*/
void
SaveBitmapFile (void)
{
char title[256], /* Tytuł pliku */ filename[256] , /* Nazwa pliku */ directory[256] ; /* Bieżąca kartoteka */ OPENFILENAME ofn; /* Struktura okna dialogowego */ void *bits; /* Dane bitmapy na ekranie */ BITMAPINFO *info; /* Nagłówek bitmapy na ekranie */
Rozdział 12. » Mapowanie tekstur_________________________________427
/*
* Zrzut ekranu...
*/
bits = ReadDIBitmap(sinfo);
if (bits == NULL)
{
DisplayErrorMessage("Nie powiodło się odczytanie obrazu z ekranu!");
return; >;
/*
* Wyświetlenie okna dialogowego...
*/
strcpy(directory, ".");
strcpy(filename, "bez nazwy.bmp");
strcpyltitle, "");
memset(sofn, O, sizeof(ofn));
ofn.lstructsize = sizeof(ofn);
ofn.hwndOwner = SceneWindow;
ofn.lpstrFilter = "Bitmapy\0*.BMP\0\0";
ofn.nFilter!ndex = 1;
ofn.lpstrFile = filename;
ofn.nMaxFile = sizeof(filename) - 1;
ofn.lpstrFileTitle = title;
ofn.nMaxFileTitle = sizeof(title) - 1;
ofn.lpstrlnitialDir = directory;
ofn.IpstrTitle = "Zapisz plik bitmapy";
ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST l
OFN_NONETWORKBUTTON;
if (GetSaveFileName(Sofn)) { /*
* Zapis pliku na dysku...
*/
if (SaveDIBitmap(filename, info, bits))
DisplayErrorMessage("Could not save to file \'%s\' -\n%s",
filename, strerror(errno)); };
/*
* Zwolnienie pamięci i powrót...
*/
free(info); free(bits);
428 Część II » Używanie OpenGL
* 'PrintBitmap()' - Wydruk wyświetlanej sceny.
*/
void
PrintBitmap(void)
{
void *bits; /* Dane obrazu */ BITMAPINFO *info; /* Nagłówek bitmapy */
/*
* Zrzut ekranu. . .
*/
bits = ReadDIBitmap (sinf o) ;
if (bits == NULL)
{
DisplayErrorMessage ("Nie powiodło się odczytanie bitmapy ^z ekranu! ") ;
return;
/*
* Wydruk bitmapy. . .
*/
PrintDIBitmap (Scenewindow, info, bits);
/*
* Zwolnienie pamięci i powrót...
*/
free (info) ; free(bits) ;
/*
* 'GetClockO1 - Czas, jaki upłynął, w milisekundach...
*/
double
GetClock(void)
{
SYSTEMTIME curtime; /* Bieżący czas systemowy */
GetSystemTime(icurtime);
return (curtime.wHour * 3600.0 +
curtime.wMinute * 60.0 +
curtime.wSecond +
curtime.wMilliseconds * 0.001);
429
Rozdział 12. * Mapowanie tekstur
Podręcznik
glTexCoord
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry s t r
Zwracana wartość Przykład Patrz także
Określa współrzędne tekstury dla wielokąta, na który nakładamy teksturę.
<gl.h>
void glTexCoordl {dfis}(TYPE s);
void glTexCoordl {dfis}v(TYPE *s);
void glTexCoord2{dfis}(TYPE s, TYPE t);
void glTexCoord2{dfis}v(TYPE *st);
void glTexCoord3 {dfis}(TYPE s, TYPE t, TYPE r);
void glTexCoord3{dfis}v(TYPE *str);
void glTexCoord4{dfis}(TYPE s, TYPE t, TYPE r, TYPE q);
void glTexCoord4{dfis}v(TYPE *strq);
Ta funkcja ustala bieżące współrzędne obrazu tekstury, dla jednego, dwóch, trzech lub czterech wymiarów. Na przykład, parametry s i t odpowiadają poziomej i pionowej współrzędnej dwuwymiarowego obrazu tekstury.
Pozioma współrzędna obrazu tekstury.
Pionowa współrzędna obrazu tekstury.
Współrzędna głębokości obrazu tekstury.
Współrzędna „czasu" obrazu tekstury
Brak.
Przejrzyj kod programu TEXSCENE.C na płytce CD-ROM.
glTextEnv, glTexGen, glTex!magelD, glTex!mage2D. glTexParameter
glTexEnv
Przeznaczenie Plik nagłówkowy Składnia
Określa parametry teksturowania.
<gl.h>
void glTexEnvf(GLenum target, GLenum pname, GLfloat param);
void glTexEnvfv(GLenum target, GLenum pname, GLfloat *param);
void glTexEnvi(GLenum target, GLenum pname, GLint param);
void glTexEnviv(GLenum target, GLenum pname, GLint *param);
430
Część II * Używanie OpenGL
Opis
Funkcja glTexEnv ustawia parametry mapowania tekstury, sterujące sposobem nakładania obrazów tekstur na wielokąty. W trybie teksturowania GL_DECAL obraz tekstury jest bezpośrednio nakładany na wielokąty. W trybach GL_BLEND i GL_MODULATE przy wyznaczaniu docelowego koloru piksela uczestniczą także już narysowane piksele oraz kolor ustawiony parametrem GL_TEXTURE_ENV_COLOR.
Parametry target
pname param
Zwracana wartość Przykład Patrz także
GLenum: Definiowane środowisko teksturowania, musi nim być GL_TEXTURE_ENV.
GLenum: Nazwa definiowanego parametru. Dostępne parametry to: GL_TEXTURE_ENV_MODE Określa tryb teksturowania. GL_TEXTURE_ENV_COLOR Określa kolor do połączenia z teksturą
Wartość parametru. Dla parametru GL_TEXTURE_ENV_COLOR param jest wskaźnikiem do wartości koloru RGBA. Dla parametru GL_TEXTURE_ENV_MODE może być jedną z poniższych stałych:
GL_DECAL Obraz tekstury jest bezpośrednio nakładany na wielokąt.
GL_BLEND Przed nałożeniem na wielokąt, obraz tekstury jest mieszany z określonym kolorem (GL_TEXTURE_ENV_COLOR).
GL_MODULATE Przed nałożeniem na wielokąt, obraz tekstury jest mnożony przez istniejący obraz w buforze ramki.
Brak.
Przejrzyj kod programu TEXSCENE.C na płytce CD-ROM.
glTexCoord, glTexGen, glTex!magelD, gITex!mage2D. glTexParameter
glTexGen
Przeznaczenie Plik nagłówkowy Składnia
Opis
Definiuje parametry generowania współrzędnych tekstur.
<gl.h>
void glTexGend(GLenum coord, GLenum pname, GLdouble param);
void glTexGenf(GLenum coord, GLenum pname, GLfloat param);
void glTexGeni(GLenum coord, GLenum pname, GLint param);
void glTexGendv(GLenum coord, GLenum pname, GLdouble *param);
void glTexGenfv(GLenum coord, GLenum pname, GLfloat *param);
void glTexGeniv(GLenum coord, GLenum pname, GLint *param);
Gdy funkcją glEnable zostanie włączona jedna z opcji, GL_TEXTURE_GEN_S, GL_TEXTURE_GEN_T, GL_TEXTURE_GEN_R lub GL_TEXTURE_GEN_Q, funkcja glTexGen ustawia parametry generowania współrzędnych tekstur.
431
Rozdział 12. » Mapowanie tekstur
Gdy parametr GL_TEXTURE_GEN_MODE zostanie ustawiony na GL_OBJECT_LINEAR, współrzędne tekstur są generowane w wyniku przemnożenia współrzędnych bieżącego obiektu (wierzchołka) przez stały wektor określony przez GL_OBJECT_PLANE:
coordinate = v[0] * p[0] + v[l] * p[l] + v[2] * p[2] + v[3] * p[3]
W przypadku GL_EYE_LINEAR są używane współrzędne obserwatora (współrzędne obiektu przemnożone przez macierz GL_MODELVIEW).
Gdy parametr GL_TEXTURE_GEN_MODE zostanie ustawiony na GL_SPHERE_MAP, współrzędne są generowane jako kula dookoła bieżącego punktu obserwacji lub początku okładu.
Parametry coord
pname param
Zwracana wartość Przykład Patrz także
GLenum: Współrzędna tekstury do wygenerowania. Musi nią być GL_S, GL_T, GL_R lub GL_Q.
GLenum: Nazwa definiowanego parametru. Dostępne parametry to:
GL_TEXTURE_GEN_MODE, GL_OBJECT_PLANE lub GL_EYE_PLANE.
Wartość parametru. Dla parametru GL_TEXTURE_GEN_MODE param może być jedną ze stałych:
GL_OBJECT_LINEAR Współrzędne tekstury są obliczane na podstawie współrzędnych obiektu (wierzchołka).
GL_EYE_LINEAR Współrzędne tekstury są obliczane na podstawie współrzędnych punktu obserwacji (współrzędnych obiektu przemnożonych przez macierz GL_MODELVIEW).
GL_SPHERE_MAP Współrzędne tekstury są generowane jako kula dookoła punktu obserwacji.
Dla parametrów GL_OBJECT_PLANE i GL_EYE_PLANE, param jest czteroelementową tablicą używaną jako mnożnik dla współrzędnych obiektu lub punktu obserwacji.
Brak.
Przejrzyj kod programu TEXSCENE.C na płytce CD-ROM.
glTexCoord, glTexEnv, glTex!magelD, glTex!mage2D. gITexParameter
glTexlmagelD
Przeznaczenie Plik nagłówkowy Składnia
Definiuje jednowymiarowy obraz tekstury.
void glTexImagelD(GLenum target, GLint Ievel, GLint components, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
432
Część II » Używanie OpenGL
Opis
Parametry target level
component wldth
border format
type
pbcels
Zwracana wartość Przykład Patrz także
Ta funkcja definiuje jednowymiarowy obraz tekstury. Dane obrazu podlegaj ą działaniu funkcji glPixelMap, glPixelStore oraz glPixelTransfer.
GLenum: Musi być GL_TEXTURE_1D.
GLint: Poziom szczegółów mipmapy. Jeśli nie są używane mipmapy, zwykle wynosi zero.
GLint: Ilość komponentów koloru, od l do 4.
GLsizei: Szerokość obrazu tekstury. Musi być potęgą liczby 2 lub być zgodna ze wzorem 2" + 2*border.
GLint: Szerokość ramki dookoła obrazu tekstury. Musi być O, l lub 2.
GLenum: Format danych pikseli. Poprawne formaty to: GL_COLOR_INDEX Wartości pikseli są indeksami kolorów GL_RED Wartości pikseli są intensywnościami czerwieni GL_GR£EN Wartości pikseli są intensywnościami zieleni GL_BLUE Wartości pikseli są intensywnościami niebieskiego GL_ALPHA Wartości pikseli są wartościami alfa GL_RGB Wartości pikseli są wartościami RGB GLJR.GBA Wartości pikseli są wartościami RGB A GL_LUMINANCE Wartości pikseli są kolorami w skali szarości
GL_ALPHA_LUMINANCE Wartości pikseli są wartościami alfa i kolorami w skali szarości
GLenum: Typ danych dla wartości pikseli (patrz glDrawPixels).
GLvoid*: Dane pikseli.
Brak.
Przejrzyj kod programu TEX1D.C na płytce CD-ROM.
glPixe!Map, glPixelStore, glPixelTransfer, glTex!mage2D
glTexlmage2D
Przeznaczenie Plik nagłówkowy Składnia
Definiuje jednowymiarowy obraz tekstury.
void glTexImage2D(GLenum target, GLint level, GLint components, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
433
Rozdział 12. o Mapowanie tekstur
Opis
Parametry target level
component width
height
border format
type
pucels
Zwracana wartość Przykład Patrz także
Ta funkcja definiuje dwuwymiarowy obraz tekstury. Dane obrazu podlegają działaniu funkcji glPixeIMap, glPixelStore oraz glPixelTransfer.
GLenum: Musi być GL_TEXTURE_2D.
GLint: Poziom szczegółów mipmapy. Jeśli nie są używane mipmapy, zwykle wynosi zero.
GLint: Ilość komponentów koloru, od l do 4.
GLsizei: Szerokość obrazu tekstury. Musi być potęgą liczby 2 lub być zgodna ze wzorem 2" + 2*border.
GLsizei: Wysokość obrazu tekstury. Musi być potęgą liczby 2 lub być zgodna ze wzorem 2" + 2*border.
GLint: Szerokość ramki dookoła obrazu tekstury. Musi wynosić O, l lub 2.
GLenum: Format danych pikseli. Poprawne formaty to: GL_COLOR_INDEX Wartości pikseli są indeksami kolorów GL_RED Wartości pikseli są intensywnościami czerwieni GL_GREEN Wartości pikseli są intensywnościami zieleni GL_BLUE Wartości pikseli są intensywnościami niebieskiego GL_ALPHA Wartości pikseli są wartościami alfa GL_RGB Wartości pikseli są wartościami RGB GL_RGBA Wartości pikseli są wartościami RGBA GL_LUMINANCE Wartości pikseli są kolorami w skali szarości
GL_ALPHA_LUMINANCE Wartości pikseli są wartościami alfa i kolorami w skali szarości
GLenum: Typ danych dla wartości pikseli (patrz glDrawPixelś).
GLvoid*: Dane pikseli.
Brak.
Przejrzyj kod programu TEX2D.C na płytce CD-ROM.
glPixelMap, glPixelStore, glPixelTransfer, glTex!magelD
Przeznaczenie Plik nagłówkowy
glTexParameter
Ustala parametry obrazu tekstury.
434
Część II » Używanie OpenGL
Składnia
Opis
Parametry target pname
param
void glTexParameterf(GLenum target, GLenum pname, GLfloat param);
void glTexParameterfv(GLenum target, GLenum pname, GLfloat *param);
void glTexParameteri(GLenum target, GLenum pname, GLint param); void glTexParameteriv(GLenum target, GLenum pname, GLint *param);
Ta funkcja ustala filtr oraz parametry powtarzania obrazu tekstury.
GLenum: Musi to być GL_TEXTURE_1D lub GL_TEXTURE_2D.
GLenum: Ustawiany parametr tekstury. Poprawne nazwy to:
GL_TEXTURE_MIN_FILTER Określa metodę lub filtr stosowany przy zmniejszaniu tekstury.
GL_TEXTURE_MAX_FILTER Określa metodę lub filtr stosowany przy powiększaniu tekstury.
GL_TEXTURE_WRAP_S Określa sposób traktowania współrzędnych S tekstury poza zakresem od 0,0 do l ,0.
GL_TEXTURE_WRAP_T Określa sposób traktowania współrzędnych T tekstury poza zakresem od 0,0 do 1,0.
GL_BORDER_COLOR Określa kolor ramki do tekstur bez ramki.
Dla GL_TEXTURE_MIN_FILTER wartością/wa/w może być GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR oraz
GL_LINEAR_MIPMAP_LINEAR. Dla GL_TEXTURE_MAX_FILTER param może przybierać wartość GL_NEAREST lub GL_LINEAR. Dla GL_TEXTURE_WRAP_S i GL_TEXTURE_WRAP_T, można go ustawić na GL_REPEAT lub GL_CLAMP. GL_REPEAT powoduje powtarzanie tekstury na całej powierzchni wielokąta. W przypadku użycia GL_CLAMP poza obszarem obrazu tekstury (zakresem od 0,0 do 1,0) jako koloru tekstury używa się koloru ramki lub określonego stałego koloru (patrz poniżej).
Dla GL_BORDER_COLOR param to tablica koloru RGB A używanego jako kolor ramki w przypadku, gdy obraz tekstury nie ma zdefiniowanych pikseli ramki.
Zwracana wartość Brak.
Przykład Patrz także
Przejrzyj kod programu TEXSCENE.C na płytce CD-ROM. glTexCoord, glTexEnv, glTexGen, glPixelTransfer, glTex!magelD
Rozdział 13.
Kwadryki: sfery, cylindry i dyski
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
* Tworzyć kwadryki w celu narysowania * glNewQuadrics prostych geometrycznych kształtów
* Kontrolować jakość rysowanych kształtów
* Rysować kształty używając innych * gluQuadricDrawStyle
prymitywów OpenGL
+ Stosować oświetlenie i tekstury dla 4 gluQuadricNormals/ gluQuadricTexture
kwadryk
Już słyszę, jak pytasz, czym są kwadryki. Cóż, kwadryki1 to część biblioteki OpenGL Utility Library (GLU32.LIB), obsługującej rysowanie prostych trójwymiarowych kształtów geometrycznych. W szczególności, zawarte w niej funkcje umożliwiają rysowanie stożków, cylindrów, dysków i sfer. W tym rozdziale poznamy praktyczne zastosowania funkcji kwadryk w naszych programach.
W rzeczywistości kwadryki to rodzina powierzchni drugiego stopnia, takich jak elipsoida, pa-raboloida itd. Za pomocą tych powierzchni można budować opisywane w tym rozdziale obiekty. (Przyp. tłum.)
436 Część II * Używanie OpenGL
Tworzenie kwadryk
Każda tworzona kwadryka ma pewien stan (kolekcję powiązanych ze sobą ustawień). Funkcja gluNewQuadric tworzy zmienną stanu, opisującą aktualny styl rysowania, orientację, tryb oświetlenia, tryb teksturowania oraz funkcję zwrotną:
GLUąuadricObj *obj; obj » gluNewOuadric() ;
Zwróć uwagę, że stan kwadryki nie określa kształtu geometrycznego, jaki zostanie utworzony. Zamiast tego opisuje, jak rysować taki kształt. Dzięki temu można stosować kwadryki przy tworzeniu wielu różnych rodzajów kształtów.
Zmiana sposobu rysowania kwadryki
Gdy utworzysz kwadrykę, możesz dostosować sposób jej rysowania zmieniając jej stan. Służą do tego funkcje biblioteki GLU: gluQuadricDrawStyle, gluQuadricNormals, glu-QuadricOrientation oraz gluQuadricTexture.
void gluOuadricDrawStyle(GLUąuadricObj *obj, GLenum drawStyle); void gluQuadricNormals(GLUąuadricObj *obj, GLenum normals); void gluOuadricOrientation(GLUąuadricObj *obj, GLenum orientation); void gluQuadricTexture(GLUąuadricObj *obj, GLboolean textureCoords);
Funkcja gluQuadricDrawStyle służy do wyboru prymitywów OpenGL używanych do konstruowania kwadryki. Domyślnie kształt kwadryki jest wypełniany wielokątami i paskami prymitywów(GLU_FILL). Inne dostępne style tworzenia zawiera tabela 13.1.
Tabela 13.1.
Style rysowania kwadryk
Styl______________Opis___________________________________
OLU_FILL Tworzone kwadryki są wypełniane przy użyciu wielokątów i pasków
prymitywów.
GLU_LINE Tworzone kwadryki są rysowane jako siatka, za pomocą odcinków.
GLU_SILHOUETTE Tworzone kwadryki są rysowane za pomocą odcinków, z tym że
rysowane sąjedynie zewnętrzne krawędzie.
GLU_POINT Tworzone kwadryki są rysowane jako zbiór punktów.
W przypadku kwadryk, normalne są zwykle generowane automatycznie. Obliczaniem normalnych steruje funkcja gluQuadricNormals. Dostępne sposoby generowania normalnych zawiera tabela 13.2.
Rozdział 13. » Kwadryki: sfery, cylindry i dyski__________________________437
Tabela 13.2.
Tryby obliczania normalnych
Tryb obliczania Opis
GLU_NONE Nie są generowane normalne.
GLU_FLAT Normalne są generowane dla wielokątów, przez co powstają płaskie ścianki.
GLU_SMOOTH Normalne są generowane dla wierzchołków, przez co powstaje gładka
powierzchnia.
Do sterowania kierunkiem normalnych służy funkcja gluQuadricOrientation, określająca, czy normalne wskazują na zewnątrz (GLU_OUTSIDE) czy do wewnątrz kwadryki (GLU_INSIDE). Ma to szczególne znaczenie w przypadku sfer (czy znajdujesz się wewnątrz lub na zewnątrz sfery).
Na koniec, w przypadku kwadryk można zażyczyć sobie automatycznego generowania współrzędnych tekstury. Do włączania i wyłączania generowania współrzędnych tekstury służy funkcja gluQuadricTexture. Do włączania generowania współrzędnych tekstur służy stała GLUJTRUE, zaś do wyłączenia - stała GLU_FALSE. O wyborze współrzędnych tekstur powiemy sobie więcej w momencie, gdy zaczniemy rysować kwadryki na ekranie.
Jak zapewne pamiętasz, współrzędne tekstur są używane przy nakładaniu obrazów tekstur na wielokąty (rozdział 12).
Rysowanie cylindrów
Do rysowania cylindrów służy funkcja gluCylinder. Cylinder rysowany za pomocą tej funkcji to po prostu tuba ułożona wzdłuż osi z (rysunek 13.1). Podstawy cylindra nie są wypełniane!
Rysunek 13.1. baseRadius
Cylinder
(0,0,height)
^P-^ M ^^^"Tk:
•(0,0,0)
topRodius
void gluCylider(GLUąuadricObj *obj, GLdouble baseRadius, GLdouble topRadius, GLdouble height, GLint slices, GLint stacks)
438____________________________________Część II » Używanie OpenGL
Parametry baseRadius i topRadius określają promień cylindra przy dolnej i górnej podstawie. Parametr height określa wysokość (czy też długość) cylindra.
Parametry slices i stacks określają,, z ilu części (ścian) oraz z ilu segmentów (pięter) będzie się składał cylinder. Ogólnie, aby nadać cylindrom gładkie ściany, powinieneś użyć około 20 ścian. Mniejsze wartości powodują, że cylinder zaczyna przypominać graniastosłup, zaś wartości większe mogą powodować powstawianie śmieci na rysunku. Gdy stosujesz światło punktowe lub światło odbłysków, powinieneś także wybrać większą ilość segmentów. W przeciwnym razie wybierz dwa segmenty, odpowiadające obu podstawom cylindra.
Cylindry można wykorzystać także do tworzenia graniastosłupów foremnych, takich jak ołówek czy sześcian.
Rysowanie stożków
Biblioteka GLU nie posiada specjalnej funkcji do rysowania stożków, można jednak użyć w tym celu funkcji gluCylinder. W tym celu wystarczy podać wartość 0,0 jako parametr topRadius lub bottomRadius.
Teksturowanie cylindrów
Podczas teksturowania cylindra, obraz tekstury jest na nim zawijany począwszy od przedniej krawędzi (O, promień, 0). To oznacza, że obraz tekstury powinien być odwrócony „do góry nogami". Nakładanie tekstury na cylinder przeprowadzimy w programie projektu w tym rozdziale.
Rysowanie dysków
Dyski to okrągłe, płaskie kształty, które mogą zawierać dziury. Przykładami dysków mogą być monety lub pierścienie.
void gluDisk(GLUquadricObj *obj,
GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops)
Parametry outerRadius i innerRadius określają promień całego dysku oraz zawartej w nim dziury. Jeśli argument innerRadius ma wartość 0,0, dysk jest rysowany jako jednolity okrąg (rysunek 13.2).
Parametr slices określa ilość ścianek dysku i powinnien wynosić około 20, jeśli chcemy, aby dysk wyglądał na gładki,. Parametr loops określa ilość koncentrycznych okręgów tworzących dysk (pomiędzy wewnętrznym a zewnętrznym promieniem); powinien być
Rozdział 13. » Kwadryki: sfery, cylindry i dyski_________________________439
równy l dla okręgów i 2 dla pierścieni. Tak jak w przypadku cylindrów, użycie większych wartości poprawia efekt światła punktowego i odbłysków.
OuterRodius
Rysunek 13.2. InnerRodius
Dysk
Teksturowanie dysku
Obrazy tekstur są nakładane tak, aby krawędzie obrazu pokrywały się z krawędziami dysku. Górna krawędź tekstury pokrywa się z górną krawędzią dysku, lewa krawędź tekstury pokrywa się z lewą krawędzią dysku itd.
Rysowanie częściowych dysków
Biblioteka GLU zawiera funkcję przeznaczoną do rysowania części dysku. Podczas rysowania takiego obiektu podaje się kąt początkowy oraz kąt zataczany przez powierzchnię dysku. Parametr startAngle określa początkowy kąt, liczony zgodnie z ruchem wskazówek zegara od góry dysku. Argument sweepAngle określa ilość stopni łuku, który ma zostać utworzony. Na przykład, 90° oznacza jedną czwartą dysku, itd.
void gluPartialDisk(GLUąuadricObj *obj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops, GLdouble startAngle, GLdouble sweepAngle)
Rysowanie sfer
Kule to puste piłki. Gdy rysujesz sferę, określasz jej promień oraz ilość ścianek.
void gluSphere(GLUąuadricObj *obj, GLdouble radius, GLint slices, GLint stacks)
Jeśli potraktujesz sferę jak sferę ziemską, parametr slices reprezentuje ilość południków, zaś parametr stacks - ilość równoleżników (rysunek 13.3).
440
Część II * Używanie OpenGL
Rysunek 13.3.
Sfera
Równoleżniki Południki
Teksturowanie sfer
Obrazy tekstur są nakładane zgodnie z południkami i równoleżnikami. Tak więc obraz mapy świata zostanie poprawnie nałożony na sferę.
Rysowanie ołówka
Aby zamknąć ten rozdział, napiszemy mały program, służący do przedstawienia obracającego się ołówka (rysunek 13.4). Ołówek będzie się składał z trzech cylindrów i dwóch obrazów tekstur. Pierwszy obraz zawiera typowe symbole dla ołówka nr 2 oraz napis „OpenGL Country Club". Jako zaostrzony koniec ołówka zastosujemy drugi obrazek, przedstawiający drewno z wystającym grafitem.
Rysunek 13.4.
Ołówek stworzony przy użyciu kwadryk
Czubek ołówka to oczywiście stożek. Drugi koniec ołówka nie jest już taki oczywisty. Ponieważ jest płaski, mógłbyś oczekiwać, że do jego utworzenia użyjemy dysku. Niestety, w przypadku użytej tekstury (rysunek 13.5) efekt jej nałożenia nie wyglądałby najlepiej. Tak więc, zamiast dysku użyjemy cylindra o zerowej wysokości i zerowym górnym promieniu.
441
Rozdział 13. » Kwadryki: sfery, cylindry i dyski
Rysunek 13.5.
Obrazy tekstur na ściany ołówka i czubek ołówka
Ponieważ kwadryki są rysowane w początku układu współrzędnych, przed ich narysowaniem musimy przesunąć współrzędne. Na przykład, aby narysować ścianki ołówka, musimy wykonać to:
glPushMatrix();
glTranslatef(0.0, 0.0, -20.0);
gluCylinder(PencilObj, 5.0, 5.0, 4.0,6,2); glPopMatrix();
W programie rysującym program (listing 13.1) za rysowanie całego ołówka odpowiada funkcja RepaintWindow. Pierwszą rysowaną częścią są ścianki ołówka, utworzone z cylindra posiadającego sześć stron.
gluQuadricNormals(PencilObj, GLU_FLAT); glCallList(PencilTexture) ;
glPushMatrix() ;
glTranslatef(0.0, 0.0, -20.0);
gluCylinder(PencilObj, 5.0, 5.0, 40.0, 6, 2); glPopMatrix();
Następnie rysujemy czubek i koniec ołówka używając tekstury „grafitu". Także rym razem stosujemy cylindry składające się z sześciu stron.
gluOuadricNormals(PencilObj, GLU_SMOOTH); glCallList(LeadTexture) ;
442____________________________________Część II » Używanie OpenGL
glPushMatrix();
glTranslatef (0.0, 0.0, 20.0);
gluCylinder(PencilObj, 5.0, 0.0, 7.5, 6, 2); glPopMatrix();
glPushMatrix();
glTranslatef (0.0, 0.0, -20.0);
gluCylinder(PencilObj, 5.0, 0.0, 0.0, 6, 2); glPopMatrix();
Podsumowanie
W tym rozdziale poznaliśmy funkcje służące do rysowania kwadryk. Kwadryki w OpenGL to geometryczne kształty tworzące podstawowe „klocki" do budowy wielu innych obiektów, zarówno sztucznych, jak naturalnych. Użycie kwadryk jest wygodnym sposobem uniknięcia konieczności tworzenia dodatkowego kodu budującego tego typu kształty.
Przejdźmy teraz do listingu 13.1, programu rysującego ołówek. Listing 13.1. Program rysujący ołówek______________________________________
/*
* Niezbędne nagłówki.
*/
łinclude "texture.h" łinclude "pencil.h" tinclude <stdarg.h>
/*
* Zmienne globalne...
*/
HWND PencilWindow; /* Okno sceny */
HPALETTE PencilPalette; /* Paleta kolorów (jeśli potrzebna) */
HDC PencilDC; /* Kontekst rysowania */
HGLRC PencilRC; /* Kontekst renderowania OpenGL */
GLuint PencilTexture, /* Obraz tekstury ołówka */ LeadTexture; /* Grafit... */
GLfloat PencilRoll = 0.0, /* Położenie ołówka */
PencilPitch = 90.0,
PencilHeading = 0.0; GLUąuadricObj *PencilObj;
/*
* Funkcje lokalne...
*/
Rozdział 13. » Kwadryki: sfery, cylindry i dyski_________________________443
void DisplayErrorMessage(char *, ...);
void MakePalette(int);
LRESOLT CALLBACK PencilProc(HWND, UINT, WPARAM, LPARAM);
void LoadAllTextures(void);
void RepaintWindow(RECT *);
void PrintBitmap(void);
/*
* 'WinMainO '
*/
int APIENTRY WinMain(HINSTANCE hlnst,
HINSTANCE hPrevInstance, LPSTR IpCmdLine, int nCmdShow) {
MSG msg; WNDCLASS we; RECT rect;
we.style = 0;
wc.lpfnWndProc = (WNDPROC)PencilProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hlnstance = hlnst;
wc.hlcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = 0;
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
wc.lpszClassName = "Textured Quadric Pencil";
if (RegisterClass(Swe) == 0) {
DisplayErrorMessage("Nie udało się zarejestrowanie okna!"};
return (FALSE);
};
PencilWindow = CreateWindow("Textured Quadric Pencil",
"Ołówek stworzony z teksturowanych
kwadryk",
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 32, 32, 400, 300, NOLL, NULL, hlnst, NULL);
if (PencilWindow == NULL) {
DisplayErrorMessage("Nie udało się utworzenie okna!");
return (FALSE);
};
ShowWindow(PencilWindow, nCmdShow); OpdateWindow(PencilWindow);
444
Część II » Używanie OpenGL
// Główna pętla komunikatów while (TRUE)
while (PeekMessagetsmsg, NULL, O, O, PM_NOREMOVE) == TRUE) if (GetMessage (smsg, NULL, O, 0)) {
TranslateMessage ( smsg) ;
DlspatchMessage (&msg) ; } else
return (1) ;
* Obrót ołówka. . .
*/
PencilRoll += 1.0; PencilPitch += 2.0; PencilHeading += 3.0;
GetclientRect (Pencilwindow, Srect) ; RepaintWindow (Srect) ;
return (msg.wParam) ;
* 'DisplayErrorMessage()' - Wyświetla okno komunikatu błędu.
*/
void DisplayErrorMessage(char *format,
// We - łańcuch formatowania w stylu printfO ...) //We - Pozostałe argumenty {
va_list ap; /* Wskaźnik argumentu */ char s[1024]; /* Łańcuch wyjściowy */
if (format == NULL) return;
va_start(ap, format); vsprintf(s, format, ap); va_end(ap);
MessageBeep(MB_ICONEXCLAMATION);
MessageBox(NULL, s, "Error", MB_OK | MB_ICONEXCLAMATION) ,
/*
* 'MakePalette () ' - Jeśli trzeba, tworzy paletę kolorów.
*/
Rozdział 13. » Kwadryki: sfery, cylindry i dyski__________________________445
void
MakePalette(int pf) /* We - ID formatu pikseli */
{
PIXELFORMATDESCRIPTOR pfd;
LOGPALETTE *pPal;
int nColors;
int i,
r max, gmax, brną x;
/*
* Sprawdzenie, czy paleta jest potrzebna... V
DescribePixelFormat(PencilDC, pf, sizeof(PIKELFORMATDESCRIPTOR) , spfd);
if (!(pfd.dwFlags & PFD_NEED_PALETTE)) {
PencilPalette = NULL;
return; };
/*
* Alokowanie pamięci dla palety.
*/
nColors = l « pfd.cColorBits;
pPal = (LOGPALETTE *)malloc(sizeof(LOGPALETTE) +
nColors * sizeof(PALETTEENTRY));
pPal->palVersion = 0x300; pPal->palNumEntries = nColors;
/*
* Odczyt maksymalnych wartości barw składowych i budowanie nColors
* kolorów
*/
rmax = (l « pfd.cRedBits) - 1;
gmax = (l « pfd.cGreenBits) - 1;
bmax = (l « pfd.cBlueBits) - 1;
for (i =0; i < nColors; i ++) {
pPal->palPalEntry[i].peRed = 255 * ( (i » pfd.cRedShift) S rmax) /
rmax ;
pPal->palPalEntry[i].peGreen = 255 * ((i » pfd.cGreenShift) 4 ^gmaK) /
gmax;
pPal->palPalEntry[i].peBlue = 255 * ((i » pfd.cBlueShift) & c>bmax) /
bmax ;
pPal->palPalEntry[i].peFlags = 0;
446 ____________________________________ Część II » Używanie OpenGL
/*
* Utworzenie, wybranie i realizacja palety
*/
PencilPalette = CreatePalette (pPal) ; SelectPalette(PencilDC, PencilPalette, FALSE) ; RealizePalette (PencilDC) ;
free (pPal) ;
/*
* ' PencilProc ( ) ' - Obsługa komunikatów okna sceny.
*/
LRESULT CALLBACK PencilProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM IParam) (
int pf; /* ID formatu pikseli */
PIXELFORMATDESCRIPTOR pfd; /* informacje o formacie pikseli */
PAINTSTRUCT ps;
RECT rect;
switch (uMsg) {
case WM_CREATE :
// Pobranie kontekstu urządzenia i renderowania,
// przygotowanie obszaru klienta dla OpenGL
PencilDC = GetDC(hWnd) ;
pfd.nSize » sizeof (pfd) ;
pfd.nVersion = i;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL
PFDJ30UBLEBUFFER; // Dla OpenGL
pfd.dwLayerMask = PFD_MAIN_PLANE; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 0; pfd.cDepthBits = 32; pfd.cStencilBits = 0; pfd.cAccumBits = 0;
pf = ChoosePixelFormat (PencilDC, spfd) ; if (pf == 0)
DisplayErrorMessage (
"Program nie mógł znaleźć odpowiedniego formatu pikseli ! ") ;
else if ( !SetPixelFormat (PencilDC, pf, Spfd)) DisplayErrorMessage (
"Program nie mógł ustawić odpowiedniego formatu pikseli! ") ;
MakePalettetpf ) ;
PencilRC = wglCreateContext (PencilDC) ;
Rozdział 13. » Kwadryki: sfery, cylindry i dyski_________________________447
wglMakeCurrent(PencilDC, PencilRC);
// Ładowanie obrazów tekstur do list wyświetlania...
LoadAllTextures(); PencilObj = gluNewQuadric(); gluQuadricTexture(PencilObj, GL_TRHE); break;
case WM_SIZE : case WM_PAINT :
// Odrysowanie obszaru klienta...
BeginPaint(hWnd, ips);
GetClientRect(hWnd, srect); RepaintWindow(Srect);
EndPaint(hWnd, &ps); break;
case WM_COMMAND : /*
* Obsługa menu...
*/
switch (LOWORD(wParam))
{
case IDM_FILE_PRINT :
PrintBitmap();
break; case IDM_FILE_EXIT :
DestroyWindow(PencilWindow);
break; }; break;
case WM_QUIT : case WM_CLOSE : /*
* Zniszczenie okna,'bitmap i wyjście...
*/
DestroyWindow(PencilWindow);
exit(0); break;
case WM_DESTROY : /*
* Zwolnienie kontekstu urządzenia, kontekstu
* renderowania i palety
*/
if (PencilRC)
wglDeleteContext(PencilRC);
if (PencilDC)
ReleaseDC(PencilWindow, PencilDC);
448____________________________________Część II » Używanie OpenGL
if (PencilPalette)
Deleteobject(PencilPalette) ;
PostQuitMessage(0) ; break;
case WM_QUERYNEWPALETTE : /*
* w razie potrzeby realizacja palety...
*/
if (PencilPalette) {
SelectPalette(PencilDC, PencilPalette, FALSE);
RealizePalette(PencilDC);
InvalidateRect(hWnd, NULL, FALSE); return (TRUE);
}; break;
case WM_PALETTECHANGED: /*
* W razie potrzeby ponowne wybranie palety...
*/
if (PencilPalette SS (HWND)wParam != hWnd) {
SelectPalette(PencilDC, PencilPalette, FALSE);
RealizePalette(PencilDC);
UpdateColors(PencilDC); }; break;
default : /*
* Standardowa obsługa wszystkich innych komunikatów
*/
return (DefWindowProc(hWnd, uMsg, wParam, IParam)); };
return (FALSE);
/*
* 'LoadAllTextures()' - Ładuje tekstury dla sceny.
*/
void
LoadAllTextures(void)
{
glNewList(PencilTexture = glGenLists(1), GL_COMPILE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT), glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT), TextureLoadBitmap("pencil.bmp"); glEndList();
Rozdział 13. » Kwadryki: sfery, cylindry i dyski _________________________ 449
glNewList (LeadTexture = glGenLists (1) , GL_COMPILE) ;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTORE_WRAP_S , GL_REPEAT) ; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAPJT, GL_REPEAT) ; TextureLoadBitmap("lead.bmp") ;
glEndList () ;
/*
* ' RepaintWindow ( ) ' - Odrysowuje obszar roboczy okna sceny.
*/
void
RepaintWindow (RECT *rect) /* We - Obszar roboczy okna */ { /*
* Wyzerowanie widoku i wyczyszczenie okna na jasny błękit
*/
glYiewport (O, O, rect->right, rect->bottom) ;
glClearColor (0.7, 0.7, 1.0, 1.0);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ;
/*
* Przygotowanie przekształcenia widoku dla bieżącego
* punktu obserwacji
*/
glMatrixMode (GL_PROJECTION) ; glLoadldentity ( ) ;
gluPerspective (45. O, (float) rect->right / (float) rect->bottom, 0.1, 1000.0);
glEnable (GL_LIGHTING) ;
glEnable (GL_LIGHTO) ;
glEnable (GL_DEPTH_TEST) ;
glEnable (GL_TEXTURE_2D) ;
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL) ;
glMatrixMode(GL_MODELVIEW) ; glPushMatrix ( ) ;
glTranslatef (0.0, 0.0, -80.0);
glRotatef (PencilHeading, 0.0, -1.0, 0.0);
glRotatef (PencilPitch, 1.0, 0.0, 0.0);
glRotatef (PencilRoll, 0.0, 0.0, -1.0);
/*
* Najpierw ścianki ołówka - cylinder z sześcioma ściankami
*/
gluOuadricNormals (PencilObj , GLU_FLAT) ; glCallList (PencilTexture) ;
glPushMatrix () ;
glTranslatef (0.0, 0.0, -20.0);
gluCylinder (PencilObj , 5.0, 5.0, 40.0, 6, 2); glPopMatrix() ;
450 ____________________________________ Część II » Używanie OpenGL
/*
* Końce ołówka - stożek na czubku i piaski stożek na końcu
*/
gluQuadricNormals (PencilObj , GLU_SMOOTH) ; glCallList (LeadTexture) ;
glPushMatrix() ;
glTranslatef (0.0, 0.0, 20.0);
gluCylinder (PencilObj, 5.0, 0.0, 7.5, 6, 2); glPopMatrix () ;
glPushMatrix ( ) ;
glTranslatef (0.0, 0.0, -20.0);
/*
* Normalnie użylibyśmy dysku, ale niestety, nie pasują
* współrzędne tekstury
*/
gluCylinder (PencilObj, 5.0, 0.0, 0.0, 6, 2); glPopMatrix () ; glPopMatrix ( ) ;
/*
* Przerzucenie buforów i powrót
*/
glFinish ( ) ;
SwapBuf fers (PencilDC) ;
/*
* 'PrintBitmapO ' - Wydruk wyświetlanej sceny.
*/
void
PrintBitmap (void)
{
void *bits; /* Dane obrazu */ BITMAPINFO *info; /* Nagłówek bitmapy */
/*
* Zrzut ekranu. . .
*/
bits = ReadDIBitmap (sinf o) ;
if (bits == NULL)
{
DisplayErrorMessage ("Nie powiodło się odczytanie bitmapy z ekranu! ") ;
return;
* Wydruk bitmapy.
*/
451
Rozdział 13. » Kwadryki: sfery, cylindry i dyski
PrintDIBitmap (PencilWindow, info, bits)
* Zwolnienie pamięci i powrót...
*/
free (info) ; free(bits) ;
Podręcznik
gluCylinder
Przeznaczenie Plik nagłówkowy Składnia
Opis
Rysuje cylinder. <glu.h>
void gluCylinder(GLUquadricObj *obj, GLdouble baseRadius, GLdouble topRadius, GLdouble height, GLint slices, GLint stacks);
Ta funkcja tworzy pusty cylinder bez podstaw, ułożony wzdłuż osi z. Jeśli parametr topRadius lub bottomRadius wynosi zero, tworzony jest stożek. Cylinder ma długość height jednostek wzdłuż osi z. Parametr slices określa ilość ścian cylindra, zaś parametr stacks - ilość segmentów wzdłuż cylindra.
Parametry obj
baseRadius
topRadius
height
slices
height
Zwracana wartość Przykład Patrz także
GLUąuadricObj*: Informacje o stanie kwadryki, używane podczas rysowania.
GLdouble: Promień dolnej podstawy cylindra (Z = 0).
GLdouble: Promień górnej podstawy cylindra (Z = height).
GLdouble: Wysokość (długość) cylindra wzdłuż osi z.
GLint: Ilość bocznych ścian cylindra.
GLint: Ilość segmentów cylindra (wzdłuż osi z).
Brak.
Przejrzyj kod programu PENCIL.C na płytce CD-ROM.
gluDeleteCjuadric, gluNewQuadric, gluQuadricCallback, gluCjuadricDrawStyle, gIuQuadricNormals, gluQuadricOrientation, gluQuadricTexture
452
Część II » Używanie OpenGL
gluDeletepuadric
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
obj
Zwracana wartość Patrz także
Usuwa obiekt stanu kwadryki.
<glu.h>
void gluDeleteQuadric(GLUquadricObj *obj);
Ta funkcja usuwa obiekt stanu kwadryki. Po usunięciu obiektu nie można go ponownie użyć do rysowania.
GLUąuadricObj*: Obiekt stanu przeznaczony do usunięcia. Brak.
gluNewQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, glupuadricOrientation, gluQuadricTexture
gluDisk
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry obj
innerRadius
outerRadius
slices
height
Zwracana wartość Patrz także
Rysuje dysk. <glu.h>
void gluDisk(GLUquadricObj *obj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops);
Ta funkcja tworzy dysk prostopadły do osi z. Jeśli parametr innerRadius wynosi zero, zamiast dysku tworzony jest pierścień. Parametr slices określa ilość ścian cylindra, zaś parametr loops — ilość pierścieni dysku.
GLUąuadricObj*: Informacje o stanie kwadryki, używane podczas rysowania.
GLdouble: Wewnętrzny promień dysku. GLdouble: Zewnętrzny promień dysku. GLint: Ilość bocznych ścian dysku. GLint: Ilość pierścieni dysku. Brak.
gluDeleteQuadric, gluNewQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture
453
Rozdział 13. » Kwadryki: sfery, cylindry i dyski
gluNewQuadric
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry Zwracana wartość
Przykład Patrz także
Tworzy nowy obiekt stanu kwadryki.
<glu.h>
GLUąuadricObj *gluNewQuadric(void);
Ta funkcja tworzy nowy obiekt stanu kwadryki. Obiekt stanu kwadryki zawiera informacje określające sposób rysowania kwadryk.
Brak.
GLUąuadricObj *: NULL w przypadku braku pamięci; w przeciwnym razie wskaźnik do obiektu stanu kwadryki.
Kod programu PENCIL.C na płytce CD-ROM.
gluDeleteQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, g!uQuadricTexture
gluPartialDisk
Przeznaczenie Plik nagłówkowy Składnia
Opis
Rysuje częściowy dysk. <glu.h>
voidgluPartialDisk(GLUquadricObj *obj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops, GLdouble startAngle, GLdouble sweepAngle);
Ta funkcja tworzy część dysku prostopadłego do osi z. Jeśli parametr innerRadius wynosi zero, zamiast dysku tworzony jest pierścień. Parametr slices określa ilość ścian cylindra, zaś parametr loops - ilość pierścieni dysku. Parametr startAngle określa początkowy kąt dysku (kąt 0° to góra dysku, zaś 90° to prawa strona dysku). Parametr sweepAngle określa łuk zajmowany przez dysk, w stopniach.
Parametry obj
innerRadlus outerRadius slices height startAngle sweepAngle Zwracana wartość
GLUąuadricObj*: Informacje o stanie kwadryki, używane podczas rysowania.
GLdouble: Wewnętrzny promień dysku. GLdouble: Zewnętrzny promień dysku. GLint: Ilość bocznych ścian dysku. GLint: Ilość pierścieni dysku. GLdouble: Początkowy kąt części dysku. GLdouble: Kątowy rozmiar części dysku. Brak.
454
Część II » Używanie OpenGL
Patrz także
gluDeleteQuadric, gluNewQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture
gluQuadricCallback
Przeznaczenie Definiuje funkcję zwrotną kwadryki.
Plik nagłówkowy <glu.h>
Składnia void gluQuadricCallback(GLUquadricObj *obj, GLenum which, void
Opis Ta funkcja definiuje funkcję zwrotną wywoływaną podczas rysowania
kształtu kwadryki. Obecnie jedyną zdefiniowaną funkcją zwrotną jest
GLU_ERROR, wywoływana w momencie wystąpienia błędu biblioteki
OpenGL lub biblioteki GLU.
Parametry obj which
Zwracana wartość Patrz także
GLUąuadricObj *: Wskaźnik do obiektu informacji o stanie kwadryki. GLenum: Definiowana funkcja zwrotna. Musi nią być GLU_ERROR.
void (*)(): Funkcja zwrotna. (Otrzymuje parametr typu GLenum zawierający kod błędu).
Brak.
gluDeleteQuadric, gluNewQuadric, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture
gluQuadricDrawStyle
Przeznaczenie Plik nagłówkowy Składnia Opis Parametry
obj
drawStyle
Określa styl rysowania kwadryki.
<glu.h>
void gluQuadricDrawStyle(GLUquadricObj *obj, GLenum drawStyle);
Ta funkcja służy do wyboru stylu rysowania kwadryki.
GLUąuadricObj *: Wskaźnik do obiektu informacji o stanie kwadryki.
GLenum: Styl rysowania. Dostępne style to:
GLU_FILL Kwadryki są tworzone za pomocą wielokątów i pasków
prymitywów.
GLU_LINE Kwadryki są rysowane jako siatka odcinków.
GLU_SILHOUETTE Kwadryki są rysowane jako siatka odcinków, z tym
że rysowane są jedynie zewnętrzne krawędzie.
GLU_POINT Kwadryki są tworzone jako zbiór punktów.
455
Rozdział 13. * Kwadryki: sfery, cylindry i dyski
Zwracana wartość Brak.
Patrz także
gluDeletepuadric, gluNewQuadric, glQuadricCallback, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture
gluQuadricNormals
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry obj normals
Zwracana wartość Przykład Patrz także
Określa sposób generowania normalnych dla punktów kwadryki.
<glu.h>
void gluQuadricNormals(GLUquadricObj *obj, GLenum normals);
Ta funkcja określa sposób generowania normalnych dla kwadryk rysowanych z użyciem tego obiektu stanu.
GLUąuadricObj *: Wskaźnik do obiektu informacji o stanie kwadryki.
GLenum: Sposób generowania normalnych. Dostępne style to: GLU_NONE Normalne nie są generowane.
GLU_FLAT Normalne są generowane dla całych wielokątów, co powoduje, że kwadryka wygląda na zbudowaną ze ścianek.
GLU_SMOOTH Normalne są generowane dla poszczególnych wierzchołków, co powoduje, że powierzchnia kwadryki wydaje się gładka.
Brak.
Przykładowy program PENCIL.C na płytce CD-ROM.
gluDeleteQuadric, gluNewQuadric, glQuadricCallback, gluQuadricDrawStyle, gluQuadricOrientation, gluQuadricTexture
gluQuadricOrientation
Przeznaczenie Plik nagłówkowy Składnia Opis
Określa kierunek normalnych kwadryki.
<glu.h>
void gluQuadricOrientation(GLUquadricObj *obj, GLenum orientation);
Ta funkcja określa kierunek normalnych dla pustych obiektów. Jeśli normalne mają wskazywać na zewnątrz obiektu, parametr orientation powinien mieć wartość GLU_OUTSIDE. Jeśli normalne mają wskazywać do wnętrza obiektu, parametr orientation powinien mieć wartość GLU INSIDE.
Parametry obj
GLUquadricObj *: Wskaźnik do obiektu informacji o stanie kwadryki.
456
Część II » Używanie OpenGL
orientation GLenum: Kierunek normalnych. Dostępne kierunki to GLU_INSIDE
i GLU_OUTSIDE. Domyślnym kierunkiem jest GLU_OUTSIDE.
Zwracana wartość Brak.
Patrz także
gluDeleteQuadric, gluNewQuadric, glQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricTexture
gluQuadricTexture
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry obj textureCoords
Zwracana wartość Patrz także
Włącza lub wyłącza generowanie współrzędnych tekstury dla kwadryk. <glu.h>
void gluQuadricTexture(GLUquadricObj *obj, GLboolean textureCoords);
Ta funkcja określa, czy będą generowane współrzędne tekstury dla kwadryki.
GLUąuadricObj *: Wskaźnik do obiektu informacji o stanie kwadryki.
GLboolean: GLU_TRUE, jeśli współrzędne tekstury mają być generowane, GLU_FALSE w przeciwnym wypadku.
Brak.
gluDeleteQuadric, gluNewQuadric, glQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation
gluSphere
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry obj
slices height Zwracana wartość
Rysuje sferę. <glu.h>
void gluSphere(GLUquadricObj *obj, GLdouble radius, GLint slices, GLint stacks);
Ta funkcja tworzy sferę, ułożoną w środku układu współrzędnych. Parametr slices określa ilość południków sfery, zaś parametr stacks - ilość jej równoleżników sfery.
GLUąuadricObj*: Informacje o stanie kwadryki, używane podczas rysowania.
Glint: Ilość południków sfery. GLint: Ilość równoleżników sfery. Brak.
Rozdział 13. » Kwadryki: sfery, cylindry i dyski_________________________457
Przykład Przejrzyj kod programu PENCIL.C na płytce CD-ROM.
Patrz także gluDeleteQuadric, gluNewQuadric, gluQuadricCallback,
gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture
Część 3
Tematy zaawansowane i efekty specjalne
Jeśli czytasz tę książkę jak podręcznik, od początku do końca, masz już całkiem solidne podstawy, aby używać OpenGL do różnych celów. W trzeciej części tej książki omówimy kilka zagadnień, które uzupełnią twoją wiedzę i zrozumienie OpenGL. Ponadto poruszymy tematy związane z efektami specjalnymi oraz możliwościami biblioteki, które mogą wymagać więcej czasu na opanowanie niż zagadnienia opisywane w poprzednich częściach książki.
Najpierw, w rozdziale 14, zajmiemy się Maszyną stanu OpenGL. Dotąd jakby mimochodem poznaliśmy kilka zmiennych stanu, omawiając je tylko o tyle, o ile było to niezbędne do opanowania materiału. Teraz spojrzymy na tę koncepcję jako całość i spróbujemy ją wykorzystać. Następnie przejdziemy do omawiania buforów stosowanych w OpenGL (rozdział 15).
Wiele scen i obiektów może wiele zyskać dzięki zastosowaniu pewnych technik dostosowywania obrazu, opisywanych w rozdziale 16. Dowiesz się w nim, jak wyostrzyć lub złagodzić obraz, a także jak tworzyć efekty przezroczystości obiektów.
Generowanie złożonych powierzchni może niejednego przyprawić o ból głowy. W rozdziale 17 poznamy kilka narzędzi wysokiego poziomu, ułatwiających tworzenie takich powierzchni. Użyteczne techniki rozbijania wielokątów na trójkąty zostaną z kolei przedstawione w rozdziale 18, zaś w rozdziale 19 nauczysz się interakcji ze sceną i jej obiektami, korzystając ze wsparcia oferowanepo przez OpenGL. Przegląd API zakończymy przyjrzeniem się z bliskaiedM|^^pastosowań OpenGL. Zobaczysz w jaki
iti(P^ronionawłaśn
liotece klas C++, zwanej
sposób rzeczywistość wirtualna w Internecie jesl Open lnventor.
Rozdział 14.
Maszyna stanu OpenGL
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
* Włączać i wyłączać opcje renderowania * glEnable/glDisable
* Odczytywać stan opcji renderowania * gllsEnabled/glGetlnteger/glGetFloat/glG
etDouble
* Zachowywać i odtwarzać zmienne stanu * glPushAttrib/glPopAttrib
Stan renderowania to właśnie ten mechanizm, który sprawia, że OpenGL jest tak szybki i efektywny w renderowaniu trójwymiarowej grafiki. Zmienne stanu są podzielone na różne kategorie, dotyczące koloru, oświetlenia, teksturowania itd. Każdy tworzony kontekst renderowania (HRC) posiada własny stan renderowania, specyficzny dla okna lub bitmapy w pamięci.
W odróżnieniu od większości rozdziałów, w tym nie ma ani jednego przykładowego programu. Sposób użycia opisywanych funkcji znajdziesz we wszystkich innych rozdziałach w książce.
Podstawowe zmienne stanu OpenGL
Dwie podstawowe funkcje służące do włączania lub wyłączania opcji renderowania OpenGL to glEnable i glDisable. Przy wywołaniu tych funkcji przekazywany jest pojedynczy argument typu wyliczeniowego, na przykład taki jak GL_DEPTH_TEST:
glEnable(GL_DEPTH_TEST); // Włącza testowanie bufora głębokości glDisable(GL_DEPTH_TEST); // Wyłącza testowanie bufora głębokości
462______________________Część III » Tematy zaawansowane i efekty specjalne
Do odczytu bieżącego stanu służą funkcje gllsEnabled, gllsDisabled oraz glGetBooleanv:
// GL_TRUE jeśli jest włączone testowanie bufora głębokości... state~= gllsEnabled(GL_DEPTH_TEST);
// GL_TRUE jeśli jest wyłączone testowanie bufora głębokości... state = gllsDisabled(GL_DEPTH_TEST);
// Zwraca wartość logiczną określającą wartość zmiennej stanu state = glGetBooleanv(GL_DEPTH_TEST, Sstate);
Większość zmiennych stanu OpenGL to wartości logiczne, włączone lub wyłączone. Inne, na przykład bieżący widok, to tablice liczb całkowitych lub tablice liczb zmienno-przecinkowych dla kolorów RGBA. Do odczytu zmiennych tego typu służą funkcję glGetDoublev, glGetFloatv i glGet!ntegerv:
GLint istate[4] ; GLfloat fstate[4]; GLdouble dstate[3];
glGet!ntegerv(GL_VIEWPORT, istate); glGetFloatv(GL_CURRENT_COLOR, fstate); glGetDoublev(GL_CURRENT_NORMAL, dstate);
Na temat zmiennych stanu porozmawiamy szerzej w dalszej części rozdziału.
Zachowywanie i odtwarzanie zmiennych stanu
Podobnie jak w przypadku macierzy rzutowania, widoku modelu i tekstury, OpenGL posiada stos do przechowywania zmiennych stanu. Jednak w odróżnieniu od stosu macierzy, stos stanu umożliwia precyzyjne wybranie zmiennych, które zostaną odłożone na stos i z niego zdjęte (rysunek 14.1).
Rysunek 14.1.
Stos atrybutów w OpenGL
Informocje o słonie
Szczyt stosu
Informacje o słonie
Informacje o stanie
Informacje o słonie
Informacje o słanie
Informacje o słonie
Informacje o stanie
Informacje o słonie
Funkcje OpenGL przeznaczone do odkładania i zdejmowania atrybutów ze stosu to glPushAttrib oraz glPopAttrib. Funkcja glPushAttrib działa podobnie do funkcji glPush-Matrix, z tym że możesz wybrać zmienną stanu, którą chcesz odłożyć na stos. Aby zachować jednocześnie wszystkie zmienne stanu renderowania, możesz użyć wywołania
glPushAttrib(GL_ALL_ATTRIB_BITS);
463
Rozdział 14. * Maszyna stanu OpenGL
Zwykle jednak będziesz chciał zachować jedynie określony zestaw informacji, takich jak bieżący kolor, szerokość linii itd. OpenGL definiuje wiele stałych dla różnych rodzajów informacji (zebrano je w tabeli 14.1), na przykład:
glPushAttrib(GL_CURRENT_BIT); // Zachowuje bieżący kolor rysowania
// itd. glPushAttrib(GL_LIGHTING_BIT); // Zachowuje bieżące ustawienia
// oświetlenia glPushAttrib(GL_TEXTURING_BIT); // Bieżące ustawienia teksturowania
Gdy wykonasz renderowanie, możesz przywrócić bity stanu wywołując funkcję glPop-Attrib. Ta funkcja nie posiada argumentu i odtwarza jedynie to, co zostało odłożone na stos ostatnim poleceniem glPushAttrib.
Tabela 14.1.
Bity atrybutów funkcji glPushAttrib
Opis
Bit atrybutu
GL_ACCUM_BUFFER_BIT GL COLOR BUFFER BIT
Wartość zerowania bufora akumulacji.
Stan testowania alfa, funkcja oraz wartości. Stan mieszania kolorów, funkcja i wartości. Stan GL_DITHER. Bieżące bufory rysowania. Stan i funkcja operacji logicznych. Bieżący kolor RGBA / indeks koloru czyszczenia tła oraz maski zapisu pikseli.
Bieżący kolor RGBA lub indeks koloru. Bieżąca normalna i współrzędne tekstury. Bieżąca pozycja rastra, stan GL_CURRENT_POSITION_VALIDGL_EDGE_FLAG, GL_DEPTH_BUFFER oraz GLJDEPTHJTEST, funkcja bufora głębokości, wartość zerowania bufora głębokości, stan GL_DEPTH_WRITEMASK.
Stan GL_ALPHA_TEST, GL_AUTO_NORMAL oraz GL_BLEND. Stan zdefiniowanych przez użytkownika płaszczyzn obcinania. Stan GL_COLOR_MATERIAL, GL_CULL_FACE, GL_DEPTH_TEST, GL_DITHER, GLJFOG, GL_LIGHTi, GL_LIGHTING, GL_LINE_SMOOTH, GL_LINE_STIPPLE, GL_LOGIC_OP, GL_MAPl_x, GL_MAP2_x, GL_NORMALIZE, GL_POINT_SMOOTH, GL_POLYGON_SMOOTH, GL_POLYGON_STIPPLE, GL_SCISSOR_TEST, GL_STENCIL_TEST, GL_TEXTURE_1D, GL_TEXTURE_2D oraz GL_TEXTURE_GEN.
Stan GL_MAPl_x oraz GL_MAP2_x, końce i podział jedno-i dwuwymiarowej siatki, stan GL_AUTO_NORMAL.
Stan GL_FOG, wartość koloru mgły, gęstości mgły, początku liniowej mgły, końca liniowej mgły, indeksu mgły oraz zmiennej GL_FOG_MODE.
StanGL_PERSPECTIVE_CORRECTION_HINT, GL_POINT_SMQOTH_HINT, GL_LINE_SMOOTH_HINT, GL POLYGON SMOOTH HINT oraz GL FOG HINT.
GL CURRENT BIT
GL ENABLE BIT
GL_EVAL_BIT GL_FOG_BIT
GL HINT BIT
464
Część III » Tematy zaawansowane i efekty specjalne
Tabela 14.1.
Bity atrybutów funkcji glPushAttrib - ciąg dalszy
Bit atrybutu
Opis
GL LIGHTING BIT
Stan GL_COLOR_MATERIAL. Wartość GL_COLOR_MATERIAL_FACE. Parametry koloru materiału śledzące kolor światła otaczającego sceny. Wartości GL_LIGHT_MODEL_LOCAL_VIEWER oraz GL_L1GHT_MODEL_TWO_SIDE. Stany GLJJGHTING oraz GL_LIGHTx. Wszystkie parametry światła. Wartość GL_SHADE_MODE.
Stany GL_LINE_SMOOTH oraz GL_LINE_STIPPLE. Deseń przerywania linii oraz licznik powtórzeń. Szerokość linii.
Wartość GL_LIST_BASE.
Ustawienia GL_RED_BIAS, GL_RED_SCALE, GL_GREEN_BIAS, GL_GREEN_SCALE, GL_BLUE_BIAS, GL_BLUE_SCALE, GL_ALPHA_BIAS, GL_ALPHA_SCALE, GL_DEPTH_BIAS, GL_DEPTH_SCALE, GL_INDEX_OFFSET, GL_INDEX_SHIFT, GL_MAP_COLOR, GL_MAP_DEPTH, GL_ZOOM_X, GL_ZOOM_Y oraz GL_READ_BUFFER.
Stan GL_POINT_SMOOTH, rozmiar punktu.
GL_CULL_FACE, GL_CULL_FACE_MODE, GL_FRONT_FACE, GL_POLYGON_MODE, GL_POLYGON_SMOOTH, GL_POLYGON_STIPPLE.
Obraz desenia dla wielokątów.
Stan GL_SCISSOR_TEST, bryła nożyc.
Stan GL_STENCIL_TEST. Funkcja szablonu i wartość odniesienia. Maska wartości szablonu. Wartość zerowania bufora szablonu oraz maska zapisu.
Stan włączenia dotyczący wszystkich współrzędnych tekstur. Kolor ramki obrazu tekstury. Filtr powiększania i pomniejszania. Współrzędne tekstur i tryby powtarzania tekstur. Kolor i tryb dla każdego środowiska tekstury. Ustawienia GL_TEXTURE_GEN_x i GL_TEXTURE_GEN_MODE. Równania dla glTexGen.
Współczynniki sześciu płaszczyzn obcinania, stan włączenia płaszczyzn obcinania. Ustawienie GL_MATRIX_MODE, stan GL_NORMALIZE.
Zakres głębokości, początek i rozciągłość układu współrzędnych.
GL_LINE_BIT
GL_LIST_BIT
GL PIXEL MODĘ BIT
GL_POINT_BIT GL_POLYGON_BIT
GL_POLYGON_STIPPLE_BIT
GL_SCISSOR_B1T_BIT
GL_STENCIL_BUFFER_BIT
GL TEXTURE BIT
GL TRANSFORM BIT
GL YIEWPORT BIT
Stan rysowania
OpenGL posiada wiele zmiennych stanu związanych z rysowaniem prymitywów wewnątrz pary wywołań glBegin/glEnd. Większość z nich jest zachowywanych w wyniku wywołania glPushAttrib(GL_CURRENT_BIT | GL_LINE_BIT); opis poszczególnych zmiennych tej grupy zawiera tabela 14.2.
Rozdział 14. » Maszyna stanu OpenGL______________________________465
Tabela 14.2.
Zmienne stanu rysowania
Zmienna stanu Opis
GL_ALPHA_TEST Test wartości alfa.
GL_BLEND Operacje mieszania kolorów pikseli.
GL_CLIP_PLANEx Obcinanie prymitywów poza określoną płaszczyzną obcinania.
GL_CULL_FACE Usuwanie wielokątów zwróconych tyłem (lub przodem).
GL_DITHER Roztrząsanie kolorów.
GL_LINE_SMOOTH Antyaliasing linii.
GL_LINE_STIPPLE Rysowanie linii przerywanych.
GL_LOGIC_OP Operacje logiczne na rysowanych pikselach.
GL_POINT_SMOOTH Antyaliasing punktów.
GL_POLYGON_SMOOTH Antyaliasing wielokątów.
GL_POLYGON_STIPPLE Deseń na wielokątach.
GL_SCISSOR_TEST Obcinanie rysunku poza regionem glScissor.
Stan bufora głębokości
Najczęstszym błędem popełnianym przez początkujących programistów OpenGL jest pominięcie włączenia testowania bufora głębokości wywołaniem glEnable(GL_DE-PTH_TEST). Bez testowania bufora głębokości nie mogą być usuwane niewidoczne powierzchnie (rozdział 15). Do zachowania stanu testowania bufora głębokości (zmienna GL_DEPTH_TEST) służy wywołanie funkcji glPushAttrib z parametrem GL_DEP-TH_BUFFER_BIT.
Stan bufora szablonu
Bufor szablonu służy do uzyskiwania wielu efektów specjalnych, na przykład cieni. Podobnie jak w przypadku bufora głębokości, można bardzo łatwo sterować jego działaniem. Aby zachować stan bufora szablonu, wywołaj funkcję glPushAttrib(GL_STEN-CIL_BUFFERJ3IT). Odkładana na stos zmienna stanu to GL_STENCIL_TEST.
Stan oświetlenia
Ze wszystkich zmiennych stanu OpenGL, zmienne związane z oświetleniem są najliczniejsze. Informacje o stanie oświetlenia obejmują ustawienia bieżącego trybu koloru i światła dla środowiska oświetlenia (modelu), definicję materiału, kolor, położenie i kierunek źródeł światła, a także kilka innych parametrów. Co więcej, przy włączonym
466
Część III » Tematy zaawansowane i efekty specjalne
automatycznym generowaniu normalnych, OpenGL wykorzystuje jeszcze dodatkowe zmienne stanu oświetlenia.
Wszystkie dostępne zmienne stanu oświetlenia wymieniono w tabeli 14.3. Jako minimum, musisz wywołać przynajmniej funkcje glEnable(GL_LIGHTING) oraz glEna-ble(GL_LIGHTO). Aby zachować bieżący stan oświetlenia, wywołaj funkcję glPushAt-trib(GL_LIGHTING_BIT | GL_EVAL_BIT).
Tabela 14.3.
Zmienne stanu oświetlenia
Zmienna stanu
GL_AUTO_NORMAL GL_COLOR_MATERIAL
GLJJGHTING GL_LIGHTx GL_MAP1_NORMAL GL_MAP2_NORMAL GL NORMALIZE
Opis
Automatyczne generowanie normalnych na podstawie parametrów funkcji glMap.
Przypisywanie koloru materiału na podstawie bieżącego koloru rysowania.
Włączanie obliczeń związanych z oświetleniem.
Włączenie źródła światła x.
Włączenie odwzorowania normalnych na podstawie współrzędnych ID.
Włączenie odwzorowania normalnych na podstawie współrzędnych 2D.
Normalizowanie wszystkich normalnych przed wykonywaniem obliczeń.
Stan teksturowania
Jeśli chodzi o złożoność, teksturowanie niewiele ustępuje oświetleniu. Dostępne zmienne stanu teksturowania zawiera tabela 14.4.
Aby zachować bieżące parametry teksturowania, wywołaj funkcję glPushAttrib z parametrami GL_TEXTUR£_BIT oraz GL_EVAL_BIT. Gdy włączasz teksturowanie, pamiętaj, aby włączyć tylko jeden z dwóch trybów - albo GL_TEXTURE_1D, albo GL_TEXTURE_2D. Specyfikacja OpenGL wymaga, aby teksturowanie 2D zastępowało teksturowanie ID, ale niektóre implementacje nie spełniają tego założenia.
Tabela 14.4.
Zmienne stanu teksturowania
Opis
Zmienna stanu
GL MAPI TEXTURE COORD l
GL MAPI TEXTURE COORD 2
W wyniku wywołań funkcji glEvalPointl, glEvalMeshl orazglEvalCoordl będzie generowana współrzędna s tekstury.
W wyniku wywołań funkcji glEvalPointl, glEvalMeshl oraz glEvalCoordl będą generowane współrzędne s i t tekstury.
467
Rozdział 14. » Maszyna stanu OpenGL
Tabela 14.4.
Zmienne stanu teksturowania - ciąg dalszy
Zmienna stanu
GL_MAP1_TEXTURE_COORD_3 GL_MAP 1_TEXTURE_COORD_4 GL_MAP2_TEXTURE_COORD_1 GL_MAP2_TEXTURE_COORD_2 GL_MAP2_TEXTURE_COORD_3 GL_MAP2_TEXTURE_COORD_4
GL_TEXTURE_1D
GL_TEXTURE_2D GL_TEXTURE_GEN_Q
GL_TEXTURE_GEN_R GL_TEXTURE_GEN_S GL TEXTURE GEN T
Opis
W wyniku wywołań funkcji glEvalPointl, glEvalMeshl orazglEvalCoordl będą generowane współrzędne s, t oraz r tekstury.
W wyniku wywołań funkcji glEvalPointl, glEvalMeshl orazglEvalCoordl będą generowane współrzędne s, t, r oraz q tekstury.
W wyniku wywołań funkcji glEvalPoint2, glEvalMesh2 oraz glEvalCoord2 będzie generowana współrzędna s tekstury.
W wyniku wywołań funkcji glEvalPoint2, g!Eva!Mesh2 oraz glEvalCoord2 będą generowane współrzędne s i t tekstury.
W wyniku wywołań funkcji glEvalPoint2, glEvalMesh2 oraz glEvalCoord2 będą generowane współrzędne s, t oraz r tekstury.
W wyniku wywołań funkcji glEvalPoint2, glEvalMesh2 oraz glEvalCoord2 będą generowane współrzędne s, t, r oraz q tekstury.
Włączenie teksturowania ID, chyba że zostanie włączone teksturowanie 2D.
Włączenie teksturowania 2D.
Przy wywołaniach funkcji glVertex automatyczne generowanie współrzędnej q tekstury.
Przy wywołaniach funkcji glVertex automatyczne generowanie współrzędnej r tekstury.
Przy wywołaniach funkcji glVertex automatyczne generowanie współrzędnej s tekstury.
Przy wywołaniach funkcji glVertex automatyczne generowanie współrzędnej t tekstury.
Stan pikseli
Transfer pikseli, przechowywanie oraz tryby odwzorowania są najczęściej najmniej zrozumiałymi i najmniej zoptymalizowanymi elementami OpenGL. Do zachowania ich stanu służy wywołanie glPusriAttrib(GL_PIXEL_BIT). Dla tych trybów nie ma zmiennych ustawianych funkcjąglEnable.
468 Część III » Tematy zaawansowane i efekty specjalne
Podręcznik
glDisable / glEnable
Przeznaczenie Włącza lub wyłącza wskazaną opcję OpenGL.
Plik nagłówkowy <gl.h>
Składnia void glDisable(GLenum feature);
glEnable
Opis glDisable wyłącza wskazaną opcję OpenGL. glEnable włącza wskazaną
opcję OpenGL.
Parametry
feature GLenum: Opcja przeznaczona do włączenia lub wyłączenia. Dostępne
opcje zebrano w tabeli 14.5.
Zwracana wartość Brak.
Patrz także gllsEnabled, glPopAttrib, glPushArtrib
Tabela 14.5.
Opcje -włączane i wytaczane funkcjami glEnable i glDisable
Opcja Opis
GL_AUTO_NORMAL Automatyczne generowanie normalnych na podstawie parametrów
funkcjiglMap.
GL_COLOR_MATERIAL Przypisywanie koloru materiału na podstawie bieżącego koloru
rysowania.
GL_LIGHTING Włączanie obliczeń związanych z oświetleniem.
GL_LIGHTx Włączenie źródła światła x.
GL_MAP 1_NORMAL Włączenie odwzorowania normalnych na podstawie
współrzędnych ID.
GL_MAP2_NORMAL Włączenie odwzorowania normalnych na podstawie
współrzędnych 2D.
GL_NORMALIZE Normalizowanie wszystkich normalnych przed wykonywaniem
obliczeń.
GL_MAP1_TEXTURE_COORD_1 W wyniku wywołań funkcji glEvalPointl, glEvalMeshl oraz
glEvalCoordl będzie generowana współrzędna s tekstury.
GL_MAP 1_TEXTURE_COORD_2 W wyniku wywołań funkcji glEvalPointl, glEvalMeshl oraz
glEvalCoordl będą generowane współrzędne s i t tekstury.
GL_MAP1_TEXTURE_COORD_3 W wyniku wywołań funkcji glEvalPointl, glEvalMeshl oraz
glEvalCoord l będą generowane współrzędne s, t oraz r tekstury.
469
Rozdział 14. * Maszyna stanu OpenGL
Tabela 14.5.
Opcje włączane i wyłączane funkcjami glEnable i glDisable - ciąg dalszy
Opcja
GL_MAP1_TEXTURE_COORD_4 GL_MAP2_TEXTURE_COORD_1 GL_MAP2_TEXTURE_COORD_2 GL_MAP2_TEXTURE_COORD_3 GL_MAP2_TEXTURE_COORD_4 GL_TEXTURE_1D
GL_TEXTURE_2D GL_TEXTURE_GEN_Q
GL_TEXTURE_GEN_R GL_TEXTURE_GEN_S GL_TEXTURE_GEN_T
GL_STENCIL_TEST
GL_DEPTH_TEST
GL_ALPHA_TEST
GL_BLEND
GL_CLIP_PLANEx
GL_CULL_FACE
GLJ3ITHER
GL_LINE_SMOOTH
GL_LINE_STIPPLE
GL_LOGIC_OP
GL_POINT_SMOOTH
GL_POLYGON_SMOOTH
GL_POLYGON_STIPPLE
GL SCISSOR TEST
Opis
W wyniku wywołań funkcji glEvalPointl, glEvalMeshl oraz glEvalCoordl będą generowane współrzędne s, t, r oraz q tekstury.
W wyniku wywołań funkcji glEvalPoint2, glEvalMesh2 oraz glEvalCoord2 będzie generowana współrzędna s tekstury.
W wyniku wywołań funkcji glEvalPoint2, glEvalMesh2 oraz glEvalCoord2 będą generowane współrzędne s i t tekstury.
W wyniku wywołań funkcji glEvalPoint2, glEvalMesh2 oraz glEvalCoord2 będą generowane współrzędne s, t oraz r tekstury.
W wyniku wywołań funkcji glEvalPoint2, glEvalMesh2 oraz glEvalCoord2 będą generowane współrzędne s, t, r oraz q tekstury.
Włączenie teksturowania l D, chyba że zostanie włączone teksturowanie 2D.
Włączenie teksturowania 2D.
Przy wywołaniach funkcji glVertex automatyczne generowanie współrzędnej q tekstury.
Przy wywołaniach funkcji glVertex automatyczne generowanie współrzędnej r tekstury.
Przy wywołaniach funkcji glVertex automatyczne generowanie współrzędnej s tekstury.
Przy wywołaniach funkcji glVertex automatyczne generowanie współrzędnej t tekstury.
Test bufora szablonu.
Test bufora głębokości.
Test wartości alfa.
Operacje mieszania kolorów pikseli.
Obcinanie prymitywów poza określoną płaszczyzną obcinania.
Usuwanie wielokątów zwróconych tyłem (lub przodem).
Roztrząsanie kolorów.
Antyaliasing linii.
Rysowanie linii przerywanych.
Operacje logiczne na rysowanych pikselach.
Antyaliasing punktów.
Antyaliasing wielokątów.
Deseń na wielokątach.
Obcinanie rysunku poza regionem glScissor.
470
Część III » Tematy zaawansowane i efekty specjalne
gllsEnabled
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
feature Zwracana wartość
Patrz także
Sprawdza, czy dana opcja OpenGL jest włączona.
GLboolean gl!sEnabled(GLenum feature);
Ta funkcja zwraca wartość GL_TRUE, jeśli wskazana opcja jest włączona, zaś wartość GL_FALSE w przeciwnym wypadku.
GLenum: Sprawdzana opcja. Dostępne opcje zebrano w tabeli 14.5.
GLboolean: GLJTRUE, jeśli dana opcja jest włączona, a GL_FALSE w przeciwnym wypadku.
glDisable, glEnable, glPopAttrib, glPushAttrib
glPopAttrib
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry Zwracana wartość Patrz także
Odtwarza zmienne stanu zachowane uprzednio funkcją glPushAttrib.
void glPopAttrib(void);
glPopAttrib odtwarza wartość zmiennych stanu zachowanych uprzednim wywołaniem funkcji glPushAttrib. Jeśli stos atrybutów jest pusty, ustawiany jest znacznik błędu OpenGL, zaś samo wywołanie jest ignorowane.
Brak. Brak. glDisable, glEnable, gllsEnabled, glPushAttrib
glPushAttrib
Przeznaczenie Plik nagłówkowy Składnia Opis
Odkłada na stos zmienne stanu OpenGL.
void glPushAttrib(GLuint bits);
Ta funkcja zachowuje zmienne stanu OpenGL określone bitami parametru bits. Jeśli stos atrybutów jest pełny, ustawiany jest znacznik błędu OpenGL, zaś odkładane zmienne są umieszczane na szczycie stosu, zastępując istniejące tam dane.
1 Rozdział 14. » Maszyna stanu OpenGL_____________________________471
Parametry
bits GLuint: Zestaw zmiennych przeznaczonych do odłożenia na stos (patrz
tabela 14.1).
! Zwracana wartość Brak.
Patrz także glDisable, glEnable, gllsEnabled, glPopAttrib
Rozdział 15.
Bufory: nie tylko do animacji
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
+ Przygotować bufory * ChoosePixelFormat/SetPixelFormat
* Użyć bufora głębokości * glEnable/glDepthFunc/glDepthRange
* Użyć bufora szablonu * glEnable/glStencilFunc
* Użyć bufora akumulacji * glEnable/glAccum
W poprzednich rozdziałach korzystaliśmy z bufora koloru i głębokości. OpenGL posiada kilka rodzajów buforów, związanych z kontekstem urządzenia graficznego:
+ Bufor koloru;
* Bufor głębokości;
* Bufor szablonu;
* Bufor akumulacji.
Jak zobaczysz w tym rozdziale, każdy bufor posiada określone właściwości, wykorzystywane nie tylko przy podwójnie buforowanej animacji i podczas usuwania niewidocznych powierzchni.
Czym są bufory?
Bufor w OpenGL to w zasadzie dwuwymiarowa tablica wartości powiązanych z pikse-lami okna lub bitmapy w pamięci. Każdy bufor zawiera tę samą liczbę kolumn i wierszy
474
Część III » Tematy zaawansowane i efekty specjalne
(ma tą samą szerokość i wysokość) co obszar roboczy bieżącego okna, z tym że w buforze przechowywane są dane innego rodzaju i z innego zakresu. Spójrz na rysunek 15.1.
Rysunek 15.1.
Organizacja buforów w OpenGL
Konfigurowanie buforów
Zanim użyjesz OpenGL, musisz skonfigurować kontekst urządzenia okna (HDC), przygotowując wymagane bufory i tryb koloru. Te informacje zawiera struktura PIXEL-FORMATDESCRIPTOR. Oto typowy sposób przygotowywania buforów:
// Ta struktura przechowuje informacje o buforach, warstwach i trybie // koloru PIXELFORMATDESCRIPTOR pfd;
// Najpierw zainicjujemy rozmiar i wersję struktury... pfd.nSize = sizeof(pfd); pfd.version = 1;
// Następnie informacje o warstwach i buforach... pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; pfd.dwLayerMask = PFD_MAIN_PLANE; pfd.iLayerType = PFD_MAIN_PLANE;
// Typ pikseli wskazuje czy chcemy użyć indeksów kolorów czy RGBA pfd.iPixelType = PFD_TYPE_RGBA;
// Teraz możemy określić *minimalną* ilość potrzebnych nam bitplanów
// dla każdego bufora. Windows wybierze format pikseli najbardziej
// zbliżony do opisanego
pfd.cColorBits = 8;
pfd.cDepthBits = 16;
pfd.cAccumBits = 0;
pfd.cStencilBits = 0;
Pole bitowe dwFlags określa, czy chcemy rysować w oknie za pomocą OpenGL. Oprócz tego informuje Windows o ilości buforów koloru, jakiej wymagamy. Spójrz na tabelę 15.1.
Rozdział 15. » Bufory: nie tylko do animacji____________________________475
Tabela 15.1.
Znaczniki opcji w strukturze PKELFORMATDESCRIPTION
Znacznik Opis
PFD_DRAW_TO_W1NDOW Rysowanie w oknie.
PFD_DRAW_TO_BITMAP Rysowanie na bitmapie w pamięci.
PFD_SUPPORT_GDI Bufor kolorów obsługuje funkcje GDI.
PFD_SUPPORT_OPENGL Bufor kolorów obsługuje polecenia OpenGL.
PFD_DOUBLEBUFFER Wartości kolorów są podwójnie buforowane.
PFD_STEREO Dostępne są dwa zestawy buforów (lewy i prawy).
PFD_DOUBLE_BUFFER_DONT_CARE Nie ma znaczenia, czy wartości kolorów są
podwójnie buforowane.
PFD_STEREO_DONTCARE Nie ma znaczenia, czy obraz jest stereoskopowy.
Pola dwLayerMask i iLayerType określają plan rysowania, który ma zostać użyty, i zwykle są ustawiane na PFD_MAIN_PLANE. Niektóre karty graficzne OpenGL umożliwiają wykorzystywanie pomocniczych buforów pod lub nad normalnym planem kolorów Windows, pozwalając na tworzenie menu lub innych obiektów graficznych bez niszczenia zawartości głównego planu. Ogólna implementacja OpenGL w Windows nie obsługuje pomocniczych planów rysunkowych.
Pole iPixelType określa sposób reprezentowania wartości kolorów i może zawierać jedną z dwóch wartości z tabeli 15.2.
Tabela 15.2.
Typypikseli
Typ pikseli Opis
PFD_TYPE_RGBA Kolory są złożone ze składowych czerwonej, zielonej, niebieskiej i alfa.
PFD_TYPE_COLORINDEX Kolory są reprezentowane jako indeksy bieżącej palety logicznej.
Pola cColorBits, cDepthBits, cAccumBits oraz cStencilBits określają rozmiar każdego z buforów. Umieszczenie w jednym z tych pól wartości O powoduje wyłączenie danego bufora, z wyjątkiem pola cColorBits. Jeśli w polu cColorBits umieścisz wartość O, Windows zastosuje najmniejszą możliwą ilość bitów na kolor, zwykle 4 lub osiem (16 lub 256 kolorów). Gdy iPixelType zostanie ustawione na PFD_TYPE_RGBA, pole cColorBits określa łączną ilość bitów czerwieni, zieleni i niebieskiego. Obecna ogólna implementacja OpenGL Microsoftu nie obsługuje bitów kanału alfa.
Gdy wypełnisz strukturę PIKELFORMATDESCRIPTOR koniecznymi informacjami, za pomocą kilku prostych wywołań możesz ustawić format pikseli dla okna:
// Uchwyt kontekstu urządzenia dla okna HDC hdc;
476_______________________Część III » Tematy zaawansowane i efekty specjalne
// Kod formatu pikseli Windows int pf;
// Wybór i ustawienie formatu pikseli... pf = ChoosePixelFormat(hdc, &pfd); if(pf == 0) {
// Nie powiodło się znalezienie odpowiedniego formatu pikseli...
MessageBox(NULL,"Nie powiodło się wybranie formatu pikseli!",
"Błąd!", MB_OK); }
else if (!SetPixelFormat(hdc, pf, spfd) {
// Nie powiodło się ustawienie formatu pikseli...
MessageBox(NULL," Nie powiodło się ustawienie formatu pikseli!",
"Błąd!", MB_OK); }
Po wywołaniu funkcji ChoosePixelFormat, struktura P1XELFORMATDESCRIPTOR jest wypełniana dostępnymi w systemie wartościami, najbardziej zbliżonymi do wymaganych. Przy powrocie, pole dwFlags może zawierać dodatkowe znaczniki, na które powinieneś zwrócić uwagę. Te znaczniki zostały zebrane w tabeli 15.3.
Tabela 15.3.
Znaczniki ustawiane przez funkcją ChoosePixelFormat
Ustawiany znacznik Opis
PFD_GENERIC_FORMAT Żądany format jest obsługiwany przez ogólną implementację.
PFD_NEED_PALETTE Bufor kolorów RGBA będzie wyświetlany na urządzeniu
korzystającym z palety, więc musi istnieć paleta logiczna.
PFD_NEED_SYSTEM_PALETTE Wartości kolorów do poprawnego wyświetlenia wymagają palety
systemowej. Wywołaj funkcję SetSystemPaletteUse() w celu wymuszenia jednoznacznego odwzorowania palety logicznej na systemową.
Jeśli znacznik PFD_NEED_PALETTE będzie ustawiony, powinieneś zdefiniować paletę logiczną określoną wartościami pól cRedBits, cRedShift, cGreenBits, cGreenShift, cBlueBits oraz cBlueShift. Oto przykład definiowania palety:
HDC hdc;
PIXELFORMATDESCRIPTOR pfd; LOGPALETTE *pal; int i,
Pf,
num_colors,
red, num_reds,
green, num_greens,
blue, num_blues;
// Pobranie indeksu formatu pikseli oraz deskryptora formatu pikseli
pf = GetPixelFormat(hdc);
DescribePixelFormat(hdc, pf, sizeof(PIKELFORMATDESCRIPTOR), spfd);
Rozdział 15. » Bufory: nie tylko do animacji____________________________477
// Czy ten format pikseli wymaga palety?
if(pfd.dwFlags & PFD_NEED_PALETTE)
{
// Tak. Paleta jest potrzebna.
// Zaalokowanie pamięci na strukturę logicznej palety
// i wszystkie jej pozycje
num_colors = l « pfd.cColorBits;
pal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) +
num_colors*sizeof(PALETTEENTRY));
// Wypełnienie nagłówka palety pal->palVersion = 0x300; // Windows 3.0 pal->palNumEntries = num_colors; // rozmiar palety
num_reds = (l « pfd.cRedBits) -1; num_greens = (l « pfd.cGreenBits) - 1; num_blues = (l « pfd.cBlueBits) -1;
for(blue =0, i = 0; blue <= num_blues; blue++) for(green = 0; green <= num_greens; green++) for(red = 0; red <= num_reds; red++) {
pal->palPalEntry[i].peRed = 255 * red / num_reds; pal->palPalEntry[i].peGreen = 255 * green / num_greens; pal->palPalEntry[i].peBlue = 255 * blue / num_blues; pal->palPalEntry[i].peFlags = 0; }
palette = CreatePalette(pal); SelectPalettefhdc, palette, FALSE); RealizePalette(hdc);
Bufor koloru
Bufor koloru przechowuje informacje o kolorze pikseli. Każdy piksel może zawierać indeks koloru lub składowe RGBA (red/green/lue/alpha) opisujące kolor danego piksela. Piksele RGBA są wyświetlane bezpośrednio przy użyciu najbliższego dostępnego koloru na ekranie. Ogólna implementacja OpenGL Microsoftu nie obsługuje obecnie składowej koloru alfa.
Wygląd pikseli indeksu kolorów jest określany przez pobranie koloru RGB z pozycji palety wyznaczonej przez dany indeks. W Windows palety są zaimplementowane jako logiczne palety kolorów. Tryb indeksu koloru jest bardzo przydatny przy graficznej reprezentacji danych tabelarycznych, na przykład mierników siły lub natężenia, co przedstawimy w drugim przykładzie zastosowania bufora głębokości, w sekcji „Inne zastosowanie bufora głębokości".
Podwójne buforowanie
Podwójne buforowanie wykorzystuje dodatkowy bufor koloru w pamięci, często używany w animacji. Przy podwójnym buforowaniu możesz narysować całą scenę w pa-
478______________________Część III » Tematy zaawansowane i efekty specjalne
mięci, poza ekranem, a następnie szybko „przerzucić" j ą na ekran, eliminując w ten sposób nieprzyjemne migotanie obrazu. Podwójne buforowanie wpływa jedynie na bufor koloru; nie ma drugiego bufora dla głębokości, szablonu czy akumulacji. Jeśli wybierzesz format pikseli z podwójnym buforowaniem, OpenGL wybierze do rysowania „tylny" bufor. Możesz zmienić to domyślne zachowanie używając funkcji glDrawBuf-fer z jednym z parametrów z tabeli 15.4.
Tabela 15.4.
Dostępne parametry funkcji glDrawBuffer
Bufor Opis
GL_FRONT OpenGL rysuje w przednim (widocznym) buforze.
GL BACK OpenGL rysuje w tylnym (niewidocznym) buforze.
GL_FRONT_AND_BACK OpenGL rysuje w obu buforach naraz.
Buforowanie stereo
Buforowanie stereo wykorzystuje dodatkowy bufor koloru w trybie pojedynczego buforowania oraz dwa dodatkowe bufory w trybie podwójnego buforowania, w celu przechowania obrazu osobno dla lewego i prawego oka (tabela 15.5). Poprzez wygenerowanie osobnych trójwymiarowych obrazów, przesuniętych o kilka „centymetrów" w stosunku do siebie dla zasymulowania rozstawu oczu, można wygenerować prawdziwe trójwymiarowe obrazy. W przypadku większości kart graficznych PC buforowanie stereo nie jest jednak dostępne.
Oprócz wyboru przedniego lub tylnego bufora, za pomocą funkcji glDrawBuffer można wybrać także bufor dla lewego lub prawego oka.
Tabela 15.5.
Bufory stereo
Bufor Opis
GL_LEFT_FRONT Rysowanie w lewym przednim buforze.
GL_LEFT_BACK Rysowanie w lewym tylnym buforze.
GL_RIGHT_FRONT Rysowanie w prawym przednim buforze.
GL_R1GHT_BACK Rysowanie w prawym tylnym buforze.
GL_FRONT Rysowanie w obu przednich buforach.
GL_BACK Rysowanie w obu tylnych buforach.
Przerzucanie buforów
OpenGL obsługuje podwójne buforowanie, lecz w samym OpenGL nie ma żadnej funkcji przerzucającej zawartość przedniego i tylnego bufora! Na szczęście, w każdej im-
Rozdział 15. » Bufory: nie tylko do animacji___________________________479
plementacji OpenGL występuje pomocnicza funkcja umożliwiająca wyświetlenie zawartości bufora na ekranie. W Windows jest nią
SwapBuffers(hdc);
gdzie hdc to kontekst urządzenia okna, w którym rysujesz. Jeśli wybrałeś format pikseli z buforowaniem stereo, obrazy dla lewego i prawego oka są przerzucane jednocześnie.
Bufor głębokości
Bufor głębokości przechowuje wartość głębokości dla każdego piksela. Każda wartość reprezentuje odległość piksela od obserwatora, przeskalowaną do bieżącej bryły obcinania, a właściwie jej bliższej i dalszej płaszczyzny. Programowa implementacja OpenGL w Windows obsługuje zarówno 16-, jak i 32-bitowe wartości bufora głębokości.
Bufor głębokości zwykle jest wykorzystywany w celu usuwania niewidocznych powierzchni. Usuwanie niewidocznych powierzchni jest procesem, który w rzeczywistym świecie odbywa się w sposób naturalny; gdy nieprzezroczysty obiekt zostanie umieszczony przed innym obiektem, obiekt bliższy zasłania część lub całość obiektu leżącego dalej.
W OpenGL, bufora głębokości można użyć w celu uzyskania interesujących efektów, takich jak na przykład usunięcie przedniej części obiektu w celu przedstawienia jego wnętrza.
Porównywanie głębokości
Gdy rysujesz w oknie posługując się OpenGL, pozycja Z każdego piksela jest porównywana z wartością w buforze głębokości. Jeśli wynik porównania wynosi True, piksel, wraz ze swoją głębokością, jest umieszczany w buforze kolorów. OpenGL definiuje osiem funkcji porównań wartości w buforze głębokości (tabela 15.6). Domyślną funkcją porównania jest GL_LESS. Aby ją zmienić, wywołaj funkcję glDepthFunction.
glDepthFunction(function);
Jeśli jest używana funkcja GL_LESS, piksele wielokąta są rysowane wtedy, gdy wartość głębokości piksela jest mniejsza niż wartość piksela w buforze głębokości (rysunki 15.2ail5.2b).
Tabela 15.6.
Funkcje porównywania głębokości
Nazwa Funkcja
GL_NEVER Zawsze False
GL_LESS True jeśli Z piksela < Z bufora
480
Część III » Tematy zaawansowane i efekty specjalne
Tabela 15.8.
Funkcje porównywania głębokości - ciąg dalszy
Funkcja
Nazwa
QL_EQUAL
GL_LEQUAL
GL_GREATER
GL_NOTEQUAL
GL_GEQUAL
GL ALWAYS
True jeśli Z piksela = Z bufora True jeśli Z piksela <= Z bufora True jeśli Z piksela > Z bufora True jeśli Z piksela != Z bufora True jeśli Z piksela >= Z bufora Zawsze True
Rysunek 15.2a.
Typowe działanie bufora głębokości przy porównaniach GL LESS
Rysunek 15.2b.
Typowe działanie bufora głębokości przy porównaniach GL GREATER
Rozdział 15. » Bufory: nie tylko do animacji___________________________481
Wartości głębokości
Gdy używasz porównań głębokości GL_EQUAL i GL_NOTEQUAL, czasem konieczna jest zmiana zakresu wykorzystywanych wartości głębokości, w celu zredukowania liczby dostępnych wartości (zmniejszenia ilości wartości do minimum). W tym celu możesz użyć funkcji glDepthRange:
glDepthRange(nera, far);
Parametry near i far to liczby zmiennoprzecinkowe z zakresu od 0,0 do 1,0 włącznie. Domyślne ustawienia to 0,0 dla near i 1,0 dla/ar. Zwykle near jest mniejsze niż far, ale możesz zmienić tę kolejność w celu uzyskania specjalnego efektu (lub użyć funkcji GL_GRATER lub GL_GEQUAL). Zredukowanie zakresu wartości przechowywanych w buforze głębokości nie wpływa na obcinanie, lecz powoduje, że bufor głębokości staje się mniej dokładny, i może prowadzić do błędów w usuwaniu niewidocznych powierzchni na ekranie.
W niektórych porównaniach głębokości potrzebna jest inna początkowa wartość głębokości. Domyślnie, bufor głębokości jest czyszczony przez wpisanie funkcją glColor wartości 1,0. Aby określić inną wartość, użyj funkcji glClearDepth:
glClearDepth(depth);
Parametr depth to liczba zmiennoprzecinkowa z zakresu od 0,0 do 1,0 włącznie, chyba że za pomocą funkcji glDepthRange zdefiniujesz mniejszy zakres. Ogólnie, w przypadku porównań GL_GREATER lub GL_GEQUAL użyj wartości 0,0, zaś w przypadku porównań GL_LESS lub GL_LEQUAL - wartości 1,0.
Zastosowania bufora głębokości
Powszechnym zastosowaniem bufora głębokości jest usuwanie niewidocznych powierzchni. Ten rodzaj aplikacji zaprezentowano na listingu 15.1. Kluczem do tego programu jest użycie funkcji glDepthFunc i glClearDepth:
glDepthFunc(depth_func);
Używamy tu zmiennej globalnej przechowującej bieżącą funkcję porównywania głębokości. W momencie uruchomienia programu zmiennej depth_func jest przypisywana wartość GL_LESS. Gdy użytkownik wciśnie klawisz D, funkcja zwrotna toggie_depth przełącza funkcję porównywania głębokości pomiędzy GLJLESS a GL_GREATER.
if (depth_function == GL_LESS)
depth_function = GL_GREATER; else
depth_function = GL_LESS;
Wywołanie glClearDepth jest potrzebne do ustawienia poprawnej początkowej wartości głębokości dla okna, gdyż domyślną wartością głębokości jest 1,0. Gdyby funkcja porównywania została ustawiona na GL_GREATER, nie zostałby narysowany żaden piksel, gdyż żaden piksel nie miałby wartości większej niż 1,0.
482 ______________________ Część III » Tematy zaawansowane i efekty specjalne
Listing 15.1. Przykład użycia bufora głębokości _________________________________
/*
* "depth.c" - Program demonstracyjny ilustrujący użycie
* funkcji glDepthFunc () .
*
* Wciśnij klawisz 'd' w celu przełączenia się pomiędzy funkcjami
* porównań GL_LESS i GL_GREATER. Wciśnij 'Esc1 w celu zakończenia
* programu.
*/
łinclude <GL/glaux.h>
/*
* Te definicje zostały wprowadzone w celu zapewnienia zgodności
* pomiędzy MS Windows a resztą świata.
*
* CALLBACK i APIENTRY to modyfikatory funkcji w MS Windows.
*/
łifndef WIN32
# define CALLBACK
# define APIENTRY
#endif /* IWIN32 */
GLenum depth_function = GL_LESS; // Bieżąca funkcja porównywania
// głębokości
/*
* 'reshape_scene () ' - Zmiana rozmiaru sceny...
*/
void CALLBACK
reshape_scene (GLsizei width, /* We - Szerokość okna w pikselach */
GLsizei height) /* We - Wysokość okna w pikselach */ { /*
* Wyzerowanie bieżącego widoku i przekształcenia perspektywicznego.
*/
glYiewport (O, O, width, height);
glMatrixMode (GL_PROJECTION) ;
glLoadldentity ( ) ;
gluPerspective(22.5, (float) width / (float) height, 0.1, 1000.0);
glMatrixMode (GL_MODELVIEW) ;
/*
* 'draw_scene () ' - Rysowanie sceny zawierającej kostkę
* i przesłaniającą ją kulę.
*/
Rozdział 15. » Bufory: nie tylko do animacji____________________________483
void CALLBACK draw_scene(void) {
static float red_light[4] = { 1.0, 0.0, 0.0, 1.0 };
static float red_pos[4] = { 1.0, 1.0, 1.0, 0.0 };
static float blue_light[4] = { 0.0, 0.0, 1.0, 1.0 };
static float blue_pos[4] = { -1.0, -1.0, -1.0, 0.0 );
/*
* Włączenie buforów i oświetlenia
*/
glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHTO); glEnable(GL_LIGHT1);
glshadeModel(GL_SMOOTH); glDepthFunc(depth_function);
/*
* Wyczyszczenie bufora koloru i głębokości.
*/
if (depth_function == GL_LESS)
glClearDepth(l.O); else
glClearDepth(0.0);
glClearColor{0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT l GL_DEPTH_BUFFER_BIT);
/*
* Rysowanie kostki i kuli w różnych kolorach
*
* W scenie umieściliśmy dwa światła. Pierwsze z nich jest czerwone
* i zostało umieszczone u góry z prawej strony, za obserwatorem.
* Drugie jest niebieskie i znajduje się u dołu po lewej stronie,
* przed obserwatorem.
*/
glLightfv(Gt_LIGHTO, GL_DIFFUSE, red_light); glLightfv(GL_LIGHTO, GL_POSITION, red_pos);
glLightfv(GL_LIGHTl, GL_DIFFUSE, blue_light); glLightfv(GL_LIGHTl, GL_POSITION, blue_pos);
glPushMatrix();
glTranslatef(-1.0, 0.0, -20.0);
auxSolidSphere(1.0) ; glPopMatrix() ;
glPushMatrix();
glTranslatef(1.0, 0.0, -20.0);
glRotatef(15.0, 0.0, 1.0, 0.0);
glRotatef(15.0, 0.0, 0.0, 1.0);
auxSolidCube(2.0); glPopMatrix();
484 ______________________ Część III » Tematy zaawansowane i efekty specjalne
glFlushO ;
/*
* ' toggle_depth() ' - Przełącza porównywanie głębokości pomiędzy
* funkcjami GL_LESS i GL_GREATER.
*/
V0id CALLBACK toggle_depth (void) {
if (depth_function == GL_LESS) depth_function = GL_GREATER; else
depth_function = GL_LESS;
/*
* 'main() ' - Inicjowanie okna i wyświetlanie sceny do momentu
* wciśnięcia klawisza 'Esc'.
*/
void
main(void)
(
aUKlnitDisplayMode (AOX_RGB | AUX_SINGLE | AUX_DEPTH) ;
auxlnitwindow( "Funkcje głębokości") ;
auxKeyFunc (AUX_d, toggle_depth) ; auxReshapeFunc (reshape_scene) ;
auxMainLoop (draw_scene) ;
* Koniec pliku "depth.c"
*/
Inne zastosowanie bufora głębokości
Bufor głębokości może zostać użyty do wygenerowania konturu sceny, przedstawiającego różne kolory dla różnych głębokości. Mapy konturowe mogą być generowane przy użyciu funkcji glReadPixels w celu odczytania komponentu głębokości:
glReadPixels(x, y, width, height, GL_DEPTH_COMPONENT, type, pixels);
Zwrócona wartość głębokości może zostać przeskalowana i przypisana wartościom kolorów, wyświetlanych jako kontury obrazu, szczególnie w trybach indeksu kolorów, na przykład tak:
łdefine WIDTH 320
#define HEIGHT 200
GLfloat pixels[WIDTH * HEIGHT];
int i;
Rozdział 15. » Bufory: nie tylko do animacji____________________________48S
// Rysowanie sceny... glEnable(GL_DEPTH_TEST);
// Odczyt bufora głębokości
glReadPixels(O, O, WIDTH, HEIGHT, GL_DEPTH_COMPONENT, GL_FLOAT,
pixels);
// Zamiana wartości głębokości na indeksy kolorów for(i = 0; i < WIDTH * HEIGHT; i++)
pixels[i] = pixels[i] * 255.0; // Zakładamy paletę z 256 pozycjami // Wyświetlenie nowych pikseli na ekranie glDisable(GL_DEPTH_TEST); glDrawPixels(O, O, WIDTH, HEIGHT, GL_COLOR_INDEX, GL_FLOAT, pixels);
W rzeczywistych aplikacjach, prawdopodobnie umożliwiłbyś użytkownikowi sterowanie paletą kolorów i zakresem wartości. Możesz użyć wartości kolorów RGBA do poprawienia wyglądu sceny, posługując się funkcją glBlendFunc w celu wymieszania „normalnego" obrazu z obrazem „głębokości".
Wycinanie fragmentów sceny
Zobaczmy, jak można wyciąć fragmenty sceny - na przykład blok silnika - w celu pokazania wewnętrznego działania, którego normalnie nie moglibyśmy zobaczyć. Przykład takiego zastosowania bufora głębokości zawiera listing 15.2. Sercem programu jest funkcja draw_scene, rysująca obraz kostki i kuli przeciętych ruchomą płaszczyzną. Aby wyciąć fragment sceny, najpierw rysujemy płaszczyznę obcinającą. Zamiast jednak rysować ją w buforze koloru, za pomocą funkcji glDrawBuffer blokujemy rysowanie w buforze kolorów:
glDrawBuffer(GL_NONE);
glColor3i(0, O, 0); glBegin(GL_POLYGON);
glVertex3f(-100.0, 100.0, cutting_plane);
glVertex3f(100.0, 100.0, cutting_plane);
glVertex3f(100.0, -100.0, cutting_plane);
glVertex3f(-100.0, -100.0, cutting_plane); glEnd();
glDrawBuffer(GL_BACK);
Po narysowaniu płaszczyzny cięcia, włączamy rysowanie w buforze koloru i normalnie rysujemy kostkę i kulę. Narysowana niewidoczna płaszczyzna ogranicza rysowane pi-ksele jedynie do tych, które leżą poza nią, przez co uzyskujemy efekt usunięcia fragmentu sceny.
Listing 15.2. Użycie funkcji glDrawBuffer w celu wycięcia wy branych fragmentów obiektów__
"depthcut.c" - Testowy program demonstrujący użycie funkcji glDepthFunc()i glDrawBuffer() w celu wycięcia fragmentów sceny.
486 ______________________ Część III » Tematy zaawansowane i efekty specjalne
* Wciśnij klawisz 'd' w celu przełączenia się pomiędzy funkcjami
* porównań GL_LESS i GLJ3REATER. Wciśnij 'Esc' w celu zakończenia
* programu.
*/
#include <GL/glaux.h>
/*
* Te definicje zostały wprowadzone w celu zapewnienia zgodności
* pomiędzy MS Windows a resztą świata.
*
* CALLBACK i APIENTRY to modyfikatory funkcji w MS Windows.
*/
łifndef WIN32
# define CALLBACK
# define APIENTRY
#endif /* IWIN32 */
GLenum depth_function = GL_LESS; //Bieżąca funkcja porównywania
//głębokości
GLfloat cutting_plane = -15.0, /* Odległość płaszczyzny obcinania */ cutting_dir = -1.0; /* Kierunek płaszczyzny obcinania */
/*
* ' reshape_scene ( ) ' - Zmiana rozmiaru sceny...
*/
void CALLBACK
reshape_scene (GLsizei width, /* We - Szerokość okna w pikselach */
GLsizei height) /* We - Wysokość okna w pikselach */ { /*
* Wyzerowanie bieżącego widoku i przekształcenia perspektywicznego.
*/
glviewport (O, O, width, height);
glMatrixMode (GL_PROJECTION) ;
glLoadldentity ( ) ;
gluPerspective(22.5, (float)width / (float)height, 0.1, 1000.0);
glMatrixMode (GL_MODELVIEW) ;
/*
* 'draw_scene ( ) ' - Rysowanie sceny zawierającej kostkę
* i przesłaniającą ją kulę.
*/
void CALLBACK draw_scene (void) {
static float red_light[4] = ( 1.0, 0.0, 0.0, 1.0 };
static float red_pos[4] = { 1.0, 1.0, 1.0, 0.0 };
static float blue_light [4] = { 0.0, 0.0, 1.0, 1.0 };
static float blue_pos[4] = { -1.0, -1.0, -1.0, 0.0 };
Rozdział 15. * Bufory: nie tylko do animacji___________________________487
/*
* Włączenie buforów i oświetlenia
*/
glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHTO); glEnable(GL_LIGHTl);
glShadeModel(GL_SMOOTH); glDepthFunc(depth_function);
/*
* Wyczyszczenie bufora koloru i głębokości.
*/
if (depth_function == GL_LESS)
glClearDepth(l.O); else
glClearDepth(0.0);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/*
* Rysowanie płaszczyzny cięcia. Zwróć uwagę, że na ten czas
* wyłączamy zwykle rysowanie w buforze koloru...
*/
glDrawBuffer(GL_NONE);
glColor3i(0, O, 0); glBegin(GL_POLYGON);
glVertex3f(-100.0, 100.0, cutting_plane);
glVertex3f(100.0, 100.0, cutting_plane);
glVertex3f(100.0, -100.0, cutting_plane);
glVertex3f(-100.0, -100.0, cutting_plane); glEnd();
g!DrawBuffer(GL_BACK);
/*
* Rysowanie kostki i kuli w różnych kolorach
*
* W scenie umieściliśmy dwa światła. Pierwsze z nich jest czerwone
* i zostało umieszczone u góry z prawej strony, za obserwatorem.
* Drugie jest niebieskie i znajduje się u dołu po lewej stronie,
* przed obserwatorem.
*/
glLightfv(GL_LIGHTO, GL_DIFFUSE, red_light); glLightfv(GL_LIGHTO, GL_POSITION, red_pos);
glLightfv(GL_LIGHTl, GL_DIFFUSE, blue_light); glLightfv(GL_LIGHTl, GL_POSITION, blue_pos);
glPushMatrix();
glTranslatef (-1.0, 0.0, -20.0);
auxSolidSphere(1.0); glPopMatrix();
488 _______________________ Część III » Tematy zaawansowane i efekty specjalne
glPushMatrix ( ) ;
glTranslatef (1.0, 0.0, -20.0);
glRotatef (15.0, 0.0, 1.0, 0.0);
glRotatef (15.0, 0.0, 0.0, 1.0);
auxSolidCube (2.0) ; glPopMatrix() ;
auxSwapBuf f ers ( ) ;
/*
* ' toggle_depth () ' - Przełącza porównywanie głębokości pomiędzy
* funkcjami GL_LESS i GL_GREATER.
*/
V0id CALLBACK toggle_depth (void) {
if (depth_function == GL_LESS) depth_function = GLJ3REATER; else
depth_function = GL_LESS;
/*
* 'move_plane ( ) ' - W wolnym czasie przesuwanie płaszczyzny cięcia.
*/
void CALLBACK move_plane (void) {
cutting_plane += cutting_dir;
/*
* W miarę potrzeby zmiana kierunku...
*/
if (cutting_plane <= -30.0 ||
cutting_plane >= -15.0) cutting_dir = -cutting_dir;
draw_scene ( ) ;
/*
* 'main()' - Inicjowanie okna i wyświetlanie sceny do momentu
* wciśnięcia klawisza 'Esc'.
*/
void main( ) {
aux!nitDisplayMode(AOX_RGB | AUX_DOUBLE | AOX_DEPTH) ;
auxlnitwindow ( "Depth Function") ;
auxKeyFunc (AUX_d, togglę_depth) ; auxReshapeFunc (reshape_scene) ; aux!dleFunc (movę_plane) ;
Rozdział 15. » Bufory: nie tylko do animacji ___________________________ 489
auxMainLoop(draw_scene) ;
/*
* Koniec pliku "depthcut .c"
*/
Bufor szablonu
Bufor szablonu służy do ograniczenia rysowania na określonych obszarach ekranu i ma wiele zastosowań, do których bufor głębokości po prostu się nie nadaje. W najprostszym przypadku, bufora szablonu można użyć do zablokowania pewnych obszarów ekranu. Na przykład, w programie symulatora lotu można wykorzystać bufor szablonu do ograniczenia operacji rysunkowych do wnętrza okrągłych wskaźników w kokpicie, takich jak sztuczny horyzont czy wskaźnik wysokości.
Jednak chyba najbardziej ekscytującym zastosowaniem bufora szablonu jest tworzenie cieni. W zależności od możliwości karty graficznej można tworzyć twarde i miękkie cienie, pochodzące od wielu źródeł światła, nadające scenie dużą dawkę realizmu.
Korzystanie z bufora szablonu
Aby użyć bufora szablonu, najpierw musisz zażądać jego utworzenia. W przypadku Windows oznacza to ustawienie pola cStencilBits struktury PIXELFORMATDESCRI-PTOR (PFD) danego okna:
pfd.cStencilBits = 1;
Gdy zażądasz utworzenia bufora szablonu, musisz jeszcze włączyć używanie szablonu, wywołując w tym celu funkcję glEnable(GL_STENCIL_TEST). Bez tego wywołania, bufor szablonu nie zostałby w ogóle uwzględniony.
Funkcje bufora szablonu
W OpenGL występuj ą cztery funkcje bufora szablonu:
void glClearStencil(GLint s);
void glStencilFunc(GLenum func, GLint ref, GLuint mask);
void glStencilMask(GLuint mask);
void glStencilOp(GLenum fail, GLenum zfail, GLenum zpass);
Funkcja glClearStencil działa podobnie do funkcji glClearColor, glClearDepth i glClear-Index; służy do ustawienia wartości używanej do zerowania bufora szablonu w momencie wywołania funkcji glCIear(GL_STENCIL_BIT); Domyślnie w buforze szablonu umieszczana jest wartość 0. W odróżnieniu od buforów głębokości i koloru, bufor szablonu nie musi być czyszczony przed każdym rysowaniem sceny. We wspomnianym
490______________________Część III » Tematy zaawansowane i efekty specjalne
wcześniej symulatorze lotów, wskaźniki kokpitu prawdopodobnie nigdy nie zmienią położenia ani rozmiaru, więc odświeżanie zawartości bufora szablonu może nie być konieczne.
Rysowanie w buforze szablonu
Gdy za pomocą funkcji glEnable włączysz atrybut GL_STENCIL_TEST, musisz określić sposób działania bufora szablonu. Domyślnie nie robi on nic, umożliwiając rysowanie na całej powierzchni ekranu bez aktualizowania zawartości bufora szablonu. Aby bufor szablonu zaczął działać, musimy w nim umieścić jakieś wartości. Kontrolują to funkcje glStencilFunc oraz glStencilDepth.
Funkcja glStencilFunc definiuje funkcję porównania, wartość odniesienia oraz maskę dla wszystkich operacji na buforze szablonu. Rysowanie jest możliwe tylko wtedy, gdy funkcja porównująca zwróci wartość True. Dostępne funkcje porównania zostały zebrane w tabeli 15.7.
Tabela 15.7.
Funkcje bufora szablonu
Funkcja Opis
GL_NEVER Zawsze False (zablokowanie rysowania).
GL_LESS True, jeśli wartość odniesienia < wartość szablonu.
GL_LEQUAL True, jeśli wartość odniesienia <= wartość szablonu.
GL_GREATER True, jeśli wartość odniesienia > wartość szablonu.
GL_GEQUAL Truejeśli wartość odniesienia >= wartość szablonu.
GL_EQUAL True, jeśli wartość odniesienia = wartość szablonu.
GL_NOTEQUAL True, jeśli wartość odniesienia != wartość szablonu.
GL_ALWAYS Zawsze True (można rysować bez ograniczeń).
Z funkcją szablonu wiążą się operacje szablonu, definiowane za pomocą funkcji glStencilOp. Dostępne operacje zostały zebrane w tabeli 15.8.
Tabela 15.8.
Operacje szablonu
Operacja Opis
GL_KEEP Pozostawiana jest bieżąca zawartość bufora szablonu.
GL_ZERO Zawartość bufora szablonu jest zerowana.
GL_REPLACE Zawartość bufora szablonu jest zastępowana wartością odniesienia.
Rozdział 15. » Bufory: nie tylko do animacji____________________________491
Tabela 15.8.
Operacje szablonu - ciąg dalszy
Operacja Opis
GL_INCR Zawartość bufora szablonu jest inkrementowana.
GL_DECR Zawartość bufora szablonu jest dekrementowana.
GL_INVERT Zawartość bufora szablonu jest negowana bitowo.
Zwykle obraz maski jest używany do określenia obszaru, w którym będzie dozwolone rysowanie. Oto przykład przygotowania do rysowania maski w buforze szablonu:
glStencilFunc(GL_ALWAYS, l, 1); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
Następnie za pomocą poleceń rysunkowych możesz wpisać jedynki do bufora szablonu. Aby rysować z zastosowaniem maski w buforze szablonu, przed przystąpieniem do rysowania sceny wywołaj poniższe funkcje:
glStencilFunc(GL_EQUAL, l, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
Ponieważ działa to ze wszystkimi poleceniami rysunkowymi OpenGL, włącznie z glBit-map, możesz użyć bufora szablonu do uzyskania wielu efektów „dziur", także na potrzeby animacji! Listing 15.3 zawiera zmodyfikowaną wersję poprzedniego przykładu, program STENCILCT.C, wykorzystujący bufor szablonu, zamiast bufora głębokości, w celu wycięcia środka kostki.
Oto serce tego programu, wykorzystujące opisane powyżej funkcje:
glStencilFunc(GL_ALWAYS, l, 1); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glPushMatrix();
glTranslatef(-1.0, 0.0, -20.0);
auxSolidSphere(1.0); glPopMatrix();
Po narysowaniu obrazu w buforze szablonu, rysujemy kostkę w tych miejscach, w których kula nie jest narysowana:
glStencilFunc(GL_NOTEQUAL, l, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glPushMatrix();
glTranslatef(1.0, 0.0, -20.0);
glRotatef(15.0, 0.0, 1.0, 0.0);
glRotatef(15.0, 0.0, 0.0, 1.0);
auxSolidCube(2.0); glPopMatrix();
492_______________________Część III » Tematy zaawansowane i efekty specjalne
Listing 15.3. STENCILCT.C, przykład wykorzystania bufora szablonu_____________________
/*
* "stencilct.c" - Testowy program demonstrujący użycie funkcji
* glStencilFunc() i glStencilOp() w celu wycięcia fragmentów sceny.
*/
ttinclude <GL/glaux.h>
/*
* Te definicje zostały wprowadzone w celu zapewnienia zgodności
* pomiędzy MS Windows a resztą świata.
*
* CALLBACK i APIENTRY to modyfikatory funkcji w MS Windows.
*/
ttifndef WIN32
# define CALLBACK
# define APIENTRY
#endif /* 1WIN32 */
/*
* 'reshape_scene()' - Zmiana rozmiaru sceny...
*/
void CALLBACK
reshape_scene(GLsizei width, /* We - Szerokość okna w pikselach */
GLsizei height) /* We - Wysokość okna w pikselach */ ( /*
* Wyzerowanie bieżącego widoku i przekształcenia perspektywicznego.
*/
glYiewport(O, O, width, height);
glMatrixMode(GL_PROJECTION);
glLoadldentity();
gluPerspective(22.5, (float)width / (float)height, 0.1, 1000.0);
glMatrixMode(GL_MODELVIEW);
/*
* 'draw_scene()' - Rysowanie sceny zawierającej kostkę i
* przesłaniającą ją kulę.
*/
void CALLBACK draw_scene(void) {
static float red_light[4] = { 1.0, 0.0, 0.0, 1.0 };
static float red_pos[4] = { 1.0, 1.0, 1.0, 0.0 };
static float blue_light[4] = { 0.0, 0.0, 1.0, 1.0 };
static float blue_pos[4] = { -1.0, -1.0, -1.0, 0.0 };
Rozdział 15. » Bufory: nie tylko do animacji____________________________493
/*
* Włączenie buforów i oświetlenia
*/
glEnable(GL_DEPTH_TEST); glEnable(GL_STENCIL_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHTO); glEnable(GL_LIGHT1);
glShadeModel(GL_SMOOTH);
/*
* Wyczyszczenie bufora koloru, szablonu i głębokości.
*/
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
/*
* Rysowanie kuli, która wytnie część kostki...
*/
glstencilFunc(GL_ALWAYS, l, 1); glStenci!Op(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glPushMatrix();
glTranslatef (-1.0, 0.0, -20.0);
auxSolidSphere(1.0); glPopMatrix();
/*
* Ponowne wyczyszczenie bufora koloru i głębokości...
*/
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/*
* Rysowanie kostki w różnych kolorach
*k
* W scenie umieściliśmy dwa światła. Pierwsze z nich jest czerwone
* i zostało umieszczone u góry z prawej strony, za obserwatorem.
* Drugie jest niebieskie i znajduje się u dołu po lewej stronie,
* przed obserwatorem.
*/
glstencilFunc(GL_NOTEQUAL, l, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glLightfv(GL_LIGHTO, GL_DIFFUSE, red_light); glLightfv(GL_LIGHTO, GL_POSITION, red_pos);
glLightfv(GL_LIGHTl, GL_DIFFUSE, blue_light); glLightfv(GL_LIGHTl, GL_POSITION, blue_pos);
glPushMatrix();
glTranslatef(1.0, 0.0, -20.0); glRotatef(15.0, 0.0, 1.0, 0.0);
494_______________________Część III » Tematy zaawansowane i efekty specjalne
glRotatef(15.0, 0.0, 0.0, 1.0); auxSolidCube (2.0); glPopMatrix();
auxSwapBuffers();
/*
* 'mainO' - Inicjowanie okna i wyświetlanie sceny do momentu
* wciśnięcia klawisza 'Esc'.
*/
void main() {
aux!nitDisplayMode(AUX_RGB | AUX_DOUBLE | AUX_DEPTH l AUX_STENCIL) ,
auxlnitwindow("Rysowanie od szablonu");
auxReshapeFunc(reshape_scene); auxMainLoop(draw_scene);
/*
* Koniec pliku "stencilct.c".
*/
Bufor akumulacji
Bufor akumulacji umożliwia uzyskanie wiele efektów specjalnych, takich jak na przykład rozmycie ruchu. Posługując się nim, można przeprowadzać pełnoekranowy anty-aliasing, choć lepiej nadają się do tego raczej inne metody, choćby multisampling.
Bufor akumulacji jest zdecydowanie mniej skomplikowany niż pozostałe, omawiane dotąd bufory. Posiada pojedynczą funkcję, glAccum, sterującą jego działaniem. Dostępne operacje zostały zebrane w tabeli 15.9.
Tabela 15.9.
Operacje akumulacji
Operacja Opis
GL_ACCUM Dodaje przeskalowane wartości z bufora koloru do wartości w buforze akumulacji.
GL_LOAD Ładuje przeskalowane wartości z bufora koloru do bufora akumulacji, zastępując
przechowywane w nim dotąd wartości.
GL_ADD Dodaje stałą wartość koloru do wartości w buforze akumulacji.
GL_MULT Mnoży wartości w buforze akumulacji przez stałą wartość koloru (efekt filtrowania).
GL_RETURN Kopiuje zawartość bufora akumulacji do głównego bufora koloru.
Rozdział 15. » Bufory: nie tylko do animacji ____________________________ 495
Zwykły sposób użycia bufora akumulacji polega na wyrenderowaniu do niego kilku widoków, a następnie wyświetleniu złożonej sceny za pomocą funkcji glAccum(GL_ RETURN, 1.0);
Użycie bufora akumulacji
w celu zasymulowania rozmycia ruchu
Jak kiedyś stwierdził jeden z naszych współpracowników, łatwo jest tak wykorzystać bufor akumulacji, aby scena wyglądała na poruszoną! Ten efekt występuje wtedy, gdy podczas robienia zdjęcia poruszysz aparatem - zbyt duże drgnięcie ręki powoduje rozmycie obrazu.
Renderowanie rozmycia ruchu jest czymś bardziej skomplikowanym niż po prostu narysowanie sekwencji klatek, w której kamera zmienia położenie pomiędzy kolejnymi klatkami. Rozmycie ruchu dostrzegamy wtedy, gdy obiekt porusza się szybciej, niż mogą za nim nadążyć oczy. Obraz zmienia się szybciej, niż mózg może go „przetworzyć", mimo że cały czas mamy na nim skupiony wzrok. W przypadku aparatu, światło padające na film naświetla go przez pewien określony czas. W zależności od aparatu i fotografa, rozmycie może ograniczać się jedynie do ostrych krawędzi lub rozciągać się na całe zdjęcie.
Gdy symulujesz rozmycie ruchu w grafice komputerowej, ważne jest, abyś pamiętał, że bieżąca (czy ostatnia) postać rozmywanego obiektu musi wyglądać na bardziej skupioną niż na pozostałych klatkach. Najprostszym sposobem osiągnięcia tego efektu jest użycie w ostatniej klatce większego współczynnika przeskalowania kolorów, tak aby więcej kolorów ostatniej klatki zostało umieszczonych w buforze akumulacji. Zwykle wygląda to mniej więcej tak:
// Rysowanie bieżącej klatki
draw_frame (0) ;
// Załadowanie do bufora akumulacji 50% bieżącej klatki
glAccum(GL_LOAD, 0.5);
// Narysowanie ostatnich 10 klatek, akumulacja po 5% każda ford = 1; i <= 10;
draw_frame (-i) ; glAccum(GL_ACCUM, 0.05); >;
// Wyświetlenie końcowej sceny glAccum(GL_RETURN, 1.0);
Zwróć uwagę, że w celu zainicjowania zawartości bufora akumulacji nie trzeba używać funkcji glClear, tak jak to było konieczne w przypadku buforów koloru, głębokości i szablonu. Zamiast niej, zwykle będziesz stosował funkcję glAccum(GL_LOAD, s) w odniesieniu do pierwszej klatki sceny. Rozmycie ruchu dla kuli i kostki demonstruje program z listingu 15.4.
496 _______________________ Część III » Tematy zaawansowane i efekty specjalne
Listing 15.4. MOTION.C: Rozmycie ruchu przy użyciu bufora akumulacji ___________________
/*
* "motion.c" - Testowy program demonstrujący użycie funkcji glAccumO
* dla uzyskania efektu rozmycia ruchu.
*/
tinclude <GL/glaux.h>
/*
* Te definicje zostały wprowadzone w celu zapewnienia zgodności
* pomiędzy MS Windows a resztą świata.
Tt
* CALLBACK i APIENTRY to modyfikatory funkcji w MS Windows.
*/
łłifndef WIN32
# define CALLBACK
# define APIENTRY
#endif /* 1WIN32 */
GLfloat rotation = 0.0;
/*
* ' reshape_scene ( ) ' - Zmiana rozmiaru sceny...
*/
void CALLBACK
reshape_scene (GLsizei width, /* We - Szerokość okna w pikselach */
GLsizei height) /* We - Wysokość okna w pikselach */ { /*
* Wyzerowanie bieżącego widoku i przekształcenia perspektywicznego.
*/
glViewport (O, O, width, height);
glMatriKMode (GL_PROJECTION) ;
glLoadldentity ( ) ;
gluPerspective(22.5, (float) width / (f loat) height, 0.1, 1000.0);
glMatrixMode (GL_MODELVIEW) ;
/*
* 'draw_scene ( ) ' - Rysowanie sceny zawierającej kostkę
* i przesłaniającą ją kulę.
*/
void CALLBACK draw_scene (void) {
GLfloat f ramę;
static float red_light[4] = { 1.0, 0.0, 0.0, 1.0 };
static float red_pos[4] = { 1.0, 1.0, 1.0, 0.0 };
static float blue_light [4] = { 0.0, 0.0, 1.0, 1.0 };
static float blue_pos[4] = { -1.0, -1.0, -1.0, 0.0 };
Rozdział 15. » Bufory: nie tylko do animacji___________________________497
/*
* Włączenie buforów i oświetlenia
*/
glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHTO); glEnable(GL_LIGHT1);
glShadeModel(GL_SMOOTH);
/*
* Wyczyszczenie bufora koloru i głębokości.
*/
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/*
* Rysowanie kostki i kuli w różnych kolorach
*
* W scenie umieściliśmy dwa światła. Pierwsze z nich jest czerwone
* i zostało umieszczone u góry z prawej strony, za obserwatorem.
* Drugie jest niebieskie i znajduje się u dołu po lewej stronie,
* przed obserwatorem.
*/
glLightfv(GL_LIGHTO, GL_DIFFOSE, red_light); glLightfv(GL_LIGHTO, GL_POSITION, red_pos);
glLightfv(GL_LIGHTl, GL_DIFFUSE, blue_light); glLightfv(GL_LIGHTl, GL_POSITION, blue_pos);
/*
* Rysowanie obiektów 11 razy, począwszy od bieżącego kąta obrotu...
*/
for (framę = 0.0; framę <= 11.0; framę ++) {
glPushMatrix();
glTranslatef(0.0, 0.0, -20.0);
glRotatef(rotation - frame, 0.0, 1.0, 0.0);
glPushMatrix();
glTranslatef(-1.O, 0.0, 0.0);
auxSolidSphere(1.0); glPopMatrix();
glPushMatrix();
glTranslatef(1.0, 0.0, 0.0); glRotatef(15.0, 0.0, 1.0, 0.0); glRotatef(15.0, 0.0, 0.0, 1.0); auxSolidCube(2.0); glPopMatrix(); glPopMatrix();
/*
* Akumulacja 50% pierwszej klatki i po 5% dla następnych
*/
498_______________________Część III » Tematy zaawansowane i efekty specjalne
if (framę == 0.0)
glAccum(GL_LOAD, 0.5); else
glAccum(GL_ACCUM, 0.05);
>;
/*
* Skopiowanie zakumulowanego wyniku do bufora koloru... V
g!Accum(GL_RETORN, 1.0); auxSwapBuffers();
/*
* ' rotate_objects ( ) ' - W wolnych chwilach obrót sceny...
*/
void CALLBACK rotate_objects (void) {
rotation += 2.0;
if (rotation >= 360.0) rotation -= 360.0;
draw_scene ( ) ;
/*
* 'mainO' - Inicjowanie okna i wyświetlanie sceny do momentu
* wciśnięcia klawisza 'Esc1.
*/
void main( ) {
aux!nitDisplayMode(AUX_RGB | AUX_DOUBLE | AUX_DEPTH | AUX_ACCOM) ;
auxlnitwindow( "Rozmycie ruchu") ;
auxReshapeFunc (reshape_scene) ; auxIdleFunc (rotate_objects) ;
auxMainLoop(draw_scene) ;
/*
* Koniec pliku "motion.c".
*/
Użycie bufora akumulacji do antyaliasingu
Kolejnym zastosowaniem bufora akumulacji jest pełnoekranowy antyaliasing. Podstawową techniką jest poruszenie obrazu po pół piksela w różnych kierunkach, w celu rozmycia krawędzi bez rozmywania jednolitych obszarów. Akumulacja czterech takich obrazów daje znaczny efekt wygładzenia. Kompilator Yisual C++ zawiera wiele przy-
499
Rozdział 15. » Bufory: nie tylko do animacji
kładów OpenGL, w których zastosowano takie poruszenie obrazu w celu uzyskania antyaliasingu. Wiele różnych zestawów wartości przesunięcia znajdziesz w pliku OpenGL\Book\Jitter.h na instalacyjnej płytce Yisual C++.
Antyaliasing przy użyciu bufora akumulacji nie jest jednak zbyt szybki. Jeśli rzeczywiście chcesz uzyskać antayaliasing w czasie rzeczywistym, powinieneś poszukać karty graficznej, która dzięki zastosowaniu multisamplingu sama wykona antyaliasing. Bufor akumulacji jest do tego zbyt wolny.
Jeśli generujesz statyczne obrazy, bufor akumulacji umożliwia uzyskanie efektów antyaliasingu i głębokości pola, których po prostu nie da się osiągnąć za pomocą samego m ultisamplingu.
Podręcznik
glAccum
Przeznaczenie Plik nagłówkowy Składnia Opis
Steruje działaniem bufora akumulacji.
void glAccum(GLenum func, GLfloat value);
Ta funkcja steruje buforem akumulacji. Z wyjątkiem operacji GL_RETURN, wartości koloru są skalowane przez parametr value i dodawane lub umieszczane w buforze akumulacji. W przypadku operacji GL_RETURN, wartości kolorów z bufora akumulacji są skalowane przez parametr value i umieszczane w bieżącym buforze koloru.
Parametry func
GLenum: Operacja na buforze akumulacji. Dostępne są następujące operacje:
GL_ACCUM Dodaje przeskalowane wartości z bufora koloru do wartości w buforze akumulacji.
GL_LOAD Ładuje przeskalowane wartości z bufora koloru do
bufora akumulacji, zastępując przechowywane w nim dotąd wartości.
GL_ADD Dodaje stałą wartość koloru do wartości w buforze
akumulacji.
GL_MULT Mnoży wartości w buforze akumulacji przez stałą wartość koloru (efekt filtrowania).
GL_RETURN K opiuje zawartość bufora akumulacji do głównego bufora koloru.
500
Część III » Tematy zaawansowane i efekty specjalne
value
GLfloat: Wartość, przez którą są skalowane wartości umieszczane w buforze.
Zwracana wartość Brak.
Przykład Kod programu MOTION.C na płytce CD-ROM.
Patrz także ChoosePixelFormat, SetPixelFormat
glCIearColor
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry
red
green
blue
alpha
Zwracana wartość Patrz także
Określa wartość używaną do czyszczenia bufora koloru.
void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
Ta funkcja ustawia wartość koloru, używaną podczas czyszczenia bufora koloru funkcją glClear(GL_COLOR_BUFFER_BIT).
GLfloat: Czerwona składowa koloru czyszczenia bufora.
GLfloat: Zielona składowa koloru czyszczenia bufora.
GLfloat: Niebieska składowa koloru czyszczenia bufora.
GLfloat: Składowa alfa koloru czyszczenia bufora.
Brak.
ChoosePixelFormat, SetPixelFormat
glCIearDepth
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
depth
Zwracana wartość Patrz także
Określa wartość używaną do czyszczenia bufora głębokości.
void glClearDepth(GLclampd depth);
Ta funkcja ustawia wartość głębokości, używaną podczas czyszczenia bufora głębokości funkcją glClear(GL_DEPTH_BUFFER_BIT).
GLclampd: Wartość czyszczenia bufora głębokości.
Brak.
ChoosePixelFormat, SetPixelFormat
501
Rozdział 15. » Bufory: nie tylko do animacji
glClearlndex
Przeznaczenie Określa wartość używaną do czyszczenia bufora koloru.
Plik nagłówkowy <gl.h>
Składnia void glClear!ndex(GLfloat index);
Opis
Parametry index
Ta funkcja ustawia indeks koloru, używany podczas czyszczenia bufora kolorów funkcją glClear(GL_COLOR_BUFFER_BIT).
GLfloat: Indeks koloru czyszczenia bufora koloru.
Zwracana wartość Brak.
Patrz także ChoosePixelFormat, SetPixelFormat
glClearStencil
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
value
Zwracana wartość Patrz także
Określa wartość używaną do czyszczenia bufora szablonu.
void glClearStencil(GLint value);
Ta funkcja ustawia wartość używaną podczas czyszczenia bufora szablonu funkcją glClear(GL_STENCIL_BUFFER_BIT).
GLint: Wartość czyszczenia bufora szablonu.
Brak.
ChoosePixelFormat, SetPixelFormat
gIDrawBuffer
Przeznaczenie Plik nagłówkowy Składnia Opis
Wybiera bufor przeznaczony do rysowania.
void glDrawBuffer(GLenum modę);
Ta funkcja służy do wyboru bufora, do którego będą się odnosić następne operacje rysunkowe. Zwykle jest używana do wyboru przedniego lub tylnego bufora przy podwójnym buforowaniu.
502
Część III » Tematy zaawansowane i efekty specjalne
Parametry modę
Zwracana wartość Znane błędy
Przykład Patrz także
Glenum: Stała (patrz tabela 15.10) określająca bufor, który ma zostać wykorzystany. Na przykład, aby wybrać do rysowania tylny bufor, użyj wywołania:
glDrawBuffer(GL_BACK);
Brak.
Ogólna implementacja Microsoftu nie obsługuje buforów stereo ani parametru modę równego GL_NONE.
Kod programu DEPTHCUT.C na płytce CD-ROM. ChoosePixelFormat, SetPixelFormat
Tabela 15.10.
Dostępne tryby w wywołaniu funkcji glDrawBuffer
Opis
Bufor
GL_NONE GL_FRONT GL_BACK GL_FRONT_AND_BACK
GLJ.EFT GL_RIGHT
GL_BACK_LEFT GL_RIGHT_BACK GL_FRONT LEFT GL FRONT RIGHT
Bez rysowania w żadnym buforze. Rysowanie w przednim (widocznym) buforze. Rysowanie w tylnym (niewidocznym) buforze.
Rysowanie w obu buforach naraz (tylko w kontekstach z podwójnym buforowaniem).
Rysowanie w buforze koloru dla lewego oka (tylko w kontekstach z buforowaniem stereo, przy podwójnym buforowaniu wybór obu buforów naraz, przedniego i tylnego).
Rysowanie w buforze koloru dla prawego oka (tylko w kontekstach z buforowaniem stereo, przy podwójnym buforowaniu wybór obu buforów naraz, przedniego i tylnego).
Rysowanie w buforze koloru dla lewego oka (tylko w kontekstach z buforowaniem stereo i przy podwójnym buforowaniu).
Rysowanie w buforze koloru dla prawego oka (tylko w kontekstach z buforowaniem stereo i przy podwójnym buforowaniu).
Rysowanie w buforze koloru dla lewego oka (tylko w kontekstach z buforowaniem stereo i przy podwójnym buforowaniu).
Rysowanie w buforze koloru dla prawego oka (tylko w kontekstach z buforowaniem stereo i przy podwójnym buforowaniu).
glDepthFunc
Przeznaczenie Wybiera funkcję porównywania dla bufora głębokości.
Plik nagłówkowy <gl.h>
Składnia void glDepthFunc(GLenum func);
503
Rozdział 15. » Bufory: nie tylko do animacji
Opis
Parametry modę
Zwracana wartość Przykład Patrz także
Ta funkcja służy do wyboru funkcji używanej przy porównywaniu wartości w buforze głębokości.
GLenum: Stała określająca funkcję porównywania. Dostępne funkcje to:
GLJNEYER Zawsze False
GL_LESS True, jeśli Z piksela < Z bufora
GL_EQUAL True, jeśli Z piksela = Z bufora
GL_LEQUAL True, jeśli Z piksela <= Z bufora
GL_GREATER True, jeśli Z piksela > Z bufora
GL_NOTEQUAL True, jeśli Z piksela != Z bufora
GL_GEQUAL True, jeśli Z piksela >= Z bufora
GL_ALWAYS Zawsze True
Brak.
Kod programu DEPTH.C na płytce CD-ROM.
ChoosePixelFormat, SetPixelFormat
glDepthRange
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
near
far
Zwracana wartość Przykład Patrz także
Ustawia zakres wartości przechowywanych w buforze głębokości.
void glDepthRange(GLclampd near, GLclampd far);
Ta funkcja ustala zakres wartości przechowywanych w buforze głębokości, używanych przy porównaniach głębokości w celu usuwania niewidocznych powierzchni. Parametr near może być większy niż/ar.
GLclampd: Bliższa wartość głębokości.
GLclampd: Dalsza wartość głębokości.
Brak.
Kod programu DEPTH.C na płytce CD-ROM.
ChoosePixelFormat, SetPixelFormat
Rozdział 16.
Efekty specjalne: przezroczystość i mgła
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
* Wyświetlać przezroczyste linie i wielokąty. * glBlendFunc
* Stworzyć efekt pyłu lub mgły + glFog
W tym rozdziale poznamy funkcje OpenGL umożliwiające stworzenie efektów przezroczystości i mgły.
Funkcje przezroczystości pozwalają na tworzenie przejrzystych obiektów, takich jak szyby, szklanki, płyny i inne tego typu rzeczy. Funkcje mgły dodają do sceny pewną zmienną ilość koloru, powodując powstanie efektu zamglenia lub wręcz mgły, bardzo wpływającej na atmosferę całej sceny.
Przy stosowaniu przezroczystości i mgły należy jednak pamiętać o jednej rzeczy. Te efekty nie wychodzą zbyt dobrze w trybach ośmiobitowych. Pamiętaj, aby uzupełnić swoje programy o opcję umożliwiającą wyłączenie tych efektów w przypadku wykorzystywania 8-bitowych kart graficznych.
Blending
Blending (łączenie kolorów) w OpenGL polega na kontrolowaniu wartości RGBA poszczególnych pikseli w scenie na podstawie pikseli już istniejących. Operacje łączenia kolorów nie są dostępne w trybie indeksu kolorów.
Aby włączyć łączenie kolorów w oknach RGBA, musisz najpierw wywołać funkcję glEnable(GL_BLEND). Potem powinieneś wywołać funkcję glBlendFunc z dwoma ar-
506
Część III » Tematy zaawansowane i efekty specjalne
gumentami: funkcjami łączenia koloru źródłowego i docelowego (tabela 16.1 i 16.2). Domyślnie, tymi argumentami są odpowiednio funkcje GL_ONE i GL_ZERO, co stanowi odpowiednik wyłączenia łączenia kolorów wywołaniem glDisable(GL_BLEND).
Tabela 16.1.
Funkcje łączenia koloru źródłowego
Funkcja
Współczynnik łączenia koloru
GL_ZERO
GL_ONE
GLJ)ST_COLOR
GL_ONE_MINUS_DST_COLOR
GL_SRC_ALPHA
GL_ONE_MINUS_SRC_ALPHA
GL_DST_ALPHA
GL_ONE_MINUS _DST_ALPHA GL SRC ALPHA SATURATE
Kolor źródłowy = O, O, O, 0.
Używa <?> koloru źródłowego.
Kolor źródłowy jest mnożony przez kolor piksela docelowego.
Kolor źródło wy jest mnożony przez (l, l, l, l, l - kolor docelowy).
Kolor źródłowy jest mnożony przez źródłową składową alfa.
Kolor źródłowy jest mnożony przez (l - źródłowa składowa alfa).
Kolor źródłowy jest mnożony przez docelową składową alfa. Nieobsługiwane w Microsoft OpenGL.
Kolor źródłowy jest mnożony przez (l - docelowa składowa alfa). Nieobsługiwane w Microsoft OpenGL.
Kolor źródłowy jest mnożony przez mniejszą z wartości: źródłową składową alfa i (l - docelowa składowa alfa). Nieobsługiwane w Microsoft OpenGL.
Tabela 16.2.
Funkcje łączenia koloru docelowego
Funkcja
Współczynnik łączenia koloru
GL_ZERO
GLJDNE
GL_DST_COLOR
GL_ONE_MINUS_DST_COLOR
GL_SRC_ALPHA
GL_ONE_MINUS_SRC_ALPHA
GLJ)ST_ALPHA
GL_ONE_MINUS _DST_ALPHA GL SRC ALPHA SATURATE
Kolor docelowy = O, O, O, 0.
Używa <?> koloru docelowego.
Kolor docelowy jest mnożony przez kolor piksela źródłowego.
Kolor docelowy jest mnożony przez (l, l, l, l, l - kolor źródłowy).
Kolor docelowy jest mnożony przez źródłową składową alfa.
Kolor docelowy jest mnożony przez (l - źródłowa składowa alfa).
Kolor docelowy jest mnożony przez docelową składową alfa. Nieobsługiwane w Microsoft OpenGL.
Kolor docelowy jest mnożony przez (l - docelowa składowa alfa). Nieobsługiwane w Microsoft OpenGL.
Kolor docelowy jest mnożony przez mniejszą ze źródłowej składowej alfa i (l - docelowa składowa alfa). Nieobsługiwane w Microsoft OpenGL.
Rozdział 16. » Efekty specjalne: przezroczystość i mgła_____________________507
Efekt przezroczystości przez łączenie kolorów
Przezroczystość jest najbardziej typowym wykorzystaniem łączenia kolorów, często używanym w celu stworzenia szyb, butelek i innych przezroczystych trójwymiarowych obiektów. Dzięki przezroczystości można także łączyć ze sobą kilka obrazów lub też osiągnąć efekt „miękkiego pędzla" w programach graficznych.
Oto funkcje łączenia kolorów wywoływane we wszystkich tego typu programach:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Powyższa kombinacja powoduje pobranie koloru piksela źródłowego i przeskalowanie go zgodnie z wartością składowej alfa, a następnie dodanie koloru piksela docelowego przeskalowanego przez l minus wartość składowej alfa. Mówiąc prościej, funkcja łącząca bierze ułamek bieżącego koloru rysowania i nakłada go na piksel na ekranie. Składowa alfa koloru może mieć wartość od O (całkowicie przezroczysty) do l (całkowicie nieprzezroczysty):
Rd = Rs * As + Rd * (l - As)
Gd = Gs * As + Gd * (l - As)
Bd = Bs * As + Bd * (l - As)
Ponieważ używana jest jedynie źródłowa składowa alfa, nie musisz posiadać karty przechowującej w buforze koloru także bity kanału alfa. To jest ważne, gdyż standardowa implementacja OpenGL Microsoftu nie obsługuje kanału alfa w buforze koloru.
Przy stosowaniu łączenia kolorów należy pamiętać, że testowanie bufora głębokości może wpłynąć na efekt, który chcesz osiągnąć. Aby mieć pewność, że przezroczyste wielokąty i linie będą poprawnie narysowane, zawsze rysuj je od tyłu do przodu.
Rysunek 16.1.
Przezroczysty imbryk do herbaty narysowany przy użyciu łączenia kolorów
508 ______________________ Część III » Tematy zaawansowane i efekty specjalne
Listing 16.1 przedstawia kod użyty do narysowania przezroczystego imbryka do herbaty z rysunku 16.1. W funkcji draw_scene rysujemy dwa imbryki do herbaty, od tyłu do przodu, w celu zapewnienia, że przedni imbryk będzie widział. Jak widać, na ekranie pozostają różne artefakty (niepożądane efekty), w miejscach, gdzie przecinają się wielokąty. Nie da się ich kompletnie wyeliminować, ale można zredukować ich ilość sortując wstępnie wielokąty według ich głębokości oraz włączając usuwanie odwróconych wielokątów funkcją glEnable(GL_CULL_FACE).
Pierwszą rzeczą, jaką robimy w funkcji draw_scene, jest włączenie mieszania kolorów w celu osiągnięcia przezroczystości na podstawie wartości alfa koloru rysowania (źródłowego):
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ;
Następnie rysujemy nieprzezroczysty imbryk przy wyłączonym mieszaniu kolorów, tak abyśmy go zawsze widzieli:
glDisable (GL_BLEND) ; glColor3f (1.0, 1.0, 0.0); auxSolidTeapot (1.0);
Na koniec włączamy mieszanie kolorów i rysujemy przezroczysty imbryk, z wartością alfa (przezroczystości) wynoszącą 0,25:
glEnable (GL_BLEND) ;
glColor4f (1.0, 1.0, 1.0, 0.25);
auxSolidTeapot (1.0);
Listing 16.1. BLENDPOT.C: użycie funkcji glBlendFunc \v celu stworzenia efektu przezroczystości _____
* "blendpot.c" - Testowy program demonstrujący użycie funkcji
* • glBlendFunc () w celu osiągnięcia przezroczystości.
V
linclude <GL/glaux.h>
Te definicje zostały wprowadzone w celu zapewnienia zgodności pomiędzy MS Windows a resztą świata.
CALLBACK i APIENTRY to modyfikatory funkcji w MS Windows.
#ifndef WIN32
ł define CALLBACK
# define APIENTRY ttendif /* 1WIN32 */
GLfloat rotation = 0.0;
/*
* 'reshape_scene () ' - Zmiana rozmiaru sceny...
*/
Rozdział 16. * Efekty specjalne: przezroczystość i mgła____________________509
void CALLBACK
reshape_scene(GLsizei width, /* We - Szerokość okna w pikselach */
GLsizei height) /* We - Wysokość okna w pikselach */ { /*
* Wyzerowanie bieżącego widoku i przekształcenia perspektywicznego.
*/
giviewport(O, O, width, height);
glMatrixMode(GL_PROJECTION);
glLoadldentity();
gluPerspective(22.5, (float)width / (float)height, 0.1, 1000.0);
glMatrixMode(GL_MODELVIEW);
/*
* 'draw_scene()' - Rysowanie sceny zawierającej dwa imbryki do
* herbaty
*/
void CALLBACK draw_scene(void) {
GLfloat frame;
static float red_light[4] = ( 1.0, 0.0, 0.0, 1.0 };
static float red_pos[4] = { 1.0, 1.0, 1.0, 0.0 };
static float blue_light[4] = { 0.0, 0.0, 1.0, 1.0 };
static float blue_pos[4] = { -1.0, -1.0, -1.0, 0.0 };
/*
* Włączenie buforów i oświetlenia
*/
glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHTO); glEnable(GL_LIGHTl);
glShadeModel(GL_SMOOTH);
/*
* Wyczyszczenie bufora koloru i głębokości.
*/
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BOFFER_BIT);
/*
* W scenie umieściliśmy dwa światła. Pierwsze z nich jest czerwone
* i zostało umieszczone u góry z prawej strony, za obserwatorem.
* Drugie jest niebieskie i znajduje się u dołu po lewej stronie,
* przed obserwatorem.
*/
glLightfv(GL_LIGHTO, GL_DIFFUSE, red_light); glLightfv(GL_LIGHTO, GL_POSITION, red_pos);
510_______________________Część III » Tematy zaawansowane l efekty specjalne
glLightfv(GL_LIGHTl, GL_DIFFUSE, blue_light); glLightfv(GL_LIGHTl, GL_POSITION, blue_pos);
glEnable(GL_COLOR_MATERIAL); g!BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glPushMatrix();
glTranslatef(0.0, 0.0, -15.0); glRotatef(-rotation, 0.0, 1.0, 0.0);
glDisable(GL_BLEND); glColor3f(1.0, 1.0, 0.0); auxSolidTeapot(1.0); glPopMatrix();
glPushMatrix();
glTranslatef(0.0, 0.0, -10.0); glRotatef(rotation, 0.0, 1.0, 0.0);
glEnable(GL_BLEND); glColor4f(1.0, 1.0, 1.0, 0.25); auxSolidTeapot(1.0); glPopMatrix();
auxSwapBuffers();
/*
* ' rotate_objects ( ) ' - w wolnych chwilach obrót sceny...
*/
void CALLBACK rotate_objects (void) {
rotation += 2.0;
if (rotation >= 360.0) rotation -= 360.0;
draw_scene ( ) ;
/*
* 'mainf)1 - Inicjowanie okna i wyświetlanie sceny do momentu
* wciśnięcia klawisza 'Esc'.
*/
void
main (void)
{
auxInitDisplayMode (AUX_RGB | AUX_DOUBLE | AUX_DEPTH) ;
auxlnitwindow( "Przezroczysty imbryk do herbaty");
auxReshapeFunc (reshape_scene) ; aux!dleFunc(rotate_objects) ;
auxMainLoop (draw_scene) ;
Rozdział 16. » Efekty specjalne: przezroczystość i mgła_____________________511
/*
* Koniec pliku "blendpot.c".
*/
Łączenie kolorów przy antyaliasingu
Używając tych samych funkcji - co przy przezroczystości GL_SRC_ALPHA i GL_ ONE_MINUS_SRC_ALPHA - można poprawić wygląd punktów, linii i wielokątów rysowanych przy włączonym antyaliasingu. W systemach ze sprzętowym antyaliasin-giem i łączeniem kolorów, łączenie kolorów daje rezultaty podobne do pełnoekranowe-go antyaliasingu za pomocą bufora akumulacji. Przy tym wszystkim, łączenie kolorów jest kilka razy szybsze niż użycie bufora akumulacji, gdyż cała scena musi być narysowana tylko raz.
Aby narysować scenę przy użyciu łączenia kolorów i prymitywów z antyaliasingiem, wywołaj poniższe funkcje:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_LINE_SMOOTH);
glEnable(GL_POINT_SMOOTH);
glEnable(GL_POLYGON_SMOOTH);
Łączenie kolorów w programach rysunkowych
Te same techniki, co w grafice 3D, mogą zostać wykorzystane także w grafice 2D. W przypadku programów „malarskich", możemy zastosować łączenie kolorów w celu uzyskania „pędzla" o miękkich krawędziach. Zaczniemy od zdefiniowania obrazów alfa dla każdego z pędzli. Obraz alfa zawiera wartości alfa bez wartości koloru (RGB) i definiuje, ile koloru pędzla pozostanie na rysunku (rysunek 16.2).
Rysunek 16.2.
Rysunek wykonany „pędzlem " wykorzystującym obraz alfa
Aby „malować" przy użyciu tego obrazu pędzla, musimy użyć nieco innego zestawu funkcji łączenia kolorów:
glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_ALPHA);
512
Część III » Tematy zaawansowane i efekty specjalne
Zamiast funkcji GL_SRC_ALPHA dla koloru źródłowego, użyjemy funkcji GL_SRC_ COLOR, uwzględniającej bieżący kolor, a nie składową alfa. Tak więc kolor będzie nakładany następująco:
Rd = Rs * Ap + Rd * (l - Ap)
Gd = GS * Ap + Gd * (l - Ap)
Bd = Bs * Ap + Bd * (l - Ap)
To znaczy, wartości alfa z obrazu pędzla zostaną użyte zamiast bieżących wartości alfa rysowanych pikseli.
Listing 16.2 przedstawia prosty program „malarski" używający do rysowania obrazu pędzla o rozmiarach 7x7 pikseli. Główna pętla komunikatów obsługuje rysowanie w oknie. Gdy przytrzymasz lewy przycisk myszy, zostanie wywołana funkcja DrawXY, rysująca obraz pędzla w bieżącym położeniu myszy:
glRasterPos2i(mousex, mousey);
glDrawPixels(7, 7, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE,
•*BlendBrush[0] ) ;
Funkcja RepaintWindow czyści obszar roboczy okna za każdym razem, gdy okno zmieni rozmiar lub wymaga odświeżenia:
glViewport(O, O, rect->right, rect->bottom);
glOrtho(0.0, (float)rect->right, (float)rect->bottom, 0.0, -1.0, 1.0);
glClearColor(0.0, 0.0, 0.0, 1.0); glColorMask(GL_TRUE, GLJTRUE, GLJTRUE, GLJTRUE) ; glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Niestety, oznacza to, że utracisz swój rysunek. W prawdziwej aplikacji rysunkowej mógłbyś funkcją glReadPixels skopiować piksele ekranu do bufora, z którego mogłyby być później odtworzone przy użyciu funkcji glDrawPixels.
Listing 16.2. BLENDRA W. C: Program rysunkowy wykorzystujący funkcję glBlendFunc
"Sld$"
Demonstracja użycia obrazów alfa. Contents:
WinMainO
- Główne wejście...
DisplayErrorMessage () - Wyświetla okno komunikatu błędu.
MakePalette () BlendProcO RepaintWindow () DrawKY ( )
Revision History: $Log$
- Tworzy paletę kolorów RGB.
- Obsługuje komunikaty okna widoku.
- Odrysowuje obszar roboczy okna.
- Rysuje w obszarze roboczym.
Rozdział 16. » Efekty specjalne: przezroczystość i mgła_____________________513
/*
* Niezbędne nagłówki.
*/
flinclude <windows.h> tfinclude <GL/gl.h>
#include "blendraw.h" tfinclude <stdarg.h> łinclude <math.h> tfifndef M_PI
# define M_PI (double)3.14159265358979323846
#endif /* !M_PI */
/*
* Zmienne globalne...
*/
HWND BlendWindow; /* Okno rysunku */
HPALETTE BlendPalette; /* Paleta kolorów (jeśli potrzebna) */
HDC BlendDC; /* Kontekst rysowania */
HGLRC BlendRC; /* Kontekst renderowania OpenGL */
unsigned char BlendBrush[7][16] = {
{ Oxff, 0x00, Oxff, 0x00, Oxff, 0x08, Oxff, 0x10, Oxff, 0x08, Oxff,
00x00, Oxff, 0x00 ), { Oxff, 0x00, Oxff, 0x08, Oxff, 0x10, Oxff, 0x20, Oxff, 0x10, Oxff,
00x08, Oxff, 0x00 }, { Oxff, 0x08, Oxff, 0x10, Oxff, 0x20, Oxff, 0x40, Oxff, 0x20, Oxff,
00x10, Oxff, 0x08 }, { Oxff, 0x10, Oxff, 0x20, Oxff, 0x40, Oxff, 0x80, Oxff, 0x40, Oxff,
00x20, Oxff, 0x10 }, { Oxff, 0x08, Oxff, 0x10, Oxff, 0x20, Oxff, 0x40, Oxff, 0x20, Oxff,
00x10, Oxff, 0x08 }, { Oxff, 0x00, Oxff, 0x08, Oxff, 0x10, Oxff, 0x20, Oxff, 0x10, Oxff,
00x08, Oxff, 0x00 }, { Oxff, 0x00, Oxff, 0x00, Oxff, 0x08, Oxff, 0x10, Oxff, 0x08, Oxff,
00x00, Oxff, 0x00 }, };
GLboolean Drawing = GL_FALSE; /* GL_TRUE jeśli rysujemy */
/*
* Funkcje lokalne...
*/
void DisplayErrorMessage(char *, ...);
void MakePalette(int);
LRESULT CALLBACK BlendProc(HWND, UINT, WPARAM, LPARAM);
void DrawXY(int, int);
void RepaintWindow(RECT *);
/*
* 'WinMainO ' V
514 ______________________ Część III » Tematy zaawansowane l efekty specjalne
int APIENTRY WinMain(HINSTANCE hlnst,
HINSTANCE hPrev!nstance, LPSTR IpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASS we;
POINT pos; /* Bieżąca pozycja myszy */
we. style = 0;
wc.lpfnWndProc = (WNDPROC) BlendProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hlnstance = hlnst;
wc.hlcon = NULL;
wc.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wc.hbrBackground = 0;
wc.lpszMenuName = MAKEINTRESOURCE (IDR_MENU1) ;
wc.lpszClassName = "Blend Paint";
if (RegisterClass (Swe) == 0) {
DisplayErrorMessage ("Nie udało się zarejestrowanie okna!");
return (FALSE);
Blendwindow = CreateWindowf "Blend Paint", "Rysowanie z łączeniem ^kolorów",
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN
WS_CLIPSIBLINGS,
32, 32, 400, 300,
NULL, NULL, hlnst, NULL) ;
if (Blendwindow == NULL) {
DisplayErrorMessage ("Nie udało się stworzenie okna!");
return (FALSE);
ShowWindow( Blendwindow, nCmdShow) ; UpdateWindow (Blendwindow) ;
// Główna pętla komunikatów
while (GetMessage (smsg, NULL, O, OJ)
{
TranslateMessage ( smsg) ;
DispatchMessagę (Smsg) ;
return (msg.wParam) ;
/*
* ' DisplayErrorMessage ()' - Wyświetla okno komunikatu błędu.
*/
Rozdział 16. » Efekty specjalne: przezroczystość i mgła ____________________ 515
void DisplayErrorMessage (char *format,
// We - łańcuch formatowania w stylu printfO . . . ) //We - Pozostałe argumenty {
va_list ap; /* Wskaźnik argumentu */ char s [1024]; /* Łańcuch wyjściowy */
i f (format == NULL) return;
va_start (ap, format) ; vsprintf(s, format, ap) ; va_end(ap) ;
MessageBeep (MB_ICONEXCLAMATION) ;
MessageBox(NULL, s, "Error", MB_OK | MB_ICONEXCLAMATION) ;
/*
* 'MakePalette () ' - Jeśli trzeba, tworzy paletę kolorów.
*/
void
MakePalette (int pf) /* We - ID formatu pikseli */
{
PIXELFORMATDESCRIPTOR pfd;
LOGPALETTE *pPal;
int nColors;
int i,
rmax, gmax, bmax ;
/*
* Sprawdzenie, czy paleta jest potrzebna. . .
*/
DescribePixelFormat (BlendDC, pf, sizeof (PIKELFORMATDESCRIPTOR) ,
if ( ! (pfd.dwFlags i PFD_NEED_PALETTE) ) {
BlendPalette = NULL;
return;
/*
* Alokowanie pamięci dla palety.
*/
nColors = l « pfd.cColorBits;
pPal = (LOGPALETTE * Jmalloc (sizeof (LOGPALETTE) +
nColors * sizeof (PALETTEENTRY) );
pPal->palVersion = 0x300; pPal->palNumEntries = nColors;
Rozdział 16. » Efekty specjalne: przezroczystość i mgła____________________517
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
// Dla OpenGL
pfd.dwLayerMask = PFD_MAIN_PLANE;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 0;
pfd.cDepthBits = 0;
pfd.cStencilBits = 0;
pfd.cAccumBits =0;
pf = ChoosePixelForrnat (BlendDC, spfd) ; if (pf == 0)
DisplayErrorMessage(
"Program nie mógł znaleźć odpowiedniego formatu ^pikseli!");
else if (!SetPixelFormat(BlendDC, pf, spfd)) DisplayErrorMessage(
"Program nie mógł ustawić odpowiedniego formatu Opikseli! ") ;
MakePalette(pf) ;
BlendRC = wglCreateContext(BlendDC); wglMakeCurrent(BlendDC, BlendRC); break;
case WM_SIZE : case WM_PAINT :
// Odrysowanie obszaru klienta naszą bitmapą
BeginPaint(hWnd, &ps);
GetClientRectthWnd, Srect); RepaintWindow(Srect);
EndPaint(hWnd, sps); break;
case WM_COMMAND : /*
* Obsługa menu...
*/
switch (LOWORD(wParam))
{
case IDM_FILE_EXIT :
DestroyWindow(BlendWindow);
break; }; break;
case WM_QUIT : case WM_CLOSE : /*
* Zniszczenie okna, bitmap i wyjście...
*/
DestroyWindow(BlendWindow);
exit(0); break;
518______________________Część III » Tematy zaawansowane i efekty specjalne
case WM_DESTROY:
/*
* Zwolnienie kontekstu urządzenia, kontekstu
* renderowania i palety
*/
if (BlendRC)
wglDeleteContext(BlendRC) ;
if (BlendDC)
ReleaseDC(BlendWindow, BlendDC);
if (BlendPalette)
Deleteobject(BlendPalette);
PostOuitMessage(0) ; break;
case WM_QUERYNEWPALETTE: /*
* W razie potrzeby realizacja palety...
*/
if (BlendPalette) {
SelectPalette(BlendDC, BlendPalette, FALSE);
RealizePalette(BlendDC);
InvalidateRect(hWnd, NULL, FALSE);
return (TRUE); }; break;
case WM_PALETTECHANGED: /*
* W razie potrzeby ponowne wybranie palety...
*/
if (BlendPalette && (HWND)wParam != hWnd) {
SelectPalette(BlendDC, BlendPalette, FALSE);
RealizePalette(BlendDC);
UpdateColors(BlendDC); }; break;
case WM_LBUTTONDOWN : /* Lewy przycisk = czerwony */ Drawing = GL_TRUE;
glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE); DrawXY(LOWORD(lParam), HIWORD(lParam)); break;
case WM_MBUTTONDOWN : /* Środkowy przycisk = zielony */ Drawing = GL_TRUE;
glColorMask(GL_FALSE, GLJTRUE, GL_FALSE, GL_TRUE); DrawKY(LOWORD(IParam), HIWORD(IParam)); break;
Rozdział 16. » Efekty specjalne: przezroczystość i mgła ____________________ 519
case WM_RBOTTONDOWN : /* Prawy przycisk = niebieski */ Drawing = GLJTRUE;
glColorMask(GL_FALSE, GL__FALSE, GL_TRUE, GLJTRUE) ; DrawXY(LOWORD(lParara) , HfwORD(lParam) ) ; break;
case WM_MOUSEMOVE : if (Drawing)
DrawXY (LOWORD(lParam) , HIWORD(lParam) ) ; break;
case WM_LBOTTONUP : case WM_MBUTTONUP : case WM_RBUTTONUP :
Drawing = GL_FALSE;
break;
default : /*
* Standardowa obsługa wszystkich innych komunikatów
*/
return (DefWindowProc (hWnd, uMsg, wParam, IParam) ) ; };
return (FALSE) ;
/*
* 'DrawXYO' - Rysuje w bieżącej pozycji myszy.
*/
void
DrawXY(int mousex, /* We - położenie myszy w poziomie */
int mousey) /* We - położenie myszy w pionie */ {
glRasterPos2i (mousex, mousey);
glDrawPixels (7, 7, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE,
^BlendBrushfO] ) ;
glFinish ( ) ;
/*
* ' RepaintWindow ( ) ' - Odrysowuje obszar roboczy okna.
*/
void
RepaintWindow (RECT *rect) /* We - Obszar roboczy okna */ { /*
* Wyzerowanie widoku i wyczyszczenie okna na jasny błękit
*/
glviewport (O, O, rect->right, rect->bottom) ;
g!0rtho(0.0, (float) rect->right, (float) rect->bottom, 0.0, -1.0,
520 ______________________ Część III » Tematy zaawansowane i efekty specjalne
glClearColor (0.0, 0.0, 0.0, 1.0);
glColorMask(GL_TRUE, GL_TRUE, GLJTRUE, GLJTRUE) ; glClear(GL,_COLOR_BUFFER_BIT) ;
glEnable (GL_BLEND) ;
glBlendFunc(GL_SRC_ALPHA, GL__ONE_MINUS__SRC_ALPHA) ;
glFinishf ) ;
/*
* End of "Sld$".
*/
Mgła
W OpenGL można uzyskać cieniowanie sceny w zależności od głębokości, w celu uzyskania pewnych efektów atmosferycznych, głównie mgły. Służy do tego funkcja glFog. Tworzenie mgły polega na dodaniu (wymieszaniu) pewnego określonego koloru do każdego wierzchołka lub obrazu tekstury, w ilości zależnej od odległości tego wierzchołka od obserwatora. Efekt mgły często wykorzystuje się w symulatorach lotów i animacjach w celu uzyskania bardziej realistycznego wyglądu scen.
W OpenGL można stosować trzy rodzaje mgły: GL_LINEAR - cieniowanie w zależności od głębokości, GL_EXP gęstą mgłę lub chmury oraz GL_EXP2 dymy i mgiełki; nieco dalej, na rysunku 16.5, widać efekt zastosowania mgły typu GL_EXP.
Do wyboru rodzaju mgły (trybu mgły) służy funkcja glFogi:
glFogi(GL_FOG_MODE, GL_LINEAR);
glFogi(GL_FOG_MODE, GL_EXP);
glFogi(GL_FOG_MODE, GL_EXP2);
Gdy już wybierzesz typ mgły, za pomocą funkcji glFogfy lub glFogiv musisz wybrać jej kolor, który będzie mieszany z kolorami sceny:
GLfloat fog_color[4] = { r, g, b, a}; glFogfv(GL_FOG_COLOR, fog_color);
GLint fog_color[4] - { r, g, b, a); glFogiv(GL_FOG_COLOR, fog_color);
W przypadku cieniowania zależnie od głębokości, zwykle kolor mgły będzie odpowiadał kolorowi tła (czerni na rysunku 16.3). W ten sposób mgła będzie wyglądać „poprawnie" dla oka - czyli dalsze obiekty będą sprawiać wrażenie, że wtapiają się w tło. W pewnych sytuacjach możesz zastosować jaskrawy kolor mgły, na przykład żółty, przez co obiekty będą się znacznie różnić od tła.
Rozdział 16. » Efekty specjalne: przezroczystość i mgła_____________________521
Rysunek 16.3.
Imbryki cieniowane w zależności od głębokości
Rysowanie imbryków cieniowanych w zależności od głębokości
Listing 16.3 rysuje dwa imbryki przy włączonym cieniowaniu w zależności od głębokości. Wszystkimi operacjami graficznymi zajmuje się funkcja draw_scene, rozpoczynając od ustawienia koloru mgły na czerń oraz trybu mgły na GL_LINEAR:
static floatfog_color[4] = { 0.0, 0.0, 0.0, 0.0 };
glEnable(GL_FOG) ;
glFogf(GL_FOG_DENSITY, 0.05);
glFogfv(GL_FOG_COLOR, fog_color);
Na koniec rysuje dwa imbryki do herbaty, umieszczone w różnych odległościach od obserwatora. Rezultat widać doskonale.
Listing 16.3. Program FOGPOT.C: imbryki cieniowane w zależności od głębokości____________
/*
* "fogpot.c" - Testowy program demonstrujący zastosowanie funkcji
* glFogOprzy cieniowaniu w zależności od głębokości.
*/
#include <GL/glaux.h>
/*
* Te definicje zostały wprowadzone w celu zapewnienia zgodności
* pomiędzy MS Windows a resztą świata.
*
* CALLBACK i APIENTRY to modyfikatory funkcji w MS Windows.
*/
522 _______________________ Część III » Tematy zaawansowane i efekty specjalne
#ifndef WIN32
# define CALLBACK
# define APIENTRY
#endif /* IWIN32 */
GLfloat rotation = 0.0;
/*
* 'reshape_scene () ' - Zmiana rozmiaru sceny...
*/
V0id CALLBACK
reshape_scene (GLsizei width, /* We - Szerokość okna w pikselach */
GLsizei height) /* We - Wysokość okna w pikselach */ { /*
* Wyzerowanie bieżącego widoku i przekształcenia perspektywicznego.
*/
glViewport (O, O, width, height);
glMatrixMode(GL_PROJECTION) ;
glLoadldentity ( ) ;
gluPerspective(22.5, (float) width / (float) height, 0.1, 1000.0);
glMatrixMode(GL_MODELVIEW) ;
/*
* 'draw_scene () ' - Rysowanie sceny zawierającej dwa imbryki do
* herbaty
*/
void CALLBACK draw_scene (void) {
static float red_light[4] = { 1.0, 0.0, 0.0, 1.0 };
static float red_pos[4] = { 1.0, 1.0, 1.0, 0.0 };
static float blue_light [4] = { 0.0, 0.0, 1.0, 1.0 };
static float blue_pos[4] = ( -1.0, -1.0, -1.0, 0.0 } ;
static float fog_color[4] = { 0.0, 0.0, 0.0, 0.0 };
/*
* Włączenie buforów i oświetlenia
*/
glEnable (GL_DEPTH_TEST) ; glEnable (GL_LIGHTING) ; glEnable (GL_LIGHTO) ; glEnable (GL_LIGHT1) ;
glShadeModel (GL_SMOOTH) ;
/*
* Wyczyszczenie bufora koloru i głębokości. V
Rozdział 16. * Efekty specjalne: przezroczystość i mgła ____________________ 523
glClearColor (0.0, 0.0, 0.0, 0.0);
glClear (GL_COLOR_BUFFER_BIT l GL_DEPTH_BUFFER_BIT) ;
W scenie umieściliśmy dwa światła. Pierwsze z nich jest czerwone i zostało umieszczone u góry z prawej strony, za obserwatorem. Drugie jest niebieskie i znajduje się u dołu po lewej stronie, przed obserwatorem. /
glLightfv(GL_LIGHTO, GL_DIFFUSE, red_light) ; glLightfv(GL_LIGHTO, GL_POSITION, red_pos);
g!Lightfv(GL_LIGHTl, GL_DIFFOSE, blue_light) ; glLightfv(GL_LIGHTl, GL_POSITION, blue_pos);
glEnable(GL_FOG) ;
glFogf (GL_FOG_DENSITY, 0.05);
glFogfv(GL_FOG_COLOR, fog_color) ;
glPushMatrix() ;
glTranslatef (-1.0, 0.0, -15.0); glRotatef (-rotation, 0.0, 1.0, 0.0);
auxSolidTeapot (1.0); glPopMatrix() ;
glPushMatrix () ;
glTranslatef (1.0, 0.0, -10.0); glRotatef (rotation, 0.0, 1.0, 0.0);
auxSolidTeapot (1.0); glPopMatrix () ;
auxSwapBuf f ers ( ) ;
/*
* 'rotate_objects () ' - W wolnych chwilach obrót sceny.
*/
void CALLBACK rotate_objects (void) {
rotation += 2.0;
if (rotation >= 360.0) rotation -= 360.0;
draw_scene ( ) ;
'main()' - Inicjowanie okna i wyświetlanie sceny do momentu
* wciśnięcia klawisza 'Esc'.
/
524 ______________________ Część III » Tematy zaawansowane i efekty specjalne
void
main (void)
{
auxInitDisplayMode (AUX_RGB | AUX_DOUBLE | AUX_DEPTH) ;
auxlnitwindow("lmbryki we mgle");
auxReshapeFunc (reshape_scene) ; aux!dleFunc (rotate_objects) ;
auxMainLoop (draw_scene) ;
/*
* Koniec pliku "fogpot.c".
*/
Inne rodzaje mgły
W przypadku innych rodzajów mgły, jej kolorem najczęściej będzie biel lub jakiś inny jasny kolor. Oprócz koloru, mgła typu GL_EXP i GL_EXP2 posiada także gęstość:
glFogf(GL_FOG_DENSITY, density) ;
Parametr density może być dowolną wartością większą od 0,0, lecz zwykle będziesz nadawał mu wartość poniżej O, l. Wpływ tego parametru na mieszanie koloru mgły przedstawia rysunek 16.4.
Rysunek 16.4.
GL_EXP2 ,,-' //
density=0.25 ,''' t t
głębokości ^V/ ,''''
Kolor mgły / , /
A^. /
/ „*' XGLJXP
* *
Gęstość mgły w zależności od
Głęboko^
Głębokość mgły
Głębokość mgły to przetransformowana współrzędna Z wywołań glVertex. Ta współrzędna Z należy do przedziału od 0,0 do 1,0, tak samo jak w przypadku bufora głębokości. Głębokość mgły oraz jej gęstość określają sposób mieszania koloru mgły z kolorami sceny, przy zastosowaniu następujących wzorów:
r _ -(</L'«.v//yr) /exp ~ "
r _ -(demityzy J exp 2
koniec — z
J linear
koniec - początek
Rozdział 16. » Efekty specjalne: przezroczystość i mgła_____________________525
Domyślnie, mgła jest dodawana na wszystkich głębokościach, od 0,0 do 1,0. Parametry GL_FOG_START i GL_FOG_END ograniczają zakres wartości głębokości używanych do obliczeń mgły i zwykle wykorzystuje się je do precyzyjniejszego modelowania obszarów zamglenia (na przykład przy przelocie przez chmury przerwy w chmurach nie powinny być zamglone).
Powrót do programu przeglądania terenu
Efekt lekkiej mgiełki może być doskonałym uzupełnieniem programu przeglądania terenu z rozdziału 12. Na rysunku 16.5 widzimy fantastyczną poprawę jakości obrazu. Osiągnęliśmy to dzięki dodaniu poniższych trzech linii kodu:
glFogf(GL_FOG_DENSITY, 0.0025); glFogi(GL_FOG_MODE, GL_EXP); glFogfv(GL_FOG_COLOR, fogcolor);
Rysunek 16.5.
Widok terenu z zastosowaniem lekkiej mgiełki
W tym przypadku kolor mgły został zdefiniowany jako jednolity kolor RGB A (1,0, 1,0, 1,0, 1,0). Aby jeszcze bardziej poprawić jakość obrazu (wydłużając czas rysowania), możemy wywołać także
glHint(GL_FOG_HINT, GL_NICEST);
To wywołanie powoduje, że mgła jest obliczana dla każdego wierzchołka, a nie dla każdego wielokąta. Niestety, w przypadku większości scen oznacza to stukrotny wzrost obliczeń!
Przejdźmy teraz do listingu 16.4, czyli zmodyfikowanej funkcji RepaintWindow.
526_______________________Część III » Tematy zaawansowane i efekty specjalne
Listing 16.4. FOGSCENE. C: Zmodyfikowana funkcja RepaintWindow z programu przeglądarki terenu, _________tym razem korzystająca z funkcji glFog_____________________________
/*
* 'RepaintWindowO ' - Odrysowuje obszar roboczy okna sceny.
*/
void
RepaintWindowfRECT *rect) /* We - Obszar roboczy okna */
{
int i;
int x, y; /* Położenie terenu (x,y) */ int last_type; /* Poprzedni typ terenu */ int *type; /* Bieżący typ terenu */ GLfloat *height, /* Bieżąca wysokość terenu */ (*n)[3]; /* Bieżąca normalna terenu */ static GLfloat sky_top[4][3] = { /* Współrzędne nieba */
{ -TERRAIN_EDGE, TERRAIN_SIZE * 0.8, -TERRAIN_EDGE }, { TERRAIN_EDGE, TERRAIN_SIZE * 0.8, -TERRAIN_EDGE }, { TERRAIN_EDGE, TERRAIN_SIZE * 0.8, TERRAIN_EDGE }, ( -TERRAIN_EDGE, TERRAIN_SIZE * 0.8, TERRAIN_EDGE }
};
static GLfloat sky_bottom[4][3] =
{
{ -TERRAIN_EDGE, 0.0, -TERRAIN_EDGE },
{ TERRAIN_EDGE, 0.0, -TERRAIN_EDGE },
{ TERRAIN_EDGE, 0.0, TERRAIN_EDGE },
( -TERRAIN_EDGE, 0.0, TERRAIN_EDGE }
>;
static GLfloat sunpos[4] = { 0.0, 1.0, 0.0, 0.0 };
static GLfloat suncolor[4] = { 64.0, 64.0, 64.0, 1.0 };
static GLfloat sunambient[4] = { 0.001, 0.001, 0.001, 1.0 };
static GLfloat fogcolor[4] = { 1.0, 1.0, 1.0, 1.0 };
/*
* Wyzerowanie widoku i wyczyszczenie okna na jasny błękit
*/
glViewport(O, O, rect->right, rect->bottom); glClearColor(0.5, 0.5, 1.0, 1.0);
glEnable(GL_DEPTH_TEST); glEnable(GL_FOG); glFogf(GL_FOG_DENSITY, 0.0025); glFogi(GL_FOG_MODE, GL_EXP); glFogfv(GL_FOG_COLOR, fogcolor);
if (Moving || Drawing) { /*
* Bez tekstur podczas rysowania lub lotu; jest za wolne...
* Rysowanie tylnego bufora dla płynnej animacji
*/
Rozdział 16. * Efekty specjalne: przezroczystość l mgła ____________________ 527
glDisable (GL_TEXTURE_2D) ; glDrawBuf f er (GL_BACK) ;
else
Włączenie tekstur, gdy przestaniemy rysować lub latać W ten sposób tworzymy scenę, którą można wydrukować lub zachować w pliku.
Ponieważ to trwa dłużej, rysujemy w przednim buforze, aby użytkownik widział przebieg rysowania...
glEnable (GL_TEXTURE_2D) ;
glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL) ;
glDrawBuf f er (GL_FRONT) ;
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ;
/*
* Przygotowanie przekształcenia widoku dla bieżącego
* punktu obserwacji
*/
glMatrixMode (GL_PROJECTION) ; glLoadldentity () ;
gluPerspective(45.0, (float) rect->right / ( float) rect->bottom, 0.1, 1000.0);
glMatrixMode (GL_MODELVIEW) ; glPushMatrix() ;
glRotatef (Roli, 0.0, 0.0, 1.0);
glRotatef (Pitch, -1.0, 0.0, 0.0);
glRotatef (Heading, 0.0, 1.0, 0.0);
glTranslatef (-PositionfO] ,
-Position [1] ,
-Position[2] ) ; glScalef (TERRAIN_SCALE, TERRAIN_SCALE, TERRAIN_SCALE) ;
if ( ! (Moving l | Drawing) ) { /*
* Rysowanie nieba. . .
*/
glDisable (GL_LIGHTING) ; glCallList (SkyTexture) ; glBegin(GL_QUAD_STRIP) ; for (i = 0; i < 4; i ++) {
glTexCoord2f ( (float) i, 0.0); glvertex3fv(sky_bottom[i] ) ;
glTexCoord2f ( (float)i, 0.8); g!Vertex3fv(sky_top[i] ) ;
528______________________Część III » Tematy zaawansowane i efekty specjalne
glTexCoord2f(4.0, 0.0); glVertex3fv(sky_bottom[0]);
glTexCoord2f(4.0, 0.8); glVertex3fv(sky_top[0]); glEnd();
glBegin(GL_TRIANGLE_FAN); glTexCoord2f(0.5, 1.0); glVertex3f(0.0, TERRAIN_SIZE, 0.0);
for (i = 0; i < 4; i ++) {
glTexCoord2f((float)i, 0.8);
glVertex3fv(sky_top[i]); };
glTexCoord2f(4.0, 0.8); glVertex3fv(sky_top[0]); glEnd(); };
/*
* Przygotowanie oświetlenia...
*/
glEnable(GL_LIGHTING);
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
glEnable(GL_LIGHTO);
glLightfv(GL_LIGHTO, GL_POSITION, sunpos); glLightfv(GL_LIGHTO, GL_DIFFUSE, suncolor); glLightfv(GL_LIGHTO, GL_AMBIENT, sunambient);
if (Moving || Drawing)
glEnable(GL_COLOR_MATERIAL); else
glDisable(GL_COLOR_MATERIAL);
/*
* Teraz teren...
*/
type = TerrainType[0];
height = TerrainHeight[0];
n = TerrainNormal[0];
for (y = 0; y < (TERRAIN_SIZE - 1); y ++)
{
last_type = -1;
for (x = 0; x < TERRAIN_SIZE; x ++, type ++, height ++, n ++) {
if (last_type != *type)
{ /*
* Jeśli zmienia się typ terenu, zakończ istniejący pasek
* i wyzeruj parametry tekstury/koloru
*/
Rozdział 16. » Efekty specjalne: przezroczystość i mgła_____________________529
if (last_type != -1) glEnd() ;
switch (*type)
{
case IDC_WATER :
if (Moving l l Drawing)
glColor3f(0.0, 0.0, 0.5); else
glCallList(WaterTexture); break; case IDC_GRASS :
if (Moving || Drawing)
glColorSf(0.0, 0.5, 0.0); else
glCallList(GrassTexture); break; case IDC_ROCKS :
if (Moving || Drawing)
glColor3f(0.25, 0.25, 0.25); else
glCallList(RocksTexture); break; case IDC_TREES :
if (Moving l| Drawing)
glColorSf(0.0, 0.25, 0.0); else
glCallList(TreesTexture); break; case IDC_MOUNTAINS :
if (Moving || Drawing)
glColor3f(0.2, 0.1, 0.05); else
glCallList(MountainsTexture); break; };
glBegin(GL_QUAD_STRIP); if (last_type != -1) {
/*
* Zaczniemy od poprEedniego miejsca, aby nie było dziur V
glTexCoord2i(x * 2 - 2, y * 2);
glNormal3fv(n[-l]);
glVertex3f((GLfloat)(x - TERRAIN_EDGE - 1), height[-l], (GLfloat)(y - TERRAIN_EDGE));
glTexCoord2i(x *2-2, y*2+2);
glNormal3fv(n[TERRAIN_SIZE - 1]);
glVertex3f((GLfloat)(x - TERRAIN_EDGE - 1), height[TERRAIN_SIZE - 1], (GLfloat)(y - TERRAIN_EDGE + 1));
};
last_type = *type;
530_______________________Część III » Tematy zaawansowane i efekty specjalne
glTexCoord2i(x * 2, y * 2);
glNorma!3fv(n[0]);
glVertex3f((GLfloat)(x - TERRAIN_EDGE),
height[0],
(GLfloat)(y - TERRAIN_EDGE)); glTexCoord2i(x * 2, y * 2 + 2); glNormal3fv(n[TERRAIN_SIZE]); glVertex3f((GLfloat)(x - TERRAIN_EDGE),
height[TERRAIN_SIZE],
(GLfloat)(y - TERRRIN_EDGE + l));
};
glEnd(); };
glPopMatrix();
/*
* Gdy lecimy lub rysujemy, używamy podwójnego buforowania.
* Jeśli trzeba, przerzuć bufory
*/
glFinish();
if (Moving || Drawing) SwapBuffers(SceneDC);
Podsumowanie
Łączenie kolorów i mgła stanowią uzupełnienie biblioteki OpenGL będąc kolejnym narzędziem służącym do zwiększania realizmu tworzonych scen. Dzięki łączeniu kolorów uzyskujemy efekt przezroczystości oraz poprawę antyaliasingu punktów, linii i wieloką-tów. Stosując mgłę możemy cieniować sceny w zależności od głębokości oraz tworzyć efekty atmosferyczne - gęste mgły, chmury lub leciutkie poranne mgiełki. Dzięki nim tworzone obrazy wyglądają na mniej precyzyjne, czyli - ironicznie - bardziej przypominające rzeczywisty świat.
Podręcznik
gIBIendFunc
Przeznaczenie Ustawia funkcję łączenia kolorów.
Plik nagłówkowy <gl.h>
Składnia void glBlendFunc(GLenum sfactor, GLenum dfactor);
Opis Ta funkcja ustala kolor źródłowy i docelowy przy łączeniu kolorów.
Aby włączyć łączenie kolorów, musisz wywołać funkcję
glEnable(GL_BLEND). Łączenie kolorów jest dostępne jedynie
531
Rozdział 16. » Efekty specjalne: przezroczystość i mgła
w kontekstach rysowania RGBA. Domyślne ustawienie łączenia kolorów to glBlendFunc(GL_ONE, GL_ZERO).
Parametry
sfactor GLenum: Funkcja łączenia koloru źródłowego.
dfactor GLenum: Funkcja łączenia koloru docelowego.
Zwracana wartość Brak.
Przykład Kod programu BLENDPOT.C na płytce CD-ROM.
glFog
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry pname
dfactor
Zwracana wartość Przykład
Określa parametry mgły. <gl.h>
void glFogf(GLenum pname, GLfloat param); void glFogfv(GLenum pname, GLfloat *params); void glFogi(GLenum pname, GLint param); void glFogiv(GLenum pname, GLint *params);
Funkcja glFog ustala parametry mgły. Aby w scenie pojawiła się mgła, musisz ją włączyć wywołaniem glEnable(GL_FOG).
GLenum: Ustawiany parametr. Dostępne parametry to:
GL_FOG_COLOR Kolor mgły; musi być tablicą czterech elementów reprezentujących kolor RGBA.
GL_FOG_DENSITY Gęstość mgły; liczba większa od 0,0. Gęstość mgły jest używana jedynie w trybach mgły GL_EXP i GL_EXP2.
GL_FOG_END Największa głębokość, dla której obliczana jest mgła. Jest to przetransformowana wartość Z (głębokość) z zakresu od 0.0 do 1.0.
GL_FOG_INDEX Indeks koloru mgły, używany, gdy kontekst renderowania OpenGL jest kontekstem korzystającym z indeksu kolorów.
GL_FOG_MODE Typ mgły; określa formułę używaną do renderowania efektu mgły (GL_LINEAR, GL_EXP lub GL_EXP2).
GL_FOG_START Najmniejsza głębokość, dla której obliczana jest mgła. Jest to przetransformowana wartość Z (głębokość) z zakresu od 0,0 do 1,0.
GLenum: Funkcja łączenia koloru docelowego.
Brak.
Kod programu FOGSCENE.C na płytce CD-ROM.
Rozdział 17.
Krzywe i powierzchnie: co to jest NURBS?!!
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
* Użyć odwzorowań do tworzenia * glMap, glEvalCoord
krzywych i powierzchni Bćziera.
* Skorzystać z wbudowanych funkcji 4 glMapGrid, glEvalMesh
obliczeń upraszczających odwzorowanie
powierzchni
* Tworzyć powierzchnie NURBS * gluNewNurbsRenderer,
gluBeginSurface, gluNurbsSurface, gluEndSurface, gluDeleteNurbsRenderer
* Tworzyć krzywe wycinania * gluBeginTrim, gluPwlCurve,
gluEndTrim
W przypadku większości aplikacji wykorzystujących grafikę 3D, przydatne stają się gładkie krzywe i powierzchnie. Za pomocą technik opisywanych w innych rozdziałach tej książki możesz dzielić takie powierzchnie na wiele mniejszych czworokątów lub trójkątów, obliczać normalne dla poszczególnych wierzchołków oraz stosować oświetlenie - tworząc coś, co przypomina bardzo gładką i połyskliwą powierzchnię. Możesz także, przy zastosowaniu czegoś więcej niż podstawowej algebry, tworzyć kod obliczający powierzchnię na podstawie równań, dających lepsze lub gorsze przybliżenie gładkiej powierzchni paskami czworokątów lub trójkątów.
Przypuśćmy jednak, że chcesz utworzyć krzywą lub powierzchnię, lecz nie masz do dyspozycji odpowiednich równań. Zadanie znalezienia wielomianu drugiego lub trzeciego stopnia wcale nie jest banalne, jeśli mamy do dyspozycji jedynie garść punktów tworzących krzywą lub powierzchnię. Czysto matematyczne podejście jest bardzo czasochłon-
534______________________Część III » Tematy zaawansowane i efekty specjalne
ne i podatne na błędy, nawet przy zastosowaniu komputera. A jeśli uważasz, że możesz dokonać takich obliczeń „w głowie", to od razu możesz o tym zapomnieć.
Dostrzegając taką potrzebę w grafice komputerowej, Pierre Bezier, projektant nadwozi samochodów w firmie Renault w latach siedemdziesiątych, stworzył zestaw modeli matematycznych służących do reprezentacji krzywych i powierzchni poprzez niewielki zestaw punktów kontrolnych. Oprócz uproszczenia reprezentacji zakrzywionych powierzchni, te modele znakomicie sprawdziły się przy interaktywnym projektowaniu kształtów krzywych i powierzchni.
Od tego momentu opracowano także inne rodzaje krzywych i powierzchni, a w zasadzie cały słownik terminów związanych z generowanymi komputerowo powierzchniami. Matematyka kryjąca się za tymi magicznymi formułami nie jest bardziej skomplikowana od operacji na macierzach opisywanych w rozdziale 7, a co więcej, opisuje tworzone obiekty w sposób bardzo intuicyjny. Tak jak w rozdziale 7, spróbujemy opisać ją w taki sposób, abyś mógł maksymalnie wykorzystać te funkcje, nie zagłębiając się przy tym w bardziej skomplikowane zagadnienia matematyczne.
Krzywe i powierzchnie
Krzywa posiada punkt początkowy, długość oraz punkt końcowy. W rzeczywistości jest tylko linią wijącą się w trójwymiarowej przestrzeni. Z drugiej strony, powierzchnia posiada zarówno długość, jak i szerokość, a w związku z tym posiada również powierzchnię. Rozdział ten zaczniemy więc od opisu sposobów rysowania gładkich trójwymiarowych krzywych, po czym poznane pojęcia rozszerzymy na trójwymiarowe powierzchnie. Zanim jednak do tego przejdziemy, poznajmy przynajmniej podstawowe terminy i kryjącą się za nimi matematykę.
Reprezentacja parametryczna
Gdy myślisz o linii prostej, najczęściej masz na uwadze słynne równanie: Y = mX + b
W tym równaniu m odpowiada pochyleniu linii, zaś b jest punktem przecięcia linii z osią Y. Być może przypominasz sobie matematykę ze szkoły średniej, gdzie uczyłeś się także o równaniach paraboli, hiperboli, krzywych wykładniczych, itd. We wszystkich tych równaniach zmienna Y (lub X) była wyrażona jako funkcja wartości na osi X (lub Y).
Innym sposobem reprezentacji równania krzywej lub prostej jest równanie parametryczne. Równanie parametryczne wyraża zarówno współrzędne X, jak i Y poprzez inną zmienną, zmieniającą się w określonym zakresie wartości. Na przykład w fizyce współrzędne X, Y i Z mogą być niekiedy wyrażone w funkcji czasu, gdzie czas może być po-
535
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS7J!
dawany w sekundach. W poniższych formułach f(), g() i h() to różne funkcje, zmienne w czasie (t):
X = f(t) Y = g(t); Z = h(t);
Gdy definiujemy krzywą w OpenGL, definiujemy jej równanie parametryczne. Parametryczną zmienną krzywej będziemy nazywać u, zaś jej zakres wartości będziemy nazywać dziedziną tej krzywej. Powierzchnie będą opisywane przy użyciu dwóch parametrów: u i v. Reprezentację krzywej i powierzchni poprzez dziedziny u i v przedstawia rysunek 17.1. Ważne jest, aby pamiętać, że zmienne parametryczne (u i v) reprezentują parametry równania, a nie same wartości współrzędnych.
Rysunek 17.1.
Parametryczna reprezentacja krzywych i powierzchni
u =1.0
u = 0.0
u = 1.0
v=1.0
x = f(u,v)
y=M
Punkty kontrolne
Krzywe są reprezentowane przez pewną ilość punktów kontrolnych, wpływających na kształt krzywej. W przypadku krzywych Beziera, punkty pierwszy i ostatni są jednocześnie pierwszym i ostatnim punktem samej krzywej. Pozostałe punkty kontrolne zachowują się jak magnes, „przyciągając" krzywą do siebie. Kilka przykładów krzywych, z różnymi ilościami punktów kontrolnych, przedstawia rysunek 17.2.
Rysunek 17.2.
W jaki sposób punkty kontrolne wpływają p na kształt krzywych l
P,
(b)
(o)
(c)
Klasę krzywej określamy przez ilość punktów kontrolnych używanych do opisu tej krzywej. Stopień krzywej jest liczbą o jeden mniejszą od klasy. Matematyczne zna-
536_______________________Część III » Tematy zaawansowane i efekty specjalne
czenie tych terminów wywodzi się z równań parametrycznych dokładnie opisujących dane krzywe, gdzie klasa oznacza ilość współczynników, zaś stopień odpowiada najwyższemu wykładnikowi zmiennej parametrycznej. Jeśli chcesz lepiej poznać matematyczne podstawy wykorzystywania krzywych Beziera, zajrzyj do dodatku B.
Krzywa z rysunku 17.2b jest nazywaną krzywą kwadratową, czyli drugiego stopnia, zaś krzywa z rysunku 17.2c jest nazywana krzywą kubiczną, czyli trzeciego stopnia. Teoretycznie, można zdefiniować krzywą dowolnego stopnia, jednak krzywe wyższych rzędów mają tendencje do niekontrolowanych oscylacji i zmian w wyniku choćby najmniejszych zmian punktów kontrolnych.
Ciągłość
Jeśli dwie krzywe umieszczone obok siebie mają wspólny punkt, razem tworzą krzywą składającą się z kawałków. Ciągłość krzywej w miejscach połączeń określa gładkie jest przejście pomiędzy poszczególnymi kawałkami. Cztery kategorie ciągłości to brak ciągłości (CO), ciągłość szczepna (Cl), ciągłość styczna (C2) oraz krzywizna (C3).
Jak widać na rysunku 17.3, brak ciągłości występuje wtedy, gdy dwa kawałki krzywej nie stykają się ze sobą. Ciągłość szczepna jest zachowanie, gdy dwa kawałki krzywej mają przynajmniej wspólny punkt początkowy. Ciągłość styczna występuje wtedy, gdy krzywe we wspólnym punkcie posiadają tę samą styczną. Na koniec, krzywizna jest zachowana wtedy, gdy pochodne obu kawałków krzywej w punkcie złączenia są równe (czyli występuje gładkie przejście z kawałka do kawałka krzywej).
Rysunek 17.3. ^_^ Styczna
Ciągłość kawałków
C°-Bralc ciągłości
C -Gąglość szczepna
krzywej
C' -Ciągłość styczna
C3-Krzywizna
Podczas tworzenia złożonych krzywych i powierzchni, składających się z wielu kawałków, zwykle będziesz się starał zachować ciągłość typu C2 lub C3. Jak później zobaczysz, można tak dobrać pewne parametry generowania krzywych i powierzchni, aby osiągnąć pożądane rezultaty.
537
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS?!!
Obliczenia
OpenGL posiada kilka funkcji bardzo ułatwiających rysowanie krzywych i powierzchni Beziera, przez określenie punktów kontrolnych oraz zakresu zmiennych parametrycznych u i v. Następnie, przez wywołanie odpowiedniej funkcji wyliczającej, generowane są punkty tworzące krzywą lub powierzchnię. Zaczniemy od utworzenia dwuwymiarowej krzywej Beziera, a następnie rozszerzymy ją o trzeci wymiar w celu zbudowania powierzchni Beziera.
Dwuwymiarowa krzywa
Najlepszym sposobem będzie przedstawienie przykładu i opisanie go linia po linii. Listing 17.1 zawiera fragmenty kodu z programu BEZIER na płytce CD-ROM. Program określa cztery punkty kontrolne krzywej Beziera, a następnie za pomocą funkcji obliczającej rysuje kolejne punkty samej krzywej. Wynik działania tego programu został przedstawiony na rysunku 17.4.
Rysunek 17.4.
Wynik działania programu BEZIER
Listing 17.1. Fragment programu BEZIER rysującego krzywą Beziera z czterema punktami kontrolnymi
II Ilość punktów kontrolnych krzywej GLint nNumPoints = 4;
GLfloat ctrlPoints[4][3]= {( -4.0f, O.Of, O.Of), // Punkt końcowy
{ -6.0f, 4.0f, O.Of}, // Punkt kontrolny
( 6.0f, -4.0f, O.Of}, // Punkt kontrolny
{ 4.0f, O.Of, O.Of }}; // Punkt końcowy
// Ta funkcja służy do rozmieszczenia punktów kontrolnych krzywej void DrawPoints (void)
int i;
538______________________Część III » Tematy zaawansowane i efekty specjalne
// Rysujemy większe punkty, aby były lepiej widoczne glPointSize(5.0f);
// Pętla dla wszystkich punktów kontrolnych w tym przykładzie glBegin(GL_POINTS);
for(i =0; i < nNumPoints; i++) glVertex2fv(ctrlPoints[i]); glEnd(); }
// Zmiana bryły widzenia i widoku.
// Wywoływane w momencie zmiany wymiaru okna
void ChangeSize(GLsizei w, GLsizei h)
<
// Zabezpieczenie przed dzieleniem przez O
if(h == 0) h = 1;
// Ustawienie widoku na wymiary okna glviewport(O, O, w, h); glMatrixMode(GL_PROJECTION); glLoadldentity();
gluOrtho2D(-10.0f, 10.Of, -10.Of, 10.Of);
// Zerowanie macierzy widoku modelu glMatrixMode(GL_MODELVIEW); glLoadldentity(); }
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
//int i;
// Wyczyszczenie okna bieżącym kolorem tła glClear(GL_COLOR_BUFFER_BIT);
// Przygotowanie krzywej Beziera
// Trzeba to zrobić tylko raz, więc powinniśmy to
// uczynić w funkcji przygotowującej kontekst.
glMaplf(GL_MAP1_VERTEX_3, // Rodzaj generowanych danych
O.Of, V// Początek zakresu u
100.Of, /-—-^7 Koniec zakresu u
3, f //Odstęp pomiędzy danymi wierzchołków
nNumPoints, ''~--J II ilość punktów kontrolnych
SctrlPoints[0] [0]); // tablica punktów kontrolnych
// Włączenie funkcji obliczającej glEnable(GL_MAP1_VERTEX_3);
Użycie łamanej "łączącej punkty" glBegin(GL_LINE_STRIP);
for (i = 0; i <= 100; i++) {
// Obliczenie krzywej dla danego punktu glEvalCoordlf((GLfloat) i); } glEnd();
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS?!!___________________539
// Wyrysowanie punktów kontrolnych DrawPoints();
// Zrzucenie poleceń graficznych glFlushO;
Pierwszym elementem w listingu 17.1 jest definicja punktów kontrolnych naszej krzywej:
// Ilość punktów kontrolnych krzywej GLint nNumPoints = 4;
GLfloat ctrlPoints[4][3]= {{ -4.0f, O.Of, O.Of), // Punkt końcowy
( -6.Ot, 4.0f, O.Of), // Punkt kontrolny
{ e.Of, -4.0f, O.Of}, // Punkt kontrolny
{ 4.0f, O.Of, O.Of }}; // Punkt końcowy
Dla ilości punktów kontrolnych oraz samej tablicy punktów kontrolnych stworzyliśmy zmienne globalne. Aby poeksperymentować, możesz je zmienić dodając kolejne punkty kontrolne lub po prostu modyfikując położenie punktów.
Funkcja DrawPoints() jest chyba oczywista. Wywołujemy ją z kodu renderującego w celu wyświetlenia punktów kontrolnych krzywej. Jest także bardzo użyteczna przy eksperymentach z położeniem punktów kontrolnych. Nasza standardowa funkcja Chan-geSize() ustanawia dwuwymiarowy rzut równoległy o rozciągłości od -10 do 10 jednostek w osiach x i y.
Na koniec, przechodzimy do kodu renderującego. Funkcja RenderScene() wywołuje na początku funkcję glMaplf (po wyczyszczeniu ekranu) w celu stworzenia odwzorowania dla naszej krzywej:
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
//int i;
// Wyczyszczenie okna bieżącym kolorem tła glClear(GL_COLOR_BUFFER_BIT);
// Przygotowanie krzywej Beziera
// Trzeba to zrobić tylko raz, więc powinniśmy to
// uczynić w funkcji przygotowującej kontekst.
glMaplf(GL_MAP1_VERTEX_3, // Rodzaj generowanych danych
O.Of, // Początek zakresu u
100.Of, // Koniec zakresu u
3, // Odstęp pomiędzy danymi wierzchołków
nNumPoints, // ilość punktów kontrolnych
SctrlPoints[0] [0]); // tablica punktów kontrolnych
Pierwszy parametr funkcji glMaplf, GL_MAP1_VERTEX_3, przygotowuje funkcję obliczającą do generowania trójek współrzędnych wierzchołków (x, y i z), w odróżnieniu od GL_MAP1_VERTEX_4, służącego do generowania trójek współrzędnych oraz składowej alfa. Możesz także przygotować funkcję obliczającą inne wartości, takie jak
540_______________________Część III » Tematy zaawansowane i efekty specjalne
współrzędne tekstury czy informacje o kolorze. Szczegóły znajdziesz w sekcji podręcznika.
Dwa następne parametry określają dolny i górny zakres parametru u dla tej krzywej. Dolna wartość określa początkowy punkt krzywej, zaś górna wartość określa ostatni punkt krzywej. Wszystkie wartości pomiędzy nimi odnoszą się do punktów tworzonej krzywej. W naszym przykładzie ustaliliśmy zakres od O do 100.
Czwarty parametr funkcji glMaplf określa ilość zmiennoprzecinkowych wartości pomiędzy wierzchołkami w tablicy punktów kontrolnych. Każdy wierzchołek składa się z trzech zmiennoprzecinkowych wartości (dla x, y i z), więc ten parametr możemy ustawić na 3. Ta elastyczność umożliwia umieszczenie punktów kontrolnych w różnych strukturach danych, pod warunkiem, że te struktury są rozmieszczone w regularnych odstępach w pamięci.
Ostatni parametr funkcji to wskaźnik do bufora zawierającego punkty kontrolne użyte do definiowania krzywej. W naszym przykładzie przekazujemy wskaźnik do pierwszego elementu tablicy. Po utworzeniu odwzorowania krzywej możemy włączyć funkcję obliczającą dla tego odwzorowania. Osiągamy to przez włączenie odpowiedniej zmiennej stanu; oto wywołanie potrzebne do włączenia funkcji obliczającej generującej punkty wzdłuż krzywej:
// Włączenie funkcji glEnable(GL_MAP1_VĘRTEX3);
Funkcja glEvalCoordl f otrzymuje pojedynczy argument: wartość parametryczną krzywej. Ta funkcja oblicza współrzędne punktu krzywej dla danej wartości zmiennej parametrycznej i wewnętrznie wywołuje funkcję glVertex dla tego punktu. Poprzez przejście (w pętli) przez dziedzinę krzywej i wywoływanie funkcji glEvalCoord w celu utworzenia wierzchołków, możemy narysować krzywą za pomocą pojedynczej łamanej:
// Użycie łamanej "łączącej punkty" glBegin(GL_LINE_STRIP);
for(i = 0; i <= 100; i++) {
// Obliczenie krzywej dla danego punktu glEvalCoordlf((GLfloat) i); } glEnd();
Na koniec, chcemy jeszcze wyrysować punkty kontrolne:
// Wyrysowanie punktów kontrolnych DrawPoints();
// Zrzucenie poleceń graficznych glFlushO ;
Obliczanie krzywej
OpenGL może jeszcze bardziej uprościć całe zadanie. Możemy przygotować siatkę punktów za pomocą funkcji glMapGrid, służącej do utworzenia równomiernej siatki punktów w dziedzinie krzywej. Następnie możemy wywołać funkcję glEvalMesh w celu
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS?!!___________________541
„połączenia kropek" przy użyciu podanego prymitywu (GLJLINE lub GL_POINTS). Poniższe dwa wywołania:
// Użycie funkcji wyższego poziomu do odwzorowania siatki, // a następnie obliczenia całości
// Odwzorowanie siatki 100 punktów od O do 100 glMapGridlddOO, 0.0, 100.0) ;
// Utworzenie siatki przy użyciu linii glEvalMeshl(GL_LINE,O,100);
całkowicie zastępują ten kod:
// Użycie łamanej "łączącej punkty" glBegin(GL_LINE_STRIP);
for(i = 0; i <= 100; i++) {
// Obliczenie krzywej dla danego punktu glEvalCoordlf((GLfloat) i); } glEnd();
Jak widać, jest to bardziej spójne i efektywne, jednak prawdziwe korzyści płynące z tych wywołań objawiają się raczej podczas tworzenia powierzchni, nie krzywych.
Powierzchnia trójwymiarowa
Tworzenie trójwymiarowej powierzchni Beziera przebiega podobnie do tworzenia dwuwymiarowej krzywej. Oprócz zdefiniowania punktów w dziedzinie u, musimy zdefiniować punkty także w dziedzinie v.
Listing 17.2 pochodzi z naszego następnego programu, BEZ3D, w którym wyświetlamy siatkę przedstawiającą powierzchnię Beziera. Pierwszą zmianą w stosunku do poprzedniego przykładu jest zdefiniowanie trzech dodatkowych zestawów punktów kontrolnych powierzchni, wzdłuż dziedziny v. Aby powierzchnia nie była skomplikowana, poza wartością Z pozostałe współrzędne punktów kontrolnych pozostają takie same. W ten sposób stworzyliśmy jednorodną powierzchnię, tak jakbyśmy „rozciągnęli" naszą dwuwymiarową krzywą Beziera wzdłuż osi Z.
Listing 17.2. Kod z programu BEZ3D tworzący powierzchnię Beziera__________________
// Ilość punktów kontrolnych krzywej GLint nNumPoints = 3;
GLfloat ctrlPoints[3][3][3]= {{ { -4.0f, O.Of, 4.0f),
{ -2.0f, 4.0f, 4.0f}, { 4.0f, O.Of, 4.0f }},
{{ -4.0f, O.Of, O.Of}, { -2.0f, 4.0f, O.Of}, { 4.0f, O.Of, O.Of }},
({ -4.0f, O.Of, -4.0f},
{ -2.0f, 4.0f, -4.0f),
{ 4.0f, O.Of, -4.0f }}};
542 ________________Część III » Tematy zaawansowane i efekty specjalne
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
//int i;
// Wyczyszczenie okna bieżącym kolorem tła glClear(GL_COLOR_BUFFER_BIT);
// Zachowanie macierzy widoku modelu glMatrixMode(GL_MODELVIEW); glPushMatrix();
// Obrót siatki, aby było ją lepiej widać glRotatef (45.0f, •O.. Of, l.Of, O.Of); glRotatef(60.Ofr-A.Of, O.Of, O.Of);
f.
II Przygotowanie krzywej Beziera
// Trzeba to zrobić tylko raz, więc powinniśmy to
// uczynić w funkcji przygotowującej kontekst.
glMap2f(GL_MAP2_VERTEX_3, // Rodzaj generowanych danych
O.Of, // Początek zakresu u
10.Of, // Koniec zakresu u
3, // Odstęp pomiędzy danymi wierzchołków
3, // Rozmiar w kierunku u (klasa)
O.Of, // Początek zakresu v
10.Of, // Koniec zakresu v
9, // Odstęp pomiędzy danymi wierzchołków
3, // Rozmiar w kierunku v (klasa)
&ctrlPoints[0] [0] [0]);// tablica punktów kontrolnych
// Włączenie funkcji obliczającej glEnable(GL_MAP2_VERTEX_3);
// Użycie funkcji wyższego poziomu do odwzorowania siatki, // a następnie obliczenia całości
// Odwzorowanie siatki 10 punktów od O do 10 glMapGrid2f(10,O.Of,10.Of,10, O.Of, 10.Of) ;
// Utworzenie siatki przy użyciu linii glEvalMesh2(GL_LINE,0,10,0,10);
// Wyrysowanie punktów kontrolnych DrawPoints ();
// Odtworzenie macierzy widoku modelu glPopMatrix();
// Zrzucenie poleceń graficznych glFlushO ;
Nasz kod renderowania także jest nieco inny. Oprócz obrócenia figury w celu uzyskania lepszego widoku, zamiast funkcji glMap l f wywołujemy funkcję glMap2f. W ten sposób określamy punkty w dziedzinach m i v, a nie tylko w dziedzinie u.
543
Rozdział 17. * Krzywe i powierzchnie: co to jest NURBS?!!
// Przygotowanie krzywej Beziera
// Trzeba to zrobić tylko raz, więc powinniśmy to
// uczynić w funkcji przygotowującej kontekst.
glMap2f(GL_MAP2_VERTEX_3, // Rodzaj generowanych danych
O.Of, // Początek zakresu u
10.Of, // Koniec zakresu u
3, // Odstęp pomiędzy danymi wierzchołków
3, // Rozmiar w kierunku u (klasa)
O.Of, // Początek zakresu v
10.Of, // Koniec zakresu v
9, // Odstęp pomiędzy danymi wierzchołków
3, // Rozmiar w kierunku v (klasa)
sctrlPoints[0] [0] [0]);// tablica punktów kontrolnych
W dalszym ciągu musimy określać początek i koniec dziedziny u; odległością pomiędzy punktami w dziedzinie u jest w dalszym ciągu 3. Jednak tym razem musimy także określić początek i koniec dziedziny v. Odstęp pomiędzy punktami w dziedzinie v to 9, gdyż mamy trójwymiarową tablicę punktów kontrolnych, w której każdy wiersz z dziedziny u składa się z trzech punktów po trzy wartości każdy (3x3= 9). Następnie informujemy funkcję glMap2f o ilości punktów w kierunku v określonych dla każdego wiersza w kierunku u, a następnie przekazujemy wskaźnik do tablicy wszystkich punktów kontrolnych.
Dwuwymiarowa funkcja obliczająca jest włączana podobnie jak funkcja jednowymiarowa, zaś funkcja glMapGrid2 jest wywoływana z ilością punktów kontrolnych w kierunkach u i v:
glEnable(GL_MAP2_VERTEX_3);
// Użycie funkcji wyższego poziomu do odwzorowania siatki, // a następnie obliczenia całości
// Odwzorowanie siatki 10 punktów od O do 10 glMapGrid2f(10,O.Of,10.Of,10,O.Of,10.Of);
Rysunek 17.5.
Wynik działania programu BEZ3D
Po przygotowaniu funkcji obliczającej, możemy wywołać dwuwymiarową (w sensie dziedzin u i v) wersję funkcji glEvalMesh w celu utworzenia siatki punktów powierz-
544
Część III » Tematy zaawansowane i efekty specjalne
chni. Siatkę tworzymy z linii, zaś zakres dziedzin u i v rozciąga się od wartości O do wartości 10.
// Utworzenie siatki przy użyciu linii glEvalMesh2(GL_LINE,O,10,O,10);
Wynik działania tego programu został przedstawiony na rysunku 17.5.
Oświetlenie i normalne
Kolejną cenną właściwością funkcji obliczających jest automatyczne generowanie normalnych. Przez prostą zmianę kodu:
// Utworzenie siatki przy użyciu linii glEvalMesh2(GL_LINE,O,10,0,10);
na
// Utworzenie siatki przy użyciu linii glEvalMesh2(GL_FILL,0,10,0,10) ;
a następnie wywołanie
glEnable(GL_A0TO_NORMAL);
w naszym kodzie inicjującym, możemy łatwo wygenerować normalne do powierzchni stworzonej za pomocą funkcji obliczającej. Rysunek 17.6 przedstawia tę samą powierzchnię, co rysunek 17.5, z tym że włączono oświetlenie i automatyczne generowanie normalnych do powierzchni. Kod tego programu znajduje się w folderze BEZLIT na płytce CD-ROM. Program stanowi jedynie nieznaczną modyfikację programu BEZ3D.
Rysunek 17.6.
Wynik działania programu BEZLIT
Rozdział 17. * Krzywe i powierzchnie: co to jest NURBS?!!___________________545
NURBS
Funkcji obliczających powierzchnie Beziera możesz używać do woli, jednak w przypadku bardziej złożonych krzywych będziesz musiał składać je z kawałków. W miarę dodawania kolejnych punktów kontrolnych coraz trudniej jest utworzyć krzywą o poprawnej ciągłości. Na szczęście, pełniejszą kontrolę można osiągnąć używając funkcji NURBS z biblioteki glu. NURBS to skrót słów non-uniform rational B-spline (niejednorodne wymierne krzywe B-sklejane). Matematycy już wiedzą, że chodzi po prostu o bardziej uogólnioną formę krzywych i powierzchni niż te, które mogą tworzyć krzywe i powierzchnie Beziera, a także kilka innych rodzajów krzywych. W przypadku funkcji NURBS można określać wpływ punktów kontrolnych podanych funkcjom obliczającym, w celu otrzymania gładszych krzywych i powierzchni o większej liczbie punktów kontrolnych.
Od krzywych Beziera do krzywych B-sklejanych
Krzywa Beziera jest zdefiniowana przez dwa punkty końcowe oraz dowolną liczbę innych punktów kontrolnych, wpływających na kształt krzywej. Trzy krzywe Beziera z rysunku 17.7 mają określone 3, 4 i 5 punktów kontrolnych. Krzywa jest styczna do linii łączącej punkty końcowe z następnymi punktami kontrolnymi. W przypadku krzywych o trzech i czterech punktach kontrolnych, otrzymana krzywa Beziera jest bardzo gładka i zwykle posiada ciągłość typu C3 (krzywizna). Jednak w przypadku większej ilości punktów kontrolnych, krzywa zaczyna być coraz mniej gładka, gdyż „wygina" ją coraz więcej elementów.
Rysunek 17.7. ,;. P, \ P, \ P)
Ciągłość krzywych *•». T ._
Beziera przy ''•/"•'*».
wzroście ilości '/ ^**s,_
• ^^^ p
punktów kontrolnych L ^s^ "2
Trzy punkty kontrolne Cztery punkty kontrolne Pięć punktów kontrolnych
Z drugiej strony, krzywe B-sklejane (bikubiczne krzywe sklejane) wyglądają podobnie do krzywych Beziera, z tym że są podzielone na segmenty. Kształt każdego z segmentów zależy wyłącznie od położenia czterech najbliższych (w sensie kolejności) punktów kontrolnych, co daje krzywą złożoną z kawałków, z których każdy zachowuje charakterystykę podobną do krzywej Beziera o czterech punktach kontrolnych. Oznacza to, że długa krzywa z wieloma punktami kontrolnymi może zachować gładkość, gdyż połączenia pomiędzy kawałkami krzywej zachowują ciągłość typu C3. Oznacza to także, że krzywa nie musi przechodzić przez żaden ze swoich punktów kontrolnych.
546
Część III » Tematy zaawansowane i efekty specjalne
Węzły
Prawdziwa potęga krzywych NURBS polega jednak na tym, że możesz określić wpływ czterech punktów kontrolnych na dany segment krzywej w celu osiągnięcia pożądanej gładkości. Odbywa się to poprzez określenie sekwencji wartości zwanych węzlami.
Dla każdego punktu kontrolnego są zdefiniowanie dwie wartości węzłów. Zakres wartości węzłów odpowiada dziedzinom parametrów u oraz v i musi być niemalejący, gdyż wartości węzłów określają wpływ punktów kontrolnych przypadających na dany zakres w przestrzeni u/v. Rysunek 17.8 przedstawia krzywą demonstrującą wpływ punktów kontrolnych na krzywą posiadającą cztery jednostki w dziedzinie parametru m. Punkty w środkowej części krzywej wyginają ją w większym stopniu, a na kształt krzywej mają wpływ jedynie punkty pomiędzy O a 3.
Rysunek 17.8.
Wptyw punktów kontrolnych wzdłuż dziedziny parametru u
Wpływ
2 3
Kluczowy jest fakt, że jedna z takich krzywych wpływu występuje dla każdego punktu w dziedzinie parametrów u/v. Sekwencja węzłów definiuje siłę wpływu punktów w tej dziedzinie. Jeśli wartość węzła się powtarza, punkty w pobliżu tej parametrycznej wartości mają jeszcze większy wpływ. Powtarzanie wartości węzłów jest nazywane multiplikacją węzłów. Wyższa multiplikacja węzłów zmniejsza krzywiznę krzywej lub powierzchni w danym regionie.
Tworzenie powierzchni NURBS
Funkcje NURBS z biblioteki glu stanowią użyteczne narzędzie do tworzenia powierzchni. Nie musisz jawnie wywoływać funkcji obliczających lub ustanawiać odwzorowań bądź siatek. Aby wyrenderować powierzchnię NURBS, musisz najpierw stworzyć obiekt NURBS, do którego będziesz się odwoływał za każdym razem przy wywoływaniu funkcji związanych z powierzchniami NURBS w celu zmodyfikowania wyglądu krzywej lub powierzchni.
Funkcja gluNewNurbsRenderer tworzy renderera dla powierzchni NURBS. Do usuwania utworzonego renderera służy funkcja gluDeleteNurbsRenderer. Użycie tych funkcji demonstruje poniższy fragment kodu:
// Wskaźnik do obiektu NURBS GLUnurbsObj *pNurb - NULL;
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS?!!___________________547
// Przygotowanie obiektu NURBS pNurb = gluNewNurbsRenderer();
// Operacje na powierzchni NURBS.
// Usunięcie obiektu NURBS, jeśli został utworzony if(pNurb)
gluDeleteNurbsRenderer(pNurb);
Właściwości obiektów NURBS
Po stworzeniu renderera NURBS, możesz ustawiać jego różnorodne właściwości:
// Ustawienie tolerancji próbkowania gluNurbsPropertyfpNurb, GLU_SAMPLING_TOLERANCE, 25.Of);
// Wypełnienie w celu otrzymania jednolitej powierzchni (jeśli chcesz // utworzyć siatkę wielokątów, użyj GLU_OUTLINE_POLYGON). gluNurbsProperty(pNurb, GLU_DISPLAY_MODE, (GLfloat)GLU_FILL);
Zwykle będziesz wywoływać te funkcje w procedurze przygotowującej, a nie cały czas w kodzie renderowania. W tym przykładzie parametr GLU_SAMPLING_TOLERANCE określa, jak szczegółowa jest siatka definiująca powierzchnię, zaś GLU_FILL informuje OpenGL, aby wypełnił powierzchnię zamiast generować siatkę.
Definiowanie powierzchni
Definicja powierzchni jest przekazywana funkcji gluNurbsSurface jako tablice punktów kontrolnych i sekwencji węzłów. Jak widać, ta funkcja jest ujęta pomiędzy wywołania gluBeginSurface oraz gluEndSurface:
// Renderuj powierzchnię NURBS // Początek definicji powierzchni gluBeginSurface(pNurb);
// Obliczenia powierzchni
gluNurbsSurface(pNurb, // Wskaźnik do renderera NURBS
8, Knots, // Ilość węzłów i tablica węzłów w kierunku u.
8/ Knots, // Ilość węzłów i tablica węzłów w kierunku v.
4*3, // Odstęp pomiędzy punktami kontrolnymi
// kierunku u.
3. // Odstęp pomiędzy punktami kontrolnymi
// kierunku v. sctrlPoints[0][0][0], // Punkty kontrolne
4. 4, // klasa powierzchni u i v
GL_MAP2_VERTEX_3); // Rodzaj powierzchni
// Koniec definiowania powierzchni gluEndSurface(pNurb);
548
Część III » Tematy zaawansowane i efekty specjalne
Możesz zastosować więcej wywołań funkcji gluNurbsSurface w celu stworzenia większej liczby powierzchni NURBS, lecz będą przy tym obowiązywać właściwości ustawione dla renderera NURBS. Często właśnie to jest pożądane - choć czasem zdarza się, że chcesz, aby dwie powierzchnie (być może połączone) miały różne rodzaje wypełnienia (jedna wypełniona, druga w postaci siatki).
Używając punktów kontrolnych i wartości węzłów pokazanych w następnym fragmencie kodu, tworzymy pjdwierzchnię NURBS przedstawioną na rysunku 17.9. Ten program, noszący nazwę NtTteBS, znajduje się na płytce CD-ROM dołączonej do książki.
» j
Rysunek 17.9.
Działanie programu NURBS
x i y
v =
V =
V =
V =
// Siatka rozciąga się na cztery jednostki od -6 do +6 wzdłuż osi
// Leży na płaszczyźnie Z
II u v (x,y,z)
GLfloat ctrlPoints[4][4][3]= {{{ -6.0f, -6.0f, O.Of}, // u = O,
-6.0f,
-6.0f,
{ -6.0f, -2.0f, O.Of}, //
2.0f, O.Of}, 6.0f, O.Of}:
v = O
v = l
v = 2
v = 3
v = O
v = l
v = 2
v = 3
v = O
v = l
v = 2
v = 3
// u = 1
-2.0f, -6.0f, O.Of},
-2.0f, -2.0f, 8.0f},
-2.0f, 2.0f, 8.0f},
-2.0f, 6.0f, O.Of}},
{{ 2.0f, -6.0f, O.Of }, // u
{ 2.0f, -2.0f, 8.0f }, //
{ 2.0f, 2.0f, 8.0f }, //
{ 2.0f, 6.0f, O.Of }},//
{{ 6.0f, -6.0f, O.Of}, // u
{ 6.0f, -2.0f, O.Of}, //
{ 6.0f, 2.0f, O.Of}, //
{ 6.0f, 6.0f, O.Of}}};//
// Sekwencja węzłów dla powierzchni
GLfloat Knots[8] = (O.Of, O.Of, O.Of, O.Of, l.Of, l.Of, l.Of, l.Of};
549
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS?!!
Wycinanie
Wycinanie wiąże się z usuwaniem sekcji powierzchni NURBS. Zwykle stosuje sieje do dosłownego obcinania ostrych krawędzi tych powierzchni. Równie łatwo można także wycinać otwory w powierzchniach. Wynik działania programu NURBT został przedstawiony na rysunku 17.10.
Rysunek 17.10.
Wynik działania programu NURBT
Widzimy na nim tę samą powierzchnię, co w poprzednim przykładzie (bez narysowanych punktów kontrolnych), z usuniętym trójkątnym obszarem. Ten program także znajduje się na płytce CD-ROM.
Listing 17.3 zawiera kod dodany do przykładowego programu NURBS w celu wycięcia trójkąta z rysowanej powierzchni. Pomiędzy klamrą wywołań gluBeginSufrace/gluEnd-Surface zostało umieszczone wywołanie gluBeginTrim, po którym następuje definicja krzywej wycinającej (poprzez wywołanie gluPwlCurve), zakończona wywołaniem gluEndTrim.
Listing 17.3. Modyfikacje programu NURBS w celu wycięcia fragmentu powierzchni ____________
// Zewnętrzne punkty wycinania, obejmujące całą powierzchnię GLfloat outsidePts [5] [2] - /* przeciwnie do ruchu wskazówek */
{{O.Of, O.Of), {l.Of, O.Of), (l.Of, l.Of}, {O.Of, l.Of}, (O.Of,
// Wewnętrzne punkty wycinania, tworzące trójkątny otwór w powierzchni GLfloat insidePts [4] [2] = /* zgodnie z ruchem wskazówek */
{{0.25f, 0.25f), (0.5f, 0.5f), <0.75f, 0.25f), { 0.25f, 0.25f}};
// Renderuj powierzchnię NURBS. // Początek definicji powierzchni gluBeginSurface(pNurb);
Część III » Tematy zaawansowane i efekty specjalne
// Obliczenia powierzchni
gluNurbsSurface(pNurb, // Wskaźnik do renderera NURBS
8, Knots, // Ilość węzłów i tablica węzłów w kierunku u.
8, Knots, // Ilość węzłów i tablica węzłów w kierunku v.
4 * 3, // Odstęp pomiędzy punktami kontrolnymi
// kierunku u.
3' , // Odstęp pomiędzy punktami kontrolnymi
// kierunku v.
sctrlPointstć^KU [0], // Punkty kontrolne
4' 4/ >. // Klasa powierzchni u i v
GL_MAP2_VERTEX_3);)<' // Rodzaj powierzchni
f
/ 7 Obszar zewnętrzny/ obejmujący całą powierzchnię gluBeginTrim (pNurb) ;
gluPwlCurve (pNurb, 5, ioutsidePts [0] [0] , 2, GLU MAPI TRIM 2) •
gluEndTrim (pNurb) ; ~ ~~ ~
// Wewnętrzny trójkątny obszar gluBeginTrim (pNurb) ;
gluPwlCurve (pNurb, 4, SinsidePts [0] [0] , 2, GLU MAPI TRIM 2)-
gluEndTrim (pNurb) ; ~ - - '
// Koniec definiowania powierzchni gluEndSurface (pNurb) ;
Wewnątrz klamry gluBeginTrim/gluEndTrim możesz określić dowolną ilość krzywych, jeśli tylko łącznie tworzą zamkniętą pętlę. Możesz także użyć funkcji gluNurbsCurve w celu zdefiniowania regionu wycinania lub części regionu wycinania. Te krzywe wycinania muszą być jednak wyrażone jako krzywe o jednostkowych dziedzinach u i v. Oznacza to, że cała dziedzina u/v rozciąga się od wartości 0,0 do l ,0.
Funkcja gluPwlCurve definiuje krzywą składającą się z odcinków łamanej - czyli po prostu kolejne punkty połączone odcinkami. W tym scenariuszu wewnętrzna trójkątna krzywa wycinająca tworzy trójkąt, lecz przy zastosowaniu większej ilości punktów możesz utworzyć przybliżenie dowolnej potrzebnej krzywej.
Wycinanie krzywej powoduje usunięcie obszaru powierzchni będącej na prawo w stosunku do kierunku krzywej. Tak więc w krzywych zgodnych z ruchem wskazówek zegara zostanie wycięte wnętrze krzywej. Zwykle określana jest także zewnętrzna krzywa obcinania, obejmująca całą przestrzeń parametryczną NURBS. Dopiero potem definiuje się mniejsze regiony wycinania, określone za pomocą zamkniętych krzywych o kierunku zgodnym z ruchem wskazówek zegara. Tę zależność ilustruje rysunek 17.1 1.
Rysunek 17.11.
Wycinany jest obszar wewnątrz krzywych ułożonych zgodnie z ruchem wskazówek zegara
Rozdziaf 17. * Krzywe i powierzchnie: co to jest NURBS?!!___________________551
Podsumowanie
Ten rozdział z łatwością mógł się stać najbardziej niezrozumiałym rozdziałem w książce. Jak jednak widzisz, koncepcje kryjące się za krzywymi i powierzchniami nie są aż tak trudne do zrozumienia. Jeśli chcesz dokładniej poznać ich matematyczne podstawy, w dodatku B znajdziesz wykaz odpowiedniej literatury.
Przykłady z tego rozdziału stanowią dobry punkt wyjścia do eksperymentowania z powierzchniami NURBS. Spróbuj dostosować punkty kontrolne i sekwencje węzłów w celu uzyskania zawiniętych lub pofałdowanych powierzchni. Spróbuj także stworzyć powierzchnie drugiego, trzeciego i wyższych stopni. Na dołączonej do książki płytki CD-ROM znajdziesz dodatkowe przykłady.
Uważaj - jedną z najczęstszych pułapek jest próba stworzenia złożonych powierzchni przy użyciu pojedynczych powierzchni NURBS. Przekonasz się, że większą elastyczność i możliwości osiągniesz wtedy, gdy skomponujesz złożoną powierzchnię z kilku mniejszych i łatwiejszych do opanowania powierzchni Beziera lub NURBS.
Podręcznik
glEyalCoord
Przeznaczenie Oblicza punkt jedno- lub dwuwymiarowej krzywej, za pomocą uprzednio
wybranej funkcji.
Plik nagłówkowy <gl.h>
Składnia void glEvalCoordld(GLdoub!e u);
void glEvalCoordlf(GLfloat u);
void glEvalCoord2d(GLdouble u, GLdouble v);
void glEvalCoord2f(GLfloat u, GLfloat v);
void glEvalCoordldv(const GLdouble *u);
void glEvaICoordlfv(const GLfloat *u);
void glEvalCoord2dv(const GLdouble *u);
void glEvalCoord2fv(const GLfloat *u);
Opis Ta funkcja używa poprzednio wybranej (poprzez wywołanie glMap)
funkcji obliczającej do stworzenia wierzchołka, koloru, normalnej oraz współrzędnych tekstury na podstawie wartości parametrów u i v. Rodzaj danych oraz wywoływanych funkcji jest określany przez wywołanie funkcji glMap l orazglMap2.
552
Część III » Tematy zaawansowane i efekty specjalne
Parametry w, v
Te parametry określają wartości parametrów u i v, dla których ma zostać obliczony punkt krzywej.
Zwracana wartość Brak.
Przykład
Patrz także
Poniższylwd z przykładowego programu BEZIER odpowiada wywołaniu funkcji glVertex3f za każdym razem, gdy jest wywoływana funkcja glEvalCoordLfHF^ołożenie wierzchołków jest obliczane z równania krzywej dla Kolejnych wartości z dziedziny krzywej, przekazywanych poprzez zmienną i.
// Ożycie łamanej "łączącej punkty" glBegin(GL_LINE_STRIP);
for(i = 0; i <= 100; i++) {
// Obliczenie krzywej dla danego punktu glEvalCoordlf((GLfloat) i); } glEnd ();
glEvalMesh, glEvalPoint, glMapl, glMap2, glMapGrid
glEyalMesh
Przeznaczenie Plik nagłówkowy Składnia
Opis
Oblicza punkt jedno- lub dwuwymiarowej siatki krzywej.
<gl.h>
void glEvalMeshl(GLenum modę, GLint ii, GLint i2);
void gIEvalMesh2(GLenum modę, GLint ii, GLint i2, GLint j l, GLint J2);
Ta funkcja jest używana razem z glMapGrid w celu utworzenia siatki punktów powierzchni równomiernie rozłożonych w dziedzinach u i v. Funkcja glEvalMesh oblicza położenie wierzchołków i tworzy punkty, odcinki lub wypełnione wielokąty.
Parametry modę
11,12
JJJ2
Zwracana wartość Przykład
GLenum: Określa, czy siatka ma być tworzona z punktów (GL_POINT), odcinków (GL_LINE) czy wypełnionych wielokątów w przypadku powierzchni (GL_FILL).
GLint: Określają pierwszą i ostatnią wartość całkowitą w dziedzinie u. GLint: Określaj ą pierwszą i ostatnią wartość całkowitą w dziedzinie v. Brak.
Poniższy kod z przykładowego programu BEZIER tworzy odwzorowanie siatki od O do 100 ze stoma podziałami. Występujące dalej wywołanie glEvalMesh oblicza punkty siatki i rysuje odcinki pomiędzy wszystkimi punktami krzywej.
// Ożycie funkcji wyższego poziomu do odwzorowania // siatki, a następnie obliczenia całości
553
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS?!!
Patrz także
// Odwzorowanie siatki 100 punktów od O do 100 glMapGridld(100,0.0,100.0);
// Utworzenie siatki przy użyciu linii glEvalMeshl(GL_LINE,O,100);
glBegin, glEvalCoord, glEvalPoint, glMapl, glMap2, glMapGrid
glEyalPoint
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry i J
Zwracana wartość Przykład
Patrz także
Oblicza i generuje pojedynczy punkt siatki.
<gl.h>
void glEvalPointl(GLint i);
void glEvalPoint2(GLint i, GLint j);
Ta funkcja może zostać użyta zamiast funkcji glEvalMesh w celu obliczenia pojedynczego punktu krzywej lub powierzchni. W wyniku obliczeń powstaje pojedynczy prymityw, GL_POINTS. Pierwsza odmiana funkcji (glEvalPointl) jest używana w przypadku krzywych, zaś druga odmiana (glEvalPoint2) w przypadku powierzchni.
GLint: Określają wartość parametrów u i v, dla których ma zostać obliczony punkt krzywej.
Brak.
Poniższy kod tworzy krzywą Beziera złożoną z punktów, a nie z segmentów linii. Jako komentarz pozostawiono niepotrzebne już wywołania, pozostałe z poprzedniego przykładu, z opisu funkcji glEvalCoord:
// Ożycie łamanej "łączącej punkty" glBegin(GL_LINE_STRIP);
for(i = 0; i o 100; i++) {
// Obliczenie krzywej dla danego punktu // glEvalCoordlf((GLfloat) i); glEvalPointl(i); } glEnd();
glEvalCoord, glEvalMesh, glMapl, glMap2, glMapGrid
giGetMap
Przeznaczenie Plik nagłówkowy Składnia
Zwraca parametry funkcji obliczającej. <gl.h>
void glGetMapdv(GLenum target, GLenum query, GLdouble *v); void gIGetMapfv(GLenum target, GLenum ąuery, GLfloat *v);
554
: III » Tematy zaawansowane i efekty specjalne
Opis
Parametry target
void glGetMapiv(GLenum target, GLenum ąuery, GLint *v);
Ta funkcja odczytuje ustawienia odwzorowania ustawione za pomocą funkcji glMap. Opis rodzajów odwzorowań znajdziesz w opisie funkcji glMapl i glMap2.
GLenum: Nazwa odwzorowania; zdefiniowane są następujące odwzorowania:
ąuery
COLOR_4, GL_MAP1 JNDEX, GL_MAP1_NORMAL,
TEXTURE_COORD_1,
TEXTURE_COORD_2,
TEXTURE_COORD_3,
JEXTURE_COORD_4,
VERTEX_3, GL_MAP1_VERTEX_4,
COLOR_4, GL_MAP2_INDEX, GL_MAP2_NORMAL,
TEXTURE_COORD_1,
TEXTURE_COORD_2,
TEXTURE_COORD_3,
TEXTURE COORD 4,
GL_MAP1
GL_MAPl"
GL_MAP1~
GL_MAPl"
GL_MAPf
GL_MAP1
GL_MAP2"
GL_MAP2~
GL_MAP2~
GL_MAP2~
GL_MAP2~
GL_MAP2_VERTEX_3, GL_MAP2_VERTEX_4. Opis odwzorowań znajdziesz w opisie funkcji glMap.
GLenum: Określa parametr odwzorowania, który ma zostać zwrócony w tablicy *v. Może to być jedna z poniższych wartości:
GL_COEFF: Zwraca tablicę zawierającą punkty kontrolne dla odwzorowania. Współrzędne są zwracane wierszami. W odwzorowaniach ID ilość zwracanych punktów kontrolnych odpowiada rzędowi krzywej, zaś w odwzorowaniach 2D jest zwracanych rząd u razy rząd v punktów.
GL_ORDER: Zwraca rząd funkcji obliczającej. W odwzorowaniach ID jest to pojedyncza wartość. W odwzorowaniach 2D są zwracane dwie wartości -tablica, w której pierwszy element odpowiada rzędowi u, zaś drugi element - rzędowi v.
GLJDOMAIN: Zwraca dziedzinę parametrów odwzorowania. Dla funkcji obliczających ID zwracana jest dolna i górna wartość parametru u. W odwzorowaniach 2D zwracana jest dolna i górna wartość parametru u, zaś po nich dolna i górna wartość parametru v.
Wskaźnik do obszaru pamięci, w którym zostaną złożone zwracane wartości. Typ danych tego obszaru powinien odpowiadać użytej funkcji (double, float lub int).
Zwracana wartość Brak.
Przykład
Poniższy kod przedstawia ustawianie, a następnie (być może w innej funkcji) odczytywanie parametrów odwzorowania. W komentarzach opisano zawartość bufora.
555
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS?!!
glMap2f(GL_MAP2_VERTEX_3, // Rodzaj generowanych danych O.Of, // Dolny zakres u 10.Of, // Górny zakres u 3, // Odległość miedzy punktami w danych 3, // Rozmiar w kierunku u (rząd) O.Of, // Dolny zakres v 10.Of, // Górny zakres v 9, // Odległość miedzy punktami w danych 3, // Rozmiar w kierunku v (rząd) sctrlPoints[0][0][0]); // tablica punktów kontrolnych
Patrz także
float parametricRange[4] ; glGetMapfv(GL_MAP2_VERTEX_3,GL_DOMAIN,parametricRange);
/* Teraz parametricRange[0] = 0.0 // Dolne u parametricRange[1] = 10.0 // Górne u parametricRange[2] = 0.0 // Dolne v parametricRange[3] = 10.0 // Górne v
*/
glEvalCoord, glMapl, glMap2
gIMap
Przeznaczenie Plik nagłówkowy Składnia
Opis
Definiuje funkcję obliczającą ID lub 2D. <gl.h>
void glMapld(GLenum target, GLdouble ul, GLdouble u2, GLint ustride, GLint uorder, const GLdouble *points);
void glMaplf(GLenum target, GLfloat ul, GLfloat u2, GLint ustride, GLint uorder, const GLfloat *points);
void glMap2d(GLenum target, GLdouble ul, GLdouble u2, GLint ustride, GLint uorder, GLdouble vi, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points);
void glMap2f(GLenum target, GLfloat ul, GLfloat u2, GLint ustride, GLint uorder, GLfloat vi, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points);
Ta funkcja definiuje funkcję obliczającą dla krzywych (ID) lub powierzchni (2D). Funkcje glMaplx są używane dla krzywych, zaś funkcje glMap2x dla powierzchni. Funkcje obliczające generują wierzchołki i związane z nimi informacje (patrz opis parametru target) dla parametrów krzywych i powierzchni z dziedziny u i v.
556_______________________Część III » Tematy zaawansowane i efekty specjalne
Parametry
target GLenum: Określa rodzaj wartości generowanych przez funkcję
obliczającą. Dostępne wartości to:
GL_MAP1_VERTEX_3 (lub GL_MAP2_VERTEX_3): Punktami kontrolnymi są trójki wartości reprezentujące wartości współrzędnych x, y i z. Podczas obliczania siatki generowane są wewnętrznie polecenia glVertex3.
GLJVIAP1JVERTEX_4 (lub GL_MAP2_VERTEX_4): Punktami kontrolnymi są czwórki wartości reprezentujące wartości współrzędnych x, y, z i w. Podczas obliczania siatki generowane są wewnętrznie polecenia glVertex4.
GL_MAP1_INDEX (lub GL_MAP2_INDEX): Generowane punkty kontrolne to pojedyncze wartości zmiennoprzecinkowe określające wartość indeksu koloru. Podczas obliczania siatki generowane są wewnętrznie polecenia gllndex. Uwaga: bieżący indeks koloru nie jest zmieniany, inaczej niż w przypadku bezpośredniego wywołania polecenia gllndex.
GL_MAP1_COLOR_4 (lub GL_MAP2_COLOR_4): Generowane punkty kontrolne to czwórki wartości zmiennoprzecinkowych określających składowe czerwoną, zieloną, niebieską oraz alfa koloru. Podczas obliczania siatki generowane są wewnętrznie polecenia glColor4. Uwaga: bieżący kolor nie jest zmieniany, inaczej niż w przypadku bezpośredniego wywołania polecenia glColor4.
GL_MAP1_NORMAL (lub GL_MAP2_NORMAL): Generowane punkty kontrolne to trójki wartości zmiennoprzecinkowych określających współrzędne x, y i z wektora normalnego. Podczas obliczania siatki generowane są wewnętrznie polecenia glNormal. Uwaga: bieżąca normalna nie jest zmieniana, inaczej niż w przypadku bezpośredniego wywołania polecenia glNormal.
GL_MAP1_TEXTURE_COORD_1 (lub
GL_MAP2_TEXTURE_COORD_1): Generowane punkty kontrolne to pojedyncze wartości zmiennoprzecinkowe określające współrzędną s tekstury. Podczas obliczania siatki generowane są wewnętrznie polecenia glTexCoordl. Uwaga: bieżące współrzędne tekstury nie są zmieniane, inaczej niż w przypadku bezpośredniego wywołania polecenia glTexCoordl.
GL_MAP1_TEXTURE_COORD_2 (lub
GL_MAP2_TEXTURE_COORD_2): Generowane punkty kontrolne to pary wartości zmiennoprzecinkowych określające współrzędne s i t tekstury. Podczas obliczania siatki generowane są wewnętrznie polecenia glTexCoord2. Uwaga: bieżące współrzędne tekstury nie są zmieniane, inaczej niż w przypadku bezpośredniego wywołania polecenia glTexCoord.
GL_MAP1_TEXTURE_COORD_3 (lub
GL_MAP2_TEXTURE_COORD_3): Generowane punkty kontrolne to trójki wartości zmiennoprzecinkowych określające współrzędne s, t i r
557
Rozdział 17. * Krzywe i powierzchnie: co to jest NURBS?!!
ul, u2
vl,v2
ustride, vstride
uorder, vorder *points
tekstury. Podczas obliczania siatki generowane są wewnętrznie polecenia glTexCoord3. Uwaga: bieżące współrzędne tekstury nie są zmieniane, inaczej niż w przypadku bezpośredniego wywołania polecenia glTexCoord.
GL_MAP1_TEXTURE_COORD_4 (lub
GL_MAP2_TEXTURE_COORD_4): Generowane punkty kontrolne to czwórki wartości zmiennoprzecinkowych określające współrzędne s, t, r i q tekstury. Podczas obliczania siatki generowane są wewnętrznie polecenia glTexCoord4. Uwaga: bieżące współrzędne tekstury nie są zmieniane, inaczej niż w przypadku bezpośredniego wywołania polecenia glTexCoord.
Określają liniowe odwzorowanie parametru u dziedziny.
Określają liniowe odwzorowanie parametru v dziedziny. Stosowane jedynie w odwzorowaniach 2D.
Określają ilość zmiennych float lub double pomiędzy kolejnymi punktami kontrolnymi w strukturze danych wskazywanej przez *points. Współrzędne każdego punktu kontrolnego zajmują określone miejsca w pamięci, zaś te parametry umożliwiają zastosowanie dowolnych odstępów pomiędzy poszczególnymi elementami.
Określają ilość punktów kontrolnych w kierunku u i v.
Wskaźnik do obszaru pamięci zawierającego punkty kontrolne. Może to być dwu- lub trójwymiarowa tablica dowolnych struktur danych.
Zwracana wartość Brak.
Przykład
Poniższy fragment kodu pochodzi z programu BEZ3D. Ustanawia odwzorowanie dla powierzchni Beziera drugiego stopnia.
// Ilość punktów kontrolnych krzywej GLint nNumPoints = 3;
GLfloat ctrlPoints[3][3][3]=
[{ { -4.0f, O.Of, 4.0f),
{ -2.0f, 4.0f, 4.0f},
{ 4.0f, O.Of, 4.0f }},
{{ -4.0f, O.Of, O.Of}, { -2.0f, 4.0f, O.Of}, { 4.0f, O.Of, O.Of } },
{{ -4.0f, O.Of, -4.0f), { -2.0f, 4.0f, -4.0f}, { 4.0f, O.Of, -4.0f }}};
// Przygotowanie krzywej Beziera
// Trzeba to zrobić tylko raz, więc powinniśmy to
// uczynić w funkcji przygotowującej kontekst.
glMap2f(GL_MAP2_VERTEX_3, // Rodzaj generowanych danych O.Of, // Początek zakresu u 10.Of, // Koniec zakresu u
558
Część III » Tematy zaawansowane i efekty specjalne
Patrz także
3, // Odstęp pomiędzy danymi wierzchołków 3, // Rozmiar w kierunku u (klasa) O.Of, // Początek zakresu v 10.Of, // Koniec zakresu v 9, // Odstęp pomiędzy danymi wierzchołków 3, // Rozmiar w kierunku v (klasa) // tablica punktów kontrolnych &ctrlPoints[0][0][0]);
glBegin, glColor, glEnable, glEvalCoord, glEvalMesh, glEvalPoint, glMapGrid, glNormal, glTexCoord, glVertex
glMapGrid
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry
un, vn
ul, u2
vl,v2
Zwracana wartość Przykład
Definiuje siatkę odwzorowania krzywej lub powierzchni.
void glMapGrid l d(GLint un, GLdouble ul, GLdouble u2); void glMapGridlf(GLint un, GLfloat ul, GLfloat u2);
void glMapGrid2d(GLint un, GLdouble ul, GLdouble u2, GLint vn, GLdouble vi, GLdouble v2);
void glMapGrid2f(GLint un, GLfloat ul, GLfloat u2, GLint vn, GLfloat vi, GLfloat v2);
Ta funkcja ustanawia siatkę odwzorowania ID lub 2D. Odwzorowanie jest używane przez funkcje glMap i glEvalMesh do obliczenia siatki współrzędnych krzywej lub powierzchni.
GLint: Określają ilość podziałów siatki w kierunku u i v. Określają dolny i górny zakres wartości dziedziny w kierunku u. Określają dolny i górny zakres wartości dziedziny w kierunku v. Brak.
Poniższy fragment kodu pochodzi z programu BEZ3D. Ustanawia odwzorowanie dla powierzchni Beziera drugiego stopnia, a następnie oblicza punkty tej powierzchni.
// Przygotowanie krzywej Beziera
// Trzeba to zrobić tylko raz, więc powinniśmy to
// uczynić w funkcji przygotowującej kontekst.
glMap2f(GL_MAP2_VERTEX_3, // Rodzaj generowanych danych O.Of, // Początek zakresu u 10.Of, // Koniec zakresu u 3, // Odstęp pomiędzy danymi wierzchołków 3, // Rozmiar w kierunku u (klasa) O.Of, // Początek zakresu v 10.Of, // Koniec zakresu v 9, // Odstęp pomiędzy danymi wierzchołków 3, // Rozmiar w kierunku v (klasa) // tablica punktów kontrolnych
559
Rozdział 17. » Krzywe i powierzchnie; co to jest NURBS711
Patrz także
&ctrlPoints[0][0][0]); // Włączenie funkcji obliczającej glEnable(GL_MAP2_VERTEX_3);
// Użycie funkcji wyższego poziomu do odwzorowania // siatki, a następnie obliczenia całości
// Odwzorowanie siatki 10 punktów od O do 10 glMapGrid2f(10,O.Of,lO.Of,10,0.0f,10.0f);
// Utworzenie siatki przy użyciu linii glEvalMesh2(GL_LINE,O,10,0,10);
glEvalCoord, glEvalMesh, glEvalPoint, glMapl, glMap2
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
nObj
Zwracana wartość Przykład
gluBeginCurye
Rozpoczyna definicję krzywej NURBS.
<glu.h>
void gluBeginCurve(GLUnurbsObj *nObj);
Ta funkcja, wraz z funkcją gluEndCurve, jest używana do określenia początku i końca definicji krzywej NURBS.
GLUnurbsObj: Określa obiekt NURBS. Brak.
Poniższy fragment kodu pochodzi z programu NURBC na płytce CD-ROM. Demonstruje zastosowanie tej funkcji w celu określenia początku definicji krzywej NURBS.
// Renderuj krzywą NURBS
// Początek definicji krzywej
gluBeginCurve(pNurb);
// Obliczanie krzywej gluNurbsCurve(pNurb,
8, Knots,
3.
SctrlPoints[0][0],
4. GL_MAP1_VERTEX_3) ;
// Koniec definiowania krzywej gluEndCurve(pNurb) ;
Patrz także
gluEndCurve
560
Część III » Tematy zaawansowane i efekty specjalne
gluBeginSurface
Przeznaczenie Plik nagłówkowy
Składnia Opis
Rozpoczyna definicję powierzchni NURBS. <glu.h>
void gluBeginSurface(GLUnurbsObj *nObj);
Ta funkcja, wraz z funkcją gluEndSurface, jest używana do określenia początku i końca definicji powierzchni NURBS.
Parametry
nObj
Zwracana wartość Przykład
Patrz także
GLUnurbsObj: Określa obiekt NURBS. Brak.
Poniższy fragment kodu pochodzi z programu NURBS na płytce CD-ROM. Demonstruje zastosowanie tej funkcji w celu określenia początku definicji powierzchni NURBS.
// Renderuj powierzchnię NURBS // Początek definicji powierzchni gluBeginSurface(pNurb);
// Obliczenia powierzchni gluNurbsSurface(pNurb,
8, Knots,
8, Knots,
4*3,
3.
&ctrlPoints[0][0][0],
4. 4, GL_MAP2_VERTEX_3);
// Koniec definiowania powierzchni gluEndSurface(pNurb);
gluEndSurface
gluBeginTrim
Przeznaczenie
Plik nagłówkowy
Składnia
Opis
Rozpoczyna definicję zamkniętej krzywej wycinającej obszar powierzchni NURBS.
<glu.h>
void gluBeginTrim(GLUnurbsObj *nObj);
Ta funkcja, wraz z funkcjągluEndTrim, jest używana do określenia początku i końca definicji krzywej wycinającej. Krzywa wycinająca jest krzywą zamkniętą lub zestawem połączonych krzywych, zdefiniowanych za pomocą funkcji gluNurbsCurye lub gluPwlCurve. Funkcje gluBeginTrim i gluEndTrim muszą występować wewnątrz pary wywołań gluBeginSurface/gluEndSurface. Gdy stosujesz wycinanie, kierunek krzywej określa, która część powierzchni zostanie wycięta. Obszar powierzchni na lewo od krzywej (patrząc w kierunku tej krzywej)
561
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS?!!
pozostaje niewycięty. Tak więc krzywe o kierunku zgodnym z ruchem wskazówek zegara powodują wycięcie wewnętrznego obszaru otoczonego krzywą, podczas gdy krzywe o kierunku przeciwnym do ruchu wskazówek zegara powodują wycięcie obszaru na zewnątrz zamkniętej krzywej.
Parametry
nObj
Zwracana wartość Przykład
GLUnurbsObj: Określa obiekt NURBS. Brak.
Poniższy fragment kodu pochodzi z programu NURBT na płytce CD-ROM i przedstawia dwie krzywe wycinania służące do wycięcia trójkątnego obszaru z powierzchni stworzonej w programie NURBS. Zewnętrzna krzywa wycinania obejmuje całą powierzchnię. Krzywa wewnętrzna ma w rzeczywistości kształt trójkąta i właśnie taki obszar wycina z powierzchni.
// Renderuj powierzchnię NURBS // Początek definicji powierzchni gluBeginSurface(pNurb);
// Obliczenia powierzchni gluNurbsSurface(pNurb,
8, Knots,
8, Knots,
4 * 3,
3.
ictrlPoints[0][0][0],
4. 4, GL_MAP2_VERTEX_3);
// Obszar zewnętrzny, obejmujący całą powierzchnię gluBeginTrim (pNurb);
gluPwlCurve (pNurb, 5, soutsidePts[0][0],
2, GLU_MAP1_TRIM_2); gluEndTrim (pNurb);
// Wewnętrzny trójkątny obszar gluBeginTrim (pNurb);
gluPwlCurve (pNurb, 4, iinsidePts[0][0],
2, GLU_MAP1_TRIM_2); gluEndTrim (pNurb);
Patrz także
// Koniec definiowania powierzchni gluEndSurface(pNurb);
gluEndTrim
gluDeleteNurbsRenderer
Przeznaczenie Plik nagłówkowy
Niszczy obiekt NURBS. <glu.h>
562
Część III » Tematy zaawansowane i efekty specjalne
Składnia void gluDeleteNurbsRenderer(GLUnurbsObj *nObj);
Opis Ta funkcja usuwa wskazany obiekt NURBS i zwalania wszelką związaną
z nim pamięć.
Parametry
nObj GLUnurbsObj: Określa obiekt NURBS przeznaczony do usunięcia.
Zwracana wartość Brak.
Przykład
Patrz także
Poniższy fragment kodu pochodzi z programu NURBS na płytce CD-ROM. Przedstawia usuwanie obiektu NURBS w momencie niszczenia głównego okna aplikacji. Zwróć uwagę, że na początku programu wskaźnik został zainicjowany wartością NULL, więc jeśli nie powiodło się jego utworzenie, nie będzie usuwany.
// Okno jest niszczone, więc przeprowadzamy porządki case WM_DESTROY:
// Odłożenie bieżącego kontekstu renderowania
// i usunięcie go
wglMakeCurrent(hDC,NULL);
wglDeleteContext(hRC);
// Usunięcie obiektu NURBS, jeśli został utworzony if(pNurb)
gluDeleteNurbsRenderer(pNurb);
if(hPalette !» NULL)
DeleteObject(hPalette);
// Poinformowanie aplikacji o zakończeniu działania
PostQuitMessage(0);
break;
gluNewNurbsRenderer
gluEndCurye
Przeznaczenie Plik nagłówkowy Składnia Opis
Parametry
nObj
Zwracana wartość Przykład Patrz także
Kończy definicję krzywej NURBS.
<glu.h>
void gluEndCurve(GLUnurbsObj *nObj);
Ta funkcja, wraz z funkcją gluBeginCurve, jest używana do określenia początku i końca definicji krzywej NURBS.
GLUnurbsObj: Określa obiekt NURBS.
Brak.
Spójrz na przykład w opisie funkcji gluBeginCurve.
gluBeginCurve
563
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS7H
gluEndSurface
Przeznaczenie Plik nagłówkowy
Składnia Opis
Parametry
nObj
Zwracana wartość Przykład Patrz także
Kończy definicję powierzchni NURBS. <glu.h>
void gluEndSurface(GLUnurbsObj *nObj);
Ta funkcja, wraz z funkcją gluBeginSurface, jest używana do określenia początku i końca definicji powierzchni NURBS.
GLUnurbsObj: Określa obiekt NURBS.
Brak.
Spójrz na przykład w opisie funkcji gluBeginSurface.
gluBeginSurface
gluEndfrim
Przeznaczenie Płik nagłówkowy Składnia Opis
Parametry
nObj
Zwracana wartość Przykład Patrz także
Kończy definicję krzywej wycinającej NURBS.
<glu.h>
void gluEndTrim(GLUnurbsObj *nObj);
Ta funkcja, wraz z funkcją gluBeginTrim, jest używana do określenia początku i końca definicji krzywej wycinającej NURBS. Więcej informacji na temat krzywych wycinających znajdziesz w opisie funkcji gluBeginTrim.
GLUnurbsObj: Określa obiekt NURBS.
Brak.
Spójrz na przykład w opisie funkcji gluBeginTrim.
gluBeginTrim
gluGetNurbsProperty
Przeznaczenie Plik nagłówkowy Składnia
Opis
Zwraca właściwość NURBS. <glu.h>
void gIuGetNurbsProperty(GLUnurbsObj *nObj, GLenum property, GLloat *value);
Ta funkcja służy do odczytania właściwości NURBS określonej dla danego obiektu NURBS. Informacje dotyczące poszczególnych właściwości znajdziesz w opisie funkcji gluNurbsProperty.
564
Część III t Tematy zaawansowane i efekty specjalne
Parametry
nObj property
Zwracana wartość Przykład
GLUnurbsObj: Określa obiekt NURBS.
GLenum: Odczytywana właściwość NURBS. Dostępne właściwości to:
GLU_SAMPLING_TOLERANCE, GLU_DISPLAY_MODE, GLU_CULLING, GLU_AUTO_LOAD_MATRIX, GLU_PARAMETRIC_TOLLERANCE, GLU_SAMPLING_METHOD, GLU_U_STEP oraz GLU_V_STEP. Szczegóły na temat tych parametrów znajdziesz w opisie funkcji gluNurbsProperty.
Brak.
Poniższy przykład przedstawia sposób ustawienia właściwości NURBS GLU_SAMPLING_TOLERANCE na 25. W dalszej części programu (być może już w innej funkcji) wywoływana jest funkcja gluGetNurbsProperty w celu odczytania tolerancji próbkowania.
gluNurbsProperty(pNurb, GLU_SAMPLING_TOLERANCE, 25.Of};
GLfloat fTolerance;
Patrz także
gluGetNurbsProperty(pNurb, GLU_SAMPLING_TOLERANCE, SfTolerance);
gluNewNurbsRenderer, gluNurbsProperty
gluLoadSamplingMatrices
Przeznaczenie Plik nagłówkowy Składnia
Opis
Ładuje macierze próbkowania i obcinania NURBS. <glu.h>
void gluLoadSamplingMatrices(GLUnurbsObj *nObj, const GLfloat modelMatrix[16], const GLfloat projMatrix[16], const GLint viewport[4]);
Ta funkcja jest używana do ponownego przeliczenia macierzy próbkowania i obcinania dla powierzchni NURBS. Macierz próbkowania jest używana przy wyznaczaniu stopnia podziału powierzchni na wielokąty, tak aby była zachowana tolerancja próbkowania. Macierz obcinania jest używana do wyznaczenia, czy przed renderowaniem powinny zostać wycięte niewidoczne powierzchnie. Zwykle nie trzeba wywoływać tej funkcji, chyba że zostanie wyłączona właściwość GLU_AUTO_LOAD_MATRIX. Najczęściej wyłącza się ją przy pracy w trybach selekcji i informacji zwrotnej.
Parametry nObj
GLUnurbsObj: Określa obiekt NURBS.
565
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS?!!
modelMatrix
projMatrix viewport
Zwracana wartość Przykład
GLfloat[16]: Określa macierz widoku modelu.
GLfloat[16]: Określa macierz rzutowania. GLint[4]: Określa widok.
Brak.
Poniższy kod może zostać użyty do ręcznego przygotowania i wykorzystnia macierzy próbkowania i obcinania.
GLfloat fModelView[16], fProjection[16], fViewport[4];
pNurb = glNewNurbsRenderer(.
// Pobranie informacji o macierzy i widoku glGetFloatv(GL_MODELVIEW_MATRIX, fModelView); glGetFloatv(GL_PROJECTION_MATRIX, fProjection); glGet!ntegerv(GL_VIEWPORT, fYiewport);
fProjection,
Patrz także
// Ręczne załadowanie macierzy gluLoadSamplingMatrices(pNurb, fModelView,
fViewport);
gluNewNurbsRenderer, gluNurbsProperty
gluNewNurbsRenderer
Przeznaczenie
Tworzy nowy obiekt NURBS.
Plik nagłówkowy <glu.h>
Składnia Opis
Zwracana wartość
Przykład
GLUnurbsObj * gluNewNurbsRenderer(void);
Ta funkcja tworzy obiekt renderowania NURBS. Ten obiekt jest wykorzystywany do sterowania zachowaniem i charakterystykami krzywych i powierzchni NURBS. Wszystkie funkcje służące do modyfikacji właściwości NURBS wymagaj ą podania wskaźnika do tego obiektu. Po zakończeniu renderowania krzywych lub powierzchni musisz usunąć obiekt NURBS wywołując funkcję gluDeleteNurbsRenderer.
Wskaźnik do nowego obiektu NURBS. Ten obiekt będzie wykorzystywany podczas wywoływania funkcji sterujących i renderujących.
Poniższy kod demonstruje tworzenie obiektu NURBS:
// Przygotowanie obiektu NURBS
// Zaczynamy od stworzenia go pNurb = gluNewNurbsRenderer();
566
Część III » Tematy zaawansowane l efekty specjalne
// Ustawienie właściwości NURBS
gluNurbsProperty(pNurb, GLU_SAMPLING_TOLERANCE, 25.Of); gluNurbsProperty(pNurb, GLU_DISPLAY_MODE, (GLfloat)GLU_FILL);
.. inne właściwości
Patrz także
gluDeleteNurbsRenderer
gluNurbsCallback
Przeznaczenie Definiuje funkcję zwrotną dla NURBS.
Plik nagłówkowy <glu.h>
Składnia void gluNurbsCallback(GLUnurbsObj *nObj, GLenum which, void
Opis
Parametry nObj which
fn
Zwracana wartość Przykład
Ta funkcja zgłasza funkcję zwrotną dla NURBS. Jedynym obsługiwanym zdarzeniem jest GL_ERROR. Gdy wystąpi błąd, wywoływana jest zgłoszona funkcja z argumentem typu GLenum, zawierającym kod jednego z 37 błędów, zdefiniowanych jako stałe od GLU_NURBS_ERROR1 do GLU_NURBS_ERROR37. Do odczytania łańcucha opisu błędu służy funkcja gluErrorString. Kody błędów wraz z opisem zebrano w tabeli 17.1.
GLUnurbsObj: Określa obiekt NURBS.
GLenum: Określa zdarzenie mające powodować wywołanie funkcji zwrotnej. Dozwolona jest jedynie wartość GLU_ERROR.
void *(): Adres zgłaszanej funkcji zwrotnej. Brak.
Poniższy kod stanowi przykład procedury obsługi błędów NURBS. Pokazany jest także kod instalujący tę procedurę obsługi. Można go znaleźć w przykładowym programie NURBS.
// Funkcja zwrotna błędów NURBS
void CALLBACK NurbsErrorHandler (GLenum nErrorCode)
{
char cMessage [64] ;
// Wydzielenie opisu błędu
strcpy (cMessage, "Wystąpił błąd NURBS: ");
strcat (cMessage, gluErrorString (nErrorCode) ) ;
// Wyświetlenie całego komunikatu Me s s ageBox ( NULL , cMe s s agę , NULL , MB_OK | MB ICONEKCLAMATION) ;
567
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS?!!
// Przygotowanie obiektu NURBS pNurb = gluNewNurbsRenderer();
// Instalowanie procedury obsługi błędów NURBS gluNurbsCallback(pNurb, GLU_ERROR,
NurbsErrorHandler);
gluNurbsProperty(pNurb, GLU_SAMPLING_TOLERANCE,
25. Of) ; . . . inne właściwości
gluErrorString
Patrz także
Tabela 17.1.
Kody błędów NURBS
Kod błędu
|
Definicja
|
GLU_NURBS_ERROR1
|
Brak obsługi kolejności splajnów.
|
GLU_NURBS_ERROR2
|
Zbyt mało węzłów.
|
GLU_NURBS_ERROR3
|
Poprawny zakres węzłów jest pusty.
|
GLU_NURBS_ERROR4
|
W sekwencji węzłów wystąpił węzeł mniejszy niż poprzedni.
|
GLU_NURBS_ERROR5
|
Multiplikacja węzłów większa niż rząd splajna.
|
GLU_NURBS_ERROR6
|
endcurve() musi występować po bgncurve().
|
GLU_NURBS_ERROR7
|
bgncurve() musi poprzedzać endcurve().
|
GLU_NURBS_ERROR8
|
Brak lub nadmiar danych geometrii.
|
GLU_NURBS_ERROR9
|
Nie można narysować krzywych pwl.
|
GLU_NURBS_ERROR 1 0
|
Brak lub nadmiar danych dziedziny.
|
GLU_NURBS_ERROR1 1
|
Brak lub nadmiar danych dziedziny.
|
GLU_NURBS_ERROR 1 2
|
endtrimO musi poprzedzać endsurface().
|
GLU_NURBS_ERROR1 3
|
bgnsurface() musi poprzedzać endsurface().
|
GLU_NURBS_ERROR14
|
Jako krzywą wycinania podano krzywą niewłaściwego typu.
|
GLU_NURBS_ERROR1 5
|
bgnsurface() musi poprzedzać bgntrim().
|
GLU_NURBS_ERROR16
|
endtrim() musi występować po bgntrim().
|
GLU_NURBS_ERROR1 7
|
bgntrim() musi poprzedzać endtrim().
|
GLU_NURBS_ERROR 1 8
|
Brak lub błędna definicja krzywej wycinania.
|
GLU_NURBS_ERROR19
|
bgntrim() musi poprzedzać pwlcurve().
|
GLU_NURBS_ERROR20
|
Dwukrotne odwołanie do krzywej pwl.
|
GLU_NURBS_ERROR2 1
|
Wymieszane krzywe pwl i nurbs.
|
568
Część III * Tematy zaawansowane i efekty specjalne
Tabela 17.1.
Kody błędów NURBS - ciąg dalszy
Kod błędu
Definicja
GLU_NURBS_ERROR22 Niewłaściwe użycie typu danych wycinania.
GLU_NURBS_ERROR23 Dwukrotne odwołanie do krzywej nurbs.
GLU_NURBS_ERROR24 Wymieszane krzywe nurbs i pwl.
GLU_NURBS_ERROR25 Dwukrotne odwołanie do powierzchni nurbs.
GLU_NURBS_ERROR26 Nieistniejąca właściwość.
GLU_NURBS_ERROR27 endsurface() musi występować po bgnsurface().
GLU_NURBS_ERROR28 Przecinające się lub ułożone w niezgodnych kierunkach krzywe wycinania.
GLU_NURBS_ERROR29 Przecinające się krzywe wycinania.
GLU_NURBS_ERROR30 NIEUŻYWANE.
GLU_NURBS_ERROR31 Niepołączone krzywe wycinania.
GLU_NURBS_ERROR32 Nieznany błąd węzła.
GLU_NURBS_ERROR33 Natrafiono na ujemną liczbę wierzchołków.
GLU_NURBS_ERROR34 Ujemny odstęp pomiędzy elementami tablicy z punktami kontrolnymi.
GLU_NURBS_ERROR35 Deskryptor nieznanego typu.
GLU_NURBS_ERROR36 Puste odwołanie do punktu kontrolnego.
GLU_NURBS_ERROR37 Zduplikowany punkt krzywej pwl.
gluNurbsCurye
Przeznaczenie Plik nagłówkowy Składnia
Opis
Parametry nObj
nknots knots
Definiuje kształt krzywej NURBS. <glu.h>
void gluNurbsCurve(GLUnurbsObj *nObj, GLint nknots, GLfloat *knots, GLint stride, GLfloat *ctlarray, GLint order, GLenum type);
Ta funkcja służy do definiowania kształtu krzywej NURBS. Definicja krzywej musi występować pomiędzy wywołaniami gluBeginCurve i gluEndCurve.
GLUnurbsObj*: Wskaźnik do obiektu NURBS (utworzonego w wyniku wywołania funkcji gluNewNurbsRenderer).
GLint: Ilość węzłów w tablicy *knots. Odpowiada ilości punktów kontrolnych plus rząd krzywej.
GLfloat*: Tablica wartości węzłów, ułożonych w kolejności niemalejącej.
569
Rozdział 17. * Krzywe i powierzchnie: co to jest NURBS?!!
strlde
ctlArray
order
type
Zwracana wartość Przykład Patrz także
GLint: Odstęp, wyrażony jako ilość wartości zmiennoprzecinkowych pojedynczej precyzji (float), pomiędzy kolejnymi wartościami punktów kontrolnych w tablicy.
GLfloat*: Wskaźnik do tablicy struktur danych zawierających punkty kontrolne dla powierzchni NURBS.
GLint: Rząd krzywej NURBS. Rząd jest o jeden większy od stopnia krzywej.
GLenum: Typ krzywej. Może nim być jeden z poniższych typów:
GL_MAP1_VERTEX_3, GL_MAP1_VERTEX_4, GL_MAP1_INDEX,
GL_MAP1_COLOR_4, GL_MAP1_NORMAL,
GL_MAP 1_TEXTURE_COORD_1,
GL_MAP1_TEXTURE_COORD_2,
GL_MAP1_TEXTURE_COORD_3,
GL_MAP 1_TEXTURE_COORD_4.
Brak.
Spójrz na przykład przy opisie funkcji gluBeginCurve.
gluBeginCurve, gluEndCurve, gluNurbsSurface
gluNurbsProperty
Przeznaczenie Plik nagłówkowy Składnia
Opis
Ustawia właściwość NURBS. <glu.h>
void gluNurbsProperty(GLUnurbsObj *nObj, GLenum property, GLfloat value);
Ta funkcja służy do ustawiania właściwości obiektu NURBS. Dostępne są następujące właściwości:
GLU_SAMPLING_TOLERANCE: Określa maksymalną długość w pikselach, która ma zostać użyta podczas stosowania metody próbkowania GLU_PATH_LENGTH. Domyślna wartość to 50,0.
GLU_DISPLAYJMODE: Określa sposób renderowania powierzchni NURBS. Parametrem może być GLU_FILL, powodujący użycie cieniowanych wielokątów, GLU_OUTLINE_POLYGON, powodujący rysowanie jedynie konturów wielokątów (już po podziale powierzchni na wielokąty składowe) oraz GLU_OUTLINE_PATCH, umożliwiający rysowanie wyłącznie konturów zdefiniowanych przez użytkownika ścieżek i krzywych wycinania. Domyślną wartością jest GLU_FILL.
GLU_CULLING: Wartość parametru jest interpretowana jako wartość logiczna, określająca, czy krzywa NURBS ma być odrzucona, jeśli jej punkty kontrolne leżą poza widokiem.
GLU_PARAMETRIC_TOLERANCE: Ustawia maksymalny odstęp pikseli używany, gdy metodą próbkowania jest
570______________________Część III » Tematy zaawansowane i efekty specjalne
GLU_PARAMETRIC_ERROR. Domyślną wartością jest 0,5. Ta właściwość pojawia się dopiero w GLU w wersji 1.1.
GLU_SAMPLING_METHOD: Określa sposób podziału powierzchni NURBS na wielokąty. Ta właściwość pojawia się dopiero w GLU w wersji 1.1. Dostępne są poniższe właściwości:
GLU_PATH_LENGTH określa, że podczas renderowania powierzchni, długości boków (w pikselach) wielokątów podziału powierzchni nie będą większe niż wartość parametru GLU_SAMPLING_TOLERANCE.
GLU_PARAMETRIC_ERROR określa, że powierzchnia będzie renderowana przy użyciu parametru GLU_PARAMETRIC_TOLERANCE, wyznaczaj ącego maksymalny dystans, w pikselach, pomiędzy wielokątami podziału a przybliżanymi przez nie powierzchniami.
GLU_DOMAIN_DISTANCE określa, we współrzędnych parametrycznych, jak wiele punktów próbkowania na jednostkę długości występuje w kierunkach u i v. Domyślną wartością jest GLU_PATH_LENGTH.
GLU_U_STEP: Ustala ilość punktów próbkowania na jednostkę długości w kierunku u, we współrzędnych parametrycznych. Ta wartość jest używana, gdy metodą próbkowania jest GLU_DOMAIN_DISTANCE. Domyślna wartość tego parametru to 100. Ta właściwość pojawia się dopiero w GLU w wersji 1.1.
GLU_V_STEP: Ustala ilość punktów próbkowania na jednostkę długości w kierunku v, we współrzędnych parametrycznych. Ta wartość jest używana, gdy metodą próbkowania jest GLU_DOMAIN_DISTANCE. Domyślna wartość tego parametru 100. Ta właściwość pojawia się dopiero w GLU w wersji 1.1.
GLU_AUTO_LOAD_MATRIX: Wartość parametru jest interpretowana jako wartość logiczna. Gdy zostanie ustawiona na GLJTRUE, powoduje, że kod NURBS ładuje z serwera OpenGL macierz widoku modelu, macierz rzutowania oraz widok w celu obliczenia macierzy próbkowania i obcinania dla każdej krzywej NURBS. Macierze próbkowania i obcinania są potrzebne do wyznaczenia podziału powierzchni NURBS na segmenty linii oraz wielokąty, jak również do obcięcia tych fragmentów powierzchni NURBS, które leżą poza widokiem. Jeśli parametr zostanie ustawiony na wartość GL_FALSE, użytkownik powinien sam dostarczyć macierze i widok dla renderera NURBS w celu użycia ich do obliczenia macierzy próbkowania i obcinania. Można to osiągnąć używając funkcji gluLoadSamplingMatrices. Domyślną wartością parametru jest GL_TRUE. Sama zmiana tego parametru nie wpływa na próbkowanie i obcinanie powierzchni, chyba że zostanie wywołana funkcja gluLoadSamplingMatrices.
571
Rozdział 17. » Krzywe i powierzchnie: co to jest NURBS?!!
Parametry nObj
property
value
Zwracana wartość Przykład
Patrz także
GLUnurbsObj*: Wskaźnik do obiektu NURBS (utworzonego w wyniku wywołania funkcji gluNewNurbsRenderer), którego właściwości mają zostać zmodyfikowane.
GLenum: Modyfikowana lub ustawiana właściwość NURBS. Może nią być jedna z poniższych właściwości:
GLU_SAMPLING_TOLERANCE, GLU_DISPLAY_MODE, GLU_CULLING, GLU_AUTO_LOAD_MATRIX, GLU_PARAMETRIC_TOLERANCE, GLU_SAMPLING_METHOD, GLU_U_STEP oraz GLU_V_STEP.
GLfloat: Wartość przypisywana ustawianej właściwości. Brak.
Poniższy kod z programu NURBS przygotowuje tryb wyświetlania powierzchni jako siatki krawędzi:
gluNurbsProperty (pNurb, GLU_DISPLAY_MODE, GLU_OUTLINE_POLYGON) ;
gluGetNurbsProperry, gluGetString, gluLoadSamplingMatrices, gluNewNurbsRenderer
gluNurbsSurface
Przeznaczenie Plik nagłówkowy Składnia
Opis
Definiuje kształt powierzchni NURBS. <glu.h>
void gluNurbsSurface(GLUnurbsObj *nObj, GLint uknotCount, GLfloat *uknot, GLint vknotCount, GLfloat *vknot, GLint ustride, GLint vstride, GLfloat *ctlarray, GLint uorder, GLint vorder, GLenum type);
Ta funkcja służy do definiowania kształtu powierzchni NURBS. Definicja powierzchni musi występować pomiędzy wywołaniami gluBeginSurface i gluEndSurface. Kształt powierzchni jest obliczany przed jakimkolwiek wycinaniem. Powierzchnia NURBS może zostać wycięta przez użycie funkcji gluBeginTrim/gluEndTrim w połączeniu z funkcjami gluNurbsCurve i/lub gluPwlCurve.
Parametry nObj
uknotCount u knot
GLUnurbsObj*: Wskaźnik do obiektu NURBS (utworzonego w wyniku wywołania funkcji gluNewNurbsRenderer).
GLint: Ilość węzłów w parametrycznym kierunku u.
GLfloat*: Tablica wartości węzłów, reprezentujących węzły w kierunku u. Wartości w tablicy muszą być ułożone w kierunku niemalejącym. Rozmiar tablicy jest określony parametrem uknotCount.
572
Część III * Tematy zaawansowane i efekty specjalne
vknotCount vknot
uStride vStride ctlArray
uorder vorder type
Zwracana wartość Przykład Patrz także
GLint: Ilość węzłów w parametrycznym kierunku v.
GLfloat*: Tablica wartości węzłów, reprezentujących węzły w kierunku v. Wartości w tablicy muszą być ułożone w kierunku niemalejącym. Rozmiar tablicy jest określony parametrem vknotCount.
GLint: Odstęp, wyrażony jako ilość wartości zmiennoprzecinkowych pojedynczej precyzji (float), pomiędzy kolejnymi wartościami punktów kontrolnych kierunku u w tablicy.
GLint: Odstęp, wyrażony jako ilość wartości zmiennoprzecinkowych pojedynczej precyzji (float), pomiędzy kolejnymi wartościami punktów kontrolnych kierunku v w tablicy.
GLfloat*: Wskaźnik do tablicy struktur danych zawierających punkty kontrolne dla powierzchni NURBS. Odstępy pomiędzy poszczególnymi punktami kontrolnymi dla kierunków u i v są przekazywane jako parametry uStride i vStride.
GLint: Rząd krzywej NURBS w kierunku u. Rząd jest o jeden większy od stopnia, więc powierzchnia kubiczna w kierunku u posiada u stopnia czwartego.
GLint: Rząd krzywej NURBS w kierunku v. Rząd jest o jeden większy od stopnia, więc powierzchnia kubiczna w kierunku v posiada v stopnia czwartego.
GLenum: Typ powierzchni. Może nim być jeden z poniższych typów:
GL_MAP2_VERTEX_3, GL_MAP2_VERTEX_4, GL_MAP2_INDEX, GL_MAP2_COLOR_4, GL_MAP2_NORMAL, GL_MAP2_TEXTURE_COORD_1, GL_MAP2_TEXTURE_COORD_2, GL_MAP2_TEXTURE_COORD_3, GL_MAP2_TEXTURE_COORD_4.
Brak.
Spójrz na przykład przy opisie funkcji gluBeginSurface.
gluBeginSurface, gluBeginTrim, gluNewNurbsRenderer, gluNurbsCurve, gluPwlCurve
gluPwICurye
Przeznaczenie Plik nagłówkowy Składnia
Opis
Określa złożoną z kawałków krzywą wycinania NURBS. <glu.h>
void gluPwICurye (GLUnurbsObj *nObj, GLint count, GLfloat *array, GLint stride, GLenum type);
Ta funkcja definiuje złożoną z kawałków krzywą wycinania dla powierzchni NURBS. Punkty w tablicy są określone w parametrycznych
573
Rozdział 17. * Krzywe i powierzchnie: co to jest NURBS?!!
współrzędnych w przestrzeni u i v. Ta przestrzeń stanowi jednostkowy kwadrat, o boku równym dokładnie jednej jednostce. Krzywe wycinania tworzone w kierunku zgodnym z ruchem wskazówek zegara powodują wycięcie otaczanego obszaru; krzywe tworzone w kierunku przeciwnym do ruchu wskazówek zegara powodują wycięcie regionu zewnętrznego. Zwykle region obcinania definiuje się przez zdefiniowanie krzywej obcinania obejmującej całą powierzchnię i wycinającej wszystko poza nią. Następnie tworzy się mniejsze krzywe, ułożone zgodnie z ruchem wskazówek zegara, wycinające otwory w powierzchni. Krzywe wycinania mogą być złożone z kawałków krzywych. To oznacza, że funkcję gluPwlCurve lub gluNurbsCurve można wywoływać więcej razy, w celu zdefiniowania regionu wycinania o dowolnym kształcie, pod warunkiem, że krzywa wycinania zostanie domknięta i obejmie zamknięty region w przestrzeni u/v.
Parametry nObj
count array stride
type
GLUnurbsObj*: Wskaźnik do obiektu NURBS (utworzonego w wyniku wywołania funkcji gluNewNurbsRenderer).
GLint: Określa ilość punktów krzywej przekazywanych w tablicy *array. GLfloat*: Tablica punktów granicznych krzywej.
GLint: Odstęp (w wartościach typu float) pomiędzy punktami krzywej w tablicy.
GLenum: Określa typ krzywej. Może nim być GLU_MAP1_TRIM_2, używany, gdy krzywa wycinania jest wyrażona we współrzędnych u i v; lub GLU_MAP1_TRIM_3, używany, gdy do określenia krzywej wycinania stosuje się dodatkową współrzędną w (skalowania).
Zwracana wartość Brak.
Przykład Poniższy przykład z programu NURBT przedstawia powierzchnię
NURBS wycinaną przez obszar opisany krzywą w kształcie trójkąta. Cała powierzchnia ujęta jest w dużą krzywą wycinania, obcinającą cały obszar poza powierzchnią. Drugi, mniejszy obszar wycinania to właśnie trójkątny otwór wycięty w powierzchni.
// Zewnętrzne punkty wycinania, obejmujące całą powierzchnię GLfloat outsidePts[5][2] = /* przeciwnie do ruchu wskazówek */ {{O.Of, O.Of), {l.Of, O.Of}, {l.Of, l.Of}, {O.Of, l.Of}, (O.Of, O.Of}};
// Wewnętrzne punkty wycinania, tworzące trójkątny otwór w powierzchni GLfloat insidePts[4][2] = /* zgodnie z ruchem wskazówek */ {(0.25f, 0.25f}, {0.5f, 0.5f}, {0.75f, 0.25f}, { 0.25f, 0.25f}};
// Renderuj powierzchnię NURBS // Początek definicji powierzchni gluBeginSurface(pNurb);
574______________________Część III » Tematy zaawansowane i efekty specjalne
// Obliczenia powierzchni
gluNurbsSurface(pNurb, // Wskaźnik do renderera NURBS
8, Knots, // Ilość węzłów i tablica węzłów w kierunku u.
8, Knots, // Ilość węzłów i tablica węzłów w kierunku v.
4*3, // Odstęp pomiędzy punktami kontrolnymi
// kierunku u.
3. // Odstęp pomiędzy punktami kontrolnymi
// kierunku v. &ctrlPoints[0][0][0], // Punkty kontrolne
4. 4, // klasa powierzchni u i v
GL_MAP2_VERTEX_3); // Rodzaj powierzchni
// Obszar zewnętrzny, obejmujący całą powierzchnię gluBeginTrim (pNurb);
gluPwlCurve (pNurb, 5, SoutsidePts[0] [0] , 2, GLU_MAP1_TRIM_2); gluEndTrim (pNurb);
// Wewnętrzny trójkątny obszar gluBeginTrim (pNurb);
gluPwlCurve (pNurb, 4, SinsidePts[0][0], 2, GLU_MAP1_TRIM_2); gluEndTrim (pNurb);
// Koniec definiowania powierzchni gluEndSurface(pNurb);
Patrz także gluBeginTrim, gluEndTrim, gluNurbsCurve
Rozdział 18.
Podział wielokątów
W tym rozdziale:
Dowiesz się, jak...______________________Używane funkcje_______
4 Użyć biblioteki GLU do rysowania skomplikowanych * gluBegin/gluEnd
wielokątów
* Użyć biblioteki GLU do rysowania skomplikowanych * gluNextContour
powierzchni
Biblioteka OpenGL Utility (glu32.1ib) zawiera stabilny i wydajny interfejs podziału wielokątów, dzięki któremu można łatwo renderować złożone wielokąty i powierzchnie. Podział wielokątów polega na tworzeniu dowolnego wielokąta z mniejszych, najczęściej trójkątnych fragmentów, podobnie jak przy tworzeniu mozaiki.
Z trójkątów składany jest dowolny pożądany kształt, zaś procedura podziału wielokątów jest przygotowana także do tworzenia rzadko spotykanych kształtów, na przykład zawierających otwory.
Złożone wielokąty
Co sprawia, że wielokąt staje się złożony? Cóż, w OpenGL złożony wielokąt to taki, który albo nie jest wypukły (posiada wcięcia), albo posiada otwory. Na rysunku 18.1 przedstawiono kilka prostych oraz kilka złożonych wielokątów, które od czasu do czasu zdarzy ci się renderować.
Za pomocą prymitywu GL_POLYGON można tworzyć jedynie proste, wypukłe wielokąty. Wielokąt jest wypukły wtedy, gdy żadna z poprowadzonych przez niego linii nie przecina jego krawędzi więcej niż dwukrotnie. Tak więc, jeśli jakakolwiek linia przeprowadzona przez wielokąt przechodzi przez pustą przestrzeń poza wielokątem, taki wielokąt jest wklęsły lub złożony.
S76______________________Część III » Tematy zaawansowane i efekty specjalne
Rysunek 18.1. Wielokąty proste Wielokąty złożone
Wielokąty proste
i złożone
Wklęsłe wielokąty to wielokąty niewypukłe, które nie posiadają otworów w swoim wnętrzu. Wielokątem wklęsłym jest wielokąt w prawym górnym rogu rysunku 18.1, nie jest wklęsły natomiast wielokąt z prawego dolnego rogu, gdyż zawiera w sobie otwór.
Wielokąty złożone posiadaj ą otwory lub zakrzywienia. Prawy dolny wielokąt z rysunku 18.1 jest właśnie wielokątem złożonym.
Rysowanie wielokątów wklęsłych
Rysowanie wklęsłych wielokątów za pomocą biblioteki glu nie jest trudne. Pierwsza rzecz, jaką musimy zrobić, to stworzenie obiektu triangulatora:
GLUtriangulatorobj *tess; tess = gluNewTess();
Struktura GLUtriangulatorobj zawiera informacje o stanie, używane przez triangulatora przy renderowaniu wielokąta.
Następnie możemy wywołać sekwencję instrukcji gluBeginPolygon, gluTessVertex oraz gluEndPolygon w celu wyrenderowania wielokąta:
GLdouble vertices[100] [3] ;
gluBeginPolygon(tess) ;
gluTessVertes(tess, vertices[0], NULL); gluTessVertes(tess, vertices[l], NULL);
gluTessVertes(tess, vertices[99], NULL); gluEndPolygon(tess);
Po wywołaniu funkcji gluEndPolygon, triangulator przystępuje do pracy i tworzy serię trójkątów, pasków oraz wachlarzy trójkątów. Ponieważ ten proces może trwać dość długo, dobrym pomysłem jest umieszczenie podzielonych wielokątów na liście wyświetlania w celu poprawy wydajności wyświetlania (patrz rozdział 10).
577
Rozdział 18. » Podział wielokątów
Rysowanie wielokątów złożonych
Rysowanie wielokątów złożonych jest nieco bardziej skomplikowane niż rysowanie wielokątów wklęsłych, jednak nie jest tak trudne, jak mogłoby się wydawać. Wielokąty złożone mogą zawierać otwory i wykrzywienia powierzchni, w związku z czym mamy do dyspozycji funkcję gluNextContour, służącą do określania rodzaju definiowanej ścieżki wierzchołków.
Typy ścieżek wybierane funkcją gluNextContour zostały zebrane w tabeli 18.1.
Tabela 18.1.
Typy ścieżek funkcji glhNextContour
Opis
Typ ścieżki
GLU_EXTERIOR GLUJNTERIOR GLU_UNKNOWN GLU_CCW
GLU CW
Ścieżka wyznacza zewnętrzne krawędzie wielokąta. Ścieżka wyznacza wewnętrzne krawędzie wielokąta. Nie wiesz, co wyznacza ścieżka; biblioteka spróbuje sama to wykryć.
To powinno być używane tylko raz, w celu wskazania, że ścieżki przeciwne do ruchu wskazówek zegara są ścieżkami zewnętrznymi, zaś ścieżki zgodne z ruchem wskazówek zegara są ścieżkami wewnętrznymi.
To powinno być używane tylko raz, w celu wskazania, że ścieżki przeciwne do ruchu wskazówek zegara są ścieżkami wewnętrznymi, zaś ścieżki zgodne z ruchem wskazówek zegara są ścieżkami zewnętrznymi.
W przykładzie pokazanym na rysunku 18.2 definiujemy zewnętrzną ścieżkę dla konturu litery, zaś ścieżkę wewnętrzną dla trójkątnego otworu w jej środku (rysunek 18.3).
Rysunek 18.2.
Litera A jako \vielokat złożony
578
Część III » Tematy zaawansowane i efekty specjalne
Rysunek 18.3.
Ścieżki wierzchołków dla litery A
Ścieżka zewnętrzna
~— Śdsika wewnętrzna
Aby narysować literę A, funkcję gluNextContour wywołujemy tylko raz, przed określeniem wewnętrznych punktów. Przykład z listingu 18.1, LETTER.C, do rysowania wirującej litery A używa poniższego kodu:
tess = gluNewTess();
gluTessCallback(tess, GLU_BEGIN, glBegin); gluTessCallbackttess, GLU_VERTEX, glVertex3dv); gluTessCallback(tess, GLU_END, glEnd);
outside[0], outside[1], outside[2], outside[3], outside[4], outside[5], outside[6],
outside[0]) outside[1]) outside[2]) outside[3]) outside[4]) outside[5]) outside[6])
gluBeginPolygon(tess), gluTessVertex(tess, gluTessVertex(tess, gluTessVertex(tess, gluTessVertex(tess, gluTessVertex(tess, gluTessVertex(tess, gluTessVertex(tess,
gluNextContour(tess, GLU_INTERIOR) ;
gluTessVertex(tess, inside[0], inside[0]>;
gluTessVertex (tess, inside[l], inside[l]);
gluTessVertex (tess, inside[2], inside[2]); gluEndPolygon(tess) ; gluDeleteTess (tess) ;
Listing 18.1. LETTER.C: Podział wielokąta w celu utworzenia litery A _______
/*
* "letter.c" - Testowy program demonstrujący użycie
* triangulatora z biblioteki GLO.
*/
łinclude <GL/glaux.h>
Te definicje zostały wprowadzone w celu zapewnienia zgodności pomiędzy MS Windows a resztą świata.
CALLBACK i APIENTRY to modyfikatory funkcji w MS Windows.
#ifndef WIN32
# define CALLBACK
# define APIENTRY łendif /* IWIN32 */
GLfloat rotation = 0.0;
'reshape_scene()' - Zmiana rozmiaru sceny...
Rozdział 18. » Podział wielokątów _________________________________ 579
void CALLBACK
reshape_scene (GLsizei width, /* We - Szerokość okna w pikselach */
GLsizei height) /* We - Wysokość okna w pikselach */ { /*
* Wyzerowanie bieżącego widoku i przekształcenia perspektywicznego.
*/
glviewport (O, O, width, height);
glMatrixMode (GL_PROJECTION) ;
glLoadldentity ( ) ;
gluPerspective(22.5, (float) width / (float)height, 0.1, 1000.0);
glMatrixMode (GL_MODELVIEW) ;
/*
* ' draw_scene ( ) ' - Rysuje scenę zawierająca, literę "A"
*/
V0id CALLBACK draw_scene (void) {
GLUtriangulatorObj *tess;
static GLdouble outside [7] [3] =
{
{ 0.0, 1.0, 0.0 },
{ -0.5, -1.0, 0.0 },
{ -0.4, -1.0, 0.0 },
{ -0.2, -0.1, 0.0 },
{ 0.2, -0.1, 0.0 },
{ 0.4, -1.0, 0.0 ),
{ 0.5, -1.0, 0.0 } >;
static GLdouble inside[3][3] -{
{ 0.0, 0.6, 0.0 },
{ -0.1, 0.1, 0.0 },
{ 0.1, 0.1, 0.0 } };
static float red_light[4] = { 1.0, 0.0, 0.0, 1.0 }; static float red_pos[4] = { 1.0, 0.0, 0.0, 0.0 }; static float blue_light [4] = { 0.0, 0.0, 1.0, 1.0 }; static float blue_pos[4] - { -1.0, 0.0, 0.0, 0.0 } ;
/*
* Włączenie buforów i oświetlenia
*/
glEnable (GL_DEPTH_TEST) ; glDisable (GL_LIGHTING) ; glEnable (GL_LIGHTO) ; glEnable (GL_LIGHT1) ;
glShadeModel (GL SMOOTH) ;
580
Część III » Tematy zaawansowane i efekty specjalne
Wyczyszczenie bufora koloru i głębokości.
glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
W scenie umieściliśmy dwa światła. Pierwsze z nich jest czerwone i zostało umieszczone u góry z prawej strony, za obserwatorem. Drugie jest niebieskie i znajduje się u dołu po lewej stronie, przed obserwatorem.
glLightfv(GL_LIGHTO, GL_DIFFUSE, red_light); glLightfv(GL_LIGHTO, GL_POSITION, red_pos);
glLightfv(GL_LIGHTl, GL_DIFFUSE, blue_light); glLightfv(GL_LIGHTl, GL_POSITION, blue_pos);
glEnable(GL_COLOR_MATERIAL) ; .
glPushMatrix();
glTranslatef(0.0, 0.0, -15.0); glRotatef(-rotation, 0.0, 1.0, 0.0);
glColor3f(0.0, 1.0, 0.0); glNormal3f(0.0, 0.0, 1.0);
tess = gluNewTess ();
gluTessCallback(tess, GLU_BEGIN, glBegin); gluTessCallback(tess, GLU_VERTEX, glVertex3dv), gluTessCallback(tess, GLU_END, glEnd);
gluBeginPolygon(tess) gluTessVertex(tess, gluTessVertex(tess, gluTessVertex(tess, gluTessVertex(tess, gluTessVertex (tess, gluTessVertex(tess, gluTessVertex(tess,
outside[0]) outside[1]) outside[2]) outside[3]) outside [4]) outside[5]) outside[6])
outside[0], outside[1], outside[2], outside[3], outside[4], outside[5], outside[6],
gluNextContour(tess, GLU_INTERIOR);
gluTessVertex(tess gluTessVertex(tess
gluEndPolygon(tess);
gluDeleteTess(tess) ;
gluTessVertex(tess, inside[0], inside[0]); inside[l], insidefl]); inside[2], inside[2]);
glPopMatrix(); auxSwapBuffers();
1rotate_objects()' - W wolnych chwilach obrót sceny...
Rozdział 18. » Podział wielokątów ________________________________ 581
V0id CALLBACK rotate_objects (void)
{
rotation +=> 2.0; if (rotation >= 360.0) rotation -= 360.0;
draw scenę ( ) ;
/*
* 'mainO' - Inicjowanie okna i wyświetlanie sceny do momentu
* wciśnięcia klawisza 'Esc'.
V
void
main (void)
{
aux!nitDisplayMode(AUX_RGB | AUX_DOUBLE | AUX_DEPTH) ;
auxlnitwindow("wielokąt w kształcie litery - GLU");
auxReshapeFunc (reshape_scene) ; auxIdleFunc (rotate_objects) ;
auxMainLoop (draw_scene) ;
/*
* Koniec pliku "letter.c".
*/
Funkcje zwrotne
Biblioteka glu definiuje kilka funkcji zwrotnych, które mogą zostać wykorzystane do osiągnięcia pewnych efektów specjalnych. Funkcja gluTessCallback umożliwia zmianę tych funkcji na własne. Wymaga podania trzech argumentów:
void gluTessCallback(GLUtriangulatorObj *tobj, GLenum which, void (*fn)());
Argument which określa definiowaną funkcję zwrotną i musi być jedną z wartości zebranych w tabeli 18.2.
Tabela 18.2.
Funkcje zwrotne triangulatora
Argument which Opis
GLU_BEGIN Funkcja jest wywoływana w momencie rozpoczęcia budowania prymitywu
GLJTRIANGLES, GL_TRIANGLE_STRIP lub GL_TRIANGLE_FAN.
Funkcja musi przyjmować pojedynczy parametr GLenum określający
tworzony prymityw, i zwykle jest wywoływana w momencie wywołania
funkcji glBegin.
582______________________Część III » Tematy zaawansowane i efekty specjalne
Tabela 18.2.
Funkcje zwrotne triangulatora - ciąg dalszy
Argument whlch Opis
GLU_EDGE_FLAG Funkcja jest wywoływana przy każdym wierzchołku wielokąta i musi
przyjmować pojedynczy argument typu GLboolean, określający, czy
wierzchołek jest wierzchołkiem oryginalnym (GLJTRUE) czy
wygenerowanym w celu podziału (GL_FALSE).
GLU_VERTEX Funkcja jest wywoływana przed każdym wysłaniem kolejnego wierzchołka
funkcją glVertex3dv. Funkcja otrzymuje kopię trzeciego argumentu funkcji
gluTessVertex.
GLU_END Funkcja jest wywoływana na zakończenie rysowania prymitywu, zwykle
funkcją glEnd. Nie posiada argumentów.
GLU_ERROR Funkcja jest wywoływana w momencie wystąpienia błędu. Musi przyjmować
pojedynczy argument typu GLenum.
Zwykle będziesz używał głównie funkcji zwrotnych GLU_BEGIN, GLU_END, GLU_VERTEX oraz GLU_ERROR. Funkcje zwrotne GLU_BEGIN, GLU_END oraz GLU_VERTEX są wywoływane w momencie wywołania, odpowiednio, funkcji glBegin, glEnd oraz glVertex3dv. Prosta funkcja wyświetlająca błędy zgłoszone przez triangulatora została przedstawiona na listingu 18.2.
Listing 18.2. Prosta procedura obsługi błędów triangulatora__________________________
void
tess_error_callback(GLenum error)
{
MessageBeep(MB_ICONEXCLAMATION);
MessageBox(NULL, gluErrorString(error), "Błąd GLU", MB_OK | MB_ICONEXCLAMATION);
Podsumowanie
Triangulator wielokątów w OpenGL może zostać użyty w celu renderowania różnorodnych złożonych wielokątów, których nie da się narysować przy użyciu prymitywu OpenGL GL_POLYGON. Podział wielokątów ma jednak swój ą cenę, dobrze jest więc umieszczać podzielone wielokąty na listach wyświetlania, w celu poprawy wydajności rysowania.
Mechanizm zwrotny daje pewną kontrolę nad wygenerowanymi wynikami, nie wpływa jednak na używany algorytm podziału. Z tego powodu funkcje zwrotne są używane dość rzadko.
Rozdział 18. » Podział wielokątów_________________________________583
Podręcznik
gluBeginPolygon_____________________
Przeznaczenie Rozpoczyna podział złożonego wielokąta.
Plik nagłówkowy <glu.h>
Składnia void gluBeginPoIygon(GLUtrianguIatorObj *tObj);
Opis Ta funkcja rozpoczyna podział złożonego wielokąta.
Parametry
tObj GLUtriangulatorObj*: Wskaźnik do obiektu triangulatora, używanego
przy podziale wielokąta.
Zwracana wartość Brak.
Przykład Przykładowy program LETTER.C na płytce CD-ROM.
Patrz także gluEndPolygon, gluNextContour, gluTessVertex
gluDeleteTess_______________________
Przeznaczenie Usuwa obiekt triangulatora.
Plik nagłówkowy <glu.h>
Składnia void gluDeleteTess(GLUtriangulatorObj *tObj);
Opis Funkcja gluDeleteTess zwalnia pamięć związaną z obiektem
triangulatora.
Parametry
tObj GLUtriangulatorObj*: Wskaźnik do obiektu triangulatora
przeznaczonego do usunięcia.
Zwracana wartość Brak.
Przykład Przykładowy program LETTER.C na płytce CD-ROM.
Patrz także gluNewTess
gluEndPolygon
Przeznaczenie Kończy podział złożonego wielokąta i renderuje go.
Plik nagłówkowy <glu.h>
Składnia void gluEndPolygon(GLUtriangulatorObj *tObj);
584______________________Część III » Tematy zaawansowane i efekty specjalne
Opis Ta funkcja kończy podział złożonego wielokąta i renderuje go.
Parametry
tObj GLUtriangulatorObj*: Wskaźnik do obiektu triangulatora, używanego
przy podziale wielokąta.
Zwracana wartość Brak.
Przykład Przykładowy program LETTER.C na płytce CD-ROM.
Patrz także gluBeginPolygon, gluNextContour, gluTessVertex
gluNewTess________________________
Przeznaczenie Tworzy obiekt triangulatora.
Plik nagłówkowy <glu.h>
Składnia GLUtriangulatorObj *gluNewTess(void);
Opis Funkcja gluDeleteTess tworzy obiekt triangulatora.
Parametry Brak.
Zwracana wartość GLUtriangulatorObj *: Nowy obiekt triangulatora.
Przykład Przykładowy program LETTER.C na płytce CD-ROM.
Patrz także gluDeleteTess
gluNextContour______________________
Przeznaczenie Rozpoczyna nowy kontur lub otwór w złożonym wielokącie.
Plik nagłówkowy <glu.h>
Składnia void gluNextContour(GLUtriangulatorObj *tObj, GLenum type);
Opis Ta funkcja rozpoczyna nowy kontur lub otwór w złożonym wielokącie.
Parametry
tObj GLUtriangulatorObj*: Wskaźnik do obiektu triangulatora, używanego
przy podziale wielokąta.
type GLenum: Typ konturu. Dostępne typy zostały zebrane w tabeli 18.1
w treści rozdziału.
Zwracana wartość Brak.
Przykład Przykładowy program LETTER.C na płytce CD-ROM.
Patrz także gluBeginPolygon, gluEndPolygon, gluTessVertex
585
Rozdział 18. * Podział wielokątów
gluTessCallback
Przeznaczenie Plik nagłówkowy Składnia
Opis
Określa funkcję zwrotną podziału wielokąta.
<glu.h>
void gluTessCallback(GLUtriangulatorObj *tObj, GLenum which, void
(*fn)0);
Ta funkcja określa funkcje zwrotne podziału wielokąta, wywoływane na różnych etapach podziału. Funkcje zwrotne nie wpływają na sposób działania triangulatora ani na jego wydajność. Umożliwiają raczej uzupełnienie generowanych wierzchołków o dodatkowe informacje, takie jak kolor czy współrzędne tekstury.
Parametry tObj
which
fn Zwracana wartość
GLUtriangulatorObj*: Wskaźnik do obiektu triangulatora, używanego przy podziale wielokąta.
GLenum: Definiowana funkcja zwrotna. Dostępne funkcje zwrotne zostały zebrane w tabeli 18.2 w treści rozdziału.
void (*)(): Wywoływana funkcja. Brak.
gluTessVertex
Składnia Opis
Parametry tObj
v data
Przeznaczenie Dodaje wierzchołek do bieżącej ścieżki wielokąta.
Plik nagłówkowy <glu.h>
void gluTessVertex(GLUtriangulatorObj *tObj, GLdouble v[3], void *data);
Ta funkcja dodaje wierzchołek do bieżącej ścieżki podziału wielokąta. Argument data jest przekazywany w funkcji zwrotnej GL_VERTEX.
GLUtriangulatorObj*: Wskaźnik do obiektu triangulatora, używanego przy podziale wielokąta.
GLdouble[3]: Wierzchołek 3D.
void *: Wskaźnik do danych, które mają zostać przekazane w funkcji zwrotnej GL_VERTEX.
Zwracana wartość Brak.
Przykład Przykładowy program LETTER.C na płytce CD-ROM.
Patrz także gluBeginPolygon, gluEndPolygon, gluNextContour
Rozdział 19.
Grafika interaktywna
W tym rozdziale:
Dowiesz się, jak... Używane funkcje
* Przypisywać nazwy selekcji OpenGL * gllnitNames/glPushName/glPopName
prymitywom lub grupom prymitywów
* Używać selekcji do wyznaczenia * glSelectBuffer/glRenderMode
obiektów wskazywanych myszką
* Korzystać ze sprzężenia zwrotnego * glFeedbackBuffer/gluPickMatrix
w celu otrzymania informacji
o narysowanych obiektach
Dotychczas uczyłeś się tworzyć za pomocą OpenGL wymyślną grafikę w aplikacjach, które nie robiły nic poza generowaniem scen. Jednak wiele aplikacji graficznych (głównie gier) wymaga większej interakcji ze sceną. Oprócz menu i okien dialogowych, musisz zapewnić jakiś sposób komunikowania się użytkownika z obiektami w scenie. W systemie Windows zwykle odbywa się to za pomocą myszki.
Selekcja, bardzo użyteczny element OpenGL, umożliwia kliknięcie myszką w pewne miejsce okna i wyznaczenie obiektu, w obrębie którego nastąpiło to kliknięcie. Fakt selekcji konkretnego obiektu sceny jest nazywany wybraniem. Przy użyciu mechanizmu selekcji OpenGL możesz określić bryłę widzenia i wyznaczyć, które obiekty się w niej znajdują. Wydajna funkcja narzędziowa tworzy specjalną matrycę, opartą wyłącznie na współrzędnych ekranu i rozmiarach w pikselach; za pomocą tej matrycy można stworzyć małą bryłę widzenia w miejscu kursora myszy. Następnie możesz użyć selekcji do sprawdzenia, jakie obiekty należą do tej bryły widzenia.
Sprzężenie zwrotne pozwala na uzyskanie od OpenGL informacji o tym, w jaki sposób wierzchołki są przekształcane i oświetlane, przed narysowaniem ich w buforze ramki. Możesz użyć tych informacji do przesyłania rezultatów renderowania przez sieć, rysowania sceny za pomocą plotera czy też łączenia obiektów OpenGL z grafiką GDI. Sprzężenie zwrotne nie służy tym samym celom co selekcja, lecz sam tryb działania jest
588_______________________Część III » Tematy zaawansowane i efekty specjalne
bardzo podobny, co umożliwia współpracę obu mechanizmów. W dalszej części rozdziału znajdziesz przykład ilustrujący takie współdziałanie.
Selekcja
Selekcja jest w rzeczywistości trybem renderowania, w którym żadne piksele nie są kopiowane do bufora ramki. Zamiast tego prymitywy są rysowane w bryle widzenia (tak jak normalnie pojawiłyby się w buforze ramki) w wyniku czego tworzą rekordy „trafień" w buforze selekcji.
Bufor selekcji należy przygotować wcześniej, a także nazwać prymitywy lub grupy prymitywów (twoich obiektów) tak, aby można je było zidentyfikować w buforze selekcji. Następnie przetwarza się bufor selekcji w celu wyznaczenia, które obiekty przecinają bryłę widzenia. Ma to marginalne znaczenie dopóty, dopóki nie zmodyfikujesz bryły widzenia przed przejściem do rysowania, w celu wyznaczenia, które obiekty znajdują się w określonym obszarze sceny. W popularnym rozwiązaniu określa się bryłę widzenia odpowiadającą wskaźnikowi myszy, a następnie sprawdza, który z nazwanych obiektów jest wskazywany przez mysz (zawarty w tej bryle widzenia).
Nazywanie prymitywów
Możesz nadać nazwę każdemu prymitywowi składającemu się na scenę, lecz rzadko jest to użyteczne. O wiele częściej będziesz nadawał nazwy grupom prymitywów, czyli obiektom lub częściom obiektów w scenie. Nazwy obiektów, podobnie jak nazwy list wyświetlania, nie są niczym innym jak cyfrowymi identyfikatorami w postaci liczb całkowitych bez znaku.
Lista nazw jest przechowywana na stosie nazw. Po zainicjowaniu stosu nazw możesz odkładać nazwy na stos lub zastępować nazwę występującą na szczycie stosu. Gdy podczas selekcji nastąpi trafienie, wszystkie nazwy ze stosu nazw są kopiowane do bufora selekcji. Tak więc pojedyncze trafienie może zwrócić więcej niż jedną nazwę.
W naszym pierwszym przykładzie postaramy się zachować maksymalną prostotę. Utworzymy uproszczony (i nie w skali) model części układu planetarnego. Gdy użytkownik kliknie lewym przyciskiem myszy, wyświetlimy komunikat opisujący planetę, na której nastąpiło kliknięcie. Listing 19.1 przedstawia fragment kodu renderowania z tego przykładowego programu, PLANETS.C. Na początku kodu zostały zdefiniowane nazwy dla obiektów Słońca, Merkurego, Wenus, Ziemi i Marsa.
Listing 19.1. Nadawanie nazw (identyfikatorów) planetom w programie PLANETS______________
tdefine SUN l tfdefine MERCURY 2
#define VENUS 3
#define EARTH 4 Idefine MARS 5
Rozdział 19. * Grafika interaktywna 589
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
// Wyczyszczenie okna bieżącym kolorem tła
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Zachowanie stanu macierzy i wykonanie obrotu glMatrixMode(GL_MODELVIEW); glPushMatrix();
// Przekształcenie całej sceny do układu obserwatora glTranslatef(O.Of, O.Of, -300.Of);
// Inicjowanie stosu nazw gllnitNames(); glPushName(0) ;
// Ustawienie koloru materiału na żółty // Słońce
glRGB(255, 255, 0); glLoadName(SUN); auxSolidSphere(15.Of);
// Rysowanie Merkurego
glRGB(128,0,0) ;
glPushMatrix () ;
glTranslatef(24.Of, O.Of, O.Of);
glLoadName(MERCURY);
auxSolidSphere(2.Of);
glPopMatrix();
// Rysowanie Wenus glPushMatrix(); glRGB(128,128,255); glTranslatef(60.Of, O.Of, O.Of); glLoadName(VENUS); auxSolidSphere(4.Of); glPopMatrix();
... Pozostałe planety
II Odtworzenie stanu macierzy glPopMatrix(); // Macierz widoku modelu
// Zrzucenie poleceń graficznych glFlushO ;
W programie PLANETS funkcja gllnitNames inicjuje i czyści stos nazw, zaś funkcja glPushName odkłada na stos identyfikator O, w celu wypełnienia stosu przynajmniej jedną nazwą. W przypadku Słońca i poszczególnych planet wywołujemy funkcje glLoadName w celu nazwania obiektu lub obiektów, które mają zostać narysowane. Te nazwy, w postaci liczb całkowitych bez znaku, nie są odkładane na stos nazw, lecz zastępują
590_______________________Część III » Tematy zaawansowane i efekty specjalne
bieżącą nazwę na szczycie stosu. Później omówimy zagadnienie utrzymywania stosu nazw. Na razie po prostu zastępujemy nazwę na szczycie stosu za każdym razem, gdy przystępujemy do rysowania obiektu (Słońca i poszczególnych planet).
Praca w trybie selekcji
Jak już wspomniano, OpenGL może pracować w trzech różnych trybach renderowania. Domyślnym trybem jest GLJRENDER, w którym wszystkie operacje graficzne pojawiają się na ekranie. Aby użyć selekcji, musimy zmienić tryb renderowania na tryb selekcji, wywołując funkcję:
glRenderMode(GL_SELECTION);
Gdy chcemy ponownie rysować na ekranie, wywołujemy funkcję
glRenderMode(GL_RENDER) ;
ustawiając OpenGL ponownie w tryb rysowania. Trzecim trybem renderowania jest GL_FEEDBECK, który zostanie omówiony w dalszej części rozdziału.
Kod nadający nazwy z listingu 19.1 nie przynosi żadnego efektu do momentu przełączenia się do trybu selekcji. Najczęściej będziesz używał tych samych funkcji do renderowania w obu trybach, GL_RENDER i GL_SELECTION, tak jak robimy to w tym programie.
Listing 19.2 przedstawia kod wykonywany w momencie kliknięcia lewym przyciskiem myszy. Procedura obsługi odczytuje współrzędne wskaźnika myszy z parametru IParam i przekazuje je funkcji ProcessSelection, przetwarzającej w tym programie kliknięcia myszką.
Listing 19.2. Kod obsługujący klikniącie lewym przyciskiem myszy_______________________
case WM_LBUTTONDOWN: {
int xPos = LOWORD(IParam); // Pozioma współrzędne kursora int yPos = HIWORD(IParam); // Pionowa współrzędne kursora
// Renderowanie w trybie selekcji i wyświetlenie rezultatu ProcessSelection(xPos, yPos) ;
Bufor selekcji
Podczas renderowania bufor selekcji jest wypełniany rekordami trafień. Rekord trafienia jest generowany za każdym razem, gdy renderowany prymityw lub grupa prymitywów przecina bryłę widzenia. W normalnych warunkach byłyby to wszystkie prymitywy, jakie pojawiłyby się na ekranie.
591
Rozdział 19. » Grafika interaktywna
Bufor selekcji to tablica liczb całkowitych bez znaku, zaś każdy rekord selekcji zajmuje przynajmniej cztery elementy tablicy. Pierwsza pozycja tablicy zawiera ilość nazw na stosie nazw w momencie wystąpienia trafienia. W przypadku programu PLANETS (listing 19.1) będzie to zawsze 1. Następne dwie pozycje zawierają minimalną i maksymalną współrzędną Z wszystkich wierzchołków zawartych w bryle widzenia od ostatniego rekordu trafienia. Ta wartość, z zakresu <0, 1>, jest skalowana do wartości liczb całkowitych bez znaku (232 - 1) w celu przechowania w buforze selekcji. Ten wzór, przedstawiony na rysunku 19.1, powtarza się dla wszystkich rekordów trafień w buforze selekcji.
Rysunek 19.1.
Format rekordu trafienia w buforze selekcji
Bufor selekcji [0] —w Ilość nazw na stosie nazw w momencie
trafienia = n,
[ l ] —^- Minimalna wartość z [2] —^- Maksymalna wartość z [n0+2] -^-Dno stosu nazw
Następny rekord trafienia -^[n,+3] -^-Ilość nazw na stosie nazw w momencie
trafienia = n, [n.+4] -^-Minimalna wartość z
Z formatu bufora selekcji nie wynika żadna informacja o ilości rekordów trafień, jakie trzeba przeanalizować. Dzieje się tak, ponieważ bufor selekcji w rzeczywistości nie jest wypełniany aż do momentu powrotu do trybu GL_RENDER. Gdy przełączysz się do tego trybu funkcją glRenderMode, wartość zwracana przez tę funkcję odpowiada ilości rekordów trafień skopiowanych do bufora.
Listing 19.3 przedstawia funkcję przetwarzającą wywoływaną w momencie kliknięcia lewym przyciskiem myszy. W przedstawionym kodzie widzimy sposób alokowania i tworzenia bufora selekcji funkcją glSelectBuffer. Ta funkcja wymaga podania dwóch argumentów: rozmiaru bufora oraz wskaźnika do tego bufora.
Listing 19.3. Funkcja przetwarzająca kliknięcia myszką_____________________________
// Przetwarzanie selekcji, wywoływane w momencie kliknięcia
// myszką w miejscu o współrzędnych (xPos, yPos)
tdefine BUFFER_LENGTH 64
void ProcessSelectionlint xPos, int yPos)
{
// Miejsce na bufor selekcji
GLuint selectBuff[BUFFER_LENGTH];
// Licznik trafień i miejsce na widok GLint hits, viewport[4];
// Przygotowanie bufora selekcji glSelectBuffer(BOFFER_LENGTH, selectBuff);
592_______________________Część III » Tematy zaawansowane i efekty specjalne
// Pobranie widoku glGetIntegerv(GL_VIEWPORT, viewport);
// Przejście do macierzy rzutowania i zachowanie jej glMatrixMode(GL_PROJECTION); glPushMatrix();
// Zmiana trybu renderowania glRenderMode(GL_SELECT);
// Ustanowienie nowej bryły widzenia jako jednostkowej kostki
// dookoła punktu wskaźnika myszy (xPos, yPos), rozciągającej się
// na dwa piksele w poziomie i w pionie.
// Ponieważ OpenGL mierzy współrzędne okna od dołu, zaś Windows od
// góry,musimy to uwzgldnić odejmując współrzędną y od góry okna.
// Daje to efekt odwrócenia układu współrzędnych (y zaczyna się
// u góry).
glLoadldentity();
gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport);
// Zastosowanie macierzy perspektywy gluPerspective(45.Of, fAspect, 1.0, 425.0);
// Wyrysowanie sceny RenderScene();
// Zliczenie trafień
hits = glRenderMode(GL_RENDER);
// Jeśli wystąpiło pojedyncze trafienie, wyświetlenie informacji if(hits == 1)
ProcessPianęt(selectBuff[3]);
// Odtworzenie macierzy rzutowania glMatrixMode(GL_PROJECTION); glPopMatrix();
// Powrót do widoku modelu w celu normalnego renderowania glMatrixMode(GL_MODELVIEW);
Wybieranie
Wybieranie następuje wtedy, gdy podczas selekcji wykorzystujesz położenie myszy do utworzenia i użycia zmodyfikowanej bryły obcinania. Po utworzeniu mniejszej bryły widzenia umieszczonej w scenie na miejscu wskaźnika myszy, trafienia będą generowane jedynie przez te obiekty, które przecinają tę bryłę widzenia. Sprawdzając zawartość bufora selekcji, możesz więc sprawdzić, na których obiektach nastąpiło kliknięcie myszką.
Podczas tworzenia macierzy opisującej nową bryłę widzenia bardzo przydaje się funkcja gluPickMatrix:
Rozdział 19. » Grafika interaktywna_______________________________593
void gluPickMatrix(GLdouble x, GLdouble y, GLdouble width, GLdouble ^height,
GLint viewport[4]);
Parametry x i y to środek żądanej bryły widzenia we współrzędnych okna. Wyrażona jest w nich pozycja myszy, zaś bryła widzenia zostanie wyśrodkowana dokładnie w tym miejscu. Parametry width i height określają szerokość i wysokość bryły widzenia w pi-kselach okna. W przypadku kliknięć w pobliżu obiektu użyj większych wartości, w przypadku kliknięć bezpośrednio na obiektach użyj mniejszych wartości. Tablica viewport zawiera współrzędne okna dla aktualnie zdefiniowanego widoku. Można je łatwo odczytać wywołując
glGetIntegerv(GL_VIEWPORT, viewport);
Aby użyć funkcji gluPickMatrix, powinieneś najpierw zachować bieżący stan macierzy rzutowania (czyli zachować bieżącą bryłę widzenia). Następnie wywołaj funkcję glLoad-Identity w celu stworzenia jednostkowej bryły widzenia. Na koniec, musisz zastosować rzutowanie perspektywiczne, jakie zastosowałeś w oryginalnej scenie, gdyż w przeciwnym razie nie otrzymasz właściwego odwzorowania. Oto jak robimy to w programie PLANETS (z listingu 19.3):
// Przejście do macierzy rzutowania i zachowanie jej glMatrixMode(GL_PROJECTION); glPushMatrix();
// Zmiana trybu renderowania glRenderMode(GL_SELECT);
// Ustanowienie nowej bryły widzenia jako jednostkowej kostki // dookoła punktu wskaźnika myszy (xPos, yPos), rozciągającej się // na dwa piksele w poziomie i w pionie. glLoadldentity();
gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport);
// Zastosowanie macierzy perspektywy gluPerspective(45.0f, fAspect, 1.0, 425.0);
// Wyrysowanie sceny RenderScene();
// Zliczenie trafień
hits = glRenderMode(GL_RENDER);
W tym segmencie, najpierw zachowujemy stan bryły widzenia. Następnie przechodzimy do trybu selekcji, modyfikujemy bryłę widzenia tak, aby obejmowała jedynie obszar pod wskaźnikiem myszy, a następnie odrysowujemy scenę wywołując funkcję RenderScene. Po wyrenderowaniu sceny ponownie wywołujemy funkcję glRenderMode w celu przełączenia OpenGL do normalnego trybu rysowania, a jednocześnie otrzymujemy ilość wygenerowanych rekordów trafień.
W następnym segmencie, jeśli wystąpiło trafienie (w tym przykładzie może wystąpić albo jedno trafienie, albo wcale), do naszej funkcji ProcessPlanet przekazujemy element bufora selekcji zawierający nazwę (identyfikator) trafionej planety. Na koniec odtwa-
594______________________Część III » Tematy zaawansowane i efekty specjalne
rzamy macierz rzutowania (czyli przywracamy starą bryłę widzenia) i przełączamy się na używanie macierzy widoku modelu, normalnie będącej domyślną macierzą.
// Jeśli wystąpiło pojedyncze trafienie, wyświetlenie informacji if(hits == 1)
ProcessPianęt(selectBuff[3]);
// Odtworzenie macierzy rzutowania glMatrixMode(GL_PROJECTION); glPopMatrix();
// Powrót do widoku modelu w celu normalnego renderowania glMatrixMode(GL_MODELVIEW);
Funkcja ProcessPlanet po prostu wyświetla okno komunikatu informujące, na której planecie nastąpiło kliknięcie. Nie będziemy przytaczać tego kodu, gdyż jest oczywisty i nie zawiera nic oprócz zwykłej instrukcji switch wybierającej teksty dla okna komunikatu.
Wynik działania programu PLANETS został pokazany na rysunku 19.2, na którym widzimy efekt kliknięcia na drugą planetę od Słońca.
Rysunek 19.2.
Działanie programu PLANETSpo kliknięciu na panetę
Wybór hierarchiczny
W programie PLANETS nie odkładaliśmy nazw na stos, lecz jedynie zastępowaliśmy znajdującą się na nim nazwę. Ta pojedyncza nazwa ze stosu była jedyną nazwą zwracaną w buforze selekcji. Po umieszczeniu większej ilości nazw na stosie nazw moglibyśmy otrzymać kilka nazw w buforze. Jest to użyteczne w sytuacjach, gdy potrzebujemy dalszych informacji, na przykład, gdy chcemy być poinformowani, że została trafiona określona śruba, w określonym kole, w określonym samochodzie w scenie itd.
W celu zademonstrowania kilku nazw zwracanych na stosie selekcji, pozostaniemy przy temacie astronomicznym z poprzedniego przykładu. Rysunek 19.3 przedstawia (przy pewnej dozie wyobraźni)dwie planety: większą z pojedynczym księżycem oraz mniejszą, czerwoną planetę z dwoma księżycami.
Rozdział 19. » Grafika Interaktywna 595
Rysunek 19.3.
Dwie planety z księżycami
Zamiast identyfikować jedynie planetę lub księżyc, na których nastąpiło kliknięcie, będziemy identyfikować także planetę związaną z danym księżycem. Kod z listingu 19.4 przedstawia nowy kod renderowania naszej sceny. Oprócz nazw planet odkładamy na stos także nazwy księżyców, przez co będzie on zawierał zarówno nazwę planety, jak i wybranego księżyca.
Listing 19.4. Kod renderowania z programu MOONS___________________________ __
łdefine EARTH l
#define MARS 2
łdefine MOON1 3
#define MOON2 4
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
// Wyczyszczenie okna bieżącym kolorem tła glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Zachowanie stanu macierzy i wykonanie obrotu glMatrixMode(GL_MODELVIEW); glPushMatrix();
// Przekształcenie całej sceny do układu obserwatora glTranslatef(O.Of, O.Of, -300.Of);
// Inicjowanie stosu nazw gllnitNamesO ; glPushNarne (0) ;
// Rysowanie Ziemi
glPushMatrix();
glRGB(0,0,255) ;
glTranslatef(-100.Of,O.Of,O.Of);
glLoadName(EARTH);
auxSolidSphere(30.Of);
596 _______________________ Część III » Tematy zaawansowane i efekty specjalne
// Rysowanie Księżyca glTranslatef (45. Of, O.Of, O.Of); glRGB(220,220,220) ; glPushName (MOON1) ; auxSolidSphere (5.0f ) ; glPopName ( ) ; glPopMatrix ( ) ;
// Rysowanie Marsa
glRGB(255,0,0) ;
glPushMatrix () ;
glTranslatef (100. Of, O.Of, O.Of);
glLoadName(MARS) ;
auxSolidSphere (20 . Of ) ;
// Rysowanie pierwszego księżyca glTranslatef (-40. Of, 40. Of, O.Of); glRGB(220,220,220) ; glPushName (MOON1) ; auxSolidSphere(5.0f) ; glPopName ( ) ;
// Rysowanie drugiego księżyca glTranslatef (O.Of, -80. Of, O.Of); glPushName (MOON2) ; auxSolidSphere(5.0f ) ; glPopName ( ) ; glPopMatrix () ;
// Odtworzenie stanu macierzy glPopMatrix () ; // Macierz widoku modelu
// Zrzucenie poleceń graficznych glFlushO ;
Tym razem w funkcji ProcessSelection także wywołujemy funkcję ProcessPlanet, lecz w tym przypadku przekazujemy jej cały bufor selekcji:
// Jeśli wystąpiło pojedyncze trafienie, wyświetlenie informacji iflhits == 1)
ProcessPlanet (selectBuf f) ;
Listing 19.5 przedstawia funkcję ProcessPlanet, która w tym przykładzie jest bardziej rozbudowana. W tym przypadku dolna nazwa na stosie nazw zawsze będzie nazwą planety, gdyż została odłożona na stos jako pierwsza. Jeśli nastąpi kliknięcie na księżyc, on także znajdzie się na stosie nazw. Ta funkcja wyświetla nazwę wybranej planety, a jeśli został trafiony księżyc, ta informacja również jest wyświetlana. Przykład działania programu został przedstawiony na rysunku 19.4.
597
Rozdział 19. » Grafika interaktywna
Rysunek 19.4.
Przykład działania programu MOONS
Listing 19.5. Kod przetwarzający bufor selekcji w przykładowym programie MOONS
// Przetwarzanie bufora selekcji w celu wyznaczenia // obiektu planety/księżyca, na który kliknięto void ProcessPlanet(GLuint *pSelectBuff)
int id,count; char CMessage[64];
// Ilość nazw na stosie nazw count = pSelectBuff [0];
// Dno stosu nazw id = pSelectBuff[3];
// Sprawdzenie, czy kliknięto na Ziemię lub na Marsa switch(id)
case EARTH:
strcpy(CMessage,"Kliknąleś na Ziemię.");
// Jeśli na stosie nazw występuje inna nazwa, // musiał zostać trafiony księżyc, if(count == 2)
strcat(CMessage,"\nKonkretnie na Księżyc.
break;
case MARS:
strcpy(CMessage,"Kliknąłeś na Marsa.");
598______________________Część III » Tematy zaawansowane i efekty specjalne
// Wiemy, że stos nazw ma tylko dwa elementy. // Znajduje się na nim nazwa trafionego księżyca if(count == 2)
if(pselectfiuff[4] == MOON1)
strcat(cMessage,"\nKonkretnie na księżyc nr 1."); else
strcat(cMessage,"\nKonkretnie na księżyc nr 2.");
break;
// Jeśli nic nie zostało trafione, nie powinno nas tu być! default:
strcpy(cMessage,"Błąd - Na nic nie kliknąłeś!");
break;
// Wyświetlenie komunikatu o trafionej planecie i ewentualnie
księżycu
MessageBox(NULL,cMessage,"Komunikat selekcji",MB_OK); }
Sprzężenie zwrotne
Sprzężenie zwrotne (ang. feedbacK), podobnie jak selekcja, jest trybem renderowania, w którym nic nie jest rysowane na ekranie. Zamiast tego, do bufora sprzężenia zwroten-go wpisywane są informacje na temat sposobu renderowania sceny. Te informacje obejmują współrzędne okna przetransformowanych wierzchołków, kolory wierzchołków już po oświetleniu, a także dane tekstury.
Przejście do trybu sprzężenia zwrotnego odbywa się podobnie jak przejście do trybu selekcji, poprzez wywołanie funkcji glRenderMode z parametrem GLJFEEDBACK. W celu wypełnienia bufora sprzężenia zwrotnego i powrotu do normalnego trybu renderowania musisz wywołać funkcję glRenderMode(GL_RENDER).
Bufor sprzężenia zwrotnego
Bufor sprzężenia zwrotnego jest tablicą wartości zmiennoprzecinkowych, tworzoną za pomocą funkcji glFeedbackBuffer:
void glFeedbackBuffer(GLsizei, GLenum type, GLfloat *buffer);
Ta funkcja otrzymuje rozmiar bufora, typ i ilość potrzebnych informacji rysunkowych oraz wskaźnik do bufora.
Dostępne wartości dla typu danych zostały zebrane w tabeli 19.1. Rodzaj danych określa ilość danych umieszczanych w buforze dla każdego wierzchołka. Dane koloru (C) są reprezentowane przez pojedynczą wartość w trybie indeksu koloru lub przez cztery wartości w trybie RGBA.
Rozdział 19. » Grafika interaktywna 599
Tabela 19.1. Typy danych w buforze sprzężenia zwrotnego
|
||||
Typ
|
Współrzędne wierzchołka
|
Dane koloru
|
Dane tekstury
|
Łączna ilość wartości
|
GL_2D
|
", y
|
-
|
-
|
2
|
GL_3D
|
x, y, z
|
-
|
-
|
3
|
GL_3D_COLOR
|
x, y, z
|
C
|
-
|
3 + C
|
GLJD_COLOR_TEXTURE
|
x, y, z
|
C
|
4
|
7 + C
|
GL_4D_COLOR_TEXTURE
|
x, y, z, w
|
C
|
4
|
8 + C
|
Dane sprzężenia zwrotnego
Bufor sprzężenia zwrotnego zawiera listę elementów, po których następują dane wierzchołków i ewentualnie koloru i tekstury. Możesz przetwarzać te elementy (tabela 19.2) w celu wyznaczenia rodzajów prymitywów, które miałyby zostać wyrenderowane.
Tabela 19.2.
Elementy bufora sprzężenia zwrotnego
Element Prymityw
GL_POINT_TOKEN Punkty
GL_LINE_TOKEN Linie
GL_LINE_RESET_TOKEN Segment linii po wyzerowaniu wzorca linii
GL_POLYGON_TOKEN Wielokąt
GL_BITMAP_TOKEN Bitmapa
GL_DRAW_PIXEL_TOKEN Narysowany prostokąt piksela
GL_COPYJ?IXEL_TOKEN Skopiowany prostokąt piksela
GL_PASS_THROUGH_TOKEN Znacznik zdefiniowany przez użytkownika
Po elementach punkt, bitmapa i piksel występują dane pojedynczego wierzchołka oraz ewentualnie dane koloru i tekstury. Zależy to od rodzaju danych z tabeli 19.1, wskazanych w wywołaniu funkcji glFeedbackBuffer. W przypadku linii zwracane są dwa zestawy danych wierzchołków, zaś bezpośrednio po elemencie wielokąta występuje wartość określająca ilość wierzchołków wielokąta. Po znaczniku definiowanym przez użytkownika (GL_PASS_THROUGH_TOKEN) występuje pojedyncza wartość zmiennoprzecin-kowa zdefiniowana przez użytkownika. Rysunek 19.5 przedstawia przykład zawartości bufora sprzężenia zwrotnego po wybraniu danych typu GL_3D.
600
Część III » Tematy zaawansowane i efekty specjalne
Rysunek 19.5.
Przykład zawartości bufora sprzężenia zwrotnego
Bufor sprzężenia zwrotnego
- GL POINTJOKEN
[I] - Współrzędna x
[2] -Współrzędnay
[3] -Współrzędna;
[4] - GL_PASS_THROUGH_TOKEN
[5] - Wartość zdefiniowana przez użytkownika
[6] • GL_POLYGON_TOKEN
[7] • Ilość wierzchołków wielokąta
[8] - Współrzędna x pierwszego wierzchołka
[9] • Współrzędna y pierwszego wierzchołka
[10] - Współrzędna z pierwszego wierzchołka
[II] - Współrzędna x drugiego wierzchołka
[n] - Współrzędna z ostatniego wierzchołka
Znaczniki użytkownika
Podczas wykonywania kodu renderowania, bufor sprzężenia zwrotnego jest wypełniany elementami i danymi wierzchołków dla każdego podanego prymitywu. Podobnie jak w trybie selekcji, możesz zaznaczać poszczególne prymitywy nadając im nazwy. W trybie sprzężenia zwrotnego możesz także ustawiać znaczniki pomiędzy prymitywami. Służy do tego funkcja glPassThrough:
void glPassThrough(GLfloat token);
Ta funkcja umieszcza element GL_PASS_THROUGH_TOKEN w buforze selekcji, a bezpośrednio po nim wartość podaną przy wywoływaniu funkcji. Przypomina to nieco nadawanie nazw prymitywom w trybie selekcji. Jest to jedyny sposób oznaczania obiektów w buforze sprzężenia zwrotnego.
Przykład
Doskonałym wykorzystaniem sprzężenia zwrotnego jest uzyskiwanie informacji o współrzędnych okna dotyczących renderowanych obiektów. Otrzymane informacje możesz wykorzystać w celu umieszczenia kontrolek w pobliżu obiektów lub w celu zintegrowania grafiki GDI z obiektami w oknie.
Aby zademonstrować sprzężenie zwrotne, użyjemy także selekcji w celu wyznaczenia, na który z dwóch obiektów na ekranie nastąpiło kliknięcie. Następnie przejdziemy do trybu sprzężenia zwrotnego i ponownie wyrenderujemy scenę w celu uzyskania informacji o wierzchołkach we współrzędnych okna. Używając tych danych wyznaczymy
Rozdział 19. » Grafika interaktywna_______________________________601
minimalną i maksymalną wartość współrzędnej obiektu, które wykorzystamy do narysowania prostokąta selekcji obiektu. W wyniku otrzymamy sposób graficznej selekcji jednego lub obu obiektów.
Nadawanie obiektom etykiet
na potrzeby sprzężenia zwrotnego
Na listingu 19.6 został przedstawiony kod renderujący z naszego przykładowego programu SELECT. Nie myl go z demonstracją trybu selekcji! Choć w tym fragmencie wykorzystujemy tryb selekcji do wybrania obiektu w oknie, jednak naszym celem jest uzyskanie informacji o obiekcie - poprzez sprzężenie zwrotne - wystarczających do narysowania za pomocą funkcji GDI prostokąta otaczającego obiekt na ekranie. Zwróć uwagę na użycie funkcji glPassThrough - w celu nadania etykiet obiektom w buforze sprzężenia zwrotnego - tuż po wywołaniach funkcji glLoadName, nadającej etykiety obiektom w buforze selekcji.
Listing 19.6. Kod renderujący z przykładowego programu SELECT_____________________
#define CUBE l
#define SPHERE 2
// Wywoływane w celu narysowania sceny
void RenderScene(void)
{
// Wyczyszczenie okna bieżącym kolorem tła glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Zachowanie stanu macierzy i wykonanie obrotu glMatrixMode(GL_MODELVIEW); glPushMatrix();
// Przekształcenie całej sceny do układu obserwatora glTranslatef(-80.Of, O.Of, -300.Of);
// Inicjowanie stosu nazw gllnitNames(); glPushName(0);
// Ustawienie koloru materiału na żółty // Kostka
glRGB(255, 255, 0); glLoadName(CUBE); glPassThrough((GLfloat)CUBE); auxSolidCube(75.Of);
// Rysowanie kuli g!RGB(128,0,0) ;
glTranslatef(130.Of, O.Of, O.Of); glLoadName(SPHERE); glPassThrough((GLfloat)SPHERE); auxSolidSphere(50.Of);
602
Część III » Tematy zaawansowane i efekty specjalne
// Odtworzenie stanu macierzy glPopMatrix(); // Macierz widoku modelu
// Zrzucenie poleceń graficznych glFlushf) ;
Krok ±: wybranie obiektu
Rysunek 19.6 przedstawia wynik działania kodu renderującego, wyświetlającego kostkę i kulę. Gdy użytkownik kliknie na jednen z obiektów, jest wywoływana funkcja Pro-cessSelection (listing 19.7). Ten kod jest bardzo podobny do kodu selekcji z poprzednich dwóch przykładów.
Rysunek 19.6.
Wynik działania programu SELECT po kliknięciu na kostkę
Listing 19.7. Przetwarzanie selekcji w programie SELECT
/l Przetwarzanie selekcji, wywoływane w momencie kliknięcia
// myszką w miejscu o współrzędnych (xPos, yPos)
#define BUFFER_LENGTH 64
void ProcessSelection(int xPos, int yPos)
{
// Miejsce na bufor selekcji
GLuint selectBuff[BUFFER_LENGTH];
// Licznik trafień i miejsce na widok GLint hits, viewport[4];
// Przygotowanie bufora selekcji glSelectBuffer(BUFFER_LENGTH, selectBuff);
// Pobranie widoku glGet!ntegerv(GL_VIEWPORT, viewport);
// Przejście do macierzy rzutowania i zachowanie jej glMatrixMode(GL_PROJECTION); glPushMatrix();
Rozdział 19. » Grafika interaktywna_______________________________603
// Zmiana trybu renderowania glRenderMode(GL_SELECT);
// Ustanowienie nowej bryły widzenia jako jednostkowej kostki
// dookoła punktu wskaźnika myszy (xPos, yPos), rozciągającej się
// na dwa piksele w poziomie i w pionie.
// Ponieważ OpenGL mierzy współrzędne okna od dołu, zaś Windows od
// góry,musimy to uwzględnić odejmując współrzędną y od góry okna.
// Daje to efekt odwrócenia układu współrzędnych (y zaczyna się
// u góry).
glLoadldentity();
gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport);
// Zastosowanie macierzy perspektywy gluPerspective(60.0f, fAspect, 1.0, 425.0);
// Wyrysowanie sceny RenderScene();
// Zliczenie trafień
hits = glRenderMode(GL_RENDER);
// Odtworzenie macierzy rzutowania glMatrixMode(GL_PROJECTION); glPopMatrix();
// Powrót do widoku modelu w celu normalnego renderowania glMatrixMode(GL_MODELVIEW);
// Jeśli wystąpiło pojedyncze trafienie, wyświetlenie informacji if(hits == 1)
MakeSelection(selectBuff[3]);
Krok 2: pobieranie informacji o narysowanych obiektach
Gdy wyznaczyliśmy już, na który obiekt nastąpiło kliknięcie, możemy przygotować bufor sprzężenia zwrotnego i ponownie wyrenderować scenę w tym trybie. Listing 19.8 przedstawia kod przygotowujący tryb sprzężenia zwrotnego i wywołujący funkcję RenderScene w celu ponownego wyrysowania sceny. Tym razem jednak, za pomocą funkcji glPassThrough, w buforze sprzężenia zwrotnego zostaną umieszczone znaczniki poszczególnych obiektów.
Listing 19.8. Ładowanie i przetwarzanie bufora sprzężenia zwrotnego_____________________
// Przejście do trybu sprzężenia zwrotnego i wyrysowanie
// prostokąta dookoła obiektu
#define FEED_BOFF_SIZE 4096
void MakeSelection(int nChoice)
{
// Miejsce na bufor sprzężenia zwrotnego
GLfloat feedBackBuff[FEED_BUFF_SIZE];
// Miejsce na liczniki itd. int size,i,j,count;
604 _______________________ Część III » Tematy zaawansowane i efekty specjalne
// Minimalne i maksymalne wartości x i y dla współrz. 2D
// wierzchołków
float nMaxX,nMaxY,nMinX,nMinY;
// Początkowe minimalne i maksymalne wartości nMaxX = nMaxY = -999999. Of; nMinK = nMinY = 999999. Of;
// Przygotowanie bufora sprzężenia zwrotnego glFeedbackBuffer (FEED_BUFF_SIZE,GL_2D, f eedBackBuf f ) ;
// Przejście do trybu sprzężenia zwrotnego glRenderMode (GL_FEEDBACK) ;
// Wyrysowanie sceny RenderScene ( ) ;
// Wyjście z trybu sprzężenia zwrotnego size = glRenderMode (GL_RENDER) ;
// Przetwarzanie bufora sprzężenia zwrotnego // w celu wyznaczenia min. i maks. współrzędnych // ekranowych (x, y) obiektu i = 0;
whilefi < FEED_BUFF_SIZE) {
// Wyszukanie odpowiedniego znacznika if ( f eedBackBuf f [i] == GL_PASS_THROUGH_TOKEN) if (feedBackBuff [i+1] == (GLfloat)nChoice) {
i+= 2;
// Praca w pętli aż do natrafienia na następny element while (feedBackBuff [i] != GL_PASS_THROUGH_TOKEN) {
// Po prostu wielokąty
if (feedBackBuff [i] == GL_POLYGON_TOKEN) {
// Pominięcie wszystkich wartości dla wielokąta count = (int) feedBackBuff [++i] ; // Ilość
// wierzchołków i++;
// Pętla dla każdego wierzchołka
for (j = 0; j < count; j++) {
// Minimalna i maksymalna współrzędna X if (feedBackBuff [i] > nMaxX) nMaxX = feedBackBuff [i] ;
if (feedBackBuff [i] < nMinX) nMinX = feedBackBuff [i] ;
// Minimalna i maksymalna współrzędna Y if (feedBackBuff [i] > nMaxY) nMaxY = feedBackBuff [i] ;
Rozdział 19. » Grafika interaktywna _______________________________ 605
if (feedBackBuff [i] < nMinY) nMinY = feedBackBuff [i] ;
else
i++; // Przejście do następnego indeksu
break;
// Narysowanie prostokąta selekcji
HighLight ( (int) floor (nMinX+0. 5) , (int) f loor (nMinY+0 . 5) , (int) floor (nMaxX+0.5) , (int) f loor (nMaxY+0 . 5) ) ;
Po wypełnieniu bufora sprzężenia zwrotnego, wyszukujemy element GL_PASS_ THROUGHJTOKEN. Gdy go znajdziemy, pobieramy następną wartość w celu sprawdzenia, czy właśnie tego obiektu szukamy. Jeśli tak, jedyne, co pozostało, to przejście przez wszystkie wielokąty tego obiektu i wyznaczenie minimalnych i maksymalnych współrzędnych ekranowych x i y. Funkcja HighLight wykorzystuje funkcję Win32 DrawFocusRect w celu wyrysowania prostokąta dookoła obiektu, na który nastąpiło kliknięcie. Ta funkcja używa trybu rysowania XOR, więc jej kolejne wywołanie powoduje usuniecie prostokąta selekcji. Dzięki temu możesz zaznaczyć obiekt klikając na niego i usunąć zaznaczenie klikając ponownie.
Podsumowanie
Selekcja i sprzężenie zwrotne to dwa bardzo użyteczne elementy OpenGL, umożliwiające interakcję użytkownika ze sceną. Selekcja i wybór są używane przy identyfikacji obiektu lub regionu sceny we współrzędnych OpenGL, a nie we współrzędnych okna. Sprzężenie zwrotne zwraca cenne informacje dotyczące położenia rysowanych prymitywów we współrzędnych okna. Możesz użyć tych informacji w celu zintegrowania grafiki OpenGL z grafiką GDI w Windows.
Podręcznik
glFeedbackBuffer
Przeznaczenie Przygotowuje bufor sprzężenia zwrotnego.
Plik nagłówkowy <gl.h>
606
Część III » Tematy zaawansowane i efekty specjalne
Składnia Opis
void glFeedbackbuffer(GLsizei size, GLenum type, GLfloat *buffer);
Ta funkcja przygotowuje bufor sprzężenia zwrotnego dla danych wskazanego typu. Sprzężenie zwrotne jest trybem renderowania; zamiast renderować do bufora ramki, w trybie sprzężenia zwrotnego OpenGL umieszcza dane wierzchołków we wskazanym buforze. Umieszczane bloki danych mogą zawierać współrzędne ekranowe x, y, z oraz w wierzchołków, a także dane koloru i tekstury. Rodzaj informacji umieszczanych w buforze zależy od parametru type.
Parametry
stze
type
buffer
GLsizei: Maksymalna ilość pozycji zaalokowanych w buforze *buffer. Jeśli blok zapisywanych do bufora danych przekroczył objętość bufora, zostanie zapisana jedynie ta część danych, która zmieści się w buforze.
GLenum: Określa rodzaj danych o wierzchołkach, umieszczanych w buforze sprzężenia zwrotnego. Dla każdego wierzchołka, w buforze umieszczany jest osobny blok danych. Dla każdego z poniższych typów, blok danych zawiera element wyznaczający rodzaj prymitywu, po którym następują dane wierzchołków. Dane wierzchołków mogą zawierać:
GL_2D: parę współrzędnych x i y. GLJ3D: trójkę współrzędnych x, y i z.
GL_3D_COLOR: współrzędne x, y i z oraz dane koloru (jedna wartość dla trybu indeksu koloru, cztery wartości dla trybu RGBA).
GL_3D_COLOR_TEXTURE: współrzędne x, y i z oraz dane koloru (jedna lub cztery wartości), a także cztery współrzędne tekstury.
GL_4D_COLOR_TEXTURE: współrzędne x, y, z i w oraz dane koloru (jedna lub cztery wartości), a także cztery współrzędne tekstury.
GLfloat*: Wskaźnik do bufora sprzężenia zwrotnego.
Zwracana wartość Brak.
Przykład
Poniższy kod z programu SELECT inicjuje bufor sprzężenia zwrotnego funkcjąglFeedbackBuffer, przełącza się do trybu sprzężenia zwrotnego, renderuje scenę, po czym wypełnia bufor przełączając się ponownie do standardowego trybu renderowania.
#define FEED BUFF SIZE 8192
// Miejsce na bufor sprzężenia zwrotnego GLfloat feedBackBuff[FEED_BUFF SIZE];
// Przygotowanie bufora sprzężenia zwrotnego glFeedbackBuffer(FEED_B0FF_SIZE,GL_2D, feedBackBuff};
Rozdział 19. » Grafika interaktywna_______________________________607
// Przejście do trybu sprzężenia zwrotnego glRenderMode(GL_FEEDBACK);
// Wyrysowanie sceny RenderScene O;
// Wyjście z trybu sprzężenia zwrotnego size = glRenderMode(GL_RENDER);
Patrz także glPassThrough, glRenderMode, glSelectBuffer
gllnitNames_______________________
Przeznaczenie Przygotowuje bufor sprzężenia zwrotnego.
Plik nagłówkowy <gl.h>
Składnia void glInitNames(void);
Opis Stos nazw jest używany w celu umożliwienia rysowania prymitywów lub
grup prymitywów o nazwach nadanych podczas renderowania w trybie
selekcji. Za każdym razem gdy prymitywowi zostaje nadana nazwa, jest
ona odkładana na stos nazw funkcjąglPushName lub zastępuje aktualną
nazwę na szczycie stosu, za pomocą funkcji glLoadName. Funkcja
gllnitNames zeruje stos nazw, usuwając z niego wszystkie występujące
nazwy.
Zwracana wartość Brak.
Przykład Poniższy kod pochodzi z programu PLANETS. Inicjuje stos nazw
i umieszcza na nim pojedynczą wartość.
// Inicjowanie stosu nazw gllnitNames O; glPushName(0);
Patrz także gllnitNames, glPushName, glRenderMode, glSelectBuffer
glLoadName________________________
Przeznaczenie Umieszcza nazwę na szczycie stosu nazw.
Plik nagłówkowy <gl.h>
Składnia void glLoadName(GLuint name);
Opis Ta funkcja umieszcza podaną nazwę na szczycie stosu nazw. Stos nazw
jest używany do nazywania prymitywów lub grup prymitywów podczas
renderowania w trybie selekcji. Podana nazwa zastępuje aktualną nazwę
na szczycie stosu.
608
Część III * Tematy zaawansowane i efekty specjalne
Parametry name
Zwracana wartość Przykład
Patrz także
GLuint: Nazwa, która ma zostać umieszczona na szczycie stosu nazw. Nazwy selekcji są liczbami całkowitymi bez znaku.
Brak.
Poniższy kod z programu PLANETS przedstawia ładowanie nazwy na stos nazw, tuż przed przystąpieniem do renderowania obiektu.
// Ustawienie koloru materiału na żółty // Słońce
glRGB(255, 255, 0); glLoadName(SUN); auxSolidSphere(15.Of);
gllnitNames, glPushName, glRenderMode, glSelectBuffer
gIPassThrough
Przeznaczenie Plik nagłówkowy Składnia Opis
Umieszcza znacznik w buforze sprzężenia zwrotnego.
void glPassThrough(GLfloat token);
Gdy OpenGL zostanie przełączony do trybu sprzężenia zwrotnego, w buforze ramki nie są rysowane żadne piksele. Zamiast tego w buforze sprzężenia zwrotnego jest umieszczana informacja o rysowanych prymitywach. Ta funkcja umożliwia umieszczenie w buforze elementu GL_PASS_THROUGH_TOKEN, a zaraz po nim wskazanej zmiennoprzecinkowej wartości. Funkcja jest wywoływana w kodzie renderującym i poza trybem sprzężenia zwrotnego nie wywołuje żadnego efektu.
Parametry token
GLfloat: Wartość, która ma zostać umieszczona w buforze sprzężenia zwrotnego bezpośrednio po elemencie GL_PASS_THROUGH_TOKEN.
Zwracana wartość Brak.
Przykład
Patrz także
Poniższy kod z programu SELECT demonstruje łączne użycie funkcji gIPassThrough i glLoadName w celu zidentyfikowania obiektu. W ten sposób obiekt jest zaznaczany zarówno w buforze selekcji, jak i w buforze sprzężenia zwrotnego.
// Ustawienie koloru materiału na żółty // Kostka
glRGB(255, 255, 0) ; glLoadName(CUBE); gIPassThrough((GLfloat)CUBE); auxSolidCube(75.0f);
glFeedbackBuffer, glRenderMode
609
Rozdział 19. «• Grafika interaktywna
gIPopName
Przeznaczenie Plik nagłówkowy Składnia Opis
Zwracana wartość Przykład
Patrz także
Zdejmuje (usuwa) nazwę ze szczytu stosu nazw.
void glPopName(void);
Stos nazw jest używany podczas selekcji w celu identyfikacji rysowanych obiektów. Ta funkcja powoduje usunięcie nazwy ze szczytu stosu nazw. Bieżącą wysokość stosu nazw można odczytać wywołując funkcję glGet z parametrem GL_NAME_STACK_DEPTH.
Brak.
Poniższy kod z programu MOONS umieszcza na stosie nazw nazwę planety oraz nazwy jej księżyców. Ten kod ilustruje zwłaszcza zdejmowanie ze stosu nazwy pierwszego księżyca przed umieszczeniem na nim nazwy następnego księżyca.
// Rysowanie Marsa
glRGB(255,0,0) ;
glPushMatrix ( ) ;
glTranslatef (lOO.Of, O.Of, O.Of);
glLoadName (MARS) ;
auxSolidSphere (20. Of) ;
// Rysowanie pierwszego księżyca glTranslatef (-40. Of, 40. Of, O.Of); glRGB(220,220,220) ; glPushName (MOON1) ; auxSolidSphere (5.0f ) ; gIPopName ( ) ;
// Rysowanie drugiego księżyca glTranslatef (O.Of, -80. Of, O.Of); glPushName (MOON2) ; auxSolidSphere (5 . Of ) ; gIPopName ( ) ; glPopMatrix ( ) ;
gllnitNames, glLoadName, glRenderMode, glSelectBuffer, glPushName
glPushName
Przeznaczenie Plik nagłówkowy Składnia Opis
Odkłada nazwę na szczyt stosu nazw.
void glPushName(GLuint name);
Stos nazw jest używany do nazywania prymitywów lub grup prymitywów podczas renderowania w trybie selekcji.
Ta funkcja odkłada podaną nazwę na szczyt stosu nazw, w celu zidentyfikowania następnych prymitywów rysowanych poleceniami graficznymi. Bieżącą wysokość stosu nazw można odczytać wywołując
610
Część III » Tematy zaawansowane i efekty specjalne
funkcję glGet z parametrem GL_NAME_STACK_DEPTH, zaś wysokość maksymalną- wywołując tę funkcję z parametrem GL_MAX_NAME_STACK_DEPTH. Maksymalna wysokość stosu nazw zależy od implementacji, jednak wszystkie implementacje muszą obsługiwać stos o wysokości co najmniej 64 pozycji.
Parametry
name
GLuint: Nazwa, która ma zostać odłożona na szczyt stosu nazw. Nazwy selekcji są liczbami całkowitymi bez znaku.
Zwracana wartość Brak.
Przykład
Patrz także
Poniższy kod z programu MOONS umieszcza na stosie nazw nazwę planety oraz nazwy jej księżyców. Ten kod ilustruje zwłaszcza odkładanie na stos nazw księżyców, po odłożeniu na niego nazwy planety. Nazwa pierwszego księżyca jest zdejmowana ze stosu przed umieszczeniem na nim nazwy następnego księżyca.
// Rysowanie Marsa
glRGB(255,0,0) ;
glPushMatrix() ;
glTranslatef(lOO.Of, O.Of, O.Of);
glLoadName(MARS);
auxSolidSphere(20. Of) ;
// Rysowanie pierwszego księżyca glTranslatef(-40.Of, 40.Of, O.Of); glRGB(220,220,220) ; glPushName(MOON1) ; auxSolidSphere(5.0f) ; glPopName();
// Rysowanie drugiego księżyca glTranslatef(O.Of, -80.Of, O.Of); glPushName(MOON2); auxSolidSphere(5.0f) ; glPopName(); glPopMatrix();
gllnitNames, glLoadName, glRenderMode, glSelectBuffer, glPopName
glRenderMode
Przeznaczenie Plik nagłówkowy Składnia Opis
Przełącza OpenGL w jeden z trzech trybów renderowania.
<gl.h>
GLint glRenderMode(GLenum modę);
OpenGL działa w jednym z trzech trybów renderowania:
GL_RENDER: Tryb rysowania (domyślny). Efekt wywołania funkcji rysunkowych umieszczany jest w buforze ramki.
GL_SELECT: Tryb selekcji. W tym trybie w buforze ramki nie są dokonywane żadne zmiany. Zamiast tego do bufora selekcji zapisywane
611
Rozdział 19. » Grafika interaktywna
są rekordy trafień, odpowiadające tym prymitywom, które po narysowaniu przecinaj ą bryłę widzenia. Bufor selekcji musi być wcześniej zaalokowany i przygotowany za pomocą funkcji glSelectBuffer.
GL_FEEDBACK: Tryb sprzężenia zwrotnego. W tym trybie w buforze ramki nie są dokonywane żadne zmiany. Zamiast tego do bufora sprzężenia zwrotnego są zapisywane informacje dotyczące wierzchołków, które zostałyby narysowane w oknie. Bufor sprzężenia zwrotnego musi być wcześniej zaalokowany i przygotowany za pomocą funkcji glFeedbackBuffer.
Parametry modę
Zwracana wartość
Przykład
GLenum: Określa tryb rasteryzacji. Może to być tryb GL_RENDER, GL_SELECT lub GL_FEEDBACK. Domyślnym trybem jest GL_RENDER.
Zwracana wartość zależy od trybu rasteryzacji, jaki został ostatnio wybrany:
GL_RENDER: Zero.
GL_SELECT: Ilość rekordów trafień zapisanych do bufora selekcji.
GL_FEEDBACK: Ilość wartości zapisanych do bufora sprzężenia zwrotnego. Zwróć uwagę, że ta liczba nie oznacza ilości skopiowanych wierzchołków.
Poniższy kod przedstawia sposób wywołania funkcji glRenderMode w celu przejścia do trybu selekcji w programie PLANETS. Ta funkcja jest wywoływana ponownie z argumentem GL_RENDERER w celu powrotu do trybu rysowania oraz skopiowania rekordów trafień do bufora selekcji.
// Przetwarzanie selekcji, wywoływane w momencie
// kliknięcia myszką w miejsce o współrzędnych (xPos, yPos)
łdefine BUFFER_LENGTH 64
void ProcessSelection(int xPos, int yPos)
{
// Miejsce na bufor selekcji
GLuint selectBuff[BUFFER_LENGTH];
// Licznik trafień i miejsce na widok GLint hits, viewport[4];
// Przygotowanie bufora selekcji glSelectBuffer(BUFFER_LENGTH, selectBuff);
// Pobranie widoku glGet!ntegerv(GL_VIEWPORT, viewport);
// Przejście do macierzy rzutowania i zachowanie jej glMatrixMode(GL_PROJECTION); glPushMatrix();
// Zmiana trybu renderowania glRenderMode(GL SELECT);
612______________________Część III » Tematy zaawansowane i efekty specjalne
// Ustanowienie nowej bryły widzenia jako
// jednostkowej kostki dookoła punktu wskaźnika // myszy (xPos, yPos), rozciągającej się // na dwa piksele w poziomie i w pionie. glLoadldentity();
gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport);
// Zastosowanie macierzy perspektywy gluPerspective(60.Of, fAspect, 1.0, 425.0);
// Wyrysowanie sceny RenderScene();
// Zliczenie trafień
hits = glRenderMode(GL_RENDER);
// Odtworzenie macierzy rzutowania glMatrixMode(GL_PROJECTION); glPopMatrix();
// Powrót do widoku modelu w celu // normalnego renderowania glMatrixMode(GL_MODELVIEW);
// Jeśli wystąpiło pojedyncze trafienie, // wyświetlenie informacji iflhits == 1)
MakeSelection(selectBuff[3]}; }
Patrz także glFeedbackBuffer, gllnitNames, glLoadName, glPassThrough,
glSelectBuffer, glPushName
gISelectBuffer
Przeznaczenie Ustawia bufor używany w trybie selekcji.
Plik nagłówkowy <gl.h>
Składnia void glSelectBuffer(GLsizei size, GLuint *buffer);
Opis Gdy OpenGL działa w trybie selekcji (GL_SELECT), polecenia
rysunkowe nie powodują rysowania pikseli w buforze ramki. Zamiast tego generuj ą rekordy trafień, które są umieszczane w buforze selekcji, przygotowanym przez tę funkcję. Każdy rekord trafienia składa się z następujących danych:
Ilości nazw na stosie nazw w momencie wystąpienia trafienia.
Minimalnej i maksymalnej wartości Z wszystkich wierzchołków prymitywów przecinających bryłę widzenia. Te współrzędne są skalowane do rozmiaru liczby całkowitej bez znaku (232 - 1).
Zawartości stosu nazw w momencie trafienia, poczynając od najgłębiej położonego elementu.
613
Rozdział 19. » Grafika interaktywna
Parametry
size
buffer
Zwracana wartość Przykład
GLsize: Rozmiar bufora wskazywanego przez *buffer. GLuint*: Wskaźnik do zaalokowanego obszaru pamięci. Brak.
Poniższy kod przedstawia tworzenie bufora selekcji w programie PLANETS.
// Przetwarzanie selekcji, wywoływane w momencie kliknięcia
// myszką w miejscu o współrzędnych (xPos, yPos)
łtdefine BUFFER_LENGTH 64
void ProcessSelection(int xPos, int yPos)
{
// Miejsce na bufor selekcji
GLuint selectBuff[BUFFER LENGTH];
Patrz także
// Przygotowanie bufora selekcji glSelectBuffer(BUFFER_LENGTH, selectBuff);
glFeedbackBuffer, gllnitNames, glLoadName, glPushName, glRenderMode
gluPickMatrix
Przeznaczenie
Plik nagłówkowy Składnia
Opis
Definiuje obszar selekcji, który może zostać użyty w celu wyznaczenia wyboru dokonanego przez użytkownika.
<glu.h>
void gluPickMatrix(GLdouble x, GLdouble y, GLdouble width, GLdouble height, GLint viewport[4]);
Ta funkcja tworzy macierz, która na podstawie współrzędnychekranu definiuje mniejszą bryłę widzenia w oparciu o współrzędne ekranu, przeznaczoną do wyboru obiektów. Używając w tej funkcji współrzędnych myszy w trybie selekcji, możesz wyznaczyć, które obiekty są wskazywane myszką. Tworzona macierz jest mnożona przez bieżącą macierz rzutowania. Zwykle powinieneś więc wywołać funkcję glLoadldentity przed wywołaniem tej funkcji, następnie przemnożyć ją przez macierz perspektywy użytej do stworzenia oryginalnej bryły widzenia. Jeśli używasz funkcji gluPickMatrix w celu wybrania powierzchni NURBS, przed zastosowaniem tej funkcji musisz wyłączyć właściwość NURBS GLU AUTO LOAD MATRIX.
Parametry
width, height
GLdouble: Środek regionu wybierania we współrzędnych okna.
GLdouble: Szerokość i wysokość żądanego obszaru wybierania w pikselach.
614
Część III » Tematy zaawansowane i efekty specjalne
viewport
GLint[4]: Bieżący widok. Możesz odczytać parametry bieżącego widoku za pomocą funkcji glGet!ntegerv z parametrem GL_VIEWPORT.
Zwracana wartość Brak.
Przykład
Patrz także
Poniższy kod pochodzi z przykładowego programu PLANETS. Używa tej funkcji do utworzenia nowej bryły widzenia pokrywającej obszar okna o wymiarach jedynie 2x2 piksele, wyśrodkowanego w miejscu kliknięcia myszką. Ta bryła jest używana do wybrania obiektu znajdującego się bezpośrednio pod wskaźnikiem myszy.
// Licznik trafień i miejsce na widok GLint hits, viewport[4];
// Przygotowanie bufora selekcji glSelectBuffer(BUFFER_LENGTH, selectBuff);
// Pobranie widoku glGetIntegerv(GL_VIEWPORT, viewport);
// Przejście do macierzy rzutowania i zachowanie jej glMatrixMode(GL_PROJECTION); glPushMatrix();
// Zmiana trybu renderowania glRenderMode(GL_SELECT);
// Ustanowienie nowej bryły widzenia jako jednostkowej // kostki dookoła punktu wskaźnika myszy (xPos, yPos), // rozciągającej się na dwa piksele w poziomie // i w pionie. glLoadldentity();
gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport);
// Zastosowanie macierzy perspektywy gluPerspective(45.0f, fAspect, 1.0, 425.0);
// Wyrysowanie sceny RenderScene ();
// Zliczenie trafień
hits = glRenderMode(GL_RENDER);
// Jeśli wystąpiło pojedyncze trafienie, // wyświetlenie informacji iffhits == 1)
ProcessPlanet(selectBuff[3]);
// Odtworzenie macierzy rzutowania glMatrixMode(GL_PROJECTION); glPopMatrix();
// Powrót do widoku modelu w celu // normalnego renderowania glMatrixMode(GL_MODELVIEW);
glGet, glLoadldentity, glMultMatrix, glRenderMode, gluPerspective
Rozdział 20.
OpenGL w Sieci: VRML
OpenGL ma wiele zastosowań. W tym rozdziale omówimy jedno z nich, które stało się ostatnio bardzo popularne: rzeczywistość wirtualną.
OpenGL doskonale nadaje się do pełnienia wielu funkcji związanych z modelowaniem i grafiką, stanowi także technologię dobrze przygotowaną do celów rzeczywistości wirtualnej. Te komputerowo generowane „światy", w których można zanurzyć wiele zmysłów użytkownika, zawierają trójwymiarowe sceny, łącznie z dźwiękami, a czasem nawet reakcją zwrotną poprzez siłowniki. Niektóre produkty oferują technologię tak zaawansowaną, że obejmuje rękawice z siłownikami, trójwymiarowe gogle oraz oprogramowanie w stylu trójwymiarowej gry komputerowej, umożliwiającej poruszanie się we wszystkich kierunkach cyfrowego świata.
Internet, będąc o wiele dojrzalszą technologią niż rzeczywistość wirtualna, ostatnio staje się coraz bardziej popularnym polem działania dla entuzjastów komputerów - nie wspominając oczywiście o profesjonalnych, naukowych i militarnych użytkownikach, dla których był pierwotnie przeznaczony. Obecnie niewiele już chyba osób nie słyszało terminu cyberprzestrzeń - oznaczającego osobny świat komputerów w sieci, w którym można odwiedzać wiele miejsc i spotykać wiele osób - zaś chyba każdemu użytkownikowi komputera zdarzyło się wędrować po tej sieci i wyszukiwać dostępne w niej informacje.
W tym rozdziale omówimy pokrótce implementację rzeczywistości wirtualnej w Inter-necie, której początki wywodzą się właśnie z OpenGL. Zakładamy przy tym, że posiadasz już pewną znajomość zagadnień Internetu, sieci WWW (czyli World Wide Web lub po prostu Sieci) oraz przeglądarek WWW, takich jak Internet Explorer czy Netscape Navigator.
Na styku dwóch światów
Niezbyt długo trwało, zanim ktoś dostrzegł ścisłe powiązania pomiędzy cyberprzestrze-nią a rzeczywistością wirtualną. W cyberprzestrzeni możesz podróżować po świecie,
616
Część III » Tematy zaawansowane i efekty specjalne
odwiedzając różne miejsca (strony WWW) i zbierając różne rodzaje informacji. Byłoby więc sensowne, gdyby można to było robić w jakimś wizualnym środowisku, a nie tylko poprzez serię trudnych do przebrnięcia tekstowych opisów.
Graficzna nawigacja w Internecie pierwszy raz pojawiła się, gdy Tim Berners-Lee z CERN (europejskie centrum fizyki nuklearnej) w Genewie opracował zestaw protokołów umożliwiających łatwe zakodowanie połączenia pomiędzy różnymi plikami zawartymi w archiwach FTP. Te połączenia wiązały ze sobą dokumenty z innymi dokumentami o powiązanej treści, umożliwiając przechodzenie z dokumentu do dokumentu, i to nawet w innej kartotece, w innym komputerze czy na innym kontynencie. W tych protokołach zostały wykorzystane nazwy URL (Universal Resource Locator), identyfikujące położenie dokumentów i stanowiące podstawę sieci WWW.
Wkrótce po tym, Marc Andersen (który później założył Netscape Communications Corporation) stworzył przeglądarkę Sieci, która mogła łączyć różne rodzaje plików, łącznie z tekstem i grafiką, w pojedynczą prezentację. Tą przeglądarką był NCSA Mosaic, który mógł reprezentować powiązania pomiędzy dokumentami, a także wykorzystywać specjalny język definiujący format dokumentów z osadzonymi obrazkami i różnymi rodzajami tekstu. Od tego momentu Internet nie był już taki jak przedtem. W ciągu niecałego roku, Internet z wyłącznej domeny profesjonalistów przekształcił się w dostępny dla każdego wirtualny świat, umożliwiający dotarcie do każdego miejsca po kilku kli-knięciach myszką.
Dwuwymiarowa nawigacja
Rysunek 20.1.
Typowa strona WWW z łączami hipertekstowymi
Strony WWW to najczęściej dokumenty tekstowe stworzone w specjalnym języku nazywanym HTML (HyperText Markup Language). W dokumentach HTML można osadzać inne dokumenty, grafikę, również wideo i dźwięk, a także hipertekstowe łącza do innych dokumentów i serwerów WWW. Rysunek 20. l przedstawia typową stronę głó-
Rozdział 20. » OpenGL w Sieci: VRML_______________________________617
wną WWW; pokazana strona należy do Silicon Graphics i w dużym stopniu wypełniona jest grafiką. Po kliknięciu na przycisk obok interesującego cię tematu lub na „aktywny" obszar większego obrazka, zostaniesz przeniesiony na inną stronę, zawierającą kolejne informacje oraz nowy zestaw kategorii i łącz do innych stron.
VRML
Graficzna, choć wciąż dwuwymiarowa metoda nawigacji po Sieci w ciągu kilku lat stała się niezwykle popularna. Taka „nawigacja" po cyberprzestrzeni jest bardzo efektywna i wygodna, pod warunkiem, że przeszukiwane informacje mogą być przedstawione w postaci dokumentów. Jednak świat nie jest biblioteką, zaś możliwość przedstawienia wszystkiego jako dwuwymiarowego obrazu jest w znacznym stopniu ograniczona.
W 1994 roku Mark Pesce i Tony Parisi stworzyli nowy typ dokumentu i przeglądarki WWW, która umożliwiała poruszanie się po trójwymiarowych dokumentach. Na dzień Św. Walentego 1994 roku powstał pierwszy trójwymiarowy serwer WWW. Serwer umożliwiał poruszanie się w przestrzeni 3D i klikanie na obiekty, stanowiące łącza do innych scen 3D lub zwykłych stron HTML.
Trójwymiarowe sceny były opisane za pomocą nowego języka skryptów, VRML (Vir-tual Reality Markup Language lub Yirtual Reality Modeling Language). Silicon Graphics (SGI), światowy lider w technologii grafiki komputerowej, zdecydował się na publiczne udostępnienie swojego języka opisu sceny Open Inventor, który stał się podstawą dla VRML w wersji l .0.
Słowo na temat Open Irwentora
Open Irwentor to interfejs modelowania 3D o znacznie wyższym poziomie niż samo API OpenGL. Open lnventor to biblioteka klas C++ stanowiąca nadbudowę OpenGL. Programiści i narzędzia programistyczne używają tej biblioteki przy tworzeniu złożonych scen 3D oraz obiektów, które byłyby zbyt skomplikowane, aby tworzyć je ręcznie, przy użyciu samego OpenGL. Obiekty Open lnventora (w sensie C++) posiadają cechę zwaną trwałością, która umożliwia zapisywanie i późniejsze odczytywanie ich z dysku. SGI udostępniło programistom VRML darmowy kod źródłowy, który jest używany do przetwarzania skryptów opisu scen, używanego przez Open lnventor do przechowywania w binarnej postaci scen i obiektów 3D, na informacje przydatne podczas rende-rowania obiektów sceny. Open lnventor zostanie opisany bardziej szczegółowo w dalszej części rozdziału.
Po udostępnieniu przez SGI darmowego kodu źródłowego, od kwietnia 1995 VRML stał się ulubieńcem popularnej prasy Internetowej. Na rynku pojawiły się przeglądarki VRML różnych producentów, dla wszystkich popularnych platform łącznie z komputerami osobistymi. Obecnie istniejąca technologia pozwala użytkownikom na coś więcej niż tylko wybieranie polecenie z menu. Obecnie można spacerować przez wirtualną bibliotekę, muzeum czy nawet sklep oraz wybierać i oglądać interesujące nas obiekty.
618 ____ Część III » Tematy zaawansowane i efekty specjalne
WebSpace
Oczywiście, to Silicon Graphics była pierwszą firmą, która stworzyła w pełni zgodną, komercyjnie dostępną przeglądarkę VRML. Przeglądarka nosiła nazwę WebSpace i stanowiła standard, z którym porównywane były wszystkie inne przeglądarki VRML. WebSpace została zaprojektowana do działania na własnych stacjach roboczych SGI, jednak niezależna firma, Template Graphics Software, otrzymała pozwolenie na opracowanie wersji programu dla Microsoft Windows i innych platform. Wszystkie wersje tej przeglądarki w pełni obsługuj ą standard VRML 1.0 i do renderowania scen wykorzystują OpenGL.
Instalacja
WebSpace może zostać zainstalowana jako pomocnicza aplikacja większości przeglądarek WWW. Aby uzyskać informacje na temat sposobu instalacji, zajrzyj do pliku README swojej przeglądarki. WebSpace ładuje plik VRML z rozszerzeniem .wrl, a także pliki scen Open Inventora z rozszerzeniem .iv. Dodatkowo, najnowsza wersja WebSpace z Template Graphics automatycznie ładuje pliki .wrl skompresowane jako gzip, w popularnym w Internecie formacie kompresji. Dzięki temu mamy do czynienia ze znacznie mniejszymi plikami, które można o wiele szybciej ściągnąć.
WebSpace na płytce CD-ROM
Kopia przeglądarki WebSpace dla Microsoft Windows jest dostępna także na dołączonej do książki płytce CD-ROM, w kartotece \TGS\ WebSpace. Oprogramowanie i przykładowe sceny zostały dołączone dzięki uprzejmości firm Silicon Graphics Inc. i Template Graphics Software. Te pliki są dostępne jako Shareware. Jeśli chcesz ich użyć do czegoś więcej niż do zabawy, powinieneś zarejestrować swoją kopię. Informacje na temat uzyskania licencji znajdziesz w pliku README.
Tryb spacerowy
Przeglądarka WebSpace może działać w dwóch trybach. Pierwszym z nich jest tryb spacerowy (Walk Yiewer), który pozwala na poruszanie się po prezentowanym modelu, takim jak muzeum czy model architektoniczny. Drugim trybem jest tryb oglądania (Examiner Yiewer), używany do oglądania obiektów w WebSpace, takich jak samolot, narzędzie czy element umeblowania. Wkrótce poznasz oba tryby w działaniu.
Rysunek 20.2 przedstawia przeglądarkę WebSpace podczas przeglądania przykładowej sceny VRML w trybie spacerowym. Ten tryb jest używany do poruszania się w trójwymiarowej scenie. Może nią być nieskomplikowany trójwymiarowy teren, architektoniczny widok budynku, dom towarowy czy nawet obszar małego miasta.
619
Rozdział 20. » OpenGL w Sieci: VRML
Rysunek 20.2.
Przeglądarka WebSpace działająca \v trybie spacerowym
Szczegółowe instrukcje na temat użycia programu
Ten rozdział stanowi wprowadzenie do VRML i rzeczywistości wirtualnej w Internecie. Programu WebSpace używamy jako punktu wyjścia przy demonstrowaniu podstawowych koncepcji nawigacji po trójwymiarowych scenach. Szczegółowe informacje na temat używania przeglądarki WebSpace znajdziesz w pliku README i plikach pomocy dołączonej do programu.
Niektóre obiekty w scenie mogą być połączone z innymi stronami lub dokumentami HTML, podobnie jak w zwykłej stronie WWW. Kontrolki w dolnej części okna noszą wspólną nazwę deski rozdzielczej; są używane do nawigowania w obrębie sceny. Czworokątny przycisk po lewej stronie to przycisk wyszukiwania; pomaga w szybkim przejściu do wskazanego punktu sceny. Aby go użyć, kliknij na niego przechodząc do trybu wyszukiwania, a następnie kliknij w dowolne miejsce sceny. Nawigator płynnie przejdzie we wskazane miejsce, bez konieczności użycia jakiegokolwiek innego narzędzia do nawigacji.
Przycisk po prawej stronie służy do przesuwania widoku sceny w poziomie i w pionie. Widok jest przesuwany jedynie w osiach x i y (w poziomie i w pionie). Kierunek widzenia nie jest pochylany w żadną stronę.
Wreszcie, na środku deski rozdzielczej znajduje się joystick - używany do poruszania się do przodu i do tyłu w scenie, obracania się w lewo i w prawo, a także pochylania „głowy" w dół i podnoszenia w górę. Po prostu kliknij na joystick i przeciągnij go
620
Część III » Tematy zaawansowane i efekty specjalne
w górę lub w dół, aby przejść do przodu lub do tyłu albo w lewo lub w prawo, aby obrócić się w wybranym kierunku.
Nawigacja w trzech wymiarach
Trójwymiarowy interfejs przeglądarki WebSpace pozostawia wiele do życzenia. Działa podobnie jak interfejs symulatora lotu lub gry zręcznościowej i z pewnością można by go uzupełnić o wiele przydatnych możliwości. Możesz oczekiwać, że w najbliższych latach takie interfejsy zostaną znacznie usprawnione, gdy tylko pojawi się większa liczba przeglądarek.
Po prawej stronie joysticka znajduje się czerwona gałka, służąca do pochylania głowy w dół lub podnoszenia jej w górę. Kliknij na nią i przeciągnij myszką w górę, aby podnieść głowę, lub w dół, aby spojrzeć w kierunku podłogi. Na rysunku 20.3 użyto tej gałki w celu spojrzenia w kierunku podłogi.
Rysunek 20.3.
Dzięki użyciu gałki najoysticku możemy spojrzeć w kierunku podłogi
Tryb oglądania
Tryb oglądania, jak sama nazwa wskazuje, umożliwia bliższe przyjrzenie się obiektom. Rysunek 20.4 przedstawia przeglądarkę WebSpace podczas oglądania modelu komputera. Wyobraź sobie, że wędrujesz po wirtualnym muzeum w trybie spacerowym, a następnie klikasz na mały obrazek komputera. Gdy przeglądarka przełączy się w tryb oglądania, możesz przyjrzeć się komputerowi bliżej. Oprócz tego, możesz mieć do dyspozycji inne hipertekstowe łącza prowadzące do opisu komputera, gier komputerowych itd.
621
Rozdział 20. » OpenGL w Sieci: VRML
Rysunek 20.4.
Tryb oglądania
Jak widać, deska rozdzielcza w trybie oglądania wygląda podobnie jak w trybie spacerowym, z tym że joystick został zastąpiony trackballem i pokrętłem. Pokrętło umożliwia zbliżanie się do obiektu lub oddalanie od niego. Kliknij na pokrętło i przeciągnij je w górę, aby oddalić się od obiektu, lub w dół, aby się przybliżyć. Rysunek 20.5 przedstawia model komputera z większej odległości.
Rysunek 20.5.
Model komputera w trybie oglądania, widziany z nieco większej odległości
622_______________________Część III » Tematy zaawansowane i efekty specjalne
Za pomocą trackballa możesz obracać obiekt w różnych kierunkach. Kliknij w dowolne miejsce trackballa i przeciągnij myszką obracając obiekt. Jeśli podczas przeciągania zwolnisz przycisk myszy, obiekt w dalszym ciągu będzie się obracał.
Open liwentor i VRML
Aby zrozumieć powiązania pomiędzy Open Inventorem a VRML, powinieneś zdobyć nieco więcej informacji na temat Open Inventora. Ta obiektowo zorientowana biblioteka oraz zestaw narzędzi jest zaimplementowana przy użyciu OpenGL. Biblioteka programowa prawie zawsze wykorzystywana jest z C++, lecz istnieją także elementy umożliwiające połączenie jej z C. Takie obiektowo zorientowane podejście zapewnia dużo wyższy stopień kontroli nad kompozycją obiektów i sceny.
Gdy do tworzenia sceny lub obiektu jest używany OpenGL, każda funkcja i polecenie wywiera natychmiastowy wpływ na bufor ramki. Dopóki nie używasz podwójnego buforowania, wynik każdej instrukcji jest od razu widoczny na ekranie. Nazywa się to renderowaniem w trybie natychmiastowym.
Z kolei Open Inventor operuje w tzw. trybie wstrzymywanym. W tym trybie, używane polecenia i funkcje służą do utworzenia bazy danych sceny. Dopiero potem baza danych obiektów i materiałów jest renderowana w całości i tworzy scenę na ekranie. Dużą zaletą tego trybu jest to, że bardzo łatwo można programowo manipulować poszczególnymi obiektami sceny. Co więcej, można ustanowić powiązania pomiędzy obiektami, co umożliwia manipulowanie jednym obiektem poprzez manipulowanie innym obiektem (na przykład elementami przekładni w modelach mechanicznych). Dzięki obiektom biblioteki Open Inventor można łatwo obracać obiekty, animować je i wykonywać inne operacje. Informacja o modyfikacji obiektu jest umieszczana w bazie danych, bez konieczności stosowania żadnego dodatkowego kodu.
Specyfikacja VRML 1.0 jest oparta wyłącznie na formacie wymiany dla „trójwymiarowych" plików Open Inventora. Ten format plików, stanowiący po prostu ustandaryzo-wany opis układu obiektów w scenie, umożliwia projektantom 3D łatwą modyfikację obiektów i scen za pomocą różnych narzędzi opartych na bibliotece Open Inventor. Dzięki nim można łatwo stworzyć i zapisać do pojedynczego pliku zarówno indywidualne obiekty, jak i całe sceny wypełnione obiektami.
Podsumowanie
WebSpace nie jest jedynym sposobem odwiedzenia cyberprzestrzeni w trzech wymiarach. Wielu innych producentów (łącznie z Microsoftem) przystąpiło do współzawodnictwa tworząc własne wersje przeglądarek VRML. Unikatowa cecha WebSpace to zgodność prawie z wszystkimi przeglądarkami WWW i operowanie na plikach VRML oraz Open-Inventora, i to skompresowanych lub nie.
Rozdział 20. » OpenGL w Sieci: VRML_______________________________623
W momencie, gdy książka trafia do druku, wśród firm programistycznych toczy się wojna o to, kto ma ustalać standardy dla VRML w wersji 2.0. W tej nowej wersji mają się znaleźć nowe funkcje przeznaczone dla animacji i rozszerzeń multimedialnych w scenach przeznaczonych do oglądania przez Internet.
Czy rzeczywistość wirtualna dostępna przez Internet to tylko przemijająca moda czy też początek rewolucji? Tylko czas to pokaże, można jednak ufać sprawdzonemu stwierdzeniu, że apetyt rośnie w miarę jedzenia. Gdy sieci komputerowe będą działać z większą szybkością i stabilnością, zaś w komputerach pojawi się lepszy sprzęt graficzny, możesz być pewien, że w naszych systemach zadomowi się także rzeczywistość wirtualna, która coraz lepiej, coraz szybciej i coraz bardziej szczegółowo będzie odzwierciedlać rzeczywisty świat.
Część 4
OpenGL i...
W czwartej i ostatniej części tej książki zajmiemy się kilkoma ogólnymi zagadnieniami programowania związanymi z używaniem OpenGL. Dwa rozdziały zostały przeznaczone dla programistów C++ używających dwóch najpopularniejszych bibliotek klas C++ w Windows: MFC i OWL. Nie opuścimy także w potrzebie programistów korzystających z języków 4GL i innych „wizualnych" narzędzi, takich ja Visual Basic. W rozdziale 23. zaprezentujemy tworzenie kontrolek OCX OpenGL, które mogą być wykorzystane praktycznie w każdym 32-bitowym środowisku programowania dla Windows.
Ponadto, żadna książka o Windows i OpenGL nie może się obejść bez opisu zagadnień dotyczących współdziałania OpenGL z innymi graficznymi interfejsami API. Oprócz GDI, należą do nich także architektura DirectK oraz 3DDDI.
Rozdział 21.
OpenGL i MFC
W tym rozdziale:
Dowiesz się, jak...
Używane funkcje
PreCreateWindow
OnCreate OnDestroy
OnSize
OnDraw
OnEraseBkgnd
OnQueryNewPalette, OnPaletteChanged
Ustawić styl okna MFC, aby spełniało wymagania OpenGL
Stworzyć i przygotować kontekst renderowania
Usunąć kontekst renderowania na zakończenie działania programu
Rozmieścić kod rzutowania i widoku Rozmieścić kod renderowania
Zabezpieczyć się przed migotaniem okna pomiędzy renderowaniami
Rozmieścić kod zarządzający paletą
Obecnie coraz większa liczba programistów przy tworzeniu aplikacji dla Windows korzysta z C++. Jak dotąd w treści książki prezentowaliśmy jedynie kod napisany w C. Na szczęście, większość programistów C++ doskonale daje sobie radę z analizą programów napisanych w C. Niestety, nie jest to już tak powszechne w odwrotnej sytuacji (wielu programistów C ma kłopoty ze zrozumieniem kodu C++). Nie twierdzimy, że C++ jest znacznie trudniejszy w opanowaniu, lecz gdy sięgasz po książkę na temat grafiki komputerowej z zamiarem opanowania właśnie tego tematu, raczej nie chcesz przy okazji uczyć się nowej składni języka.
Choć większość przykładów w książce można skompilować zarówno za pomocą kompilatora C, jak i C++, większość programistów C++ tworzących aplikacje dla Windows nie pisze kodu w C. Zamiast niego najczęściej używają komercyjnych pakietów programistycznych lub własnych bibliotek klas C++. Różnica polega na tym, że większość
628________________________________________Część IV » OpenGL i...
aplikacji C++ nie zawiera procedur takich jak przedstawione w tej książce ani tych „instrukcji case z piekła rodem", obsługujących komunikaty przekazywane do okien.
Celem tego rozdziału jest pomoc programiście C++ w wykorzystaniu jednej z popularnych bibliotek klas C++ jako punktu wyjścia dla własnych aplikacji C++. Biblioteką klas opisywaną w tym rozdziale jest MFC (Microsoft Foundation Classes). Przykłady i zrzuty okien występujące w tym rozdziale zostały przygotowane przy użyciu Microsoft Yisual C++ 5.0. Inne kompilatory i pakiety wykorzystujące MFC powinny działać podobnie.
Uwaga:
Jeśli używasz OWL (Object Windows Library Borlanda), przejdź do rozdziału 22.
Pisząc ten rozdział, zakładaliśmy, że już sam potrafisz zrobić pewne rzeczy:
* Umiesz wykorzystać Yisual C++ i MFC do tworzenia aplikacji dla Windows NT i Windows 95.
* Opanowałeś materiał z czwartego rozdziału tej książki, opisujący OpenGL dla Windows oraz użycie kontekstów renderowania.
* Rozumiesz zagadnienia związane z obsługą palety, opisywane w rozdziale 8.
Wydzielenie kodu związanego z OpenGL
W przypadku każdej aplikacji dobrym pomysłem jest uczynienie kodu źródłowego na tyle modularnym, na ile to jest możliwe. Wydzielając funkcjonalne fragmenty kodu, można dużo łatwiej konserwować i ponownie wykorzystywać poszczególne procedury. Przez oddzielenie „czystego" kodu OpenGL w osobnym module, możesz efektywnie zastępować ten moduł specyficznym kodem, zachowując przy tym funkcjonalność reszty aplikacji. Dzięki temu, stosunkowo łatwo było przekonwertować przedstawiony w tym rozdziale przykład w C na program w C++, wykorzystujący bibliotekę MFC.
Zaczniemy od zadeklarowania trzech funkcji w pliku źródłowym C, glcode.c. Plik glcode.h zawiera deklaracje tych funkcji i jest włączany do pliku implementacji naszej klasy, wyprowadzonej z klasy CYiew, w celu umożliwienia dostępu do funkcji.
// glcode.h
// Deklaracje dla zewnętrznego modułu OpenGL. Te funkcje są
// zaimplementowane w pliku glcode.c i są wywoływane z wnętrza
// funkcji składowych klasy wyprowadzonej z CView.
#ifndef _GLCODE_
łdefine _GLCODE_
extern "C" {
Rozdział 21. » OpenGL i MFC____________________________________629
void GLSetupRC(void *pData);
void GLRenderScene(void *pData); void GLResize(GLsizei h, GLsizei w); }
Funkcja GLSetupRC zawiera cały kod związany z inicjowaniem kontekstu renderowania. Zawarty w niej kod może być bardzo prosty, przygotowujący jedynie kolor czyszczenia tła, lub dość skomplikowany, określający warunki oświetlenia. Funkcja GLRenderScene będzie wywoływana z wnętrza metody OnDraw naszej klasy widoku (wyprowadzonej z CYiew) w celu wyrysowania sceny. Na koniec, funkcja GLResize będzie wywoływana z procedury obsługi komunikatu WM _SIZE (metody OnSize), z przekazaną nową szerokością i wysokością obszaru roboczego okna. Możesz w niej zawrzeć wymagane obliczenia związane z ustanowieniem nowego widoku i bryły widzenia.
Zwróć uwagę, że funkcje GLSetupRC i GLRenderScene otrzymują wskaźniki typu void. Dzięki temu do funkcji renderującej możesz przekazywać dane dowolnego typu, bez konieczności późniejszych zmian interfejsu. Choć moglibyśmy z pliku glcode.c uczynić plik C++, łatwiej jest skopiować istniejący kod C z dowolnego źródła i wstawić go do programu MFC. Yisual C++ po prostu skompiluje ten plik jako plik C i połączy go z resztą aplikacji.
W tym miejscu nie będziemy prezentować zawartości pliku glcode.c, gdyż ten plik jest dość długi, a jeśli koniecznie chcesz się z nim zapoznać, możesz go skopiować z płytki CD-ROM. Ponadto, ten sam plik zostanie wykorzystany w przykładzie dla OWL w następnym rozdziale.
Zaczynamy od AppWizarda
Wiele aplikacji napisanych w C++ zaczyna istnienie od AppWizarda. Architekturę do-kumentu-widoku można porównywać z architekturą modelu-widoku w innych obiektowo zorientowanych środowiskach programistycznych. Nawet w przypadku szybkich, próbnych aplikacji lub eksperymentalnych projektów, AppWizard może w niecałą minutę utworzyć aplikację SDI (z pojedynczym dokumentem), MDI (z wieloma dokumentami) lub opartą na oknie dialogowym. Tak więc sensownie będzie użyć właśnie AppWizarda w celu stworzenia szkieletu aplikacji SDI MFC, w której później wykorzystamy także OpenGL. Aby stworzyć prostą scenę OpenGL, dodamy funkcje i zmienne do klasy widoku, wyprowadzonej z CYiew. Podobnych metod będziesz mógł użyć w celu dodania elementów OpenGL do każdej klasy wyprowadzonej z CWnd.
Budowanie szkieletu
Zaczniemy od zbudowania szkieletu aplikacji SDI za pomocą AppWizarda, pomijając opcje związane z dostępem do baz danych i funkcjonalnością OLE. Rysunek 21.1 przedstawia oryginalną szkieletową aplikację SDI stworzoną przez AppWizarda.
Część IV » OpenGL i
630
Rysunek 21.1.
Oryginalna szkieletowa aplikacja SDI stworzona przez AppWizarda
Być może zechcesz także wyłączyć opcję wydruku (Print) i podglądu wydruku (Print Preview). Sceny OpenGL mogą być renderowane w kontekście urządzenia drukarki tylko wtedy, gdy drukarka jest drukarką kolorową obsługującą co najmniej cztery lub więcej bitplanów koloru (16 lub więcej kolorów). Drukowanie na monochromatycznej drukarce laserowej lub mozaikowej także jest możliwe, lecz dość kłopotliwe. Przykład kodu służącego do drukowania scen przy użyciu nowych elementów OpenGL w wersji l. l znajdziesz w uzupełniającym przykładzie GLPRTNT, na płytce CD-ROM, w folderze \OPENGL11 \SAMPLES\GLPRINT.
Dodawanie bibliotek
Zanim zaczniemy dodawać do szkieletu aplikacji jakikolwiek kod OpenGL, musimy dodać do projektu biblioteki OpenGL. W tym celu wybierz w menu polecenie Project/ Settings. Rysunek 21.2 przedstawia miejsce, gdzie powinieneś dopisać nazwy bibliotek OpenGL. Być może dodasz do projektu także inne biblioteki, w zależności od potrzeb swojej aplikacji. Na rysunku zostały przedstawione jedynie pliki wymagane dla aplikacji OpenGL.
Oprócz tego będziesz musiał dodać do projektu pliki nagłówkowe OpenGL. Najlepiej umieścić je w pliku stdafx.h (a potem można już o nich zapomnieć). Dopisz po prostu dwie poniższe linie włączające pliki nagłówkowe, które zostaną dopisane także do pre-kompilowanego pliku nagłówkowego:
łtinclude <gl\gl.h> tinclude <gl\glu.h>
// Biblioteka OpenGL
// Biblioteka GLU OpenGL
631
Rozdział 21. » OpenGL i MFC
Rysunek 21.2.
Dodawanie bibliotek OpenGL do projektu VisualC++
Przygotowanie klasy widoku dla OpenGL
Gdy użyjesz architektury dokument-widok przygotowanej przez AppWizarda dla aplikacji SDI, otrzymasz klasę wyprowadzoną z CYiew, odpowiedzialną za prezentację wyników działania twojej aplikacji. W naszym przykładzie ta klasa nosi nazwę CMfcgl-Yiew. Jest zadeklarowana w pliku mfcglYiew.h, zaś zaimplementowana w pliku mfcglYiew.cpp.
Najważniejszym wymaganiem co do okien, które mają być używane przez OpenGL, jest zastosowanie stylów okna WS_CLIPCHILDREN oraz WS_CLIPSIBLINGS. Możemy je łatwo dodać wewnątrz wirtualnej funkcji składowej PreCreateWindow naszej klasy okna (ta funkcja jest domyślnie wstawiana do pliku mfcglYiew.cpp). Dzięki tej funkcji możemy modyfikować strukturę CREATESTRUCT tuż przed utworzeniem okna. Jedno z pól tej struktury zawiera style okna używane przy jego tworzeniu. Możemy po prostu ustawić dodatkowe bity stylu wykonując logiczną operację OR, na przykład tak:
BOOL CMfcglView::PreCreateWindow(CREATESTROCT& es)
// Dodaje style okna wymagane przez OpenGL
CS.style |= (WS_CLIPCHILDREN | WS_CLIPSIBLINGS l CSJDWNDC);
return CView::PreCreateWindow(es);
Zwróć uwagę, że ustawiamy także styl CS_OWNDC, dzięki któremu okno może posiadać prywatny kontekst urządzenia. Choć nie jest to rygorystycznie wymagane, powoduje szybsze i wydajniejsze działanie programu. Niektóre wskaźniki do kontekstów urządzeń
Część IV » OpenGL i...
632
zwracane przez MFC są tymczasowe i nie mogą być przechowywane do późniejszego użycia. W naszym przypadku lepiej jest pobrać taki wskaźnik i przechować.
W klasie CMfcglYiew przygotujemy także miejsce na kontekst urządzenia oraz kontekst renderowania:
public:
HGLRC m_hRC; HDC m hDC;
// Kontekst renderowania // Kontekst urządzenia
Format pikseli i kontekst renderowania
Teraz, gdy mamy już okno z ustawionymi stylami wymaganymi przez OpenGL, musimy ustawić format pikseli OpenGL. Ponieważ do utworzenia formatu pikseli wymagany jest kontekst urządzenia, musimy to zrobić już po utworzeniu okna. Możemy użyć ClassWizarda w celu dodania procedury obsługi komunikatu WM_CREATE, wysyłanego do tego okna. Rysunek 21.3 przedstawia okno dialogowe ClassWizarda, zawierającego pozycję także dla komunikatu WM_DESTROY.
Rysunek 21.3.
Dodawanie pozycji mapy komunikatów dla komunikatów WM_CREATE i WM DESTROY
IOJ4PP ABOUT 10 APP_EX1T ID~EDIT COPY IDlEDIT~CUT ID EDIT PASIE ID EDIT UNDO
WM CTLCOLOR WM DEADCHAR WM DELETEITEM WM_DESTHOY
WM DESTROYCUPBOARD
OnDestioy
OnDiao.
PieOeałeWindow
Ustawienie formatu pikseli wewnątrz procedury obsługi komunikatu WM_CREATE jest stosunkowo proste. Listing 21.1 przedstawia naszą procedurę obsługi komunikatu zawierającą kod przygotowujący format pikseli dla kontekstu urządzenia.
Listing 21.1. Procedura obsługi komunikatu WM CREATE, w której przygotowujemy format pikseli____
int CMfcglView::OnCreate(LPCREATESTRUCT IpCreateStruct) {
if (CView::OnCreate(IpCreateStruct) == -1) return -1;
int nPixelFormat;
m hDC = ::GetDC(m hWnd);
// Indeks formatu pikseli
// Pobranie kontekstu
// urządzenia
Rozdział 21. » OpenGL i MFC____________________________________633
static PIKELFORMATDESCRIPTOR pfd = {
sizeof(PIKELFORMATDESCRIPTOR), // Rozmiar tej struktury
l, // Wersja struktury
PFD_DRAW__TO_WINDOW | // Rysowanie w oknie (nie na
// bitmapie) PFD_SUPPORT_OPENGL | // Obsługa wywołań OpenGL w tym
// oknie
PFD_DOUBLEBUFFER, // Tryb podwójnego buforowania
PFD_TYPE_RGBA, // Tryb kolorów RGBA
24, // Chcemy 24-bitowego koloru
0,0,0,0,0,0, // Nieużywane przy wybieraniu trybu
0,0, // Nieużywane przy wybieraniu trybu
0,0,0,0,0, // Nieużywane przy wybieraniu trybu
32, // Rozmiar bufora głębokości
O, // Nieużywane przy wybieraniu trybu
O, // Nieużywane przy wybieraniu trybu
PFD_MAIN_PLANE, // Rysowanie na głównym planie
O, // Nieużywane przy wybieraniu trybu
0,0,0 }; // Nieużywane przy wybieraniu trybu
// Wybranie formatu pikseli najbardziej zbliżonego do wskazanego
// w pfd
nPixelFormat = ChoosePixelFormat(m_hDC, Spfd);
// Ustawienie formatu pikseli dla kontekstu urządzenia VERIFY(SetPixelFormat(m_hDC, nPixelFormat, spfd));
// Utworzenie kontekstu renderowania m_hRC = wglCreateContext(m_hDC);
// Uczynienie kontekstu renderowania bieżącym, przeprowadzenie // inicjowanie, a następnie odłożenie kontekstu VERIFY(wglMakeCurrent(m_hDC,m_hRC)); GLSetupRC(m_hDC); wglMakeCurrent(NULL, NULL) ;
// W razie potrzeby utworzenie palety InitializePalette();
SetTimer(101,175,NULL); // 8 razy na sekundę... return 0;
Zwróć uwagę, że kontekst urządzenia i kontekst renderowania przechowujemy w zmiennych składowych klasy, m_hDC i m_hRC. Natychmiast po stworzeniu kontekstu renderowania czynimy go bieżącym i wywołujemy zewnętrzną funkcję GLSetupRC. Ta funkcja zainicjuje wymagany kontekst renderowania, po czym możemy go odłożyć. Dzięki temu możemy używać kilku kontekstów renderowania, na wypadek gdybyśmy chcieli zastosować OpenGL w kilku oknach. (Nie robimy tego w naszym przykładzie, ale jeśli wykorzystasz go jako punkt wyjścia dla własnego programu, nie będziesz już musiał przebudowywać całego kodu).
634________________________________________Część IV » OpenGL i...
Usuwanie kontekstu renderowania
Powinniśmy pójść krok dalej i zanim o tym zapomnimy, dodać kod czyszczący i usuwający kontekst renderowania. Wykorzystamy do tego procedurę obsługi komunikatu WM_DESTROY, także pokazaną na rysunku 21.3. Przy okazji zwolnimy kontekst urządzenia pobrany tuż po utworzeniu okna.
// Okno jest niszczone, wiec robimy porządki
void CMfcglView::OnDestroy()
{
// Usuwanie timera
KillTimer(101);
// Usunięcie kontekstu renderowania...
wglDeleteContext(m_hRC);
// ... i kontekstu urządzenia
::ReleaseDC(m_hWnd,m_hDC);
CView::OnDestroy();
Obsługa zmiany rozmiaru okna
Gdy zmienia się rozmiar okna, otrzymuje ono komunikat WM_SIZE. Za pomocą ClassWizarda dodamy procedurę obsługi tego komunikatu i z jej wnętrza wywołamy zewnętrzną funkcję GLResize, przekazując jej nowe wymiary obszaru roboczego okna. Przed wywołaniem tej funkcji musimy uczynić kontekst renderowania bieżącym, gdyż w przeciwnym razie wywołania funkcji OpenGL w funkcji GLResize nie przyniosą efektu. Oto kod procedury obsługi:
void CMfcglView::OnSize(UINT nType, int ex, int cy) {
CView::OnSize(nType, ex, cy);
VERIFY(wglMakeCurrent(m_hDC,m_hRC)); GLResize(ex, cy); VERIFY(wglMakeCurrent(NULL,NULL));
Renderowanie sceny
Teraz możemy dodać kod odpowiedzialny za renderowanie sceny w OpenGL. Metoda OnDraw klasy widoku jest wywoływana za każdym razem, gdy okno otrzymuje komunikat WM_PAINT. Wewnątrz tej metody wybieramy kontekst renderowania jako bieżący i wywołujemy funkcję GLRenderScene, zawierającą jedynie wywołania funkcji OpenGL. Ponieważ wcześniej zażądaliśmy trybu z podwójnym buforowaniem, zaraz po tym wywołujemy funkcję SwapBuffers i odkładamy kontekst renderowania.
Rozdział 21. » OpenGL i MFC____________________________________635
// Wywoływane, gdy okno otrzymuje komunikat WM_PAINT, renderuje scenę
void CMfcglYiew::OnDraw(CDC* pDC)
{
// Uczynienie kontekstu renderowania bieżącym
wglMakeCurrent(m_hDC,m_hRC);
// Wywołanie zewnętrznego kodu OpenGL GLRenderScene(NULL);
// Wyświetlenie sceny w oknie SwapBuffers(m_hDC);
// Odłożenie kontekstu renderowania wglMakeCurrent(m_hDC,NULL);
Unikanie niepotrzebnego czyszczenia okna
Za każdym razem, gdy okno zmienia rozmiar lub jest unieważniane, MFC czyści tło okna przed odrysowaniem. Ponieważ naszym kolorem tła OpenGL jest czerń, to wymazywanie (które powoduje wymalowanie okna na biało) powoduje migotanie za każdym razem, gdy jest wywoływana funkcja OnDraw.
Aby zapobiec migotaniu, przesłonimy domyślną procedurę obsługi komunikatu WM_ERA-SEBACKGROUND. Zwykle okno jest wymazywane przed odmalowaniem po zmianie rozmiaru. Jeśli ta funkcja zwróci wartość FALSE, okno nie będzie odmalowywane i nie wystąpi migotanie. Zwykle ta funkcja zwraca wartość funkcji CView::OnEraseBkgn-d(pDC), odpowiadającej za wymazywanie tła, ale możemy po prostu zwrócić wartość FALSE.
// Przesłonięte w celu uniemożliwienia wymazywania tła przy
// każdym odmalowywaniu okna
BOOL CMfcglView::OnEraseBkgnd(CDC* pDC)
{
return FALSE;
Obsługa palety
Ostatnie poprawki dotyczące naszego przykładu MFC związane są z obsługą palety, co wiąże się ze stworzeniem i zrealizowaniem palety RGB dla urządzeń korzystających z palety (kart 256-kolorowych). Zamiast przechowywać uchwyt palety, tak jak w rozdziale 8, stworzymy obiekt MFC typu CPalette. Dla listingu 21.2, w pliku mfcglYiew.h deklarujemy egzemplarz obiektu klasy CPalette:
CPalette m_GLPalette; // Paleta logiczna
a następnie ręcznie dodajemy do klasy CMfcglYiew metodę inicjującą paletę. Jej kod jest niemal identyczny z kodem funkcji GetOpenGLPalette zaprezentowanej w rozdziale 8, z tym że zamiast zwracania uchwytu palety konstruowany jest obiekt klasy CPalette.
636________________________________________Część IV » OpenGL i...
Listing 21.2. Tworzenie i inicjowanie obiektu CPalette_____________________________
// Inicjowanie obiektu CPalette
void CMfcglView::InitializePalette(void)
(
PIXELFORMATDESCRIPTOR pfd; // Deskryptor formatu pikseli LOGPALETTE *pPal; // Wskaźnik do obszaru pamięci dla palety
// logicznej
int nPixelFormat; // Indeks formatu pikseli
int nColors; // Ilość pozycji palety
int i; // Licznik
BYTE RedRange,GreenRange,BlueRange;
// Zakres dla każdej pozycji koloru (7,7 // i 3)
// Pobranie indeksu formatu pikseli oraz deskryptora formatu
// pikseli
nPixelFormat = GetPixelFormat(m_hDC);
DescribePixelFormat(m_hDC, nPixelFormat,
sizeof(PIXELFORMATDESCRIPTOR), spfd);
// Czy ten format pikseli wymaga palety? Jeśli nie, po prostu nie // twórz palety i zwróć wartość NULL if(!(pfd.dwFlags & PFD_NEED_PALETTE)) return;
// Ilość pozycji w palecie. 8 bitów oznacza 256 pozycji nColors = l « pfd.cColorBits;
// Zaalokowanie pamięci na strukturę logicznej palety
// i wszystkie jej pozycje
pPal = {LOGPALETTE*Jmalloc(sizeof(LOGPALETTE)
+nColors*sizeof(PALETTEENTRY));
// Wypełnienie nagłówka palety
pPal->palVersion = 0x300; // Windows 3.0 pPal->palNumEntries = nColors; // rozmiar tabeli
// Budowanie maski wszystkich jedynek. Tworzy liczbę
// reprezentowaną przez x dolnych bitów ustawionych, gdzie x =
// pfd.cRedBits, pfd.cGreenBits,oraz pfd.cBlueBits.
RedRange = (l « pfd.cRedBits) -1;
GreenRange = (l « pfd.cGreenBits) - 1;
BlueRange = (l « pfd.cBlueBits) -1;
// Przejście przez wszystkie pozycje palety
ford = 0; i < nColors; i++)
{
// Wypełnienie 8-bitowych odpowiedników dla każdego komponentu pPal->palPalEntry[i].peRed = (i » pfd.cRedShift) & RedRange; pPal->palPalEntry[i].peRed = (unsigned char)(
(double) pPal->palPalEntry[i].peRed * 255.0 / RedRange);
pPal->palPalEntry[i].peGreen = (i » pfd.cGreenShift) s
^GreenRange;
pPal->palPalEntry[i].peGreen = (unsigned char)(
(double)pPal->palPalEntry[i].peGreen * 255.0 /
^GreenRange) ;
pPal->palPalEntry[i].peBlue = (i » pfd.cBlueShift) & ^BlueRange;
Rozdział 21. » OpenGL i MFC ____________________________________ 637
pPal->palPalEntry [i] .peBlue = (unsigned char) (
(double)pPal->palPalEntry [i] .peBlue * 255.0 / BlueRange);
pPal->palPalEntry [i] .peFlags = (unsigned char) NULL; }
// Utworzenie palety m_GLPalette . CreatePalette (pPal ) ;
// Wybranie i zrealizowanie palety dla bieżącego kontekstu // urządzenia
SelectPalette (m_hDC, (HPALETTE)m_GLPalette, FALSE) ; RealizePalette (m_hDC) ;
// Zwolnienie pamięci użytej przez strukturę palety logicznej free(pPal) ;
Po użyciu ClassWizarda w celu dodania odpowiednich funkcji obsługujących komunikaty WM_QUERNEWPALETTE oraz WM_PALETTECHANGED, nasz kod odpowiadający za realizację palety wygląda tak, jak na listingu 21.3.
Listing 21.3. Kod realizujący paletę dla klasy widoku
BOOL CMfcglView: :OnQueryNewPalette ( ) {
//Jeśli paleta została utworzona.
i f ( (HPALETTE)m_GLPalette)
{
int nRet;
// Wybranie palety w bieżącym kontekście urządzenia SelectPalette (m_hDC, (HPALETTE)m_GLPalette, FALSE);
// Odwzorowanie pozycji bieżącej palety na paletę systemową. // Zwracaną wartością jest ilość zmodyfikowanych pozycji // palety. nRet = RealizePalette (m_hDC) ;
// Przemalowanie, które wymusza nowe odwzorowanie palety // w bieżącym oknie InvalidateRect (NULL, FALSE) ;
return nRet;
}
return CView: :OnQueryNewPalette ( ) ;
void CMfcglView: :OnPaletteChanged (CWnd* pFocusWnd) {
if ( ( (HPALETTE)m_GLPalette !=NULL) && (pFocusWnd !=this)) {
// Wybranie palety w kontekście urządzenia SelectPalette (m hDC, (HPALETTE)m GLPalette, FALSE) ;
638__________________________________________Część IV » OpenGL I...
// Odwzorowanie pozycji w paletę systemową RealizePalette(m_hDC);
// Przemapowanie bieżących kolorów do nowo zrealizowanej // palety
UpdateColors(m_hDC); return;
CView::OnPaletteChanged(pFocusWnd) ; }
Kod realizujący paletę bardzo przypomina kod z rozdziału 8. Tutaj jednak Windows nie przekazuje tych komunikatów bezpośrednio do klasy widoku, lecz do klasy CMainFrame aplikacji. Dzieje się tak, ponieważ Windows wysyła komunikaty o zmianie palety jedynie do głównego okna aplikacji; główne okno odpowiada za rozesłanie tych komunikatów do odpowiednich okien potomnych.
Ponownie więc użyjemy ClassWizarda w celu dodania dwóch procedur obsługi komunikatów palety do klasy CMainFrame. W tych procedurach po prostu wyznaczamy aktywny widok i posyłamy mu niezmienione komunikaty palety, pozwalając widokowi na ich samodzielne obsłużenie. Te procedury obsługi zostały przedstawione na listingu 21.4.
Listing 21.4. Kod klasy CMainFrame przesyłający komunikaty palety do okna widoku ___________
// Przekierowanie komunikatu do okna widoku
void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd)
CView* pYiew = GetActiveView(); if (pView)
// Metoda OnPaletteChanged nie jest publiczna, więc wysyłamy
// komunikat
pView->SendMessage(WM_PALETTECHANGED,
(WPARAM)(pFocusWnd->GetSafeHwnd()),
(LPARAM)O);
// Przekierowanie komunikatu do okna widoku
BOOL CMainFrame::OnQueryNewPalette()
{
CView* pView = GetActiveView{);
if (pView)
{
// Metoda OnQueryNewPalette nie jest publiczna, wiec wysyłamy // komunikat
return pView->SendMessage(WM_QUERYNEWPALETTE,
(WPARAM) O, (LPARAM) 0) ; } return FALSE;
Rozdział 21. » OpenGL i MFC____________________________________639
Oprócz tego dodaliśmy do klasy widoku procedurę obsługi komunikatów WM_TIMER oraz utworzyliśmy timer, dzięki któremu możemy animować scenę (rysunek 21.4). Funkcja timera po prostu unieważnia obszar roboczy okna, wymuszając jego odmalowanie. W module glcode.c funkcja renderująca przy każdym odrysowywaniu okna inkrementuje kąt obrotu, tworząc w ten sposób efekt animacji. Cały kod jest dostępny na płytce CD-ROM, jako program MFCGL.
Rysunek 21.4.
Końcowa animowana scena tworzona przez program MFCGL
Podsumowanie
W tym rozdziale omówiliśmy zagadnienia związane z używaniem OpenGL w aplikacjach opartych na MFC, pokazując, gdzie należy ustawić style okna wymagane przez OpenGL, gdzie i kiedy ustawić format pikseli, a także jak stworzyć kontekst renderowa-nia. Przykładowy program ilustruje także, kiedy należy uczynić kontekst renderowania bieżącym oraz jak w razie potrzeby zrealizować paletę za pomocą klasy MFC CPalette.
Przykładową aplikację zaprezentowaną w tym rozdziale będziesz mógł wykorzystać jako punkt wyjścia dla własnych programów OpenGL. Oprócz tego, szkielet aplikacji -wraz z całym kodem OpenGL w module glcode.c - może posłużyć do zamiany innych przykładów C/OpenGL na programy MFC. Inne przykładowe programy MFC korzystające z OpenGL znajdziesz na płytce CD-ROM.
Rozdział 22.
OpenGL i OWL
W tym rozdziale:
Używane funkcje
Dowiesz się, jak...
EvCreate EvCreate EvDestroy
EvSize
EvPaint
EvEraseBkgnd
EvQueryNewPalette, EvPaletteChanged
Ustawić styl okna OWL, aby spełniało wymagania OpenGL Stworzyć i przygotować kontekst renderowania
Usunąć kontekst renderowania na zakończenie działania programu
Rozmieścić kod rzutowania i widoku Rozmieścić kod renderowania
Zabezpieczyć się przed migotaniem okna pomiędzy renderowaniami
Rozmieścić kod zarządzający paletą
Obecnie coraz większa liczba programistów przy tworzeniu aplikacji dla Windows korzysta z C++. Dotychczas w treści książki prezentowaliśmy jedynie kod napisany w C. Na szczęście, większość programistów C++ doskonale daje sobie radę z analizą programów napisanych w C. Niestety, nie jest to już tak powszechne w odwrotnej sytuacji (wielu programistów C ma kłopoty ze zrozumieniem kodu C++). Nie twierdzimy, że C++ jest znacznie trudniejszy w opanowaniu, lecz gdy sięgasz po książkę na temat grafiki komputerowej z zamiarem opanowania właśnie tego tematu, raczej nie chcesz przy okazji uczyć się nowej składni języka.
Choć większość przykładów w książce można skompilować zarówno za pomocą kompilatora C, jak i C++, większość programistów C++ tworzących aplikacje dla Windows nie pisze kodu w C. Zamiast niego najczęściej używają komercyjnych pakietów programistycznych lub własnych bibliotek klas C++. Różnica polega na tym, że większość aplikacji C++ nie zawiera procedur takich jak przedstawione w tej książce ani tych „instrukcji case z piekła rodem", obsługujących komunikaty przekazywane do okien.
642________________________________________Część IV » OpenGL i...
Celem tego rozdziału jest pomoc programiście C++ w wykorzystaniu jednej z popularnych bibliotek klas C++ jako punktu wyjścia dla własnych aplikacji C++. Biblioteką klas opisywaną w tym rozdziale jest OWL (Object Windows Library) Borlanda. Przykłady i zrzuty okien występujące w tym rozdziale zostały przygotowane przy użyciu Borland C++5.0.
Uwaga
Jeśli używasz MFC (Microsoft Foundation Classes), wróć do rozdziału 21
Pisząc ten rozdział, zakładaliśmy, że już sam potrafisz zrobić pewne rzeczy:
* Umiesz wykorzystać Borland C++ i OWL do tworzenia aplikacji dla Windows NT i Windows 95.
* Opanowałeś materiał z czwartego rozdziału tej książki, opisujący OpenGL dla Windows oraz użycie kontekstów renderowania.
* Rozumiesz zagadnienia związane z obsługą palety, opisywane w rozdziale 8.
Wydzielenie kodu związanego z OpenGL
W przypadku każdej aplikacji dobrym pomysłem jest uczynienie kodu źródłowego na tyle modularnym, na ile to jest możliwe. Wydzielając funkcjonalne fragmenty kodu, można o wiele łatwiej konserwować i ponownie wykorzystywać poszczególne procedury. Przez oddzielenie „czystego" kodu OpenGL w osobnym module, możesz efektywnie zastępować ten moduł specyficznym kodem, zachowując przy tym funkcjonalność reszty aplikacji. Dzięki temu stosunkowo łatwo było przekonwertować przedstawiony w tym rozdziale przykład w C na program w C++, wykorzystujący bibliotekę OWL.
Zaczniemy od zadeklarowania trzech funkcji w pliku źródłowym C, glcode.c. Plik glcode.h zawiera deklaracje tych funkcji i jest włączany do pliku implementacji naszej klasy, wyprowadzonej z klasy TWindowYiew, w celu umożliwienia dostępu do funkcji.
// glcode.h
// Deklaracje dla zewnętrznego modułu OpenGL. Te funkcje są
// zaimplementowane w pliku glcode.c i wywoływane z wnętrza
// funkcji składowych klasy wyprowadzonej z TWindowYiew.
tifndef _GLCODE_
#define _GLCODE_
extern "C" {
void GLSetupRC(void *pData);
void GLRenderScene(void *pData); void GLResize(GLsizei h, GLsizei w); }
Funkcja GLSetupRC zawiera cały kod związany z inicjowaniem kontekstu renderowa-nia. Zawarty w niej kod może być bardzo prosty, przygotowujący jedynie kolor czy-
Rozdział 22. » OpenGL i OWL____________________________________643
szczenią tła, lub dość skomplikowany, określający warunki oświetlenia. Funkcja GLRen-derScene będzie wywoływana z wnętrza metody OnDraw naszej klasy widoku (wyprowadzonej z TWindowYiew) w celu wyrysowania sceny. Na koniec, funkcja GL-Resize będzie wywoływana z procedury obsługi komunikatu WM_SIZE, z przekazaną nową szerokością i wysokością obszaru roboczego okna. Możesz w niej zawrzeć wymagane obliczenia związane z ustanowieniem nowego widoku i bryły widzenia.
Zwróć uwagę, że funkcje GLSetupRC i GLRenderScene otrzymują wskaźniki typu void. Dzięki temu do funkcji renderującej możesz przekazywać dane dowolnego typu, bez konieczności późniejszych zmian interfejsu. Choć moglibyśmy z pliku glcode.c uczynić plik C++, łatwiej jest skopiować istniejący kod C z dowolnego źródła i wstawić go do programu OWL. Borland C++ po prostu skompiluje ten plik jako plik C i połączy go z resztą aplikacji.
W tym miejscu nie będziemy prezentować zawartości pliku glcode.c, gdyż ten plik jest dość długi, a jeśli koniecznie chcesz się z nim zapoznać, możesz go skopiować z płytki CD-ROM. Ponadto ten sam plik został wykorzystany w przykładzie dla MFC w poprzednim rozdziale.
Zaczynamy od AppExperta
Wiele aplikacji napisanych w C++ zaczyna istnienie od AppExperta. Architekturę doku-mentu-widoku można porównywać z architekturą modelu-widoku w innych obiektowo zorientowanych środowiskach programistycznych. Nawet w przypadku szybkich, próbnych aplikacji lub eksperymentalnych projektów, AppExpert może w niecałą minutę stworzyć aplikację SDI (z pojedynczym dokumentem), MDI (z wieloma dokumentami) lub opartą na oknie dialogowym. Tak więc sensownie będzie użyć właśnie AppExperta w celu stworzenia szkieletu aplikacji SDI OWL, w której później wykorzystamy także OpenGL. Aby stworzyć prostą scenę OpenGL, dodamy funkcje i zmienne do klasy widoku, wyprowadzonej z TWindowYiew. Podobnych metod będziesz mógł użyć w celu dodania elementów OpenGL do każdej klasy wyprowadzonej z TWindow.
Budowanie szkieletu
Zaczniemy od zbudowania szkieletu aplikacji SDI za pomocą AppExperta, pomijając opcje związane z funkcjonalnością OLE, przeciąganiem i upuszczaniem itd. Rysunek 22.1 przedstawia pierwszy dialog AppExperta przy tworzeniu szkieletu aplikacji OWL.
Być może zechcesz także wyłączyć opcję wydruku i podglądu wydruku (Print and Print Preview). Sceny OpenGL mogą być renderowane w kontekście urządzenia drukarki tylko wtedy, gdy drukarka jest drukarką kolorową obsługującą co najmniej cztery lub więcej bitplanów koloru (16 lub więcej kolorów). Drukowanie na monochromatycznej drukarce laserowej lub mozaikowej także jest możliwe, lecz dość kłopotliwe. Przykład kodu służącego do drukowania scen przy użyciu nowych elementów OpenGL w wersji
Część IV » OpenGL I...
644
l. l znajdziesz w uzupełniającym przykładzie GLPRINT, na płytce CD-ROM, w folderze \OPENGL11 \SAMPLES\GLPRINT.
Rysunek 22.1.
Początek tworzenia nowej aplikacji SDI za pomocą AppExperta
Możesz pozostawić opcje aplikacji w ich domyślnym stanie lub też wyłączyć paski narzędzi, pasek stanu itd. Dodatkowo musisz pamiętać, aby w oknie MainWindow Basic Options włączyć style okna wymagane przez OpenGL (Clip Children i Clip Siblings). Na koniec, wybierz kategorię SDI Client i wskaż, że główne okno ma być wyprowadzone z klasy TWindowWiev, tak jak pokazano na rysunku 22.2.
Rysunek 22.2.
Wskazywanie, że okno klienta ma być wyprowadzone z klasy TWindowYiew
Po wygenerowaniu kodu i zbudowaniu szkieletowej aplikacji, otrzymasz program pokazany na rysunku 22.3.
645
Rozdział 22. » OpenGL i OWL
Rysunek 22.3.
Szkieletowa aplikacja SDI wygenerowana przez AppExperta
Dodawanie nagłówków
Zanim zaczniemy dodawać do szkieletu aplikacji jakikolwiek kod OpenGL, musimy dodać do projektu nagłówki OpenGL. Najlepiej umieścić je w pliku owlglapp.h. Dopisz po prostu dwie poniższe linie włączające pliki nagłówkowe:
tfinclude <gl\gl.h> łinclude <gl\glu.h>
// Biblioteka OpenGL
// Biblioteka GLU OpenGL
Zgodnie z ogólną zasadą, Borland automatycznie linkuje programy z biblioteką importową zawierającą wszystkie funkcje API Win32. Czasem te biblioteki nie są zsynchronizowane z najnowszą wersją systemu operacyjnego, a wtedy musisz stworzyć własne biblioteki importowe i połączyć z nimi program. (Zajrzyj do dyskusji na temat Borland C++ we wprowadzeniu do książki).
Dodawanie procedur obsługi komunikatów
Tworzenie szkieletowej aplikacji dla OpenGL w OWL zakończymy dodaniem procedur obsługi dla przynajmniej pierwszych pięciu komunikatów okienkowych zebranych w tabeli 22.1. Pięć pierwszych komunikatów z tabeli powinno być obsługiwanych w każdej szanującej się aplikacji OpenGL dla Windows. Komunikaty palety są wymagane jedynie wtedy, gdy w programie jest zawarty kod obsługi palety i przewidujesz, że aplikacja będzie używana w trybach 256-kolorowych. Komunikat WM_TIMER jest opcjonalny, jednak jest przydatny, gdy chcesz stworzyć prostą animację. W naszym przykładzie w dalszej części rozdziału wykorzystujemy komunikat WMJTIMER właśnie do celów animacji.
Część IV » OpenGL i...
646
Tabela 22.1.
Podstawowe komunikaty obsługiwane w aplikacjach OpenGL
Komunikat
Przeznaczenie procedury obsługi
Tworzenie okna. Ustala wymagane style okna i tworzy kontekst renderowania.
Zrobienie porządków przez usunięcie kontekstu renderowania.
Poinformowanie GDI, aby nie wymazywało tła przed odświeżeniem zawartości okna.
Odrysowanie zawartości okna. W tej procedurze jest wywoływany kod renderowania OpenGL.
Wywołanie kodu modyfikującego informacje o widoku OpenGL.
Ewentualna realizacja palety aplikacji.
Ewentualna reakcja na zmianę palety.
Regularne wywoływanie funkcji, na przykład do celów animacji.
WM_CREATE
WM_DESTROY WM_ERASEBKGND
WM_PAINT
WM_SIZE
WM_QUERYNEWPALETTE WM_PALETTECHANGED WM TIMER
Okno AppExperta, w którym dodajemy procedury obsługi tych komunikatów, zostało przedstawione na rysunku 22.4.
Rysunek 22.4.
Dodawanie procedur obsługi komunikatów za pomocą ClassExperta.
Wypełnianie szkieletu aplikacji
W tym momencie mamy już kompletną szkieletową aplikację, ze zdefiniowanymi procedurami obsługi komunikatów przeznaczonymi do inicjowanie i destrukcji okna, rysowania, zmiany rozmiaru i obsługi palety. Do tego szkieletu dodamy kod umożliwiający OpenGL rysowanie w oknie. Osiągamy to przez wywołanie funkcji Win32 specyfi-
Rozdział 22. » OpenGL i OWL____________________________________647
cznych dla OpenGL, a następnie wywołanie naszego kodu OpenGL w odpowiednich funkcjach z modułu glcode.c.
Przygotowanie klasy widoku dla OpenGL
AppExpert generuje klasę TOwlglWindowYiew, wyprowadzoną bezpośrednio z klasy TWindowYiew. Ta klasa jest odpowiedzialna za obszar roboczy okna aplikacji. W naszym przykładzie ta klasa jest zadeklarowana w pliku owlglwnv.h, zaś zaimplemento-wana w pliku owlglwnv.cpp.
Najpierw wypełnimy kod procedury obsługi komunikatu WM_CREATE. Jak już wspomniano, OpenGL wymaga przede wszystkim, aby tworzone okno miało ustawione style WS_CLIPCHILDREN i WS_CLIPSIBLINGS. Ponieważ ustawiliśmy te style podczas tworzenia szkieletu aplikacji za pomocą AppExperta, nie musimy już nic w tym kierunku robić. Gdybyś jednak musiał ustawić je w programie, możesz wykorzystać do tego właśnie procedurę obsługi komunikatu WM_CREATE, na przykład tak:
BOOL TOwlglWindowView::EvCreateWindow(CREATESTRUCT& es) {
int result;
// Dodaje style okna wymagane przez OpenGL
es.style |= (WS_CLIPCHILDREN | WS_CLIPSIBLINGS | CS_OWNDC);
result = TWindowView::EvCreate(es);
Zwróć uwagę, że ustawiamy także styl CS_OWNDC, dzięki któremu okno może posiadać prywatny kontekst urządzenia. Choć nie jest to rygorystycznie wymagane, powoduje szybsze i wydajniejsze działanie programu. Niektóre wskaźniki do kontekstów urządzeń zwracane przez OWL są tymczasowe i nie mogą być przechowywane do późniejszego użycia (podobnie jak w MFC). W naszym przypadku lepiej jest pobrać taki wskaźnik i przechować go.
W klasie TOwlglWindowYiew (w pliku nagłówkowym owlglwnv.h) przygotujemy także miejsce na kontekst urządzenia, kontekst renderowania oraz paletę
public:
HGLRC m_hRC; // Kontekst renderowania
HDC m_hDC = NULL; // Kontekst urządzenia
TPalette *m_pPalette; // Paleta 3-3-2
Format pikseli i kontekst renderowania
W pozostałej części procedury obsługi komunikatu WM_CREATE ustawimy format pikseli i stworzymy kontekst renderowania dla okna. Ponieważ do utworzenia formatu pikseli wymagany jest kontekst urządzenia, musimy to zrobić już po utworzeniu okna. Ustawianie formatu pikseli w tej funkcji przebiega w taki sam sposób, jak w każdym z przykładowych programów C w tej książce począwszy od rozdziału 4 (jak pamiętasz,
648_______________________________________Część IV » OpenGL i...
aż do trzeciego rozdziału posługiwaliśmy się biblioteką AUX). Listing 21.1 przedstawia ukończoną procedurę obsługi komunikatu zawierającą także kod przygotowujący format pikseli dla kontekstu urządzenia.
Listing 21.1. Procedura obsługi komunikatu WM_CREATE, \v której przygotowujemy format pikseli____
int TOwlglWindowView::EvCreate(CREATESTRUCT fars createStruct) {
int result;
createStruct.style |= (WS_CLIPCHILDREN | WS_CLIPSIBLINGS | •=>CS_OWNDC) ;
result - TWindowView::EvCreate(createStruct);
// Select pixel format/rendering context
Static PIKELFORMATDESCRIPTOR pfd = {
sizeof(PIKELFORMATDESCRIPTOR), // Rozmiar tej struktury
l, // Wersja struktury
PFD_DRAW_TO_WINDOW | // Rysowanie w oknie (nie na
// bitmapie)
PFD_SUPPORT_OPENGL ] // Obsługa wywołań OpenGL w tym oknie
PFD_DOUBLEBUFFER, // Tryb podwójnego buforowania
PFD_TYPE_RGBA, // Tryb kolorów RGBA
24, // Chcemy 24-bitowego koloru
0,0,0,0,0,0, // Nieużywane przy wybieraniu trybu
0,0, // Nieużywane przy wybieraniu trybu
0,0,0,0,0, // Nieużywane przy wybieraniu trybu
32, // Rozmiar bufora głębokości
O, // Nieużywane przy wybieraniu trybu
O, // Nieużywane przy wybieraniu trybu
PFD_MAIN_PLANE, // Rysowanie na głównym planie
O, // Nieużywane przy wybieraniu trybu
0,0,0 }; // Nieużywane przy wybieraniu trybu
// Pobranie kontekstu urządzenia m_hDC = ::GetDC(this->GetHandle());
// Wybranie formatu pikseli najbardziej zbliżonego do wskazanego w pfd int nPixelFormat = ChoosePixelFormat(m_hDC, Spfd);
// Ustawienie formatu pikseli dla kontekstu urządzenia SetPixelFormat(m_hDC, nPixelFormat, ipfd);
// Utworzenie palety 3-3-2 SetupPalette(m_hDC);
// Utworzenie kontekstu renderowania m_hRC = wglCreateContext(m_hDC);
// Uczynienie kontekstu renderowania bieżącym, przeprowadzenie // inicjowanie
wglMakeCurrent(m_hDC,m_hRC); GLSetupRC(m_hDC);
SetTimer(200,101,NULL); // 5 razy na sekundę return result;
Rozdział 22. » OpenGL i OWL____________________________________649
Natychmiast po utworzeniu kontekstu renderowania czynimy go bieżącym i wywołujemy zewnętrzną funkcję GLSetupRC. Ta funkcja zainicjuje wymagany kontekst renderowania, po czym możemy go odłożyć. Dzięki temu możemy używać kilku kontekstów renderowania, na wypadek gdybyśmy chcieli zastosować OpenGL w kilku oknach. (Nie robimy tego w naszym przykładzie, ale jeśli wykorzystasz go jako punkt wyjścia dla własnego programu, nie będziesz już musiał przebudowywać całego kodu).
Usuwanie kontekstu renderowania
Powinniśmy pójść krok dalej i zanim o tym zapomnimy, dodać kod czyszczący i usuwający kontekst renderowania. Wykorzystamy do tego procedurę obsługi komunikatu WM_DESTROY, pokazaną na listingu 22.2.
Listing 22.2. Procedura obsługi komunikatu WM_DESTROY, usuwająca kontekst renderowania______
// Okno jest niszczone, więc robimy porządki
void TOwlglWindowView::EvDestroy()
{
// Usuwanie timera
KillTimer(101);
// Usunięcie kontekstu renderowania... wglMakeCurrent(NULL,NULL); wglDeleteContext(m_hRC);
// ... i kontekstu urządzenia
::ReleaseDC(this->GetHandle() ,m_hDC);
TWindowYiew::EvDestroy();
Obsługa zmiany rozmiaru okna
Gdy zmienia się rozmiar okna, otrzymuje ono komunikat WM_SIZE. Za pomocą Class-Experta dodamy procedurę obsługi tego komunikatu i z jej wnętrza wywołamy zewnętrzną funkcję GLResize, przekazując jej nowe wymiary obszaru roboczego okna. Przed wywołaniem tej funkcji musimy uczynić kontekst renderowania bieżącym, gdyż w przeciwnym razie wywołania funkcji OpenGL w funkcji GLResize nie przyniosą efektu. Oto kod procedury obsługi (listing 22.3):
Listing 22.3. Procedura obsługi komunikatu WM_PAINT, aktualizująca wymiary okna roboczego___
void TOwlglWindowView::EvSize(uint sizeType, TSizei size) {
TWindowView::EvSize(sizeType, size);
wglMakeCurrent(m_hDC,m_hRC); GLResize(size.ex, size.cy); wglMakeCurrent(m_hDC,NULL);
650 Część IV «• OpenGL i...
Renderowanie sceny
Teraz możemy dodać kod odpowiedzialny za renderowanie sceny w OpenGL. Metoda EvPaint klasy widoku jest wywoływana za każdym razem, gdy okno otrzymuje komunikat WM_PAINT. Wewnątrz tej metody wybieramy kontekst renderowania jako bieżący i wywołujemy funkcję GLRenderScene, zawierającą jedynie wywołania funkcji OpenGL. Kod tej metody został przedstawiony na listingu 22.4. Ponieważ wcześniej zażądaliśmy trybu z podwójnym buforowaniem, zaraz po tym wywołujemy funkcję SwapBuffers. Oprócz tego, w obsłudze komunikatu WM_PAINT należy zatwierdzić obszar roboczy okna informując Windows o zakończeniu rysowania. Jeśli tego nie uczynimy, Windows będzie bez przerwy posyłać do okna kolejne komunikaty WM_PAINT.
Listing 22.4. Procedura obsługi komunikatu WM_PAINT____________________________
void TOwlglWindowView::EvPaint() {
// Uczynienie kontekstu renderowania bieżącym
wglMakeCurrent(m_hDC,m_hRC);
// Wywołanie zewnętrznego kodu OpenGL GLRenderScene(NULL); wglMakeCurrent(NULL,m_hRC);
// Wyświetlenie sceny w oknie SwapBuffers(m_hDC);
// Zatwierdzenie okna Validate();
Unikanie niepotrzebnego czyszczenia okna
Za każdym razem, gdy okno zmienia rozmiar lub jest unieważniane, Windows czyści tło okna przed odrysowaniem. Ponieważ naszym kolorem tła OpenGL jest czerń, to wymazywanie (które powoduje wymalowanie okna na biało) powoduje migotanie za każdym razem, gdy jest wywoływana funkcja EvPaint.
Aby zapobiec migotaniu, przesłonimy domyślną procedurę obsługi komunikatu WM_ ERASEBACKGROUND. Zwykle okno jest wymazywane przed odmalowaniem po zmianie rozmiaru. Jeśli ta funkcja zwróci wartość FALSE, okno nie będzie odmalowywane i nie wystąpi migotanie. Zwykle ta funkcja zwraca wartość funkcji TWindow-View::EvEraseBkgnd(dc), odpowiadającej za wymazywanie tła, ale możemy po prostu zwrócić wartość FALSE (listing 22.5).
Rozdział 22. » OpenGL i OWL____________________________________651
Listing 22.5. Zabezpieczenie okna przed niepotrzebnym wymazywaniem____________________
// Obsługa komunikatu WM_ERASEBACKGROUND bool TOwlglWindowView::EvEraseBkgnd(HDC dc) (
return FALSE;
Niech się kręci
Choć oczywiście nie jest to wymogiem, w przykładzie w tym rozdziale używamy timera w celu unieważniania obszaru roboczego okna co 200 milisekund (czyli wymuszamy odrysowanie zawartości okna przez kod OpenGL). Kod w module glcode.c przy każdym wywołaniu obraca nasze obiekty. Dzięki temu powstaje efekt płynnej animacji obiektów - w tym przypadku trzech trójwymiarowych liter. Zaimplementowanie timera jest proste: należy ustawić timer w funkcji EvCreate(), dodać procedurę obsługi komunikatu WM_TIMER, a na zakończenie programu usunąć timer w funkcji EvDestroy. Stanowi to standardową praktykę programowania w Windows, zaś odpowiedni kod został przedstawiony na listingu 22.6.
Wynik działania programu w obecnej postaci został przedstawiony na rysunku 22.5.
Rysunek 22.5.
Animowane litery w programie OWLGL
Listing 22.6. Kod tworzący, obsługujący i niszczący timer wykorzystywany do animacji_____
int TOwlglWindowYiew::EvCreate(CREATESTRUCT fars createStruct)
SetTimer(200,101,NULL); // 5 razy na sekundę
652__________________________________________Część IV » OpenGL i.
// Obsługa komunikatu WM_TIMER
void TOwlglWindowView::EvTimer(uint timerld)
{
TWindowView::EvTimer(timerld);
// Wymuszenie odrysowania okna Invalidate(); }
// Okno jest niszczone, więc robimy porządki
void TOwlglWindowView::EvDestroy()
{
// Usuwanie timera
KillTimer(101);
Obsługa palety
Ostatnie poprawki do naszego przykładu OWL dotyczą obsługi palety, co wiąże się z utworzeniem i zrealizowaniem palety RGB dla urządzeń korzystających z palety (kart 256-kolorowych). Zamiast przechowywać uchwyt palety, tak jak w rozdziale 8, stworzymy obiekt OWL typu TPalette. W pliku owlglwnf.h deklarujemy wskaźnik do obiektu klasy TPalette:
TPalette *m_pPalette; // Paleta logiczna
a następnie ręcznie dodajemy do klasy TOwlglWindowYiew metodę inicjującą paletę. Jej kod, przedstawiony na listingu 22.7, jest niemal identyczny z kodem funkcji Get-OpenGLPalette, zaprezentowanej w rozdziale 8, z tym że zamiast zwracania uchwytu palety konstruowany jest obiekt klasy TPalette.
Listing 22.7. Tworzenie i inicjowanie obiektu CPalette_____________________________
// W razie potrzeby utworzenie palety
void TOwlglWindowYiew::SetupPalette(HDC hDC)
{
PIKELFORMATDESCRIPTOR pfd; // Deskryptor formatu pikseli LOGPALETTE *pPal; // Wskaźnik do obszaru pamięci dla palety
// logicznej
int nPixelFormat; // Indeks formatu pikseli
int nColors; // Ilość pozycji palety
int i; // Licznik
BYTE RedRange,GreenRange,BlueRange;
// Zakres dla każdej pozycji koloru (7,7 // i 3)
// Pobranie indeksu formatu pikseli oraz deskryptora formatu // pikseli
Rozdział 22. » OpenGL i OWI____________________________________653
nPixelFormat = GetPixelFormat(hDC); DescribePixelFormat(hDC, nPixelFormat,
sizeof(PIKELFORMATDESCRIPTOR), Spfd);
// Czy ten format pikseli wymaga palety? Jeśli nie, po prostu nie // twórz palety i wróć
if(!(pfd.dwFlags & PFD_NEED_PALETTE)) return;
// Ilość pozycji w palecie. 8 bitów oznacza 256 pozycji nColors = l « pfd.cColorBits;
// Zaalokowanie pamięci na strukturę logicznej palety // i wszystkie jej pozycje
pPal = (LOGPALETTE*)malloc(sizeof(LOGPftLETTE) +nColors*sizeof(PALETTEENTRY));
// Wypełnienie nagłówka palety pPal->palVersion = 0x300; // Windows 3.0 pPal->palNumEntries = nColors; // rozmiar tabeli
// Budowanie maski wszystkich jedynek. Tworzy liczbę
// reprezentowaną przez x dolnych bitów ustawionych, gdzie
// x = pfd.cRedBits, pfd.cGreenBits oraz pfd.cBlueBits.
RedRange = (l « pfd.cRedBits) -1;
GreenRange = (l « pfd.cGreenBits) - 1;
BlueRange = (l « pfd.cBlueBits) -1;
// Przejście przez wszystkie pozycje palety
for(i =0; i < nColors; i++)
{
// Wypełnienie 8-bitowych odpowiedników dla każdego komponentu pPal->palPalEntry[i].peRed = (i » pfd.cRedShift) & RedRange; pPal->palPalEntry[i].peRed = (unsigned char)(
(double) pPal->palPalEntry[i].peRed * 255.0 / RedRange);
pPal->palPalEntry[i].peGreen = (i » pfd.cGreenShift) &
GreenRange; pPal->palPalEntry[i].peGreen = (unsigned char)(
(double)pPal->palPalEntry[i].peGreen * 255.0 /
^GreenRange) ;
pPal->palPalEntry[i].peBlue = (i » pfd.cBlueShift) &
^BlueRange;
pPal->palPalEntry[i].peBlue = (unsigned char)(
(double)pPal->palPalEntry[i].peBlue * 255.0 / BlueRange);
pPal->palPalEntry[i].peFlags = (unsigned char) NULL; }
// Utworzenie palety
m_pPalette = new TPalette(pPal);
// Wybranie i zrealizowanie palety dla bieżącego kontekstu // urządzenia
if (SelectPalette(hDC,m_pPalette->GetHandle() ,FALSE) <== NULL) ::MessageBox(NULL,"Nie powiódł się wybór palety", "Błąd",MB OK);
654________________________________________Część IV » OpenGL i.
if(RealizePalette(hDC) == NULL)
::MessageBox(NULL,"Nie powiodło się zrealizowanie palety", "Błąd",MB_OK);
// Zwolnienie pamięci użytej przez strukturę palety logicznej free(pPal);
Nie zapomnij o wywołaniu tej funkcji w procedurze obsługi komunikatu WM_CREATE. Powinieneś to zrobić przed utworzeniem kontekstu renderowania:
// Ustawienie formatu pikseli dla kontekstu urządzenia SetPixelFormat(m_hDC, nPixelFormat, &pfd);
// Utworzenie palety 3-3-2 SetupPalette(m_hDC);
// Utworzenie kontekstu renderowania m_hRC = wglCreateContext(m_hDC);
Po użyciu ClassExperta w celu dodania odpowiednich funkcji obsługujących komunikaty WM_QUERNEWPALETTE oraz WM_PALETTECHANGED, nasz kod odpowiadający za realizację palety wygląda tak, jak na listingu 22.8.
Listing 22.8. Kod realizujący paletę dla klasy -widoku______________________________
// Obsługuje komunikat WM_QUERYNEWPALETTE bool TOwlglWindowView::EvQueryNewPalette()
bool result;
// Jeśli paleta została utworzona if(m_pPalette != NULL)
int nRet;
// Wybranie palety w bieżącym kontekście urządzenia
if(SelectPalette(m_hDC, m_pPalette->GetHandle(),FALSE) ==
ONULL)
::MessageBox(NULL,"Nie można wybrać palety","Błąd",MB_OK);
// Odwzorowanie pozycji bieżącej palety na paletę systemową. // Zwracaną wartością jest ilość zmodyfikowanych pozycji // palety. nRet = RealizePalette(m_hDC);
// Przemalowanie, które wymusza nowe odwzorowanie palety // w bieżącym oknie Invalidate();
return nRet;
// Wywołanie domyślnej funkcji result = TWindowView::EvQueryNewPalette(); return result; }
Rozdział 22. * OpenGL i OWI____________________________________655
void TOwlglWindowView::EvPaletteChanged(HWND hWndPalChg) {
// Tylko, gdy paleta została utworzona lub nie jest to
// okno, które ją utworzyło
if((m_pPalette != NULL) ss (hWndPalChg != this->HWindow))
{
// Wybranie palety w kontekście urządzenia
::SelectPalette(m_hDC,m_pPalette->GetHandle(),FALSE);
// Odwzorowanie pozycji w paletę systemową ::RealizePalette(m_hDC);
// Przemapowanie bieżących kolorów do nowo zrealizowanej // palety
::UpdateColors(m_hDC); return; }
// Wywołanie domyślnej funkcji TWindowYiew::EvPaletteChanged(hWndPalChg);
Kod realizujący paletę bardzo przypomina kod z rozdziału 8. Tutaj jednak Windows nie przekazuje tych komunikatów bezpośrednio do klasy widoku, lecz do klasy TDecora-tedFrame aplikacji (w naszym przykładzie SDIDecFrame). Dzieje się tak, ponieważ Windows wysyła komunikaty o zmianie palety jedynie do głównego okna aplikacji; główne okno odpowiada za rozesłanie tych komunikatów do odpowiednich okien potomnych.
Ponownie więc użyjemy ClassExperta w celu dodania dwóch procedur obsługi komunikatów palety do klasy SDIDecFrame. W tych procedurach po prostu wyznaczamy potomny widok TWindowYiew i posyłamy mu niezmienione komunikaty palety, pozwalając widokowi na ich samodzielne obsłużenie. Te procedury obsługi zostały przedstawione na listingu 22.9.
Listing 22.9. Kod klasy SDIDecFrame przesyłający komunikaty palety do okna widoku___________
// Przekierowanie komunikatu WM_QUERYNEWPALETTE do okna widoku
bool TSDIDecFrame::EvQueryNewPalette()
{
bool result;
TWindow *pGLWindow;
// Pobranie potomnego okna SDI pGLWindow = GetClientWindow();
// Wysłanie komunikatu if(pGLWindow)
pGLWindow->SendMessage(WMJ2UERYNEWPALETTE, O, 0) ;
return TRUE;
656________________________________________Część IV » OpenGL l
// Przekierowanie komunikatu WM_PALETTECHANGES do okna widoku
void TSDIDecFrame::EvPaletteChanged(THandle hWndPalChg)
(
TWindow *pGLWindow;
// Pobranie potomnego okna SDI pGLWindow = GetClientWindow();
// Wysłanie komunikatu if(pGLWindow)
pGLWindow->SendMessage(WM_PALETTECHANGED, (UINT)hWndPalChg,
(OINT)0);
Podsumowanie
W tym rozdziale omówiliśmy zagadnienia związane z użyciem OpenGL w aplikacjach opartych na OWL, pokazując, gdzie należy ustawić style okna wymagane przez OpenGL, gdzie i kiedy ustawić format pikseli, a także jak utworzyć kontekst renderowania. Przykładowy program ilustruje także, kiedy należy uczynić kontekst renderowania bieżącym oraz jak w razie potrzeby zrealizować paletę za pomocą klasy OWL TPalette.
Przykładową aplikację zaprezentowaną w tym rozdziale będziesz mógł wykorzystać jako punkt wyjścia dla własnych programów OpenGL. Oprócz tego, szkielet aplikacji - wraz z całym kodem OpenGL w module glcode.c - może posłużyć do zamiany innych przykładów C/OpenGL na programy OWL. Inne przykładowe programy OWL korzystające z OpenGL znajdziesz na płytce CD-ROM.
658_______________________________________Część IV » OpenGL i...
argumenty, typ zwracanej wartości oraz plik DLL-a, w którym zawarta jest dana funkcja.
Z używaniem OpenGL w jednym ze wspomnianych środowisk wiążą się jednak dwie niedogodności. Po pierwsze, korzystanie z funkcji OpenGL jest niesłychanie żmudne! W każdym ze środowisk musi zostać zdefiniowana i zaimportowana każda używana funkcja OpenGL. Oprócz tego, jej argumenty i zwracane wartości muszą zostać odwzorowane na lokalne typy danych używanego środowiska. Co więcej, nie tylko funkcje muszą zostać zdefiniowane, ale także wszystkie zmienne stanu oraz znaczniki (GL_AC-CUM, GL_LOAD itd.) z plików nagłówkowych. Co gorsza, trzeba to zrobić osobno w każdym środowisku, w którym chcesz korzystać z OpenGL!
Kolejną niedogodnością jest wymaganie OpenGL, aby tworzone okna miały ustawione style WS_CLIPCHILDREN oraz WS_CLIPSIBLINGS. W niektórych ze środowisk bardzo trudno jest dobrać się do jakichkolwiek niskopoziomowych stylów okna, chyba że gdzieś dostępne jest okno dialogowe z odpowiednimi opcjami. W najgorszym przypadku trzeba wyeksportować z Windows funkcję CreateWindow i wywołać ją z wnętrza swojego programu.
Jeśli nie przeraża cię perspektywa brnięcia przez te niedogodności w celu użycia OpenGL powiedzmy, w Yisual Basicu, być może powinieneś po prostu napisać DLL-a w C, który zawierałby cały kod związany z renderowaniem OpenGL, a następnie wywoływać go z wnętrza procedur Basica. To rozwiązanie, mimo że najlepsze pod względem wydajności, nie ma jednak zastosowania dla programistów nie obeznanych z C/C++.
Jeśli jednak kupiłeś tę książkę, aby nauczyć się programować przy użyciu OpenGL i do tej pory dawałeś sobie radę z analizą przykładów i definicji funkcji, wciąż jeszcze jest dla ciebie nadzieja!
Magia obiektów
Termin obiektowo zorientowany jest chyba, podobnie jak termin klient/serwer, jednym z najczęściej nadużywanych i błędnie używanych sloganów lat 90-tych. Chcielibyśmy uniknąć poważnych rozważań na ten temat, należy jednak wspomnieć o jednej z najważniejszych technologii umożliwiających wielokrotne wykorzystanie tego samego kodu.
Tą technologią jest OLE (Object Linking and Embedding) lub - co ważniejsze dla tego rozdziału - OCX (OLE Gustom Control). Gdy Microsoft opracował Yisual Basic i umożliwił tworzenie własnych kontrolek poprzez VBX, w ciągu dosłownie jednej nocy powstała nowa gałąź przemysłu. Powstały nowe firmy, przynoszące fortuny, zajmujące się dostarczaniem programistom Yisual Basica nowych i interesujących programowych gadżetów. Wkrótce powstały konkurencyjne środowiska (PowerBuilder, Delphi i inne), także umożliwiające użycie kontrolek VBX w swoich aplikacjach. To jeszcze bardziej podsyciło zapał do tworzenia komponentów nadających się do wielokrotnego zastosowania.
Rozdział 23.
OpenGL w Visual Basic S4GL
Poza rozdziałami 21 i 22, w całej książce opisywaliśmy OpenGL API z punktu widzenia programisty C. Jednak żaden opis zagadnień związanych z programowaniem Windows nie mógłby być kompletny bez dyskusji na temat języków czwartej generacji (4GL) i innych popularnych wizualnych środowisk programowych. W tym rozdziale omówimy pokrótce wymagania związane z używaniem OpenGL w niektórych z takich środowisk. Oprócz tego, zademonstrujemy dołączone do tej książki kontrolki OCX OpenGL, które mogą zostać użyte w dwóch najpopularniejszych środowiskach programowych Win32: w Yisual Basic 6.0 Microsoftu oraz Delphi 2.0 Borlanda.
Pisząc ten rozdział, zakładaliśmy, że potrafisz się posługiwać wykorzystywanym przez siebie środowiskiem (Yisual Basic lub Delphi) oraz wiesz, jak używać i wywoływać metody kontrolek OCX. Jednak nawet jeśli nie masz doświadczenia z kontrolkami OCX, przekonasz się, jak proste mogą one być w użyciu.
Wymagany dostęp niskiego poziomu
Z OpenGL może skorzystać każdy język lub środowisko programowania Windows, pod warunkiem, że umożliwia niskopoziomowy dostęp do Win32 API i innych bibliotek zawartych w plikach DLL. Większość środowisk i narzędzi na to pozwala, w celu umożliwienia integracji aplikacji z innymi bibliotekami oraz choćby po to, aby programista mógł skorzystać z nowych elementów systemu operacyjnego, które jeszcze nie były dostępne w momencie tworzenia danego narzędzia.
Całe API OpenGL mieści się w dwóch plikach DLL: opengl32.dll oraz glu32.dll. Tak jak większość API Win32 jest dostępnych bezpośrednio z bibliotek DLL, także w przypadku bibliotek opengl32.dll, glu32.dll i innych możesz wywoływać ich funkcje bezpośrednio z języka programowania wyższego poziomu. W każdym narzędziu i środowisku jest to jednak odmiennie zorganizowane. Zwykle musisz podać nazwę funkcji, jej
660________________________________________Część IV » OpenGL i...
prostu umieść kontrolkę w formularzu i zacznij wywoływać jej metody, tak jakby były funkcjami i poleceniami OpenGL.
Każde polecenie nosi nazwę taką jak w OpenGL API, lecz z odrzuconym przedrostkiem g/. Gdy kontrolkę nazwiesz g/, twój kod będzie wyglądał bardzo podobnie do kodu C używającego OpenGL. Aby się o tym przekonać, zajrzyj do przykładów dla VB i Delphi w dalszej części rozdziału.
Kontrolka odpala dwa zdarzenia, które możesz przechwycić w swojej aplikacji. Pierwszym z nich jest SetupRC, wywoływane za pierwszym razem, gdy kontrolka próbuje odrysować swój obszar roboczy. W tym momencie format pikseli i kontekst renderowania zostały już utworzone i przygotowane. W swoim kodzie możesz przygotować oświetlenie, kolor tła itd. Drugim zdarzeniem jest Render, wywoływane za każdym razem, gdy kontrolka ma zostać odrysowana. Umieszczając w tym miejscu swój kod renderowania, możesz efektywnie wypełnić obszar roboczy za pomocą OpenGL.
Podczas używania kontrolki musisz mieć na uwadze jeszcze parę innych rzeczy:
* Ponieważ mógłbyś użyć w swojej aplikacji więcej niż jednej kontrolki OpenGL, kontrolka nie może zakładać, że kontekst renderowania zawsze jest dostępny dla niej. W związku z tym zostały dostarczone dwie metody: MakeCurrent oraz MakeNotCurrent. Wszystkie wywołania OpenGL muszą być ujęte pomiędzy wywołania tych dwóch funkcji; dotyczy to także funkcji SetupRC i Render.
* Oprócz tego, jeśli uczynisz kontekst renderowania bieżącym dla kontrolki, zawsze możesz bezpośrednio wywoływać OpenGL API. Możesz to robić w celu poprawienia wydajności lub w przypadku chęci wykorzystania nowych funkcji w następnych wersjach OpenGL, które nie zostały zawarte na liście metod kontrolki. Do dyspozycji masz także kod źródłowy kontrolki, więc jeśli masz Yisual C++ i spore zacięcie, zawsze możesz sam zmodyfikować kontrolkę.
* Dla kontrolki jest tworzona paleta 3-3-2, realizowana za każdym razem, gdy jest odpalane zdarzenie Render. Próby samodzielnego manipulowania paletą mogą prowadzić do nieprzewidzianych rezultatów.
* Na koniec, okno kontrolki jest podwójnie buforowane, więc aby wyświetlić obraz, zawsze musisz wywołać metodę SwapBuffers.
Znaczniki OpenGL
Niemożliwe jest korzystanie z funkcji i poleceń OpenGL bez możliwości dostępu do wielu specjalnych znaczników i zmiennych stanu. Każda z wartości tych znaczników jest dostępna poprzez metodę o nazwie odpowiadającej nazwie danego znacznika. Nazwy tych metod zawierają jednak jedynie małe litery, gdyż przy zachowaniu oryginalnych nazw występowały problemy z prawdziwymi definicjami w plikach nagłówkowych. Choć w pewnych przypadkach sensowne byłoby zaimplementowanie pewnych zmiennych stanu jako właściwości kontrolki, jednak nie zawsze byłoby to możliwe. Dla zachowania spójności zdecydowaliśmy się zastosować metody dopasowane do OpenGL na tyle, na ile to było możliwe.
Rozdział 23. » OpenGL w Visual Basic i 4GL___________________________661
Choć wiele funkcji posiada kilka odmian, wszystkie zostały zaimplementowane jako pojedyncza metoda. Oznacza to, że funkcje takie jak
void glVertex2fv(const GLfloat *v) ;
zostały zaimplementowane w postaci metody jako
Vertex2(float x, float y)
Dostępny jest plik pomocy (WaiteGL.hlp) zawierający spis wszystkich metod zdefiniowanych w kontrolce WaiteGL. Zostały one podzielone na trzy biblioteki OpenGL (gl, glu i glaux) oraz definicje wszystkich stałych. Aby skorzystać z pliku pomocy, odszukaj potrzebną funkcję OpenGL, a następnie poszukaj opisu metody kontrolki WaiteGL dla tej funkcji.
Przejdźmy teraz do tworzenia programów korzystających z OpenGL w dwóch najpopularniejszych środowiskach 4GL. W następnej sekcji omówimy zagadnienia związane z Yisual Basicem. Jeśli używasz Delphi 2.0 (w wersji 32-bitowej), możesz przejść od razu do następnej sekcji.
Instalacja i użycie kontrolki WaiteGL w Visual Basicu 6.0
Aby użyć kontrolki WaiteGL, musisz ją najpierw zarejestrować w systemie operacyjnym (Windows NT lub Windows 95). Skopiuj plik ,ocx do kartoteki systemowej i uruchom dostarczony program ocxreg.exe. W linii poleceń tego programu podaj nazwę pliku ,ocx oraz polecenie install (aby zarejestrować kontrolkę) lub uninstall (aby ją wyrejestrować). Na przykład:
ocxreg.exe WaiteGL.ocx install
Program (wraz z kodem źródłowym) znajdziesz na dołączonej do książki płytce CD-ROM, w kartotece tego rozdziału.
Instalowanie kontrolki
Po zarejestrowaniu kontrolki w systemie, musisz ją zainstalować w palecie narzędzi Yisual Basica. W tym celu kliknij prawym przyciskiem myszy w obszarze palety komponentów i z wyświetlonego menu lokalnego wybierz opcję Components.... Z listy w oknie dialogowym z rysunku 23.1 wybierz pozycję WaiteGL OLE Control module, po czym kliknij na przycisk OK. Teraz będziesz mógł przeciągnąć kontrolkę z palety na formularz, a następnie dostosować jej rozmiar.
Część IV «• OpenGL i.
662
Rysunek 23.1.
Instalowanie kontrolki WaiteGL w Visual Basicu
Przykład w Visual Basicu
W naszej przykładowej aplikacji VB umieścimy kontrolkę na formularzu i nadamy jej nazwę g/. W formularzu umieścimy także timer, odpalany co 200 milisekund. Spójrz na rysunek 23.2. Zauważysz, że kontrolka nie odrysowuje ani nie czyści swojego obszaru roboczego. Dzieje się tak, ponieważ kod rysunkowy musi zostać napisany w Basicu i umieszczony w procedurze obsługi zdarzenia Render.
Rysunek 23.2.
Formularz VB z kontrolka OCX
Jak już wspominaliśmy, w naszym kodzie musimy obsłużyć dwa zdarzenia zgłaszane przez kontrolkę. Stworzymy kod inicjujący kontekst renderowania przez ustawienie początkowej bryły widzenia, wybranie koloru tła, a być może również koloru rysowania i definicji oświetlenia. Listing 23.1 zawiera kod służący do przygotowania kontekstu renderowania. Nasz kod po prostu ustawia kolor tła, kolor rysowania oraz bryłę widzenia.
Rozdział 23. » OpenGL w Visual Basic i 4GL___________________________663
Listing 23.1. Przygotowanie kontekstu renderowania w Yisual Basicu____________________
Private Sub gl_SetupRC()
Rem Uczyń kontekst renderowania bieżącym gl.MakeCurrent
Rem Ustawienie koloru tła na czarny gl.ClearColor 0#, 0#, 0#, 1#
Rem ustanowienie bryły widzenia
gl.Loadldentity
gl.Ortho -100#, 100#, -100#, 100#, -100#, 100#
Rem Ustalenie koloru rysowania
REM Zrzucenie poleceń graficznych
Rem Odłożenie kontekstu renderowania
gl.Color O, O, 255, 255
gl.Flush
gl.MakeNotCurrent
End Sub
Malowanie w oknie OpenGL
Kolejnym zdarzeniem, które musimy obsłużyć, jest zdarzenie Render. To zdarzenie jest odpalane przez kontrolkę za każdym razem, gdy trzeba odświeżyć jej obszar roboczy. W tej funkcji umieścimy nasz kod odwołujący się do metod kontrolki służących do rysowania. Listing 23.2 zawiera kod Yisual Basica rysujący siatkowy imbryk do herbaty z biblioteki AUX.
Listing 23.2. Kod Visual Basica rysujący siatkowy imbryk do herbaty_____________________
Private Sub gl_Render()
Rem Uczyń kontekst renderowania bieżącym gl.MakeCurrent
Rem Wyczyszczenie ekranu i wyrysowanie imbryka z biblioteki AUX gl.Clear (gl.glColorBufferBit)
gl.auxWireTeapot (55#)
Rem Zrzucenie poleceń graficznych
Rem Odłożenie kontekstu renderowania
Rem Przerzucenie buforów
gl.Flush
gl.MakeNotCurrent
gl.SwapBuffers End Sub
Zwróć uwagę, że najpierw kontekst renderowania jest wybierany jako kontekst bieżący, zaś po wywołaniu kodu rysunkowego jest odkładany. Nie jest to potrzebne, gdy posiadasz tylko jedną kontrolkę i kontekst renderowania, lecz zapewnia, że nie będą wymagane późniejsze zmiany w kodzie, gdy zechcesz zastosować dodatkową kontrolkę
Część IV » OpenGL i...
664
WaiteGL. Po odłożeniu kontekstu renderowania musisz wywołać metodę SwapBuffers w celu przywołania rysunku na ekran.
Trochę ruchu
Przedstawiony powyżej kod wystarczy do wyświetlenia rysunku OpenGL. W tym przykładzie jednak spróbujemy dodać nieco animacji. W formularzu z rysunku 23.2 umieścimy timer, odpalany co 200 milisekund. Za każdym tyknięciem timera nasza funkcja uczyni kontekst renderowania bieżącym dla kontrolki, obróci macierz widoku o 5°, a następnie odłoży kontekst renderowania. Na koniec polecimy kontrolce, aby się od-rysowała, wywołując bezpośrednio metodę gl_Render. Spójrz na listing 23.3.
Listing 23.3. Funkcja timera obracająca bryłą widzenia o 5"__________________________
Private Sub Timerl_Timer()
Rem Uczyń kontekst renderowania bieżącym gl.MakeCurrent
Rem Obrót o 5 stopni gl.Rotate 5#, 1#, 1#, 0.5
Rem Odłożenie kontekstu renderowania Rem i wymuszenie odmalowania gl.MakeNotCurrent gl_Render
End Sub
Ukończona aplikacja Yisual Basica została przedstawiona na rysunku 23.3.
Rysunek 23.3.
Wynik działania programu w VB korzystającego z OpenGL
665
Rozdział 23. * OpenGL w Visual Basic i 4GL
Wykorzystanie kontrolki OCX w Delphi 2.0
Aby użyć kontrolki WaiteGL, musisz ją najpierw zarejestrować w systemie operacyjnym (Windows NT lub Windows 95). Skopiuj plik .ocx do kartoteki systemowej i uruchom dostarczony program ocxreg.exe. W linii poleceń tego programu podaj nazwę pliku .ocx oraz polecenie install (aby zarejestrować kontrolkę) lub uninstall (aby ją wyrejestrować). Na przykład:
ocxreg.exe WaiteGL.ocx install
Program (wraz z kodem źródłowym) znajdziesz na dołączonej do książki płytce CD-ROM, w kartotece tego rozdziału.
Instalowanie kontrolki
Po zarejestrowaniu kontrolki w systemie, musisz ją zainstalować w palecie narzędzi Delphi. W głównym menu wybierz Component, a następnie polecenie Install. Kliknij na przycisk OCX, po czym w oknie dialogowym z rysunku 23.4 pojawi się lista zarejestrowanych kontrolek OCX, które mogą zostać zainstalowane.
Wybierz pozycję WaiteGL OLE Control module, po czym kliknij na przycisk OK. Spowoduje to dodanie naszej kontrolki do palety narzędzi. Po prostu przeciągnij kontrolkę na środek formularza, a otrzymasz okno przeznaczone do rysowania za pomocą OpenGL.
Rysunek 23.4.
Instalowanie kontrolki WaiteGL w Delphi
Import OLE Control
VBSMSChailWizaid yD First Impression Library VD Formuła One Lfciary VCI WsualSoellet Litu
Przykład w Delphi
W przykładzie w Delphi zaczniemy od utworzenia nowego formularza i umieszczenia na jego środku kontrolki WiggleGL, tak aby zajmowała większość powierzchni. Oprócz
666
Część IV » OpenGL i...
tego w formularzu umieścimy timer, który zostanie wykorzystany do prostej animacji. Ukończony formularz przedstawia rysunek 23.5. Zauważ, że kontrolką nie odrysowuje ani nie czyści swojego obszaru roboczego. Dzieje się tak, ponieważ kod rysunkowy musi zostać napisany w Pascalu i umieszczony w procedurze obsługi zdarzenia Render.
Rysunek 23.5.
Formularz Delphi z kontrolką OCX
Na rysunku 23.6 widzimy zakładkę Events inspektora obiektów; jak widać, kontrolką wywołuje dwie unikatowe procedury obsługi: OnRender oraz OnSetupRC
Rysunek 23.6.
Zdarzenia dostępne dla kontrolki WaiteGL
ijgl: TWaileGLDrl
'
Po dwukrotnym kliknięciu na zdarzenie OnSetupRC zostanie stworzona funkcja glSe-tupRC. Edytor zostanie otwarty umożliwiając zdefiniowanie funkcji. Kod z listingu 23.4 przedstawia przygotowanie kontekstu renderowania, sprowadzające się do wybrania czarnego tła oraz równoległej bryły widzenia.
Listing 24.3. Kod Delphi wywoływany w odpowiedzi na zdarzenie SetupRC wywołane przez kontrolką
procedurę TForml.glSetupRC(Sender: TObject); begin
Rozdział 23. » OpenGL w Visual Basic i 4GL___________________________667
// Uczyń kontekst renderowania bieżącym gl.MakeCurrent();
// Ustalenie koloru tła i bryły widzenia gl.ClearColor(0.0, 0.0, 0.0, 1.0); gl.Loadldentity(); gl.0rtho(-100,100,-100,100,-100,100);
// Zrzucenie poleceń graficznych i odłożenie // kontekstu renderowania gl.Flush(); gl.MakeNotCurrent(); end;
Malowanie w oknie OpenGL
Funkcja glRender jest tworzona w podobny sposób, przez dwukrotne kliknięcie na zdarzenie OnSetupRC. W tej funkcji umieścimy nasz kod odwołujący się do metod kon-trolki służących do rysowania. Listing 23.5 zawiera kod Delphi rysujący siatkowy imbryk do herbaty z biblioteki AUX. Zwróć uwagę, że najpierw kontekst renderowania jest wybierany jako kontekst bieżący, zaś po wywołaniu kodu rysunkowego jest odkładany. Nie jest to potrzebne, gdy posiadasz tylko jedną kontrolkę i kontekst renderowania, lecz zapewnia, że nie będą wymagane późniejsze zmiany w kodzie, gdy zechcesz zastosować dodatkową kontrolkę WaiteGL. Po odłożeniu kontekstu renderowania musisz wywołać metodę SwapBuffers w celu przywołania rysunku na ekran.
Listing 23.5. Kod Delphi rysujący siatkowy imbryk do herbaty_________________________
procedurę TForml.glRender(Sender: TObject); begin
// Uczyń kontekst renderowania bieżącym
gl.MakeCurrent();
// Wyczyszczenie tła i rysowanie imbryka gl.Clear(gl.glColorBufferBit()); gl.Color(0, O, 255, 255);
gl.auxWireTeapot(55.0);
// Zrzucenie poleceń graficznych i odłożenie // kontekstu renderowania // Przerzucenie buforów gl.Flush (); gl.MakeNotCurrent (); gl.SwapBuffers () ; end;
Trochę ruchu
Przedstawiony powyżej kod wystarczy do wyświetlenia rysunku OpenGL. W tym przykładzie jednak spróbujemy dodać nieco animacji. W formularzu z rysunku 23.6 umieś-
Część IV » OpenGL i...
668
ciliśmy timer odpalany co 200 milisekund. Za każdym tyknięciem timera, nasza funkcja uczyni kontekst renderowania bieżącym dla kontrolki, obróci macierz widoku o 5°, a następnie odłoży kontekst renderowania. Na koniec polecimy kontrolce, aby się odry-sowała, wywołując pośrednio funkcję Delphi Invalidate(). Ponieważ w Delphi wszystkie kontrolki OCX są oknami, wszelkie komunikaty lub polecenia wysyłane do okien mogą być wysyłane bezpośrednio do kontrolki. Spójrz na listing 23.6.
Listing 23.6. Funkcja timera obracająca bryłę widzenia o 5"__________________________
procedurę TForml.TimerITimer(Sender: TObject); begin
// Uczyń kontekst renderowania bieżącym
// Nieco obróć scenę
gl.MakeCurrent();
gl.Rotate(5.0,0.0,1.0,0.5);
gl.MakeNotCurrent();
// Odmaluj kontrolkę gl.Invalidate();
end;
Ukończona aplikacja Delphi została przedstawiona na rysunku 23.7.
Rysunek 23.7.
Wynik działania programu stworzonego w Delphi, korzystającego z OpenGL
Parę uwag na temat kodu źródłowego
Kontrolka WaiteGL została napisana w Yisual C++ 4.0 przy użyciu MFC w wersji 4.0. Ta nowa wersja Yisuala znacznie ułatwia tworzenie kontrolek ÓCX i z pewnością spowoduje, że zaleją nas niezliczone ilości wykorzystywalnych kontrolek OLE. Celem tego rozdziału nie było wprowadzenie do tworzenia kontrolek OCX. Chcieliśmy jedynie za-
Rozdział 23. » OpenGL w Visual Basic i 4GL___________________________669
prezentować jedną z nich, używającą OpenGL i umożliwiającą wywoływanie kodu OpenGL z Yisual Basica, Delphi i innych środowisk obsługujących kontrolki OCX.
Oczywiście, na płytce CD-ROM, w folderze tego rozdziału, znajduje się pełny kod źródłowy kontrolki. Kod został wygenerowany przez kreatora Microsoft Control Wizard i jest wypełniony komentarzami. Oprócz tego, metody i znaczniki zostały podzielone pomiędzy cztery pliki źródłowe, w celu ułatwienia analizy i modyfikacji kodu. Plik ocxgl.cpp zawiera funkcje pośrednie dla funkcji biblioteki gl; podobnie, plik ocxglu.cpp zawiera funkcje biblioteki glu. Plik ocxAux.cpp zawiera funkcje pośrednie dla funkcji biblioteki AUX tworzących siatkowe i jednolite obiekty, takie jak imbryk. Na koniec, plik ocxflags.cpp zawiera różne definicje oraz funkcje dostępu służące do odczytywania stanu znaczników OpenGL.
Głównym plikiem projektu jest plik WaitGLCtl.cpp, zawierający kod odpowiedzialny za przygotowanie kontekstu renderowania oraz odpalanie zdarzeń przygotowania i malowania. Znajdują się w nim także wszystkie funkcje związane z przygotowaniem okna i GDI. Dodatkowo, w tym pliku występują funkcje pomocnicze bezpośrednio zwracające kontekst renderowania i kontekst urządzenia, na wypadek gdybyś potrzebował ich w swoim niskopoziomowym kodzie.
Zwróć uwagę, że kontrolka korzysta z biblioteki MFC w osobnym pliku DLL. Dla twojej wygody, w podkartotece \REDIST zostały umieszczone redystrybuowalne pliki MFC.
Podsumowanie
W tym rozdziale omówiliśmy możliwości i wyzwania kryjące się za użyciem OpenGL w pewnych popularnych środowiskach programowych. Choć w większości tych środowisk możliwy jest bezpośredni dostęp do niskopoziomowych funkcji API, dużo łatwiej jest jednak posłużyć się przygotowaną przez nas kontrolka OCX. Większość przykładowych programów z tej książki może zostać łatwo przerobionych tak, aby można je było zaimplementować w języku czwartej generacji (4GL) przy użyciu tej kontrolki. Oprócz tego na płytce CD-ROM zostały zamieszczone uzupełniające przykłady.
Rozdział 24.
Przyszłość OpenGL w Windows
Ta książka nie jest wyłącznie o OpenGL - ona jest na temat OpenGL w systemie Microsoft Windows. Pozwolimy sobie w tym miejscu na omówienie bieżącej sytuacji OpenGL oraz spróbujemy wysnuć wnioski co do jego rozwoju i zastosowań w najbliższej przyszłości.
OpenGL to w założeniu programowy interfejs dla sprzętu 3D. Choć zarówno w Windows NT, jak w Windows 95 dostępna jest „ogólna", czysto programowa implementacja tej biblioteki, wynika to głównie z faktu, że sprzęt 3D dla komputerów osobistych jest jeszcze w powijakach. Oczywiście, w przypadku OpenGL powinno się korzystać z takiego sprzętu, jeśli tylko jest to możliwe.
W momencie powstawania tej książki rynek akceleratorów 3D jest jeszcze bardzo młody. Ceny kart akceleratorów 3D zgodnych z OpenGL zaczynają ostatnio spadać, jednak prawdziwym motorem wymuszającym znaczny spadek cen są gry komputerowe przeznaczone do uruchamiania na komputerach osobistych. Gry komputerowe wymagają najszybszego dostępnego sprzętu i najwydajniej napisanych programów. Komputery osobiste z przynajmniej kilku powodów dobrze nadają się na maszynki do grania. Przy odpowiednim osprzęcie, możesz grać na ekranie wysokiej jakości monitora, który o parę klas przewyższa ekran domowego odbiornika telewizyjnego. To wszystko uzupełnia dźwięk, nawet z syntezą \vave-table, prawie nie różniący się od brzmienia rzeczywistych instrumentów. Oprócz joysticka z jednym lub dwoma przyciskami masz do dyspozycji mysz i całą klawiaturę, co otwiera zupełnie nowe możliwości w interakcji z grą. Dodaj do tego ogromne pojemności choćby zwykłych płytek CD-ROM oraz możliwość przechowywania (powiedzmy sobie szczerze: kopiowania) programów na dysku. Jeśli złożysz to wszystko razem, otrzymasz bardzo drogi, lecz niezwykle wydajny sprzęt do zabawy.
Oczywiście, niewiele osób przyznaje się, że kupuje komputer wyłącznie do grania (chyba że chodzi o tzw. gry edukacyjne). Ale nie oszukujmy się - skoro masz już w domu potężny komputer przeznaczony do pracy lub łączności ze światem, nie zaszkodzi chyba, że pozwolisz sobie na nieco rozrywki, prawda? Gdy Micorosft Windows opanowały
672__________________________________________Część IV » OpenGL i...
rynek na tyle, że „poważne" aplikacje stały się znacznie powszechniejsze dla tego systemu niż dla DOS-a, wiele osób zainstalowało ten system w swoich komputerach. Mimo to, do mniej więcej roku temu, twórcy gier unikali Windows jak ognia, tworząc gry prawie wyłącznie dla DOS-a.
Wszystkie powody ku temu można ująć w jednym słowie: wydajność. Windows znacznie ułatwiają życie twórcom aplikacji, gdyż wszystkie polecenia graficzne wywołuje się tak samo bez względu na sprzęt zainstalowany w komputerze. Chcesz narysować prostokąt? Po prostu wywołaj funkcję rysującą ten prostokąt! Nie musisz konwertować współrzędnych ekranowych na adres fizyczny w pamięci ekranu ani martwić się, że nie znasz szybkich algorytmów. Wszystko, co potrzebne do obsłużenia karty graficznej, to odpowiedni sterownik Windows, tłumaczący wywołania GDI na instrukcje sprzętowe.
Niestety, w takim podejściu pomiędzy instrukcjami graficznymi wywoływanymi przez programistę a samą kartą graficzną występuje wiele pośrednich warstw kodu związanego z tworzeniem obrazu na ekranie. Powoduje to powstawanie pewnego zjawiska, które nosi nazwę P-O-W-O-L-I. Żaden szanujący się twórca gier nie mógł pozwolić sobie na tworzenie gier wideo dla Windows, więc przez dłuższy czas jedynymi przykładami pasjonujących gier dla Windows były pasjans i saper.
Dostawcy sprzętu starający się utrzymać na rynku komputerów wykorzystywanych głównie do prac biurowych i przetwarzania tekstów, zaczęli tworzyć karty graficzne oferujące sprzętową akcelerację wielu powszechnie używanych poleceń rysunkowych Windows. Rynek zalała powódź kart graficznych z akceleratorami 2D, przyspieszającymi działanie systemów Windows i sprawiającymi, że gry dla Windows przestały być czymś nierealnym. Programistom ciężko było się oprzeć pokusie wykorzystania nowego, kolorowego interfejsu, jakże atrakcyjnego w porównaniu ze zwykłym tekstowym interfejsem DOS-a. Powoli zaczęły powstawać gry karciane, gry strategiczne, a nawet powstało kilka gier wideo.
W tym momencie każdy już zdawał sobie sprawę, że standardem jest system Windows, jednak większość najlepszych gier (głównie gier akcji i symulatorów pojazdów) wciąż była przeznaczona dla DOS-a. Programiści po prostu nie mogli osiągnąć takiej ilości klatek na sekundę i prędkości wyświetlania, jakie można było osiągnąć w DOS-ie.
Pierwszą próbą, jaką Microsoft podjął w celu wsparcia twórców gier, było WinG API. W praktyce nowy interfejs nie zawierał wiele więcej niż kilka funkcji umożliwiających bardzo szybkie przerzucanie bitmap. WinG stanowiło znaczny postęp, jednak było to wciąż za mało, aby znacząca ilość twórców gier zwróciła się ku Windows.
Kamieniem milowym dla rynku gier okazało się powstanie systemu Windows 95. Microsoft desperacko starał się ustanowić Windows 95 32-bitowym następcą DOS-a dla biurowych i domowych systemów. Tak się złożyło, że Windows NT były wykorzystywane głównie do poważniejszych zadań, więc Windows 95 znalazły sobie przytulną (i obszerną) niszę jako system do zastosowań domowych. Jednak nawet zanim stało się to oczywiste, Microsoft chciał uczynić z Windows 95 podstawowy system do gier. W związku z tym konieczna była rozbudowa możliwości multimedialnych tego systemu, i to w bardzo dużym stopniu.
Rozdział 24. * Przyszłość OpenGL w Windows___________________________673
Aby umożliwić twórcom gier bardziej bezpośredni dostęp do sprzętu, Microsoft opracował zestaw interfejsów API, znanych teraz pod wspólną nazwą DirectX.
Należą do nich Direct Draw, przeznaczony do szybkiego przerzucania bitmap na ekran, Direct Sound do szybkiej obsługi dźwięku i MIDI, Direct Play do umożliwienia gry w sieci oraz Direct Input do obsługi joysticka i innych urządzeń wejściowych. Pomiędzy sprzętem a aplikacją występuje jedynie bardzo „cienka" warstwa pośrednia oprogramowania, dzięki czemu programista ma niespotykany dotąd dostęp do sprzętu, zatem gry mogą zyskać niespotykaną dotąd w Windows szybkość.
Ostatnim komponentem dodanym do pakietu DirectX był DirectSD. Dzisiejsze gry wideo nie są już płaskimi, dwuwymiarowymi scenkami. Są wysoce złożonymi symulatorami lotów lub trójwymiarowymi strzelaninami, pełnymi teksturowanych potworów, ścian i korytarzy. Direct3D jest ściśle zintegrowany z DirectDraw i kartami akceleratorów 3D. Jeśli jakieś funkcje nie są obsługiwane przez sprzęt, są emulowane programowo. Dzięki temu twórcy gier mogą kodować i testować swoje aplikacje, a później bezproblemowo korzystać z dodatkowych możliwości oferowanych przez nowy sprzęt.
Jak to wszystko ma się do OpenGL? Całkiem zwyczajnie: co jest dobre dla gier, jest również dobre dla OpenGL. Za rok czy dwa od momentu ukazania się tej książki, akceleratory 3D zapewne staną się standardowym wyposażeniem większości komputerów osobistych. Istnieje wiele historycznych przesłanek, które mogą utwierdzić nas w tym przekonaniu. Na przykład, gdy pojawiły się napędy CD-ROM, nie można w nich było odtwarzać muzycznych płyt CD. Wkrótce jednak ktoś spostrzegł, że napęd posiadający taką możliwość z pewnością będzie się lepiej sprzedawał. Któż nie chciałby słuchać muzyki pracując przy komputerze? Oczywiście, dzisiaj nie można kupić nowego napędu CD-ROM, w którym nie dałoby się odtwarzać muzycznych płytek CD.
To samo działo się z początkowymi akceleratorami 2D dla Windows. Karty z akceleracją szybko stały się bardzo tanie, oferując sprzętowe przyspieszenie operacji za bardzo niewielką dodatkową cenę. Na koniec, podobnie dzieje się w przypadku faksmodemów. Idź do sklepu i spróbuj kupić nowy modem, który równocześnie nie mógłby działać jako faks. Producenci układów scalonych zespolili funkcje obu urządzeń w jednej kostce i dzięki masowej produkcji cena faksmodemu spadła do ceny zwykłego modemu.
Oczywiście, należy zakładać że podobnie będzie się działo z kartami graficznymi z akceleratorami 3D, a z czasem będzie coraz lepiej. W 1995 roku Microsoft kupił firmę RenderMorphics, Ltd., twórcę Reality Lab 3D API. Jest to wysokowydajna biblioteka 3D przeznaczona do tworzenia trójwymiarowej grafiki w czasie rzeczywistym na komputerach osobistych. Reality Lab API jest szybsze niż OpenGL, lecz uzyskuje to kosztem pewnej utraty jakości tworzonej grafiki. Dodatkowo, w tej bibliotece nie są dostępne wszystkie efekty specjalne i możliwości dostępne w OpenGL. Jednak dla gier komputerowych nie ma to większego znaczenia, gdyż w tym przypadku szybkość ma (na razie!) o wiele większe znaczenie niż realizm grafiki.
W następnej wersji DirectX, Reality Labs API zostanie dołączone do Direct3D. Di-rect3D będzie mogło pracować w jednym z dwóch trybów, w trybie wstrzymywanym, będącym oryginalnym trybem Reality Labs, oraz w trybie natychmiastowym, stanowiącym niskopoziomowy interfejs dostępu do sprzętu akceleratora 3D. Relacje pomiędzy
674________________________________________Część IV » OpenGL i...
trybem natychmiastowym a trybem wstrzymywanym są podobne do relacji pomiędzy OpenGL a Open Inventorem. Tryb wstrzymywany to interfejs wyższego poziomu upraszczający tworzenie scen i manipulowanie obiektami, zbudowany na bazie trybu natychmiastowego.
Dobrą wiadomością dla programistów OpenGL jest to, że OpenGL będzie mógł korzystać ze sterowników Direct3D przyspieszających działanie DirectSD w trybie natychmiastowym. Tak więc w przypadku kart graficznych z akceleratorem 3D zostanie przyspieszone również działanie aplikacji OpenGL. Gdy komputery staną się szybsze, a poganiani przez konkurencję producenci kart 3D stworzą szybsze modele, nadejdzie czas, gdy na przeciętnym komputerze osobistym będzie można oglądać skomplikowane animacje 3D tworzone wyłącznie w OpenGL. Ten czas się zbliża i programiści (może nawet Ty) będą musieli brać pod uwagę nie tylko szybkość działania, ale też wizualną jakość swoich produktów.
OpenGL będzie doskonałym narzędziem do tworzenia zapierających dech w piersiach efektów i realistycznych animacji. Gdy szybkie 3D stanie się rzeczywistością, twoja inwestycja w OpenGL nie pójdzie na marne. W najbliższej przyszłości najprawdopodobniej to DirectX API zapanuje w komputerach z systemem Windows. Jeśli jednak chodzi o tworzenie kodu przeznaczonego do działania na różnych platformach, OpenGL jest nie do pobicia. Obecnie najszersze zastosowanie dla OpenGL można znaleźć w dziedzinie przemysłu rozrywkowego (efekty specjalne w kinie i w reklamach), modelowania naukowego i edukacyjnego oraz w symulacji. Oprócz tego, wielu twórców gier decyduje się na użycie OpenGL przy tworzeniu plansz tytułowych, bitmap tła oraz tekstur, a nawet do tworzenia komputerowo generowanych animacji (plików .avi i .mpg).
Wnioski
Gdy stały się dostępne pierwsze akceleratory 2D, w rzeczywistości tylko niewielka liczba użytkowników potrzebowała dodatkowej szybkości. Obecnie karta graficzna z akceleratorem dla Windows jest standardowym wyposażeniem każdego komputera. W grach w dalszym ciągu rysowanie odbywa się bez pomocy akceleratora 3D, jednak ogół projektantów jest gotowy do zaakceptowania akceleracji 3D, jak tylko stanie się ona powszechnie dostępna.
Możesz być pewien, że rozmiar, złożoność i wymagania oprogramowania będą zawsze do końca wykorzystywały możliwości sprzętu. Trudno sobie wyobrazić, że kiedyś komputery z kolorowym monitorem były uważane za niepotrzebne fanaberie. Kto pamięta czasy, kiedy 386 były uważane za w najwyższym stopniu zaawansowany procesor, przeznaczony jedynie dla serwerów i naukowych stacji roboczych? To samo mówiono o 486, potem o Pentium, a teraz o Pentium Pro. Każdy, kto ma współczynnik inteligencji powyżej dwóch, powinien dostrzec tu jakiś wzorzec.
Wkrótce standardowe, przeciętne karty graficzne będą zawierały zarówno akcelerację 3D, jak i akcelerację 2D dla Windows. Tak jak komputery z kolorowym monitorem przestały być uważane za maszynki do grania, tak trójwymiarowe technologie wykorzystywane
Rozdział 24. » Przyszłość OpenGL w Windows___________________________675
w grach będą zastosowane także w „poważniejszych" dziedzinach. Różnica pomiędzy grafiką trójwymiarową generowaną wyłącznie programowo a grafiką 3D obsługiwaną przez sprzęt jest kolosalna, mniej więcej taka jak między wykorzystaniem głośniczka komputera do tworzenia muzyki a wykorzystaniem do tego karty Sound Blaster. Podobnie jak karty dźwiękowe stały się tak popularne jak kolorowe monitory, również karty graficzne z akceleratorami 3D staną się standardowym wyposażeniem każdego komputera, kupowanego w promocyjnej cenie w lokalnym supermarkecie.
/^'"<e-
Dodatek A
Poprawianie wydajności OpenGL w Windows
Celem tej książki jest przedstawienie OpenGL z funkcjonalnego punktu widzenia. Jeśli przeczytałeś całą książkę, poznałeś OpenGL pod kątem wykorzystania jego funkcji i poleceń. Poznałeś także kilka technik, takich jak tworzenie cieni, które nie są związane bezpośrednio z konkretnymi funkcjami czy zestawami funkcji. Posiadając te informacje masz solidne podstawy do tworzenia aplikacji wymagających renderowania 3D.
Jednak „grafika niejedno ma imię". Nawet jeśli programujesz dopiero od tygodnia, z pewnością już wiesz, że zamierzony cel można osiągnąć na kilka sposobów. Strategie - czy te najszersze, polegające na wyborze narzędzi, czy te mniejsze, dotyczące stosowanych algorytmów - mogą się znacznie od siebie różnić, lecz wciąż prowadzą do określonego celu. Twoim zadaniem, jako twórcy oprogramowania, jest dokonanie optymalnego wyboru w celu stworzenia jak najmniej kosztownego i najefektywniejszego programu.
Teraz, gdy wiesz już, jak programować przy użyciu OpenGL, chcielibyśmy podsunąć ci kilka rad i uwag ułatwiających tworzenie jak najbardziej optymalnego kodu OpenGL. Te rady i uwagi są ogólnymi zaleceniami i mogą zostać zastosowane w programach bez względu na platformę, której używasz.
Listy wyświetlania
* Używaj list wyświetlania za każdym razem, gdy ten sam obiekt ma być rende-rowany więcej niż raz. Nawet w wyłącznie programowych implementacjach. Listy wyświetlania mogą znacznie poprawić wydajność.
* Spróbuj osadzić kosztowne przekształcenia macierzy i zmiany stanu w listach wyświetlania - szczególnie podczas komponowania tekstur. Do takich przekształceń należą funkcje Rotate, Translate oraz Scalę.
* Niektóre systemy i karty graficzne mogą bezpośrednio korzystać z list wyświetlania OpenGL (na przykład poprzez użycie DMA), więc zastosowanie list
680________________________________________________Dodatki
wyświetlania poprawia prędkość komunikacji pomiędzy procesorem a kartą graficzną. Jednak operacje takie jak glPushAttrib, glPopAttrib, glCallList oraz glCallLists mogą zwolnić ten proces, gdyż pewne fragmenty list wyświetlania nie mogą być przekazywane kanałem DMA. Tak więc lepiej jest wywoływać serię list wyświetlania niż pojedynczą zagnieżdżoną listę.
Operacje na macierzach
Używaj wbudowanych funkcji (glRotate, glTranslate, glScale) zamiast samemu komponować i przemnażać własne macierze. Wbudowane funkcje są wysoce zoptymalizowane, szczególnie jeśli w systemie znajduje się odpowiedni sprzęt.
Używaj funkcji glLoadldentity do czyszczenia stosu macierzy zamiast ładować własną macierz, a to z powodów wymienionych powyżej.
Zmienne stanu odkładaj i zdejmuj ze stosu (glPushAttrib/glPopAttrib) zamiast odczytywać i ustawiać je indywidualnie.
Operacje związane z oświetleniem
Jeśli nie potrzebujesz gładkiego cieniowania, użyj zamiast niego funkcji glSha-deModel(GL_FLAT).
Określa własne jednostkowe wektory normalne zamiast zmuszać OpenGL, aby wyliczył je za ciebie.
Unikaj używania funkcji glScale podczas dokonywania obliczeń oświetlenia. Lepiej jest ręcznie przeskalować obiekt przed umieszczeniem go w scenie.
Jeśli to możliwe, w celu zmiany właściwości materiału zamiast funkcji glMa-terial używaj funkcji glColorMaterial. Użycie funkcji glMaterial jest praktyczne tylko wtedy, gdy zmienia się jedynie pojedynczy zestaw właściwości materiału.
Konstruowanie obiektów
Gdziekolwiek jest to możliwe, używaj prymitywu GLJTR.IANGLES. Często krócej trwa narysowanie dwóch lub więcej trójkątów niż jednego czworokąta lub wielokąta. Z kolei GL_QUADS jest szybszy od GL_POLYGON, będąc przy tym równie szybkim lub szybszym niż GL_TRIANGLES w czysto programowych implementacjach OpenGL.
Grupy podobnych prymitywów umieszczaj wewnątrz pojedynczej pary instrukcji glBegin/glEnd.
Używaj danych wierzchołków i innych poleceń w formie tablicowej, w celu przekazania jak największej ilości danych w jak najmniejszej ilości instrukcji.
Dodatek A. » Poprawianie wydajności OpenGL w Windows ___________________ 681
* Podczas rysowania lub kopiowania obrazów, wyłącz rasteryzację i operacje na fragmentach; w przeciwnym wypadku OpenGL zastosuje tekstury do obrazów.
* Używaj pasków prymitywów (np. GL_QUAD_STRJPS) podczas podziału płaskich powierzchni; to radykalnie upraszcza obliczenia związane z podziałem.
Inne rady
Nie dodawaj programowi niepotrzebnej pracy, na przykład wciąż ustawiając ten sam kolor lub ustawiając ten sam znacznik stanu.
Manualnie usuwaj niewidoczne części sceny. Postaraj się nie rysować obiektów, o których wiesz, że nie pojawią się w scenie (takich jak obiekty za tobą). Nie próbuj sprawdzać widoczności każdego obiektu, ale raczej tak dostosuj strukturę swojego programu, aby wyeliminować oczywistych kandydatów (zajrzyj do symulatora czołgu w rozdziale 7).
W Windows jednym z ważniejszych wąskich gardeł jest przerzucanie buforów. Gdy zmianie ulega jedynie niewielka część sceny, użyj funkcji rozszerzenia glAddSwapHintRectWIN.
W celu przyspieszenia renderowania zredukuj ilość szczegółów sceny. Jeśli posiadasz akcelerator sprzętowy, w celu uzyskania lepszych efektów graficznych możesz zwiększyć ilość szczegółów. Obecność akceleratora sprzętowego możesz sprawdzić za pomocą funkcji DescribePixelFormat. W wersji 1.1 i późniejszych sprawdź obecność znacznika PFD_GENERIC_ACCELERATED w polu dwFlags struktury PIXELFORMATDESCRIPTOR.
Używaj 16-bitowego bufora głębokości, chyba że twoja aplikacja wymaga dodatkowej precyzji. W ten sposób nie tylko oszczędzisz pamięć, ale także będziesz mógł skorzystać z większości tańszych akceleratorów, które nie obsługują 32-bitowego bufora głębokości.
Dodatek B
Dalsza lektura
W tym dodatku zamieściliśmy listę źródeł dalszych informacji dotyczących programowania OpenGL. Wśród podanych książek znajdują się zarówno pozycje związane bezpośrednio z OpenGL, jak i z programowaniem Windows w ogólności, zaś kilka z nich dotyczy zaawansowanych technik programowania grafiki 3D. Oprócz tego zamieściliśmy kilka ciekawych adresów stron internetowych wypełnionych informacjami na temat programowania OpenGL, przykładowymi programami oraz łączami do innych związanych z tym stron.
Książki na temat programowania Windows
Windows 95 Win32 Programming APl Bibie
Richard J. Simon, with Michael Conker and Brian Barnes
Waite Group Press
Windows 95 Common Controls & Messages APl Bibie Richard J. Simon Waite Group Press
Windows 95 Multimedia & ODBC APl Bibie Richard J. Simon Waite Group Press
Programming Windows 95
Charles Petzold, Paul Yao
Microsoft Press
(polskie wydanie:Programowanie Windows 95 READ ME)
684_______________________________________________Dodatki
32-Bit Windows Programming
Ben Ezzell
SAMS
Książki i materiały na temat OpenGL
The OpenGL Programming Guid
Jackie Neider/OpenGL Architecture Review Board
OpenGL Reference Manual OpenGL Architecture Review Board Addison-Wesley
The Imentor Mentor
Josie Wemecke/Open Irwentor Architecture Group
Addison-Wesley
The Imentor Toolmaker
Josie Wernecke
Addison-Wesley Publishing Company
3D Graphics Programming with OpenGL
Clayton Walnum
QUE
Książki komputerowe na temat programowania grafiki (w szczególności 3D)
Computer Graphics: Principles and Practice Foley, Van Dam, Feiner and Hughes Addison-Wesley
Serwery FTP i WWW związane z OpenGL
Firma________________URL___________________________
Silicon Graphics http://www.sgi.com/
Silicon Graphics http://sgigate.sgi.com/
685
Dodatek B. » Dalsza lektura
Firma
URL
Silicon Graphics/OpenGL WWW Center
Template Graphics
Microsoft
Yiewpoint Datalabs
3D Accelerator Information
Mark Kilgard's home page
Silicon Graphics/Mark Kilgard
http://www.sgi.com/Technology/openGL/
http://www.cts.com/~template/
http://www.microsoft.com/ntworkstation/opengl.htm
http://www.viewpoint.com/
http://www.cs.columbia.edu/~bm/3dcards/3d-cardsl.html
http://reality.sgi.com/employees/mjk_asd/home.html
http://www.sgi.com/Technology/openGL/glut3.html
Składnice VRML
The VRML Repository Paragraph International Silicon Graphics Vertex International The Geometry Center Ziff-Davis ORC
http://www.sdsc.edu/vrmy
http://vrml.paragraph.com/
http://webspace.sgi.com/Repository/
http://www.vrml. com:80/models/vertex/
http://www.geom.umn.edu/~daerorL/bin/legitlist.cgi
http://www.zdnet.com/zdi/vrml/
http://www.ocnus/models/models.html
Dodatek C
OpenGL wersja 1.1
W grudniu 1995 roku, podczas powstawania tej książki, grupa OpenGL Architecture Review Board ratyfikowała i zaaprobowała nową wersję, 1.1, specyfikacji OpenGL. Wraz z wypuszczeniem Windows NT 4.0, Microsoft stanie się jednym z pierwszych, jeśli nie pierwszym, dostawcą pełnej implementacji nowej specyfikacji OpenGL, przeznaczonej do komputerów osobistych. Oprócz zachowania zgodności z nową specyfikacją, Microsoft poprawił wydajność OpenGL oraz dodał kilka nowych elementów i możliwości, między innymi możliwość umieszczania wywołań OpenGL w rozszerzonych metaplikach, a także poprawioną obsługę wydruku.
Do najciekawszych elementów nowej specyfikacji OpenGL należą:
* Tablice wierzchołków, umożliwiające szybsze przekazywanie położenia wierzchołków, normalnych, kolorów oraz indeksów kolorów, współrzędnych tekstur oraz znaczników krawędzi.
* Operacje logiczne na pikselach w trybie RGBA, a nie tylko w trybie indeksu kolorów.
4 Wiele nowych i ulepszonych właściwości związanych z teksturami^to jest prawdopodobnie najważniejszym dodatkiem zawartym w nowej specyfikacji).
Nowa wersja OpenGL dla Windows 95 ma się pojawić w kilka miesięcy od ukazania się Windows NT 4.0, czyli już po wydaniu tej książki. Tak więc aby móc odpowiednio przedstawić nową specyfikację i rozszerzenia dodane przez Microsoft, na płytce CD-ROM zamieściliśmy specjalną kartotekę. Kartoteka \OpenGLll zawiera bardziej kompletną dokumentację na temat nowych elementów wersji 1.1, a także trochę dodatkowych rzeczy dorzuconych przez Microsoft. Znajdziesz tam także kilka przykładowych programów.
Dodatek D
Słownik
Alfa
Czwarta wartość koloru dodana w celu umożliwienia określenia przezroczystości koloru obiektu. Wartość alfa 0,0 oznacza całkowitą przezroczystość, zaś wartość l ,0 oznacza zupełny brak przezroczystości.
Antyaliasing
Metoda renderowania używana do uzyskiwania gładkich linii i krzywych. Ta technika uśrednia kolor pikseli przyległych do linii i daje wizualny efekt złagodzenia przejścia z pikseli linii do pikseli otaczających linię, przez co linia wydaje się gładsza.
Aspekt ekranu
Stosunek szerokości okna do jego wysokości, szerokość okna w pikselach podzielona przez wysokość okna w pikselach.
Biblioteka AUX
Niezależna biblioteka narzędziowa. Użyteczna przy pośpiesznym tworzeniu przenośnych programów OpenGL.
Bitplan
Tablica bitów odwzorowywanych bezpośrednio na piksele ekranu.
Bryła widzenia
Obszar przestrzeni 3D, który można obserwować przez okna na ekranie. Obiekty i punkty poza bryłą widzenia zostaną obcięte (nie będą widoczne).
Bufor
Obszar pamięci używany do przechowywania informacji o obrazie. Tymi informacjami mogą być kolor, głębokość czy informacje o mieszaniu kolorów. Bufory dla czerwieni, zieleni i błękitu noszą wspólną nazwę bufora koloru.
690______________________________________________Podatki
Culling
Eliminacja przedniej lub tylnej ściany prymitywu, w wyniku czego nie jest ona rysowana.
Czworokąt
Wielokąt o dokładnie czterech bokach.
Deseń
Wzorzec binarny używany przy blokowaniu operacji w buforze ramki podczas rysowania linii i wielokątów. Przypomina to użycie bitmapy maski, lecz w przypadku linii używane są jednowymiarowe desenie, zaś w przypadku wielokątów - dwuwymiarowe.
Krzywa Beziera
Krzywa, której kształt jest wyznaczany przez punkty kontrolne w pobliżu krzywej, a nie przez same punkty krzywej.
Krzywa parametryczna
Krzywa, której kształt jest wyznaczony przez jeden (w przypadku krzywej) lub dwa (w przypadku powierzchni) parametry. Te parametry występują w osobnych równaniach, wyznaczających współrzędne x, y i z punktów należących do krzywej.
Lista wyświetlania
Skompilowana lista poleceń i funkcji OpenGL. Gdy zostanie wywołana, jest wykonywana szybciej, niż trwałoby wywoływanie poszczególnych poleceń i funkcji z listy.
Literał
Wartość, a nie nazwa zmiennej. Oznacza łańcuch lub stałą liczbową wpisaną bezpośrednio w kodzie źródłowym.
Macierz
Dwuwymiarowa tablica liczb. Na macierzach można wykonywać operacje matematyczne, zaś same macierze są wykorzystywane przy przekształceniach wierzchołków.
Macierz widoku modelu
Macierz OpenGL przeznaczona do transformacji prymitywów ze współrzędnych obiektu do współrzędnych obserwatora.
Mapowanie tekstury
Proces nakładania obrazu tekstury na powierzchnię. Powierzchnia nie musi być przy tym planarna (płaska). Mapowanie tekstury często jest używane w celu „owinięcia" obrazu dookoła zakrzywionego obiektu lub w celu uzyskania powierzchni z deseniem, na przykład drewna czy marmuru.
Dodatek D. » Słownik________________________________________691
Normalizacja
Oznacza redukcję wektora normalnego do wektora jednostkowego, przy zachowaniu oryginalnego kierunku. Normalna jednostkowa jest wektorem, którego długość wynosi dokładnie 1,0.
Normalna
Wektor kierunkowy wskazujący prostopadle do płaszczyzny lub powierzchni w danym punkcie. Jeśli używasz normalnych, musisz określić je dla każdego wierzchołka prymitywu.
NURBS
Skrót od Non-Uniform Rational B-Spline (niejednorodna wymierna krzywa B-skleja-na). Metoda parametrycznej reprezentacji krzywych i powierzchni.
Obcinanie
Eliminacja części pojedynczego prymitywu lub grupy prymitywów. Punkty, które miałyby zostać narysowane poza regionem lub bryłą obcinania, nie są rysowane. Bryla obcinania to ogólnie używane określenie macierzy rzutowania.
Open Irwentor
Biblioteka klas C++ oraz zestaw narzędzi przeznaczone do tworzenia interaktywnych aplikacji 3D. Open Inventor jest zbudowany na podstawie OpenGL.
Ostrosłup widzenia
Bryła widzenia w kształcie ostrosłupa używana w rzucie perspektywicznym (bliższe obiekty są większe, dalsze są mniejsze).
Paleta
Zestaw kolorów dostępnych dla operacji rysunkowych. W przypadku 8-bitowych trybów Windows paleta zawiera 256 kolorów, które muszą wystarczyć do wyrysowania wszystkich pikseli na ekranie.
Perspektywa
Tryb rzutowania, w którym obiekty położone dalej od obserwatora wydają się mniejsze od obiektów położonych bliżej.
Piksel
Nazwa pochodzi od złożenia słów picture element - element obrazu. Najmniejszy element obrazu na ekranie komputera. Piksele są ułożone w wiersze i kolumny ekranu i są im przypisywane odpowiednie kolory składające się na całkowity obraz.
Podwójne buforowanie
Technika rysowania używana w OpenGL. Obraz, który ma zostać wyświetlony, tworzony jest w pamięci, a następnie przerzucany na ekran w pojedynczej operacji, w przeciwieństwie do tworzenia obrazu prymityw po prymitywie, bezpośrednio na ekranie.
692____________________________________________________Dodatki
Podwójne buforowanie jest szybką i płynną operacją odświeżania ekranu i powinno być wykorzystywane przy animacji.
Podział wielokata
Proces podziału złożonego wielokata lub powierzchni analitycznej na siatkę płaskich wielokątów wypukłych. Może zostać zastosowany także w celu rozbicia złożonej krzywej na serię mniej złożonych odcinków.
Prymityw
Płaski wielokątny kształt zdefiniowany w OpenGL. Wszystkie obiekty i sceny składają się z różnorodnych kombinacji prymitywów.
Przekształcenie
Manipulacja układem współrzędnych. Do przekształceń należą obroty, translacje (przesunięcia), skalowanie (w określonym kierunku lub równomiernie we wszystkich kierunkach) oraz podział perspektywiczny.
Pasteryzacja
Zamiana prymitywów we współrzędnych obiektu na obraz w buforze ramki. Tzw. kanał renderowania to proces, w którym polecenia i instrukcje OpenGL zostają zamienione w piksele na ekranie.
Roztrząsanie
Metoda używana do symulacji szerszego zakresu kolorów, niż jest sprzętowo dostępny, przez rozmieszczanie obok siebie odpowiednio pokolorowanych pikseli, tworzących wzory dające wrażenie przejścia między kolorami.
Rzut równoległy
Tryb rzutowania, w którym nie występuje perspektywa. W tym rzutowaniu rozmiary wszystkich prymitywów pozostają niezmienne bez względu na ich orientację i odległość od obserwatora.
Rzutowanie
Przekształcenie linii, punktów i wielokątów ze współrzędnych obserwatora do obciętych współrzędnych na ekranie.
Siatka
Reprezentacja jednolitych obiektów przez siatkę linii zamiast wypełnionych, cieniowanych wielokątów. Modele siatkowe zwykle są rysowane szybciej i mogą służyć do przedstawienia zarówno przedniej, jak i tylnej ściany modelu równocześnie.
Splajn
Ogólne określenie każdej krzywej utworzonej przez rozmieszczenie dookoła niej punktów kontrolnych, wpływających na kształt tej krzywej. Ułożenie krzywej przypomina reakcję elastycznego materiału na nacisk przyłożony w różnych jego miejscach.
Dodatek D. » Słownik________________________________________693
Światło otaczające
Światło w scenie, które nie pochodzi z żadnego konkretnego źródła ani kierunku. Światło otaczające równomiernie iluminuje wszystkie obiekty ze wszystkich stron.
Teksel
Podobny do piksela (elementu obrazu), z tym że stanowi element tekstury (texture element). Teksel reprezentuje kolor tekstury, jaki zostanie zastosowany w buforze ramki dla pikseli reprezentujących dany fragment wielokąta z nałożoną teksturą.
Tekstura
Obraz (bitmapa) nakładany na powierzchnię prymitywu.
Transcluencja
Stopień przezroczystości obiektu. W OpenGL reprezentowany jest przez wartość alfa z zakresu od l ,0 (nieprzezroczysty) co 0,0 (całkowicie przezroczysty).
Tryb indeksu kolorów
Tryb koloru, w którym kolory sceny są wybierane z ustalonej liczby kolorów dostępnych w palecie. Kolory w scenie określa się za pomocą indeksów pozycji palety.
Tryb natychmiastowy
Tryb renderowania grafiki, w którym funkcje i polecenia natychmiast wpływają na stan renderowania.
Układ kartezjański
Układ współrzędnych oparty na trzech skierowanych osiach, ułożonych pod kątem prostym do siebie. Współrzędne w tym układzie oznaczane są literami x, y i z.
Widok
Obszar okna używany do wyświetlania obrazów OpenGL. Zwykle obejmuje cały obszar roboczy okna Windows. Rozciągnięte lub skurczone widoki mogą służyć do tworzenia powiększonego lub pomniejszonego obrazu w fizycznym oknie na ekranie.
Wielokąt
Dwuwymiarowy kształt z dowolną (lecz większą niż dwa) liczbą wierzchołków.
Wielokąty wypukłe
Wielokąty, które nie posiadają „wcięć" ani „wgnieceń". Precyzyjnie: wielokąt wypukły to taki, w którym żadna przechodząca przez niego linia nie przecina krawędzi więcej niż dwa raz (raz „wchodzi" i raz „wychodzi").
Wierzchołek
Pojedynczy punkt w przestrzeni. Używany przy definiowaniu wielokątów i linii, definiuje także punkt, w którym łączą się dwie krawędzie wielokąta.
694________________________________________________Dodatki
Współrzędne obserwatora
Układ współrzędnych oparty na położeniu obserwatora. Oko obserwatora znajduje się na osi z, a patrzy w kierunku ujemnych wartości tej osi.
Wyciąganie
Proces dodawania trzeciego wymiaru do płaskiego obrazu lub kształtu, na przykład zamiana dwuwymiarowych czcionek na trójwymiarowe litery.
Skorowidz
.BMP 367 3DDI 36 4GL 52, 657 8514 240
alpha blending 260
ambient 272, 273
animacja 74
animacja palety 259
ANSI 356
antyaliasing 260,498
API 35,51
aplikacja konsoli 59
AppExpert 643
AppWizard 629
ARB 37
Architecture Review Board 37
aspect ratio 72
automatyczne generowanie współrzędnych
tekstury 401 AUX 33, 51, 52 AUX_0 83 AUX_9 83 AUX_A 83 AUX_a 83 AUX_ACCUM 81 AUX_ALPHA 81 AUX_DEPTH 81 AUX_DEPTH16 81 AUX_DOUBLE 80 AUX_DOWN 83 AUX_ESCAPE 83 AUX_FIXED_332_PAL 81 AUX_INDEX 80 AUX_LEFT 83 AUX_LEFTBUTTON 84 AUX_MIDDLEBUTTON 84 AUX_MOUSEDOWN 84 AUX MOUSEUP 84
AUX_RETURN 83 AUX_RGBA 80 AUX_RIGHT 83 AUX_RIGHTBUTTON 84 AUX_SINGLE 80 AUX_SPACE 83 AUX_STENCIL 81 AUX_UP 83 AUX_Z 83 AUX_z 83 aux!dleFunc() 74,79 auxInitDisp!ayMode() 77, 80 aux!nitPosition() 61, 81 aux!nitWindow() 62,81 auxKeyFunc() 82 auxMainLoop() 67, 83 auxMouseFunc() 84 auxReshapeFunc() 69, 85 auxSetOneColor() 85 auxSolidBox() 86 auxSo!idCone() 86, 87 auxSolidCylinder() 87 auxSolidDodecahedron() 88 auxSolid!cosahedron() 88 auxSolidOctahedron() 88 auxSolidSphere() 89 auxSolidTeapot() 89 auxSolidTetrahedron() 90 auxSolidTorus() 90 auxSolidxxxx() 78 auxSwapBuffers() 77,91 auxWireBox() 91 auxWireCone() 91,92,208 auxWireCylinder() 92 auxWireDodecahedron() 93 auxWire!cosahedron() 93 auxWireOctahedron() 94 auxWireSphere() 94 auxWireTeapot() 78 auxWireTeapot() 95
696
Dodatki
auxWireTetrahedron() 95 auxWireTorus() 95 auxWirexxxx() 78
B
BeginPaint() 104 Bezier 535 Bezier 545
biblioteka AUX 33,51,56 biblioteka narzędziowa 52 bitmapa 175
BITMAPINFOHEADER 365, 367 bitmapy 351 blending 505 błąd
GL_INVALID_ENUM 139 GL_INVALID_OPERATION 138, 139 GL_INVALID_VALUE 139 GL_NO_ERROR 138, 139 GL_OUT_OF_MEMORY 138, 139 GL_STACK_OVERFLOW 139 GL_STACK_UNDERFLOW 139 GLU_INVALID_ENUM 139 GLU_INVALID_VALUE 139 GLU_OUT_OF_MEMORY 138, 139 błędy 133 bryła obcinania 49 bryła widzenia 50,217 bufor
akumulacji 494 głębokości 479 koloru 64, 477 selekcji 590
sprzężenia zwrotnego 598 stereo 478 szablonu 489 buforowanie 76, 77 bufory 60, 473
C 52
C++ 52
CAD 36, 49
CALLBACK 66
CGA 239
ChangeSizeO 69,114
chipset GLINT 40
ChoosePixelFormat() 109,116, 476
ciągłość krzywej 536
cienie 302
cieniowanie 43, 235, 246
cień 44
COLORREF 62
cprintf() 60 CreatePalette() 254 CS_PARENTDC 108 cylindry 435 czcionki bitmapowe 355 czworokąty 174 czystość kodu 55 czyszczenie okna 62
dane typu
double 54
float 54
GLbitfield 54
GLboolean 54
GLbyte 54
GLclampd 54
GLclampf 54
GLdouble 54
GLenum 54
GLfloat 54
GLint 54
GLshort 54
GLsizei 54
GLubyte 54
GLuint 54
GLushort 54
long 54
short 54
signed char 54
unsigned char 54
unsigned long 54
unsigned short 54 DDI 38 DEC 37 definiowanie
bryły obcinania 72
tekstur 389
widoku 71 Delphi 657,665 DescribePixelFormat() 118, 476 deseń 176
Device Driver Interface 38 diffuse 272,273 DirectDraw 36 DirectX 36 dithering 242, 250 DLL 53
domyślny kolor rysowania 163 dopasowywanie kolorów 249 DOS 59 double 54
drukowanie bitmap 372 dyski 435
697
Skorowidz
efekty specjalne 459
EGA 240
ekran 44
EndDoc() 373
EndPageO 373
EndPaint() 104
etykiety sprzężenia zwrotnego 601
fala świetlna 236
false 54
FL_FOG_HINT 525
float 54
format pikseli 108
fosfor 238
funkcja renderująca 66
funkcja
aux!dleFunc() 74, 79 aux!nitDisplayMode() 77, 80 aux!nitPosition() 61, 81 aux!nitWindow() 62, 81 auxKeyFunc() 82 auxMainLoop() 67, 83 auxMouseFunc() 84 auxReshapeFunc() 69, 85 auxSetOneColor() 85 auxSolidBox() 86 auxSolidCone() 86 auxSolidCube() 87 auxSolidCylinder() 87 auxSolidDodecahedron() 88 auxSolid!cosahedron() 88 auxSolidOctahedron() 88 auxSolidSphere() 89 auxSolidTeapot() 89 auxSolidTetrahedron() 90 auxSolidTorus() 90 auxSolidxxxx() 78 auxSwapBuffers() 77, 91 auxWireBox() 91 auxWireCone() 91 auxWireCube() 92, 208 auxWireCylinder() 92 auxWireDodecahedron() 93 auxWire!cosahedron() 93 auxWireOctahedron() 94 auxWireSphere() 94 auxWireTeapot() 78,95 auxWireTetrahedron() 95 auxWireTorus() 95 auxWirexxxx() 78 BeginPaint() 104
ChangeSize() 69,114 ChoosePixelFormat() 109,116,476 cprintfO 60 CreatePalette() 254 DescribePixelFormat() 118,476 EndDoc() 373 EndPage() 373 EndPaint() 104 getch() 60
GetPixelFormat() 122 glAccumO 495,499 glBeginO 149, 184,340 glBitmapO 352 glBlendFunc() 507, 530 glCallListO 343 glCallLists() 344 glClear() 64, 170 glClearColor() 62, 96, 500 glClearDepth() 481,500 glClear!ndex() 263, 501 glClearStencilO 489, 501 glColorO 245, 263 glColorMask() 265 glColorMaterial() 279, 309 glCopyPixels() 366, 377 glCullFaceO 185,310 glDeleteLists() 345 glDepthFuncO 502 glDepthRange() 481, 503 glDisableO 153,160,461,468 glDrawBuffer() 501 gIDrawPixels() 359, 362, 378 glEdgeFlagO 181, 186 glEnable() 153, 160,461,468 glEndO 149, 188,340 glEndListO 340, 346 glEvalCoord() 540,551 glEvalMesh() 541,552 glEvalPoint() 553 glFeedbackBuffer() 598, 605 glFlush() 96 glFog() 531
glFrontFace() 164,173,188,310 glFrustum() 218,225 glGenList() 346 glGet() 153 glGetBoolean() 462 glGetErrorO 134, 138 glGetFloatO 153 glGetLastErrorO 138 glGetLight() 312 glGetMapO 553 glGetMaterial() 311 glGetPolygonStipple() 189
698
Dodatki
funkcja
glGetStringO 136, 139 glHintO 137, 140, 525 gllndex() 265 gl!ndexMask() 266 gllnitNamesO 607 glIsDisabled() 462 gllsEnabledO 462, 470 gllsListO 347 glLight() 300,314 glLightModelO 315 glLineStippleO 160, 190 glLineWidthO 158, 191 glListBase() 348 glLoadEntityO 147 glLoadldentityO 72, 212, 226 glLoadMatrix() 223,226 glLoadName() 607 glLogicOpO 267 glMap() 539, 555 glMapGridO 541, 558 glMaterialO 293,317 glMatrixMode() 147, 212, 224, 227 glMultMatrix() 224, 228 glNewList() 340, 349 glNormalO 284,318 glOrtho() 69, 97, 147 gIPassThroughO 600, 608 glPixelMap() 362, 379 glPixelStore() 362, 380 glPixelTransfer() 360, 382 glPixelZoom() 362,383 glPointSize() 152, 193 glPolygonMode() 173,194 glPolygonStipple() 195 glPopAttrib() 301,470 glPopMatrix() 148,216,228 glPopNameO 609 glPushAttribO 301,462,470 glPushMatrix() 148, 216, 229 glPushNameO 609 glRasterPos() 353 glReadPixels() 363, 384 glRect() 66,98 glRenderMode() 590,610 glRotate() 79, 148, 209, 216, 229 glScale() 210,230 glSelectBuffer() 612 glSet() 153 glShadeMode() 169 glShadeModel() 268 glStencilFunc() 489 glStencilMask() 489 glStencilOpO 489
glTexCoord() 394, 429 glTexEnv() 429 glTexGen() 430 glTex!magelD() 389,431 glTexImage2D() 391,432 glTexParameter() 433 glTextEnv() 392 glTranslateO 209,216,231 gluBeginCurve() 559 gluBeginPolygon() 576, 583 gluBeginSurfaceO 547, 560 gluBeginTrimO 549,560 gluCylinder() 437,451 gluDeleteNurbsRenderer() 547, 561 gluDeleteQuadric() 452 gluDeleteTessO 583 gluDiskO 438,452 gluEndCurve() 562 gluEndPolygon() 576, 583 gluEndSurface() 547,563 gluEndTrim() 549, 563 gluErrorStringO 135, 141 gluGetNurbsPropertyO 563 gluGetStringO 136, 141 gluLoadSamplingMatrices() 654 gluLookAt() 231 gluNewContour() 584 gluNewNurbsRenderer() 547, 565 gluNewQuadric() 436, 453 gluNewTess() 576, 584 gluNextContour() 577 gluNurbsCallback() 566 gluNurbsCurve() 568 gluNurbsProperty() 547, 569 gluNurbsSurfaceO 547, 571 gluOrtho2D() 232 gluPaitialDisk() 439, 453 gluPerspective() 218,233 gluPickMatrix() 593,613 gluPwlCurve() 549, 572 gluQuadricCallback() 454 gluQuadricDrawStyle() 436, 454 gluQuadricNormals() 436, 455 gluQuadricOrientation() 436, 455 gluQuadricTexture() 436,456 gluSphere() 439, 456 gluTessCallback() 581,585 gluTessVertex() 576, 585 glVertex() 148, 196 glViewport() 69, 71, 97 RenderScene() 66, 115, 148,289 SetPixelFormat() 109, 122 SetTimerO 114 SetupRCO 277
699
Skorowidz
funkcja
StretchBltO 373 SwapBuffersO 115, 123 wgICreateContext() 106, 124 wglDeleteContext() 106, 125 wglGetCurrentContext() 125 wglGetCurrentDCO 126 wglGetProcAddress() 126 wglMakeCurrent() 106, 127 wglShareListsO 128 wglUseFontBitmaps() 129, 355 wglUseFontOutlinesO 130 WinMain() 111 WndProcO 111
funkcje bufora szablonu 489
funkcje Wiggle 106
funkcje zwrotne 581
GDI 35, 102, 245 getch() 60
GetPixelFormat() 122 GL 36 gl 52 gl.h 52
GL_2_BYTES 344 GL_2D 599, 606 GL_3_BYTES 344 GL_3D 599,606 GL_3D_COLOR 599, 606 GL_3D_COLOR_TEXTURE 599, 606 GL_4_BYTES 344 GL_4D_COLOR_TEXTURE 599, 606 GL_ACCUM 494, 499 GL_ACCUM_BUFFER_BIT 463 GL_ADD 494,499 GL_ALPHA 379, 432, 433 GL_ALPHA_BIAS 383,464 GL_ALPHA_LUMINANCE 432, 433 GL_ALPHA_SCALE 383, 464 GL_ALPHA_TEST 463 GL_ALWAYS 480,503 GL_AMBIENT 309
GL_AMBIENT_AND_DIFFUSE 279, 309 GL_AND 268 GL_AND_INVERTED 268 GL_AND_REVERSE 268 GL_AUTO_NORMAL 463 GL_BACK 173, 186, 195, 309, 478, 502 GL_BACK_AND_FRONT 309 GLJBITMAP 360,379 GL_BITMAP_TOKEN 599 GL_BLEND 393, 430, 463, 505 GL_BLUE 379, 432, 433
GL_BLUE_BIAS 383, 464 GL_BLUE_SCALE 383,464 GL_BORDER_COLOR 434 GLJ3YTE 344,379 GL_CCW 164, 189,310 GL_CLAMP 434 GL_CLEAR 268 GL_COEFF 554 GL_COLOR 378
GL_COLOR_BUFFER_BIT 170,463 GL_COLOR_INDEX 359, 379, 432, 433 GL_COLOR_INDEXES 312 GL_COLOR_MATERIAL 279, 309, 463, 464 GL_COLOR_MATERIAL_FACE 464 GL_COMPILE 341, 349 GL_COMPILE_AND_EXECUTE 341,349 GL_CONSTANT_ATTENUATION 313 GL_COPY 268 GL_COPY_INVERTED 268 GL_COPY_PIXEL_TOKEN 599 GL_CULL_FACE 172, 186, 310, 463, 464 GL_CULL_FACE_MODE 464 GL_CURRENTJBIT 463 GL_CURRENT_POSITION_VALID 463 GL_CW 164, 189 GL_DECAL 393,430 GL_DECR 491 GL_DEPTH 378 GL_DEPTH_BIAS 383,464 GL_DEPTH_BUFFER 463 GL_DEPTH_BUFFER_BIT 170 GL_DEPTH_COMPONENT 379 GL_DEPTH_SCALE 383,464 GL_DEPTH_TEST 170, 461, 463 GL_DEPTH_WRITEMASK 463 GLJDIFFUSE 309 GL_DITHER 250, 463 GL_DOMAIN 554 GL_DONT_CARE 141 GL_DRAW_PIXEL_TOKEN 599 GL_DST_ALPHA 506 GL_DST_COLOR 506 GL_EDGE_FLAG 463 GL_EMISSION 309 GL_ENABLE_BIT 463 GL_EQUAL 480, 503 GL_EQUIW 268 GL_EVAL_BIT 463 GL_EXP 520 GL_EXP2 520 GL_EXTENSIONS 136, 140 GL_EYE_LINEAR 431 GL_EYE_PLANE 431 GL FALT 169
Dodatki
700
GL_FASTEST 141
GLJFEEDBACK 598,611
GL_FILL 173, 195
GL_FLAT 268
GL_FLOAT 344, 379
GL_FOG 463
GL_FOG_BIT 463
GL_FOG_COLOR 531
GL_FOG_DENSITY 524, 531
GL_FOG_END 531
GL_FOG_HINT 140,463
GL_FOG_INDEX 531
GL_FOG_MODE 463,520,531
GL_FOG_START 531
GL_FRONT 186, 195, 309, 478, 502
GL_FRONT_AND_BACK 195,478,502
GL_FRONT_FACE 464
GL_GEQUAL 480, 503
GL_GREATER 480, 503
GL_GREEN 379,432,433
GL_GREEN_BIAS 383,464
GL_GREEN_SCALE 383, 464
GL_HINT_BIT 463
GLJNCR 491
GL_INDEX_OFFSET 383, 464
GL_INDEX_SHIFT 383,464
GLJNT 344, 379
GL_INVALID_ENUM 135, 139
GL_INVALID_OPERATION 138, 139
GL_INVALID_VALUE 139
GLJNYERT 268, 491
GLJCEEP 490
GL_LEFT_BACK 478, 502
GL_LEFT_FRONT 478, 502
GL_LEQUAL 480, 503
GL_LESS 479, 503
GL_LIGHT 313
GL_LIGHT_MODEL_AMBIENT 278,315
GL_LIGHT_MODEL_LOCAL_VIEWER
316,464
GL_L1GHT_MODEL_TWO_SIDE 315,464 GL_LIGHTO 288 GL_LIGHTi 463 GL_LIGHTING 277, 463, 464 GL_LIGHTING_BIT 301,464 GL_LIGHTx 464 GL_LINE 173, 195 GL_LINE_BIT 464 GL_LINE_LOOP 156, 185 GL_L1NE_RESET_TOKEN 599 GL_LINE_SMOOTH 463, 464 GL_LINE_SMOOTH_HINT 140, 463 GL_LINE_STIPPLE 160,190,463,464 GL_LINE_STRIP 156, 184
GL_LINE_TOKEN 599 GL_LINE_WIDTH_GRANULARITY 158 GL_LINE_WIDTH_RANGE 158 GLJJNEAR 390, 520 GL_LINEAR_ATTENUATION 313 GL_LINEAR_MIPMAP_LINEAR 391,396 GL_LINEAR_M1PMAP_NEAREST 391,396 GL_LINES 155, 184 GL_LIST_BASE 464 GL_LIST_BIT 464 GL_LOAD 494, 499 GL_LOGIC_OP 267, 463 GL_LUMINANCE 359, 379, 432, 433 GL_LUMINANCE_ALPHA 379 GL_MAP_COLOR 380, 382, 464 GL_MAP_DEPTH 464 GL_MAP_STENCIL 380, 382 GL_MAP1_COLOR_4 554, 556, 569 GL_MAP1_INDEX 554, 556, 569 GL_MAP1_NORMAL 554, 556, 569 GL_MAP1_TEXTURE_COORDJ 554, 556,
569 GL_MAP1_TEXTURE_COORD_2 554, 556,
569 GL_MAP1_TEXTURE_COORD_3 554,556,
569 GL_MAP1_TEXTURE_COORD_4 554,556,
569
GL_MAP1_VERTEX_3 539,554,556,569 GL_MAP1_VERTEX_4 539, 554, 556, 569 GL_MAPl_x 463
GL_MAP2_COLOR_4 554, 556, 572 GL_MAP2JNDEX 554, 556, 572 GL_MAP2_NORMAL 554, 556, 572 GL_MAP2_TEXTURE_COORD_1 554, 556,
572 GL_MAP2_TEXTURE_COORD_2 554, 556,
572 GL_MAP2_TEXTURE_COORD_3 554, 556,
572 GL_MAP2_TEXTURE_COORD_4 554, 556,
572
GL_MAP2_VERTEX_3 554, 556, 572 GL_MAP2_VERTEX_4 554, 556, 572 GL_MAP2_x 463 GL_MATRIX_MODE 464 GL_MAX_MODELVIEW_STACK_DEPTH
214
GL_MAX_NAME_STACK_DEPTH 610 GL_MAX_PROJECTION_STACK_DEPTH
214
GL_MODELVIEW 212, 224, 227, 431 GL_MODULATE 393,430 GL MULT 494,499
Skorowidz
701
GL_NAME_STACK_DEPTH 609 GL_NAND 268 GL_NEAREST 390
GL_NEAREST_MIPMAP_LINEAR 391,396 GL_NEAREST_MIPMAP_NEAREST 391, 396 GL_NEVER 479, 503 GL_NICEST 141,525 GL_NO_ERROR 135, 138, 139 GL_NOOP 268 GL_NOR 268
GL_NORMALIZE 285,318,463,464 GL_NOTEQUAL 480, 503 GL_OBJECT_LINEAR 401,430,431 GL_ONE 506,531
GL_ONE_MINUS_DST_ALPHA 506 GL_ONE_MINUS_DST_COLOR 506 GL_ONE_MINUS_SRC_ALPHA 506 GL_OR 268
gl_or_inverted 268 gl_or_reverse 268
GL_ORDER 554
GL_OUT_OF_MEMORY 135, 138, 139 GL_PACK_ALIGNMENT 381 GL_PACK_LSB_FIRST 381 GL_PACK_ROW_LENGTH 381 GL_PACK_SKIP_PIXELS 381 GL_PACK_SKIP_ROWS 381 GL_PACK_SWAP_BYTES 381 GL_PASS_THROUGH_TOKEN 599, 608 GL_PERSPECTIVE_CORRECTION_HINT
140, 463
GL_PIXEL_MAP_A_TO_A 380 GL_PIXEL_MAP_B_TO_B 380 GL_PIXEL_MAP_G_TO_G 380 GL_PIXEL_MAP_I_TO_A 380 GL_PIXEL_MAP_I_TO_B 380 GL_PIXEL_MAP_I_TO_G 380 GL_PIXEL_MAP_I_TO_I 380 GL_PIXEL_MAP_I_TO_R 380 GL_PIXEL_MAP_R_TO_R 380 GL_PIXEL_MAP_S_TO_S 380 GL_PIXEL_MODE_BIT 464 GL_POINT 195 GL_POINT_BIT 464
GL_POINT_SIZE_GRANULARITY 153, 193 GL_POINT_SIZE_RANGE 153, 193 GL_POINT_SMOOTH 463, 464 GL_POINT_SMOOTH_HINT 141,463 GL_POINT_TOKEN 599 GL_POINTS 149, 184 GL_POLYGON 185,575 GL_POLYGON_B1T 464 GL_POLYGON_MODE 464 GL_POLYGON SMOOTH 463,464
GL_POLYGON_SMOOTH_HINT 141,463 GL_POLYGON_STIPPLE 176, 195,463,464 GL_POLYGON_STIPPLE_BIT 464 GL_POLYGON_TOKEN 599 GL_POSITION 298,313 GL_PROJECTION 227 GL_Q 431
GL_QUAD_STRIP 175, 185 GL_QUADRATIC_ATTENUATION 313 GL_QUADS 174, 185 GL_R 431
GL_READ_BUFFER 464 GL_RED 379,432,433 GL_RED_BIAS 383,464 GL_RED_SCALE 383, 464 GL_RENDER 590,610 GL_RENDERER 140,611 GL_REPEAT 394, 434 GL_REPLACE 490 GL_RETURN 494, 499 GL_RGB 359,379,432,433 GL_RGBA 379,432,433 GL_RIGHT_BACK 478, 502 GL_RIGHT_FRONT 478, 502 GL_S 401,431 GL_SCISSOR_BIT_BIT 464 GL_SCISSOR_TEST 463, 464 GL_SELECT 610 GL_SELECTION 590 GL_SET 268 GL_SHADE_MODE 464 GL_SHININESS 292,312 GL_SHORT 344, 379 GL_SMOOTH 169,268 GL_SPECULAR 292, 309 GL_SPHERE_MAP 431 GL_SPOT_CUTOFF 300,313 GL_SPOT_DIRECTION 313 GL_SPOT_EXPONENT 300,313 GL_SRC_ALPHA 506 GL_SRC_ALPHA_SATURATE 506 GL_STACK_OVERFLOW 139,214 GLJSTACKJJNDERFLOW 139,214 GL_STENCIL 378 GL_STENCIL_BUFFER_BIT 464 GL_STENCILJNDEX 379 GL_STENCIL_TEST 463, 464 GL_T 401,431 GL_TEXTURE 227 GL_TEXTURE_1D 434,463 GL_TEXTURE_2D 432, 433, 434, 463 GL_TEXTURE_BIT 464 GL_TEXTURE_ENV 430 GL TEXTURE ENV COLOR 430
702
Dodatki
GL_TEXTURE_ENV_MODE 430 GL_TEXTURE_GEN 463 GL_TEXTURE_GEN_MODE 430, 464 GL_TEXTURE_GEN_Q 430 GL_TEXTURE_GEN_R 430 GL_TEXTURE_GEN_S 430 GL_TEXTURE_GEN_T 430 GL_TEXTURE_GEN_x 464 GL_TEXTURE_MAX_FILTER 434 GL_TEXTURE_MIN_FILTER 390,434 GL_TEXTURE_WRAP_S 394, 434 GL_TEXTURE_WRAP_T 434 GL_TRANSFORM_BIT 464 GL_TRIANGLE_FAN 165,185,581 GL_TRIANGLE_STRIP 164,185,581 GLJTRIANGLES 162,185,581 GL_UNPACK_ALIGNEMNT 382, 379 GL_UNPACK_LSB_FIRST 381 GL_UNPACK_ROW_LENGTH 362, 382 GL_UNPACK_SKIP_PIXELS 362, 382 GL_UNPACK_SKIP_ROWS 363, 382 GL_UNPACK_SWAP_BYTES 381 GL_UNSIGNED_BYTE 344, 379 GLJJNSIGNEDJNT 344, 379 GL_UNSIGNED_SHORT 344, 379 GL_VENDOR 140 GL_VERSION 140 GL_VIEWPORT_BIT 464 GL_XOR 268 GL_ZERO 490,506,531 GL_ZOOM_X 464 GL_ZOOM_Y 464 glAccumO 495,499 glaux.h 52 glaux.lib 52 glBegin() 149,184,430 GLbitfield 54 glBitmapO 352 glBlendFuncO 507, 530 GLboolean 54 GLbyte 54 glCallList() 343, 344 GLclampd 54 GLclampf 54 glClear() 64, 170 glClearColorO 62, 96, 500 glClearDepthO 481,500 glCleadndex() 263, 501 glClearStencilO 489, 501 glColorO 245, 263 glColorMaskO 265 glColorMaterial() 279, 309 glCopyPixels() 366, 377 glCullFaceO 185,310
glDeleteLists() 345 glDepthFuncO 502 glDepthRange() 481,503 glDisableO 153, 160,461,468 GLdouble 54 glDrawBuffer() 501 glDrawPixels() 359, 362, 378 glEdgeFlagO 181, 186 glEnable() 153,160,461,468 glEnd() 149,188,340 glEndListO 340, 346 GLenum 54 glEvalCoord() 540,551 glEvalMesh() 541,552 glEvalPoint() 553 glFeedbackBuffer() 598, 605 GLfloat 54 glFlush() 96 glFogO 520,531 glFrontFaceO 164, 173, 188, 310 glFrustumO 218,225 glGenList() 346 glGet() 153 glGetBoolean() 462 glGetErrorO 134, 138 glGetFloatO 153 glGetLastError() 138 glGetLightO 312 glGetMapO 553 glGetMaterial() 311 glGetPolygonStippleO 189 glGetStringO 136,139 glGetStringO 139 glHintO 137, 140, 525 gllndex() 265 gl!ndexMask() 266 gllnitNamesO 607 GLINT 40 GLint 54 glIsDisabled() 462 glIsEnabled() 462, 470 glIsList() 347 glLight() 300,314 glLightModelO 315 glLineStippleO 160, 190 glLineWidth() 158, 191 glListBase() 348 glLoadEntityO 147 glLoadIdentity() 212,226,72 glLoadMatrix() 223, 226 glLoadName() 607 glLogicOpO 267 glMapO 539, 555 glMapGridO 541, 558
Skorowidz
703
glMaterialO 293,317
glMatrixMode() 147,212,224,227
glMultMatrix() 224,228
glNewList() 340, 349
glNormalO 284,318
glOrthoO 69, 97, 147
glPassThrough() 600,608
glPixelMap() 362,379
glPixelStore() 362, 380
glPixelTransfer() 360, 382
glPixelZoom() 362, 383
glPointSize() 152, 193
glPolygonMode() 173, 194
glPolygonStipple() 195
glPopAttrib() 301,470
glPopMatrix() 148,216,228
glPopName() 609
glPushAttribO 301,462,470
glPushMatrix() 148, 216, 229
glPushNameO 609
glRasterPos() 353
glReadPixels() 363, 384
glRectO 66,98
glRenderMode() 590,610
glRotate() 79, 148, 209, 216, 229
glScale() 210,230
g!SelectBuffer() 612
glSet() 153
glShadeMode() 169
glShadeModelO 268
GLshort 54
GLsizei 54
glStencilFunc() 489
glStencilMask() 489
glStencilOpO 489
g!TexCoord() 394, 429
glTexEnv() 429
glTexGen() 430
glTex!magelD() 389,431
glTexImage2D() 391,432
glTexParameter() 433
glTextEnv() 392
glTranslateO 209,216,231
glu 52
glu.h 52
GLU_AUTO_LOAD_MATRIX 564, 570, 613
GLUJ3EGIN 581
GLU_CCW 577
GLU_CULLING 564, 569
GLU_CW 577
GLU_DISPLAY_MODE 564, 569
GLU_DOMAIN_DISTANCE 570
GLU_EDGE_FLAG 582
GLU END 582
GLU_ERROR 566, 567, 582 GLU_EXTENSIONS 136 GLU_EXTERIOR 577 GLU_FALSE 437 GLU_FILL 436, 454, 569 GLU_FLAT 437, 455 GLU_INSIDE 455 GLU_INTERIOR 577 GLU_INVALID_ENUM 139 GLU_INVALID_VALUE 139 GLU_LINE 436 GLU_LINE 454 GLU_MAP1_TRIM_2 573 GLU_MAP1_TRIM_3 573 GLU_NONE 437,455 GLU_NURBS_ERRORx 566,567 GLU_OUT_OF_MEMORY 135,138, 139 GLU_OUTLINE_PATCH 569 GLU_OUTLINE_POLYGON 569 GLU_OUTSIDE 455 GLU_PARAMETRIC_ERROR 570 GLU_PARAMETRIC_TOLERANCE 564, 569 GLU_PATH_LENGTH 569, 570 GLU_POINT 436, 454 GLU_SAMPLING_METHOD 564, 570 GLU_SAMPLING_TOLERANCE 564, 569 GLU_SILHOUETTE 436, 454 GLU_SMOOTH 437,455 GLUJTRUE 437 GLU_U_STEP 564, 570 GLUJJNKNOWN 577 GLU_V_STEP 564, 570 GLU_VERTEX 582 glu32.dll 52 gluBeginCurve() 559 gluBeginPołygonO 576, 583 gluBeginSurfaceO 547, 560 gluBeginTrimO 549,560 GLubyte 54 gIuCylinder() 437,451 gluDeleteNurbsRenderer() 547, 561 gluDeleteQuadric() 452 gluDeleteTessO 583 gluDiskO 438, 452 gluEndCurve() 562 gluEndPolygonO 576, 583 gluEndSurface() 547, 563 gluEndTrim() 549, 563 gluErrorStringO 135, 141 gluGetNurbsPropertyO 563 gluGetStringO 136, 141 GLuint 54
gluLoadSamplingMatricesO 654 gluLookAt() 231
704
Dodatki
gluNewContour() 584 gluNewNurbsRenderer() 547, 565 gluNewQuadric() 436, 453 gluNewTess() 576,584 gluNextContour() 577 gluNurbsCallback() 566 gluNurbsCurve() 568 GLUnurbsObj 546 gluNurbsPropertyO 547, 569 gluNurbsSurfaceO 547, 571 gluOrtho2D() 232 gluPartialDisk() 439, 453 gluPerspective() 218,233 gluPickMatrix() 593,613 gluPwlCurve() 549,572 gluQuadricCallback() 454 gluQuadricDrawStyle() 436, 454 gluQuadricNormals() 436, 455 GLUąuadricObj 436 gluQuadricOrientation() 436, 455 gluQuadricTexture() 436, 456 GLushort 54 gluSphereO 439, 456 gluTessCallback() 581,585 gluTessVertes() 576, 585 GLUtriangulatorObj 576 glVertex() 148, 196 glViewport() 69,71,97 GLYPHMETR1CSFLOAT 131 głębia koloru 241 głębokość mgły 524 grafika interaktywna 587 grafika rastrowa 351 Graphics Device Interface 35 GUI 59
H
HAL 39
Hardware Abstraction Layer 39
HBRUSH 103
Hercules 239
HGLRC 106
hierarchia obiektów 594
HPALETTE 251
l
IBM 37 imbryk 78 inicjowanie 67 Intel 37 Internet 615 IR1S GL 36, 37
jednolite obiekty 166 język 4GL 52 język GL 36
K
karta
8514 240
EGA 240
Hercules 239
VGA 240
kartezjański układ współrzędnych 45 kierunek trójkąta 163 kierunek wielokątów 285 kierunkowe źródło światła 299 klasa krzywej 535 klawiatura 83 kolejka poleceń 64 kolejka przekształceń 207 kolor 43, 63, 235
dopasowywanie 249
paleta 249 komunikat WM_CLOSE 338
WM_CREATE 107
WM_DESTROY 107
WM_PAINT 104, 107,338
WM_PALETTECHANGED 116,253
WM_QUERYNEWPALETTE 252
WM_QUERYPALETTE 116
WM_SIZE 114
WMJTIMER 115,337 konfigurowanie buforów 474 konstruowanie wielokątów 179 kontekst renderowania OpenGL 105 kontekst urządzenia GDI 102 kontrolki OCX 659 konwencje nazw funkcji 55 kopiowanie pixmap 366 kostka kolorów 243 krzywe 533 krzywe
Beziera 535, 545
ciągłość 536
dwuwymiarowe 537
klasa 535
obliczenia 537
punkty kontrolne 535
reprezentacja parametryczna 534
stopień 535
węzły 546 kwadryki 435
705
Skorowidz
lampa 44
linie 145
linie przerywane 160
listy wyświetlania 340
LOGPALETTE 251
long 54
Ł
ładowanie macierzy 223 łamane 156 łamane zamknięte 156 łączenie kolorów 509
M
macierze 206, 680
macierz
GL_MODELVIEW 227 GL_PROJECTION 227 GL_TEXTURE 227 ładowanie 223 rzutowania 216 stos macierzy 213 tożsamościowa 211 widoku modelu 208
makro RGB 103,245
mapowanie tekstur 387
maszyna stanu OpenGL 461
materiał 275 oświetlenie 275 właściwości 275, 279
MFC 52, 627
mgła 505, 520 GL_FOG_COLOR 531 GL_FOG_DENSITY 531 GL_FOG_END 531 GL_FOG_INDEX 531 GL_FOG_MODE 531 GL_FOG_START 531
mipmapy 389, 394
model 202
model cieniowania 246
model oświetlenia 278
modelowanie obiektów 321
mysz 84
N
nakładanie tekstur 388 nazwy funkcji 55 nazywanie prymitywów 588 niezależność od platformy 57 normalizacja 285 normalne 544
normalne jednostkowe 285 NURBS 54,533,545
obcinanie współrzędnych 46 obiekt GLUnurbsObj 546
GLUquadricObj 436
GLUtriangulatorObj 576 obrót 203, 209 obszar obcinania 46, 49 obszar roboczy okna 46 OCX 659 odbłyski 291 odczytplikow.BMP 368 odtwarzanie zmiennych stanu 462 odwzorowanie GL_MAP1_COLOR_4 554, 556, 569
GL_MAP1_INDEX 554,556,569
GL_MAP1_NORMAL 554, 556, 569
GL_MAP1_TEXTURE_COORD_1 554,556, 569
GL_MAP1_TEXTURE_COORD_2 554,556, 569
GL_MAP1_TEXTURE_COORD_3 554,556, 569
GL_MAP1_TEXTURE_COORD_4 554, 556, 569
GL_MAP1_VERTEX_3 554,556,569
GL_MAP1_VERTEX_4 554,556,569
GL_MAP2_COLOR_4 554, 556, 572
GL_MAP2_INDEX 554, 556, 572
GL_MAP2_NORMAL 554, 556, 572
GL_MAP2_TEXTURE_COORD_1 554, 556, 572
GL_MAP2_TEXTURE_COORD_2 554, 556, 572
GL_MAP2_TEXTURE_COORD_3 554, 556, 572
GL_MAP2_TEXTURE_COORD_4 554, 556, 572
GL_MAP2_VERTEX_3 554, 556, 572
GL_MAP2_VERTEX_4 554, 556, 572 okno widoku 46 oko 41
Open Inventor 617 opengl32.dll 52 operacja GL_AND 268
GL_AND_INVERTED 268
GL_AND_REVERSE 268
GL_CLEAR 268
GL_COPY 268
GL_COPY_INVERTED 268
GL_EQUIW 268
706
Dodatki
operacja
GLJNYERT 268
GLJMAND 268
GL_NOOP 268
GL_NOR 268
GLJDR 268
GL ORJNYERTED 268
GL~OR_REVERSE 268
GL_SET 268
GL_XOR 268
opróżnianie kolejki poleceń 64 osie układu współrzędnych 45 ostrosłup widzenia 218 oświetlenie 271 oświetlenie materiału 275 otwarte API 57 OWL 52,641
PAINTSTRUCT 104 paleta systemowa 251 paleta 242,249
3-3-2 255
animacja 259
struktura 254
tworzenie 253, 258
usuwanie 258 PALETTEENTRY 254 pasek czworokątów 175 perspektywa 50, 218 pędzle 103 PFD_DOUBLE_BUFFER_DONT_CARE 121,
475
PFD_DOUBLEBUFFER 121,475 PFD_DRAW_TO_B1TMAP 121,475 PFD_DRAW_TO_WINDOW 121,475 PFD_GENERIC_FORMAT 121,476 PFD_MA1N_PLANE 122 PFD_NEED_PALETTE 121,254,476 PFD_NEED_SYSTEM_PALETTE 121,476 PFD_OVERLAY_PLANE 122 PFD_STEREO 121,475 PFD_STEREO_DONT_CARE 121,475 PFD_SUPPORT_GDI 121, 475 PFD_SUPPORT_OPENGL 121,475 PFD_TYPE_COLORINDEX 122,260,475 PFD_TYPE_RGBA 122,475 PFD_UNDERLAY_PLANE 122 PKELFORMATDESCRIPTOR 109, 254, 474 pixmapy 359 plik .BMP 367
gl.h 52
glaux.h 52
glaux.lib 52
glu.h 52
glu32.dll 52
opengl32.dll 52
WINSRV.DLL 38 pliki nagłówkowe 60 Pług and Play 659 płaskie cieniowanie 248 płaszczyzna 44 płaszczyzna xy 45 płynne cieniowanie 246 podwójne buforowanie 76, 77, 477 podział wielokąta 181,575 PO1NTFLOAT 131 pojedyncze buforowanie 60 poprawianie wydajności 340, 679 porównywanie głębokości 479 powierzchnie 533, 541
normalne 544
oświetlenie 544 pozycjonowanie okna 60 pozycyjne źródło światła 299 PRINTDLG 372 prostokąt 66 prymityw
GL_LINE_LOOP 156, 185
GL_LINE_STRIP 156, 184
GLJJNES 155, 184
GL_POINTS 149, 184
GL_POLYGON 185,575
GL_QUAD_STRIP 175, 185
GL_QUADS 174, 185
GL_TR1ANGLE_FAN 165,185,581
GL_TRIANGLE_STRIP 164, 185, 581
GLJRIANGLES 162, 185, 581 prymitywy 47, 145 przedrostki nazw funkcji 52 przekształcenia 200 przekształcenie modelu 202 przekształcenie okna 206 przekształcenie punktu obserwacji 202 przekształcenie rzutowania 205 przerzucanie kolorów 478 przestrzeń trójwymiarowa 146 przesunięcie 203,208 przezroczystość 505 przygotowanie modelu oświetlenia 278 przygotowanie okna 108 przygotowanie właściwości materiału 279 przyrostki nazw zmiennych 54 punkt zbiegu 205 punkty 145 punkty kontrolne 535
Skorowidz
707
ay tracing 35
Reality Labs 39
remapowanie kolorów 360
renderowanie 66
RenderSceneO 66, 115, 148,289
reprezentacja parametryczna krzywych 534
RGB 51, 103,245
RGBA 60, 239
rotacja 203
rozdzielczość ekranu 241
roztrząsanie 242, 250
równanie parametryczne 534
rysowanie bitmapy 351
cylindrów 437
czworokąty 174
domyślny kolor 163
dysków 438
jednolite obiekty 166
kierunek trójkąta 163
konstruowanie wielokątów 179
linie 155
linie przerywane 160
łamane 156
łamane zamknięte 156
pasek czworokątów 175
pixmapy 359
podział wielokąta 181
prymitywów 47
punkty 149
sfer 439
stożków 438
światła punktowego 301
trójkątów 162,261
tryby wielokątów 173
ustawienie koloru wielokątów 169
usuwanie niewidocznych powierzchni 171
wachlarz trójkątów 165
wielokątów 289
wielokątów wklęsłych 576
wielokątów złożonych 577
wycinków dysku 439
wypełnianie wielokątów 175
znacznik krawędzi 181 rzutowanie perspektywiczne 205, 218 rzutowanie równoległe 205,217 rzutowanie 216,322 rzuty 48 rzuty perspektywiczne 50
selekcja 588 SetPixelFormat() 109, 122
SetTimerO 114 SetupRCO 277 sfery 435 SGI 35 short 54
sieć komputerowa 615 signed char 54 Silicon Graphics 35 skalowanie pixmapy 362 skalowanie 68, 114,203,210 specular 272, 273 sprzężenie zwrotne 587 stała AUX_0 83
AUX_9 83
AUX_A 83
AUX_a 83
AUX_ACCUM 81
AUX_ALPHA 81
AUX_DEPTH 81
AUX_DEPTH16 81
AUX_DOUBLE 80
AUX_DOWN 83
AUX_ESCAPE 83
AUX_FIXED_332_PAL 81
AUX_1NDEX 80
AUX_LEFT 83
AUX_LEFTBUTTON 84
AUX_MIDDLEBUTTON 84
AUX_MOUSEDOWN 84
AUX_MOUSEUP 84
AUX_RETURN 83
AUX_RGBA 80
AUX_RIGHT 83
AUX_RIGHTBUTTON 84
AUX_SINGLE 80
AUX_SPACE 83
AUX_STENCIL 81
AUX_UP 83
AUX_Z 83
AUX_z 83
COLORREF 62
false 54
FL_FOG_HINT 525
GL_2_BYTES 344
GL_2D 599, 606
GL_3_BYTES 344
GL_3D 599,606
GL_3D_COLOR 599, 606
GL_3D_COLOR_TEXTURE 599, 606
GL_4_BYTES 344
GL_4D_COLOR_TEXTURE 599, 606
GL_ACCUM_BUFFER_BIT 463
GL_ALPHA 379, 432, 433
GL_ALPHA_BIAS 383,464
708
Dodatki
stalą
GL_ALPHA_LUMINANCE 432, 433 GL_ALPHA_SCALE 383,464 GL_ALPHA_TEST 463 GL_ALWAYS 480, 490, 503 GL_AMBIENT 309
GL_AMBIENT_AND_DIFFUSE 279, 309 GL_AND 268 GL_AND_INVERTED 268 GL_AND_REVERSE 268 GL_AUTO_NORMAL 463 GL_BACK 173, 186, 195, 309, 478, 502 GL_BACK_AND_FRONT 309 GL_BITMAP 360,379 GL_BITMAP_TOKEN 599 GL_BLEND 430, 463, 505 GL_BLUE 379,432,433 GL_BLUE_BIAS 383,464 GL_BLUE_SCALE 383, 464 GL_BORDER_COLOR 434 GL_BYTE 344, 379 GL_CCW 164,189,310 GL_CLAMP 434 GL_CLEAR 268 GL_COEFF 554 GL_COLOR 378
GL_COLOR_BUFFER_BIT 170,463 GL_COLOR_INDEX 359, 579, 432, 433 GL_COLOR_INDEXES 312 GL_COLOR_MATER1AL 279 GL_COLOR_MATERIAL 309, 463, 464 GL_COLOR_MATERIAL_FACE 464 GL_COMPILE 341,349 GL_COMPILE_AND_EXECUTE 341,349 GL_CONSTANT_ATTENUATION 313 GL_COPY 268 GL_COPY_INVERTED 268 GL_COPY_PIXEL_TOKEN 599 GL_CULL_FACE 172,186,310,463,464 GL_CULL_FACE_MODE 464 GL_CURRENT_BIT 463 GL_CURRENT_POSITION_VALID 463 GL_CW 164, 189 GL_DECAL 392,430 GL_DEPTH 378 GL_DEPTH_BIAS 383, 464 GL_DEPTH_BUFFER 463 GL_DEPTH_BUFFER_BIT 170 GL_DEPTH_COMPONENT 379 GL_DEPTH_SCALE 383,464 GL_DEPTH_TEST 170,461,463 GL_DEPTH_WRITEMASK 463 GLJDIFFUSE 309 GL DITHER 250, 463
GL_DOMAIN 554 GL_DONT_CARE 141 GL_DRAW_PIXEL_TOKEN 599 GL_DST_ALPHA 506 GL_DST_COLOR 506 GL_EDGE_FLAG 463 GL_EMISS10N 309 GL_ENABLE_BIT 463 GL_EQUAL 480, 490, 503 GL_EQUIW 268 GL_EVAL_BIT 463 GL_EXP 520 GL_EXP2 520 GL_EXTENSIONS 136, 140 GL_EYE_LINEAR 431 GL_EYE_PLANE 431 GL_FALT 169 GL_FASTEST 141 GL_FEEDBACK 590, 598, 611 GL_FILL 173, 195 GL_FLAT 268 GL_FLOAT 344, 379 GL_FOG 463 GL_FOG_BIT 463 GL_FOG_COLOR 531 GL_FOG_DENSITY 524,531 GL_FOG_END 531 GL_FOG_HINT 140,463 GL_FOG_INDEX 531 GL_FOG_MODE 463,520,531 GL_FOG_START 531 GL_FRONT 186, 195, 309, 478, 502 GL_FRONT_AND_BACK 195, 478, 502 GL_FRONT_FACE 464 GL_GEQUAL 480, 490, 503 GL_GREATER 480, 490, 503 GL_GREEN 379,432,433 GL_GREEN_BIAS 383,464 GL_GREEN_SCALE 383,464 GL_HINT_BIT 463 GL_INDEX_OFFSET 383, 464 GL_INDEX_SHIFT 383, 464 GLJNT 344, 379 GL_INVAL1D_ENUM 135, 139 GL_INVALID_OPERATION 138, 139 GL_1NVALID_VALUE 139 GLJNYERT 268 GL_LEFT_BACK 478, 502 GL_LEFT_FRONT 478, 502 GL_LEQUAL 480, 490, 503 GL_LESS 479,490,503 GL_LIGHT 313
GL_LIGHT_MODEL_AMBIENT 278,315 stalą
Skorowidz
709
GL_LIGHT_MODEL_LOCAL_VIEWER 316,
464
GL_LIGHT_MODEL_TWO_SIDE 315,464 GL_LIGHTO 288 GL_LIGHTi 463 GL_LIGHTING 277, 463, 464 GL_LIGHTING_BIT 301,464 GL_LIGHTx 464 GL_LINE 173, 195 GL_LINE_B1T 464 GL_LINE_LOOP 156, 185 GL_LINE_RESET_TOKEN 599 GL_LINE_SMOOTH 463,464 GL_LINE_SMOOTH_HINT 140,463 GL_LINE_STIPPLE 160, 190, 463, 464 GL_LINE_STRIP 156, 184 GL_LINE_TOKEN 599 GL_LINE_WIDTH_GRANULARITY 158 GL_LINE_WIDTH_RANGE 158 GL_LINEAR 390, 520 GL_LINEAR_ATTENUATION 313 GL_LINEAR_MIPMAP_LINEAR 391,396 GL_LINEAR_MIPMAP_NEAREST 391,396 GL_LINES 155, 184 GL_LIST_BASE 464 GL_LIST_BIT 464 GL_LOGIC_OP 267, 463 GL_LUMINANCE 359, 379, 432, 433 GL_LUMINANCE_ALPHA 379 GL_MAP_COLOR 380, 382, 464 GL_MAP_DEPTH 464 GL_MAP_STENCIL 380, 382 GL_MAP1_COLOR_4 554, 556, 569 GL_MAP1_INDEX 554, 556, 569 GL_MAP1_NORMAL 554,556,569 GL_MAP1_TEXTURE_COORD_1 554, 556,
569 GL_MAP1_TEXTURE_COORD_2 554,556,
569 GL_MAP1_TEXTURE_COORD_3 554, 556,
569 GL_MAP1_TEXTURE_COORD_4 554, 556,
569
GL_MAP1_VERTEX_3 539,554,556,569 GL_MAP1_VERTEX_4 539, 554, 556, 569 GL_MAPl_x 463
GL_MAP2_COLOR_4 554, 556, 572 GL_MAP2_INDEX 554, 556, 572 GL_MAP2_NORMAL 554, 556, 572 GL_MAP2_TEXTURE_COORD_1 554, 556,
572 GL_MAP2_TEXTURE_COORD_2 554, 556,
572
GL_MAP2_TEXTURE_COORD_3 554, 556,
572 GL_MAP2_TEXTURE_COORD_4 554, 556,
572
GL_MAP2_VERTEX_3 554, 556, 572 GL_MAP2_VERTEX_4 554, 556, 572 GL_MAP2_x 463 GL_MATRIX_MODE 464 GL_MAX_MODELVIEW_STACK_DEPTH
214
GL_MAX_NAME_STACK_DEPTH 610 GL_MAX_PROJECTION_STACK_DEPTH
214
GL_MODELVIEW 212,224,227,431 GL_MODULATE 430 GL_NAME_STACK_DEPTH 609 GL_NAND 268 GL_NEAREST 390
GL_NEAREST_MIPMAP_LINEAR 391,396 GL_NEAREST_MIPMAP_NEAREST 391,
396
GL_NEVER 479, 490, 503 GLJNICEST 141,525 GL_NO_ERROR 135, 138, 139 GL_NOOP 268 GL_NOR 268
GL_NORMALIZE 285,318,463,464 GL_NOTEQUAL 480, 490, 503 GL_OBJECT_LINEAR 430 GL_OBJECT_PLANE 401,431 GL_ONE 506,531
GL_ONE_MINUS_DST_ALPHA 506 GL_ONE_MINUS_DST_COLOR 506 GL_ONE_MINUS_SRC_ALPHA 506 GL_OR 268
GL_OR_INVERTED 268 GL_OR_REVERSE 268 GL_ORDER 554
GL_OUT_OF_MEMORY 135, 138, 139 GL_PACK_ALIGNMENT 381 GL_PACK_LSB_FIRST 381 GL_PACK_ROW_LENGTH 381 GL_PACK_SKIP_PIXELS 381 GL_PACK_SKIP_ROWS 381 GL_PACK_SWAP_BYTES 381 GL_PASS_THROUGH_TOKEN 599, 608 GL_PERSPECTIVE_CORRECTION_HINT
140,463
GL_PIXEL_MAP_A_TO_A 380 GL_PIXEL_MAP_B_TO_B 380 GL_PIXEL_MAP_G_TO_G 380 GL_PIXEL_MAP_I_TO_A 380 GL PIXEL MAP I TO B 380
Dodatki
710
stała
GL_PIXEL_MAP_I_TO_G 380 GL_PIXEL_MAP_I_TO_I 380 GL_PIXEL_MAP_1_TO_R 380 GL_PIXEL_MAP_R_TO_R 380 GL_PIXEL_MAP_S_TO_S 380 GL_PIXEL_MODE_BIT 464 GL_POINT 195 GL_POINT_BIT 464
GL_POINT_SIZE_GRANULARITY 153,193 GL_POINT_SIZE_RANGE 153,193 GL_POINT_SMOOTH 463, 464 GL_POINT_SMOOTH HINT 141,463 GL_POINT_TOKEN 599 GL_POINTS 149, 184 GL_POLYGON 185,575 GL_POLYGON_BIT 464 GL_POLYGON_MODE 464 GL POLYGON_SMOOTH 463, 464 GLJPOLYGON_SMOOTH_HINT 141,463 GL_POLYGON_STIPPLE 176, 195,463,464 GL_POLYGON_STIPPLE_BIT 464 GL_POLYGON_TOKEN 599 GL_POSITION 298,313 GL_PROJECTION 227 GL_Q 431
GL_QUAD_STRIP 175, 185 GL_QUADRATIC_ATTENUATION 313 GL_QUADS 174, 185 GL_R 431
GL_READ_BUFFER 464 GL_RED 379,432,433 GL_RED_BIAS 383,464 GL_RED SCALĘ 383,464 GL_RENDER 590,610 GL RENDERER 140,611 GL~REPEAT 394, 434 GL_RGB 359,379,432,433 GL_RGBA 379, 432, 433 GL RIGHT_BACK 478, 502 GL~RIGHT_FRONT 478, 502 GL_S 401,431 GL_SCISSOR_BIT_BIT 464 GL_SCISSOR_TEST 463, 464 GL_SELECT 610 GL_SELECTION 590 GL_SET 268 GL_SHADE_MODE 464 GL_SHININESS 292,312 GL_SHORT 344, 379 GL_SMOOTH 169,268 GL_SPECULAR 292, 309 GL_SPHERE_MAP 431 GL SPOT CUTOFF 300,313
GL_SPOT_DIRECTION 313 GL_SPOT_EXPONENT 300,313 GL_SRC_ALPHA 506 GL_SRC_ALPHA_SATURATE 506 GL_STACK_OVERFLOW 139,214 GL_STACK_UNDERFLOW 139,214 GL_STENCIL 378 GL_STENCIL_BUFFER_BIT 464 GL_STENC1L_INDEX 379 GL_STENCIL_TEST 463, 464 GL_T 401,431 GL_TEXTURE 227 GL_TEXTURE_1D 392, 434, 463 GL_TEXTURE_2D 392, 432, 433, 434, 463 GL_TEXTURE_BIT 464 GL_TEXTURE_ENV 392,430 GL_TEXTURE_ENV_COLOR 430 GL_TEXTURE_ENV_MODE 392, 430 GL_TEXTURE_GEN 463 GL_TEXTURE_GEN_MODE 430, 464 GL_TEXTURE_GEN_Q 430 GL_TEXTURE_GEN_R 430 GL_TEXTURE_GEN_S 430 GL_TEXTURE_GEN_T 430 GL_TEXTURE_GEN_x 464 GL_TEXTURE_MAX_FILTER 434 GL_TEXTURE_MIN_FILTER 390, 434 GL_TEXTURE_WRAP_S 394, 434 GL_TEXTURE_WRAP_T 434 GL_TRANSFORM_BIT 464 GL_TRIANGLE_FAN 165, 185, 581 GL_TRIANGLE_STRIP 164,185,581 GLJTRIANGLES 162, 185, 581 GL_UNPACK_ALIGNEMNT 382, 379 GL_UNPACK_LSB_FIRST 381 GL_UNPACK_ROW_LENGTH 362, 382 GL_UNPACK_SKIP_PIXELS 362, 382 GL_UNPACK_SKIP_ROWS 363, 382 GL_UNPACK_SWAP_BYTES 381 GL_UNSIGNED_BYTE 344, 379 GLJJNSIGNEDJNT 344, 379 GL_UNSIGNED_SHORT 344, 379 GL_VENDOR 140 GL_VERSION 140 GL_VIEWPORT_BIT 464 GL_XOR 268 GL_ZERO 506 GL_ZERO 531 GL_ZOOM_X 464 GL_ZOOM_Y 464
GLU_AUTO_LOAD_MATRIX 564,570,613 GLU_BEGIN 581 GLU_CCW 577 GLU CULLING 564, 569
711
Skorowidz
stała
GLU_CW 577
GLU_DISPLAY_MODE 564, 569 GLU_DOMAIN_DISTANCE 570 GLU_EDGE_FLAG 582 GLU_END 582 GLU_ERROR 566, 567, 582 GLU_EXTENSIONS 136 GLU_EXTERIOR 577 GLU_FALSE 437 GLU_FILL 436, 454, 569 GLU_FLAT 437, 455 GLUJNSIDE 455 GLUJNTERIOR 577 GLU_INVALID_ENUM 139 GLU_INVALID_VALUE 139 GLU_LINE 436,454 GLU_MAP1_TRIM_2 573 GLU_MAP1_TRIM_3 573 GLU_NONE 437, 455 GLU_NURBS_ERRORx 566, 567 GLU_OUT_OF_MEMORY 135, 138, 139 GLU_OUTLINE_PATCH 569 GLU_OUTLINE_POLYGON 569 GLU_OUTSIDE 455 GLU_PARAMETRIC_ERROR 570 GLU_PARAMETRIC_TOLERANCE 564, 569 GLU_PATH_LENGTH 569, 570 GLU_POINT 436, 454 GLU_SAMPLING_METHOD 564, 570 GLU_SAMPLING_TOLERANCE 564, 569 GLU_SILHOUETTE 436,454 GLU_SMOOTH 437,455 GLU_TRUE 437 GLU_U_STEP 564, 570 GLUJJNKNOWN 577 GLU_V_STEP 564, 570 GLU_VERTEX 582 PFD_DOUBLE_BUFFER_DONT_CARE 121,
475
PFD_DOUBLEBUFFER 121,475 PFD_DRAW_TO_BITMAP 121,465 PFD_DRAW_TO_WINDOW 121,475 PFD_GENERIC_FORMAT 121 PFD_MAIN_PLANE 122 PFD_NEED_PALETTE 121,254 PFD_NEED_SYSTEM_PALETTE 121 PFD_OVERLAY_PLANE 122 PFD_STEREO 121,475 PFD_STEREO_DONT_CARE 121,475 PFD_SUPPORT_GDI 121,475 PFD_SUPPORT_OPENGL 121,475 PFD_TYPE_COLOR1NDEX 122, 260, 475 PFD TYPE_RGBA 122, 475
PFD_UNDERLAY_PLANE 122
PIXELFORMATDESCRIPTOR 254
true 54
WGL_FONT_LINES 131
WGL_FONT_POLYGONS 131 stan bufora głębokości 465
bufora szablonu 465
oświetlenia 465
pikseli 467
rysowania 464
teksturowania 466 stereoskop 42 stopień krzywej 535 stopień poryskliwości 293 stos macierzy 213 stosunek współrzędnych 72 StretchBltO 373 struktura palety 254 struktura
BITMAPFILEHEADER 367
BITMAPINFOHEADER 365
GLYPHMETRICSFLOAT 131
LOGPALETTE 251
PAINTSTRUCT 104
PALETTEENTRY 254
PIXELFORMATDESCRIPTOR 109,474
POINTFLOAT 131
PRINTDLG 372 style okien 108 styl
CS_PARENTDC 108 WS_CLIPCHILDREN 108 WS_CL1PSIBLINGS 108 WS_OWNDC 107 Super VGA 61 SwapBuffers() 115, 123 szybkość działania 336
ścieżka
GLU_CCW 577 GLU_CW 577 GLU_EXTERIOR 577 GLUJNTERIOR 577 GLUJJNKNOWN 577 światło 44,236 światła punktowe 298 światło jako cząstka 237 światło kierunkowe 299 światło
ambient 272, 273 diffuse 272, 273 odbłysku 272, 273 otaczające 272, 273
712
Dodatki
światło
rozproszone 272, 273 specular 272, 273
tablice odwzorowań kolorów 361
teksturowanie cylindrów 438
teksturowanie dysku 439
teksturowanie sfer 440
tekstury 387
test szybkości 336
timer 114
transformacje współrzędnych 199
translacja 203
trójkąt 162,261
trójwymiarowe współrzędne kartezjańskie 48
TrueColor 259
true 54
tryb cieniowania 248
tryb graficzny 241
tryb indeksu koloru 239, 259, 308
tryb konsoli 59
tryb renderowania
GL_FEEDBACK 590
GL_RENDER 590
GL_SELECTION 590 tryb RGBA 239 tryb wyświetlania 60 tryb-RGBA 60 tryby wielokątów 173 trzeci wymiar 41, 78 tworzenie kontekstu renderowania 106 tworzenie kwadryk 436 tworzenie palety 253, 258 tworzenie powierzchni NURBS 546 tworzenie własnych przekształceń 224 typ danych
GL_2D 599,606
GL_3D 599,606
GL_3D_COLOR 599, 606
GL_3D_COLOR_TEXTURE 599, 606
GL_4D_COLOR_TEXTURE 599, 606
GL_2_BYTES 344
GL_3_BYTES 344
GL_4_BYTES 344
GL_BYTE 344
GL_FLOAT 344
GLJNT 344
GL_SHORT 344
GL_UNSIGNED_BYTE 344
GL_UNSIGNED_INT 344
GL UNSIGNED SHORT 344
HGLRC 106 HPALETTE 251 typy danych 53
U
uchwyt palety 251 uchwyt pędzla 103 udostępnianie palety 251 układ współrzędnych 44, 45
kartezjański 45
osie 45
UNICODE 356 UNIX 56 unsigned char 54 unsigned long 54 unsigned short 54 ustalanie grubości linii 158 ustalanie koloru rysowania 245 ustalanie rozmiaru punktu 152 ustawianie bryły obcinania 68 ustawianie widoku 68 ustawienie koloru wielokątów 169 usuwanie niewidocznych linii 43 usuwanie niewidocznych powierzchni 171 usuwanie palety 258 uśrednianie normalnych 294 używanie źródła światła 281
VGA 45,61,240 Yisual Basic 52,657 VRML 617
W
wachlarz trójkątów 165 wartość
AUX_0 83
AUX_9 83
AUX_A 83
AUX_a 83
AUX_ACCUM 81
AUX_ALPHA 81
AUX_DEPTH 81
AUX_DEPTH16 81
AUX_DOUBLE 80
AUX_DOWN 83
AUX_ESCAPE 83
AUX_FIXED_332_PAL 81
AUX_INDEX 80
AUX_LEFT 83
AUX_LEFTBUTTON 84
AUX MIDDLEBUTTON 84
713
Skorowidz
wartość
AUX_MOUSEDOWN 84 AUX_MOUSEUP 84 AUX_RETURN 83 AUX_RGBA 80 AUX_RIGHT 83 AUX_RIGHTBUTTON 84 AUX_SINGLE 80 AUX_SPACE 83 AUX_STENCIL 81 AUX_UP 83 AUX_Z 83 AUX_z 83 COLORREF 62 false 54
FL_FOG_HINT 525 GL_2_BYTES 344 GL_2D 599, 606 GL_3_BYTES 344 GL_3D 599, 606 GL_3D_COLOR 599, 606 GL_3D_COLOR_TEXTURE 599, 606 GL_4_BYTES 344 GL_4D_COLOR_TEXTURE 599, 606 GL_ACCUM_BUFFER_BIT 463 GL_ALPHA 379, 432, 433 GL_ALPHA_BIAS 383,464 GL_ALPHA_LUMINANCE 432, 433 GL_ALPHA_SCALE 383,464 GL_ALPHA_TEST 463 GL_ALWAYS 480, 490, 503 GL_AMBIENT 309
GL_AMBIENT_AND_DIFFUSE 279, 309 GL_AND 268 GL_AND_INVERTED 268 GL_AND_REVERSE 268 GL_AUTO_NORMAL 463 GL_BACK 173, 186, 195, 309, 478, 502 GL_BACK_AND_FRONT 309 GL_BITMAP 360,379 GL_BITMAP_TOKEN 599 GL_BLEND 430, 463, 505 GL_BLUE 379,432,433 GL_BLUEJ3IAS 383,464 GL_BLUE_SCALE 383, 464 GL_BORDER_COLOR 434 GL_BYTE 344,379 GL_CCW 164,189,310 GL_CLAMP 434 GL_CLEAR 268 GL_COEFF 554 GL_COLOR 378
GL_COLOR_BUFFER_BIT 170,463 GL COLOR_INDEX 359, 579, 432, 433
GL_COLOR_INDEXES 312 GL_COLOR_MATERIAL 279 GL_COLOR_MATERIAL 309, 463, 464 GL_COLOR_MATERIAL_FACE 464 GL_COMPILE 341,349 GL_COMPILE_AND_EXECUTE 341,349 GL_CONSTANT_ATTENUATION 313 GL_COPY 268 GL_COPY_INVERTED 268 GL_COPY_PIXEL_TOKEN 599 GL_CULL_FACE 172,186,310,463,464 GL_CULL_FACE_MODE 464 GL_CURRENT_BIT 463 GL_CURRENT_POSITION_VALID 463 GL_CW 164, 189 GL_DECAL 392, 430 GL_DEPTH 378 GL_DEPTH_BIAS 383,464 GL_DEPTH_BUFFER 463 GL_DEPTH_BUFFER_BIT 170 GL_DEPTH_COMPONENT 379 GL_DEPTH_SCALE 383,464 GL_DEPTH_TEST 170, 461, 463 GL_DEPTH_WRITEMASK 463 GL_DIFFUSE 309 GL_DITHER 250, 463 GL_DOMAIN 554 GLJDONT_CARE 141 GL_DRAW_PIXEL_TOKEN 599 GL_DST_ALPHA 506 GL_DST_COLOR 506 GL_EDGE_FLAG 463 GL_EMISSION 309 GL_ENABLE_BIT 463 GL_EQUAL 480, 490, 503 GL_EQUIW 268 GL_EVAL_BIT 463 GL_EXP 520 GL_EXP2 520 GL_EXTENSIONS 136, 140 GL_EYE_LINEAR 431 GL_EYE_PLANE 431 GL_FALT 169 GL_FASTEST 141 GL_FEEDBACK 590,598,611 GL_FILL 173, 195 GL_FLAT 268 GL_FLOAT 344, 379 GL_FOG 463 GL_FOG_BIT 463 GL_FOG_COLOR 531 GL_FOG_DENSITY 524,531 GL_FOG_END 531 GL_FOG_HINT 140,463
Dodatki
714
wartość
GL_FOG_INDEX 531 GL_FOG_MODE 463,520,531 GL_FOG_START 531 GL_FRONT 186, 195, 309, 478, 502 GL_FRONT_AND_BACK 195,478,502 GL_FRONT_FACE 464 GL_GEQUAL 480, 490, 503 GL_GREATER 480, 490, 503 GL_GREEN 379, 432, 433 GL_GREEN_B1AS 383,464 GL_GREEN_SCALE 383, 464 GL_H1NT_BIT 463 GL_INDEX_OFFSET 383, 464 GL_INDEX_SHIFT 383,464 GLJNT 344, 379 GL_INVALID_ENUM 135, 139 GL_INVALID_OPERATION 138, 139 GL_INVALID_VALUE 139 GLJNYERT 268 GL_LEFT_BACK 478, 502 GL_LEFT_FRONT 478, 502 GL_LEQUAL 480, 490, 503 GLJJESS 479, 490, 503 GL_LIGHT 313
GL_LIGHT_MODEL_AMBIENT 278,315 GL_LIGHT_MODEL_LOCAL_VIEWER 316,
464
GL_LIGHT_MODEL_TWO_SIDE 315,464 GL_LIGHTO 288 GL_LIGHTi 463 GL_LIGHTING 277, 463, 464 GL_LIGHTING_BIT 301, 464 GL_LIGHTx 464 GL_LINE 173, 195
gl_l1ne_bit 464 gl_line_loop 156, 185 gl_line_reset_token 599 gl_line smooth 463,464 gl_line~smooth_hint 140,463 gl_line_stipple 160,190,463,464 gl_line_strip 156, 184 gl_line_token 599 gl_line_width_granularity 158 gl_line_w1dth_range 158
GL_L1NEAR 390, 520 GL_LINEAR_ATTENUAT1ON 313 GL_LINEAR_MIPMAP_LINEAR 391,396 GL_LINEAR_MIPMAP_NEAREST 391,396 GL_LINES 155, 184 GL_LIST_BASE 464 GL_LIST_BIT 464 GL_LOGIC_OP 267,463 GL_LUMINANCE 359, 379, 432, 433
GL_LUMINANCE_ALPHA 379 GL_MAP_COLOR 380, 382, 464 GL_MAP_DEPTH 464 GL_MAP_STENCIL 380, 382 GL_MAP1_COLOR_4 554,556,569 GL_MAP1_INDEX 554,556,569 GL_MAP1_NORMAL 554, 556, 569 GL_MAP1_TEXTURE_COORD_1 554, 556,
569 GL_MAP1_TEXTURE_COORD_2 554, 556,
569 GL_MAP1_TEXTURE_COORD_3 554, 556,
569 GL_MAP1_TEXTURE_COORD_4 554, 556,
569
GL_MAP1_VERTEX_3 539,554,556,569 GL_MAP1_VERTEX_4 539,554,556,569 GL_MAPl_x 463
GL_MAP2_COLOR_4 554, 556, 572 GL_MAP2_INDEX 554, 556, 572 GL_MAP2_NORMAL 554, 556, 572 GL_MAP2_TEXTURE_COORD_1 554, 556,
572 GL_MAP2_TEXTURE_COORD_2 554, 556,
572 GL_MAP2_TEXTURE_COORD_3 554, 556,
572 GL_MAP2_TEXTURE_COORD_4 554, 556,
572
GL_MAP2_VERTEX_3 554, 556, 572 GL_MAP2_VERTEX_4 554, 556, 572 GL_MAP2_x 463 GL_MATRIX_MODE 464 GL_MAX_MODELVIEW_STACK_DEPTH
214
GL_MAX_NAME_STACK_DEPTH 610 GL_MAX_PROJECTION_STACK_DEPTH
214
GL_MODELVIEW 212,224,227,431 GL_MODULATE 430 GL_NAME_STACK_DEPTH 609 GL_NAND 268 GL_NEAREST 390
GL_NEAREST_MIPMAP_LINEAR 391,396 GL_NEAREST_M1PMAP_NEAREST 391,
396
GL_NEVER 479, 490, 503 GL_NICEST 141,525 GL_NO_ERROR 135, 138, 139 GL_NOOP 268 GL_NOR 268
GL_NORMALIZE 285,318,463,464 GL_NOTEQUAL 480,490, 503 GL OBJECT LINĘ AR 430
Skorowidz
715
wartość
GL_OBJECT_PLANE 401,431 GL_ONE 506,531
GL_ONE_MINUS_DST_ALPHA 506 GL_ONE_MINUS_DST_COLOR 506 GL_ONE_MINUS_SRC_ALPHA 506 GL_OR 268
GL_OR_INVERTED 268 GL_OR_REVERSE 268 GLJ3RDER 554
GL_OUT_OF_MEMORY 135, 138, 139 GL_PACK_ALIGNMENT 381 GL_PACK_LSB_FIRST 381 GL_PACK_ROW_LENGTH 381 GL_PACK_SKIP_PIXELS 381 GL_PACK_SKIP_ROWS 381 GL_PACK_SWAP_BYTES 381 GL_PASS_THROUGH_TOKEN 599, 608 GL_PERSPECTIVE_CORRECTION_HINT
140,463
GL_PIXEL_MAP_A_TO_A 380 GL_PIXEL_MAP_B_TO_B 380 GL_PIXEL_MAP_G_TO_G 380 GL_PIXEL_MAP_I_TO_A 380 GL_PIXEL_MAP_I_TO_B 380 GL_PIXEL_MAP_I_TO_G 380 GL_PIXEL_MAP_I_TO_I 380 GL_PIXEL_MAP_I_TO_R 380 GL_PIXEL_MAP_R_TO_R 380 GL_PIXEL_MAP_S_TO_S 380 GL_PIXEL_MODE_BIT 464 GL_POINT 195 GL_POINT_BIT 464
GL_POINT_SIZE_GRANULARITY 153, 193 GL_POINT_SIZE_RANGE 153, 193 GL_POINT_SMOOTH 463, 464 GL_POINT_SMOOTH_HINT 141,463 GL_POINT_TOKEN 599 GL_POINTS 149, 184 GL_POLYGON 185,575 GL_POLYGON_BIT 464 GL_POLYGON_MODE 464 GL_POLYGON_SMOOTH 463, 464 GL_POLYGON_SMOOTH_HINT 141,463 GL_POLYGON_STIPPLE 176, 195,463,464 GL_POLYGON_STIPPLE_BIT 464 GL_POLYGON_TOKEN 599 GL_POSITION 298,313 GL_PROJECTION 227 GL_Q 431
GL_QUAD_STRIP 175, 185 GL_QUADRATIC_ATTENUATION 313 GL_QUADS 174, 185 GL R 431
GL_READ_BUFFER 464 GL_RED 379,432,433 GL__RED_BIAS 383,464 GL_RED_SCALE 383,464 GL_RENDER 590,610 GL_RENDERER 140,611 GL_REPEAT 394,434 GL_RGB 359, 379, 432, 433 GL_RGBA 379,432,433 GL_RIGHT_BACK 478, 502 GL_RIGHT_FRONT 478, 502 GL_S 401,431 GL_SCISSOR_BIT_BIT 464 GL_SCISSOR_TEST 463, 464 GL_SELECT 610 GL_SELECTION 590 GL_SET 268 GL_SHADE_MODE 464 GL_SHININESS 292,312 GL_SHORT 344, 379 GL^SMOOTH 169,268 GL_SPECULAR 292, 309 GL_SPHERE_MAP 431 GL_SPOT_CUTOFF 300,313 GL_SPOT_DIRECTION 313 GL_SPOT_EXPONENT 300,313 GL_SRC_ALPHA 506 GL_SRC_ALPHA_SATURATE 506 GL_STACK_OVERFLOW 139,214 GL_STACK_UNDERFLOW 139,214 GL_STENCIL 378 GL_STENCIL_BUFFER_BIT 464 GL_STENCIL_INDEX 379 GL_STENCIL_TEST 463, 464 GL_T 401,431 GL_TEXTURE 227 GL_TEXTURE_1D 392,434,463 GL_TEXTURE_2D 392, 432, 433, 434, 463 GL_TEXTURE_BIT 464 GL_TEXTURE_ENV 392, 430 GL_TEXTURE_ENV_COLOR 430 GL_TEXTURE_ENV_MODE 392, 430 GL_TEXTURE_GEN 463 GL_TEXTURE_GEN_MODE 430, 464 GL_TEXTURE_GEN_Q 430 GL_TEXTURE_GEN_R 430 GL_TEXTURE_GEN_S 430 GL_TEXTURE_GEN_T 430 GL_TEXTURE_GEN_x 464 GL_TEXTURE_MAX_FILTER 434 GL_TEXTURE_MIN_FILTER 390, 434 GL_TEXTURE_WRAP_S 394, 434 GL_TEXTURE_WRAP_T 434 GL TRANSFORM BIT 464
716
Dodatki
wartość
GL_TRIANGLE_FAN 165, 185, 581 GL_TRIANGLE_STRIP 164,185,581 GLJTRIANGLES 162,185,581 GL_UNPACK_ALIGNEMNT 382, 379 GL_UNPACK_LSB_FIRST 381 GL_UNPACK_ROW_LENGTH 362, 382 GLJJNPACK_SKIP_PIXELS 362, 382 GL_UNPACK_SKIP_ROWS 363, 382 GL_UNPACK_SWAP_BYTES 381 GL_UNSIGNED_BYTE 344, 379 GLJJNSIGNEDJNT 344, 379 GL_UNSIGNED_SHORT 344, 379 GL_VENDOR 140 GL_VERSION 140 GL_VIEWPORT_BIT 464 GL_XOR 268 GL_ZERO 506 GL_ZERO 531 GL_ZOOM_X 464 GL_ZOOM_Y 464
GLU_AUTO_LOAD_MATRIX 564,570,613 GLU_BEGIN 581 GLU_CCW 577 GLU_CULLING 564, 569 GLU_CW 577
GLU_DISPLAY_MODE 564, 569 GLU_DOMAIN_DISTANCE 570 GLU_EDGE_FLAG 582 GLU_END 582 GLU_ERROR 566, 567, 582 GLU_EXTENSIONS 136 GLU_EXTERIOR 577 GLU_FALSE 437 GLU_FILL 436, 454, 569 GLU_FLAT 437, 455 GLUJNSIDE 455 GLUJNTERIOR 577 GLU_1NVALID_ENUM 139 GLU_1NVALID_VALUE 139 GLU_LINE 436, 454 GLU_MAP1_TRIM_2 573 GLU_MAP1_TRIM_3 573 GLU_NONE 437, 455 GLU_NURBS_ERRORx 566,567 GLU_OUT_OF_MEMORY 135, 138, 139 GLU_OUTLINE_PATCH 569 GLU_OUTLINE_POLYGON 569 GLU_OUTSIDE 455 GLU_PARAMETRIC_ERROR 570 GLU_PARAMETRIC_TOLERANCE 564, 569 GLU_PATH_LENGTH 569, 570 GLU_POINT 436, 454 GLU_SAMPLING METHOD 564, 570
GLU_SAMPLING_TOLERANCE 564, 569
GLU_SILHOUETTE 436, 454
GLU_SMOOTH 437,455
GLU_TRUE 437
GLU_U_STEP 564, 570
GLUJJNKNOWN 577
GLU_V_STEP 564, 570
GLU_VERTEX 582
PFD_DOUBLE_BUFFER_DONT_CARE 121, 475
PFD_DOUBLEBUFFER 121,475
PFD_DRAW_TO_BITMAP 121,465
PFD_DRAW_TO_WINDOW 121, 475
PFD_GENERIC_FORMAT 121
PFD_MAIN_PLANE 122
PFD_NEED_PALETTE 121,254
PFD_NEED_SYSTEM_PALETTE 121
PFD_OVERLAY_PLANE 122
PFD_STEREO 121,475
PFD_STEREO_DONT_CARE 121,475
PFD_SUPPORT_GDI 121,475
PFD_SUPPORT_OPENGL 121,475
PFD_TYPE_COLORINDEX 122, 260, 475
PFD_TYPE_RGBA 122,475
PFD_UNDERLAY_PLANE 122
PKELFORMATDESCRIPTOR 254
WGL_FONT_LINES 131
WGL_FONT_POLYGONS 131 wartość TRUE 54 WebSpace 618 wejście/wyjście 57 wektor normalny 283 węzły krzywych 546 WGL_FONT_LINES 131 WGL_FONT_POLYGONS 131 wglCreateContext() 106, 124 wglDeleteContext() 106, 125 wglGetCurrentContext() 125 wglGetCurrentDC() 126 wglGetProcAddress() 126 wglMakeCurrent() 106, 127 wglShareListsO 128 wglUseFontBitmapsO 129,355 wglUseFontOutlines() 130 widok 46 wielokąty 145 wielokąty płaskie 180 wielokąty złożone 575, 577 wierzchołki 48 wiggle 101 Win32 SDK 53 Win32 38 WindowsAPI 35 WinG 35
717
Skorowidz
WinMain() 111 WINSRV.DLL 38 właściwości materiału 275 włączanie oświetlenia 277 WM_CLOSE 338 WM_CREATE 107 WM_DESTROY 107 WM_PAINT 104, 107,338 WM_PALETTECHANGED 116, 253 WM_QUERYNEWPALETTE 252 WM_QUERYPALETTE 116 WM_SIZE 114 WMJTIMER 115,337 WndProc() 111 WS_CLIPCHILDREN 108 WS_CLIPSIBLINGS 108 WS_OWNDC 107 współrzędne obserwatora 201 współrzędne okna 61 WWW 615 wybieranie 592
wybieranie kontekstu rendcrowania 106 wybór hierarchiczny 594 wybór koloru 243 wycinanie powierzchni 549 wydajność 340, 679 wykrawanie obszarów 362 wykrywanie błędów 133 wypełnianie okna kolorem 62 wypełnianie wielokątów 175 wyświetlanie bitmap 375
zachowywanie zmiennych stanu 462
zapis pliku .BMP 370
zmienna stanu GL_ALPHA_BIAS 464
GL_ALPHA_SCALE 464
GL_ALPHA_TEST 463
GL_AUTO_NORMAL 463
GL_BLEND 463
GL_BLUE_BIAS 464
GL_BLUE_SCALE 464
GL_COLOR_MATERIAL 463, 464
GL_COLOR_MATERIAL_FACE 464
GL_CULL_FACE 463, 464
GL_CULL_FACE_MODE 464
GL_CURRENT_POSITION_VALID 463
GL_DEPTH_BIAS 464
GL_DEPTH_BUFFER 463
GL_DEPTH_SCALE 464
GL_DEPTH_TEST 463
GL DEPTH WRITEMASK 463
GL_DITHER 463 GL_EDGE_FLAG 463 GL_FOG 463
gl_fog_hint 463 gl_fog_mode 463 gl_front_face 464 gl_green_bias 464 gl_green_scale 464 gl_index_offset 464 gl index_shift 464 gl~light_model_local_viewer 464 gl_light_model_two_side 464
GLJLIGHTi 463
GL_LIGHTING 463,464
GL_LIGHTx 464
GL_LINE_SMOOTH 463, 464
GL_LINE_SMOOTH_HINT 463
GL_LINE_STIPPLE 463, 464
GL_LIST_BASE 464
GL_LOGIC_OP 463
GL_MAP_COLOR 464
GL_MAP_DEPTH 464
GL_MAPl_x 463
GL_MAP2_x 463
GL_MATRIX_MODE 464
GL_NORMALIZE 463, 464
GL_PERSPECTIVE_CORRECTION_HINT 463
GL_POINT_SMOOTH 463, 464
GL_POINT_SMOOTH_HINT 463
GL_POLYGON_MODE 464
GL_POLYGON_SMOOTH 463, 464
GL_POLYGON_SMOOTH_HINT 463
GL_POLYGON_STIPPLE 463, 464
GL_READ_BUFFER 464
GL_RED_BIAS 464
GL_RED_SCALE 464
GL_SCISSOR_TEST 463, 464
GL_SHADE_MODE 464
GL_STENCIL_TEST 463, 464
GL_TEXTURE_1D 463
GL_TEXTURE_2D 463
GL_TEXTURE_GEN 463
GL_TEXTURE_GEN_MODE 464
GL_TEXTURE_GEN_x 464
GL_ZOOM_X 464
GL_ZOOM_Y 464 znacznik krawędzi 181 znaczniki OpenGL 660 znaczniki użytkownika 600 znajdowanie normalnej 286 źródła światła 271