Texture 2D de police

Giuseppe D'Agata et Jeff Molofee (NeHe)

Traducteur

Alexandre Laurent

Ce tutoriel vous est présenté par NeHe et Giuseppe D'Agata… ♪

- Titre : Texture 2D de police

- Auteur : Giuseppe D'Agata et Jeff Molofee (NeHe)

- Traducteur : Alexandre Laurent

- Parution : 19 décembre 2012

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2006 Nehe Gamedev.net. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Contributions

- II. Tutoriel

- III. Téléchargements

- IV. Remerciements

- V. Liens

I - Contributions

Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com.

II - Tutoriel

Je sais que tout le monde en a marre des polices. Les tutoriels que j'ai faits jusqu'à présent n'affichent pas seulement du texte, ils affichent du texte en 3D, du texte avec des textures, et peuvent accepter des variables. Mais qu'est-ce qui se passe si vous portez votre code sur une machine qui ne supporte pas les Bitmaps ou les polices vectorielles ?

Grâce à Giuseppe D'Agata, nous avons un autre tutoriel sur les polices. Vous vous demandez ce qui pourrait manquer !? Si vous vous rappelez dans le premier tutoriel sur les polices, j'ai mentionné l'utilisation de textures pour dessiner les lettres à l'écran. Habituellement, lorsque vous utilisez des textures pour dessiner du texte à l'écran vous utilisez votre logiciel de dessin préféré, vous sélectionnez une police puis vous tapez les lettres que vous voulez afficher. Vous sauvegardez le Bitmap et le chargez dans votre programme comme texture. Ce n'est pas très efficace pour un programme qui requiert beaucoup de texte, ou du texte qui change continuellement !

Ce programme utilise seulement une texture pour afficher n'importe lequel des 256 caractères à l'écran. Gardez à l'esprit que les caractères font 16 pixels de large et environ 16 de hauteur. Si vous prenez votre texture standard de 256x256, il est facile de voir que vous pouvez mettre 16 lettres sur une ligne, et que vous avez un total de 16 lignes. Si vous avez besoin d'une explication plus détaillée : la texture a une largeur de 256 pixels, un caractère une largeur de 16 pixels. 256 divisé par 16 = 16. :)

Donc… Créons une démo d'une texture de police 2D. Ce programme utilise le code de la leçon 1. Dans la première section du programme, nous incluons les bibliothèques math et stdio. Nous avons besoin de la bibliothèque math pour bouger les lettres dans l'écran en utilisant SIN et COS et de la bibliothèque stdio pour être sûr que les Bitmaps que nous voulons utiliser existent avant d'essayer d'en faire des textures.

#include <windows.h>                                // Fichier d'entête pour Windows
#include <math.h>                                // Fichier d'entête pour la bibliothèque mathématique        (AJOUT)
#include <stdio.h>                                // Fichier d'entête pour pour les entrées/sorties    (AJOUT)
#include <gl\gl.h>                                // Fichier d'entête pour la bibliothèque OpenGL32
#include <gl\glu.h>                                // Fichier d'entête pour la bibliothèque GLu32
#include <gl\glaux.h>                                // Fichier d'entête pour la bibliothèque GLaux

HDC        hDC=NULL;                            // Contexte de périphérique privé GDI
HGLRC        hRC=NULL;                            // Contexte de rendu permanent
HWND        hWnd=NULL;                            // Contient notre ID de la fenêtre
HINSTANCE    hInstance;                            // Contient l'instance de l'application

bool    keys[256];                                // Tableau utilisé pour la fonction du clavier
bool    active=TRUE;                                // Drapeau de fenêtre active définie à TRUE par défaut
bool    fullscreen=TRUE;                            // Drapeau de plein écran défini en plein écran par défaut

Nous allons ajouter une variable appelée base pour pointer sur nos listes d'affichage. Nous ajouterons aussi texture[2] pour contenir les deux textures que nous allons créer. La texture 1 sera la texture de la police, et la texture 2 va être une texture de relief pour créer notre objet simple 3D.

Nous ajoutons une variable loop qui va être utilisée pour exécuter les boucles. Finalement nous ajoutons cnt1 et cnt2 qui seront utilisées pour déplacer le texte sur l'écran et faire tourner notre objet simple 3D.

GLuint    base;                                    // Base pour la liste d'affichage de la police
GLuint    texture[2];                                // Stockage pour les textures de notre police
GLuint    loop;                                    // Variables génériques pour les boucles

GLfloat    cnt1;                                    // Premier compteur utilisé pour déplacer le texte et pour la coloration
GLfloat    cnt2;                                    // Second compteur utilisé pour déplacer le texte et pour la coloration

LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);                // Déclaration de WndProc

Maintenant le code du chargement des textures. C'est exactement le même que celui de la leçon sur le placage de texture (leçon 6).

AUX_RGBImageRec *LoadBMP(char *Filename)                    // Charge une image Bitmap
{
FILE *File=NULL;                            // Descripteur de fichier
if (!Filename)                                // Vérifie qu'un nom de fichier a été donné
{
return NULL;                            // Sinon, retourne NULL
}
File=fopen(Filename,"r");                        // Contrôle si le fichier existe
if (File)                                // Est-ce que le fichier existe ?
{
fclose(File);                            // Ferme le descripteur
return auxDIBImageLoad(Filename);                // Ouvre le Bitmap et renvoie un pointeur
}
return NULL;                                // Retourne NULL si le chargement a échoué
}

Le code qui suit a aussi très peu changé par rapport au code des tutoriels précédents. Si vous n'êtes pas sûr de ce que fait chaque ligne, revenez en arrière et révisez.

Notez que TextureImage[ ] va contenir deux images RGB. C'est très important de vérifier deux fois le code qui gère le chargement et le stockage des textures. Un mauvais nombre pourrait provoquer des crashs ou des fuites de mémoire !

int LoadGLTextures()                                // Ouvre un Bitmap et le convertit en texture
{
int Status=FALSE;                            // Indicateur du statut
AUX_RGBImageRec *TextureImage[2];                    // Crée un espace pour les textures

La ligne suivante est la plus importante à voir. Si vous vouliez remplacer le 2 par n'importe quel autre nombre, de gros problèmes surviendraient. La double vérification ! Ce nombre doit correspondre au nombre que vous utilisez lorsque vous définissez TextureImages[ ].

Les deux textures que nous allons charger sont font.bmp (notre police), et bumps.bmp. La seconde texture peut être remplacée par n'importe quelle texture de votre choix. Je ne me sentais pas très créatif, donc la texture que j'ai décidé d'utiliser peut être un peu terne.

memset(TextureImage,0,sizeof(void *)*2);                // Initialise les pointeurs à NULL

if ((TextureImage[0]=LoadBMP("Data/Font.bmp")) &&            // Charge le Bitmap de la police
(TextureImage[1]=LoadBMP("Data/Bumps.bmp")))            // Charge le Bitmap de la texture
{
Status=TRUE;                            // Met la variable Status à TRUE

Autre chose d'important à vérifier à deux fois. Je ne peux pas dire combien de courriels j'ai reçus venant de personnes demandant "Pourquoi je ne vois qu'une seule texture, ou pourquoi toutes mes textures sont blanches !?!". Généralement le problème vient de cette ligne. Si vous remplacez le 2 par un 1, seule une texture sera créée et la seconde texture apparaîtra toute blanche. Si vous remplacez le 2 par un 3, votre programme peut planter !

Vous devriez avoir un seul appel à glGenTextures(). Après glGenTextures() vous devriez générer toutes vos textures. J'ai vu des gens mettre une ligne glGenTextures() pour chaque texture qu'ils voulaient créer. Généralement cela peut faire que la nouvelle texture écrasera les textures que vous avez déjà créées. C'est une bonne idée de décider du nombre de textures que vous avez besoin de créer, d'appeler glGenTextures() une seule fois, et de construire toutes les textures. Il n'est pas sage de mettre glGenTextures() dans une boucle si vous n'avez aucune raison pour cela.

glGenTextures(2, &texture[0]);                    // Créer deux textures

for (loop=0; loop<2; loop++)                    // Boucle pour toutes les textures
{
// Construction de toutes les textures
glBindTexture(GL_TEXTURE_2D, texture[loop]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
}
}

Les lignes suivantes vérifient si le fichier Bitmap que nous avons chargé pour construire notre texture utilise la mémoire RAM. Si c'est le cas, la mémoire est libérée. Remarquez que nous vérifions et libérons les deux images enregistrées. Si nous utilisions trois images différentes pour construire nos textures, nous devrions vérifier et libérer trois images.

for (loop=0; loop<2; loop++)
{
if (TextureImage[loop])                        // Si la texture existe
{
if (TextureImage[loop]->data)             // Si l'image de la texture existe
{
free(TextureImage[loop]->data);        // Libère l'image de la texture
}
free(TextureImage[loop]);            // Libère la structure de l'image
}
return Status;                                // Renvoie le statut
}

Maintenant nous allons créer notre police. Je vais parcourir cette section de code en détail. Ce n'est pas vraiment complexe, mais il y a un peu de mathématiques à comprendre, et je sais que les maths ne sont pas une chose que tout le monde apprécie.

GLvoid BuildFont(GLvoid)                            // Construit la liste d'affichage de la police
{

Les deux prochaines variables vont être utilisées pour contenir la position de chaque lettre dans la texture de la police. cx contiendra la position de gauche à droite dans la texture, et cy contiendra la position de haut en bas.

float    cx;                                // Contient la coordonnée du caractère sur les X
float    cy;                                // Contient la coordonnée du caractère sur les Y

Après nous disons à OpenGL que nous voulons construire 256 listes d'affichage. La variable base va pointer sur l'emplacement de la première liste d'affichage. La seconde liste d'affichage sera base+1, la troisième base+2, etc.

La seconde ligne de code ci-dessous sélectionne la texture de la police (texture[0]).

base=glGenLists(256);                            // Crée les 256 listes d'affichage
glBindTexture(GL_TEXTURE_2D, texture[0]);                // Sélectionne la texture de la police

Maintenant nous commençons la boucle. La boucle va créer les 256 caractères, sauvegardant chaque caractère dans sa propre liste d'affichage.

for (loop=0; loop<256; loop++)                        // Boucle à travers les 256 listes d'affichage
{

La première ligne ci-dessous peut sembler un peu déroutante. Le symbole % signifie le reste après la division de loop par 16. cx va nous déplacer à travers la texture de la police de gauche à droite. Vous remarquerez plus tard dans le code, que nous enlevons cy à 1 pour nous déplacer de haut en bas au lieu de bas en haut. Le symbole % est assez dur à expliquer, mais je vais essayer.

Tout ce qui nous concerne est ce (loop%16). Le /16.0f convertit juste le résultat en coordonnées de texture. Donc si loop était égal à 16… cx serait égal au reste de 16/16 qui est 0. Mais cy serait égal à 16/16 qui est 1. Donc nous descendrions de la hauteur d'un caractère, mais pas du tout sur la droite. Maintenant si loop était égal à 17, cx serait égal à 17/16 ce qui fait 1.0625. La reste .0625 est aussi égal à 1/16. Signifiant que nous nous déplacerions d'un caractère sur la droite. cy sera toujours égal à 1 parce que nous ne nous intéressons qu'au nombre à gauche du symbole décimal. 18/16 nous donnerait 2 sur 16, nous déplaçant de deux caractères sur la droite, et toujours un caractère vers le bas. Si loop était 32, cx serait encore une fois égal à 0, parce qu'il n'y a pas de reste à la division de 32 par 16, mais cy serait égal à 2. Parce le nombre sur la gauche de la décimale serait maintenant 2, nous déplaçant de deux caractères vers le bas sur notre texture. Est-ce que c'est clair ?

cx=float(loop%16)/16.0f;                    // Position du caractère sur X
cy=float(loop/16)/16.0f;                    // Position du caractère sur Y

Ouf. :) OK. Donc maintenant nous construisons notre police 2D en sélectionnant un caractère sur la texture selon les valeurs de cx et cy. Dans la ligne ci-dessous nous ajoutons la valeur de loop à la valeur de base, si nous ne le faisions pas, chaque lettre serait construite dans la première liste d'affichage. Nous ne voulons certainement pas que cela arrive donc en ajoutant loop à base, chaque caractère que nous créons est sauvegardé dans la liste d'affichage suivante.

glNewList(base+loop,GL_COMPILE);                // Début de la création de la liste

Maintenant que nous avons sélectionné la liste d'affichage que nous voulons construire, nous créons notre caractère. Ceci est réalisé en dessinant un carré, texturé avec un caractère provenant de la texture de la police.

glBegin(GL_QUADS);                    // Utilisation d'un carré pour chaque caractère

cx et cy devraient contenir des petites valeurs décimales de 0.0f à 1.0f. Si cx et cy étaient égaux à 0, la première ligne de code ci-dessous serait alors : glTexCoord2f(0.0f, 1-0.0f - 0.0625f). Rappelez-vous que 0.0625 est exactement 1/16 de notre texture, ou la largeur/hauteur d'un de nos caractères. La coordonnée de texture serait le point en bas à gauche de notre texture.

Remarquez que nous utilisons glVertex2i(x,y) à la place de glVertex3f(x,y,z). Notre police est une police 2D, donc nous n'avons pas besoin d'une valeur pour z. Parce que nous utilisons un écran orthogonal, nous n'avons pas besoin de nous déplacer dans l'écran. Tout ce que vous avez à faire pour dessiner dans un écran orthogonal est de spécifier les coordonnées x et y. Parce que notre écran est en pixels de 0 à 639 et de 0 à 479, nous n'avons pas besoin de nombres flottants ni de nombres négatifs. :)

De la façon dont nous définissons l'écran orthogonal, (0,0) est situé en bas à gauche de l'écran. (640,480) va être en haut à droite de l'écran. 0 est le bord gauche de l'écran sur l'axe des X, 639 le bord droit de l'écran. 0 est le bord bas de l'écran sur l'axe des Y et 479 le bord haut de l'écran. Normalement, nous avons enlevé les coordonnées négatives. C'est aussi pratique pour les gens qui ne s'intéressent pas aux perspectives et qui préfèrent travailler avec des pixels plutôt qu'avec des unités. :)

glTexCoord2f(cx,1-cy-0.0625f);            // Coordonnées de texture (bas gauche)
glVertex2i(0,0);                // Coordonnées du vecteur (bas gauche)

La prochaine coordonnée de texture est à 1/16 sur la droite de la coordonnée précédente (exactement la largeur d'un caractère). Donc cela va être le point en bas à droite de la texture.

glTexCoord2f(cx+0.0625f,1-cy-0.0625f);        // Coordonnées de texture (bas droit)
glVertex2i(16,0);                // Coordonnées du vecteur (bas droit)

La troisième coordonnée de texture reste sur le côté droit de notre caractère, mais se déplace vers le haut d'un 1/16 sur notre texture (exactement la hauteur d'un caractère). Cela va être le point en haut à droite de notre caractère.

glTexCoord2f(cx+0.0625f,1-cy);            // Coordonnées de texture (haut droit)
glVertex2i(16,16);                // Coordonnées du vecteur (haut droit)

Finalement, nous nous déplaçons sur la gauche pour notre dernière coordonnée, en haut à gauche de notre caractère.

glTexCoord2f(cx,1-cy);                // Coordonnées de texture (haut gauche)
glVertex2i(0,16);                // Coordonnées du vecteur (haut gauche)
glEnd();                        // Fin de la construction du carré (caractère)

Finalement, nous nous déplaçons de dix pixels sur la droite, nous plaçant à la droite de notre texture. Si nous ne nous déplacions pas, les lettres seraient dessinées les unes sur les autres. Parce que notre police est si étroite, nous ne voulons pas nous déplacer de 16 pixels sur la droite. Si nous le faisions, il y aurait de grands espaces entre chaque lettre. Se déplacer de seulement 10 pixels élimine ces espaces.

glTranslated(10,0,0);                    // Se déplace sur la droite du caractère
glEndList();                            // Fin de la construction de la liste d'affichage
}                                    // Boucle jusqu'à la fin de la construction des 256 listes
}

