Disegna testo in OpenGL ES


131

Attualmente sto sviluppando un piccolo gioco OpenGL per la piattaforma Android e mi chiedo se esiste un modo semplice per eseguire il rendering del testo sopra il riquadro di rendering (come un HUD con il punteggio del giocatore, ecc.). Il testo dovrebbe usare anche un carattere personalizzato.

Ho visto un esempio usando una vista come overlay, ma non so se voglio farlo dal momento che potrei voler portare il gioco su altre piattaforme in seguito.

Qualche idea?


dai un'occhiata a questo progetto: code.google.com/p/rokon
whunmr

Guarda come Libgdx lo fa tramite Bitmap Fonts.
Robert Massaioli,

Risposte:


103

L'SDK di Android non offre un modo semplice per disegnare testo su viste OpenGL. Lasciandoti con le seguenti opzioni.

  1. Posiziona un oggetto TextView su SurfaceView. Questo è lento e negativo, ma l'approccio più diretto.
  2. Rendi stringhe comuni alle trame e disegna semplicemente quelle trame. Questo è di gran lunga il più semplice e veloce, ma il meno flessibile.
  3. Roll-your-own codice di rendering del testo basato su uno sprite. Probabilmente la seconda scelta migliore se 2 non è un'opzione. Un buon modo per bagnare i piedi, ma nota che mentre sembra semplice (e le caratteristiche di base lo sono), diventa più difficile e più impegnativo quando aggiungi più funzionalità (allineamento delle trame, gestione delle interruzioni di riga, caratteri a larghezza variabile ecc. ) - se segui questo percorso, rendilo il più semplice possibile!
  4. Utilizzare una libreria standard / open-source. Ce ne sono alcuni in giro se si caccia su Google, la parte difficile è farli integrare e funzionare. Ma almeno, una volta fatto, avrai tutta la flessibilità e la maturità che offrono.

3
Ho deciso di aggiungere una vista sul mio GLView, potrebbe non essere il modo più efficiente per farlo, ma la vista non viene aggiornata molto spesso, inoltre mi dà la flessibilità di aggiungere qualunque carattere mi piacerebbe. Grazie per tutte le risposte!
shakazed,

1
Come posso rendere stringhe comuni alle trame e disegnare semplicemente quelle trame? Grazie.
VansFannel,

1
VansFannel: basta usare un programma di disegno per mettere tutte le stringhe in un'immagine, quindi nella tua app utilizzare gli offset per eseguire il rendering solo della parte dell'immagine che contiene la stringa desiderata.
Dave,

2
Oppure vedi la risposta di JVitela di seguito per un modo più programmatico per raggiungere questo obiettivo.
Dave,

4
La risposta di JVitela è migliore. Questo è quello che sto attualmente usando. Il motivo per cui passi da Android vier + canvas standard a opengl è (tra gli altri) per la velocità. L'aggiunta di una casella di testo sopra il tuo tipo di opzione ne annulla.
Drago di Shivan,

166

Il rendering del testo su una trama è più semplice di come appare la demo di Sprite Text, l'idea di base è utilizzare la classe Canvas per eseguire il rendering su una bitmap e quindi passare la bitmap a una trama 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();

5
Questo mi ha aiutato a visualizzare un piccolo contatore fps sulla mia app per il debug, grazie!
Stealthcopter,

2
Puoi usarlo per generare tutte le lettere e i numeri come trame quando carichi le altre trame e poi le unisci per formare parole o numeri. Quindi non sarà meno efficiente di qualsiasi altra texture gl.
twDuke,

9
Questo è molto lento, ucciderà i fps in un gioco per il testo che cambia sempre (punteggio, ecc.), Tuttavia funziona bene per cose semi-statiche (nome del giocatore, nome del livello, ecc.).
led42

3
Vorrei sottolineare che il codice in questa risposta è probabilmente solo una demo e non è ottimizzato per niente! Si prega di ottimizzare / cache a modo tuo.
Sherif elKhatib

1
Potresti fornirlo per OpenGL ES 2.0?
illimitato101

36

