web-dev-qa-db-fra.com

Dessiner du texte dans OpenGL ES

Je développe actuellement un petit jeu OpenGL pour la Android) et je me demande s’il existe un moyen simple de rendre le texte au-dessus du cadre affiché (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 tant que superposition, mais je ne sais pas si je souhaite le faire, car je souhaiterais peut-être porter le jeu sur d'autres plates-formes ultérieurement.

Des idées?

122
shakazed

Le SDK Android ne propose pas de méthode simple pour dessiner du texte sur des vues OpenGL. Il vous laisse les options suivantes.

  1. Placez un TextView sur votre SurfaceView. C'est lent et mauvais, mais c'est l'approche la plus directe.
  2. Rendez les chaînes communes en textures et dessinez simplement ces textures. C'est de loin le plus simple et le plus rapide, mais le moins flexible.
  3. Code de rendu de texte personnalisé basé sur un Sprite. Probablement le deuxième meilleur choix si 2 n'est pas une option. Un bon moyen de se mouiller les pieds, mais notez que bien que cela paraisse simple (et que les fonctionnalités de base le soient), cela devient de plus en plus difficile et complexe à mesure que vous ajoutez des fonctionnalités (alignement de la texture, traitement des sauts de ligne, des polices à largeur variable, etc.). ) - si vous prenez cet itinéraire, rendez-le aussi simple que possible!
  4. tilisez une bibliothèque standard/open-source. Il y en a quelques-uns si vous êtes à la recherche sur Google, la difficulté est de les intégrer et de les utiliser. Mais au moins, une fois que vous aurez fait cela, vous aurez toute la souplesse et la maturité qu’elles offrent.
100
Dave

Le rendu du texte en texture est plus simple que ne le donne la démo Sprite Text. L'idée de base est d'utiliser la classe Canvas pour restituer le rendu dans un bitmap, puis de le transmettre à 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, e.g. 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();
162
JVitela

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

Le principal avantage de ma méthode, par rapport aux différents générateurs d’atlas de polices, est qu’il est possible d’expédier de petits fichiers de polices (.ttf .otf) avec votre projet, au lieu de devoir envoyer de grandes images bitmap pour chaque variation et taille de police. Il peut générer des polices de qualité parfaite à n'importe quelle résolution en utilisant uniquement un fichier de polices :)

Le tutoriel inclut le code complet qui peut être utilisé dans n'importe quel projet :)

35
free3dom

Selon ce lien:

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

Vous pouvez rendre n'importe quelle vue en 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 restituer en bitmap.

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

8
JWGS

Jetez un coup d'œil à CBFG et au port Android du code de chargement/rendu). Vous devriez pouvoir insérer 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

7
Codehead

J'ai regardé l'exemple de texte Sprite et cela a l'air terriblement compliqué pour une telle tâche. J'ai aussi envisagé de rendre une texture, mais je suis inquiet des conséquences que cela pourrait avoir sur les performances. Je devrais peut-être avoir une vue à la place et m'inquiéter du portage lorsqu'il est temps de traverser ce pont :)

6
shakazed

Regardez l'exemple "Sprite Text" dans le exemples GLSurfaceView .

4
Thomas Zoechling

À mon humble avis, 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. Pour avoir plus de contrôle sur le processus de rendu;
  3. Bénéficier du traitement parallèle GPU;

Dessiner du texte est toujours un problème dans la conception de jeux, car vous dessinez des objets, vous ne pouvez donc pas avoir l’aspect d’une activité courante, avec des widgets, etc.

Vous pouvez utiliser un framework pour générer des polices Bitmap à partir de polices TrueType et les restituer. Tous les cadres que j'ai vus fonctionnent de la même manière: générer les coordonnées de sommet et de texture pour le texte en temps d’apparition. Ce n'est pas l'utilisation la plus efficace d'OpenGL.

Le meilleur moyen consiste à allouer des tampons distants (objets de sommets de sommets - VBO) pour les sommets et les textures au début du code, en évitant les opérations de transfert de mémoire paresseux au moment de l'établissement.

Gardez à l'esprit que les joueurs n'aiment pas lire du texte, vous n'écrirez donc pas un texte long généré de manière dynamique. Pour les étiquettes, vous pouvez utiliser des textures statiques, en laissant du texte dynamique pour le temps et le score, 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éez une texture pour les nombres 0 à 9, ":", "+" et "-". Une texture pour chaque personnage;
  3. Générez des VBO distants pour toutes les positions de l’écran. Je peux rendre du texte statique ou dynamique 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. Dans draw time, je rend le texte statique;
  6. Pour le texte dynamique, je peux jeter un coup d’œil à la position VBO, obtenir la texture du caractère et la dessiner, caractère par caractère.

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 (en fonction du pourcentage diagonal de l'écran) et des textures (statique et caractères), puis je charge ce fichier XML avant le rendu.

Pour obtenir un débit en FPS élevé, vous devez éviter de générer des VBO au moment de la publication.

4
cleuton

Si vous insistez pour utiliser GL, vous pouvez rendre le texte en textures. En supposant que la majeure partie du HUD soit relativement statique, vous ne devriez pas avoir à charger les textures trop souvent dans la mémoire de texture.

3
Tal Pressman

Jetez un coup d'œil à CBFG et au port Android du code de chargement/rendu). Vous devriez pouvoir insérer 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 n'affiche qu'un seul caractère. Lorsque j'essaie de modifier la taille du bitmap de la police (j'ai besoin de lettres spéciales), le tirage au complet échoue :(

3
Aetherna

Cela faisait quelques heures que je cherchais cela, c’était le premier article que j’ai découvert et bien qu’il ait la meilleure réponse, les réponses les plus populaires, à mon avis, sont erronées. Certainement pour ce dont j'avais besoin. Les réponses de Weichsel et de Shakazed étaient exactes au bouton mais un peu obscurcies dans les articles. Pour vous mettre droit au projet. Ici: Créez simplement un nouveau Android projet basé sur un exemple 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.

2
Justin

Pour texte statique :

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

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

  • Laissons Android dessiner sur un canevas bitmap (solution de JVitela).
  • Chargez ceci en tant que matériau pour un avion.
  • Utilisez différentes coordonnées de texture pour chaque mot.

Pour un nombre (formaté 00.0):

  • Générez une image avec tous les nombres et un point.
  • Chargez ceci en tant que matériau pour un avion.
  • Utilisez ci-dessous shader.
  • Dans votre événement onDraw, ne mettez à jour que 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 par 0 à la 7ème colonne de la 2e ligne de l’atlas de police (texture).

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

1
Pete