La partie suivante de code est la même que dans les autres tutoriels sur les polices, libérant les listes d'affichage avant la fin du programme. Toutes les 256 listes d'affichage en commençant à base vont être détruites. (Bonne chose à faire !)

GLvoid KillFont(GLvoid)                                // Détruit la police de la mémoire
{
glDeleteLists(base,256);                        // Détruit toutes les 256 listes d'affichage
}

La prochaine section de code est là où tout notre affichage est réalisé. Pratiquement tout est nouveau donc je vais essayer d'expliquer chaque ligne en détail. Juste une petite note : beaucoup de choses peuvent être ajoutées à ce code, comme le support des variables, la taille des caractères, l'espacement, et aussi nombre de vérifications pour restaurer les paramètres dans leur état initial, d'avant l'affichage.

glPrint() prend trois paramètres. Le premier est la position sur l'axe des X sur l'écran (la position de gauche à droite). Le suivant est la position sur l'axe des Y sur l'écran (de haut en bas… où 0 est le bas). Puis nous avons notre chaîne de caractères (le texte que nous voulons afficher), et finalement une variable appelée 'set'. Si vous regardez le Bitmap que Giuseppe D'Agata a fait, vous remarquerez qu'il y a deux séries de caractères. La première série contient les caractères normaux, et la deuxième les caractères en italique. Si set est à 0, la première série de caractères est sélectionnée. Si set est à 1 ou plus grand, la seconde série de caractères est sélectionnée.

