Note: J'ai volontairement laissé certains termes en anglais (comme vertex array et screen space) car les traduire aurait complexifié les choses. Ces termes étant de toute façon prononcé et écrit en anglais, même en français.

Une introduction à l'OpenGL "Moderne". Chapitre 1: Le Pipeline Graphique

Sommaire
:longBar:
Intro

OpenGL existe depuis longtemps, et en lisant toute la documentation accumulée ça et là sur internet, il n'est pas toujours aisé de distinguer quelles sont les parties anciennes de celle qui sont vraiment utile et supporté par les cartes graphiques modernes.

Il est temps de proposer une nouvelle introduction à OpenGL qui aborde uniquement les parties qui sont toujours d'actualités.

Mise à jour: Joignez la discution sur Reddit.

:longBar:
Qu'est-ce qu'OpenGL?

OpenGL_logo.jpg Wikipedia donne un bon aperçu de ce à quoi sert OpenGL et de son historique, mais je n'en ferais qu'un rapide résumé ici.

Dans sa forme moderne, OpenGL est une librairie multiplateforme qui sert à communiquer avec les GPU programmable, dans le but de rendre des images 3d en temps réel.

Son utilisation est fréquente dans les jeux vidéos, la CAO, et les applications de visualisation de données (médecine par exemple).

OpenGL débuta son histoire dans le début des années 90, en temps que standard multiplateforme de la librairie graphique propriétaire de SGI, qui pilotait les composants graphiques qui étaient utilisé dans leur stations de travail haut de gamme. silicon_graphics_logo_new.jpg

Quelques années plus tard, GLQuake et les cartes "accélératrices" Voodoo 3dfx démocratisèrent les cartes graphiques, et OpenGL devenu un standard au coté de Direct3d (la librairie propriétaire de Microsoft). "pour contrôler les cartes graphiques des gens" 3dfx_logo.gif

Un logo et beaucoup de souvenirs...

glquake8ks.jpg

GLQuake, pas de pixels sur les textures, et en 640x480! Trop la classe pour l'époque...

En 2006, c'est le Khronos Group qui pris la gérance des spécifications OpenGL:

  • Les mettant à jours pour supporter les caractéristiques des GPU programmable modernes.
  • Les développant dans le domaine de l'embarqué et du web avec, respectivement, OpenGL ES et WebGL.
  • Les rationalisant ("nettoyant") avec OpenGL 3, en dépréciant les caractéristiques désuètes qui encombraient les versions antérieures de la librairie.

Khronos-1600-Transparent-May07.jpg

Un autre développement récent fut la démocratisation des librairies de "calcul générique sur le processeurs graphiques" (GPGPU), comme CUDA de nVidia et OpenCL du Khronos Group.

Ces librairies implémentent la syntaxe du langage C en ajoutant la gestion des données parallèle, permettant au GPU d'être utilisé pour des calculs génériques, sans avoir à utiliser les fonctions "orienté graphique" d'OpenGL.

Cela dit, ces librairies GPGPU ne remplace pas OpenGL; dans la mesure ou le calcul graphique n'est pas leur fonction première, elles ne donne accès qu'aux unités de calcul du GPU, en ignorant les composants de calcul spécifiquement graphiques.

Elles peuvent, en revanche, être utilisé aux cotés d'OpenGL.

CUDA et OpenCL peuvent tous les deux partager des buffers de la mémoire du GPU avec OpenGL et transmettre des données entre les programmes GPU et le pipeline Graphique.

Le GPGPU ne sera pas abordé dans cet article; Je me focaliserai sur l'utilisation d'OpenGL pour les taches graphiques.

Pour ces tutoriaux, je vais partir du principe que vous êtes développeur et que vous connaissez le C, mais que vous n'avez pas nécessairement utilisé OpenGL ou fait de la programmation graphique avant.

Connaitre au moins quelques concepts d'algèbre de base et de géométrie vous aidera beaucoup.

