使用 openGL ES 2.0 结合其他线条绘制功能的字体渲染(Freetype)不起作用

Posted

技术标签:

【中文标题】使用 openGL ES 2.0 结合其他线条绘制功能的字体渲染(Freetype)不起作用【英文标题】:Font rendering (Freetype) with openGL ES 2.0 combined with other line drawing functions does not work 【发布时间】:2018-06-22 07:17:30 【问题描述】:

本帖与https相关://***.com/questions/50955558/render-fonts-with-sdl2-opengl-es-2-0-glsl-1-0-freetype

我在结合字体渲染和使用此功能时遇到问题,如下所示:

// Create VBO (Vertex Buffer Object) based on the vertices provided, render the vertices on the 
// background buffer and eventually swap buffers to update the display.
// Return index of VBO buffer
GLuint drawVertices(SDL_Window *window, Vertex *vertices, GLsizei numVertices, int mode)

  // Number of vertices elements must be provided as a param (see numVertices) because 
  // sizeof() cannot calculate the size of the type a pointer points to  
  //GLsizei vertSize = sizeof(vertices[0]);

  //SDL_Log("Vertices size is %d, no of vertices is %d", vertSize, numVertices);

  // Create a VBO (Vertex Buffer Object)
  GLuint VBO = vboCreate(vertices, numVertices);

  if (!VBO) 
    // Failed. Error message has already been printed, so just quit
    return (GLuint)NULL;
  

  // Set up for rendering the triangle (activate the VBO)
  GLuint positionIdx = 0; // Position is vertex attribute 0
  glBindBuffer(GL_ARRAY_BUFFER, VBO);
  glVertexAttribPointer(positionIdx, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)0);
  glEnableVertexAttribArray(positionIdx);

  if (mode & CLEAR)
    // Set color of the clear operation
    glClearColor(0.0, 0.0, 0.0, 1.0);
    // Clears the invisible buffer
    glClear(GL_COLOR_BUFFER_BIT);
  

  // Now draw!
  //  GL_POINTS = Draw only the pixels that correspond to the vertices coordinates
  //  GL_LINE_STRIP = Draw line that connects the vertices coordinates
  //  GL_LINE_LOOP = Draw line that connects the vertices coordinates plus a line that re-connects the last coordinate with the first
  if (mode & RENDER) glDrawArrays(GL_LINE_STRIP, 0, numVertices); 

  // Don’t forget to flip the buffers as well, to display the final image:
  // Update the window
  if (mode & UPDATE) SDL_GL_SwapWindow(window); 

  return VBO;

此函数使用 glDrawArrays() 绘制一系列连接所提供顶点的线。标志 CLEAR、RENDER 和 UPDATE 被用来让我做类似的事情:

drawVertices(window, vertices, sizeOfVertices, CLEAR | RENDER);
drawVertices(window, vertices, sizeOfVertices, RENDER);
drawVertices(window, vertices, sizeOfVertices, RENDER | UPDATE);

我对字体渲染功能做了同样的事情,因此我可以在不同的 x,y 坐标中绘制多个字符串。接下来的两个函数根据我首先提交的代码进行字体渲染,并且偏离了您的更正。

void render_text(const char *text, float x, float y, float sx, float sy) 
  const char *p;

  FT_GlyphSlot g = face->glyph;

  SDL_Log("Debug info: glyph w: %d, glyph rows: %d", g->bitmap.width, g->bitmap.rows);

  for(p = text; *p; p++) 

    // If FT_Load_Char() returns a non-zero value then the glyph in *p could not be loaded
    if(FT_Load_Char(face, *p, FT_LOAD_RENDER)) continue; 

    glTexImage2D(
      GL_TEXTURE_2D,
      0,
      GL_RED,
      g->bitmap.width,
      g->bitmap.rows,
      0,
      GL_RED,
      GL_UNSIGNED_BYTE,
      g->bitmap.buffer
    );

    float x2 = x + g->bitmap_left * sx;
    float y2 = -y - g->bitmap_top * sy;
    float w = g->bitmap.width * sx;
    float h = g->bitmap.rows * sy;

    GLfloat box[4][4] = 
        x2,     -y2    , 0, 0,
        x2 + w, -y2    , 1, 0,
        x2,     -y2 - h, 0, 1,
        x2 + w, -y2 - h, 1, 1,
    ;

    glBufferData(GL_ARRAY_BUFFER, sizeof box, box, GL_DYNAMIC_DRAW);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    x += (g->advance.x>>6) * sx;
    y += (g->advance.y>>6) * sy;
  



