超酷三维汉字特效

  大家好,我是Skyman(SM3D),喜欢三维编程,现在正在系统学习OpenGL,DirectX,VR等。我经常到各大论坛上去转转。其中去得最多的地方是NeHe的OpenGL网站(http://nehe.gamedev.net),那是我所见过的最好的学习OpenGL的网站,强烈推荐!!!这是我的第一篇关于OpenGL的文章,写得不是很好,欢迎大家指正。大家知道,在OpenGL中输出文本有两个函数:wglUseFontBitmaps和wglUseFontOutlines,前者用来输出2维文字,后者用来输出3维文字。而要输出汉字,必须使用TrueType字体。本文就教你如何使用wglUseFontOutlines来创建很酷的三维汉字特效。从此文,你可以学到三点知识:

  1. 如何从文件中载入位图作为纹理;
  2. 如何生成三维汉字;
  3. 如何将纹理贴到三维汉字上。 

  我使用的是NeHe的Simple框架。为了简便起见,我这里只列出增加的代码。首先,再头文件定义区加上: #include <stdio.h>

  接着,在bool fullscreen=TRUE后面定义要用到的变量:

GLuint texture[1];		//纹理ID
int rot=0;			//三维汉字绕x轴旋转的角度
HFONT hFont;			//字体句柄
LOGFONT lf;			//逻辑字体
GLYPHMETRICSFLOAT gmf[128];	//包括字形的位置和方向信息的结构
unsigned int ichar=0; 		//字符的整型值
char cchar; 			//要转换为显示列表的字符
unsigned int i=0;		//循环变量
unsigned int j=0; 		//循环变量
char Text[128];			//存放要显示的字符的数组
BYTE TextList[128];		//显示列表
const GLuint ListBase=1000; 	//显示列表的基

  然后在LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);后面定义载入文理的函数LoadBMP和LoadGLTextures,这个在NeHe的许多教程里都有,这里再写一下:

AUX_RGBImageRec *LoadBMP(char* filename)            // 载入位图
{
	FILE* file = NULL;								// 文件句柄
	if (!filename)										//文件名已给了吗?
	{
		return NULL;									// 若没有则返回NULL
	}
	file = fopen(filename"r");					// 检查文件是否存在
	if (file)												// 文件存在吗?
	{
		fclose(file);									// 关闭文件
		return auxDIBImageLoad(filename);		// 载入位图并返回位图的指针
	}
	return NULL;										// 若载入失败则返回NULL
}
int LoadGLTextures()							// 载入位图并转换为纹理
{
	int status = FALSE;							// 状态指示
	AUX_RGBImageRec* textureImage[1];				// 为纹理创建存储空间
	memset(textureImage, 0, sizeof(void *)*1);		// 清零
	
	// 载入位图,检查误,如位图不存在则退出
	if (textureImage[0] = LoadBMP("data/tex.bmp"))	//纹理图片的路径
	{
		status = TRUE;									// 状态设为TRUE
		glGenTextures(1, &texture[0]);		// 创建纹理
		glBindTexture(GL_TEXTURE_2D, texture[0]);
		gluBuild2DMipmaps(GL_TEXTURE_2D, 3, textureImage[0]->sizeX, textureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, textureImage[0]->data);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);									//过滤
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);		//过滤
		glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);		//自动生成纹理坐标
		glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);		//自动生成纹理坐标
		glEnable(GL_TEXTURE_GEN_S);
		glEnable(GL_TEXTURE_GEN_T);
	}
	
	if (textureImage[0])						// 如果纹理存在
	{
		if (textureImage[0]->data)		// 如果纹理图象存在
		{
			free(textureImage[0]->data);	// 释放纹理图象所占用的内存
		}
		free(textureImage[0]);					// 释放图象结构
	}
	return status;								// 返回状态
}

  注明:因为三维字体是比较复杂的模型,所以要手工定义纹理坐标很难。我们可以让OpenGL自动生成纹理坐标。函数glTexGen()用于自动生成纹理坐标。

void gltexgen{ifd}(GLenum coord,GLenum pname,TYPE param);

  参数coord必须是GL_S,GL_T,GL_R或GL_Q,表明是否要生成纹理坐标s,t,r或q。参数pname为GL_TEXTURE_GEN_MODE,表示纹理坐标的生成模式。参数param可选GL_OBJECT_LINEAR,GL_EYE_LINEAR或GL_SPHERE_MAP。 GL_OBJECT_LINEAR表示纹理图象相对于一个移动的物体静止,本程序就采用这种方式。

  然后在InitGL()函数中的glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);后面加入如下语句:

glEnable(GL_LIGHT0);							// 激活默认光照 (虽然光照效果不是很好但是速度快)
glEnable(GL_LIGHTING);						// 激活光照
glEnable(GL_COLOR_MATERIAL);			// 激活材质颜色
glEnable(GL_TEXTURE_2D);					// 激活二维纹理
glBindTexture(GL_TEXTURE_2D, texture[0]);	// 绑定纹理
memset(&lf, 0, sizeof(LOGFONT));		// 清空内存
//设定逻辑字体lf的属性
lf.lfHeight = -1;							// 指定字高
lf.lfWidth = -1;							// 指定字宽
lf.lfEscapement = 0;					// 指定角度(1/10度)
lf.lfOrientation = 0;					// 指定字符的基线与x轴的角度(1/10度)
lf.lfWeight = FW_BOLD;			// 指定字体的重量(FW_BOLD=700)
lf.lfItalic = FALSE;						// 指定是否斜体
lf.lfUnderline = FALSE; 				// 指定是否有下划线
lf.lfStrikeOut = FALSE; 				// 指定是否是StrikeOut字体
lf.lfCharSet = ANSI_CHARSET;	// 指定字符集
lf.lfOutPrecision = OUT_TT_PRECIS;	// 指定输出精度
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS ;	// 指定裁剪精度
lf.lfQuality = PROOF_QUALITY;		// 指定输出质量
lf.lfPitchAndFamily = VARIABLE_PITCH | TMPF_TRUETYPE | FF_MODERN ;	// 指定字体的定位和外观
lstrcpy (lf.lfFaceName, "宋体") ;	// 指定字体名称
hFont=CreateFontIndirect(&lf);		// 创建逻辑字体
SelectObject(hDC, hFont);				// 将逻辑字体选入设备环境

  然后在DrawGLScene()函数中的glLoadIdentity();后面加入:

strcpy(text, "重庆大学欢迎你");		// 指定所要显示的字符
i = 0;		// 对i赋初制值
j = 0;		// 对j赋初制值
while (i < 128)
{
	if(IsDBCSLeadByte(text[i]))	// 判断是否为双字节
	{
		ichar = text[i];
		ichar = (ichar << 8) + 256; 	// 256为汉字内码的“偏移量”
		ichar = ichar + text[i + 1];
		i++;
		i++;
		wglUseFontOutlines(
			hDC, 	// 设备环境句柄
			ichar,	// 要转换为显示列表的第一个字符
			1, 			// 要转换为显示列表的字符数
			1000 + j,	// 显示列表的基数
			0.0f, 		// 指定与实际轮廓的最大偏移量
			0.2f, 		// 在Z轴负方向的值
			WGL_FONT_POLYGONS,	// 指定显示列表用多边形创建字体
			&gmf[j]
		); 	// 接受字符的地址
		textList[j] = j;
		j++;
	}
	else		//若是单字节(英文)
	{
		cchar = text[i];
		i++;
		wglUseFontOutlines(
			hDC, 	// 设备环境句柄
			cchar, 	// 要转换为显示列表的第一个字符
			1,			// 要转换为显示列表的字符数
			1000 + j, // 显示列表的基数
			0.0f, 		// 指定与实际轮廓的最大偏移量
			0.5f, 		// 在Z轴负方向的值
			WGL_FONT_POLYGONS, 	// 指定显示列表用多边形创建字体
			&gmf[j]
		); 	// 接受字符的地址
		textList[j] = j;
		j++;
	}
}
glListBase(listBase);		// 设置显示列表的初始索引值
glTranslatef(-4.0f, 0.0f, -10.0f);	// 要显示的文本的位置
glRotatef(float(rot), 1.0f, 0.0f, 0.0f);	// 文本绕x轴旋转
glCallLists(strlen(Text),GL_UNSIGNED_BYTE,& TextList);//调用多显示列表绘制文本
glFlush(); // 强制绘图
rot = (rot + 1) % 360;	// 旋转变量递增

注明:

  1. 双字节内码DBCS(双字节字符集) 支持很多不同的东亚语言字母,如汉语、日语和朝鲜语等。 DBCS 使用数字 0–128 表示 ASCII 字符集。其它大于 128 的数字作为前导字节字符,它并不是真正的字符,只是简单的表明下一个字符属于非拉丁字符集。在 DBCS 中,ASCII 字符的长度是一个字节,而日语、朝鲜语和其它东亚字符的长度是 2 个字节。汉字内码的偏移量为256。
  2. 执行多显示列表可以有效的提高渲染速度。方法是将显示列表的索引值放入一个数组中,然后调用glCallLists()命令。对于有多种字体的情况,需要为每种字体建立一个不同的初始显示列表索引值。这些初始索引值可以在调用glCallLists()函数之前,通过调用glListBase()函数来指定。该函数指定一个偏移量,该偏移量被加到glCallLists()函数中的显示列表索引中,以获取最终的显示列表索引值。函数glCallLists(n,type,*lists)执行n个显示列表。所执行的列表索引值是由当前显示列表基(由glListBase()指定)指明的偏移量,加上由 lists指向的数组中的整数之和。

你可能感兴趣的:(超酷三维汉字特效)