在OpenGL ES中绘制文本


131

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

我已经看到了一个使用“视图”作为叠加层的示例,但是我不知道是否要这样做,因为以后可能要将游戏移植到其他平台上。

有任何想法吗?


看看这个项目:code.google.com/p/rokon
whunmr 2010年

查看libgdx通过位图字体执行此操作的方式。
罗伯特·马赛利

Answers:


103

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

  1. 将TextView放在SurfaceView上。这既缓慢又糟糕,但却是最直接的方法。
  2. 将常见的字符串渲染为纹理,然后简单地绘制这些纹理。到目前为止,这是最简单,最快,但最不灵活的。
  3. 基于Sprite的自行编写文本呈现代码。如果没有选择2,可能是第二好的选择。弄湿脚的一种好方法,但请注意,虽然它看起来很简单(并且具有基本功能),但随着添加更多功能(纹理对齐,换行符,可变宽度字体等),它变得更加困难且更具挑战性。 )-如果您选择这条路线,请尽量简化路线!
  4. 使用现成的/开源库。如果您在Google上打猎,可能会有一些麻烦的地方,那就是将它们集成并运行。但是至少,一旦这样做,您将拥有它们提供的所有灵活性和成熟度。

3
我已经决定在GLView上添加视图,这可能不是最有效的方法,但是视图不是经常更新,而且它使我可以灵活地添加所需的字体。感谢所有的答复!
09年

1
如何将常见的字符串渲染为纹理,并简单地绘制这些纹理?谢谢。
VansFannel 2011年

1
VansFannel:只需使用绘画程序将所有字符串放入一个图像中,然后在应用程序中使用偏移量仅渲染图像中包含所需字符串的部分。
戴夫

2
或者,请参见下面的JVitela答案,以更编程的方式实现这一目标。
戴夫

4
JVitela的答案更好。这就是我目前正在使用的。您从标准android vier + canvas切换到opengl的原因(以及其他)是为了提高速度。在您的opengl上添加一个文本框可以消除这种情况。
Shivan Dragon

166

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

5
这帮助我在我的应用程序上以小fps计数器显示以进行调试,谢谢!
隐身直升机2011年

2
加载其他纹理,然后将它们放在一起形成单词或数字时,可以使用它生成所有字母和数字作为纹理。然后,它的效率将不亚于任何其他gl纹理。
twDuke 2011年

9
这非常慢,会在游戏中由于文本不断变化(得分等)而导致fps消失,但是对于半静态内容(玩家名称,关卡名称等)则效果很好。
led42

3
我想指出的是,此答​​案中的代码可能只是一个演示,并且从未进行过优化!请以您自己的方式进行优化/缓存。
Sherif elKhatib 2013年

1
您可以为OpenGL ES 2.0提供此功能吗?
无限

36

我编写了一个教程,扩展了JVitela发布的答案。基本上,它使用相同的想法,但不是将每个字符串呈现为纹理,而是将字体文件中的所有字符呈现为纹理,并使用它来实现完整的动态文本呈现,而不会进一步减速(一旦完成初始化) 。

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

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


我目前正在研究此解决方案,并且可以确定会及时找到答案,但是您的实现是否使用任何运行时分配?
Nick Hartung

@Nick-创建字体实例时完成所有分配(纹理,顶点缓冲区等),使用字体实例渲染字符串不需要进一步的分配。因此,您可以在“加载时”创建字体,而在运行时无需进一步分配。
Free3dom 2013年

哇,辛苦了!这确实很有用,尤其是在文本经常更改的情况下。
mdiener 2015年

对于必须在运行时频繁更改文本的应用程序,这是性能方面的最佳答案。还没有看到其他比Android更好的东西。不过,它可以使用OpenGL ES的端口。
greeble31年

1
如果您不想处理文本对齐,换行等问题,可以使用TextView。TextView可以轻松呈现到画布中。性能上不应该比给定的方法重,您只需要一个TextView实例即可呈现所需的所有文本。这样,您还可以免费获得简单的HTML格式。
Gena Batsyan

8

根据此链接:

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

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

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


是的,我在游戏中使用MapView进行了处理,并将其绑定到gl纹理,因此将地图和opengl结合在一起。是的,所有视图都具有onDraw(Canvas c),您可以传入任何画布并将任何画布绑定到任何位图。
HaMMeReD 2011年


6

我查看了sprite文本示例,该任务看起来非常复杂,我也考虑过渲染到纹理,但是我担心这可能会导致性能下降。我可能只需要查看一个视图,并担心何时该跨过桥进行移植:)



4

恕我直言,在游戏中使用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”(顶点缓冲对象)吗?
丹·赫尔姆

3

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


3

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

  1. 脑血流量

  2. Android加载器

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


2

我已经寻找了好几个小时,这是我的第一篇文章,尽管它有最好的答案,但我认为最受欢迎的答案并不可行。当然是因为我需要的。weichsel和shakazed的答案就在按钮上,但是在文章中有些模糊。为了使您直接参与该项目。此处:只需根据现有示例创建一个新的Android项目。选择ApiDemos:

在源文件夹下查看

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

您会找到所需的一切。


1

对于静态文本

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

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

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

对于数字(格式为00.0):

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

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

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

请参阅https://www.shadertoy.com/view/Xl23Dw进行演示(但纹理错误)


0

在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。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.