Navigation▲
Tutoriel précédent : la transparence |
Tutoriel suivant : extensions OpenGL |
I. Introduction▲
Dans ce tutoriel, on va apprendre à afficher un texte en 2D par-dessus le contenu 3D. Ici, ce sera un simple chronomètre :
II. Les fonctions▲
On va implémenter cette simple interface (dans common/text2D.h) :
void
initText2D(const
char
*
texturePath);
void
printText2D(const
char
*
text, int
x, int
y, int
size);
void
cleanupText2D();
Afin que le code fonctionne, que ce soit en 640 x 480 ou 1080p, x et y seront des coordonnées dans l'espace [0-800][0-600]. Le vertex shader adaptera les coordonnées pour correspondre à la taille de l'écran.
Regardez common/text2D.cpp pour voir l'implémentation complète.
III. La texture▲
initText2D lit simplement une texture et un couple de shaders. Il n'y a rien de spécial là-dedans, mais voyez à quoi ressemble la texture :
Cette texture a été générée avec CBFG, un des nombreux outils pouvant générer une texture à partir d'une police de caractères. Puis la texture a été importée dans Paint.NET où j'ai ajouté un fond rouge (seulement pour des raisons de visualisation : là où c'est rouge, cela devrait être transparent).
Le but de printText2D sera donc de générer des rectangles avec les positions écran appropriées et les coordonnées de texture.
IV. L'affichage▲
On doit remplir ces tampons :
std::
vector<
glm::
vec2>
vertices;
std::
vector<
glm::
vec2>
UVs;
Pour chaque caractère, on calcule les coordonnées des quatre sommets définissant le rectangle et on ajoute les deux triangles :
for
( unsigned
int
i=
0
; i<
length ; i++
){
glm::
vec2 vertex_up_left =
glm::
vec2( x+
i*
size , y+
size );
glm::
vec2 vertex_up_right =
glm::
vec2( x+
i*
size+
size, y+
size );
glm::
vec2 vertex_down_right =
glm::
vec2( x+
i*
size+
size, y );
glm::
vec2 vertex_down_left =
glm::
vec2( x+
i*
size , y );
vertices.push_back(vertex_up_left );
vertices.push_back(vertex_down_left );
vertices.push_back(vertex_up_right );
vertices.push_back(vertex_down_right);
vertices.push_back(vertex_up_right);
vertices.push_back(vertex_down_left);
Maintenant les coordonnées UV. La coordonnée du coin supérieur gauche est calculée comme suit :
char
character =
text[i];
float
uv_x =
(character%
16
)/
16.0
f;
float
uv_y =
(character/
16
)/
16.0
f;
Cela fonctionne (en quelque sorte - voyez ci-dessus) car le code ASCII pour A est 65.
65 % 16 = 1, donc A est sur la colonne numéro 1 (on commence à 0 !).
65 / 16 = 4, donc A est sur la ligne numéro 4 (c'est une division entière, donc ce n'est pas 4.0625 comme cela devrait l'être).
Les deux sont divisés par 16.0 pour rentrer dans l'échelle [0.0 - 1.0] nécessaire aux textures OpenGL.
Et maintenant, on doit faire une chose très proche de ce que nous faisions, mais pour les coordonnées de texture :
glm::
vec2 uv_up_left =
glm::
vec2( uv_x , 1.0
f -
uv_y );
glm::
vec2 uv_up_right =
glm::
vec2( uv_x+
1.0
f/
16.0
f, 1.0
f -
uv_y );
glm::
vec2 uv_down_right =
glm::
vec2( uv_x+
1.0
f/
16.0
f, 1.0
f -
(uv_y +
1.0
f/
16.0
f) );
glm::
vec2 uv_down_left =
glm::
vec2( uv_x , 1.0
f -
(uv_y +
1.0
f/
16.0
f) );
UVs.push_back(uv_up_left );
UVs.push_back(uv_down_left );
UVs.push_back(uv_up_right );
UVs.push_back(uv_down_right);
UVs.push_back(uv_up_right);
UVs.push_back(uv_down_left);
}
Le reste est habituel : liaison des tampons, remplissage, sélection du shader program, liaison de la texture, activation/liaison/configuration des attributs de sommets, activation du mélange et appel de la fonction glDrawArrays. Hourra ! Vous avez fini.
Les coordonnées sont générées sur l'échelle [0-800][0-600]. En d'autres mots, il n'y a PAS BESOIN de matrices ici. Le vertex shader doit juste les passer à l'échelle [-1,1][-1,1] avec une simple opération mathématique (cela aurait aussi pu être fait en C++).
void
main
(
){
// position de sortie du sommet, dans l'espace de découpage
// map [0..800][0..600] to [-1..1][-1..1]
vec2
vertexPosition_homoneneousspace =
vertexPosition_screenspace -
vec2
(
400
,300
); // [0..800][0..600] -> [-400..400][-300..300]
vertexPosition_homoneneousspace /=
vec2
(
400
,300
);
gl_Position
=
vec4
(
vertexPosition_homoneneousspace,0
,1
);
// coordonnées UV du sommet. Pas d'espace de coordonnées spécifique pour celles-ci.
UV =
vertexUV;
}
Le fragment shader ne fait que très peu de choses :
void
main
(
){
color =
texture
(
myTextureSampler, UV );
}
D'ailleurs, n'utilisez pas ce code en production, car il ne gère que l'alphabet latin. Ou alors ne vendez rien en Inde, Chine, Japon (ou même Allemagne, car il n'y a pas de ß dans cette image). Cette texture marchera principalement en France (remarquez la présence des é, à, ç, etc.) car elle a été générée avec ma locale. Et soyez attentionné lors de l'adaptation du code des autres tutoriels ou lorsque vous avez recours à des bibliothèques, la plupart d'entre eux utilisent OpenGL 2 qui n'est pas compatible. Malheureusement, je ne connais pas de bibliothèque qui gère UTF-8.
À propos de cela, vous devez lire The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) (le minimum absolu que chaque développeur de logiciels devrait absolument connaître sur l'Unicode et les ensembles de caractères (pas d'excuse !) de Joel Spolsky.
Regardez aussi l'article de Valve si vous avez besoin de grand texte.
V. Remerciements▲
Cet article est une traduction autorisée dont le texte original peut être trouvé sur opengl-tutorial.org.
Navigation▲
Tutoriel précédent : la transparence |
Tutoriel suivant : extensions OpenGL |