Posts tagged ‘graphics programming’

Płaska wizualizacja funkcji 3D

2009-06-02 18:43

Funkcje, których dziedziną jest podzbiór R2 (czyli płaszczyzna) ciężko jest przedstawić na płaskim ekranie w sposób poglądowy, a jednocześnie pozwalający na odczytanie jakichś wartości. O ile bowiem izometryczny rzut 3D wygląda efektownie, to poza ogólnym kształtem powierzchni nie przedstawia wielkiej ilości informacji. Sprawa wygląda trochę lepiej, jeśli taką figurę możemy jeszcze obracać… co aczkolwiek może być z kolei trudne do zrealizowania np. na wydruku :]

Jednym z wyjść może być tutaj zastosowanie sposobu używanego do map wysokości w grach. Nietrudno zauważyć, że są one niczym innym jak właśnie funkcjami typu R2->R, a reprezentowane są najczęściej jako bitmapy w odcieniach szarości: jasność piksela x,y odpowiada w nich wysokości odpowiedniego punktu terenu.
Takie heightmapy wprawdzie łatwo się rysuje, ale niespecjalnie nadają się do pokazania jako ilustracja czegokolwiek – odcienie szarości są przecież z definicji szare i nieciekawe :) Dlatego bardzo spodobał mi się prosty pomysł na uczynienie tej reprezentacji znacznie ładniejszą, o którym to usłyszałem niedawno.

Idea jest prosta: po co używać samych szarości, skoro mamy do dyspozycji całą paletę kolorów?… Każdy kolor w reprezentacji RGB jest przecież tożsamy z liczbą, co widać najbardziej, jeśli weźmiemy jego typową reprezentację 32-bitową:

W niej “największym” kolorem jest kolor biały (0x00FFFFFF, czyli 224 – 1), zaś “najmniejszym” czarny (0x0), a pomiędzy nimi włącznie występuje każdy z 2563 możliwych kolorów. Jeśli teraz przeskalujemy wartości naszej funkcji do tak określonego przedziału (lub nieco mniejszego), a następnie przekonwertujemy każdą z tych 32-(a właściwie 24-)bitowych liczb na odpowiadający jej kolor RGB, to otrzymamy w ten sposób heightmapę o znacznie bogatszej palecie barw niż poprzednio.
Jeśli ponadto używamy dokładnie takiej reprezentacji liczbowej kolorów, jak powyżej (najmłodsze bity na kolor niebieskie, a najstarsze używane na czerwony), to zauważymy, że rozkład barw w wyniku jest całkiem znajomy. I tak obszary niebieskie odpowiadają wartościom najmniejszym, zielone średnim, żółte – wysokim, a czerwone – najwyższym. Łącznie przypomina mapę hipsometryczną – czyli, jakby nie patrzył, mapę wysokości właśnie :)

Do czego może przydać się takie cudo, oprócz wspomnianej już wizualizacji funkcji R2->R na płaszczyźnie? Pewnie do niewielu rzeczy :) Ja wpadłem jednak na jeszcze jedno potencjalne zastosowanie.
Ponieważ wynikowy obrazek używa tak “geograficznych” barw, może on całkiem dobrze wyglądać jako… tło minimapy pokazywanej w grze, która rozgrywa się na terenie opisanym mapą wysokości. Oczywiście rzecz wymagałaby dopracowania: przede wszystkim bardziej starannego dobrania kolorów brzegowych (niekoniecznie białego i czarnego) oraz być może kolorów pośrednich (np. kolor poziomu morza), a i pewnie zastosowania jakichś filtrów na wynikowym obrazku (np. wygładzania). Efekty mogłyby być jednak interesujące, co widać obok.

Nie tylko alpha blending

2009-03-18 10:31

