Dessiner du texte dans OpenGL ES

131

Je développe actuellement un petit jeu OpenGL pour la plate-forme Android et je me demande s'il existe un moyen simple de rendre du texte au-dessus du 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 utilisant une vue comme superposition, mais je ne sais pas si je veux faire cela car je pourrais vouloir porter le jeu sur d'autres plates-formes plus tard.

Des idées?

secoué
la source
jetez un œil à ce projet: code.google.com/p/rokon
whunmr
Regardez la façon dont libgdx fait cela via les polices Bitmap.
Robert Massaioli

Réponses:

103

Le SDK Android ne propose aucun moyen simple de dessiner du texte sur des 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. Transformez les chaînes courantes 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 basé sur un sprite. Probablement le deuxième meilleur choix si 2 n'est pas une option. Un bon moyen de vous mouiller les pieds, mais notez que même si cela semble simple (et que les fonctionnalités de base le sont), cela devient de plus en plus difficile et difficile à mesure que vous ajoutez plus de fonctionnalités (alignement de texture, gestion des sauts de ligne, des polices à largeur variable, etc. ) - si vous empruntez cette voie, rendez-la aussi simple que possible!
  4. Utilisez une bibliothèque standard / open source. Il y en a quelques-uns si vous recherchez sur Google, le plus difficile est de les intégrer et de les exécuter. Mais au moins, une fois que vous faites cela, vous aurez toute la flexibilité et la maturité qu'ils offrent.
Dave
la source
3
J'ai décidé d'ajouter une vue sur mon GLView, ce n'est peut-être pas le moyen le plus efficace de le faire, mais la vue n'est pas mise à jour très souvent, et cela me donne la possibilité d'ajouter la police que je souhaite. Merci pour toutes vos réponses!
shakazed
1
Comment puis-je convertir des chaînes courantes en textures et simplement dessiner ces textures? Merci.
VansFannel
1
VansFannel: utilisez simplement un programme de peinture pour mettre toutes vos chaînes dans une seule image, puis dans votre application, utilisez des décalages pour ne rendre que la partie de l'image qui contient la chaîne que vous voulez.
Dave
2
Ou consultez la réponse de JVitela ci-dessous pour une manière plus programmatique d'y parvenir.
Dave
4
La réponse de JVitela est meilleure. C'est ce que j'utilise actuellement. La raison pour laquelle vous passez d'Android vier + canvas standard à opengl est (entre autres) pour la vitesse. L'ajout d'une zone de texte sur votre sorte de opengl nie cela.
Shivan Dragon
166

Le rendu du texte en une texture est plus simple que ce à quoi ressemble la démo de Sprite Text, l'idée de base est d'utiliser la classe Canvas pour rendre un Bitmap, puis de passer 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, 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();
JVitela
la source
5
Cela m'a aidé à afficher au petit compteur fps sur mon application pour le débogage, merci!
stealthcopter
2
Vous pouvez l'utiliser pour générer toutes les lettres et tous les chiffres sous forme de textures lorsque vous chargez vos autres textures, puis les assemblez pour former des mots ou des nombres. Ensuite, ce ne sera pas moins efficace que toute autre texture gl.
twDuke
9
C'est très lent, cela va tuer les fps sur un jeu pour le texte qui change toujours (score, etc.), mais cela fonctionne bien pour les trucs semi-statiques (nom du joueur, nom du niveau, etc.).
led42
3
Je voudrais noter que le code de cette réponse n'est probablement qu'une démo et n'est pas optimisé quoi que ce soit! Veuillez optimiser / cache à votre manière.
Sherif elKhatib
1
Pourriez-vous fournir cela pour OpenGL ES 2.0?
illimité101
36

J'ai écrit un tutoriel qui développe la réponse publiée par JVitela . Fondamentalement, il utilise la même idée, mais au lieu de rendre chaque chaîne dans une texture, il rend tous les caractères d'un fichier de police vers une texture et l'utilise pour permettre un rendu de texte dynamique complet sans autres ralentissements (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 que vous pouvez expédier de petits fichiers de polices (.ttf .otf) avec votre projet au lieu d'avoir à expédier 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 police :)

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

free3dom
la source
Je suis actuellement à la recherche de cette solution et je suis sûr que je trouverai la réponse à temps, mais votre implémentation utilise-t-elle des allocations de temps d'exécution?
Nick Hartung
@Nick - Toutes les allocations (texture, tampons de vertex, etc.) sont effectuées lors de la création d'une instance de police, le rendu des chaînes à l'aide de l'instance de police ne nécessite aucune allocation supplémentaire. Ainsi, vous pouvez créer la police au «moment du chargement» sans autres allocations au moment de l'exécution.
free3dom le
Wow, excellent travail! Ceci est vraiment utile, en particulier dans les cas où votre texte change souvent.
mdiener le
C'est la meilleure réponse, en termes de performances, pour les applications qui doivent modifier fréquemment le texte lors de l'exécution. Je n'ai rien vu d'aussi beau que ça pour Android. Il pourrait cependant utiliser un port vers OpenGL ES.
greeble31
1
Si vous ne voulez pas vous occuper de l'alignement du texte, des sauts de ligne, etc., vous pouvez utiliser un TextView. Un TextView peut facilement être rendu dans un canevas. Les performances ne devraient pas être plus lourdes que l'approche donnée, vous n'avez besoin que d'une seule instance de TextView pour rendre tous les textes dont vous avez besoin. De cette façon, vous bénéficiez également d'un formatage HTML simple gratuitement.
Gena Batsyan le
8

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 page une vue selon vos besoins (y compris du texte, des images, etc.), puis la restituer sous forme de bitmap.

