使用Freetype和OpenGL渲染字符出现问题

10
我正在尝试开发一个简单的2D游戏引擎,需要能够绘制文本,因此我决定使用著名的freetype库。 我的当前目标是渲染从0到255的每个字符,并将适当的信息存储在字形对象中,包括字符的顶部和左侧位置、宽度和高度以及一个OpenGL纹理。(在未来,我希望消除每个字形的1个纹理,但在开始处理之前,我需要确保我的字符正确显示)。 起初,我得到了所有字符都乱码的结果,但后来我查看了this question,发现我必须将位图缓冲区复制到一个新的缓冲区中,使其具有OpenGL所需的纹理大小(即宽度和高度都是2的幂)。从那时起,每个字符看起来都很好,于是我尝试了不同的字体大小,看看是否一切正常,猜猜怎么着...并不是,对于小字体大小(即<30),每个宽度较小的字符('l','i',';',':')都会出现乱码。

http://img17.imageshack.us/img17/2963/helloworld.png

所有扭曲的字符宽度都为1,这意味着它们实际上有两个单位的宽度,因为2是第一个2的幂。以下是每个字符的渲染代码。
bool Font::Render(int PixelSize)
{
    if( FT_Set_Pixel_Sizes( mFace, 0, PixelSize ) != 0)
        return false;

    for(int i = 0; i < 256; ++i)
    {
        FT_UInt GlyphIndex;

        GlyphIndex = FT_Get_Char_Index( mFace, i );

        if( FT_Load_Glyph( mFace, GlyphIndex, FT_LOAD_RENDER ) != 0)
            return false;


        int BitmapWidth = mFace->glyph->bitmap.width;
        int BitmapHeight = mFace->glyph->bitmap.rows;

        int TextureWidth = NextP2(BitmapWidth);
        int TextureHeight= NextP2(BitmapHeight);

        printf("Glyph is %c, BW: %i, BH: %i, TW: %i, TH: %i\n", i, BitmapWidth, BitmapHeight, TextureWidth, TextureHeight);

        mGlyphs[i].SetAdvance(mFace->glyph->advance.x >> 6);
        mGlyphs[i].SetLeftTop(mFace->glyph->bitmap_left, mFace->glyph->bitmap_top);

        GLubyte * TextureBuffer = new GLubyte[ TextureWidth * TextureHeight ];

        for(int j = 0; j < TextureHeight; ++j)
        {
            for(int i = 0; i < TextureWidth; ++i)
            {
                TextureBuffer[ j*TextureWidth + i ] = (j >= BitmapHeight || i >= BitmapWidth ? 0 : mFace->glyph->bitmap.buffer[ j*BitmapWidth + i ]);
            }
        }

        for(int k = 0; k < TextureWidth * TextureHeight; ++k)
            printf("Buffer is %i\n", TextureBuffer[k]);

        Texture GlyphTexture;

        GLuint Handle;
        glGenTextures( 1, &Handle );

        GlyphTexture.SetHandle(Handle);
        GlyphTexture.SetWidth(TextureWidth);
        GlyphTexture.SetHeight(TextureHeight);

        glBindTexture(GL_TEXTURE_2D, Handle);

        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, TextureWidth, TextureHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, TextureBuffer);

        mGlyphs[i].SetTexture(GlyphTexture);

        delete [] TextureBuffer;
    }

    return true;
}

我找到的唯一解决方案是加上一个小的if语句,如果TextureWidth的值为2,则将其更改为4,这样文本在任何字体大小下看起来都很好。但我不明白为什么OpenGL会拒绝宽度为2的纹理?哦,我已经确定问题不在缓冲区中,我打印出来看起来很好。

你能想到任何原因吗?

以下是代码(非常)混乱的其余部分,以防问题不在Render函数中。

int NextP2(int Val)
{
    int RVal = 2;

 while(RVal < Val) RVal <<= 1;

 return RVal;
}

class Texture
{
    public:
        void Free() { glDeleteTextures(1, &mHandle); }

