qt+opengl 三维坐标系(三)

文章目录

  • 前言
  • 一、深度测和投影矩阵、观察矩阵
  • 二、绘制坐标系
  • 三、添加箭头
  • 四、添加文字
  • 五、放大缩小
  • 六、旋转
  • 七、移动
  • 八、完整代码
  • 总结


前言

效果如图


一、深度测和投影矩阵、观察矩阵

这部分不明白,网上查的都是这个步骤,用起来也没问题。

void MOpenGLWidget3D::initializeGL()
{
    initializeOpenGLFunctions();
    glClearColor(0.0, 0.2, 0.3, 1.0);
    glShadeModel(GL_SMOOTH);
    glClearDepth( 1.0 );
    glEnable( GL_DEPTH_TEST );
    glDepthFunc( GL_LEQUAL );
    //所作深度测试的类型。

    //上面这三行必须做的是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。
    //我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。这样您就不会将
    //一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分。

    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
    //真正精细的透视修正。这一行告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
}
void MOpenGLWidget3D::resizeGL(int w, int h)
{
    //防止height为0
    if (h == 0) h = 1;
    //重置当前的视口
    glViewport(0, 0,(GLint)w, (GLint)h);
    //选择投影矩阵
    glMatrixMode(GL_PROJECTION);
    //重置投影矩阵
    glLoadIdentity();
    //建立透视投影矩阵
    gluPerspective(45.0, (GLfloat)w / (GLfloat)h, 0.001, 100.0);
    //选择模型观察矩阵
    glMatrixMode(GL_MODELVIEW);
    //重置模型观察矩阵
    glLoadIdentity();

    //将屏幕设置为透视图, 越远的东西看起来越小, 创建了一个现实外观的场景。
    //此处透视按照基于窗口宽度和高度的m_isize 45度视角来计算。 0.1, 100.0是我们在场景中所能绘制深度的起点和终点
}

二、绘制坐标系

头文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

template<class T>
class glVec3{
public :
    glVec3():x(0), y(0), z(0){};
    glVec3(T x, T y, T z) : x(x), y(y), z(z){};
    T x;
    T y;
    T z;

    friend QDebug operator<<(QDebug debug, const glVec3 &gl)
        {
        return debug<<"glVec3(x, y, z)("<<gl.x<<", "<<gl.y<<", "<<gl.z<<")";
    };

protected:
};



class MOpenGLWidget3D : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{
    Q_OBJECT
public:
    explicit MOpenGLWidget3D(QWidget* parent = nullptr);
public:
    template<class T>
    void GLGrid(glVec3<T> start,  glVec3<T> end, int num);
    template<class T>
    void GLText(glVec3<T> start, QColor color, QString text);


protected:
    // 深度
    int m_scloe;
    // 三维坐标系范围
    int m_isize;
    // 三维坐标系位移参数
    glVec3<float> m_translate;
    // 鼠标按下坐标参数
    glVec3<float> m_press;
    // 窗口位移参数
    glVec3<float> m_rot1;
    glVec3<float> m_rot2;
    //
    QVector<glVec3<float>> m_vecVal;

public slots:
    void UpdateSlots();

protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();

protected:
    virtual void mousePressEvent(QMouseEvent *event);
    virtual void mouseReleaseEvent(QMouseEvent *event);
    virtual void mouseMoveEvent(QMouseEvent *event);
    virtual void wheelEvent(QWheelEvent *event);
};

初始化变量

    //坐标系Z轴方向偏移量
    m_scloe = -15;
    //轴长度
    m_isize = 8;
    //XYZ轴交汇原点偏移量,使偏移量是轴长的一半(正负代表方向),是为了使实际原点在坐标系的中心(空间)
    m_translate = glVec3<float>(-m_isize/2.0f,-m_isize/2.0f,-m_isize/2.0f);

先看结果:

注释掉//glTranslatef(m_translate.x, m_translate.y, m_translate.z);
qt+opengl 三维坐标系(三)_第1张图片
保留glTranslatef(m_translate.x, m_translate.y, m_translate.z);
qt+opengl 三维坐标系(三)_第2张图片

结合上图和下面代码说明下glTranslatef(x,y,x);,分两种情况讨论:

