Witam na swojej stronie domowej poświęconej programowaniu, ze szczególnym uwzględnieniem gier i Agile Development. Poza zwykłymi wpisami znajdziesz tu również dane o moich projektach, czy artykułach.

Projekty Artykuły Osiągnięcia O mnie Nurkowanie Galeria zdjęć

środa, 23 września 2009

Strona tylko po angielsku - http://wtomandev.blogspot.com

Niestety podjąłem decyzję. Zaprzestaję prowadzenia bloga w języku polskim. Wynika to z kilku rzeczy:
  • większość rzeczy, które opisuję jest związana pośrednio/bezpośrednio z nGENEm, a większość tych, którzy próbują z niego korzystać jest z zagranicy,
  • tłumaczenie postów zajmuje cenny czas (np. od ostatniego posta tutaj napisałem 2 na angielskiej wersji bloga, a dziś pewnie napiszę jeszcze 1), który wolę wykorzystać np. na poprawianie bugów,
  • jakakolwiek zmiana w strukturze, szablonie strony musi być wykonywana w dwóch miejscach (o czym zwykle zapominam).
Blog w angielskiej wersji dostępny jest pod adresem: http://wtomandev.blogspot.com .

Liczę, że czytelnicy mojego bloga przerzucą się na angielską wersję, zwłaszcza że dla programistów nie powinno mieć większego znaczenia czy czytają teksty napisane po polsku, czy po angielsku.

Pozdrawiam i liczę na zrozumienie :)

niedziela, 20 września 2009

Kodowanie Huffmana

Algorytm kodowania Huffmana to kolejna metoda, którą stosuję w nGENE Tech do kompresji plików zasobów (podobno w kodowaniu arytmetycznym, które daje lepsze wyniki łatwo nadziać się na jakiś patent). Wykorzystuję ją jako ostatni krok kompresji, zaraz po wykonaniu na danych wejściowych opisywanego ostatnio Run-Length Encoding.

Algorytm tu opisywany opiera się na częstotliwości występowania poszczególnych znaków w bloku danych.

Powiedzmy, że mamy jakiś ciąg znaków, w którym częstotliwości poszczególnych znaków przedstawiają się jak poniżej (dokładna postać ciągu jest nieistotna gdyż algorytm nie uwzględnia ich kolejności):
a: 10
b: 2
c: 21
d: 44
e: 78
f: 1

Jednocześnie nie występuje jakikolwiek inny znak.

Normalnie każdy z tych znaków byłby reprezentowany przy pomocy 1 bajtu (8 bitów). Ale łatwo zauważyć, że można by je reprezentować za pomocą binarnych kodów o różnej długości (>= 1 bit). Im częściej występuje dany znak, tym jego kod powinien być krótszy dzięki czemu wszystkie jego wystąpienia zajmą dużo mniej miejsca, co jest równoważne wyższemu stopniowi kompresji, np. znak 'e' (najczęściej występujący w przykładzie powyżej) mógłby być reprezentowany przez '1' tj. pojedynczy bit. Oznacza to, że wszystkie jego wystąpienia zajęłyby 78 bitów ~ 10 bajtów zamiast standardowych 78 bajtów!

Jak znaleźć kody dla znaków

Ale jak znaleźć kody dla każdego znaku (najlepiej, optymalne)? Tutaj do gry wchodzi algorytm Huffmana. Jeśli znamy już częstotliwości wszystkich znaków musimy zbudować specjalne drzewo binarne, które w prosty sposób pozwoli nam na ich znalezienie. Zanim przejdę do jego omawiania o to jak będzie ono wyglądało dla danych z wcześniejszego przykładu:

Mając drzewo w takiej postaci znalezienie kodów jest trywialne. Zwróćmy uwagę, że w liściach znajdują się poszczególne znaki z naszego alfabetu (są tam też podane ich częstotliwości). Jeśli chcemy znaleźć kod dla danego znaku, dla którego znamy już częstotliwość występowania, zaczynając w korzeniu z kodem "" idziemy w dół dodając do kodu '0' jeśli obieramy lewą krawędź i '1' jeśli prawą aż dotrzemy do docelowego znaku.

Dla przykładu dla znaku 'f' kod jest następujący: '00000' (uwaga! to nie jest 0!!!) ponieważ 5 razy obieramy lewą krawędź, dla 'c' - '001', dla 'd' - '01', a dla znaku 'e' - '1'. Jak widać im częściej dany znak występuje, tym krótszy ma kod (co chcieliśmy osiągnąć).