void glRenderText(char *text, int _x, int _y, SDL_Color rgb, int mode) 

  float x = _x;
  float y = _y;

  int w=0, h=0;

  SDL_GetWindowSize(SDLmain.window, &w, &h);
  float xMax = 2.0 / (float)w;
  float yMax = 2.0 / (float)h;

  GLuint color_loc = glGetUniformLocation( shaderProg, "color" );
  float col[4] =  (float)rgb.r/255, (float)rgb.g/255, (float)rgb.b/255, 1 ; // red and opaque
  glUniform4fv( color_loc, 1, col); 

  // Clear invisible buffer
  if (mode & CLEAR) glClearColor(0.0, 0.0, 0.0, 1); glClear(GL_COLOR_BUFFER_BIT); 

  // If coordinate system required is:
  // COORD_SYS_CONVENTIONAL = (1) left bottom corner is 0,0 and moving towards right and top sides we reach max screen size in pixels e.g 1024 * 768 pixels
  // COORD_SYS_CARTECIAN = (2) left bottom corner is -1,-1 and moving towards right and top sides we reach +1,+1 . The center of the display is always 0,0
  if (mode & ~COORD_SYS_CARTECIAN)
    x = (_x * xMax)-1;
    y = (_y * yMax)-1;
  

  // Draw the text on the invisible buffer
  if (mode & RENDER) render_text(text, x, y, xMax, yMax); 

  // Update display
  if (mode & UPDATE) SDL_GL_SwapWindow(SDLmain.window); 

因此我可以这样做:

  glRenderText(tmp, 0, 0, Olive, CLEAR | RENDER | UPDATE);
  glRenderText(tmp, 0, 150, Yellow_Green, RENDER);
  glRenderText(tmp, 0, 300, Light_Coral, RENDER | UPDATE);

事实证明,我既可以在不同的 x,y 坐标处渲染字体,也可以使用函数 drawVertices 来渲染连接这些顶点的线,但不能同时渲染两者。也就是说,我不能这样做:

  glRenderText(tmp, 0, 0, Olive, CLEAR | RENDER);
  glRenderText(tmp, 0, 150, Yellow_Green, RENDER);
  glRenderText(tmp, 0, 300, Light_Coral, RENDER);

  drawVertices(window, vertices, sizeOfVertices, RENDER);
  drawVertices(window, vertices, sizeOfVertices, RENDER);
  drawVertices(window, vertices, sizeOfVertices, RENDER | UPDATE);

正如你所知道的,逻辑是在任何一个函数中你都必须使用 CLEAR | RENDER 标志,然后只执行 RENDER 并在最后一次调用任一函数时使用 RENDER |更新。

问题:

(1) 在我尝试做前面的事情时,即结合 glRenderText() + drawVertices() 我失败了,因为在一个接一个地调用它们之前显然需要设置一些东西。

(2) 我面临的另一个问题是,在我的 raspi3 上运行代码导致 drawVertices() 在字体方面工作正常,我只能看到 glClearColor() 和 glClear(GL_COLOR_BUFFER_BIT) 的效果,这意味着显示已通过 glClearColor() 设置的颜色清除,但看不到字体渲染。我尝试了两种 GL 驱动程序模式。有一种叫 FULL KMS GL 驱动,另一种叫 FAKE KMS GL 驱动。

另外,为了让 drawVertices() 工作,我必须注释下面提供的代码:

  FT_Set_Pixel_Sizes(face, 0, 200);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

  GLuint vbo;
  GLuint attribute_coord=0;

  glGenBuffers(1, &vbo);
  glEnableVertexAttribArray(attribute_coord);
  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glVertexAttribPointer(attribute_coord, 4, GL_FLOAT, GL_FALSE, 0, 0);

我仍然必须保持以下代码处于活动状态:

  // Load the shader program and set it for use
  shaderProg = shaderProgLoad("shaderV1.vert", "shaderV1.frag");

  GLuint tex_loc   = glGetUniformLocation( shaderProg, "tex" );
  GLuint color_loc = glGetUniformLocation( shaderProg, "color" );

  // Activate the resulting shaders program
  glUseProgram(shaderProg);


  glUniform1i( tex_loc, 0 ); // 0, because the texture is bound to of texture unit 0
  // Define RGB color + Alpha
  float col[4] =  0.0f, 1.0f, 1.0, 1.0f ;
  glUniform4fv( color_loc, 1, col);

【问题讨论】:

【参考方案1】:

