Dessinez du texte dans OpenGL ES

Je suis en train de développer un petit jeu OpenGL pour la plate-forme Android et je me demande s’il existe un moyen facile de rendre du texte par-dessus le cadre rendu (comme un HUD avec le score du joueur, etc.). Le texte devrait également utiliser une police personnalisée.

J’ai vu un exemple d’utilisation d’une vue en superposition, mais je ne sais pas si je veux le faire car je souhaiterais peut-être porter le jeu sur d’autres plates-formes ultérieurement.

Des idées?

Le SDK Android ne comporte aucun moyen simple de dessiner du texte sur les vues OpenGL. Vous laissant avec les options suivantes.

  1. Placez un TextView sur votre SurfaceView. C’est lent et mauvais, mais l’approche la plus directe.
  2. Rendre les chaînes communes aux textures et dessiner simplement ces textures. C’est de loin le plus simple et le plus rapide, mais le moins flexible.
  3. Roll-vous-propre code de rendu de texte basé sur une image-object. Probablement deuxième meilleur choix si 2 n’est pas une option. Un bon moyen de vous mettre à l’aise, mais notez que même si cela semble simple (et que les fonctionnalités de base le sont), il devient de plus en plus difficile d’append de nouvelles fonctionnalités (alignement de texture, sauts de ligne, fonts à largeur variable, etc. ) – si vous prenez cette voie, rendez-la aussi simple que possible!
  4. Utilisez une bibliothèque standard / open-source. Il y en a quelques-uns si vous pratiquez la chasse sur Google, le problème est de les intégrer et de les faire fonctionner. Mais au moins, une fois que vous faites cela, vous aurez toute la flexibilité et la maturité qu’ils offrent.

Le rendu du texte dans une texture est plus simple que ce que la démo Sprite Text lui donne, l’idée de base est d’utiliser la classe Canvas pour effectuer un rendu sur un bitmap et de transmettre le bitmap à une texture OpenGL:

// Create an empty, mutable bitmap Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_4444); // get a canvas to paint over the bitmap Canvas canvas = new Canvas(bitmap); bitmap.eraseColor(0); // get a background image from resources // note the image format must match the bitmap format Drawable background = context.getResources().getDrawable(R.drawable.background); background.setBounds(0, 0, 256, 256); background.draw(canvas); // draw the background to our bitmap // Draw the text Paint textPaint = new Paint(); textPaint.setTextSize(32); textPaint.setAntiAlias(true); textPaint.setARGB(0xff, 0x00, 0x00, 0x00); // draw the text centered canvas.drawText("Hello World", 16,112, textPaint); //Generate one texture pointer... gl.glGenTextures(1, textures, 0); //...and bind it to our array gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); //Create Nearest Filtered Texture gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); //Different possible texture parameters, eg GL10.GL_CLAMP_TO_EDGE gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT); //Use the Android GLUtils to specify a two-dimensional texture image from our bitmap GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); //Clean up bitmap.recycle(); 

J’ai écrit un tutoriel qui développe la réponse de JVitela . Fondamentalement, il utilise la même idée, mais au lieu de rendre chaque chaîne à une texture, il restitue tous les caractères d’un fichier de police en une texture et l’utilise pour permettre un rendu de texte dynamic sans ralentissement supplémentaire (une fois l’initialisation terminée) .

Le principal avantage de ma méthode, comparé aux divers générateurs d’atlas de fonts, est que vous pouvez envoyer de petits fichiers de fonts (.ttf .otf) avec votre projet au lieu d’envoyer de grandes images bitmap pour chaque variation de taille et de police. Il peut générer des fonts de qualité parfaite à n’importe quelle résolution en utilisant uniquement un fichier de police 🙂

Le tutoriel comprend un code complet pouvant être utilisé dans tout projet 🙂

Jetez un coup d’oeil à CBFG et au port Android du code de chargement / rendu. Vous devriez pouvoir déposer le code dans votre projet et l’utiliser immédiatement.

CBFG – http://www.codehead.co.uk/cbfg

Chargeur Android – http://www.codehead.co.uk/cbfg/TexFont.java

Selon ce lien:

http://code.neenbedankt.com/how-to-render-an-android-view-to-a-bitmap

Vous pouvez rendre toute vue dans un bitmap. Cela vaut probablement la peine de supposer que vous pouvez mettre en forme une vue selon vos besoins (y compris du texte, des images, etc.), puis la convertir en bitmap.

En utilisant le code de JVitela ci-dessus, vous devriez pouvoir utiliser cette bitmap en tant que texture OpenGL.

J’ai regardé l’exemple de texte de sprite et il est terriblement compliqué pour une telle tâche, j’ai pensé à un rendu à une texture aussi, mais je m’inquiète de la performance qui pourrait en résulter. Je devrais peut-être plutôt partir avec une vue et m’inquiéter du portage quand il est temps de traverser ce pont 🙂

Regardez l’exemple “Texte de Sprite” dans les exemples de GLSurfaceView .

Si vous insistez pour utiliser GL, vous pouvez rendre le texte sur des textures. En supposant que la plupart des HUD soient relativement statiques, vous ne devriez pas avoir à charger les textures trop souvent dans la mémoire de textures.

Jetez un coup d’oeil à CBFG et au port Android du code de chargement / rendu. Vous devriez pouvoir déposer le code dans votre projet et l’utiliser immédiatement.

  1. CBFG

  2. Chargeur Android

J’ai des problèmes avec cette implémentation. Il affiche un seul caractère, quand j’essaie de changer la taille du bitmap de la police (j’ai besoin de lettres spéciales)

