如何只使用OpenGL方法绘制文本?

52

我没有其他选项可以使用OpenGL方法(即glxxx()方法)。我需要仅使用GL方法来绘制文本。阅读了红皮书之后,我了解到唯一的方式是通过glBitmap()方法。如果这是唯一可行的方法,那么有没有人能够提供包含所有字符的像素数组信息呢?是否有其他方法可以绘制文本?

6个回答

100

理论

为什么它很困难

流行的字体格式,如TrueTypeOpenType,都是矢量轮廓格式:它们使用贝塞尔曲线来定义字母的边界。

图片来源

将这些格式转换为像素数组(光栅化)过于具体,超出了OpenGL的范围,特别是因为OpenGL没有非直线的基本图形(例如参见为什么OpenGL中没有圆形或椭圆形的基本图形?)。

最简单的方法是首先在CPU上自己光栅化字体,然后将像素数组作为纹理传递给OpenGL。

OpenGL非常擅长处理像素数组的纹理。

纹理图集

我们可以为每一帧光栅化字符并重新创建纹理,但这不是很高效,特别是如果字符具有固定大小。

更高效的方法是将您计划使用的所有字符光栅化并压缩到一个单一的纹理中。

然后将其一次性传输到GPU,并使用自定义的UV坐标来选择正确的字符。

这种方法被称为纹理图集,它不仅可以用于纹理,还可以用于其他重复使用的纹理,比如2D游戏中的瓷砖或Web界面图标。
维基百科上的完整纹理图片,它本身来自freetype-gl,很好地说明了这一点。 我怀疑将字符放置优化到最小纹理问题是一个NP难问题,参见:什么算法可以用于将不同大小的矩形尽可能地打包到最小的矩形中? 在网页开发中,也使用了相同的技术来一次传输多个小图像(如图标),但这被称为“CSS Sprites”:https://css-tricks.com/css-sprites/,用于隐藏网络延迟而不是CPU / GPU通信的延迟。 非CPU光栅方法 还存在一些不使用CPU光栅到纹理的方法。
CPU光栅化很简单,因为它尽可能少地使用GPU,但我们也开始思考是否可能进一步提高GPU的效率。 这个FOSDEM 2014视频解释了其他现有的技术。

透视下的三维几何内的字体

在三维几何体内使用透视(与正交HUD相比)来渲染字体要复杂得多,因为透视可能使字符的某一部分离屏幕更近且更大,从而使统一的CPU离散化(如光栅化、细分)在近处看起来不好。这实际上是一个活跃的研究课题。

enter image description here

距离场是现在流行的技术之一。

实现

以下示例均在Ubuntu 15.10上进行了测试。

由于这是一个复杂的问题,正如之前讨论的那样,大多数示例都很大,会超过此答案的30k字符限制,所以只需克隆相应的Git存储库进行编译。

然而,它们都是完全开源的,所以你可以直接阅读源代码。

FreeType解决方案

FreeType看起来是主要的开源字体光栅化库,因此它允许我们使用TrueType和OpenType字体,是最优雅的解决方案。

示例/教程:

其他字体光栅化器

这些似乎不如FreeType好,但可能更轻量级:

Anton的OpenGL 4教程示例26“位图字体”

字体是由作者手动创建并存储在单个.png文件中。字母以数组形式存储在图像内。

这种方法当然不太通用,而且在国际化方面可能会遇到困难。

构建方式:

make -f Makefile.linux64

输出预览:

enter image description here

opengl-tutorial 第11章 "2D字体"

纹理是从DDS文件生成的。

本教程解释了如何使用CBFGPaint.Net创建DDS文件。

输出预览:

enter image description here

由于某种原因,Suzanne对我来说消失了,但计时器正常工作:https://github.com/opengl-tutorials/ogl/issues/15

FreeGLUT

GLUT有glutStrokeCharacter,而FreeGLUT是开源的... https://github.com/dcnieho/FreeGLUT/blob/FG_3_0_0/src/fg_font.c#L255

OpenGLText

https://github.com/tlorach/OpenGLText

TrueType光栅化。由NVIDIA员工开发。旨在实现可重用性。尚未尝试过。
ARM Mali GLES SDK示例 http://malideveloper.arm.com/resources/sample-code/simple-text-rendering/似乎将所有字符编码到PNG上,并从中裁剪。
glText

https://github.com/vallentin/glText

单头文件C库,使用嵌入到库中的位图字体。

SDL_ttf

enter image description here

来源:https://github.com/cirosantilli/cpp-cheat/blob/d36527fe4977bb9ef4b885b1ec92bd0cd3444a98/sdl/ttf.c

独立于SDL的树上,易于集成。

然而,它不提供纹理图集实现,因此性能会受到限制:如何高效地使用SDL2渲染字体和文本?

相关主题


9
在纯OpenGL中绘制文本并不是一项简单的任务。你可能需要查看用于此目的的库(无论是使用库还是作为示例实现)。
一些良好的起点可以是GLFontOpenGL字体调查NeHe位图字体教程(Windows)
请注意,如字体调查中所提到的,位图并不是在OpenGL中实现文本的唯一方法。

8

本文介绍了如何使用各种技术在OpenGL中呈现文本。

仅使用OpenGL,有以下几种方法:

  • 使用glBitmap
  • 使用纹理
  • 使用显示列表

8

在此输入图片描述

使用 glutStrokeCharacter(GLUT_STROKE_ROMAN, myCharString)

