9/17
4.1. Principe
L’objectif de cet exemple est d’animer le triangle, ou plus précisément, de le faire tourner à l’aide d’une rotation.
Approche générale
Animer un objet consiste à afficher, à chaque itération de la boucle d’animation, un objet légèrement différent.
Une approche naïve pour appliquer une rotation à un objet consiste à calculer de nouvelles coordonnées sur le CPU à chaque boucle d’itération, puis de renvoyer celles-ci sur le GPU. Cette approche est possible, mais se révèle couteuse en temps de calcul dû à l’utilisation continue de la bande passante entre la mémoire du CPU et celle du GPU, en particulier pour un nombre important de sommets.
Une approche classique consiste à toujours utiliser les mêmes données d’entrée, mais à appliquer, directement dans le shader (dans le GPU) une rotation différente à chaque itération de la boucle d’animation. La transformation de l’objet est alors réalisée directement sur le GPU de manière efficace en parallèle sur chaque sommet, et sans utilisation de bande passante entre CPU et GPU.
Nous avons vu précédemment comment envoyer des données définies par sommets. Dans le cas d’une transformation, nous souhaitons envoyer un même paramètre à appliquer sur chaque sommet: ici une matrice de rotation. Cette matrice sera la même pour l’ensemble des sommets lors de l’affichage, mais sera mise à jour à chaque itération de la boucle d’animation. Pour cela, il est possible d’utiliser des paramètres uniform aux shaders.
Les paramètres uniforms peuvent être vus comme des variables que le code exécuté sur CPU peut passer aux shaders de la carte graphique. Les paramètres uniforms sont constant pour l’ensemble des sommets d’une forme, mais peuvent être modifiés par le programme C++ entre deux appels d’affichages.
Les paramètres uniforms sont passés aux shaders par les fonctions glUniformX, où X désigne le type de paramètre.
Application à la rotation
Remarque préalable: Cette partie décrit le principe du passage de paramètre, vous n’avez pas nécessairement besoin de l’écrire vous même (vous le pouvez si vous le souhaitez). Le code implémentant cette rotation est fourni en partie suivante.
Dans le cas d’une rotation, il est possible de passer la matrice suivante au vertex shader.
\[ R = \left( \begin{array}{rrrr} \cos(\theta) & \sin(\theta) & 0 & 0 \\ -\sin(\theta) & \cos(\theta) & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ \end{array} \right) \mbox{ ,} \] où \(\theta\) is incrémenté à chaque itération de la boucle d’animation.
Les changements à appliquer au code sont les suivants.
Shader
Premièrement, le shader doit recevoir et utiliser le paramètre uniform. Dans notre cas, nous ajoutons la déclaration d’une variable uniform de type mat4 appelé R, et appliquons cette matrice à la position du sommet.
#version 330 core
layout (location = 0) in vec4 position;
uniform mat4 R;
void main()
{
gl_Position = R*position;
}
Envoie d’uniforms
La matrice de transformation peut être calculée dans le programme principal. Dans notre cas, il est possible d’utiliser un un std::array de 16 valeurs (n’oubliez pas l’include correspondant) [1]
int counter = 0; // incrementing this parameter at each iteration
while( !glfwWindowShouldClose(window) ) {
// Compute a slowly increasing variable
const float t = counter/100.0f;
// Compute the current rotation matrix
const std::array<GLfloat,16> Rotation = {
std::cos(t),-std::sin(t),0,0,
std::sin(t), std::cos(t),0,0,
0, 0,1,0,
0, 0,0,1
};
// ...
}
Il est ensuite nécessaire d’indiquer la correspondance entre la variable Rotation du programme principal et la variable uniform R attendue dans le shader.
-
Premièrement, le paramètre cible doit être localisé dans le shader. Cette localisation est réalisé par le nom de la variable (ici R). Pour cela, on utilise la fonction glGetUniformLocation qui renvoie sous la forme d’un entier la localisation dans le shader.
const GLint R_loc = glGetUniformLocation(shader, "R");
-
Avant la demande d’affichage (et après l’activation du shader), le paramètre uniform doit être envoyé sur le GPU, ici par la fonction glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value).
-
location: Localisation de la variable uniform renvoyée par la fonction glGetUniformLocation
-
count: Nombre de paramètre uniforms à envoyer par cet appel. Dans notre cas un seul.
-
transpose: Indique si la matrice doit être transposée une fois passée au shader. GLSL utilise une représentation par colonne, qui est la version transposée de la représentation implicitement utilisée lors de la définition de la matrice dans le programme principal. Nous plaçons ainsi ce paramètre à true.
-
value: Adresse du premier élément des données (rem. la matrice doit nécessairement être stockée sous la forme d’une suite de valeurs contigües en mémoire).
-
glUniformMatrix4fv(R_loc, 1, GL_TRUE, &Rotation[0]); // Send rotation matrix as uniform parameter
Remarque:
-
Lors d’une animation, il est nécessaire d’effacer l’écran entre chaque affichage (rappel: glClear) sinon les images vont s’afficher les unes sur les autres.
-
La valeur d’un paramètre uniforme reste le même jusqu’au moment où l’on réécrit dessus. Un changement de shader nécessite également la remise à jour des paramètres uniforms.