glTranslatef(0, 0, m_scloe); 
这里是直接将原矩阵沿Z轴移动s_scloe(向量),而在这个坐标系矩阵中,所有系统都是相对的,其实就等于没有变化(相对于整个坐标系)。
而这个整体的移动的变化是体现在整个窗体中的,体现在坐标系在窗体中显示的远近上。
glPushMatrix();
{
    glTranslatef(m_translate.x, m_translate.y, m_translate.z);
}
glPopMatrix();
与下文示例代码一致,不同点在于glPushMatrix()函数会拷贝原坐标系矩阵压入栈中,此时操作矩阵的是拷贝的矩阵,直到退出栈。
对拷贝的矩阵的操作与原矩阵是互不影响的。
就如上图所示,绘制的白色原点(0,0,0);若需要在XYZ轴交汇处绘制点,就需要
glVertex3f(m_translate.x, m_translate.y, m_translate.z);
或者
> glPushMatrix();
{
    glTranslatef(m_translate.x, m_translate.y, m_translate.z);
    glVertex3f(0,0,0);
}
或者
在描绘坐标系前就glTranslatef(m_translate.x, m_translate.y, m_translate.z);
就直接偏移了原坐标系的位置,也就不需要后面的操作都需要偏移位置
之所以使用,是为了体现这种拷贝原坐标系操作,既不影响原坐标系又能实现图像位置偏移。在示例代码中无法体现出作用,
但是,当窗口中有多个坐标系(例如绘制机器人部件),需要修改其中一个坐标系但又不影响其他坐标系,这种构造方式就很合适了。
void MOpenGLWidget3D::paintGL()
{
    //清除屏幕和深度缓存
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //重置当前的模型观察矩阵
    glLoadIdentity();

    //!**********************************************************
    //!********************    旋转显示窗口    ********************
    //!**********************************************************
    //将窗口矩阵移动一个向量(0, 0, m_scloe), m_scloe = -15;可以很好的理解为将视图向后(也就是z轴负方向)移动,使整个坐标系展现出来
    // 直接修改了原坐标系矩阵
    // 坐标系放大缩小就是修改该变量
    glTranslatef(0, 0, m_scloe); 
    //绕原点X轴旋转, 通过修改m_rot1坐标系上下偏转角度
    glRotatef(abs(m_rot1.z), m_rot1.x, 0, 0);
    //绕原点Y轴旋转, 通过修改m_rot2坐标系左右偏转角度
    glRotatef(abs(m_rot2.z), 0, m_rot2.y, 0);

    //!**********************************************************
    //!********************   绘制坐标系原点   ********************
    //!**********************************************************
    //将当前矩阵压入栈(压入栈后位移矩阵不影响原矩阵)
    glPushMatrix();
    {
        glColor3f(0.9, 0.9, 0.9);
        glPointSize(8);
        //glTranslatef(x, y, z)分别指定沿x,y,z轴方向的平移分量;在当前原点的基础上平移一个(x,y,z)向量
        //原点(0,0,0) =>  (m_translate.x, m_translate.y, m_translate.z)
        //将矩阵位移,使坐标系正好在窗口中心(实际的坐标系原点不变,屏幕中显示的坐标系原点是偏移了如下设置的偏移量)
        // glPushMatrix()拷贝了原矩阵并要入栈中,退出栈前,修改的是拷贝的矩阵,不影响原矩阵
        glTranslatef(m_translate.x, m_translate.y, m_translate.z); 
        glBegin(GL_POINTS);
        {
            glVertex3f(0, 0, 0);
        }
        glEnd();
    }
    glPopMatrix();

    //!**********************************************************
    //!********************     绘制坐标系     ********************
    //!**********************************************************
    //! X轴
    {
        // X轴栅格
        glPushMatrix();
        {
            glColor3f(1, 0, 0);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            glRotatef(90, 1, 0, 0.0);
            GLGrid(glVec3(0.0f, 0.0f, 0.0f), glVec3(8.0f, 8.0f, 0.0f), 40);
        }
        glPopMatrix();
    }
    ```
```cpp
    //! Y轴
    {
        glPushMatrix();//将当前矩阵压入栈
        {
            glColor3f(1, 1, 0);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            glRotatef(0, -1, 0, 0);
            GLGrid(glVec3(0.0f, 0.0f, 0.0f), glVec3(8.0f, 8.0f, 0.0f), 40);
        }
        glPopMatrix();
    }
    //! Z轴
    {
        glPushMatrix();//将当前矩阵压入栈
        {
            glColor3f(0, 1, 1);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            //glRotatef(90, 0, 0, 1);
            GLGrid(glVec3(0.0f, 0.0f, 0.0f), glVec3(0.0f, 8.0f, 8.0f), 40);
        }
        glPopMatrix();
    }
 }

绘制轴线的函数

//!
//! \brief MOpenGLWidget3D::GLGrid
//! \param start 起始点坐标
//! \param end   终点坐标(网格类型即起始点对角坐标)
//! \param num   数量
//!
//! 起点终点的Z值最好为0,
//!
template<class T>
void MOpenGLWidget3D::GLGrid(glVec3<T> start,  glVec3<T> end, int num)
{
    const T xSpacing = (end.x - start.x) / num;
    const T ySpacing = (end.y - start.y) / num;
    const T zSpacing = (end.z - start.z) / num;

    glLineWidth(0.1f);
    glLineStipple(1, 0x0303);//线条样式

    glBegin(GL_LINES);
    {
        glEnable(GL_LINE_SMOOTH);
        for(int i = 0; i < num; ++i)
        {
            T x = start.x + xSpacing * i;
            T y = start.y + ySpacing * i;
            T z = start.z + zSpacing * i;

            // 横线
            glVertex3f(start.x, y, z);
            glVertex3f(end.x, y, z);
            // 竖线
            glVertex3f(x, start.y, z);
            glVertex3f(x, end.y, z);
            // 垂直于 横线 与 竖线 所在平面
            glVertex3f(x, y, start.z);
            glVertex3f(x, y, end.z);
        }
    }
    glEnd();
}

三、添加箭头

//GLUquadricObj*, 底部半径, 顶部半径, 高, 圆柱体围绕z轴细分为切片, 沿 z 轴细分为堆栈
gluCylinder(objCylinder, 0.1, 0.0, 0.2, 100, 1);
绘制的 底部半径为0.1,顶部半径为0, 高为0.2的圆柱(圆锥)

qt+opengl 三维坐标系(三)_第3张图片

//创建并返回指向新 quadric 对象的指针。 调用四边形呈现和控制函数时,请参阅此对象。
//返回值为零表示没有足够的内存分配给对象。
GLUquadricObj *objCylinder = gluNewQuadric();
//X轴箭头
glPushMatrix();
{
    glColor3f(1, 0, 0);
    // 1. glTranslatef(m_translate.x, m_translate.y, m_translate.z);
    // 2. glTranslatef(m_isize, 0, 0);
    // 1: 偏移到指定点 2:偏移轴长; 合成一步
    glTranslatef(m_translate.x + m_isize, m_translate.y, m_translate.z);
    glRotatef(90, 0, 1, 0.0);
    //GLUquadricObj*, 底部半径, 顶部半径, 高, 圆柱体围绕z轴细分为切片, 沿 z 轴细分为堆栈
    gluCylinder(objCylinder, 0.1, 0.0, 0.2, 100, 1);
}
glPopMatrix();
//Y轴箭头
glPushMatrix();
{
    glColor3f(1, 1, 0);
    glTranslatef(m_translate.x, m_translate.y+m_isize, m_translate.z);
    glRotatef(90, -1, 0, 0);
    gluCylinder(objCylinder, 0.1, 0.0, 0.2, 100, 1);
}
glPopMatrix();
//Z轴箭头
glPushMatrix();
{
    glColor3f(0, 1, 1);
    glTranslatef(m_translate.x, m_translate.y, m_translate.z+m_isize);
    //glRotatef(90, 0, 0, 1);
    gluCylinder(objCylinder, 0.06, 0.0, 0.12, 100, 1);
}
glPopMatrix();

四、添加文字

绘制文字步骤:

  1. QPainterPath 得到文字路径
  2. toSubpathPolygons()将文字路径转换成多边形,以列表返回
  3. 遍历返回列表获取得到每个文字的多边形列表
  4. 遍历每个多边形列表并将这组列表绘制线条,用GL_LINE_STRIP。

qt+opengl 三维坐标系(三)_第4张图片

//X轴文字
glPushMatrix();
{
    GLText(glVec3<float>(8.0f, 0.0f, 0.0f), QColor(255, 0, 0), "X");
}
glPopMatrix();
//Y轴文字
glPushMatrix();
{
    GLText(glVec3<float>(0.0f, 8.0f, 0.0f), QColor(255, 255, 0), "Y");
}
glPopMatrix();
//Z轴文字
glPushMatrix();
{
    GLText(glVec3<float>(0.0f, 0.0f, 8.0f), QColor(0, 255, 255), "Z");
}
glPopMatrix();

绘制文字的函数

