gKit2 light
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 :

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 :

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) );
vecteur generique, utilitaire.
Definition: vec.h:146
vecteur generique 4d, ou 3d homogene, utilitaire.
Definition: vec.h:168

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 :

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 ?

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.
}