Windows下使用GetGlyphOutline在OpenGL中渲染字体

时间:2021-09-12 18:35:27

        欢迎转载,请标明出处:http://blog.csdn.net/tianyu2202/

        无图无JB,先上图。使用OpenGL绘制字体,支持多种字体,支持TrueType轮廓字体,支持自选字体纹理大小和输出大小,支持在三维空间内绘制。

Windows下使用GetGlyphOutline在OpenGL中渲染字体

        关于OpenGL中字体的显示网上其实有很多的教程,不过经常用到的方式有比较简单的Bitmap方式、比较复杂的FreeType方式。而本文介绍的方式虽然只能在Windows下实现,却有着和FreeType一样的显示效果,最重要的是非常简单,仅仅200多行代码即可实现。

        首先是字体信息的保存,每个文字都有数个信息需要保存,包括了纹理的宽高、起始位置、字体宽度和所在的纹理序号。本文将每个字体保存为一个纹理,而不是常见的一个纹理保存数十个文字。这是首先是为了简单,二来还有应用上的一些特殊性。有这方面要求的同学可以自行优化。

class CFontData
{
public:
float m_Width, m_Height;
float m_OrigX, m_OrigY;
float m_FontWidth;
GLuint m_TextureID;

CFontData()
{
m_Width = 0.0f;
m_Height = 0.0f;
m_TextureID = 0;
m_FontWidth = 0.0f;
m_OrigX = 0.0f;
m_OrigY = 0.0f;
}

~CFontData()
{

}
};

        然后是文字绘制类,非常简单,保存了字体名称、字体大小和一些资源,方法一共两个就是在三维空间内进行绘制和产生纹理字体。

class CFontPrinter
{
private:
int m_FontSize;
wchar_t m_FontName[64];
std::map<wchar_t, CFontData*> m_FontMap;
HFONT m_Font;

public:
CFontPrinter(int fontSize, const wchar_t* fontName)
{
wcscpy_s(m_FontName, 64, fontName);
m_FontSize = fontSize;
m_Font = NULL;
}

~CFontPrinter()
{

}

bool makeChar(wchar_t wChar);

void print3DText(float x, float y, float z, float red, float green, float blue, float height, wchar_t* Text);

void print3DText(float x, float y, float z, float height, wchar_t* Text)
{
return print3DText(x, y, z, 1.0f, 1.0f, 1.0f, height, Text);
}
};

        在makeChar中,根据传入的字符产生纹理并存入m_FontMap,在print3DText中进行绘制。直接使用print3DText也可以自动调用makeChar进行文字数据的生成。首先检查m_Font是否已经生成,没有生成则根据字体信息进行生成。然后使用传入的字符获取Buffer的大小,然后建立Buffer并将它传入函数GetGlyphOutlintW以获取生成的字体数据。而这里生成的数据是Gray64的,每个字节代表一个像素,而且是4字节对齐的。这里我为了节省更多的显卡资源,将PixelStore的对齐设为1字节。使用一个SwapBuffer将数据对齐为1字节并传入TexImage2D。

        然后计算所需的数据,存入FontMap中等待绘制。

