Filtres d'écran en 2D avec OpenGL dans le Blender Game Engine

| SolarLune | gbdivers |

Traducteur

Guillaume Belz

Blender est à l'origine un logiciel libre de dessin 3D, mais propose de plus en plus de fonctionnalités avancées d'animation. En particulier, Blender intègre un moteur de jeux appelé Blender Game Engine (BGE), qui permet aux utilisateurs d'écrire leurs propres shaders en utilisant les langages Python et OpenGL Shading Language (GLSL).
Dans cet article, l'auteur présente les bases pour écrire ses propres shaders et les paramétrer dans Blender à partir d'un exemple simple : un filtre de flou.

N'hésitez pas à commenter cet article ! Commentez

- Titre : Filtres d'écran en 2D avec OpenGL dans le Blender Game Engine

- Auteurs : SolarLune, gbdivers

- Traducteur : Guillaume Belz

- Parution : 21 juin 2012

- Licence :

Sommaire

- I. Partie I

- II. Partie 2

- III. Partie 3

- IV. Remerciements

I - Partie I

Pour commencer ce tutoriel, nous allons regarder quelques fonctions OpenGL Shading Language (GLSL) simples pour faire des filtres d'écran en 2D, avec le moteur de jeu de Blender. Théoriquement, on pourrait aussi l'implémenter avec d'autres moteurs de jeux, même ceux utilisant des langages de shaders différents, tel que le Microsoft High Level Shading Language (HLSL).

Le principe du filtre écran en 2D est très simple. L'idée fondamentale est que chaque pixel à l'écran exécute votre script, dans lequel vous déterminez la couleur du pixel. Vous effectuez des calculs mathématiques sur la couleur du pixel et vous pouvez même passer des variables externes, comme un minuteur ou un pourcentage.

Étonnement, cela est simple. Aujourd'hui, nous allons faire un filtre d'écran en 2D qui permutera quelques couleurs, changeant le rouge en vert, le vert en bleu et le bleu en rouge. Le seul problème qui peut se poser est l'utilisation du C++ et GLSL pour le filtre, même si nous communiquons avec le Blender Game Engine (BGE) en utilisant Python. Toutefois, le code n'est pas très complexe et si vous avez déjà vu le C++, même un tout petit peu, il n'y a probablement rien de très surprenant ici. Donc, nous allons jeter un œil au code source :

from bge import logic

cont = logic.getCurrentController()
obj = cont.owner

filter = cont.actuators['Filter 2D'] # Récupérer l'actionneur pour le filtre 2D

# Notez l'utilisation du Python pour le BGE utilise Python, le filtre 2D (ou les shaders 3D) est
# écrit en C++ et utilise le GLSL. Ainsi, la syntaxe et le style de notre script sont écrits en Python
# jusqu'à ce que l'on commence à écrire le code du filtre en dessous, dans lequel on passe au C++
# (d'où les points-virgules et les commentaires commençants par //).

filter.shaderText = """
uniform sampler2D bgl_RenderedTexture;

void main(void)
{
vec4 color = texture2D(bgl_RenderedTexture, gl_TexCoord[0].st);
// Cette ligne récupère la couleur de la texture que le BGE utilise suivant les coordonnées passées
// on utilise la coordonnée de texture de chaque pixel dans la fenêtre d'affichage avec
// gl_TexCoord[0]. Je crois que gl_TexCoord[1] contient les coordonnées de texture de
// chaque pixel (dans la fenêtre d'affichage) après avoir été adapté à la taille de la
// fenêtre.

gl_FragColor = color.gbra;
// Cette ligne définit la couleur finale de chaque pixel (gl_FragColor) comme étant le vecteur
// de couleur, dans lequel r est le canal rouge, b est le bleu, g est le vert et a est le canal
// alpha. En les permutant circulairement, on peut manipuler la couleur finale de l'écran afin
// que le rouge devienne  vert, le bleu soit remplacé par le vert et le vert à la place du rouge.
}

"""

