Programowanie gier komputerowych Tomasz Martyn Wykład 6. Teksturowanie
Informacje podstawowe W procesie rasteryzacji wartości przekazywane na wyjście shadera wierzchołków (np. wartości atrybutów) podlegają interpolacji – w wyniku rasteryzacji otrzymuje się fragmenty (tj. piksele wraz wartością głębi), którym przypisane są odpowiednie wartości po interpolacji. W szczególności, tymi wartościami mogą być współrzędne tekstury (lub tekstur) przekazywane do potoku graficznego jako atrybuty wierzchołków (DirectX – wsp. UVW, OpenGL – wsp. STR) Nominalne wartości współrzędnych tekstury należą do zakresu [0,1], sposób interpretowania wartości spoza tego zakresu zależy od parametrów adresowania tekstury Przez teksturowanie będziemy rozumieli operację modyfikowania wartości koloru fragmentów przy użyciu wartości odczytanych z tekstury (lub kilku tekstur) za pomocą funkcji filtrowania tekstury w miejscu określonym przez współrzędne tekstury (lub tekstur) skojarzone z danym fragmentem w procesie rasteryzacji. W celu teksturowania w grach wykorzystuje się najczęściej tekstury 2D, a także tekstury kubiczne.
Podstawowe operacje związane z teksturowaniem stosowane w grach W grach, w celu teksturowania należy zwykle wykonać następujące operacje (niekoniecznie w podanej kolejności): utworzenie bazowego obrazu tekstury wygenerowanie sekwencji mipmap utworzenie obiektu tekstury (np. poprzez wczytanie odpowiednich danych z pliku) w odpowiedniej pamięci (zwykle Video RAM) zdefiniowanie odwzorowania geometria tekstura poprzez przypisanie wierzchołkom odpowiednich współrzędnych z układu współrzędnych tekstury zdefiniowanie parametrów filtrowania tekstury zdefiniowanie parametrów trybu adresowania (zawijania) tekstury zdefiniowanie parametrów mieszania tekstury z bieżącym kolorem fragmentu oraz ew. parametrów mieszania tekstur pochodzących z różnych jednostek teksturowania w przypadku multiteksturowania przypisanie tekstur do jednostek teksturowania i... renderowanie
Tworzenie zawartości tekstury Tekstury statyczne, to jest te, których zawartość nie ulega zmianie podczas działania gry, wraz sekwencją mipmap tworzone są na ogół off-line w fazie projektowania gry (np. pliki DDS). Tekstury dynamiczne generowane są najczęściej przy użyciu techniki renderingu do tekstury (np. mapy cieni, mapy dla dynamicznego otoczenia), zaś odpowiednia sekwencja mipmap – przez kartę graficzną (o ile posiada taką możliwość).
Tworzenie odwzorowania tekstury na geometrię Odwzorowanie geometria tekstura określa w sposób jednoznaczny w jaki (najczęściej dwuwymiarowa) tekstura jest nakładana na powierzchnię obiektu geometrycznego (zwykle siatkę trójkątów) opisanego przez ciąg wierzchołków. W przypadku standardowego teksturowania przyporządkowanie to tworzone jest na ogół w fazie projektowania gry, podczas tworzenia modelu przy użyciu dedykowanej aplikacji graficznej (3D Studio, Blender,...). Jednakże przyporządkowanie to może być później dynamicznie modyfikowane w trakcie działania gry – np. efekty bazujące na tzw. animacji współrzędnych tekstury). Nadto, w wielu efektach (mniej lub bardziej) specjalnych, przyporządkowanie współrzędnych tekstury do wierzchołków/fragmentów może odbywać się dynamicznie podczas wykonywania programu (np. projekcja tekstury, mapowanie cieni, mapowanie otoczenia, ...)
Utworzenie obiektu tekstury (1): DirectX API DirectX udostępnia wygodne w użyciu funkcje utworzenia obiektu (interfejsu) tekstury na podstawie danych obrazu tekstury umieszczonych w pliku (obsługiwanych jest wiele formatów graficznych, w tym dds, jpg, bmp, png, tga). W przypadku, gdy plik nie zawiera obrazów mipmap, obie funkcje generują odpowiednią sekwencję (wersja uproszczona - pełną sekwencję, wersja rozszerzona – w podanym zakresie MipLevels). (D3DX 10 i 11 analogicznie przy wykorzystaniu funkcji D3D1*CreateShaderResourceViewFromFile)
Utworzenie obiektu tekstury (2): OpenGL W OpenGL obsługa wczytywania danych obrazu tekstury z pliku przerzucona jest na programistę. Po wczytaniu danych obrazu do pamięci RAM, obiekt tekstury w pamięci Video RAM tworzy się przy wykorzystaniu funkcji glGenTextures i glBindTexture łącznie z funkcją glTexImage*D. W przypadku, gdy plik nie zawiera mipmap, sekwencję taką można wygenerować i załadować do obiektu automatycznie wykorzystując funkcje gluBuild*DMipmaps (lub gluBuild*DMipmapsLevels - wersja rozszerzona). , file
Filtrowanie tekstur (1) Parametry filtrowania tekstury definiują sposób wyznaczania wartości określonych współrzędnymi tekstury na podstawie sekwencji mipmap i/lub pojedynczej tekstury. filtr pomniejszający jest stosowany gdy powierzchnia teksela (po zrzutowaniu na ekran) jest mniejsza od powierzchni piksela najbliższy (teksel) – wybierana jest wartość przechowywana w tekselu, który znajduje się najbliżej piksela liniowy – wartość wyznaczana jest poprzez dwuliniową interpolację wartości tekseli w otoczeniu piksela anizotropowy – kosztowny obliczeniowo, redukuje artefakty w odwzorowaniu tekstury powstające, gdy kąt między normalną do trójkąta i kierunkiem „patrzenia” kamery jest duży filtr powiększający jest stosowany gdy powierzchnia teksela jest większa od powierzchni piksela (rodzaje jak w filtrze pomniejszającym) filtrowanie mipmap najbliższa (mipmapa) – wybierana jest mipmapa o powierzchni tekseli najbliższej powierzchni pikseli i następnie stosowane jest albo filtrowanie pomniejszające, albo powiększające liniowe – wybierane są dwie sąsiednie mipmapy o powierzchniach tekseli najbliższych powierzchni piksela, następnie stosowane są do nich, odpowiednio, filtr powiększający i pomniejszający; wartość finalna wyznaczana jest poprzez liniową interpolację wartości z poprzedniego kroku
Filtrowanie tekstur (2): DirectX W API DirectX parametry filtrowania tekstury należą do stanu tzw. samplera, który można utożsamiać z jednostką teksturującą o zadanym numerze. W potoku D3DX 9 (stałym i programowalnym) oraz programowalnym D3DX 10 i 11 parametry filtrowania tekstury można zdefiniować przy użyciu metody SetSamplerState interfejsu urządzenia (D3DX 10 i 11 metody CreateSamplerState i PSSetSamplers), np: W potokach programowalnych D3DX 9 – 11parametry filtrowania samplera można również określić bezpośrednio w kodzie shaderów poprzez zdefiniowanie obiektu stanu samplera, np:
Filtrowanie tekstur (3): OpenGL W OpenGL parametry filtrowania tekstury określa się przy użyciu funkcji glTexParameteri wywoływanej z odpowiednimi argumentami, na ogół, w czasie tworzenia obiektu tekstury (wówczas parametry filtrowania zapamiętywane są w obiekcie tekstury; jednakże można je później modyfikować dla danej tekstury wywołując funkcje glTexParameteri po „dowiązaniu” tej tekstury przy użyciu funkcji glBindTexture): w kontekście aktywnej jednostki teksturującej (funkcja glActiveTexture) Począwszy od wersji 3.3 można używać w tym celu funkcji glSamplerParameter.
Adresowanie tekstury (1) Parametry adresowania (zawijania) tekstury określają sposób odwołania do zawartości tekstury w przypadku, gdy wartości współrzędnych tekstury wykraczają poza nominalny zakres [0, 1].
Adresowanie tekstury (2) W DirectX parametry adresowania tekstury należą stanu samplera (podobnie jak parametry filtrowania). W potoku D3DX 9 (stałym i programowalnym) oraz programowalnym D3DX 10 i 11 parametry adresowania tekstury (oddzielnie dla każdej współrzędnej tekstury) można zdefiniować przy użyciu metody SetSamplerState interfejsu urządzenia (D3DX 10 i 11 metody CreateSamplerState i PSSetSamplers), np.: W potokach programowalnych (D3DX 9 – 11) parametry te można określić bezpośrednio w kodzie shaderów w obiekcie stanu samplera, np: Podobnie, w OpenGL używa się w tym celu funkcji glTexPameteri, np: wywoływanej w kontekście aktywnej jednostki teksturującej (funkcja glActiveTexture)
Operacje mieszania tekstur Operacje mieszania w teksturowaniu określają sposób modyfikacji koloru bieżącego fragmentu przy użyciu wartości otrzymanej z tekstury przynależnej do i-tej jednostki teksturującej. W stałym potoku graficznym operacje mieszania definiowane są odrębnie dla każdej jednostki teksturującej, zaś mieszanie barwy fragmentu z wartościami pobranymi z tekstur następuje w sposób sekwencyjny. W stałym potoku DirectX 9 operacje te definiuje się przy wykorzystaniu metody SetTextureStageState interfejsu urządzenia oddzielnie dla koloru RGB i kanału , np: W stałym potoku OpenGL służy do tego funkcja środowiska tekstur glTexEnv*, wykorzystanej w kontekście aktywnej jednostki teksturującej (glActiveTexture) bieżący kolor fragmentu tekstura W potokach programowalnych operację mieszania tekstur definiuje się pisząc swój własny kod shadera pikseli (powyższe funkcje w API bez potoku stałego zostały wycofane).
Przypisanie tekstury do jednostki teksturowania W OpenGL (we wszystkich wersjach) przypisanie obiektu tekstury do jednostki teksturowania dokonywane jest przy użyciu funkcji glBindTexture z nazwą utworzonego obiektu tekstury jako argumentem, wywołanej w kontekście aktywnej jednostki teksturującej (glActiveTexture). (W celu dokonywania teksturowania należy je również uaktywnić funkcją glEnable). W stałym potoku (ale również programowalnym) DirectX 9 obiekt (interfejs) tekstury można dowiązać do jednostki teksturującej za pomocą metody SetTexture(NoStage, pTexture) interfejsu urządzenia. W programowalnym potoku DirectX 9 dowiązanie obiektu tekstury następuje poprzez jego dowiązanie do odpowiedniej zmiennej reprezentującej teksturę w shaderze pikseli przy użyciu metody interfejsu efektu (HLSL) SetTexture(”uchwyt_zmiennej”, pTexture). W podobny sposób dowiązanie tekstury do shadera pikseli następuje w potokach D3DX10 i D3DX11.
Usuwanie obiektów tekstur W DirectX interfejsy reprezentujące tekstury usuwa się poprzez wywołanie standardowej metody dla obiektów COM, to jest metody Release interfejsu tekstury. W OpenGL obiekty tekstur usuwa się przy użyciu funkcji glDeleteTextures.
Wnioski Podobnie jak w przypadku buforów wierzchołków/indeksów (por. wnioski do Wykładu 5), dla potrzeb wieloplatformowego silnika graficznego należy: stworzyć, niezależne od API graficznego, klasy (być może szablonowe) reprezentujące obiekty tekstur i samplerów, w których metody interfejsu obudowują implementacje stosowane w konkretnych API graficznych same zaś implementacje dla konkretnego API należy dostarczyć w pliku implementacyjnym