在 OpenGL 中绘制文本

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

我看到过一个使用 View 作为覆盖的例子,但是我不知道是否要这样做,因为我可能想稍后将游戏移植到其他平台。

有什么想法吗?

116389 次浏览

如果您坚持使用 GL,您可以将文本渲染为纹理。假设平视显示器的大部分是相对静态的,那么就不必经常将纹理加载到纹理内存中。

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

我看了精灵文本的例子,它看起来非常复杂,这样的任务,我考虑渲染纹理,但我担心的性能打击,可能会导致。 我可能只需要使用一个视图来代替,并且在跨越那个桥梁的时候担心移植的问题:)

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

  1. 在你的 SurfaceView 上放置一个文本视图。 这是一个缓慢而糟糕的方法,但是是最直接的方法。
  2. 将公共字符串渲染为纹理,然后简单地绘制这些纹理。这是目前为止最简单、最快的,但是最不灵活的。
  3. 基于精灵的滚动自己的文本呈现代码。如果2不是一个选项,那么可能是第二个最佳选择。这是一个很好的方法,但是要注意,虽然它看起来很简单(基本功能也很简单) ,但是随着你添加更多功能(纹理对齐、处理分行符、可变宽度字体等等) ,它会变得更加困难和具有挑战性——如果你选择这条路线,尽可能的简单!
  4. 使用现成的/开源的库。如果你在谷歌上搜索的话,会发现有一些,棘手的是如何将它们集成并运行。但至少,一旦你这样做了,你将拥有他们提供的所有灵活性和成熟性。

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

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

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

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

根据这个链接:

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

可以将 任何观点渲染为位图。假设您可以根据需要对视图(包括文本、图像等)进行布局,然后将其呈现为 Bitmap,这可能是值得的。

使用 JVitela 的代码 以上,您应该能够使用该位图作为 OpenGL 纹理。

我一直在寻找这几个小时,这是第一篇文章,我看到的,虽然它有最好的答案,最流行的答案,我认为是离谱的。当然是为了我所需要的。 威克塞尔和沙克兹的答案都在按钮上,但在文章中有点模糊。 让你直接进入这个项目。这里: 只需基于现有的示例创建一个新的 Android 项目。选择 ApiDemos:

看看源文件夹下面

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

你会找到你需要的一切。

我已经写了一个 教程,扩展了由 JVitela发布的答案。基本上,它使用了相同的思想,但是它没有将每个字符串渲染为一个纹理,而是将字体文件中的所有字符都渲染为一个纹理,并且使用这种方法来支持完整的动态文本渲染,而不需要进一步的减速(一旦初始化完成)。

与各种字体地图生成器相比,我的方法的主要优点是可以提供小型字体文件(。TTF.而不必为每种字体变化和大小发布大的位图。它可以生成高质量的字体在任何分辨率只使用一个字体文件:)

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

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

  1. CBFG

  2. 安卓加载程序

我在这个实现中遇到了问题。它只显示一个字符,当我尝试改变字体的位图大小(我需要特殊的字母)整个绘制失败: (

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

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

在游戏设计中,绘制文本总是一个问题,因为您正在绘制东西,所以您不能拥有常见活动的外观和感觉,包括小部件等等。

可以使用框架从 TrueType 字体生成 Bitmap 字体并呈现它们。我见过的所有框架的操作方式都是一样的: 在绘制时为文本生成顶点和纹理坐标。这不是 OpenGL 最有效的用法。

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

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

所以,我的解决办法很简单:

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

如果使用远程静态缓冲区,绘制操作是快速的。

我创建了一个带有屏幕位置(基于屏幕的对角线百分比)和纹理(静态和字符)的 XML 文件,然后在呈现之前加载这个 XML。

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

对于 静态文本:

  • 生成一个包含 PC 上使用的所有单词的图像(例如,使用 GIMP)。
  • 加载这作为一个纹理和使用它作为材料的飞机。

对于需要时不时更新的 很长的短信:

  • 让 Android 在位图画布上绘制(JVitela 的解决方案)。
  • 把这个装上飞机的材料。
  • 对每个单词使用不同的纹理坐标。

对于 一个数字(格式为00.0) :

  • 生成一个包含所有数字和点的图像。
  • 把这个装上飞机的材料。
  • 使用下面的着色器。
  • 在 onDraw 事件中,只更新发送到着色器的值变量。

    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)

在 OpenGL ES 2.0/3.0中,你还可以结合 OGL View 和 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。