参考博客
https://blog.csdn.net/qq_35629971/article/details/126203543?spm=1001.2014.3001.5506
效果图
新建opengl工程
新建一个qt的空白工程,附带UI界面,我的工程名称就叫my_3d
UI界面可以可以放一些自己想要的按键、文本或者其他控件。这个不影响3D效果的展示,这些控件都会展示在3D效果图的上层,不会被3D效果覆盖
首先我们新添加一个类:
这样就会添加好了我刚开始界面上显示的myopenglwidget.cpp和myopenglwidget.h,
我们对头文件的类进行修改,继承其他类:
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QOpenGLWidget>
class QOpenGLShaderProgram;
class QOpenGLTexture;
/* 同时继承自QOpenGLWidget类和QOpenGLFunctions类 */
class MyOpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions {
Q_OBJECT
public:
explicit MyOpenGLWidget(QWidget* parent = 0);
protected:
void initializeGL();//初始化函数
void paintGL();//画图函数,每次刷新页面都会调用
void resizeGL(int width, int height);//页面大小设置函数
public:
GLfloat pitch, yaw, roll;//航向角、翻滚角和俯仰角
private:
QOpenGLShaderProgram* program; //声明了一个QOpenGLShaderProgram对象指针,作为着色器程序。
QOpenGLBuffer cache;//缓存器
QOpenGLTexture* texture[6];//纹理地址
};
#endif // MYOPENGLWIDGET_H
暂且不对上述代码内的变量做解释,在后面会在cpp文件内调用说明
initializeGL()函数
void MyOpenGLWidget::initializeGL()
{
// 绑定QOpenGLFunctions函数
initializeOpenGLFunctions();
// 增加图像深度
glEnable(GL_DEPTH_TEST);
// 创建顶点着色器
QOpenGLShader* peak_shader = new QOpenGLShader(QOpenGLShader::Vertex, this);
const char* peak_shader_code = " \n"
"#version 330 \n"
"in vec4 peak; \n"
"in vec4 intexture; \n"
"out vec4 outtexture; \n"
"uniform mat4 matrix; \n"
"void main() { \n"
" outtexture = intexture; \n"
" gl_Position = matrix * peak; \n"
"} \n";
peak_shader->compileSourceCode(peak_shader_code);
// 创建片段着色器
QOpenGLShader* texture_shader = new QOpenGLShader(QOpenGLShader::Fragment, this);
//设置源码并编译 //参数:R G B 透明度(1.0为不透明)
const char* texture_shader_code = "#version 330 \n"
"uniform sampler2D texture; \n"
"in vec4 outtexture; \n"
"out vec4 real_texture; \n"
"void main() { \n"
" real_texture = texture2D(texture, outtexture.st); \n"
"} \n";
texture_shader->compileSourceCode(texture_shader_code); //为着色器设置源码并编译
// 创建着色器程序
program = new QOpenGLShaderProgram;
program->addShader(peak_shader);
program->addShader(texture_shader);
program->link(); //将所有加人到程序中的着色器链接到一起,最后
program->bind(); //将该着色器程序绑定到当前OpenGL环境中。
//for (int i = 0; i < 6; i++)
//textures[i] = new QOpenGLTexture(QImage(QString(":/new/side/IMU图/side%1.png").arg(i + 1)).mirrored());
texture[0] = new QOpenGLTexture(QImage(QString(":/new/side/IMU图/side1.png")));
texture[1] = new QOpenGLTexture(QImage(QString(":/new/side/IMU图/side2.png")));
texture[2] = new QOpenGLTexture(QImage(QString(":/new/side/IMU图/side3.png")));
texture[3] = new QOpenGLTexture(QImage(QString(":/new/side/IMU图/side4.png")));
texture[4] = new QOpenGLTexture(QImage(QString(":/new/side/IMU图/side5.png")));
texture[5] = new QOpenGLTexture(QImage(QString(":/new/side/IMU图/side6.png")));
}
side1-side6这6张图是我对着陀螺仪拍的他六个面的图片,用来贴到后面我们画的长方体的六个面上,这样我们看到的就是一个3D的陀螺仪,这六张图我加入了我的工程的资源文件里,而不是用的绝对/相对文件路径的形式,这样这个软件即使在其他机器上使用也可以正常显示3D图。
如果是多面体,都想要贴纹理,那么可以用上述for循环的方法批量处理纹理
resizeGL(int,int)函数
这个其实是设置窗口大小的,但是我没有用到,这里我也会提到两个方式去显示3D图。
一个是主窗体显示,一个是小窗体显示,因为OpenGL默认的原点在窗体的中心点,所以如果放在主窗体的话,他就会占据窗体中心,导致我很多控件什么的都没法有比较好的布局显示,所以我用的是嵌入式显示,就是增加一个窗体专门用于显示这个3D图,看下两个的区别:
主窗体显示:
小窗体嵌入显示:
如果是在主窗体显示,那么就注释掉原来主窗体的显示:
#include "my_3d.h"
#include "myopenglwidget.h"
#include <QApplication>
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
// my_3d w;
// w.show();
MyOpenGLWidget w;
w.show();
return a.exec();
}
如果想要嵌入式小窗体显示那就在UI界面创建一个widget的控件,将这个控件提升为类myopenglwidget:
之后3D的显示就会显示在这个新的widget窗体中,而不会显示在主窗体,已达到嵌入显示的效果
paintGL()函数
#define LENGTH 1.2f
#define HALF_LENGTH LENGTH / 2
#define WIDTH 0.9f
#define HALF_WIDTH WIDTH / 2
#define HEIGHT 0.2f
#define HALF_HEIGHT HEIGHT / 2
#define X 1.0
void MyOpenGLWidget::paintGL()
{
// 顶点位置 两个面 每个面四个顶点 每个顶点三个坐标值
GLfloat peak_coordinate[6][4][4] = {
{ { -HALF_LENGTH, HALF_HEIGHT, HALF_WIDTH, X }, { -HALF_LENGTH, -HALF_HEIGHT, HALF_WIDTH, X }, { HALF_LENGTH, -HALF_HEIGHT, HALF_WIDTH, X }, { HALF_LENGTH, HALF_HEIGHT, HALF_WIDTH, X } },
{ { HALF_LENGTH, HALF_HEIGHT, HALF_WIDTH, X }, { HALF_LENGTH, -HALF_HEIGHT, HALF_WIDTH, X }, { HALF_LENGTH, -HALF_HEIGHT, -HALF_WIDTH, X }, { HALF_LENGTH, HALF_HEIGHT, -HALF_WIDTH, X } },
{ { HALF_LENGTH, HALF_HEIGHT, -HALF_WIDTH, X }, { HALF_LENGTH, -HALF_HEIGHT, -HALF_WIDTH, X }, { -HALF_LENGTH, -HALF_HEIGHT, -HALF_WIDTH, X }, { -HALF_LENGTH, HALF_HEIGHT, -HALF_WIDTH, X } },
{ { -HALF_LENGTH, HALF_HEIGHT, -HALF_WIDTH, X }, { -HALF_LENGTH, -HALF_HEIGHT, -HALF_WIDTH, X }, { -HALF_LENGTH, -HALF_HEIGHT, HALF_WIDTH, X }, { -HALF_LENGTH, HALF_HEIGHT, HALF_WIDTH, X } },
{ { -HALF_LENGTH, HALF_HEIGHT, -HALF_WIDTH, X }, { -HALF_LENGTH, HALF_HEIGHT, HALF_WIDTH, X }, { HALF_LENGTH, HALF_HEIGHT, HALF_WIDTH, X }, { HALF_LENGTH, HALF_HEIGHT, -HALF_WIDTH, X } },
{ { -HALF_LENGTH, -HALF_HEIGHT, -HALF_WIDTH, X }, { -HALF_LENGTH, -HALF_HEIGHT, HALF_WIDTH, X }, { HALF_LENGTH, -HALF_HEIGHT, HALF_WIDTH, X }, { HALF_LENGTH, -HALF_HEIGHT, -HALF_WIDTH, X } },
};
//顶点颜色
GLfloat texture_coordinate[6][4][2] = {
{ { 0.0f, 1.0f }, { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f } },
{ { 0.0f, 1.0f }, { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f } },
{ { 0.0f, 1.0f }, { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f } },
{ { 0.0f, 1.0f }, { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f } },
{ { 0.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f } },
{ { 0.0f, 1.0f }, { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f } }
};
cache.create(); //在OpenGL服务器中创建缓存对象
cache.bind(); //将与该对象相关联的缓存绑定到当前OpenGL环境
cache.allocate(peak_coordinate, 144 * sizeof(GLfloat)); //在缓存中为数组分配空间并将缓存初始化为数组的内容。
//这里以原点为中心设置了一个正方形的4个顶点,首先是左上角的顶点,然后沿逆时针方向设置了其他3个顶点,顶点顺序可以是顺时针也可以是逆时针,逆时针绘制出来的是正面,而顺时针绘制出来的是反面。
GLuint peak = program->attributeLocation("peak");
program->setAttributeBuffer(peak, GL_FLOAT, 0, 4, 0);
glEnableVertexAttribArray(peak);
cache.write(96 * sizeof(GLfloat), texture_coordinate, 48 * sizeof(GLfloat));
GLuint intexture = program->attributeLocation("intexture");
program->setAttributeBuffer(intexture, GL_FLOAT, 96 * sizeof(GLfloat), 2, 0);
glEnableVertexAttribArray(intexture);
program->setUniformValue("tex", 0);
QMatrix4x4 matrix;
matrix.perspective(40.0f, 1, 0.01f, 5.1f);
matrix.translate(-0, -0, -3);
matrix.rotate(pitch, 1.0, 0.0, 0.0);
matrix.rotate(yaw, 0.0, 1.0, 0.0);
matrix.rotate(roll, 0.0, 0.0, 1.0);
program->setUniformValue("matrix", matrix);
// 绘制
for (int i = 0; i < 6; i++) {
texture[i]->bind();
glDrawArrays(GL_TRIANGLE_FAN, i * 4, 4);
}
}
GLfloat peak_coordinate[6][4][4]
解释下这个GLfloat peak_coordinate[6][4][4]数组,这个是用来保存顶点的,那么我们知道一个正方体有6个面,每个面有四个角,这四个角就是四个顶点,确定一个面的四个顶点,就可以确定这个面的面积和位置,我们知道3D空间是三维的,所以代码里的第四个参数X可以不需要,这个不在目标范围内,不是必须的,这个可以当成是缩放比例来用,不设置他的时候他默认是1.0,我是之前想做测试,所以加了这个参数,如果不关心这个参数只需要GLfloat peak_coordinate[6][4][3]就行;
解释下3D坐标系的划分和方向:
Z轴为正对屏幕中心方向远离屏幕是正,接近屏幕是负;
GLfloat texture_coordinate[6][4][2]
解释下GLfloat texture_coordinate[6][4][2]这个变量:
这个是刚才我们拍的6个面的图片,我们要将这个图片贴到6个面上,这个变量里存储的就是图片的坐标,这个坐标与上述坐标不是一个坐标系,我们正对图片,图片左下角坐标为(0,0),左上角为(0,1),右上角为(1,1),右下角为(1,0),这四个坐标与每个面的四个顶点不同的组合可以显示不同的贴图效果。
cache变量
解释下这个变量的作用,这个变量是一个缓存对象,用来存储上面两个变量的坐标数据,如果没这个缓存的话,每次刷新界面重新绘图时都会重新从这个客户内存的去复制这些数据,而有了缓存之后就会把这些数据放到图形内存,而不需要每次都去复制客户内存的数据。
cache.allocate(peak_coordinate, 144 * sizeof(GLfloat)); /
解释下这个144的由来,虽然初始化缓存的对象是peak_coordinate,但是我们后面还要写问题图片的坐标,所以我们还要考虑纹理坐标数组的大小,所以这个144是顶点坐标数量96+纹理坐标数量48
cache.write(96 * sizeof(GLfloat), texture_coordinate, 48 * sizeof(GLfloat));
这里就对应了上述的顶点坐标数量和纹理坐标数量第一个参数是96 * sizeof(GLfloat),这个是偏移量,就是之前我们初始化的时候已经吧peak_coordinate作为对象存入进去了,为了不覆盖顶点坐标的数据,所以在顶点坐标存储位置的后面存入纹理坐标数据;
QMatrix4x4 matrix;
matrix.perspective(40.0f, 1, 0.01f, 5.1f);
matrix.translate(-0, -0, -3);
matrix.rotate(pitch, 1.0, 0.0, 0.0);
matrix.rotate(yaw, 0.0, 1.0, 0.0);
matrix.rotate(roll, 0.0, 0.0, 1.0);
program->setUniformValue("matrix", matrix);
perspective为透视投影设置,第一个参数40.0f也可以认为是缩放参数,数值越大,3D图像越小,第二个参数是窗口纵横比,第三个参数和第四个参数可以认为是远近比例,官方历程里设置的是0.1和100.0,我是针对我的图像做了调整,大家可以调整这些参数看看有什么区别。
translate是坐标平移,之前说过3D图像的坐标原点是屏幕中心,我这里是把Z轴往屏幕里面缩了三个单位,这个应该是和前面perspective的第四个参数有关联,translate函数的Z轴参数如果大于perspective第四个参数3D图就不可见了,也就是往里缩出了屏幕之外。
rotate就是旋转了,我是分别对三个轴进行了旋转设置,我是创建了这三个轴的变量,之后把这三个轴的变量和陀螺仪的数据绑定后就可以实时显示陀螺仪姿态了
动态显示
因为陀螺仪部分代码不可披露,所以我用按键来控制图像的三个轴的变化,效果见文章开头:
头文件,如果你创建工程的时候没有重命名,那么这个就是mainwindow.h,我是重命名了工程名称,所以生成的是my_3d.h:
#ifndef MY_3D_H
#define MY_3D_H
#include "myopenglwidget.h"
#include <QMainWindow>
namespace Ui {
class my_3d;
}
class my_3d : public QMainWindow {
Q_OBJECT
public:
explicit my_3d(QWidget* parent = 0);
~my_3d();
void keyPressEvent(QKeyEvent* event);//按键函数
MyOpenGLWidget* w;//指针
private:
Ui::my_3d* ui;
};
#endif // MY_3D_H
.cpp文件:
#include "my_3d.h"
#include "QKeyEvent"
#include "ui_my_3d.h"
my_3d::my_3d(QWidget* parent)
: QMainWindow(parent)
, ui(new Ui::my_3d)
{
ui->setupUi(this);
w = ui->widget_2;
w->show();
}
my_3d::~my_3d()
{
delete ui;
}
void my_3d::keyPressEvent(QKeyEvent* event)
{
switch (event->key()) {
case Qt::Key_Up:
w->pitch += 10;
break;
case Qt::Key_Left:
w->yaw += 10;
break;
case Qt::Key_Right:
w->roll += 10;
break;
default:
break;
}
w->update();
}
这样就可以通过键盘右下角的上左右三个方向键控制3D图像的三轴转动
想显示陀螺仪的实时姿态可以加上串口数据的解析,将陀螺仪三个轴的角度数据传给这三个变量,就可以实时显示陀螺仪姿态;
作者仅仅会用,理解不深,也尝试加摄像机搞个完美视角,但是失败了,如果文章有误,请帮忙指出,谢谢。
源码下载:https://download.csdn.net/download/SXD_SJJ/89935303