Direct3D Cell Shading

background image

1

DirectX ▪ Cell Shading

Po lekcji teoretycznej czas pokazać, co możemy zrobić w praktyce. Jak już wiemy tak naprawdę cieniowanie kreskówkowe
nie stanowi wielkiego problemu i cały ambaras polega w zasadzie na odpowiednim przygotowaniu tekstur, których użyjemy
w przykładzie. Żeby za dużo nie kombinować oprzemy się na tych, z lekcji teoretcznej.
Co do samego kodu to w zasadzie dwie główne rzeczy będą nam potrzebne - a mianowicie odpowiednia struktura
wierzchołka i vertex shader, który policzy co trzeba.
W cieniowaniu kreskówkowym w zasadzie nie używa się tekstur - przynajmniej darmo wypatrywać czegoś podobnego w
znanych komiksach czy bajkach rysunkowych. My aby sobie nie zaciemniać przykładu także ograniczymy się tylko do
mechanizmu samego cieniowania i nadawania bryłom konturów. A tekstury przecież możecie nałożyć sobie sami, bo
robiliśmy to już chyba dziesiątki razy.
A więc po pierwsze struktura wierzchołka:

struct SVertex
{
float x, y, z; // position
float nx, ny, nz; // normal
float u1, v1; // firts texture channel
float u2, v2; // second texture channel
};

Oczywiście pozycja, normalna która tutaj okaże się niezbędna (w końcu wyliczamy oświetlenie) no i dwa zestawy
współrzędnych tekstur - jeden dla tekstury cieniującej a drugi dla tekstury definiującej kontury obiektu. Tak naprawdę to
wszystko jest dla nas już w tym momencie zupełnym banałem, więc nawet nie powinienem tego opisywać, no ale dla
porządku niech wam będzie.
Inicjalizację urządzenia, ładowanie geometrii i tekstur także pomijamy, bo mamy ważniejsze sprawy - a mianowicie druga
decydująca w naszym przykładzie sprawa, czyli vertex shader:

vs.1.1

dcl_position v0
dcl_normal v3
dcl_texcoord0 v7
dcl_texcoord1 v8

; c0-c3 - world transform matrix
; c4-c7 - view*proj transform matrix
; c8 - eye position
; c9 - light vector

def c10, 0.0f, 1.0f, 0.0f, 0.0f

; position to world
dp4 r0.x, v0, c0
dp4 r0.y, v0, c1
dp4 r0.z, v0, c2
dp4 r0.w, v0, c3

; and final
dp4 oPos.x, r0, c4
dp4 oPos.y, r0, c5
dp4 oPos.z, r0, c6
dp4 oPos.w, r0, c7

; normal to world
dp3 r1.x, v3, c0
dp3 r1.y, v3, c1
dp3 r1.z, v3, c2

; normalize normal
dp3 r1.w, r1, r1
rsq r1.w, r1.w
mul r1, r1, r1.w

; output color
mov oD0, c10

; and toon textures
dp3 oT0.x, r1,-c9

; eye vector
sub r2, c8, r0

background image

2

DirectX ▪ Cell Shading

dp3 r2.w, r2, r2
rsq r2.w, r2.w
mul r2, r2, r2.w

; outline texture
dp3 oT1.x, r1, r2

Jak widać shader nie jest trudny bo i być nie może. Dla zasady jednak postarajmy sobie zobaczyć poszczególne kawałki,
żeby w razie wątpliwości nie stresować się.

; position to world...
dp4 r0.x, v0, c0
dp4 r0.y, v0, c1
dp4 r0.z, v0, c2
dp4 r0.w, v0, c3

Najpierw oczywiście manewrujemy naszym obiektem w świecie, czyli mnożymy pozycję wejściową wierzchołka poprzez
macierz świata umieszczoną w rejestrach

c0

do

c3

. Wynik zostawiamy w rejestrze

r0

, ponieważ jeszcze się nam przyda.

; ...and final
dp4 oPos.x, r0, c4
dp4 oPos.y, r0, c5
dp4 oPos.z, r0, c6
dp4 oPos.w, r0, c7

Żeby jednak za dużo nie mieszać od razu też wyślemy nasz wierzchołek na ekran, czyli przepuszczamy przez przemnożone
przez siebie macierze widoku i projekcji, co w sumie wrzuci nam go na ekran.

; normal to world
dp3 r1.x, v3, c0
dp3 r1.y, v3, c1
dp3 r1.z, v3, c2

Oczywiście, żeby policzyć oświetlenie podczas manewrowania obiektem po scenie musimy także manewrować normalnymi
wierzchołków, które muszą pokonać dokładnie tę samą drogę co wierzchołki. Normalne zapamiętujemy jak zwykle już w
rejestrze tymczasowym

r2

.

; normalize normal
dp3 r1.w, r1, r1
rsq r1.w, r1.w
mul r1, r1, r1.w

