Android OpenGL Vykreslování terénu
Terén Většinou je reprezentovaný pomocí 2D výškové mapy Na běžných GPU lze výškovou mapu číst přímo ve vertex shaderu, mobilní GPU však tuto funkci nepodporují Nezbývá než výškovou mapu prostě uložit do pole se souřadnicemi vrcholů
Rozsáhlý Terén Pro vykreslování rozsáhlého terénu je obvykle potřeba algoritmus level of detail Kvůli absenci čtení textur vertex shaderem je těžší používat složité LOD techniky Přehled používaných technik lze nalézt na http://vterrain.org/lod/papers/
Mobilní terén Kvůli jednoduchosti mobilních GPU ve spojením s ne příliš silným CPU (zatím) nelze přenést algoritmy, navržené pro PC Je třeba se uchýlit ke starším technikám Vyhnout se aktualizaci vertex bufferů Vyhnout se složitému napojování dílců
Jednoduchý terén Terén je uložený jako stejně velké dlaždice o několika různých rozlišeních Dlaždice jsou již ve formátu vhodném pro kreslení (ne bitmapa, ale vertex array) Díky předem dané velikosti dlaždic není nutné ukládat index array Předpokládáme že terén se celý vejde do paměti (v zájmu udržení jednoduchosti, bez ztráty obecnosti algoritmu)
Jednoduchý terén (jednotlivé dílce terénu)
Navazování dlaždic a) Neřešíme b) Uděláme svislé sukénky (filleting, Sun) http://java.sun.com/products/jfc/tsc/articles/jcanyon/ c) Použijeme stínové dílce (Pouderoux, Marvie: Adaptive Streaming and Rendering of Large Terrains using Strip Masks )
Výběr detailu dlaždic Lze vypočítat zkreslení v závislosti na výšce dlaždice a výšce a vzdálenosti pozorovatele Nebo lze jednoduše použít vzdálenost od pozorovatele (Losasso, Hoppe: Geometry clipmaps: Terrain rendering using nested regular grids )
Výběr detailu dlaždic (červená = vyšší detail)
Omezení kreslení dlaždic Známým algoritmem je ořezávání záběrem pohledu (view frustum culling) Na základě směru pohledu a pozice pozorovatele jde jednoduše otestovat viditelnost dlaždic ještě před kreslením Výhodné mít dlaždice ve stromové struktuře (např. v octree) Pro členité terény lze předpočítat sadu viditelných dlaždic z různých míst (PVS) Mimo rámec tohoto tutorialu
Ořezávání záběrem pohledu Pohled má tvar pyramidy Jednotlivé roviny jdou spočítat z projekční matice Test zda je dílec terénu před rovinou je primitivní, s výhodou lze využít obalové geometrie jako např. bounding sphere
Texturování Použijeme jednoduchou opakující se texturu V pixel shaderu podle výšky vrcholu určíme podíly jednotlivých textur (dole je tráva, nad ní skály, nahoře je sníh) Pro větší počet vrstev lze použít GL_ARB_texture_array (přítomné na Tegra)
Nebe Lze použít tzv. skybox, jedná se o texturu nebe, mapovanou na nekonečně velkou krychli Krychle může být konečně velká, ale pozorovatel musí být uprostřed Je nutné vypnout depth test gldisable(gl_depth_test); gldepthmask(0); // vypne i zápisy Lze použít rozšíření GL_ARB_cube_map obrázek z nvidia.com
Nebe Pro dynamičtější nebe lze použít tzv. skydome, tedy jakousi polokouli, která zakrývá terén, na ni je možné mapovat pohybující se texturu mraků Pomocí přepočtu souřadnic v pixel shaderu lze skydome realizovat i s krychlí, nebo dokonce jen s jedním trojúhelníkem obrázek z nvidia.com
Nebe na jeden trojúhelník Jeden trojúhelník, pokrývající celou obrazovku (případně jeden obdélník) Vertex shader spočítá směry pohledu v jednotlivých rozích obrazovky Fragment shader může Přečíst skybox z cubemap textury Promítnout směr pohledu na kouli trojúhelník pokrývající obrazovku
Nebe na jeden trojúhelník Vertex shader precision highp float; // specifikace přesnosti attribute vec3 v_pos; // pozice vstup z dat vrcholu uniform mat4 t_modelview_projection_matrix_inverse; // další strana varying vec3 v_view_direction; // výstup směr pohledu void main() { } gl_position = vec4(v_pos, 1.0); // pozice na obrazovce = pozice vec4 v_worldspace = (t_modelview_projection_matrix_inverse * vec4(v_pos, 1.0)); v_view_direction = v_worldspace.xzy * vec3(1.0, -1.0, 1.0);
Nebe na jeden trojúhelník Výpočet matice pro vertex shader Matrix modelview, projection; // vstupní matice modelview a projection scény Matrix mv_nomove = modelview; mv_nomove[3] = 0; mv_nomove[7] = 0; mv_nomove[11] = 0; mv_nomove[15] = 1; // odstraní z modelview posun (oko bude tím pádem vždy ve středu) Matrix modelview_projection_matrix_inverse = Matrix.Inverse(projection * mv_nomove); // vypočítá součin a inverzi matic
Nebe na jeden trojúhelník Fragment shader precision highp float; // specifikace přesnosti varying vec3 v_view_direction; // vstup směr pohledu const vec3 v_sky = vec4(.7 *.9,.9 *.9, 1.0, 0.0), // barva nebe v_tint = vec4(0.5, 0.5, 0.5, 0.0); // zbarvení horizontu void main() { vec3 v_dir = normalize(v_view_direction); // normalizuje směr float f_y = v_dir.y *.5 +.2; // spočítá váhu horizontu float f_tint = (1.0 - f_y) * (1.0 - f_y) * 1.3 -.3; } gl_fragcolor = v_sky + v_tint * min(f_tint,.6); // míchá barvu
Výsledek
Konec