Programowanie Gier -> Wykład: Zaawansowane teksturowanie
PomocSpisTreści
1. Trochę rzeczy podstawowych
Texture mapping: co to jest unwrapping
Wrap mode: repeat, mirrored repeat, clamp.
Mamy clamp to border i clamp to edge. OpenGLowe GL_CLAMP oznacza że dokładnie na bokach mamy "half border, half edge" (bo środek texela jest na (0.5,0.5) w rozdzielczości obrazka w/g OpenGLa), i zazwyczaj nie jest tym czego chcemy (chociaż na niektórych starszych gpu zachowuje się jak GL_CLAMP_TO_EDGE). Typowy artefakt to przyciemnione boki tekstury.
Generalnie, zazwyczaj chcemy GL_CLAMP_TO_EDGE, nie GL_CLAMP.
GL_CLAMP, GL_CLAMP_TO_BORDER_ARB (z ARB_texture_border_clamp) jest Ok kiedy kontrolujemy border tekstury, dobre do naklejek w multitexturing etc.
Skybox: 6 tekstur. Obrót gracza, ale nie przesunięcie, wpływa na skyboxa.
Pamiętajcie dobrać rozmiar cube żeby cały cube mieścił się pomiędzy near/far projection.
Możemy je animować. Możemy mieć ich kilka warstw, nakładanych z blending.
Demo: pokaż wireframe, fountain_final.wrl, background_alpha_tex.wrl.
2. Filtrowanie
Przypomnienie: jak działa nearest, linear (aka bilinear), mipmapy (rózne kombinacje, generalnie linear_mipmap_linear = trilinear) żeby na raz pobierać wiele texeli.
Jak generować mipmapy?
Można mieć zapisane w pliku (np. DDS pozwala na to), zaleta: generowane offline, algorytmem super jakości.
Generować na CPU w czasie runtime: gluBuild2DMipmaps (albo własnymi metodami): nie polecane, bo zajmujemy CPU w czasie ładowania levelu.
SGIS_generate_mipmap (Dostępne na prawie wszystkich nowych GPU.)
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_FALSE / GL_TRUE)Czyli generuj automatycznie na GPU. Nienajgorsza metoda, generowanie na GPU jest dużo szybsze niż na CPU. Ale — problemy w niektórych sytuacjach, czasami GPU naprawdę nie wie kiedy dokładnie ma uhonorować i zrobić mipmapy, np. nie działa w ogóle dla cube maps bo nie wiedziałby kiedy ma uznać że uaktualnianie cube mapy skończone.
Dlatego najlepsze i zalecane:
EXT_framebuffer_object (Dostępne na prawie wszystkich nowych GPU.)
glGenerateMipmapEXT(GL_TEXTURE_2D / GL_TEXTURE_CUBE_MAP / GL_TEXTURE_3D);Uwaga: znany błąd ATI, target musi być enabled, więc najlepiej
glPushAttrib(GL_ENABLE_BIT); glEnable(Target); glGenerateMipmapEXT(Target); glPopAttrib;
Jak GL wyznacza którą mipmapę użyć? Generalnie wewnętrzna sprawa gpu, ale typowy alg: projection pixela na teksturę, weź dłuższy bok recta, starasz się wybrać level tak żeby na 1 pixel ekranu przypadał 1 texel.
Kiedy mamy shadery, możemy z tym eksperymentować, funkcje próbkujące tekstury z Lod pozwalają nam samemu obliczyć level mipmap jak chcemy.
Wikipedia o mipmapProblem z mipmapami: isotropowość, czyli nieczułość na kierunek: tekstura 2x mniejsza w każdym wymiarze.
Były też inne pomysły, np. ripmapy (coś jak mipmapy, ale maleją o 2 *albo* w jednym *albo* w drugim wymiarze; większa pamięć (4x)), summed-area table (precalculated sumy texeli, wymagają pamięci jak tekstura ale większej precyzji).
Nie przyjęły się, zajmują więcej pamięci, a są anisotropy ale głównie dla pionowych/poziomych pasków.
Anisotropic filtering — prawdziwe anisotropy, po prostu próbkuj normalnie ale kilka razy wzdłuż odcinka. Generalnie nie ma narzutu na pamięć, ale jest narzut na czas. Wspomanagane przez wszystkie obecne GPU.
Artykuł NVidii
Wikipedia
Specyfikacja EXT_texture_filter_anisotropicAbsolutnie trywialne w OpenGLu. Teksturom które user może widzieć pod małym kątem (typowo: repeatable tekstury podłogi, sufitu, niektórych ścian) dobrze jest włączyć anisotropic filtering. (Nie należy z tym szaleć dla wszystkich tekstur, bo narzut czasowy może być odczuwalny).
Notka: chociaż teoretycznie jest ortogonalne do filtrowania minification, w praktyce (przynajmniej na fglrx) wygląda na to że jest ignorowane kiedy nie mamy co najmniej bilinear zarówno na minification i magnification. Być może zależy to od OpenGLa. Nie jest to w praktyce żaden problem — po anisotropic generalnie sięgamy wtedy kiedy nawet najlepsze GL_LINEAR_MIPMAP_LINEAR nam nie wystarcza.
Demo:
- kambi_vrml_test_suite/x3d/tex_anisotropic.x3dv (rózne viewpoints)
- atcs_anisotropy_demo.wrl z różnymi ustawieniami filtrowania.
Anisotropic 1, 2, 4, 16. 16 = typowy limit GPU (chociaż oczywiście zależy od GPU, pytajcie o GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT).
(Większość dem które pokażę na tym wykładzie będzie oglądanych przez moje castle-model-viewer. Większość ze scen jest w https://github.com/castle-engine/demo-models).
3. Multi-texturing
Napisałem kiedyś stosunkowo krótkie opowiadanie o tym jak działa multi-texturing i jak robić za jego pomocą (jeszcze bez shaderów) bump mapping:
bump_mapping/README, na wykładzie omówimy sekcje GL_ARB_multitexture, GL_EXT_texture_env_combine, Dot bump mapping (using multitexturing).
(Notka: w shaderach będzie większa swoboda z mieszaniem tekstur. Ale samo przekazywanie i próbowanie tekstur takie samo, więc i tak musimy to wiedzieć.)
Demo bump mapping przez dot na multitex, z i bez normalizacji przez cube map.
4. Łapanie obrazków z OpenGLa do CPU (off-screen rendering) i do textury (render-to-texture)
4.1. Klasyczne metody: glReadPixels do CPU, glCopyTex[Sub]Image2D.
Problemy:
glReadPixels jest wolne (naturalne, konwersja do CPU),
glCopyTex[Sub]Image2D nieznacznie wolne (chociaż obraz nie przechodzi przez CPU, przechodzi najpierw przez color buffer i dopiero potem jest kopiowany — konwersja formatu pixeli na GPU)
rozmiar tekstury/obrazka ograniczony do rozmiaru ekranu. Można robić sztuczki z "tiling" (renderować duży obraz poprzez sklejanie wielu małych), ale to żmudne i sprawia że łapanie obrazu jest jeszcze bardziej wolne.
OK do:
screenshotów (raczej nie do nagrywania filmów — zazwyczaj zbyt wolne)
stosunkowo Ok do łapania małych tekstur (których rozmiary na pewno zmieszczą się w ramach okienka, np. 128 x 128 lub 256 x 256), np. do reflections.
Uwagi:
przy double buffer, reliable jest tylko odczyt z back buffer (nigdy front!, bo front może być zniszczony podczas wyświetlania/zasłaniania przez inne okna; demo)
reliable jest tylko działanie przy widocznym (realized, mapped etc.) okienku. (zależy i od OpenGLa i od window system, np. w moim doświadczeniu pod Linuxem ukryte okno działa zawsze Ok, pod Windowsem nie zawsze).
glReadPixels poza konwersją formatów ponadto wymaga synchronizacji pomiędzy GPU a CPU (wszystkie komendy OpenGLa muszą zakończyć działanie). Kolejna wada. Używanie PBOs (pixel buffer objects) jako celu glReadPixels eliminuje ją. (Trochę (zasłużonych) peanów na cześć VBO, PBO i FBO można znaleźć np. tutaj).
Formaty BGRA i BGR są generalnie najszybsze. Próbujcie ich używać. EXT_bgra jest w OpenGL >= 1.2, więc naprawdę naprawdę wszędzie. (Jeżeli ktoś nie ma OpenGL 1.2, można z czystym sumieniem posłać go w diabły.)
Czyli nie jest Ok do:
- nagrywania filmów off-screen w dużej rozdz
- łapania tekstur w dużej rozdz (a w małej jest nieoptymalny)
4.2. Off-screen rendering poprzez specjalne konteksty OpenGLa
- glX: render to GLXPixmap
- wgl: render to bitmap
- OSMesa: specjalnie zbudowana Mesa
- pbuffer (na poziomie glX / wgl, ale trochę lepsze (sama idea przenośna, i hw accelerated, i mogą zapisywać do glowych tekstur)) Prezentacja NVidii
I kilka innych, stare ale dobre omówienie tutaj
Problemy:
- wgl, glX są OS-specific
- auxillary not supported by most OpenGLs (FBO replaced them)
- większość technik oznacza fallback na software'ową implementację OpenGLa. A więc 1. wolno 2. zazwyczaj brak shaderów etc. (Nowa Mesa ma implementację GLSL, ale nie jest to jeszcze na poziomie production use. Zresztą i tak jest i zawsze będzie strasznie wolne kiedy idzie przez CPU.)
Czyli jedyne co zyskujemy (w porównaniu z klasycznymi) to że nie potrzebujemy okienka i nie jesteśmy ograniczeni rozmiarami okienka. Sam czas renderowania jest jeszcze gorszy...
4.3. Frame Buffer Object
Wspaniałe rozszerzenie, w specyfikacji OpenGLa 3.0, wcześniej jako GL_EXT_framebuffer_object i GL_ARB_framebuffer_object, bardzo popularne wśród obecnych implementacji OpenGL. W zasadzie, niweluje wszystkie problemy:
- FBO tworzymy i obsługujemy w całości poleceniami OpenGLa, więc przenośne. Jest też obsługiwane przez naszego OpenGLa, więc wspomagany hardware'owo.
- Rozmiar FBO nie zależy od rozmiaru okienka.
- Można "podczepić" teksturę do FBO, dzięki temu mamy render-to-texture bez żadnej konwersji formatu pixeli po drodze. Czyli szybkie.
Demo z omówienia powyżej, pokaż rozmiar tekstury 1024.
Notki: jak zrobić mipmapy textury tak stworzonej? Nie pomaga GL_GENERATE_MIPMAP = GL_TRUE, bo OpenGL nie zna explicite momentu w którym móglby to wywołać. Odpowiedź: glGenerateMipmapEXT, wspomniane już wcześniej. Dlatego właśnie glGenerateMipmapEXT zostało wprowadzone razem z rozszerzeniem FBO, jest po prostu nieodzowne w typowych zastosowaniach FBO.
5. Environment mapping
Są inne (sphere i paraboloid mapping), ale chyba nie warto się nimi zajmować na tym wykładzie. To co nas interesuje to cube environment mapping.
Ładowanie Cube map w OpenGLu — ARB_texture_cube_map, łagodne wprowadzenie NVidii.
Generowanie tex coords: patrz na wektor od kamery do vertexa, odbij w/g wektora normalnego. Masz kierunek w 3D. Niekoniecznie znormalizowany, ale to nic: wybierz ścianę cube map (patrz na max(abs(x / y / z)) i na jego znak), pozostałe dwie współrzędne podzielone przez tą wybraną (bo musi być = 1 żeby wypadła na ścianie) to współrzędne 2d na tej ściane (ew. zanegowane).
Oczywiście OpenGL zrobi to za nas: glTexGeni(GL_S / T / R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB);
Demo cubemap_*.x3dv (composed, dds).
Jak sprawić żeby wygenerowane tex coords były w world space? Krótko o tym co to jest camera/world/object space, OpenGL nie wie gdzie jest world space. Musimy ustawić 4 komponent tex coord na 0 (bo REFLECTION_MAP generuje kierunek, nie pozycję), i przemnożyć tex coords przez CameraInverseMatrix.
- Woda! Environment mapping to podstawa do zrobienie ładnej wody w grze.
Elegancka woda to kilka efektów, i można kombinować
na różne sposoby. Ogólne spostrzeżenia:
Woda faluje. Przede wszystkim oznacza to że zmieniają się wektory normalne. W wersji prostej, zmieniamy faktyczną geometrię i wektory normalne na wierzchołkach — bardzo nieoptymalne, ale działa Ok, demo.
W wersjach ciekawszych, zmieniamy normal mapy i robimy bump mapping + używamy wektora normalnego z bump mappingu do obliczenia odbicia w lustrze wody. To wcale nie jest takie trudne, ale z shaderami. (Bez shaderów w zasadzie można, skoro bump mapping można zrobić bez shaderów, ale robi się dość koszmarnie, w porównaniu ze stosunkowo łatwą implementacją na shaderach.)
EMBM = Environment Map Bump Mapping, czyli właśnie wektor normalny z BM używany do env mapping.
Jak wspomniałem powyżej, woda jest też lustrem. Można kombinować na różne sposoby, ale generalnie render otoczenia do tekstury jak cube mapa i użycie tej tekstury jako environment mapa (z reflections) na wodzie to podstawa. Jak widać, działa Ok.
Woda jest też przezroczysta. W wersjach prostych, po prostu ustawiamy transparency (blending), ale można dużo lepiej: na shaderach można napisać refraction, króre działa całkiem jak reflections (może używać cube mapy z punktu wody), tylko oblicza wektor w głąb wody. Przy okazji fajnie jest dodać mgłę pod wodą (przy okazji, można też obcinać rendering elementów pod wodą do tekstury, żeby nie trwał zbyt długo).
- Na koniec, drobnostki: oczywiście pamiętamy o dobrym materiale dla wody. Przede wszystkim duże specular, i światła ustawione tak żeby ten specular było widać.
Notka: OpenGL tak czy siak generuje te współrzędne tylko per-vertex. *Można* uzyć odpowiedniego fragment shadera który robiłby to per-fragment (aka pixel). (Ale o tym nie na tym wykładzie.)
Demo (cubemap_generated_in_dynamic_world.x3dv, cubemap_generated_recursive.x3dv).
Zwracam uwagę na Box, który wygląda tragicznie, i teapot który jest całkiem Ok. Wnioski — do ~planar reflections, kiepska metoda — rozdzielczość tekstury musi być naprawdę duża.
6. S3TC
http://en.wikipedia.org/wiki/S3_Texture_Compression
Kompresja specjalnie dla tekstur. Stratna (ale w przypadku typowych tekstur, strata nie jest duża.) Szczególna właściwość kompresji: jest fixed-ratio, i zawsze wiadomo jak pobrać z tekstury pixel (x, y) bez dekompresji całej tekstury.
Idea: GPU trzyma teksturę w wersji skompresowanej. Dekompresuje tylko kawałki tekstury "w locie" kiedy musi pobrać konkretny texel. GPU nie dekompresuje nigdy całej tekstury.
Zalety: Tekstury są mniejsze w pamięci GPU. Czas działania pozostaje ~taki sam (dekompresja dla konkretnego pixela jest błyskawiczna).
Wady: Nieznaczny spadek jakości tekstur. Zobaczymy na demach że w zasadzie jest niezauważalny.
Konkretne metody kompresji to DXT*, jak to działa:
DXT1 ma najlepszą kompresję (8:1, w porównaniu z RGBA: 8 bajtów na 4x4 pixele). W każdym 4x4 mamy 2 kolory bazowe (5:6:5), które dają 4 kolory (2 interpolowane, albo 1 interpolowany + 1 na oznaczenie transparent) + 2-bitowy index na każdy pixel.
OpenGL ma osobne
- GL_COMPRESSED_RGB_S3TC_DXT1_EXT
- GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
Wersja bez "A" dekoduje przezroczyste pixele do opaque (nieprzezroczyste, z alpha = zawsze 1) z czarnym kolorem. Wersja RGBA dekoduje alpha poprawnie, tzn. alpha = 0 lub 1.
DXT2-5 traktują kanał alpha trochę lepiej. Samo RGB kodują tak samo (tyle że zawsze mają wersję "2 interpolowane", bo alpha jest osobno).
DXT2-3 ma po prostu 4 bity na alpha channel każdego pixela, więc jest dobre do ostrych zmian alpha (kiedy zależy nam na osobnej kompresji alpha dla każdego pixela).
DXT4-5 ma alpha poprzez interpolację (8 wartości, pomiędzy 2 granicznymi 8-bitowymi), więc DXT4-5 jest najlepsze do gładkich zmian alpha.
DXT2 i DXT4 to odpowiedniki DXT3 i DXT5, ale mają premultiplied alpha — czyli kolor RGB jest już przemnożony przez alpha. Idae: Jeżeli zamierzamy użyć obrazków z alpha z blending (alpha * kolor obrazka + (1-alpha) * kolor tła), to premultiplied alpha zaoszczędza nam pierwsze mnożenie. OpenGL nie obsługuje DXT2/4 (w specyfikacji EXT_texture_compression_s3tc są potraktowane notką "No -- insufficient interest"). Chociaż w praktyce można po prostu podać je jako DXT3/5, i dopasować glBlendFunc, więc nie ma strachu.
Kiedy obrazek zawiera gwałtowne zmiany kolorów, artefakty są wyraźne, ale na normalnej teksturze jest całkiem Ok. Złe do normalmaps!
Różnice DXT1-5 są tylko w kodowaniu kanału alpha — RGB jest dokładnie tak samo! Dla tekstur bez kanału alpha, nie ma co się zastanawiać, używajcie DXT1.
Demo (dds_compressed_test.x3dv).
Jak użyć w OpenGLu?
Każemy OpenGLowi kompresować teksturę podczas glTexImage2D.
Zalety: można używać dowolnych obrazków, zmiana w kodzie programu jest minimalna - po prostu poproś OpenGLa o kompresję przy podawaniu tekstury.
Wady: Kompresja zajmuje czas podczas ładowania. I nie jest perfekcyjna (OpenGL musi się spieszyć). Z tych powodów, to nie jest zalecane.
Podajemy już skompresowaną teksturę za pomocą glCompressedTexImage2D. To oznacza że kompresujemy wcześniej, zapewne w jakimś normalnym programie na CPU. Typowa sytuacja: mamy format obrazka z taką kompresją (DDS), więc kompresuje plugin GIMPa/Photoshopa podczas zapisu DDS. W naszej grze odczytujemy skompresowany obrazek, i tak przekazujemy go OpenGLowi.
Zalety: Przy okazji czas ładowania jest błyskawiczny. Tekstury są już skompresowane, a sam czas ładowania plików też jest mniejszy, bo pliki skompresowane są przecież mniejsze.
Jakość kompresji jest perfekcyjna.
Wady: Dane gry trzeba odpowiednio przygotować, i odczytywać w naszym engine. Zazwyczaj oznacza to że chcemy używać formatu DDS (są pluginy do GIMPa i Photoshopa, a jak odczytać to w naszym programie — zaraz opowiemy).
DirectX nazywa różne schematy kompresji FourCC. Na DXT1/3/5 mówi też BC1/2/3. Ot, gdybyście kiedyś zetknęli się z tymi nazwami.
- Nie używajcie S3TC do niegładkich tekstur, np. normalmaps. Chociaż są odmiany DXT* które nadają się bardziej do normalmaps (3Dc by ATI, GL_ATI_texture_compression_3dc), nie są jeszcze zbyt powszechne.
Linki:
ARB_texture_compression
EXT_texture_compression_s3tcWikipedia
DXTC Examples and Technical Comparison
Notki: uwagi o kiepskim dekompresorze DXT1 nVidii są już od dawna nieaktualne, AFAIK.
gamasutra o Texture Compression Techniques and TipsDemo: acts1_normal.wrl, acts1_dds.wrl — demo jak wygląda prawdziwy level gry (ACTS z tremulousa przerobiony na VRMLa do mojego engine'u) po skompresowaniu wszystkich tekstur przez DXT1 (czyli 8 razy!).
Jak dla mnie, spadek jakości jest kompletnie niezauważalny. Trzeba odpowiednio ustawić okienka do porównania żeby w ogóle zauważyć drobne różnice, a nawet wtedy — bez podpowiedzi nie da się powiedzieć która wersja jest tą oryginalną.
Naturalnie, można na ten zysk spojrzeć inaczej: jeżeli przy DXT2-5 zyskujemy 4x, to możemy robić 4x większe tekstury (2x większa szerokość/wysokość) i mamy lepszą jakość przy takiej samej pamięci.
7. DDS
GIMP plugin ze źródłami, Pluginy NVidii do Photoshopa.
Mogą zawierać teksturę 2D (z/bez mipmap),
albo cube mapę (z/bez mipmap, dowolne spośród 6 ścian),
albo teksturę 3d (z/bez mipmap).
(Albo texture array, ale o tym nie będziemy opowiadać na tym wykładzie. W zasadzie, to coś jak tekstura 3D, tyle że nie ma interpolacji pomiędzy slices. Przydatne do różnych rzeczy na shaderach.)Mogą zawierać tekstury skompresowane S3TC (tekstury 3D lub oparte na floatach nic mogą być skompresowane).
Mogą zawierać tekstury oparte na floatach.
Format stosunkowo trywialny: header stałej długości. Potem po prostu ciąg obrazków, bez żadnych przerw etc. Jak długie, i ile, jest obrazków — to trzeba wyciągnąć samemu z headera.
Pixel format mówi jak jest ułożony pixel (teoretycznie, mamy sporo możliwości; w praktyce, support popularnych formatów wystarcza do gry; uwaga na endianess kiedy bawimy się bitami w maskach kanałów: wszystko w DDS jest little endian, czy *na odwrót* niż kolejność bajtów w pamięci).
Obrazki są zapisane wierszami (dla nieskompresowanych, pitch może dodatkowo określić padding wiersza). Wiersze są od góry do dołu (a więc na odwrót niż lubi OpenGL).
Nie ma żadnej kompresji, poza kompresją S3TC. Ale w jej przypadku chcemy załadować skompresowane dane tekstury i podać je do glCompressedTexImage2D. Więc żadnej dekompresji nie implementujemy.
- Reading DDS files to OpenGL by NVidia, Wikipedia (na dole link do trywialnego loadera DDS w C++).
Problemy specyficzne dla OpenGLa:
Wiersze tekstury są zapisane od góry do dołu. To wcale nie jest taki trywialny problem:
Kiedy tekstura nie jest skompresowana, możemy łatwo odwrócić ją sami, już w trakcie odczytywania obrazka z pliku. Jest to komplikacja kodu i nieznaczny spadek szybkości podczas ładowania, ale akceptowalny.
Prawdziwy problem pojawia się gdy mamy do czynienia ze skompresowaną S3TC teksturą. Możemy odwrócić każdy blok 4x4 poprzez odpowiednie odwrócenie "lookup tables" dla pixeli (2 bity na pixel w DXT1; w DXT2-3 trzeba dodatkowo odwrócić 4-bitowe alpha, a w DXT4-5 3-bitowe lookup table do alpha). Możemy też odwrócić wiersze bloków 4x4, więc w zasadzie jest to łatwe. Przykładowy kod, z wyjaśnieniem. Jest też moja implementacja.
Zauważcie że to nie zadziała dla tekstur o wysokości nie będącej wielokrotnością 4. Dla tekstur których wysokość = 2, 3 trzeba napisać specjalny kod, a dla tekstur których wysokość > 4 i niepodzielna na 4... w zasadzie nie da się nic zrobić poza dekompresją i kompresją od nowa, a więc bez sensu — dużo pracy, duży czas ładowania, to po cholerę bawimy się w DDSa?
Na szczęście, tekstury i tak muszą mieć rozmiary o potęgach 2 (używanie GL_TEXTURE_NON_POWER_OF_2 jest tradycyjnie straszliwie wolne i generalnie to zły pomysł).
Inne rozwiązania to odwrócić współrzędne tekstury OpenGLa. Jeżeli oryginalna współrzędna wynosiła t, to nowa wynosi 1-t (samo "-t" nie jest dobre kiedy tekstura nie jest ustawiona na GL_REPEAT). Ale to ma swoje własne problemy:
- jeżeli robimy to w trakcie ładowania iterując po współrzędnych tekstury, to tracimy czas podczas ładowania
- jeżeli robimy to w trakcie wyświetlania aplikując transformację macierzy tekstury to też mamy niewygodnie — np. w shaderach trzeba pamiętać żeby wszędzie transformować współrzędne tekstury.
- Wielu ludzi po prostu odwraca obrazki DDS przed zapisaniem w GIMPie/Photoshopie. Chociaż jest to trochę niewygodne dla artysty.
W praktyce, polecam zaimplementować flipping obrazków podczas ładowania albo nie implementować nic i obracać w GIMPie. Nie polecam walczyć z odwracaniem współrzędnych tekstury w rendererze.
Dla cube map: coordinate system DirectXa jest left-handed, OpenGLa jest right-handed. Dlatego positive/negative Y muszą być zamienione, tzn. to co DirectX (a więc i dokumentacja DDS) nazywa "positive Y" powinno być załadowane do "negative Y" OpenGLa.
Notka przy okazji: GIMPa można używać w batch mode. Patrz GIMP Batch Mode, Script-Fu and plug-ins for The GIMP, "Pomoc->Przeglądarka Procedur" w menu GIMPa. Przyjemne żeby automatycznie przerobić obrazki na skompresowany DDS (i np. przy okazji zrobić (gimp-image-flip image 1)), albo zrobić normalmapy. Naturalnie, jest to przydatne tylko do testing albo jako "pierwsza przymiarka" — optymalnie, normalmapy i dds powinien generować, sprawdzać czy dobrze wyglądają i ew. tweakować ustawienia autor modeli. Można podglądnąć moje zabawy z GIMP batch mode do DDS i normal map tutaj.
8. 3d textures (aka volume textures)
Tylko jeśli będzie czas na wykładzie. Ciekawe zastosowania tekstur 3d są dopiero z shaderami, więc można to odłożyć...
Idea w programach do modelowania - trywialne mapowanie tekstury na dowolne kształty 3d, łatwo wygenerować tekstury 3d które wyglądają super.
Dla OpenGLa, duże tekstury będą bardzo kosztowne, więc jest trochę trudniej. Używamy wtedy kiedy naprawdę nie da się zrobić unwrap i użyć tekstury 2D. Albo: (ciekawsze) używamy do różnych sztuczek. Przykłady sztuczek:
- próbkowanie mgły w scenie 3d — mini-raytracing żeby zrobić volumetric fog elegancko
- próbkowanie światła w scenie 3d — Volume Lightmapping, dające nam eleganckie oświetlenie i gładkie cienie (chociaż statyczne).
Naturalnie, tekstura jest wprawdzie 3d ale ciągle nakładamy ją na płaskie trójkąty. Chociaż przy pomocy tekstury 3d można robić "volume rendering", ale sama tekstura 3d nie wystarczy, trzeba jeszcze zrobić shader odpowiednio tą teksturę próbkujący. To co jest ważne w teksturze 3d to że mamy filtrowanie pomiędzy "slice'ami" tekstury (w porównaniu z animowaniem pomiędzy zestawem tekstur 2d, gdzie musielibyśmy robić to sami w shaderze).
Można samemu generować, dużo prac, np. strona Texture Generation using Random Noise zawiera bardzo eleganckie omówienie, zupełnie od podstaw, a pokazuje bardzo konkretnie generowanie typowych zrandomizowanych wzorków 2D i 3D.
Volumetric fog (chyba opowiemy dokładniej dopiero przy shaderach?)
Demo: show animated volumtetric fog
That's not all folks, ale nie mamy już czasu/potrzeby na inne rzeczy:
depth textures (głównie do shadow maps, ARB_depth_texture)
- float textures (do wielu rzeczy na shaderach, gdzie precyzja 256/kanał jest zbt mała albo po prostu konieczność skalowania/przesuwania pomiędzy zakresem 0...1 a czymś innymi jest niewygodna)
- gloss mapping: bardzo trywialne na shaderach, po prostu oddzielne tekstury dla diffuse i specular. Np. rdzawa powierzchnia.