Posts tagged ‘graphics programming’

Całkiem proste krzywe

2007-12-13 22:27

W grafice dąży się do tego, żeby rezultat był ładny. Jest to rzecz jasna pojęcie bardzo względne, ale wiadomo na przykład, że kształty opływowe są generalnie lepiej postrzegane niż kanciate. W projektowaniu królują więc linie krzywe, a jednym z ich rodzajów są krzywe wielomianowe stopnia trzeciego, zwane też kubicznymi.

Jak ich nazwa wskazuje, są one opisane wielomianami stopnia trzeciego, czyli ich równania parametryczne zawierają współczynniki w trzeciej potędze. Taki stopień wystarcza, by krzywe te były wystarczająco gładkie (zarówno w rozumieniu matematycznych – ciągłość pochodnych – jak i zwyczajnie “na oko”), a jednocześnie niezbyt kosztowne obliczeniowo. Ponadto można dla nich relatywnie łatwo określić zależność między poszczególnymi czterema parametrami a kształtem wynikowej figury.
Krzywa BezieraTo jednak zależy od tego, jaką reprezentację przyjmiemy. Mającą najciekawsze własności i pewnie najpopularniejszą jest reprezentacja Béziera, stworzona przez pewnego włoskiego inżyniera w latach 60. ubiegłego stulecia. Mogę przypuszczać, że niemal każdy średnio zorientowany programista czy grafik słyszał o tego rodzaju krzywych – chociażby dlatego, że narzędzie Krzywa w Paincie jest właśnie krzywą Béziera ;) Spotyka się je też zresztą w bardzo wielu miejscach, łącznie z tekstem (czcionki TrueType są opisane takimi właśnie splajnami).

Dlaczego?… Jak wiadomo, krzywa trzeciego stopnia w postaci Béziera jest opisana czterema punktami: początkiem, końcem oraz dwoma punktami kontrolnymi, których położenie determinuje kształt krzywej. Współrzędne tych czterech punktów można podstawić do równania parametrycznego i dla wartości parametru przebiegających od 0 do 1 wyliczyć dowolny punkt leżący na krzywej.
W praktyce ta reprezentacja ma kilka zalet:

  • Dobrze widać, w jaki sposób położenie punktów kontrolnych wpływa na kształt krzywej. Wystarczy przez chwilę pobawić się choćby wspomnianym narzędziem z programu Paint, by zobaczyć, że punkty te dość intuicyjny sposób “wyciągają” krzywą, której kształt jest jakby wypadkową przyciągania dwóch “sił”.
  • Można dokładnie określić wielokąt otaczający krzywą: jest to czworokąt opisany na czterech punktach, które ją definiują. To oczywiście przydaje się do różnego rodzaju przycinania.
  • Aby przekształcić krzywą Béziera poprzez translację, obrót lub skalowanie, wystarczy przekształcić opisujące ją punkty. Taka transformacja jest więc bardzo tania, zwłaszcza w porównaniu z przekształcaniem każdego z wielu punktów, których użylibyśmy do przybliżania naszej krzywej.
  • Łatwo jest też sklejać krzywe Béziera za sobą, zachowując w punktach połączeń nie tylko ciągłość, ale również w miarę potrzeby gładkość taką samą, jak na całej długości sklejanych krzywych.

Krzywą Béziera można także w miarę szybko narysować, jako że przy stałym (i małym) kroku dla parametru równania przybliżone wyliczanie kolejnych punktów wymaga tylko dodawań. To aczkolwiek cecha wszystkich krzywych wielomianowych, podobnie też jak jedna z ich wad: niemożność dokładnego odwzorowania okręgu. W praktyce gdy ogranicza nasz rozdzielczość pikselowa, taka niedokładność nie jest jednak zbytnim problemem.

Tak więc krzywe Béziera są obecnie prawdopodobnie najlepszym sposobem reprezentowania linii krzywych zarówno na płaszczyźnie, jak i w przestrzeni. Ale oczywiście w przestrzeni trójwymiarowej moglibyśmy chcieć zobaczyć coś więcej, na przykład… płaty Béziera :) To już jednak temat na inną okazję. Na razie mogliśmy się przekonać, że wersje dwuwymiarowe są, jak na krzywe, całkiem proste ;]

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

HLSL i kolorowanie składni

2007-11-25 21:38

