opengl ksiega eksperta (2)


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

0x01 graphic


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 pro­gram sprzedał, gdy miał lat dwanaście. Pod koniec studiów w SUNY Institute of Tech­nology w Nowym Jorku Michael pracował jako konsultant do spraw grafiki kom­puterowej. 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 robo­czych, 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ży­wać do tworzenia trójwymiarowej grafiki. Obecnie przeciętny komputer jest wydajniej­szy 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 kompute­rowej, 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ą platfor­mą, 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, zapowia­dają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 zapowiada­ją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 le­ktury 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 odpo­wiedzi 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, najpopularniej­szego systemu wszechczasów, Microsoft Windows. Dodatkowo, na końcu każdego roz­dział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ójwy­miarową 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 przezna­czonych 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 roz­prowadzane 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ż do­stępne takie funkcje również dla Windows 95. Najświeższe informacje znajdziesz w pli­ku 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 ma­tematycznym!) z co najmniej ośmioma megabajtami pamięci RAM. Nie są to duże wy­magania; 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ą grafi­czną. 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 zorganizo­wany kod C, co nie zawsze jest prawdą w sytuacji odwrotnej. Istnieje popularna biblio­teka 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 przy­kładzie znajdziesz pliki projektu Yisual C++.

Ponieważ wszystkie przykłady są napisane w C i nie korzystają z żadnych bibliotek spe­cyficznych 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 przy­kł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ędzia­mi 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 wprowa­dzenie do OpenGL i podstaw jej używania w Microsoft Windows. W części drugiej po­znamy 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ę bufora­mi, 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ła­twiamy ż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 gru­py funkcji OpenGL.

Rozdział 4. - OpenGL for Windows: OpenGL + Win32 = Wiggle

W tym rozdziale zaczniesz pisać prawdziwe programy Windows (posiadające pętlę ko­munikató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 spo­sobach 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 obra­cać, 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ży­wać 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), przezna­czone 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 Open­GL. 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 na­leżą 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 informa­cje 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ól­nie dopiero w tym dodatku. Na płytce CD-ROM znajdują się najaktualniejsze i uzu­peł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 przy­kł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ę oso­bne 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żywa­nia 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 do­datkowych programów demonstrujących te nowe możliwości.

Pamiętaj, aby sprawdzić zawartość pliku readme.txt w głównej kartotece w celu po­znania najbardziej aktualnych informacji o zawartości płytki CD-ROM. Ten plik zawie­ra 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, szcze­rze 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 modelowa­niu 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. Za­nim poznałem SQL, nawet nie wyobrażałem sobie, jaką potęgą może władać programi­sta 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 zaofero­wania 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 kompute­rach, 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 wy­rażeniami wykorzystywanymi w całej dalszej części książki.

W rozdziale trzecim rozpoczniesz tworzenie swoich pierwszych programów OpenGL. Poznasz wymaga­ne biblioteki i pliki nagłówkowe, a także konwencje nazw bibliotek i funkcji OpenGL. Na początku pozna­my 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!

0x01 graphic


(zdział 1.

o to jest OpenGL?


0x01 graphic

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ć ele­gancką 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 zapro­jektowany i zoptymalizowany pod kątem wyświetlania i operowania grafiką 3D. Je­dynie 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ć zopty­malizowane 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 wy­starczają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 inter­fejsó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 ar­chitektoniczne aż do generowania komputerowych dinozaurów w najnowszych filmach. Wprowadzenie przemysłowego API 3D na masowy rynek systemów operacyjnych, ta­kich jak Microsoft Windows, także obiecuje pewne ekscytujące możliwości. Gdy po­wszechna 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 po­staci płaskiego, dwuwymiarowego wykresu? Jeśli sądzisz, że dodanie trzeciego wymia­ru do zwykłej aplikacji jest ekstrawagancją, przypomnij sobie firmy, które pierwsze zastosowały ten pomysł w praktyce. Quattro Pro, jedna z pierwszych aplikacji pozwala­jących na łatwe tworzenie trójwymiarowych wykresów, niemal całkowicie zdomino­wał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 biuro­wych. 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 po­dnieść walory wizualne programu!

O OpenGL

Powiedzmy sobie teraz parę słów na temat pochodzeniu biblioteki OpenGL, jej twór­cach 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ła­sny 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 je­dna 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 ubie­gać się o wiele... wcześniej, gdyż w celu zwiększenia produktywności spotkania odby­wają 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 biblio­teki 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 bi­blioteka odnosi się do całego produktu w ogólności, o tyle o języku można mówić raczej w konte­kś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 in­terfejsu API, zawierającego około 120 poleceń i funkcji. Służą one rysowaniu prymity­wów graficznych, takich jak punkty, odcinki i wielokąty w trzech wymiarach. Ponadto OpenGL obsługuje oświetlenie i cieniowanie, mapowanie tekstur, animację oraz dodat­kowe 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 za­pewnienie 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 poja­wił 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ójwymiaro­wej, 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 rze­czywistości, bieżący wybór pędzli, piór, czcionek i innych obiektów GDI nie ma ża­dnego 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 gra­fiki). 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 biblio­teki. 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łą akcelera­cją 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.


0x01 graphic

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łkowi­cie pomija GDI i komunikuje się bezpośrednio z kartą graficzną, być może za pośredni­ctwem 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, prze­kazywane poprzez interfejs sterownika grafiki trójwymiarowej (3D DDI). 3D DDI jest opracowany tak, aby umożliwić producentom sprzętu akcelerację OpenGL i innych trój­wymiarowych API, takich jak Reality Labs API. (Dyskusja na temat OpenGL i API fir­my 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 inter­fejsu 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 dru­kowania grafiki OpenGL na monochromatycznych drukarkach lub drukarkach koloro­wych 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 posia­da 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 korzy­stać 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 mode­lowania i wizualizacji, jednak z czasem z pewnością powstanie także ogromna liczba gier, programów rozrywkowych i aplikacji biurowych wykorzystujących trójwymiaro­wą 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 sta­cji 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 podstawo­wego 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 „trzecie­go" 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 tym­czasową 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



0x01 graphic

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 odda­la 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 wyko­rzystują 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 fragmen­ty 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 świe­cie 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ś per­spektywa 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.


0x01 graphic

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 rzeczywi­sty obiekt, skąd wiesz, gdzie jest jego tył, a gdzie przód? To proste - tył jest prze­sł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ła­niał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 na­zywa się usuwaniem niewidocznych linii i została zastosowana w przypadku kostki z ry­sunku 2.3.


0x01 graphic

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ła­dnie ten sam kolor, co tło, zaś wszystko, co widać, to przednie ścianki. Rzeczywista ko­stka 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 ko­stkę 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



0x01 graphic

Rysunek 2.4.

Pokolorowana kostka, ale bez cieniowania


0x01 graphic

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 po­wierzchnie o jednakowym kolorze, po oświetleniu z boku, wydają się mieć różne cienio­wanie. Po drugie, obiekty nie przepuszczające światła (czyli większość wypełnionych obiektów) po oświetleniu rzucają cień (rysunek 2.6).


0x01 graphic

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, po­zbawione kierunku, to po prostu jednolite oświetlenie mogące powodować efekt cieniowa­nia 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 rysu­jesz 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 środ­ku 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, zoba­czmy, 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 prze­cinkiem. 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 kierun­ki 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ć po­dane 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.


0x01 graphic

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 za­miany 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 pod­czas wypisywania tekstu od dołu do góry, takie odwzorowanie jest inne niż to, do które­go przywykliśmy w szkole.

Widoki, twoje okna na trójwymiarowy świat

Rzadko wysokość i szerokość obszaru obcinania dokładnie odpowiadają szerokości i wy­sokości okna w pikselach. W związku z tym układ współrzędnych musi zostać odwzo­rowany 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 robocze­go. 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 powo­dowałoby dwukrotne zwiększenie odpowiedniej współrzędnej (współrzędnej piksela) w oknie.


0x01 graphic

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.


0x01 graphic

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. Reprezen­tuje 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 zro­bili, 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).


0x01 graphic

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 dodat­ku 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 ob­szary obcinania?), którą chcesz wyświetlić w oknie, a także sposób przekształcania jej współrzędnych.


0x01 graphic

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 prosto­kątną lub sześcienną bryłę rzutowania. Nic, co znajduje się poza tą bryłą, nie jest rzuto­wane. 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 ar­chitektonicznym i programach CAD (Computer Aided Design - projektowanie wspo­magane komputerem).