GLvoid glPrint(GLint x, GLint y, char *string, int set)                // Où l'affichage se passe
{

La première chose que nous avons à faire est de s'assurer que set est à 0 ou 1. Si set est plus grand que 1, nous le mettrons à 1.

if (set>1)                                // set plus grand que 1
{
set=1;                                // Alors, nous le mettons à 1
}

Maintenant nous sélectionnons notre texture de la police. Nous le faisons juste dans le cas où une autre texture aurait été sélectionnée avant d'appeler la fonction d'affichage de texte.

glBindTexture(GL_TEXTURE_2D, texture[0]);                // Sélectionne notre police de la texture

Maintenant, nous désactivons le test de profondeur. La raison pour laquelle je fais ceci est pour avoir le fondu (blending) fonctionnant correctement. Si vous ne désactivez pas le test de profondeur, le texte peut être caché par quelque chose, ou le fondu ne sera pas correct. Si vous ne prévoyez pas de fondu pour le texte (donc que les espaces noirs ne soient pas visibles autour des lettres) vous pouvez laisser le test de profondeur activé.

glDisable(GL_DEPTH_TEST);                        // Désactive le test de profondeur

Les prochaines lignes sont très importantes ! Nous sélectionnons notre matrice de projection. Juste après cela, nous utilisons la commande appelée glPushMatrix. glPushMatrix() sauvegarde la matrice courante (projection). Un peu comme le bouton de mémoire d'une calculatrice.

glMatrixMode(GL_PROJECTION);                        // Sélectionne la matrice de projection
glPushMatrix();                                // Sauvegarde la matrice de projection

Maintenant que notre matrice de projection a été sauvegardée, nous réinitialisons la matrice et nous définissons notre écran orthogonal. Le premier et le troisième nombre (0) représentent le coin en bas à gauche de l'écran. Nous pouvions mettre le côté gauche à -640 si nous le voulions, mais pourquoi vouloir travailler avec des nombres négatifs si nous n'en avons pas besoin ? Le second et quatrième paramètres représentent le haut droit de l'écran. Il est sage de faire correspondre ces valeurs avec la résolution que vous utilisez. Il n'y a pas de profondeur donc nous mettons les valeurs de Z à -1 et 1.

glLoadIdentity();                            // Réinitialise la matrice de projection
glOrtho(0,640,0,480,-1,1);                        // Met en place l'écran orthogonal

Maintenant nous sélectionnons la matrice modelview, et nous sauvegardons les paramètres courants avec glPushMatrix(). Nous réinitialisons alors la matrice modelview pour pouvoir travailler avec en utilisant la vue orthogonale.

glMatrixMode(GL_MODELVIEW);                        // Sélectionne la matrice modelview
glPushMatrix();                                // Sauvegarde la matrice modelview
glLoadIdentity();                            // Réinitialise la matrice modelview

Avec nos paramètres de perspective sauvegardés et notre écran orthogonal défini, nous pouvons maintenant dessiner notre texte. Nous commençons par nous déplacer à la position à laquelle nous voulons dessiner notre texte sur l'écran. Nous utilisons glTranslated() à la place de glTranslatef() parce que nous travaillons avec les pixels, donc les valeurs décimales ne sont pas importantes. Après tout, vous ne pouvez pas avoir la moitié d'un pixel. :)

