Otwórz sie na OpenGL - Wstęp do programowania
Otwórz się na OpenGL - Wstęp do programowania
Dan Hawkins / delirium (c) 2000, 2012
last revision: Sat Mar 17 21:00:08 CET 2012
Każdy z koderów zapewne spotkał się z magiczną nazwą OpenGL - czymś co zeszło z wyżyn najbardziej zaawansowanych graficznie systemów komputerowych i pojawiło się wśród zwykłych śmiertelników. Skrót ten stał się synonimem grafiki wysokiej jakości generowanej w czasie rzeczywistym i być może dlatego wiele osób ma pewne obawy przez zagłębieniem się w tajniki programowania z użyciem tego interfejsu. W rzeczywistości nie taki wilk straszny jak go malują - OpenGL jest po prostu biblioteką funkcji stworzoną jak na realia komputerowe dawno temu przez Silicon Graphics Inc. Pracowało (i nadal pracuje) nad nią wielu ludzi, dzięki temu jest to jeden z najprzyjemniejszych interfejsów graficznych z jakimi miałem okazję się zetknąć (pisałem ten tekst pierwotnie w czasach gdy dominującą wersją 3D API w DirectX był Direct3D 5.x - 6.x; od tamtej pory dużo się zmieniło i szala przewagi przechyliła się na korzyść Direct3D, zwłaszcza w kontekście debugowania). OpenGL to pośrednik między programem a sprzętowym systemem graficznym. Składa się z zestawu około 150 różnych poleceń pozwalających na opisanie obiektów i operacji potrzebnych do ich wizualizacji. OpenGL zostało zaprojektowane jako wydajny, niezależny od sprzętu interfejs programistyczny możliwy do zaimplementowania na wielu różnych platformach programowo-sprzętowych. W celu osiągnięcia tej niezależności (różne systemy operacyjne, procesory, karty, itd. wymagają innych rozwiązań) w samym OpenGL nie zawarto żadnych poleceń odpowiedzialnych za obsługę zdarzeń związanych z obsługą okien i zdarzeń pochodzących od użytkownika. Każdy system operacyjny wspierający OpenGL zawiera szereg funkcji pozwalających na przygotowanie okna/ekranu do pracy a następnie prezentowania wyników działania aplikacji. Kolejną cechą tego interfejsu jest jego niskopoziomowość - najbardziej złożonymi obiektami jakie można wykreślić to szereg wielokątów, linii i punktów. Istnieje także rozszerzenie dostępne w każdej implementacji OpenGL zwane GLU - OpenGL Utility Library, udostępniające bardziej zaawansowane funkcje umożliwiające generowanie powierzchni krzywoliniowych (m.in. NURBS).
Co powinieneś umieć aby zacząć poznawać OpenGL?
Naprawdę niedużo - potrzebna jest znajomość podstaw grafiki 3D
(matematyka i geometria), języka C (w nim będę podawał przykłady) i wiedzieć
jak napisać program otwierający okno w systemie Windows. Zakładam, że
pracujesz właśnie w środowisku Win32 (Windows 95 OSR2, NT 4.0 i wyżej) i
używasz Visual C++.
Pierwszą podstawową rzeczą jaką powinieneś wiedzieć
jest to, że OpenGL jest tak zwaną "maszyna stanów". Oznacza to, że
wewnątrz OpenGL jest zestaw "przełączników" które wpływają na
sposób wykonywania kolejnych poleceń przez system, polecenia te mogą także wpływać
na stan przełączników. Przykładowo jeżeli ustawisz kolor czerwony i
narysujesz trójkąt, to rysując dalej trójkąty, kropki i kreski
otrzymasz je w kolorze czerwonym, będzie on aktywny dopóki go nie przełączysz
na inny kolor lub nie nakażesz OpenGL samemu określać koloru obiektów poprzez
np. wstawienie źródła światła i uaktywnieniu go (przełącznik "włącz
oświetlenie"). Wszystkie rozkazy OpenGL zaczynają się od liter gl_,
polecenia pochodzące z biblioteki GLU zaczynają się znakami glu_.
Zestaw komend zależnych od systemu operacyjnego a dotyczących współpracy
z OpenGL zaczyna się przykładowo znakami wgl_ w systemie Windows, glX_ w
systemach używających XWindow jako systemu graficznego lub agl_ pod MacOS.
Proponuję zapoznać się z dołączonym do artykułu przykładem, sądzę że będzie on
dość dobry na początku.
Na początek przygotujmy naszą aplikację do współpracy z OpenGL, użyjemy do tego WinAPI. Najpierw trzeba dołączyć kilka plików nagłówkowych:
#include <windows.h> // cały stuff Windowsa #include <gl/gl.h> // podstawowy plik naglówkowy OpenGL #include <gl/glu.h> // OpenGL Utilities Library #include <gl/glaux.h> // różne dodatki
następnie tworzymy klasę okna dodając styl CS_OWNDC:
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
tworząc okno ważne jest dodanie opcji WS_CLIPCHILDREN i WS_CLIPSIBLINGS:
hwnd = CreateWindow("OpenGL Window Class", "Prosta aplikacja OpenGL", WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, CW_USEDEFAULT, CW_USEDEFAULT, 300, 300, 0, NULL, hInstance, 0);
potrzebne będą dwie zmienne globalne:
HGLRC hRC; // OpenGL rendering context HDC hDC; // graficzny Device Context
teraz trzeba przygotować wstępne ustawienia OpenGL - funkcja Init3D, najlepszym miejscem na jej wywołanie jest odpowiedź na komunikat WM_CREATE. Listing funkcji Init3D wraz z komentarzami:
HRESULT Init3D(HWND hwnd) // funkcja pobiera uchwyt do okna { PIXELFORMATDESCRIPTOR pfd; // struktura w ktorej opiszemy porządany format piksela GLuint pixelFormat; // identyfikator formatu piksela hDC = GetDC( hwnd); // wypełnienie struktury PIXELFORMATDESCRIPTOR ZeroMemory(&pfd, sizeof(pfd)); // zerowanie pfd.nSize = sizeof (pfd); pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | // flagi określające czego potrzebujemy: PFD_DOUBLEBUFFER; // wspomagania kreślenia w oknie, // wspomaganie dla OpenGL i podwójnego buforowania pfd.iPixelType = PFD_TYPE_RGBA; // potrzebujemy direct color, ponieważ współcześnie // żaden akcelerator nie wspomaga 3D w trybach z paletą kolorów pfd.cColorBits = 16; // liczba bitów koloru pfd.cDepthBits = 16; // minimalna ilość bitów z-bufora pfd.iLayerType = PFD_MAIN_PLANE; // znajdź najbliższy pasujący format bufora do określonego powyżej pixelFormat = ChoosePixelFormat( hDC, &pfd); // czy został znaleziony? if (!pixelFormat) { return E_FAIL; } // *** // znaleziono jakiś format piksela, spróbujmy ustawić go w naszym kontekście graficznym if (!SetPixelFormat(hDC, pixelFormat, &pfd)) { return E_FAIL; } // teraz spróbujmy stworzyć z tego kontekst dla OpenGL hRC = wglCreateContext(hDC); if (!hRC) { return E_FAIL; } // udało się, spróbujmy uczynić go aktywnym if (!wglMakeCurrent(hDC, hRC)) { return E_FAIL; } // wszystko poszło dobrze! nasze okno jest przygotowane do renderowania OpenGL return S_OK; }
Osoby, które miały kontakt z Direct3D mogą poczuć się trochę zaskoczone
- "I to wszystko?". Tak! Jeżeli zażądasz 16 bitów na kolor a ekran
aktualnie jest w 32 bitach system przestawi się automatycznie na 32 bity. Ma to
swoje dobre i złe strony. Dobra to ta, iż masz mniej pracy do zrobienia przy
inicjalizacji. Zła strona objawia się tym, że jeżeli twój sprzęt nie
wspomaga sprzętowo 32 bitowego renderingu to OpenGL zrobi to programowo i
wydajność twojej aplikacji drastycznie spadnie. Podobna sytuacja wystąpi, jeżeli
ekran będzie w trybie 8 bitowym - tym razem każdy współczesny akcelerator 3D
odmówi współpracy i wylądujesz w software'owym renderingu. Jeżeli sobie
tego życzysz, możesz sprawdzić jaki format piksela został wybrany przez
system. Po sprawdzeniu czy coś się znajduje w zmiennej pixelFormat (miejsce
zaznaczone trzema gwiazdkami) wywołaj funkcję DescribePixelFormat i sprawdź
parametry, które cię interesują.
Kolejną sprawą jaką teraz zrobimy, to poinformujemy OpenGL
o rozmiarze bufora do jakiego będziemy renderować. Przy okazji ustawimy kilka
rzadko zmieniających się elementów takich jak np. pole widzenia (frustrum).
Nasza funkcja będzie nazywać się Reshape, jej parametrami będzie szerokość
i wysokość tej części okienka, która jest dostępna dla użytkownika (bez
ramek, menu, itp.). Najlepszym miejscem jej wywołania jest odpowiedź na
komunikat WM_SIZE, gdyż jest on wysyłany za pierwszym razem, kiedy okienko
jest tworzone a następnie za każdym razem gdy jego rozmiar ulega zmianie.
Pozwoli to na bieżąco dostosowywać się do aktualnych rozmiarów okna:
case WM_SIZE: Reshape( LOWORD(lParam), HIWORD(lParam));
Zawartość funkcji Reshape przedstawia się następująco:
void Reshape( GLsizei width, GLsizei height) { // ustaw rozmiary na width x height glViewport( 0, 0, width, height); // poinformuj, ze wszystkie operacje na macierzach beda dokonywane na macierzy pojekcji glMatrixMode( GL_PROJECTION ); // zaladuj macierz jednostkowa glLoadIdentity(); // upewnij sie, ze wysokosc nie jest zerowa if ( height == 0) height++; // gluPerspective tworzy macierz projekcji z podanych parametrow, nastepnie mnozy // przez nia aktualna macierz (w naszym przypadku bedzie to // macierz_jednostkowa * macierz_projekcji) gluPerspective( 45.0f, // FOV w stopniach (GLfloat)width/(GLfloat)height, // aspect ratio (stosunek szer. do wys.) 0.1f, // near clipping plane (z_near = 0.1) 100.0f); // far clipping plane (z_far = 100) // przelacz sie na macierz obiektow (swiata) glMatrixMode( GL_MODELVIEW ); }
Jesteśmy już całkowicie przygotowani do tworzenia i rysowania! Jeszcze tylko wywołamy w momencie tworzenia okienka funkcje inicjującą potrzebne nam parametry a które wystarczy ustawić tylko raz:
void InitRenderStates(void) { glClearColor( 0.1f, 0.2f, 0.3f, 1.0f); // ustaw kolor wypelnienia tla - R,G,B,A glClearDepth( 1.0f); // ustaw wartosc jaka bedzie czyszczony z-bufor glShadeModel( GL_SMOOTH); // bedziemy uzywac cieniowania Gourauda glEnable( GL_DITHER); // wlacz dithering glEnable( GL_DEPTH_TEST); // wlacz testowanie z-bufora - akurat w naszym przykladzie jest to nieprzydatne glDisable( GL_CULL_FACE); // wylacz face-culling glMatrixMode( GL_MODELVIEW ); // przelacz sie na macierz obiektow (swiata) }
Poniżej przedstawię funkcję rysującą obracający się kolorowy trójkąt i jeden druciany kwadrat. Funkcja ta będzie wywoływana w wolnym czasie procesora (patrz przykład - pętla odbierania komunikatów na końcu WinMain) i będzie to nasza główna funkcja rysująca:
void Render(void) { static float obrot = 0.0f; obrot += 0.5f; // uaktualnij kat obrotu glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // wyczysc bufor i z-bufor glLoadIdentity(); // zaladuj macierz jednostkowa jako macierz swiata // utworz macierz kamery i pomnoz nia aktualna macierz (jednostkowa w tej chwili) // parametry: x,y,z oka, x,y,z celu, x,y,z wektora okreslajacego "górę" gluLookAt (0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glPushMatrix(); // zapamietaj aktualna macierz na stosie glRotatef( obrot, 0.0f, 1.0f, 0.0f); // pomnoz aktualna macierz przez macierz obrotu o kat "obrot" wokol osi Y glBegin(GL_TRIANGLES); // rozpocznij rysowanie trojkatow, kazda dostarczona trojka wierzcholkow // spowoduje narysowanie trojkata. Zanim dostarczysz wierzcholek, ustaw // wczesniej jego parametry, np. normalną, kolor, wsp. tekstury. glColor3f( 1.0f, 0.0f, 0.0f); // ustaw kolor na czerwony glVertex3f( 0.2f, 0.4f, 0.0f); // 1 wiercholek glColor3f( 0.0f, 1.0f, 0.0f); // nastepny bedzie zielony glVertex3f( -0.05f, -0.4f, 0.0f); // 2 wiercholek glColor3f( 0.0f, 0.0f, 1.0f); // a teraz niebieski glVertex3f( -0.2f, 0.2f, 0.0f); // 3 wiercholek - mamy trojkat! glEnd(); // koniec rysowania trojkatow (u nas - jednego) glPopMatrix(); // przywroc macierz sprzed glRotate glPushMatrix(); // i ponownie ja zapamietaj - ostatecznie cofniemy efekt obrotu wzgledem osi Y glTranslatef( 0.3f, 0.0f, 0.2f); // przesunmy sie troche (pamietaj! to jest mnozenie macierzy) glRotatef( obrot, 0.8f, 0.0f, 1.0f); // obrocmy sie wokol osi X oraz Z glColor3f( 1.0f, 1.0f, 0.0f); // ustaw kolor na zolty glBegin(GL_LINE_LOOP); // bedziemy rysowac zestaw linni ktorych koniec OpenGL polaczy z poczatkiem glVertex3f( 0.1f, 0.1f, 0.0f); glVertex3f( 0.1f,-0.1f, 0.0f); glVertex3f(-0.1f,-0.1f, 0.0f); glVertex3f(-0.1f, 0.1f, 0.0f); glEnd(); glPopMatrix(); // przywroc macierz // // jeszcze cos rysuj... // glFinish(); // upewnij sie, ze OpenGL skonczylo rendering SwapBuffers( hDC); // niech Windows zrobi page-flipping } // end
To wszystko wcale nie wygląda tak strasznie! Następnym razem pokażę ci jak się ustawia światła i dodaje tekstury, to powinno pomóc w napisaniu całkiem niezłego dema. Przydałoby się jeszcze tylko na koniec trochę posprzątać:
void Cleanup3D( HWND hwnd) { // uczyn kontekst OpenGL nieaktualnym wglMakeCurrent( NULL, NULL); // zwolnij kontekst urzadzenia ReleaseDC( hwnd, hDC); // skasuj kontekst OpenGL wglDeleteContext( hRC); }
I to wszystko na dziś. Życzę powodzenia w nauce. Jeżeli miałbyś jakieś pytania przyślij je do mnie: danhawk@scene.pl . Polecam ci także odwiedzić moją stronę pod adresem http://www.kki.net.pl/~daniel77 znajdziesz tam trochę materiałów dotyczących OpenGL.
Polecam odwiedzić następujące miejsca w sieci:
Wszystko o OpenGL: http://www.opengl.org
Comments
Displaying 0 of 0 comments ( View all | Add Comment )