Pierwszy shader.

Cześć!

Dzisiaj przed nami ważna lekcja, wprowadzająca nas w nowy i wspaniały świat shaderów na początku naszej nauki programowania w Direct3D. Przede wszystkim – shadery są prostsze (o ile można w ten sposób powiedzieć o nich) od standardowego podejścia Fixed Pipeline. Za czasów D3D8 te małe programy faktycznie nie były zbyt intuicyjne. Assembler GPU nie jest zbyt popularnym językiem, również raził brak podstawowych konstrukcji sterowania przepływem. Dzisiaj sytuacja wygląda zupełnie inaczej. Od tamtego okresu minęło kilka lat i 3 Shader Modele. ;) Mamy do dyspozycji języki wysokiego poziomu pozwalające na pisanie tych małych programów sprawnie i prosto.

Shadery są szczególnie przydatne dlatego, że pozwalają na znaczące uproszczenie wielu operacji. Multiteksturing czy operacje na modelu lub pikselach na najniższym możliwym stopniu abstrakcji nigdy nie były tak proste. Nie musimy się babrać ze „stanami tekstur” (Texture Stage States) czy innymi tego typu, dzisiaj byśmy powiedzieli „egoztycznymi” wynalazkami.

Spróbujmy więc napisać nasz pierwszy shader – na razie nie będziemy go podpinać pod naszą aplikację, a jedynie omówimy sobie jego strukturę.

   1: void vsmain(float4 pos : POSITION,
   2:             float4 color : COLOR,
   3:             out float4 oPos : POSITION,
   4:             out float4 oColor : COLOR)
   5: {
   6:     oPos = pos;
   7:     oColor = color;
   8: }
   9:  
  10: float4 psmain(float4 color : COLOR) : COLOR
  11: {
  12:     return color;
  13: }
  14:  
  15: technique std
  16: {
  17:     pass
  18:     {
  19:         VertexShader = compile vs_2_0 vsmain();
  20:         PixelShader = compile ps_2_0 psmain();
  21:     }
  22: }