Ho scritto un tutorial che si espande sulla risposta pubblicata da JVitela . Fondamentalmente, utilizza la stessa idea, ma invece di eseguire il rendering di ogni stringa su una trama, esegue il rendering di tutti i caratteri da un file di caratteri a una trama e lo utilizza per consentire il rendering di testo dinamico completo senza ulteriori rallentamenti (una volta completata l'inizializzazione) .

Il vantaggio principale del mio metodo, rispetto ai vari generatori di atlanti di font, è che puoi spedire piccoli file di font (.ttf .otf) con il tuo progetto invece di dover spedire bitmap di grandi dimensioni per ogni variazione e dimensione del font. Può generare caratteri di qualità perfetta a qualsiasi risoluzione usando solo un file di caratteri :)

Il tutorial include il codice completo che può essere utilizzato in qualsiasi progetto :)


Sto attualmente esaminando questa soluzione e sono sicuro di trovare la risposta in tempo, ma l'implementazione utilizza allocazioni di runtime?
Nick Hartung,

@Nick: tutte le allocazioni (texture, buffer di vertici, ecc.) Vengono eseguite durante la creazione di un'istanza di font, il rendering delle stringhe utilizzando l'istanza di font non richiede ulteriori allocazioni. Quindi è possibile creare il carattere in "tempo di caricamento" senza ulteriori allocazioni in fase di esecuzione.
free3dom

Caspita, ottimo lavoro! Questo è davvero utile soprattutto nei casi in cui il testo cambia spesso.
mdiener,

Questa è la risposta migliore, dal punto di vista delle prestazioni, per le applicazioni che devono cambiare frequentemente il testo in fase di esecuzione. Non ho visto nient'altro di bello come questo per Android. Tuttavia, potrebbe utilizzare una porta per OpenGL ES.
greeble31,

1
Se non si desidera gestire l'allineamento del testo, le interruzioni di riga, ecc., È possibile utilizzare un TextView. Un TextView può essere facilmente trasformato in una tela. Le prestazioni non dovrebbero essere più pesanti dell'approccio dato, è necessaria solo un'istanza TextView per eseguire il rendering di tutti i testi necessari. In questo modo ottieni anche una semplice formattazione HTML gratuitamente.
Gena Batsyan,

8

Secondo questo link:

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

È possibile eseguire il rendering di qualsiasi vista su una bitmap. Probabilmente vale la pena presumere che sia possibile impaginare una vista come desiderato (incluso testo, immagini ecc.) E quindi renderizzarla su una bitmap.

Usando il codice JVitela sopra , dovresti essere in grado di usare quella Bitmap come trama OpenGL.


Sì, l'ho fatto con un MapView in un gioco e l'ho legato a una trama gl, quindi combina mappe e opengl. Quindi sì, tutte le viste hanno onDraw (Canvas c) e puoi passare qualsiasi canvas e associare qualsiasi canvas a qualsiasi bitmap.
HaMMeReD,


6

Ho guardato l'esempio del testo sprite e sembra terribilmente complicato per tale compito, ho considerato anche il rendering su una trama, ma sono preoccupato per il colpo di performance che potrebbe causare. Potrei invece semplicemente andare con una vista e preoccuparmi del porting quando è il momento di attraversare quel ponte :)



4

IMHO ci sono tre ragioni per usare OpenGL ES in un gioco:

  1. Evita le differenze tra le piattaforme mobili utilizzando uno standard aperto;
  2. Per avere un maggiore controllo del processo di rendering;
  3. Per beneficiare dell'elaborazione parallela della GPU;

Disegnare il testo è sempre un problema nella progettazione del gioco, perché stai disegnando cose, quindi non puoi avere l'aspetto di un'attività comune, con i widget e così via.

È possibile utilizzare un framework per generare caratteri Bitmap dai caratteri TrueType e renderli. Tutti i framework che ho visto funzionano allo stesso modo: genera il vertice e le coordinate della trama per il testo in tempo di disegno. Questo non è l'uso più efficiente di OpenGL.