if not 'init' in obj:
cont.activate(filter)
obj['init'] = 1
# Le BGE gardera un filtre 2D actif tant que l'actionneur 2D filtre est actif ; notez
# qu'on ne fait cela que cette fois puisque l'activation du filtre 2D prend un peu de temps, il est
# donc beaucoup plus efficace de l'activer qu'une seule fois plutôt que de le faire constamment.

Rien n'est particulièrement exceptionnel, sauf pour le script du shader lui-même. On récupère seulement l'actionneur pour le filtre 2D connecté au module Python qui exécute ce script. Puis on définit celui-ci, l'active et l'exécute. Simple. Donc, le vrai souci est de comprendre le script.

uniform sampler2D bgl_RenderedTexture;

void main(void)
{
vec4 color = texture2D(bgl_RenderedTexture, gl_TexCoord[0].st);

gl_FragColor = color.gbra;
}

La première ligne récupère la texture de rendu que le BGE a créé et que l'on pourra manipuler. C'est ce que l'on appelle un sampler2D, qui est fondamentalement une image 2D. La ligne void main(void) commence simplement la partie principale du script du shader.

La première ligne crée un vecteur à quatre composantes qui contient la couleur d'origine de l'écran. La ligne suivante est aussi la dernière : gl_FragColor = color.gbra. Cette ligne affecte simplement la couleur finale du pixel avec la couleur contenue dans la variable du vecteur de couleur.

Remarquez cependant qu'il affecte la couleur finale avec les canaux habituels en décalé. Normalement, la couleur d'un pixel est dans le format RGBA, où R est rouge, G est vert, B est bleu et A est le canal alpha. Cependant, on réarrange les valeurs de couleur en déplaçant simplement les lettres qui correspondent à chaque couleur. Au final, le vert est devenu rouge, le bleu est maintenant vert et le rouge est maintenant vert. C'est tout pour le code du shader et nous nous retrouvons avec une palette de couleurs qui ont été échangées, juste comme ça ! Télécharger la source dans le fichier blend ici.

Le tutoriel suivant implémentera un filtre 2D d'écran plus complexe. Continuez a venir et je continuerai à faire plus de tutoriels. Amusez-vous !

II - Partie 2

Dans cette seconde partie du tutoriel, nous allons jeter un coup d'œil à un autre filtre 2D un peu plus complexe : le filtre classique de flou (youpi !). Un filtre de flou est un filtre très utilisé et certains jeux l'utilisent pour flouter l'écran quelque temps lorsque l'on prend des dégâts. Il est également utilisé de façon radiale (ce qui rend très bien ; ça sera peut-être abordé plus tard dans cette série d'articles). Nous allons jeter un coup d'œil sur le code du filtre simple de flou ci-dessous.

filter.shaderText = """

uniform sampler2D bgl_RenderedTexture;

void main(void)
{
float value = 0.0015;
// Ici, value = la distance, pour vérifier la couleur des pixels

vec4 color = texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x + value, gl_TexCoord[0].st.y + value));
// Zone d'échantillonnage autour du pixel courant
color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x - value, gl_TexCoord[0].st.y - value));
color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x + value, gl_TexCoord[0].st.y - value));
color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x - value, gl_TexCoord[0].st.y + value));

color /= 4.0;
// Calcul de la moyenne de la couleur finale en divisant par le nombre d'échantillons ; cela pourrait être fait
// dans une boucle for, mais apparemment, certaines cartes graphiques (comme la mienne) ne les supportent pas

gl_FragColor = color;

}
"""

Comme il n'y a pas trop de différences entre le fichier source blend et la version du tutoriel précédente, je n'ai indiqué que le code du shader (le bloc de texte qui suit le code permettant de récupérer la propriété shaderText de l'actionneur du filtre). Vous pouvez facilement changer de scripts de shader en faisant des copier-coller des différents scripts dans la propriété shaderText de l'actionneur du filtre 2D ou même en attribuant les shaders à des variables et en appliquant ensuite l'actionneur avec le texte dont vous avez besoin.

Bon, comme vous pouvez le voir, il n'y a pas beaucoup de nouveau texte au début du script. Au début, nous créons notre texture de rendu de l'écran. Il s'agit d'une simple texture 2D qui sera normalement dessinée à l'écran, sauf que nous modifions la sortie de cette texture. Nous commençons ensuite la fonction principale dans laquelle se trouve la substance de ce script :

float value = 0.0015;
// Ici, value = la distance, pour vérifier la couleur des pixels

Dans ce bloc ci-dessus, nous créons une simple variable que l'on peut facilement utiliser pour personnaliser l'importance de l'effet de flou. Dans la ligne suivante, nous créons une nouvelle variable, la couleur. Il s'agit d'un vectoriel à quatre composantes (rouge, vert, bleu et alpha), comme indiqué dans la première partie de ce tutoriel. Dans la ligne suivante, nous voyons?

vec4 color = texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x + value, gl_TexCoord[0].st.y + value));
// Zone d'échantillonnage autour du pixel courant
color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x - value, gl_TexCoord[0].st.y - value));
color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x + value, gl_TexCoord[0].st.y - value));
color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x - value, gl_TexCoord[0].st.y + value));