0x01 graphic

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 po­woduje postanie efektu zmniejszenia rozmiarów bardziej odległych obiektów. Bryła widze­nia (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 znaj­dujące się dalej, wraz ze wzrostem oddalenia mają coraz mniejsze rozmiary. Ten rodzaj rzu­towania odgrywa najważniejszą rolę w nadawaniu realizmu symulacjom i animacjom 3D.


0x01 graphic

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 wy­korzystać 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, nad­szedł czas, aby samemu zająć się tworzeniem programów. Ten rozdział rozpoczynamy od omówienia używania OpenGL z kompilatorem; przy okazji poznasz także konwen­cje 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 fun­kcja 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 wprowadze­nia 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ć fun­kcje w bibliotekach C, także mogą korzystać z OpenGL. Więcej szczegółów na ten te­mat znajduje się w rozdziale 23.



0x01 graphic

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 zo­stał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 rysunko­wego 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 (pomocni­cza), 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 two­jego kompilatora nie dostarczył tych plików, możesz je otrzymać jako część Win32 SDK Microsoftu. Nazwy wszystkich funkcji z tej biblioteki rozpoczy­nają 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ła­twiające wykonywanie powszechnych operacji, takich jak rysowanie kuł, dys­kó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 ob­sługujących specyfikację OpenGL. Nazwy funkcji w tej bibliotece są poprze­dzone 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 po­leceń z biblioteki gl.



0x01 graphic

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 Micro­softu. Jeśli używasz Borland C++, powinieneś stworzyć własne biblio­teki eksportowe, przy pomocy programu implib.exe Borlanda.



Typy danych OpenGL

Aby ułatwić przenoszenie kodu OpenGL pomiędzy różnymi platformami, OpenGL de­finiuje 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ą, informu­jącą o bibliotece, z której pochodzi funkcja, oraz o rodzaju i typie jej argumentów. Naz­wy 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 wyma­gają 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 argu­mentó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 przedro­stków bibliotek i rdzeni nazw. Tak więc wszystkie wariacje funkcji glColor (glColor3f, glColor4f, glColor3i etc.) są opisane jako pojedyncza pozycja - glColor.



0x01 graphic

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 od­powiedniej 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 poje­dynczej 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ła­twiają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óła­mi 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ży­wa się jej głównie we wstępnej fazie, do przetestowania swoich pomysłów. Brak pod­stawowych 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 implemen­tacjach 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 imple­mentuje także pewne funkcje pomocnicze, umożliwiające wykonywanie operacji specy­ficznych 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 konie­czności poznawania wszystkich złożonych szczegółów programowania Windows.

Niestety, mało prawdopodobne jest, aby większość funkcji użytecznej aplikacji spro­wadzał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, za­nim stworzysz pełną aplikację, możesz użyć biblioteki AUX do dopieszczenia kodu zaj­mują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 gra­fiki, 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 po­jedynczej 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ź je­dnak pod uwagę, że jednym z głównych założeń twórców tego systemu była jego nie­zależ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śli­by 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 interfej­sem 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 ope­racyjnego, jednak funkcje udostępniane przez nią pozostają takie same na wszystkich platformach.

Biblioteka AUX zawiera tylko kilka funkcji przeznaczonych do obsługi okien, klawia­tury 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 przezna­czone 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 prezentu­je program shortest.c, zaś wynik jego działania został pokazany na rysunku 3.2.


0x01 graphic

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();



0x01 graphic

Tryby konsoli

Aplikacja konsoli to program Win32 działający w oknie trybu tekstowe­go. Taki program bardzo przypomina program DOS-a uruchomiony w Win­dows 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ą ograniczo­ne 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 (Grap­hics User Interface - graficzny interfejs użytkownika) mogą w razie potrzeby tworzyć okna konsoli. Biblioteka AUX umożliwia łatwe pisa­nie programów konsoli, zawierających funkcję main(), która tworzy po­mocnicze okno GUI dla rysunku tworzonego w OpenGL.



Aby zbudować ten program, musisz ustawić opcje kompilatora i linkera tak, aby po­wstała aplikacja konsoli Win32. Musisz dołączyć bibliotekę AUX, glaux.lib, oraz bi­bliotekę 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 nie­bieskim 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). Przej­rzyjmy 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 od­bywa 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 ko­munikatu 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ą komuni­kató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 tworze­nia okna. Zastosowane przez nas znaczniki nakazują użycie okna z pojedynczym bufo­rem (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 wy­sokość. 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 - prze­ciwnie 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



0x01 graphic

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 korzysta­ją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, poje­dynczym 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. Do­danie 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 zaini­cjowania 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ży­wany 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 miesza­nina 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 ewen­tualnie w danej palecie. Bliżej zajmiemy się tym w rozdziale 8.

Tabela 3.3 zawiera listę niektórych częściej wykorzystywanych kolorów oraz odpowia­dają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ży­wany 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świetlo­nego 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 po­wiedzieć o wiele więcej niż tylko o wartości alfa; w rozdziale 16 napiszemy przykłado­wy 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 po­siadają 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 ko­loró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 po­lecenia. 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 przy­spieszenia 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 na­ciś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 na­rysować. 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. Zmodyfikowa­ny 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 ry­sowania 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 znaj­dzie 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 wyczy­szczenia 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 wy­peł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 argu­menty 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 reprezen­tuje 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żytko­wnik 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 fun­kcja 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 śro­dku; w oknie większym te współrzędne wskazują lewy dolny róg okna (rysunek 3.5).

Rysunek 3.5.

0x01 graphic

250

Efekty zmiany rozmiaru okna

0x01 graphic

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 podo­bny, lecz większy rysunek. Widać to w większości programów rysunkowych, gdy zmniej­szasz 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 wyso­kość okna. Oś z biegnie prostopadle do obserwatora, przez co obiekty rysowane na pła­szczyź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 rzeczywi­stoś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, wi­dzimy 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 śro­dowiskach, biblioteka AUX dostarcza funkcji auxReshapeFunc(), która rejestruje fun­kcję 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 zmodyfiko­wania 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 przedsta­wia 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 zo­stanie wyśrodkowany w pionie, daleko na lewo od środka. Jeśli wydłużysz okno w pio­nie, 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



0x01 graphic

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 ekra­nowe, 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 obcina­nia, 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 prosto­kątny). Stosunek o wartości 0,5 oznacza że na każde dwa piksele w osi poziomej przy­pada jeden piksel w osi pionowej.

Jeśli widokowi zostanie przypisany stosunek współrzędnych różny od 1,0 i widok zo­stanie odwzorowany w prostokątną bryłę obcinania, spowoduje to, że obrazy będą znie­kształ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 przy­padku 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ą minimal­ną 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 prze­kształ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 te­go stopnia, że nasz prostokąt mógłby nawet nie ukazać się w oknie.


0x01 graphic

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

0x01 graphic

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ła­szczyzna 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 je­dnak, 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ść zmie­niałaby współrzędne obiektu i wywoływałaby funkcję RenderScene(), co powodowało­by, ż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. Wy­woł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. Mu­simy 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 kompute­rze. 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 bu­forowania. Ta funkcja umożliwia tworzenie rysunku w niewidocznym buforze, a na­stę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świe­tlić 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 prze­rzucona 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ą ani­mację, 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 po­dwó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 bu­fora. (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).


0x01 graphic

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 pro­stoką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.

0x01 graphic

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 modyfi­kowaniu przykładów połączyć kod z biblioteką glu32.1ib.

Podsumowanie

