Android OpenGL Pokročilé shadery
Struktura programu Reálná aplikace zpravidla obsahuje více než jeden shader Kód pro inicializaci shaderu je dobré mít ve třídě (méně opisování stejného kódu) Shadery není praktické mít ve stringu tak, jak to bylo v předchozích tutorialech Lze použít resource
Základní třída Shader Obsahuje handle na OpenGL objekty public class Shader { int[] p_shader = new int[2]; // vertex a fragment shadery int n_program_object; // program object (shader handle) Znovupoužitelná funkce pro kompilaci protected void Compile(String vertexshadersrc, String fragmentshadersrc); Znovupoužitelná funkce pro linkování protected void Link(); Funkce pro aktivaci shaderu public void Use(); }
Základní třída Shader Funkce pro kompilaci vytvoří i program object a připojí k němu shadery Kompilace a linkování je oddělené Před linkováním se nastavují propojení vertex atributů s proměnnými vertex shaderu Funkce pro aktivaci shaderu Zděděné třídy ji v případě potřeby přepíšou, aby obsahovala kód pro nastavení uniformů
Shadery v resource Lze vytvořit novou sekci, kam jednotlivé shadery uložit Nevýhodou je výskyt náhodných znaků na začátku shaderu (Android bug) Všechny shadery naštěstí začínají stejně precision highp float;
Shadery v resource K základní třídě shader přidáme funkce public static String ReadResourceFile(Context ctx, int resourceid); // přečte textový soubor resourceid z resource sekce aplikace ctx public static String MakeSafeShader(String src); // odstraní ze shaderu náhodné znaky na začátku // (zdrojový kód musí začínat klíčovým slovem precision ) protected void Compile(Context ctx, int vertexshaderresourceid, int fragmentshaderresourceid); // jen volá ReadResourceFile() a zkompiluje přečtené shadery // pomocí již existující funkce stejného jména
Použití třídy Shader Zdědíme třídu Shader public class TextureShader extends Shader { Dodáme členské proměnné pro adresy uniformů int n_modelview_projection_uniform_location; // MVP uniform
Použití třídy Shader Napíšeme konstruktor public TextureShader(Context ctx) { Compile(ctx, R.raw.texturecube_vertex, R.raw.texturecube_fragment); // kompiluje shadery glbindattriblocation(n_program_object, 0, "v_pos"); glbindattriblocation(n_program_object, 1, "v_tex"); // nastaví vertex attributy (musí se udělat před linkováním) Link(); // slinkuje program n_modelview_projection_uniform_location = glgetuniformlocation(n_program_object, "t_mvp"); // zjistí kde leží parametr modelview-projection matice
Použití třídy Shader Konstruktor (pokračování) int n_tex_uni = glgetuniformlocation(n_program_object, "n_texture"); // zjistí kde leží parametr nastavení texturovací jednotky gluseprogram(n_program_object); // nastaví program jako aktivní } gluniform1i(n_tex_uni, 0); // nastaví texturovací jednotku na nulu // (to už nebude třeba v budoucnu měnit)
Použití třídy Shader Přepíšeme funkci Use() pokud je třeba Zejména pokud je třeba nastavovat uniformy public void Use(float[] modelviewprojectionmatrix) { gluseprogram(n_program_object); // nastaví program jako aktivní } gluniformmatrix4fv(n_modelview_projection_uniform_location, 1, false, modelviewprojectionmatrix, 0); // nastaví matici
Použití třídy Shader V kódu již jen vytvoříme shader (až ve chvíli kdy bylo inicializováno OpenGL, např. v onsurfacecreated()) Při kreslení jen zavoláme Use()
Blinn-Phong Shader Jednoduchý osvětlovací model ( ) L + V I = ia + id N L + is V L + V α L L+V N N je normála povrchu V je směr k pozorovateli L je směr ke světlu L+V je tzv. half vector, aproximace odrazu V α ovlivňuje velikost tzv. hot spotu ( prasátko ) V
Blinn-Phong Shader Vektory připravíme ve vertex shaderu Normálu můžeme u jednoduchých tvarů určit z pozice (u jednotkové koule je rovna pozici) Nebo může být uložená v datech vrcholů Musíme ji transformovat tzv. normálovou maticí Normálová matice je 3x3 podmatice inverzní transponované modelview matice
Blinn-Phong Shader Součinem modelview matice s pozicí spočíntáme pozici p v prostoru oka Směr k pozorovateli V = -p Směr ke světlu L = p light - p p light je pozice světla v prostoru oka spočítá se vynásobením world-space pozice světla maticí objektu (pokud to neuděláme, světlo se pohybuje s kamerou což ale může být cílem)
Blinn-Phong Shader Ve fragment shaderu již jen interpolované vektory N, L a V normalizujeme a dosadíme Složky světla můžeme rozdělit na difůzní (matné) a spekulární (lesklé oblasti) Pro jednotlivé složky můžeme mít různé textury (viz. example) difůzní spekulární
Blinn-Phong Shader
Procedurální textury Shadery mají k dispozici množství matematických funkcí, pomocí niž lze generovat procedurální textury Procedurální textury mohou být rychlejší než klasické textury Příkladem jednoduché procedurální textury je šachovnice Ještě jednodušší jsou pruhy (1D verze)
Procedurální pruhy Souřadnice s Modulo mod(s,.2) Krok step(.1, mod(s,.2))
Procedurální šachovnice a = step(.1, mod(s,.2)) b = step(.1, mod(t,.2)) ab a + b a - b
Procedurální šachovnice
Pokročilé procedurální textury Šachovnice a pruhy lze různě kombinovat Místo diskrétního step() lze použít abs() nebo smoothstep k vytvoření efektu záře Pomocí sin(), cos() a podobných lze v procedurální textuře kreslit animace
Pokročilé procedurální textury
Pong shader Pruhy se dají kreslit pomocí funkce step(velikost / 2, abs(t střed)) s y s x v y v x
Pong shader Svit (glow) se kreslí jako max(0, velikost / 2 abs(t střed)) / (velikost / 2) Pyramidová funkce s y s x v y 1 v x v x v y
Pong shader Pohyb míčku po pilovité funkci x = 2 * abs(x floor(x + 0.5)) Jiná perioda horizontálně a jiná vertikálně Hráči se pohybují s míčkem, pokud je míček daleko, rozkmitají se navíc po sinusovce sin(t)
Konec