glTranslated(x,y,0);                            // Positionne le texte (0,0 - bas gauche)

La ligne ci-dessous va sélectionner la série de la police à utiliser. Si nous voulions la seconde série , nous ajouterions 128 à la base de la liste d'affichage (128 est la moitié de 256 caractères). En ajoutant 128 nous sautons les 128 premiers caractères.

glListBase(base-32+(128*set));                        // Sélectionne la série de la police (0 ou 1)

Maintenant, tout ce qui nous reste à faire est l'affichage des lettres à l'écran. Nous le faisons de la même façon que ce que nous avions fait dans les autres tutoriels sur les polices. Nous utilisons glCallLists(). strlen(string) est la longueur de notre chaîne de caractères (combien de caractères nous voulons dessiner), GL_UNSIGNED_BYTE signifie que chaque caractère est représenté par un unsigned byte (un octet peut être n'importe quelle valeur entre 0 et 255. Finalement, string contient le texte actuel à afficher à l'écran.

glCallLists(strlen(string),GL_UNSIGNED_BYTE,string);            // Écrit le texte à l'écran

Tout ce qui reste à faire est de remettre la vue en mode perspective. Nous sélectionnons la matrice de projection et utilisons glPopMatrix() pour rappeler les paramètres qui ont été sauvegardés avec glPushMatrix(). C'est important de restaurer les choses dans l'ordre inverse de la sauvegarde.

glMatrixMode(GL_PROJECTION);                        // Sélectionne la matrice de projection
glPopMatrix();                                // Restaure l'ancienne projection de matrice

Maintenant nous sélectionnons la matrice modelview, et nous faisons la même chose. Nous utilisons glPopMatrix() pour restaurer la matrice modelview aux valeurs qui étaient avant que nous définissions l'affichage orthogonal.

glMatrixMode(GL_MODELVIEW);                        // Sélectionne la matrice modelview
glPopMatrix();                                // Restaure l'ancienne matrice de projection

Finalement, nous activons le test de profondeur. Si vous ne l'avez pas désactivé précédemment, vous n'avez pas besoin de cette ligne.

glEnable(GL_DEPTH_TEST);                        // Active le test de profondeur
}