Niby kod można pisać w Notatniku, ale własnej równowagi psychicznej chyba lepiej zaopatrzyć się w edytor, który oferuje przynajmniej podświetlanie elementów składniowych języka. Wiadomo przecież, że mnogość kolorów poprawia samopoczucie :)
Co więc zrobić, gdy zamierzamy pisać efekty w języku HLSL (lub bardzo podobnym Cg)? Trzeba zdecydować się na jakieś narzędzie. Możliwych jest kilka wyjść:

Jak widać, nie jesteśmy więc skazani na surową, czarno-białą czcionkę. A to dobrze, bo po dodaniu tej całej skomplikowanej matematyki, dziwnej semantyki dla danych wierzchołków i niezliczonych dyrektyw kompilacji warunkowej, kod shaderów jest już wystarczająco skomplikowany :)

Finalne demo Rega

2007-11-20 0:31

Regedit zaprezentował dzisiaj w końcu demko prezentujące możliwości jego silnika, który – jak sam utrzymuje – niedawno ukończył. Wprawdzie ja nadal mam wątpliwości, czy wyrazy ‘silnik’ i ‘ukończony’ w ogóle powinny pojawiać się obok siebie, ale nie zmienia to faktu, że przynajmniej aktualnie istniejąca część jego engine‘u prezentuje się imponująco. Moje gratulacje!

The Final Quest by Regedit - screen z dema The Final Quest by Regedit - screen z dema The Final Quest by Regedit - screen z dema

Po więcej szczegółów zapraszam tutaj:

The Final Quest

A ja w międzyczasie mogę dumać nad tym, ile to jeszcze miesięcy i lat zajmie mi dojście do podobnego poziomu ;)

Tags: , , ,
Author: Xion, posted under Internet, Programming » Comments Off on Finalne demo Rega

Szare shadery

2007-11-19 19:58

Dawno, dawno temu – co w dziedzinie programowania grafiki oznacza perspektywę kilkuletnią – większość przetwarzania odbywała się we wbudowanym, stałym potoku graficznym. Shadery wprawdzie były, ale oferowane przez nie możliwości były dosyć ubogie (zwłaszcza jeśli chodzi o te do pikseli), a poza tym należało kodować je w niezbyt przyjaznym języku podobnym do asemblera. Pewnie dlatego, a dodatkowo także z powodu małego wsparcia kart graficznych, większość efektów realizowano przy pomocy odpowiednich tekstur, stanów ich faz i tym podobnych wynalazków. Bardzo często tak było po prostu łatwiej.
Zadziwiające jest to, że teraz nierzadko bywa dokładnie odwrotnie :) Chociaż na początku można jeszcze uważać, że ustawianie przeróżnych stanów stałego potoku i poleganie wbudowanie jest łatwiejsze, a shadery dają “tylko” większe możliwości, to jednak z czasem to myślenie zawsze powinno się zmienić. W końcu przecież napisanie algorytmu określającego dokładnie krok po kroku, co się dzieje z wierzchołkiem lub pikselem, dla programisty z założenia musi być łatwiejsze niż ustawianie jakichś dziwnych flag, będącymi swoistymi wytrychami do standardowego sposobu przetwarzania.

Zresztą nie wszystko można tymi wytrychami łatwo osiągnąć. Weźmy niezwykle prosty efekt wyświetlania w skali szarości, jak na czarno-białym zdjęciu. Jeżeli chodzi o realizację tego efektu na fixed pipeline, to chwilowo nie mam na to żadnego sensownego pomysłu – oczywiście poza podmianą wszystkich tekstur na ich ‘szare’ wersje.
A wystarczyłoby tylko użyć prostego pixel shadera, który jawnie implementuje wzór na obliczenie natężenia koloru:

  1. float3 PS(float4 cl : COLOR0, /* itd */)
  2. {
  3.    float gray = 0.3f * cl.r + 0.59f * cl.g + 0.11f * cl.b;
  4.    return float3(gray, gray, gray);
  5. }

I już, efekt gotowy. Nietrudno byłoby też połączyć go z jakimkolwiek innym. W ogólności jest jednak ‘trochę’ trudniej – ale to już temat na inną okazję ;P

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

Buforowanie stanów urządzenia

2007-10-25 19:32

