文章目录
- 前言
- 一、深度测和投影矩阵、观察矩阵
- 二、绘制坐标系
- 三、添加箭头
- 四、添加文字
- 五、放大缩小
- 六、旋转
- 七、移动
- 八、完整代码
- 总结
前言
效果如图
一、深度测和投影矩阵、观察矩阵
这部分不明白,网上查的都是这个步骤,用起来也没问题。
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 <QObject>
#include <QWidget>
#include <GL/glu.h>
#include <QGL>
#include <QtOpenGL>
#include <QGLWidget>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
#include <QGridLayout>
#include <QDebug>
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);
保留glTranslatef(m_translate.x, m_translate.y, m_translate.z);
结合上图和下面代码说明下
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的圆柱(圆锥)
//创建并返回指向新 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();
四、添加文字
绘制文字步骤:
- QPainterPath 得到文字路径
- toSubpathPolygons()将文字路径转换成多边形,以列表返回
- 遍历返回列表获取得到每个文字的多边形列表
- 遍历每个多边形列表并将这组列表绘制线条,用GL_LINE_STRIP。
//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();
}
八、完整代码
#ifndef MOPENGLWIDGET3D_H
#define MOPENGLWIDGET3D_H
#include <QObject>
#include <QWidget>
#include <GL/glu.h>
#include <QGL>
#include <QtOpenGL>
#include <QGLWidget>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
#include <QGridLayout>
#include <QDebug>
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 <QDebug>
#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();
}
总结
我也不知道讲的有没有错,反正运行效果如图,有不对的地方感谢指正啊。