W tym rozdziale zostaliśmy wprowadzeni do narzędziowej biblioteki AUX oraz pozna­liś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 ele­menty i zastosowania biblioteki AUX. Przejrzyj także inne przykłady do rozdziału trze­ciego, 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ą t
ej 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 spo­czywa 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 Win­dows, potrafiące korzystać ze wszystkich elementów systemu operacyjnego. Dowiesz się, jaką charakterystykę musi posiadać okno Windows, aby mogło służyć do prezen­towania 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ójwy­miarowej grafiki, wymagana była jedynie podstawowa umiejętność programowania w C. Jednak od tego momentu zakładamy, że znasz przynajmniej podstawy pisania pro­gramó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 wy­mienionych w dodatku B, dotyczącym dalszej lektury związanej z tematem programo­wania 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 (Grap­hics 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ła­nia 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ę komunika­tów, a także funkcja WndProc, obsługująca komunikaty skierowane do głównego okna.

0x01 graphic

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 doty­czące tworzenia i wyświetlania okna, z tego programu omówimy więc jedynie kod od­powiedzialny 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 utworze­nia 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ą konte­kstó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 Win­dows, środowisko OpenGL jest osadzone w czymś, co nosi nazwę kontekstu rendero­wania. 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 przy­kł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 poje­dynczym 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 Open­GL są pośrednio powiązane z oknem przez bieżący kontekst renderowania.


0x01 graphic

0x01 graphic

Rysunek 4.2.

W jaki sposób polecenia OpenGL odnajdują swoje okno



0x01 graphic

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ą inter­akcji 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 rendero­wania 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 rendero­wania. Funkcja wglCreateContext() otrzymuje uchwyt kontekstu urządzenia GDI i zwraca uchwyt kontekstu renderowania OpenGL. Podobnie jak kontekst urządzenia GDI, kon­tekst 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 rendero­wania staje się bieżący (w wyniku wywołania funkcji wglMakeCurrent()), nie jest ko­nieczne podawanie kontekstu urządzenia użytego przy tworzeniu tego kontekstu renderowania. Jednak wskazany przy tym kontekst urządzenia musi mieć tę samą chara­kterystykę, co kontekst urządzenia wskazany podczas tworzenia kontekstu renderowa­nia. 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 ren­derowania albo przez wybranie innego kontekstu renderowania, albo przez wywołanie funkcji wglMakeCurrent z parametrem NULL w miejscu uchwytu kontekstu rende­rowania. (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 konte­kstu urządzenia, jak i kontekstu renderowania może wydawać się dość kłopotliwe, je­dnak 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 Win­dows 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ć nieu­waż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 animowa­na, 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ść cza­su polega na jednokrotnym uczynieniu kontekstu renderowania bieżącym i pozostawie­niu 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 konie­czności regularnego wywoływania funkcji wglMakeCurrent.

Jedynie dwa komunikaty okienkowe wymagająjakiegokolwiek kodu związanego z two­rzeniem 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 szkie­letowa 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 ro­boczy 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;



0x01 graphic

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 buforo­waniu. 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. Mu­sisz poznać jeszcze dwa ważne wstępne kroki, zanim będziesz mógł skorzystać z konte­kstu renderowania.

Style okien

Aby OpenGL mógł rysować w oknie, okno musi zostać utworzone z ustawionymi styla­mi 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 stru­kturę PIXELFORMATDESCRIPTOR zgodnie z charakterystyką i zachowaniem okna, jakie chcesz osiągnąć (poznamy je za chwilę). Następnie przekazujesz tę strukturę fun­kcji 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 wy­peł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 roz­dział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 przenie­sione 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. Ry­sunek 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 komunika­ty skierowane do okna.

0x01 graphic

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 po­niższy kod:

case WMJTIMER: { IdleFunctionO ;

IiwalidateRect (hWnd, NULL, FALSE) ;

}

break;

Funkcja IdleFunction jest identyczna z funkcją z przykładu BOUNCE2, z jednym wy­ją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 ob­sł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ą, do­wolną 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



0x01 graphic

Brakujący kod obsługi palety

Gdy porównasz zamieszczony w książce kod programu GLRECT z ko­dem 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 ob­sł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ć ko­loró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 wie­działo, w którym oknie ma wykonywać operacje graficzne. Wiesz już, jak wybór i usta­wienie 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 opi­sywanych w tym rozdziale oraz dodatkowe informacje o funkcjach jeszcze nie omawia­nych, 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 oprogramowa­nia 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 wy­stąpi błąd, musisz mieć jakiś sposób jego wychwycenia i wykrycia przyczyny. To je­dyna 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 rysu­jesz 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 ko­rzystać z takich sztuczek optymalizacji; funkcja TextOut działa zdecydowanie szybciej niż funkcja wglUseFontBitmaps. (Oczywiście, jeśli masz taką super-hiper kartę gra­ficzną, TextOut może nie być już najszybszym sposobem wyświetlania napisów). Pro­stym sposobem zabezpieczenia się przed tym rodzajem katastrof jest sprawdzenie wersji i producenta biblioteki OpenGL. Jeśli używana implementacja to ogólna imple­mentacja 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 produ­centa biblioteki, powinieneś sprawdzać w programie, czy numer wersji i producent zga­dzają 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);


0x01 graphic

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 zna­czniki. 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 zero­wana, 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 momen­tu 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 fun­kcją 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, zre­sztą 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 wie­dzieć, 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ży­cia 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. Dozwo­lone 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. Do­zwolone 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, zmodyfi­kowanej wersji programu odbijającego się kwadratu. Tym razem uzupełniliśmy pro­gram 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ą roz­szerzenia OpenGL umożliwiające zastosowanie specyficznych optymalizacji lub pe­wnych popularnych rozszerzeń, które jeszcze nie stały się częścią standardu. Te rozszerzenia mogą znacznie poprawić wydajność twojej aplikacji. Jeśli jednak korzy­stasz 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 przetwo­rzyć ten łańcuch w celu sprawdzenia obecności konkretnego rozszerzenia. Więcej infor­macji na temat rozszerzeń OpenGL znajdziesz w opisie funkcji wglGetProcAddress w rozdziale 4 lub w dokumentacji swojej implementacji OpenGL. Rozszerzenia w im­plementacji 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 implemen­tacji, 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 przy­kł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ące­go 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óbo­wać jakoś im zaradzić. Możemy także odczytywać informacje o producencie i numerze wersji biblioteki, dzięki czemu mamy możliwość korzystania ze specyficznych rozsze­rzeń 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 for­mat 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
pry
mitywów. ^^

0x01 graphic

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 kolej­nych 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 powinie­neś 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ę Chan­geSize, 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 wy­niku 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 ko­lejce nie będą oczekiwać inne komunikaty. (Więcej informacji na temat timera znaj­dziesz 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ę, przed­stawia 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 ina­czej. 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ę zaga­dnieniami przekształceń przestrzeni trójwymiarowej na dwuwymiarowy obraz na ekra­nie 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 manipulo­wać, 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ły­wanej 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



0x01 graphic

Dlaczego zaczynamy od tego?

Gdy spojrzysz na kod źródłowy w tym rozdziale, zauważysz kilka no­wych 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 po­zwalają 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 prze­strzeni, a w tym rozdziale chcemy się skupić tylko na tym. Dzięki te­mu, ż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 na­stępnym rozdziale dowiesz się, jak manipulować rysunkiem za pomo­cą 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 liczbo­wego, 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 parame­trów, określających współrzędną x, y oraz z.

Dwie inne formy funkcji glVertex wymagają podania dwóch i czterech parametrów. Mo­glibyś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 momen­cie napotkania instrukcji glBegin. Koniec interpretacji następuje w momencie napotka­nia 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ż poprze­dni 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();



0x01 graphic

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ść programi­stó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.

0x01 graphic

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 przy­kładowe programy nie byłyby interesujące. Także w pozostałych przy­kł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 trygo­nometryczne 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() wyma­gają 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ą śre­dnicę (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 war­toś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 wy­maga, 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.



0x01 graphic

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 sta­nu. 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 roz­dziale 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 dozwolo­nego 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;



0x01 graphic

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 rozmia­rem 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 sze­rokiego 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. Wspo­minamy 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 wska­zanego 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 na­stępny prymityw, GL_LINES. Poniższy krótki fragment kodu rysuje pojedynczy odci­nek 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 nieparzy­stą 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 progra­mu 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 ;



0x01 graphic

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 li­sty 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 łama­nej jest rysowany dodatkowy segment, zamykający całą łamaną. Używając tego prymi­tywu 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)


0x01 graphic

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, je­dnak może zajmować bardzo dużo czasu w przypadku większych i bardziej skompliko­wanych 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 li­stingu 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 sto­sowana 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 ( ) ;



0x01 graphic

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 wszy­stkie 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 naj­mniejszej 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ą do­zwoloną 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 po­chodzi z programu LINESW i rysuje dziesięć odcinków o różnych grubościach. Zaczy­na 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.

0x01 graphic

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 fun­kcji 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 prze­rywane. 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 rysowa­niu linii:

void glLineStipple(GLint factor, GLushort pattern);



0x01 graphic

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

0x01 graphic

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 li­nii 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.



0x01 graphic

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 przesu­wa 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ło­wieka) zajmowałaby zbyt dużo cennego czasu procesora.



Listing 6.7 przedstawia przykład użycia wzorca linii, składającego się z serii naprze­miennych 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.


0x01 graphic

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 naryso­wać 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 jednoli­tych 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 kra­wędzie. Do rysowania trójkątów służy prymityw GL_TRJANGLES; trójkąty są two­rzone 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


0x01 graphic

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 () ;

0x01 graphic

12.

tmitywu ILEŚ


! Zwróć uwagę, że trójkąty zostaną wypełnione bieżącym kolorem rysowania. Jeśli w pe­wnym momencie nie określisz koloru rysowania, nie będziesz wiedział, jaki kolor zo­stanie użyty (nie ma domyślnego koloru rysowania).


0x01 graphic

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 wie­rzchoł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 przy­padku, 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 kie­runkiem. 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 ru­chu wskazówek zegara (ang. counterclockwise winding, CCW), tak jak pokazano na rysunku 6.13.


164

Część II » Używanie OpenGL



0x01 graphic

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 tyl­nej 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 (roz­dział 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 powie­rzchni jednolitych obiektów. W następnych sekcjach, poświęconych jednolitym obie­ktom, 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 przeci­wnego 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 (przeci­wnego do ruchu wskazówek zegara) każdego trójkąta.


165

Rozdział 6. + Rysowanie w trzech wymiarach: linie, punkty i wielokąty



0x01 graphic

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 prze­mawiają 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 narysowa­nia 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 po­wierzchni dobrze jest korzystać właśnie z trójkątów, a nie z innych prymitywów.



0x01 graphic

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 zosta­je 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 techni­ki powiemy sobie w rozdziale 9.



Wachlarze trójkątów


0x01 graphic

0x01 graphic

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. Pry­mityw 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 pierw­szego 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 zega­ra, 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ój­ką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 mo­mencie spoglądamy dokładnie w dół osi z i widzimy jedynie koło utworzone przez wa­chlarz trójkątów. Poszczególne trójkąty zróżnicowano nadając im na przemian kolory czerwony i zielony.

0x01 graphic

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ć poszcze­gó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 wierz­choł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, po­przez 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.

0x01 graphic

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 po­siada 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 war­toś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 znaj­dować 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 ra­zem gdy przystępujemy do narysowania sceny. Bufor głębokości jest podobny do bu­fora 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 da­lej 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łębo­kości jest praktycznie nieodzowny jeśli chodzi o rysowanie jednolitych trójwymiaro­wych obiektów.

0x01 graphic

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 powierz­chnia 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łączo­nym (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.


0x01 graphic

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ój­ność 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 zreduko­wać 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).

0x01 graphic

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 zasa­dy wymagającej, aby wszystkie wielokąty na powierzchni obiektu miały ten sam kie­runek. 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.

0x01 graphic

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 przy­kł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ą ry­sowane jednolitym kolorem, możesz jednak zmienić to zachowanie nakazując, by wie­loką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. Po­niż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 sia­tka. (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

0x01 graphic

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ą je­dynymi 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 poku­sić 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 pry­mityw 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 wska­zówek zegara.


0x01 graphic

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 po­wstawania 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 nary­sowania 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.


0x01 graphic

Rysunek 6.25.

Proces tworzenia

wielokąta

GL POLYGON



0x01 graphic

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 narysowa­liśmy w rozdziale 3. Funkcja glRect() zapewnia łatwy i wygodny sposób rysowania prostokątów, bez konieczności odwoływania się do prymity­wu 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 mo­nochromatyczną 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ży­wanego przy przerywanych liniach, z tym że bufor ma rozmiar wystarczający do po­mieszczenia 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.


0x01 graphic

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



0x01 graphic

Przechowywanie pikseli

Jak dowiesz się w rozdziale 11, możesz modyfikować sposób inter­pretacji 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 repre­zentuje 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.



0x01 graphic

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ż opanu­jesz 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.

0x01 graphic

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).

0x01 graphic

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 pa­miętać o dwóch ważnych zasadach.

Po pierwsze, wszystkie wielokąty muszą być płaskie. Tzn. wszystkie wierzchołki wielo­kąta muszą leżeć na tej samej płaszczyźnie, jak przedstawiono na rysunku 6.29. Wie­lokat nie może być „wykręcony" ani „wygięty" w przestrzeni.


180

Część II » Używanie OpenGL



0x01 graphic

0x01 graphic

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ła­szczyznę. (Jeśli więc uda ci się narysować niewłaściwy trójkąt, oczywiście poza usta­wieniem 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 wie­lokąt musi być wielokątem wypukłym. „Wypukły" oznacza że, nie może posiadać ża­dnych 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 wielo­kąta więcej niż dwa razy, taki wielokąt nie jest wypukły. Przykłady poprawnych i nie­poprawnych wielokątów pokazuje rysunek 6.30.


0x01 graphic

0x01 graphic

Rysunek 6.30.

Dozwolone i niedozwolone wielokąty


Wielokąty dozwolone

Wielokąty niedozwolone



0x01 graphic

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 za­stosowania 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 na­wet 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 przy­kł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 kon­struowania wielokątów. Jednak gwiazda po prawej stronie jest skonstruowana z sześciu trójkątów, które są zupełnie dozwolone.


0x01 graphic

0x01 graphic

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łada­ją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. Usta­wiają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ć wido­czne). 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 zna­cznik zostanie ustawiony na wartość True, wszystkie następne wierzchołki będą defi­niował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 rysun­kach 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.


0x01 graphic

Rysunek 6.32a.

Program STAR z włączonymi wewnętrznymi krawędziami


183

Rozdział 6. « Rysowanie w trzech wymiarach: linie, punkty i wielokaty



0x01 graphic

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ójwy­miarową 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 dwu­wymiarowych 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ł eksperymento­wać, a jednocześnie, dzięki pewnej praktyce, łatwiej opanujesz dalszy materiał książki.



0x01 graphic

Czas na symulację czołgu/robota

Poczynając od tego rozdziału zaczynamy tworzenie symulatora czołgu/ro­bota, 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ą poru­szać 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 opi­sywanych 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 opi­sywanych 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 prze­suwamy układ współrzędnych, tak aby uzyskać pożądany widok). Możliwość umie­szczania i orientowania obiektów w scenie jest podstawową sprawą dla każdego progra­misty 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 prze­sunąć 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 modelwania, przechowywane przez OpenGL. Aby zapoznać cię z tymi macierzami, ten roz­dział 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óbo­wać cię oszukać, twierdząc, że „nauczysz się tworzyć trójwymiarową grafikę bez wszy­stkich 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 znajo­mości zaawansowanej matematyki, podobnie jak możesz każdego dnia prowadzić sa­mochó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 wie­dzieć, 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 dwuwy­miarowy 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 wierz­choł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 obserwa­tora 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 od­niesienia. 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.


0x01 graphic

0x01 graphic

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.

0x01 graphic

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 pun­ktu 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.


0x01 graphic

0x01 graphic

Rysunek 7.3.

Przekształcenia modelu

y---y

Przesunięcia (a)


0x01 graphic

Końcowy wygląd sceny lub obiektu zależy głównie od kolejności zastosowanych prze­kształ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 prze­kształ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


0x01 graphic

Rysunek 7.4.

Przekształcenia

modelu:

obrót/przesunięcie

oraz

przesunięcie/obrót


0x01 graphic


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 ni­mi 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 odnie­sienia-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 rzeczywi­stości są one tym samym, czyli przekształceniem widoku modelu.


0x01 graphic

0x01 graphic

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 za­stosowanym 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 de­finiuje 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 doku­mentacji 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 zmniejsza­nie 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 pun­kcie. Taki punkt nosi nazwę punktu zbiegu.

Zaletą rzutowania perspektywicznego jest to, że nie musisz sam decydować, które obie­kty 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 za­stosować 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.


0x01 graphic

0x01 graphic

0x01 graphic

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 pro­stych 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 odle­gł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 samo­chodu, 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ć znie­kształcone. W większości przypadków rzutowanie perspektywiczne jest najodpo­wiedniejsze.

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 odwzorowa­nie 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 prze­prowadzenia 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ść ele­mentów; także wszystkie kolumny muszą mieć jednakową wysokość, tj. zawierać je­dnakową 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 macie­rzami. Jeśli chcesz dowiedzieć się czegoś więcej o manipulowaniu macierzami i samo­dzielnym 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 modyfiko­wać 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ł tyl­ko 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


0x01 graphic

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 wyelimi­nowanie 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);


0x01 graphic

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 wska­zó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);


0x01 graphic

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);


0x01 graphic

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ś po­kusić 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);

0x01 graphic

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



0x01 graphic

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ścio­wej. 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 otrzymu­jemy 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 kolej­nego 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żyte­czne, 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.


0x01 graphic

Rysunek 7.16.

Działanie stosu macierzy


Stos macierzy