template<class T>
void MOpenGLWidget3D::GLText(glVec3<T> start, QColor color, QString text)
{
    glTranslatef(m_translate.x, m_translate.y, m_translate.z);

    //生成文字绘制路径
    QPainterPath path;
    path.addText(QPointF((start.x+start.y+start.z)/2, 0), QFont("Microsoft YaHei UI", 2), text);
    //转换成多边形列表
    QList<QPolygonF> poly = path.toSubpathPolygons();
    //
    QList<QPolygonF>::iterator iter = poly.begin();
    while(iter != poly.end())
    {
        glLineWidth(1);
        glBegin(GL_LINE_LOOP);
        {
            QPolygonF::iterator it = (*iter).begin();
            while(it != iter->end())
            {
                glVertex3f(start.x + it->rx() * 0.1, start.y - it->ry() * 0.1, start.z+0.02);
                glColor3f(color.redF(), color.greenF(), color.blueF());
                it++;
            }
        }
        glEnd();
        iter++;
    }
}

五、放大缩小

重写鼠标滚轮事件,通过滚动的角度合理的改变m_scloe的值(scloe的值决定原坐标系矩阵位置)

void MOpenGLWidget3D::wheelEvent(QWheelEvent *event)
{
    QOpenGLWidget::wheelEvent(event);

    if (event->angleDelta().y() > 0) {
        m_scloe += m_scloe < -1 ? 1 : 0;

    } else if (event->angleDelta().y() < 0) {
        m_scloe -= m_scloe > -100 ? 1 : 0;
    }
    update();
}

六、旋转

glRotatef(angle, x, y , z);
x、y、z 都为0时,绕x轴旋转angle
x不为0时绕x轴转angle,y、z同理
x、y不为0时,绕x轴旋转angle且绕y轴旋转angle,其他同理

旋转可以分解为左右旋转(绕Y轴)和上下旋转(绕X轴)
所以有:

//绕原点X轴旋转, 通过修改m_rot1坐标系上下偏转角度
glRotatef(abs(m_rot1.z), m_rot1.x, 0, 0);
//绕原点Y轴旋转, 通过修改m_rot2坐标系左右偏转角度
glRotatef(abs(m_rot2.z), 0, m_rot2.y, 0);

从下文示例代码中可以看出 m_rot1.z == m_rot1.x; m_rot2.z = m_rot1.y; 之所以让这两个值相等,是应为:
m_rot1.z 或 m_rot2.z代表的是旋转角度,且这个角度是有方向的(正负),而控制旋转的x、y、z只要不为0就可以控制旋转方向,旋转的角度直接就可以取m_rot.z的绝对值。
···
(如果控制旋转方向的x、y不与角度z用同一个值,就会出现z不为0,但下,y为0的情况,这种情况下就成了glRotatef(abs(m_rot2.z), 0, 0, 0); 默认绕X轴旋转,实际应为不旋转的)
注释:m_rot1.z和 m_rot2.z存储的旋转角度

void MOpenGLWidget3D::mousePressEvent(QMouseEvent *event)
{
    QOpenGLWidget::mousePressEvent(event);

    QPoint pos = event->pos();
    GLdouble modelview[16];
    GLdouble projection[16];
    GLint viewport[4];
    GLdouble wX, wY, wZ;

    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);

    // 将鼠标坐标转换为世界坐标
    gluUnProject(pos.x(), pos.y(), 0, modelview, projection, viewport, &wX, &wY, &wZ);
    m_press.x = wX;
    m_press.y = wY;
    m_press.z = 0;
}
void  MOpenGLWidget3D::mouseReleaseEvent(QMouseEvent *event)
{
    QOpenGLWidget::mouseReleaseEvent(event);
    m_press.x = 0;
    m_press.y = 0;
    m_press.z = 0;
}
void MOpenGLWidget3D::mouseMoveEvent(QMouseEvent *event)
{
    QOpenGLWidget::mouseMoveEvent(event);

    QPoint pos = event->pos();

    GLdouble modelview[16];
    GLdouble projection[16];
    GLint viewport[4];
    GLdouble wx, wy, wz;
    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);
    // 将鼠标坐标转换为世界坐标
    gluUnProject(pos.x(), pos.y(), 0, modelview, projection, viewport, &wx, &wy, &wz);

    if(event->buttons() & Qt::LeftButton)
    {
        //上下旋转, 绕X轴,计算的鼠标移动值(不是旋转角度)
        //只要m_rot.x不为0,就绕x旋转
        // glRotatef(abs(m_rot1.z), m_rot1.x, 0, 0);
        // m_rot1.z = m_rot1.x的作用是保持旋转的角度与实际旋转方向一致,能这么做的原因是:只要m_rot.x不为0,就绕x旋转,正负值控制方向(因此角度值z取绝对值)
        m_rot1.x += (wy-m_press.y)*100;
        m_rot1.z = m_rot1.x;
        //左右旋转, 绕Y轴(将 || 改为 && 即可让鼠标左键控制上下翻转,右键控制左右翻转)
        //同上
        m_rot2.y += (wx-m_press.x)*100;
        m_rot2.z = m_rot2.y;
    }

    //if(event->buttons() & Qt::RightButton)
    //{
    //    m_translate.x += (wx-m_press.x)*10.0;
    //    m_translate.y += -(wy-m_press.y)*10.0;
    //}

    m_press.x = wx;
    m_press.y = wy;
    m_press.z = 0;

    update();
}

七、移动

修改控制偏移的变量

void MOpenGLWidget3D::mouseMoveEvent(QMouseEvent *event)
{
    QOpenGLWidget::mouseMoveEvent(event);

    QPoint pos = event->pos();

    GLdouble modelview[16];
    GLdouble projection[16];
    GLint viewport[4];
    GLdouble wx, wy, wz;
    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);
    // 将鼠标坐标转换为世界坐标
    gluUnProject(pos.x(), pos.y(), 0, modelview, projection, viewport, &wx, &wy, &wz);

    if(event->buttons() & Qt::RightButton)
    {
        m_translate.x += (wx-m_press.x)*10.0;
        m_translate.y += -(wy-m_press.y)*10.0;
    }
    
    m_press.x = wx;
    m_press.y = wy;
    m_press.z = 0;

    update();
}

八、完整代码

qt+opengl 三维坐标系(三)_第5张图片

#ifndef MOPENGLWIDGET3D_H
#define MOPENGLWIDGET3D_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

template<class T>
class glVec3{
public :
    glVec3():x(0), y(0), z(0){};
    glVec3(T x, T y, T z) : x(x), y(y), z(z){};
    T x;
    T y;
    T z;

    friend QDebug operator<<(QDebug debug, const glVec3 &gl)
        {
        return debug<<"glVec3(x, y, z)("<<gl.x<<", "<<gl.y<<", "<<gl.z<<")";
    };

protected:
};



class MOpenGLWidget3D : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{
    Q_OBJECT
public:
    explicit MOpenGLWidget3D(QWidget* parent = nullptr);
public:
    template<class T>
    void GLGrid(glVec3<T> start,  glVec3<T> end, int num);
    template<class T>
    void GLText(glVec3<T> start, QColor color, QString text);


protected:
    // 深度
    int m_scloe;
    // 三维坐标系范围
    int m_isize;
    // 三维坐标系位移参数
    glVec3<float> m_translate;
    // 鼠标按下坐标参数
    glVec3<float> m_press;
    // 窗口位移参数
    glVec3<float> m_rot1;
    glVec3<float> m_rot2;
    //
    QVector<glVec3<float>> m_vecVal;

public slots:
    void UpdateSlots();

protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();

protected:
    virtual void mousePressEvent(QMouseEvent *event);
    virtual void mouseReleaseEvent(QMouseEvent *event);
    virtual void mouseMoveEvent(QMouseEvent *event);
    virtual void wheelEvent(QWheelEvent *event);
};

#endif // MOPENGLWIDGET3D_H
#include "mopenglwidget3d.h"
#include 
#define PI 3.1415926

MOpenGLWidget3D::MOpenGLWidget3D(QWidget *parent)
    : QOpenGLWidget{parent}
{
    //坐标系Z轴方向偏移量
    m_scloe = -15;
    //轴长度
    m_isize = 8;
    //XYZ轴交汇原点偏移量,使偏移量是轴长的一半(正负代表方向),是为了使实际原点在坐标系的中心(空间)
    m_translate = glVec3<float>(-m_isize/2.0f,-m_isize/2.0f,-m_isize/2.0f);

    QTimer* timer = new QTimer(this);
    timer->setInterval(10);
    connect(timer, SIGNAL(timeout()), this, SLOT(UpdateSlots()));
    timer->start();

    //设置多采样的值
    QSurfaceFormat fmt = format();
    fmt.setSamples(18);
    setFormat(fmt);
}

void MOpenGLWidget3D::UpdateSlots()
{
    static float valx = 0;
    glVec3<float> m_value(valx*0.1, sin(valx)*0.1, cos(valx)*0.1);
    valx+= 0.05f;
    m_vecVal.push_back(m_value);

    //if(m_vecVal.size()>1000) m_vecVal.pop_front();

    update();
}
void MOpenGLWidget3D::initializeGL()
{
    initializeOpenGLFunctions();
    glClearColor(0.0, 0.2, 0.3, 1.0);


    glShadeModel(GL_SMOOTH);
    glClearDepth( 1.0 );
    glEnable( GL_DEPTH_TEST );
    glDepthFunc( GL_LEQUAL );
    //所作深度测试的类型。

    //上面这三行必须做的是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。
    //我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。这样您就不会将
    //一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分。

    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
    //真正精细的透视修正。这一行告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
}

void MOpenGLWidget3D::resizeGL(int w, int h)
{
    //防止height为0
    if (h == 0) { h = 1; }
    //重置当前的视口
    glViewport(0, 0,(GLint)w, (GLint)h);
    //选择投影矩阵
    glMatrixMode(GL_PROJECTION);
    //重置投影矩阵
    glLoadIdentity();
    //建立透视投影矩阵
    gluPerspective(45.0, (GLfloat)w / (GLfloat)h, 0.001, 100.0);
    //选择模型观察矩阵
    glMatrixMode(GL_MODELVIEW);
    //重置模型观察矩阵
    glLoadIdentity();

    //将屏幕设置为透视图, 越远的东西看起来越小, 创建了一个现实外观的场景。
    //此处透视按照基于窗口宽度和高度的m_isize 45度视角来计算。 0.1, 100.0是我们在场景中所能绘制深度的起点和终点
}

//glRotatef(angle, x, y, z);
//默认逆时针旋转angle
// - 表示逆时针, + 表示顺时针
//xyz都为0时默认以x轴旋转
//xyz不为0的表示以不为0的轴旋转角度(转动方向由angle和x\ angle和y \ angle和z 的正负共同决定)
void MOpenGLWidget3D::paintGL()
{
    //清除屏幕和深度缓存
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //重置当前的模型观察矩阵
    glLoadIdentity();

    //!**********************************************************
    //!********************    旋转显示窗口    ********************
    //!**********************************************************
    //将窗口矩阵移动一个向量(0, 0, m_scloe), m_scloe = -15;可以很好的理解为将视图向后(也就是z轴负方向)移动,使整个坐标系展现出来
    // 直接修改了原坐标系矩阵
    // 坐标系放大缩小就是修改该变量
    glTranslatef(0, 0, m_scloe);
    //绕原点X轴旋转, 通过修改m_rot1坐标系上下偏转角度
    glRotatef(abs(m_rot1.z), m_rot1.x, 0, 0);
    //绕原点Y轴旋转, 通过修改m_rot2坐标系左右偏转角度
    glRotatef(abs(m_rot2.z), 0, m_rot2.y, 0);


    //!**********************************************************
    //!********************   绘制坐标系原点   ********************
    //!**********************************************************
    //将当前矩阵压入栈(压入栈后位移矩阵不影响原矩阵)
    glPushMatrix();
    {
        glColor3f(0.9, 0.9, 0.9);
        glPointSize(8);
        //glTranslatef(x, y, z)分别指定沿x,y,z轴方向的平移分量;在当前原点的基础上平移一个(x,y,z)向量
        // 将矩阵位移,使坐标系正好在窗口中心(实际的坐标系原点不变,屏幕中显示的坐标系原点是偏移了如下设置的偏移量)
        // glPushMatrix()拷贝了原矩阵并要入栈中,退出栈前,修改的是拷贝的矩阵,不影响原矩阵
        glTranslatef(m_translate.x, m_translate.y, m_translate.z);
        glBegin(GL_POINTS);
        {
            glVertex3f(0, 0, 0);
        }
        glEnd();
    }
    glPopMatrix();


    //!**********************************************************
    //!********************     绘制坐标系     ********************
    //!**********************************************************

    //创建并返回指向新 quadric 对象的指针。 调用四边形呈现和控制函数时,请参阅此对象。 返回值为零表示没有足够的内存分配给对象。
    GLUquadricObj *objCylinder = gluNewQuadric();

    //! X轴
    {
        // X轴栅格
        glPushMatrix();
        {
            glColor3f(1, 0, 0);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            glRotatef(90, 1, 0, 0.0);
            GLGrid(glVec3(0.0f, 0.0f, 0.0f), glVec3(8.0f, 8.0f, 0.0f), 40);
        }
        glPopMatrix();

        //X轴箭头
        glPushMatrix();
        {
            glColor3f(1, 0, 0);
            // 1. glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            // 2. glTranslatef(m_isize, 0, 0);
            // 1: 偏移到指定点 2:偏移轴长; 合成一步
            glTranslatef(m_translate.x + m_isize, m_translate.y, m_translate.z);
            glRotatef(90, 0, 1, 0.0);
            //GLUquadricObj*, 底部半径, 顶部半径, 高, 圆柱体围绕z轴细分为切片, 沿 z 轴细分为堆栈
            gluCylinder(objCylinder, 0.1, 0.0, 0.2, 100, 1);
        }
        glPopMatrix();

        //X轴文字
        glPushMatrix();
        {
            GLText(glVec3<float>(8.0f, 0.0f, 0.0f), QColor(255, 0, 0), "X");
        }
        glPopMatrix();
    }
    //! Y轴
    {
        glPushMatrix();//将当前矩阵压入栈
        {
            glColor3f(1, 1, 0);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            glRotatef(0, -1, 0, 0);
            GLGrid(glVec3(0.0f, 0.0f, 0.0f), glVec3(8.0f, 8.0f, 0.0f), 40);
        }
        glPopMatrix();

        glPushMatrix();
        {
            glColor3f(1, 1, 0);
            glTranslatef(m_translate.x, m_translate.y+m_isize, m_translate.z);
            glRotatef(90, -1, 0, 0);
            gluCylinder(objCylinder, 0.1, 0.0, 0.2, 100, 1);
        }
        glPopMatrix();

        glPushMatrix();
        {
            GLText(glVec3<float>(0.0f, 8.0f, 0.0f), QColor(255, 255, 0), "Y");
        }
        glPopMatrix();
    }

    //! Z轴
    {
        glPushMatrix();//将当前矩阵压入栈
        {
            glColor3f(0, 1, 1);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            //glRotatef(90, 0, 0, 1);
            GLGrid(glVec3(0.0f, 0.0f, 0.0f), glVec3(0.0f, 8.0f, 8.0f), 40);
        }
        glPopMatrix();

        glPushMatrix();
        {
            glColor3f(0, 1, 1);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z+m_isize);
            //glRotatef(90, 0, 0, 1);
            gluCylinder(objCylinder, 0.06, 0.0, 0.12, 100, 1);
        }
        glPopMatrix();

        glPushMatrix();
        {
            GLText(glVec3<float>(0.0f, 0.0f, 8.0f), QColor(0, 255, 255), "Z");
        }
        glPopMatrix();
    }

    //!**********************************************************
    //!********************    添加动态数据    ********************
    //!**********************************************************

    glPushMatrix();
    {
        glTranslatef(m_translate.x, m_translate.y, m_translate.z);
        glTranslatef(m_isize/2, m_isize/2, m_isize/2);

        glLineWidth(0.2);
        glColor3f(1, 0, 0);
        //开始绘制连续的线
        glBegin(GL_LINE_STRIP);
        {
            for (int i = 0; i < m_vecVal.size(); i++) {
                glVertex3f(m_vecVal[i].x, m_vecVal[i].y, m_vecVal[i].z);
                glFlush(); //强制刷新
            }
        }
        glEnd();
    }
    glPopMatrix();

    glPushMatrix();
    {
        glTranslatef(m_translate.x, m_translate.y, m_translate.z);
        glTranslatef(m_isize/2, m_isize/2, m_isize/2);
        glLineWidth(0.2);
        glColor3f(1, 1, 0);
        glBegin(GL_LINE_STRIP);
        {
            for (int i = 0; i < m_vecVal.size(); i++) {
                glVertex3f(m_vecVal[i].z, m_vecVal[i].x, m_vecVal[i].y);
            }
        }
        glEnd();
        glFlush();
    }
    glPopMatrix();

    glPushMatrix();
    {
        glTranslatef(m_translate.x, m_translate.y, m_translate.z);
        glTranslatef(m_isize/2, m_isize/2, m_isize/2);
        glLineWidth(0.2);
        glColor3f(0, 1, 1);
        glBegin(GL_LINE_STRIP);
        {
            for (int i = 0; i < m_vecVal.size(); i++) {
                glVertex3f(m_vecVal[i].y, m_vecVal[i].z, m_vecVal[i].x);
            }
        }
        glEnd();
        glFlush();
    }
    glPopMatrix();
}

//!
//! \brief MOpenGLWidget3D::GLGrid
//! \param start 起始点坐标
//! \param end   终点坐标(网格类型即起始点对角坐标)
//! \param num   数量
//!
//! 起点终点的Z值最好为0,
//!
template<class T>
void MOpenGLWidget3D::GLGrid(glVec3<T> start,  glVec3<T> end, int num)
{
    const T xSpacing = (end.x - start.x) / num;
    const T ySpacing = (end.y - start.y) / num;
    const T zSpacing = (end.z - start.z) / num;

    glLineWidth(0.1f);
    glLineStipple(1, 0x0303);//线条样式

    glBegin(GL_LINES);
    {
        glEnable(GL_LINE_SMOOTH);
        for(int i = 0; i < num; ++i)
        {
            T x = start.x + xSpacing * i;
            T y = start.y + ySpacing * i;
            T z = start.z + zSpacing * i;

            // 横线
            glVertex3f(start.x, y, z);
            glVertex3f(end.x, y, z);
            // 竖线
            glVertex3f(x, start.y, z);
            glVertex3f(x, end.y, z);
            // 垂直于 横线 与 竖线 所在平面
            glVertex3f(x, y, start.z);
            glVertex3f(x, y, end.z);
        }
    }
    glEnd();
}


template<class T>
void MOpenGLWidget3D::GLText(glVec3<T> start, QColor color, QString text)
{
    glTranslatef(m_translate.x, m_translate.y, m_translate.z);

    //生成文字绘制路径
    QPainterPath path;
    path.addText(QPointF((start.x+start.y+start.z)/2, 0), QFont("Microsoft YaHei UI", 2), text);
    //转换成多边形列表
    QList<QPolygonF> poly = path.toSubpathPolygons();
    //
    QList<QPolygonF>::iterator iter = poly.begin();
    while(iter != poly.end())
    {
        glLineWidth(1);
        glBegin(GL_LINE_LOOP);
        {
            QPolygonF::iterator it = (*iter).begin();
            while(it != iter->end())
            {
                glVertex3f(start.x + it->rx() * 0.1, start.y - it->ry() * 0.1, start.z+0.02);
                glColor3f(color.redF(), color.greenF(), color.blueF());
                it++;
            }
        }
        glEnd();
        iter++;
    }
}



void MOpenGLWidget3D::wheelEvent(QWheelEvent *event)
{
    QOpenGLWidget::wheelEvent(event);

    if (event->angleDelta().y() > 0) {
        m_scloe += m_scloe < -1 ? 1 : 0;

    } else if (event->angleDelta().y() < 0) {
        m_scloe -= m_scloe > -100 ? 1 : 0;
    }

    update();
}

void MOpenGLWidget3D::mousePressEvent(QMouseEvent *event)
{
    QOpenGLWidget::mousePressEvent(event);

    QPoint pos = event->pos();
    GLdouble modelview[16];
    GLdouble projection[16];
    GLint viewport[4];
    GLdouble wX, wY, wZ;

    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);

    // 将鼠标坐标转换为世界坐标
    gluUnProject(pos.x(), pos.y(), 0, modelview, projection, viewport, &wX, &wY, &wZ);
    m_press.x = wX;
    m_press.y = wY;
    m_press.z = 0;
}

void  MOpenGLWidget3D::mouseReleaseEvent(QMouseEvent *event)
{
    QOpenGLWidget::mouseReleaseEvent(event);
    m_press.x = 0;
    m_press.y = 0;
    m_press.z = 0;
}

void MOpenGLWidget3D::mouseMoveEvent(QMouseEvent *event)
{
    QOpenGLWidget::mouseMoveEvent(event);

    QPoint pos = event->pos();

    GLdouble modelview[16];
    GLdouble projection[16];
    GLint viewport[4];
    GLdouble wx, wy, wz;
    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);
    // 将鼠标坐标转换为世界坐标
    gluUnProject(pos.x(), pos.y(), 0, modelview, projection, viewport, &wx, &wy, &wz);

    if(event->buttons() & Qt::LeftButton)
    {
        //上下旋转, 绕X轴,计算的鼠标移动值(不是旋转角度)
        //只要m_rot.x不为0,就绕x旋转
        // glRotatef(abs(m_rot1.z), m_rot1.x, 0, 0);
        // m_rot1.z = m_rot1.x的作用是保持旋转的角度与实际旋转方向一致,能这么做的原因是:只要m_rot.x不为0,就绕x旋转,正负值控制方向(因此角度值z取绝对值)
        m_rot1.x += (wy-m_press.y)*100;
        m_rot1.z = m_rot1.x;
        //左右旋转, 绕Y轴(将 || 改为 && 即可让鼠标左键控制上下翻转,右键控制左右翻转)
        //同上
        m_rot2.y += (wx-m_press.x)*100;
        m_rot2.z = m_rot2.y;
    }

    if(event->buttons() & Qt::RightButton)
    {
        m_translate.x += (wx-m_press.x)*10.0;
        m_translate.y += -(wy-m_press.y)*10.0;
    }

    m_press.x = wx;
    m_press.y = wy;
    m_press.z = 0;

    update();
}

总结

我也不知道讲的有没有错,反正运行效果如图,有不对的地方感谢指正啊。

你可能感兴趣的:(opengl,qt)