This is the Polish translation of Getting-started/OpenGL article of learnopengl.com tutorial series.
Zanim zaczniemy, musimy najpierw zdefiniować czym właściwie jest OpenGL. OpenGL jest głównie postrzegany jako API (ang. Application Programming Interface), które dostarcza duży zbiór funkcji pozwalających na manipulowanie obrazami i wyświetlaną grafiką. Jednakże, OpenGL nie jest sam w sobie API, jest to jedynie specyfikacja rozwijana i utrzymywana przez konsorcjum Khronos Group.
Specyfikacja OpenGL dokładnie definiuje, co powinno być wynikiem każdej z funkcji oraz jak każda funkcja powinna się zachowywać, co robić. Zadaniem programisty sterowników jest implementacja tego w jaki dokładnie sposób dana funkcja powinna działać - jaki zestaw instrukcji powinien zostać wykonany. Skoro specyfikacja OpenGL nie podaje szczegółów dot. implementacji, to różne wersje sterowników OpenGL mogą być zaprogramowane w zupełnie inny sposób, o ile zwracają te same rezultaty i zachowują się tak samo jak zostało to ujęte w specyfikacji.
Ludzie zajmujący się programowaniem biblioteki OpenGL są przeważnie osobami pracującymi w firmach zajmujących się tworzeniem kart graficznych. Każda karta graficzna, którą można kupić wspiera konkretne wersje OpenGL, które to wersje są rozwijane dla tej karty (serii kart). Używając systemu Apple’a biblioteka OpenGL jest utrzymywana przez Apple, podczas gdy pod Linux’em istnieje wiele dostawców sterowników i hobbystów, którzy zajmują się rozwijaniem tych bibliotek. Może to również oznaczać, że jeżeli OpenGL wykazuje dziwne zachowanie (nie zachowuje się tak jak w specyfikacji), jest to najprawdopodobniej wina twórców sterowników.
Skoro większość implementacji OpenGL jest tworzona przez producentów kart graficznych, to kiedy jest jakiś błąd w implementacji sterownika to jest on zazwyczaj rozwiązywany przez zaktualizowanie sterowników karty graficznej; te sterowniki zawierają najnowsze wersje OpenGL, które wspiera Twoja karta. To jest jeden z powodów, dla których zawsze dobrze jest aktualizować sterowniki karty graficznej.
Khronos publicznie udostępnia wszystkie specyfikacje dla wszystkich wersji OpenGL. Zainteresowani czytelnicy mogą znaleźć specyfikację OpenGL wersji 3.3 (której będziemy używać) tutaj, którą jest dobrze przeczytać jeżeli chcesz zagłębić się w szczegóły OpenGL (zauważ, że większość z nich opisuje działanie, a nie implementację). Specyfikacje są również znakomitym źródłem informacji na temat dokładnego działa funkcji OpenGL.
Core-profile vs Tryb natychmiastowy (Immediate mode)
W dawnych czasach, używanie OpenGL oznaczało programowanie w trybie natychmiastowym (często określanym jako stały potok), który był łatwą w użyciu metodą rysownia grafiki. Większość funkcjonalności OpenGL została ukryta w bibliotece, a programiści nie posiadali zbyt dużo swobody w tym jak OpenGL przeprowadzał kalkulacje. W końcu programiści poczuli głód większej swobody i z upływem czasu specyfikacja stała się bardziej elastyczna; programiści dostali więcej kontroli nad tym jak ich grafika powinna zostać wyświetlona. Tryb natychmiastowy jest naprawdę łatwy w użyciu i zrozumieniu, ale jest też bardzo niewydajny. Z tego powodu specyfikacja zaczęła oznaczać funkcje z trybu natychmiastowego jako przestarzałe od wersji 3.2 i zaczęła zachęcać programistów do budowania aplikacji z wykorzystaniem core-profile OpenGL, który usunął wszystkie przestarzałe funkcje.
Podczas używania core-profile, OpenGL wymusza na nas używania nowoczesnych praktyk. Za każdym razem kiedy próbujemy użyć przestarzałej funkcji, OpenGL wyrzuca błąd i przestaje rysować. Zaletą nauczenia się nowoczesnego podejścia jest to, że jest wydajne i daje programiście dużo elastyczności, ale jest też trudniejsze do opanowania. Tryb natychmiastowy ukrywał dosyć dużo z faktycznych operacji jakie OpenGL wykonywał i o ile było to łatwe do nauczenia, to było trudno zrozumieć jak właściwie OpenGL działał. Nowoczesne podejście wymaga od programisty prawdziwego zrozumienia działania OpenGL i programowania grafiki i o ile jest to trochę trudniejsze do nauczenia się, to daje dużo więcej swobody, jest bardziej wydajne i co najważniejsze daje większe zrozumienie tego jak programować grafikę.
Jest to również powód, dla którego nasze kursy są skoncentrowane na Core-Profile OpenGL w wersji 3.3. Chociaż jest to trudne, to jest warte włożenia w to wysiłku.
Obecnie dostępne są nowsze wersje OpenGL (w czasie pisania artykuły wersja 4.5). Można by zapytać: dlaczego mam się uczyć OpenGL 3.3 skoro dostępna jest wersja 4.5? Odpowiedź na to pytanie jest relatywnie prosta. Wszystkie przyszłe wersje OpenGL poczynając od wersji 3.3 dodają nowe, użyteczne funkcjonalności do OpenGL bez wprowadzania zmian w rdzennych mechanikach działania OpenGL; nowsze wersje po prostu wprowadzają trochę bardziej wydajne lub bardziej użyteczne sposoby rozwiązywania tych samych zadań. W rezultacie wszystkie koncepty, idee i techniki pozostają te same dla wszystkich nowoczesnych wersji OpenGL, a więc jest to dobre, by zacząć naukę OpenGL od wersji 3.3. Kiedy tylko będziesz czuł się gotowy lub bardziej doświadczony, będziesz mógł bez większych problemów użyć konkretnych funkcjonalności z nowszych wersji OpenGL.
Używanie najnowszych funkcjonalności z najnowszych wersji OpenGL wymaga nowoczesnych kart graficznych, które będą w stanie obsłużyć wybraną wersję OpenGL. Większość producentów GPU implementuje starsze wersje OpenGL i opcjonalnie aktywują funkcjonalności z nowszych wersji.
W niektórych częściach tego kursu możesz się natknąć na bardziej nowoczesne funkcjonalności.
Rozszerzenia
Bardzo fajną cechą OpenGL jest jego wsparcie dla rozszerzeń. Gdy jakaś firma (przeważnie producenci kart graficznych) wpadnie na pomysł nowej techniki renderowania lub nowej, dużej optymalizacji procesu renderingu to z reguły można to znaleźć w rozszerzeniu (ang. extension), które jest zaimplementowane w sterowniku.
Jeżeli sprzęt, na którym jest uruchamiana aplikacja, wspiera dane rozszerzenie, programista może używać funkcjonalności oferowanych przez to rozszerzenie, które pozwolą mu na bardziej zaawansowane lub wydajne użycie karty graficznej. Dzięki temu, programista może używać wcześniej tych funkcjonalności bez potrzeby czekania, aż pojawią się one w przyszłych wersjach OpenGL. Wystarczy, że jego aplikacja sprawdzi czy dane rozszerzenie jest wspierane przez daną kartę graficzną. Często bywa tak, że jeżeli dane rozszerzenie jest popularne lub bardzo użyteczne to z reguły staje się w przyszłości częścią OpenGL.
Programista musi “zapytać” sprzęt czy dane rozszerzenie, którego chce użyć jest dostępne na danej karcie graficznej (można w tym celu również użyć biblioteki do rozszerzeń OpenGL). Pozwala to programiście na stworzenie lepszych lub bardziej wydajnych funkcji, bazując na tym czy dane rozszerzenie jest dostępne:
if(GL_ARB_extension_name)
{
// Można robić nowe, wspaniałe i nowoczesne rzeczy!
}
else
{
// Rozszerzenie nie jest wspierane... Trzeba robić "po staremu"...
}
Podczas używania OpenGL 3.3 będziemy rzadko korzystać z rozszerzeń dla większości technik, ale kiedy będzie to niezbędne, będzie podana odpowiednia instrukcja jak to zrobić.
Maszyna stanów
OpenGL sam w sobie jest wielką maszyną stanów: zbiorem zmiennych, które definiują to w jaki sposób OpenGL działa. Stan, w którym obecnie znajduje się OpenGL jest nazywany kontekstem (ang. context). Kiedy korzystamy z OpenGL, często zmieniamy jego stan poprzez ustawianie pewnych opcji, manipulując buforami, by następnie narysować coś, używając obecnego kontekstu.
Za każdym razem kiedy mówimy OpenGL, że teraz zamiast rysowania linii chcemy rysować trójkąty, to zmieniamy stan OpenGL poprzez zmianę zmiennych zawartych w kontekście, które są odpowiedzialne za to w jaki sposób OpenGL powinien rysować. Gdy tylko zmienimy stan, mówiąc OpenGL o tym, że powinien rysować linie, to następne komendy odpowiadające za rysowanie będą rysować linie zamiast trójkątów.
Podczas pracy z OpenGL natkniemy się na kilka funkcji zmieniających stan (ang. state-changing), które zmieniają kontekst i na kilka funkcji korzystających z danego stanu (ang. state-using), które wykonują pewne operacje bazując na obecnym stanie OpenGL. Dopóki będziesz zawsze pamiętał o tym, że OpenGL to wielka maszyna stanów, to większość tych funkcjonalności będzie miała dla Ciebie więcej sensu.
Obiekty
API OpenGL jest napisane w C i pozwala to na używanie go w wielu innych językach programowania, ale rdzeń tego API pozostaje biblioteką napisaną w C. Z powodu tego, że dużo konstrukcji języka C nie przekłada się dobrze na mechanizmy w innych językach wysokopoziomowych, to OpenGL został rozwijany biorąc pod uwagę kilka abstrakcji. Jedną z tych abstrakcji są obiekty w OpenGL.
Obiekt w OpenGL jest zbiorem opcji, które reprezentują podzbiór stanów OpenGL. Na przykład, moglibyśmy mieć obiekt reprezentujący ustawienia okna, w którym będziemy rysować; moglibyśmy zatem ustawić jego rozmiar, ile wspiera kolorów itp. Można by było to zwizualizować za pomocą struktury C/C++:
struct object_name {
float option1;
int option2;
char[] name;
};
Kiedy chcemy użyć obiektu, ogólnie wygląda to mniej więcej tak (kontekst OpenGL zwizualizowany jako duża struktura):
// Stan OpenGL
struct OpenGL_Context {
...
object* object_Window_Target;
...
};
// Stwórz obiekt
GLuint objectId = 0;
glGenObject(1, &objectId);
// Podepnij obiekt pod kontekts OpenGL
glBindObject(GL_WINDOW_TARGET, objectId);
// Ustaw opcje obiektu, który jest obecnie podpięty pod GL_WINDOW_TARGET
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
//Ustaw cel (ang. target) kontekstu na wartość domyślną
glBindObject(GL_WINDOW_TARGET, 0);
Ten mały kawałek kodu przedstawia częsty sposób pracy w OpenGL. Na początku tworzymy obiekt i zapisujemy do niego referencję w postaci ID tego obiektu (prawdziwy obiekt jest schowany przed użytkownikiem). Następnie podpinamy obiekt do lokacji celu (ang. target) konkretnego kontekstu (lokacja przykładowego celu obiektu okna jest zdefiniowana jako GL_WINDOW_TARGET). Następnie ustawiamy opcje okna i na końcu odpinamy obiekt poprzez ustawienie celu na obiekt o ID równego 0. Opcje, które ustawiamy są przechowywane w obiekcie, do którego odwołujemy się za pomocą objectId.
Przykładowe kody, które zostały przedstawione są tylko uogólnieniem tego jak OpenGL działa; w dalszej części kursu spotkasz prawdziwe przykłady.
Obiektów w naszej aplikacji można mieć więcej niż jeden, możemy ustawiać ich parametry i kiedy chcemy coś zrobić, co wymaga użycia stanu OpenGL, podpinamy obiekt, który ma zdefiniowane preferowane parametry. Istnieją obiekty, które zachowują się jak kontenery na obiekty 3D (dom lub postać) i kiedy chcemy je narysować, podpinamy obiekt przechowujący dane tego modelu, który chcemy teraz narysować. Posiadając kilka obiektów, możemy wcześniej zapełnić je danymi i ustawić im odpowiednie opcje, a kiedy chcemy je narysować wystarczy, że podepniemy je pod kontekst i nie musimy ustawiać im wcześniej ustawionych opcji.
Zaczynamy!
Nauczyłeś się już trochę na temat tego jak w przybliżeniu działa OpenGL. Nie przejmuj się jeżeli teraz wszystkiego nie zrozumiałeś; podczas przechodzenia kolejnych kursów będziemy krok po kroku przechodzić przez przykłady, by zrozumieć jak to wszystko działa. Jeżeli jesteś gotowy to możemy przejść do kolejnego kroku, w którym stworzymy kontekst OpenGL i pierwsze okienko.
Dodatkowe materiały
- opengl.org: oficjalna strona OpenGL.
- OpenGL registry: udostępnia specyfikacje dla wszystkich wersji OpenGL i jego rozszerzeń.