BETA
Aby się zalogować, najpiew wybierz portal.
Aby się zarejestrować, najpiew wybierz portal.
Podaj słowa kluczowe
Słowa kluczowe muszą mieć co najmniej 3 sąsiadujące znaki alfanumeryczne
Pole zawiera niedozwolone znaki

Baza wiedzy











Programowanie grafiki z użyciem techniki Vertex Shader z przykładami w C#

05-05-2004 16:54 | km1023787

Wstęp

Technika Vertex Shader, pozwala na przetwarzanie strumienia danych graficznych. Dużą zaletą jest bardzo duża elastyczność tej techniki, poza standartowymi zastosowaniami jak przetwarzanie tekstur, operacje z światłem i kolorem, shadery pozwalają na stworzenie bardzo wyszukanych efektów. Przykładem może być technika Morphing czy Sphere Mapping realizowana w oparciu o shadery.

Przetwarzanie jest realizowane za pomocą jednostki arytmetyczno-logicznej (Vertex ALU) karty graficznej i za pomocą zestawu rejestrów wejściowych i wyjściowych. Dzięki zastosowaniu tej techniki programista może w pełni kontrolować sprzętowe możliwości karty graficznej. Programowanie shader-ów przypomina programowanie w asemblerze (najczęściej są to jakieś operacje na rejestrach), jest jednak łatwiejsze a napisany kod jest bardziej czytelny.

Co dokładniej oznacza technika Vertex Shader?

Najprościej mówiąc jest to mechanizm do przekształcania wierzchołków, który jest zaimplementowany w potoku Direct3D na poziomie przekształceń geometrycznych. Warto w tym miejscu pokrótce opisać jak wygląda wcześniej wspomniany potok.

Poniżej znajdują się poszczególne etapy wchodzące w skład potoku 3D:

Wszystkie dane przechodzące przez potok muszą najpierw zostać umieszczone w pamięci systemowej, następnie wędrują one do pamięci karty graficznej poczym przekazywane są do potoku stałych przekształceń i oświetlenia lub są poddawane programowalnemu mechanizmowi vertex shader (co zostało zaznaczone na powyższym rysunku). Jak widać vertex shader nie jest wymagany i można się obejść bez niego ale znacznie lepsze rezultaty (większa wydajność przez odciążenie procesora głównego) i efekty można uzyskać programując wcześniej wspomniany układ. Wynik jest przekazywany do jednostki obcinającej te vertexy, które nie będą widoczne. W tym momencie kończona jest faza przekształcania wierzchołków i dane są przesyłane do fazy mieszania, gdzie są przetwarzane standardowo przez potok multiteksturowania lub są obrabiane przez mechanizm piksel shader. Jest to mechanizm, który też można programować i jest odpowiedzialny za wygląd piksela. Następnie dodawana jest mgła, (jeżeli istnieje) poczym dane są już gotowe do wyświetlenia. Każdy piksel jest sprawdzany czy zawiera nowsze informacje od tego przechowywanego aktualnie w ramce i jeżeli jest taka potrzeba wstawiany do ramki, która zostaje wyświetlona na ekranie.

Podsumowując programowanie vertex shadera daje możliwość przeniesienia różnego rodzaju obliczeń typu przesuwanie, obracanie, definiowanie współrzędnych tekstury do procesora karty graficznej, który jest optymalizowany pod kątem tych operacji (mnożenie wektorów, macierzy). Zyskujemy, zatem na wydajności. Przykładowo w celu stworzenia ruchomej tekstury na obiekcie nie musimy na bieżąco obliczać przez procesor główny współrzędnych tekstury, tylko możemy napisać prosty program dla vertex shader, który będzie tą czynność wykonywał w karcie graficznej.

Działanie mechanizmu Vertex shader

W trakcie wykonywania potoku Direct3D, w momencie, kiedy na ekranie ma zostać wyświetlona dowolna figura zbudowaną na kilku vertexach np.: kwadrat, dane o wszystkich wierzchołkach są przekazywane po kolei (jeden za drugim) do vertex shadera gdzie zostaje on przetworzony np.(odpowiednio przesunięty, zostaną dodane do niego współrzędne tekstury, koloru, itd.) poczym trafi do dalszych etapów potoku.

Ważne jest to, że nie można tworzyć ani usuwać dynamicznie wierzchołków przy pomocy vertex shader. Zasadą jest, że jeden wierzchołek wchodzi do obróbki i jeden wychodzi po obróbce.

Poniżej został przedstawiony schemat blokowy ilustrujący architekturę techniki Vertex Shader. Elementem centralnym jest jednostka Vertex ALU, która wykonując operacje współpracuje z zestawami rejestrów wejściowych i wyjściowych.