214 Część II » Używanie OpenGL



0x01 graphic

Stos macierzy tekstur

Stos macierzy tekstur jest jeszcze jednym stosem dostępnym dla pro­gramisty. 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 zbuduje­my 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 rzuto­wania 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").

0x01 graphic

Rysunek 7.17.

Działanie programu ATOM

Nasz program ATOM, korzystając z timera, przesuwa elektrony cztery razy w ciągu se­kundy (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 pun­ktu 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 pomo­cą funkcji glOrtho określając położenie bliższej, dalszej, lewej, prawej, górnej oraz dol­nej 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ówno­ległego, tak jak we wszystkich poprzednich przykładach.


0x01 graphic

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.


0x01 graphic

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 pu­dełko nie zwęża się wraz ze wzrostem odległości, nie jest to obraz zbliżony do rzeczy­wistego. Aby uzyskać perspektywę, zastosujemy rzutowanie perspektywiczne.


218

Część II * Używanie OpenGL



0x01 graphic

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 widze­nia jest różna od szerokości płaszczyzny przedniej. Tak więc obiekt położony przy prze­dniej 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 ostro­sł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.


0x01 graphic

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ła­szczyzny obcinania.


0x01 graphic

Rysunek 7.22.

Ostrosłup widzenia definiowany funkcją gluPerspective


Listing 7.2 przedstawia zmodyfikowany program rzutowania równoległego z poprze­dniego 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.


0x01 graphic

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

0x01 graphic

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 rzutowa­nia, 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, utrzymu­ją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ąda­ją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.


0x01 graphic

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. Zale­camy 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ą ma­cierz. 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 fun­kcję 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 ry­sunek 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ść „ „59 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 fun­kcję 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 szcze­gólne korzyści. Rzeczywistym powodem korzystania z możliwości wypełnienia macie­rzy 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 roz­dziale 9. Inne przekształcenia umożliwiają zawinięcie obiektu dookoła innego obiektu lub uzyskanie pewnych efektów soczewek. Informacje o tych zaawansowanych zastoso­waniach znajdziesz w dodatku B.

Podsumowanie

W tym rozdziale poznałeś koncepcje mające podstawowe znaczenie dla tworzenia trój­wymiarowych 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 prze­kształ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ójwy­miarowym świecie i oglądanie rozmieszczonych w nim obiektów. Jeśli przeanalizujesz kod tego programu, lepiej poznasz sposób wykorzystania rzutowania perspektywiczne­go 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 szkie­letowych 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 apli­kacji 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że­nie, wydajność, cena i pewność działania także się liczą, ale powiedzmy sobie szcze­rze - 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 progra­mować 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 ro­zejrzyj 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 postrzega­my? 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 prze­strzeni, 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.


0x01 graphic

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.


0x01 graphic

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ą kolo­rami. 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 kolo­rami. 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 bombardo­wany „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 mate­riały. Ilustruje to rysunek 8.3.

0x01 graphic

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 ko­lor. 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 auto­matycznie 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 czerwo­nego, 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 pro­cent 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

0x01 graphic

• ••..••:•-, ^•••-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 gene­rować 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 strumie­nia 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

0x01 graphic

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 pomo­cą 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 dotychcza­sowych przykładach. Przy rysowaniu w tym trybie można precyzyjnie określić kolor przez podanie intensywności jego trzech składowych (czerwonej, zielo­nej 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 kompute­rze 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 stu­diach 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 pun­któ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 apli­kacji, 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 grafi­czne, a nawet grafika 3D w komputerach PC stały się nie tylko dostępne, ale także uza­sadnione. 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 milio­nów dostępnych kolorów. W tym momencie otwarły się podwoje dla wszelkiego rodza­ju 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 li­dera na rynku kart graficznych do komputerów osobistych. Inni producenci zaczęli twrzyć 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. Obe­cne 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 ty­się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 robo­czej 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, gra­fika 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 Win­dows. 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 dyspozy­cji 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 zna­czny 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 satysfakcjonu­ją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 odcie­ni 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ą zoptymali­zowane 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ę wy­dajność 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 obe­cnych, 32-bitowych procesorach Intela, dostęp do adresów pamięci ułożonych na gra­nicy 32 bitów odbywa się znacznie szybciej niż w innych przypadkach.

W celu bardziej efektywnego wykorzystania pamięci często stosuje się dwa inne popu­larne 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 do­stę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 obra­zó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-bi­towym 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łado­wych. Sprzęt lub sterownik urządzenia pobierają tę 24-bitową wartość i przed ustawie­niem 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 doty­czą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łado­wych, 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 efe­kcie 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łado­wych (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 po­wierzchni 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).


0x01 graphic

Rysunek 8.7.

Przestrzeń kolorów RGB

Żółć (255,255,0)

Cyjan (0,255,255)

Czerwony

Fiolet (255,0,255)

(0,0,255) Niebieski

Niebieski


0x01 graphic

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łado­wy 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 zaj­mują 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 argu­mentó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 roz­dziale 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 intensy­wności. Jeśli jednak przyzwyczaiłeś się do programowania w Windows, może być ci ła­twiej 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ży­cie 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 istnie­ją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żące­go 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ży­wany 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 prze­strzeni kolorów RGB (rysunek 8.7) mogą zostać połączone linią prostą.

Płynne (ang. smooth) cieniowanie powoduje zmianę koloru odcinka w miarę przecho­dzenia 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 od­cinka odpowiadają kolorom leżącym wzdłuż odcinka łączącego wierzchołki kostki ko­loró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.


0x01 graphic

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 kolo­ró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.

0x01 graphic

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 zape­wnienia 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 we­wną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 wierz­choł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 repre­zentację płaszczyzny przedstawionej graficznie na rysunku 8.10.


0x01 graphic

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 bar­dziej 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 przed­stawiają wynik działania przykładowego programu TRIANGLE odpowiednio przy 16 i przy 16 milionach kolorów. Mimo że rysunki nie są wydrukowane w kolorze, widzi­my, że cieniowanie drugiego trójkąta jest znacznie płynniejsze.


0x01 graphic

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 omawia­nymi 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 nie­bieskiej; 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 ste­rownika 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 jedno­stki (5 bitów) lub 64 jednostki (6 bitów) w danym kierunku. Sterownik urządzenia do­pasowuje 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.

0x01 graphic

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 ko­lory 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żo­ny 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 ko­lorach, 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 wza­jemne 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 pa­lecie. 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óbo­waliś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 zna­cznej poprawie jakości grafiki komputerowej. Gdy Windows działa w trybie umożliwia­ją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 dys­pozycji względnie szeroki zakres kolorów, zaś podczas wybierania koloru zostałby wy­brany 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 do­branie 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 modelowa­nia 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 przypad­ku 256-kolorowych bitmap, format .BMP w Windows posiada nawet własną paletę ko­loró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 wszy­stkie 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 efe­kcie 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 apli­kację, 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 zreali­zować 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ć reali­zowana 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 pa­lety, 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 odzwier­ciedlającej oryginalne kolory obrazu. Jednak w przypadku ogólnych aplikacji OpenGL, przydatniejsze będzie wybranie palety kolorów zapewniającej najbardziej ogólne roz­mieszczenie 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 sce­nie 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 prze­kazywana 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 za­awansowane 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 roz­prowadzone w obrębie kostki kolorów, ale musi także być ułożone w odpowiedniej ko­lejnoś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ę inde­ksem do naszej palety. Intensywności czerwieni od O do 7 w palecie 3-3-2 muszą odpo­wiadać 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 zade­monstrowania 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 pa­letę 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, zie­leni 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 wy­korzystuje 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ądze­nia, 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 bie­rzemy 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 systemo­wej. 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 odzwiercie­dlają 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 ope­racji 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 fun­kcje 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 od­cieni szarości, możesz je uzyskać. Jednak w trybie indeksu kolorów nie są dostępne pe­wne 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 pa­lety, 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 spe­cjalnych.


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 ob­szaró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 nie­bieskim).

Oprócz ograniczeń co do wyboru kolorów, w trybie indeksu kolorów nie można korzy­stać 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 ko­rzystać 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 odcie­ni 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 try­bie 8-bitowym, przy użyciu palety 3-3-2, nie byłoby możliwe.

0x01 graphic

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 cie­niowanie także w trybie 8-bitowym. Kod użyty do utworzenia tej palety został przedsta­wiony 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 ko­loru 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 przedstawio­nego 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 ko­lorów 4-, 8-, 16- i 24-bitowych. Zademonstrowaliśmy budowanie palety 3-3-2, używa­nej 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ójwy­miarowej 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 pro­gramó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 do­brą 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 rze­czywistym świecie nie liczy się tylko kolor obiektu, jaki zaczęliśmy określać w roz­dziale 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 ja­snym 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ąza­nych 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, opar­tego 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ój­ką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.

0x01 graphic

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 obie­ktu. Przypuśćmy, że stworzyłeś model tego samolotu i pomalowałeś każdą powierz­chnię 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 rzeczy­wistego świata. Dopóki obiekt nie emituje własnego źródła światła, w OpenGL jest ilu­minowany 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.


0x01 graphic

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.

0x01 graphic

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 odbija­ne 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

0x01 graphic

Złóżmy to razem

Żadne źródło światła nie jest złożone wyłącznie z jednego z tych trzech rodzajów świa­tła. Składa się raczej z różnych intensywności każdego z rodzajów. Na przykład, czer­wone ś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 rozpra­szają 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 ża­dnego 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ć opi­sane 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ły­skó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 obe­cności cząsteczek dymu i kurzu - skieruje troszkę światła na wszystkie obiekty w po­mieszczeniu. 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 obie­kty 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 ob­serwatora. 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 świa­tłem, obserwatorowi będzie wydawała się czarna, ponieważ całe światło zostanie po­chł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 refleksyj­nych. 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 czer­wona, ale tym razem musimy określić także właściwości refleksyjne materiału w odnie­sieniu 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 absorbo­wać 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 nie­co praktyki. Nie ma tu kostek kolorów ani prostych reguł dających szybkie i proste odpo­wiedzi. 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 wa­runkiem, ż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 katego­riach 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 war­toś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 ota­czają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

0x01 graphic

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 ota­czają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 mate­riał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 od­błysków. Ogólny efekt jako wartość RGB jest obliczany tak samo jak w przypadku świa­tł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, wszy­stkie 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 obci­nana 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 za­cznijmy 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 powie­rzchni 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 wierz­choł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ła­dów opartych na programie JET, a przekonasz się, że w każdym z nich, tuż po przygo­towaniu 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

0x01 graphic

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ą fun­kcji 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ży­cie 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 - ta­blicę 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 fra­gment 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 prze­dniej (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. Je­dnak 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, zwa­nej ś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 otacza­jące o połowę, otrzymamy obraz pokazany na rysunku 9.8. Osiągamy to przez ustawie­nie następujących wartości RGBA światła otaczającego:

GLfloat ąmbientLightn = { 0.5f, 0.5f, 0.5f, l.Of };

0x01 graphic

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);



0x01 graphic

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ńczo­nej 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ć świa­tło punktowe, z zadanym stożkiem światła, jak również manipulować innymi charakte­rystykami ś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 bie­gnące od źródła (każdego poza czystym światłem otaczającym) będą uderzać pod pe­wnym 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 ^\ »£""

0x01 graphic

Z punktu widzenia programisty, ukazuje to pewną niedogodność koncepcji. Każdy wie­loką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 nazy­wana 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ę pro­stopadłą 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

0x01 graphic

. ... '' 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 pier­wszych 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 po­szarpanej 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ła­szczyznę 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 wy­brany 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 po­czą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.


0x01 graphic

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 we­ktory 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 wska­zujący w kierunku prostopadłym do tego trójkąta. W tym przykładzie normalne wszy­stkich 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.



0x01 graphic

Kierunek wielokątów

Zwróć szczególną uwagę na kolejność wierzchołków w trójkącie samo­lotu. 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 przecwnym do ruchu wskazówek zegara. Właśnie to nazywa się kierunkiem wielokąta. Domyślnie, przednia strona wielokąta jest zdefiniowana ja­ko strona, od której wierzchołki wydają się ułożone w kierunku przeci­wnym 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 pro­stu 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 kie­runku, 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 nor­malnych dla dowolnego wielokąta w przestrzeni 3D.


0x01 graphic

Rysunek 9.13.

Niebanalne

wyznaczanie

normalnej

Normalna


Możesz łatwo obliczyć wektor normalny dla każdego wielokąta składającego się z przy­najmniej 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 we­ktoró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 we­ktorowego wektorów VI i V2.

0x01 graphic

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 wekto­ró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 zacho­waniem 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 przygotowaw­czego 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łado­wymi 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 świa­tłem pod coraz to innymi kątami.


0x01 graphic

Rysunek 9.16.

Działanie programu LITJET



0x01 graphic

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 prze­chowanie 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. Pa­miętaj, przykłady w tym rozdziale mają na celu zademonstrowanie kon­cepcji, niekoniecznie muszą stanowić najefektywniejszy możliwy kod.

Efekty oświetlenia

Światło otaczające i rozpraszające w przykładzie LITJET wystarczy jedynie do zapewnie­nia 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 sa­molotu. 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 mo­mencie, 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 od­bijane 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 świa­tł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ści­woś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łado­wej 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 rozpra­szają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 ustawia­nym 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ę, od­bijają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 mate­riały. Uczyniliśmy to celowo, aby wszystkie wielokąty samolotu wyglądały, jakby zo­stał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, eks­tremalnie 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 ta­kim 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 parame­tru 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 pro­gramu LITJET (oczywiście poza nazwą programu). W tym programie samolot wydaje się bardzo połyskliwy. Rysunek 9.17 przedstawia wynik działania tego programu, je­dnak aby móc w pełni docenić efekt, powinieneś uruchomić program i przytrzymać je­den 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 ) ;



