基于Qt的OpenGL学习(6)—— 摄像机
2019-08-12 本文已影响0人
玖零儛
简介
要学习OpenGL的话,强烈安利这个教程JoeyDeVries的learnopengl,这里是中文翻译好的版本。教程中使用OpenGL是通过GLFW这个库,而在Qt中对OpenGL封装得很好,并且和GUI以及IO相关的处理Qt更便捷,学习起来更轻松。这里就对每篇教程,在Qt在分别直接使用OpenGL的函数和Qt封装好的类以作对比。
教程中使用的OpenGL版本为3.3,在Qt中需要使用此版本的OpenGL只需要继承类QOpenGLFunctions_3_3_Core
即可。如果为了在不同设备上都能用OpenGL的话,Qt提供了类QOpenGLFunctions
,这个类包含了大部分公共的函数,可能会有个别函数不能用。
对比说明
教程地址
原教程地址,相关知识可以点击链接学习。
我的工程地址,每篇教程一个commit,可以切换着看,查看本篇代码 git checkout v1.6
,喜欢就点个Star吧~
不同点 (仅列出新增)
- Qt中封装的OpenGL相关的类之前基本都对比完了,其余部分基本还是用glxxx()的函数,后面可能就只在QtFunctionWidget.cpp中修改了。
- 我的更新是通过QTimer调用update()实现的,就没有计算教程中的时间差了。
运行结果
运行结果修改的文件
QtFunctionWidget.h
#ifndef QTFUNCTIONWIDGET_H
#define QTFUNCTIONWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include "Camera.h"
class QtFunctionWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
public:
QtFunctionWidget(QWidget *parent = nullptr);
~QtFunctionWidget() Q_DECL_OVERRIDE;
protected:
virtual void initializeGL() Q_DECL_OVERRIDE;
virtual void resizeGL(int w, int h) Q_DECL_OVERRIDE;
virtual void paintGL() Q_DECL_OVERRIDE;
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
void keyReleaseEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE;
private:
QOpenGLShaderProgram shaderProgram;
QOpenGLBuffer vbo;
QOpenGLVertexArrayObject vao;
QOpenGLTexture *texture1 = nullptr;
QOpenGLTexture *texture2 = nullptr;
QTimer* m_pTimer = nullptr;
int m_nTimeValue = 0;
// camera
std::unique_ptr<Camera> camera;
bool m_bLeftPressed;
QPoint m_lastPos;
};
#endif // QTFUNCTIONWIDGET_H
QtFunctionWidget.cpp
#include "QtFunctionWidget.h"
#include <QDebug>
#include <QTimer>
QtFunctionWidget::QtFunctionWidget(QWidget *parent) : QOpenGLWidget (parent),
vbo(QOpenGLBuffer::VertexBuffer)
{
camera = std::make_unique<Camera>(QVector3D(5.0f, 0.0f, 10.0f));
m_bLeftPressed = false;
m_pTimer = new QTimer(this);
connect(m_pTimer, &QTimer::timeout, this, [=]{
m_nTimeValue += 1;
update();
});
m_pTimer->start(40);
}
QtFunctionWidget::~QtFunctionWidget(){
makeCurrent();
vbo.destroy();
vao.destroy();
delete texture1;
delete texture2;
doneCurrent();
}
void QtFunctionWidget::initializeGL(){
this->initializeOpenGLFunctions();
bool success = shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/textures.vert");
if (!success) {
qDebug() << "shaderProgram addShaderFromSourceFile failed!" << shaderProgram.log();
return;
}
success = shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/textures.frag");
if (!success) {
qDebug() << "shaderProgram addShaderFromSourceFile failed!" << shaderProgram.log();
return;
}
success = shaderProgram.link();
if(!success) {
qDebug() << "shaderProgram link failed!" << shaderProgram.log();
}
//VAO,VBO data
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
QOpenGLVertexArrayObject::Binder vaoBind(&vao);
vbo.create();
vbo.bind();
vbo.allocate(vertices, sizeof(vertices));
// position attribute
int attr = -1;
attr = shaderProgram.attributeLocation("aPos");
shaderProgram.setAttributeBuffer(attr, GL_FLOAT, 0, 3, sizeof(GLfloat) * 5);
shaderProgram.enableAttributeArray(attr);
// texture coord attribute
attr = shaderProgram.attributeLocation("aTexCoord");
shaderProgram.setAttributeBuffer(attr, GL_FLOAT, sizeof(GLfloat) * 3, 2, sizeof(GLfloat) * 5);
shaderProgram.enableAttributeArray(attr);
// texture 1
// ---------
texture1 = new QOpenGLTexture(QImage(":/container.jpg"), QOpenGLTexture::GenerateMipMaps);
if(!texture1->isCreated()){
qDebug() << "Failed to load texture";
}
// set the texture wrapping parameters
texture1->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
texture1->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
// set texture filtering parameters
texture1->setMinificationFilter(QOpenGLTexture::Linear);
texture1->setMagnificationFilter(QOpenGLTexture::Linear);
// texture 2
// ---------
texture2 = new QOpenGLTexture(QImage(":/awesomeface.png").mirrored(true, true), QOpenGLTexture::GenerateMipMaps);
if(!texture2->isCreated()){
qDebug() << "Failed to load texture";
}
// set the texture wrapping parameters
texture2->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
texture2->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
// set texture filtering parameters
texture2->setMinificationFilter(QOpenGLTexture::Linear);
texture1->setMagnificationFilter(QOpenGLTexture::Linear);
// tell opengl for each sampler to which texture unit it belongs to (only has to be done once)
shaderProgram.bind(); // don't forget to activate/use the shader before setting uniforms!
shaderProgram.setUniformValue("texture1", 0);
shaderProgram.setUniformValue("texture2", 1);
vbo.release();
// configure global opengl state
// -----------------------------
glEnable(GL_DEPTH_TEST);
}
void QtFunctionWidget::resizeGL(int w, int h){
glViewport(0, 0, w, h);
}
static 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)
};
void QtFunctionWidget::paintGL(){
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // also clear the depth buffer now!
camera->processInput(1.0f);
// bind textures on corresponding texture units
glActiveTexture(GL_TEXTURE0);
texture1->bind();
glActiveTexture(GL_TEXTURE1);
texture2->bind();
shaderProgram.bind();
QMatrix4x4 projection;
projection.perspective(camera->zoom, 1.0f * width() / height(), 0.1f, 100.f);
shaderProgram.setUniformValue("projection", projection);
// camera/view transformation
shaderProgram.setUniformValue("view", camera->getViewMatrix());
{// render box
QOpenGLVertexArrayObject::Binder vaoBind(&vao);
for (unsigned int i = 0; i < 10; i++) {
// calculate the model matrix for each object and pass it to shader before drawing
QMatrix4x4 model;
model.translate(cubePositions[i]);
float angle = (i + 1.0f) * m_nTimeValue;
model.rotate(angle, QVector3D(1.0f, 0.3f, 0.5f));
shaderProgram.setUniformValue("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
}
texture1->release();
texture2->release();
shaderProgram.release();
}
void QtFunctionWidget::keyPressEvent(QKeyEvent *event)
{
int key = event->key();
if (key >= 0 && key < 1024)
camera->keys[key] = true;
}
void QtFunctionWidget::keyReleaseEvent(QKeyEvent *event)
{
int key = event->key();
if (key >= 0 && key < 1024)
camera->keys[key] = false;
}
void QtFunctionWidget::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton){
m_bLeftPressed = true;
m_lastPos = event->pos();
}
}
void QtFunctionWidget::mouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event);
m_bLeftPressed = false;
}
void QtFunctionWidget::mouseMoveEvent(QMouseEvent *event)
{
if (m_bLeftPressed) {
int xpos = event->pos().x();
int ypos = event->pos().y();
int xoffset = xpos - m_lastPos.x();
int yoffset = m_lastPos.y() - ypos;
m_lastPos = event->pos();
camera->processMouseMovement(xoffset, yoffset);
}
}
void QtFunctionWidget::wheelEvent(QWheelEvent *event)
{
QPoint offset = event->angleDelta();
camera->processMouseScroll(offset.y()/20.0f);
}
Camera.h
#ifndef CAMERA_H
#define CAMERA_H
#include <QVector3D>
#include <QMatrix4x4>
#include <QKeyEvent>
// Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods
enum Camera_Movement {
FORWARD,
BACKWARD,
LEFT,
RIGHT,
UP,
DOWN
};
// Default camera values
const float YAW = -90.0f;
const float PITCH = 0.0f;
const float SPEED = 1.0f;
const float SENSITIVITY = 0.01f;
const float ZOOM = 45.0f;
class Camera {
public:
Camera(QVector3D position = QVector3D(0.0f, 0.0f, 0.0f), QVector3D up = QVector3D(0.0f, 1.0f, 0.0f),
float yaw = YAW, float pitch = PITCH);
~Camera();
QMatrix4x4 getViewMatrix();
void processMouseMovement(float xoffset, float yoffset, bool constraintPitch = true);
void processMouseScroll(float yoffset);
void processInput(float dt);
QVector3D position;
QVector3D worldUp;
QVector3D front;
QVector3D up;
QVector3D right;
//Eular Angles
float picth;
float yaw;
//Camera options
float movementSpeed;
float mouseSensitivity;
float zoom;
//Keyboard multi-touch
bool keys[1024];
private:
void updateCameraVectors();
void processKeyboard(Camera_Movement direction, float deltaTime);
};
#endif // CAMERA_H
Camera.cpp
#include "Camera.h"
#include <QDebug>
Camera::Camera(QVector3D position, QVector3D up, float yaw, float pitch) :
position(position),
worldUp(up),
front(-position),
picth(pitch),
yaw(yaw),
movementSpeed(SPEED),
mouseSensitivity(SENSITIVITY),
zoom(ZOOM) {
this->updateCameraVectors();
for(uint i = 0; i != 1024; ++i)
keys[i] = false;
}
Camera::~Camera()
{
}
// Returns the view matrix calculated using Euler Angles and the LookAt Matrix
QMatrix4x4 Camera::getViewMatrix()
{
QMatrix4x4 view;
view.lookAt(this->position, this->position + this->front, this->up);
return view;
}
// Processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems)
void Camera::processKeyboard(Camera_Movement direction, float deltaTime)
{
float velocity = this->movementSpeed * deltaTime;
if (direction == FORWARD)
this->position += this->front * velocity;
if (direction == BACKWARD)
this->position -= this->front * velocity;
if (direction == LEFT)
this->position -= this->right * velocity;
if (direction == RIGHT)
this->position += this->right * velocity;
if (direction == UP)
this->position += this->worldUp * velocity;
if (direction == DOWN)
this->position -= this->worldUp * velocity;
}
// Processes input received from a mouse input system. Expects the offset value in both the x and y direction.
void Camera::processMouseMovement(float xoffset, float yoffset, bool constraintPitch)
{
xoffset *= this->mouseSensitivity;
yoffset *= this->mouseSensitivity;
this->yaw += xoffset;
this->picth += yoffset;
if (constraintPitch) {
if (this->picth > 89.0f)
this->picth = 89.0f;
if (this->picth < -89.0f)
this->picth = -89.0f;
}
this->updateCameraVectors();
}
// Processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis
void Camera::processMouseScroll(float yoffset)
{
if (this->zoom >= 1.0f && this->zoom <= 45.0f)
this->zoom -= yoffset;
if (this->zoom > 45.0f)
this->zoom = 45.0f;
if (this->zoom < 1.0f)
this->zoom = 1.0f;
}
void Camera::processInput(float dt)
{
if (keys[Qt::Key_W])
processKeyboard(FORWARD, dt);
if (keys[Qt::Key_S])
processKeyboard(BACKWARD, dt);
if (keys[Qt::Key_A])
processKeyboard(LEFT, dt);
if (keys[Qt::Key_D])
processKeyboard(RIGHT, dt);
if (keys[Qt::Key_E])
processKeyboard(UP, dt);
if (keys[Qt::Key_Q])
processKeyboard(DOWN, dt);
}
void Camera::updateCameraVectors()
{
// Calculate the new Front vector
QVector3D front;
front.setX(cos(this->yaw) * cos(this->picth));
front.setY(sin(this->picth));
front.setZ(sin(this->yaw) * cos(this->picth));
this->front = front.normalized();
this->right = QVector3D::crossProduct(this->front, this->worldUp).normalized();
this->up = QVector3D::crossProduct(this->right, this->front).normalized();
}