Kontekst OpenGL Biblioteki pomocnicze nowoczesnej OpenGL

Mirosław Głowacki 1

1Akademia Górniczo-Hutnicza im. Stanisława Staszica w Krakowie Wydział Inżynierii Metali i Informatyki Stosowanej Katedra Informatyki Stosowanej i Modelowania

Marzec 2017 / Październik 2019

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 1 / 30 Spis treści

1 Wstęp

2 Definicja kontekstu

3 Ustawienia wstępne systemu OpenGL

4 Biblioteka SFML

5 Dopasowanie kodu do karty graficznej

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 2 / 30 Spis treści

1 Wstęp

2 Definicja kontekstu

3 Ustawienia wstępne systemu OpenGL

4 Biblioteka SFML

5 Dopasowanie kodu do karty graficznej

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 3 / 30 Wstęp

Ta część wykładu ma na celu naukę podstaw OpenGL w wersji 3.2 / 3.3 - specyfikacji służącej do rozwijania nowoczesnych aplikacji graficznych. Nie będziemy omawiać żadnej ze starych części specyfikacji OpenGL - w tym często omawianej wersji 2.1 Oznacza to, że będziemy próbować pracować bez korzystania z nieaktualnych funkcji, takich jak glBegin czy glLight. Wszystko, co nie jest bezpośrednio związane z OpenGL, np. tworzenie okna, wczytywanie tekstur z plików, itp zostanie wykonane przy użyciu kilku małych bibliotek. Aby pokazać, jak bardzo opłaca się programować samemu, posłużymy się szeregiem interaktywnych przykładów łatwych i przyjemnych do nauki Będą one poruszać różne aspekty korzystania z niskopoziomowej biblioteki graficznej OpenGL!

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 4 / 30 Wymagania Zanim będziemy mogli wystartować, trzeba upewnić się, czy mamy wszystko, co jest potrzebne: 1 Rozsądne doświadczenie w programowaniu ++, 2 Kartę graficzną kompatybilną z OpenGL 3.2 (lub wyższą wersją), 3 jedną z bibliotek - każdy wybierze dla siebie najlepszą: SFML (Simple and Fast Multimedia Library) - polecana dla programistów C++, GLFW (proste API do tworzenia okien), lub SDL (Simple DirectMedia Layer) do tworzenia kontekstu i obsługi wejścia - różnice między nimi zostaną wyjaśnione później. 4 bibliotekę GLEW (OpenGL Extension Wrangler Library), która pozwoli na używanie nowszych funkcji OpenGL, 5 bibliotekę SOIL (Simple OpenGL Image Library) do obsługi tekstur, 6 bibliotekę GLM (OpenGL Mathematics) do operacji na wektorach i macierzach. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 5 / 30 Spis treści

1 Wstęp

2 Definicja kontekstu

3 Ustawienia wstępne systemu OpenGL

4 Biblioteka SFML

5 Dopasowanie kodu do karty graficznej

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 6 / 30 Kontekst OpenGL

Przed rozpoczęciem rysowania, trzeba zainicjować OpenGL. Odbywa się to poprzez stworzenie kontekstu OpenGL.

Kontekst jest zasadniczo maszyną stanu, która przechowuje wszystkie dane związane z wyświetlaniem aplikacji.

Gdy aplikacja jest zamykana, kontekst OpenGL jest niszczony.

Problemem jest to, że tworzenie oknai kontekstu OpenGL nie jest częścią specyfikacji OpenGL .

To oznacza, że odbywa się to inaczej na każdej platformie.

Tworzenie aplikacji z wykorzystaniem OpenGL zapewnia przenośność, więc kontekst musi być dopasowany do środowiska programistyczno-sprzętowego.

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 7 / 30 Kontekst OpenGL

Na szczęście istnieją biblioteki zewnętrzne, umożliwiające ten proces, tak aby można było użyć tego samego kodu bazowego dla wszystkich obsługiwanych platformach.

Chociaż dostępne biblioteki mają swoje wady i zalety, umożliwiają pewien określony przebieg programu:

1 Na początku określane są właściwości okna renderingu, takie jak: tytuł oraz właściwości kontekstu OpenGL. 2 Następnie aplikacja inicjalinizuje pętlę zdarzeń , która zawiera zestaw zadań, które będą wykonywane w pętli do momentu zamknięcia okna. 3 Zadania te zwykle obsługują dla okna takie zdarzenia jak: kliknięcia myszą, aktualizacja stanu renderingu i aktualizacja rysunku.

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 8 / 30 Pseudokod aplikacji OpenGL