0x01 graphic

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 nor­malnych, 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



0x01 graphic

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ą nor­malnych poszczególnych płaszczyzn. Wspólny wierzchołek dwóch stykających się pła­szczyzn (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.


0x01 graphic

0x01 graphic

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). Powie­rzchnia 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 kra­wę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



0x01 graphic

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 zmie­nić 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 powie­rzchnia 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 wy­nosi 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.

0x01 graphic

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 ja­kimś 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 pod­stawy 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łuje­my 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.

0x01 graphic

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łado­wy program pochodzi z rozdziału 2). Kostka z rysunku 9.24b z cieniem wygląda o wie­le bardziej wiarygodnie.


303

Rozdział 9. * Oświetlenie i źródła światła



0x01 graphic

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ła­szczają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.


0x01 graphic

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 po­wietrzu, wysoko nad ziemią. Umieścimy źródło światła ponad nim i nieco z lewej stro­ny. 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.


0x01 graphic

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 sa­molot 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. Na­stępnie ponownie rysujemy samolot (zmodyfikowaliśmy kod, tak aby korzystał ze zna­cznika 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 pe­wnych 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 pojedyn­czego 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 prakty­cznych aplikacjach.

Ogólnie, większość publikacji dotyczących OpenGL zaleca unikanie trybu indeksu ko­loru 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 manipulo­wanie ś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. Pro­gramy demonstracyjne w tym rozdziale są całkiem proste, ale więcej przykładów znaj­dziesz 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.

0x01 graphic

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ść, szero­kość 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 przy­kł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 do­dano 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 na­szego 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świe­tlania ś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. Zacho­wujemy 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 ele­ment 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 pe­wne 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 skomplikowa­nych obiektów.

Zdecydowaliśmy się podzielić śrubę na trzy części: główkę, trzpień i gwint. Takie po­dejś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 skomplikowa­nych 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. Mo­glibyśmy użyć prymitywów GL_QUAD i GL_POLYGON, aby rysować przy jak naj­mniejszej 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.


0x01 graphic

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.


0x01 graphic

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 roz­kł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 ry­sunek 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ą po­wierzchnię, 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 przed­stawia rysunek 10.5.

0x01 graphic

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ła­szczyzn 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.


0x01 graphic

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.


0x01 graphic

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. Wszy­stko, co musimy zrobić, to ułożyć części w odpowiednim położeniu, zaś bufor głębo­koś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.

0x01 graphic

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 wymode­lować. Składając się z ponad 1700 trójkątów, jest to jak dotąd najbardziej skompliko­wany 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 akcelerato­ró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ń, potrze­bujemy 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 pro­gram 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 komu­nikat 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 powo­dó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 zu­pełnie poprawny test czasu.



0x01 graphic

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 progra­my (a także dwa zamieszczone tutaj), na czas trwania testu powi­nieneś przynajmniej zakończyć działanie wszystkich innych programów w systemie. Na szybkość renderowania ma wpływ wiele różnych czyn­ników, ale jeśli warunki dla obu programów będą mniej więcej jedna­kowe, powinieneś zauważyć różnicę pomiędzy obiema wersjami.



Być może kusi cię zebranie po prostu garści wywołań funkcji RenderScene i zmierze­nie, jak długo będą wykonywane. To działa, jednak zamknięcie aplikacji stawia się bar­dzo 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 nie­skończoność będą posyłały aplikacji komunikat WM_PAINT. W strumieniu tych ko­munikató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 przedsta­wia 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°.

0x01 graphic

Rysunek 10.9.

Działanie programu SPINBOLT


340

Część II » Używanie OpenGL



Funkcja ulGetProfileTime po prostu zwraca ilość tyknięć zegara systemowego i zamie­nia 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 przedsta­wione 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 oblicze­niach 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świe­tlania 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 je­dynie ich wyniki, przekazywane funkcjom OpenGL. Już z tego powinieneś się zorien­tować, dlaczego listy wyświetlania są tak dobrym pomysłem.



0x01 graphic

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 przypad­ków mają trudności z „wykryciem" wzrostu wydajności oprogramowa­nia 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 po­mysł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 odpowie­dnim 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ą wszy­stkie 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 ;



0x01 graphic

Rysunek 10.10.

Wykorzystujemy listy wyświetlania -program SLSTBOLT


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 na­szego nowego i poprawionego programu. Tym razem do wykonania całego obrotu po­trzeba 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.



0x01 graphic

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 zau­ważyć różnicę w działaniu!



Podsumowanie

Wykorzystywaliśmy ten rozdział do pokazania, w jaki sposób tworzy się złożone trój­wymiarowe 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, poten­cjalnie ponownie wykorzystywalne części, możesz oszczędzić czas renderowania sceny tworząc listy wyświetlania. W sekcji podręcznika znajdziesz więcej funkcji przeznaczo­nych do wykorzystania, wyświetlania i zarządzania listami. Oprócz tego, na końcu roz­dział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ójwymiaro­wą 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ójwymiaro­wych 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 ekra­nie znaków lub symboli (na przykład ikon). Różni się to od (błędnej) definicji stosowa­nej 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 usta­lany 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śmie­chnięte buźki. Kod (listing 11.1) służący do stworzenia takiego obrazu składa się z da­nych bitmapy, po których następuje wywołanie funkcji glBitmap.


0x01 graphic

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śmie­chnię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.



0x01 graphic

Parę słów na temat bitmap

Bitmapy OpenGL zwykle zdefiniowane „do góry nogami". To zna­czy, 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ą wy­sokość. 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 prze­ciwnym wypadku.


354

Część II » Używanie OpenGL



0x01 graphic

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 gra­nic 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 wska­zuje bitmapę przeznaczoną do narysowania, wyrównaną do granicy 32 bitów. Parame­try 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 pozy­cji 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.



0x01 graphic

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 ob­ciętych granicą bieżącego widoku. Na przykład, oto jak możesz nary­sować 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 le­wo (-4,0) przed narysowaniem rzeczywistej bitmapy w następnym wy­woł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 po­mocą 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 two­rzenia 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ść, pochyle­nie) 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 zna­ków międzynarodowych i znaków diakrytycznych, użyj zamiast niego zestawu znaków UNICODE (UNICODE_CHARSET). Pewne czcionki zawierają zestawy znaków spe­cjalnych. 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 usu­wania 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 przy­padku funkcji FontCreateBitmaps, aby umożliwić działanie z międzynarodowym zesta­wem 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



0x01 graphic

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 znaj­dą 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 poprze­dniej uwadze, a także poprzez wywołanie funkcji glGetlntegen/ (opisywa­nej 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 fun­kcji 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 indekso­wanym 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ść mini­malna 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).

0x01 graphic

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 prze­glą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 przy­kład, aby wyświetlić centralny obszar o rozmiarze 300 x 300 pikseli z obrazu o rozmia­rze 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ść orygi­nalnego 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 obra­zu. Pomijamy pierwsze (640 - 300) / 2, czyli 170 pikseli z lewej strony, w celu wyświe­tlenia ś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.



0x01 graphic

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 wido­ku, jak pokazano poniżej, za pomocą funkcji glGetlntegery. (Ta funkcja zostanie opisa­na 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 od­czytania 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. Parame­try width i height określają rozmiar kopiowanego obrazu. Piksele są kopiowane z poda­nej 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 na­pisać program OpenGL będący przeglądarką plików .BMP w Windows. Zadania nasze­go 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 fol­derze 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 stru­ktury 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 bitma­py w pamięci i wszystko razem zapiszemy na dysk. Funkcję SaveDIBitmap zawiera li­sting 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 stan­dardowych usług druku GDI.

Pierwszą rzeczą, jaką musimy zrobić, jest wyświetlenie standardowego okna dialogo­wego 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 konte­kstu 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 druko­wanej 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 pli­ku .BMP. Podobnie jak większość programów OpenGL, także ten zaczyna się od usta­lenia 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 zachowa­niem 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). Fun­kcje 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ła­danie obrazów na wielokąty w scenie. O tym, jak te obrazy zostaną nałożone, decydu­jesz 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 standardo­wych 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 mapo­wanie tekstur, procesor nie musi zajmować się obliczeniami związanymi z przygotowa­niem i nakładaniem obrazów - wszystkim zajmuje się karta graficzna.

Przykłady w tym rozdziale działaj ą na wszystkich kartach graficznych zgodnych z Win­dows. 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 szero­koś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 jednowymia­rową 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.

0x01 graphic

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 przy­kł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 wy­miaru. 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


0x01 graphic

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ć te­ksturę. Obrazy tekstur są przechowywane tak samo jak bitmapy (omawiane w rozdziale 11 .)•



0x01 graphic

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 (opisywa­nych 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 ram­ki) 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 ze­brane 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 do­datkowo 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);



