Zeichnen Sie Text in OpenGL ES

131

Ich entwickle derzeit ein kleines OpenGL-Spiel für die Android-Plattform und frage mich, ob es eine einfache Möglichkeit gibt, Text über dem gerenderten Frame zu rendern (wie ein HUD mit der Punktzahl des Spielers usw.). Der Text müsste auch eine benutzerdefinierte Schriftart verwenden.

Ich habe ein Beispiel mit einer Ansicht als Overlay gesehen, weiß aber nicht, ob ich das tun möchte, da ich das Spiel möglicherweise später auf andere Plattformen portieren möchte.

Irgendwelche Ideen?

wackelte
quelle
Schauen Sie sich dieses Projekt an: code.google.com/p/rokon
whunmr
Sehen Sie sich an, wie libgdx dies über Bitmap-Schriftarten tut.
Robert Massaioli

Antworten:

103

Das Android SDK bietet keine einfache Möglichkeit, Text in OpenGL-Ansichten zu zeichnen. Sie haben folgende Möglichkeiten.

  1. Platzieren Sie eine Textansicht über Ihrer Oberflächenansicht. Dies ist langsam und schlecht, aber der direkteste Ansatz.
  2. Rendern Sie allgemeine Zeichenfolgen für Texturen und zeichnen Sie diese Texturen einfach. Dies ist bei weitem die einfachste und schnellste, aber die am wenigsten flexible.
  3. Erstellen Sie Ihren eigenen Text-Rendering-Code basierend auf einem Sprite. Wahrscheinlich die zweitbeste Wahl, wenn 2 keine Option ist. Ein guter Weg, um Ihre Füße nass zu machen, aber beachten Sie, dass es zwar einfach erscheint (und grundlegende Funktionen sind), aber schwieriger und herausfordernder wird, wenn Sie weitere Funktionen hinzufügen (Texturausrichtung, Umgang mit Zeilenumbrüchen, Schriftarten mit variabler Breite usw.). ) - Wenn Sie diesen Weg nehmen, machen Sie es so einfach wie möglich!
  4. Verwenden Sie eine Standard- / Open-Source-Bibliothek. Wenn Sie bei Google jagen, gibt es einige, die schwierig sind, sie zu integrieren und zum Laufen zu bringen. Aber zumindest haben Sie danach die Flexibilität und Reife, die sie bieten.
Dave
quelle
3
Ich habe beschlossen, eine Ansicht über meine GLView hinzuzufügen. Dies ist möglicherweise nicht die effizienteste Methode, aber die Ansicht wird nicht sehr oft aktualisiert. Außerdem kann ich flexibel die gewünschte Schriftart hinzufügen. Danke für alle Antworten!
Shakazed
1
Wie kann ich allgemeine Zeichenfolgen für Texturen rendern und diese Texturen einfach zeichnen? Vielen Dank.
VansFannel
1
VansFannel: Verwenden Sie einfach ein Malprogramm, um alle Zeichenfolgen in einem Bild zu platzieren. Verwenden Sie dann in Ihrer App Offsets, um nur den Teil des Bildes zu rendern, der die gewünschte Zeichenfolge enthält.
Dave
2
Oder lesen Sie die Antwort von JVitela unten, um einen programmatischeren Weg zu finden, um dies zu erreichen.
Dave
4
JVitelas Antwort ist besser. Das ist es, was ich gerade benutze. Der Grund, warum Sie von Standard Android Vier + Canvas zu OpenGL wechseln, ist (unter anderem) die Geschwindigkeit. Das Hinzufügen eines Textfelds über Ihrem OpenGL negiert dies.
Shivan Dragon
166

Das Rendern von Text in eine Textur ist einfacher als in der Sprite Text-Demo. Die Grundidee besteht darin, die Canvas-Klasse zum Rendern in eine Bitmap zu verwenden und die Bitmap dann an eine OpenGL-Textur zu übergeben:

// 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
quelle
5
Dies hat mir geholfen, einen kleinen FPS-Zähler in meiner App zum Debuggen anzuzeigen, danke!
Stealthcopter
2
Sie können dies verwenden, um alle Buchstaben und Zahlen als Texturen zu generieren, wenn Sie Ihre anderen Texturen laden und sie dann zu Wörtern oder Zahlen zusammenfügen. Dann ist es nicht weniger effizient als jede andere Gl-Textur.
TwDuke
9
Dies ist sehr langsam, es tötet fps in einem Spiel für Text, der sich ständig ändert (Punktzahl usw.), funktioniert jedoch gut für semistatische Dinge (Spielername, Levelname usw.).
LED42
3
Ich möchte darauf hinweisen, dass der Code in dieser Antwort wahrscheinlich nur eine Demo ist und nicht optimiert wurde! Bitte optimieren / cachen Sie auf Ihre eigene Weise.
Sherif elKhatib
1
Könnten Sie dies für OpenGL ES 2.0 bereitstellen?
unbegrenzt101
36

Ich habe ein Tutorial geschrieben , das die Antwort von JVitela erweitert . Grundsätzlich wird dieselbe Idee verwendet, aber anstatt jede Zeichenfolge in eine Textur zu rendern, werden alle Zeichen aus einer Schriftartdatei in eine Textur gerendert, um eine vollständige dynamische Textwiedergabe ohne weitere Verlangsamungen zu ermöglichen (sobald die Initialisierung abgeschlossen ist). .

Der Hauptvorteil meiner Methode im Vergleich zu den verschiedenen Generatoren für Schriftatlas besteht darin, dass Sie kleine Schriftdateien (.ttf .otf) mit Ihrem Projekt versenden können, anstatt große Bitmaps für jede Schriftvariante und -größe liefern zu müssen. Es kann mit nur einer Schriftdatei Schriftarten in perfekter Qualität in jeder Auflösung erzeugen :)

Das Tutorial enthält vollständigen Code, der in jedem Projekt verwendet werden kann :)

free3dom
quelle
Ich prüfe derzeit diese Lösung und bin sicher, dass ich die Antwort rechtzeitig finden werde. Verwendet Ihre Implementierung jedoch Laufzeitzuweisungen?
Nick Hartung
@Nick - Alle Zuordnungen (Textur, Scheitelpunktpuffer usw.) werden beim Erstellen einer Schriftinstanz vorgenommen. Für das Rendern von Zeichenfolgen mit der Schriftinstanz sind keine weiteren Zuordnungen erforderlich. So können Sie die Schriftart zur "Ladezeit" ohne weitere Zuordnungen zur Laufzeit erstellen.
free3dom
Wow, tolle Arbeit! Dies ist besonders in Fällen hilfreich, in denen sich Ihr Text häufig ändert.
Mdiener
Dies ist in Bezug auf die Leistung die beste Antwort für Anwendungen, die den Text zur Laufzeit häufig ändern müssen. Ich habe noch nichts Schöneres für Android gesehen. Es könnte jedoch einen Port für OpenGL ES verwenden.
greeble31
1
Wenn Sie sich nicht mit Textausrichtung, Zeilenumbrüchen usw. befassen möchten, können Sie eine Textansicht verwenden. Eine Textansicht kann einfach in eine Leinwand gerendert werden. Die Leistung sollte nicht schwerer sein als der angegebene Ansatz. Sie benötigen nur eine TextView-Instanz, um alle benötigten Texte zu rendern. Auf diese Weise erhalten Sie auch eine einfache HTML-Formatierung kostenlos.
Gena Batsyan
8

Nach diesem Link:

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

Sie können jede Ansicht in eine Bitmap rendern . Es lohnt sich wahrscheinlich anzunehmen, dass Sie eine Ansicht nach Bedarf gestalten können (einschließlich Text, Bilder usw.) und sie dann in eine Bitmap rendern können.

Wenn Sie den obigen JVitela-Code verwenden , sollten Sie diese Bitmap als OpenGL-Textur verwenden können.

JWGS
quelle
Ja, ich habe es mit einem MapView in einem Spiel gemacht und es an eine gl-Textur gebunden, also kombiniere Maps und opengl. Also ja, alle Ansichten haben onDraw (Canvas c) und Sie können jede Leinwand übergeben und jede Leinwand an jede Bitmap binden.
HaMMeReD
6

Ich habe mir das Sprite-Textbeispiel angesehen und es sieht für eine solche Aufgabe furchtbar kompliziert aus. Ich habe überlegt, auch eine Textur zu rendern, aber ich mache mir Sorgen über den Leistungseinbruch, der dazu führen könnte. Möglicherweise muss ich stattdessen nur eine Ansicht wählen und mir Gedanken über die Portierung machen, wenn es Zeit ist, diese Brücke zu überqueren :)

wackelte
quelle
4

IMHO gibt es drei Gründe, OpenGL ES in einem Spiel zu verwenden:

  1. Vermeiden Sie Unterschiede zwischen mobilen Plattformen, indem Sie einen offenen Standard verwenden.
  2. Um mehr Kontrolle über den Renderprozess zu haben;
  3. Um von der GPU-Parallelverarbeitung zu profitieren;

Das Zeichnen von Text ist im Spieldesign immer ein Problem, da Sie Dinge zeichnen, sodass Sie nicht das Erscheinungsbild einer allgemeinen Aktivität mit Widgets usw. haben können.

Sie können ein Framework verwenden, um Bitmap-Schriftarten aus TrueType-Schriftarten zu generieren und zu rendern. Alle Frameworks, die ich gesehen habe, funktionieren auf die gleiche Weise: Generieren Sie die Scheitelpunkt- und Texturkoordinaten für den Text in der Zeichenzeit. Dies ist nicht die effizienteste Verwendung von OpenGL.

Der beste Weg ist, Remote-Puffer (Vertex Buffer Objects - VBOs) für die Vertices und Texturen zu Beginn des Codes zuzuweisen, um die verzögerten Speicherübertragungsvorgänge in der Zeichenzeit zu vermeiden.

