Desenhar texto no OpenGL ES

131

Atualmente, estou desenvolvendo um pequeno jogo OpenGL para a plataforma Android e me pergunto se existe uma maneira fácil de renderizar texto em cima do quadro renderizado (como um HUD com a pontuação do jogador etc.). O texto precisaria usar uma fonte personalizada também.

Eu vi um exemplo usando uma View como uma sobreposição, mas não sei se quero fazer isso, pois talvez queira portar o jogo para outras plataformas posteriormente.

Alguma ideia?

abalado
fonte
dê uma olhada neste projeto: code.google.com/p/rokon
whunmr 13/04/10
Veja como a libgdx faz isso através das fontes Bitmap.
Robert Massaioli 16/05

Respostas:

103

O SDK do Android não vem com nenhuma maneira fácil de desenhar texto nas visualizações OpenGL. Deixando você com as seguintes opções.

  1. Coloque um TextView sobre o SurfaceView. Isso é lento e ruim, mas a abordagem mais direta.
  2. Renderize seqüências comuns em texturas e simplesmente desenhe essas texturas. Este é de longe o mais simples e rápido, mas o menos flexível.
  3. Crie seu próprio código de renderização de texto com base em um sprite. Provavelmente a segunda melhor opção se 2 não for uma opção. Uma boa maneira de molhar os pés, mas observe que, embora pareça simples (e os recursos básicos sejam), fica mais difícil e desafiador à medida que você adiciona mais recursos (alinhamento de textura, lidar com quebras de linha, fontes de largura variável etc.) ) - se você seguir esse caminho, torne-o o mais simples possível!
  4. Use uma biblioteca pronta para uso / de código aberto. Existem alguns por aí, se você procurar no Google, o mais complicado é integrá-los e executá-los. Mas, pelo menos, depois de fazer isso, você terá toda a flexibilidade e maturidade que eles fornecem.
Dave
fonte
3
Decidi adicionar uma visualização ao meu GLView, talvez não seja a maneira mais eficiente de fazê-lo, mas a visualização não é atualizada com muita frequência, além de me dar a flexibilidade de adicionar qualquer fonte que eu queira. Obrigado por todas as respostas!
shakazed
1
Como renderizar seqüências comuns em texturas e simplesmente desenhar essas texturas? Obrigado.
VansFannel
1
VansFannel: basta usar um programa de pintura para colocar todas as suas strings em uma imagem e, no seu aplicativo, usar offsets para renderizar apenas a parte da imagem que contém a string que você deseja.displayed.
Dave
2
Ou veja a resposta da JVitela abaixo para uma maneira mais programática de conseguir isso.
Dave
4
A resposta de JVitela é melhor. É o que estou usando atualmente. A razão pela qual você muda do android veio + canvas padrão para o opengl é (entre outros) a velocidade. Adicionar uma caixa de texto ao seu tipo de opengl nega isso.
Shivan Dragon
166

A renderização de texto em uma textura é mais simples do que a demonstração do Texto de Sprite faz parecer, a idéia básica é usar a classe Canvas para renderizar em um Bitmap e depois passar o Bitmap para uma 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
fonte
5
Isso me ajudou a exibir no pequeno contador de fps no meu aplicativo para depuração, obrigado!
Stealthcopter
2
Você pode usar isso para gerar todas as letras e números como texturas ao carregar suas outras texturas e depois juntá-las para formar palavras ou números. Então não será menos eficiente do que qualquer outra textura gl.
TwDuke
9
Isso é muito lento, mata fps em um jogo para texto que está sempre mudando (pontuação, etc), mas funciona bem para coisas semi-estáticas (nome do jogador, nome do nível etc.).
led42
3
Eu gostaria de observar que o código nesta resposta provavelmente é apenas uma demonstração e não está otimizado! Por favor, otimize / armazene em cache à sua maneira.
Sherif elKhatib 19/03/2013
1
Você poderia fornecer isso para o OpenGL ES 2.0?
unlimited101
36