Uaktywnienie łączenia alfa w DirectX na pierwszy rzut oka wydaje się dość skomplikowana. Niby występuje tam oczywiste ustawienie stanu renderowania D3DRS_ALPHABLENDENABLE na true, jednak to nie wszystko. Potem zwykle od razu występują przynajmniej dwa inne stany: D3DRS_SRCBLEND i D3DRS_DESTBLEND, ustawiane na równie tajemnicze wartości. Wyjaśnimy sobie dzisiaj, co one oznaczają i jak można je wykorzystać.

Należy zacząć od tego, że ten zagadkowy alpha blending to nic innego, jak zwykła suma ważona:

SourceBlend * SourceColor + DestBlend * DestColor

(z dokładnością do tego, że dodawanie można zastąpić innym działaniem przy pomocy D3DRS_BLENDOP, ale tę możliwość zostawimy w spokoju ;]). To, co jest tutaj sumowane, to dwa kolory: źródłowy – pochodzący od właśnie mającego się narysować piksela, oraz docelowy – będący już w danym miejscu bufora ramki (ekranu). Normalnie ten pierwszy zwyczajnie zastąpiłby ten drugi, ale dzięki łączeniu alfa można zamiast tego uzyskać jakąś kombinację ich obu – czyli na przykład symulację półprzezroczystości. Wagami dla sumy są SourceBlend i DestBlend, i to właśnie je ustawiamy przy pomocy stanów renderowania.

Możliwych wartości jest kilka i obejmują one zarówno tak proste jak zero czy jeden aż po same kolory: źródłowe i docelowe (podobnie jak one, współczynniki te są 4-elementowymi wektorami), wartości ich kanału alfa, specjalny globalny współczynnik blend factor – jak również negacje tych wszystkich wartości. Sporo tego, prawda? :) Zobaczmy więc kilka typowych kombinacji:

  • SourceBlend = SourceAlpha; DestBlend = InvSourceAlpha

    To najbardziej typowy sposób osiągania przezroczystości. Używamy tutaj źródłowej wartości alfa, by określić, jak bardzo kolor źródłowy ma wpływać na wynikowy oraz jak bardzo ma być pominięty wpływ koloru docelowego – że tak spróbuje się “intuicyjnie” wyrazić :) Ostatecznie mamy wtedy równanie typu:

    SourceAlpha * SourceColor + (1 – SourceAlpha) * DestColor

    czyli tzw. kombinację liniową obu kolorów, która w wyniku daje wrażenie półprzezroczystości.

  • SourceBlend = One; DestBlend = One

    Ustawienie obu wag na 1 sprawia, że nasza suma ważona stają się zwykłą sumą, więc kolory są po prostu dodawane. Taka konfiguracja jest stosowana na przykład do łączenia ze sobą efektów oświetlania sceny kilkoma różnymi światłami i nazywa się blendingiem addytywnym.

  • SourceBlend = DestColor; DestBlend = Zero

    Takie dziwne ustawienie wag sprawia z kolei, że zamiast dodawania otrzymujemy w istocie mnożenie przez siebie kolorów – czyli blending multiplikatywny. Istnieje oczywiście jeszcze jedna konfiguracja współczynników dająca ten sam efekt, która jest symetryczna do powyższej.

Powyższe warianty nie są naturalnie wszystkimi możliwymi ani nawet wszystkimi praktycznie wykorzystywanymi. Widać z nich jednak, że tak zwany alpha blending nie służy tylko do przezroczystości i nierzadko wykorzystujemy go również wtedy, gdy wszystkie obiekty naszej sceny nie są ani trochę prześwitujące.

Tags: ,
Author: Xion, posted under Programming » 2 comments

Kwantowanie kierunku

2009-03-13 17:20

Wszyscy wiedzą jak, mając dane dwa punkty, wyznaczyć wektor kierunkowy od pierwszego punktu do drugiego. Niekiedy jednak nie chcemy, aby możliwe było uzyskanie dowolnego kierunku. Może tak być np. przy poruszaniu jednostkami na mapie, gdy z jakiegoś powodu chcemy dopuścić, powiedzmy, tylko 8 czy 16 podstawowych kierunków. W jaki sposób należy wtedy “zaokrąglić” nasz dowolny wektor, uzyskany jako różnica aktualnej pozycji jednostki i np. miejsca kliknięcia myszką przez gracza?