bool CFontPrinter::makeChar(wchar_t wChar)
{
HDC hdc = CreateCompatibleDC(wglGetCurrentDC());

if(!hdc)
return false;

HBITMAP hbitmap = CreateCompatibleBitmap(hdc, 1, 1);
HBITMAP hbitmapOld = (HBITMAP)SelectObject(hdc, hbitmap);

if((DWORD)hbitmapOld == GDI_ERROR)
return false;

CFontData* fontData = new CFontData();

glGenTextures(1, &fontData->m_TextureID);
glBindTexture(GL_TEXTURE_2D, fontData->m_TextureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_FALSE);
glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

int iTexWidth = m_FontSize;
int iTexHeight = m_FontSize;
int iLevel = 0;

if (!m_Font)
{
// 创建文字
m_Font = CreateFontW(-m_FontSize, 0, 0, 0, 500, false, false, false,
DEFAULT_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | (m_FontName?FF_DONTCARE:FF_SWISS), m_FontName);
}
if(!m_Font)
return false;

HFONT hfontOld = (HFONT)SelectObject(hdc, m_Font);
if((DWORD)hfontOld == GDI_ERROR)
return false;

GLYPHMETRICS gm={0,};
MAT2 const matrix22_identity={{0,1},{0,0},{0,0},{0,1}};

DWORD dwBuffSize = GetGlyphOutlineW(hdc, wChar, GGO_GRAY8_BITMAP, &gm, 0, NULL, &matrix22_identity);

if((signed)dwBuffSize == -1)
{
return false;
}

if(GetGlyphOutlineW(hdc, wChar, GGO_METRICS, &gm, 0, NULL, &matrix22_identity)==GDI_ERROR)
{
return false;
}

BYTE *pBuff = new BYTE[dwBuffSize];
BYTE *pSwapBuff=new BYTE[dwBuffSize];
memset(pBuff, 0xff, dwBuffSize);
memset(pSwapBuff, 0xff, dwBuffSize);

if(GetGlyphOutlineW(hdc, wChar, GGO_GRAY8_BITMAP, &gm, dwBuffSize, pBuff, &matrix22_identity) == GDI_ERROR)
{
delete[] pBuff;
return false;
}

if(gm.gmBlackBoxY == 0)
{
printf("black box Y zero!!!\n");
return false;
}

// 从 Gray64 转换到 Gray256
unsigned int const uiRowSize = dwBuffSize / gm.gmBlackBoxY;
BYTE* pPtr;
BYTE* pSPtr;
for(unsigned int nY = 0; nY < gm.gmBlackBoxY; nY++)
{
pPtr = pBuff + uiRowSize * nY;
pSPtr = pSwapBuff + gm.gmBlackBoxX * nY;
for (unsigned int nX = 0; nX < gm.gmBlackBoxX; nX++)
{
if (*pPtr == 0)
{
*pSPtr = 0;
}
else if (*pPtr == 0x40)
{
*pSPtr = 0xff;
}
else
{
*pSPtr = *pPtr << 2;
}

pPtr++;
pSPtr++;
}
}

// 加载纹理
glTexImage2D(GL_TEXTURE_2D, iLevel, GL_LUMINANCE8, gm.gmBlackBoxX, gm.gmBlackBoxY, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pSwapBuff);

// 记录文字信息
fontData->m_FontWidth = (float)gm.gmCellIncX / (float)m_FontSize;
fontData->m_Width = (float)gm.gmBlackBoxX / (float)m_FontSize;
fontData->m_Height = (float)gm.gmBlackBoxY / (float)m_FontSize;
fontData->m_OrigX = (float)gm.gmptGlyphOrigin.x / (float)m_FontSize;
fontData->m_OrigY = 1.0f - (float)gm.gmptGlyphOrigin.y / (float)m_FontSize;

m_FontMap[wChar] = fontData;

delete[] pBuff;
delete[] pSwapBuff;

return true;
}

        在Main函数中建立一个OpenGL窗口,并且初始化文字,在渲染循环中调用print3DText即可进行绘制。

void Render()
{
static float FrameID = 0;

if (FrameID > 2 * PI)
{
FrameID = 0.0f;
}
else
{
FrameID += PI / 180.0f;
}

glDrawBuffer(GL_BACK);
glViewport(0, 0, m_Width, m_Height);
glClearColor(0.3f, 0.4f, 0.5f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

for (int i = 0; i < 20; i++)
{
fontPrinter->print3DText(sin(FrameID - PI180 * i) * 0.3f - 0.7f, cos(FrameID - PI180 * i), 0.0f, 1.0f, 1.0f, 1.0f, 0.3f, L"中文输出");
fontPrinter->print3DText(cos(FrameID - PI180 * i) * 0.4f - 0.8f, sin(FrameID - PI180 * i) * 0.4f, 0.0f, 0.0f, 1.0f, 0.0f, 0.2f, L"Hello Class");
fontPrinterSong->print3DText(cos(FrameID - PI180 * i) * 0.5f - 0.9f, sin(FrameID - PI180 * i) * 0.6f, 0.0f, 0.0f, 0.0f, 1.0f, 0.4f, L"Font Class");
}
fontPrinter->print3DText(-0.7f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.2f, L"Font World");
fontPrinterSong->print3DText(-0.7f, -0.6f, 0.0f, 1.0f, 0.0f, 0.0f, 0.2f, L"宋体字");
fontPrinter->print3DText(-0.7f, -0.2f, 0.0f, 1.0f, 0.0f, 0.0f, 0.2f, L"微软雅黑");

glFlush();

SwapBuffers(m_hDC);
}

// 初始化文字
void InitFont()
{
// 参数1 单个文字纹理的最大大小
// 参数2 字体的文件名称
fontPrinter = new CFontPrinter(96, L"微软雅黑");
fontPrinterSong = new CFontPrinter(96, L"宋体");
}

int __stdcall WinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in_opt LPSTR lpCmdLine, __in int nShowCmd )
{
MSG msg;

CreateClientWindow();
InitFont();

while (true)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) // 有消息在等待吗?
{
if (msg.message == WM_QUIT) // 收到退出消息?
{
// 退出程序
break;
}
else
{
TranslateMessage(&msg); // 翻译消息
DispatchMessage(&msg); // 发送消息
}
}
else
{
Render();
}
}

if (fontPrinter != NULL)
{
delete fontPrinter;
}

return 0;
}

        工程代码在 http://download.csdn.net/detail/tianyu2202/7797885 ,欢迎下载。