Tutoriel 5 - Variables uniformes

Ogldev

Traducteur

Sylvain DOREMUS

Dans ce tutoriel, vous allez apprendre à envoyer des informations à votre GPU à travers les variables uniformes.

Commentez

- Titre : Tutoriel 5 - Variables uniformes

- Auteur : Ogldev

- Traducteur : Sylvain DOREMUS

- Parution : 15 mai 2014

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 ogldev. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Navigation

- II. Contexte

- III. Explication du code

- IV. Sources

- V. Remerciements

- Navigation

Navigation

Tutoriel précédent : shaders

Sommaire

Tutoriel suivant : les transformations : translation

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

III - 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é.

ScaleLocation = 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

IV - Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources

V - Remerciements

Merci à Etay Meiri de nous permettre de traduire son tutoriel.

Merci à LittleWhite pour ses corrections et à Phanloga pour sa relecture.

Navigation

Tutoriel précédent : shaders

Sommaire

Tutoriel suivant : les transformations : translation