Grafika Komputerowa 3D Etap drugi Należy wybrać i zrealizować w scenie z etapu pierwszego dowolny zestaw z podanych poniżej efektów tak, aby zgromadzić odpowiednią liczbę punktów. Nie można za drugi etap otrzymać więcej niż 20 punktów. Poszczególne efekty nie muszą ze sobą współpracować, a zatem włączenie jednego efektu może powodować wyłączenie innych, jeśli to ułatwi implementację. Uwaga dla piszących w C/C++ z użyciem OpenGL: W nagłówku gl.h znajdują się tylko deklaracje dotyczące jednej z pierwszych wersji standardu. Z późniejszych rozszerzeń wygodnie jest korzystać przy pomocy GLEW (OpenGL Extension Wrangler) dostępnego pod adresem http://glew.sourceforge.net/. W poniższym opisie [x pkt] oznacza x punktów za pojedyncze zadanie niezależnie od wykonania pozostałych zadań na danej liście, a [+y pkt] oznacza możliwość zdobycia dodatkowych y punktów pod warunkiem, że zostało również wykonane pierwsze zadanie na danej liście. Ostateczny termin oddania projektu to 9 grudnia. 1. Filtrowanie tekstur Teksturowanie w ogólności polega na wyznaczeniu przekształcenia odwzorowującego piksele na ekranie w obszar obrazu tekstury (zbiór tekseli). Poza przypadkami szczególnymi wyznaczony obszar tekstury rzadko pokrywa się z pojedynczym tekselem. W celu uzyskania atrakcyjnego efektu na ekranie, wylicza się kolor piksela jako średnią ważoną kilku tekseli. Filtr powiększenia stosowany jest, gdy obraz piksela jest mniejszy od rozmiaru teksela. Do wyboru mamy dwa rozwiązania: najbliższy teksel środka piksela (czyli brak filtrowania) oraz dwuliniową interpolację pomiędzy kolorami czterech sąsiednich tekseli. Filtr pomniejszenia stosowany jest, gdy obraz piksela jest większy od rozmiaru teksela. W takim przypadku zwykle używa się mipmap, czyli ciągu coraz mniejszych tekstur (poziomów mipmap), w którym pojedynczy teksel kolejnej tekstury jest średnią wartością czwórki tekseli poprzedniej tekstury. Filtrowanie przy pomniejszeniu może polegać na: wyborze najbliższego teksel środka piksela, dwuliniowej interpolacji pomiędzy kolorami czterech sąsiednich tekseli odpowiedniego poziomu mipmap, trzyliniowej interpolacji mipmap, na którą składa się wykonanie dwuliniowej interpolacji w dwóch sąsiednich poziomach mipmap i wykonanie liniowej interpolacji tak uzyskanych kolorów. Zadanie 1. [3 pkt]
Wybierz przynajmniej jeden obiekt w scenie. Upewnij się, że ma on przypisane współrzędne tekstury. Nałóż teksturę i włącz liniowy filtr dla powiększenia oraz filtrowanie trzyliniowe mipmap dla pomniejszenia. Zadanie 2. [+1 pkt] Dodaj interfejs umożliwiający dla jednego obiektu (np. ściany pokoju) zmianę poziomu mipmap wybieranego podczas teksturowania przez DirectX lub OpenGL. W efekcie spowoduje to rozmycie lub wyostrzenie obrazu na powierzchni obiektu. Służy do tego instrukcja gltexparameterf(gl_texture_2d, GL_TEXTURE_LOD_BIAS,...) w OpenGL lub ustawienie device.samplerstate[i].mipmaplevelofdetailbias w DirectX Managed. 2. Automatyczne generowanie i przekształcenia współrzędnych tekstury DirectX oraz OpenGL umożliwiają automatyczne generowanie współrzędnych tekstury na podstawie położenia wierzchołka w scenie lub w układzie kamery. Dodatkowo istnieje możliwość zdefiniowania macierzy przekształcenia dla współrzędnych tekstury. Można wyróżnić dwa zastosowania automatycznego generowania współrzędnych tekstury: rzutowanie równoległe lub perspektywiczne obrazu na powierzchnię siatek oraz mapowanie środowiska. W pierwszym przypadku postępujemy podobnie jak podczas rzutowania geometrii do widoku z kamery - mnożymy pozycje wierzchołków przez zadaną macierz, ale tym razem wynik traktowany jest jako współrzędne tekstury, a nie pozycja na ekranie. Jeśli przykładowo potraktujemy lampę jak drugą kamerę w scenie, w wyniku takiego rzutowania otrzymamy współrzędne wierzchołków siatki trójwymiarowego obiektu w układzie lampy. Jeżeli podczas teksturowania obiektu wykorzystamy te właśnie współrzędne tekstury efektem będzie projekcja pewnego obrazu rzucanego przez lampę na powierzchnię obiektu. Podczas mapowania środowiska najlepiej jest wykorzystać mapę sześcienną. Mapa sześcienna to jedna tekstura, która reprezentuje szóstkę dwuwymiarowych tekstur przypisanych poszczególnym ścianom sześcianu. Współrzędne tekstury dla mapy sześciennej to trójka liczb, które wyznaczają kierunek promienia wychodzącego ze środka takiego sześcianu. Element tekstury pobrany dla danych współrzędnych odpowiada przecięciu tego promienia z powierzchnią sześcianu. Mapowanie środowiska uzyskamy, jeśli wykorzystamy jako współrzędne mapy sześciennej nakładanej na pewien obiekt, wektory odbicia promienia biegnącego od obserwatora względem płaszczyzny stycznej do powierzchni (wyznaczonej przez wektor normalny). W DirectX Managed do automatycznego generowania współrzędnych tekstury na podstawie współrzędnych wierzchołka siatki i ewentualnie normalnej w tym wierzchołku (przy mapowaniu środowiska) służą instrukcje postaci: device.texturestate[0].texturecoordinateindex =...; device.texturestate[0].texturetransform =...;
Do przekształcenia współrzędnych tekstury przypisanych do obiektu służy instrukcja postaci: device.settransform(transformtype.texture0,...); W OpenGL odpowiednie są to polecenia postaci: gltexgeni(gl_s, GL_TEXTURE_GEN_MODE,...); gltexgenfv(gl_s, GL_OBJECT_PLANE,...); glenable(gl_texture_gen_s); glmatrixmode(gl_texture); Zadanie 1. [5 pkt] Wykonaj rzutowanie perspektywiczne pewnego obrazu rzucanego przez światło reflektorowe (np. lampę) na pokój i obiekty w nim. Zadanie 2. [5 pkt] Wykonaj mapowanie środowiska - renderuj odbicia mapy sześciennej w pewnym niepłaskim obiekcie sceny. Mapa sześcienna może ale nie musi odpowiadać widokowi pokoju widzianemu ze środka sześcianu otaczającego ten obiekt. 3. Mieszanie alfa (przezroczystość) Mieszanie alfa polega na wykorzystaniu dodatkowej czwartej składowej koloru jako współczynnika w równaniu, które na podstawie koloru już zapisanego w buforze (destination color - dst_color, destination alpha - dst_alpha) oraz koloru właśnie rysowanego obiektu (source color - src_color, source alpha - src_alpha) obliczy nową wartość koloru w buforze. W przypadku przezroczystości odpowiedni wzór to src_alpha * src_color + (1-src_alpha) * dst_color. Mieszanie addytywne z kolei to wzór postaci src_alpha * src_color + dst_color. Trudna część realizacji przezroczystości związana jest z tym, że kiedy na ekranie nakłada się na siebie kilka warstw powierzchni przezroczystych nie można ich rysować w dowolnej kolejności. Aby otrzymać poprawny efekt trzeba je uporządkować według relacji zasłaniania. W przypadku mieszania addytywnego kolejność rysowania obiektów jest dowolna. W przypadku bryły wypukłej realizacja przezroczystości jest prosta: wystarczy wykorzystać backface culling aby przy dwukrotnym rysowaniu tej samej bryły w pierwszej kolejności narysować jej tylnią część, a następnie przednią. Jeśli mamy kilka brył, dla których kule opisane na nich są rozłączne, to możemy uporządkować je, sortując na podstawie odległości środków tych kul od kamery. Możemy też wykorzystać równanie płaszczyzny, która rozdziela obiekty. W zależności od tego po której stronie płaszczyzny będzie się znajdować w danym momencie kamera, będziemy wiedzieć, które obiekty należy narysować jako pierwsze.
Zadanie 1. [4 pkt] Należy wybrać zestaw trzech obiektów i podświetlać je po najechaniu na nie kursorem myszy. Podświetlenie polega na narysowaniu powiększonej kopii obiektu, ale tym razem bez teksturowania, używając innego materiału (np. w kolorze czerwonym) i z włączonym mieszaniem addytywnym. Zadanie 2. [4 pkt] Umieść w scenie trzy bryły obrotowe (powierzchnie zakreślone przez pełny obrót krzywej wokół ustalonej osi np. walce) reprezentujące butelki, szklanki, wazony itp., z nałożoną dowolną półprzezroczystą teksturą i przypisanym materiałem z ułamkową wartością kanału alfa. Zapewnij poprawne wyświetlanie przezroczystości (rysowanie wszystkich powierzchni tych brył w kolejności od najdalszej do najbliższej). 4. Multiteksturowanie Multiteksturowanie polega na obliczeniu koloru powierzchni obiektu na podstawie kolorów pobranych z więcej niż jednej tekstury jednocześnie. Obliczenie koloru zwykle przebiega w ten sposób, że na początku mieszany jest kolor uzyskany w wyniku obliczeń oświetlenia z kolorem pierwszej tekstury, a potem uzyskany kolor jest mieszany z kolorem kolejnej tekstury itd. Zadanie 1. [2 pkt] Użyj przynajmniej dwóch różnych tekstur by uzyskać określony sposób pokolorowania powierzchni obiektu. Zadanie 2. [+3 pkt] Kalkomania. Naklej pewien znak lub siatkę kopii pewnego znaku danego w postaci tekstury z kanałem alfa na powierzchnię (może być to powierzchnia płaska) oteksturowanego już obiektu. Zapewnij prosty interfejs do zmiany wielkości i obracania kalkomanii na powierzchni obiektu (należy w tym celu wykorzystać macierz przekształcenia współrzędnych tekstury omawianą w poprzednim punkcie). 5. Multisampling
Multisampling jest metodą wygładzania krawędzi renderowanej geometrii. Każdemu pikselowi renderowanego obrazu odpowiada ustalona liczba n próbek, z których każda pamięta osobną wartość koloru i głębokości. Podczas renderowania dla każdego piksela renderowanego obiektu kolor liczony jest jednokrotnie, ale to czy kolor zostanie przypisany każdej z n próbek zależy od testów, czy dana próbka należy do obszaru renderowanego obiektu i czy spełnia test bufora głębokości. Ostateczny kolor w każdym pikselu wynikowego obrazu jest średnią kolorów wszystkich próbek w tym pikselu. Zadanie [2 pkt] Należy umożliwić włączanie i wyłączanie z poziomu programu wygładzania krawędzi przy pomocy multisamplingu. 6. Lustro Zrealizowanie poprawnego (nie przybliżonego) efektu płaskiego lustra wymaga wykonania trzech przebiegów rysowania geometrii: - najpierw rysowana jest oryginalna scena; - następnie rysowana jest powierzchnia płaskiego lustra (test bufora głębokości musi być w tym przebiegu ustawiony na mniejsze lub równe, jeśli rysujemy drugi raz te same piksele; przed rysowaniem należy wyczyścić bufor szablonu i ustawić operację bufora szablonu tak, aby piksele lustra miały w nim ustawioną niezerową wartość; bufor szablonu stencil buffer to kolejny bufor obok bufora głębokości i buforów koloru bufor związany z kontekstem renderowania, więc tworząc kontekst renderowania trzeba upewnić się, że zostanie utworzony także bufor szablonu); - wreszcie rysowana jest cała scena jeszcze raz, ale odbita względem płaszczyzny lustra (przed rysowaniem trzeba wyczyścić bufor głębokości, oraz ustawić test bufora szablonu tak, aby rysowanie odbywało się tylko tam, gdzie widać piksele lustra, a więc gdzie w buforze szablonu jest niezerowa wartość; jeśli za lustrem znajdują się obiekty w oryginalnej scenie, to albo trzeba je zignorować w tym przebiegu, albo włączyć obcinanie rysowanej geometrii płaszczyzną lustra). Można dodać ewentualnie czwarty przebieg i narysować półprzezroczystą powierzchnię lustra w określonym kolorze lub z określoną teksturą (np. teksturą rys). Przy rysowaniu odbitej sceny należy pamiętać o odbiciu również świateł. Odbicie geometrii (i świateł) uzyskuje się poprzez ustawienie odpowiednio macierzy przekształcenia świata. Po wykonaniu tego dodatkowo zmieni się orientacja trójkątów, zatem trzeba zmodyfikować odpowiednio ustawienia backface culling. Zadanie 1. [5 pkt] Zrealizować efekt lustra renderując całą scenę drugi raz odbitą względem płaszczyzny lustra i wykorzystując bufor szablonu do ograniczenia rysowania tylko do widocznych fragmentów lustra.
Zadanie 2. [+2 pkt] Dodatkowe 2 punkty można zdobyć, jeśli lustro ma kształt elipsy oraz może mieć dowolne położenie i orientację w scenie (ustaloną przez pewne stałe w programie). 7. Płaszczyzny obcinania DirectX oraz OpenGL umożliwiają zdefiniowanie, zwykle do sześciu, płaszczyzn obcinania geometrii sceny ( user clipping planes ). Tylko ta część geometrii sceny, która znajduje się po przedniej stronie aktywnych płaszczyzn obcinania jest rysowana. Do definiowania i włączania płaszczyzn obcinania w OpenGL służą polecenia: glenable(gl_clip_plane0+i), glclipplane(...). W DirectX Managed służy do tego struktura device.clipplanes. Zadanie [3 pkt] Płaszczyznę można zdefiniować jednoznacznie podając dowolny jej punkt i wektor prostopadły do niej (normalną). Niech w scenie zostanie wykorzystana płaszczyzna obcinania, która przechodzi przez środek pokoju i której normalną można dowolnie obracać. Należy narysować scenę dwukrotnie, z różnymi ustawieniami kolorów i tekstur, w pierwszym przebiegu obcinając geometrię po jednej stronie płaszczyzny, w drugim przebiegu po przeciwnej stronie płaszczyzny. Wystarczy jeśli połowa sceny będzie rysowana normalnie, przy użyciu kolorowych materiałów i tekstur, a druga połowa bez użycia tekstur przy pomocy szarych materiałów, ale można oczywiście wybrać w dowolny inny sposób dwa różne zestawy tekstur, materiałów i świateł. 8. Bilbordy Bilbordem nazywamy półprzezroczystą bitmapę, nałożoną na prostokąt, którego orientacja w przestrzeni dopasowuje się automatycznie do widoku z kamery, podczas gdy położenie środka bilbordu w scenie, oraz jego rozmiary w układzie świata, są stałe. Będziemy zajmować się dwoma wariantami bilbordów. Pierwszy to bilbord, który obraca się tylko wokół jednej osi. Jako przykład można sobie wyobrazić prostokąt z teksturą butelki, którego obrót ograniczony jest do jej osi symetrii. Drugi to bilbord, który ustawia się zawsze prostopadle do kierunku patrzenia. Takie bilbordy wykorzystywane są często jako podstawa systemu cząstek, na przykład do wizualizacji dymu. W przypadku wielu bilbordów, trzeba pamiętać o poprawnym rozwiązaniu problemu przezroczystości. Istnieją na to dwa sposoby: test alfa oraz mieszanie alfa. W przypadku testu alfa, w kanale alfa tekstury zapisana jest maska kształtu, który chcemy wyświetlić.
Piksele, którym odpowiada wartość alfa nie spełniająca testu nie są ani rysowane na ekranie, ani uwzględniane w buforze głębokości. Brzegi kształtu uzyskane w ten sposób są poszarpane (problem aliasingu), ale nie ma potrzeby rysowania bilbordów w ustalonej kolejności. Dużo bardziej atrakcyjne efekty zapewnia mieszanie alfa (alpha blending), kiedy to możemy w teksturze bilbordu określić stopień przezroczystości każdego jej fragmentu. W takim przypadku konieczne jest sortowanie bilbordów po współrzędnej głębokości w układzie kamery i rysowanie od najdalszych do najbliższych. Zadanie 1. [5 pkt] Umieść w scenie oba rodzaje bilbordów. Każdy z nich powinien mieć nałożoną półprzezroczystą teksturę. Należy poprawnie rysować oba bilbordy w przypadku, gdy pierwszy częściowo zasłania drugi i na odwrót. Zadanie 2. [+2 pkt] Zrealizować prosty system cząstek, czyli zbiór bilbordów poruszających się w czasie, gdzie stare bilbordy (cząstki) giną po ustalonym okresie, a na ich miejsce tworzone są nowe bilbordy w punkcie (lub na powierzchni obiektu) zwanym generatorem cząstek. Przezroczystość bilbordów powinna maleć do zera w trakcie jego życia. Należy wykorzystać mieszanie alfa i odpowiednio sortować cząstki. 9. Mgła Karty graficzne wspomagające grafikę 3D pozwalają dokonać interpolacji pomiędzy wyliczonym kolorem piksela, a zadanym kolorem mgły, na podstawie położenia renderowanego fragmentu powierzchni względem kamery. W najprostszym przypadku interpolacja odbywa się na podstawie współrzędnej z w układzie kamery i jest wykonywana sprzętowo dla każdego piksela. Mgła często jest używana do tego, by ukryć fakt obcięcia geometrii dalszą płaszczyzną obcinania w rzutowaniu perspektywicznym. Zadanie [3 pkt] Dodać dowolny rodzaj mgły do sceny, z możliwością jej włączania/wyłączania i zmiany gęstości. Niech dodatkowo kolor czyszczenia ekranu będzie identyczny jak kolor mgły dla pikseli blisko dalszej płaszczyzny obcinania geometrii (brak skokowej zmiany koloru w miejscu obcięcia najdalszych obiektów). 10. Renderowanie do tekstury Renderowanie do tekstury polega na tym, że tworzony obraz nie zostaje zapisany w buforze koloru, tylko w teksturze. W kolejnym przebiegu renderowania, gdy renderowany jest już obraz do buforu koloru, tekstura ta może zostać wykorzystana do nałożenia na
obiekty w scenie. Przykładowo możemy wyobrazić sobie monitor umieszczony w scenie, na który nakładana jest tekstura widoku tej właśnie sceny. Ciekawe efekty można uzyskać łącząc renderowanie do tekstury z renderowaniem prostokątów pełnoekranowych. Renderowanie prostokąta pełnoekranowego polega na: ustawieniu jednostkowej macierzy dla projekcji i przekształceń widoku/świata, narysowaniu pojedynczego prostokąta równoległego do ekranu, który wypełni całe okno widoku. Jeśli ustawimy kanał alfa dla koloru materiału prostokąta pełnoekranowego, oraz nałożymy na jego powierzchnię teksturę z wyrenderowanym właściwym obrazem sceny, możemy na przykład uzyskać efekt rozmycia ruchu. W tym celu nie czyścimy nigdy bufora koloru, a rysując prostokąt pełnoekranowy z nałożonym obrazem sceny, używamy mieszania typowego dla przezroczystości: src_alpha * src_color + (1-src_alpha) * dst_color. Wielkość efektu zależy od wartości kanału alfa koloru materiału prostokąta. W DirectX Managed instrukcje przydatne do realizacji renderowania do tekstury to: new Texture(device, w, h, 1, Usage.RenderTarget, Format.X8R8G8B8, Pool.Default); device.setrendertarget(...); (dodatkowo może się przydać obiekt klasy RenderToSurface). W OpenGL na nowszych kartach, wspierających odpowiednie rozszerzenie, warto skorzystać z instrukcji: glgenframebuffersext(1, &fbo); glbindframebufferext(gl_framebuffer_ext, fbo); glframebuffertexture2dext(gl_framebuffer_ext, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, colortex, 0); glframebuffertexture2dext(gl_framebuffer_ext, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depthtex, 0); glbindframebufferext(gl_framebuffer_ext,...);, z kolei na prawie wszystkich kartach graficznych można skorzystać z jednej prostej instrukcji: glcopyteximage2d(...);w tym drugim przypadku odbywa się faktycznie kopiowanie z bufora koloru do tekstury, więc tekstura musi mieć identyczne rozmiary i format jak bufor koloru (trzeba wziąć pod uwagę, że starsze karty graficzne mogą nie obsługiwać tekstur, których wymiary nie są potęgami dwójki). Czasem w OpenGL można poradzić sobie bez renderowania do tekstury z pomocą bufora akumulacji, którego z kolei nie ma w DirectX. Zadanie 1. [2 pkt] Wykorzystaj wyrenderowany obraz jako teksturę w scenie. Zadanie 2. [4 pkt] Zrealizuj w opisany powyżej sposób rozmycie ruchu z użyciem renderowania do tekstury i prostokąta pełnoekranowego. [2 pkt] można otrzymać alternatywnie za implementację z użyciem bufora akumulacji (nie można otrzymać 6 pkt za obie metody) 11. Proceduralne generowanie tekstur
Szum Perlina to pewna pseudolosowa funkcja R n [ 1; 1]. Stała się ona popularna w grafice komputerowej, ponieważ pozwala generować dodające scenie realizmu tekstury (np. chmur, drewna, marmuru) i zaburzenia geometrii (np. rzeźbę górzystego terenu). Szczegóły implementacji funkcji szumu Perlina można znaleźć pod adresem: http://www.noisemachine.com/talk1/index.html, a kod takiej funkcji pod adresem: http://mrl.nyu.edu/~perlin/doc/oscar.html. Dwuwymiarowa tekstura z szumem Perlina po przekształceniu wartości z przedziału [-1;1] do [0;255] wygląda mniej więcej tak: Zadanie 1. [5 pkt] Należy na podłogę w scenie nałożyć teksturę drewnianych klepek. W tym celu należy generować w programie szum Perlina i na jego podstawie liczyć kolory poszczególnych pikseli tekstury według poniższego opisu. Wyobraźmy sobie falę rozchodzącą się z określonego punktu płaszczyzny: z(x,y) = A * cos( B*r(x,y) + C) + D, gdzie A, B, C, D to pewne stałe, a r(x,y) to odległość punktu płaszczyzny od źródła fali. Jeśli pobierzemy wartość dwuwymiarowego szumu Perlina dla współrzędnych (x,y), zaburzymy nią wartość promienia r(x,y), a otrzymaną wartość z(x,y) wykorzystamy do zmieszania w odpowiedniej proporcji dwóch kolorów, to otrzymamy obraz, który może przypominać słoje drewna. Wynikowa tekstura na tym etapie powinna wyglądać mniej więcej tak:
Na koniec należy poszczególnym prostokątom ostatecznej tekstury, reprezentującym regularną siatkę klepek, przypisać losowe prostokąty powyższej tekstury, dodatkowo zaburzając losowo oba kolory użyte do jej wygenerowania. W wyniku powinien powstać następujący obraz: Zadanie 2. [+3 pkt] Należy na bazie szumu Perlina wygenerować trójwymiarową teksturę przypominającą marmur i użyć jej przy rysowaniu wybranego obiektu w scenie. Przy nakładaniu trójwymiarowej tekstury należy włączyć automatyczne generowanie współrzędnych tekstury przez DirectX/OpenGL. Generowane współrzędne tekstury powinny być współrzędnymi wierzchołków teksturowanego obiektu w lokalnym układzie współrzędnych związanym z obiektem, ewentualnie po odpowiednim przeskalowaniu i przesunięciu tak, aby mieściły się w przedziale wartości [0;1]. Poniżej znajdują się wzory na wygenerowanie wartości trójwymiarowej tekstury marmuru c x, y, z na podstawie szumu Perlina 3D oznaczonego jako f(x, y, z), przy założeniu, że argumentami dla poniższych obliczeń będą zmienne x,y,z z przedziału [0;1].
a = 2 f 4x, 4y, 4z + f 8x, 8y, 8z + 1 2 f 16x, 16y, 16z + 1 f 32x, 32y, 32z 4 b = 1 2 + 3 8x + a, 2y + a, 8z + a 2 c = 255 min 1; max 0; b Ostateczny efekt powinien wyglądać następująco: