Dibujar texto en OpenGL ES

131

Actualmente estoy desarrollando un pequeño juego OpenGL para la plataforma Android y me pregunto si hay una manera fácil de renderizar texto sobre el marco renderizado (como un HUD con la puntuación del jugador, etc.). El texto también necesitaría usar una fuente personalizada.

He visto un ejemplo usando una Vista como superposición, pero no sé si quiero hacerlo, ya que es posible que desee trasladar el juego a otras plataformas más adelante.

¿Algunas ideas?

shakazed
fuente
eche un vistazo a este proyecto: code.google.com/p/rokon
whunmr
Mire la forma en que libgdx hace esto a través de fuentes de mapa de bits.
Robert Massaioli

Respuestas:

103

El SDK de Android no viene con ninguna forma fácil de dibujar texto en vistas OpenGL. Dejándote con las siguientes opciones.

  1. Coloque un TextView sobre su SurfaceView. Esto es lento y malo, pero el enfoque más directo.
  2. Representa cadenas comunes para texturas y simplemente dibuja esas texturas. Este es, con mucho, el más simple y rápido, pero el menos flexible.
  3. Roll-your-own código de representación de texto basado en un sprite. Probablemente la segunda mejor opción si 2 no es una opción. Es una buena manera de mojarse los pies, pero tenga en cuenta que si bien parece simple (y las características básicas lo son), se vuelve más difícil y más desafiante a medida que agrega más características (alineación de textura, manejo de saltos de línea, fuentes de ancho variable, etc. ): si tomas esta ruta, ¡hazlo lo más simple posible!
  4. Use una biblioteca estándar o de código abierto. Hay algunos por ahí si caza en Google, lo difícil es lograr que se integren y se ejecuten. Pero al menos, una vez que lo haga, tendrá toda la flexibilidad y madurez que proporcionan.
Dave
fuente
3
Decidí agregar una vista sobre mi GLView, puede que no sea la forma más eficiente de hacerlo, pero la vista no se actualiza muy a menudo, además de que me da la flexibilidad para agregar cualquier fuente que desee. ¡Gracias por todas las respuestas!
shakazed
1
¿Cómo puedo representar cadenas comunes a las texturas y simplemente dibujar esas texturas? Gracias.
VansFannel
1
VansFannel: solo use un programa de pintura para poner todas sus cadenas en una imagen, luego en su aplicación use compensaciones para representar solo la parte de la imagen que contiene la cadena que desea.
Dave
2
O vea la respuesta de JVitela a continuación para obtener una forma más programática de lograr esto.
Dave
44
La respuesta de JVitela es mejor. Eso es lo que estoy usando actualmente. La razón por la que cambias de Android vier + canvas estándar a opengl es (entre otros) por la velocidad. Agregar un cuadro de texto sobre su tipo de opengl lo niega.
Shivan Dragon
166

La representación del texto en una textura es más simple de lo que parece la demostración de Sprite Text, la idea básica es usar la clase Canvas para representar en un mapa de bits y luego pasar el mapa de bits a una textura 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
fuente
55
Esto me ayudó a mostrar un pequeño contador de fps en mi aplicación para la depuración, ¡gracias!
stealthcopter
2
Puede usar esto para generar todas las letras y números como texturas cuando carga sus otras texturas y luego las junta para formar palabras o números. Entonces no será menos eficiente que cualquier otra textura gl.
twDuke el
9
Esto es muy lento, matará fps en un juego para texto que siempre cambia (puntaje, etc.), sin embargo, funciona bien para cosas semiestáticas (nombre del jugador, nombre del nivel, etc.).
led42
3
Me gustaría señalar que el código en esta respuesta es probablemente solo una demostración y no está optimizado. Optimice / caché a su manera.
Sherif elKhatib
1
¿Podría proporcionar esto para OpenGL ES 2.0?
ilimitado101
36

