Základy programování shaderů v OpenGL část 1 – program Petr Felkel, Jaroslav Sloup Katedra počítačové grafiky a interakce, ČVUT FEL místnost KN:E-413 (Karlovo náměstí, budova E) E-mail:
[email protected] S použitím materiálů Vlastimila Havrana Poslední změna: 14.10.2015
Obsah přednášky Dnes Tok dat v OpenGL se shadery Překlad a sestavení programu Struktura programu typu shader Příště Předávání dat a parametrů do programu typu shader Příklady zdrojových kódů pro shadery
Hrubé schéma OpenGL s programovatelným zobrazovacím řetězcem
Vertex Data
Pixel Data
Vertex Shader
Primitive Assembly and Rasterization
Fragment Shader
Fragment operations
Texture Store
OpenGL verze 3.1
Každá aplikace definuje dva programy (tzv. shadery): vertex shader (VS) program zapsaný v jazyku GLSL, který zpracovává vrcholy
fragment shader (FS) program zapsaný v GLSL zpracovává fragmenty
Shadery a OpenGL Shadery jsou programy, které plně běží na grafické kartě GPU OpenGL na CPU „jen organizuje“ (data a shadery) Shadery pracují paralelně – SIMD (single instruction multiple data)
• •
VS po vrcholech FS po fragmentech
Fragment = kandidát na zobrazený pixel
• •
Zda bude nakonec zobrazen rozhodne další část zobrazovacího řetězce Obecně nese více informací, než jen zobrazenou barvu (hloubku, průhlednost, normálu, směr ke světlu, …)
PGR
4
Princip „zpracování primitiv na pixely“
VS
FS
[http://www.lighthouse3d.com/tutorials/glsl-tutorial/pipeline-overview/]
PGR
5
Zjednodušený model proudového zpracování
Aplikace
Vrcholy
Vrcholy Vertex Processing
Vertex Shader
Framebuffer
GPU Data Flow
Pixely
Fragmenty
Rasterizer
Fragment Processing
Fragment Shader
Tok dat v OpenGL – pipeline
Primitive assembly
PGR
7
Tok dat v OpenGL – po částech 1/3 OpenGL okopíruje blok dat na GPU (blok vrcholů)
[Gortler]
Pro každý vrchol se spustí jeden Vertex shader Příkaz draw vykreslí trojice vrcholů jako trojúhelníky (souřadnice a atributy)
VS obdrží souřadnice a atributy vrcholu + Uniformní proměnné (společné více primitivům)
VS vypočítá normalizovanou 2D pozici vrcholu v okně (-1,-1) – (1,1) + další proměnné měnící se v ploše trojúhelníka (out,varying) PGR
Primitive assembly
Blok primitive assembly počká na tři vrcholy a poskládá z vrcholů zase trojúhelník
8
Tok dat v OpenGL – po částech 2/3 [Gortler]
Primitive assembly
Blok primitive assembly předá trojúhelníky (trojice vrcholů) rasterizátoru
Rasterizer • Vygeneruje fragmenty trojúhelníka • Dále odešle pozici fragmentu a • Interpolované hodnoty proměnných (varying) ve vrcholech
PGR
9
Tok dat v OpenGL – po částech 3/3 [Gortler]
FS • se spustí pro každý fragment • Vypočítá barvu a hloubku fragmentu • Odešle je do grafické paměti Fragment se stane pixelem na obrazovce, pokud projde testy (výstřižek, šablona, alfa, hloubka)
PGR
10
Shadery Shader je program, který běží na grafické kartě (GPU) Definuje funkci jednoho bloku zobrazovacího řetězce OpenGL používá jazyk podobný jazyku C, který se jmenuje OpenGL Shading Language (GLSL)
shader zapsán v GLSL
PGR
…
…
vstupní atributy / proměnné
výstupní proměnné
11
Shadery (typické úlohy) vertex shader (VS) – pracuje na úrovni vrcholů transformace vrcholu transformace a normalizace normály transformace souřadnic pro textury změna atributů asociovaných s vrcholy (např. některé složky barvy)
fragment shader (fs)– pracuje na úrovni fragmentů přístup k textuře a další zpracování textury mlha, míchání barev, stínování operace nad interpolovanými hodnotami
PGR
12
Moderní programování v OpenGL Moderní programy v OpenGL při vykreslování obrázku v zásadě provádějí pouze tyto čtyři kroky: 1. Přelož všechny shadery a nahraj je na GPU 2. Rezervuj místo pro data („buffer objects“) a nahraj do nich data 3. Propoj - data v bufferech se vstupními proměnnými (VS) a - výstupní proměnné (VS) se vstupy dalšího bloku (FS) 4. Spusť vykreslování, obrázek je vypočítán na GPU a zobrazen.
PGR
13
Každý shader se překládá zvlášť
Vytvoř shader
PGR
I. glCreateShader()
Nahraj zdrojový kód pro shader
glShaderSource()
Zkompiluj shader
glCompileShader()
Tato část se opakuje pro každý shader
14
Programy s použitím shaderu v OpenGL Vytvoř program
Před použitím musí být shadery přeloženy a sestaveny
II.
glCreateProgram()
I. Vytvoř shader
Překládá se až za běhu aplikace na konkrétním HW OpenGL má proto příkazy pro kompilátor a linker programu Každý program musí obsahovat: • vertex shader • fragment shader
PGR
glCreateShader()
Nahraj zdrojový kód pro shader
glShaderSource()
Zkompiluj shader
glCompileShader()
Připoj shader k programu
glAttachShader()
Sestav program
glLinkProgram()
Použij program
glUseProgram()
Tato část se opakuje pro každý shader
15
Zápis programu typu shader Program pro shader je text typicky se ukládá ve zvláštních souborech
• •
vertex shader přípona .vert, .vs, … fragment shader .frag, .fs, …
lze jej uložit i přímo v kódu jazyka C do textových řetězců
PGR
16
Příklady zdrojových kódů
Příklad textu programu pro fragment shader
Příklad textu programu pro vertex shader #version 400
#version 400
in vec3 VertexPosition; in vec3 VertexColor; out vec3 Color;
in vec3 Color; out vec4 FragColor; void main() { FragColor = vec4(Color, 1.0); }
void main() { Color = VertexColor; gl_Position = vec4(VertexPosition, 1.0); }
PGR
17
Práce se shadery v kódu aplikace I.
Kroky při překladu shaderů: 1. Vytvoř objekt typu shader (shader object) 2. Předej mu zdrojový kód programu 3. Přelož zdrojový kód shaderu uvnitř objektu typu shader (compile) 4. Zkontroluj výsledek (status) překladu
II. Kroky při sestavení programu (vertex a fragment shader) 5. 6. 7. 8.
Vytvoř objekt programu Připoj objekty shaderů k objektu programu Sestav program (link) Zkontroluj status sestaveného programu
III. Použití programu v aplikaci PGR
18
I. Překlad programu typu shader I.
Kroky při překladu shaderů: 1. Vytvoř objekt typu shader (shader object) 2. Předej mu zdrojový kód programu (jeden textový řetězec nebo pole řetězců) 3. Přelož zdrojový kód shaderu uvnitř objektu typu shader 4. Zkontroluj výsledek (status) překladu shader source
…
check status
shader object
shader source
glShaderSource compile
PGR
19
1. Vytvoření objektu typu shader
Jméno shaderu = kladná hodnota odkazující na vytvořený shader objekt
Typ vytvářeného shaderu GL_VERTEX_SHADER nebo GL_FRAGMENT_SHADER
GLuint vertShader = glCreateShader( GL_VERTEX_SHADER ); if (vertShader == 0) { // check for error fprintf(stderr, "Error creating vertex shader.\n"); exit(1); } Objekt shaderu udržuje zdrojový kód a přeložený shader. • Po sestavení programu (link) lze objekt shaderu smazat • Odkazujeme na něj jménem (kladná celočíselná hodnota) PGR
20
2. Nahrání zdrojového kódu z pole řetězců Funkce sloužící k nahrání textů zdrojového kódu shaderu z externího souboru
const GLchar * shaderCode1 = loadShaderAsString("basic.vert");… const GLchar* codeArray[] = {shaderCode1, shaderCode2}; glShaderSource( vertShader, 2, codeArray, NULL ); Jméno objektu shader Počet řetězců zdrojových kódů
Pole ukazatelů na řetězce → zdrojový text shaderu lze poskládat z více částí PGR
Pole hodnot Glint → délky jednotlivých řetězců zdrojových kódů (NULL indikuje, že řetězce jsou ukončeny nulovým znakem 0x00)
21
2. Nahrání zdroj. kódu z jednoho řetězce Funkce sloužící k nahrání textů zdrojového kódu shaderu z externího souboru
const GLchar * shaderCode = loadShaderAsString("basic.vert"); glShaderSource( vertShader, 1, &shaderCode, NULL ); Jméno objektu shader Počet řetězců zdrojových kódů
Reference na odkaz na řetězec (celý program je v jediném řetězci) PGR
NULL indikuje, že jediný řetězec je ukončen nulovým znakem 0x00
22
3. – 4. Překlad a kontrola správnosti Překlad zdrojového kódu glCompileShader(vertShader); v objektu shaderu GLint result; glGetShaderiv(vertShader, GL_COMPILE_STATUS, &result); if (result == GL_FALSE) { Dotaz na výsledek fprintf( stderr, "Vertex shader compilation failed!\n" ); GLint logLen;
překladu
glGetShaderiv(vertShader, GL_INFO_LOG_LENGTH, &logLen ); if ( logLen > 0 ) { char * log = (char *)malloc(logLen); GLsizei written;
Dotaz na délku chybové zprávy o překladu
glGetShaderInfoLog(vertShader, logLen, &written, log); fprintf(stderr, "Shader log:\n%s", log); free(log); } } PGR
Získání textové zprávy o chybě 23
Shader pomocí knihovny pgr Jméno shaderu = kladná hodnota odkazující na vytvořený shader objekt
Typ vytvářeného shaderu GL_VERTEX_SHADER nebo GL_FRAGMENT_SHADER
GLuint vertShader = pgr::createShaderFromFile ( GL_VERTEX_SHADER,
"basic.vert");
Soubor s textem shaderu
if (vertShader == 0) { // check for error fprintf(stderr, "Error creating vertex shader.\n"); exit(1); } PGR
24
II. Sestavení objektu typu program se shadery
Přeložené shadery musí být sestaveny do shader programu (vertex + fragment shader)
Kroky k sestavení shader programu: 5. 6. 7. 8.
Vytvoř objekt programu Připoj shadery k objektu typu program (attach) Sestav program (link) Zkontroluj výsledek sestavování programu shader object
attach
… shader object
attach
shader program object
check status
link PGR
25
5. Vytvoř objekt typu program
Vytvoř prázdný objekt programu
Nenulová hodnota k identifikaci vytvořeného program objektu
GLuint programHandle = glCreateProgram(); if (programHandle == 0) {// check for error fprintf(stderr, "Error creating program object.\n"); exit(1); } Objekt typu program v OpenGL obsahuje kód všech shaderů použitých v programu (alespoň vertex + fragment shader). PGR
26
6-7. Připoj shader k objektu typu program
Připoj vertex shader k programu
Připoj fragment shader k programu
glAttachShader(programHandle, vertShader); glAttachShader(programHandle, fragShader); glLinkProgram(programHandle); Sestav spustitelný program „typu .gpuexe“, který běží na dvou komponentách GPU: • programovatelný vertex processor (je-li shader objekt typu GL_VERTEX_SHADER připojen k programu) • programovatelný fragment processor (je-li shader objekt typu GL_FRAGMENT_SHADER připojen k programu) PGR
27
8. Zkontroluj stav sestaveného programu
Příklad části programu v C++ GLint status;
glGetProgramiv(programHandle, GL_LINK_STATUS, &status); Zkontroluj zda sestavení if (status == GL_FALSE) { proběhlo v pořádku fprintf( stderr, "Failed to link shader program!\n" ); GLint logLen; glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &logLen);
if ( logLen > 0 ) {
Dotaz na délku chyb. char * log = (char *)malloc(logLen); zprávy o sestavení GLsizei written; glGetProgramInfoLog(programHandle, logLen, &written, log); fprintf(stderr, "Program log: \n%s", log); Získej textové zprávy free(log);
} }
PGR
o chybě sestavení programu a vypiš je 28
Program pomocí knihovny pgr Jméno programu = kladná hodnota odkazující na vytvořený program objekt
Seznam objektů shaderů
const GLuint shaderIdArray[] = {vertShader, fragShader, 0}; GLuint programHandle = pgr::createProgram (shaderIdArray); if (programHandle == 0) { // check for error fprintf(stderr, "Error creating vertex shader.\n"); exit(1); }
PGR
29
III. Nahraj program do zobrazovacího řetězce OpenGL if (status == GL_FALSE) { } else { glUseProgram(programHandle); }
Instaluj program s připojenými shadery do zobrazovacího řetězce na GPU
Příklad části programu v C++ Jméno objektu programu, který se nyní bude provádět v zobrazovacím řetězci → program je připraven pro spuštění a zobrazovaní obrazu na výstup Objektů typu program lze vytvořit několik a poté mezi nimi přepínat příkazem glUseProgram() v rámci běhu jedné aplikace. Programy přitom mohou mít připojené stejné shadery. PGR
30
Struktura zdrojového kódu shaderu Verze GLSL (zde 4.0) Příklad zdrojového programu pro vertex shader:
vstupní proměnné shaderu (pro VS atributy)
#version 400
výstupní proměnné shaderu
out vec3 Color;
Funkce main(), která je spouštěna pro každý vrchol (VS) nebo fragment (FS)
in vec3 VertexPosition; in vec3 VertexColor;
void main() { Color = VertexColor; gl_Position = vec4(VertexPosition, 1.0); } PGR
Vestavěná výstupní proměnná (prefix gl_)
31
Kvalifikátory proměnných Kvalifikátor uložení v paměti in
Význam Vstup atributu (VS), nebo propojení na výstup předchozího bloku (FS - výstup rasterizátoru)
out
Výstupní hodnota shaderu – propojí se s dalším blokem (VS s rasterizátorem, FS s blokem testů)
uniform
Hodnota společná více primitivům. Je přístupná ve všech shaderech (VS i FS). Nastavuje ji OpenGL (v C++) před příkazem draw.
Způsob interpolace hodnot v rasterizátoru se nastavuje interpolačním kvalifikátorem (pro out ve VS a pro in FS): Interpolační kvalifikátor smooth flat noperspective
význam perspektivně správná interpolace hodnot bez interpolace lineární interpolace PGR
32
Propojení shaderů navzájem Propojení VS a FS (FS dostávají interpolované hodnoty)
fragment shader
…
Rasterizace a interpolace
…
…
…
vertex shader
Příklad zdroj. kódu vertex shader Příklad zdroj. kódu fragment shader
#version 400 in vec3 VertexPosition; in vec3 VertexColor;
#version 400
out vec3 Color;
out vec4 FragColor;
void main() {
in vec3 Color; Jméno a datový typ musí být stejný
Color = VertexColor; gl_Position = vec4(VertexPosition, 1.0); }
PGR
void main() { FragColor = vec4(Color, 1.0); } 33
Připojení atributů = vstupů vertex shaderu Každá vstupní proměnná vertex shaderu má vlastní celočíselný index (location). Ten lze nastavit: V OpenGL aplikaci před sestavením shader programu příkazem glBindAttribLocation() glBindAttribLocation(programHandle, 0, “VertexPosition”); Index (location) atributu
Jméno atributu
V shaderu pomocí kvalifikátoru layout: layout (location = 0) in vec3 VertexPosition;
Pokud hodnotu indexu (location) explicitně nenastavíme, je index vygenerován při PGR sestavení programu automaticky34
Připojení atributů = vstupů vertex shaderu Pokud je index atributu přiřazen automaticky linkerem při sestavení programu, lze se na jeho hodnotu dotázat (po zkompilování a slinkování!) příkazem: glGetAttribLocation(programHandle, “VertexPosition”);
vrací index atributu (=location, typ GLint)
jméno atributu
Index atributu (location) nabývá hodnot
• •
0,1,2,,…, pokud byl atribut ve VS „aktivně“ použit –1 pokud nebyl v textu VS nalezen, nebo v nebyl v shaderu použit a byl proto vypuštěn PGR
35
Atributy vrcholu a proměnné uniform
UNIFORM (společné proměnné) 0 1 0 1
GPU
fragment shader
…
…
vertex shader
n
…
…
ATRIBUTY vrcholu
…
n
attribLocation (index vstupu VS)
uniformLocation (index společné proměnné uniform) PGR
36
Předání dat do shaderu pomocí proměnných s kvalifikátorem uniform uniform kvalifikátor pro proměnné
• • • •
Vhodný pro hodnoty, které se mění málo (např. jsou stejné pro celý objekt, třeba transformační matice) Hodnoty se nastavují v OpenGL API, v C++ Vždy jsou vždy použity jako vstupní proměnné V kódu shaderu je nelze měnit (read-only) Příklad zdrojového kódu pro Vertex shader
Deklarace proměnné s kvalifikátorem uniform a jménem RotationMatrix (matice k transformaci vrcholů)
#version 400 in vec3 VertexPosition; in vec3 VertexColor; out vec3 Color;
uniform mat4 RotationMatrix; void main() { Color = VertexColor; gl_Position = RotationMatrix* vec4(VertexPosition, 1.0); } PGR
37
Předání dat do shaderu pomocí proměnných s kvalifikátorem uniform (pokr.) Příklad části programu v C++ // clear the color buffer glClear(GL_COLOR_BUFFER_BIT); // use glm (part of PGR-framework) function to create a transformation matrix mat4 rotationMatrix = glm::rotate(mat4(1.0f), angle, vec3(0.0f,0.0f,1.0f)); // find location of a uniform variable GLuint location = glGetUniformLocation(programHandle, "RotationMatrix"); if (location >= 0) { // location == -1 if uniform is not active // assign a value to the uniform variable glUniformMatrix4fv(location, 1, GL_FALSE, &rotationMatrix[0][0]); } // bind to the vertex array object and call glDrawArrays to initiate rendering glBindVertexArray(vaoHandle); glDrawArrays(GL_TRIANGLES, 0, 3 );PGR
38
Varianty příkazu glUniform glUniform{1|2|3|4}{f|i|ui}
• • • • • •
1 pro float, int, unsigned int, bool; 2 pro vec2, ivec2, uvec2, bvec2, atd. f pro float (float, vec2, vec3, vec4, nebo jejich pole) i pro int (int, ivec2, ivec3, ivec4, nebo jejich pole) ui pro unsigned int, uvec2, uvec3, uvec4, nebo jejich pole) i, ui or f pro bool (bool, bvec2, bvec3, bvec4, nebo jejich pole) false pro 0 a 0.0f a true pro nenulové hodnoty
PGR
39
Příklady příkazu glUniform Jednotlivé hodnoty – 1 až 4x float glUniform1f(location, 7.5f); glUniform4f(location , 0.5f , 0.5f , 0.2f , 1.0f); Pole hodnot – int pole[count] – pole o délce count glUniform1iv(location, count, pole); Matice či pole matic glUniformMatrix4fv(location, 1, GL_FALSE, &rotationMatrix[0][0]);
• •
1 matice Nebude se transponovat PGR
40
Typ datových proměnných základní datové typy:
float, double, bool, int, uint
stejně jako v jazyce C
vektory s 2, 3 nebo 4 složkami:
vec{2,3,4}
{2,3,4}-složkový vektor s jednoduchou přesností (float)
dvec{2,3,4}
{2,3,4}-složkový vektor s dvojitou přesností (double)
bvec{2,3,4}
{2,3,4}-složkový vektor s hodnotami bool
ivec{2,3,4}
{2,3,4}-složkový vektor s celočíselnými hodnotami
čtvercové matrice:
mat2, dmat2
matice 2×2 s jednoduchou či dvojnásobnou přesností
mat3, dmat3
matice 3×3 dtto
mat4, dmat4
matice 4×4 dtto PGR
v pohyblivé řádové čárce
41
Deklarace a inicializace proměnných int bool
a = 2; d = true;
float b = 2; float c = float(a);
// a is initialized with 2 // d is true // incorrect, no automatic type casting supported // correct. c is 2.0
vec3 f; // declaring f as a vec3 vec3 g = vec3(1.0, 2.0, 3.0); mat4 m = mat4(1.0) // initializing the diagonal of the matrix with 1.0 mat2 k = mat2(1.0,0.0,1.0,0.0); // all elements are specified float frequencies[3] = float[](3.4, 4.2, 5.0);
// initialize array of floats
struct dirLight { // type definition vec3 direction; vec3 color; }; dirlight d1; dirlight d2 = dirlight(vec3(1.0,1.0,0.0),vec3(0.8,0.8,0.4)); // structure initialization PGR
42
Přístup ke složkám vektorových proměnných vec3 pos = vec3(1.0, 2.0, 3.0); float f = 1.2; double c = 2.0LF; // float and double constants pos.x // is legal, the same as pos.r pos.xy // is legal, the same as pos.rg pos.w // is illegal f.x // is legal, the same as f.r or f.s f.y // is illegal const int L = pos.length(); // number of components in vector // the order of the components can be different to swizzle them, or replicated vec3 swiz= pos.zyx; // swizzled = (3.0, 2.0, 1.0) vec4 dup = pos.xxyy; // duplicated = (1.0, 1.0, 2.0, 2.0) vec4 dup = f.xxxx; // duplicated = (1.2, 1.2, 1.2, 1.2) // accessing matrix components mat4 m; m[1] = vec4(2.0); // sets the second column to all 2.0 m[2][3] = 2.0; // sets the 4th element of the third column to 2.0 PGR
43
Vestavěné funkce v GLSL trigonometrické funkce
radians(), degrees(), sin(), cos(), atan(), …
exponenciální funkce
pow(), exp(), log(), sqrt(), …
geometrické funkce pro vektory
dot(), cross(), length(), normalize(), reflect() …
maticové funkce
transpose(), determinant(), inverse()
funkce pracující s vektory relačně po složkách:
bvec equal(vec x, vec y), bvec greaterThan(vec x, vec y)
běžné algebraické funkce
abs(), floor(), min(), max(), mix(), … PGR
44
Uživatelské funkce v rámci shaderu Definice funkce může být přetížena pokud jsou parametry funkce rozdílné Chování rekurzivních funkcí není definováno
Uživatelsky definovaná funkce s identifikátorem funkce12
in vec3 VertexPosition; in float VertexStartTime; out float Transp; uniform float ParticleLifetime; Uniform mat4 MVP; void funkce12(float Time) { float age = Time - VertexStartTime; Transp = 0.0; if(Time >= VertexStartTime) Transp = 1.0 - age / ParticleLifetime;
Vestavěná výstupní proměnná (prefix gl_)
gl_Position = MVP * vec4(VertexPosition, 1.0); }
PGR
45
Příště Předávání dat z aplikace a mezi shadery
PGR
46
Zajímavé odkazy
David Wolff: OpenGL 4.0 Shading Language Cookbook. Packt Publishing, 2011, ISBN 978-1-849514-76-7.
Richard S. Wright, Nicholas Haemel, Graham Sellers, Benjamin Lipchak: OpenGL SuperBible: Comprehensive Tutorial and Reference. 5th ed., Addison-Wesley Professional, 2010, ISBN 0-321-71261-7.
Ed Angel, Dave Shreiner: An Introduction to Modern OpenGL Programming, SIGGRAPH 2011 tutorial, http://www.daveshreiner.com/SIGGRAPH/s11/ModernOpenGL.pptx
Joe Groff. An intro to modern OpenGL. Updated July 14, 2010 http://duriansoftware.com/joe/An-intro-to-modern-OpenGL.-Table-of-Contents.html
Vertex Array Object na OpenGL Wiki: http://www.opengl.org/wiki/Vao
Vertex Specification na OpenGL Wiki: http://www.opengl.org/wiki/Vertex_Specification
PGR
47