位图字体

图像字体A:

这一课我们将创建一些基于2D图像的字体,它们可以缩放,但不能旋转,并且总是面向前方,但作为基本的显示来说,我想已经够了。

使用位图字体比起使用图形字体(贴图)看起来不止强100倍。你可以随时改变显示在屏幕上的文字,而且用不着为它们逐个制作贴图。只需要将文字定位,再使用我最新的gl命令就可以在屏幕上显示文字了。

我尽可能试着将命令做的简单。你只需要敲入glPrint("Hello") 。它是那么简单。

一个小注释,这段代码是专门针对Windows写的,它使用了Windows的wgl函数来创建字体。

我们从第十课的典型代码开始,最后加上math.h头文件,这样我们就可以使用SIN和COS函数在屏幕中移动文字了。

 

另外,我们还要添加3个变量。base将保存我们创建的第一个显示列表的编号。每个字符都需要有自己的显示列表。例如,字符‘A’在显示列表中是65,‘B’是66,‘C’是67,等等。所以,字符‘A’应保存在显示列表中的base + 65这个位置。

然后添加两个计数器(cnt1 和 cnt2),它们采用不同的累加速度,通过SIN和COS函数来改变文字在屏幕上的位置。在屏幕上创造出一种看起来像是半随机的移动方式。同时,我们用这两个计数器来改变文字的颜色(后面会进一步解释)。

  1. //第十一课图像字体  
  2. GLuint  base;           // 绘制字体的显示列表的开始位置  
  3. GLfloat cnt1;           // 字体移动计数器1  
  4. GLfloat cnt2;           // 字体移动计数器2  


构建真实字体


下面这段代码用来构建真实的字体,这也是最难写的一部分代码。

‘HFONT font’告诉Windows我们将要使用一个Windows字体。Oldfont用来存放字体。

接下来我们在定义base的同时使用glGenLists(96)创建了一组共96个显示列表。

  1. GLvoid COpenglbaseView::BuildFont(GLvoid)  
  2. {  
  3.     HFONT   font;                       // 字体句柄  
  4.     HFONT   oldfont;                        // 旧的字体句柄  
  5.       
  6.     base = glGenLists(96);                  // 创建96个显示列表  


下面该有趣的部分了,我们将创建属于自己的字体。我们从指定字体的大小开始,你会注意到它是一个负数,我们通过加上一个负号来告诉Windows寻找一个基于CHARACTER高度的字体。如果我们使用一个正数,就是寻找一个与基于CELL的高度相匹配的字体。

  1. font = CreateFont(  -24,                    // 字体高度  
  2.     0,              // 字体宽度 Windows就会使用默认值        
  3.     0,              // 字体的旋转角度 Angle Of Escapement  
  4.     0,              // 字体底线的旋转角度Orientation Angle用于指定每个字的底边和显示设备的X轴之间的角度,每个单位是十分之一个角度  
  5.     FW_BOLD,                // 字体的重量 FW_DONTCARE是0, FW_NORMAL是400, FW_BOLD是700 and FW_BLACK是900。值越大,字体就越粗。    
  6.     FALSE,              // 是否使用斜体  
  7.     FALSE,              // 是否使用下划线  
  8.     FALSE,              // 是否使用删除线    
  9.     ANSI_CHARSET,           // 设置字符集  
  10.     OUT_TT_PRECIS,          // 输出精度   
  11.     CLIP_DEFAULT_PRECIS,        // 裁剪精度  
  12.     ANTIALIASED_QUALITY,        // 输出质量   
  13.     FF_DONTCARE|DEFAULT_PITCH,      // Family And Pitch   
  14. "Courier New");         // 字体名称  打开Microsoft Word找一个你喜欢的字体。将‘Courier New’替换为你想用的字体的名字,你就可以使用它了。(中文还不行,需要别的方法)  
  15.       


 现在,选择我们刚才创建的字体。Oldfont将指向被选择的对象。然后我们从第32个字符(空格)开始建立96个显示列表。如果你愿意,也可以建立所有256个字符,只要确保使用glGenLists建立256个显示列表就可以了。然后我们将oldfont对象指针选入hDC并且删除font对象。

  1.       
  2.  oldfont=(HFONT)SelectObject(m_pDC->GetSafeHdc(),font); // 选择我们需要的字体  
  3.  wglUseFontBitmaps(m_pDC->GetSafeHdc(),32,96,base);  //创建96个显示列表,绘制ASCII码为32-128的字符 
  4. //可以使用wglUseFontBitmaps函数来批量的产生显示字符用的显示列表。
  5.  SelectObject(m_pDC->GetSafeHdc(),oldfont);  //选择原来的字体  
  6.  DeleteObject(font);         //删除字体  
  7.   
  8. }  

接下来的代码很简单。它在内存中从base开始删除96个显示列表。我不知道Windows是否会做这些工作,但还是保险为好。

  1. GLvoid COpenglbaseView::KillFont(GLvoid)  
  2. {  
  3.     glDeleteLists(base,96);     //删除96个显示列表  
  4.   
  5. }  

// 自定义GL输出字体函数

然后我们将GL_LIST_BIT压入属性堆栈,它会防止glListBase影响到我们的程序中的其它显示列表。

GlListBase(base-32)是一条有些难解释的命令。比如说要写字母‘A’,它的相应编号为65。如果没有glListBase(base-32)命令,OpenGL就不知道到哪去找这个字母。它会在显示列表中的第65个位置找它,但是,假如base的值等于1000,那么‘A’的实际存放位置就是1065了。所以通过base设置一个起点,OpenGL就知道到哪去找到正确的显示列表了。减去32是因为我们没有构造过前32个显示列表,那么就跳过它们好了。于是,我们不得不通过从base的值减去32,作为偏移的基准来让OpenGL知道这一点。我希望这些有意义。
GlCallLists是一个很有趣的命令。它可以同时将多个显示列表的内容显示在屏幕上。

下面的代码做后续工作。首先,它告诉OpenGL我们将要在屏幕上显示出显示列表中的内容。

strlen(text)函数用来计算我们将要显示在屏幕上的文字的长度。

然后,OpenGL需要知道我们允许发送给它的列表的最大值。我们不能发送长度大于255的字符串。这个字符列表的参数被当作一个无符号字符数组处理,它们的值都介于0到255之间。

最后,我们通过传递str(它指向我们的字符串)来告诉OpenGL显示的内容。

也许你想知道为什么字符不会彼此重叠堆积在一起。那时因为每个字符的显示列表都知道字符的右边缘在那里,在写完一个字符后,OpenGL自动移动到刚写过的字符的右边,在写下一个字或画下一个物体时就会从GL移动到的最后的位置开始,也就是最后一个字符的右边。

最后,我们将GL_LIST_BIT属性弹出堆栈,将GL恢复到我们使用glListBase(base-32)设置base那时的状态。

 

  1. GLvoid COpenglbaseView::glPrint(unsigned int base, char *str)  
  2. {  
  3.     if((base==0)||(str==NULL))  
  4.         return ;  
  5.     glPushAttrib(GL_LIST_BIT); 
  6. /*******************************************************************************************************
  7. GL依据相关性将其属性划分为20组。例如,所有的多边形属性位于GL_POLYGON_BIT组中,

    所有的直线属性位于GL_LINE_BIT中,我们可以将多组属性或者全部属性(GL_ALL_ATTRIBUTE_BITS)

    通过函数glPushAttrib压入属性堆栈中,恢复则用glPopAttrib。

  8. 与glPushMatrix的区别:

  9. glPushMatrix操作的对象是矩阵,主要是对画图的平移、旋转、放缩等进行操作;

  10. glPushAttrib操作的对象是各种属性,对绘制的各种属性(颜色等),进行操作。

    *********************************************************************************************************/
  11.     glListBase(base-32);  
  12.     glCallLists((GLsizei)strlen(str),GL_UNSIGNED_BYTE,str);  
  13.     glPopAttrib();  
  14. }  
