在OpenGL ES中绘制文本

时间:2009-08-27 06:10:47

标签: android opengl-es text-rendering

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

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

有什么想法吗?

13 个答案:

答案 0 :(得分:161)

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

答案 1 :(得分:100)

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

  1. 在SurfaceView上放置TextView。这是缓慢而糟糕的,但却是最直接的方法。
  2. 将常见字符串渲染为纹理,并简单地绘制这些纹理。这是迄今为止最简单,最快速但最不灵活的。
  3. 基于精灵滚动自己的文字渲染代码。如果2不是选项,可能是第二个最佳选择。让你的脚湿润的一个好方法,但请注意,虽然看起来很简单(和基本功能),但是当你添加更多功能(纹理对齐,处理换行符,可变宽度字体等)时,它会变得更难,更具挑战性。 ) - 如果你采取这条路线,尽量让它变得简单!
  4. 使用现成的/开源库。如果您在Google上搜索,有一些问题,那就是让它们集成并运行。但至少,一旦你这样做,你将拥有他们提供的所有灵活性和成熟度。

答案 2 :(得分:35)

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

与各种字体图集生成器相比,我的方法的主要优点是,您可以随项目一起发送小字体文件(.ttf .otf),而不必为每个字体变体和大小发送大型位图。只需使用字体文件,它就可以在任何分辨率下生成完美品质的字体:)

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

答案 3 :(得分:8)

根据这个链接:

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

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

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

答案 4 :(得分:7)

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

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

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

答案 5 :(得分:6)

我查看了sprite文本示例,它看起来非常复杂,我考虑渲染到纹理,但我担心可能导致的性能损失。 我可能只需要使用视图而担心在跨越该桥时移植:)

答案 6 :(得分:4)

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

答案 7 :(得分:3)

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

答案 8 :(得分:3)

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

  1. CBFG

  2. Android loader

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

答案 9 :(得分:3)

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

  1. 使用开放标准避免移动平台之间的差异;
  2. 要更好地控制渲染过程;
  3. 受益于GPU并行处理;
  4. 绘图文本在游戏设计中总是一个问题,因为你正在绘制东西,所以你不能拥有常见活动的外观和感觉,使用小部件等等。

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

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

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

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

    1. 为常见标签和警告创建纹理;
    2. 为数字0-9,“:”,“+”和“ - ”创建纹理。每个角色一个纹理;
    3. 为屏幕中的所有位置生成远程VBO。我可以在那个位置渲染静态或动态文本,但VBO是静态的;
    4. 只生成一个纹理VBO,因为文本总是以一种方式呈现;
    5. 在绘画时间,我渲染静态文本;
    6. 对于动态文字,我可以查看位置VBO,获取角色纹理并绘制它,一次一个角色。
    7. 如果使用远程静态缓冲区,绘制操作很快。

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

      要获得较高的FPS率,您应该避免在抽奖时生成VBO。

答案 10 :(得分:2)

我一直在寻找这个几个小时,这是我第一篇文章,虽然它有最好的答案,但我认为最受欢迎的答案是不合适的。当然是我需要的。 weichsel和shakazed的答案都在按钮上,但在文章中有点模糊。 让你正确的项目。这里: 只需根据现有示例创建一个新的Android项目。选择ApiDemos:

查看源文件夹

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

你会找到你需要的一切。

答案 11 :(得分:1)

对于静态文字

  • 使用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;
    
    }
    

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

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

答案 12 :(得分: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。