四边形上的纹理输出不正确。

3
我正在尝试使用freetype在我的应用程序中显示文本。起初,我认为这个库会有内置的函数来绘制文本,但实际上只有一个绘制符号的函数。于是我决定逐个字符将它们放入纹理中。但是我又感到失望:所有指南都使用单个图像的一个纹理(可能glTexSubImage2D可以帮助我?)。现在我把一个符号放在纹理上,并将纹理传给OpenGL元素。这是我的代码(很混乱,但我现在只是想弄清楚它是如何工作的):
//init:

    if (FT_Init_FreeType(&ft)) {
        fprintf(stderr, "Could not init freetype library\n");
        return 0;
    }
    if (FT_New_Face(ft, fontfilename, 0, &face)) {
        fprintf(stderr, "Could not open font %s\n", fontfilename);
        return 0;
    }
    FT_Set_Pixel_Sizes(face, 0, 48);    FT_GlyphSlot g = face->glyph;

并从display()中显示:

 void display()
 {
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(1.0 ,1.0, 1.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();//load identity matrix
std::string s = "QWERTYOG0l  ";

for(int i = 0; i < s.size(); i++){
    FT_Load_Char( face, s[i], FT_LOAD_RENDER );
    FT_GlyphSlot g = face->glyph;
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR); // Linear Filtering
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR); // Linear Filtering
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    gluBuild2DMipmaps( GL_TEXTURE_2D, 
          GL_RED, 
          g->bitmap.width, 
          g->bitmap.rows, 
          GL_RED, 
          GL_UNSIGNED_BYTE, 
          g->bitmap.buffer );
    glBegin(GL_QUADS);
      glTexCoord2f(0.0f, 0.0f);glVertex3f(0.1f*i-0.1,0.07f,0.0f); //top left
      glTexCoord2f(0.0f, 1.0f);glVertex3f(0.1f*i,0.07f,0.0f); //top right
      glTexCoord2f(1.0f, 1.0f);glVertex3f(0.1f*i,-0.07f,0.0f); // bottom right
      glTexCoord2f(1.0f, 0.0f);glVertex3f(0.1f*i-0.1,-0.07f,0.0f); //bottom left
    glEnd();
}

代码结果

如您所见,“O”和“T”是正确的(如果我更改纹理的左下和右上角,它将是完全正确的)。但其他符号似乎向左移动(例如,“E”从上到下向左移动)。

完整代码:

#include <math.h>
#include <iostream>
#include <GL/glew.h>
#include <GL/glut.h>

#include <ft2build.h>
#include FT_FREETYPE_H


FT_Library ft;
FT_Face face;
const char *fontfilename = "LucidaTypewriterBold.ttf";
GLuint      texture[10];  

GLint uniform_mytexture;
int setup() { 
    if (FT_Init_FreeType(&ft)) {
        fprintf(stderr, "Could not init freetype library\n");
        return 0;
    }
    if (FT_New_Face(ft, fontfilename, 0, &face)) {
        fprintf(stderr, "Could not open font %s\n", fontfilename);
        return 0;
    }
    FT_Set_Pixel_Sizes(face, 0, 48);
    FT_Load_Char( face, 'O', FT_LOAD_RENDER );
    FT_GlyphSlot g = face->glyph;

    glGenTextures(1, &texture[0]);    // Create The Texture
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR); // Linear Filtering
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR); // Linear Filtering
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    gluBuild2DMipmaps(GL_TEXTURE_2D,  GL_RGBA, g->bitmap.width, g->bitmap.rows,  GL_RED, GL_UNSIGNED_BYTE, g->bitmap.buffer);
    return 1;
 }
void display()
{
    glClear(GL_COLOR_BUFFER_BIT);
    glClearColor(1.0 ,1.0, 1.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();//load identity matrix
    std::string s = "QWERTYOG0l  ";

    for(int i = 0; i < s.size(); i++){
        FT_Load_Char( face, s[i], FT_LOAD_RENDER );
        FT_GlyphSlot g = face->glyph;
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR); // Linear Filtering
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR); // Linear Filtering
        //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        gluBuild2DMipmaps( GL_TEXTURE_2D, 
              GL_RED, 
              g->bitmap.width, 
              g->bitmap.rows, 
              GL_RED, 
              GL_UNSIGNED_BYTE, 
              g->bitmap.buffer );
        glBegin(GL_QUADS);
          glTexCoord2f(0.0f, 0.0f);glVertex3f(0.1f*i-0.1,0.07f,0.0f); //top left
          glTexCoord2f(0.0f, 1.0f);glVertex3f(0.1f*i,0.07f,0.0f); //top right
          glTexCoord2f(1.0f, 1.0f);glVertex3f(0.1f*i,-0.07f,0.0f); // bottom right
          glTexCoord2f(1.0f, 0.0f);glVertex3f(0.1f*i-0.1,-0.07f,0.0f); //bottom left
        glEnd();
    }

    //glActiveTexture(GL_TEXTURE0);
    //glBindTexture(GL_TEXTURE_2D, texture[0]);               // Select Our Texture

 // glUniform1i(uniform_mytexture, /*GL_TEXTURE*/0);
    glutPostRedisplay();
    glutSwapBuffers();
}
void TimerFunction(int value)
{


}
int main(int argc, char *argv[])
{
       glutInit(&argc, argv);
       glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
       glutInitWindowSize(800,600);
       glutCreateWindow("Hello World");
        //glutTimerFunc(30, TimerFunction, 1);
       glewInit();
       glEnable (GL_TEXTURE_2D);

       setup();
       glutDisplayFunc(display);

       glutMainLoop();

       return 0;
}

奇怪的行为。如果你在字符串中只放置“Q”字母,会发生什么?它是否像之前一样扭曲?另外,如果你交换“T”和“Q”字母的位置,那么“T”是否仍然能够正确显示,“Q”则会扭曲? - Piotr Chojnacki
你为什么要在gluBuild2DMipmaps()中传递GL_RED而不是GLU_RED?同样的情况也出现在GL_RGBA上。 - genpfault
genpfault,因为我找不到GLU_RED。我从未听说过这个常量。 蚊子,字母的位置不会影响绘图。 - vlastachu
2个回答

3

我已经研究了一段时间,虽然这个答案可能不完整,但也许它可以帮助你找到答案。

初步说明

在我介绍我所发现的内容之前,我需要指出您的纹理坐标存在问题。您有以下内容:

glTexCoord2f(0.0f, 0.0f);glVertex3f(0.1f*i-0.1,0.07f,0.0f); //top left
glTexCoord2f(0.0f, 1.0f);glVertex3f(0.1f*i,0.07f,0.0f); //top right
glTexCoord2f(1.0f, 1.0f);glVertex3f(0.1f*i,-0.07f,0.0f); // bottom right
glTexCoord2f(1.0f, 0.0f);glVertex3f(0.1f*i-0.1,-0.07f,0.0f); //bottom left

当它应该看起来像这样:
glTexCoord2f(0.0f, 0.0f);glVertex3f(0.1f*i-0.1,0.07f,0.0f); //top left
glTexCoord2f(1.0f, 0.0f);glVertex3f(0.1f*i,0.07f,0.0f); //top right
glTexCoord2f(1.0f, 1.0f);glVertex3f(0.1f*i,-0.07f,0.0f); // bottom right
glTexCoord2f(0.0f, 1.0f);glVertex3f(0.1f*i-0.1,-0.07f,0.0f); //bottom left

请注意,左上角对应纹理坐标中的0,0,而右下角对应1,1。这是因为(在此有些猜测)freetype将左上角视为其原点。

可能有所帮助的内容

Freetype不会生成其尺寸必须是2的幂的位图,这通常需要进行mipmapping(参见:https://gamedev.stackexchange.com/a/7929)。

因此,如果您想测试此功能(注意:不要在代码中实际使用此功能;这仅用于说明),您可以将display中的gluBuild2DMipmaps调用替换为以下内容(请确保#include <cstring>):

int pitch = g->bitmap.pitch;
if (pitch < 0) {
    pitch = -pitch;
}

unsigned char data[4096] = {0};
for (int row = 0; row < g->bitmap.rows; ++row) {
    std::memcpy(data + 64 * row, g->bitmap.buffer + pitch * row, pitch);
}

gluBuild2DMipmaps(
    GL_TEXTURE_2D, 
    GL_RGBA, 
    64, 
    64, 
    GL_RED, 
    GL_UNSIGNED_BYTE, 
    data
);

它的作用是将位图缓冲区复制到不同的 64x64 字节缓冲区的左上角,然后从中构建出mipmaps。这就是结果:

result

进一步说明

我的插图代码很糟糕,因为它在每次重绘时都会复制每个字形的位图数据,并且它没有考虑到位图缓冲区的实际大小,或者如果间距大于64。您可能也不希望在每次重绘时重新生成mipmaps,但是如果您只是想学习如何将单词传递到OpenGL中,请不要担心:)

编辑:我必须使用与您不同的字体,因为我没有您的字体。


非常感谢您的出色回复。问题不在字形大小上,而是使用二次幂会看起来更好。至于字母的角度——是我愚蠢的错误。 - vlastachu

1
如tecus所说,正确的解决方案是使用二次幂大小的纹理。
在那个答案之前,我还发现了另一个解决方案:glPixelStorei(GL_UNPACK_ALIGNMENT, 1);gluBuild2DMipmaps之前。但是这里会出现更多问题,比如纹理周围的灰色边框。
对于那些寻求类似目标的人,我想分享我的经验: 在透明背景上制作黑色:
GLfloat swizzleMask[] = { 0,0,0, GL_RED}; 
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask);

更新 有一个更简单和明显的解决方案,而不需要使用OpenGL扩展。 gluBuild2DMipmaps(GL_TEXTURE_2D, GL_ALPHA, g->bitmap.width, g->bitmap.rows, GL_RGBA, GL_UNSIGNED_BYTE, g->bitmap.buffer)

将所有字母连接到单个纹理中

我认为这样对性能更好,但不确定我是否改变了正确的方式。

    if(text[i] == ' ') left += 20; else
    for (int row = 0; row < g->bitmap.rows; ++row) {
        std::memcpy(data + left + 64*(strSize*(row + 64 -  g->bitmap_top))
            , g->bitmap.buffer + pitch * row, pitch);
    }
    left += g->advance.x >> 6;

在将数据连接到数组之前,最好计算宽度和高度(并四舍五入为2的幂次方)。

如果您想要字距调整,则应编写自己较慢的memcpy实现,在其中添加(而不是完全更改)该值并检查UCHAR_MAX是否超出。

我的最终结果:enter image description here


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