Le second argument de la fonction Texture2D permet de préciser la position sur laquelle récupérer la couleur. Cet argument est la coordonnée dans la texture du pixel. Nous la modifions avec value (une distance) que nous avons spécifié avant (en pourcentage, non en pixels).

Figure 1 : Différence entre la version avec et sans flou

Nous recommençons à plusieurs reprises pour flouter dans toutes les directions, puis effectuons une moyenne grâce à la division par le nombre d'échantillons utilisés pour l'effet. Pour terminer, nous mettons le résultat final dans la variable gl_FragColor, correspondant au pixel flouté des pixels environnants. L'effet devrait beaucoup ressembler à celui présenté sur la partie gauche de l'image.

Eh bien, c'est tout ce qu'il faut pour faire un filtre en 2D de flou pour le BGE en utilisant le GLSL. Noter que même si le code est spécifique au BGE, le principe sous-jacent devrait se convertir facilement en d'autres langages de script.

Cliquez ici pour télécharger le filtre en 2D de flou. Amusez-vous bien !

III - Partie 3

Pour terminer ce tutoriel, nous allons nous pencher sur quelque chose de nouveau : un filtre de flou ! Quoi ? Nous l'avons déjà fait ! Oh? Bien? Et si nous le contrôlions à l'aide d'une variable ?

Alors? Ouais. C'est le nouveau filtre que nous allons faire, un filtre de flou contrôlé par une variable. C'est en fait assez simple. En fait, le script est essentiellement le même que le script de flou, la seule différence est que nous réalisons l'interpolation entre la texture floutée et la texture non floutée avec une variable. Voici le script?

filter.shaderText = """

uniform sampler2D bgl_RenderedTexture;
uniform float percent;

void main(void)
{
float value = 0.0015; // Ici, value = distance à laquelle on vérifie la couleur des pixels.

vec4 color = texture2D(bgl_RenderedTexture,
vec2(gl_TexCoord[0].st.x + value, gl_TexCoord[0].st.y + value));
// Zone d'échantillonnage autour du pixel courant

color += texture2D(bgl_RenderedTexture,
vec2(gl_TexCoord[0].st.x - value, gl_TexCoord[0].st.y - value));
color += texture2D(bgl_RenderedTexture,
vec2(gl_TexCoord[0].st.x + value, gl_TexCoord[0].st.y - value));
color += texture2D(bgl_RenderedTexture,
vec2(gl_TexCoord[0].st.x - value, gl_TexCoord[0].st.y + value));

color /= 4.0;   // Calcul de la moyenne pour la couleur finale en divisant par le nombre d'échantillons.
// Cela pourrait être fait dans une boucle for.

vec4 origcolor = texture2D(bgl_RenderedTexture, gl_TexCoord[0].st);
// c'est une légère différence dans le filtre flou

gl_FragColor = origcolor + percent * (color - origcolor);
// Ici, nous utilisons une formule simple. Si percent = 0, alors nous utilisons simplement
// la couleur d'origine. Sinon, nous utilisons la couleur floutée. Cette formule est appelée
// interpolation linéaire et s'écrite "a + scaler * (b - a)". Ainsi, lorsque scaler = 0, a
// est retourné et lorsque scaler = 1, alors b est retourné. En utilisant cela, nous pouvons
// contrôler la façon la puissance de notre filtre.
}
"""