Pierwszym możliwym sposobem jest zamiana jego reprezentacji ze współrzędnych prostokątnych (x, y) na biegunowe (r – długość, theta – kąt między dodatnią półosią X a wektorem). Jest to możliwe przy pomocy obecnej w wielu językach funkcji atan2(y, x). Wystarczy potem sprawdzić, któremu wzorcowemu wektorowi jest najbliższy otrzymany kąt.

Swego czasu znalazłem jednak nieco inny sposób, który daje się zastosować bez zmiany reprezentacji wektora. Oryginalnie służył on do wyboru jednego z ośmiu podstawowych kierunków, ale nietrudno było zauważyć, że da się go trochę uogólnić:

  1. VEC2 Quantize(const VEC2 v, int k)
  2. {
  3.     v.Normalize();
  4.     v *= k;
  5.     return VEC2((int)Round(v.x), (int)Round(v.y));
  6. }

Użyta tutaj funkcja Round(t) oznacza zaokrąglenie w stronę najbliższej liczby całkowitej, czyli floor(t + 0.5). Współczynnik k kontroluje natomiast, ile wektorów wzorcowych znajduje się w naszej “róży wiatrów”. Powyższa funkcja dzieli bowiem kąt pełny na dokładnie 2k+2 wycinków, dając tyle też możliwych wektorów wynikowych. Osiem kierunków otrzymamy więc dla k = 1, szesnaście dla k = 2, itd. Teoretycznie możliwe są też ułamkowe wartości współczynnika, co najprawdopodobniej pozwałoby kontrolować “czułość dopasowania” wektora do wzorca. Nie podjąłem się jednak opracowania jakiejś ścisłej zależności :) Jedyne co udało mi się zaobserwować to to, że dla k = 2/3 otrzymujemy dopasowanie do czterech podstawowych kierunków (N, S, W, E).
Ogólnie więc nie jest to może epokowe odkrycie w dziedzinie wektorologii stosowanej, ale istnieje szansa, że komuś do czegoś się przyda ;]

Tags: , ,
Author: Xion, posted under Programming » Comments Off on Kwantowanie kierunku

Grzechy grafiki

2008-11-14 22:22

Karty graficzne mogą podołać renderowaniu coraz większej liczby wierzchołków, więc grafika w grach jest coraz bardziej szczegółowa – to oczywiste. Niezbyt oczywiste jest jednak to, czy jest ona faktycznie lepsza, bowiem nawet mając bardzo dobrze zrobione modele i tekstury niekoniecznie można złożyć je w przyzwoicie wyglądający świat.
Według mnie jest kilka głównych grzechów, jakie można przy tej okazji popełnić:


  • Model postaci ładny,
    ale co to za teren?

    Syndrom pustyni z drzewkami. Nazywam w ten sposób wrażenie, że na scenie obiekty niespecjalnie pasują do siebie. Przykładem czegoś takiego jest heightmapa pokryta niezbyt urozmaiconą teksturą (stąd ‘pustynia’), do której “przyklejone” są wyraźnie odcinające się drzewa. Taki teren jest jak najbardziej poprawny w demie silnika, ale w grze oczekiwalibyśmy trochę więcej urozmaicenia i trochę więcej spójności. Może jakieś trawy i krzaczki? ;)

  • Ocean w kałuży. Woda to okazja do wielu ciekawych efektów, z refleksami światła i odbiciami na czele. Jednak inaczej wygląda duży zbiornik wodny, a inaczej małe jeziorko. Dlaczego więc tak często widzimy oceany płaskie jak stół i stawy, w których wręcz szaleją sztormy? Czyżby tak trudno było dobrać wagi dla używanej w vertex shaderze funkcji sinus? :)

  • Uwaga na słońce! ;P

    Za dużo blooma. Odpowiednie efekty typu post-process to, oprócz upiększania sceny, także dobry sposób na to, by przyciągnąć uwagę do wybranych miejsc (w domyśle: tych lepiej zrobionych niż inne). Jeśli jednak przesadzi się z bloomem, lens-flare i innymi pseudorealistycznymi upiększaczami, to całość robi się mniej imponująca, a bardziej męcząca.