Budowanie drzewa kodów Huffmana

Budowanie drzewa kodów jest dosyć prostą sprawą. Mając listę elementów ('lista' jest tu użyta raczej w dowolny sposób. Zwykle wykorzystywaną strukturą jest bowiem kopiec), wybieramy w każdej iteracji algorytmu dwa o najmniejszych częstotliwościach występowania po czym łączymy je w drzewo w taki sposób, że stają się one liśćmi, a ich rodzic-korzeń ma częstotliwość będącą sumą ich częstotliwości występowania. W pierwszej iteracji łączymy w ten sposób znaki 'f' oraz 'b' (o częstotliwościach odpowiednio 1 i 2) i wpisujemy 3 jako częstotliwość ich rodzica.


To drzewo z powrotem wstawiamy na kopiec, przywracamy jego właściwości i wybieramy kolejne dwa elementy. Tym razem będzie to właśnie '3' i '10' (co odpowiada znakowi 'a'). Postępujemy tak dotąd aż liczba elementów kopca nie wyniesie 1. Gdy tak się stanie - znaleźliśmy drzewo kodów.

Mając to drzewo, kompresja (kodowanie) jest bardzo proste. Przechodząc przez kolejne znaki ciągu wejściowego musimy po prostu wyszukać odpowiadający im kod, i wypisać go na wyjście. Oczywiście należy pamiętać, żeby wypisywać bity, a nie całe bajty, co można zrobić np. tak jak pokazano w poniższym pseudokodzie:

unsigned char curr = 0;
usigned int currByte = 0;
usingned int bits = 0;
while(!data.eof())
{
// First get next character from the input
curr = data.getNextCharacter();
// Look up its code in the table
const char* code = huffmanCodes[curr];

usingned char c = 0;
while(code++)
{
c = code[i];
currByte <<= 1;
// Write one in the least significant bit
if(c == '1')
currByte |= 1;
++bits;
// When whole byte has been filled write it to the output
if(bits == 8)
{
result.write((char*)&currByte, 1);
bits = 0;
currByte = 0;
}
}
}


Oczywiście należy też zadbać o ewentualne wolne bity pod koniec, ale to już pomijam.

Dekompresja danych

Jak zdekodować dane skompresowane za pomocą tego algorytmu? Aby było to możliwe musimy na wyjście zapisać dodatkowe dane, które pozwolą nam na rekonstrukcję drzewa (nie jest możliwe znalezienie częstotliwości w oparciu o skompresowane dane :) ). Mianowicie musimy zapisać drzewo kodów. Dla każdego węzła rozpoczynając od korzenia zapisujemy takie dane:
  • 0 - jeśli jest to wewnętrzny węzeł (1 bit),
  • 1 + znak - jeśli jest to liść drzewa (9 bits). Znak to oczywiście znak, z którym dany kod jest powiązany, np. dla kodu '1' w naszym przypadku będzie to 'e'.
Oczywiście oznacza to pewne dodatkowe dane, ale w większości przypadków jest to niemal pomijalne. Za to dzięki temu rekonstrukcja drzewa jest trywialna, a tym samym dekodowanie znaków również. Wczytujemy dane bit po bicie i rozpoczynając w korzeniu idziemy w lewo jeśli wartość bitu wynosi 0 i w prawo jeśli 1, aż nie znajdziemy się w liściu, co oznacza, że powinniśmy wypisać znak w nim się znajdujący i znów zacząć od korzenia, bo kolejny bit należy już do innego znaku. Oczywiście kody są jednoznaczne, np. dla naszego przykładu kod:

111001101

zostanie odkodowany jako:

eeeced

Z innych rzeczy, zarejestrowałem się na Twitterze na fali jego popularności i dodałem odpowiedni widget do tej strony.

wtorek, 15 września 2009

Run-Length Encoding

W tym wpisie opiszę algorytm RLE (Run-Length Encoding), który jest jednym z najprostszych algorytmów do bezstratnej kompresji danych i często używany jest jako jeden z kroków w kompresji (inne mogą uwzględniać np. przekształcenie Borrowsa-Wheelera czy Move-to-Front, kodowanie arytmetyczne itd.). Istota działania algorytmu jest banalnie prosta. Przechodząc przez blok danych zlicza liczbę takich samych znaków występujących obok siebie i zapisuje ją (wraz ze znakiem, którego dotyczy) na wyjściu.

