在 OpenGL ES 中绘制文本

发布于 2024-08-02 14:05:30 字数 172 浏览 5 评论 0原文

我目前正在为 Android 平台开发一个小型 OpenGL 游戏,我想知道是否有一种简单的方法可以在渲染帧之上渲染文本(例如带有玩家分数的 HUD 等)。文本还需要使用自定义字体。

我见过一个使用视图作为叠加层的示例,但我不知道是否要这样做,因为我稍后可能想将游戏移植到其他平台。

有什么想法吗?

I'm currently developing a small OpenGL game for the Android platform and I wonder if there's an easy way to render text on top of the rendered frame (like a HUD with the player´s score etc). The text would need to use a custom font also.

I've seen an example using a View as an overlay, but I don't know if I want to do that since I might want to port the game to other platforms later.

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

定格我的天空 2024-08-09 14:05:30

将文本渲染到纹理比 Sprite Text 演示使它看起来更简单,基本思想是使用 Canvas 类渲染到位图,然后将位图传递到 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();

Rendering text to a texture is simpler than what the Sprite Text demo make it looks like, the basic idea is to use the Canvas class to render to a Bitmap and then pass the Bitmap to an OpenGL texture:

// 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();
祁梦 2024-08-09 14:05:30

Android SDK 没有提供任何在 OpenGL 视图上绘制文本的简单方法。留给您以下选择。

  1. 将 TextView 放在 SurfaceView 上。这是缓慢且糟糕的方法,但却是最直接的方法。
  2. 将常见字符串渲染为纹理,然后简单地绘制这些纹理。这是迄今为止最简单、最快的,但最不灵活。
  3. 基于 sprite 编写您自己的文本渲染代码。如果 2 不是一个选项,则可能是第二佳选择。这是一个入门的好方法,但请注意,虽然它看起来很简单(基本功能也很简单),但随着您添加更多功能(纹理对齐、处理换行符、可变宽度字体等),它会变得更加困难和更具挑战性。 ) - 如果您选择这条路线,请使其尽可能简单!
  4. 使用现成/开源库。如果您在 Google 上搜索,就会有一些库,棘手的地方是如何集成并运行它们。但至少,一旦你这样做了,你将拥有他们提供的所有灵活性和成熟度。

The Android SDK doesn't come with any easy way to draw text on OpenGL views. Leaving you with the following options.

  1. Place a TextView over your SurfaceView. This is slow and bad, but the most direct approach.
  2. Render common strings to textures, and simply draw those textures. This is by far the simplest and fastest, but the least flexible.
  3. Roll-your-own text rendering code based on a sprite. Probably second best choice if 2 isn't an option. A good way to get your feet wet but note that while it seems simple (and basic features are), it get's harder and more challenging as you add more features (texture-alignment, dealing with line-breaks, variable-width fonts etc.) - if you take this route, make it as simple as you can get away with!
  4. Use an off-the-shelf/open-source library. There are a few around if you hunt on Google, the tricky bit is getting them integrated and running. But at least, once you do that, you'll have all the flexibility and maturity they provide.
秋叶绚丽 2024-08-09 14:05:30

我写了一个教程扩展了 JVitela 发布的答案。基本上,它使用相同的想法,但不是将每个字符串渲染到纹理,而是将字体文件中的所有字符渲染到纹理,并使用它来允许完全动态文本渲染,而不会进一步减慢(初始化完成后) 。

与各种字体图集生成器相比,我的方法的主要优点是您可以在项目中提供小字体文件(.ttf .otf),而不必为每种字体变体和大小提供大位图。它可以仅使用字体文件在任何分辨率下生成完美质量的字体:)

教程 包含可在任何项目中使用的完整代码:)

I've written a tutorial that expands on the answer posted by JVitela. Basically, it uses the same idea, but instead of rendering each string to a texture, it renders all characters from a font file to a texture and uses that to allow for full dynamic text rendering with no further slowdowns (once the initialization is complete).

The main advantage of my method, compared to the various font atlas generators, is that you can ship small font files (.ttf .otf) with your project instead of having to ship large bitmaps for every font variation and size. It can generate perfect quality fonts at any resolution using only a font file :)

The tutorial includes full code that can be used in any project :)

纵情客 2024-08-09 14:05:30

看一下 CBFG 和加载/渲染代码的 Android 端口。您应该能够将代码放入您的项目中并立即使用它。

CBFG - http://www.codehead.co.uk/cbfg

Android 加载程序 - http://www.codehead.co.uk/cbfg/TexFont.java

Take a look at CBFG and the Android port of the loading/rendering code. You should be able to drop the code into your project and use it straight away.

CBFG - http://www.codehead.co.uk/cbfg

Android loader - http://www.codehead.co.uk/cbfg/TexFont.java

梦巷 2024-08-09 14:05:30

根据此链接:

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

您可以将任何视图渲染为位图。可能值得假设您可以根据需要布局视图(包括文本、图像等),然后将其渲染为位图。

使用 JVitela 的代码上面你应该能够使用该位图作为 OpenGL 纹理。

According to this link:

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

You can render any View to a bitmap. It's probably worth assuming that you can layout a view as you require (including text, images etc.) and then render it to a Bitmap.

Using JVitela's code above you should be able to use that Bitmap as an OpenGL texture.

眼趣 2024-08-09 14:05:30

我查看了精灵文本示例,对于这样的任务来说,它看起来非常复杂,我也考虑过渲染到纹理,但我担心这可能会导致性能下降。
我可能只需要去看风景,然后担心在过桥时需要移植:)

I looked at the sprite text example and it looks awfully complicated for such a task, I considered rendering to a texture too, but I'm worried about the performance hit that might cause.
I might just have to go with a view instead and worry about porting when it's time to cross that bridge :)

甩你一脸翔 2024-08-09 14:05:30

恕我直言,在游戏中使用 OpenGL ES 有以下三个原因:

  1. 通过使用开放标准避免移动平台之间的差异;
  2. 对渲染过程有更多的控制;
  3. 受益于 GPU 并行处理;

绘制文本始终是游戏设计中的一个问题,因为您正在绘制东西,因此您无法拥有常见活动(例如小部件等)的外观和感觉。

您可以使用框架从 TrueType 字体生成位图字体并渲染它们。我见过的所有框架都以相同的方式运行:在绘制时生成文本的顶点和纹理坐标。这并不是 OpenGL 最有效的使用方式。

最好的方法是在代码的早期为顶点和纹理分配远程缓冲区(顶点缓冲区对象 - VBO),避免绘制时的惰性内存传输操作。

请记住,游戏玩家不喜欢阅读文本,因此您不会编写长的动态生成的文本。对于标签,您可以使用静态纹理,保留时间和分数的动态文本,两者都是带有几个字符长的数字。

所以,我的解决方案很简单:

  1. 为常见标签和警告创建纹理;
  2. 为数字 0-9、“:”、“+”和“-”创建纹理。每个角色都有一个纹理;
  3. 为屏幕中的所有位置生成远程 VBO。我可以在这些位置渲染静态或动态文本,但 VBO 是静态的;
  4. 仅生成一个纹理 VBO,因为文本始终以一种方式渲染;
  5. 在绘制时,我渲染静态文本;
  6. 对于动态文本,我可以查看 VBO 位置,获取字符纹理并一次绘制一个字符。

如果您使用远程静态缓冲区,绘制操作会很快。

我创建一个包含屏幕位置(基于屏幕对角线百分比)和纹理(静态和字符)的 XML 文件,然后在渲染之前加载此 XML。

为了获得高 FPS 速率,您应该避免在绘制时生成 VBO。

IMHO there are three reasons to use OpenGL ES in a game:

  1. Avoid differences between mobile platforms by using an open standard;
  2. To have more control of the render process;
  3. To benefit from GPU parallel processing;

Drawing text is always a problem in game design, because you are drawing things, so you cannot have the look and feel of a common activity, with widgets and so on.

You can use a framework to generate Bitmap fonts from TrueType fonts and render them. All the frameworks I've seen operate the same way: generate the vertex and texture coordinates for the text in draw time. This is not the most efficient use of OpenGL.

The best way is to allocate remote buffers (vertex buffer objects - VBOs) for the vertices and textures early in code, avoiding the lazy memory transfer operations in draw time.

Keep in mind that game players don't like to read text, so you won't write a long dynamically generated text. For labels, you can use static textures, leaving dynamic text for time and score, and both are numeric with a few characters long.

So, my solution is simple:

  1. Create texture for common labels and warnings;
  2. Create texture for numbers 0-9, ":", "+", and "-". One texture for each character;
  3. Generate remote VBOs for all positions in the screen. I can render static or dynamic text in that positions, but the VBOs are static;
  4. Generate just one Texture VBO, as text is always rendered one way;
  5. In draw time, I render the static text;
  6. For dynamic text, I can peek at the position VBO, get the character texture and draw it, a character at a time.

Draw operations are fast, if you use remote static buffers.

I create an XML file with screen positions (based on screen's diagonal percentage) and textures (static and characters), and then I load this XML before rendering.

To get a high FPS rate, you should avoid generating VBOs at draw time.

血之狂魔 2024-08-09 14:05:30

查看 GLSurfaceView 示例 中的“Sprite Text”示例。

Look at the "Sprite Text" sample in the GLSurfaceView samples.

衣神在巴黎 2024-08-09 14:05:30

如果您坚持使用 GL,您可以将文本渲染到纹理上。假设大部分 HUD 是相对静态的,您不必过于频繁地将纹理加载到纹理内存中。

If you insist on using GL, you could render the text on to textures. Assuming that most of the HUD is relatively static, you shouldn't have to load the textures to texture memory too often.

情魔剑神 2024-08-09 14:05:30

看一下CBFG和加载/渲染的Android端口
代码。您应该能够将代码放入您的项目中并使用它
马上。

  1. CBFG

  2. Android loader

我对此实现有问题。它只显示一个字符,当我尝试更改字体位图的大小(我需要特殊字母)时,整个绘制失败:(

Take a look at CBFG and the Android port of the loading/rendering
code. You should be able to drop the code into your project and use it
straight away.

  1. CBFG

  2. Android loader

I have problems with this implementation. It displays only one character, when I try do change size of the font's bitmap (I need special letters) whole draw fails :(

黯然 2024-08-09 14:05:30

我已经找了几个小时了,这是我遇到的第一篇文章,虽然它有最好的答案,但我认为最受欢迎的答案是不切实际的。当然是为了我需要的。
weichsel 和 shakazed 的答案就在按钮上,但在文章中有点模糊。
让您正确参与该项目。这里:
只需根据现有示例创建一个新的 Android 项目即可。选择 ApiDemos:

查看源文件夹

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

你会找到你需要的一切。

I have been looking for this for a few hours, this was the first article i came accross and although it has the best answer, the most popular answers i think are off the mark. Certainly for what i needed.
weichsel's and shakazed's answers were right on the button but a bit obscured in the articles.
To put you right to the project. Here:
Just create a new Android project based on existing sample. Choose ApiDemos:

Look under the source folder

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

And you will find everything you need.

自由如风 2024-08-09 14:05:30

对于静态文本

  • 使用 PC 上使用的所有文字生成图像(例如使用 GIMP)。
  • 将其加载为纹理并将其用作平面的材质。

对于需要偶尔更新的长文本

  • 让android在位图画布上绘制(JVitela的解决方案)。
  • 将其加载为飞机的材料。
  • 为每个单词使用不同的纹理坐标。

对于数字(格式为 00.0):

  • 生成包含所有数字和一个点的图像。
  • 将其加载为飞机的材料。
  • 使用下面的着色器。
  • 在 onDraw 事件中仅更新发送到着色器的值变量。

    精度高浮点数;
    高精度二维采样器;
    
    统一浮动uTime;
    统一浮点数uValue;
    统一 vec3 iResolution;
    
    改变 vec4 v_Color;
    改变 vec2 vTextureCoord;
    均匀采样器2D s_texture;
    
    无效主(){
    
    vec4 fragColor = vec4(1.0, 0.5, 0.2, 0.5);
    vec2 uv = vTextureCoord;
    
    浮点数除数 = 10.75;
    浮点数;
    浮动我;
    浮动uCol;
    浮动uRow;
    
    如果(uv.y < 0.45){
        如果 (uv.x > 0.75) {
            数字 = 下限(uValue*10.0);
            数字 = 数字 - 下限(数字/10.0)*10.0;
            i = 48.0 - 32.0 + 数字;
            uRow = 楼层(i / 10.0);
            uCol = i - 10.0 * uRow;
            fragColor =texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-1.5) / devisor, uRow / devisor) );
        } 否则如果 (uv.x > 0.5) {
            uCol = 4.0;
            u行 = 1.0;
            fragColor =texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-1.0) / devisor, uRow / devisor) );
        } 否则如果 (uv.x > 0.25) {
            数字=下限(uValue);
            数字 = 数字 - 下限(数字/10.0)*10.0;
            i = 48.0 - 32.0 + 数字;
            uRow = 楼层(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) {
            数字 = 下限(uValue/10.0);
            数字 = 数字 - 下限(数字/10.0)*10.0;
            i = 48.0 - 32.0 + 数字;
            uRow = 楼层(i / 10.0);
            uCol = i - 10.0 * uRow;
            fragColor =texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-0.0) / devisor, uRow / devisor) );
        } 别的 {
            fragColor = vec4(0.0, 0.0, 0.0, 0.0);
        }
    } 别的 {
        fragColor = vec4(0.0, 0.0, 0.0, 0.0);
    }
    gl_FragColor = fragColor;
    
    }
    

上面的代码适用于纹理图集,其中数字从字体图集(纹理)第二行的第七列的 0 开始。

参考https://www.shadertoy.com/view/Xl23Dw进行演示(纹理错误尽管)

For static text:

  • Generate an image with all words used on your PC (For example with GIMP).
  • Load this as a texture and use it as material for a plane.

For long text that needs to be updated once in a while:

  • Let android draw on a bitmap canvas (JVitela's solution).
  • Load this as material for a plane.
  • Use different texture coordinates for each word.

For a number (formatted 00.0):

  • Generate an image with all numbers and a dot.
  • Load this as material for a plane.
  • Use below shader.
  • In your onDraw event only update the value variable sent to the 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;
    
    }
    

Above code works for a texture atlas where numbers start from 0 at the 7th column of the 2nd row of the font atlas (texture).

Refer to https://www.shadertoy.com/view/Xl23Dw for demonstration (with wrong texture though)

很糊涂小朋友 2024-08-09 14:05:30

在OpenGL ES 2.0/3.0中,您还可以结合OGL视图和Android的UI元素:

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

创建布局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>

要从渲染线程更新元素,可以使用Handler/Looper。

In the OpenGL ES 2.0/3.0 you can also combining OGL View and Android's UI-elements:

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

Create 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>

To update elements from the render thread, can use Handler/Looper.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文