Je vais utiliser OpenGL 2.0, et éviter de parler des fonctions de l'API qui ont dépréciées ou supprimées dans OpenGL 3 ou OpenGL ES.

Si j'écris assez de chapitres, je pourrais parler des quelques nouvelles possibilités d'OpenGL 3 et 4, après que nous ayons traité les bases.

En plus d'OpenGL, je vai utiliser deux librairies bien pratiques:

  • GLUT (GL Utility Toolkit), qui fournit une interface multiplateforme entre le système de fenêtrage et OpenGL.
  • GLEW (GL Extensions Wrangler), qui simplifie la gestion des différentes versions d'OpenGL et ses extensions.

glewLogo.png

Un chapeau de cowboy, le logo de GLEW...
:longBar:
Ou puis-je récupérer OpenGL, GLUT et GLEW?

OpenGL est livré en standard (d'une façon ou une autre) sur MacOS X, Windows et la plupart des distributions Linux.

Si vous voulez suivre ces tutoriaux, vous devez être sur que votre implémentation d'OpenGL supporte au moins la version 2.0

L'implémentation OpenGL de MacOS X supporte toujours OpenGL 2.0, au moins en software si le pilote de la carte graphique ne la fournit pas.

Sur Windows, cela dépend des pilotes de votre carte graphique.

Vous pouvez utiliser le l'OpenGL Extensions Viewer de RealTech pour déterminer quelle version d'OpenGL votre driver supporte (NDT: Je lui préfère GPU Caps Viewer, plus complet, mais uniquement sous Windows).

Les drivers OpenGL nVidia et AMD supportent au moins OpenGL 2.0 sur toute les cartes graphiques sorties au court des quatre dernières années.

Les utilisateurs de chipset intégré Intel et de carte graphiques plus anciennes ont moins de chance.

Comme solution de replis, Mesa fournit une implémentation software multiplateforme et open-source d'OpenGL 2.1 qui fonctionne sur windows et presque toute les plateformes Unix.

Mesa est également, l'implémentation OpenGL la plus communément utilisé sous Linux, ou elle fonctionne également avec X server pour interfacer OpenGL avec la carte graphique en utilisant les pilotes "direct rendering interface" (DRI).

Vous pouvez déterminer si votre pilote DRI supporte OpenGL 2.0 en lançant la commande glxinfo depuis un xterm. Si OpenGl 2.0 n'est pas supporté sur votre machine, vous pouvez désactiver le driver pour vous replier vers Mesa et son implémentation software. nVidia fournit également sa propre implémentation OpenGL propriétaire pour Linux qui vise uniquement leur GPU; Cette implémentation devrait fournir OpenGL 2.0 ou plus sur n'importe quel carte graphique nVidia récente.

Pour installer GLUT et GLEW, allez chercher les packages binaires sur leurs sites respectifs. MacOS X est livré avec GLUT préinstallé.

La plupart des distribution linux ont GLUT et GLEW disponible aux travers de leurs "system package", bien que pour GLUT, vous devriez avoir à activer les dépôts optionnels "non-free", dans la mesure ou sa licence n'est pas, techniquement, open source.

Il y a un clone de GLUT open source appelé OpenGLUT si vous êtes pointilleux sur ce genre de choses.

Si vous êtes un développeur C chevronné, vous devriez être capable d'installer ses librairies et de les faire fonctionner avec votre IDE sans problèmes.

Mais avant qu'on se salisse les mains avec du code, je vais revenir sur les principaux conceptes.

Dans ce premier chapitre, je vais expliquer le fonctionnement d'un pipeline graphique et le cheminement des données dans un rendu.

Dans le chapitre suivant, nous écrirons un simple programme "hello world" qui dessine le contenu d'une image sur l'écran, montrant comment mettre le pipeline en pratique.

:longBar:
Le Pipeline Graphique

gl1-pipeline-01.png

Revenez sur ce schéma fréquemment durant la suite de ce chapitre

Toujours, depuis les premier jours de la 3d temps réel, le triangle a été le "pinceaux" avec lequel les scenes furent déssinées.

Bien que les GPU modernes puisse faire toute sorte d'effets flashys pour dissimuler cet horrible secret, en dessous de tout ce que vous voyez, les triangles restent ceux avec quoi ils travaillent.

Le pipeline graphique qu'OpenGL implémente ressemble à ça:

  • Le programme principale remplis des buffers de la mémoire géré par OpenGL avec des vertex arrays (des tableaux de vertex).
  • Ces vertex sont projetés dans l'espace écran ("screen space" en anglais) par les vertex shaders, assemblé en triangle et enfin "rasterizé" (pixelisé) en fragments de la taille d'un pixel (en gros, un fragment = un pixel).
  • Finalement, ces fragments (les pixels) se voit assigné des couleurs (par des fragments shaders) et sont dessinés sur le framebuffer.

Les GPU modernes doivent leur flexibilité au fait de déléguer les étapes de "projection-dans-le-screen-space" (vertex shader) et "assignation-d'une-couleur" (fragment shader) a des petits programmes, envoyé au GPU, appelé shaders.

Regardons chaque étape plus en détail...

:longBar:
Les vertex et element arrays

Une tache de rendu (un rendering job) commence sa journée dans le pipeline sous la forme d'un groupe ("set" en anglais), ou plus, de vertex buffer. Chaque groupe est remplis avec des tableaux d'attributs par vertex (vertex attributes).

Ses attributs sont utilisé en tant qu'input au vertex shader.

Généralement, les vertex attributes incluent la position des vertex dans l'espace 3d, et un set, ou plus, de coordonnées de texture (UV) qui projettent le vertex sur une texture (ou plus).

Les groupes de vertex buffer fournissant les données au rendering job sont collectivement appelé les vertex arrays.

Quand un render job est lancé, nous fournissons un tableau d'élément (element array) supplémentaire aux vertex array: Un tableau d'index, qui indique quel sont les vertex qui vont entrer dans le pipeline.

L'ordre de ses index contrôle également comment les vertex sont assemblé en triangle (voir plus loin).

:longBar:
Uniform state and textures

Un rendering job a également des "états uniformes" (uniform states), qui fournissent aux shaders des valeurs partagé en lecture-seul, a toute les "étapes programmable" du pipeline.

Ceci permet au shader (program) de prendre des paramètres qui ne change pas entre les vertex ou fragment (ils sont disponible tout le long du rendu). Les uniforms states peuvent être des textures (qui sont en fait des array en un, deux, ou trois dimensions), qui peuvent être utilisé par les shaders.

Comme leur nom l'indique, les textures sont généralement utilisées pour plaquer des images sur des surfaces.

Elles peuvent également être utilisé comme simple tableau ("lookup tables") pour des fonctions précalculées ou pour stocker d'autres informations (normal, opacité, etc..) pouvant être utilisé pour divers types d'effets.

:longBar:
Les vertex shaders

Le GPU commence en lisant chaque vertex sélectionné parmi le vertex array et en le passant dans le vertex shader, un programme qui prend un set de vertex attributes en argument (input), et renvoie (output) un nouveaux set de vertex attribute, qui définit autant de valeurs que prendra en compte le rasteriszer.

Au minimum, le vertex shader calcul la position des vertex projeté sur le screen space.

Le vertex shader peut également générer des outputs différents, comme la couleur ou les coordonnées de textures Le rasterizer interpolera ensuite ces valeurs entre chaque sommet (le long de la surface). (NDT: Voir ce billet)

:longBar:
Assemblage des triangles

gl1-triangle-assembly-01.png Ensuite, le GPU relit les vertex projetés sous forme de triangle. Il fait cela en prenant les vertex dans l'ordre spécifié par l'element array et en les groupant par paquets de trois.

Les vertices peuvent être groupé de trois façon différentes:

  • Prendre chacun des trois éléments en temps que triangle indépendant.
  • Faire une "bande de triangle" (triangle strip), en réutilisant les deux derniers vertex de chaque triangle comme les deux premiers vertex du triangle suivant.
  • Faire un "ventilateur de triangle" (triangle fan), en reliant le premier élément à chaque paire d'élément suivant.

Le diagramme montre comment les trois méthodes se comportent.

Les méthodes "strip" et "fan" n'ont besoin que d'un seul nouvel index par triangle après avoir initialisé les trois premiers, ce qui permet d'économiser de la mémoire dans l'element array.

:longBar:
La rasterisation (ou pixelisation)

gl1-rasterization-01.png Le rasterizer prend chaque triangle, le découpe, rejette les éléments qui sont en dehors de l'écran, et brise les petites parties restantes en fragments de la taille d'un pixel.

Comme mentionné au dessus, les différents outputs du vertex shader sont également interpolé le long de la surface rasterisé de chaque triangle, en assignant un dégradé des valeurs à chaque fragment (pixel).

Par exemple, si le vertex shader assigne une couleur à chaque vertex, le rasterizer va dégrader ses couleurs le long de la surface pixelisé, comme montré dans le diagrame.

:longBar:
Le fragment shader

Les fragments générés (qui ne sont pas encore tout à fait des pixels) passe ensuite dans un autre "program" appelé le fragment shader.

Le fragment shader reçoit, en input, les valeurs de l'output du vertex shader (qui ont été, rappellez vous, interpolé par le rasterizer).

Sa couleur et sa profondeur en output sont ensuite écrites dans le framebuffer. La plupart des opérations des fragments shaders concerne le texture mapping et l'éclairage.

Dans la mesure ou le fragment shader fonctionne indépendament pour chaque pixel dessiné, il peut effectuer des effets spéciaux plus sophistiqués; En revanche, il est également le point le plus sensible en terme de performance dans le pipeline graphique.

:longBar:
Les framebuffers, test, et fusion

Un framebuffer, c'est la destination de l'output d'une tache de rendu.

En plus du framebuffer par défault qu'OpenGL fournit pour dessiner sur l'écran, la plupart des implémentations OpenGL modernes vous donnent la possibilité de faire des framebuffers objects qui écrivent en dehors de l'écran (le renderbuffer) ou directement dans les textures.

Ces textures peuvent ensuite être utilisé comme inputs à d'autres taches de rendu.

Un framebuffer est plus qu'une simple image 2d; Combiné à un color buffer (ou plus), un framebuffer peut avoir un depth buffer et/ou un stencil buffer (un masque), chacun pouvant éventuellement filter les fragment shaders avant qu'ils soient dessiné dans le framebuffer: Un test de profondeur (depth testing) pour, par exemple, ignorer des fragments. Suivi d'un test sur le stencil buffer (stencil testing) servant à contraindre une zone particulière du framebuffer, "découpant" le rendering job.

Les fragments qui "survivent" à ses deux tests voit leur couleurs mélangées par transparence (grace à leur alphas) avec la couleur du dessous, et les valeurs finals de couleur, profondeur et stencil sont dessinné sur les buffers correspondant.

:longBar:
Conclusion

C'est tout cela le processus. Des vertex buffers au framebuffer, c'est ce que font vos données quand vous faites un simple appel "draw" en OpenGL.

Rendre une scene nécéssite généralement plusieurs draw jobs (passage d'une texture à une autre, autres uniforms states) ou shaders, entre les passes, en utilisant la profondeur et le stencil des framebuffers pour obtenir le résultat de chaque passe.

Maintenant que nous avont vu le cheminement général des données d'un rendu 3d, nous somme capable d'écrire un simple programme pour voir comment tout ça ce goupille dans OpenGL.

Tout au long de ce tutoriel, j'aimerais avoir votre avis. Faites moi savoir si ça vous aide ou si quelque chose n'a pas de sens.

Vous pouvez retrouver l'article original sur la page de Joe Groff, un fois de plus: Merci à lui!