OpenGL - GLSL

Tak už máte za sebou několik kapitol o matematice, další kapitoly o různých knihovnách s OpenGL souvisejících, ale stále žádný pořádný OpenGL program. Pořád vás to neodradilo? Super. V této kapitole se dočkáte zasloužené odměny a konečně začnete programovat. Podíváme se na dvě třetiny z grafické pipeline, které jsou potřebné pro vykreslení obrazu s OpenGL. Na shadery.

O GLSL

Z grafické pipeline vás budou zajímat hlavě tyto 3 kroky: nahrání vertex dat do bufferů, vertex shader a fragment shader. S výkladem začnu od prostředka, tedy vertex a fragment shaderem.

Oba tyto kroky v pipeline se programují pomocí GLSL (OpenGL Shader Language).

Připomínám, že vertex shader má za úkol spočítat výslednou pozici vertexu (vrcholu trojúhelníka). Volá se pro každý vertex (vrchol).

Fragment shader se volá pro každý fragment, tedy pro každý čtvereček uvnitř trojůhelníka, na které je trojúhelník rozdělen. Jeho hlavním úkolem je zpočítat barvu.

Struktura jazyka GLSL

GLSL je jazyk trochu podobný céčku. Takto bude vypadat vertex shader, který použiji v prvním příkladu:

attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;

varying vec4 vColor;

void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        vColor = aVertexColor;
}

Vstupním bodem je funkce main(). V příkladu dělá jen dvě věci. Spočítá výslednou pozici vertexu a uloží ji do globální proměnné gl_Position.

Dále vezme hodnotu proměnné aVertexColor a předá jí do proměnné vColor. Tato proměnná je označena jako varying, což znamená, že se stane vstupní hodnotou pro proměnnou ve fragment shaderu stejného jména a typu.

GLSL program pro fragment shader vypadá takto jednoduše:

varying vec4 vColor;

void main(void) {
        gl_FragColor = vColor;
}
Interpolace barev

Interpolace barev

Pouze předá barvu vColor do globální proměnné gl_FragColor, která se použije jako barva fragmentu.

Do proměnné typu varying se dosadí hodnota interpolovaná z vrcholů trojúhelníka.

Na obrázku můžete vidět trojůhelník, který měl pro levý vrchol barvu vColor modrou, pro pravý vrchol zelenou a pro horní vrchol červenou. Barvy uvnitř trojúhelníku jsou interpolované.

Do fragment shaderu z vertex shaderu můžete přenášet leccos. Třeba i pozici vrcholu. Interpolovaná hodnota pozice vrcholu bude v fragment shaderu odpovídat pozici shaderu.

Datové typy v GLSL

Některé datové typy v GLSL
bool, int, uint, float, double Znáte z jazyka C. Typ uint odpovídá unsigned int.
vec2, vec3, vec4 Struktury, které obsahují atributy (x,y), (x,y,z), (x,y,z,w) typu float
ivec2, ivec3, ivec4 Struktury, které mají atributy (x,y, …) typu int
uvec2, uvec3, uvec4 Sruktury, které mají atributy typu unsigned int
mat2, mat3, mat4 Čtvercové matice (s prvky typu float)
sampler2D 2D textura.

S datovými typy je v GLSL větší sranda než v C. Vektor vec4 má 4 atributy (typu float). Jsou pojmenované x,y,z,w. Ale také r,g,b,a.

Takže např. vColor.w obsahuje totéž, co vColor.a. Můžete si vybrat jméno, které chcete. Pro barvy je vhodnější druhé označení, pro souřadnice to první.

Další legrační způsob zápisu je takový, který vám umožní atributy různě zkombinovat a vytvořit tak nový vektor. Např: vColor.xyz je proměnná typu vec3, která obsahuje první 3 atributy z vColor. Můžete zapsat i vColor.xx, což je vec2, obsahující v prvním i druhém argumentu x-ovou hodnotu z vColor. Hustý, co?

Nový vektor můžete vytvořit třeba takto: vec3 souradnice = vec3(1.0, 0.0, 1.0);.
Nebo třeba takto: vec3 jinaSouradnice = vec3(souradnice.xy, 0.0);.

Ten druhý způsob můžete vidět v příkladu vertex shaderu, kde vytvářím z proměnné aVertexPosition typu vec3 vektor typu vec4. (Protože matice jsou typu 4x4, tudíž je mohu násobit jen vektorem vec4.)

A tím se dostáváme k další zvlášnosti, a to je násobení struktur. V GLSL můžete normálně vynásobit matice, matice s vektory, vektory se scaláry atd. Cokoliv, pro co je definována matematická operace, je možné (i sčítání matic, například).

V jazyku C něco takového možné není, ale v jazyku C++ ano. Díky tzv. přetěžování operátorů můžete definovat, co je výsledkem nějaké operace (nejen) nad strukturami. V GLSL to sami definovat nemůžete, ale pro matice a vektory už to definováno je.

Datový typ sampler2D se používá pro uložení textur (obrázků). K jeho použití se vrátím v kapitole o používání textur :-).

Kvalifikátory proměnných

Proměnné mohou mít krom svého typu ještě vzv. kvalifikátory. Pro globální proměnné (definované vně jakékoliv funkce) existují tyto základní kvalifikátory:

Kvalifikátory
attribute Používá se jako vstupní proměnná ve vertex shaderu (ve fragment shaderu toto použít nemůžete). Při každém volání programu ve vertex shaderu se naplní další hodnotou v řadě (souřadnicemi vertexu, jeho barvou atp.). Jak se určí, co se tu má dosazovat, to je otázka prvního kroku v pipeline, který popíši v další kapitole. (Při vykreslení trojúhelníka se zavolá vertex shader 3x (pro každý vrchol), tudíž za attribute proměnné se 3x dosadí nějaká hodnota.)
uniform Používá se jako vstupní proměnná pro vertex nebo i fragment shader. Tato hodnota se během provádění pipeline nemění. (Dosadí se jen 1x před začátkem provádění pipeline.)
varying Ve vertex shaderu je to výstupní proměnná, která se předává do fragment shaderu. Ve fragment shadederu je to tedy vstupní proměnná. Proměnná musí existovat v obou shaderech, mít stejný datový typ a stejné jméno. Do každého fragment shaderu se dostane hodnota interpolovaná z tří vertexů, viz trojúhelník výše.

Teď už by vám měly být programy pro vertex a fragment shader jasné.

Ve vertex shaderu si nejdříve definuji vstupní proměnné pro souřadnice a barvy vrcholu (které jsou pro každý vertex jiné). Pak si definuji proměnnou pro matici kombinující modelu a pohled (model a view) uMVMatrix a proměnnou matici perspektivy uPMatrix. Tyto matice jsou stejné pro každý vrchol, proto jsou uniform.

Pak vypočítám transformovanou polohu vertexu jako násobek uMVMatrix, uPMatrix a původních suřadnic vertexu (aVertexPosition). Výsledek uložím do globální proměnné gl_Position, ve které OpenGL výsledek očekává.

Dále ještě předám barvu vrcholu do fragment shaderu skrze proměnou vColor. Fragment shader jen vezme interpolovanou barvu ze tří vertexů a uloží ji do globální proměnné gl_FragColor, která určuje výslednou barvu fragmentu.

Tak to alespoň funguje ve starší verzích OpenGL, nebo např. v OpenGL ES, na kterém je postaveno WebGL. V nové verzi OpenGL se věci trochu změnili. Podívejte se na modernější verzi vertex a fragment shaderů:

#version 330 core
layout (location = 0) in vec3 aVertexPosition;
layout (location = 1) in vec4 aVertexColor;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;

out vec4 vColor;

void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        vColor = aVertexColor;
}

Na prvním řádku je makro, které říká, o jakou verzí jazyka GLSL. (V nových verzích toto číslo odpovídá verzi OpenGL.)