He escrito un tutorial que amplía la respuesta publicada por JVitela. . Básicamente, utiliza la misma idea, pero en lugar de representar cada cadena en una textura, renderiza todos los caracteres de un archivo de fuente a una textura y la utiliza para permitir la representación de texto dinámico completo sin más ralentizaciones (una vez que se completa la inicialización) .

La principal ventaja de mi método, en comparación con los diversos generadores de atlas de fuentes, es que puede enviar archivos de fuentes pequeñas (.ttf .otf) con su proyecto en lugar de tener que enviar mapas de bits grandes para cada variación y tamaño de fuente. Puede generar fuentes de calidad perfecta en cualquier resolución usando solo un archivo de fuente :)

El tutorial incluye código completo que se puede usar en cualquier proyecto :)

free3dom
fuente
Actualmente estoy buscando esta solución, y estoy seguro de que encontraré la respuesta a tiempo, pero ¿su implementación utiliza alguna asignación de tiempo de ejecución?
Nick Hartung
@Nick: todas las asignaciones (textura, búferes de vértices, etc.) se realizan al crear una instancia de fuente, por lo que las cadenas que utilizan la instancia de fuente no requieren más asignaciones. Por lo tanto, puede crear la fuente en "tiempo de carga" sin más asignaciones en tiempo de ejecución.
free3dom el
¡Guau, buen trabajo! Esto es realmente útil, especialmente en los casos en que su texto cambia con frecuencia.
mdiener
Esta es la mejor respuesta, en cuanto a rendimiento, para aplicaciones que tienen que cambiar el texto con frecuencia en tiempo de ejecución. No he visto nada más agradable como esto para Android. Sin embargo, podría usar un puerto para OpenGL ES.
greeble31
1
Si no desea lidiar con la alineación del texto, los saltos de línea, etc., puede usar TextView. Un TextView se puede representar fácilmente en un lienzo. El rendimiento inteligente no debería ser más pesado que el enfoque dado, solo necesita una instancia de TextView para representar todos los textos que necesita. De esta manera también obtienes formato HTML simple de forma gratuita.
Gena Batsyan
8

Según este enlace:

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

Puede representar cualquier vista en un mapa de bits. Probablemente valga la pena suponer que puede diseñar una vista según lo requiera (incluyendo texto, imágenes, etc.) y luego representarla en un mapa de bits.

Usando el código de JVitela anterior , debería poder usar ese mapa de bits como una textura OpenGL.

JWGS
fuente
Sí, lo hice con un MapView en un juego y lo uní a una textura gl, así que combina mapas y opengl. Entonces, sí, todas las vistas tienen onDraw (Canvas c) y puede pasar cualquier lienzo y vincular cualquier lienzo a cualquier mapa de bits.
HaMMeReD
6

Miré el ejemplo del texto de sprite y parece terriblemente complicado para tal tarea, también consideré renderizar a una textura, pero estoy preocupado por el impacto en el rendimiento que podría causar. En su lugar, podría tener que ir con una vista y preocuparme por la transferencia cuando sea el momento de cruzar ese puente :)

shakazed
fuente
4

En mi humilde opinión, hay tres razones para usar OpenGL ES en un juego:

  1. Evite las diferencias entre plataformas móviles mediante el uso de un estándar abierto;
  2. Para tener más control del proceso de renderizado;
  3. Para beneficiarse del procesamiento paralelo de GPU;

Dibujar texto siempre es un problema en el diseño del juego, porque estás dibujando cosas, por lo que no puedes tener la apariencia de una actividad común, con widgets, etc.

Puede usar un marco para generar fuentes de mapa de bits a partir de fuentes TrueType y representarlas. Todos los marcos que he visto funcionan de la misma manera: generan las coordenadas de vértice y textura para el texto en tiempo de dibujo. Este no es el uso más eficiente de OpenGL.

La mejor manera es asignar memorias intermedias remotas (objetos de memoria intermedia de vértices - VBO) para los vértices y texturas al principio del código, evitando las operaciones de transferencia de memoria diferida en tiempo de extracción.

