Wstęp
W tym odcinku kursu poznamy niezbędne wiadomości wstępne dotyczące historii oraz
podstaw budowy i działania biblioteki OpenGL. Czytelnik znajdzie tutaj także uzasadnienie do
konwencji przyjętej w dalszej części kursu.
Historia OpenGL
Specyfikacja pierwszej wersji biblioteki OpenGL (ang. Open Graphics Library) została
opublikowana 1 czerwca 1992 roku na bazie języka GL opracowanego przez firmę Silicon Graphics
Inc. (SGI) na potrzeby stacji graficznych IRIS. Początkiem sukcesu OpenGL stała się jednak dopiero
wersja 1.1, opublikowana 4 marca 1997 roku, którą zaimplementowano m.in. w systemach z rodziny
Microsoft Windows. Kolejne wersje biblioteki (1.2, 1.3, 1.4, 1.5, 2.0, 2.1, 3.0 i najnowsza 3.1 z 24
marca 2009 roku) są znakomitą ilustracją rozwoju sprzętu graficznego i tendencji w programowym
generowaniu grafiki.
Przez tych kilkadziesiąt lat jedną z głównych idei twórców OpenGL była zgodnośd wstecz
biblioteki. W praktyce programy przygotowane dla wcześniejszych wersji biblioteki działały
prawidłowo także na jej najnowszych implementacjach. Drugą zasadą OpenGL jest jej
wieloplatformowośd – programy z niej korzystające działają praktycznie na wszystkich współczesnych
systemach operacyjnych. Cechy te są miarą sukcesu OpenGL, która jest obecnie podstawową
niskopoziomową biblioteką graficzną 3D, standardem powszechnie wykorzystywanym przez
producentów oprogramowania użytkowego i gier.
Od początku rozwojem OpenGL zajmowała się ARB (ang. Architecture Review Board), w skład
której wchodzili przedstawiciele najważniejszych firm z branży komputerowej. Taki sposób
wprowadzania zmian w bibliotece zapewnia OpenGL zachowanie niezależności od jednej platformy
sprzętowej lub programowej przy jednoczesnym uwzględnieniu najnowszych osiągnięd w dziedzinie
grafiki komputerowej. Oczywiście współpraca wielu firm, o często sprzecznych interesach, nie zawsze
przebiegała idealnie. Producenci sprzętu graficznego promowali własne rozwiązania,
niekompatybilne z rozwiązaniami konkurencji, a spory wewnątrz ARB powodowały opóźnienia w
przygotowywaniu nowych wersji specyfikacji.
Od 2006 roku rolę opiekuna OpenGL przejął Khronos Group, który rozwija także inne otwarte
standardy programistyczne. Początkowo krytykowany za opóźnienia, Khronos Group w drugiej
połowie 2008 roku wprowadził specyfikację wersji 3.0 i krótko po tym opublikowane zostały kolejne
wersje OpenGL oznaczone numerami 3.1 i 3.2. Ewolucję OpenGL zaproponowaną przez Khronos
Group opiszemy nieco dalej, z kilkuletniej perspektywy wydaje się jednak, że zmiana ta wywarła
pozytywny wpływ na tempo i kierunek rozwoju biblioteki.
Rozszerzenia
Jednym z najczęściej dyskutowanych mechanizmów OpenGL są rozszerzenia. Rozszerzenia
określają dodatkowe możliwości implementacji biblioteki i mogą dotyczyd najróżnorodniejszych jej
elementów. Zaletą tego mechanizmu jest dostępnośd z poziomu OpenGL najnowszych możliwości
sprzętu graficznego, ale jest jednocześnie to także wada. Rozszerzenia proponowane przez niektóre
firmy nie były implementowane przez inne (czasami wynikało to jednak z braku możliwości
technicznych), co czyniło rozszerzenia mechanizmem niepewnym w zastosowaniach komercyjnych.
Mimo tych wad rozszerzenia na trwałe wpisały się w bibliotekę OpenGL i stanowią ważny mechanizm
wspomagający jej rozwój.
OpenGL 3.0 - rewolucja przez ewolucję
Wspomniana wcześniej wersja 3.0 biblioteki OpenGL zapoczątkowała zupełnie nowe
podejście do dotychczas obowiązujących zasad. Twórcy standardu zauważyli bowiem, że dalsze
utrzymywanie tak rozbudowanej i w znacznej części przestarzałej funkcjonalności nie znajduje
praktycznego uzasadnienie. Wyodrębniono liczną grupę tzw. funkcji przestarzałych, która została
wytypowana do usunięcia w przyszłych wersjach specyfikacji. Wskazuje to na generalną tendencję
ewolucji OpenGL – stopniowe usuwanie przestarzałych jej elementów i zastępowanie ich
nowoczesnymi programowalnymi mechanizmami.
Kolejną nowością w bibliotece OpenGL 3.0 jest wymuszenie na twórcach systemów
okienkowych utworzenia funkcji obsługujących nowy kontekst renderingu. Związane jest to z
zupełnie nową w bibliotece OpenGL koncepcją profilowania kontekstów renderingu oraz
planowanym cyklem usuwania z biblioteki przestarzałych mechanizmów. W przyszłości planowane są
profile przeznaczone do rozrywki, tworzenia dokumentów i stron WWW, obsługi aplikacji
desktopowych, czy też dedykowane do urządzeo mobilnych. Warto zauważyd, że ARB zastrzegła
sobie wyłącznośd do tworzenia profili OpenGL. Sposób obsługi nowego kontekstu renderingu musi
umożliwiad aplikacji wskazanie konkretnego profilu niezbędnego do działania programu. Zauważmy,
że brak nowego kontekstu renderingu przy redukcji funkcjonalności OpenGL, mógłby doprowadzid w
przyszłości do błędów przy uruchamianiu starszych programów.
Początkowo wersja 3.0 OpenGL spotkała się z dośd chłodnym przyjęciem ze strony
środowiska programistycznego. Wśród głównych zarzutów pojawiały się tezy o braku pełnej
rezygnacji z przestarzałych funkcji oraz braku modelu obiektowego. Wszystko to było związane z
oczekiwaniami na zupełnie nowe API biblioteki. Całkiem słusznie niezadowolenie wzbudzało także
wielomiesięczne opóźnienie w prezentacji specyfikacji biblioteki.
Analiza decyzji Khronos Group wskazuje jednak, że podjęto bardzo racjonalne decyzje. Na
obecnym etapie rozwoju biblioteki nie była możliwa rezygnacja z całej jej dotychczasowej
funkcjonalności. Zbyt dużo jest bowiem na rynku oprogramowania z tego korzystającego – w grę
wchodzą chodby niezwykle skomplikowane aplikacje inżynierskie i naukowe. Wprowadzenie nowego
kontekstu renderingu, mechanizmu profili oraz redukcji funkcjonalności ułatwi stopniowe
przechodzenie do przyszłych wersji biblioteki.
OpenGL 3.1 – dalszy etap rewolucji przez ewolucję
Kolejna odsłona biblioteki OpenGL pojawiła się bardzo szybko (oczywiście w porównaniu do
poprzednich standardów w tym zakresie) i stanowi kolejny istotny krok w jej rozwoju. Zdecydowano
się na usunięcie wszystkich funkcji określonych w wersji 3.0 jako przestarzałe, chod pozostawiono
jeszcze dodatkową furtkę w postaci rozszerzenia GL_ARB_compatibility, które zawiera wszystkie
usunięte funkcje. Dzięki temu API stał się znacznie mniejsze, co widad chodby po objętości
specyfikacji. Pełna jej wersja obejmująca opis funkcji przestarzałych to blisko 500 stron, a właściwa
zajmuje nieco ponad 350 stron.
Usunięcie przestarzałej funkcjonalności znaczne uprościło bibliotekę i jednocześnie ułatwiło
jej stosowanie. Jednak z ułatwieo tych w pełni skorzystają jedynie twórcy nowych aplikacji, którzy od
początku zdecydują się korzystanie wyłącznie z nowoczesnych rozwiązao oferowanych przez OpenGL
3.0/3.1.
OpenGL 3.2 – szybka ewolucja
OpenGL 3.2 to kolejny etap rewolucji przez ewolucję. Specyfikacja została opublikowana
nieco ponad cztery miesiące po upublicznieniu wersji 3.1 i wnosi szereg nowych możliwości do
biblioteki, w tym . Jedną z bardziej praktycznych zmian jest zdefiniowanie dwóch profili:
podstawowego, obejmującego tylko nieusuniętą funkcjonalnośd i kompatybilnego, zgodnego ze
wszystkim poprzednimi wersjami OpenGL. Nie zmieniono przy tym praktycznie funkcjonalności
określonych jako przestarzałe, co wskazuje na pewną stabilizację w dalszym kierunku rozwoju
biblioteki.
OpenGL w wersji 3.2 bez rozszerzeo można w pełni porównywad z aktualnym jej
odpowiednikiem, tj. biblioteką DirectX 10.0, a wraz z zaaprobowanymi przez ARB rozszerzeniami:
GL_ARB_draw_buffers_blend,
GL_ARB_sample_shading,
GL_ARB_texture_cube_map_array,
GL_ARB_texture_gather i GL_ARB_texture_query_lod z biblioteką DirectX w wersji 10.1.
GLSL
Język programów cieniowania OpenGL GLSL (ang. OpenGL Shading Language), zwanymi
najczęściej shaderami, został wprowadzony w wersji 2.0 biblioteki, a od wersji 3.1 stanowi
podstawowy element potoku renderingu zarówno na etapie przetwarzania wierzchołków jaki
fragmentów. Sam język GLSL sukcesywnie ewoluował wraz z kolejnymi wersjami OpenGL. Aktualnie
jego najnowszą wersją jest 1.50, która odpowiada możliwościom tzw. Shader Model 4.1 języka HLSL z
biblioteki DirectX 10.1. Dodajmy jeszcze, że wersja 3.1 OpenGL obsługuje shadery GLSL w wersji 1.30 i
1.40, a wersja 3.2 shadery w wersji 1.40 i 1.50.
Język GLSL jest opisany w odrębnej specyfikacji, która tworzy spójną całośd ze specyfikacją
podstawowego API biblioteki OpenGL.
Powiązania z systemami okienkowymi
Biblioteka OpenGL wymaga wsparcia API systemu okienkowego do tworzenia i zarządzania
kontekstami graficznymi, renderingu w oknie oraz obsługi innych zasobów. Wszystkie liczące się
współczesne systemy operacyjne posiadają API obsługujące bibliotekę OpenGL. W systemie X
Window, dostępnym głownie pod Unix i Linux, jest to GLX. Warto także wspomnied, że
implementacje GLX istnieją również w systemach Microsoft Windows, MacOS X i kilku innych
platformach, na których jest dostępny X Serwer. OpenGL w systemach Microsoft Windows
obsługiwany jest przez WGL. Quartz, warstwa graficzna systemu MacOS X, zawiera kilka interfejsów
API obsługujących OpenGL. Są to CGL, AGL i NSGLView.
Oczywiście stosowanie rozwiązao specyficznych dla danego systemu operacyjnego powoduje,
że danego programu nie można skompilowad i uruchomid w innym systemie operacyjnym bez
dokonania szeregu zmian w kodzie źródłowym. Rozwiązanie tego problemu stanowią biblioteki
oferujące jeden, niezależny od systemu operacyjnego, interfejs do obsługi okien i komunikatów.
Pierwszą biblioteką tego typu była biblioteka AUX (ang. Auxiliary Library), jednak bodaj największą
popularnośd zdobyła biblioteka GLUT (ang. OpenGL Utility Toolkit), opracowana i rozwijana w latach
1994-1998 przez Marka J. Kilgarda. Pomimo sędziwego wieku jest ona ciągle powszechnie stosowana.
Typy danych
Typy danych w obsługiwane przez bibliotekę OpenGL przedstawia Tabela 1. Specyfikacja nie
określa jakiego rodzaju typy danych są użyte w konkretnej implementacji, zawiera jedynie minimalne
wymagania związane z precyzją i wykonywaniem operacji na całkowitych i zmiennoprzecinkowych
typach danych. W szczególności implementacja może używad typów danych o większej ilości bitów
niż minimalne wskazane we wspomnianej wyżej tabeli. Typy GLintptr, GLsizeiptr i GLsync
mają ilośd bitów opisaną jako prtbits, co oznacza minimalną ilośd bitów niezbędną w danej
implementacji do umieszczenia wskaźnika. Typy te muszą umożliwid zapamiętanie dowolnego
adresu. Trzeba także wyraźnie podkreślid, że typy danych OpenGL nie są typami danych
występującymi w języku C (pomimo częściowej zgodności nazw). Tak więc, na przykład, typ GLint
niekoniecznie jest równoznaczny z typem int w C. Ponadto, poza wymienionymi typami funkcje
OpenGL korzystają z typu pustego GLvoid.
Typ GL
Minimalna ilość
bitów
Opis
GLboolean
1
typ logiczny
GLbyte
8
liczba całkowita ze znakiem w kodzie uzupełnieo do 2
GLubyte
8
liczba całkowita bez znaku
GLchar
8
znaki tworzące ciągi
GLshort
16
liczba całkowita ze znakiem w kodzie uzupełnieo do 2
GLushort
16
liczba całkowita bez znaku
GLint
32
liczba całkowita ze znakiem w kodzie uzupełnieo do 2
GLuint
32
liczba całkowita bez znaku
GLint64
64
liczba całkowita ze znakiem w kodzie uzupełnieo do 2
GLuint64
64
liczba całkowita bez znaku
GLsizei
32
nieujemna liczba całkowita
GLenum
32
typ wyliczeniowy
GLintptr
ptrbits
liczba całkowita ze znakiem w kodzie uzupełnieo do 2
GLsizeiptr
ptrbits
nieujemna liczba całkowita
GLsync
ptrbits
uchwyt obiektu synchronizacji
GLbitfield
32
pole bitowe
GLhalf
16
liczba zmiennoprzecinkowa połówkowej precyzji zakodowana
jako skalar bez znaku
GLfloat
32
liczba zmiennoprzecinkowa
GLclampf
32
liczba zmiennoprzecinkowa ograniczona do przedziału
GLdouble
64
liczba zmiennoprzecinkowa podwójnej precyzji
GLclampd
64
liczba zmiennoprzecinkowa podwójnej precyzji ograniczona
do przedziału
Tabela 1 Typy danych w bibliotece OpenGL
Składnia poleceń
Polecenia OpenGL są funkcjami lub procedurami. Różne grupy poleceo wykonują tę samą
operację, lecz różnią się sposobem w jaki dostarczane są do nich argumenty. Ogólnie rzecz biorąc,
deklaracja polecenia posiada formę:
rtype Name{1|2|3|4}{b|s|i|i64|f|d|ub|us|ui}{v}( [args ,] T arg1, … ,
T argN [, args] );
Polecenia OpenGL są zbudowane z nazwy, po której następuje, w zależności od konkretnego
przypadku, maksymalnie do 4 znaków. rtype jest typem zwracanym przez funkcję. Nawiasy {}
dołączają szereg znaków (lub zestawów znaków), z których tylko jeden jest używany. Pierwszy znak
{1|2|3|4} oznacza liczbę wartości wskazanego typu, które muszą byd przedstawione do
polecenia. Drugi znak lub zestaw znaków {b|s|i|i64|f|d|ub|us|ui} wskazuje konkretne
rodzaje argumentów: 8-bitowe liczby całkowite, 16-bitowe liczby całkowite, 32-bitowe liczby
całkowite, 64-bitowe liczby całkowite, liczby zmiennoprzecinkowe pojedynczej precyzji lub liczby
zmiennoprzecinkowe podwójnej precyzji – patrz Tabela 2. Zauważmy, że wskazując na typ bez znaku
dodajemy literę u na początku tego nazwy typu (na przykład unsigned byte jest skracany do
ubyte). Ostatni znak {v}, jeśli jest obecny, wskazuje, że polecenie zawiera wskaźnik do tablicy
(wektora) wartości, a nie serię poszczególnych argumentów.
Litera Odpowiadający typ GL
b
GLbyte
s
GLshort
i
GLint
i64
GLint64
f
GLfloat
d
GLdouble
ub GLubyte
us GLushort
ui GLuint
Tabela 2 Powiązanie sufiksów poleceo GL z typami argumentów. Patrz Tabela 1 z definicjami typów GL
Argumenty poleceo umieszczone w nawiasach [args ,] i [, args] mogą, ale nie muszą
byd obecne. N argumentów od arg1 do argN ma typ T, który odpowiada jednemu ze znaków lub
parze znaków wskazanych w Tabela 2. Jeśli ostatnim znakiem nie jest v, to N jest podane przez znaki
{1|2|3|4}
(jeśli ich nie ma, to liczba argumentów jest ustalona). Jeśli ostatnim znakiem jest v, to
tylko arg1 jest obecny i jest to tablica N wartości wskazanego typu. Argumenty, których typ jest
stały (tzn. nie wskazany przez sufiks polecenia) są jednym z typów danych OpenGL zawartych w
Tabela 1 lub wskaźnikami do jednego z tych typów.
Przykładowo grupa funkcji glUniform* w specyfikacji OpenGL jest opisywana następująco:
void Uniform{1234}{if}( int location, T value );
co wskazuje osiem deklaracji funkcji, przedstawionych poniżej w notacji ANSI C:
void Uniform1i( int location, int value );
void Uniform1f( int location, float value );
void Uniform2i( int location, int v0, int v1 );
void Uniform2f( int location, float v0, float v1 );
void Uniform3i( int location, int v0, int v1, int v2 );
void Uniform3f( int location, float v0, float v2, float v2 );
void Uniform4i( int location, int v0, int v1, int v2, int v3 );
void Uniform4f( int location, float v0, float v1, float v2,
float v3 );
W dalszej części kursu będziemy używad pełnych deklaracji funkcji w języku ANSI C, jednak
prezentacja zasad budowy poleceo OpenGL ułatwi Czytelnikowi samodzielne zgłębianie specyfikacji
biblioteki.
Zmienne stanu
Zmienne stanu (ang. state) są ważnym wewnętrznym elementem OpenGL opisującym
ustawienia biblioteki. Wiele zmiennych stanu jest dwustanowych, inne mają wartości całkowite lub
zmiennoprzecinkowe. Wszystkie zmienne stanu tworzą tzw. maszynę stanów (ang. state machine),
która steruje zachowaniem OpenGL.
Wyróżniamy dwa rodzaje zmiennych stanu. Pierwszy typ, zwany zmiennymi serwera GL,
znajduje się w serwerze GL. Większośd zmiennych stanu GL wchodzi w skład tej kategorii. Drugi typ,
zwany zmiennymi klienta GL, znajduje się w kliencie GL. W typowym przypadku klientem jest
aplikacja wykorzystująca polecenia OpenGL, a serwerem aktualnie używana implementacja biblioteki
(np. w sterowniku karty graficznej). Każdy egzemplarz kontekstu OpenGL zawiera jeden pełny zestaw
zmiennych stanu serwera GL. Natomiast każde połączenie od klienta do serwera zawiera odrębny
zbiór zmiennych stanu zarówno klienta jak i serwera OpenGL.
Zmienne stanu modyfikowane są, w sposób jawny lub nie, przez polecenia biblioteki OpenGL,
które będziemy poznawad w dalszych odcinkach kursu. Natomiast do odczytu zmiennych stanu służy
bardzo liczna grupa funkcji glGet*, z których najbardziej podstawowe są następujące funkcje:
void glGetBooleanv( GLenum pname, GLboolean *params );
void glGetIntegerv( GLenum pname, GLint *params );
void glGetInteger64v( GLenum pname, GLint64 *params );
void glGetFloatv( GLenum pname, GLfloat *params );
void glGetDoublev( GLenum pname, GLdouble *params );
Parametr pname określa, którą wartośd maszyny stanów OpenGL chcemy pobrad (wszystkie
zmienne stanu przedstawiamy w odrębnym odcinku kursu), a params wskaźnik na zwracaną
wartośd. W zależności od rodzaju pobieranej zmiennej tablica params może zawierad pojedynczą
zmienną lub tablicę zmiennych. Program musi zapewnid odpowiednią ilośd miejsca w pamięci.
Zauważmy, że rodzaj zwracanej lub zwracanych wartości jednoznacznie określa koocowa częśd nazwy
funkcji.
Obsługa błędów
Biblioteka OpenGL zawiera mechanizmy obsługi błędów. Jednak z założenia wykrywanych
jest tylko częśd sytuacji spośród zbioru warunków, które mogłyby byd uważane za błędy. Stanowi to
kompromis na rzecz efektywności, ponieważ w wielu przypadkach kontrola błędów miałby
niekorzystny wpływ na wydajnośd programu wolnego od błędów.
Informację o kodzie bieżącego błędu zwraca funkcja:
GLenum glGetError( void );
Zestawienie zwracanych kodów błędów zawiera Tabela 3. Działanie OpenGL jest
niezdefiniowane tylko w przypadku wystąpienia błędu o kodzie GL_OUT_OF_MEMORY. W
pozostałych przypadkach błąd nie powoduje przerwania wykonywania programu, nie jest
wykonywana jedynie funkcja odpowiedzialna za jego powstanie. Polecenie takie na ma wpływu na
stan OpenGL lub zawartośd bufora ramki. Jeśli polecenie generujące błąd zwraca wartości, zwracana
jest wartośd zero. Jeśli polecenie generujące błąd zmienia wartości poprzez wskaźnik do argumentu,
nie są dokonywane zmiany tych wartości.
Kod błędu
Opis
Czy błędne polecenie
jest ignorowane?
GL_INVALID_ENUM
Argument GLenum
poza zakresem
Tak
GL_INVALID_VALUE
Argument liczbowy
poza zakresem
Tak
GL_INVALID_OPERATION
Nielegalna operacja w
bieżącym stanie
Tak
GL_INVALID_FRAMEBUFFER_OPERATION Niekompletny obiekt
bufora ramki
Tak
GL_OUT_OF_MEMORY
Brak pamięci do
wykonania polecenia
Nieokreślone
GL_NO_ERROR
Brak błędu
-
Tabela 3 Zestawienie kodów błędów OpenGL
Kiedy błąd zostanie wykryty, ustawiana jest flaga błędu i rejestrowany jest jego kod. Jeżeli
występują kolejne błędy nie wpływają one na ten zarejestrowany kod. Dopiero, gdy wywołana jest
funkcja glGetError, zwracany jest kod błędu i czyszczona jest flaga, tak aby kolejny błąd mógł byd
zarejestrowany. Jeżeli glGetError zwraca GL_NO_ERROR oznacza to, że od ostatniego
wywołania glGetError (lub od kiedy GL został zainicjowany) nie był wykryty żaden błąd.
Implementacja może przechowywad więcej niż jedną informację o błędzie. W takim przypadku
wywołanie glGetError zwraca kody kolejnych zarejestrowanych błędów, tak długo aż nie zostanie
zwrócona wartośd GL_NO_ERROR.
Typowy kod odczytujący błędy zgłaszane przez bibliotekę OpenGL ma postad poniższej pętli:
GLenum error;
while( ( error = glGetError() ) != GL_NO_ERROR )
{
// obsługa błędu
}
Ogólne warunki generowania błędów przedstawione są poniżej:
Jeśli polecenie wymagające wartości wyliczeniowej określonej jako stała symboliczna
otrzymuje wartośd, która nie należy do dopuszczalnych dla tego polecenia, generowany jest
błąd GL_INVALID_ENUM.
Jeśli przekazywana jest ujemna liczba, a typ argumentu jest określony jako GLsizei lub
GLsizeiptr, zostanie wygenerowany błąd GL_INVALID_VALUE.
Jeśli ubocznym efektem wykonania polecenia jest wyczerpanie pamięci, może byd
wygenerowany błąd GL_OUT_OF_MEMORY.
W innych wypadkach, błędy generowane są tylko na warunkach, które są wyraźnie opisane w
specyfikacji biblioteki OpenGL.
Operacje OpenGL i potok renderingu
OpenGL koncentruje się jedynie na renderingu do bufora ramki oraz czytaniu wartości
zapisanych w tym buforze. Biblioteka nie ma wsparcia dla innych urządzeo, takich jak myszy i
klawiatury, stąd trzeba używad innych mechanizmów w celu przetworzenia informacji
wprowadzanych przez użytkownika.
Rysunek 1 przedstawia schemat działania OpenGL. Z lewej strony diagramu wprowadzane są
polecenia OpenGL. Niektóre z nich określają obiekty geometryczne, które mają zostad narysowane,
inne kontrolują obsługę obiektów przez różne etapy renderingu. Polecenia zawsze są przetwarzane w
kolejności, w jakiej zostały przekazane do OpenGL, a ewentualne opóźnienie realizacji skutków
danego polecenia nie mogą mied wpływu na wyniki późniejszych poleceo. To samo dotyczy operacji
pobierania zmiennych stanu zapytania i odczytu pikseli, które zwracają stan odpowiadający
kompletnemu wykonaniu wszystkich wcześniej wywoływanych poleceo OpenGL (poza przypadkami
wyraźnie określonymi w specyfikacji). OpenGL wiąże dane w chwili wywoływania polecenia, a
wszelkie późniejsze zmiany danych nie mają wpływu na jego wynik.
Rysunek 1 Diagram blokowy potoku renderingu OpenGL (źródło: specyfikacja OpenGL)
Pierwszy etap potoku renderingu operuje na prymitywach geometrycznych: punktach,
segmentach linii i wielokątach, które są opisane przez wierzchołki. W tym etapie wierzchołki są
przekształcane i oświetlane, a prymitywy są obcinane do bryły widoku w celu przygotowania do
następnego etapu, rasteryzacji. Możliwe jest także programowe generowanie nowych prymitywów
lub usuwanie istniejących. Rasteryzator generuje szereg fragmentów - elementów bufora ramki
używanych do dwuwymiarowego opisu punktów, segmentów linii lub wielokątów. Każdy tak
wyprodukowany fragment jest przekazywany do następnego etapu, gdzie wykonywane są operacje
na poszczególnych fragmentach przed finalnym zapisem w buforze ramki. Operacje na buforze ramki
obejmują m.in. obsługę wartości bufora głębokości, mieszanie kolorów oraz maskowanie i inne
operacje logiczne na wartościach fragmentów. Dane bufora ramki mogą byd również odczytywane
lub kopiowane pomiędzy różnymi częściami bufora ramki.
Poszczególne etapy potoku renderingu OpenGL poznamy bliżej w kolejnych odcinkach kursu.
Podstawowa zasada OpenGL - powtarzalność
Specyfikacja biblioteki OpenGL opisuje poszczególne operacje realizowane przez OpenGL, nie
zakłada jednak i nie zapewnia zgodności na poziomie pikseli pomiędzy różnymi jej implementacjami.
Poniżej krótko przedstawimy podstawową zasadę implementacji OpenGL – powtarzalnośd.
Powtarzalnośd jest rozumiana jako identycznośd zawartości bufora ramki przy takich samych
poleceniach i początkowych wartościach zmiennych stanu. Jest to szczególnie istotne przy typowych
w OpenGL technikach podwójnego buforowania i algorytmach wieloprzebiegowych, bowiem przy
braku powtarzalności każdy z tworzonych obrazów mógłby różnid się tworząc widoczne artefakty.
Powtarzalnośd ma także dalej idące znaczenie, przykładowo dwie sceny różniące się tylko niewielkim
przesunięciem wielokąta oczywiście nie będą identycznie wyrenderowane, ale zakładamy zgodnośd
tych fragmentów sceny, które nie obejmują pierwotnego i przesuniętego wielokąta. Oto podstawowe
zasady powtarzalności:
1. Dla danego kontekstu renderingu i zmiennych stanu bufora ramki dowolne polecenie
OpenGL da taki sam efekt koocowy w buforze ramki i wartościach zmiennych stanu.
2. Zmiany zawartości bufora ramki, buforów koloru do zapisu, parametrów testu nożycowego,
masek zapisu składowych oraz wartości czyszczących bufory nie mogą mied wpływów
ubocznych na inne zmienne stanu.
3. Specyfikacja zaleca także, aby zmiany parametrów bufora szablonowego, testu głębokości,
mieszania kolorów, operacji logicznych na pikselach, zapisu pikseli oraz przesuwania wartości
głębokości wielokątów nie miały wpływu ubocznego na inne zmienne stanu.
4. Generowanie fragmentu jest niezmienne w stosunku do zasad wymienionych w zasadzie 2 i
3.
5. Arytmetyka operacji na fragmentach jest niezmienna za wyjątkiem parametrów
bezpośrednio je kontrolujących.
6. Obrazy generowane w różnych buforach koloru współdzielących ten sam bufor ramki,
używające równocześnie lub oddzielnie tej samej sekwencji poleceo są identyczne na
poziomie pikseli.
7. Identyczny shader wierzchołków lub fragmentów (czyli zgodny na poziomie kodu
źródłowego) wykonywany wielokrotnie dla takich samych danych wejściowych i wartościach
zmiennych stanu generuje ten sam wynik.
8. Wszystkie shadery fragmentów, które warunkowo lub bezwarunkowo przypisują
gl_FragCoord.z do gl_FragDepth są niezmienne w zakresie wartości głębokości w
odniesieniu do każdego innego shadera, dla tych fragmentów, gdzie przypisanie do
gl_FragDepth zostało zrealizowane.
Chod powyższe zasady określone są dla implementatorów OpenGL warto o nich pamiętad i
wykorzystywad także w trakcie opracowywania programów. Częśd zasad odnosi się do szczegółów, z
którymi będziemy się bliżej zapoznawad w kolejnych odcinkach niniejszego kursu.
Przyjęte w kursie konwencje i zasady
Programy w kursie zasadniczo wymagają dostępności biblioteki OpenGL w wersji 3.2, ale w
wielu przypadkach wystarczy dostępnośd wersji 3.1 lub 3.0. Przy ich pisaniu wykorzystano nowe
funkcje zarządzające kontekstem OpenGL, co powoduje, że programy nie mogą byd bezpośrednio
uruchamiane na starszych niż 3.0 wersjach biblioteki. Jednakże częśd z nich, po odpowiednich
modyfikacjach, może działad z OpenGL 2.0/2.1. Programy przykładowe przy tworzeniu okien
korzystają bezpośrednio z Xlib (systemu UNIX i Linux) oraz API WIN32 (systemy Microsoft Windows).
Fragmenty programu specyficzne dla danego systemu operacyjnego są oddzielone od elementów
korzystających z biblioteki OpenGL, co ułatwi Czytelnikowi ewentualną migrację programu na
wybraną platformę systemową lub programową.
Wszystkie programy napisane są przy użyciu języka C++ i kompilowane za pomocą
kompilatorów: Microsoft Visual C++ 2008 EE, Turbo C++ 2006 Explorer oraz kompilatora GCC 4.4 w
systemie Linux Mandriva 2010. Programy testowano na karcie graficznej NVidia GeForce 8600GT.
Do kompilacji programów nie są wymagane żadne dodatkowe zewnętrzne biblioteki. Jednak
najczęściej używane funkcje zostały pogrupowane w odrębne pliki, które mogą stanowią przydatną
podręczną bibliotekę.