To na pewno nie wszystkie błędy, które dają się w miarę łatwo zauważyć. Wydaje mi się jednak, że te należą do jednych z łatwiejszych do popełnienia. W końcu skoro już napisaliśmy jakiś fajny shader, to warto go wcisnąć, gdzie się da, prawda? :) No cóż, nie zawsze jest to dobry kierunek postępowania…

Tags: , ,
Author: Xion, posted under Games, Thoughts » 5 comments

Engine jest wielki, a ja malutki

2008-10-31 18:03

Zapewne niespotykana często koniunkcja planet sprawiła, że na forum Warsztatu pojawiło się ostatnio kilka przypadków dość specyficznych pytań. Ich autorzy chcieli dowiedzieć się, czy – najogólniej mówiąc – są w stanie coś konkretnego w dziedzinie programowania osiągnąć. “Czy dam radę napisać taką-a-taką grę?” jest przykładem takiego właśnie pytania.

Skrajnie zresztą dziwnego, moim skromnym zdaniem. Nie chodzi tu nawet o fakt, że odpowiedź na podobne pytanie jest niemożliwa do udzielenia. Bardziej zdumiewa mnie to, iż obecni adepci sztuki koderskiej jakoś lubią wynajdywać sobie różne potencjalne przeszkody. A może za mało wiem o programowaniu? Może za słabo znam matematykę? Albo jestem po prostu za młody?… Takie i podobne “argumenty” są bowiem przytaczane przy okazji wspomnianych pytań.
Jest dla mnie dosyć zaskakujące, że dzisiejsi ‘młodzi’ (raczej w sensie umiejętności) tak szybko się “starzeją”. Jeszcze całkiem nieźle pamiętam, że będąc początkującym prawie w ogóle nie przejmowałem się realnością podejmowanych amatorskich projektów. Gra 3D, strategiczna czy sieciowa (MMORPG-ów raczej wtedy nie było) z innowacyjnym gameplayem i nowatorskimi rozwiązaniami technicznymi? Ależ oczywiście, toż to praca najwyżej na tydzień ;D Jak się kończyły takie zamiary, nietrudno zgadnąć. Ani mi, ani podobnym do mnie ‘kolegom po fachu’ nie przeszkadzało to jednak próbować.

Co więc zmieniło się, że teraz widzę coraz więcej nowicjuszy pełnych wątpliwości? Czy chociażby programowanie gier stało się trudniejsze? Powiedziałbym raczej, że wręcz przeciwnie (weźmy np. coraz więcej gotowych silników i coraz lepsze wersje graficznych API), a trend ten wydaje się przy tym stabilny. Ale nawet gdyby tak nie było, to jaki sens zadawanie niedorzecznych pytań w stylu “Czy mi się uda?”. Nie, nie uda ci się – to mogę ci powiedzieć w ciemno. Z każdego nieudanego projektu można jednak wyciągnąć wnioski i nauczyć się pożytecznych rzeczy. I dlatego nie ma co przerażać się, że to trudne, niezrozumiałe czy wymagające napisania wielkich ilości świetnie działającego kodu. Wszystko przecież przychodzi z czasem, a porażki są nieodłączną częścią postępów w nauce.
Zatem – motyki w garść, Słońce czeka ;)

Prawie jak DirectX

2008-09-14 23:28

