|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
TUTORIAL 2: RENDERING PIPELINE, OPENGL AND GLUT |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
INTRODUCTION Lors de la leçon
précédente, nous avons mis en place les premiers éléments
de notre pipeline de rendu : comment acquérir les données
d'un objet et sauvegarder toutes ces données dans une structure.
Aujourd'hui, nous allons accomplir les poins 2 et 3 :
TRANSFORMATIONS POUR PLACER LES OBJETS DANS LE MONDE (transformations de modélisation et de visualisation) Après avoir défini la structure objet, il faut appliquer quelques transformations à tous les points que nous avons avant de pouvoir les voir à l'écran. Nous voulons créer un monde, ou pour être plus exact, (soyons fou pour les simulateurs spaciaux) un uniivers complet. La première transformation est la TRANSFORMATION de MODELISATION. Comme nous avons l'intentation de créer des vaisseaux spaciaux qui, sans la preuve du contraire, ne sont pas des objets statiques ;-), nous devons transformer le système de coordonnées locales de l'objet (qui est relatif à la position centrale de chaque objet) en un système de coordonnées absolues (qui est relatif à l'unviers tout entier ;-) ). En d'autres termes, nous devons déplacer l'objet en ajoutant la position de l'objet dans l'univers (qui peut changer si l'objet continue de bouger) aux coordonnées locales des points. Ensuite vient la TRANSFORMATION de VISUALISATION. Notre but principal, bien sûr, est d'explorer cet univers en s'y déplaçant comme bon nous semble, transformant l'écran en une caméra libre. Comment pouvons-nous faire ce genre de tranformations ? La réponse est relativement simple. En fait, il suffit simplement de considérer que la caméra est toujours positionnée en 0,0 sans aucune rotation. Et alors ? C'est simple ! Nous prenons n'importe quelle transformation que nous devrions appliquer à la caméra pour obtenir un certain mouvement et nous appliquons la transformation inverse à tous les objets au lieu de déplacer la caméra. Par exemple, supposons que nous voulions déplacer notre point de vue vers l'objet de 10 points selon l'axe des Z et de regarder l'objet d'en haut (rotation selon l'axe des x de 40 degrés). Ce qui se passe réellement le plus souvent dans les moteurs graphiques c'est que l'objet a reculé de 10 selon l'axe des Z et a ensuite effectué ensuite une rotaion de -40 degrés selon l'axe des X. Cela simplifie grandement l'utilisation du moteur car la caméra sera toujours à l'origine.
RENDU DE LE SCENE SUR UN ECRAN EN 2D (back face culling, transformation de projection et de visualisation, tampons de couleur et de profondeur) L'opération suivante à réaliser est le BACK FACE CULLING (face cachée). Cela signifie que nous allons exclure les triangles qui ne sont pas visibles, c'est à dire les faces qui sont au dos des faces visibles de l'objet. Ainsi nous pouvons gagner énormément de temps de rendu car la fonction de dessin ne va dessiner que la moitié de la totalité des traingles. OpenGL réalise cette opération pour nous La transformation suivante est la TRANSFORMATION de PROJECTION . Maintenant nous devons "écraser" une scène 3d sur un écran en 2d. Nous devons simuler l'axe Z car notre pauvre moniteur ne possè que les axes X et Y. La faç la plus simple de réaliser cette translation est de diviser tous les coordonnées (x,y) des points apar leur profondeur Z. L'effet de cette procé est de rapprocher les points distants ainsi ils donnent l'impression de se rapprocher du point central x = 0 et y = 0. C'est exactement ce qu'il se produit en OpenGL. Nombreux d'entre vous doivent connaître ceci comme étant la "projection en perspective ". La dernière transformation est la VIEWPORT TRANSFORMATION (transformation d'affichage). Cela convertit juste tous les points qui vont être utilisés dans la résolution de l'affichage courant. Un tampon est une zone mémoire dans laquelle il est possible de sauvegrader des données. Les tampns d'OpenGL sont des régions précisément aussi grande que la zone d'affichage (viewport). Par exemple, si nous avons une fenêtre d'un taille de 640 x 480, nous allouons un tampon de: 640 x 480 = 307200 pixels. Ce qui équivaut en mode couleur 16 bits: 307200*16 = 4915200 bits. Ce qui correponds à peu près à 614 Koctets de mémoire vidéo! OpenGL possède deux tampons principaux: le TAMPON de COULEURS (qui peut être simple ou double) et le TAMPON de PROFONDEUR (DEPTH BUFFER). Le TAMPON de COULEUR est ce que l'on voit à l'écran et où aboutissent toutes les opérations de dessin. Après avoir réalisé tous les calculs géométriques, OpenGL commence à remplir ce tampon pixel par pixel, remplissant nos triangles. Si la sc&egave;ne est animée, le TAMPON de COULEURS est dessiné et effacé &agave; chaque frame. Le TAMPON de COULEURS fonctionne souvent par paire, il s'agit du TAMPON DOUBLE (DOUBLE BUFFER), qui est ce dont nous allons nous servir. Le TAMPON DOUBLE à afficher un tampon pendant que l'autre est effacé et rempli avec l'image suivante. Une fois cette opération réalisée, les tampons sont échangés. En utilisant cette technique on élimine quasiment l'effet scintillement. Maintenant, supposons qu'il y ait deux triangles dans notre scène, l'un devant l'autre, les deux étant visibles. Dans ce cas, l'ordre dans lequel les triangles sont dessinés est très important. Si le triangle le plus proche de nous est dessiné en premier et ensuite on dessine le plus loin, les pixels du triangle le plus proche seront recouverts par ceux du triangle le plus éloigné, créant un effet indésirable. Une technique pour éviter ce désagrément consiste à trier tous les triangles visibles selon leur profondeur Z. Et ensuite les dessiner dans cet ordre, du plus distant au plus proche. Cette technique est appelée "l'ALGORITHME du PEINTRE". Nous n'allons pas utiliser cette méthode car OpenGL nous offre un outils plus puissant: le TAMPON de PROFONDEUR (DEPTH BUFFER). Ce tampon a les mêmes dimensions que le TAMPON de COULEUR mais au lieu de contenir la couleur des pixels, il contient des informations à propos de la profondeur de chaque pixel selon l'axe des Z. Sauvegarder cette information est très important et ce pour une raison très simple. Quand nous allons dessiner nos triangles pixel par pixel à l'écran, nous allons d'abord faire un test pour savoir si le pixel que nous allons dessiner est plus proche que celui actuellement sauvegardé dans le Z BUFFER (tampon de profondeur). S'il est plus proche, il faut alors mettre à jour le Z BUFFER avec la nouvelle valeur et écrire dans le tampon de couleurs. Si ce n'est pas le cas alors le pixel n'est pas pris en compte pour le dessin. Cette procédure donne d'excellents résultats! Nous n'avons pas besoin de nous soucier de comment coder ces opérations à la main car notre superbe librairie OpenGL va réaliser tous les calculs pour nous. OpenGL va aussi s'occuper de toutes les opérations bas niveau de dessins, y compris le remplissage des triangles ainsi que l'application des couleurs aussi bien que l'éclairage et les effets de mapping. Nous n'avons rien à faire? Donc, qu'est ce que nous faisons là? Nous perdons notre temps? Non! Nous devons fournir à OpenGL toutes les informations il a besoin pour qu'il puisse faire tous ces calculs, faire le lien avec la carte graphique et réaliser toutes les opérations de bas niveau en utilisant (si elle est présente) l'accélération 3d matérielle.
OPENGL, ENFIN! OpenGL est une librairie qui nous permet d'interagir avec le matériel graphique. Il possèssde de nombreuses fonctions permettant de dessiner des points, des lignes et des polygones et effectue tous les calculs nécessaire pour l'éclairage, le shading et les transformations de points. GLUT, quant à elle, est une boîte à outils utilisée pour faire le lien entre OpenGL et le système de fenêtrage. Il nous permet de créer une fenêtre indépendamment de la plateforme utilisée (Windows ou Linux). Elle gère aussi les entrées au clavier. La structure de notre programme OpenGL est divisé en plusieurs sections: -La fonction init : utilisé pour démarrer OpenGL et initialiser les matrices de modélisation, de visualisation et de projection. Il est aussi possible de mettre toutes les autres initialisations dans cette fonction. -La function resize : appelée à chaque fois que l'utlisateur démarre le programme ou change la résolution de la fenêtre de sortie. Elle est nécessaire pour communiquer la nouvelle taille de la fenêtre de sortie à OpenGL -La fonction keyboard: appelée à chaque fois qu l'utilisateur appuie sur une touche. -La fonction de dessin: nettoyage de tous les tampons (couleurs et profondeur). Toutes les transformations de modélisation, de visualisation et de projection sont effectuées et la scène est dessiné. Enfin les 2 tampons de couleurs sont échangés. -Boucle principale: une boucle infinie, qui appelle toutes les fonctions à chaqe image. Une fonction OpenGL typique ressemble à: glFunctionName(GL_TYPE arguments). Pour les fonctions GLUT, cela ressemble à: glutFunctionName(arguments). OpenGL possède aussi des types propres pour faciliter la portablilité. Ces types commencent avec le préfixe "GL" et sont suivis de "u" (pour les valeur non signées) et apr le type(float, int etc.). Par exemple, on peut utiliser GLfloat ou GLuint pour déclarer des variables de types similaires au type "float" et "unsigned int" en C.
ENTETES La première chose à faire est d'inclure les fichiers d'entête nécessaire: windows.h (pour les utlisateurs de Windows) et glut.h #include <windows.h> En incluant glut.h, nous aussi indirectement inclus gl.h et glu.h (les fichiers d'entête d'OpenGL). Il est aussi très important de mettre en place les options du linker dans le compilateurPour cela il faut inclure les librairies opengl32.lib, glu32.lib et glut32.lib Maintenant, nous devons déclarer une fonction qui intialise OpenGL.
LA FONCTION INIT void init(void) Analysons le code: -void
glClearColor( GLfloat rouge, GLfloat vert, GLfloat bleu,
GLfloat alpha); définit
les veleurs rouge, vert, bleu et alpha utilisées par glClear
pour nettoyer les tampons de couleurs. Nous utilisons un bleu profond
comme couleur de fond donc nous assignons 0.2 à la composante
bleue. Les autres composantes sont mise à 0.0 aisni que la
composante alpha (la signification de cette composante sera expliquée
dans un prochain tutorial). J'ai oublié de vous dire que la
gamme des valeurs admises pour les paramètres des fonctions
OpenGL est 0-1. Nous pouvons donc mélanger toutes les composantes
pour créer n'importe quelle couleur. Par exemple, pour créer
un fond jaune, nous devons mettre la composante bleue à 0 et
les composantes verte et rouge à 1.
LA FONCTION RESIZE Cette fonction est très similaire à "init". Elle nettoie les tampons, redéfinit notre fenêtre d'affichage et reaffiche notre scène. void resize (int width,
int height) Voici les fonctions que nous n'avons pas encore vu: -void glClear( GLbitfield
mask ); nettoie les tampons spécifiés
par "mask". Nous pouvons indiquer plus de 1 tampon en les séparant
par l'opérateur logique "|". Dans notre cas, nous nettoyons
les tampons de couleurs et de profondeur.
LES FONCTIONS KEYBOARD Nous allons définir deux fonctions pour le clavier: une pour gérer les entrées de caractères ASCII ("r" et "R" et un caractère vide ' ') et une autre pour gérer les touches de direction: void keyboard (unsigned
char key, int x, int y) Nous utilisons trois variables pour autoriser
note objet à tourner autour des axes désirés:
rotation_x_increment, rotation_y_increment and rotation_z_increment.
On peut réinitialiser chacune de ces variables en utilisant
la touche espace et arrêter le mouvement de notre objet dans
sa position actuelle. Nous pouvons aussi changer le mode de dessin
pour nos polygones de rempli à filaire avec les touche "r"
ou "R". Cette fonction est vraiment semblable à la précédente mais elle gère les touches de direction.Remarquez les constantes GLUT: GLUT_KEY_UP, GLUT_KEY_DOWN, GLUT_KEY_LEFT et GLUT_KEY_RIGHT qui identifient leur directions respectives et incrémente ou décrémente les valeurs de rotations.
LA FONCTION DE DESSIN Ladies and gentlemen, la fonction que vous attendez tous: la fonction de dessin! void display(void) la première partie de cette fonction nettoie les tampons de couleur et de profondeur et applique les transformations de visualisation et de modélisation. Nous indiquons que la matrice courante est la matrice "modelview" en utilisant GL_MODELVIEW dans glMatrixMode. Ensuite nous initialisons la matrice à chaque imageen appelant glLoadIdentity. -void glTranslatef(
GLfloat x, GLfloat y, GLfloat z );
déplace notre objet dans le monde 3D. Cette fonction multiplie
la matrice du modèle par une matrice de translation définie
en utilisant les paramètres x,y,z. La lettre "f" à la
fin du nom indique que nous allons utiliser des valeurs de type flottant,
alors que la lettre "d" indique des paramètres de type double.
Nous utilisons glTranslate pour bouger l'objet de 50 points vers l'avant
dans ce cas. Vous rappelez-vous la caméra et les transformations
de visualisation? Bien, nous pouvons considérer cette opération
comme une translation de notre caméra de -50. Ce mouvement
est nécessaire car nous devons déplacer l'objet d'une
petite distance au loin pour pouvoir le voir. Une fois le projet compilé
vous pouvez essayer de modifier la valeur du Z, juste pour voir comment
cela affecte la distance. glBegin(GL_TRIANGLES); La seconde partie de la fonction de dessin utilise les fonctions glBegin et glEnd. Ces deux commandes indiquent les points qui définissent une primitive graphique. -void glBegin( GLenum
mode ); indique le début d'une liste de
données pour les points qui définissent une primitve
géométrique. Le paramètre "mode" permet d'indiquer
le type de primitives qu'on va dessiner. Il y a dix types de primitives
disponibles (GL_TRIANGLES, GL_POYLGON, GL_LINES etc.). Nous utilisons
GL_TRIANGLES car nous voulons dessiner notre cube en utilisant
12 triangles. Nous faisons ceci en démarrant une boulce "for"
dans laquelle sont effectués 3 appels à glVertex3f et
glColor3f.
LA FONCTION MAIN int main(int argc,
char **argv) Les quatre fonctions suivantes de la librairie GLUT nous permettent de créer notre fenêtre pour la sortie graphique. glutInit(&argc,
argv); -void glutInit(&argc, argv);
initialise la bibliothèque GLUT. Nous devons appeler cette
fonction avant d'appeler n'importe quelles autres fonctions GLUT. Pour définir les callbacks, nous utilisons: glutDisplayFunc(display); -void glutDisplayFunc(void
(*func) (void)); spécifie la fonction à
appeler quand la fenêtre doit être redessiner, c'est-à-dire
quand il y a un appel à glutPostRedisplay ou quand il y a une
erreur signalée par le système de fenêtre.
CONCLUSIONS Alors? Ca y est tout est là! Je sais, c'est un travil de longue haleine mais regardez! Vous êtes maintenant de créer un objet 3D réel en rotation! Ce qui signifie que vous avez créé votre premier moteur 3D! Dans la leçon suivante, nous allons étudier comment réliser le texture mapping (le plaçage de texture en français?) Maintenant, il est temps que vous me donniez votre opinion. Dîtes moi si vous trouvez un bug. &Eeacute;crivez moi à info[at]spacesimulator.net
CODE SOURCE Pour compiler et exécuter ce projet, vous devez avoir les librairies GLUT qui peuvent être trouvées à: www.opengl.org/developers/documentation/glut.html Téléchargez le source C/C++
et l'exétable dans un zip:
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
©2000-2005 Damiano Vitulli. All Rights Reserved. No Portion Of This Site May Be Reproduced Without Permission. Best viewed at 1024x768. |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||