摘要:本文主要是利用 QOpenGLWidget
实现的3D魔方效果,可实现自动宣传及鼠标拖动,可根据自己喜好贴不同的背景图及编写内容。代码实现了一个带有旋转立方体的 OpenGL
小部件,立方体的六个面上分别绘制了一些文字。由于本人能力有限,难免有疏漏之处。
文中源码文件【获取方式】:关注公众号:利哥AI实例探险,
给公众号发送 “qt3D魔方” 获取下载方式,关注发送关键字即可获取下载方式,免费,无套路,关注即可!
系统演示
基本介绍
- 初始化时,代码加载立方体几何形状和纹理,准备好 OpenGL 的基本配置和着色器。
- 鼠标移动控制立方体的旋转,通过捕获鼠标事件计算旋转轴和速度。
- 使用定时器逐步降低旋转速度,模拟摩擦力的效果。
- 在每一帧中,代码重新计算并设置投影矩阵,绑定纹理,并最终绘制立方体。
- 立方体表面的纹理图像由 InitCube() 动态生成,使用 QPainter 绘制带有随机颜色和字体的文字。
通过这些技术和逻辑,代码实现了一个可以根据鼠标输入旋转的立方体,每一面都显示了不同的图像和文字。
- 使用 initializeGL() 方法初始化 OpenGL 上下文,包括设置深度测试和背面剔除。
- 使用 paintGL() 方法进行绘制,负责绘制立方体的每一帧画面。
- 通过重载 mousePressEvent 和 mouseMoveEvent 方法,代码捕获并处理鼠标按下和移动事件,以计算并更新立方体的旋转角度。
- 计算鼠标移动的方向和距离,将其转化为旋转轴和旋转速度。
- 使用 timerEvent() 方法每隔一定时间触发一次,逐步减少旋转速度,实现类似摩擦力的效果,从而使立方体旋转逐渐停止。
- 使用 initShaders() 方法加载和编译顶点着色器和片段着色器,构建并绑定着色器程序。这一步为后续在 OpenGL 管线中使用着色器进行绘制做了准备。
- 使用 initTextures() 方法加载纹理,将其绑定到立方体的各个面。设置纹理的缩小和放大过滤模式,以及纹理的包裹模式(Repeat)。
- 使用 GeometryEngine 类管理立方体的几何形状,并在 paintGL() 方法中调用其 rawCubeGeometry() 方法绘制立方体。
- InitCube() 方法将预定义的文字转换为图像,并将多个图像合并成一个纹理,用于立方体的表面。使用 QPainter 类在图像上绘制文字,文字的字体和颜色是随机生成的。
- 使用 QMatrix4x4 进行模型视图变换和透视投影计算,在 resizeGL() 和 paintGL() 方法中设置合适的投影矩阵。
核心代码
vshader.glsl文件
#ifdef GL_ES
// Set default precision to medium
precision mediump int;
precision mediump float;
#endif
uniform mat4 mvp_matrix;
attribute vec4 a_position;
attribute vec2 a_texcoord;
varying vec2 v_texcoord;
//! [0]
void main()
{
// Calculate vertex position in screen space
gl_Position = mvp_matrix * a_position;
// Pass texture coordinate to fragment shader
// Value will be automatically interpolated to fragments inside polygon faces
v_texcoord = a_texcoord;
}
//! [0]
fshader.glsl文件
#ifdef GL_ES
// Set default precision to medium
precision mediump int;
precision mediump float;
#endif
uniform sampler2D texture;
varying vec2 v_texcoord;
//! [0]
void main()
{
// Set fragment color from texture
gl_FragColor = texture2D(texture, v_texcoord);
}
//! [0]
主要部分代码,代码比较长,有需要的可以到文章摘要中获取下载方式,免费下载使用。
#include "DlgCube.h"
#include <QMouseEvent>
#include <math.h>
#include <QPen>
#include <QPainter>
#include <QTime>
DlgCube::DlgCube(int width, int height, QWidget *parent) :
QOpenGLWidget(parent),
geometries(0),
texture(0),
angularSpeed(0)
{
this->setFixedSize(width, height);
}
DlgCube::~DlgCube()
{
// Make sure the context is current when deleting the texture
// and the buffers.
makeCurrent();
delete texture;
delete geometries;
doneCurrent();
}
void DlgCube::mousePressEvent(QMouseEvent *e)
{
// Save mouse press position
mousePressPosition = QVector2D(e->localPos());
}
void DlgCube::mouseMoveEvent(QMouseEvent *e)
{
//qDebug()<<e->pos();
// Mouse release position - mouse press position
QVector2D diff = QVector2D(e->localPos()) - mousePressPosition;
// Rotation axis is perpendicular to the mouse position difference
// vector
QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized();
// Accelerate angular speed relative to the length of the mouse sweep
qreal acc = diff.length() / 100.0;
// Calculate new rotation axis as weighted sum
rotationAxis = (rotationAxis * angularSpeed + n * acc).normalized();
// Increase angular speed
angularSpeed += acc;
mousePressPosition = QVector2D(e->localPos());
}
void DlgCube::timerEvent(QTimerEvent *)
{
// Decrease angular speed (friction)
angularSpeed *= 0.95;
// Stop rotation when speed goes below threshold
if (angularSpeed < 0.01) {
angularSpeed = 0.0;
} else {
// Update rotation
rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angularSpeed) * rotation;
// Request an update
update();
}
}
void DlgCube::initializeGL()
{
initializeOpenGLFunctions();
//球 后 widget 的背景色
// glClearColor(1, 1, 1, 0);
initShaders();
initTextures();
// Enable depth buffer
glEnable(GL_DEPTH_TEST);
// Enable back face culling
glEnable(GL_CULL_FACE);
geometries = new GeometryEngine;
// Use QBasicTimer because its faster than QTimer
timer.start(12, this);
}
void DlgCube::initShaders()
{
// Compile vertex shader
if (!program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vshader.glsl"))
close();
// Compile fragment shader
if (!program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/fshader.glsl"))
close();
// Link shader pipeline
if (!program.link())
close();
// Bind shader pipeline for use
if (!program.bind())
close();
}
void DlgCube::initTextures()
{
// Load cube.png image
texture = new QOpenGLTexture(myImage.mirrored());
// Set nearest filtering mode for texture minification
texture->setMinificationFilter(QOpenGLTexture::Nearest);
// Set bilinear filtering mode for texture magnification
texture->setMagnificationFilter(QOpenGLTexture::Linear);
// Wrap texture coordinates by repeating
// f.ex. texture coordinate (1.1, 1.2) is same as (0.1, 0.2)
texture->setWrapMode(QOpenGLTexture::Repeat);
}
void DlgCube::resizeGL(int w, int h)
{
// Calculate aspect ratio
qreal aspect = qreal(w) / qreal(h ? h : 1);
// Set near plane to 3.0, far plane to 7.0, field of view 45 degrees
//fov 可以调节大小
const qreal zNear = 2.5, zFar = 7.0, fov = 45.0;
// Reset projection
projection.setToIdentity();
// Set perspective projection
projection.perspective(fov, aspect, zNear, zFar);
}
void DlgCube::paintGL()
{
// Clear color and depth buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
texture->bind();
// Calculate model view transformation
QMatrix4x4 matrix;
matrix.translate(0.0, 0.0, -5.0);
matrix.rotate(rotation);
// Set modelview-projection matrix
program.setUniformValue("mvp_matrix", projection * matrix);
// Use texture unit 0 which contains cube.png
program.setUniformValue("texture", 0);
// Draw cube geometry
geometries->drawCubeGeometry(&program);
}
void DlgCube::InitCube()
{
m_which_color = 0;
tip_1.clear(); tip_2.clear(); tip_3.clear(); tip_4.clear(); tip_5.clear(); tip_6.clear();
tip_1.push_back("<静夜思>");
tip_1.push_back("床前明月光");
tip_1.push_back("疑是地上霜");
tip_1.push_back("举头望明月");
tip_1.push_back("低头思故乡");
tip_2.push_back("<江雪>");
tip_2.push_back("千山鸟飞绝");
tip_2.push_back("万径人踪灭");
tip_2.push_back("孤舟蓑笠翁");
tip_2.push_back("独钓寒江雪");
tip_3.push_back("<闻乐天授江州司马>");
tip_3.push_back("残灯无焰影幢幢");
tip_3.push_back("此夕闻君谪九江");
tip_3.push_back("垂死病中惊坐起");
tip_3.push_back("暗风吹雨入寒窗");
tip_4.push_back("问世间情是何物");
tip_4.push_back("直教生死相许");
tip_4.push_back("天南地北双飞客");
tip_4.push_back("老翅几回寒暑");
tip_4.push_back("欢乐趣");
tip_5.push_back("离别苦");
tip_5.push_back("就中更有痴儿女");
tip_5.push_back("君应有语");
tip_6.push_back("渺万里层云,千山暮雪,只影向谁去?");
//merge
QString str = QString(":image/edge.png");
QImage resultImg(str);
//
QPainter painter;
painter.begin(&resultImg);
//在新区域画图
painter.drawImage( 0, 0, Text_To_Image(0));
painter.drawImage(396, 0, Text_To_Image(1));
painter.drawImage(792, 0, Text_To_Image(2));
painter.drawImage( 0, 400, Text_To_Image(3));
painter.drawImage(396, 400, Text_To_Image(4));
painter.drawImage(792, 400, Text_To_Image(5));
painter.end();
QString strResultImg = QString("./tiledball.png");
resultImg.save(strResultImg);
myImage = resultImg;
}
void DlgCube::MoveLittle()
{
//move 初始位置露出一个角
QVector2D diff = QVector2D(QPoint(180,180));
QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized();
qreal acc = diff.length() / 100.0;
rotationAxis = (rotationAxis * angularSpeed + n * acc).normalized();
angularSpeed += acc;
}
QImage DlgCube::Text_To_Image(int count)
{
QVector <QString> tip;
QImage img;
switch(count)
{
case 0:tip = tip_1; img.load(":image/11.png");break;
case 1:tip = tip_2; img.load(":image/22.png");break;
case 2:tip = tip_3; img.load(":image/33.png");break;
case 3:tip = tip_4; img.load(":image/44.png");break;
case 4:tip = tip_5; img.load(":image/55.png");break;
case 5:tip = tip_6; img.load(":image/66.png");break;
default:break;
}
QPainter painter;
painter.begin(&img);
QFont font;
font.setBold(true);
int tip_size = tip.size()>8?8:tip.size();
for(int i=0;i<tip_size;i++)
{
switch (m_which_color%2)
{
case 0:font.setFamily("Noto Sans S Chinese Thin");break;
case 1:font.setFamily("Noto Sans S Chinese Bold");break;
default:break;
}
font.setPixelSize(random_fontpixsize(tip[i].length()));
painter.setFont(font);
QPen pen = random_Color();
painter.setPen(pen);
//左右各留20px
int x = 20 + qrand()%((360 - tip[i].length()*font.pixelSize())+1);
if(x<0)
x = 5;
//y会在实际图上向下偏移10个坐标
int y = 380/tip_size*i + qrand()%((380/tip_size - font.pixelSize())+1);
painter.drawText(QRect(x, y, 380,380), Qt::AlignLeft|Qt::AlignTop,tip[i]);
}
painter.end();
return img;
}
QPen DlgCube::random_Color()
{
QPen pen;
switch (m_which_color % 4) {
case 0:pen.setColor(QColor(151,255,244));
break;
case 1:pen.setColor(QColor(255,195,109));
break;
case 2:pen.setColor(QColor(124,204,255));
break;
case 3:pen.setColor(QColor(255,255,255));
break;
default:
break;
}
m_which_color++;
return pen;
}
int DlgCube::random_fontpixsize(int fontlen)
{
if(0 == fontlen)
return 1;
int size = (380 / fontlen / 2) + qrand()%(380 / fontlen / 2);
//字体大小 25<size<38
if (size > 38)
size = 33 + qrand() % 6;
if(size < 25)
size = 25 + qrand() % 6;
return size;
}
往期文章回顾
结束语
由于本人能力有限,难免有疏漏之处!
文中源码文件【获取方式】:关注公众号:利哥AI实例探险
给公众号发送 “qt3D魔方” 获取下载方式,免费,无套路,关注即可!!!