Navigation▲
I. Picking en utilisant un hack OpenGL▲
Cette technique n'est pas vraiment conseillée, mais c'est une méthode facile et rapide pour ajouter un picking simple. Dans tous les cas, évitez d'utiliser cela dans les jeux, car cela peut provoquer une chute de FPS. Toutefois, si vous avez une simulation et que vous ne vous souciez pas des performances liées au picking, cette technique peut être la solution parfaite.
Le code source pour ce tutoriel est disponible dans le fichier au nom lourd de sens : misc05_picking/misc05_picking_slow_easy.cpp.
I-A. Idée de base▲
L'idée derrière cette technique est d'afficher la scène, comme d'habitude, mais au lieu d'utiliser un joli rendu, vous dessinez chaque objet avec une couleur spécifique et unique.
Ensuite, vous récupérez la couleur du pixel sous le curseur de la souris et vous utilisez cette couleur pour identifier l'objet. Vous obtenez ainsi l'objet sur lequel l'utilisateur a cliqué.
Voici un exemple :
Dans cette capture d'écran, chaque singe possède une couleur légèrement différente permettant de les identifier.
Bien sûr, vous ne voulez pas voir l'image avec toutes ces couleurs étranges donc vous devez nettoyer l'écran et redessiner comme d'habitude.
II. Implémentation▲
II-A. Donner un identifiant à chaque objet▲
Chaque objet de la scène doit avoir un identifiant unique. La façon la plus facile de le faire est de donner à chaque objet un entier et de le convertir en une couleur. Cette couleur ne doit pas avoir de sens ; cette technique est simplement un hack de toute façon.
Dans le code source accompagnant ce tutoriel, 100 objets sont créés et conservés dans un std::vector dont l'identifiant n'est que l'index de l'objet dans ce vecteur. Si vous avez une hiérarchie plus complexe, vous devez certainement ajouter l'identifiant dans votre classe de Mesh et maintenir une sorte de std::map pour associer l'identifiant à l'objet.
II-B. Détecter le clic▲
Dans cet exemple simple, le picking est effectué à chaque image lorsque le bouton gauche de la souris est appuyé :
if
(glfwGetMouseButton(GLFW_MOUSE_BUTTON_LEFT)){
Dans une application réelle, vous souhaitez certainement le faire uniquement lorsque l'utilisateur relâche le bouton et donc vous devez garder un booléen estCeQueLeBoutonGaucheAEteAppuyeLorsDeLImagePrecedente, ou mieux, utilisez glfwSetMouseButtonCallback() (lisez le manuel GLFW pour connaître comment l'utiliser).
II-C. Convertir l'identifiant en une couleur spécifique▲
Comme nous devons afficher chaque modèle avec une couleur différente, la première étape est de calculer cette couleur. Une méthode simple est de placer les bits de poids faible dans le canal rouge et les bits de poids fort dans le canal bleu :
// Converti « i », l'identifiant de modèle en une couleur RBG
int
r =
(i &
0x000000FF
) >>
0
;
int
g =
(i &
0x0000FF00
) >>
8
;
int
b =
(i &
0x00FF0000
) >>
16
;
Cela peut paraître effrayant, mais c'est un code de manipulation de bits standard. Vous obtenez finalement trois entiers, chacun compris entre 0 et 255. Avec cette méthode, vous pouvez représenter 255^3 = 16 millions d'objets différents, ce qui est certainement suffisant.
II-D. Afficher la scène avec cette couleur▲
Nous avons maintenant besoin d'un shader pour utiliser cette couleur. C'est très simple. Le vertex shader ne fait rien :
#
version
330
core
// Données d'entrée du sommet, différentes pour chaque exécution de ce shader.
layout
(
location =
0
) in
vec3
vertexPosition_modelspace;
// Valeurs constantes pour tout le modèle.
uniform
mat4
MVP;
void
main
(
){
// Position de sortie du sommet, dans l'espace clip : MVP * position
gl_Position
=
MVP *
vec4
(
vertexPosition_modelspace,1
);
}
Et le fragment shader écrit simplement la couleur voulue dans le tampon d'image :
#
version
330
core
// Données de sortie
out
vec4
color;
// Valeurs constantes pour tout le modèle.
uniform
vec4
PickingColor;
void
main
(
){
color =
PickingColor;
}
Facile !
La seule astuce est que vous devez envoyer vos couleurs en nombres flottants (compris dans [0, 1]) alors que vous avez des entiers (compris dans [0, 255]), donc vous devez effectuer une petite division lors de l'appel à glUniformXX() :
// OpenGL attend des couleurs entre [0,1], donc divisez-les par 255.
glUniform4f(pickingColorID, r/
255.0
f, g/
255.0
f, b/
255.0
f, 1.0
f);
Vous pouvez maintenant dessiner les modèles comme d'habitude (glBindBuffer, glVertexAttribBuffer, glDrawElements) et vous obtiendrez l'étrange image ci-dessus.
II-E. Obtenir la couleur sous le curseur de souris▲
Lorsque vous avez dessiné tous les modèles (certainement avec une boucle for()), vous devez appeler glReadPixels(), permettant d'obtenir les pixels rasterizés sur le CPU. Mais pour que cela fonctionne, quelques étapes supplémentaires sont nécessaires.
Premièrement, vous devez appeler glFlush(). Cela indiquera au pilote OpenGL d'envoyer les commandes en attente (incluant votre dernier glDrawXX) au GPU. Cela n'est typiquement pas fait automatiquement, car les commandes sont envoyées en groupe et non immédiatement (cela signifie que lorsque vous appelez glDrawElements(), rien n'est dessiné. Cela VA être dessiné quelques millisecondes plus tard). Cette opération est LENTE.
Ensuite, vous devez appeler glFinish(), qui attendra jusqu'à ce que tout soit vraiment affiché. La différence avec glFlush() est que glFlush() envoie simplement les commandes ; glFinish() attend que les commandes soient exécutées. Cette opération est LEEENTE.
Vous devez aussi configurer comment glReadPixels() se comportera par rapport à l'alignement mémoire. C'est quelque peu hors sujet, mais simplement, vous devez appeler glPixelStore(GL_UNPACK_ALIGNMENT, 1).
Finalement, vous pouvez appeler glReadPixels() ! Voici le code complet :
// Attend jusqu'à ce que toutes les commandes de dessin soient vraiment exécutées.
// C'est ultra méga lent !
// Il y a habituellement un long moment entre glDrawElements() et le rendu de tous les fragments.
glFlush();
glFinish();
glPixelStorei(GL_UNPACK_ALIGNMENT, 1
);
// Lit le pixel au centre de l'écran.
// Vous pouvez aussi utiliser glfwGetMousePos().
// C'est aussi ultra méga lent, même pour 1 pixel,
// car le tampon d'image est sur le GPU.
unsigned
char
data[4
];
glReadPixels(1024
/
2
, 768
/
2
,1
,1
, GL_RGBA, GL_UNSIGNED_BYTE, data);
Votre couleur est maintenant dans le tableau data. Ici, vous pouvez voir que l'identifiant est 19 :
II-F. Convertir la couleur en un identifiant▲
Vous pouvez maintenant retrouver votre identifiant à partir du tampon data. Le code est exactement l'opposé du code de conversion de l'identifiant en une couleur :
// Convertit la couleur en un identifiant
int
pickedID =
data[0
] +
data[1
] *
256
+
data[2
] *
256
*
256
;
II-G. Utiliser cet identifiant▲
Vous pouvez maintenant utiliser l'identifiant comme vous le souhaitez. Dans l'exemple, le texte de l'interface est mis à jour, mais bien sûr, vous pouvez faire ce que vous souhaitez.
if
(pickedID ==
0x00ffffff
){
// Blanc, cela doit être le fond !
message =
"background"
;
}
else
{
std::
ostringstream oss; // Les chaines de caractères C++ sont nulles
oss <<
"mesh "
<<
pickedID;
message =
oss.str();
}
III. Avantages et inconvénients▲
-
Avantages :
- facile et rapide à implémenter ;
- aucun besoin de bibliothèque externe ni de mathématique complexe.
-
Inconvénients :
- utilise glFlush(), glFinish(), glReadPixels() qui sont toutes des fonctions notoirement lentes, car elles forcent le CPU à attendre le GPU, détériorant ainsi les performances ;
- vous n'avez pas une information précise : quel triangle est touché, la normale en ce point, etc.
IV. Remarques finales▲
Bien que cela ne soit pas recommandé, cette technique peut être vraiment utile, mais elle est complètement restreinte au picking. Les méthodes dans les deux prochains tutoriels peuvent être utilisées pour d'autres buts, comme la détection des collisions, faire marcher un avatar sur le sol, effectuer des requêtes de visibilité pour l'intelligence artificielle, etc.
Si finalement vous utilisez cette technique et que vous devez obtenir plusieurs points dans une même image, vous devez obtenir ces points en une seule fois. Par exemple, si vous devez gérer cinq points de toucher, ne dessinez pas cinq fois la scène !
V. Remerciements▲
Cet article est une traduction autorisée dont le texte original peut être trouvé sur opengl-tutorial.org.