Rien n'a changé dans ReSizeGLScene() donc nous passons directement à InitGL().

int InitGL(GLvoid)                                // Toute la configuration d'OpenGL se met ici
{

Nous passons à notre code de construction des textures. Si la construction échoue pour une raison quelconque, nous retournons FALSE. Cela permet à notre programme de savoir qu'une erreur est arrivée et se fermer proprement.

if (!LoadGLTextures())                            // Va dans notre routine de chargement de texture
{
return FALSE;                            // Si la texture n'a pas été chargée, retourne FALSE
}

S'il n'y a pas eu d'erreur, nous passons au code de construction de la police. Peu de choses peuvent mal se passer lorsque nous construisons notre police donc nous ne vérifions pas les erreurs.

BuildFont();                                // Construction de la police

Maintenant, faisons notre initialisation classique d'OpenGL. Nous définissons la réinitialisation de la couleur du fond à noir, la réinitialisation du tampon de profondeur à 1.0. Nous choisissons le mode de test de profondeur, ainsi que le mode de fondu. Nous activons les ombres douces, et finalement nous activons l'application de textures 2D.

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);                    // Fond d'écran noir
glClearDepth(1.0);                            // Configuration du tampon de profondeur
glDepthFunc(GL_LEQUAL);                            // Le type de test de profondeur à faire
glClearDepth(1.0f);                            // Configuration du tampon de profondeur
glBlendFunc(GL_SRC_ALPHA,GL_ONE);                    // Sélectionne le type de fondu
glShadeModel(GL_SMOOTH);                        // Active les ombres douces
glEnable(GL_TEXTURE_2D);                        // Active le mappage de texture 2D
return TRUE;                                // Initialisation OK
}

Le code de la section ci-dessous va créer notre scène. Nous dessinons les objets 3D en premier puis le texte pour qu'il apparaisse au-dessus des objets 3D, à la place d'avoir les objets 3D sur le texte. La raison pour laquelle je décide d'ajouter des objets 3D est pour vous montrer que la perspective et la vue orthogonale peuvent être utilisées en même temps.

int DrawGLScene(GLvoid)                                // Ici  nous faisons tous les dessins
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // Efface l'écran et le tampon de profondeur
glLoadIdentity();                            // Réinitialise la matrice modelview

Nous sélectionnons notre texture bumps.bmp pour construire notre petit objet 3D. Nous nous déplaçons de cinq unités dans l'écran pour pouvoir voir l'objet 3D. Nous le tournons selon l'axe des Z de 45 degrés. Cela va faire tourner notre carré de 45 degrés, dans le sens des aiguilles d'une montre et faire que le carré ressemblera plus à un diamant qu'à un carré.

glBindTexture(GL_TEXTURE_2D, texture[1]);                // Sélectionne notre deuxième texture
glTranslatef(0.0f,0.0f,-5.0f);                        // Déplace de cinq unités dans l'écran
glRotatef(45.0f,0.0f,0.0f,1.0f);                    // Tourne sur l'axe des Z de 45 degrés (sens des aiguilles d'une montre)

Après avoir effectué la rotation de 45 degrés, nous tournons l'objet selon l'axe des X et des Y de 30 fois la variable cnt1. Cela fait que notre objet tourne comme un diamant tournant sur un point.

glRotatef(cnt1*30.0f,1.0f,1.0f,0.0f);                    // Tourne sur l'axe des X et Y de cnt1 * 30 (gauche à droite)

