Nowoczesna OpenGL - rendering wielokątów Rendering wielokątów w kontekście biblioteki SFML Mirosław Głowacki 1 1 Akademia Górniczo-Hutnicza im. Stanisława Staszica w Krakowie Wydział Inżynierii Metali i Informatyki Stosowanej Katedra Informatyki Stosowanej i Modelowania Marzec 2017 Mirosław Głowacki (AGH, UJK) OpenGL 2017 1 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 2 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 3 / 79
Potok OpenGL Przetwarzanie strumienia grafiki komputerowej przy użyciu shaderów obejmuje następujące po sobie etapy przetwarzania danych wejściowych w celu uzyskania obrazu końcowego - poszczególne kroki ilustruje rysunek: Mirosław Głowacki (AGH, UJK) OpenGL 2017 4 / 79
Wierzchołki Tworzenie obrazu przy użyciu OpenGL zaczyna się od utworzenia zbioru wierzchołków (ang. vertex). Wierzchołkami nazywamy punkty, z których kształtuje się trójkąty i inne wielokąty. Każdy z tych punktów jest przechowywany z pewnymi atrybutami i to do programisty należy decydzja, jakie cechy należy przechowywać. Powszechnie stosowane są atrybuty pozycji 3D we współrzędnych świata oraz współrzędne tekstury. Wierzchołki są danymi wejściowymi shadera wierzchołków - części oprogramowania sprzętowego karty graficznej. Shader wierzchołków to niewielki program uruchamiony na karcie graficznej, który przetwarza każdy z wierzchołków indywidualnie Mirosław Głowacki (AGH, UJK) OpenGL 2017 5 / 79
Shader wierzchołków Shader wierzchołków to miejsce, gdzie odbywa się przekształcanie pozycji wierzchołków: z trójwymiarowego układu współrzędnych świata do znormalizowanego układu urządzenia NDC. Ważne atrybuty wierzchołków, takie jak kolor czy współrzędne zaczepienia tekstury, przechodzą dalej wzdłuż strumienia grafiki. Z tak przekształconych wierzchołków karta graficzna będzie tworzyć trójkąty, linie lub punkty w procesie zwanym shape assembly. Utworzone prymitywy stanowią podstawę złożonych kształtów. Do wyboru jest kilka dodatkowych trybów rysowania, takich jak: pasma trójkątów (ang. triangle strips ), i linii (ang. line strips ). Umożliwia to ograniczenie liczby przekazywanych wierzchołków dla obiektów, dla których każdy następny prymityw jest połączony z poprzednim - np. ciągła linia składająca się z kilku segmentów. Mirosław Głowacki (AGH, UJK) OpenGL 2017 6 / 79
Shader geomertii Kolejnym krokiem przetwarzania strumienia grafiki jest shader geometrii (ang. geometry shader ), który jest całkowicie opcjonalny i został wprowadzony do użytku dopiero niedawno. W przeciwieństwie do shadera wierzchołków shader geometrii może generować i przekazać dalej dodatkowe informacje. Wejściowe prymitywy z etapu shape assembly mogą być: przekazywane dalej w dół strumienia grafiki bez zmian, modyfikowane przed przekazaniem, w całości odrzucone, zastąpione innymi prymitywnami. Ponieważ komunikacja pomiędzy GPU a resztą komputera jest stosunkowo wolna, etap ten może zmniejszyć rozmiar danych, które muszą zostać przekazane dalej. Przykładowo kostki voxeli mogą być tworzone w shaderze geometrii na bazie wierzchołków punktowych i ich współrzędnych i atrybutów oraz przekazywane dalej zamiast wierzchołków. Mirosław Głowacki (AGH, UJK) OpenGL 2017 7 / 79
Shader fragmentów Po tym, jak ostateczna lista kształtów jest kompletna i dostosowana do współrzędnych ekranu, rasteryzator konwertuje widoczne elementy kształtów na zbiór fragmentów wielkości piksela. Atrybuty wierzchołków pochodzące z shadera wierzchołków lub shadera geometrii są interpolowane dla każdego fragmentu i przekazywane jako dane wejściowe do shadera fragmentów. Kolory są równomiernie interpolowane dla każdego z fragmentów, które tworzą trójkąt, choć zostały określone jedynie dla trzech punktów stanowiących wierzchołki trójkąta. Shader fragmentów przetwarza pojedynczo każdy fragment wraz z jego interpolowanymi atrybutami i określa jego ostateczny kolor poprzez: pobranie próbki z tekstury zaczepionej w wierzchołkach lub proste przekazanie koloru fragmentu. Mirosław Głowacki (AGH, UJK) OpenGL 2017 8 / 79
Mieszanie fragmentów i testowanie buforów W bardziej zaawansowanych scenariuszach, są również kalkulowane: oświetlenie i cieniowanie, efekty specjalne, możliwość opuszczenia fragmentu, co oznacza, że kształt będzie w tym miejscu przezroczysty. Ostatecznie, efekt końcowy jest budowany ze wszystkich fragmentów kształtu przez: mieszanie ich ze sobą, testowanie bufora głębokości dla każdego fragmentu, testowanie bufora szablonowego dla poszczególnych fragmentów. Negatywny wynik jednego z powyższych testów powoduje odrzucenie fragmentu. Na przykład, jeśli jeden trójkąt zasłonia inne trójkąty, to wynik testowania bufora głębokści jest dla fragmentów tych innych trójkątów negatywny. Mirosław Głowacki (AGH, UJK) OpenGL 2017 9 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 10 / 79
Obiekty sceny graficznej Na początek należy określić dane niezbędne do tego, aby karta graficzna zwróciła prawidłową scenę. Jak już wspomniano, te dane to atrybuty wierzchołka. Istnieje dowolność w przekazywaniu atrybutów, ale podstawowe i zawsze wymagane to pozycja we współrzędnych świata - niezależnie od tego czy tworzymy grafikę 2D czy 3D. Gdy wierzchołki zostaną przetworzone wg przedstawionego algorytmu, ich współrzędne zostaną przekształcone do współrzędnych urządzenia. Mirosław Głowacki (AGH, UJK) OpenGL 2017 11 / 79
Obiekty sceny graficznej Współrzędne urządzenia to x i y, odwzorowywane na ekranie w zakresie ( 1.0, 1.0). Umieśćmy na scenie trójkąt o wierzchołkach ulokowanych w punktach (0, 0.5), (0.5, 0.5) i ( 0.5, 0, 5) zgodnie z ruchem wskazówek zegara. Mirosław Głowacki (AGH, UJK) OpenGL 2017 12 / 79
Obiekty sceny graficznej Jedyną różnica między wierzchołkami przedstawionymi na rysunku jest pozycja, więc będzie to jedyny atrybut. Ponieważ definiujemy trójkąt bezpośrednio we współrzędnych urządzenia, to x i y są wystarczające. OpenGL oczekuje na wysyłanie wszystkich wierzchołków w postaci jednego wektora, co może na początku wydawać się mylące. Aby zrozumieć format tego wektora, zobaczymy jak będzie on wyglądał dla naszego przykładowego trójkąta. float vertices[] = { 0.0f, 0.5f, // Vertex 1 (X, Y) 0.5f, -0.5f, // Vertex 2 (X, Y) -0.5f, -0.5f // Vertex 3 (X, Y) }; Mirosław Głowacki (AGH, UJK) OpenGL 2017 13 / 79
Wektor wierzchołków Wektor powinien zawierać listę wszystkich wierzchołków z ich atrybutami spakowane razem. Kolejność w jakiej pojawiają się atrybuty, nie ma znaczenia tak długo, jak jest ona taka sama dla każdego wierzchołka. Kolejność wierzchołków nie musi być sekwencyjna - czyli nie muszą one występować koniecznie w kolejności, w której powstają kształty. Wymaga to jednak dostarczenia dodatkowych danych w postaci bufora elementów. Kwestia ta zostanie omówiona na końcu wykładu, ponieważ teraz byłoby to skomplikowane. Mirosław Głowacki (AGH, UJK) OpenGL 2017 14 / 79
Vertex Buffer Object - VBO Następnym krokiem jest przesłanie danych dotyczących wierzchołków do karty graficznej. Jest to ważne, ponieważ pamięć karty graficznej jest o wiele szybsza i nie ma wtedy potrzeby przesyłania danych za każdym razem, gdy scena musi być renderowana (około 60 razy na sekundę). Można to zrobić tworząc Vertex Buffer Object ( VBO ) GLuint vbo; glgenbuffers(1, &vbo); // Generate 1 buffer Pamięć jest zarządzana przez OpenGL, więc zamiast wskaźnika dodatnia liczba jest odniesieniem do bufora. GLuint vbo (substytut unsigned int vbo ) jest liczbą potrzebną do uaktywnienia VBO lub zniszczenia go, gdy przestaje być potrzebny. Mirosław Głowacki (AGH, UJK) OpenGL 2017 15 / 79
Vertex Buffer Object - VBO Aby przesłać aktualne dane do VBO trzeba najpierw uczynić go aktywnym obiektem wywołując glbindbuffer : glbindbuffer(gl_array_buffer, vbo); Oprócz GL_ARRAY_BUFFER typu wyliczeniowego istnieją inne rodzaje buforów, ale nie są one ważne w tej chwili. Dyskutowana instrukcja czyni właśnie utworzony VBO aktywnym, co pozwala na skopiowanie do niego danych wierzchołków instrukcją: glbufferdata(gl_array_buffer, sizeof(vertices), vertices, GL_STATIC_DRAW); Należy zauważyć, że funkcja nie odnosi się do ID naszego VBO, lecz do GL_ARRAY_BUFFER. Mirosław Głowacki (AGH, UJK) OpenGL 2017 16 / 79
Vertex Buffer Object - VBO Drugi parametr określa rozmiar wektora wierzchołków w bajtach. Wartość ostatniego parametru narzuca sposób wykorzystania danych wierzchołków - dane te są przesyłane raz, a następnie: używane wiele razy (GL_STATIC_DRAW), zmieniane od czasu do czasu i używane wiele razy (GL_DYNAMIC_DRAW), użyte raz (GL_STREAM_DRAW). Wartość ta określa w jakiej pamięci dane są zapisywane na karcie graficznej, aby najwyższą wydajność. Przykładowo, VBO z GL_STREAM_DRAW powoduje użycie pamięci, która umożliwia szybkie zapisywanie kosztem nieco wolniejszego rysowania. Mirosław Głowacki (AGH, UJK) OpenGL 2017 17 / 79
Przetwarzanie danych wierzchołkowych Wierzchołki i ich atrybuty zostały skopiowane do karty graficznej, ale nie są jeszcze gotowe do użytku - nadchodzi więc pora na wyjaśnienie jak karta graficzna radzi sobie z atrybutami. Jak wiemy, istnieją trzy etapy przetwarzania danych wymagające użycia shaderów - każdy z nich ma ściśle określony cel. W starszych wersjach OpenGL można było tylko w niewielkim zakresie modyfikować operacje wykonywane przez kartę graficzną. Nowoczesna OpenGL pozwala na instruowanie karty graficznej o sposobie postępowania z danymi. Aby uzyskać efekt na ekranie, należy zaimplementować zarówno shader wierzchołków, jak i shader fragmentów. Shader geometrii jest opcjonalny i zostanie omówiony później. Mirosław Głowacki (AGH, UJK) OpenGL 2017 18 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 19 / 79
Shadery Shadery są programowane w języku podobnym do C o nazwie GLSL (OpenGL Shading Language). OpenGL kompiluje kod źródłowy w trakcie wykonywania programu i kopiuje go do karty graficznej. Każda wersja OpenGL posiada własną wersję języka cieniującego z pewnym zestawem funkcji - wersje te stanowią parę. Dzieje się tak dlatego, że shadery zostały wprowadzone dopiero dla OpenGL 2.0 jako w wersja GLSL 1.10. Następne pary (OpenGL, GLSL) to: (2.1, 1.20), (3.0, 1.30), (3.1, 1.40), a do wersji 3.2 OpenGL przypisana jest wersja 1.50 GLSL. Począwszy od wersji 3.3 OpenGL wersja GLSL jest taka sama jak wersja OpenGL. Mirosław Głowacki (AGH, UJK) OpenGL 2017 20 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 21 / 79
Shader wierzchołków Shader wierzchołków ( vertex shader ) jest programem karty graficznej, który przetwarza każdy wierzchołek i jego atrybuty w kolejności ich występowanie w wektorze wierzchołków. Jego obowiązkiem jest obliczenie końcowego położenia wierzchołków we współrzędnych urządzenia i przekazanie wymaganych danych do shadera fragmentów. Dlatego właśnie tutaj zachodzą wszelkie przekształcenia obiektów w przestrzeni 3D i ich rzutowanie do 2D. Shader fragmentów wymaga atrybutów, takich jak kolor czy współrzędne tekstury, które zwykle są przekazywane przez shader wierzchołków z wejścia na wyjście bez żadnych zmian. Mirosław Głowacki (AGH, UJK) OpenGL 2017 22 / 79
Shader wierzchołków W naszym przykładzie shader wierzchołków nie będzie miał wiele do roboty, gdyż: wierzchołki zostały określone we współrzędnych urządzenia, nie wprowadziliśmy żadnych atrybutów, const char* vertexsource = R"glsl( #version 150 in vec2 position; void main(){ gl_position = vec4(position, 0.0, 1.0); } )glsl"; dyrektywa preprocesora #version została użyta w celu określenia wersji GLSL ( 1.50 ). Następnie stwierdzono, że istnieje tylko jeden atrybut - pozycja. Mirosław Głowacki (AGH, UJK) OpenGL 2017 23 / 79
Shader wierzchołków Oprócz typów oferowanych przez język C, GLSL posiada wbudowane typy wektorowe i macierzowe identyfikowane przez vec* i mat*. Typ wartości wewnętrznych w tych konstrukcjach jest dla OpenGL 3.2 i 3.3 typem float. Liczba występująca po vec zamiast gwiazdki określa liczbę elementów ( x, y, z, w ), a liczba po mat określa liczbę wierszy/kolumn macierzy kwadratowych. Ponieważ atrybut position zawiera tylko dwie współrzędne - x i y - typ vec2 jest odpowiedni dla tego zestawu danych. Przedrostek in oznacza, że mamy do czynienia z danymi wejściowymi. Mirosław Głowacki (AGH, UJK) OpenGL 2017 24 / 79
Shader wierzchołków Końcowa pozycja wierzchołka jest przypisywana do specjalnej zmiennej gl_position, która będzie używana podczas łączenia prymitywów oraz wielu innych procesów. Aby te działały poprawnie, ostatnia wartość (tzn. w ) musi mieć wartość 1.0f. Poza tym istnieje duża dowolność w pracy z atrybutami i w dalszej części wykładu przedstawimy sposoby nadawania trójkątowi kolorów. Również praca z typami języka GLSL jest elastyczna. Mirosław Głowacki (AGH, UJK) OpenGL 2017 25 / 79
Shader wierzchołków W zprezentowanym przykładzie użyto skrótu, aby ustawić dwa pierwsze pola wektora vec4 przy użyciu pól wektora vec2. Można jednak inaczej - poniżesz dwie linie kodu powodują taki sam efekt: gl_position = vec4(position, 0.0, 1.0); gl_position = vec4(position.x, position.y, 0.0, 1.0); Podczas pracy z kolorami można uzyskać dostęp do poszczególnych składników modelu RGBA - tzn. r, g, b i a w sposób identyczny jak w przypadku x, y, z i w. Może to pomóc w przejrzystości. Mirosław Głowacki (AGH, UJK) OpenGL 2017 26 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 27 / 79
Shader fragmentów Dane wyjściowe z modułu shadera wierzchołków podlegają interpolacji do wszystkich pikseli ekranu pokrytych przez prymityw w procesie zwanym rasteryzacją. Piksele te są nazywane fragmentami i są obiektami, na których działa shader fragmentów. Podobnie jak shader wierzchołków generuje on na wyjściu jeden obowiązkowy atrybut - końcowy kolor fragmentu. Do programisty należy utworzenie kodu generującego ten kolor na podstawie: koloru wierzchołków, współrzędnych tekstury innych danych pochodzących z modułu shadera wierzchołków. Mirosław Głowacki (AGH, UJK) OpenGL 2017 28 / 79
Shader fragmentów Nasz trójkąt składa się tylko z białych pikseli, więc shader fragmentów po prostu generuje ten kolor dla każdego fragmentu: const char* fragmentsource = R"glsl( #version 150 out vec4 outcolor; void main(){ outcolor = vec4(1.0, 1.0, 1.0, 1.0); } )glsl"; Od razu zauważymy, że nie użyliśmy żadnej wbudowanej zmiennej do wyprowadzania koloru, takiej jak np. gl_fragcolor. To dlatego, że shader fragmentów może wygenerować wiele kolorów, a jak się takimi zmiennymi posługiwać zobaczymy podczas omawiania ładowania shaderów. Mirosław Głowacki (AGH, UJK) OpenGL 2017 29 / 79
Shader fragmentów Zmienna outcolor używa typu vec4, ponieważ każdy kolor składa się z kolorów podstawowych: czerwonego, zielonego, niebieskiego, oraz kanału alfa. Kolory w OpenGL są reprezentowane jako zmiennoprzecinkowe liczby z zakresu ( 0.0, 1.0 ) zamiast powszechnie używanego zakresu całkowitoliczbowego ( 0, 255 ). Mirosław Głowacki (AGH, UJK) OpenGL 2017 30 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 31 / 79
Kompilowanie kodu shaderów Kompilowanie kodu shaderów jest łatwe i następuje po załadowaniu kodu źródłowego z pliku lub z tablicy znakowej typu GLchar*. Proces zaczyna się od: utworzenia obiektu shadera oraz załadowania do niego kodu shadera, co dla shadera wierzchołków wygląda to tak: GLuint vertexshader = glcreateshader(gl_vertex_shader); glshadersource(vertexshader, 1, &vertexsource, NULL); W przeciwieństwie do VBO, zamiast aktywować kod shadera lub stosować podobne rozwiązania, należy jedynie przekazać funkcji glshadersource uchwyt do obiektu shadera ( vertexshader ) oraz wskaźnik do tablicy znakowej ( vertexsource ) zawierającej kod shadera. Mirosław Głowacki (AGH, UJK) OpenGL 2017 32 / 79
Kompilowanie kodu shaderów Prototyp funkcji glshadersource to: void glshadersource(gluint shader, GLsizei count, const GLchar **string, const GLint *length); Funkcja może zatem przyjmować nie jedną, ale wiele ( count ) tablic znakowych - zazwyczaj stosuje się jednak kod zawarty w jednej tablicy znakowej, tak jak w naszym przykładzie. Ostatni parametr może stanowić: count -elementowa tablica długości poszczególnych tablic znakowych ( *string ) - terminatorów null nie wlicza się wtedy do długości, NULL co oznacza, że każda tablica znakowa kończy się terminatorem null. Mirosław Głowacki (AGH, UJK) OpenGL 2017 33 / 79
Kompilowanie kodu shaderów Teraz pozostaje skompilowanie programu shadera na kod maszynowy, który może być wykonany przez kartę graficzną: glcompileshader(vertexshader); Należy pamiętać, że jeśli kompilacja się nie powiedzie, np. ze względu na błąd składni, funkcja glgeterror zwracająca błędy OpenGL nie odnotuje tego faktu! Aby sprawdzić poprawność kompilacji należy wykonać test: GLint status; glgetshaderiv(vertexshader, GL_COMPILE_STATUS, &status); Jeśli status jest równy wartości GL_TRUE, kod programu shadera został z powodzeniem skompilowany. Mirosław Głowacki (AGH, UJK) OpenGL 2017 34 / 79
Kompilowanie kodu shaderów W wypadku wystąpienia błędów można pobrać log kompilacji za pomocą instrukcji: char buffer[512]; glgetshaderinfolog(vertexshader, 512, NULL, buffer); Instrukcje te zapiszą pierwsze 511 bajtów logu kompilacji + terminator null do zadefiniowanego bufora znakowego buffer. Log kompilacji może również zawierać przydatne ostrzeżenia i to nawet wtedy, gdy kompilacja zakończyła się pomyślnie, więc warto sprawdzić go od czasu do czasu podczas kodowania shaderów. Trzeci parametr funkcji glgetshaderinfolog zachowuje się analogicznie jak ostatni parametr funkcji glshadersource. Mirosław Głowacki (AGH, UJK) OpenGL 2017 35 / 79
Kompilowanie kodu shaderów Postępowanie z shaderem fragmentów jest dokładnie takie samo jak postępowanie z shaderem wierzchołków. GLuint fragmentshader = glcreateshader(gl_fragment_shader); glshadersource(fragmentshader, 1, &fragmentsource, NULL); glcompileshader(fragmentshader); Poprawność kompilacji i jej log należy sprawdzić ponownie, tym razem zastępując vertexshader przez fragmentshader - może to oszczędzić w przyszłości sporo wysiłku. Mirosław Głowacki (AGH, UJK) OpenGL 2017 36 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 37 / 79
Łączenie shaderóww program Na razie wierzchołki i fragmenty są osobnymi obiektami. Mimo, że zostały zaprogramowane do współpracy, nie są jeszcze w rzeczywistości połączone. Odbywa się to poprzez utworzenie programu z obu shaderów. GLuint shaderprogram = glcreateprogram(); glattachshader(shaderprogram, vertexshader); glattachshader(shaderprogram, fragmentshader); Shader fragmentów może przekazywać dane do wielu buforów, należy więc jednoznacznie określić, które wyjście jest zapisywane do którego bufora i musi to nastąpić przed łączeniem programu. glbindfragdatalocation(shaderprogram, 0, "outcolor"); Mirosław Głowacki (AGH, UJK) OpenGL 2017 38 / 79
Łączenie shaderóww program Buforem domyślnym shadera fragmentów jest ten o numerze 0 i nasz shader zawiera tylko jedno wyjście, więc wywołanie funkcji glbindfragdatalocation nie jest konieczne Podczas renderowania wielu buforów można w razie potrzeby użyć funkcji gldrawbuffers, która wyspecyfikuje bufor dla shadera fragmentów - wyjście pierwsze jest wyjściem domyślnym. Po dołączeniu shaderów, należy dokonać linkowania programu. gllinkprogram(shaderprogram); Po tej operacji można zmieniać shadery, ale nie będzie to miało wpływu na program dopóki linkowanie nie zostanie powtórzone. Obiekt shadera może również zostać usunięty za pomocą funkcji gldeleteshader, ale funkcja nie da efektu, dopóki nie zostanie on odłaczony od wszystkich programów przy użyciu funkcji gldetachshader. Mirosław Głowacki (AGH, UJK) OpenGL 2017 39 / 79
Łączenie shaderóww program Aby zacząć używać shaderów w programie OpenGL, wystarczy odwołać się do funkcji: gluseprogram(shaderprogram); Podobnie jak to miało miejsce w przypadku bufora wierzchołków, tylko jeden program shadera może być aktywny równocześnie. Mirosław Głowacki (AGH, UJK) OpenGL 2017 40 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 41 / 79
Wiązanie wierzchołków z atrybutami Chociaż mamy teraz sprecyzowane dane wierzchołków i program shaderów, OpenGL nadal nie wie, jak sformatowane i uporządkowane są atrybuty. Aby rozwiązać problem najpierw należy pobrać uchwyt do wektora position w shaderze wierzchołków: GLint posattrib = glgetattriblocation(shaderprogram, "position"); Jego wartość jest liczbą zależną od kolejności definicji danych wejściowych - pierwsza i jedyna pozycja wejściowa w maszym przykładzie zawsze będzie miała wartość 0. Mirosław Głowacki (AGH, UJK) OpenGL 2017 42 / 79
Wiązanie wierzchołków z atrybutami Po ustaleniu uchwytu do danych wejściowych należy określić, w jaki sposób dane te będą pobierane z tablicy: glvertexattribpointer(posattrib, 2, GL_FLOAT, GL_FALSE, 0, 0); Pierwszy parametr odwołuje się do wejścia. Drugi parametr określa liczbę wartości tego wejścia, która jest taka sama jak liczba elementów vec2. Trzeci parametr określa typ każdego składnika, Czwarty parametr określa, czy wartości wejściowe powinny być normalizowane między -1.0 a 1.0 (lub 0.0 i 1.0 w zależności od formatu), jeśli nie są liczbami zmiennoprzecinkowymi. Mirosław Głowacki (AGH, UJK) OpenGL 2017 43 / 79
Wiązanie wierzchołków z atrybutami Ostatnie dwa parametry są dla nas najważniejsze, ponieważ określają, jak atrybut jest ulokowany w tablicy wierzchołków. Pierwsza liczba określa krok ( stride ), tzn. liczbę bajtów między poszczególnymi pozycjami wierzchołków w tablicy - wartość 0 oznacza, że nie ma między nimi innych danych. Dzieje się tak wtedy, gdy pozycja każdego wierzchołka następuje bezpośrednio po pozycji poprzedniego wierzchołka. Ostatni parametr określa przesunięcie ( offset ) atrybutów względem początku tablicy - tj. numer kolejny bajtu w tablicy, w którym atrybut się rozpoczyna - ponieważ nie ma innych atrybutów, wartość ta wynosi również 0. Mirosław Głowacki (AGH, UJK) OpenGL 2017 44 / 79
Wiązanie wierzchołków z atrybutami Należy pamiętać, że funkcja glvertexattribpointer wiąże nie tylko stride i offset, ale także VBO, który jest aktualnie przypisany do GL_ARRAY_BUFFER. Oznacza to, że: nie ma konieczności arbitralnego odwoływania się do odpowiedniego VBO podczas wywoływannia funkcji rysowania. można użyć różnych VBO dla różnych atrybutów. Na koniec wiązania wierzchołków z atrybutami tablica atrybutów wierzchołków musi zostać aktywowana. glenablevertexattribarray(posattrib); Mirosław Głowacki (AGH, UJK) OpenGL 2017 45 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 46 / 79
Vertex Array Objects Można się spodziewać, że prawdziwe programy graficzne używają wielu różnych shaderów i układów wierzchołków. Zmiana aktywnego programu shadera jest łatwa dzięki funkcji gluseprogram, ale ponowne ustawianie wszystkich atrybutów byłoby to dość niewygodne. Na szczęście OpenGL rozwiązuje ten problem przy pomocy obiektów Vertex Array Objects ( VAO ). VAO przechowują wszystkie powiązania między atrybutami a VBO zawierającym surowe dane wierzchołkowe. Obiekty VAO są tworzone w taki sam sposób jak obiekty VBO : GLuint vao; glgenvertexarrays(1, &vao); Mirosław Głowacki (AGH, UJK) OpenGL 2017 47 / 79
Vertex Array Objects Pracę z VAO należy rozpocząć przyłączając go instrukcję: glbindvertexarray(vao); Gdy tylko jakiś VAO zostanie przyłączony, każde wywołanie funkcji glvertexattribpointer spowoduje, że informacje będą przechowywane w tym VAO. Stąd przełączanie pomiędzy różnymi danymi i formatami wierzchołków jest proste i polega na wiązaniu różnych VAO! VAO nie przechowuje żadnych danych wierzchołkowych, a jedynie odwołuje się do utworzonych VBO i zawiera informacje o sposobach pobierania z nich atrybutów. Należy się upewnić, że VAO zostało utworzone i przywiązane na początku programu, gdyż wszystkie bufory wierzchołków przyłączone wcześniej niż VAO będą ignorowane. Mirosław Głowacki (AGH, UJK) OpenGL 2017 48 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 49 / 79
Obrazowanie Po załadowaniu danych wierzchołków, utworzeniu programów shaderowych i połączeniu danych z atrybutami, można już narysować trójkąt. Obiektr VAO przechowujący informacje o atrybutach został przyłączony, więc nie ma potrzeby się tym martwić. Pozostaje tylko proste wywołanie funkcji gldrawarrays w głównej pętli: gldrawarrays(gl_triangles, 0, 3); Pierwszy parametr określa typ prymitywu (zwykle punkt, odcinek lub trójkąt). Drugi określa, liczbę wierzchołków pomijanych na początku. Ostatni parametr określa liczbę wierzchołków do przetworzenia ( UWAGA: nie prymitywów! ). Mirosław Głowacki (AGH, UJK) OpenGL 2017 50 / 79
Obrazowanie Po uruchomieniu programu, na ekranie powinno się pokazać okno: Mirosław Głowacki (AGH, UJK) OpenGL 2017 51 / 79
Obrazowanie Jeśli po uruchomieniu programu nic nie widać, należy się upewnić, że zostały spełnione wszystkie warunki: shadery zostały poprawnie skompilowane, program został poprawnie zlinkowany, tablica atrybutów została przyłączona, VAO został przyłączony przed określeniem atrybutów, dane wierzchołkowe są prawidłowe, funkcja glgeterror z biblioteki OpenGL zwraca wartość 0. Jeśli błędy wystąpiły, to funkcja zwóci kod błędu ( GLenum errorcode ) - informacje o błędzie można uzyskać odwołując się do gluerrorstring(errorcode). Mirosław Głowacki (AGH, UJK) OpenGL 2017 52 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 53 / 79
Uniforms Dotychczasowy program ustalił biały kolor trójkąta w kodzie shadera fragmentów. Co jednak gdyby zaszła potrzeba zamiany tego koloru już po skompilowaniu tego modułu? Okazuje się, że atrybuty wierzchołków nie są jedynym sposobem przekazywania danych do programów cieniujących. Inny sposób przekazywania danych do shaderów wykorzystuje system tzw. uniforms. Uniforms to zasadniczo zmienne globalne, mające tę samą wartość dla wszystkich wierzchołków i/lub fragmentów. Aby zademonstrować, jak z nich korzystać, zmienimy kolor trójkąta w samym programie OpenGL. Mirosław Głowacki (AGH, UJK) OpenGL 2017 54 / 79
Uniforms Nadanie jednolitego koloru trójkątowi wymaga nstępującego (lub podobnego) kodu shadera fragmentów: #version 150 uniform vec3 trianglecolor; out vec4 outcolor; void main(){ outcolor = vec4(trianglecolor, 1.0); } Ostatnim parametrem koloru wyjściowego jest przeźroczystość, którą obecnie nie będziemy się zajmować i która ustalona została na 1 - brak przeźroczystości. Teraz program wygeneruje czarny (niewidoczny na czarnym tle) trójkąt, ponieważ wartość trianglecolor nie została jeszcze ustawiona. Mirosław Głowacki (AGH, UJK) OpenGL 2017 55 / 79
Uniforms Zmiana wartości zmiennych typu uniform przebiega tak samo jak zmiana atrybutów wierzchołków - na początek należy uchwycić lokalizację wektora trianglecolor : GLint unicolor = glgetuniformlocation(shaderprogram,"trianglecolor"); Wartości uniforms zmienia się przy użyciu jednej z funkcji gluniformxy, gdzie X oznacza liczbę elementów, a Y oznacza typ. Typowymi typami są f dla float, d dla double i i dla integer. gluniform3f(unicolor, 1.0f, 0.0f, 0.0f); Uruchomienie programu teraz spowoduje wyświetlenie czerwonego trojkata. Mirosław Głowacki (AGH, UJK) OpenGL 2017 56 / 79
Uniforms Aby utworzyć bardziej ekscytujący obraz, można zmieniać kolor w czasie za pomocą konstrukcji umieszczonej w pętli głównej: auto t_start = std::chrono::high_resolution_clock::now(); //... auto t_now = std::chrono::high_resolution_clock::now(); float time = std::chrono::duration_cast<std::chrono:: duration<float>>(t_now - t_start).count(); gluniform3f(unicolor, (sin(time * 4.0f) + 1.0f) / 2.0f, 0.0f, 0.0f); Chociaż powyższy przykład nie jest być może bardzo ekscytujący, to jednak pokazuje, że uniforms są niezbędne do kontrolowania zachowań shaderów w czasie wykonywania programu. Z drugiej strony do opisu pojedynczego wierzchołka bardziej nadają się atrybuty wierzchołków. Mirosław Głowacki (AGH, UJK) OpenGL 2017 57 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 58 / 79
Więcej kolorów Choć uniforms mają swoje zalety, to kolor jest czymś, co należy określać dla każdego wierzchołka trójkąta. Aby to osiągnąć należy dodać atrybut koloru do wierzchołków. W tym celu należy uzupełnić dane wierzchołkowe. Przejrzystość jest w zasadzie domeną całego trójkąta, więc dodamy tylko barwy - czerwoną, zieloną i niebieską: float vertices[] = { 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, // Vertex 1: Red 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // Vertex 2: Green -0.5f, -0.5f, 0.0f, 0.0f, 1.0f // Vertex 3: Blue }; Mirosław Głowacki (AGH, UJK) OpenGL 2017 59 / 79
Więcej kolorów Teraz należy zmienić shader wierzchołków tak, aby pobierał nowe dane wejściowe i kierował część z nich do shadera fragmentów: #version 150 in vec2 position; in vec3 color; out vec3 Color; void main(){ Color = color; // input ---> output gl_position = vec4(position, 0.0, 1.0); } Wejściem ( in ) shadera są wektory: dwuelementowy position zawierający 2 współrzędne pozycji i trójelementowy color zawierający 3 wartości atrybutów koloru. Wyjściem ( out ) shadera jest trójelementowy wektor Color zawierający 3 wartości atrybutów koloru. Mirosław Głowacki (AGH, UJK) OpenGL 2017 60 / 79
Więcej kolorów Color stanowi dane wejściowe do shadera fragmentów: #version 150 in vec3 Color; out vec4 outcolor; void main(){ outcolor = vec4(color, 1.0); } Należy się upewnić, że wyjście shadera wierzchołków i wejście shadera fragmentów mają tę samą nazwę - inaczej nie można poprawnie powiązać shaderów. Mirosław Głowacki (AGH, UJK) OpenGL 2017 61 / 79
Więcej kolorów Należy również nieco zmienić kod wskaźnika atrybutów, aby uwzględnić nową kolejność atrybutów x, y, R, G, B. GLint posattrib = glgetattriblocation(shaderprogram, "position"); glenablevertexattribarray(posattrib); glvertexattribpointer(posattrib, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), 0); GLint colattrib = glgetattriblocation(shaderprogram, "color"); glenablevertexattribarray(colattrib); glvertexattribpointer(colattrib, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void*)(2*sizeof(float))); stride w funkcji glvertexattribpointer jest ustawiony na 5*sizeof(float) - każdy wierzchołek to 5 wartości float. offset wynosi 2*sizeof(float) - przesunięcie atrybutów koloru to 2 wartości zmiennoprzecinkowe. Mirosław Głowacki (AGH, UJK) OpenGL 2017 62 / 79
Obrazowanie Tym razem na ekranie powinno się pokazać okno: Mirosław Głowacki (AGH, UJK) OpenGL 2017 63 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 64 / 79
Bufor elementów W obecnej wersji programu wierzchołki są określone w kolejności ich rysowania. Jeśli chcesz dodać kolejny trójkąt, musisz dodać 3 dodatkowe wierzchołki do tablicy wierzchołków. Istnieje sposób kontrolowania kolejności, co pozwala również na ponowne wykorzystanie istniejących wierzchołków - tablica elementów. Może to zaoszczędzić wiele pamięci podczas pracy z prawdziwymi modelami 3D w przyszłości, ponieważ każdy wierzchołek przynależy zazwyczaj do trzech trójkątów! Tablica elementów jest wypełniona liczbami całkowitymi bez znaku odnoszącymi się do wierzchołków związanych z GL_ARRAY_BUFFER. Mirosław Głowacki (AGH, UJK) OpenGL 2017 65 / 79
Bufor elementów Jeśli należy je narysować w dotychczasowej kolejności, będzie to wyglądać tak: GLuint elements[] = { 0, 1, 2 }; Elementy są ładowane do pamięci karty graficznej tak jak dane wierzchołkowe tworząc EBO (Element Buffer Object): GLuint ebo; glgenbuffers(1, &ebo); //... glbindbuffer(gl_element_array_buffer, ebo); glbufferdata(gl_element_array_buffer, sizeof(elements), elements, GL_STATIC_DRAW); Jedyną rzeczą, która różni bufor EBO od VBO jest argument GL_ELEMENT_ARRAY_BUFFER. Mirosław Głowacki (AGH, UJK) OpenGL 2017 66 / 79
Bufor elementów Aby skorzystać z tego bufora, należy zmienić polecenie rysuj na: gldrawelements(gl_triangles, 3, GL_UNSIGNED_INT, 0); Pierwszy parametr jest tu taki sam jak w przypadku gldrawarrays, ale wszystkie pozostałe odnoszą się do bufora elementów. Drugi parametr określa liczbę elementów do narysowania. Trzeci parametr określa typ danych elementu. Ostatni parametr określa przesunięcie. Jedyna różnica polega na tym, że teraz mówimy o elementach zamiast o wierzchołkach. Mirosław Głowacki (AGH, UJK) OpenGL 2017 67 / 79
Bufor elementów Zalety bufora elementów można zauważyć rysując prostokąt złożony z dwóch trójkątów. float vertices[] = { -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // Top-left 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // Top-right 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // Bottom-right }; 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // Bottom-right -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, // Bottom-left -0.5f, 0.5f, 1.0f, 0.0f, 0.0f // Top-left Wywołując gldrawarrays zamiast gldrawelements tak jak przedtem, bufor elementów zostanie po prostu zignorowany: gldrawarrays(gl_triangles, 0, 6); Mirosław Głowacki (AGH, UJK) OpenGL 2017 68 / 79
Bufor elementów Prostokąt jest renderowany tak, jak powinien, ale powtórzenie danych wierzchołków to marnotrawstwo pamięci. Korzystanie z bufora elementów umożliwia ponowne użycie danych: float vertices[] = { -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // Top-left 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // Top-right 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // Bottom-right -0.5f, -0.5f, 1.0f, 1.0f, 1.0f // Bottom-left }; //... GLuint elements[] = { 0, 1, 2, 2, 3, 0 }; //... gldrawelements(gl_triangles, 6, GL_UNSIGNED_INT, 0); Mirosław Głowacki (AGH, UJK) OpenGL 2017 69 / 79
Obrazowanie Teraz program powinien wygenerować okno: Mirosław Głowacki (AGH, UJK) OpenGL 2017 70 / 79
Spis treści 1 Potok grafiki OpenGL 2 Realizacja przedstawionego scenariusza 3 Shadery 4 Shader wierzchołków 5 Shader fragmentów 6 Kompilowanie kodu shaderów 7 Łączenie shaderów w program 8 Wiązanie wierzchołków z atrybutami 9 VAO - Vertex Array Objects 10 Obrazowanie 11 Uniforms 12 Więcej kolorów 13 Bufor elementów 14 Przykładowy program Mirosław Głowacki (AGH, UJK) OpenGL 2017 71 / 79
Kolorowy trojkąt 1/8 // Link statically with GLEW #define GLEW_STATIC // Nagłówki #include <GL/glew.h> #include <SFML/Window.hpp> // Kody shaderów const GLchar* vertexsource = R"glsl( #version 150 core in vec2 position; in vec3 color; out vec3 Color; void main(){ Color = color; gl_position = vec4(position, 0.0, 1.0); } )glsl"; Mirosław Głowacki (AGH, UJK) OpenGL 2017 72 / 79
Kolorowy trojkąt 2/8 const GLchar* fragmentsource = R"glsl( #version 150 core in vec3 Color; out vec4 outcolor; void main() { outcolor = vec4(color, 1.0); } )glsl"; int main() { sf::contextsettings settings; settings.depthbits = 24; settings.stencilbits = 8; Mirosław Głowacki (AGH, UJK) OpenGL 2017 73 / 79
Kolorowy trojkąt 3/8 // Okno renderingu sf::window window(sf::videomode(800, 600, 32), "OpenGL", sf::style::titlebar sf::style::close, settings); // Inicjalizacja GLEW glewexperimental = GL_TRUE; glewinit(); // Utworzenie VAO (Vertex Array Object) GLuint vao; glgenvertexarrays(1, &vao); glbindvertexarray(vao); // Utworzenie VBO (Vertex Buffer Object) // i skopiowanie do niego danych wierzchołkowych GLuint vbo; glgenbuffers(1, &vbo); Mirosław Głowacki (AGH, UJK) OpenGL 2017 74 / 79
Kolorowy trojkąt 4/8 GLfloat vertices[] = { 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f }; glbindbuffer(gl_array_buffer, vbo); glbufferdata(gl_array_buffer, sizeof(vertices), vertices, GL_STATIC_DRAW); // Utworzenie i skompilowanie shadera wierzchołków GLuint vertexshader = glcreateshader(gl_vertex_shader); glshadersource(vertexshader, 1, &vertexsource, NULL); glcompileshader(vertexshader); Mirosław Głowacki (AGH, UJK) OpenGL 2017 75 / 79
Kolorowy trojkąt 5/8 // Utworzenie i skompilowanie shadera fragmentów GLuint fragmentshader = glcreateshader(gl_fragment_shader); glshadersource(fragmentshader, 1, &fragmentsource, NULL); glcompileshader(fragmentshader); // Zlinkowanie obu shaderów w jeden wspólny program GLuint shaderprogram = glcreateprogram(); glattachshader(shaderprogram, vertexshader); glattachshader(shaderprogram, fragmentshader); glbindfragdatalocation(shaderprogram, 0, "outcolor"); gllinkprogram(shaderprogram); gluseprogram(shaderprogram); Mirosław Głowacki (AGH, UJK) OpenGL 2017 76 / 79
Kolorowy trojkąt 6/8 // Specifikacja formatu danych wierzchołkowych GLint posattrib = glgetattriblocation(shaderprogram, "position"); glenablevertexattribarray(posattrib); glvertexattribpointer(posattrib, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(glfloat), 0); GLint colattrib = glgetattriblocation(shaderprogram, "color"); glenablevertexattribarray(colattrib); glvertexattribpointer(colattrib, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(glfloat), (void*)(2 * sizeof(glfloat))); Mirosław Głowacki (AGH, UJK) OpenGL 2017 77 / 79
Kolorowy trojkąt 7/8 // Rozpoczęcie pętli zdarzeń bool running = true; while (running) { sf::event windowevent; while (window.pollevent(windowevent)) { switch (windowevent.type) { case sf::event::closed: running = false; break; } } // Nadanie scenie koloru czarnego glclearcolor(0.0f, 0.0f, 0.0f, 1.0f); glclear(gl_color_buffer_bit); Mirosław Głowacki (AGH, UJK) OpenGL 2017 78 / 79
Kolorowy trojkąt 8/8 // Narysowanie trójkąta na podstawie 3 wierzchołków gldrawarrays(gl_triangles, 0, 3); // Wymiana buforów tylni/przedni window.display(); } // Kasowanie programu i czyszczenie buforów gldeleteprogram(shaderprogram); gldeleteshader(fragmentshader); gldeleteshader(vertexshader); gldeletebuffers(1, &vbo); gldeletevertexarrays(1, &vao); // Zamknięcie okna renderingu window.close(); return 0; } Mirosław Głowacki (AGH, UJK) OpenGL 2017 79 / 79