我能够通过重置这两个操作 (glRenderText() 和 drawVertices()) 不常见的几乎所有内容来解决这个问题 以下代码在调用任何两个函数 ()glRenderText() 和 drawVertices()) 之前保持原样。这两个函数已更新,以便在到达 glDrawArrays() 执行点之前完成正确的重置

// Load the shader program and set it for use
shaderProg = shaderProgLoad("shaderV1.vert", "shaderV1.frag");

// Activate the resulting shaders program
glUseProgram(shaderProg);

// After the shaders (vertex & fragment) have been compiled & linked into a program
// we can query the location index value of a uniform variable existing in the program.
// In this case we are querying uniform variables "tex" that exist in the fragment shader 
GLuint tex_loc   = glGetUniformLocation( shaderProg, "tex" );
// Set the value of the uniform variable "tex_loc" to 0, because the texture is bound to of texture unit 0
glUniform1i( tex_loc, 0 );

这是一个更新的函数,它重置了一些选项,以便我们得到我们需要的结果。例如,glDisable(GL_BLEND);用于在绘制线条时禁用混合。最重要的当然是我每次调用 drawVertices() 时都使用 glBindBuffer() 设置适当的缓冲区供 opengl 使用。 glGenBuffers() 仅在对应的对象名称为 0 时使用一次,这意味着已使用的对象名称尚未分配给 vbo。

GLuint drawVertices(SDL_Window *window, GLuint vbo, Vertex *vertices, GLsizei numVertices, SDL_Color rgb, int mode)

  float col[4] =  (float)rgb.r/255, (float)rgb.g/255, (float)rgb.b/255, 1.0 ;

  // Get an available object name for glBindBuffer() when object name is ZERO
  if (!vbo) glGenBuffers(1, &vbo); 

  // Check for problems
  GLenum err = glGetError();

  // Deal with errors
  if (err != GL_NO_ERROR) 
    // Failed
    glDeleteBuffers(1, &vbo);
    SDL_Log("Creating VBO failed, code %u\n", err);
    vbo = 0;
  
  else if (!vbo) 
    // Failed. Error message has already been printed, so just quit
    return (GLuint)NULL;
  

  if (mode & CLEAR)
    // Set color of the clear operation
    glClearColor(0.0, 0.0, 0.0, 1.0);
    // Clears the invisible buffer
    glClear(GL_COLOR_BUFFER_BIT);
  

  if (mode & RENDER)
    // Dissable blending when drawing lines
    glDisable(GL_BLEND);
    // Set up for rendering the triangle (activate the vbo)
    // Position is vertex attribute 0
    GLuint attribute_coord = 0; 
    // Specifies the index of the generic vertex attribute and enables it
    glEnableVertexAttribArray(attribute_coord);
    // Set the buffer to be used from now on
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    // Define an array of generic vertex attribute data
    glVertexAttribPointer(attribute_coord, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)0);
    // Get the location of the uniform variable "color_loc"
    GLuint color_loc = glGetUniformLocation( shaderProg, "color" );  
    // Set the value of the uniform variable "color_loc" to array "col"
    glUniform4fv( color_loc, 1, col);
    // Copy vertices into buffer
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * numVertices, vertices, GL_STATIC_DRAW);

    // Now draw!
    //  GL_POINTS = Draw only the pixels that correspond to the vertices coordinates
    //  GL_LINE_STRIP = Draw line that connects the vertices coordinates
    //  GL_LINE_LOOP = Draw line that connects the vertices coordinates plus a line that re-connects the last coordinate with the first
    //  GL_TRIANGLE_FAN = 
    glDrawArrays(GL_LINE_STRIP, 0, numVertices);
  

  // Don’t forget to flip the buffers as well, to display the final image:
  // Update the window
  if (mode & UPDATE) SDL_GL_SwapWindow(window); 

  return vbo;

函数 glRenderText() 的工作方式几乎相同

