OpenGL - tekstury Mapowanie tekstur 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 Maj 2017 / Czerwiec 2019 Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 1 / 39
Spis treści 1 Obiekty tekstur i ich parametry 2 Nakładanie tekstur (wrapping) 3 Filtrowanie tekstur 4 Ładowanie obrazów tekstur 5 Biblioteka SOIL 6 Używanie tekstur 7 Jednostki teksturujące Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 2 / 39
Spis treści 1 Obiekty tekstur i ich parametry 2 Nakładanie tekstur (wrapping) 3 Filtrowanie tekstur 4 Ładowanie obrazów tekstur 5 Biblioteka SOIL 6 Używanie tekstur 7 Jednostki teksturujące Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 3 / 39
Obiekty tekstur i ich parametry Tekstury, Podobnie jak obiekty: bufora wierzchołków VBO i tablicy wierzchołków VAO, są obiektami, które muszą zostać wygenerowane przez wywołanie odpowiednich funkcji. W tym momencie można sie już spodziewać, jak taka funkcja będzie wyglądać. GLuint tex; glgentextures(1, &tex); Tekstury są zazwyczaj używane jako "obrazy do dekoracji" modeli 3D, ale w rzeczywistości mogą być używane do przechowywania wielu różnych rodzajów danych. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 4 / 39
Obiekty tekstur i ich parametry Istnieją tekstury 1D, 2D i, jak wiemy, nawet 3D, które mogą być używane do przechowywania danych masowych w GPU. Przykładem innego wykorzystania tekstur jest przechowywanie informacji o terenie. W wykładzie zwrócono uwagę na użycie tekstur obrazów, ale zasady te mają zasadniczo zastosowanie do wszystkich rodzajów tekstur. Podobnie jak inne obiekty, tekstury muszą zostać przystosowane do wykonywania na nich operacji. Ponieważ obrazy są dwywymiarowymi macierzami pikseli, należy przywiązać je do obiektu typu GL_TEXTURE_2D. glbindtexture(gl_texture_2d, tex); Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 5 / 39
Obiekty tekstur i ich parametry Podczas operacji rysowania piksele w strukturze tekstury będą posiadały adresy w postaci współrzędnych tekstury. Współrzędne te leżą w przedziale od 0, 0 do 1, 0 gdzie: adres (0, 0) konwencjonalnie jest lewym dolnym rogiem tekstury, adres (1, 1) jest prawym górnym rogiem obrazu tekstury. Operacja, która wykorzystuje współrzędne tekstur do pobierania informacji o kolorze pikseli nazywa się próbkowaniem (ang. sampling ). Istnieją różne sposoby podejścia do tego problemu, z których każdy jest odpowiedni dla różnych scenariuszy. OpenGL oferuje wiele opcji kontrolujących sposób pobierania próbek - omówimy ich wspólne cechy. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 6 / 39
Spis treści 1 Obiekty tekstur i ich parametry 2 Nakładanie tekstur (wrapping) 3 Filtrowanie tekstur 4 Ładowanie obrazów tekstur 5 Biblioteka SOIL 6 Używanie tekstur 7 Jednostki teksturujące Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 7 / 39
Nakładanie tekstur Pierwszą rzeczą, którą trzeba wziąć pod uwagę, sposób pobrania próbk, gdy żądana współrzędna wykracza poza zakres od 0 do 1. OpenGL oferuje 4 sposoby obsługi takiej czynności: GL_REPEAT - część całkowita współrzędnej zostanie zignorowana i zostanie utworzony powtarzający się wzór. GL_MIRRORED_REPEAT - tekstura zostanie również powtórzona, ale obraz będzie obrazem zwierciadlanym, gdy część całkowita współrzędnej jest nieparzysta. GL_CLAMP_TO_EDGE - współrzędna zostanie przekształcona do obszaru pomiędzy 0 a 1. GL_CLAMP_TO_BORDER - Współrzędnym poza zakresem zostanie przypisany wybrany kolor obramowania. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 8 / 39
Nakładanie tekstur Te wyjaśnienia mogą być nieco tajemnicze, więc zobaczmy jak wyglądają wszystkie te przypadki: Na początek przykładowy obraz tekstury Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 9 / 39
Nakładanie tekstur A teraz wszystkie wymienione przypadki: Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 10 / 39
Obiekty tekstur i ich parametry Clamping polega na przyporządkowaniu piksela o współrzędnych (x, y, z) tekselowi o współrzędnych (s, t, r). Parametry obsługi tekstur można zmieniać przy użyciu funkcji gltexparameter* w następujący sposób: gltexparameteri(gl_texture_2d, GL_TEXTURE_WRAP_S, GL_REPEAT); gltexparameteri(gl_texture_2d, GL_TEXTURE_WRAP_T, GL_REPEAT); Jak zazwyczaj i wskazuje tu typ wartości, którą należy określić. Kolor obramowania dla czynności GL_CLAMP_TO_BORDER określa GL_TEXTURE_BORDER_COLOR ustawiany przy pomocy tablicy wartości RGBA : float color[] = { 1.0f, 0.0f, 0.0f, 1.0f }; gltexparameterfv(gl_texture_2d, GL_TEXTURE_BORDER_COLOR, color); Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 11 / 39
Spis treści 1 Obiekty tekstur i ich parametry 2 Nakładanie tekstur (wrapping) 3 Filtrowanie tekstur 4 Ładowanie obrazów tekstur 5 Biblioteka SOIL 6 Używanie tekstur 7 Jednostki teksturujące Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 12 / 39
Filtrowanie tekstur Ponieważ współrzędne tekstur są niezależne od rozdzielczości, nie zawsze odpowiadają dokładnie położeniom pikseli. Dzieje się tak, gdy obraz tekstur jest rozciągany powyżej (lub ściskany poniżej) oryginalnego rozmiaru. W takich sytuacjach OpenGL oferuje różne metody decydowania o próbkowanych kolorach. Proces ten nazywa się filtrowaniem i są dostępne następujące jego metody: GL_NEAREST zwraca teksel znajdujący się najbliżej żądanych współrzędnych, GL_LINEAR zwraca średnią ważoną 4 tekseli otaczających podane współrzędne. GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR - próbki pochodzą z MIP-map. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 13 / 39
Filtrowanie tekstur Zanim omówimy MIP-mapy, zwróćmy najpierw uwagę na różnicę między interpolacją GL_NEAREST i GL_LINEAR. Na rysunku oryginalny obraz jest 16-krotnie mniejszy niż prostokąt, na którym został on rasteryzowany. Chociaż interpolacja liniowa daje gładszy wynik, to nie zawsze jest to najbardziej idealna opcja. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 14 / 39
Filtrowanie tekstur Typ interpolacji, jaki ma zostać użyty do skalowania tekstur określa się dla dwóch oddzielnych przypadków identyfikowanych przez dwa słowa kluczowe: GL_TEXTURE_MIN_FILTER - skalowanie obrazu w dół, GL_TEXTURE_MAG_FILTER - skalowanie obrazu w górę. gltexparameteri(gl_texture_2d, GL_TEXTURE_MIN_FILTER, GL_LINEAR); gltexparameteri(gl_texture_2d, GL_TEXTURE_MAG_FILTER, GL_LINEAR); W przypadku GL_TEXTURE_MAG_FILTER nie ma sensu stosować metod filtrowania innych niż GL_NEAREST i GL_LINEAR, gdyż MIP-mapy są mniejszymi kopiami tekstur. MIP-mapy są filtrowane w trakcie ich tworzenia. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 15 / 39
Filtrowanie tekstur Zaleca się używanie MIP-map w miejsce skalowania tekstur podstawowych, ponieważ zapewniają zarówno wyższą jakość, jak i wyższą wydajność. Generowanie MIP-map jest proste i następuje poprzez wywołanie funkcji: glgeneratemipmap(gl_texture_2d); Nie ma więc powodu, aby ich nie używać! Należy jednak pamiętać o załadowaniu samej tekstury głównej przed poleceniem generującym MIP-mapy. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 16 / 39
Filtrowanie MIP-map Używanie MIP-map wymaga wyboru jednej z czterech metod ich filtrowania: GL_NEAREST_MIPMAP_NEAREST - wybiera MIP-mapę, która najbardziej pasuje do rozmiaru piksela, który jest teksturowany i używa kryterium GL_NEAREST (elementu tekstury najbliższego określonym współrzędnym). GL_LINEAR_MIPMAP_NEAREST - wybiera MIP-mapę, która najbardziej pasuje do rozmiaru piksela, który jest teksturowany i używa kryterium GL_LINEAR (średnia ważona czterech elementów tekstury, które są najbliższe określonym współrzędnym tekstury). GL_NEAREST_MIPMAP_LINEAR - wybiera dwie MIP-mapy, które najbardziej odpowiadają rozmiarowi piksela, który jest teksturowany i używa kryterium GL_NEAREST, aby uzyskać wartość tekstur z każdej mapy MIP - ostateczna wartość tekstury jest średnią ważoną tych dwóch wartości. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 17 / 39
Filtrowanie MIP-map GL_LINEAR_MIPMAP_LINEAR - wybiera dwie MIP-mapy, które najbardziej odpowiadają rozmiarowi piksela, który jest teksturowany i używa kryterium GL_LINEAR, aby uzyskać wartość tekstur z każdej MIP-mapy - ostateczna wartość tekstury jest średnią ważoną tych dwóch wartości. Ostatni przypadek oznacza, że zostanie zastosowane filtrowanie dwuliniowe każdego odpowiedniego poziomu mipmapy i trzeci filtr liniowy między sąsiednimi poziomami mipmapy - stąd termin filtrowanie trójliniowe. Istnieją inne parametry tekstur, które są one dostosowane do specjalnych operacji - więcej szczegółów na ten temat można znaleźć w specyfikacji na stronie internetowej http://docs.gl/gl3/gltexparameter. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 18 / 39
Spis treści 1 Obiekty tekstur i ich parametry 2 Nakładanie tekstur (wrapping) 3 Filtrowanie tekstur 4 Ładowanie obrazów tekstur 5 Biblioteka SOIL 6 Używanie tekstur 7 Jednostki teksturujące Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 19 / 39
Ładowanie obrazów tekstur Po tym, jak gdy obiekt tekstury zostanie skonfigurowany, nadchodzi czas na załadowanie obrazu tekstury. Odbywa się przez proste załadowanie tablicy pikseli przy pomocy funkcji glteximage2d - w naszym przypadku załadowana zostanie czarno-biała szachownica: // Czarno-biała szachownica float pixels[] = { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; glteximage2d(gl_texture_2d, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_FLOAT, pixels); Pierwszy parametr oznacza rodzaj tekstury. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 20 / 39
Ładowanie obrazów tekstur Drugi paramatr to poziom szczegółowości - 0 oznacza, że mamy do czynienie z teksturą podstawową - parametr ten może być używany do ładowania własnych obrazów MIP-map (jak już wiemy mogą one również zostać wygenerowane z tekstury podstawowej). Trzeci parametr ( GL_RGB ) określa wewnętrzny format pikseli, format, w którym piksele powinny być zapisywane na karcie graficznej - dostępnych jest wiele różnych formatów, w tym skompresowanych. Parametry czwarty i piąty ( 2 i 2 ) określają szerokość i wysokość obrazu liczoną w pikselach - wszystkie implementacje obsługują obrazy tekstur o szerokości co najmniej 1024 tekseli. Szósty parametr powinien zawsze mieć wartość 0. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 21 / 39
Ładowanie obrazów tekstur Parametry siódmy i ósmy ( GL_RGB i GL_FLOAT ) określają format pikseli i typ danych w tablicy, która zostanie załadowana. Ostatni parametr ( pixels ) określa samą tablicę. Funkcja ładuje obraz poczynając od współrzędnych (0, 0) - należy na to zwrócić uwagę. Pojawia się teraz pytanie co do samej tablicy pikseli - w jaki sposób powinna być zorganizowana? Tekstury w aplikacjach graficznych będą zazwyczaj bardziej wyrafinowane niż proste wzorce i będą ładowane z plików. Najlepszą praktyką jest posiadanie plików w formacie, który jest natywnie obsługiwany przez sprzęt, ale czasami bardziej wygodne jest ładowanie tekstur ze wspólnych formatów graficznych, takich jak JPG czy PNG. Niestety OpenGL nie oferuje żadnych funkcji pomocniczych do załadowywania pikseli z tych plików graficznych. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 22 / 39
Spis treści 1 Obiekty tekstur i ich parametry 2 Nakładanie tekstur (wrapping) 3 Filtrowanie tekstur 4 Ładowanie obrazów tekstur 5 Biblioteka SOIL 6 Używanie tekstur 7 Jednostki teksturujące Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 23 / 39
SOIL I tu z pomocą przychodzą nam biblioteki innych firm - jedną z nich jest biblioteka SOIL. SOIL (Simple OpenGL Image Library) to mała i łatwa w użyciu biblioteka, która ładuje pliki obrazu bezpośrednio do obiektów tekstur lub tworzy takie obiekty. Aby zacząć jej używać, należy dodać katalog SOIL do ścieżki include kompilatora i SOIL -framework dla linkera. Chociaż SOIL zawiera funkcje automatycznego tworzenia tekstury z obrazu, używa przy tym innych funkcji, które nie są dostępne w nowoczesnych OpenGL. Z tego powodu po prostu użyjemy SOIL do załadowania obrazu i sami stworzymy teksturę. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 24 / 39
SOIL Następujący kod spowoduje załadowanie obrazu z pliku i utworzenie tekstury. int width, height; unsigned char* image = SOIL_load_image("img.png", &width, &height, 0, SOIL_LOAD_RGB); glteximage2d(gl_texture_2d, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); Teraz można przystąpić do konfiguracji parametrów tekstury i generowania MIP-map. Pamięć obrazu można wyczyścić bezpośrednio po załadowaniu go do tekstury. SOIL_free_image_data(image); Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 25 / 39
Spis treści 1 Obiekty tekstur i ich parametry 2 Nakładanie tekstur (wrapping) 3 Filtrowanie tekstur 4 Ładowanie obrazów tekstur 5 Biblioteka SOIL 6 Używanie tekstur 7 Jednostki teksturujące Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 26 / 39
Używanie tekstur Jak widać, tekstury są próbkowane przy użyciu współrzędnych tekstur - trzeba je więc dodać do danych wierzchołkowych jako dodatkowe atrybuty. Wykorzystajmy dane z programu rysującego wielokąty dodając współrzędne tekstury. Nowa macierz wierzchołków będzie teraz zawierać współrzędne s i t dla każdego wierzchołka: float vertices[] = { // Position Color Texcoords -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // Top-left 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // Top-right 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // Bottom-right -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f // Bottom-left }; Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 27 / 39
Używanie tekstur Shader wierzchołków musi również zostać zmodyfikowany tak, aby współrzędne tekstury mogły być interpolowane dla każdego fragmentu podobnie jak współrzędne koloru.... in vec2 texcoord; out vec3 Color; out vec2 Texcoord;... void main(){ Texcoord = texcoord;... Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 28 / 39
Używanie tekstur Tak jak poprzednio wskaźniki atrybutów muszą zostać dostosowane do nowego formatu: glvertexattribpointer(posattrib, 2, GL_FLOAT, GL_FALSE, 7*sizeof(float), 0); glvertexattribpointer(colattrib, 3, GL_FLOAT, GL_FALSE, 7*sizeof(float), (void*)(2*sizeof(float))); GLint texattrib = glgetattriblocation(shaderprogram, "texcoord"); glenablevertexattribarray(texattrib); glvertexattribpointer(texattrib, 2, GL_FLOAT, GL_FALSE, 7*sizeof(float), (void*)(5*sizeof(float))); Ponieważ dwie współrzędne zostały dodane, każdy z wierzchołków jest teraz opisany 7 danymi, a atrybuty tekstury zajmują 2 ostatnie pozycje Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 29 / 39
Używanie tekstur Teraz pozostaje tylko zapewnić dostęp do tekstury dla shadera fragmentów tak, aby był w stanie dokonywać próbkowania tekstury. Odbywa się to przez dodanie zmiennej uniform typu sampler2d, która będzie miała domyślną wartość równą 0. Wartość tę należy zmienić tylko wtedy, gdy chcemy zapewnić dostęp do wielu tekstur. Jako przykład posłuży nam użyty wcześniej obraz kotka, który zostanie załadowany przy użyciu biblioteki SOIL. W tym cely funkcji SOIL_load_image należy przekazać jako pierwszy parametr nazwę pliku zawierającego ten obraz. Obraz powinien się znaleźć w katalogu roboczym aplikacji. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 30 / 39
Używanie tekstur Aby pobrać próbkę z tekstury 2D przy użyciu samplera, należy wywołać funkcję texture z właściwym samplerem i współrzędnymi tekstury jako parametrami. Można również pomnożyć uzyskaną próbkę z atrybutem koloru, co może dostarczyć interesujących efektów. Shader fragmentów wygląda teraz tak: #version 150 in vec3 Color; in vec2 Texcoord; out vec4 outcolor; uniform sampler2d tex; void main(){ outcolor = texture(tex, Texcoord) * vec4(color, 1.0); } Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 31 / 39
Używanie tekstur Efekt końcowy przeprowadzonych operacji jest natępujący: Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 32 / 39
Spis treści 1 Obiekty tekstur i ich parametry 2 Nakładanie tekstur (wrapping) 3 Filtrowanie tekstur 4 Ładowanie obrazów tekstur 5 Biblioteka SOIL 6 Używanie tekstur 7 Jednostki teksturujące Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 33 / 39
Jednostki teksturujące Sampler w shaderze cieniującym był do tej pory związany z jednostką teksturującą nr 0. Jednostka teksturująca to odniesienie do konkretnego obiektu tekstury, która ma być próbkowana w module cieniującym. Tekstury są związane z jednostkami teksturującycmi używanymi wcześniej przez glbindtexture. Ponieważ do tej pory nie określono jawnie żadnej jednostki teksturującej, tekstura była związana z domyślną jednostką GL_TEXTURE0. Dlatego sampler w module cieniującym działałał prawidłowo. Funkcja glactivetexture określa który z obiektów tekstury jest związany z konkretną jednostką podczas wywoływania funkcji glbindtexture. glactivetexture(gl_texture0); Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 34 / 39
Jednostki teksturujące Liczba obsługiwanych jednostek teksturyjących różni się dla każdej od karty graficznej, ale nie może być mniejsza niż 48. Można śmiało powiedzieć, że limit ten jest nadmiarowy nawet w najbardziej ekstremalnych aplikacjach graficznych. Aby pokazać próbkowanie z wielu tekstur, spróbujmy zmieszać zdjęcia kotka ze zdjęciem szczeniaka. Należy przy tym zmień moduł shadera fragmentów tak, aby próbkowanie zachodziło z dwóch tekstur:... uniform sampler2d texkitten; uniform sampler2d texpuppy; void main(){ vec4 colkitten = texture(texkitten, Texcoord); vec4 colpuppy = texture(texpuppy, Texcoord); outcolor = mix(colkitten, colpuppy, 0.5); } Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 35 / 39
Jednostki teksturujące Funkcja mieszająca mix to specjalną funkcją GLSL, która interpoluje liniowo wartość między dwoma zmiennymi w oparciu o trzeci parametr. Wartość 0.0 wskazuje na pierwszą zmienną, a 1.0 na drugą - wartość pośrednia x miesza obie zmienne w stosunku x 1 x. Gdy samplery są gotowe, należy przypisać im pierwsze dwie jednostki teksturujące i powiązać z tymi jednostkami odpowiednie tekstury. Odbywa się to przez dodanie wywołań glactivetexture do kodu ładowania tekstur. Całość kodu przedstawiono na dwóch kolejnych przeźroczach. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 36 / 39
Jednostki teksturujące GLuint textures[2]; glgentextures(2, textures); int width, height; unsigned char* image; glactivetexture(gl_texture0); glbindtexture(gl_texture_2d, textures[0]); image = SOIL_load_image("Kitty.png", &width, &height, 0, SOIL_LOAD_RGB); glteximage2d(gl_texture_2d, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); SOIL_free_image_data(image); gluniform1i(glgetuniformlocation(shaderprogram, "texkitten"), 0); Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 37 / 39
Jednostki teksturujące glactivetexture(gl_texture1); glbindtexture(gl_texture_2d, textures[1]); image = SOIL_load_image("Puppy.png", &width, &height, 0, SOIL_LOAD_RGB); glteximage2d(gl_texture_2d, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); SOIL_free_image_data(image); gluniform1i(glgetuniformlocation(shaderprogram, "texpuppy"), 1); Jednostki teksturujące samplerów ustawia się za pomocą funkcji gluniform*, która akceptuje liczbę całkowitą określającą jednostkę teksturującą. Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 38 / 39
Używanie tekstur Przedstawiony kod prowadzi do następującego obrazu: Mirosław Głowacki (AGH, UJK) OpenGL 2017/2019 39 / 39