Il n'y a pas vraiment beaucoup de différences entre le script de flou dans la partie 2 et ce script. Examinons en détail les changements dans le code.

vec4 origcolor = texture2D(bgl_RenderedTexture, gl_TexCoord[0].st);
gl_FragColor = origcolor + percent * (color - origcolor);

Comme vous pouvez le voir, nous n'avons pas simplement attribué à gl_FragColor la variable color comme nous l'avions fait dans la partie précédente de cette série. Dans la première ligne, nous créons une nouvelle variable vec4, la couleur d'origine du pixel que nous souhaitons modifier. Nous avons alors deux variables vec4 à cet endroit du code : la couleur floutée et la couleur d'origine. Ensuite, nous attribuons à gl_FragColor le résultat d'une formule simple appelée une interpolation linéaire.

La formule pour une interpolation linéaire est (a + scaler * (b - a)). Fondamentalement, cette formule dit que lorsque scaler est égal à 0, alors vous utilisez la valeur de a. Et quand scaler est égal à 1, vous utilisez la valeur de b. Vous pouvez utiliser différentes valeurs numériques pour a et b, c'est ce que nous faisons dans notre exemple en utilisant nos deux couleurs.

Ajout du 21 mars 2011 : la formule d'interpolation linéaire fonctionne et est une bonne formule à connaître, mais vous pouvez également utiliser la fonction mix dans un script GLSL. Cette fonction prend comme arguments un vec4 pour la première couleur, un vec4 pour la deuxième couleur et le pourcentage comme dernier argument.

Nous devrions être en mesure de contrôler la façon dont notre image sera floutée à l'aide de la variable scaler, qui sera dans notre cas un pourcentage. Où est créée cette variable dans le script ? Eh bien, elle n'est pas créée dans le script du tout. Nous la créons en fait en dehors du script du shader et elle est passée comme paramètre dans l'une des premières lignes du script, la ligne avec le code uniform float percent. C'est le but des variables uniformes : elles nous permettent de transmettre des valeurs depuis l'extérieur du script du shader. Un autre exemple de variable uniforme est la variable bgl_RenderedTexture que nous ne créons pas non plus, le BGE la crée et la passe au script du shader pour nous.

Dans ce script, nous pouvons contrôler la variable percent dans notre code de jeu et le script sera automatiquement mis à jour pour flouter plus ou moins notre écran en fonction de ce que l'on souhaite. Toutefois, nous avons encore besoin de créer la variable percent dans notre jeu, car c'est une variable définie par l'utilisateur. Le BGE gère les variables uniform définies par l'utilisateur comme des variables objet : si nous créons une variable objet, alors elle sera utilisée par le BGE comme variable uniform dans le script du shader. Donc, tout ce que nous devons faire est de créer une variable objet percent comme on peut le voir dans la capture d'écran. Puisque que la variable objet percent possède le même nom que dans notre script du shader, le script fera référence à cette variable objet quand il fonctionnera. C'est tout ce qu'il y a à faire !

Maintenant, vous devriez être en mesure de contrôler un filtre 2D par l'intermédiaire d'une variable uniform définie par l'utilisateur, que le BGE transmet automatiquement à partir d'une variable objet. Téléchargez le code source de la troisième partie de « Filtres d'écran en 2D avec OpenGL dans le BGE » ici. Comme toujours, amusez-vous !

IV - Remerciements

Merci à SolarLune de nous avoir autorisé à traduire les articles "OpenGL 2D Screen Filters in the BGE" Partie 1, Partie 2 et Partie 3.

Merci à LittleWhite pour sa relecture technique et à FirePrawn pour sa relecture orthographique.