Nous désactivons le fondu (nous voulons que l'objet 3D apparaisse solide), et nous définissons la couleur à blanc. Puis nous dessinons un simple carré avec une texture.

glDisable(GL_BLEND);                            // Désactive le fondu avant de dessiner en 3D
glColor3f(1.0f,1.0f,1.0f);                        // Blanc
glBegin(GL_QUADS);                            // Dessine notre premier rectangle avec une texture
glTexCoord2d(0.0f,0.0f);                    // Premières coordonnées de texture
glVertex2f(-1.0f, 1.0f);                    // Premier vertex
glTexCoord2d(1.0f,0.0f);                    // Secondes coordonnées de texture
glVertex2f( 1.0f, 1.0f);                    // Second vertex
glTexCoord2d(1.0f,1.0f);                    // Troisièmes coordonnées de texture
glVertex2f( 1.0f,-1.0f);                    // Troisième vertex
glTexCoord2d(0.0f,1.0f);                    // Quatrièmes coordonnées de texture
glVertex2f(-1.0f,-1.0f);                    // Quatrième vertex
glEnd();                                // Fin du dessin du premier rectangle

Tout de suite après avoir dessiné le premier carré, nous effectuons une rotation de 90 degrés sur les axes X et Y. Puis nous dessinons un nouveau carré. Le second carré va passer au travers du milieu du premier, créant une jolie forme.

glRotatef(90.0f,1.0f,1.0f,0.0f);                    // Rotation sur les axes X et Y de 90 degrés (gauche à droite)
glBegin(GL_QUADS);                            // Dessin du second carré texturé
glTexCoord2d(0.0f,0.0f);                    // Premières coordonnées de texture
glVertex2f(-1.0f, 1.0f);                    // Premier vertex
glTexCoord2d(1.0f,0.0f);                    // Secondes coordonnées de texture
glVertex2f( 1.0f, 1.0f);                    // Second vertex
glTexCoord2d(1.0f,1.0f);                    // Troisièmes coordonnées de texture
glVertex2f( 1.0f,-1.0f);                    // Troisième vertex
glTexCoord2d(0.0f,1.0f);                    // Quatrièmes coordonnées de texture
glVertex2f(-1.0f,-1.0f);                    // Quatrième vertex
glEnd();                                // Fin du dessin du premier carré

Après le dessin des deux carrés texturés, nous activons le fondu, et nous dessinons le texte.

glEnable(GL_BLEND);                            // Activation du fondu
glLoadIdentity();                            // Réinitialisation de la vue

Nous utilisons la même coloration fantaisiste que dans les autres tutoriels. La couleur est changée progressivement en suivant la position du mouvement du texte sur l'écran.

// Coloration basée sur la position du texte
glColor3f(1.0f*float(cos(cnt1)),1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)));

Ensuite nous dessinons notre texte. Nous utilisons toujours glPrint(). Le premier paramètre est la position sur les X. Le second paramètre la position sur les Y. Le troisième paramètre '"NeHe") est le texte à écrire à l'écran, et le dernier paramètre est la série de caractères à utiliser (0 - normal, 1 - italique).

Comme vous l'avez certainement deviné, nous déplaçons le texte sur l'écran en utilisant COS et SIN, suivant les deux compteurs cnt1 et cnt2. Si vous ne comprenez pas ce que SIN et COS font, revenez en arrière et lisez les tutoriels précédents.

glPrint(int((280+250*cos(cnt1))),int(235+200*sin(cnt2)),"NeHe",0);    // Affiche le texte OpenGL à l'écran

glColor3f(1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)),1.0f*float(cos(cnt1)));
glPrint(int((280+230*cos(cnt2))),int(235+200*sin(cnt1)),"OpenGL",1);    // Affiche le texte OpenGL à l'écran

Nous définissons la couleur à bleu foncé et écrivons le nom de l'auteur en bas de l'écran. Nous allons écrire son nom sur l'écran une deuxième fois en utilisant des lettres blanches. Les lettres blanches sont un peu décalées sur la droite par rapport aux lettres bleues. Cela crée un effet d'ombre. (Si le fondu n'était pas désactivé, l'effet ne fonctionnerait pas.)

glColor3f(0.0f,0.0f,1.0f);                        // Définit la couleur à bleu
glPrint(int(240+200*cos((cnt2+cnt1)/5)),2,"Giuseppe D'Agata",0);    // Affiche le texte à l'écran

glColor3f(1.0f,1.0f,1.0f);                        // Définit la couleur à blanc
glPrint(int(242+200*cos((cnt2+cnt1)/5)),2,"Giuseppe D'Agata",0);    // Affiche le texte à l'écran

La dernière chose à faire est d'augmenter les deux compteurs à différentes vitesses. Cela fait se déplacer le texte, et tourner l'objet 3D.

cnt1+=0.01f;                                // Augmente le premier compteur
cnt2+=0.0081f;                                // Augmente le second compteur
return TRUE;                                // Tout s'est bien passé
}

Le code dans KillGLWindow(), CreateGLWindow() et WndProc() n'a pas changé donc nous le passons.

int WINAPI WinMain(    HINSTANCE    hInstance,                // Instance
HINSTANCE    hPrevInstance,                // Instance précédente
LPSTR        lpCmdLine,                // Paramètres de la ligne de commande
int        nCmdShow)                // L'état d'affichage de la fenêtre
{
MSG        msg;                            // Structure d'un message Windows
BOOL    done=FALSE;                            // Variable pour gérer la boucle infinie

// Demander à l'utilisateur quel mode d'exécution (fenêtre ou plein écran) il préfère
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE;                        // Mode fenêtré
}