Eu escrevi um tutorial que expande a resposta postada por JVitela . Basicamente, ele usa a mesma ideia, mas, em vez de renderizar cada string em uma textura, renderiza todos os caracteres de um arquivo de fonte em uma textura e o usa para permitir a renderização dinâmica completa do texto sem mais lentidões (quando a inicialização é concluída) .

A principal vantagem do meu método, em comparação com os vários geradores de atlas de fontes, é que você pode enviar pequenos arquivos de fonte (.ttf .otf) com seu projeto, em vez de ter que enviar bitmaps grandes para cada variação e tamanho de fonte. Ele pode gerar fontes de qualidade perfeita em qualquer resolução usando apenas um arquivo de fonte :)

O tutorial inclui código completo que pode ser usado em qualquer projeto :)

free3dom
fonte
Atualmente, estou analisando esta solução e tenho certeza de que encontrarei a resposta em tempo, mas sua implementação usa alocações de tempo de execução?
Nick Hartung
@Nick - Todas as alocações (textura, buffers de vértices, etc.) são feitas ao criar uma instância de fonte, renderizar strings usando a instância de fonte não requer mais alocações. Assim, você pode criar a fonte no "tempo de carregamento" sem mais alocações no tempo de execução.
precisa saber é
Uau, ótimo trabalho! Isso é realmente útil, especialmente nos casos em que seu texto muda frequentemente.
Mdiener
Essa é a melhor resposta, em termos de desempenho, para aplicativos que precisam alterar o texto frequentemente em tempo de execução. Não vi nada tão bom quanto isso para o Android. No entanto, ele poderia usar uma porta para o OpenGL ES.
precisa saber é o seguinte
1
Se você não quiser lidar com alinhamento de texto, quebras de linha, etc - você pode usar um TextView. Um TextView pode ser facilmente renderizado em uma tela. Em termos de desempenho, não deve ser mais pesado que a abordagem especificada; você só precisa de uma instância do TextView para renderizar todos os textos necessários. Dessa forma, você também obtém formatação HTML simples de graça.
Gena Batsyan
8

De acordo com este link:

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

Você pode renderizar qualquer exibição em um bitmap. Provavelmente, vale a pena supor que você pode fazer o layout de uma exibição conforme necessário (incluindo texto, imagens etc.) e depois renderizá-la em um Bitmap.

Usando o código da JVitela acima, você poderá usar esse Bitmap como uma textura OpenGL.

JWGS
fonte
Sim, eu fiz isso com um MapView em um jogo e vinculei a uma textura gl para combinar mapas e opengl. Então, sim, todas as visualizações têm onDraw (Canvas c) e você pode passar qualquer tela e vincular qualquer tela a qualquer bitmap.
HaMMeReD 23/06
6

Eu olhei para o exemplo de texto do sprite e parece muito complicado para uma tarefa como essa, também considerei renderizar uma textura, mas estou preocupado com o impacto no desempenho que pode causar. Talvez eu precise apenas visualizar e me preocupar em portar quando for a hora de atravessar a ponte :)

abalado
fonte
4

IMHO, existem três razões para usar o OpenGL ES em um jogo:

  1. Evite diferenças entre plataformas móveis usando um padrão aberto;
  2. Ter mais controle do processo de renderização;
  3. Beneficiar-se do processamento paralelo da GPU;

Desenhar texto é sempre um problema no design de jogos, porque você desenha coisas, portanto não pode ter a aparência de uma atividade comum, com widgets e assim por diante.

Você pode usar uma estrutura para gerar fontes Bitmap a partir de fontes TrueType e renderizá-las. Todas as estruturas que eu vi funcionam da mesma maneira: gere as coordenadas de vértice e textura para o texto em tempo de desenho. Este não é o uso mais eficiente do OpenGL.