W swej najprostszej postaci dla ciągu znaków:

AAAAFFFBAAAAACCDCBBBBBBBB

Algorytm zwraca:

4A3F1B5A2C1D1C8B

Ponieważ najpierw w ciągu wejściowym występują 4 litery A, 3F, 1 B, 5 A itd. Widzimy, że algorytm faktycznie dokonuje kompresji. Ale teraz weźmy ciąg znaków:

ABABABABABABABABAB

Dla niego na wyjściu otrzymujemy:

1A1B1A1B1A1B1A1B1A1B1A1B1A1B1A1B1A1B

I po kompresji - wyjście jest dwa razy dłuższe niż ciąg wejściowy!

Trzeba trochę zmienić algorytm. Lepszym rozwiązaniem jest sprawdzanie przy przechodzeniu przez blok czy obok siebie występują dwa identyczne znaki. Jeśli tak zapisujemy oba znaki na wejściu i zliczamy liczbę takich znaków za nimi. Może być to dowolna wartość, również 0. Jednak dla poprzedniego przykładu gwarantuje to, że po wykonaniu algorytmu uzyskany ciąg będzie miał taką samą długość jak wejściowy (choć nie rozwiązuje problemu dla ciągu postaci AABBAABB..).

Natomiast dla ciągu:

AAAAFFFBAAAAACCDCBBBBBBBB

Zwraca:

AA2AFF1BAA3CCDCBB2

Nieco gorzej niż poprzednio, ale nadal jest to kompresja. Ponadto przekształcenia takie jak BWT (Burrows-Wheeler Transform) sprawiają, że dane lepiej podlegają kompresji Run-Length Encoding. Ale to przekształcenie wraz z innymi opiszę nieco później.

Ale po co w ogóle zajmuję się takimi algorytmami (dotychczas pisałem głównie o graficznych)? Cóż kończę właśnie implementację wirtualnego systemu plików (VFS) dla nGENE Tech, więc były mi one potrzebne by mieć możliwość kompresji archiwum.

EDIT:
Aha... i jeszcze jedna sprawa. Być może jest to jeden z ostatnich wpisów w języku polskim. Od pewnego czasu prowadzę też anglojęzyczną wersję bloga i niestety prowadzenie dwóch blogów jest nieco uciążliwe (2x więcej pisania :) ).

piątek, 11 września 2009

Light-shafty update

Niedawno była notka o light-shaftach. Wówczas prezentowany filmik zawierał jednak denerwujący artefakt... jeśli komuś podobało się wtedy niech obejrzy teraz (powinno być DUŻO lepiej). Po komentarzu Rega, zmieniłem model testowy :)

czwartek, 10 września 2009

nGENE Update

Tym razem będą tylko obrazki... sporo poprawek, zachęcam do ściągania aktualnego kodu z repozytorium SVN :) Do ideału ciągle daleko, ale uwierzcie... poprawiłem tyle rzeczy w ostatnich tygodniach, że sam w to niemal nie wierzę :)

niedziela, 6 września 2009

Renderowanie trawy

Jak wszyscy wiemy renderowanie trawy nie jest łatwym zadaniem. Nawet zastosowanie najbardziej popularnej techniki tj. renderingu trawy jako billboardy mierzymy się z godnym przeciwnikiem - olbrzymim spadkiem wydajności jeśli chcemy trawy wyrenderować naprawdę dużo.

Są dwie rzeczy, które pomagają ten problem rozwiązać:
  • pierwsza jest w miarę naturalna. Batchujemy billboardy o tej samej teksturze i renderujemy razem. Często dobrym pomysłem jest zrobienie takich batchy jak rozmiar kratek terenu (bo pewnie na nim przyjdzie nam trawę umieścić). Wówczas jeśli kratka jest ukryta, to paczka źdźbeł trawy również.
  • nie używanie alpha blendingu. Zamiast tego stosujemy alpha testing + dissolving. Dissolving pomoże nam nadrobić niedostatki testu alfa. Polega to w uproszczeniu na tym, że wartość alfa mnożymy przez wartość pobraną z tekstury tego typu (losowe wartości, ale na tyle gładkie, żeby efekt był przyjemny dla oka):Dodatkowo jeśli stosujemy takie batche jak napisałem wcześniej warto zadbać o to, aby wartość referencyjna dla alpha testu była zmienna pomiędzy nimi w taki sposób, aby te będące dalej od kamery zanikały (inaczej uzyskamy bardzo niefajny efekt :) ).