Il modo migliore è allocare buffer remoti (oggetti buffer vertici - VBO) per i vertici e le trame all'inizio del codice, evitando le operazioni di trasferimento della memoria lazy in tempo di disegno.

Tieni presente che ai giocatori non piace leggere il testo, quindi non dovrai scrivere un lungo testo generato dinamicamente. Per le etichette, puoi usare trame statiche, lasciando testo dinamico per tempo e punteggio, ed entrambi sono numerici con una lunghezza di alcuni caratteri.

Quindi, la mia soluzione è semplice:

  1. Crea texture per etichette e avvertenze comuni;
  2. Crea trama per i numeri 0-9, ":", "+" e "-". Una trama per ogni personaggio;
  3. Genera VBO remoti per tutte le posizioni nella schermata. Posso eseguire il rendering di testo statico o dinamico in quelle posizioni, ma i VBO sono statici;
  4. Genera solo un VBO Texture, poiché il testo viene sempre visualizzato in un modo;
  5. In fase di disegno, rendering il testo statico;
  6. Per un testo dinamico, posso dare un'occhiata alla posizione VBO, ottenere la trama del personaggio e disegnarla, un personaggio alla volta.

Le operazioni di disegno sono veloci se si utilizzano buffer statici remoti.

Creo un file XML con posizioni dello schermo (in base alla percentuale diagonale dello schermo) e trame (statico e caratteri), quindi carico questo XML prima del rendering.

Per ottenere un alto tasso di FPS, dovresti evitare di generare VBO al momento dell'estrazione.


Per "VOB", intendi "VBO" (oggetto buffer vertici)?
Dan Hulme,

3

Se insisti nell'utilizzare GL, puoi rendere il testo su trame. Supponendo che la maggior parte dell'HUD sia relativamente statica, non è necessario caricare le trame nella memoria delle texture troppo spesso.


3

Dai un'occhiata CBFGe la porta Android del codice di caricamento / rendering. Dovresti essere in grado di rilasciare il codice nel tuo progetto e utilizzarlo immediatamente.

  1. CBFG

  2. Caricatore Android

Ho problemi con questa implementazione. Visualizza solo un carattere, quando provo a cambiare la dimensione della bitmap del font (ho bisogno di lettere speciali) l'intero disegno fallisce :(


2

Ho cercato questo per alcune ore, questo è stato il primo articolo che ho incontrato e sebbene abbia la migliore risposta, le risposte più popolari penso siano fuori dal comune. Certamente per quello che mi serviva. Le risposte di weichsel e shakazed erano proprio sul pulsante ma un po 'oscurate negli articoli. Per metterti diritto al progetto. Qui: basta creare un nuovo progetto Android basato sull'esempio esistente. Scegli ApiDemos:

Guarda sotto la cartella di origine

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

E troverai tutto ciò di cui hai bisogno.


1

Per testo statico :

  • Genera un'immagine con tutte le parole utilizzate sul tuo PC (ad esempio con GIMP).
  • Caricalo come texture e usalo come materiale per un aereo.

Per testi lunghi che devono essere aggiornati di tanto in tanto:

  • Lascia che Android disegni su una tela bitmap (la soluzione di JVitela).
  • Caricalo come materiale per un aereo.
  • Usa coordinate di trama diverse per ogni parola.

Per un numero (formattato 00.0):

  • Genera un'immagine con tutti i numeri e un punto.
  • Caricalo come materiale per un aereo.
  • Usa sotto lo shader.
  • Nel tuo evento onDraw aggiorna solo la variabile value inviata allo 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;
    
    }

Il codice sopra funziona per un atlante delle trame in cui i numeri iniziano da 0 alla settima colonna della seconda fila dell'atlante dei caratteri (trama).

Per la dimostrazione, consultare https://www.shadertoy.com/view/Xl23Dw (con trama errata)


0

In OpenGL ES 2.0 / 3.0 puoi anche combinare OGL View e gli elementi dell'interfaccia utente di 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);
        ...
    }
}

Crea layout 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>

Per aggiornare gli elementi dal thread di rendering, è possibile utilizzare Handler / Looper.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.