piątek, 20 lutego 2009

NPR-u ciąg dalszy oraz proceduralne tworzywo drzewopodobne

scr000 Była notka o scenie naturalnej, pojawiło się demo, zatem teraz czas na udokomentowanie tego w kontekście Yesty. Dodatkowo, pojawi się małe i wybitnie niespektakularne dokończenie tematu NPR, czyli oświetlenie w technice toon shading w moim wykonaniu.

Toon shading

W skrócie i na “chłopski rozum” można określić toon shading (bądź też cell-shading [cel]) jako kreśkowkowe przedstawienie obiektów na scenie. Główna cecha to dokładne (oczywiście, w miarę możliwości) zaznaczenie krawędzi modelu oraz brak stosowania przejść barw na jednej płaszczyźnie (przynajmniej tak to rozumuję), choć oczywiście można tą technikę mieszać z teksturowaniem. Innymi słowy – obiekt ma pola jaśniejsze i ciemniejsze, które są stosunkowo łatwo rozróżnialne. Zresztą, wystarczy sobie przypomnieć dowolny komiks z lat młodości (Kaczor Donald, chlip…). Przede wszystkim jednak, różnica pomiędzy realistycznym renderingiem a nierealistycznym tyczy się wyszczególnieniu krawędzi. Widać to dokładnie na screenie obok, który pochodzi z gry XIII w całości zrealizowanej w tym stylu graficznym.

Przyznam uczciwie, że odrzuca mnie taka grafika. Oczywiście, przyznaję, że jest trudna w porządnej realizacji i budzi zachwyt, ale mimo wszystko nigdy nie grałem w XIII właśnie ze względu na wygląd. Może to też dlatego, iż nie jestem zwolennikiem anime oraz mangi i nie cierpię tego stylu (ale mi się teraz dostanie). Toon shading może być jednak użyteczny w pewnych warstwach gry, zatem, korzystając z tego rozdziału książki o Cg, spróbowałem zaimplementować ten rodzaj oświetlenia w frameworku. Wprawdzie przeprowadziłem za mało testów, ale uznałem, że mogę wszystko udokumentować i wspomnieć o tym w notce. Do samej próby implementacji natchnął mnie onegdaj Maskl (pozdrawiam), który wspomniał, że nie jest to takie proste w realizacji. Zachwycony tym, że coś-nie-jest-proste-więc-pewnie-mi-się-nie-uda-ale-może-znajdę-fajny-tutorial-z-kodem-źródłowym (w skrócie – CNJPWPMSNUAMZFTZKŹ) umieściłem toon shading na liście TODO i gdy przyszedł właściwy czas, skorzystałem z przykładów dołączonych do Cg Toolkit i jest. Cała sztuczka polega na stworzeniu odpowiednich tekstur jednowymiarowych, które charakteryzują się stałą wartością dla większości argumentów z zakresu [0, 1] oraz zwykle gwałtownym przeskokiem gdzieś pomiędzy (ilustracja tegoż znajduje się po lewej – prosto z tutoriala NVIDII). Tworzy się trzy takie tekstury – dla światła rozproszonego (diffuse), odbitego (lub też punktowego - specular) oraz dla krawędzi (edge). Podobnie, jak chociażby przy parallax mappingu, diffuse to iloczyn skalarny znormalizowanych wektorów normalnego oraz kierunku światła, specular – wektorów normalnego i połowy sumy wektora widoku (kamery) i kierunku światła, natomiast do współczynnika krawędzi liczymy dot product wektora normalnego (znowu…) i widoku. Wszystko oczywiście jest znormalizowane. Następnie te współczynniki próbkujemy z teksturami jednowymiarowymi, które wcześniej utworzyliśmy i w ten sposób tworzy się obraz. Brzmi lekko skomplikowanie, ale tak naprawdę nie jest to taki gigantyczny wysiłek. Choć nie przeczę – sam bym na to nie wpadł (podobnie jak na większość rzeczy, które opisuję).

Jeśli kogoś interesuje ten temat, znalazłem taką prezentacyjkę. Oczywiście, gdzie? Na stronie NVIDII.

Proceduralne drzewo

Tak, to, z czym męczyłem się jakieś dwa tygodnie. Kluczem do rozpoczęcia prac okazało się poszukanie odpowiedniego przykładu na generowanie węzłów będących podstawą drzewa. Udało mi się nawet odkopać link do wątku na forum, gdzie to znalałem. Proszę:

Help With Procedural Tree Algorithm

Głębszą analizę pozostawię Wam, ale powiem najważniejsze – scr002 drzewo to tak naprawdę jeden obiekt typu TreeNode, który sam w sobie reprezentuje węzeł. Ponadto, posiada listę węzłów potomnych oraz wskaźnik na następnego kolegę z paczki. Innymi słowymi – wszystko generuje się rekurencyjnie i to prawdopodobnie odpowiedź na pytanie dlaczego podczas wprowadzania tego algorytmu do frameworka szlag mnie trafiał co chwilę. Nie wiem, czy się ze mną zgodzicie, ale funkcje rekursywne (zwłaszcza takie, które mają bardzo dużo wywołań w różne strony) niesamowicie trudno się testuje. Przynajmniej na kartce – ta metoda nie zawodzi mnie w 80% przypadków, ale pozostałe 20% wypełniają prawie w całości rekurencje.

scr000 W każdym razie, pierwszy problem był taki, że przez jakieś kilka dni projekt się nie kompilował i najgorsze, że nie znałem przyczyny. Problem ten opisałem w tym wątku i jak sądziłem, chodziło o jakiś błąd w klasie CArrayFloat. Trudno jednak opisać moje rozczarowanie, gdy wszystko było w porządku. Problematyczny okazał się generator liczb pseudolosowych oparty na Mersenne Twister i nie chodzi tutaj o złe wyniki czy funkcje losujące, tylko o niepozorne makro. Kompilator nie chciał nawet słyszeć (tzn. chciał, ale resztę interpretował na opak) o takim makrze:

   1: #define Size 624