Pseudokod opisujący ten schemat wygląda następująco: #include int main(){ createWindow(title, width, height); createOpenGLContext(settings);

while (windowOpen){ while (event= newEvent()) handleEvent(event); updateScene(); drawGraphics(); presentGraphics(); } return0; }

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 9 / 30 Pseudokod aplikacji OpenGL

Podczas renderowania ramki, wyniki zostają zapisane w buforze znanym jako tylny, którego zawartość nie podlega wyświetlaniu. Funkcja presentGraphics() powoduje przepisanie zawartości tylnego bufora do bufora przedniego - którego zawartość jest wyświetlana w oknie renderingu. Każda aplikacja korzystająca z grafiki w czasie rzeczywistym charakteryzuje sie takim schematem programu, niezależnie do tego, czy korzysta z bibliotek czy z kodu natywnego. Wspieranie skalowania okien OpenGL wprowadza pewne komplikacje, dotyczące przeładowywania zasobówi odtworzenia buforów w celu dopasowania się do rozmiaru nowego okna. Na obecnym etapie wygodne będzie nieprzejmowanie sie takimi szczegółami, więc będziemy pracować z oknami o stałym rozmiarze pełnego ekranu.

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 10 / 30 Kontekst OpenGL

Domyślnie biblioteki tworzą kontekst OpenGL, który obsługuje starsze wersje funkcji, aby zabezpieczyć wsteczną kompatybilność. Jest to niefortunne i nowe programy nie powinny wykorzystywać takich funkcji, które mogą stać się niedostępne w przyszłości. Dobrą wiadomością jest to, że można poinformować sterowniki, że aplikacja jest gotowa na przyszłośći nie korzysta z dawnych funkcji. Złą wiadomością jest to, że w tej chwili tylko biblioteka GLFW pozwala to określić. Ta mała wada nie ma żadnych negatywnych skutków w tej chwili, więc nie powinna mieć zbyt dużego wpływu na wybór biblioteki. Jednakże zaletą tak zwanego rdzennego profilu kontekstu jest to, że użyte przez przypadek stare funkcje generują błąd: "invalid operation error".

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 11 / 30 Spis treści

1 Wstęp

2 Definicja kontekstu

3 Ustawienia wstępne systemu OpenGL

4 Biblioteka SFML

5 Dopasowanie kodu do karty graficznej

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 12 / 30 Ustawienia wstępne

Pierwszą rzeczą, którą należy zrobić, gdy zaczyna się nowy projekt OpenGL jest dynamiczne połączenie z OpenGL. Akcja taka jest zależna od platformy programistyczno sprzętowej: w przypadku systemu Windows należy dodać opengl32.lib do opcji linkera, w przypadku systemu Linux trzeba uzupełnić opcje kompilatora o -lGL , w przypadku macOS trzeba dodać -framework OpenGL do opcji kompilatora. Uwaga dla programistów systemu Windows Nie należy dołączać biblioteki opengl32.lib do aplikacji, ponieważ plik ten jest już dołączony do systemu, a jego wersje mogą się różnić - dołączenie aktualnej wersji może powodować problemy na innych komputerach.

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 13 / 30 Ustawienia wstępne

Pozostałe kroki zależą od biblioteki, jaką wybrano do tworzenia okna i kontekstu. Istnieje wiele bibliotek, które mogą tworzyć okna i towarzyszący im kontekst OpenGL, przy czym nie ma jednej najlepszej biblioteki, bo każdy programista ma inne potrzeby i ideały

Omówione zostaną podstawy trzech najpopularniejszych bibliotek - dla każdej z nich można znaleźć bardziej szczegółowe przewodniki na stronach internetowych.

Prezentowany w następnych wykładach kod będzie niezależnyod wyboru biblioteki.

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 14 / 30 Biblioteka SFML

SFML jest wieloplatformową biblioteką multimedialną języka C++ , która zapewnia dostęp do: grafiki, wejścia, audio, sieci i systemu. Wadą korzystania z tej biblioteki jest to, że stara się być rozwiązaniem typu all-in-one i tylko w niewielkim stopniu pozwala na kontrolę tworzenia kontekstu OpenGL, gdyż została zaprojektowana do użytku z własnym zestawem funkcji rysowania.

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 15 / 30 Biblioteki SDL i GLFW