0x01 graphic

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ń grafi­cznych, 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 pa­mięci karty graficznej.



Rysowanie wielokątów z nałożoną teksturą

Gdy już zdefiniujesz teksturę, musisz jeszcze włączyć teksturowanie. Aby włączyć te­ksturowanie 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 te­ksturowanie 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 ja­sność 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 war­tość 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.


0x01 graphic

0x01 graphic

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 kafel­ka idealnie pokrywały się z krawędziami następnego.



0x01 graphic

Automatyczne generowanie współrzędnych tekstury

Generowanie współrzędnych tekstur może być bardzo nużącym zada­niem. W dalszej części rozdziału poznasz funkcję glTexGen, automaty­cznie generującą współrzędne tekstur.



Mipmapy

Dotychczas posługiwaliśmy się jedynie obrazami z pojedynczymi teksturami. To zna­czy, 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 spacero­wania 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 sto­sowaniu 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 wi­zualne robią wrażenie. Dodatkowo, mipmapy mogą poprawić wydajność renderowania, gdyż zmniejszają konieczność stosowania filtru GL_LINEAR.



0x01 graphic

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ła­du, 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 po­ziomie 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 wyso­kiej 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 jedne­go, 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 rozdziel­czoś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ą po­drę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, zawiera­ją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



0x01 graphic

Rysunek 12.4.

Okno edycji terenu



Serce interfejsu rysowania znajduje się w funkcji DrawTerrain. Używamy w niej mecha­nizmu 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 zo­staną 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 pun­któ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 prze­lotu 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 ko­loru 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 ko­lorem 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 przed­stawia rysunek 12.6.


Rozdział 12. » Mapowanie tekstur_________________________________401

0x01 graphic

Rysunek 12.6.

Algorytm rysowania terenu

Jak widać, algorytm nie śledzi terenu dokładnie, jednak jest szybki i prosty w imple­mentacji. 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ży­wany 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ć generowa­nie 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 je­dnowymiarowych obrazów tekstur. Dla obrazu ID współrzędną S wygenerujesz najpra­wdopodobniej 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 try­bie 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 przesu­wa 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 przy­kł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 wa­runkowych 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ć wer­sję z teksturowaniem lub bez, przynajmniej do czasu aż sprzętowe akceleratory 3D sta­ną 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 pa­skami 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 nor­malnych 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śla­ją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ę we­wną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 tekstu­ry 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 te­kstur 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!

0x01 graphic

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 pod­stawie. 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 pa­rametr topRadius lub bottomRadius.

Teksturowanie cylindrów

Podczas teksturowania cylindra, obraz tekstury jest na nim zawijany począwszy od prze­dniej 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 pro­jektu 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 je­dnolity 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.

0x01 graphic

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ź te­kstury pokrywa się z lewą krawędzią dysku itd.

Rysowanie częściowych dysków

Biblioteka GLU zawiera funkcję przeznaczoną do rysowania części dysku. Podczas ry­sowania takiego obiektu podaje się kąt początkowy oraz kąt zataczany przez powie­rzchnię 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łudni­ków, zaś parametr stacks - ilość równoleżników (rysunek 13.3).


440

Część II * Używanie OpenGL



0x01 graphic

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 obraca­ją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.


0x01 graphic

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. Nie­stety, 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


0x01 graphic

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 naryso­waniem 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 Open­GL 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 opisy­wane 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 omawia­nia 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 rozbi­jania 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