例如:星球大战字幕滚动效果。

#include <windows.h>
#include <string.h>
#include <GL\glut.h>
#include <iostream.h>
#include <fstream.h>

GLfloat UpwardsScrollVelocity = -10.0;
float view=20.0;

char quote[6][80];
int numberOfQuotes=0,i;

//*********************************************
//*  glutIdleFunc(timeTick);                  *
//*********************************************

void timeTick(void)
{
    if (UpwardsScrollVelocity< -600)
        view-=0.000011;
    if(view < 0) {view=20; UpwardsScrollVelocity = -10.0;}
    //  exit(0);
    UpwardsScrollVelocity -= 0.015;
  glutPostRedisplay();

}


//*********************************************
//* printToConsoleWindow()                *
//*********************************************

void printToConsoleWindow()
{
    int l,lenghOfQuote, i;

    for(  l=0;l<numberOfQuotes;l++)
    {
        lenghOfQuote = (int)strlen(quote[l]);

        for (i = 0; i < lenghOfQuote; i++)
        {
          //cout<<quote[l][i];
        }
          //out<<endl;
    }

}

//*********************************************
//* RenderToDisplay()                       *
//*********************************************

void RenderToDisplay()
{
    int l,lenghOfQuote, i;

    glTranslatef(0.0, -100, UpwardsScrollVelocity);
    glRotatef(-20, 1.0, 0.0, 0.0);
    glScalef(0.1, 0.1, 0.1);



    for(  l=0;l<numberOfQuotes;l++)
    {
        lenghOfQuote = (int)strlen(quote[l]);
        glPushMatrix();
        glTranslatef(-(lenghOfQuote*37), -(l*200), 0.0);
        for (i = 0; i < lenghOfQuote; i++)
        {
            glColor3f((UpwardsScrollVelocity/10)+300+(l*10),(UpwardsScrollVelocity/10)+300+(l*10),0.0);
            glutStrokeCharacter(GLUT_STROKE_ROMAN, quote[l][i]);
        }
        glPopMatrix();
    }

}
//*********************************************
//* glutDisplayFunc(myDisplayFunction);       *
//*********************************************

void myDisplayFunction(void)
{
  glClear(GL_COLOR_BUFFER_BIT);
  glLoadIdentity();
  gluLookAt(0.0, 30.0, 100.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
  RenderToDisplay();
  glutSwapBuffers();
}
//*********************************************
//* glutReshapeFunc(reshape);               *
//*********************************************

void reshape(int w, int h)
{
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(60, 1.0, 1.0, 3200);
  glMatrixMode(GL_MODELVIEW);
}

//*********************************************
//* int main()                                *
//*********************************************


int main()
{
    strcpy(quote[0],"Luke, I am your father!.");
    strcpy(quote[1],"Obi-Wan has taught you well. ");
    strcpy(quote[2],"The force is strong with this one. ");
    strcpy(quote[3],"Alert all commands. Calculate every possible destination along their last known trajectory. ");
    strcpy(quote[4],"The force is with you, young Skywalker, but you are not a Jedi yet.");
    numberOfQuotes=5;

    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(800, 400);
    glutCreateWindow("StarWars scroller");
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glLineWidth(3);

    glutDisplayFunc(myDisplayFunction);
    glutReshapeFunc(reshape);
    glutIdleFunc(timeTick);
    glutMainLoop();

    return 0;
}

51
glutStrokeCharacter不是一个OpenGL函数。 - BЈовић
19
虽然这符合 glxxx() 操作的要求 :) - AndroidGecko
1
这是修改后能在Ubuntu上运行的代码(glutInit,包括):https://gist.github.com/tgandor/3963f112324030a2c833 - Tomasz Gandor
1
你的第四个引用中存在缓冲区溢出! - flownt
看起来你是正确的。“警告所有指令。计算沿着它们最后已知的轨迹的每个可能的目的地。”是一个92字节的字符串,但只有80字节被分配了。 - James M. Lay

4

使用带有字符的图像作为纹理,并根据您想要的字符绘制该纹理的一部分。您可以使用绘画程序创建该纹理,硬编码它或使用窗口组件来绘制到图像并检索该图像以获得系统字体的精确副本。

无需使用Glut或任何其他扩展,只需基本的OpenGL操作能力即可完成任务。它可以胜任工作,更不用说专业程序员在非常成功的游戏和其他应用程序中已经像这样做了几十年。


2
甚至有一些程序可以用来为您生成图像纹理。 - fintelia

2
我认为在OpenGL中绘制文本的最佳解决方案是纹理字体,我已经使用它们很长时间了。它们灵活、快速且外观漂亮(除了一些罕见的例外)。我使用特殊的程序将字体文件(例如.ttf)转换为纹理,并保存到某种内部“字体”格式的文件中(我开发了格式和程序,基于http://content.gpwiki.org/index.php/OpenGL:Tutorials:Font_System,尽管我的版本支持Unicode等方面已经相当远离原始版本)。启动主应用程序时,从这个“内部”格式加载字体。请参阅上面的链接获取更多信息。
使用这种方法,主应用程序不使用任何特殊的库,如FreeType,这对我来说也是不可取的。文本使用标准的OpenGL函数绘制。

1
如果我可以为这个已经非常好的答案添加一些内容,那就要注意任何字体渲染系统的优化,因为它可能会影响到在移动设备等非常有限的硬件资源上运行时的性能。 - Maurizio Benedetti

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