Zgadnijcie, co zaczął akceptować.

   1: #define Size1 624

Taaaak… Z jakiegoś powodu brak jednej cyferki przy stałej doprowadzał mnie do katastrofy emocjonalnej i wściekłości. Ale po naprawieniu tej niedogodności wcale nie zaczęło być lepiej…

Podany wyżej algorytm generuje drzewo składające się z różnokolorowych odcinków, co widać na jednym ze screenów (ogólnie, obrazki wokół są porozmieszczane w kolejności chronologicznej i nie są przypisane do żadnego akapitu). Potrzebny był zatem pomysł na obudowanie odcinków bryłami i następnie ich oteksturowaniu. Mogę śmiało powiedzieć, że pierwsze w miarę wyszło, drugie kompletnie nie. Albo też drugie wynika z pierwszego. Sam nie wiem.

scr004 Ponieważ odcinki są także rysowane rekurencyjnie, stosowana jest technika glPushMatrix() i glPopMatrix(), czyli przekształceń macierzy modelowania. Dlatego też próby wygenerowania siatki brył dla drzewa za pomocą osobnej funkcji spaliły na panewce i zamiast ładnej kory wychodziła choinka świąteczna z lewitującymi bombkami. Dlatego też odcinki są obudowywane za pomocą przekształceń i powiem uczciwie, że z perspektywy czasu wydaje mi się to lepszym rozwiązaniem w kontekście umieszczenia później liści na gałęziach. Jest jednak druga strona medalu – teksturowanie wygląda beznadziejnie i prawdopodobnie przez to nie udał się pomysł zastosowania relief mappingu na korze.

scr008 A co to za bryły? Graniastosłup o podstawie sześciokąta foremnego (czyli sześć quadów na każdy odcinek), ale z wiadomych przyczyn pozbawiony obu podstaw. Każda bryła ma taki sam rozstaw. Łatwo to sobie rozrysować na kartce – niech będzie dany sześciokąt foremny. Wtedy punkt (0, 0) jest w samym środku, a grubość gałęzi to wysokość pojedynczego trójkąta równobocznego (jednego z sześciu). Oczywiście, trzeba było pomyśleć o grubszym pnie i mniejszych zakończeniach. Wszystko zostało zrealizowane (w pierwszym przypadku) w oparciu o dodatkowy parametr podawany jako argument, czy funkcja jest wywoływana po raz pierwszy (wtedy należy rozszerzyć quad) oraz (a to w drugim przypadku) sprawdzeniu, czy obecna gałąź nie ma elementów na liście swoich potomków (wtedy należy zwężać trzeci i czwarty wierzchołek każdego quada).

Jeśli ktoś zrozumiał cokolwiek z poprzedniego akapitu to szczerze gratuluję – ja sam po przeczytaniu tego nie wiem jak się nazywam.

Gdy to się “udało”, należało zająć się liśćmi. Tutaj z pomocą scr007 przyszedł jeden projekt na Warsztacie – niestety, zgubiłem link, przepraszam i pozdrawiam autora (chodzi o Cadune Tree Demo) – gdzie spotkałem się z rozwiązaniem, że nie generujemy pojedyncze liście (co by wyglądało pewnie jeszcze gorzej aniżeli moja trawa), ale całe quady z teksturą paru liści i kanałem alfa. Szczerze mówiąc, nie wiem, czy takie rozwiązanie się stosuje w innych projektach, ale bardzo szybko pomysł podchwyciłem i jeszcze opatrzyłem billboardami. Przez to ostatnie liście mogą czasami sprawiać wrażenie irytujących sąsiadek – oglądają się za kamerą wszędzie i jeszcze niestraszne im wbijanie się w korę drzewa. Quady są umieszczane na każdej gałązce oprócz pierwszej i wygląda to… Hmm… Może pozostańmy na nie-tak-strasznie.

Co do shaderów – o nieudanym i FPSożernym relief mappingu na korze już wspominałem. Natomiast animacja liści wygląda przyzwoicie, choć być może zbyt delikatnie. Jako że nie zamierzam w najbliższej przyszłości poprawiać tego aspektu frameworka (tak, wiem, z wodą też tak mówiłem), toteż na razie wygląda to tak paskudnie, jak wygląda. Problem leży w samym teksturowaniu brył drzewa – sam relief działa chyba w porządku, choć muszę to jeszcze przetestować.

Na zakończenie jeszcze nadmienię, iż ciekawy artykuł odnośnie realistycznego odwzorowania drzewa znajduje się tutaj.

Pozdrawiam i dziękuję – SceNtriC.

2 komentarze:

Riddlemaster pisze...

Jeśli chodzi o toon shading da się go osiągnąć na wiele sposobów: per obiekt (np. techniką dot3 cel shading), per scena (np. color grading czy z zastosowaniem median filter).

TeMPOraL pisze...

Ostatnio z cel-shadingiem miałem do czynienia z punktu widzenia renderowania animacji w XSI (w ramach studiów) i powiem szczerze, że jego zastosowanie pomagało bardzo łatwo wywiązać się z postawionego przed nami na uczelni zadania.

Ludzie zdają się wymagać dużo mniej od grafiki cel-shaded niż od tej normalnej, która przecież aspiruje do bycia fotorealistyczną. Za pomocą cel-shadingu (jeśli tylko zachowa się spójny styl) można ukryć sporo drobnych defektów i optymalizacji. Przykładem tego niech będą animacje, które wykonali koledzy z uczelni.