I robimy oczywiście bardzo ważny manewr, o którym nigdy nie powinniśmy zapominać - czyli normalizujemy wszelkie
wektory przed obliczeniami oświetlenia.

mov oD0, c10

Do wyjściwoego rejestru koloru wysyłamy jakiś dobrany przez nas kolor diffuse obiektu, ponieważ akurat w tym przypadku
nie zajmujemy się materiałami wczytanymi bezpośrednio z modelu, no ale przecież nie w tym rzecz. W tym przykładzie
wymyśliłem sobie obiekt zielony, ale możecie sobie zdefiniować jaki chcecie tak naprawdę. Jak ktoś będzie robił finalną
aplikację to oczywiście musi sobie opracować mechanizm wysyłania kolorów materiałów przez stałe shadera.

; and toon textures
dp3 oT0.x, r1,-c9

Teraz nastąpi właściwy bajer w naszym przykładzie czyli pocieniowanie naszego obiektu za pomocą tekstur. Jak widać
powyżej, w kolorze diffuse nie zawieramy żadnej warotści wyliczonej na podstawie normalnej obiektu a jedynie stałą
wartość koloru. Gdybyśy nie pocieniowali obiektu w inny sposób to dostalibyśmy na ekranie coś zupełnie bez wyrazu i głębi.
W rejestrze

c9

wpada do shadera wektor światła znajdującego się na scenie. Odwracamy go (za pomocą znaku "-") aby móc

wyliczyć własćiwą warość iloczynu skalarnego pomiędzy nim a normalną. Minus jest po to, aby kąt pomiędzy normalną a
światłem mieścił się w przedziale od 0 do 180 stopni. Wynikiem iloczynu skalarnego jak wiemy będzie liczba - cosinus kąta
pomiędzy wektorami. Im większy kąt tym mniejsza wartość iloczynu i tym mniejsza liczba. Większy kąt oznacza nie mniej
ni więcej to, że światło pada na daną normalną pod większym kątem, co oznacza coraz mniej światła. Nasza wartość wędruje
zatem do rejestru tekstury, który zawiera współrzędne odpowiedzialne za mapowanie tekstur na poziomie nr 0 a tam znajdzie
się nasza tekstura cieniująca. I jeśli tekstura będzie skonstruowana w sposób odpowiedni (dla małych wartości mapowania

background image

3

DirectX ▪ Cell Shading

będzie ciemniejsza, dla dużych jaśniejsza) to otrzymamy poprawne cieniowanie, co wiemy już z teorii. Ponieważ nasza
tekstura będzie miała znaczące zmiany wartości tylko w jednym wymiarze, więc w tym momencie druga współrzędna nie ma
tak naprawdę znaczenia i możemy ją ustawić dowolnie, ponieważ dla każdej wartości y, współrzędna x będzie wyglądała tak
samo.

; eye vector
sub r2, c8, r0
dp3 r2.w, r2, r2
rsq r2.w, r2.w
mul r2, r2, r2.w

Pozostało nam zatem wyliczyć już tylko dane dla tekstury wyznaczającej kontur naszego obiektu. Jak pamiętamy z lekcji
teoretycznej potrzebny nam będzie wektor łączący oko z przekształconym w przestrzeni świata wierzchołkiem. Wierzchołek
mamy w rejestrze

r0

, pozycję oka w stałej shadera

c8

. No i wyliczamy taki wektor odejmując pozycję wierzchołka od

pozycji oka i umieszczamy w tymczasowym rejestrze

r2

. Oczywiście dany wektor normalizujemy, aby uniknąc przykrych

niespodzianek.

; outline texture
dp3 oT1.x, r1, r2

Na koniec robimy podobny manewr co poprzednio, czyli na podstawie iloczynu wektorowego będziemy wyliczać
mapowanie dla tekstury. Tym razem iloczyn wyliczymy pomiędzy wektorem normalnym a wektorem, który wyliczyliśmy
przed chwilą - łączącym oko z wierzchołkiem. Jeśli ten iloczyn będzie mały - znaczy się, że wektory są położone niemal
prostopadle co prawdopodobnie oznacza krawędź, a przynajmniej można odnieść takie wrażenie patrząc na obiekt. Jeśli
iloczyn będzie bliski zeru, znaczy że wektory leżą niemal w jednej linii i krawędź toto na pewno nie jest. Tak więc wyliczona
wartość wędruje na poziom nr 1, gdzie panoszy się tekstura odpowiedzialna za pocieniowanie naszej krawędzi.

Shader powinien działać bez zarzutu, czas więc zobaczyć co się dzieje w aplikacji. Jak wspomniałem, wszelkie ładowania,
inicjalizacje itp. darujemy sobie, bo szkoda tylko miejsca a przejdziemy do sedna sprawy, czyli renderingu naszej bryły. W
przykładzie pobawiłem się troszkę i prezentuję trzy różne możliwości renderingu:

