Introduction
Dans ce tutoriel, vous allez apprendre à envoyer des informations à votre GPU à travers les variables uniformes.
Contexte
Dans ce tutoriel, nous allons rencontrer un nouveau type de variables pour les shaders : les variables uniformes. La différence entre les attributs de sommets et les variables uniformes est que les attributs de sommets contiennent des données qui sont spécifiques aux sommets et sont donc rechargées avec une nouvelle valeur depuis le tampon de sommets à chaque appel du shader.
Au contraire, la valeur des variables uniformes reste constante durant toute la fonction de dessin. Cela signifie que vous chargez la valeur avant d’appeler la fonction de dessin et que vous pouvez accéder à la même valeur à chaque appel du vertex shader.
Les variables uniformes sont utiles pour stocker des données telles que les paramètres de lumière (position, direction…), les matrices de transformation, les identificateurs des objets de texture et ainsi de suite.
Dans ce tutoriel, nous allons enfin avoir quelque chose qui bouge sur l’écran. Nous allons faire cela en utilisant une combinaison de variables uniformes dont nous changerons la valeur à chaque image via le callback d’inactivité fourni par GLUT.
En fait, GLUT n’appelle pas notre callback de rendu de manière répétée, à moins qu’il ne le doive. GLUT doit appeler le callback de rendu à la suite d’événements tels que la minimisation ou maximisation de la fenêtre ou son découvrement par une autre fenêtre.
Si nous ne changeons rien dans l’agencement des fenêtres après le lancement de l’application, le callback de rendu n’est appelé qu’une seule fois. Vous allez voir la sortie une seule fois et vous la verrez ensuite encore si vous minimisez puis maximisez la fenêtre.
Il était suffisant d’enregistrer uniquement le callback de rendu dans GLUT dans les tutoriels précédents mais là, nous souhaitons changer de manière répétée la valeur d’une variable. Nous le faisons en enregistrant un callback d’inactivité. La fonction d’inactivité est appelée par GLUT quand aucun événement venant du système de fenêtrage n’est reçu. Vous pouvez avoir une fonction dédiée pour ce callback, avec laquelle vous pouvez faire n’importe quelle opération de comptage telle que la mise à jour du temps, ou simplement enregistrer votre fonction de rendu en tant que callback d’inactivité.
Dans ce tutoriel nous appliquerons ce dernier choix et mettrons à jour la variable dans la fonction de rendu.
Explication du code
glutIdleFunc(RenderSceneCB);
Ici, nous enregistrons notre fonction de rendu en tant que callback d’inactivité. Notez que si vous choisissez d’utiliser une fonction d’inactivité spécifique, vous devrez appeler glutPostRedisplay
à la fin de celle-ci sinon le callback d’inactivité sera appelé encore et encore mais pas la fonction de rendu. glutPostRedisplay
marque la fenêtre actuelle comme devant être redessinée et pendant le prochain tour de la boucle principale de GLUT le callback de rendu sera appelé.
gScaleLocation = glGetUniformLocation(ShaderProgram, "gScale"); assert(gScaleLocation != 0xFFFFFFFF);
Après avoir lié le programme, nous lui demandons l’emplacement de la variable uniforme.
C’est un autre exemple du cas où l’environnement d’exécution de l’application C/C++ a besoin d’être associé à l’environnement d’exécution du shader. Vous ne pouvez pas accéder directement au contenu du shader et ne pouvez donc pas mettre directement ses variables à jour.
Quand vous compilez le shader, le compilateur GLSL affecte un indice à chacune des variables uniformes. Dans la représentation interne du shader dans le compilateur, l’accès à la variable est possible grâce à cet indice. Cet indice est récupérable par l’application via glGetUniformLocation
.
Vous appelez cette fonction avec l’identificateur du programme et le nom de la variable. Cette fonction retourne l’indice ou -1 s’il y a eu une erreur.
Il est très important de vérifier les erreurs (comme nous le faisons ci-dessus avec l’assertion) ou les futures mises à jour de la variable ne seront pas transmises au shader. Il y a principalement deux raisons pour lesquelles cette fonction peut échouer :
- vous pouvez avoir mal écrit le nom de la variable ;
- elle a été supprimée suite à une optimisation du compilateur.
Si le compilateur GLSL voit qu’une variable n’est pas utilisée dans le shader, elle est tout simplement supprimée. Dans ce cas glGetUniformLocation
échouera.
static float Scale = 0.0f; Scale += 0.001f; glUniform1f(gScaleLocation, sinf(Scale));
Nous créons une variable flottante statique que nous incrémentons un peu à chaque appel de la fonction de rendu (vous pouvez jouer sur 0.001 si ça va trop lentement ou trop vite sur votre machine).
La valeur qui est réellement passée au shader est le sinus de la variable « Scale », ceci afin de créer une jolie oscillation entre -1.0 et 1.0.
Notez que sinf()
prend des radians et non des degrés en paramètre mais à ce stade ça n’a tout simplement pas d’importance. Nous voulons juste la sinusoïde générée par la fonction.
Le résultat de sinf()
est passé au shader en utilisant glUniform1f
.
OpenGL fournit de multiples instances de cette fonction avec la forme générale glUniform{1234}{if}
. Vous pouvez les utiliser pour charger des valeurs dans des vecteurs 1D, 2D, 3D ou 4D (selon le nombre qui suit glUniform
) de flottants ou d’entiers (suffixe « f » ou « i »).
Il y a aussi des versions qui prennent l’adresse d’un vecteur en paramètre, ainsi que des versions spéciales pour les matrices.
Le premier paramètre de cette fonction est l’indice de l’emplacement que nous avons récupéré en utilisant glGetUniformLocation
.
Nous allons maintenant regarder les changements qui ont été faits dans le vertex shader (le pixel shader reste inchangé).
uniform float gScale;
Ici nous déclarons la variable uniforme dans le shader.
gl_Position = vec4(gScale * Position.x, gScale * Position.y, Position.z, 1.0);
Nous multiplions les valeurs X et Y de la position du vecteur avec la valeur qui est changée par l’application à chaque image. Pouvez-vous expliquer pourquoi le triangle est retourné au milieu de la boucle ?
Résultat
Remerciements
Merci à Etay Meiri de me permettre de traduire ses tutoriels.