piątek, 16 stycznia 2009

W czasie końcówki semestru

Niestety, nadszedł nieubłagany okres końca semestru, kiedy nagle student sobie przypomina, że beztroskie chodzenie na zajęcia i zbijanie bąków nie może trwać wiecznie – kiedyś musi przyjść weryfikacja wtłoczonej wiedzy. Dlatego też tak dużo czasu minęło od ostatniej notki – a do następnej pewnie też sporo wody w rzece upłynie. Nieważne w jakiej.

Tak czy inaczej programy na zaliczenie zostały oddane (nikomu nie życzę, żeby program przestał działać przy prezentacji…), kolokwium z list napisane, indeks wypełniony – przyszedł jednak czas na zaliczenia “całosemestralne” – jedno podejście (oprócz poprawki) i koniec. To nie przelewki. Ani orzeszki.

Przy okazji jednak poznałem sposób, dzięki któremu można generować plansze sudoku (a jak niektórzy pewnie pamiętają, jakiś czas temu pisałem o tym notkę). Na próbę implementacji przyjdzie chyba jednak czekać do wakacji.

Napiszę więc co nieco o tym, co udało mi się zrobić już jakiś czas temu i zostało już (albo jest w trakcie) przenosin do frameworka, jednak jeszcze nieudokumentowane. I nie zanosi się na to tak szybko.

Szum Perlina

Jest to właśnie zjawisko widoczne na małym niebieskawym obrazku przy tytule tej notki. Ogólnie, jest to efekt pozwalający na generowanie losowych zaburzonych obrazów, które mogą znaleźć zastosowanie przy chociażby renderowaniu chmur. Póki co, funkcje obliczające szum Perlina (wraz z czymś, co się ładnie nazywa Fractional Brownian Motion) znalazły legowisko w osobnej klasie CPerlinNoise i na razie nie planują przeprowadzki – planuję napisanie od czasu do czasu takich w miarę podstawowych rzeczy i wsadzenie tego w miarę nieoblegane miejsce w strukturze klas, aby potem to wszystko połączyć i opatrzyć ramką Precompiled Headers (albo chociaż tak to przyjmować). Dlatego też systematycznie (aczkolwiek bardzo powoli, podobnie jak cały framework) rozrasta się część CMath zawierająca przydatne i mniej przydatne twory do obliczeń. Ale wracając do meritum.

Pod tym linkiem można znaleźć “przyjemny” opis zagadnienia algorytmu wprowadzonego przez Kena Perlina. Piszę z przekąsem (można pisać z przekąsem) przyjemny, gdyż jakoś nigdy nie miałem sympatii do anglojęzycznych technicznych artykułów, lecz chyba trzeba będzie się powoli przyzwyczajać. Sama zasada działania, nie jest wybitnie skomplikowana – polega na losowaniu kolejnych elementów obrazu, wygładzaniu (takim blurowaniu) okolic, interpolowaniu i voila. Niby proste, ale jak zwykle, gdy dochodzi co do czego to człowiek nie wie, gdzie ma głowę. Każdy obraz może być przetworzony wielokrotnie (ilość wyliczeń określają oktawy) na podstawie częstotliwości i amplitudy. Ale to by było za proste, żeby po prostu losować wartość dla danego piksela – należałoby jeszcze oprzeć to o interpolację wylosowanych składowanych. Ale składowe z kolei pochodzą z wygładzenia obrazu w oparciu o rogi, boki i środek. A te wartości są z kolei już wygenerowane – pod koniec artykułu jest pseudokod, gdzie można to prześledzić, a w razie wątpliwości patrzeć powyżej i czytać dokładniej o kolejnych elementach. Ja akurat w klasie CPerlinNoise dałem możliwość wyboru generacji pomiędzy obrazem 1D, 2D i 3D, którego nie ma w artykule, ale w oparciu o niego można łatwo i analogicznie to skonstruować. Jedyne problemy to większa ilość kombinacji, które należy przetworzyć przy interpolacji (liniowej lub cosinusowej) i przy wygładzaniu, gdzie mamy nie 3, nie 9, tylko 27 części do prześledzenia. Jak łatwo zauważyć, jest to trójka podniesiona do potęgi n, gdzie n to wymiar w jakim generujemy – przy czterech byłoby 81 części branych pod uwagę. Można też nieco zuniwersalizować funkcje przetwarzające szum – w pseudokodzie są funkcje w stylu InterpolatedNoise_1 (czyli działająca na sąsiednich pikselach – jeśli byłaby dwójka, to oddalonych o dwie jednostki) i jest napisane, żeby stworzyć odpowiedniki dla większych iteracji pętli. Uznałem jednak, że nie ma co się bawić w dodatkowe funkcje, zwłaszcza, że programista korzystający z tej klasy nie jest zainteresowany samym procesem. Dlatego też przykładowy nagłówek funkcji wygląda tak:

   1: float CPerlinNoise::SmoothNoise (unsigned iteration, float x, float y, float z)