A melhor maneira é alocar buffers remotos (objetos de buffer de vértice - VBOs) para os vértices e texturas no início do código, evitando as operações de transferência de memória lenta no tempo de desenho.

Lembre-se de que os jogadores não gostam de ler texto, portanto, não escreverá um texto longo gerado dinamicamente. Para rótulos, você pode usar texturas estáticas, deixando texto dinâmico por tempo e pontuação, e ambos são numéricos com alguns caracteres.

Então, minha solução é simples:

  1. Crie textura para etiquetas e avisos comuns;
  2. Crie textura para os números 0-9, ":", "+" e "-". Uma textura para cada personagem;
  3. Gere VBOs remotos para todas as posições na tela. Eu posso processar texto estático ou dinâmico nessas posições, mas os VBOs são estáticos;
  4. Gere apenas um VBO de textura, pois o texto é sempre renderizado de uma maneira;
  5. Na hora do sorteio, renderizo o texto estático;
  6. Para texto dinâmico, posso espiar a posição VBO, pegar a textura do personagem e desenhá-lo, um caractere de cada vez.

As operações de desenho são rápidas, se você usar buffers estáticos remotos.

Crio um arquivo XML com posições de tela (com base na porcentagem diagonal da tela) e texturas (estática e caracteres) e carrego esse XML antes da renderização.

Para obter uma alta taxa de FPS, evite gerar VBOs na hora da compra.

Cleuton
fonte
Por "VOB", você quer dizer "VBO" (objeto de buffer de vértice)?
Dan Hulme
3

Se você insistir em usar o GL, poderá renderizar o texto em texturas. Supondo que a maior parte do HUD seja relativamente estática, você não precisará carregar as texturas para texturizar a memória com muita frequência.

Tal Pressman
fonte
3

Dê uma olhada na CBFGporta Android do código de carregamento / renderização. Você deve poder soltar o código no seu projeto e usá-lo imediatamente.

  1. CBFG

  2. Carregador Android

Estou com problemas com esta implementação. Ele exibe apenas um caractere. Quando tento alterar o tamanho do bitmap da fonte (preciso de letras especiais), o sorteio inteiro falha :(

Aetherna
fonte
2

Estou procurando por isso há algumas horas, este foi o primeiro artigo que encontrei e, embora tenha a melhor resposta, as respostas mais populares que acho que estão erradas. Certamente pelo que eu precisava. as respostas de weichsel e shakazed estavam no botão, mas um pouco obscurecidas nos artigos. Para colocá-lo direto no projeto. Aqui: basta criar um novo projeto Android com base na amostra existente. Escolha ApiDemos:

Procure na pasta de origem

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

E você encontrará tudo o que precisa.

Justin
fonte
1

Para texto estático :

  • Gere uma imagem com todas as palavras usadas no seu PC (por exemplo, com o GIMP).
  • Carregue isso como uma textura e use-o como material para um avião.

Para textos longos que precisam ser atualizados de vez em quando:

  • Deixe o Android desenhar em uma tela de bitmap (solução da JVitela).
  • Carregue isso como material para um avião.
  • Use coordenadas de textura diferentes para cada palavra.

Para um número (formatado em 00.0):

  • Gere uma imagem com todos os números e um ponto.
  • Carregue isso como material para um avião.
  • Use o sombreador abaixo.
  • No seu evento onDraw, atualize apenas a variável de valor enviada ao 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;
    
    }

O código acima funciona para um atlas de textura em que os números começam em 0 na 7ª coluna da 2ª linha do atlas de fonte (textura).

Consulte https://www.shadertoy.com/view/Xl23Dw para obter uma demonstração (com textura incorreta)

Pete
fonte
0

No OpenGL ES 2.0 / 3.0, você também pode combinar o OGL View e os elementos de interface do usuário do 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);
        ...
    }
}

Crie o 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>

Para atualizar elementos do encadeamento de renderização, use Handler / Looper.

alexrnov
fonte