zoukankan      html  css  js  c++  java
  • Qt OpenGL 轮廓字体

    这次教程中,我将教大家绘制3D的轮廓字体,当然肯定不是贴图方式了,它们可像一般的3D模型一样进行旋转,放缩。
    创建轮廓字体的方法与13课位图的位图字体类似,但轮廓字体要酷得多!轮廓字体可以在屏幕中以3D方式旋转,而且轮廓字体还可以有一定的厚度,而不再是平面的2D字符了。使用轮廓字体,我们可以将计算机中的任何字体转换为OpenGL的3D字体,是不是听起来很诱人呢?
    程序运行时效果如下:
    下面进入教程:
    我们这次将在第13课的基础上修改代码,由于有13课代码的基础,这节课会简单许多。我只解释新增的代码,首先打开myglwidget.h文件,将类声明更改如下:
     1 #ifndef MYGLWIDGET_H
     2 #define MYGLWIDGET_H
     3  
     4 #include <QWidget>
     5 #include <QGLWidget>
     6  
     7 class MyGLWidget : public QGLWidget
     8 {
     9     Q_OBJECT
    10 public:
    11     explicit MyGLWidget(QWidget *parent = 0);
    12     ~MyGLWidget();
    13  
    14 protected:
    15     //对3个纯虚函数的重定义
    16     void initializeGL();
    17     void resizeGL(int w, int h);
    18     void paintGL();
    19  
    20     void keyPressEvent(QKeyEvent *event);           //处理键盘按下事件
    21  
    22 private:
    23     void buildFont();                               //创建字体
    24     void killFont();                                //删除显示列表
    25     void glPrint(const char *fmt, ...);             //输出字符串
    26  
    27 private:
    28     bool fullscreen;                                //是否全屏显示
    29     HDC m_HDC;                                      //储存当前设备的指针
    30  
    31     GLYPHMETRICSFLOAT m_Gmf[256];                   //记录256个字符的信息
    32     GLfloat m_Deep;                                 //移入屏幕的距离
    33     GLuint m_Base;                                  //储存绘制字体的显示列表的开始位置
    34     GLfloat m_Rot;                                  //用于旋转字体
    35 };
    36  
    37 #endif // MYGLWIDGET_H
    由于我们没有准备让轮廓字体移动,所以删掉两个计数器。接着增加m_Deep来控制移入屏幕的距离,其实就是来控制字体的放大缩小的。然后再增加m_Rot来控制字体的旋转。最后增加了GLYPHMETRICSFLOAT变量数组m_Gmf[256]用来保存256个轮廓字体显示列表中对应的每一个列表的位置和方向信息,我们通过m_Gmf[num]来选择字母。要注意的是,每个字符的宽度可以不相同,使用GLYPHMETRICS会大大简化我们的工作。
    接下来,我们需要打开myglwidget.cpp,先修改构造函数,不多解析了,具体代码如下:
     1 MyGLWidget::MyGLWidget(QWidget *parent) :
     2     QGLWidget(parent)
     3 {
     4     fullscreen = false;
     5     m_Deep = -10.0f;
     6     m_Rot = 0.0f;
     7  
     8     HWND hWND = (HWND)winId();                          //获取当前窗口句柄
     9     m_HDC = GetDC(hWND);                                //通过窗口句柄获得HDC
    10  
    11     QTimer *timer = new QTimer(this);                   //创建一个定时器
    12     //将定时器的计时信号与updateGL()绑定
    13     connect(timer, SIGNAL(timeout()), this, SLOT(updateGL()));
    14     timer->start(10);                                   //以10ms为一个计时周期
    15 }

    继续,我们需要对buildFont()、killFont()、glPrint()三个函数作一定的修改,具体代码如下:

     1 void MyGLWidget::buildFont()                            //创建轮廓字体
     2 {
     3     HFONT font;                                         //字体句柄
     4     m_Base = glGenLists(256);                           //创建256个显示列表
     5     font = CreateFont(-18,                              //字体高度
     6                       0,                                //字体宽度
     7                       0,                                //字体的旋转角度
     8                       0,                                //字体底线的旋转角度
     9                       FW_BOLD,                          //字体的重量
    10                       FALSE,                            //是否斜体
    11                       FALSE,                            //是否使用下划线
    12                       FALSE,                            //是否使用删除线
    13                       ANSI_CHARSET,                     //设置字符集
    14                       OUT_TT_PRECIS,                    //输出精度
    15                       CLIP_DEFAULT_PRECIS,              //剪裁精度
    16                       ANTIALIASED_QUALITY,              //输出质量
    17                       FF_DONTCARE | DEFAULT_PITCH,      //Family and Pitch的设置
    18                       LPCWSTR("Comic Sans MS"));        //字体名称(电脑中已装的)
    19  
    20     SelectObject(m_HDC, font);                          //选择字体
    21  
    22     wglUseFontOutlines(m_HDC,                           //当前HDC
    23                        0,                               //从ASCII码第一个字符开始
    24                        255,                             //字符数
    25                        m_Base,                          //第一个显示列表的名称
    26                        0.0f,                            //字体光滑度,越小越光滑
    27                        0.2f,                            //在z方向突出的距离(字体的厚度)
    28                        WGL_FONT_POLYGONS,               //使用多边形来生成字符,每个顶点具有独立法线
    29                        m_Gmf);                          //用于储存字形度量数据(高度,宽度等)
    30 }
    1 void MyGLWidget::killFont()                             //删除显示列表
    2 {
    3     glDeleteLists(m_Base, 256);                          //删除96个显示列表
    4 }
     1 void MyGLWidget::glPrint(const char *fmt, ...)          //自定义输出文字函数
     2 {
     3     float length = 0;
     4     char text[256];                                     //保存字符串
     5     va_list ap;                                         //指向一个变量列表的指针
     6  
     7     if (fmt == NULL)                                    //如果无输入则返回
     8     {
     9         return;
    10     }
    11  
    12     va_start(ap, fmt);                                  //分析可变参数
    13         vsprintf(text, fmt, ap);                        //把参数值写入字符串
    14     va_end(ap);                                         //结束分析
    15  
    16     for (unsigned int i=0; i<strlen(text); i++)                  //计算整个字符串的长度
    17     {
    18         length += m_Gmf[(int)text[i]].gmfCellIncX;
    19     }
    20     glTranslatef(-length / 2, 0.0f, 0.0f);              //左移字符串一半的长度
    21  
    22     glPushAttrib(GL_LIST_BIT);                          //把显示列表属性压入堆栈
    23     glListBase(m_Base);                                 //设置显示列表的基础值
    24     glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);  //调用显示列表绘制字符串
    25     glPopAttrib();                                      //弹出属性堆栈
    26 }
    首先是buildFont()函数。首先创建字体的方法与上一课基本一致,只是把m_FontSize换成了-18。接着,将wglUseFontBitmaps()函数替换成wglUseFontOutlines()函数,这个函数包含了8个参数前4个参数大家自己看注释,第5个参数为光滑度系数,这个值越小,字体看起来会越光滑(其实看不出明显差别)。第6个参数简单点说指的是字体的厚度,有厚度的字体才有立体感嘛,如果这个值为0.0就变成2D字体了。第7个参数告诉OpenGL用多边形来生成字符,使每个顶点都会具有独立的法线,这样加上光源后会有不错的效果(光源效果我们的代码中没有,大家可以自己加加看)。最后一个参数告诉OpenGL把创建的显示列表的度量数据(高度、宽度等)放在数组m_Gmf[]中。
    然后是killFont()函数。它很简单,就是调用glDeleteLists()函数从m_Base开始删除256个显示列表。
    最后是glPrint()函数。我们只是在原来的基础上加了一些代码,我们首先看到我们增加了一个浮点变量length用来统计整个字符串的宽度。接着我们利用循环,在循环中,由于我们已经将每个字符的度量值储存在m_Gmf[]中,我们利用m_Gmf[text[i]].gmfCellIncX来获得每个字符的宽度,累加起来就得到字符串的总宽度。然后,我们把视图原点左移length/2的距离,这样就保证字符串总是在屏幕的中心了。最后,把glListBase(m_Base-32)换成glListBase(m_Base),这是因为我们这次是从ASCII码第一个字符开始创建的显示列表。
    然后,我们修改一下paintGL()函数,与之前的代码很神似,只是更改了旋转和平移部分的代码,最后让旋转变量增加,不过多解释了,具体代码如下:
     1 void MyGLWidget::paintGL()                              //从这里开始进行所以的绘制
     2 {
     3     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
     4     glLoadIdentity();                                   //重置当前的模型观察矩阵
     5  
     6     glTranslatef(0.0f, 0.0f, m_Deep);                   //移入屏幕10.0单位
     7     glRotatef(m_Rot, 1.0f, 0.0f, 0.0f);                 //绕x轴旋转
     8     glRotatef(m_Rot*1.5f, 0.0f, 1.0f, 0.0f);            //绕y轴旋转
     9     glRotatef(m_Rot*1.4f, 0.0f, 0.0f, 1.0f);            //绕z轴旋转
    10     //根据字体位置设置颜色
    11     glColor3f(1.0f*float(cos(m_Rot/20.0f)), 1.0f*float(sin(m_Rot/25.0f)),
    12               1.0f-0.5f*float(cos(m_Rot/17.0f)));
    13     //输出文字到屏幕上
    14     glPrint("NeHe - %3.2f", m_Rot/50.0f);
    15     m_Rot += 0.5f;                                      //旋转变量增加
    16 }

    最后,修改键盘控制的代码,就是按PageUp和PageDown可以放大缩小字体,具体代码如下:

     1 void MyGLWidget::keyPressEvent(QKeyEvent *event)
     2 {
     3     switch (event->key())
     4     {
     5     case Qt::Key_F1:                                    //F1为全屏和普通屏的切换键
     6         fullscreen = !fullscreen;
     7         if (fullscreen)
     8         {
     9             showFullScreen();
    10         }
    11         else
    12         {
    13             showNormal();
    14         }
    15         updateGL();
    16         break;
    17     case Qt::Key_Escape:                                //ESC为退出键
    18         close();
    19         break;
    20     case Qt::Key_PageUp:                                //PageUp按下字体缩小
    21         m_Deep -= 0.2f;
    22         break;
    23     case Qt::Key_PageDown:                              //PageDown按下字体放大
    24         m_Deep += 0.2f;
    25         break;
    26     }
    27 }

    现在就可以运行程序查看效果了!

  • 相关阅读:
    Python统计字符串中出现次数最多的人名
    初探CORBA组件化编程
    shell脚本—基础知识,变量
    Java多线程--线程交替
    Qt中采用多线程实现Socket编程
    Python字符串格式化--formate()的应用
    JAVA中浅复制与深复制
    Python这些问题你会吗?
    PHP控制反转(IOC)和依赖注入(DI
    Go 语言指针
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/14048314.html
Copyright © 2011-2022 走看看