HLSL (lub Cg, to podobne języki) – czyli języki programowania shaderów – są mocno wzorowane na C++. Mamy więc prawie taką samą składnię z paroma rozszerzeniami. Tutaj widzimy właściwie nie sam shader, a plik efektu. Jest to coś a`la skrypt, zawierający w sobie Vertex Shader, Pixel Shader oraz sekcję, w której możemy ustawiać stany urządzenia.

Przejdźmy do omawiania Vertex Shadera. Widzimy, że składa się on z głównej funkcji (u nas nazwanej vsmain), jej listy parametrów oraz ciała. Już w argumentach widzimy coś ciekawego – owe „: POSITION” oraz „: COLOR” to tak zwane semantyki. Mówią one karcie graficznej co jest czym – innymi słowy, pod wektorem pos przechowujemy pozycję, a oColor – kolor. Kolejnym ciekawym elementem jest nieznany nam wcześniej typ danych – float4. Jest on przedstawicielem typów wektorowych – czyli poprostu wektorem czterech floatów. Każdy wbudowany w jezyk typ (float, int, bool oraz half – 16. bitowy floating-point) ma tę właściwość, że możemy tworzyć z niego wektory 1-4 elementowe. Karta grafiki działa właśnie na wektorach, dlatego opłaca się je stosować prawie w każdym przypadku. Z floatów możemy także tworzyć macierze (float4x4 itd). Jest jeszcze keyword out – oznacza on wartość zwracaną przez funkcję.

Nasz Vertex Shader pobiera pozycję wierzchołka na ekranie (jest to tak zwany wierzchołek pretransformed – Vertex Shader nie jest co prawda wykonywany, ale nie zaszkodzi, jeśli go sobie napiszemy, choćby dla treningu składni HLSL) oraz kolor. To, co pobiera VS opisuje na FVF, o którym mówiliśmy ostatnio. Ten shader normalnie wykonałby trywialną robotę przekazując poprostu swoje argumenty dalej.

Następnie natrafiamy na Pixel Shader. Jego konstrukcja jest mniej-więcej podobna do VS-a, jednak teraz zwracamy kolor nie przez parametr, a standardowo, przez wartość. PS również nie robi wiele a tylko przepisuje kolor wejściowy na wyjście. Zauważyć możemy jednak brak semantyki POSITION na jego wejściu – spowodowane jest to tym, iż jest to jedyna semantyka która „gubi się” między VS-em a PS-em i nie może być wykorzystywana w tym typie shadera.

   1: ID3DXEffect* pEffect;
   2: // ...
   3: // Po inicie D3D
   4: ID3DXBuffer* pErrs;
   5: D3DXCreateEffectFromFile(pDev, "shader.fx", 0, 0, 0, 0, &pEffect, &pErrs);
   6: if (pErrs) MessageBox(hWnd, (char*)pErrs->GetBufferPointer(), "zóo", 0);
   7:  
   8: // Rysowanie
   9: pDev->Clear(0, 0, D3DCLEAR_TARGET, 0xff000000, 1, 0);
  10: pDev->BeginScene();
  11:  
  12: UINT passes;
  13: D3DXHANDLE tech;
  14: pEffect->FindNextValidTechnique(0, &tech);
  15: pEffect->SetTechnique(tech);
  16: pEffect->Begin(&passes,0);
  17: for (UINT pass = 0; pass < passes; pass++)
  18: {
  19:     pEffect->BeginPass(pass);
  20:     
  21:     pDev->SetFVF(OURVERT_FVF);
  22:     pDev->SetStreamSource(0, pVB, 0, sizeof(OurVertex));
  23:     pDev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
  24:  
  25:     pEffect->EndPass();
  26: }
  27: pEffect->End();
  28:  
  29: pDev->EndScene();
  30: pDev->Present(0, 0, 0, 0);

Zobaczmy na kod odpowiedzialny za ładowanie skryptu efektu. Na szczęście całość została za nas już napisana przez programistów Microsoftu (cokolwiek sądzisz o tej firmie prawda jest taka, że się spisali :P ). Biblioteka D3DX której tutaj (i w prawie każdej następnej lekcji) używamy zapewnia nam masę (i mam to na myśli) funkcji do bardzo wielu zadań. Nawet tak, zdawałoby się, egoztycznych jak oświetlenie za pomocą harmonik sferycznych, VIMP, czy tesselacja N-Patch. Posłuży nam ona również w dalszej części poradnika do bardziej przyziemnych czynności – wczytywania tekstur oraz siatek.

Samo tworzenie efektu nie jest skomplikowane. Wywołujemy funkcję tworzacą ów obiekt z pliku. Wymienianie jej wszystkich parametrów nie ma większego sensu, więc ewentualni dociekliwi mogą poszukać na stronie MSDN oraz potraktować to jako naukę korzystania z tej nieocenionej pomocy. Jedynie parametr &pErrs wymaga komentarza – bufor ten zawiera w sobie ewentualne błędy kompilacji efektu.

O renderowaniu musimy sobie trochę powiedzieć. Jak widzieliśmy wcześniej, nasz efekt zawierał sekcje technique oraz pass. Oznaczają one kolejno technikę, czyli zestaw przebiegów renderowania passów. Pass zaś zawiera parę Pixel oraz Vertex Shader wraz z odpowiednimi stanami. Na początku znajdujemy pierwszą technikę która jest prawidłowa na naszym sprzęcie (która się skompiluje) oraz ustawiamy ją. Następnie wywołujemy funkcję odpowiedzialną za rozpoczęcie renderowania z użyciem danego efektu. Funkcja ta zwraca nam ilość przebiegów w danej technice. Potem zaś renderujemy każdy przebieg w pętli for (nie zapominając o rozpoczęciu go – BeginPass).

Kolejna ciężka lekcja za nami. Zdaję sobię sprawę, iż kilka rzeczy może pozostać niejasnych – o ile część z nich omówimy w kolejnych lekcjach, o tyle dociekliwym szczególnie polecam książkę Cg Tutorial autorstwa NVIDII, a dostępną za darmo w internecie, omawiającą tajniki języka programowania shaderów HLSL/Cg.

Do lekcji dołączony jest kod źródłowy. Zalecam eksperymentowanie z nim (a szczególnie z zawartym w nim shaderem, który można otworzyć nawet notatnikiem).

image


komentarzy 9 to “Pierwszy shader.”

  1. Wierzchołki w tym przykładzie są już przetransformowane(RWH) więc vertex shader się nie wykonuje. Może warto o tym wspomnieć w tekście.

    //EDIT by Charibo
    Fakt, moja kulpa. Dopisałem.
    Zapomniałem o tym, ponieważ wierzchołków rhw używam sam po raz pierwszy odkąd się uczyłem D3D. :)

  2. Godny następca Robala(pisał o DirectX 8.1,brak wielu ważnych elementów DX9) ,Frankowskiego(pisał trochę za krótko)i innych ,szczególnie ,ze zawiera efekty w których jestem trochę cienki.
    PS.
    Zauważyłem ,że poczciwy Directx 8.1 chodzi mi o wiele płynniej niz DirectX 9c na tym samym kodzie ,który przerobiłem do niego – chodzi mi o VS 1.1 bez efektów.Moze to ta dodatkowa funkcjonalność 9-ki,tak obniża wydajność…tragedia
    na tym samym kodzie skonwertowanym

  3. Fajnie jak by te programy po wyłączeniu zwalniały zasoby.

  4. Fajnie jakby te programy zwalniały zasoby po wyłączeniu.

  5. Eeee nie no co ty … nie lubisz mieć uruchomionych procesów bez okien ?

  6. dzięki bardzo :)
    pomogłeś mi tym tutkiem w przejściu z dx8.0 i vsa/psa na hlsl’a :)

  7. A pliki .fx da się importować do Unreal Developement Kit?

  8. A ja miałbym prośbę do autora, napisz jeszcze coś dla nas, zrób system dotacji :)

  9. Mam problem… Shader nie chce mi się skompilować.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

 
%d bloggers like this: