OpenGL工程实现实用教程3---freeglut库中文文字渲染

使用freeglut渲染英文字体有个非常简单的办法,那就是直接调用glutBitmapCharacter函数进行渲染。代码如下:

//要显示的字符
char str[20] = "Hello world!!!";
int n = strlen(str);
//设置要在屏幕上显示字符的起始位置
glRasterPos2i(0, 0);
//逐个显示字符串中的每个字符
for (int i = 0; i < n; i++)
  glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *(str + i));

然而glutBitmapCharacter函数无法设置字体样式,无法改变字体大小,最要命的还有它无法使用中文字体。

解决上面提到的这些问题,最简单的方法是读取字体文件到内存中,然后生成纹理进行贴图。

这一过程有两种实现方法:

其一与操作系统相关,利用操作系统的API来读取字体文件。该实现方法的好处是代码量比较小,而且不用自行配置别的库文件。缺点则是无法跨平台。

其二与操作系统无关,利用FreeType开源库来加载字体到内存中。该实现方法相对而言代码量要多一点,而且多了配置FreeType库的步骤。好处则是可以跨平台。

由于考虑到大部分的初学者是在Windows上面学习的,因此本教程采用了第一种实现方法。如对第二种实现方法有兴趣的可以去看看LearnOpenGL的教程,该教程详细的介绍了如何在glfw库中使用FreeType显示英文字符(中文类似),同时也做了字符渲染原理的详细介绍,地址如下:

https://learnopengl-cn.github.io/06%20In%20Practice/02%20Text%20Rendering/

接下来简要的讲一下实现字体渲染需要注意的点:

  1. 无论使用操作系统的API还是使用FreeType来读取字体到内存中,都是从文件读取到内存的过程,如果文字量很大或者是反复读取,会严重的影响程序的性能。因此在具体实现的时候,最好将已经读取过的文字放入一张映射表中,然后在渲染的时候查询该表来判断是否需要进行字符的读取,以避免反复的字符读取操作。当然这样做如果文字量很大会有内存占用过大的问题,需要想办法进行优化,不过对于一般应用而言无需担心。
  2. 字符读取到内存中后是一个nxm的方块,在方块的内部0值代表这个像素没有点,而其他值则代表该点是构成文字的一个像素。一般在文字内部,如果是64阶的话,那么文字所占的点多为64,而在文字的边沿处则存在着更小的值,这些值是模糊过度的值,不可忽略,否则文字渲染出来将存在很多锯齿,对比图如下(这也是网上很多给出代码的demo看着很别扭的原因)。

请添加图片描述请添加图片描述
到此我们就将中文文字渲染需要注意的点讲完了,如果还想更深入了解文字渲染的细节可以阅读下面放出来的代码,或者去上面提到的LearnOpenGL教程看看。

OK,下面放出本人封装好的代码(部分参考了网上别的博主给出的代码,本来要上链接,结果发现不知道当时从哪里找到的。。。如发现有雷同可以告知我我加上。需要强调的是,本人对该代码做了很多修改和注释,绝非复制粘贴):

Font.hpp
#pragma once

#include <Windows.h>
#include <gl/GL.h>
#include <gl/GLU.h>
#include <map>
#include <math.h>

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;
	}
};

class CFontPrinter
{
private:
	int m_FontSize;//字体大小
	char m_FontName[64];//字体名称
	std::map<wchar_t, CFontData*> m_FontMap;//文字映射表
	HFONT m_Font;//字体对象
	//字体颜色
	unsigned char red;
	unsigned char green;
	unsigned char blue;
	//背景颜色
	unsigned char bg_r = 0;
	unsigned char bg_g = 0;
	unsigned char bg_b = 255;
	//父窗口大小
	float fwinwidth;
	float fwinheight;
public:
	CFontPrinter(int fontSize, const char* fontName,int fwwidth,int fwheight)
	{
		strcpy_s(m_FontName, 64, fontName);//获得字体名称
		m_FontSize = fontSize;//获得字体大小
		m_Font = NULL;
		red = 255;
		green = 255;
		blue = 255;
		fwinwidth = (float)fwwidth;
		fwinheight = (float)fwheight;
	}
	bool 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();
		m_FontMap[wChar] = new CFontData();
		glGenTextures(1, &m_FontMap[wChar]->m_TextureID);//生成纹理,放入m_TextureID中
		glBindTexture(GL_TEXTURE_2D, m_FontMap[wChar]->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);
		//指定OpenGL如何从数据缓冲区中解包图像数据 
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
		glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
		glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);

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

		if (!m_Font)//创建字体
		{
			LOGFONTA lf;
			lf.lfHeight = -m_FontSize;
			lf.lfWidth = 0;
			lf.lfEscapement = 0;
			lf.lfOrientation = 0;
			lf.lfWeight = 5;
			lf.lfItalic = 0;    //斜体
			lf.lfUnderline = 0; //下划线
			lf.lfStrikeOut = 0;
			lf.lfCharSet = DEFAULT_CHARSET;
			lf.lfOutPrecision = 0;
			lf.lfClipPrecision = 0;
			lf.lfQuality = PROOF_QUALITY;
			lf.lfPitchAndFamily = 0;
			strcpy_s(lf.lfFaceName, m_FontName);
			m_Font = CreateFontIndirectA(&lf);
			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} };

		BYTE* pBuff = new BYTE[m_FontSize * m_FontSize];
		BYTE* pSwapBuff = new BYTE[m_FontSize * m_FontSize * 3];
		memset(pBuff, 0xff, m_FontSize * m_FontSize);
		memset(pSwapBuff, 0xff, m_FontSize * m_FontSize * 3);
		DWORD dwBuffSize;
		if ((dwBuffSize = GetGlyphOutlineW(hdc, wChar, GGO_GRAY8_BITMAP, &gm, m_FontSize * m_FontSize, pBuff, &matrix22_identity)) == GDI_ERROR)
		{
			delete[] pBuff;
			return false;
		}
		// 原本字体的灰度值为0~64,经过调整映射到0~255
		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*3 * nY;
			for (unsigned int nX = 0; nX < gm.gmBlackBoxX; nX++)
			{
				if (*pPtr == 0)//背景
				{
					*pSPtr = bg_r;
					pSPtr++;
					*pSPtr = bg_g;
					pSPtr++;
					*pSPtr = bg_b;
					pSPtr++;
				}
				else if (*pPtr == 64)
				{
					*pSPtr = red;
					pSPtr++;
					*pSPtr = green;
					pSPtr++;
					*pSPtr = blue;
					pSPtr++;
				}
				else
				{
					float time = (* pPtr)/(float)64;
					int blend_r = unsigned char(red * time) + unsigned char(bg_r * (1 - time));
					if (blend_r > 255)
					{
						blend_r = 255;
					}
					*pSPtr = blend_r;
					pSPtr++;
					
					int blend_g = unsigned char(green * time) + unsigned char(bg_g * (1 - time));
					if (blend_g > 255)
					{
						blend_g = 255;
					}
					*pSPtr = blend_g;
					pSPtr++;

					int blend_b = unsigned char(blue * time) + unsigned char(bg_b * (1 - time));
					if (blend_b > 255)
					{
						blend_b = 255;
					}
					*pSPtr = blend_b;
					pSPtr++;
				}
				pPtr++;
				
			}
			//std::cout << std::endl;
		}
		// 设置纹理,将字体装入其中
		// iLevel表示图像级别,GL_LUMINANCE表示亮度
		glTexImage2D(GL_TEXTURE_2D, iLevel, GL_RGB, gm.gmBlackBoxX, gm.gmBlackBoxY, 0, GL_RGB, GL_UNSIGNED_BYTE, pSwapBuff);
		/*glTexImage2D(GL_TEXTURE_2D, iLevel, GL_LUMINANCE8, gm.gmBlackBoxX, gm.gmBlackBoxY, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pSwapBuff);*/
		// 将字符对象加入到映射地图中,这样就不需要反复创建
		// 字体所占宽度比例,比如设置9而所占96则最后得到的就是1
		m_FontMap[wChar]->m_FontWidth = (float)gm.gmCellIncX / (float)m_FontSize;
		// 字体实际宽度比例,一般获得的字体宽度要比我们设定的窄
		m_FontMap[wChar]->m_Width = (float)gm.gmBlackBoxX / (float)m_FontSize;
		// 字体实际高度比例
		m_FontMap[wChar]->m_Height = (float)gm.gmBlackBoxY / (float)m_FontSize;
		// 字体实际左边x坐标与设定字尺寸的比例,因为字实际高度比设定的要窄,因此这个起点就定义了实际上字是从哪个坐标上开始的
		m_FontMap[wChar]->m_OrigX = (float)gm.gmptGlyphOrigin.x / (float)m_FontSize;
		// 字体实际左边x坐标与设定字尺寸的比例
		m_FontMap[wChar]->m_OrigY = 1.0f - (float)gm.gmptGlyphOrigin.y / (float)m_FontSize;
		//m_FontMap[wChar] = fontData;

		//释放对象
		DeleteObject(hbitmapOld);
		DeleteObject(hbitmap);
		DeleteDC(hdc);
		delete[] pBuff;
		delete[] pSwapBuff;

		return true;
	}
	void setFontColor(unsigned char r, unsigned char g, unsigned char b)//设置文字颜色
	{
		red = r;
		green = g;
		blue = b;
	}
	void setBGColor(unsigned char r, unsigned char g, unsigned char b)//设置背景颜色
	{
		bg_r = r;
		bg_g = g;
		bg_b = b;
	}
	void setfatherWinSize(int w, int h)//设置父窗口大小
	{
		fwinwidth = (float)w;
		fwinheight = (float)h;
	}
	void print3DText(float x, float y,float width, const wchar_t* Text)//绘制文字
	{
		glColor3f(0xff / (float)255, 0xff / (float)255, 0xff / (float)255);//设置颜色空间为白色,否则会影响绘图
		size_t iTextLenth = wcslen(Text);
		//计算空格的个数
		wchar_t space[] = L" ";//空格处理
		int spacenum = 0;
		for (size_t i = 0; i < iTextLenth; i++)
		{
			if (Text[i] == space[0])//处理空格
			{
				spacenum++;
			}
		}
		//启用2D纹理
		glEnable(GL_TEXTURE_2D);
		//居中绘制文字
		float fX = x + width / 2 - (spacenum+ (iTextLenth- spacenum) *2) * m_FontSize / 2 / 2;
		float fY = fwinheight - y;
		float fZ = 0;
		
		for (size_t i = 0; i < iTextLenth; i++)
		{
			if (m_FontMap[Text[i]] == NULL && Text[i] != space[0])
			{
				makeChar(Text[i]);
			}
			// 绘制字体
			CFontData* fontData = m_FontMap[Text[i]];
			if (fontData)
			{
				glMatrixMode(GL_PROJECTION);//将当前矩阵指定为投影矩阵,以便进行投影操作
				glLoadIdentity();//将操作矩阵设为单位矩阵
				glOrtho(0, fwinwidth, 0, fwinheight, 0, 100);

				float iWidth = fontData->m_Width * m_FontSize;
				float iOrigX = fontData->m_OrigX * m_FontSize;
				float iHeight = fontData->m_Height * m_FontSize;
				float iOrigY = fontData->m_OrigY * m_FontSize;

				glBindTexture(GL_TEXTURE_2D, fontData->m_TextureID);
				glBegin(GL_QUADS);
				glTexCoord2f(0.0f, 1.0f);
				glVertex3f(fX + iOrigX, fY - iOrigY - iHeight, fZ);
				glTexCoord2f(0.0f, 0.0f);
				glVertex3f(fX + iOrigX, fY - iOrigY, fZ);
				glTexCoord2f(1.0f, 0.0f);
				glVertex3f(fX + iOrigX + iWidth, fY - iOrigY, fZ);
				glTexCoord2f(1.0f, 1.0f);
				glVertex3f(fX + iOrigX + iWidth, fY - iOrigY - iHeight, fZ);
				glEnd();
				fX += fontData->m_FontWidth * m_FontSize;
				glLoadIdentity();// 重置当前的模型观察矩阵,消除投影矩阵调用带来的影响
			}
			else if(Text[i] == space[0])//处理空格
			{
				fX += m_FontSize/2;
			}
		}
		glDisable(GL_TEXTURE_2D);
	}
};
调用代码
#include <gl/freeglut.h>
#include "Font.hpp"

//窗口大小
int widows_width = 800;
CFontPrinter* font = NULL;

void display(void)
{
  //设置背景颜色
  glClearColor(0x00/float(255), 0x2C / float(255), 0x3E / float(255), 1.0);
  glClear(GL_COLOR_BUFFER_BIT);//以设置清除窗口
  //设置标题
  font->print3DText(0, 10, widows_width, L"综 合 视 频 处 理 系 统");//第一个参数为x,第二个参数为y,第三个参数为宽度,坐标系x向右为正,y向下为正
  glFlush();
  //缓冲区翻转显示图像
  glutSwapBuffers();
}

void changeSize(int w, int h)
{
  widows_width = w;
  glViewport(0, 0, w, h);//设定视口的大小
  font->setfatherWinSize(w, h);//通知字体渲染对象窗口大小改变了
}

//UI初始化
void UIInit()
{
  int ppi = 2;
  font = new CFontPrinter(28 * ppi, "微软雅黑", 800, 600);//创建字体对象
  font->setFontColor(0xF7, 0xF8, 0xF3);//设置字体颜色
  font->setBGColor(0x00, 0x2C, 0x3E);//设置背景颜色
}

int main(int argc, char* argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);//设置绘图模式
  UIInit();//初始化UI
  glutInitWindowPosition(0, 0);//设置窗口弹出位置
  glutInitWindowSize(1920, 1080);//设置窗口大小
  glViewport(0, 0, 1920, 1080);//设定视口的大小
  glutCreateWindow("imgshow");//创建窗口
  glutDisplayFunc(&display);//定义绘图回调函数
  glutReshapeFunc(changeSize);//注册窗口大小改变响应函数
  glutFullScreen();//窗口全屏
  glutMainLoop();//进入消息循环
  
  return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值