Opis rejestrów

Wszystkie rejestry są wektorami 4-ro elementowymi. Każdy element jest liczbą rzeczywistą o pojedynczej precyzji. Dostęp do poszczególnych składowych rejestru jest wyznaczony przez x, y, z i w np. r0.x.

Rejestry wejściowe

v0 - v15

To rejestry z danymi wejściowymi (elementami wierzchołka) do przetworzenia mogą zawierać pozycję, współrzędne normalnych, współrzędne tekstur, kolor.

Rejestry vn są przeznaczone tylko do odczytu, ich zawartość jest ładowana przed wykonaniem shadera.

c0 - c95

To rejestry przeznaczone do przechowywania stałych wartości.

Mogą to być np. macierze przekazane z aplikacji do shadera (np. macierze projekcji, widoku), różne rodzaje kolorów, kierunki światła.

Ich zawartość jest przeznaczona tylko do odczytu.

W wersji vertex shader 1.1, możliwe jest indeksowanie względne wykorzystujące rejestr a0.

c[n]               ; indeksowanie bezwzględne
c[a0.x + n]      ; indeksowanie względne

r0 – r11

Jest to grupa rejestrów roboczych, często wykorzystywana do przechowywania wyników pośrednich. Próba odczytu zawartości przed zapisaniem będzie powodem wywołania błędu.

a0

Dostępny od wersji 1.1 rejestr adresowy, używany do względnego adresowania rejestrów np.

c[a0.x + n]

Rejestry wyjściowe

Ta grupa rejestrów jest przeznaczona do przechowywania wyników działania shadera. Nie można odczytywać ich wartości, służą tylko do zapisu.

oD0, oD1

Te rejestry przechowują wartość kolorów:

oD0 – diffuse, oD1 – specular

oPos

Rejestr przechowujący pozycję vertexa. Zawartość tego rejestru musi być zapisana w shaderze.

oPts

Rejestr służący do przechowywania rozmiaru wierzchołka. Tylko współrzędna x jest używana, ponieważ jest to wielkość skalarna.

oT0-oT7

Rejestry przeznaczone do przechowywania współrzędnych tekstur.

oFog

Rejestr przechowujący wartość mgły. Używana jest tylko składowa x rejestru. Na podstawie wartości mgły dla vertexów tworzona jest tablica z wartościami mgły wykorzystywana przy renderowaniu.

Opis przykładowego programu testującego technikę Vertex Shader

W celu wykorzystania techniki Vertex Shader w aplikacji C# musimy wykonać kilka kroków.

1. Deklaracja vertexów.

Vertexami będą po prostu cztery wierzchołki prostokąta o środku w pkt (0,0). Dlatego typ danych reprezentujący nasz vertex to po prostu Vector2 (czyli wektor dwu elementowy).

W ogólności pojedynczy vertex może zawierać poza położeniem jeszcze inne informacje takie jak np. : współrzędne tekstury, wektora normalnego, kolor.

Aby zdefiniować pojedynczego vertexa należy stworzyć strukturę w ten sposób:

public struct Vertex
{
public Vector3 p;
public Vector3 n;
public float tu, tv;
public static readonly VertexFormats Format = VertexFormats.Position | VertexFormats.Normal | VertexFormats.Texture1;
};

Wierzchołki te zostaną zapisane w buforze klasy VertexBuffer. Bufor będzie źródłem danych dla strumienia vertexów.

vertexBuffer = new VertexBuffer(typeof(Vector2), numberVertices, device, Usage.WriteOnly, 0, Pool.Default);
Vector2[] vertices  = (Vector2[])vertexBuffer.Lock(0, 0);
vertices[0] = new Vector2(-1.0f,-1.0f);
vertices[1] = new Vector2(+1.0f,-1.0f);
vertices[2] = new Vector2(+1.0f,+1.0f);
vertices[3] = new Vector2(-1.0f,+1.0f);
vertexBuffer.Unlock();

2. Sprawdzenie, czy istnieje obsługa shaderów w systemie.

protected override bool ConfirmDevice(Caps caps, VertexProcessingType vertexProcessingType, Format adapterFormat, Format backBufferFormat)
{
     if ((vertexProcessingType == VertexProcessingType.Hardware) ||
          (vertexProcessingType == VertexProcessingType.PureHardware) ||
          (vertexProcessingType == VertexProcessingType.Mixed))
               {
                    if (caps.VertexShaderVersion.Major < 1)
                         return false;
               }
               return true;
}

Powyższa metoda ConfirmDevice jest przesłonięciem metody z klasy GraphicsSample, z której dziedziczymy. Jej celem jest sprawdzenie czy system posiada wystarczające właściwości do poprawnego działania naszej aplikacji.

3. Stworzenie obiektu deklaracji elementów wierzchołka.

VertexElement[] decl = new VertexElement[] {     
new VertexElement( 0, 0, DeclarationType.Float2, DeclarationMethod.Default, DeclarationUsage.Position, 0),                                   
VertexElement.VertexDeclarationEnd
};
ourDeclaration = new VertexDeclaration(device, decl);

Obiekt deklaracji ma określić powiązanie rejestrów wejściowych shadera (v0-v15) i danych w strumieniu(ach) graficznych.

Dzięki niemu można zarządzać wieloma strumieniami danych.

W tym przypadku mamy tylko jeden strumień o numerze 0 (pierwszy parametr konstruktora VertexElement).

Każdy element tablicy przyporządkowuje konkretny element vertexa (pozycje, wektor normalny, kolor, itd.) do konkretnego miejsca w strumieniu danych.

W naszym przypadku jedynym elementem vertexa będzie jego pozycja i dlatego tablica decl ma poza wskazaniem końca deklaracji (VertexElement.VertexDeclarationEnd) tylko jeden element. Określamy w nim, że elementem będzie pozycja vertexa DeclarationUsage.Position i jest reprezentowana przez dwie liczby rzeczywiste DeclarationType.Float2, ponieważ pozycja naszego vertexa jest określona przez Vector2 i tak została zapisana w buforze vertexBuffer.

4. Stworzenie obiektu shadera.

GraphicsStream code = null;
code = ShaderLoader.FromFile("nazwashadera.vsh", null, 0);
// Stworzenie obiektu przechowującego vertex shader
ourShader = new VertexShader(device, code);
code.Close();

Za pomocą klasy ShaderLoader ładujemy shadera z wcześniej przygotowanego pliku, a wynik przekazujemy jako drugi parametr konstruktora tworzącego shadera. Zawartość pliku z shaderem zostanie omówiona później.

5. Ustawienie zawartości stałych rejestrów shadera.

Matrix mat = Matrix.Multiply(viewMatrix, projectionMatrix);
mat.Transpose(mat);
//Ustawienie stałych rejestrów
device.SetVertexShaderConstant(0, new Matrix[] { mat });
device.SetVertexShaderConstant(4, new float[] {0,1,0,0});

Ustawianie zawartości rejestrów cn realizuje się za pomocą metody SetVertexShaderConstant pierwszy parametr oznacza numer rejestru, którego zawartość ustawiamy. Jeśli ustawiany obiekt (np. macierz) nie zmieści się w pierwszym rejestrze to angażowane są następne. Z tego powodu macierz mat będzie się znajdować w rejestrach c0, c1, c2, c3 a wektor {0,1,0,0} w rejestrze c4.

Jest to sposób przekazywania dowolnych danych do shadera w trakcie działania programu.

6. Renderowanie vertexów.

device.BeginScene();
     device.VertexDeclaration = ourDeclaration;
     device.VertexShader = ourShader;
device.SetStreamSource(     0, vertexBuffer, 0, DXHelp.GetTypeSize(typeof(Vector2)));          
     device.DrawPrimitives(PrimitiveType.TriangleFan,0,2);
device.EndScene();

Zawartość funkcji render jest bardzo przejrzysta. W pierwszej i drugiej linii opisu sceny przyporządkowujemy obiektowi device, wcześniej przygotowane obiekty opisujące shadera. Następnie ustawiamy źródło dla strumienia danych graficznych na vertexBuffer (ponieważ on przechowuje zdefiniowane przez nas vertexy)

Ostatnim momentem jest właściwe rysowanie prostokąta (metodą trójkątnych wachlarzy). Istotne jest to, iż przed narysowaniem vertexa jest on poddawany procesowi przetwarzania, zaprojektowanego w shaderze.

7. Opis wykorzystanego shadera (simple.vsh) :

vs.1.1
dcl_position v0
m4x4 oPos, v0, c0  //przeksztalc wierzcholki przez macierz widoku/projekcji
mov oD0, c4        //ustaw kolor zdefiniowany w stalym rejestrze c4

Pierwsza instrukcja to zadeklarowanie wersji shadera.

Następnie występuje deklaracja typu danych dla rejestru wejściowego v0.

Następnie wykorzystując pozycje vertexa i macierz widoku/projekcji (c0,c1,c2,c3 – zawartość ustawiona została w aplikacji za pomocą metody SetVertexShaderConstant) wyznaczamy pozycję wyjściową i zapisujemy ją w rejestrze wyjściowym oPos,

Kolor diffuse ustawiamy instrukcją mov na wartość znajdującą się w rejestrze c4 (zawartość rejestru również ustawiona z poziomu aplikacji).

rys.1 Efekt działania prostego shadera simple.vsh.

Inne przykłady Vertex Shader

1. Uzależnienie koloru punktu od pozycji.

Następny przykład znajduje się w pliku colorfrompos.vsh

vs.1.1
dcl_position v0
m4x4 oPos, v0, c0     // przeksztalc wierzcholki przez macierz widoku/projekcji
add r0, c0, v0     // dodanie stalego koloru do pozycji vertexa
mov oD0, r0          // zaladowanie koloru z rejestru v0

Tym razem wartość koloru dla vertexa uzależnimy od jego pozycji (v0) oraz od zawartości rejestru c0 (czyli od pierwszego wiersza macierzy widoku/projekcji).

Zawartość macierzy jest obliczana w aplikacji i zależy od aktualnego kąta obrotu, więc kolor punktu na płaszczyźnie będzie w efekcie zależał od położenia i kąta obrotu płaszczyzny.

rys.2 Uzależnienie koloru od pozycji za pomocą shadera.

2. Ustawienie tekstury z poziomu shadera. (tekstury.vsh)

vs.1.1
dcl_position v0
dcl_texcoord0 v6
m4x4 oPos, v0, c0     // przeksztalc wierzcholki przez macierz widoku/projekcji
add r0, c0, v0     // dodanie stalego koloru do pozycji vertexa
mov oD0, r0          // zaladowanie koloru z rejestru v0
mov oT0.xy, v6     // kopiowanie wspolrzednych tekstury

Zmiany wprowadzone do tego shadera w porównaniu z poprzednim to deklaracja rejestru wejściowego dcl_texcoord0 v6 zawierającego współrzędne tekstury oraz przypisanie tych współrzędnych do rejestru wyjściowego shadera  mov oT0.xy, v6.

rys.3 Efekt nałożenia tekstury z poziomu shadera.

Te modyfikacje w kodzie shadera wystarczają do nałożenia tekstury na naszą płaszczyznę, jednak, aby przykład działał należało wprowadzić kilka zmian w aplikacji. Zmiany są właściwie rozwinięciem poprzedniej aplikacji, kod jest nieco bardziej skomplikowany, dlatego dla celów dydaktycznych do opisu została przedstawiona poprzednia aplikacja, ponieważ jej kod jest bardziej przyswajalny.

Ta nowa aplikacja jest zgodna ze wszystkimi opisywanymi wcześniej shaderami w tym sensie, że wystarczy zmienić nazwę pliku tworzonego shadera (bez dokonywania innych zmian) aby zobaczyć stworzony przez niego efekt.

Dodatek A – macierze przekształceń

Ze względu na to, że podczas programowania shaderów głównie opieramy się na operacjach dodawania, mnożenia, itp. wektorów oraz macierzy przedstawimy poniżej przypomnienie jak wyglądają macierze przekształceń, translacji, skalowania, oraz obrotu. Tak, tak proste operacje, które mogłyby być przechowywane w pojedynczych wektorach tu zostaną przechowywane w macierzach, dla początkujących może okazać się to dość dziwne i niepotrzebne, jest to jak najbardziej uzasadnione a co najważniejsze praktyczne w użyciu.

Macierz przekształcenia tożsamościowego, jak kto woli macierz jednostkowa jest podstawową macierzą transformacji. Wiadomo, że po pomnożeniu dowolnej macierzy przez macierz jednostkową dostaniemy tą samą macierz.

Macierz translacji (przesunięcia) –przechowuje informacje na temat przesunięcia obiekty(np. pojedynczego wierzchołka)

Xn=X+a
Yn=Y+b
Zn=Z+c

Wartości w czwartej kolumnie informują, o jakie wartości ma zostać przesunięty obiekt.

Macierz skalowania – przechowuje informację na temat skali, o jaką ma zostać przeskalowany obiekt.

Xn=X*a
Yn=Y*b
Zn=Z*c

Wartości na przekątnej informują o ile ma zostać przeskalowany obiekt.

Jak już wcześniej wspomniałem macierz translacji lub skalowania można zapisać jako pojedynczy wektor [a,b,c] i dodać lub pomnożyć przez wektor położenia.