Argument iteration to właśnie aktualna iteracja (ich ilość zależy od oktaw), którą właśnie przetwarza pętla, natomiast parametry x, y i z to oczywiście współrzędne obrazu, który aktualnie generujemy. A co, jeśli nie chcemy trójwymiaru tylko porządny szum dwuwymiarowy? Po prostu jako z jest podawane 0 – nie musimy się tym przejmować.

Nie uważam, że zrobiłem wszystko dobrze. Ba, jestem przekonany, że sama klasa nie działa, zwłaszcza, że jej nie testowałem. A testy jednak odgrywają ogromną rolę – szczególnie, jak się ich nie przeprowadza. Jednak dla własnych badań czy po prostu ciekawskich, zamieściłem klasę na Chomiku – wymaga to wprawdzie modułu CMath (który jest rozwinięciem skromnej biblioteki HMath, też niegdyś umieszczonej na Chomiku), ale większość potrzebnych funkcji zawiera standardowa biblioteka C++ <cmath>, natomiast takie rzeczy jak interpolacja są prawie żywcem wzięte z artykułu. Tradycyjnie czekam na komentarze i będzie mi niezmiernie miło, jeśli ktoś dzięki mnie głębiej zainteresuje się tematem. No i poprawi moje błędy. Choć niewykluczone, że kiedyś też zanurzę się głębiej w tej kwestii, także od strony czysto matematycznej.

Trawa

Początkowo zainteresowałem się szumem Perlina właśnie ze względu na renderowanie w-miarę-wyglądającej-choć-oczywiście-niezmiernie-sztampowej trawy, ale szybko uznałem, że do końca mi się to nie przyda – wystarczy gotowy obrazek z wygenerowanym obrazem przedstawiającym Nie Wiadomo Co. Właściwie, to wspominam tylko o tym zagadnieniu przy okazji – nie mam całej klasy gotowej, nawet nie mam połowy. Nie tylko dlatego, że nie mam czasu – po prostu “coś” nie działa i mnie strasznie tym denerwuje (konkretnie – kanał alfa w shaderze jakoś nie chce usuwać przezroczystych części tekstury, aczkolwiek to kwestia czasu i zabrania się za to). Sam temat został podjęty po obejrzeniu tego demka firmy NVIDIA (screen po prawej) i przejrzeniu (dobra – przeglądnięciu obrazków) tego artykułu. Sama idea, jaką sobie założyłem, polega na wczytaniu czarno-białego obrazu TGA (przynajmniej na razie dwukolorowego, może potem się to zmieni) i wyszukania punktów, gdzie mają być rozstawione kępki trawy. Każda kępka to trzy skrzyżowane czworokąty, gdzie każdy ma własną teksturę (nie ma wymogu, aby były różne, ale muszą być podane wszystkie). Klasa CGrass posiada własny manager tekstur, więc wszystko dzieje się w miarę automatycznie. Wykorzystana też została struktura samorozszerzającej się tablicy typu float, czyli CArrayFloat. Zastosowałem to rozwiązanie ze względu na ogromną ilość współrzędnych wierzchołków (12 wierzchołów, czyli 36 elementów tablicy na jedną kępkę) i to akurat się sprawdza. Można też ustawić rozpiętość trawy, jej wysokość, skalowalność… Ale o tym się rozpiszę, jak uda mi się szczęśliwie dokończyć cały mechanizm.

scr001 Generalnie, problem pojawia się w procesie renderowania (jak zwykle). Na pierwszym obrazku (ale moim – nie czasem tym z NVIDII, takim specjalistą nie jestem) widać ładne (choć rozstrzelone) krzaczki. Wszystko byłoby piękne, gdyby nie to, że to wersja bez shaderów – wtedy testowałem rendering bez ich udziału. Oczywiscr002ście, po ich dołączeniu, wszystko się cudowne posypało, co akurat mnie specjalnie nie zaskoczyło – jakoś często zdarzają mi się przygody z programami dla GPU. Efekt widać na drugim screenie (tak, ten z czarnymi i wesoło machającymi tapetami) – cały problem tkwi w kanale alfa, a raczej w sposobie jego omijania. W czystym OpenGL zadziałało:


   1: glEnable(GL_ALPHA_TEST);
   2: glAlphaFunc(GL_GREATER, g_fAlphaBarrier);

Natomiast kooperacja tego kawałka kodu z shaderami przebiegła nie po mojej myśli. Dlaczego tak się dzieje – chwilowo nie wiem i szczerze mówiąc nie mam czasu teraz się tym zająć – czekają inne projekty. I sesja.

Pozdrawiam i dziękuję – SceNtriC.

0 komentarze: