gKit2 light

- Main Page
- Related Pages
- Modules
- Classes
- Files

shaders et GLSL

le pipeline a besoin d'un vertex shader et d'un fragment shader pour fonctionner, repassez dans introduction api 3d, openGL et pipeline graphique si nécessaire, et dans compiler et linker un shader program pour savoir comment les compiler, et afficher plusieurs triangles, modifier les paramètres uniform d'un shader program pour afficher simplement quelques triangles (un cube, par exemple).

## présentation GLSL

GLSL est le langage permettant d'écrire des shaders, il est très proche du C/C++, avec quelques limitations :

- pas de pointeurs,
- pas de fonctions récursives,
- des tableaux, mais une dimension,
- des structs, mais pas d'heritage,
- pas de passage de valeur par référence, mais par copie.

il existe plusieurs versions du langage, il faut préciser laquelle avec la directive `#version xxx`. la version classique est la version 330 / openGL 3.3. la plupart des shaders commencent donc par la ligne `#version 330`.

les types de base sont classiques : int, uint, bool, float, mais les vecteurs et les matrices sont définies ainsi que leurs opérations :

- vec2, vec3, vec4 : pour des vecteurs à 2, 3, 4 composantes float,
- ivec2, ivec3, ivec4 : pour des vecteurs d'entiers,
- uvec2, uvec3, uvec4 : pour des vecteurs d'entiers non signés,
- bvec2, bvec3, bvec4 : pour des vecteurs de bool,
- mat2, mat3, mat4 : pour les matrices 2x2, 3x3 et 4x4.

la librairie standard contient à peu près tout ce qui est nécessaire pour des calculs graphiques, consultez la doc officielle : openGL SDK, par exemple les produits de matrices, les produits matrices / vecteurs...

l'initialisation des vecteurs, matrices et vecteurs n'utilise pas toujours la même syntaxe que le C++, par exemple :

vec3 point;
point= vec3(1, 1, 1);
vec3 extremite= vec3(1, 0, 0); // vec3 extremite(1, 0, 0); n'existe pas
vec3 direction= extremite - point;
mat4 m= mat4( vec4(....), vec4(...), vec4(...), vec4(...) );
vec3 positions[3]= vec3[3]( vec3(-0.5, -0.5, 0), vec3(0.5, -0.5, 0), vec3(0, 0.5, 0) );

la déclaration des structures est sans surprise :

struct Sphere
{
vec3 centre;
float rayon;
};
Sphere scene[4];

les opérateurs sur les vecteurs et les matrices fonctionnent, là encore, sans surprises :

vec4 point= vec4( ... );
mat4 m= mat4( ... );
vec4 r= m * point;
vec4 q= r + vec4( ... );
vec4 s= r + point;

l'opérateur de sélection de composantes sur les vecteurs est plutôt pratique :

vec4 a= vec4( ... );
vec3 b= a.xyz;
vec4 c= a.zwxx;
vec3 p= vec3( ... );
vec3 q.xzy= p.xzy; // quelle valeur est affectee a q ?

les constructeurs acceptent aussi d'extraire les premières composantes, pour les vecteurs mais aussi pour les matrices :

vec4 p= vec4( ... );
vec3 q= vec3(p); // ou q= p.xyz;
mat4 m= mat4( ... );
mat3 t= mat3(m); // t[0]= vec3(m[0]); t[1]= vec3(m[1]); t[2]= vec3(m[2]);

et ça fonctionne aussi dans l'autre sens, on peut construire un vecteur à partir d'un autre vecteur plus court, plus d'autres valeurs :

#version 330
vec3 position= vec3( ... );
mat4 mvpMatrix= mat4( ... );
gl_Position= mvpMatrix * vec4(position, 1); // position 3 floats + un float == 4 floats

### fonctions et passage par copie

les fonctions (non récursives) s'écrivent comme d'habitude, la seule différence notable est le passage des paramètres qui utilise des mot clés `in`, `out`, `inout` pour indiquer les paramètres d'entrée, de sortie et d'entrée / sortie :

void f1( in float a, out float b )
{
b= a + 10;
}
void f2( in a, inout b )
{
b= a + 2*b;
}

il est également possible d'utiliser `const` sur les parametres des fonctions :

void f1( const in float a, out float b )
{
b= a + 10;
}
void f2( const in a, inout b )
{
b= a + 2*b;
}

## vertex shader et GLSL

un vertex shader est composée d'une fonction principale, `void main( void )`, et doit écrire les coordonnées des sommets dans une variable globale `vec4 gl_Position` (rappel : coordonnées x, y, z et w dans le repère projectif).

les attributs se déclarent en global avec le mot clé `in`, les varyings/sorties avec `out`, les constantes initialisées avec `const`.

les variables fournies par l'application sont déclarées avec le mot-clé `uniform` en plus de leur type et de leur nom.

le vertex shader à également accès à des variables définies par le pipeline, elles sont toutes prefixées par `gl_` (cf openGL SDK, section glsl, lettre g), par exemple : gl_VertexID, l'indice du sommet à traiter.

exemple :

// vertex shader de tuto3GL
#version 330
uniform vec3 positions[36]; // declare un uniform, un tableau de vec3
uniform float time; // declare un uniform, time de type float
const vec3 deplace= vec3(...); // declare une constante
void main( )
{
gl_Position= vec4( positions[gl_VertexID] + deplace * time / 1000.0, 1.0 );
// positions[gl_VertexID] est un vec3 + vec3 * float / float, ce qui donne bien un vec3
// et le vec3 est complete par une valeur pour etre affecte a un vec4
}

le vertex shader déclare, en général, des attributs de sommets initialisés par le pipeline (l'application devra configurer un vertex array et créer des buffers, cf configurer un format de sommet, vertex array object) :

// vertex shader et attributs
#version 330
in vec3 position; // declare un attribut, position de type vec3
uniform mat4 mvpMatrix; // declare un uniform, mvpMatrix de type mat4
void main( void )
{
gl_Position= mvpMatrix * vec4(position, 1);
}

## fragment shader et GLSL

le fragment shader est similaire au vertex shader, mais il ne peut pas utiliser les valeurs des attributs, ils ne sont plus accessibles, uniquement leurs valeurs interpolées, les varyings (repassez dans introduction api 3d, openGL et pipeline graphique et pipeline graphique openGL, si ce n'est pas clair)

la couleur du fragment doit être écrite dans un `out vec4 gl_FragColor` ou une variable en sortie déclarée par le shader `out vec4 fragment_color;` selon la version d'openGL.

un fragment shader peut déclarer des uniforms et des varyings, et accéder à plusieurs variables définies par le pipeline, par exemple :

- `vec4 gl_FragCoord`, les coordonnées dans le repère image du fragment,
- `int gl_PrimitiveID`, l'indice de la primitive, du triangle, traité par le shader,
- `bool gl_FrontFacing`, l'orientation de la primitive traitée par le shader, toujours vrai si les faces arrières sont éliminées avant la fragmentation (cf introduction api 3d, openGL et pipeline graphique).

exemple:

// fragment shader et variables
#version 330
uniform vec3 back_color; // uniform, couleur pour les faces mal orientees
out vec4 fragment_color; // varying, couleur du fragment, ecrite dans l'image par le pipeline
void main( void )
{
vec3 color;
// utiliser l'indice du triangle, gl_PrimitiveID, pour fabriquer une couleur "aleatoire"
color= vec3(1.0 - float(gl_PrimitiveID % 100) / 99.0, float(gl_PrimitiveID % 10) / 9.0, float(gl_PrimitiveID % 1000) / 999.0);
// glsl n'autorise pas les casts avec la syntaxe du C,
// donc utiliser la notation constructeur comme en C++
// si le triangle est une face arriere, l'afficher d'une couleur differente, cf parametre uniform, initialise par l'application
if(gl_FrontFacing == false)
color= back_color;
fragment_color= vec3(color, 1); // couleur opaque, alpha= 1
}

## uniforms, varyings et variables

à quoi servent les différents types de variables ?

- les `uniform` correspondent à des valeurs fournies par l'application, elles ne changent pas de valeur pendant l'exécution des shaders, cf compiler et linker un shader program,
- les `in` sont les entrées du shader :
  - pour un vertex shader, ils représentent les attributs du sommet, c'est le pipeline qui affecte les valeurs en fonction du vertex array object sélectionné par l'application au moment du draw( ). cf configurer un format de sommet, vertex array object
  - pour les autres shaders, les fragment shaders, par exemple, ce sont des varyings, des entrées qui doivent correspondre à des sorties déclarées par les shaders précédents dans le pipeline. ces variables n'existent que pendant l'exécution du pipeline et ne sont pas accessibles par l'application.
- les `out` sont les sorties du shader.

exemple : si chaque sommet est décrit par une position et une couleur, les entrées du vertex shader sont 2 attributs, déclarés avec `in` et configurés par l'application.

#version 330
// vertex shader
in vec3 position; // attribut
in vec4 color; // attribut
void main( )
{
gl_Position= ... ; // obligatoire, affecter une position
}

si on veut que le fragment shader utilise la couleur des sommets pour calculer sa couleur, il faut les lui transmettre explicitement, il ne peut pas y accéder tout seul. dans ce cas, la couleur est un varying, déclarée comme sortie optionnelle du vertex shader (avec `out`) et comme entrée (avec `in`) du fragment shader.

#version 330
// vertex shader
in vec3 position;
in vec4 color;
out vec4 vertex_color; // sortie, varying
void main( )
{
vertex_color= color;
gl_Position= ... ;
}
#version 330
// fragment shader
in vec4 vertex_color; // entree, varying, meme type et meme nom que dans le vertex shader
out vec4 fragment_color; // varying, couleur du fragment, ecrite dans l'image par le pipeline
void main( )
{
fragment_color= vertex_color;
// le fragment shader doit ecrire une couleur dans la variable declaree en sortie, cf out vec4 fragment_color.
}