0x01 graphic

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 kon­tekst 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 roz­dział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 po­jedynczy 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. In­ne, 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 ma­cierzy, 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 za­chować 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 we­wną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 (zmien­na 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. Po­dobnie jak w przypadku bufora głębokości, można bardzo łatwo sterować jego działa­niem. 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ą najli­czniejsze. 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 mini­mum, 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 zmien­ne stanu teksturowania zawiera tabela 14.4.

Aby zachować bieżące parametry teksturowania, wywołaj funkcję glPushAttrib z para­metrami GL_TEXTUR£_BIT oraz GL_EVAL_BIT. Gdy włączasz teksturowanie, pa­mię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 zmien­nych 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 posia­da 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, wykorzy­stywane nie tylko przy podwójnie buforowanej animacji i podczas usuwania niewido­cznych 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 bufo­rze przechowywane są dane innego rodzaju i z innego zakresu. Spójrz na rysunek 15.1.


0x01 graphic

Rysunek 15.1.

Organizacja buforów w OpenGL


Konfigurowanie buforów

Zanim użyjesz OpenGL, musisz skonfigurować kontekst urządzenia okna (HDC), przy­gotowują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 zwy­kle 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 niszcze­nia zawartości głównego planu. Ogólna implementacja OpenGL w Windows nie obsłu­guje pomocniczych planów rysunkowych.

Pole iPixelType określa sposób reprezentowania wartości kolorów i może zawierać je­dną 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, Win­dows 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 cColor­Bits określa łączną ilość bitów czerwieni, zieleni i niebieskiego. Obecna ogólna imple­mentacja 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 wyma­ganych. Przy powrocie, pole dwFlags może zawierać dodatkowe znaczniki, na które po­winieneś 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ć pale­tę 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ć in­deks 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 kolo­ru na ekranie. Ogólna implementacja OpenGL Microsoftu nie obsługuje obecnie składo­wej 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ży­wany 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 spo­só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 wybie­rzesz 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 bufo­rowania oraz dwa dodatkowe bufory w trybie podwójnego buforowania, w celu prze­chowania 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ójwy­miarowe 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 fun­kcji 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 za­wartoś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 obcina­nia, 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 po­wierzchni. Usuwanie niewidocznych powierzchni jest procesem, który w rzeczywistym świecie odbywa się w sposób naturalny; gdy nieprzezroczysty obiekt zostanie umie­szczony przed innym obiektem, obiekt bliższy zasłania część lub całość obiektu leżące­go 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ówny­wana 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 war­tość 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



0x01 graphic

Rysunek 15.2a.

Typowe działanie bufora głębokości przy porównaniach GL LESS



0x01 graphic

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 konie­czna 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 po­wierzchni na ekranie.

W niektórych porównaniach głębokości potrzebna jest inna początkowa wartość głębo­koś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 przypad­ku 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 powie­rzchni. Ten rodzaj aplikacji zaprezentowano na listingu 15.1. Kluczem do tego progra­mu jest użycie funkcji glDepthFunc i glClearDepth:

glDepthFunc(depth_func);

Używamy tu zmiennej globalnej przechowującej bieżącą funkcję porównywania głębo­koś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 ko­loró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 sterowa­nie paletą kolorów i zakresem wartości. Możesz użyć wartości kolorów RGBA do po­prawienia 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 po­kazania wewnętrznego działania, którego normalnie nie moglibyśmy zobaczyć. Przy­kł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 fra­gmentu 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 najpro­stszym 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 momen­cie 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 ryso­wanie 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 zebra­ne 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 ry­sowania 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 po­trzeby 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 przy­kł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 roz­mycie obrazu.

Renderowanie rozmycia ruchu jest czymś bardziej skomplikowanym niż po prostu na­rysowanie sekwencji klatek, w której kamera zmienia położenie pomiędzy kolejnymi klatkami. Rozmycie ruchu dostrzegamy wtedy, gdy obiekt porusza się szybciej, niż mo­gą 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 pada­jące na film naświetla go przez pewien określony czas. W zależności od aparatu i foto­grafa, 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. Podsta­wową techniką jest poruszenie obrazu po pół piksela w różnych kierunkach, w celu roz­mycia 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 rzeczy­wiś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 anty­aliasingu 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 prze­zroczystoś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 wyko­rzystywania 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 łącze­nia 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 sta­nowi 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łko­wicie 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 prze­chowują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.

0x01 graphic

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 herba­ty 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óco­nych 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ć naryso­wana 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 defi­niuje, ile koloru pędzla pozostanie na rysunku (rysunek 16.2).

0x01 graphic

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 na­kł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 zmie­ni 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 uzy­skania 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 wierz­choł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 odpowia­dał kolorowi tła (czerni na rysunku 16.3). W ten sposób mgła będzie wyglądać „po­prawnie" 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

0x01 graphic

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łębo­kości. Wszystkimi operacjami graficznymi zajmuje się funkcja draw_scene, rozpoczy­nają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 ob­serwatora. 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 na­dawał mu wartość poniżej O, l. Wpływ tego parametru na mieszanie koloru mgły przed­stawia 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 ob­szaró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 tere­nu 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

0x01 graphic

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 na­rzę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 przypomi­nają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 je
dynie


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świe­tlenie - 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 oblicza­jący powierzchnię na podstawie równań, dających lepsze lub gorsze przybliżenie gład­kiej powierzchni paskami czworokątów lub trójkątów.

Przypuśćmy jednak, że chcesz utworzyć krzywą lub powierzchnię, lecz nie masz do dy­spozycji 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 mate­matycznych 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 skomplikowa­na 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 posia­da 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. Za­nim 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 wszy­stkich 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 parametry­czne. 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. Parame­tryczną 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ą parame­try równania, a nie same wartości współrzędnych.


0x01 graphic

0x01 graphic

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ą jedno­cześnie pierwszym i ostatnim punktem samej krzywej. Pozostałe punkty kontrolne za­chowują 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.


0x01 graphic

0x01 graphic

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 naj­wyższemu wykładnikowi zmiennej parametrycznej. Jeśli chcesz lepiej poznać matema­tyczne 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 naj­mniejszych 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 za­chowana 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

0x01 graphic

0x01 graphic

Ciągłość kawałków

C°-Bralc ciągłości

C -Gąglość szczepna

krzywej


0x01 graphic

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 zoba­czysz, 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 parametry­cznych u i v. Następnie, przez wywołanie odpowiedniej funkcji wyliczającej, generowane są punkty tworzące krzywą lub powierzchnię. Zaczniemy od utworzenia dwuwymiaro­wej 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. Li­sting 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 obli­czają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 jedno­stek 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óżnie­niu 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 po­drę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 po­mię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 usta­wić 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 pierwsze­go elementu tablicy. Po utworzeniu odwzorowania krzywej możemy włączyć funkcję obliczającą dla tego odwzorowania. Osiągamy to przez włączenie odpowiedniej zmien­nej stanu; oto wywołanie potrzebne do włączenia funkcji obliczającej generującej pun­kty wzdłuż krzywej:

0x01 graphic

// 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 parame­trycznej 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ę pun­któw za pomocą funkcji glMapGrid, służącej do utworzenia równomiernej siatki pun­któ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 dwu­wymiarowej krzywej. Oprócz zdefiniowania punktów w dziedzinie u, musimy zdefinio­wać 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 poprze­dniego przykładu jest zdefiniowanie trzech dodatkowych zestawów punktów kontrol­nych 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 spo­só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 dzie­dziny u składa się z trzech punktów po trzy wartości każdy (3x3= 9). Następnie infor­mujemy 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 pun­któw kontrolnych.

Dwuwymiarowa funkcja obliczająca jest włączana podobnie jak funkcja jednowy­miarowa, 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

0x01 graphic

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 nor­malnych. 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ą powierz­chnię, co rysunek 17.5, z tym że włączono oświetlenie i automatyczne generowanie nor­malnych do powierzchni. Kod tego programu znajduje się w folderze BEZLIT na płytce CD-ROM. Program stanowi jedynie nieznaczną modyfikację programu BEZ3D.


0x01 graphic

Rysunek 17.6.

Wynik działania programu BEZLIT


0x01 graphic


Rozdział 17. * Krzywe i powierzchnie: co to jest NURBS?!!___________________545

NURBS

Funkcji obliczających powierzchnie Beziera możesz używać do woli, jednak w przy­padku 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 po­prawnej 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 (nieje­dnorodne 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ę in­nych 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 krzy­wych 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,_

0x01 graphic

0x01 graphic

• ^^^ 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 segmen­tó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 charakte­rystykę 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 war­toś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


0x01 graphic

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 powierz­chni. 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 fun­kcji związanych z powierzchniami NURBS w celu zmodyfikowania wyglądu krzywej lub powierzchni.

Funkcja gluNewNurbsRenderer tworzy renderera dla powierzchni NURBS. Do usuwa­nia 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 usta­wione 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łnie­nia (jedna wypełniona, druga w postaci siatki).

Używając punktów kontrolnych i wartości węzłów pokazanych w następnym fragmen­cie kodu, tworzymy pjdwierzchnię NURBS przedstawioną na rysunku 17.9. Ten pro­gram, noszący nazwę NtTteBS, znajduje się na płytce CD-ROM dołączonej do książki.

0x01 graphic

» j

Rysunek 17.9.

Działanie programu NURBS


0x01 graphic



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ł przedsta­wiony na rysunku 17.10.

Rysunek 17.10.

Wynik działania programu NURBT

0x01 graphic

Widzimy na nim tę samą powierzchnię, co w poprzednim przykładzie (bez narysowa­nych 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 krzy­wej 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 sto­sunku do kierunku krzywej. Tak więc w krzywych zgodnych z ruchem wskazówek ze­gara 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 kierun­ku zgodnym z ruchem wskazówek zegara. Tę zależność ilustruje rysunek 17.1 1.

0x01 graphic

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 po­wierzchniami 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ą elasty­czność 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 powierz­chnie. Podział wielokątów polega na tworzeniu dowolnego wielokąta z mniejszych, naj­częś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 wielo­ką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 prze­prowadzona 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

0x01 graphic

Rysunek 18.1. Wielokąty proste Wielokąty złożone
Wielokąty proste
i złożone

0x01 graphic

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łu­go, dobrym pomysłem jest umieszczenie podzielonych wielokątów na liście wyświetla­nia 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).


0x01 graphic

Rysunek 18.2.

Litera A jako \vielokat złożony


578

Część III » Tematy zaawansowane i efekty specjalne



0x01 graphic

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śle­niem wewnętrznych punktów. Przykład z listingu 18.1, LETTER.C, do rysowania wiru­ją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 ze­branych 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 triangula­tora 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óżnoro­dnych 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 stwo­rzyć 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ć, ryso­wania 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 roz­dział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ą ko­piowane 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 „tra­fień" w buforze selekcji.

Bufor selekcji należy przygotować wcześniej, a także nazwać prymitywy lub grupy pry­mitywó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łę widze­nia odpowiadającą wskaźnikowi myszy, a następnie sprawdza, który z nazwanych obie­któ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 pod­czas 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żytko­wnik 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 naz­wy 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 je­dną nazwą. W przypadku Słońca i poszczególnych planet wywołujemy funkcje glLoad­Name 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 poja­wiają się na ekranie. Aby użyć selekcji, musimy zmienić tryb renderowania na tryb se­lekcji, 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 ren­derowania 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, ja­kie 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 (li­sting 19.1) będzie to zawsze 1. Następne dwie pozycje zawierają minimalną i maksy­malną 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ą generowa­ne 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ę fun­kcja 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 przy­padku 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 od­czytać 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 prze­ciwnym 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 przechodzi­my 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ę Render­Scene. 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 pla­necie 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 wi­dzimy efekt kliknięcia na drugą planetę od Słońca.

Rysunek 19.2.

0x01 graphic

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ą zwra­caną 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 mniej­szą, czerwoną planetę z dwoma księżycami.


Rozdział 19. » Grafika Interaktywna 595

0x01 graphic

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ą pla­nety, 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 pro­gramu został przedstawiony na rysunku 19.4.


597

Rozdział 19. » Grafika interaktywna



0x01 graphic

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 obej­mują 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 se­lekcji, poprzez wywołanie funkcji glRenderMode z parametrem GLJFEEDBACK. W celu wypełnienia bufora sprzężenia zwrotnego i powrotu do normalnego trybu rende­rowania 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 wierz­choł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, wskaza­nych w wywołaniu funkcji glFeedbackBuffer. W przypadku linii zwracane są dwa zesta­wy 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żytkowni­ka (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 try­bie 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 obie­któ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 zintegrowa­nia 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 infor­macji 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 nary­sowania 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 pro­gramu 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 na­rysowania 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 poprze­dnich dwóch przykładów.


0x01 graphic

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ć bu­for 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ę Ren­derScene w celu ponownego wyrysowania sceny. Tym razem jednak, za pomocą fun­kcji 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 spraw­dzenia, 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 powo­duje 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żliwia­ją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 prymity­wów we współrzędnych okna. Możesz użyć tych informacji w celu zintegrowania grafi­ki 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ży
wany 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 wir­tualnej. Te komputerowo generowane „światy", w których można zanurzyć wiele zmy­słó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 za­awansowaną, że obejmuje rękawice z siłownikami, trójwymiarowe gogle oraz oprogra­mowanie 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 sta­je 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żytko­wnikowi 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 posia­dasz 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 tyl­ko 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 na­wet w innej kartotece, w innym komputerze czy na innym kontynencie. W tych proto­koł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 Cor­poration) 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 nie­cał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


0x01 graphic

Rysunek 20.1.

Typowa strona WWW z łączami hipertekstowymi


Strony WWW to najczęściej dokumenty tekstowe stworzone w specjalnym języku na­zywanym HTML (HyperText Markup Language). W dokumentach HTML można osa­dzać 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 Grap­hics (SGI), światowy lider w technologii grafiki komputerowej, zdecydował się na publiczne udostępnienie swojego języka opisu sceny Open Inventor, który stał się pod­stawą dla VRML w wersji l .0.



0x01 graphic

Słowo na temat Open Irwentora

Open Irwentor to interfejs modelowania 3D o znacznie wyższym pozio­mie niż samo API OpenGL. Open lnventor to biblioteka klas C++ sta­nowią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 od­czytywanie 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 szcze­gół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 kompute­rami osobistymi. Obecnie istniejąca technologia pozwala użytkownikom na coś więcej niż tylko wybieranie polecenie z menu. Obecnie można spacerować przez wirtualną bi­bliotekę, 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 sta­nowił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 opraco­wanie 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 wykorzystu­ją OpenGL.


Instalacja


WebSpace może zostać zainstalowana jako pomocnicza aplikacja większości przegląda­rek 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ąć.



0x01 graphic

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 Soft­ware. 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 spa­cerowy (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ójwy­miarowej scenie. Może nią być nieskomplikowany trójwymiarowy teren, architektoni­czny widok budynku, dom towarowy czy nawet obszar małego miasta.


619

Rozdział 20. » OpenGL w Sieci: VRML



0x01 graphic

Rysunek 20.2.

Przeglądarka WebSpace działająca \v trybie spacerowym



0x01 graphic

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 Web­Space 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 na­rzę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 wi­dzenia 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.



0x01 graphic

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 interfej­sy 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.


0x01 graphic

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



0x01 graphic

Rysunek 20.4.

Tryb oglądania



Jak widać, deska rozdzielcza w trybie oglądania wygląda podobnie jak w trybie space­rowym, 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 przed­stawia model komputera z większej odległości.


0x01 graphic

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 bibliote­ka oraz zestaw narzędzi jest zaimplementowana przy użyciu OpenGL. Biblioteka pro­gramowa 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ól­nymi obiektami sceny. Co więcej, można ustanowić powiązania pomiędzy obiektami, co umożliwia manipulowanie jednym obiektem poprzez manipulowanie innym obie­ktem (na przykład elementami przekładni w modelach mechanicznych). Dzięki obie­ktom 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ójwymiaro­wych" 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 indywi­dualne 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 stwier­dzeniu, ż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ść wir­tualna, która coraz lepiej, coraz szybciej i coraz bardziej szczegółowo będzie odzwier­ciedlać 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żywa­ją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 archi­tektura DirectK oraz 3DDDI.

0x01 graphic


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 ko­rzysta 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 kom­puterowej 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ą kom­pilatora 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 progra­mistycznych 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 „in­strukcji 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 popular­nych 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 Micro­soft Yisual C++ 5.0. Inne kompilatory i pakiety wykorzystujące MFC powinny działać podobnie.



0x01 graphic

Uwaga:

Jeśli używasz OWL (Object Windows Library Borlanda), przejdź do roz­dział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ływa­na 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 na­stę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 obie­ktowo 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 wi­doku, 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



0x01 graphic

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 tyl­ko 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 folde­rze \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 apli­kacji 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



0x01 graphic

Rysunek 21.2.

Dodawanie bibliotek OpenGL do projektu VisualC++


Przygotowanie klasy widoku dla OpenGL

Gdy użyjesz architektury dokument-widok przygotowanej przez AppWizarda dla apli­kacji 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 mfcgl­Yiew.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 kon­tekst 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, musi­my ustawić format pikseli OpenGL. Ponieważ do utworzenia formatu pikseli wymaga­ny 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ła­nego do tego okna. Rysunek 21.3 przedstawia okno dialogowe ClassWizarda, zawiera­jącego pozycję także dla komunikatu WM_DESTROY.


0x01 graphic

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 za­wierają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 zmien­nych składowych klasy, m_hDC i m_hRC. Natychmiast po stworzeniu kontekstu rende­rowania 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 usu­wają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 komu­nikat 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 wyma­zywanie (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 roz­dziale 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 komuni­katy WM_QUERNEWPALETTE oraz WM_PALETTECHANGED, nasz kod odpo­wiadają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 CMainFra­me 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 komu­nikatów do odpowiednich okien potomnych.

Ponownie więc użyjemy ClassWizarda w celu dodania dwóch procedur obsługi komu­nikatów palety do klasy CMainFrame. W tych procedurach po prostu wyznaczamy akty­wny 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). Fun­kcja timera po prostu unieważnia obszar roboczy okna, wymuszając jego odmalowanie. W module glcode.c funkcja renderująca przy każdym odrysowywaniu okna inkremen­tuje 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.

0x01 graphic

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 aplika­cjach 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ć ja­ko 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 korzysta­ją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 ko­rzysta 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ą progra­mó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ą kom­pilatora 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 progra­mistycznych 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 „in­strukcji 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 popu­larnych bibliotek klas C++ jako punktu wyjścia dla własnych aplikacji C++. Biblioteką klas opisywaną w tym rozdziale jest OWL (Object Windows Library) Borlanda. Przy­kłady i zrzuty okien występujące w tym rozdziale zostały przygotowane przy użyciu Borland C++5.0.



0x01 graphic

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 procedu­ry. Przez oddzielenie „czystego" kodu OpenGL w osobnym module, możesz efektywnie zastępować ten moduł specyficznym kodem, zachowując przy tym funkcjonalność re­szty 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ć wyma­gane 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 poprze­dnim 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 wi­doku, 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 folde­rze \OPENGL11 \SAMPLES\GLPRINT.


0x01 graphic

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 na­rzę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ć wyprowadzo­ne z klasy TWindowWiev, tak jak pokazano na rysunku 22.2.


0x01 graphic

Rysunek 22.2.

Wskazywanie, że okno klienta ma być wyprowadzone z klasy TWindowYiew



Po wygenerowaniu kodu i zbudowaniu szkieletowej aplikacji, otrzymasz program po­kazany 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 do­dać 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ą impor­tową zawierającą wszystkie funkcje API Win32. Czasem te biblioteki nie są zsynchro­nizowane 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 ta­beli 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 jedy­nie 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.


0x01 graphic

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 pro­cedurami obsługi komunikatów przeznaczonymi do inicjowanie i destrukcji okna, ryswania, 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 na­szym 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ż wspo­mniano, 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 kie­runku robić. Gdybyś jednak musiał ustawić je w programie, możesz wykorzystać do te­go 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 po­siadać prywatny kontekst urządzenia. Choć nie jest to rygorystycznie wymagane, powo­duje 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 pi­kseli i stworzymy kontekst renderowania dla okna. Ponieważ do utworzenia formatu pi­kseli 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 for­mat 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łuje­my zewnętrzną funkcję GLSetupRC. Ta funkcja zainicjuje wymagany kontekst rende­rowania, 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 usu­wają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 prze­ciwnym 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 komu­nikat 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 uczy­nimy, 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 wy­mazywanie (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 odmalowy­wane 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 time­ra 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 komu­nikatu WM_TIMER, a na zakończenie programu usunąć timer w funkcji EvDestroy. Stanowi to standardową praktykę programowania w Windows, zaś odpowiedni kod zo­stał przedstawiony na listingu 22.6.

Wynik działania programu w obecnej postaci został przedstawiony na rysunku 22.5.


0x01 graphic

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, stwo­rzymy obiekt OWL typu TPalette. W pliku owlglwnf.h deklarujemy wskaźnik do obie­ktu 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 komunika­ty WM_QUERNEWPALETTE oraz WM_PALETTECHANGED, nasz kod odpowia­dają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 po­tomnych.

Ponownie więc użyjemy ClassExperta w celu dodania dwóch procedur obsługi komuni­katów palety do klasy SDIDecFrame. W tych procedurach po prostu wyznaczamy potomny widok TWindowYiew i posyłamy mu niezmienione komunikaty palety, pozwalając wi­dokowi 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 Open­GL, 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 przy­kł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ć odwzo­rowane 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 przy­padku 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 Open­GL 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 wy­dajnoś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 unknąć 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 po­wstał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 zasto­sowania.


Rozdział 23.

OpenGL w Visual Basic S4GL

Poza rozdziałami 21 i 22, w całej książce opisywaliśmy OpenGL API z punktu widze­nia programisty C. Jednak żaden opis zagadnień związanych z programowaniem Win­dows 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 progra­mowych 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 do­stę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 przypa­dku 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ży­wają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. Pierw­szym 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 rendero­wania 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 rendero­wania, 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 Open­GL, 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, za­wsze 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 zmien­nych stanu jako właściwości kontrolki, jednak nie zawsze byłoby to możliwe. Dla za­chowania 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 zdefinio­wanych 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 najpopu­larniejszych ś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 operacyj­nym (Windows NT lub Windows 95). Skopiuj plik ,ocx do kartoteki systemowej i uru­chom 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 Yi­sual 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



0x01 graphic

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.


0x01 graphic

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 po­czą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 ren­derowania. 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 ry­sowania. 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 posia­dasz tylko jedną kontrolkę i kontekst renderowania, lecz zapewnia, że nie będą wyma­gane 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 przy­kł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.


0x01 graphic

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 operacyj­nym (Windows NT lub Windows 95). Skopiuj plik .ocx do kartoteki systemowej i uru­chom 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 zarejestro­wanych kontrolek OCX, które mogą zostać zainstalowane.

Wybierz pozycję WaiteGL OLE Control module, po czym kliknij na przycisk OK. Spo­woduje 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.


0x01 graphic

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


0x01 graphic



Na rysunku 23.6 widzimy zakładkę Events inspektora obiektów; jak widać, kontrolką wywołuje dwie unikatowe procedury obsługi: OnRender oraz OnSetupRC


0x01 graphic

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 zda­rzenie 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ła­dany. Nie jest to potrzebne, gdy posiadasz tylko jedną kontrolkę i kontekst renderowa­nia, lecz zapewnia, że nie będą wymagane późniejsze zmiany w kodzie, gdy zechcesz zastosować dodatkową kontrolkę WaiteGL. Po odłożeniu kontekstu renderowania mu­sisz 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 przy­kł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 wszy­stkie 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.


0x01 graphic

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ą spo­woduje, ż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 ma­lowania. 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 środo­wisk 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łado­wych 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 Mi­crosoft 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 Win­dows NT, jak w Windows 95 dostępna jest „ogólna", czysto programowa implemen­tacja 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ło­dy. Ceny kart akceleratorów 3D zgodnych z OpenGL zaczynają ostatnio spadać, jednak prawdziwym motorem wymuszającym znaczny spadek cen są gry komputerowe prze­znaczone 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 rzeczywi­stych instrumentów. Oprócz joysticka z jednym lub dwoma przyciskami masz do dy­spozycji 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 (chy­ba ż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 chy­ba, ż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 sy­stemu 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 zna­cznie 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ązane­go 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 pa­sjonują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ś niereal­nym. 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 wykorzy­stywane 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 syste­mu, 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 opraco­wał 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 oprogra­mowania, 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 wi­deo nie są już płaskimi, dwuwymiarowymi scenkami. Są wysoce złożonymi symulato­rami lotów lub trójwymiarowymi strzelaninami, pełnymi teksturowanych potworów, ścian i korytarzy. Direct3D jest ściśle zintegrowany z DirectDraw i kartami akcelerato­rów 3D. Jeśli jakieś funkcje nie są obsługiwane przez sprzęt, są emulowane pro­gramowo. 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, akce­leratory 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 akcelera­cją 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ć ja­ko 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 kom­puterach osobistych. Reality Lab API jest szybsze niż OpenGL, lecz uzyskuje to ko­sztem 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 upra­szczający tworzenie scen i manipulowanie obiektami, zbudowany na bazie trybu naty­chmiastowego.

Dobrą wiadomością dla programistów OpenGL jest to, że OpenGL będzie mógł korzy­stać ze sterowników Direct3D przyspieszających działanie DirectSD w trybie natych­miastowym. 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 in­westycja w OpenGL nie pójdzie na marne. W najbliższej przyszłości najprawdopodo­bniej 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), mode­lowania 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 te­kstur, 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 li­czba użytkowników potrzebowała dodatkowej szybkości. Obecnie karta graficzna z akce­leratorem dla Windows jest standardowym wyposażeniem każdego komputera. W grach w dalszym ciągu rysowanie odbywa się bez pomocy akceleratora 3D, jednak ogół pro­jektantów jest gotowy do zaakceptowania akceleracji 3D, jak tylko stanie się ona po­wszechnie 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ś kom­putery 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 inteli­gencji 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. Podo­bnie 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 kom­putera, kupowanego w promocyjnej cenie w lokalnym supermarkecie.


0x01 graphic

/^'"<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 stoso­wanych algorytmów - mogą się znacznie od siebie różnić, lecz wciąż prowadzą do określonego celu. Twoim zadaniem, jako twórcy oprogramowania, jest dokonanie opty­malnego 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 prze­kształceń należą funkcje Rotate, Translate oraz Scalę.

* Niektóre systemy i karty graficzne mogą bezpośrednio korzystać z list wyświe­tlania 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 same­mu komponować i przemnażać własne macierze. Wbudowane funkcje są wy­soce 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 prakty­czne 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 pro­gramowych implementacjach OpenGL.

Grupy podobnych prymitywów umieszczaj wewnątrz pojedynczej pary instru­kcji 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ła­skich 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ć obie­któ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 (zaj­rzyj 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 po­siadasz 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 doda­tkowej 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 programo­wania OpenGL. Wśród podanych książek znajdują się zarówno pozycje związane bez­poś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ąza­nych 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, prze­znaczonej do komputerów osobistych. Oprócz zachowania zgodności z nową specyfi­kacją, Microsoft poprawił wydajność OpenGL oraz dodał kilka nowych elementów i możliwości, między innymi możliwość umieszczania wywołań OpenGL w rozszerzo­nych 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 wierz­choł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 pra­wdopodobnie 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 kom­pletną dokumentację na temat nowych elementów wersji 1.1, a także trochę dodatko­wych 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 pun­kty 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 rysowa­nia 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ówna­niach, 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 wykony­wana 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śre­dnio w kodzie źródłowym.

Macierz

Dwuwymiarowa tablica liczb. Na macierzach można wykonywać operacje matematy­czne, 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 obie­ktu 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 try­bó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 ele­ment 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, tworzo­ny jest w pamięci, a następnie przerzucany na ekran w pojedynczej operacji, w przeci­wień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 krzy­wej 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 (prze­sunięcia), skalowanie (w określonym kierunku lub równomiernie we wszystkich kie­runkach) 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 odle­gł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, cienio­wanych 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 pun­któ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. Świa­tło otaczające równomiernie iluminuje wszystkie obiekty ze wszystkich stron.

Teksel

Podobny do piksela (elementu obrazu), z tym że stanowi element tekstury (texture ele­ment). 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 pro­stym 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, defi­niuje 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 za­miana dwuwymiarowych czcionek na trójwymiarowe litery.

0x01 graphic


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



0x01 graphic

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



Wyszukiwarka

Podobne podstrony:
opengl ksiega eksperta wydanie iii UZFSAM5UH2NWETWCPGG2PS3RHAX75LU5XCNZJJI
opengl ksiega eksperta wydanie iii UZFSAM5UH2NWETWCPGG2PS3RHAX75LU5XCNZJJI
OpenGL Ksiega eksperta Wydanie V 2
OpenGL Ksiega eksperta Wydanie V opglk5
OpenGL Ksiega eksperta Wydanie V opglk5
OpenGL Ksiega eksperta Wydanie V opglk5
OpenGL Ksiega eksperta Wydanie V
OpenGL Ksiega eksperta Wydanie V opglk5
Access 2002 Projektowanie baz danych Ksiega eksperta ac22ke
C 3 0 dla NET 3 5 Ksiega eksperta csh3ke
43, ciekawostki, Linux - Ksiega Eksperta, Linux - ksiega eksperta, Linux - księga eksperta
34, ciekawostki, Linux - Ksiega Eksperta, Linux - ksiega eksperta, Linux - księga eksperta
58, ciekawostki, Linux - Ksiega Eksperta, Linux - ksiega eksperta, Linux - księga eksperta
26, ciekawostki, Linux - Ksiega Eksperta, Linux - ksiega eksperta, Linux - księga eksperta
08, ciekawostki, Linux - Ksiega Eksperta, Linux - ksiega eksperta, Linux - księga eksperta
10, ciekawostki, Linux - Ksiega Eksperta, Linux - ksiega eksperta, Linux - księga eksperta
57, ciekawostki, Linux - Ksiega Eksperta, Linux - ksiega eksperta, Linux - księga eksperta

więcej podobnych podstron