En utilisant le code de JVitela ci-dessus, vous devriez pouvoir utiliser ce Bitmap comme texture OpenGL.

JWGS
la source
Oui, je l'ai fait avec un MapView dans un jeu et je l'ai lié à une texture gl afin de combiner des cartes et opengl. Donc oui, toutes les vues ont onDraw (Canvas c) et vous pouvez passer n'importe quel canevas et lier n'importe quel canevas à n'importe quel bitmap.
HAMMeReD
6

J'ai regardé l'exemple de texte de sprite et cela semble terriblement compliqué pour une telle tâche, j'ai également envisagé de rendre une texture, mais je m'inquiète des performances que cela pourrait causer. Je devrais peut-être simplement aller avec une vue à la place et m'inquiéter du portage lorsqu'il est temps de traverser ce pont :)

secoué
la source
4

À 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 une norme ouverte;
  2. Pour avoir plus de contrôle sur le processus de rendu;
  3. Pour 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'apparence d'une activité commune, avec des widgets, etc.

Vous pouvez utiliser un framework pour générer des polices Bitmap à partir de polices TrueType et les rendre. Tous les frameworks que j'ai vus fonctionnent de la même manière: générer les coordonnées de sommet et de texture du texte au moment du dessin. Ce n'est pas l'utilisation la plus efficace d'OpenGL.

Le meilleur moyen est d'allouer des tampons distants (objets de tampon de sommet - VBO) pour les sommets et les textures au début du code, en évitant les opérations de transfert de mémoire paresseux dans le temps de dessin.

Gardez à l'esprit que les joueurs n'aiment pas lire de texte, vous n'écrirez donc pas un long texte généré dynamiquement. Pour les étiquettes, vous pouvez utiliser des textures statiques, 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 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 Texture, car le texte est toujours rendu dans un sens;
  5. Au moment du dessin, je rend le texte statique;
  6. Pour le texte dynamique, je peux jeter un œil à la position VBO, obtenir la texture du personnage et la dessiner, un caractère à 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.

Cleuton
la source
Par "VOB", voulez-vous dire "VBO" (objet tampon de sommet)?
Dan Hulme
3

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

Tal Pressman
la source
3

Jetez un œil au CBFGport Android du code de chargement / rendu. Vous devriez être en mesure de déposer le code dans votre projet et de 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'essaye de changer la taille du bitmap de la police (j'ai besoin de lettres spéciales), tout le dessin échoue :(

Étherna
la source
2

Je cherchais ceci depuis quelques heures, c'était le premier article que je suis tombé et bien qu'il ait la meilleure réponse, les réponses les plus populaires sont à mon avis erronées. 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 mettre directement au projet. Ici: créez simplement un nouveau projet Android 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.

Justin
la source
1

Pour le texte statique :

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

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

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

Pour un nombre (formaté 00.0):

  • Générez une image avec tous les nombres et un point.
  • Chargez-le comme matériau pour un avion.
  • Utilisez ci-dessous le 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 textures où les nombres commencent à 0 à la 7e colonne de la 2e ligne de l'atlas de polices (texture).

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

Pete
la source
0

Dans OpenGL ES 2.0 / 3.0, vous pouvez également combiner OGL View et les éléments d'interface utilisateur d'Android:

public class GameActivity extends AppCompatActivity {
    private SurfaceView surfaceView;
    @Override
    protected void onCreate(Bundle state) { 
        setContentView(R.layout.activity_gl);
        surfaceView = findViewById(R.id.oglView);
        surfaceView.init(this.getApplicationContext());
        ...
    } 
}

public class SurfaceView extends GLSurfaceView {
    private SceneRenderer renderer;
    public SurfaceView(Context context) {
        super(context);
    }

    public SurfaceView(Context context, AttributeSet attributes) {
        super(context, attributes);
    }

    public void init(Context context) {
        renderer = new SceneRenderer(context);
        setRenderer(renderer);
        ...
    }
}

Créez la mise en page activity_gl.xml:

<?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout
        tools:context=".activities.GameActivity">
    <com.app.SurfaceView
        android:id="@+id/oglView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <TextView ... />
    <TextView ... />
    <TextView ... />
</androidx.constraintlayout.widget.ConstraintLayout>

Pour mettre à jour les éléments du thread de rendu, vous pouvez utiliser Handler / Looper.

Alexrnov
la source