case eOutline:
{
g_lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2 );
g_lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
g_lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_CURRENT );
g_lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_MODULATE );
g_lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE );
g_lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLORARG2, D3DTA_CURRENT );
}

Pierwszym trybem jest rendering samych krawędzi obiektu. Jak widać na rysunku krawędzie rysują się w dosyć
charakterystyczny sposób i widać dodatkowo w jaki sposób jest zbudowana siatka obiektu. Taki a nie inny efekt zawdzięczać
trzeba odpowiednio spreparowanej teksturze krawędzi, która zawiera zbiór odpowiednio przygotowanych mipmap. Tekstura
ta jest w formacie *.dds, który jest charakterystyczny dla pakietu Direct3D. Może on zawierać nie tylko jedną teksturę, ale
całe zestawy łącznie ze wszystkimi poziomami mpmap. W pakiecie Direct3D jest narzędzie, dzięki któremu możecie nawet
poznać budowę takiej tekstury i obejrzeć jej posczególne elementy, no ale po szczegóły to dzisiaj odsyłam do SDK - to nie

background image

4

DirectX ▪ Cell Shading

temat na teraz. Wracając zaś do naszego przykładu to zaby zobaczyć tylko krawędzie obiektu z nałożonym na obiekt kolorem
diffuse po prostu modulujemy je na poziomie nr 1, ponieważ właśnie tam znajduje się tekstura konturu.

case eShade:
{
g_lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
g_lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
g_lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_CURRENT );
g_lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_SELECTARG2 );
g_lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLORARG2, D3DTA_CURRENT );
}

Drugim przypadkiem renderingu dostępnym w przykładzie jest rendering wykorzystujący samo cieniowanie za pomocą
tekstury cieniującej, umieszczonej na poziomie 0 bez uwzględniania konturów obiektu. Aby uwidocznić taki a nie inny
sposób po prostu mieszamy na poziomie nr 0 kolor difuuse i kolor tekstury cieniującej a następnie przepuszczamy ten
rezultat na poziom 1, który w naszym przypadku oczywiście zawsze jest poziomem wyjściowym.

case eFinal:
{
g_lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
g_lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
g_lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_CURRENT );
g_lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_MODULATE );
g_lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE );
g_lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLORARG2, D3DTA_CURRENT );
}

I na sam koniec oczywiście efekt w pełnej krasie czyli jednocześnie i cieiowanie i wyciąganie krawędzi. Na każdym
poziomie nie robimy nic innego, tylko modulujemy kolory przychodzące z poziomów poprzednich.

background image

5

DirectX ▪ Cell Shading

No i to w zasadzie byłoby tyle, jeśli chodzi o rendering kreskówek za pomocą Direct3D. Można powiedzieć, że łatwizna i
faktycznie, jeśli chodzi o kod żródłowy to raczej tak to właśnie wygląda na naszym poziomie znajomości zagadnień grafiki.
Inna sprawa to odpowiednio spreparowane tekstury, które akurat w tym przypadku grają główne role. Jeśli na przykład
tekstura konturu byłaby nie taka, nie zawierałaby odpowiednio spreparowanych poziomów mipmap to zapewne kontur nie
byłby tak wyraźny i może trudno byłoby go wogóle dostrzec. Tekstura cieniująca natomiast jest przygotowana pod kątem
cieniowania kreskówkowego - gdybyśmy na przykład zrobili z niej ładny, ciągły gradient, to otrzymalibyśmy tradycyjne
cieniowanie Gourauda zamiast kilku poziomów oświetlenia i charakterystycnego efektu.
Pragnę także zwrócić waszą uwagę na możliwości, jakie ze sobą niesie ta technika. Spróbójcie zresztą sami trochę
poeksperymentować, zwłaszcza z teksturą cieniującą. Dodajcie do niej kanał alfa, włączcie przeźroczystość i twórzcie.
Poprzestawiajcie kolory cieniujące, może dajcie zupełnie inne odcienie niż czarne. Może wpadnie wam do głowy jakiś fajny
efekt z teksturą konturu na przykład?
Na wszystkie ciekawe pomysły oczywiście czekamy z niecierpliwością, może jakieś dema albo najlepiej efekty do naszego
browsera :). Teraz powinno być łatwiej napisać cokolwiek, więc bardzo zachęcam ;).


Wyszukiwarka

Podobne podstrony:
A dynamic model for solid oxide fuel cell system and analyzing of its performance for direct current
The use of electron beam lithographic graft polymerization on thermoresponsive polymers for regulati
Direct3D 11 Tessellation
Premier Press Beginning DirectX 9
Active Directory
5. Prensa, Hiszpański, Kultura, España en directo
Cell surface in the interaction Nieznany
Active Directory
Direct3D Vertex shader 1
Cell Cycle
Intermediate Short Stories with Questions, Driving Directions
Bacterial cell shape
Directional Movement Index, giełda(3)
directx
Komunikacja rynkowa direct response marketing

więcej podobnych podstron