本帖将参考LearnOpenGL这一经典教程,使用Qt的原生环境完成教程中所提的所有流程,并尽量和原教程保持一致,如有错误,欢迎评论!
为了方便大家参考,我将项目分享至了gitee,并实时进行更行:Qt实现OpenGL的经典教程: (gitee.com)
Qt版本:6.7
操作系统:Windows10
旋转摄像机
对摄像机的操控这里需要用到LookAt矩阵,好在Qt的QMatrix4x4库中也对它进行了封装,这里我们对上一篇中的代码进行修改,只修改PaintGL中的内容,调整摄像机矩阵View,具体代码如下:
void MyOpenGLWidget::paintGL()
{
//由于继承了QOpenGLFunctions,可以直接使用OpenGL中的函数
// render
// ------
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清除颜色和深度缓冲
// bind Texture
glActiveTexture(GL_TEXTURE0);
_texture->bind();
glActiveTexture(GL_TEXTURE1);
_texture1->bind();
QMatrix4x4 model;
QMatrix4x4 view;
QMatrix4x4 projection;
float radius = 10.0f;
float camX = sin(QTime::currentTime().msecsSinceStartOfDay()/1000.0f) * radius;
float camZ = cos(QTime::currentTime().msecsSinceStartOfDay()/1000.0f) * radius;
view.lookAt(QVector3D(camX, 0.0f, camZ),
QVector3D(0.0f, 0.0f, 0.0f),
QVector3D(0.0f, 1.0f, 0.0f));
projection.perspective(45.0f,
float(this->width())/float(this->height()),
0.1f,
100.0f);
// update shader uniform
_shaderProgram->bind();
_shaderProgram->setUniformValue("view", view);
_shaderProgram->setUniformValue("projection", projection);
// render the triangle
_vao->bind();
for(int i = 0; i < _cubePositions.size(); ++i){
model = QMatrix4x4();
model.translate(_cubePositions[i]);
model.rotate(20.0f * i, 1.0f, 0.3f, 0.5f);
_shaderProgram->setUniformValue("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
}
我们主要使用了LookAt方法来修改定义的View举证,lookat三个参数分别是摄像机位置,目标位置,以及上向量。通过这种方法,便可以自由地控制摄像机视角。我们通过sin和cos来实现一个摄像机圆周运动,点击运行,可得到如下结果:
自由移动
现在,让我们添加一些监控键盘与鼠标输入来控制摄像机的位置。在Qt中捕捉键盘输入需要重写继承的keyPressEvent函数,在MyOpenGLWidget.h的类定义中添加以下代码:
protected:
void initializeGL()override;
void resizeGL(int width, int height)override;
void paintGL()override;
//重写键盘事件
void keyPressEvent(QKeyEvent *event)override;
同时为了计算帧生成时间和控制相机位置,我们在MyOpenGLWidget.h的类定义中同时添加以下代码:
private:
//帧处理时间
int _lastMescCount = 0;
int _deltaTime = 0;
//摄像机相关
QVector3D _cameraPos = {0.0f, 0.0f, 3.0f};
QVector3D _cameraFront = {0.0f, 0.0f, -1.0f};
QVector3D _cameraUp = {0.0f, 1.0f, 0.0f};
float _moveSpeed = 1.0f;
我们在keyPressEvent定义中更行相机位置信息:
void MyOpenGLWidget::keyPressEvent(QKeyEvent *event){
float moveSpeed = static_cast<float>(_moveSpeed * _deltaTime);
if(event->key() == Qt::Key_W){
_cameraPos += moveSpeed * _cameraFront;
}
if(event->key() == Qt::Key_S){
_cameraPos -= moveSpeed * _cameraFront;
}
if(event->key() == Qt::Key_A){
_cameraPos -= QVector3D::crossProduct(_cameraFront, _cameraUp).normalized() * moveSpeed;
}
if(event->key() == Qt::Key_D){
_cameraPos += QVector3D::crossProduct(_cameraFront, _cameraUp).normalized() * moveSpeed;
}
//执行默认事件,向下传递
QOpenGLWidget::keyPressEvent(event);
}
这里注意以下,我们要开启MyOpenGLWidget对键盘事件的捕捉,在其构造函数中添加以下一行代码:
//开启键盘事件捕捉
this->grabKeyboard();
最后,我们在PaintGL中更新帧生成时间和生成用于控制相机位置的view矩阵:
//更新帧时间,由于定时器60fps,所以这里的_deltaTime为17上下
_deltaTime = QTime::currentTime().msecsSinceStartOfDay() - _lastMescCount;
_lastMescCount = QTime::currentTime().msecsSinceStartOfDay();
QMatrix4x4 model;
QMatrix4x4 projection;
QMatrix4x4 view;
view.lookAt(_cameraPos, _cameraPos + _cameraFront, _cameraUp);
点击运行,便得到一个可以键盘控制的相机:
鼠标控制视角
接着,我们捕捉鼠标输入,对摄像机的视角朝向进行设置,同理,我们重写鼠标的移动函数MouseMoveEvent
同时在MyOpengGLWidget.h中添加以下内容
float _yaw = -90.0f;
float _pitch = 0.0f;
float _sensitivity = 0.1f;
//屏幕中心点
QPoint _lastMousePos = rect().center();
这是它的头文件内容是这样的:
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QWidget>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
class MyOpenGLWidget : public QOpenGLWidget, public QOpenGLFunctions_3_3_Core
{
Q_OBJECT
public:
explicit MyOpenGLWidget();
~MyOpenGLWidget();
protected:
void initializeGL()override;
void resizeGL(int width, int height)override;
void paintGL()override;
//重写键盘事件
void keyPressEvent(QKeyEvent *event)override;
//重写鼠标事件
void mouseMoveEvent(QMouseEvent *event)override;
private:
QOpenGLShaderProgram *_shaderProgram;
QOpenGLTexture *_texture;
QOpenGLTexture *_texture1;
QOpenGLVertexArrayObject *_vao;
QOpenGLBuffer *_vbo;
QOpenGLBuffer *_ebo;
QList<QVector3D> _cubePositions = {
QVector3D( 0.0f, 0.0f, 0.0f),
QVector3D( 2.0f, 5.0f, -15.0f),
QVector3D(-1.5f, -2.2f, -2.5f),
QVector3D(-3.8f, -2.0f, -12.3f),
QVector3D( 2.4f, -0.4f, -3.5f),
QVector3D(-1.7f, 3.0f, -7.5f),
QVector3D( 1.3f, -2.0f, -2.5f),
QVector3D( 1.5f, 2.0f, -2.5f),
QVector3D( 1.5f, 0.2f, -1.5f),
QVector3D(-1.3f, 1.0f, -1.5f)
};
//帧处理时间
int _lastMescCount = 0;
float _deltaTime = 0;
//摄像机相关
QVector3D _cameraPos = {0.0f, 0.0f, 3.0f};
QVector3D _cameraFront = {0.0f, 0.0f, -1.0f};
QVector3D _cameraUp = {0.0f, 1.0f, 0.0f};
float _moveSpeed = 5.0f;
float _yaw = -90.0f;
float _pitch = 0.0f;
float _sensitivity = 0.1f;
//屏幕中心点
QPoint _lastMousePos = rect().center();
};
#endif // MYOPENGLWIDGET_H
我们在mouseMoveEvent的定义中进行以下实现:
void MyOpenGLWidget::mouseMoveEvent(QMouseEvent *event){
float xOffset = event->pos().x() - _lastMousePos.x();
float yOffset = - event->pos().y() + _lastMousePos.y();
// 获取当前widget的中心点坐标
// 将鼠标设置到中心点
QCursor::setPos(mapToGlobal(_lastMousePos));
xOffset *= _sensitivity;
yOffset *= _sensitivity;
_yaw += xOffset;
_pitch += yOffset;
// make sure that when pitch is out of bounds, screen doesn't get flipped
if (_pitch > 89.0f)
_pitch = 89.0f;
if (_pitch < -89.0f)
_pitch = -89.0f;
_cameraFront.setX(cos(qDegreesToRadians(_yaw)) * cos(qDegreesToRadians(_pitch)));
_cameraFront.setY(sin(qDegreesToRadians(_pitch)));
_cameraFront.setZ(sin(qDegreesToRadians(_yaw)) * cos(qDegreesToRadians(_pitch)));
_cameraFront.normalize();
}
同时开启鼠标跟踪:
MyOpenGLWidget::MyOpenGLWidget()
{
//设置上下文属性
QSurfaceFormat format;
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
format.setVersion(3, 3); //OpenGL 版本号3.3
format.setProfile(QSurfaceFormat::CoreProfile);// 核心模式
this->setFormat(format);
//开启键盘事件捕捉
this->grabKeyboard();
//开启捕捉鼠标事件
this->grabMouse();
// 启用鼠标追踪
this->setMouseTracking(true);
}
点击运行,便得到了一个FPS视角的3D界面了!