Místo attribute se používá kvalifikátor in. Pomocí layout (location = 0) explicitně určuji, pod jakým číslem (identifikátorem, ID) budu k proměnné přistupovat v C programu. Funkce OpenGL pracující s proměnnými z GLSL očekávají jako jeden z parametrů toto ID. To se týká první fáze pipeline (nahrávání vertex dat) a budu se tomu věnovat v další kapitole.

Kvalifikátor uniform zůstává uniform. Místo varying se pro výstupní proměnnou používá out. V fragment shaderu musí existovat proměnná stejného jména a stejného datového typu s kvalifikátorem in.

#version 330 core
in vec4 vColor;

out vec4 fragColor;

void main(void) {
        fragColor = vColor;
}

V fragment shaderu je ještě jedna změna. Jako výstup se místo globální proměnné gl_FragColor používá libovolná proměnná typu vec4 označená kvalifikátorem out. Resp. jde o proměnnou libovolného jména, která je typu vec4 a má kvalifikátor out. Globální proměnná gl_FragColor je deprecated.

Podmínky a cykly

V GLSL můžete používat podmínky a cykly pro řízení programu, jak to znáte z jazyka C. Používat byste je však měli co nejméně, protože jsou to zabijácí výkonu.

Řízení programu
Iterace for(;;;) {break, continue}
while() {break, continue}
do {break, continue} while();
Výběr if() else {}
switch() { case integer: ... break; ... default:}
Ukončení return
discard // Ukončení fragment shaderu

Funkce

GLSL obsahuje několik užitečných funkcí. V tabulce jsou vypsány ty nejužitečnější z nejužitečnějších :-). Typy argumentů a návratových hodnot jsou většinou float, vecX, nebo matX. Nebudu je v tabulce specifikovat (vyjma funkce texture()), myslím, že si je snadno domyslíte.

Řízení programu
radians(s), degrees(r) Funkce pro převod stupně na radiány a opačně.
sin(a), cos(a),tan(a),asin(a),acos(a),atan(y,x) goniometrické funkce
pow(x,y) xy
sqrt(x) odmocnina
abs(x) Absolutní hodnota
sign(x) vrací -1.0, 0.0, 1.0, dle znamánka x
clamp(x, minVal, maxVal) min(max(x,minVal), maxVal) (x se ořízne mezi minVal a maxVal)
length(v) Délka vektoru
distance(v1,v2) Vzdálenost dvou bodů
dot(v1,v2) Dot product
cross(v1,v2) Cross product
normalize(v) Normalizace vektoru
reflect(v,n) Spočte směr odrazu
transpose(m) Transponuje matici
inverse(m) Inverzní matice
texture(sampler2d t, vec2 coord) Vrátí barvu textury t na pozici coord.
mix(v1,v2,x) smíchá hodnoty v1 a v2 v poměru udaném číslem x, kde x je mezi 0.0 a 1.0.
(Pokud je x = 0, výsledkem je v1, pokud je x = 1.0, výsledkem je v2.)

Díky funkci sign() se můžete vyvarovat použití pomalého if.

Operátory

Operátory jsou v GLSL podobné, jako v C. Máte tu operátor přiřazení =, +=, *= …, operátory relační ==,!=,<,>,<=,>=, aritmetické operátory +,-,*,/,%, binární operátory, unární operátory …

Závěr

Toto byl stručný popis GLSL. Pokud je na vás moc stručný, můžete si na stránce www.khronos.org/developers/reference-cards stáhnout free PDF Reference Guide pro OpenGL. Jedná se o stručný přehled funkcí OpenGL i jazyka GLSL. Najdete tam všechny funkce, operátory, datové typy, globální proměnné atd. (Těch je mimochodem opravdu málo. Modře označené jsou navíc deprecated, takže byste je neměli používat).

Teď už umíte napsat program pro vertex a fragment shader. Příští kapitola se ještě nebude zabývat první fází s pipeline – jak připravit data pro in a uniform proměnné, ale a tím, jak o vytvořit OpenGL program ze zdrojových souborů pro vertex a fragment shader.

Komentář Hlášení chyby
Created: 24.12.2016
Last updated: 14.12.2017
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..