Ten en cuenta que a los jugadores no les gusta leer texto, por lo que no escribirás un texto largo generado dinámicamente. Para las etiquetas, puede usar texturas estáticas, dejando texto dinámico para tiempo y puntaje, y ambos son numéricos con algunos caracteres de longitud.

Entonces, mi solución es simple:

  1. Crear textura para etiquetas y advertencias comunes;
  2. Cree textura para los números 0-9, ":", "+" y "-". Una textura para cada personaje;
  3. Genere VBO remotos para todas las posiciones en la pantalla. Puedo representar texto estático o dinámico en esas posiciones, pero los VBO son estáticos;
  4. Genere solo un VBO de textura, ya que el texto siempre se representa de una manera;
  5. En tiempo de sorteo, renderizo el texto estático;
  6. Para texto dinámico, puedo mirar la posición VBO, obtener la textura del personaje y dibujarlo, un personaje a la vez.

Las operaciones de dibujo son rápidas, si usa buffers estáticos remotos.

Creo un archivo XML con posiciones de pantalla (basado en el porcentaje diagonal de la pantalla) y texturas (estática y caracteres), y luego cargo este XML antes de renderizar.

Para obtener una alta tasa de FPS, debe evitar generar VBO en el momento del sorteo.

cleuton
fuente
Por "VOB", ¿quiere decir "VBO" (objeto de búfer de vértices)?
Dan Hulme
3

Si insiste en usar GL, puede representar el texto en texturas. Suponiendo que la mayor parte del HUD es relativamente estático, no debería tener que cargar las texturas para texturizar la memoria con demasiada frecuencia.

Tal Pressman
fuente
3

Eche un vistazo CBFGy el puerto de Android del código de carga / representación. Debería poder colocar el código en su proyecto y usarlo de inmediato.

  1. CBFG

  2. Cargador de Android

Tengo problemas con esta implementación. Muestra solo un carácter, cuando intento cambiar el tamaño del mapa de bits de la fuente (necesito letras especiales) falla todo el dibujo :(

Aetherna
fuente
2

He estado buscando esto durante unas horas, este fue el primer artículo que encontré y, aunque tiene la mejor respuesta, creo que las respuestas más populares están fuera de lugar. Ciertamente para lo que necesitaba. Las respuestas de Weichsel y Shakazed estaban en el botón, pero un poco oscurecidas en los artículos. Para ponerte derecho al proyecto. Aquí: solo cree un nuevo proyecto de Android basado en una muestra existente. Elija ApiDemos:

Mira debajo de la carpeta de origen

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

Y encontrarás todo lo que necesitas.

Justin
fuente
1

Para texto estático :

  • Genere una imagen con todas las palabras utilizadas en su PC (por ejemplo, con GIMP).
  • Cargue esto como una textura y úselo como material para un avión.

Para textos largos que deben actualizarse de vez en cuando:

  • Deje que Android dibuje en un lienzo de mapa de bits (la solución de JVitela).
  • Cargue esto como material para un avión.
  • Usa diferentes coordenadas de textura para cada palabra.

Para un número (formateado 00.0):

  • Genere una imagen con todos los números y un punto.
  • Cargue esto como material para un avión.
  • Use debajo del sombreador.
  • En su evento onDraw, solo actualice la variable de valor enviada al sombreador.

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

El código anterior funciona para un atlas de texturas donde los números comienzan desde 0 en la séptima columna de la segunda fila del atlas de fuentes (textura).

Consulte https://www.shadertoy.com/view/Xl23Dw para la demostración (aunque con una textura incorrecta)

Pete
fuente
0

En OpenGL ES 2.0 / 3.0 también puede combinar OGL View y elementos UI de 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);
        ...
    }
}

Crear diseño 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>

Para actualizar elementos del hilo de renderizado, puede usar Handler / Looper.

alexrnov
fuente