Dobrą zasadą programowania obiektowego jest to, że każdy obiekt powinien w miarę możliwości dbać wyłącznie o siebie. Nazywamy to enkapsulacją. W przypadku obiektów na scenie chcielibyśmy więc, aby potrafiły one odrysować się same bez konieczności zakładania czegokolwiek choćby o stanie urządzenia przed wywołaniem metody Render danego węzła sceny.
Obiekt do samodzielnego narysowania się może jednak potrzebować ustawienia wielu parametrów: stanów renderowania, tekstur czy nawet shaderów. Z jednej strony część z nich może się powtarzać, a z drugiej pobieranie ustawień bezpośrednio od urządzenia przed każdą potencjalną zmianą kosztowałoby o wiele za dużo.

Prostym rozwiązaniem jest tutaj tzw. menedżer stanów, czyli warstwa pośrednicząca. Obiekt ten zachowuje wszystkie już ustawione stany urządzenia i kontroluje każde żądanie zmiany z zapisanymi wcześniej wartościami. Faktyczny stan urządzenia jest modyfikowany tylko wówczas, gdy nowa wartość jest rzeczywiście różna od starej.

Menedżer stanów urządzenia

Stworzenie takiego menedżera pozwala więc odciążyć nieco urządzenie zajęte ciągłym przełączaniem stanów w przypadku, gdy zgodnie ze słuszną filozofią OOPu każdy węzeł sceny renderuje się, dbając wyłącznie o siebie. Jedyną niedogodnością jest to, że w naszym menedżerze należy skopiować większość interfejsu urządzenia DirectX – a przynajmniej tę jego część, którą używamy. Jest to jednak potrzebne, aby w połączeniu z sortowaniem fragmentów geometrii sceny względem materiałów uzyskać jako taką wydajność całości.

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

Trudny wybór: sprawa materiałów

2007-10-21 14:48

W programowaniu też trzeba często podejmować różne trudne decyzje; zwykle dotyczą one kwestii projektowych. Szczególnie złożone robią się one tam, gdzie idee OOPu muszą się spotkać ze sprowadzeniem na ziemię przez wymagania wydajnościowe obecne w programowaniu grafiki i interfejs graficznych API takich jak DirectX.
Na co więc tak narzekam? Otóż chodzi o odpowiednie opakowanie systemu materiałów. Jak wiadomo, materiał to jest taka cecha geometrii, która nie jest zapisana w danych o wierzchołkach – czyli właściwie wszystko, co może być w geometrii interesujące :) Materiał określa więc cechy powierzchni (gładka, bump-mapowana), właściwości świetlne albo nawet tak fundamentalne cechy jak półprzezroczystość lub renderowanie jako siatki (wireframe).

Zakodowanie takiego systemu materiałów zgodnie z regułami programowania obiektowego oznaczałoby przede wszystkim to, że część potoku odpowiedzialna za rysowanie nie musiałaby nawet wiedzieć, z jakiego dokładnie materiału korzysta dany fragment geometrii. Za pomocą metod wirtualnych można by bowiem albo pobrać odpowiednie ustawienia stanów renderowania, albo wręcz kazać materiałowi, aby sam je ustawił.
To da się zrobić. Sęk w tym, że wszystkie te dane trzeba przekazać do shadera, który już taki sprytny nie jest. W rzeczy samej, dopiero od niezbyt w sumie długiego czasu shadery można pisać w czymś, co przypomina język wysokiego poziomu, a o OOPie czy tym bardziej polimorfizmie nawet nie ma co marzyć. Chociaż… w HLSL już teraz do ewentualnego przyszłego użycia zarezerwowano słowo kluczowe class :)

Na teraz trzeba jednak zdecydować się na rozwiązanie pośrednie, które mniej więcej zadowoli obie strony – elastyczną obiektowość i “sztywny” shader. Osobiście widzę trzy rozwiązania dla organizacji klas(y) materiałów w silniku:

  • Jedna duża klasa materiału, zawierająca pola z wartościami wszystkich możliwych ustawień. Jest więc flaga półprzezroczystości, wskaźnik na ewentualną normal-mapę, wskaźnik na tekstury powierzchni i tym podobne. Zaletą tego rozwiązania jest prostota (chociaż wynikowa klasa byłaby dość duża) i w łatwość “wyciągania” danych potrzebnych do sortowania fragmentów geometrii.
  • Kilka klas ułożonych w nieskomplikowaną hierarchię dziedziczenia. Jej ‘rozpiętość pionowa’ (możliwość dodawania kolejnych klas pochodnych) byłaby aczkolwiek pozorna, bo cała ta hierarchia musiałaby znaleźć odbicie w shaderze.
  • Jedna klasa materiału, składającego się z abstrakcyjnych “klocków” definiujących poszczególne jego cechy. To właśnie według nich odbywałoby się sortowanie geometrii. Konsekwencją jest jednak to, że w podobny (addytywny) sposób musiałby też być (s)konstruowany shader.