在初始化代码中添加BuildFont( );
  1. BOOL COpenglbaseView::InitializeOpenGL(CDC *pDC)  
  2. {  
  3.     m_pDC=pDC;  
  4.     if(!SetupPixelFormat())  
  5.         return FALSE;  
  6.     m_hRC=::wglCreateContext(m_pDC->GetSafeHdc());//产生一个新的opengl绘图描述表使之适合在参数hdc给出的设备上画图    
  7.     ::wglMakeCurrent(m_pDC->GetSafeHdc(),m_hRC);  
  8.       
  9.     SetLight();//设置光照环境  
  10.     myInit();//设置绘图环境  
  11.     BuildFont();  
  12.   
  13.   
  14.     return TRUE;  
  15. }  

绘图代码

  1. BOOL COpenglbaseView::RenderScene()  
  2. {  
  3.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);         // 清除屏幕及深度缓存  
  4.     glLoadIdentity();  
  5.     glTranslatef(0.0f,0.0f,-1.0f);              // 移入屏幕一个单位  
  6.     // 根据字体位置设置颜色  
  7.     glColor3f(1.0f*float(cos(cnt1)),1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)));   
  8.     // 设置光栅化位置,即字体的位置  
  9.     glRasterPos2f(-0.45f+0.05f*float(cos(cnt1)), 0.35f*float(sin(cnt2)));  
  10.     glPrint(base,"You are great!");     // 输出文字到屏幕  
  11.     cnt1+=0.051f;                       // 增加计数器值  
  12.     cnt2+=0.005f;  
  13.   
  14.     return TRUE;                        // 继续运行  
  15.   
  16. }  


 

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

补充解释

  函数原型:void glListBase(GLuintbase);

  函数功能:使得OpengL可以找到绘制对应字符的显示列表的位置。

  函数参数介绍:

  GLuint base 这个参数表示显示列表的基质。

  关于使用glListBase(GLuint base)进行字体创建有几点说明:

  void glListBase(GLuint base)的用法让人容易误解,还有为什么会使用glListBase(ListBase - 32)的情况;

  现在做一个解释:

  1 通过ListBase = glGenList(96);创建96个显示列表,从ListBase索引的位置开始的96个显示列表

  2 wglUseFrontBitmaps(hDC,32,96,ListBase);根据hDC中的字体属性从ASCII码32开始创建96个显示列表,(ASCII:32到127)而且这96个显示列表依次对应于显示列表从ListBase到ListBase+96.注意例如'A'的ASCII值是65那么对应的显示列表中位于ListBase+65

  3 glListBase(ListBase - 32);因为OpengL如果要寻找‘A’那么它会首先根据ListBase值+65得到‘A’的位置。但是wglUseFrontBitmaps()使得ListBase的值与ASCII为32的一样,所以就需要将ListBase的值减32与OpengL的查询方式保持一致。

  特别说明

  glListBase(ListBase - 32) 的说明 在没有这段命令之前ListBase的值对应着ASCII为32的显示列表

  如果使用glCallListBase()来显示字符时 如要显示‘A’ 那么他会从ListBase开始向后推进56位找到A

  也就是找到ListBase+56对应位置的字符 但是ListBase对应着ASCII为32 所以ListBase+56就不是

  A 所以 需要将ListBase-32 得到正确的基值。

  关于glListBase(GLuint base)的补充:

  1、如果想要正确理解glListBase的用法请先理解函数glCallLists(GLsizei n,GLenum type,const GLvoid *lists)的用法:此函数执行n个显示列表,执行数由n决定,所执行的列表索引是由当前显示列表基(由glListBase函数指定)指明的偏移量,加上由lists指向的数组中的有符号整数之和。

  2、正如上面glGenList(96)和wglUseFrontBitmap用法,但有点小错误(个人认为),即‘A'所对应的的显示列表中的位置应是ListBase+65-32,即ListBase+33,而不是ListBase+65。

  3、因为’A'的ASCII为65,而glListBase(GLuint base)中,base的默认值为0,所以如果不调用glListBase(ListBase - 32),而直接调用glCallLists(GLsizei n,GLenum type,const GLvoid *lists),么那它在想显示'A'时,调用的显示列表的索引应为base+65,即为65,这显然不是在'A'在显示列表中对应的位置ListBase+33,而当调用glListBase(ListBas - 32)后,则调用的显示列表的索引应为:ListBas - 32+65,即ListBase+33,正好便是字符'A'在显示列表中对应的位置。

--------------------------------------------------------------------------------------------------------

 第十一课B:位图字体独立为单独的头文件和源文件

OpenGL位图字体

 

 

   以下在第10课代码基础上进行修改。        点击打开链接   

 用GDI显示文字当然时非常容易,但是用OpenGL来显示文字则相对来说比较复杂一点,OpenGL中有三种字体:位图字体,轮廓字体,纹理映射字体。而采用位图字体可能时最简洁,最清楚的显示文本的方法,主要用到了"wiggle"函数wglUseFontBitmaps()来创建它们,也比较简单,此函数将从系统载入的字体文件中生成位图。下面时关于位图字体创建,使用的源代码例子:

/OpenGLFontInit.h

#ifndef _OPENGLFONTINIT_H_
#define _OPENGLFONTINIT_H_

unsigned int CreateBitmapFont(char *fontName,int fontSize,HDC g_HDC);
void PrintString(unsigned int base,char *str);
void ClearFont(unsigned int base);
unsigned int InitializeFont(char *fontName,int fontSize,HDC g_HDC);

#endif

/OpenGLFontInit.cpp

#include "stdafx.h"
#include <gl\gl.h>
#include <gl\glu.h>
#include "OpenGLFontInit.h"

unsigned int CreateBitmapFont(char *fontName,int fontSize,HDC g_HDC)
{
HFONT hFont;
unsigned int base;
base = glGenLists(96);

hFont=CreateFont(fontSize,0,0,0,FW_BOLD,FALSE,FALSE,FALSE,
               ANSI_CHARSET,OUT_TT_PRECIS,
               CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY,
      FF_DONTCARE|DEFAULT_PITCH,
      fontName);
if(!hFont)
   return 0;
SelectObject(g_HDC,hFont);
wglUseFontBitmaps(g_HDC,32,96,base);
    return base;
}

void PrintString(unsigned int base,char *str)
{
if((base==0)||(str==NULL))
   return ;

glPushAttrib(GL_LIST_BIT);
glListBase(base-32);
glCallLists((GLsizei)strlen(str),GL_UNSIGNED_BYTE,str);
glPopAttrib();
}

void ClearFont(unsigned int base)
{
if(base!=0)
   glDeleteLists(base,96);
}

unsigned int InitializeFont(char *fontName,int fontSize,HDC g_HDC)
{
return CreateBitmapFont(fontName,fontSize,g_HDC);
}

例子:在opengl配置好的MFC单文档中使用opengl位图字体

1.在视图源文件中#include "OpenGLFontInit.h",并且创建一个变量:

        unsigned int listBase;//创建一个字体显示列表的基准ID

2.在视图源文件中的InitializeOpenGL(CDC *pDC)函数中添加:

       listBase=InitializeFont("宋体",20,wglGetCurrentDC());

  1. listBase=InitializeFont("Courier New",-24,m_pDC->GetSafeHdc());//不支持中文字体  
  2.  SetLight();//设置光照环境  
  3.  myInit();//设置绘图环境  

3.最后就可以在视图源文件的RenderScene()函数中使用。如:

进入屏幕的深度不同,glRasterPos可调节的精度就不一样。

       

  1. BOOL COpenglbaseView::RenderScene()  
  2. {  
  3.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);         // 清除屏幕及深度缓存     
  4.     glLoadIdentity();    
  5.     glTranslatef(0.0f,0.0f,-1.0f);              // 移入屏幕一个单位     
  6.     // 根据字体位置设置颜色     
  7.     glColor3f(0.0,0.0,1.0f);     
  8.     // 设置光栅化位置,即字体的位置     
  9.     glRasterPos2f(0.0, 0.0);   
  10.     PrintString(listBase,"You are great!");     // 输出文字到屏幕    
  11.     return TRUE;                        // 继续运行  
  12.   
  13. }  

4.最后在视图源文件的OnDestroy()函数中添加:

      ClearFont(listBase);

如上使用就没问题了。

glRasterPos2f(x,y)

 

glOpenGL 預設的座標系統,
(1,1) 在視窗的右上方,
(0,0) 在視窗中心,
(-1,-1) 在視窗的左下方.

RasterPos2i()


glRasterPos2i(200, 200); 改变光栅位置
光栅(Raster):由像素构成的一个矩形网格。要在光栅上显示的数据保存于帧缓存内。

  1. BOOL COpenglbaseView::RenderScene()  
  2. {  
  3.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);         // 清除屏幕及深度缓存     
  4.     glLoadIdentity();    
  5.     glTranslatef(0.0f,0.0f,-10.0f);              // 移入屏幕一个单位     
  6.     // 根据字体位置设置颜色     
  7.     glColor3f(0.0,0.0,1.0f);     
  8.     // 设置光栅化位置,即字体的位置     
  9.     glRasterPos2f(-5, 0.5);   
  10.     PrintString(listBase,"You are great!");     // 输出文字到屏幕    
  11.     return TRUE;                        // 继续运行  
  12.   
  13. }  



第十一课C:OpenGL显示中文字体

添加格式控制的又该如何呢?希望继续完善

 OpenglFontInit文件添加中文显示点击打开链接

  1. void PrintCNString(const char* str,HDC hDC)  
  2. {  
  3.     int len, i;  
  4.     wchar_t* wstring;  
  5.     GLuint list = glGenLists(1);  
  6.     // 计算字符的个数  
  7.     // 如果是双字节字符的(比如中文字符),两个字节才算一个字符  
  8.     // 否则一个字节算一个字符  
  9.     len = 0;  
  10.     for(i=0; str[i]!=''; ++i)  
  11.     {  
  12.         if( IsDBCSLeadByte(str[i]) )  
  13.             ++i;  
  14.         ++len;  
  15.     }  
  16.       
  17.     // 将混合字符转化为宽字符  
  18.     wstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t));  
  19.     MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);  
  20.     wstring[len] = L'';  
  21.       
  22.     // 逐个输出字符  
  23.     for(i=0; i<len; ++i)  
  24.     {  
  25.         wglUseFontBitmapsW(hDC, wstring[i], 1, list);  
  26.         glCallList(list);  
  27.     }  
  28.       
  29.     // 回收所有临时资源  
  30.     free(wstring);  
  31.     glDeleteLists(list, 1);  
  32.   
  33. }  


 

显示中文
原则上,显示中文和显示英文并无不同,同样是把要显示的字符做成显示列表,然后进行调用。
但是有一个问题,英文字母很少,最多只有几百个,为每个字母创建一个显示列表,没有问题。但是汉字有非常多个,如果每个汉字都产生一个显示列表,这是不切实际的。
我们不能在初始化时就为每个字符建立一个显示列表,那就只有在每次绘制字符时创建它了。当我们需要绘制一个字符时,创建对应的显示列表,等绘制完毕后,再将它销毁。
这里还经常涉及到中文乱码的问题,我对这个问题也不甚了解,但是网上流传的版本中,使用了MultiByteToWideChar这个函数的,基本上都没有出现乱码,所以我也准备用这个函数:)
通常我们在C语言里面使用的字符串,如果中英文混合的话,例如“this is 中文字符.”,则英文字符只占用一个字节,而中文字符则占用两个字节。用MultiByteToWideChar函数,可以转化为所有的字符都占两个字节(同时解决了前面所说的乱码问题:))。
转化的代码如下:

// 计算字符的个数
// 如果是双字节字符的(比如中文字符),两个字节才算一个字符
// 否则一个字节算一个字符
len 0;
for(i=0; str[i]!=''; ++i)
{
    if( IsDBCSLeadByte(str[i]) )
        ++i;
    ++len;
}

// 
将混合字符转化为宽字符
wstring (wchar_t*)malloc((len+1) sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);
wstring[len] L'';

// 
用完后记得释放内存
free(wstring);



加上前面所讲到的wglUseFontBitmaps函数,即可显示中文字符了。

void drawCNString(const char* str) {
    int len, i;
    wchar_t* wstring;
    HDC hDC wglGetCurrentDC();
    GLuint list glGenLists(1);

    // 
计算字符的个数
    // 如果是双字节字符的(比如中文字符),两个字节才算一个字符
    // 否则一个字节算一个字符
    len 0;
    for(i=0; str[i]!=''; ++i)
    {
        if( IsDBCSLeadByte(str[i]) )
            ++i;
        ++len;
    }

    // 
将混合字符转化为宽字符
    wstring (wchar_t*)malloc((len+1) sizeof(wchar_t));
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);
    wstring[len] L'';

    // 
逐个输出字符
    for(i=0; i<len; ++i)
    {
        wglUseFontBitmapsW(hDC, wstring[i], 1, list);
        glCallList(list);
    }

    // 
回收所有临时资源
    free(wstring);
    glDeleteLists(list, 1);
}



注意我用了wglUseFontBitmapsW函数,而不是wglUseFontBitmapswglUseFontBitmapsWwglUseFontBitmaps函数的宽字符版本,它认为字符都占两个字节。因为这里使用了MultiByteToWideChar,每个字符其实是占两个字节的,所以应该用wglUseFontBitmapsW

void display(void) {
    glClear(GL_COLOR_BUFFER_BIT);

    selectFont(48, ANSI_CHARSET, "Comic Sans MS");
    glColor3f(1.0f, 0.0f, 0.0f);
    glRasterPos2f(-0.7f, 0.4f);
    drawString("Hello, World!");

    selectFont(48, GB2312_CHARSET, "
楷体_GB2312");
    glColor3f(1.0f, 1.0f, 0.0f);
    glRasterPos2f(-0.7f, -0.1f);
    drawCNString("
当代的中国汉字");

    selectFont(48, DEFAULT_CHARSET, "
华文仿宋");
    glColor3f(0.0f, 1.0f, 0.0f);
    glRasterPos2f(-0.7f, -0.6f);
    drawCNString("
傳統的中國漢字");

    glutSwapBuffers();
}


效果如图:
[转载]opengl文字处理(1) <wbr>(入门学习十六)(转) 

 

纹理字体
把文字放到纹理中有很多好处,例如,可以任意修改字符的大小(而不必重新指定字体)。
对一面飘动的旗帜使用带有文字的纹理,则文字也会随着飘动。这个技术在三国志系列游戏中经常用到,比如关羽的部队,旗帜上就飘着个字,张飞的部队,旗帜上就飘着个字,曹操的大营,旗帜上就飘着个字。三国人物何其多,不可能为每种姓氏都单独制作一面旗帜纹理,如果能够把文字放到纹理上,则可以解决这个问题。(参见后面的例子:绘制一面字旗)
如何把文字放到纹理中呢?自然的想法就是:如果前面所用的显示列表,可以直接往纹理里面绘制,那就好了。不过,绘制到纹理这种技术要涉及的内容可不少,足够我们专门拿一课的篇幅来讲解了。这里我们不是直接绘制到纹理,而是用简单一点的办法:先把汉字绘制出来,成为像素,然后用glCopyTexImage2D把像素复制为纹理。
glCopyTexImage2DglTexImage2D的用法是类似的(参见第11课),不过前者是直接把绘制好的像素复制到纹理中,后者是从内存传送数据到纹理中。要使用到的代码大致如下:

// 先把文字绘制好
glRasterPos2f(XXX, XXX);
drawCNString("
");
// 
分配纹理编号
glGenTextures(1, &texID);
// 
指定为当前纹理
glBindTexture(GL_TEXTURE_2D, texID);
// 
把像素作为纹理数据
// 将屏幕(0, 0)  (64, 64)的矩形区域的像素复制到纹理中
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 64, 64, 0);
// 
设置纹理参数
glTexParameteri(GL_TEXTURE_2D,
    GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,
    GL_TEXTURE_MAG_FILTER, GL_LINEAR);

然后,我们就可以像使用普通的纹理一样来做了。绘制各种物体时,指定合适的纹理坐标即可。
有一个细节问题需要特别注意。大家看上面的代码,指定文字显示的位置,写的是glRasterPos2f(XXX, XXX);这里来讲讲如何计算这个显示坐标。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值