Przedwczoraj w końcu udało mi się skończyć pewien “drobny” projekt, który – pomimo tego że był ściśle związany z programowaniem grafiki – nie należał wcale do lekkich, łatwych i przyjemnych. To pewnie dlatego, że chodzi tu o software’owy renderer, który (na)pisałem w Javie (!) jako projekt uczelniany. Niewykluczone, że niektórzy pamiętają jeszcze, o co chodzi ;-)
W wymaganiach przewidziana była na szczęście tylko jedna, za to konkretna scena – mianowicie pewien znany skądinąd most. W porównaniu z oryginałem nie wyszedł on może specjalnie imponująco, ale nie zapominajmy, że każdy z jego pikseli musiał zostać pieczołowicie wyliczony przez biedny procesor ;) Rezultaty prezentują się zaś następująco:

 

I wprawdzie tego nie widać, ale manipulowanie tą sceną (złożoną z kilkuset trójkątów) mogło się odbywać w sposób całkiem płynny – o ile oczywiście powiększenie nie było zbyt duże :) Tym niemniej nie wróżę jednak temu rozwiązaniu zbyt wielu praktycznych zastosowań. Co oczywiście ani na jotę nie zmienia faktu, że jego implementacja okazała się całkiem pouczająca. Jednym z praktycznych wniosków jest chociażby to, że modelowanie za pomocą ręcznego opisywania sceny we własnym formacie opartym na XML to zdecydowanie nie jest dobry pomysł ;]

Tags: ,
Author: Xion, posted under Programming, Studies » 8 comments

Wektory w różnych przestrzeniach

2008-07-28 17:26

Kiedy zaczynamy uczyć się programowania, dowiadujemy się, że zmiennym przypisane są zawsze konkretne typy, których należy się trzymać. Trochę później, gdy zajmujemy się już shaderami i programowalnym potokiem graficznym, okazuje się, że większość wartości jest tam tego samego typu (wektor trzech lub czterech floatów). Różnią się one jednak przypisaną im semantyką, czyli znaczeniem w opisie wierzchołka lub piksela (POSITION, NORMAL, COLOR, i tak dalej).
Jednak sama semantyka nie jest jedyną cechą charakterystyczną danego wektora. Nawet mając do czynienia z dwoma wektorami “geometrycznymi” (czyli opisującymi pozycję lub normalną, a nie np. kolor), nie zawsze możemy wykonywać na nich łączne operacje. Należy bowiem zadbać o to, by wektory te znajdowały się w tej samej przestrzeni. W przeciwnym razie konieczne jest odpowiednie przekształcenie, co sprowadza się do pomnożenia przez jakąś macierz.

Aby łatwiej zorientować się, z którą przestrzenią mamy do czynienia, można stosować odpowiednie nazewnictwo, na przykład w postaci sufiksów. Przyrostki te mogą być przyporządkowane choćby następująco:

  • _o (np. vPos_o) niech odpowiada przestrzeni obiektu (object space), związanej z układem lokalnym konkretnej instancji modelu
  • _w (np. vPos_w) może oznaczać globalną przestrzeń świata (world space)
  • _v to z kolei przestrzeń widoku (view space), związana z pozycją obserwatora
  • _t możemy przyporządkować do tangent space przy obliczeniach związanych z oświetleniem
  • _l może w końcu być przyrostkiem wektorów w przestrzeni związanej z pozycją konkretnego światła

Oczywiście cały ten pomysł na kilometr śmierdzi sławetną notacją węgierską, jednak w tym przypadku “dekorowanie” nazw zmiennych ma znacznie głębszy sens. Błędów spowodowanych operacjami na wektorach z różnych przestrzeni nie wykryje nam bowiem żaden kompilator, a ich samodzielne wyśledzenie bywa bardzo trudne (podobnie jak większości innych błędów matematycznych). Dlatego należy zawsze zwracać baczną uwagę na to, z jaką przestrzenią aktualnie pracujemy, i stosować jeśli nie specjalne nazewnictwo, to chociaż szczegółowe i jednoznaczne komentarze.

Tags: ,
Author: Xion, posted under Programming » 4 comments
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.