Dee's profile picture

Published by

published

Category: Web, HTML, Tech

Otwórz się na OpenGL - Wstęp do programowania [2000, demoscene]

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


0 Kudos

Comments

Displaying 0 of 0 comments ( View all | Add Comment )