Denken Sie daran, dass Spieler keinen Text lesen möchten, sodass Sie keinen langen, dynamisch generierten Text schreiben. Für Beschriftungen können Sie statische Texturen verwenden, wobei dynamischer Text für Zeit und Punktzahl verbleibt. Beide sind numerisch und haben eine Länge von wenigen Zeichen.

Meine Lösung ist also einfach:

  1. Erstellen Sie eine Textur für allgemeine Beschriftungen und Warnungen.
  2. Erstellen Sie eine Textur für die Nummern 0-9, ":", "+" und "-". Eine Textur für jedes Zeichen;
  3. Generieren Sie Remote-VBOs für alle Positionen auf dem Bildschirm. Ich kann statischen oder dynamischen Text an diesen Positionen rendern, aber die VBOs sind statisch.
  4. Generieren Sie nur eine Textur VBO, da Text immer in eine Richtung gerendert wird.
  5. In der Zeichenzeit rendere ich den statischen Text.
  6. Für dynamischen Text kann ich einen Blick auf die Position VBO werfen, die Charaktertextur abrufen und sie zeichenweise zeichnen.

Zeichenvorgänge sind schnell, wenn Sie statische Remote-Puffer verwenden.

Ich erstelle eine XML-Datei mit Bildschirmpositionen (basierend auf dem diagonalen Prozentsatz des Bildschirms) und Texturen (statisch und Zeichen) und lade diese XML vor dem Rendern.

Um eine hohe FPS-Rate zu erzielen, sollten Sie vermeiden, beim Zeichnen VBOs zu generieren.

Cleuton
quelle
Mit "VOB" meinen Sie "VBO" (Vertex Buffer Object)?
Dan Hulme
3

Wenn Sie darauf bestehen, GL zu verwenden, können Sie den Text in Texturen rendern. Angenommen, der größte Teil des HUD ist relativ statisch, sollten Sie die Texturen nicht zu oft in den Texturspeicher laden müssen.

Tal Pressman
quelle
3

Schauen Sie sich CBFGden Android-Port des Lade- / Rendering-Codes an. Sie sollten in der Lage sein, den Code in Ihr Projekt einzufügen und ihn sofort zu verwenden.

  1. CBFG

  2. Android Loader

Ich habe Probleme mit dieser Implementierung. Es wird nur ein Zeichen angezeigt, wenn ich versuche, die Größe der Bitmap der Schriftart zu ändern (ich benötige spezielle Buchstaben). Die gesamte Zeichnung schlägt fehl :(

Aetherna
quelle
2

Ich habe ein paar Stunden danach gesucht, dies war der erste Artikel, auf den ich gestoßen bin, und obwohl er die beste Antwort hat, sind die beliebtesten Antworten meiner Meinung nach falsch. Sicher für das, was ich brauchte. Die Antworten von weichsel und shakazed waren direkt auf dem Knopf, aber in den Artikeln etwas verdeckt. Um Sie direkt zum Projekt zu bringen. Hier: Erstellen Sie einfach ein neues Android-Projekt basierend auf dem vorhandenen Beispiel. Wählen Sie ApiDemos:

Schauen Sie unter dem Quellordner nach

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

Und Sie finden alles, was Sie brauchen.

Justin
quelle
1

Für statischen Text :

  • Generieren Sie ein Bild mit allen auf Ihrem PC verwendeten Wörtern (z. B. mit GIMP).
  • Laden Sie dies als Textur und verwenden Sie es als Material für eine Ebene.

Für Langtext , der gelegentlich aktualisiert werden muss:

  • Lassen Sie Android auf eine Bitmap-Leinwand zeichnen (JVitelas Lösung).
  • Laden Sie dies als Material für ein Flugzeug.
  • Verwenden Sie für jedes Wort unterschiedliche Texturkoordinaten.

Für eine Nummer (formatiert 00.0):

  • Generieren Sie ein Bild mit allen Zahlen und einem Punkt.
  • Laden Sie dies als Material für ein Flugzeug.
  • Verwenden Sie unter Shader.
  • Aktualisieren Sie in Ihrem onDraw-Ereignis nur die an den Shader gesendete Wertvariable.

    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;
    
    }

Der obige Code funktioniert für einen Texturatlas, bei dem die Zahlen in der 7. Spalte der 2. Zeile des Schriftatlas (Textur) bei 0 beginnen.

Weitere Informationen finden Sie unter https://www.shadertoy.com/view/Xl23Dw (allerdings mit falscher Textur).

Pete
quelle
0

In OpenGL ES 2.0 / 3.0 können Sie auch OGL View und die UI-Elemente von Android kombinieren:

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);
        ...
    }
}

Erstellen Sie das 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>

Verwenden Sie Handler / Looper, um Elemente aus dem Render-Thread zu aktualisieren.

alexrnov
quelle