IMHO il y a trois raisons d’utiliser OpenGL ES dans un jeu:

  1. Évitez les différences entre les plates-formes mobiles en utilisant un standard ouvert;
  2. Avoir plus de contrôle sur le processus de rendu;
  3. Bénéficier du parallel processing GPU;

Le dessin de texte est toujours un problème dans la conception de jeux, car vous dessinez des choses, vous ne pouvez donc pas avoir l’apparence d’une activité commune, avec des widgets, etc.

Vous pouvez utiliser une structure pour générer des fonts Bitmap à partir de fonts TrueType et les rendre. Tous les frameworks que j’ai vus fonctionnent de la même manière: générez les coordonnées de sumt et de texture pour le texte en temps de dessin. Ce n’est pas l’utilisation la plus efficace d’OpenGL.

Le meilleur moyen consiste à allouer des tampons distants (objects de mémoire tampon de sumts – VBO) pour les sumts et les textures au début du code, en évitant les opérations de transfert de mémoire paresseuses au moment du tirage.

Gardez à l’esprit que les joueurs n’aiment pas lire le texte, vous n’écrirez donc pas un long texte généré dynamicment. Pour les étiquettes, vous pouvez utiliser des textures statiques, en laissant du texte dynamic pour l’heure et la partition, et les deux sont numériques avec quelques caractères.

Donc, ma solution est simple:

  1. Créer une texture pour les étiquettes et les avertissements courants;
  2. Créer une texture pour les nombres 0-9, “:”, “+” et “-“. Une texture pour chaque caractère;
  3. Générez des VBO distants pour toutes les positions de l’écran. Je peux rendre du texte statique ou dynamic dans ces positions, mais les VBO sont statiques;
  4. Générez un seul VBO de texture, car le texte est toujours rendu dans un sens;
  5. Au moment du tirage au sort, je rend le texte statique;
  6. Pour le texte dynamic, je peux regarder la position VBO, obtenir la texture des caractères et la dessiner, un personnage à la fois.

Les opérations de dessin sont rapides si vous utilisez des tampons statiques distants.

Je crée un fichier XML avec des positions d’écran (basées sur le pourcentage de diagonale de l’écran) et des textures (statiques et caractères), puis je charge ce XML avant le rendu.

Pour obtenir un taux de FPS élevé, vous devez éviter de générer des VBO au moment du tirage.

Je cherchais ceci depuis quelques heures, c’était le premier article que j’ai rencontré et bien qu’il ait la meilleure réponse, les réponses les plus populaires ne sont pas à la hauteur. Certainement pour ce dont j’avais besoin. Les réponses de weichsel et de shakazed étaient juste sur le bouton mais un peu obscurcies dans les articles. Pour vous faire droit au projet. Ici: créez simplement un nouveau projet Android basé sur un échantillon existant. Choisissez ApiDemos:

Regardez sous le dossier source

ApiDemos/src/com/example/android/apis/graphics/spritetext

Et vous trouverez tout ce dont vous avez besoin.

Pour le texte statique :

  • Générez une image avec tous les mots utilisés sur votre PC (par exemple avec GIMP).
  • Chargez-le comme une texture et utilisez-le comme matériau pour un plan.

Pour un texte long qui doit être mis à jour de temps en temps:

  • Laissez Android dessiner sur un canevas bitmap (solution de JVitela).
  • Chargez ceci comme matériel pour un avion.
  • Utilisez des coordonnées de texture différentes pour chaque mot.

Pour un numéro (formaté 00.0):

  • Générer une image avec tous les nombres et un point.
  • Chargez ceci comme matériel pour un avion.
  • Utilisez ci-dessous shader.
  • Dans votre événement onDraw, mettez à jour uniquement la variable de valeur envoyée au shader.

     precision highp float; precision highp sampler2D; uniform float uTime; uniform float uValue; uniform vec3 iResolution; varying vec4 v_Color; varying vec2 vTextureCoord; uniform sampler2D s_texture; void main() { vec4 fragColor = vec4(1.0, 0.5, 0.2, 0.5); vec2 uv = vTextureCoord; float devisor = 10.75; float digit; float i; float uCol; float uRow; if (uv.y < 0.45) { if (uv.x > 0.75) { digit = floor(uValue*10.0); digit = digit - floor(digit/10.0)*10.0; i = 48.0 - 32.0 + digit; uRow = floor(i / 10.0); uCol = i - 10.0 * uRow; fragColor = texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-1.5) / devisor, uRow / devisor) ); } else if (uv.x > 0.5) { uCol = 4.0; uRow = 1.0; fragColor = texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-1.0) / devisor, uRow / devisor) ); } else if (uv.x > 0.25) { digit = floor(uValue); digit = digit - floor(digit/10.0)*10.0; i = 48.0 - 32.0 + digit; uRow = floor(i / 10.0); uCol = i - 10.0 * uRow; fragColor = texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-0.5) / devisor, uRow / devisor) ); } else if (uValue >= 10.0) { digit = floor(uValue/10.0); digit = digit - floor(digit/10.0)*10.0; i = 48.0 - 32.0 + digit; uRow = floor(i / 10.0); uCol = i - 10.0 * uRow; fragColor = texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-0.0) / devisor, uRow / devisor) ); } else { fragColor = vec4(0.0, 0.0, 0.0, 0.0); } } else { fragColor = vec4(0.0, 0.0, 0.0, 0.0); } gl_FragColor = fragColor; } 

Le code ci-dessus fonctionne pour un atlas de texture où les nombres commencent à 0 à la 7ème colonne de la 2ème ligne de l’atlas des fonts (texture).

Reportez-vous à https://www.shadertoy.com/view/Xl23Dw pour une démonstration (avec une texture incorrecte)