Tutaj można już zauważyć, że operując na macierzach wykonujemy zawsze operacje mnożenia (składania macierzy) a na wektorach raz mnożymy innym razem dodajemy, itp. co czasami utrudnia nam prace, ale sprawa się komplikuje dopiero przy macierzy obrotu którą przedstawimy poniżej.

Macierz obrotu – przechowuje opis obrotu wokół wszystkich trzech osi. Poniżej zostaną przedstawione macierze obrotu dla wszystkich osi x, y, z:

Oczywiście chcąc obrócić równocześnie obiekt o pewien kąt według osi xz należy złożyć te dwie macierze tzn. pomorzyć je przez siebie rotXZ=rotX*rotZ.

Podsumowując w celu dokonania jakichkolwiek modyfikacji obiektu należy odpowiednio przygotować macierze a następnie połączyć je (pomnożyć). Pamiętajmy, że jest ważna kolejność wykonywania mnożenia macierzy.

Przykładowo wykonując operację M=Mp*Ms*rotX (gdzie Mp - macierz położenia, Ms – Macierz skalowania, rotX – macierz obrotu) dokonujemy przesunięcia, przeskalowania a na końcu obrotu danego obiektu.

Załączniki:

Podobne artykuły

Komentarze 6

User 131096
User 131096
189 pkt.
Junior
21-01-2010
oceń pozytywnie 0
Podobało się: Podjęcie BARDZO ambitnego tematu. Nie podobało się: Artykuł jest napisany chaotycznie! Widać, że nie przechodził korekty po napisaniu; Mieszanie języków jest złym pomysłem, jeśli nie zrobi się tego umiejętnie: "na ekranie ma zostać wyświetlona dowolna figura zbudowaną na kilku vertexach", "Renderowanie vertexów". "Vertexami będą po prostu cztery wierzchołki prostokąta" - vertex = wierzchołek...; Brak odpowiedniego formatowania tekstu artykułu (chociażby wyróżnienia nazw klas w tekście). W przykładowym kodzie w wielu interesujących miejscach brakuje komentarzy. Inne: Warto odstawić własny artykuł na dwa, trzy dni i przeczytać go ponownie, krytycznie. Wyłapie się wtedy najbardziej rażące usterki. Warto też zadbać o odpowiednią strukturę artykułu i zadbać o określenie grona odbiorców i pisania pod jego kątem.
jps1301
jps1301
6 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
Artykuł jest według mnie całkiem niezły. No i oczywiście porusza temat, którego wcześniej w portalu nie było.
User 79209
User 79209
2 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
Niestety nie mozna nie zgodzic sie z Marcinem Seredynskim :(. Artykul porusza BARDZO ciekawy temat, ale jego przedstawienie jest bardzo slabe. Ponizej dorzucam kilka dodatkowych przykladow, ktore pokazuja, ze tekst nie byl redagowany z nalezyta uwaga, a szkoda:
1.Warto bylo wyraznie zaznaczyc, ze vertex shader to funkcjonaly nadzbior mechanizmu "przekszatlcenia i oswietlenie"
2."Każdy piksel jest sprawdzany czy zawiera nowsze informacje" - wyraz nowsze nic nie tłumaczy
3."rozmiaru wierzchołka" - co to jest rozmiar wierzchołka ?
4.Zrodla sie nie kompiluja, dostaje blad, ze klasa Font nie posiada metody Begin i End, co zgadza sie z msdnem http://tinyurl.com/2g73h. Uzywam DirectX 9.0 SDK Update – (Summer 2003).
Ale koniec koncow jestem optymista :) i mysle, ze np. w artykule o Microsoftowym odpowiedniku Cg(HLSL) postarasz sie o lepsza redakcje.
aibi4990
aibi4990
0 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
Temat ciekawy, ale za malo przykladow. Brakuje mi np. gotowej sceny stworzonej z vertex sharedami i bez - wowczas latwiej dostrzec zalety.
User 79130
User 79130
9 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
No niestety, tym razem się nie udało. Zgadzam się z Marcinem Seredyńskim.
poczta1849
poczta1849
1 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
Moim zdaniem artykuł jest REWELACYJNY, ponieważ w końcu porusza ambitny temat odbiegający od przeciętnych artykułów które zwykle są na poziomie szkoły podstawowej. Podobało się: przedstawione przykłady shaderów są dobrym wprowadzeniem do nauki tej techniki (nawet bez dobrej znajomości DirectX), podoba mi się dobry wstęp teoretyczny. Rzeczywiście mieszanie nazw vertex i wierzchołek mogłoby być dla czytelnika kłopotliwe, jednak każde zdanie można dość łatwo zrozumieć, ponieważ wszystko wynika jednoznacznie z kontekstu.
pkt.

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.