SDL jest również wieloplatformową biblioteką multimedialną, ale ukierunkowanych na język C . To sprawia, że jest nieco mniej wygodna dla programistów C++, ale jest to doskonała alternatywa dla SFML. Wspiera bardziej egzotyczne platformy i co najważniejsze, zapewnia większą kontrolę nad tworzeniem kontekstu OpenGL niż SFML. GLFW jest biblioteką języka C specjalnie zaprojektowaną do użytku z OpenGL. W przeciwieństwie do SDL i SFML obsługuje jedynie niezbędne funkcje: tworzenie okna i kontekstu renderingu, zarządzanie urządzeniami wejściowymi.

Spośród trzech bibliotek GLFW oferuje największą kontrolę nad tworzeniem kontekstu OpenGL .

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 16 / 30 Spis treści

1 Wstęp

2 Definicja kontekstu

3 Ustawienia wstępne systemu OpenGL

4 Biblioteka SFML

5 Dopasowanie kodu do karty graficznej

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 17 / 30 Biblioteka SFML

Stosując bibliotekę SFML kontekst OpenGL jest tworzony domyślnie podczas otwierania nowego okna - otwarcie okna to wszystko, co należy zrobić. SFML ma swój własny pakiet graficzny, ale ponieważ mamy zamiar używać OpenGL bezpośrednio, więc nie będziemy go wykorzystywać. Bibliotekę można pobrać w postaci plików binarnych pakietu SFML lub skompilować pakiet samodzielnie - w obu przypadkach potrzebne pliki znajdują się w katalogach lib i include - następnie należy: dodać katalog include do folderu, w którym kompilator poszukuje plików nagłówkowych, dodać katalog lib do folderu, w którym linker poszukuje plików bibliotecznych, w ustawieniach linkera należy dołączyć biblioteki sfml-system i smfl-window do listy bibliotek linkera. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 18 / 30 Kodowanie grafiki z użyciem SFML

Aby sprawdzić czy wszystko zostało zrobione poprawnie należy skompilować i uruchamić następujący kod: #include int main() { sf::sleep(sf::seconds(1.f)); return0;} O poprawności świadczy pojawienie się okna aplikacji konsolowej na sekundę, po czym program powinien się zakończyć. Aplikację OpenGL rozpoczynamy od przyłączenia pakietu window i zdefiniowania funkcji main() .

#include int main() { return0;}

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 19 / 30 Kodowanie grafiki z użyciem SFML

Okno renderingu pojawia się po utworzeniu nowej instancji klasy sf::Window , po wcześniejszym ustawieniu opcji kontekstu. sf::ContextSettings settings; settings.depthBits= 24; settings.stencilBits=8; settings.antialiasingLevel=2; // Opcjonalnie

sf::Window window(sf::VideoMode(800, 600), "OpenGL", sf::Style::Close, settings);

Parametrami podstawowego konstruktora tej klasy są: klasa sf::VideoMode oraz tytuł i styl okna. Klasa sf::VideoMode określa szerokość, wysokość i opcjonalnie głębokość bitową( bitPerPixel) okna.

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 20 / 30 Kodowanie grafiki z użyciem SFML

Wymóg stałego rozmiaru okna jest określony przez parametr Style::Close przesłaniający domyślną jego wartość: Style::Resize|Style::Close . Możliwe jest również okno pełnoekranowe, gdy konstruktorowi przekażemy styl Style::Fullscreen . Konstruktor może mieć jeszcze jeden parametr w postaci instancji klasy sf::ContextSettings . Pozwala on określić: poziom antyaliasingu oraz dokładność i głębokość buforów: szablonowegoi głębokości. Praca z buforami zostanie omówiona później, więc teraz nie ma się czym martwić.

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 21 / 30 Kodowanie grafiki z użyciem SFML

Po uruchomieniu dotychczasowego kodu można zauważyć, że aplikacja zamyka się natychmiast po utworzeniu okna - aby temu zaradzić należy dodać pętlę zdarzeń:

bool running= true; while (running){ sf::Event windowEvent; while (window.pollEvent(windowEvent)){ ... } } Kiedy cokolwiek dzieje się z oknem, zdarzenie powodujące zmianę zostaje dodane do kolejki zdarzeń. Istnieje wiele różnych zdarzeń, takich jak: zmiana rozmiaru okna, ruch myszy czy naciśnięcie klawisza klawiatury.

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 22 / 30 Kodowanie grafiki z użyciem SFML

Od programisty zależy, które zdarzenia zostaną obsłużone, ale jest co najmniej jedno, które musi być obsługiwane aby aplikacja działała dobrze. switch (windowEvent.type) { case sf::Event::Closed: running= false; break; } Gdy użytkownik próbuje zamknąć okno zdarzenie Closed uruchamia operację wyjścia z aplikacji. Usunięcie tej linii uniemożliwia zamknięcie okna przy użyciu zwykłych metod.

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 23 / 30 Kodowanie grafiki z użyciem SFML

Tryb pełnoekranowy wymaga dodatkowo obsługi klawisza Esc w celu zamknięcia okna. case sf::Event::KeyPressed: if (windowEvent.key.code == sf::Keyboard::Escape) running= false; break; W ten sposób powstał program tworzący okno renderingu i obsługujący ważne zdarzenia, a jednocześnie przygotowany do umieszczania na ekranie obiektów graficznych. Po narysowaniu czegokolwiek należy jednak zamienić miejscami bufory: tylnyi przedni komendą window.display() .

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 24 / 30 Kodowanie grafiki z użyciem SFML

Po uruchomieniu aplikacji, powinno pojawić się puste okno:

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 25 / 30 Spis treści

1 Wstęp

2 Definicja kontekstu

3 Ustawienia wstępne systemu OpenGL

4 Biblioteka SFML

5 Dopasowanie kodu do karty graficznej

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 26 / 30 Dostosowanie kodu do kartu graficznej

Przygotowany kontekst nie pozwala jeszcze na używanie niezbędnych funkcji OpenGL. To dlatego, producenci kart graficznnych mają obowiązek realizacji funkcji OpenGL wspieranych przez kartę w postaci odpowiednich sterowników. Aby program nie był kompatybilny jedynie z daną wersją sterownika i karty graficznej, należy zrobić coś mądrego. W czasie wykonywania program musi sprawdzić, które funkcje są dostępnei przyłączyć je dynamicznie. Odbywa się to poprzez: znalezienie adresów funkcji, przypisanie ich do wskaźników funkcji wywoływanie funkcji.

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 27 / 30 Dostosowanie kodu do kartu graficznej

Może to wyglądać mniej więcej tak:

// Specyfikacja prototypu funcji typedef void(*GENBUFFERS) (GLsizei, GLuint*); // Zaladowanie adresu funkcji // i przypisanie go do wskaznika funkcji: GENBUFFERS glGenBuffers= (GENBUFFERS)wglGetProcAddress( "glGenBuffers"); // Windows GENBUFFERS glGenBuffers= (GENBUFFERS)glXGetProcAddress( (const GLubyte*) "glGenBuffers"); // Linux GENBUFFERS glGenBuffers= (GENBUFFERS)NSGLGetProcAddress( "glGenBuffers"); // MacOS // Wywolanie funkcji w standardowy sposob GLuint buffer; glGenBuffers(1,&buffer);

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 28 / 30 Biblioteka GLEW

To żmudne i nieefektywne, ale istnieją biblioteki, które rozwiązaują ten problem za nas. Najbardziej popularną i najlepiej obecnie rozwijaną jest biblioteka GLEW Przyłaczenie GLEW jest analogiczne jak przyłączenie SFML. Należy zapewnić powiązanie projektu ze statyczną biblioteką GLEW umieszczoną w folderze lib - jest to albo glew32s.lib albo GLEW w zależności od platformy. Dodać folder include do ścieżki przeglądanej przez kompilator. Teraz po prostu trzeba dodać odpowiedni nagłówek do pliku prograniu, przed nagłówkami biblioteki OpenGL i/lub przed nagłówkami biblioteki używanej do tworzenia okna.

#define GLEW_STATIC #include

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 29 / 30 Biblioteka GLEW

Po utworzeniu okna i kontekstu OpenGL należy wywołać funkcję glewInit() .

glewExperimental= GL_TRUE; glewInit(); Linia glewExperimental jest konieczna, aby zmusić GLEW do używania nowoczesnych metod OpenGL do sprawdzania, czy funkcja jest dostępna. Można teraz sprawdzić poprawność ustawień projektu poprzez wywołanie funkcji glGenBuffers , która została załadowana przez GLEW - poniższy kod powinien wypisać na ekranie 1

GLuint vertexBuffer; glGenBuffers(1,&vertexBuffer); cout << vertexBuffer;

Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 30 / 30