FAQ OpenGL
FAQ OpenGL Consultez toutes les FAQ
Nombre d'auteurs : 9, nombre de questions : 74, dernière mise à jour : 24 juin 2021
- Comment faire de la 2D avec OpenGL ?
- Comment désactiver l'écriture dans le color buffer ?
- Comment désactiver l'écriture dans le Z-buffer ?
- Comment activer l'écriture dans le stencil buffer ?
- Comment rendre une couleur transparente, par exemple sur un sprite ?
- Comment faire une capture d'écran (screenshot) ?
- Comment charger et afficher un modèle 3D ?
- Comment afficher du texte avec OpenGL ?
- Comment et pourquoi utiliser une métatexture ?
- Comment créer une métatexture ?
- Comment effectuer un rendu dans une image (off-screen) ?
- Comment savoir quel objet se trouve sous le pointeur (picking) ?
- Comment placer une vidéo dans une texture ?
- Comment avoir de l'anticrénelage ?
- Comment activer le multisampling ?
OpenGL n'offre aucune fonction destinée à la 2D, du moins directement. Il est cependant très simple de faire du rendu 2D si l'on considère qu'il s'agit de rendu 3D avec un axe fixe.
En pratique, il suffit de paramétrer correctement les matrices de transformation :
// On place la matrice de modelview à l'identité, mais rien n'empêche par la suite
// d'appliquer des rotations, translations ou mises à l'échelle
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// On définit une matrice orthogonale pour la perspective, ce qui permettra d'avoir une vue 2D et non en perspective
glMatrixMode(GL_PERSPECTIVE);
glOrtho(left, right, bottom, top, near, far);
// Ou gluOrtho2D(left, right, bottom, top);
Puis, ne pas oublier d'utiliser des fonctions manipulant des coordonnées 2D, comme glVertex2f() au lieu de glVertex3f().
Certains effets nécessitent d'écrire dans le Z-Buffer sans écrire dans le color buffer (par exemple pour les shadow volumes).
Afin d'effectuer cet effet, il existe deux possibilités en OpenGL :
1- Positionner les paramètres de blending sur GL_ZERO, GL_ONE. Cela fonctionne, mais c'est coûteux (les fragments sont tout de même traités), et cela risque d'interférer avec le blending utilisé dans certains autres effets.
glBlendFunc(GL_ZERO, GL_ONE);
2- Utiliser la fonction glColorMask :
glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
En positionnant tous les paramètres à GL_FALSE, cette fonction désactive l'écriture dans le color buffer tout en continuant à écrire dans les autres buffers (stencil, depth).
L'écriture dans le Z-buffer (ou encore depth-buffer) est contrôlée par la fonction glDepthMask :
/* Activer l'écriture dans le Z-buffer */
glDepthMask
(
GL_TRUE);
/* Désactiver l'écriture dans le Z-buffer */
glDepthMask
(
GL_FALSE);
L'activation ou la désactivation de l'écriture dans le stencil buffer (ainsi que les tests) s'effectue en appelant glEnable / glDisable avec le flag GL_STENCIL_TEST :
/* Activer l'écriture et les tests de stencil */
glEnable
(
GL_STENCIL_TEST);
/* Désactiver l'écriture et les tests de stencil */
glDisable
(
GL_STENCIL_TEST);
Il faut pour cela que la texture utilisée ait un canal alpha correctement défini, c'est-à-dire que les pixels transparents aient une valeur alpha à 0.
Il ne faut donc pas oublier de spécifier lors de la création de la texture (avec glTexImage2D ou autre) que le format de l'image source contient un canal alpha (GL_RGBA), et que la texture doit également en contenir un (nombre de composantes égal à 4 et non 3).
glTexImage2D
(
GL_TEXTURE_2D, 0
, 4
, largeur, hauteur, 0
, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
Si votre image ne possède pas de canal alpha, mais plutôt une couleur que vous souhaitez rendre transparente, vous pouvez très bien parcourir les pixels de celle-ci et affecter manuellement les valeurs alpha selon la couleur des pixels.
Une fois la texture correctement munie d'un canal alpha, il existe deux solutions pour activer la transparence. Comme expliqué dans la FAQ 3D (« Comment avoir une texture trouée »), le meilleur moyen est d'activer l'alpha-test.
Sous OpenGL, l'alpha-test s'active via le code suivant :
glEnable
(
GL_ALPHA_TEST);
glAlphaFunc
(
GL_GREATER, 0
.0f
);
/* Dessin du sprite avec transparence */
glDisable
(
GL_ALPHA_TEST);
Ici, nous paramétrons l'alpha-test pour qu'il ne garde que les pixels ayant une valeur alpha strictement supérieure 0.
La seconde option est d'activer l'alpha-blending de la manière suivante :
glEnable
(
GL_BLEND);
glBlendFunc
(
GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
/* Dessin du sprite avec transparence */
glDisable
(
GL_BLEND);
Ceci dit cette méthode a deux inconvénients : premièrement les sprites devront être triés d'arrière en avant ; ensuite ce ne sera pas aussi performant que l'alpha-test.
OpenGL fournit un moyen très simple de récupérer le contenu du color buffer avec la fonction glReadPixels :
// Width et Height représentent les dimensions du backbuffer
// Buffer est un pointeur vers une zone mémoire suffisamment grande pour recevoir les pixels
glReadPixels(0
, 0
, Width, Height, GL_BGRA, GL_UNSIGNED_BYTE, Buffer);
Par contre aucun mécanisme n'est fourni pour enregistrer l'image récupérée dans un fichier, pour cela il vous faudra passer par une bibliothèque tierce (voir Quelles bibliothèques utiliser avec OpenGL ?).
Attention : récupérer les pixels du color buffer est une opération très lente et à n'utiliser que dans cette optique. N'envisagez par exemple pas d'utiliser ce mécanisme pour effectuer des traitements en temps réel.
Ni OpenGL, ni GLU ou encore GLUT, ne fournissent de mécanisme aidant au chargement ou à l'affichage de modèles 3D, quel que soit leur format. Ainsi il vous faudra mettre la main à la pâte pour gérer ceux-ci. Plusieurs solutions s'offrent à vous.
La première est d'utiliser un format ASCII simple à décortiquer (ASE par exemple) et de coder un chargeur capable de lire ce format. Le site des formats de fichiers fournit une description détaillée de la plupart des formats existants ; si jamais vous n'y trouviez pas le vôtre, alors pensez à Google.
Game Tutorials propose également de nombreux tutoriels permettant de charger les formats les plus courants (3DS, OBJ, ASE, MD2, MD3), mais il faudra payer pour accéder à ceux-ci…
Notez que la distribution de GLUT contient un code source permettant de charger les fichiers au format OBJ (voir progs/demos/smooth/glm.c).
La seconde solution est d'utiliser un programme permettant de convertir un modèle directement en code C, contenant les appels OpenGL permettant de construire et d'afficher le modèle. Cette solution est toutefois déconseillée dans le sens où elle n'est absolument pas flexible : pour afficher un nouveau modèle, il faudra en effet recompiler l'application.
Quelques logiciels proposent cette conversion, par exemple PolyTrans ou encore 3D Exploration.
Enfin, pour les plus courageux, il est bien sûr possible de concevoir un format de modèles personnel. Deux solutions pour créer les modèles : coder des scripts d'exportation pour votre logiciel de modélisation préféré (pas forcément évident…), ou coder un convertisseur à partir d'un format existant (mais dans ce cas, autant utiliser ce format directement).
Pour plus d'informations sur les formats à utiliser n'ayant pas de lien avec OpenGL, vous pouvez consulter Quel format de modèle 3D utiliser ?.
OpenGL ne fournit pas de moyen direct pour afficher du texte, c'est pourquoi vous devrez utiliser d'autres fonctionnalités d'OpenGL pour l'affichage de polices, comme l'affichage de bitmaps ou de pixmaps, la création de texture maps contenant une table complète de caractères, ou encore la création de formes géométriques 3D.
Utilisation de bitmaps ou pixmaps : La manière la plus classique d'afficher du texte avec OpenGL est l'utilisation de glBitmap() ou glDrawPixels() pour chaque caractère. Le résultat est un simple texte 2D qui convient dans la majorité des cas. Préférez glBitmap() à glDrawPixels(), car ce dernier est bien plus lent. Généralement, chaque appel à glBitmap(), un pour chaque caractère, est placé dans une liste d'affichage (display list), qui est indexée par sa valeur de caractère ASCII. Ensuite, un appel à glCallLists() permet d'afficher une chaîne entière de caractères.
Vous pouvez aussi utiliser des fonctions propres à certains toolkits et API comme glXUseXFont() pour le serveur X, wglUseFontBitmaps() pour Windows ou encore glutBitmapCharacter() pour GLUT.
Par texture mapping : Dans beaucoup d'implémentations OpenGL, glBitmap() et glDrawPixels() sont très lents par rapport à un carré texturé. Utilisez dans ces cas-là cette solution. L'idée est de créer une texture qui contient tous les caractères d'une police (ou au moins ceux qui seront affichés). Pour afficher un caractère, dessinez un carré texturé, avec les coordonnées de la texture configurées pour sélectionner le caractère voulu dans la texture. Vous pouvez aussi jouer avec la valeur alpha pour désactiver le fond de la police.
Voilà les deux méthodes les plus classiques pour afficher du texte dans une scène OpenGL. Ces méthodes sont applicables dans de nombreux contextes (langages, plateformes, OS…). Cependant, si le résultat obtenu ne vous convient pas (si vous souhaitez avoir des polices 3D, etc.), il existe des solutions plus techniques. Cependant la plupart de ces solutions sont propres à une API ou à un système d'exploitation.
Pour la création d'images pour les textures, il existe sur Internet de petits programmes pour les générer. Vous pouvez cependant coder votre propre FontFactory.
Si vous utilisez Java, n'hésitez pas à faire appel aux API du JDK et plus particulièrement à Java2D qui vous facilitera grandement la tâche.
Il existe aussi des bibliothèques vous facilitant grandement l'affichage de texte avec OpenGL :
Lien : NeHe - polices Bitmap
Lien : NeHe - polices vectorielles
Lien : NeHe - Application de texture sur les polices vectorielles
Lien : NeHe - Texture 2D de police
La fonction glBindTexture est une fonction relativement lourde. Ce qui veut dire que lorsque vous voulez afficher une scène, il est généralement déconseillé de passer d'une texture à une autre trop souvent.
Une technique intéressante est de regrouper les images dans une unique texture et de s'en servir comme métatexture. En utilisant les paramètres passés à la fonction glTextureCoord2f, on peut sélectionner les parties de la métatexture afin d'avoir la texture que l'on cherche.
Une question qui peut se poser est de savoir comment gérer ce genre de métatexture, surtout lorsque la texture que l'on veut se compose aussi de sous-parties.
Prenons d'abord un exemple, supposons que la texture A se compose de quatre parties :
Et une texture B se compose de deux autres parties :
Lorsqu'on les mettra ensemble, cela donnera la texture C :
Lorsqu'on avait une seule texture A, on faisait :
glBindTexture(GL_TEXTURE_2D, index_de_la_texture_A);
Si on voulait avoir la partie noire, on aurait fait :
glBegin(GL_QUADS);
glTexCoord2f(0.0
f, 1.0
f);
glVertex3f(-
1.0
f, 1.0
f, 0.0
f); /* coin haut-gauche */
glTexCoord2f(0.5
f, 1.0
f);
glVertex3f( 1.0
f, 1.0
f, 0.0
f); /* coin haut-droit */
glTexCoord2f(0.5
f, 0.5
f);
glVertex3f( 1.0
f, -
1.0
f, 0.0
f); /* coin bas-droit */
glTexCoord2f(0.0
f, 0.5
f);
glVertex3f(-
1.0
f, -
1.0
f, 0.0
f); /* coin bas-gauche */
glEnd();
Mais avec la texture C, qu'est-ce qui change ? Déjà la fonction glBindTexture va devoir charger la texture C, mais les arguments de glTexCoord2f changent également… C'est là où avoir un gestionnaire de textures est pratique. Il suffit d'avoir un gestionnaire qui nous donne les coordonnées et l'indice de la texture que l'on cherche.
Lorsque je veux afficher mon carré noir, mon programme a son propre indice pour savoir que je veux cette partie. Pourquoi ? Parce que rien n'oblige OpenGL à redonner à chaque exécution les mêmes indices de textures (c'est pour cela qu'on utilise glGenTextures).
Donc lorsque je veux avoir l'indice OpenGL d'une texture, rien ne vaut un gestionnaire central des ressources (voir ce tutoriel sur la gestion des ressources).
Pour revenir à notre problème, nous avons donc un gestionnaire de textures à qui nous pouvons dire :
tex =
gestionnaireTextures.getTexture(indice_carre_noir);
Qu'est-ce que nous mettons dans tex ? Les coordonnées de la sous-texture voulue dans la métatexture.
Les coordonnées seront données par position du coin inférieur gauche (comme en OpenGL le coin inférieur gauche est donné par les coordonnées (0.0,0.0)) et (largeur/hauteur). Donc une structure de ce type :
typedef
struct
sTexture
{
float
debx, deby;
float
larg, haut;
}
STexture;
Finalement, notre code de départ doit être modifié ; en effet, nous devons prendre en compte la position de la sous-texture. La transformation de code se fera en deux étapes. Pour la première, nous allons enlever les coordonnées écrites en dur et passer par des variables :
float
gauche =
0
.0f
, droite =
0
.5f
, haut =
1
.0
, bas =
0
.5
;
glBegin
(
GL_QUADS);
glTexCoord2f
(
gauche, haut);
glVertex3f
(-
1
.0f
, 1
.0f
, 0
.0f
); /* coin haut-gauche */
glTexCoord2f
(
droite, haut);
glVertex3f
(
1
.0f
, 1
.0f
, 0
.0f
); /* coin haut-droit */
glTexCoord2f
(
droite,bas);
glVertex3f
(
1
.0f
, -
1
.0f
, 0
.0f
); /* coin bas-droit */
glTexCoord2f
(
gauche, bas);
glVertex3f
(-
1
.0f
, -
1
.0f
, 0
.0f
); /* coin bas-gauche */
glEnd
(
);
Ensuite, pour prendre en compte la position dans la métatexture, il suffit de modifier le calcul fait aux variables gauche, droite, haut et bas.
Dans notre cas (si nous voulions la sous-texture A), nous aurions comme valeurs :
tex.begx =
0
.0
;
tex.begy =
0
.0f
;
tex.haut =
1
.0f
;
tex.larg =
0
.5
;
Et le code d'affichage deviendrait :
float
gauche =
tex.begx +
tex.larg *
0
.0f
;
float
droite =
tex.begx +
tex.larg *
0
.5f
;
float
haut =
tex.begy +
tex.haut *
1
.0f
;
float
bas =
tex.begy +
tex.haut *
0
.5
;
glBegin
(
GL_QUADS);
glTexCoord2f
(
gauche, haut);
glVertex3f
(-
1
.0f
, 1
.0f
, 0
.0f
); /* coin haut-gauche */
glTexCoord2f
(
droite, haut);
glVertex3f
(
1
.0f
, 1
.0f
, 0
.0f
); /* coin haut-droit */
glTexCoord2f
(
droite,bas);
glVertex3f
(
1
.0f
, -
1
.0f
, 0
.0f
); /* coin bas-droit */
glTexCoord2f
(
gauche, bas);
glVertex3f
(-
1
.0f
, -
1
.0f
, 0
.0f
); /* coin bas-gauche */
glEnd
(
);
Un dernier point important : quand faire un glBindTexture ?
En effet, n'oublions pas que nous avons fait cela pour limiter les appels à la fonction glBindTexture. Le plus simple est de laisser le gestionnaire le faire. Nous lui demandons de le faire, et il pourra vérifier si la sous-texture que nous voulons est déjà en place (si elle se trouve dans la métatexture déjà chargée). Si ce n'est pas le cas, le gestionnaire la charge.
Finalement, si les sous-textures sont mises ensemble de façon optimale, il est possible de faire tenir en une seule métatexture toutes les textures d'une scène.
Voilà, vous savez maintenant utiliser une métatexture et la gérer correctement.
Il existe deux techniques principales : la création avant le lancement du programme, et la création pendant le programme.
La première solution est de prendre votre programme de dessin favori, ouvrir les deux textures qui vous intéressent et les mettre ensemble pour former une texture plus grande.
Un exemple est donné dans la question https://jeux.developpez.com/faq/opengl/?page=techniques#TECHNIQUES_meta_texture.
La deuxième technique est de créer virtuellement cette texture en juxtaposant les images lorsqu'elles sont chargées en mémoire. Lorsque le programme se charge, il va demander à un gestionnaire de ressources de charger plusieurs textures. Le gestionnaire ne va pas les charger individuellement, mais va les mettre ensemble pour former des métatextures. Ceci est bien sûr fait pour limiter le nombre d'appels à glBindTexture.
Cette technique demande que le programme utilise le gestionnaire pour :
- charger la texture ;
- récupérer les coordonnées d'une texture ;
- faire appel à la fonction glBindTexture, s'il le faut.
Lorsqu'on charge une image, avant de la donner à OpenGL, on la stocke dans un tableau contenant les couleurs rouge, vert et bleu de chaque pixel. Il est donc facile de garder ce tableau et de le concaténer avec une autre texture avant de passer ce nouveau tableau à la fonction glTexImage2D.
Il arrive parfois que l'on ait besoin d'effectuer le rendu d'une scène non pas à l'écran, mais dans une image ou une quelconque surface en mémoire système ; c'est ce que l'on appelle le rendu off-screen.
Un tel rendu n'est pas disponible directement avec les fonctionnalités d'OpenGL. En effet, cela dépend du contexte d'affichage et non de la bibliothèque en elle-même, ce qui est donc dépendant du système d'exploitation.
Voici un lien expliquant comment réaliser un rendu off-screen sous différents systèmes : OpenGL/Mesa off-screen rendering.
Afin de déterminer quel objet de la scène 3D se trouve sous un point donné (en général le pointeur de la souris), il faut utiliser ce que l'on appelle le picking. Il existe plusieurs méthodes pour réaliser du picking.
Habituellement, on utilise le mode de rendu GL_SELECT fourni par OpenGL, qui permet d'identifier instantanément quel objet se trouve sous un point de l'écran.
Voici quelques tutoriels expliquant comment utiliser le mode GL_SELECT :
- Picking tutorial
- Sélection et picking avec OpenGL
Cependant il existe d'autres méthodes, qui fonctionnent tout aussi bien.
La première méthode consiste à transformer les coordonnées 2D du point dans l'espace 3D. Pour cela, il suffit de lui faire subir les transformations inverses de celles qui sont appliquées aux objets 3D, ce qui donnera un rayon (et non un point unique, puisqu'on ajoute une dimension). Il suffira ensuite d'effectuer des tests d'intersection entre ce rayon et les objets de la scène, afin de déterminer lequel se trouve sous le pointeur. La fonction gluUnproject permet de générer facilement un tel rayon.
Une autre méthode est d'effectuer un rendu de la scène en assignant à chaque objet une couleur unique et constante, puis d'aller lire dans le back buffer (tampon de travail) la couleur du point en question, ce qui vous donnera instantanément l'identifiant de l'objet que vous recherchez.
Lire une vidéo et placer son contenu dans une texture OpenGL est une tâche difficile, et surtout dépendante du système d'exploitation. De ce fait, OpenGL ne fournit aucune fonctionnalité pour réaliser ce genre de manipulation.
Voici tout de même un tutoriel de NeHe expliquant comment lire une vidéo dans une texture sous Windows :
http://nehe.gamedev.net/tutorial/playing_avi_files_in_opengl/23001/.
Il existe plusieurs méthodes pour effectuer de l'anticrénelage (antialising) avec OpenGL.
La première, que l'on voit souvent dans la littérature, est d'utiliser les paramètres suivants :
glHint
(
GL_LINE_SMOOTH_HINT,GL_NICEST);
glHint
(
GL_POLYGON_SMOOTH_HINT,GL_NICEST);
glEnable
(
GL_POLYGON_SMOOTH);
glEnable
(
GL_LINE_SMOOTH);
Cette méthode est à éviter à tout prix. En effet pour avoir un anticrénelage correct avec cette méthode, il faut :
- trier les polygones du plus lointain au plus proche ;
- ne pas avoir d'intersection entre polygones.
Une autre méthode présentée est d'utiliser le tampon d'accumulation, puis de rendre la scène plusieurs fois en modifiant légèrement la position de la caméra. Bien que moins contraignante du point de vue de la structure de données, cette méthode est aussi à éviter, car elle est très consommatrice de ressources (plusieurs rendus pour une même scène…).
la dernière méthode, utilisée dans les jeux vidéo, consiste à utiliser le multisampling (multiéchantillonnage) qui permet d'avoir un anticrénelage de qualité pour un coût de traitement moindre.
L'activation du multisampling avec OpenGL dépend du système de fenêtrage utilisé.
Sous GLUT, il suffit de passer le paramètre GLUT_MULTISAMPLE lors de l'initialisation du contexte. Par exemple :
glutInitDisplayMode
(
GLUT_DOUBLE |
GLUT_RGB |
GLUT_MULTISAMPLE);
Avec la SDL il faut utiliser l'attribut SDL_GL_MULTISAMPLEBUFFERS :
/* Utilisation de l'anticrénelage possible ? */
if
(
SDL_GL_SetAttribute
(
SDL_GL_MULTISAMPLEBUFFERS, 1
) ==
-
1
)
{
fprintf
(
stderr, "
Impossible d'initialiser SDL_GL_MULTISAMPLEBUFFERS à 1
\n
"
);
}
/* Nombre de tampons utilisés pour l'anticrénelage (la valeur utilisée dépend de la carte graphique) */
if
(
SDL_GL_SetAttribute
(
SDL_GL_MULTISAMPLESAMPLES, 6
) ==
-
1
)
{
fprintf
(
stderr, "
Impossible d'initialiser SDL_GL_MULTISAMPLESAMPLES à 6
\n
"
, fsaa);
}
Où le nombre de tampons définit le niveau d'anticrénelage. Toutes les cartes graphiques ne supportent pas ou n'ont pas assez de puissance à certains niveaux.
Enfin, avec l'API Win32, le code est un peu plus long, mais il est très bien décrit dans ce tutoriel de NeHe : Leçon 46.