// render_text is called by glRenderText()
void render_text(const char *text, float x, float y, float sx, float sy) 
  const char *p;

  FT_GlyphSlot g = face->glyph;

  //SDL_Log("Debug info: glyph w: %d, glyph rows: %d", g->bitmap.width, g->bitmap.rows);

  for(p = text; *p; p++) 

    // If FT_Load_Char() returns a non-zero value then the glyph in *p could not be loaded
    if(FT_Load_Char(face, *p, FT_LOAD_RENDER)) continue; 

    glTexImage2D(
      GL_TEXTURE_2D,
      0,
      GL_RED,
      g->bitmap.width,
      g->bitmap.rows,
      0,
      GL_RED,
      GL_UNSIGNED_BYTE,
      g->bitmap.buffer
    );

    float x2 = x + g->bitmap_left * sx;
    float y2 = -y - g->bitmap_top * sy;
    float w = g->bitmap.width * sx;
    float h = g->bitmap.rows * sy;

    GLfloat box[4][4] = 
        x2,     -y2    , 0, 0,
        x2 + w, -y2    , 1, 0,
        x2,     -y2 - h, 0, 1,
        x2 + w, -y2 - h, 1, 1,
    ;

    glBufferData(GL_ARRAY_BUFFER, sizeof box, box, GL_DYNAMIC_DRAW);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    x += (g->advance.x>>6) * sx;
    y += (g->advance.y>>6) * sy;
  



GLuint glRenderText(char *text, int fontSize, GLuint vbo, int _x, int _y, SDL_Color rgb, int mode) 

  float x = _x;
  float y = _y;

  float xMax = 2.0 / (float)getWindowWidth();
  float yMax = 2.0 / (float)getWindowHeight();
  GLuint attribute_coord=0;

  float col[4] =  (float)rgb.r/255, (float)rgb.g/255, (float)rgb.b/255, 1 ;

  // Enable blending when drawing fonts
  glEnable(GL_BLEND);

  // Set the W & H of the font loaded
  FT_Set_Pixel_Sizes(face, 0, fontSize);

  // If vbo is ZERO setup and get an object name      
  if (!vbo)
    // Enables blending operations
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Set texture parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // Specifies the alignment requirements for the start of each pixel row in memory
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    // Save into vbo one unused buffer name (index) for use with glBindBuffer
    glGenBuffers(1, &vbo);
    // Specifies the index of the generic vertex attribute and enables it
    glEnableVertexAttribArray(attribute_coord);
      

  // Set the buffer to be used from now on to the one indicated by vbo
  glBindBuffer(GL_ARRAY_BUFFER, vbo);

  // Define an array of generic vertex attribute data
  glVertexAttribPointer(attribute_coord, 4, GL_FLOAT, GL_FALSE, 0, 0);  

  GLuint color_loc = glGetUniformLocation( shaderProg, "color" );

  // Set the value of the uniform variable "color_loc" from array "col"
  glUniform4fv( color_loc, 1, col); 

  // Clear invisible buffer
  if (mode & CLEAR)
    glClearColor(0.0, 0.0, 0.0, 1); 
    glClear(GL_COLOR_BUFFER_BIT); 
  

  // If coordinate system required is:
  // COORD_SYS_CONVENTIONAL = (1) left bottom corner is 0,0 and moving towards right and top sides we reach max screen size in pixels e.g 1024 * 768 pixels
  // COORD_SYS_CARTECIAN = (2) left bottom corner is -1,-1 and moving towards right and top sides we reach +1,+1 . The center of the display is always 0,0
  if (mode & ~COORD_SYS_CARTECIAN)
    x = (_x * xMax)-1;
    y = (_y * yMax)-1;
  

  // Draw the text on the invisible buffer
  if (mode & RENDER) render_text(text, x, y, xMax, yMax); 

  // Update display
  if (mode & UPDATE) SDL_GL_SwapWindow(SDLmain.window); 

  return vbo;

逻辑是在 main() 或全局范围内定义了 2 个 vbo(一个用于通过 drawVertices 绘制线条,一个用于通过 glRenderText() 绘制字体)并传递给 glRenderText() 和 drawVertices() 。这两个函数更新其本地副本的值并返回 vbo,以便更新 main 或全局范围中的 vbo。当然可以通过完全通过引用传递它们来完成,而不是采用我的方法。

不过,我还没有测试我的 raspi3 中的功能。我很快就会回来更新。无论如何,上面给出的功能都是功能齐全的。

再次感谢您的宝贵时间。

【讨论】:

以上是关于使用 openGL ES 2.0 结合其他线条绘制功能的字体渲染(Freetype)不起作用的主要内容,如果未能解决你的问题,请参考以下文章

使用 OpenGL 2.0(不是 ES)绘制三角形

使用 OpenGL ES 2.0 绘制 2D 图像

OPENGL ES 2.0 知识串讲 ——OPENGL ES 详解II(传入绘制信息)

OPENGL ES 2.0 知识串讲 ——OPENGL ES 详解II(传入绘制信息)

如何在Android上使用OpenGL ES 2.0绘制点

OpenGL ES 2.0,使用多个顶点缓冲区进行绘制