to get the file
Transkrypt
to get the file
Instrukcja 1 1 Architektura procesorów graficznych Temat: Vertex Shader Przygotował: mgr inż. Tomasz Michno Wstęp 1.1 Czym jest shader Shader jest programem (zazwyczaj krótkim), wykonywanym przez kartę graficzną. Nie jest to jednak typowy program (taki jak na przykład programy w języku Pascal, czy C), ponieważ jego zadaniem jest sterowanie częścią potoku graficznego/renderowania (graphics/rendering pipeline). Potok graficzny - najprościej mówiąc, potok, który przetwarza obraz 3D na obraz 2D. W uproszczeniu można go przedstawić następująco: Więcej o potoku renderowania i shaderach: http://developer.nvidia. com/node/76 Wyróżniamy następujące shadery: • Vertex shader - służy do wykonania przekształceń na wierzchołku (uruchamiany raz dla każdego wierzchołka), możliwy jest dostęp do współrzędnych wierzchołka, jego koloru i współrzędnych tekstur. Nie jest możliwe tworzenie nowych wierzchołków. • Geometry shader - służy do dodawania lub usuwania wierzchołków z siatki wierzchołków; dostępny od DirextX 10 i OpenGL 3.1 • Pixel/Fragment Shader - służy do wyliczania koloru pikseli, w DirectX stosowana jest nazwa Pixel Shader, w OpenGL Fragment Shader; pozwala zastosować operacje związane głownie z kolorem obiektów - cieniowanie, oświetlanie, ale również np. mapowanie nierówności Obecnie najczęściej shadery pisane są w jednym z trzech języków: GLSL, HLSL lub Cg (na którym się skupimy). 1 1.2 Podstawy języka Cg Język Cg jest językiem wysokiego poziomu stworzonym przez nVidię przy współpracy Microsoftu (jest kompatybilny z HLSL). Został w dużej mierze oparty o język C - posiada tą samą składnię, instrukcje sterujące i typy danych (zostały dodane też typy przydatne dla programowania shaderów grafiki). 1.2.1 Podstawowe typy: • float - 32-bitowa liczba zmiennoprzecinkowa • half - 16-bitowa liczba zmiennoprzecinkowa • int - 32-bitowa liczba całkowita • fixed - 12-bitowa liczba typu fixed-point • bool - wartość boolowska (true, false) • sampler* - używane do reprezentacji tekstur • tablice-wektory na bazie podstawowych typów - w postaci np. float4 • macierze na bazie podstawowych typów - w postaci np. float4x4 • uniform - odpowiednik const - informuje, że zmienna nie będzie zmieniana w kodzie shadera • struktury - struct - używane podobnie jak w C/C++ 1.2.2 Przykład 1 - ustawienie koloru obiektu Pierwszy przykład Vertex Shadera będzie polegał na zmianie koloru obiektu w kodzie shadera. 1 2 3 4 5 s t r u c t Vertex { f l o a t 4 p o s i t i o n : POSITION ; float4 color : COLOR0; }; 6 7 8 9 10 Vertex main ( Vertex IN , { Vertex OUT; IN . c o l o r . x =0.0 f ; uniform f l o a t 4 x 4 ModelViewProj ) 2 IN . c o l o r . y =1.0 f ; IN . c o l o r . z =1.0 f ; 11 12 13 OUT. p o s i t i o n = mul ( ModelViewProj , IN . p o s i t i o n ) ; OUT. c o l o r . xyz = IN . c o l o r . xyz ; 14 15 16 r e t u r n OUT; 17 18 } Listing 1: Kod Shadera Powyżej znajduje się kod jednego z najprostszych shaderów (Listing 1). Jego działanie można zobrazować rysunkiem: Jak widać, kod programu otrzymuje na wejściu Vertex (jego pozycję i kolor) i zwraca również Vertex. Dlatego na początku kodu shadera należy zdefiniować strukturę, która będzie reprezentowała wierzchołek (w naszym kodzie struktura o nazwie Vertex, linie 1-5). Tworzenie struktury odbywa się w identyczny sposób, jak w języku C. Pozycja i kolor powinny być typu float4 (4-elementowy wektor liczb float). Jedyną różnicą, którą można zauważyć są etykiety POSITION i COLOR0, które informują kompilator, która ze zmiennych opisuje pozycję Vertex’a, a która kolor. Ich użycie jest niezbędne dla kompilatora, ponieważ w inny sposób nie ma możliwości dowiedzenia się, do jakich rejestrów powinny zostać przypisane konkretne zmienne (podobnie jak w C, zmienne mogą mieć dowolne nazwy). Oprócz tych dwóch zmiennych, w strukturze można umieścić jeszcze inne elementy, które będą służyły do przekazywania danych/instrukcji z programu OpenGL. Następnym krokiem jest utworzenie funkcji main(), która powinna zwracać przetworzony vertex (w naszym przykładzie jest to struktura o nazwie Vertex). Parametrami zazwyczaj są: vertex (Vertex IN) i macierz projekcji (uniform float4x4 ModelViewProj). W kodzie funkcji main powinien znaleźć się kod modyfikujący vertex’a. W naszym przykładzie: w linii nr 9 tworzymy zmienną typu Vertex o nazwie OUT, która później zostanie zwrócona jako wynik funkcji - vertex wyjściowy w liniach 10-12 modyfikujemy kolor vertex’a (dostęp do pól struktur jest identyczny jak w C) - pole x odpowiada za czerwony, y za zielony i z za niebieski 3 w linii 14 ustawiamy współrzędne vertex’a wyjściowego, która musi zostać zawsze pomnożona przez macierz widoku w linii 15 kopiujemy kolor do vertex’a wyjściowego - jak widać możliwe jest skopiowanie tylko części pól (np. użycie color.xz skopiowałoby tylko pola x i z). 1 2 3 #i n c l u d e <GL/ g l . h> #i n c l u d e <GL/ g l u . h> #i n c l u d e <GL/ g l u t . h> 4 5 6 #i n c l u d e <Cg/ cg . h> #i n c l u d e <Cg/cgGL . h> 7 8 9 #i n c l u d e < s t d l i b . h> #i n c l u d e <s t d i o . h> 10 11 12 13 14 CGcontext cgContext ; CGprogram cgProgram ; CGprofile cgVertexProfile ; CGparameter modelViewMatrix , p o s i t i o n , c o l o r ; 15 16 void i n i t ( ) { 17 18 g l E n a b l e (GL_DEPTH_TEST) ; 19 20 21 22 23 24 cgContext = c g C r e a t e C o n t e x t ( ) ; i f ( cgContext==0){ p r i n t f ( " Nie mozna utworzyc k o n t e k s t u Cg ! " ) ; exit (1) ; } 25 26 27 28 29 30 c g V e r t e x P r o f i l e = c g G L G e t L a t e s t P r o f i l e (CG_GL_VERTEX) ; i f ( c g V e r t e x P r o f i l e == CG_PROFILE_UNKNOWN) { p r i n t f ( " Nieznany p r o f i l ! " ) ; exit (1) ; } 31 32 cgGLSetOptimalOptions ( c g V e r t e x P r o f i l e ) ; 33 34 cgProgram = cgCreateProgramFromFile ( cgContext , CG_SOURCE, " s h a d e r . cg " , c g V e r t e x P r o f i l e , "main" , 0 ) ; 35 36 37 38 i f ( cgProgram == 0 ) { CGerror E r r o r = cgGetError ( ) ; 39 4 f p r i n t f ( s t d e r r , "%s \n" , c g G e t E r r o r S t r i n g ( E r r o r ) ) ; e x i t ( −1) ; 40 41 } 42 43 cgGLLoadProgram ( cgProgram ) ; position = cgGetNamedParameter ( cgProgram , "IN . p o s i t i o n " ) ; color = cgGetNamedParameter ( cgProgram , "IN . c o l o r " ) ; modelViewMatrix = cgGetNamedParameter ( cgProgram , " ModelViewProj " ) ; 44 45 46 47 48 } 49 50 51 52 void display ( ) { g l C l e a r (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ; glLoadIdentity () ; 53 gluLookAt ( 5 0 . 0 f , 2 5 0 . 0 f , −845.0 f , 0 . 0 f , 0 . 0 f , 0 . 0 f , 0 , 1 , 0 ) ; cgGLSetStateMatrixParameter ( modelViewMatrix , CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY) ; cgGLEnableProfile ( cgVertexProfile ) ; cgGLBindProgram ( cgProgram ) ; 54 55 56 57 58 g l C o l o r 3 f (1 , 1 , 0) ; glutSolidCube (250) ; cgGLDisableProfile ( cgVertexProfile ) ; 59 60 61 62 glutSwapBuffers () ; 63 64 } 65 66 /∗ P o z o s t a l a c z e s c programu standardowa . . . ∗/ Listing 2: Kod programu OpenGL (fragmenty) Kod programu OpenGL używającego shaderów tak na prawdę wiele się nie różni od zwykłego programu. Musimy dołączyć nagłówki języka Cg (linie 5 i 6). W liniach 11-14 tworzone są zmienne, które są niezbędne do użycia programu shadera: cgContext - kontekst urządzenia cgProgram - przechowuje skompilowany program Cg cgVertexProfile - profil VertexShadera - używany w celu jak najlepszej optymalizacji kodu modelViewMatrix, position, color - parametry używane w programie shadera Następnie w liniach 20-32 tworzony jest kontekst i pobierany jest profil (więcej informacji w tutorialu do języka Cg od nVidii). Linia 34 jest bardzo ważna, ponieważ odpowiada za kompilację programu shadera. Jako parametry funkcji cgCreateProgramFromFile podajemy kontekst, flagę CG_SOURCE, ścieżkę do kodu źródłowego programu Cg, profil 5 VertexShadera i nazwę głównej funkcji programu. Jeśli coś się nie powiodło (funkcja zwróciła 0), odczytujemy informacje o błędzie i je wyświetlamy. W linii 44 ładujemy program do pamięci oraz w liniach 45-47 łączymy parametry funkcji main programu Cg ze zmiennymi z programie OpenGL. W celu użycia shadera, w funkcji wyświetlającej ustawiamy macierz widoku (linia 55), włączamy profil VertexShadera (56), bindujemy (wybieramy) program shadera (57) i rysujemy, to co mamy narysować. Później należy wyłączyć profil VertexShadera (linia 61). 1.2.3 Przykład 2 - przekazywanie parametrów z kodu głównego do kodu shader’a Przykład pokazuje, w jaki sposób przekazywać parametry do programu Cg. Program będzie spłaszczał sferę, poprzez ustawienie tej samej wartości współrzędnej y dla vertexów znajdujących się wyżej niż ustalona granica. 1 2 3 4 5 6 s t r u c t InputVertex { f l o a t 4 p o s i t i o n : POSITION ; float4 color : COLOR0; f l o a t yThreshold ; }; 7 8 9 10 11 12 s t r u c t OutputVertex { f l o a t 4 p o s i t i o n : POSITION ; float4 color : COLOR0; }; 13 14 15 16 OutputVertex main ( I n p u t V e r t e x IN , uniform f l o a t 4 x 4 ModelViewProj ) { OutputVertex OUT; 17 i f ( IN . p o s i t i o n . y>IN . yThreshold ) { IN . p o s i t i o n . y=IN . yThreshold ; } 18 19 20 21 OUT. p o s i t i o n = mul ( ModelViewProj , IN . p o s i t i o n ) ; 22 23 OUT. c o l o r . xyz = IN . c o l o r . xyz ; 24 25 r e t u r n OUT; 26 27 } Listing 3: Kod programu shader’a 6 Sam kod programu Cg uległ jedynie niewielkim zmianom. Został wprowadzony podział na strukturę dla vertexów wejściowych (InputVertex) i wyjściowych (OutputVertex). W strukturze InputVertex pojawił się nowy element yThreshold (linia nr 5), przez który będziemy przekazywali graniczną wartość y, powyżej której sfera ma być spłaszczona. W funkcji main, poza zmianą struktur vertexów, została dodana instrukcja if (linie 18-20). Sprawdza ona, czy współrzędna y vertexa jest większa od zmiennej yThreshold, jeśli tak, to ustawia ją na wartość yThreshold (dzięki czemu sfera zostaje spłaszczona). W celu ustawiania parametru yThreshold, należy w programie głównym (dla CPU): • stworzyć dodatkową zmienną typu CGparameter (w przykładzie jest to yThresholdVertexShader) • połączyć ją z parametrem głównej funkcji programu shader’a; Po modyfikacji może to wyglądać tak: 1 2 3 4 p o s i t i o n=cgGetNamedParameter ( cgProgram , "IN . p o s i t i o n " ) ; c o l o r=cgGetNamedParameter ( cgProgram , "IN . c o l o r " ) ; y T h r e s h o l d V e r t e x S h a d e r=cgGetNamedParameter ( cgProgram , "IN . yThreshold " ) ; modelViewMatrix=cgGetNamedParameter ( cgProgram , " ModelViewProj " ) ; • w funkcji display, po zbindowaniu programu Cg należy ustawić ten parametr na jakąś wartość, np: cgGLSetParameter1f(yThresholdVertexShader, 10); (w przykładzie zamiast stałej liczby stosujemy zmienną o nazwie yThreshold, która zmienia swoją wartość w czasie działania programu) Do ustawiania parametrów służy rodzina funkcji cgGLSetParameter, najpopularniejsze z nich to: 1 2 v o i d cgGLSetParameter1 { f d } ( CGparameter param , TYPE x ) ; 3 4 5 6 v o i d cgGLSetParameter2 { f d } ( CGparameter param , TYPE x , TYPE y ) ; 7 8 v o i d cgGLSetParameter3 { f d } ( CGparameter param , 7 TYPE x , TYPE y , TYPE z ) ; 9 10 11 12 13 14 15 16 17 v o i d cgGLSetParameter4 { f d } ( CGparameter param , TYPE x , TYPE y , TYPE z , TYPE w ) ; gdzie: • liczba po cgGLSetParameter oznacza, ile pól posiada ten parametr (np. dla float3 dajemy 3, dla zwykłego float 1) • litera f lub d w nawiasie klamrowym służy do wyboru odpowiedniego typu (wybieramy tylko jedną z nich) - f oznacza float, d double. 1.3 Przydatne linki The Cg Tutorial - http://developer.nvidia.com/node/76 - po prawej stronie znajdują się linki do kolejnych rozdziałów Artykuł w NeHe Productions - http://nehe.gamedev.net/tutorial/ cg_vertex_shader/25002/ (polskie tłumaczenie: http://aklimx.sppieniezno.pl/nehepl/display. php?id=47) 8