---- 本文详细讨论了在OpenGL中显示文本的几种方法。
----也许大多数程序员使用OpenGL更多的是将精力集中于动 态三维图形应用,因此,OpenGL中的文本显示往往被忽视,使人有不见积薪之感。本文 介绍了几种文本显示的方法,希望能对使用OpenGL的编程者有所帮助。
建立并修改程序
----建立一个MFC SDI Windows应用工程Text,除单文档属 性外,使用其他的所有默认选择。在菜单Project打开Settings对话框,在Link属性页的 object/library modules编辑框中加入opengl32.lib glu32.lib glaux.lib三个GL库。我们利用这些 库函数完成图形编辑工作。----为使VC++的AppWizard产生的SDI应用程序能使用 OpenGL绘图,还需要作一些修改,说明如下。
----1.介绍PreCreateWindow函数
---- OpenGL窗口必须具有WS_CLIPCHILDREN(创建父窗口 使用的Windows风格,用于重绘时剪裁子窗口所覆盖的区域)和WS_CLIPIBLINGS(创 建子窗口使用的Windows风格,用于重绘时剪裁其他子窗口所覆盖的区域)两种风格。 此外,窗口类属性不能包括CS_PARENTDC风格。具体程序实现如下:
BOOL CTextView::PreCreateWindow(CREATESTRUCT& cs) { // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs //An OpenGL window must be created with the following flag // and must not include CS_PARENTIDC for the class style. cs.style|=WS_CLIPSIBLINGS|WS_CLIPCHILDREN; return CView::PreCreateWindow(cs); }
----2.OnCreate函数中定义像素格式PIXELFORMAT和创建 RC
----要使窗口支持OpenGL绘图,必须对窗口进行初始化。其 中包括定义像素格式PIXELFORMAT和创建RC,为OpenGL指定一个合适的像素格式, 创建着色上下文并将它和窗口的设备上下文关联起来。着色上下文保存着当前着色环境 的信息。可在OnCreate中调用一个自建视口成员函数SetupPixelFormat(),具体函数如下:
BOOL CTextView::SetupPixelFormat() { //Create a rendering context CDC* m_pDC = GetDC(); if(m_pDC == NULL) //failure to get DC { MessageBox(“Could't get a valid DC."); return FALSE; } //Default pixel format is a single-buffered, //OpenGL support hardware-accelerated,RGBA mode format PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR),//Structure size. 1, // Structure version number.Property flags(特性标志): PFD_DRAW_TO_WINDOW | // support window PFD_SUPPORT_OPENGL | // support OpenGL PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, // RGBA type 24, // 32-bit color. 0, 0, 0, 0, 0, 0, // Not concerned with these:不涉及的属性 0, // No alpha :无alpha缓存 0, // Shift bit ignored:忽略转换位 0, 0, 0, 0, 0,// No accum buffer:没有累积缓存 32, // 32-bit depth buffer. 0, // No stencil:无模板缓存 0, // No auxliliary buffers:无辅助缓存 PFD_MAIN_PLANE, // Main layer type.:主层类型 0, // Reserved.:保留结构数 0, 0, 0 // Unsupported.:不支持结构数 }; int nPixelFormat= ChoosePixelFormat(m_pDC->GetSafeHdc(),&pfd); if( nPixelFormat ==0) { MessageBox(“ChoosePixelFormat failed."); return FALSE; } if(SetPixelFormat(m_pDC->GetSafeHdc(), nPixelFormat,&pfd)==0) { MessageBox(“SetPixelFormat failed."); return FALSE; } if( (m_hRC=wglCreateContext(m_pDC-> GetSafeHdc())) ==0) { MessageBox(“wglCreateContext failed."); return FALSE; } if( (wglMakeCurrent(m_pDC->GetSafeHdc(),m_hRC)) ==0) { MessageBox(“wglMakeCurrent failed."); return FALSE; } if(m_pDC) ReleaseDC(m_pDC); return TRUE; }
----3.在OnCreate()函数中调用初始化背景函数 InitializeOpenGL()
void CTextView::InitializeOpenGL() { glClearColor(0.2f, 0.2f, 0.2f, 0.0f); glClearDepth(1.0); glDepthFunc(GL_LESS); glEnable(GL_DEPTH_TEST); glShadeModel(GL_SMOOTH); }
----4.在OnCreate()中启动动画定时器
SetTimer(0,40,NULL);
----5.在收到WM_SIZE消息时要重新计算场景尺寸,用OnSize 设置图形显示模式
----为了使物体能合适的显示,必须要经过投影和确定视口的 工作。
void CTextView::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); // TODO: Add your message handler code here //Save the wide and height of the current window Client GLsizei nWidth=(GLsizei)cx; GLsizei nHeight=(GLsizei)cy; ratio=(double)cx/(double)cy; //Coupute the aspect ratio GLdouble dAspect=(GLdouble)nWidth/(GLdouble)nHeight; glViewport(0,0,nWidth,nHeight); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective (FOV,dAspect,NEARPLANE,FARPLANE); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); }
----6.在OnDraw()中简单调用DrawScene()以执行OpenGL函数
void CTextView::OnDraw(CDC* pDC) { CTextDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here DrawScene(); //Invalidate(); }
----7.撤销视窗时删除上下文并撤销定时器
void CTextView::OnDestroy() { CView::OnDestroy(); // TODO: Add your message handler code here //This call makes the current RC not current if(wglMakeCurrent(0,0)==FALSE) MessageBox(“wglMakeCurrent failed."); //delete the RC if(m_hRC && (wglDeleteContext(m_hRC)==FALSE)) MessageBox(“wglDeleteContext fail."); KillTimer(1); }
----8.当40ms定时器时间到时,简单地将整个场景的客户区 置无效,使之重画
void CTextView::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default Invalidate(); CView::OnTimer(nIDEvent); }
修改界面
---- 删除菜单IDR_MAINFRAME中File下除去Exit菜单项 外的所有选项,添加“显示GDI文字” “列表制作文字”“列表三维文字”三个菜单项, 并给他们分配适当的ID。使用ClassWizard为这三个菜单项在视类中添加命令处理函数和 界面更新函数,其中显示GDI文字的对应的函数如下:void CTextView::OnGdiText() { // TODO: Add your command handler code here m_iWhichText=0; Invalidate(); } void CTextView::OnUpdateGdiText(CCmdUI* pCmdUI) { // TODO: Add your command update UI handler code here if(m_iWhichText==0) pCmdUI->SetCheck(); else pCmdUI->SetCheck(0); }
----增加Draw3DText()、DrawListText()和DrawGdiText()三个 函数用于三种不同的文本绘制方法。增加DrawScene()函数,它被OnDraw函数调用,用于 绘制场景。在DrawScene()函数中,当m_iWhichText为0、1、2时,分别调用 DrawGdiText()、DrawListText()和Draw3DText()显示文本。具体函数实现如下:
void CTextView::OnDraw(CDC* pDC) { CTextDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here DrawScene(); //Invalidate(); } void CTextView::DrawScene() { glClear (GL_COLOR_BUFFERBIT|GL_DEPTH_BUFFER_BIT); glPushMatrix(); glTranslatef(0.0f,0.0f,-FARPLANE); //TextureMap(); glPopMatrix(); glPushMatrix(); glTranslatef (0.0f,0.0f,-(FARPLANE+NEARPLANE)/2); if(m_iWhichText==1) DrawListText(); if(m_iWhichText==2) Draw3DText(); glPopMatrix(); glFinish(); SwapBuffers(wglGetCurrentDC()); if(m_iWhichText==0) DrawGdiText(); }
GDI 显示文本
---- 调用wglGetCurrentDC()函数取得当前的设备上下文,使 用TextOut函数显示文本,不过要注意在DoubleBuffer模式下,绘制函数要在glFinish()和 SwapBuffers(wglGetCurrentDC())函数之后调用,否则会产生闪烁,在绘制OpenGL结束之前 使用GDI函数,要除去闪烁则只能使用SingleBuffer模式,具体函数如下:void CTextView::DrawGdiText() { HDC hdc=wglGetCurrentDC(); ::SetBkMode( hdc, TRANSPARENT ); ::SetTextColor( hdc, RGB(250,0,0) ); CString sState(“显示GDI文本。"); ::TextOut(hdc,5,5,sState,sState.GetLength()); }
wglUseFontBitmaps
函数显示文字
使用wglUseFontBitmaps()将ASCII字符装入显示列表, 然后使用glCallLists()函数利用显示列表序列显示文本。wglUseFontBitmaps有四个参数, 分别是当前使用的DC、从第几个ASCII字符起始装入列表、装入列表的ASCII字符数和 起始的列表序号。glListBase()指定glCallLists执行的起始列表序列号。glCallLists()含有三个参数:执行列表序列的个数、列表值的类型和所要显示的文本。注意如果所要显示的 文本是字符串,它所提供的信息是相对于起始装入ASCII字符的偏移量,因此最终所显 示的ASCII字符是从glListBase()所指定的列表起始号在经过glCallLists()中偏移后的列表, 因此wglUseFontBitmaps的从第几个ASCII字符起始装入列表参数、glListBase()指定的 glCallLists执行的起始列表序列号和glCallLists()中的所要显示的文本参数都可以影响最终 显示结果。由于显示的是ASCII 字符,因此不能显示汉字。glRasterPos3f函数决定在 OpenGL视景体坐标系下的偏移。具体函数实现如下:
void CTextView::DrawListText() { wglUseFontBitmaps(wglGetCurrentDC(),0,256,1000); glListBase(1000); glRasterPos3f(-5.0f,0.0f,0.0f); glCallLists(20,GL_UNSIGNED_BYTE,“Draw with List Text."); }
wglUseFontOutlines
函数显示三维文字
----wglUseFontOutlines使得OpenGL可以显示三维文字。它 的用法与wglUseFontBitmaps函数大致相同,但是多了内插计算参数、字体深度、显示方 式和装载字模的缓存四个参数,且只能显示TrueType字体,显示前应该先选择字体类型。 具体函数实现如下:
void CTextView::Draw3DText() { GLYPHMETRICSFLOAT agmf[256]; // create display lists for glyphs 0 through 255 // with 0.1 extrusion and default deviation. //The display list numbering starts at 1000 (it could be any number) wglUseFontOutlines(wglGetCurrentDC(),0,255,1000,0.3f,0.8f, WGL_FONT_LINES ,agmf); // Set up transformation to draw the string glTranslatef(-15.0f,0.0f,0.0f); glScalef(4.0f, 4.0f, 4.0f); // Display a string glListBase(1000); // Indicates the start of display lists for the glyphs // Draw the characters in a string glCallLists(26, GL_UNSIGNED_BYTE,“Draw outline list 3D text."); }