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

Podobne dokumenty