Posts tagged ‘rendering’

Sam zrobiłem ten trójkąt!

2008-01-17 20:53

Software’owo rasteryzowany trójkątGdyby zorganizować konkurs na jak największą ilość kodu napisaną celem osiągnięcia jak najprostszego efektu, to pewnie obrazek po lewej (i kod, który za nim stoi) mógłby zająć w nim całkiem dobrą lokatę. Na pierwszy rzut oka to tylko niebieski trójkąt na żółtym tle; na drugi, trzeci i każdy następny zresztą też :) Na tym arcyprostym obrazku nie widać całego, dość skomplikowanego mechanizmu, dzięki któremu możemy go oglądać.

Rzeczony trójkąt jest bowiem efektem pracy programowego renderera, którego to od jakiegoś czasu – z konieczności, acz nie bez pewnej przyjemności – staram się popełnić. Taki kawałek oprogramowania ma za zadanie robić mniej więcej to, co potrafią zaawansowane biblioteki graficzne w rodzaju DirectX i OpenGL. Są oczywiście istotne różnice, wśród których największą jest brak wykorzystania typowych możliwości współczesnych kart graficznych – czyli właśnie przetwarzania trójkątów. Wręcz przeciwnie: wszystkie obliczenia pracowicie wykonuje główny procesor, zajmując się po kolei nie tylko każdym wielokątem, ale także każdym pikselem. Ma więc wyjątkowo dużo roboty, z którą jednak potrafi sobie poradzić.
O czym świadczy więc pokazany tutaj trójkąt? Ano o tym, że podstawowy potok renderowana ma się całkiem dobrze. W jego skład wchodzi transformowanie trójkątów przekształceniami macierzowymi, oświetlenie per-vertex, sprawdzanie widoczności pikseli przy pomocy bufora Z oraz rzutowanie perspektywiczne i rasteryzacja wynikowej płaskiej geometrii. Zgadza się, to zupełne podstawy podstaw, nieobejmujące chociażby teksturowania, lecz i tak realizujący je kod nie wiadomo kiedy rozrósł się do ponad dwóch tysięcy linijek. Faktycznie więc to był dosyć pracochłonny trójkąt :)

Zabłysnę jeszcze przykładowym kodem wykorzystującym renderer, który to wyglądać może mniej więcej tak:

  1. Device dev = new Device(/* ... */);
  2. // ...
  3. dev.clear (new Color(1.0f, 1.0f, 0.0f));
  4. dev.begin();
  5.    dev.setRenderState (RenderStates.FillMode, FillModes.Solid);
  6.    dev.setRenderState (RenderStates.ShadeModes, ShadeModes.Flat);
  7.  
  8.    // światło punktowe z pozycją i kolorem
  9.    dev.addLight (new PointLight(new Vector3(0f, 0f, -10f), new Color(1f, 1f, 1f)));
  10.  
  11.    // wierzchołek zawiera pozycję, normalną i kolor rozproszenia
  12.    Vertex[] vertices = new Vertex[]
  13.    {
  14.       new Vertex(new Vector3(-10f, 0f, -1f),
  15.          new Vector3(0f, 0f, -1f),
  16.          new Color(0f, 0f, 1f)),
  17.       new Vertex(new Vector3(0f, 2f, -1f),
  18.          new Vector3(0f, 0f, -1f),
  19.          new Color(0f, 0f, 1f)),
  20.       new Vertex(new Vector3(10f, -3f, -1f),
  21.          new Vector3(0f, 0f, -1f),
  22.          new Color(0f, 0f, 1f))
  23.    };
  24.    dev.drawTriangle (vertices);
  25. dev.end();

Inspiracje pewną popularną biblioteką w zakresie interfejsu są, jak sądzę, doskonale widoczne :)

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

Ten straszny rendering

2007-09-28 17:27

Mogę bronić się rękami i nogami, mogę starać się obchodzić temat ze wszystkich stron, ale w końcu przyjdzie taki czas, że po prostu trzeba będzie zająć się esencją silnikologii – czyli grafiką 3D :) Zwiększenie liczby wymiarów o 50% powoduje mniej więcej podobny przyrost potencjalnych powodów bólu głowy. Dlatego też nie należy się wybierać w tę wyprawę bez odpowiedniego przygotowania i planu.

Plan natomiast jest generalnie dość prosty, lecz jak wiemy diabeł tkwi w szczegółach. W grafice 3D mamy oczywiście do czynienia ze sceną, w której mogą się znaleźć przeróżne jej elementy – zwane też węzłami lub encjami. Takim elementem może być instancja modelu, teren oparty na mapie wysokości, emiter cząsteczek czy jeszcze coś innego. Ważne jest, że każdy taki element zajmuje się w przestrzeni określoną pozycję i miejsce; są one najprościej definiowane przez otaczający prostopadłościan równoległy do osi układu współrzędnych, czyli axis-aligned bounding box (AABB).

Zadaniem obiektu sceny jest między innymi szybka odpowiedź na pytanie, czy dany węzeł znajduje się w polu widzenia kamery. Jako że pole widzenia jest najczęściej perspektywiczne i ma kształt ściętego ostrosłupa, czynność ta (eliminowanie niewidocznych obiektów) jest znana jako frustum culing. Można ją przeprowadzać, organizując odpowiednio przestrzeń sceny, dzieląc ją na sektory – na przykład przy pomocy drzewa ósemkowego.

Naturalnie wszystkiego wyeliminować się nie da i w końcu trzeba będzie coś narysować :) I tutaj znowu mamy kolejną dość skomplikowaną kwestię. Stosunkowo łatwo jest zaprogramować rysowanie każdego rodzaju obiektów tak, aby każdy odpowiadał tylko za siebie i nie zakładał nic chociażby o stanach renderowania przed i po tej operacji. Rzecz w tym, że o ile wygląda to bardzo ładnie z punktu widzenia zasad programowania obiektowego (jak okiem sięgnąć – hermetyzacja!), to w praktyce efektywność takiego rozwiązania byłaby co najmniej wątpliwa. Niestety dla karty graficznej przypisanie konkretnego wierzchołka do konkretnego obiektu w scenie nie ma żadnego znaczenia. Liczy się bowiem to, jakie stany renderowania, mieszania tekstur, itp. trzeba ustawić, aby ów wierzchołek narysować.

Stąd potrzebne jest pojęcie materiału, znane z edytorów grafiki 3D. Tutaj jednak oznacza ono nie tyle wygląd powierzchni, co wszystkie właściwości wpływające na wygląd geometrii, które nie są zapisane w danych wierzchołków. Może to być więc zarówno tekstura, jak i właściwości świetlne czy nawet określenie, czy dana powierzchnia jest półprzezroczysta czy nie.
Aby efektywnie wyrenderować scenę, trzeba więc grupować fragmenty jej geometrii nie względem obiektu, ale materiału. Dodatkowo trzeba też pamiętać o tym, że pewne zmiany są bardziej kosztowane niż inne (taniej jest zmienić choćby teksturę niż shader) i uwzględniać to przy sortowaniu.

Na koniec pozostaje jeszcze ostatni etap, gdy dane o wierzchołkach trafiają już do karty graficznej i muszą być przetworzone przez shader, aby mogły zostać odpowiednio pokazane. Napisanie takie shadera, a potem sterowanie nim (np. włączanie lub wyłączanie pewnych jego fragmentów) to też nie jest lekki orzech do zgryzienia. Jest to solidny kawałek matematyki i geometrii połączonej z kombinowaniem, jak to wszystko zmieścić w limicie instrukcji, który jest nieubłagany :)

To oczywiście nie wszystko – nie wspomniałem na przykład w ogóle o oświetleniu czy cieniach, które wymagają renderowania potraktowanych nimi fragmentów więcej niż raz. Ale już z obecnego opisu widać, że jedna literką ‘D’ więcej to jednocześnie sporo dodatkowych literek ‘P’ – jak ‘problemy’ ;P

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


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