Le titre de la fenêtre a changé.

// Créer notre fenêtre OpenGL
if (!CreateGLWindow("NeHe & Giuseppe D'Agata's 2D Font Tutorial",640,480,16,fullscreen))
{
return 0;                            // Quitter si la fenêtre n'a pas été créée
}

while(!done)                                // Boucle qui s'exécute tant que done=TRUE
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))            // Y a-t-il un message en attente ?
{
if (msg.message==WM_QUIT)                // Avons-nous reçu un message pour quitter ?
{
done=TRUE;                    // Si c'est le cas, on met done a TRUE
}
else                            // Sinon on gère les messages
{
TranslateMessage(&msg);                // Traduire le message
DispatchMessage(&msg);                // Envoyer le message
}
}
else                                // S'il n'y a pas de message
{
// Dessine la scène. Vérifie si Echap a été appuyée
if ((active && !DrawGLScene()) || keys[VK_ESCAPE])    // Active? Y avait-il un message reçu pour quitter ?
{
done=TRUE;                    // Échap ou DrawGLScene ont signalé qu'il faut quitter
}
else                            // On ne quitte pas, mise à jour de l'écran
{
SwapBuffers(hDC);                // Échange des buffers (Double Buffering)
}
}
}

// Fermeture

La dernière chose à faire est d'ajouter KillFont() à la fin de KillGLWindow() tout comme je le montre ci-dessous. C'est important d'ajouter cette ligne. Cela nettoie les choses avant de sortir de notre programme.

if (!UnregisterClass("OpenGL",hInstance))                // Peut-on désenregistrer la classe
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL;                            // Définit hInstance à NULL
}

KillFont();                                // Détruit la police
}

Je pense que je peux officiellement dire que mon site enseigne toutes les possibilités pour afficher du texte à l'écran {sourire}. Globalement, je pense que c'est un assez bon tutoriel. Le code peut être utilisé sur n'importe quel ordinateur qui peut exécuter OpenGL, il est facile à utiliser et écrire du texte à l'écran en utilisant cette méthode nécessite très peu de puissance du processeur.

J'aimerais remercier Giuseppe D'Agata pour la version originale du tutoriel. Je l'ai grandement modifié, converti au nouveau code de base, mais sans son code, je ne l'aurais probablement pas écrit. Sa version a quelques options supplémentaires, comme l'espacement des caractères, etc., mais je l'ai remplacé par un superbe objet 3D {sourire}.

J'espère que tout le monde va apprécier ce tutoriel. Si vous avez des questions, envoyez un courriel à Giuseppe D'Agata ou à moi-même.

Giuseppe D'Agata

Jeff Molofee (NeHe)

III - Téléchargements

Compte tenu du nombre de versions de codes sources pour les tutoriels NeHe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à les comprendre :

- Borland C++ Builder 6 (Conversion by Christian Kindahl) ;

- C# (Conversion by Brian Holley) ;

- Code Warrior 5.3 (Conversion by Scott Lupton) ;

- Cygwin (Conversion by Stephan Ferraro) ;

- Delphi (Conversion by Michal Tucek) ;

- Dev C++ (Conversion by Dan) ;

- Euphoria (Conversion by Evan Marshall) ;

- Game GLUT (Conversion by Milikas Anastasios) ;

- Irix/GLUT (Conversion by Rob Fletcher) ;

- Java (Conversion by Jeff Kirby) ;

- JoGL (Conversion by Nicholas Campbell) ;

- LCC Win32 (Conversion by Robert Wishlaw) ;

- Linux/GLX (Conversion by Mihael Vrbanec) ;

- Linux/SDL (Conversion by Ti Leggett) ;

- LWJGL (Conversion by Mark Bernard) ;

- Mac OS (Conversion by Anthony Parker) ;

- Mac OS X/Cocoa (Conversion by Bryan Blackburn) ;

- MASM (Conversion by Greg Helps) ;

- Visual C++ / OpenIL (Conversion by Denton Woods) ;

- Pelles C (Conversion by Pelle Orinius) ;

- Visual Basic (Conversion by Ross Dawson) ;

- Visual C++ ;

- Visual Studio .NET (Conversion by Grant James).

IV - Remerciements

Merci Winjerome pour sa relecture ainsi que ClaudeLELOUP pour ses corrections.

V - Liens

Sommaire.

Tutoriel précédent : Effet de brouillard réaliste.

Tutoriel suivant : Quadriques