在OpenGL ES中绘制文本

134

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

我看到过使用View作为叠加层的示例,但我不确定是否要这样做,因为我可能以后想把游戏移植到其他平台。

有什么想法吗?


请查看这个项目: http://code.google.com/p/rokon/ - whunmr
看看libgdx是如何通过位图字体实现这一点的。 - Robert Massaioli
ftgles 库非常完整。特别是,它包括从字体生成网格的支持(请参见矢量化器)。我认为这是在OpenGL中绘制文本最干净的方式。 - Alexis Wilke
13个回答

169

将文本渲染到纹理中比 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();

5
这帮助我在我的应用程序上显示一个小的FPS计数器以进行调试,谢谢! - stealthcopter
2
你可以使用这个来生成所有字母和数字的纹理,当你加载其他纹理时,然后将它们组合在一起形成单词或数字。那么它的效率不会比任何其他gl纹理差。 - twDuke
11
这很慢,会降低游戏的帧率,如果文本一直在变化(比如得分等),但对于半静态的内容(玩家名称、关卡名称等)效果还不错。 - led42
3
我想指出这个答案中的代码可能只是一个演示,没有做任何优化!请根据自己的方式进行优化/缓存。 - Sherif elKhatib
1
你能提供OpenGL ES 2.0的吗? - unlimited101
显示剩余11条评论

107

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

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

3
我决定在我的GLView上添加一个视图,虽然这可能不是最有效的方法,但该视图更新频率不高,而且它为我提供了添加任何我想要的字体的灵活性。感谢所有回复! - shakazed
1
我该如何将常见字符串渲染成纹理,并简单地绘制这些纹理呢?谢谢。 - VansFannel
1
VansFannel:只需使用绘图程序将所有字符串放在一个图像中,然后在您的应用程序中使用偏移量仅呈现包含所需字符串的部分图像。 - Dave
2
或者查看JVitela下面的答案,以获得更多编程方式来实现这一点。 - Dave
4
JVitela的回答更好。这是我目前正在使用的。你从标准的Android View+Canvas转换到OpenGL的原因之一是为了提高速度。在你的OpenGL上添加文本框有点抵消了这个优势。 - Shivan Dragon

39
我写了一个tutorial,扩展了JVitela发布的答案。基本上,它使用相同的思路,但是不是将每个字符串呈现为纹理,而是将字体文件中的所有字符呈现为纹理,并使用该纹理允许完全动态文本呈现,无需进一步减速(一旦初始化完成)。
与各种字体图集生成器相比,我的方法的主要优点是,您可以使用小型字体文件(.ttf .otf)随项目一起发布,而不必为每种字体变体和大小都发布大型位图。它可以仅使用字体文件以任何分辨率生成完美质量的字体:) 教程包括可用于任何项目的完整代码:)

我目前正在研究这个解决方案,我相信我会及时找到答案,但是你的实现是否使用任何运行时分配? - Nick Hartung
@Nick - 所有分配(纹理,顶点缓冲区等)都是在创建字体实例时完成的,使用字体实例渲染字符串不需要进行进一步的分配。因此,您可以在“加载时间”创建字体,而无需在运行时进行进一步的分配。 - free3dom
哇,太棒了!这真的很有帮助,特别是在你的文本经常变化的情况下。 - mdiener
这是性能方面最好的答案,适用于需要在运行时频繁更改文本的应用程序。对于Android来说,我还没有看到比这更好的东西。不过它可以使用OpenGL ES进行移植。 - greeble31
1
如果您不想处理文本对齐、换行等问题,可以使用 TextView。TextView 可以轻松地呈现在画布上。从性能方面来看,这种方法不应该比给定的方法更重,您只需要一个 TextView 实例即可呈现所需的所有文本。这样,您还可以免费获得简单的 HTML 格式化。 - Gena Batsyan
你好,使用setEGLContextClientVersion(3)让它工作是可能的吗? - Siarhei

8

是的,我在游戏中使用了MapView,并将其绑定到一个gl纹理上,以便结合地图和OpenGL。所以,是的,所有视图都有onDraw(Canvas c),您可以传递任何画布并将任何画布绑定到任何位图。 - HaMMeReD

8

6

我看了一下精灵文本的示例,对于这样一个任务来说,它看起来非常复杂。我也考虑过渲染到纹理上,但担心可能会导致性能问题。也许我只能使用视图,并在需要跨越这个障碍时担心移植的问题 :)


5

在游戏中使用OpenGL ES有三个原因:

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

在游戏设计中,绘制文字总是一个问题,因为你正在绘制物体,所以不能像常规活动那样具有外观和感觉,带有小部件等等。

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

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

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

因此,我的解决方案很简单:

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

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

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

要获得高FPS率,您应避免在绘制时生成VBO。


你所说的“VOB”,是指“VBO”(顶点缓冲对象)吗? - Dan Hulme

4

3
如果你坚持使用GL,你可以将文本渲染到纹理上。假设大部分HUD是相对静态的,你不需要太经常地将纹理加载到纹理内存中。

3

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

  1. CBFG

  2. Android加载器

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接