        void SetHandle(GLuint Handle) { mHandle = Handle; }
        void SetWidth(int Width) { mWidth = Width; }
        void SetHeight(int Height) { mHeight = Height; }

        inline GLuint Handle() const { return mHandle; }
        inline int Width() const { return mWidth; }
        inline int Height() const { return mHeight; }
    private:
        GLuint mHandle;
        int mWidth;
        int mHeight;
};

class Glyph
{
    public:

        void SetAdvance(int Advance) { mAdvance = Advance; }
        void SetLeftTop(int Left, int Top) { mLeft = Left; mTop = Top; }
        void SetTexture(Texture texture) { mTexture = texture; }

        Texture & GetTexture() { return mTexture; }
        int GetAdvance() const { return mAdvance; }
        int GetLeft() const { return mLeft; }
        int GetTop() const { return mTop; }

    private:
        int mAdvance;
        int mLeft;
        int mTop;
        Texture mTexture;

};

class Font
{
    public:
        ~Font() { for(int i = 0; i < 256; ++i) mGlyphs[i].GetTexture().Free(); }
        bool Load(const std::string & File);
        bool Render(int PixelSize);

        Glyph & GetGlyph(unsigned char CharCode) { return mGlyphs[CharCode]; }

    private:
        FT_Face mFace;
        Glyph mGlyphs[256];

};

我认为这就是所有的问题了,如果您找到了问题,请告诉我。
最好的问候和感谢。

你尝试在另一台电脑上运行过吗?这可能是驱动程序问题。 - Yngve Hammersland
没有,我会尝试并稍后回复。谢谢你的建议! :) - Carlos
4个回答

17

上周我遇到了与freetype和OpenGL完全相同的问题。

默认情况下,OpenGL认为纹理数据行在4字节边界上对齐。当纹理宽度变为2或1(都是有效的2的幂大小)时,这会导致问题。

将行对齐设置为1字节边界是否解决了您的问题?使用

glPixelStore(GL_UNPACK_ALIGNMENT, 1)

希望对你有帮助


10
为了不让这个问题“无解”,你或许应该“接受”这个答案 :) - rotoglup

0

你可能遇到了纹理包裹问题。

在OpenGL尝试正确平铺纹理时,渲染在纹理边界上的像素会“溢出”到另一侧。如果我没错的话,将包裹参数更改为

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

应该让OpenGL在贴图边缘处使用clamp模式,并正确绘制小纹理。

更新

我在一个教程页面上发现了一些内容:

如果小字体的位图与屏幕像素不对齐,双线性插值会导致伪影。解决这个问题的简单方法是将插值方法从GL_LINEAR更改为GL_NEAREST,但这可能会在旋转文本的情况下产生其他伪影。(如果旋转文本对您很重要,您可以尝试告诉OpenGL从更大的字体位图进行超级采样。)


很抱歉,那个方法没有起作用。一切都保持不变。你看了打印屏幕吗?对我来说,那根本不像平铺纹理。它们似乎是随机像素值,我不知道为什么它们会出现在那里,因为缓冲区甚至不应该那么大。这真的很令人沮丧...它根本没有任何意义!为什么小宽度字符会失败,而其他字符却毫无问题?我已经反复检查了我的代码,没有发现任何问题,位图和纹理缓冲区都被正确渲染了...还有其他想法吗? - Carlos
实际上,在寻找解决方案时,我偶然发现了那个教程,将其改为GL_NEAREST并没有起作用...但还是谢谢你的时间 :) - Carlos

0

准备好支持超过256个字符(那是历史),以及从右到左和从左到右的文本流以及自上而下的流。

这只是一个针对英语使用者的不同主题提示。


谢谢,但我目前不打算支持Unicode。这只是一个小型自制项目,而不是通用的文本渲染库。 :) - Carlos

0
使用Freetype与ICU。单独使用Freetype无法正确渲染字形,因此需要使用ICU来实现。

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