Zapewne to ostatnie rozwiązanie jest najbardziej elastyczne, rzeźnickie i w ogóle “naj” – tyle że jest też najbardziej skomplikowane i zdecydowanie nie chciałbym się za nie zabierać już za pierwszym razem. Dlatego, jak sądzę, powinienem wybrać coś między opcją pierwsza a drugą. Obie są właściwie pewnymi odcieniami tego samego wariantu, w którym możliwe cechy materiału są po prostu wpisane na sztywno w kodzie – zarówno samej aplikacji, jak i shaderów.
W przypadku drugiej opcji istnieje naturalnie tradycyjny dylemat: czy dana cecha zasługuje tylko na osobne pole, czy może wymaga już nowej klasy pochodnej. Myślę jednak, że tutaj można zastosować dość prostą zasadę związania klasy z shaderem. Jeśli więc dwa materiały należą do tej samej klasy, to różnią się tylko wartościami stanów renderowania. Natomiast materiały z różnych klas różnią się samym zestawem stanów, jakie można dla nich ustawiać; potrzebują zatem innych shaderów.

Tags: , , ,
Author: Xion, posted under Programming » Comments Off on Trudny wybór: sprawa materiałów

Rzucamy cień

2007-10-09 15:55

W naturze gdy jakiś obiekt jest oświetlony, automatycznie powstaje za nim obszar cienia (a czasem i półcienia) tylko z tego powodu, że promienie świetlne są zatrzymywane przez ów przedmiot. Jest to w gruncie rzeczy bardzo proste zjawisko, lecz opiera się ono na śledzeniu torów promieni (raytracing) i dlatego wszelkie jego symulacje są kosztowne obliczeniowo. Oczywiście nie dotyczy to prawdziwych fotonów, bo one “same się śledzą” :)

Jeżeli więc chcemy mieć na scenie 3D coś, co przynajmniej stara się wyglądać jak prawdziwe cienie, musimy sami o nie zadbać. Sposobów na osiągnięcie tego celu jest oczywiście mnóstwo; różnią się one zarówno stopniem skomplikowania, jak i kosztem obliczeniowym czy “wspieralnością” przez różne karty graficzne.
Prawdopodobnie dwie najprostsze (i najszerzej obsługiwane) to:

  • Shadow maps, czyli mapy cieni. W tej metodzie najpierw renderujemy scenę patrząc na nią od strony światła i w ten sposób – przy pomocy bufora głębokości – określamy, które piksele są oświetlone. Następnie rysujemy już normalnie (z pozycji kamery) i przy pomocy jednego sprytnego porównania możemy wówczas określić, czy dany piksel jest w cieniu, czy nie.
  • Shadow volumes (“bryły cieni”) wymagają z kolei wyznaczenia figury przestrzennej określonej przez obiekt rzucający cień, promienie i płaszczyznę rzutowania cienia. Potrzebne są więc dodatkowe informacje o geometrii sceny. Zaletą jest fakt, że dla N świateł wymaganych jest tylko N+1 przebiegów renderowania.

Decyzja, którą z tych technik wybrać (czy może posłużyć się jeszcze jakąś inną), ma wbrew pozorom znacznie dalej idące konsekwencje niż tylko sam wygląd cieni czy szybkość ich generowania. W każdym przypadku inaczej wygląda bowiem ‘współpraca’ cieni z różnymi cechami materiałów, jak choćby półprzezroczystością czy mapowaniem nierówności.
Chwilowo bardziej skłaniam się ku shadow mapom z tegoż to ambitnego powodu, iż są prostsze w implementacji :) Wypadałoby jednak wpierw zrobić krótkie rozpoznanie ewentualnych problemów, jakie mogą z tego później wyniknąć…

 


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