W nGENE'ie wygląda to aktualnie tak:

Dodatkowo aby rozwiązać problem z prawidłowym oświetleniem trawy ustawiam normalną dla billboardów na normalną terenu w punkcie umieszczenia danego źdźbła. Wygląda to całkiem ok.

Nadal nienajlepiej, ale nie dysponuję teksturami trawy wyższej jakości :) Za to w powyższej scenie jest ich naprawdę sporo. Jeśli w ogóle chodzi o nGENE poprawiłem w ostatnim czasie całą masę rzeczy i usterek. Od mniejszych, po większe czy nawet dosyć poważne błędy. Od jutra kontynuuję przerwaną implementację animacji szkieletowej (przerwaną zgłoszeniami błędów):

środa, 2 września 2009

Zdjęcia: makro, HDR, light graffiti

Ostatni weekend spędziłem pod znakiem fotografii, daleko od komputera i programowania nGENE'u (ale dzięki temu teraz mam dużo energii i zapału :) i aktualnie kończę wrappować jointy z PhysXa, poprawiać GUI i różne bugi). Postanowiłem przećwiczyć kilka technik, o których dużo ostatnio czytałem, ale albo nie miałem okazji wcześniej ich przećwiczyć, albo poprzednie próby było średnio udane. Mam tu na myśli:

  • light graffiti,
  • makro fotografia,
  • HDR.

Miałem też zamiar znów porobić zdjęcia nocne, ale warunki były mocno niesprzyjające (duże zachmurzenie, deszcz, mało naturalnego światła).

Light graffiti

Light graffiti to ciekawa technika polegająca na tym, że w nocy (lub w bardzo ciemnym pomieszczeniu) stajemy naprzeciw obiektywu ubrani na czarno ze słabą latarką w dłoni. Czas naświetlania ustawiamy na dosyć długi (kilka sekund powinno wystarczyć), ISO na ok. 100 i jak najmniejszą przesłonę (oczywiście zależy to od warunków i mocy naszej latarki, ale chodzi o to, żeby mieć chwilę czasu na rysowanie) po czym rozpoczynamy "malowanie" światłem. A możemy narysować, napisać co tylko chcemy... Ja się np. podpisałem:

Oczywiście gdyby było jakieś tło byłoby dużo lepiej. Miały być gwiazdy, ale musiałbym dać jakiś potwornie długi czas ekspozycji przy zastanych warunkach, więc zrezygnowałem z tego pomysłu.  Tutaj obraz jest w 1 płaszczyźnie, ale spokojnie można się pokusić o rysowanie w 3-wymiarach (wstęgi i inne cuda).

Jedyne o czym należy pamiętać to to, że obraz finalny będzie w lustrzanym odbiciu. Rozwiązania są dwa. Albo rysować w lustrzanym odbiciu, albo odwrócić zdjęcie w programie graficznym. Wbrew pozorom to pierwsze też jest po chwili treningu wykonalne :)

Makro fotografia

Następnego dnia zgodnie z prognozami pojawiły się opady deszczu. Ponieważ wiedziałem, że gwarantuje to ciekawe zdjęcia makro z niecierpliwością czekałem na koniec opadów. I doczekałem się. Poniżej pokaz slajdów z Picasy:

Do zrobienia zdjęć użyłem filtra, soczewki +10 dioptrii, ale teraz poważnie zastanawiam się nad zakupem odpowiedniego obiektywu, bo zabawa jest przednia. Obserwowanie makro świata jest fascynujące, aż trudno uwierzyć jak wiele szczegółów umyka naszej uwadze.

HDR

Robienie zdjęć HDR już kiedyś opisywałem. Tutaj ostatnie osiągnięcia w tej dziedzinie (również pokaz slajdów):

W skrócie zdjęcia robione z wykorzystaniem mechanizmu exposure bracketing (w przypadku mojego Canona EOS 400D oznacza to serię 3 zdjęć), czyli automatyczne zmienianie ustawień ekspozycji pomiędzy kolejnymi zdjęciami. Część zdjęć wykonana ze statywu, część z ręki. Zdjęcia nie są naturalne, ale po obejrzeniu iluś setek zdjeć HDR w internecie stwierdziłem, że mało kto się o to stara, a tak przynajmniej można osiągnąć ciekawe efekty.

czwartek, 27 sierpnia 2009

Renderowanie light-shaftów

Zrobiłem sobie chwilę przerwy w pracach nad animacją i usuwaniem bugów na zaprogramowanie light-shaftów. Efekt jest o tyle fajny, że jest stosunkowo prosty a jednocześnie pozwala na łatwe zbudowanie atmosfery tajemniczości czy grozy.

Technika, którą wykorzystuję to technika zaproponowana przez Jasona Mitchella w ShaderXach. Stara, prosta, ale w większości przypadków wystarczająca.

Light-shafty to po polsku promienie światła :) chodzi jednak o to, że są one widoczne gołym okiem, a to za sprawą cząsteczek w powietrzu (np. kurz), które odbijają i rozpraszają światło.

Ponieważ jest to efekt wolumetryczny, należy skorzystać do jego wyrenderowania z jednego ze sposobów renderingu tego typu efektów. Możliwości jest kilka, stosunkowo często wykorzystywana to rozbicie objętości na kilka (-naście, -dziesiąt) równoległych do siebie płaszczyzn, na które później nanoszone są różne tekstury z użyciem rzutowania. Oczywiście od liczby tych płaszczyzn zależy jakość oraz wydajność całego efektu. Płaszczyzny te, w tym przypadku renderowane są prostopadle do wektora patrzenia w taki sposób, aby wypełniały całą bryłę otaczającą stożka światła.

Najpierw tworzona jest kostka wierzchołków o wymiarach 1 x 1 x 1 (a dokładniej grupa płaszczyzn o wymiarach 1 x 1 w odległości 1 / (liczba_płaszczyzn - 1) od siebie). Następnie w vertex shaderze wierzchołki są przemieszczane w odpowiednie miejsce w następujący sposób:

float4 stretchedMaxBounds = float4(maxBounds, 0.0f);
float4 pos = float4(minBounds, 0.0f) * IN.position +
(stretchedMaxBounds * (1.0f - IN.position));
co sprawia, że wypełniają całą bryłę otaczającą stożka światła. W powyższym kodzie: minBounds i maxBounds opisują prostopadłościan otaczający stożek światła w przestrzeni widoku.

W pixel shaderze wykonywany jest cały scattering. Zanik światła to oczywiście standardowe 1 / (odległość * odległość).

Ponieważ na tym etapie light-shafty wyglądałyby nieco nienaturalnie w dalszej kolejności dodamy kilka ulepszeń:

  • Gobo/cookie - wzór nakładany na wszystkie płaszczyzny,
  • Animowany szum - sprawi, że uzyskamy efekt poruszającego się powietrza,
  • Cień - nałożenie shadow mapy sprawi, że light-shaft nie będzie widoczny w zacienionym miejscu.

Aha... polecam też stosować płaszczyzny przycinania, ponieważ w przeciwnym razie pixel shader będzie uruchamiany dla pikseli, które i tak powinny zostać odrzucone, bo leżą poza stożkiem światła. Najlepiej wykorzystać w tym celu płaszczyzny opisujące stożek światła.

Gobo, cookie

Jest to nic innego jak wzór nakładany na wszystkie płaszczyzny. Tam gdzie mamy kolor biały rysujemy piksele. Tam gdzie czarny - odrzucamy. W ten sposób można light-shaftowi nadać konkretny kształt i wygląd zamiast ograniczać się do zwykłego przekroju koła.

Przykładowe "ciasteczko":

Animowany szum

Animowany szum sprawi, że uzyskamy efekt poruszającego się powietrza. Wystarczy nałożyć jakąś teksturę szumu i wykorzystać zmieniające się w czasie współrzędne tekstury.

Cień

Wykorzystujemy shadow mapę wyrenderowaną dla jakiegoś światła. Następnie w pixel shaderze sprawdzamy czy dany piksel należący do płaszczyzny light-shafta jest dalej niż odpowiadający mu w shadow mapie. Jeśli jest większy tzn., że znajduje się on w cieniu, więc odrzucamy go.

A o to przykład działania z nGENE'a:

Jest jeszcze sporo niedoróbek i błędów, ale są proste do usunięcia, a chciałem jakoś zobrazować swój wywód. Na początku cookie jest włączone, od mniej więcej połowy - wyłączone.