1.8.1 摄像机

一、摄像机

OpenGL本身没有摄像机的概念,但是我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机,产生一种我们在移动的感觉,而不是场景在移动。

本节将会讨论如何在OpenGL中配置一个摄像机,让你能够在3D场景中自由移动,也会讨论键盘和鼠标输入,最终完成一个自定义的摄像机类。

1、摄像机/观察空间

当讨论摄像机/观察空间(Camera/View Space)的时候,是在讨论以摄像机的视角作为场景原点时场景中所有的顶点坐标:观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。要定义一个摄像机,需要它在世界空间中的位置、观察的方向、一个指向它右侧的向量以及一个指向它上方的向量,实际上创建了一个三个单位轴相互垂直的、以摄像机的位置为原点的坐标系。

1)摄像机位置

摄像机位置就是世界空间中一个指向摄像机位置的向量。把摄像机位置设置为上一节的那个位置:

QVector3D cameraPos = QVector3D(0.0f, 0.0f, 3.0f); //位置

2)摄像机方向

摄像机的方向是摄像机指向的方向。现在让摄像机指定场景原点(0, 0, 0)。用场景原点向量减去摄像机位置向量的结果就是摄像机的指向向量。我们知道摄像机指向z轴负方向,但是希望方向向量指向摄像机的z轴正方向,交换相减的顺序,就会获得一个指向摄像机z轴正方向的向量:

QVector3D cameraTarget = QVector3D(0.0f, 0.0f, 0.0f);
QVector3D cameraDirection = cameraPos - cameraTarget; //方向
cameraDirection.normalize(); //单位向量

方向向量不是最好的名字,因为它实际上指向从它到目标向量的相反方向,如上图中蓝色的方向向量大概指向z轴的正方向,与摄像机实际指向的方向是相反的。

3)右轴

需要一个右向量,它代表摄像机空间的x轴的正方向。为获得右向量需要先使用一个小技巧:先定义一个上向量,把上向量和第二步的方向向量进行叉乘,两个向量叉乘的结果会同时垂直于这两个向量,因此会得到指向x轴正方向的那个向量(如果交换两个向量叉乘的顺序就会得到指向x轴负方向的向量):

QVector3D up = QVector3D(0.0f, 1.0f, 0.0f);
QVector3D cameraRight = QVector3D::crossProduct(up, cameraDirection); //叉乘,右向量
cameraRight.normalize(); //单位向量

4)上轴

现在把x轴向量和z轴向量进行叉乘,得到y轴向量

QVector3D cameraUp = QVector3D::crossProduct(cameraDirection,cameraRight); //叉乘,上向量

2、Look At

可以用3个相互垂直的轴定义一个坐标空间,可以用3个轴外加一个平移向量来创建一个矩阵,并且可以用这个矩阵乘以任何向量来将其变换到那个坐标空间,这正是LookAt矩阵所做的。

其中R是右向量,U是上向量,D是方向向量,P是摄像机位置向量(位置向量是相反的)

只需要给Qt中lookAt()函数提供一个摄像机位置、一个目标位置和一个表示世界空间中的上向量的向量,就可以得到一个观察矩阵(因为在上面四步中用摄像机位置、目标位置和上向量计算出其他向量,lookAt函数会自动计算出其他向量)

QMatrix4x4 view;
view.lookAt(cameraPos, cameraTarget, up);

下面实现摄像机在场景中旋转,摄像机的注视点保持在(0, 0, 0)。

在myopenglwidget.h文件中声明了6个QVector3D类型的变量,用于存储摄像机相关的数据;QTime类型的变量用于计时:

#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QTimer>
#include <QTime>

class MyOpenGLWidget : public QOpenGLWidget,QOpenGLFunctions_3_3_Core
{
    Q_OBJECT

public:
    explicit MyOpenGLWidget(QWidget *parent = nullptr);
    ~MyOpenGLWidget();

protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();

    void keyPressEvent(QKeyEvent *event);

private slots:
    void onTimeout();

private:
    QOpenGLShaderProgram m_shaderProgram;
    QOpenGLTexture *m_textureWall;
    QOpenGLTexture *m_textureSmile;
    QOpenGLTexture *m_textureSmall;

    float mixValue = 0.5;

    QTimer m_timer;
    QTime m_time;

    QVector3D m_cameraPos;
    QVector3D m_cameraTarget;
    QVector3D m_cameraDirection;
    QVector3D m_up;
    QVector3D m_cameraRight;
    QVector3D m_cameraUp;
};

#endif // MYOPENGLWIDGET_H

更改myopenglwidget.cpp代码如下:

#include "myopenglwidget.h"
#include <QDebug>
#include <QKeyEvent>

unsigned int VBO; //顶点缓冲对象
unsigned int VAO; //顶点数组对象
unsigned int EBO; //元素缓冲对象

MyOpenGLWidget::MyOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    setFocusPolicy(Qt::StrongFocus);

    //位置
    m_cameraPos = QVector3D(0.0f, 0.0f, 3.0f);

    //方向
    m_cameraTarget = QVector3D(0.0f, 0.0f, 0.0f);
    m_cameraDirection = m_cameraPos - m_cameraTarget;
    m_cameraDirection.normalize(); //单位向量

    //右向量
    m_up = QVector3D(0.0f, 1.0f, 0.0f);
    m_cameraRight = QVector3D::crossProduct(m_up, m_cameraDirection); //叉乘,右向量
    m_cameraRight.normalize(); //单位向量

    //上向量
    m_cameraUp = QVector3D::crossProduct(m_cameraDirection, m_cameraRight); //叉乘,上向量

    connect(&m_timer, &QTimer::timeout, this, &MyOpenGLWidget::onTimeout);
    m_timer.start(100); //100ms
    m_time.start(); //开始计时
}

MyOpenGLWidget::~MyOpenGLWidget()
{
    if(m_timer.isActive())
        m_timer.stop();

    makeCurrent();
    glDeleteBuffers(1, &VBO);
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &EBO);
    doneCurrent();
}

void MyOpenGLWidget::onTimeout()
{
    update();
}

void MyOpenGLWidget::initializeGL()
{
    //初始化OpenGL函数
    initializeOpenGLFunctions();

    //创建VBO,并赋予ID
    glGenBuffers(1, &VBO);
    //绑定VBO对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    //顶点数据
    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
    };
    //把顶点数据复制到显存中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //创建VAO对象,并赋予ID
    glGenVertexArrays(1, &VAO);
    //绑定VAO对象
    glBindVertexArray(VAO);

    //创建EBO对象,并赋予ID
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    unsigned int indices[] = {
                               0, 1, 3, //第一个三角形
                               1, 2, 3 //第二个三角形
                             };
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    //位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0); //开启VAO管理的第一个属性值

    //纹理属性
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1); //开启VAO管理的第三个属性值

    //解绑VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    //解绑VAO
    glBindVertexArray(0);

    //创建一个程序对象
    m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shapes.vert");
    m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shapes.frag");
    bool success = m_shaderProgram.link();
    if(!success)
        qDebug()<<"ERR:" << m_shaderProgram.log();

    m_shaderProgram.bind();
    m_shaderProgram.setUniformValue("vertexColor", 0.0, 1.0, 0.0, 1.0);

    m_textureWall = new QOpenGLTexture(QImage(":/images/wall.jpg").mirrored()); //mirrored消除镜像
    m_shaderProgram.setUniformValue("textureWall", 0); //把纹理单元传给片段着色器中的采样器

    m_textureSmile = new QOpenGLTexture(QImage(":/images/awesomeface.png").mirrored()); //mirrored消除镜像
    m_shaderProgram.setUniformValue("textureSmile", 1); //把纹理单元传给片段着色器中的采样器

    m_textureSmall = new QOpenGLTexture(QImage(":/images/small.png").mirrored()); //mirrored消除镜像
    m_shaderProgram.setUniformValue("textureSmall", 2); //把纹理单元传给片段着色器中的采样器

    m_shaderProgram.setUniformValue("mixValue", mixValue);
}

void MyOpenGLWidget::resizeGL(int w, int h)
{
    Q_UNUSED(w);

    Q_UNUSED(h);
    //glViewport(0, 0, w, h);
}

QVector<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 MyOpenGLWidget::paintGL()
{
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置墨绿色背景
    glEnable(GL_DEPTH_TEST); //打开深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清空

    //观察矩阵
    QMatrix4x4 view; //创建单位矩阵
    const float radius = 10.0f;
    float time = m_time.elapsed()/1000.0;
    float camX = sin(time) * radius;
    float camZ = cos(time) * radius;
    view.lookAt(QVector3D(camX, 0.0, camZ), m_cameraTarget, m_up);
    m_shaderProgram.setUniformValue("view", view);

    //投影矩阵
    QMatrix4x4 projection; //创建单位矩阵
    projection.perspective(45.0f, (float)width()/height(), 0.1f, 100.0f); //透视投影
    m_shaderProgram.setUniformValue("projection", projection);

    //绘制
    m_shaderProgram.bind(); //激活程序对象
    glBindVertexArray(VAO); //绑定VAO
    m_textureWall->bind(0); //绑定激活纹理单元0
    m_textureSmile->bind(1); //绑定激活纹理单元1
    m_textureSmall->bind(2); //绑定激活纹理单元2

    foreach(auto item, cubePositions)
    {
        //模型矩阵
        QMatrix4x4 model; //创建单位矩阵
        model.translate(item); //移动
        model.rotate(time, 1.0f, 1.f, 0.0f); //绕向量(1.0f, 1.f, 0.0f)旋转
        m_shaderProgram.setUniformValue("model", model); //传给顶点着色器
        glDrawArrays(GL_TRIANGLES, 0, 36); //绘图
    }
}

void MyOpenGLWidget::keyPressEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_Up)
        mixValue+=0.1;
    else if(event->key() == Qt::Key_Down)
        mixValue-=0.1;
    else
        return;

    if(mixValue > 1.0)
        mixValue = 1.0;

    if(mixValue < 0.0)
        mixValue = 0.0;

    makeCurrent();
    m_shaderProgram.setUniformValue("mixValue", mixValue);
    doneCurrent();
    update();

    QOpenGLWidget::keyPressEvent(event);
}

在构造函数中初始化6个QVector3D类型的变量;同时变量m_time开始计时;

在paintGL()函数中通过更改摄像机的位置来生成不同的观察矩阵,使摄像机达到随着时间流逝围绕场景转动的效果

//观察矩阵
QMatrix4x4 view; //创建单位矩阵
const float radius = 10.0f;
float time = m_time.elapsed()/1000.0; //elapsed()函数:从start开始到现在一共经历了多少毫秒
float camX = sin(time) * radius;
float camZ = cos(time) * radius;
view.lookAt(QVector3D(camX, 0.0, camZ), m_cameraTarget, m_up);
m_shaderProgram.setUniformValue("view", view);

下面这行代码使用三角学的知识来计算每一帧的x和z坐标,预先设定圆的半径radius,QVector3D(camX, 0.0, camZ)表示圆上的一点;随着时间的流逝,会遍历圆上所有的点,这样摄像机就会绕着场景旋转了

float camX = sin(time) * radius;
float camZ = cos(time) * radius;

运行结果如下:

二、自由移动

下面用键盘上A、S、D、W键来控制摄像机移动

更改myopenglwidget.h代码如下,新定义一个方向向量 m_cameraFront:

#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QTimer>
#include <QTime>

class MyOpenGLWidget : public QOpenGLWidget,QOpenGLFunctions_3_3_Core
{
    Q_OBJECT

public:
    explicit MyOpenGLWidget(QWidget *parent = nullptr);
    ~MyOpenGLWidget();

protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();

    void keyPressEvent(QKeyEvent *event);

private slots:
    void onTimeout();

private:
    QOpenGLShaderProgram m_shaderProgram;
    QOpenGLTexture *m_textureWall;
    QOpenGLTexture *m_textureSmile;
    QOpenGLTexture *m_textureSmall;

    float mixValue = 0.5;

    QTimer m_timer;
    QTime m_time;

    QVector3D m_cameraPos;
    QVector3D m_cameraTarget;
    QVector3D m_cameraDirection;
    QVector3D m_up;
    QVector3D m_cameraRight;
    QVector3D m_cameraUp;
    QVector3D m_cameraFront;
};

#endif // MYOPENGLWIDGET_H

更改myopenglwidget.cpp代码如下:

#include "myopenglwidget.h"
#include <QDebug>
#include <QKeyEvent>

unsigned int VBO; //顶点缓冲对象
unsigned int VAO; //顶点数组对象
unsigned int EBO; //元素缓冲对象

#define TIMEOUTMSEC 100

MyOpenGLWidget::MyOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    setFocusPolicy(Qt::StrongFocus);

    //位置
    m_cameraPos = QVector3D(0.0f, 0.0f, 3.0f);

    //方向
    m_cameraTarget = QVector3D(0.0f, 0.0f, 0.0f);
    m_cameraDirection = m_cameraPos - m_cameraTarget;
    m_cameraDirection.normalize(); //单位向量

    //右向量
    m_up = QVector3D(0.0f, 1.0f, 0.0f);
    m_cameraRight = QVector3D::crossProduct(m_up, m_cameraDirection); //叉乘,右向量
    m_cameraRight.normalize(); //单位向量

    //上向量
    m_cameraUp = QVector3D::crossProduct(m_cameraDirection, m_cameraRight); //叉乘,上向量
    m_cameraFront = QVector3D(0.0, 0.0, -1.0);

    connect(&m_timer, &QTimer::timeout, this, &MyOpenGLWidget::onTimeout);
    m_timer.start(TIMEOUTMSEC); //ms
    m_time.start(); //开始计时
}

MyOpenGLWidget::~MyOpenGLWidget()
{
    if(m_timer.isActive())
        m_timer.stop();

    makeCurrent();
    glDeleteBuffers(1, &VBO);
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &EBO);
    doneCurrent();
}

void MyOpenGLWidget::onTimeout()
{
    update();
}

void MyOpenGLWidget::initializeGL()
{
    //初始化OpenGL函数
    initializeOpenGLFunctions();

    //创建VBO,并赋予ID
    glGenBuffers(1, &VBO);
    //绑定VBO对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    //顶点数据
    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
    };
    //把顶点数据复制到显存中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //创建VAO对象,并赋予ID
    glGenVertexArrays(1, &VAO);
    //绑定VAO对象
    glBindVertexArray(VAO);

    //创建EBO对象,并赋予ID
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    unsigned int indices[] = {
                               0, 1, 3, //第一个三角形
                               1, 2, 3 //第二个三角形
                             };
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    //位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0); //开启VAO管理的第一个属性值

    //纹理属性
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1); //开启VAO管理的第三个属性值

    //解绑VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    //解绑VAO
    glBindVertexArray(0);

    //创建一个程序对象
    m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shapes.vert");
    m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shapes.frag");
    bool success = m_shaderProgram.link();
    if(!success)
        qDebug()<<"ERR:" << m_shaderProgram.log();

    m_shaderProgram.bind();
    m_shaderProgram.setUniformValue("vertexColor", 0.0, 1.0, 0.0, 1.0);

    m_textureWall = new QOpenGLTexture(QImage(":/images/wall.jpg").mirrored()); //mirrored消除镜像
    m_shaderProgram.setUniformValue("textureWall", 0); //把纹理单元传给片段着色器中的采样器

    m_textureSmile = new QOpenGLTexture(QImage(":/images/awesomeface.png").mirrored()); //mirrored消除镜像
    m_shaderProgram.setUniformValue("textureSmile", 1); //把纹理单元传给片段着色器中的采样器

    m_textureSmall = new QOpenGLTexture(QImage(":/images/small.png").mirrored()); //mirrored消除镜像
    m_shaderProgram.setUniformValue("textureSmall", 2); //把纹理单元传给片段着色器中的采样器

    m_shaderProgram.setUniformValue("mixValue", mixValue);
}

void MyOpenGLWidget::resizeGL(int w, int h)
{
    Q_UNUSED(w);

    Q_UNUSED(h);
    //glViewport(0, 0, w, h);
}

QVector<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 MyOpenGLWidget::paintGL()
{
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置墨绿色背景
    glEnable(GL_DEPTH_TEST); //打开深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清空

    float time = m_time.elapsed()/1000.0;

    //观察矩阵
    QMatrix4x4 view; //创建单位矩阵
    view.lookAt(m_cameraPos, m_cameraPos + m_cameraFront, m_up);
    m_shaderProgram.setUniformValue("view", view);

    //投影矩阵
    QMatrix4x4 projection; //创建单位矩阵
    projection.perspective(45.0f, (float)width()/height(), 0.1f, 100.0f); //透视投影
    m_shaderProgram.setUniformValue("projection", projection);

    //绘制
    m_shaderProgram.bind(); //激活程序对象
    glBindVertexArray(VAO); //绑定VAO
    m_textureWall->bind(0); //绑定激活纹理单元0
    m_textureSmile->bind(1); //绑定激活纹理单元1
    m_textureSmall->bind(2); //绑定激活纹理单元2

    foreach(auto item, cubePositions)
    {
        //模型矩阵
        QMatrix4x4 model; //创建单位矩阵
        model.translate(item); //移动
        model.rotate(time, 1.0f, 1.f, 0.0f); //绕向量(1.0f, 1.f, 0.0f)旋转
        m_shaderProgram.setUniformValue("model", model); //传给顶点着色器
        glDrawArrays(GL_TRIANGLES, 0, 36); //绘图
    }
}

void MyOpenGLWidget::keyPressEvent(QKeyEvent *event)
{
    float cameraSpeed = 2.5 * TIMEOUTMSEC / 1000.0;

    switch (event->key()) {
    case Qt::Key_Up: mixValue += 0.1; break;
    case Qt::Key_Down: mixValue -= 0.1; break;
    case Qt::Key_W: m_cameraPos += cameraSpeed * m_cameraFront; break;
    case Qt::Key_S: m_cameraPos -= cameraSpeed * m_cameraFront; break;
    case Qt::Key_D: m_cameraPos += cameraSpeed * m_cameraRight; break;
    case Qt::Key_A: m_cameraPos -= cameraSpeed * m_cameraRight; break;
    default: break;
    }

    if(mixValue > 1.0)
        mixValue = 1.0;

    if(mixValue < 0.0)
        mixValue = 0.0;

    makeCurrent();
    m_shaderProgram.setUniformValue("mixValue", mixValue);
    doneCurrent();
    update();

    QOpenGLWidget::keyPressEvent(event);
}

首先,进行宏定义

#define TIMEOUTMSEC 100

更改一下定时器

m_timer.start(TIMEOUTMSEC);

方向向量初始化,使摄像机一直向前看

m_cameraFront = QVector3D(0.0, 0.0, -1.0);

更改观察矩阵,把摄像机位置设置为之前定义的m_cameraPos,方向是当前的位置加上新定义的方向向量m_cameraFront,这样能够保证无论怎么移动,摄像机都会注视着目标方向。

//观察矩阵
QMatrix4x4 view; //创建单位矩阵
view.lookAt(m_cameraPos, m_cameraPos + m_cameraFront, m_up);
m_shaderProgram.setUniformValue("view", view);

更改keyPressEvent()函数代码,使键盘上A、S、D、W键起作用

//移动步长
float cameraSpeed = 2.5 * TIMEOUTMSEC / 1000.0;

switch (event->key())
{
    case Qt::Key_Up: mixValue += 0.1; break;
    case Qt::Key_Down: mixValue -= 0.1; break;
    case Qt::Key_W: m_cameraPos += cameraSpeed * m_cameraFront; break;
    case Qt::Key_S: m_cameraPos -= cameraSpeed * m_cameraFront; break;
    case Qt::Key_D: m_cameraPos += cameraSpeed * m_cameraRight; break;
    case Qt::Key_A: m_cameraPos -= cameraSpeed * m_cameraRight; break;
    default: break;
}

2.5是固定步长,当只有固定步长时,一直按着某一个键,刷新速度快的机器移动速度就快,为了消除不同机器的影响,使移动速度都一样,使固定步长乘以定时器刷新时间(TIMEOUTMSEC是毫秒,除以1000变成秒)(定时器刷新时间可以当做机器刷新时间)

当向前或向后移动时,把位置向量加减方向向量;左右移动时,把位置向量加减右向量(对右向进行量标准化是为了匀速移动);

运行程序,使用WASD键可以控制摄像机移动,摄像机一直看着前方

三、视角移动

通过鼠标改变方向向量m_cameraFront,来改变摄像机的视角

1、欧拉角

欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉在18世纪提出,一共有3中欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),如下图所示:

  • 第一张图片展示俯仰角,俯仰角是描述如何往上或往下看的角;
  • 第二张图片展示偏航角,偏航角是描述如何往左或往右看的角;
  • 第三张图片展示滚转角,滚转角是描述如何翻滚摄像机。
  • 把三个角结合起来就能够计算3D空间中任何的旋转向量。在摄像机系统,只关心俯仰角和偏航角。

上图中把斜边边长定义为1,领边的长度是

对边边长是

这样这样获得了能够得到x和y方向长度的通用公式,它们取决于所给的角度,使用它来计算方向向量的分量:

这个三角形看起来和前面的三角形很像,所以如果我们想象自己在xz平面,看向y轴,我们可以基于第一个三角形来计算它的长度/y方向的强度(我们往上或往下看多少),从图中可以看到对于一个给定俯仰角的y值等于sinθ:

cameraFront.setY(sin(pitch*PI/180)); //先把角度转为弧度

这里只更新了y值,仔细观察x和z分量也被影响l,从三角形中我们可以看到它们的值等于:

cameraFront.setX(cos(pitch*PI/180));
cameraFront.setZ(cos(pitch*PI/180));

看看我们能否为偏航角找到需要的分量:

就像俯仰角三角形一样,我们可以看到x分量取决于cos(yaw)的值,z值同样取决于偏航角的正弦值。把这个加到前面的值中,会得到基于俯仰角和偏航角的方向向量:

cameraFront.setX(cos(yaw*PI/180) * cos(pitch*PI/180));
cameraFront.setY(sin(pitch*PI/180));
cameraFront.setZ(sin(yaw*PI/180) * cos(pitch*PI/180));

这样就有了一个可以把俯仰角和偏航角转化为用来自由旋转视角的摄像机的3维方向向量了。

2、鼠标输入

在myopenglwidget.h中重载鼠标事件:

void mouseMoveEvent(QMouseEvent *event);

在myopenglwidget.cpp的构造函数的上面定义两个全局变量

float PI = 3.1415926; //π值
QPoint deltaPos; //上次鼠标的差值

在myopenglwidget.cpp的构造函数中捕获鼠标事件

setMouseTracking(true);

mouseMoveEvent()函数代码

void MyOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
    static float yaw = -90; //偏航角默认-90度
    static float pitch = 0;
    static QPoint lastPos(width()/2,height()/2); //上一帧鼠标的位置,初始化为屏幕中心
    
    auto currentPos = event->pos(); //当前帧的鼠标位置
    deltaPos = currentPos - lastPos; //当前帧和上一帧鼠标位置的偏移量
    lastPos = currentPos;
    
    float sensitivity = 0.1f; //灵敏度
    deltaPos *= sensitivity;
    
    yaw += deltaPos.x();
    pitch -= deltaPos.y();
    
    //限制范围
    if(pitch > 89.0f) pitch = 89.0f;
    if(pitch < -89.0f) pitch = -89.0f;

    //通过俯仰角和偏航角来计算得到真正的方向向量
    m_cameraFront.setX(cos(yaw*PI/180) * cos(pitch*PI/180));
    m_cameraFront.setY(sin(pitch*PI/180));
    m_cameraFront.setZ(sin(yaw*PI/180) * cos(pitch*PI/180));
    m_cameraFront.normalize();

    update();
}

运行程序,通过鼠标的移动可以控制摄像头向上、下、左或右转动:

3、缩放

之前的教程中视野(Field of View) 或fov定义了我们可以看到场景中多大的范围,当视野变小时,场景投影出来的空间就会减小,产生放大(Zoom In)的感觉。

在myopenglwidget.h中重载鼠标滚轮事件:

void wheelEvent(QWheelEvent *event);

在myopenglwidget.cpp的构造函数的上面定义一个全局变量

float fov = 45.0;

添加鼠标滚轮事件代码

void MyOpenGLWidget::wheelEvent(QWheelEvent *event)
{
    if(fov >= 1.0f && fov <= 75.0f)
        fov -= event->angleDelta().y()/120;//一步是120
    if(fov <= 1.0f) fov = 1.0f;
    if(fov >= 75.0f) fov = 75.0f;

    update();
}

运行程序,通过鼠标滚轮控制视野的放大或缩小:

四、摄像机类

把camera.h拷贝到项目目录下

#ifndef CAMERA_H
#define CAMERA_H

#include<QMatrix4x4>

#include <vector>

// 移动方向枚举量. 是一种抽象,以避开特定于窗口系统的输入方法
// 我们这里是WSAD
enum Camera_Movement {
    FORWARD,
    BACKWARD,
    LEFT,
    RIGHT
};

// 默认值
const float YAW         = -90.0f;
const float PITCH       =  0.0f;
const float SPEED       =  2.5f;
const float SENSITIVITY =  0.1f;
const float ZOOM        =  45.0f;


// 一个抽象的camera类,用于处理输入并计算相应的Euler角度、向量和矩阵,以便在OpenGL中使用
class Camera
{
public:
    // camera Attributes
    QVector3D Position;
    QVector3D Front;
    QVector3D Up;
    QVector3D Right;
    QVector3D WorldUp;
    // euler Angles
    float Yaw;
    float Pitch;
    // camera options
    float MovementSpeed;
    float MouseSensitivity;
    float Zoom;

    // constructor with vectors
    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) : Front(QVector3D(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
    {
        Position = position;
        WorldUp = up;
        Yaw = yaw;
        Pitch = pitch;
        updateCameraVectors();
    }
    // constructor with scalar values
    Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(QVector3D(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
    {
        Position = QVector3D(posX, posY, posZ);
        WorldUp = QVector3D(upX, upY, upZ);
        Yaw = yaw;
        Pitch = pitch;
        updateCameraVectors();
    }

    // returns the view matrix calculated using Euler Angles and the LookAt Matrix
    QMatrix4x4 GetViewMatrix()
    {
        QMatrix4x4 theMatrix;
        theMatrix.lookAt(Position, Position + Front, Up);
        return theMatrix;
    }

    // 处理从任何类似键盘的输入系统接收的输入。接受摄像机定义枚举形式的输入参数(从窗口系统中提取)
    void ProcessKeyboard(Camera_Movement direction, float deltaTime)
    {
        float velocity = MovementSpeed * deltaTime;
        if (direction == FORWARD)
            Position += Front * velocity;
        if (direction == BACKWARD)
            Position -= Front * velocity;
        if (direction == LEFT)
            Position -= Right * velocity;
        if (direction == RIGHT)
            Position += Right * velocity;
    }

    // 处理从鼠标输入系统接收的输入。需要x和y方向上的偏移值。
    void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true)
    {
        xoffset *= MouseSensitivity;
        yoffset *= MouseSensitivity;

        Yaw   += xoffset;
        Pitch += yoffset;

        // 确保当投球超出边界时,屏幕不会翻转
        if (constrainPitch)
        {
            if (Pitch > 89.0f)
                Pitch = 89.0f;
            if (Pitch < -89.0f)
                Pitch = -89.0f;
        }

        // 使用更新的Euler角度更新前、右和上矢量
        updateCameraVectors();
    }

    // 处理从鼠标滚轮事件接收的输入。仅需要在垂直车轮轴上输入
    void ProcessMouseScroll(float yoffset)
    {
        Zoom -= (float)yoffset;
        if (Zoom < 1.0f)
            Zoom = 1.0f;
        if (Zoom > 75.0f)
            Zoom = 75.0f;
    }

private:
    // 根据相机的(更新的)Euler角度计算前矢量
    void updateCameraVectors()
    {
        // calculate the new Front vector
        float PI=3.1415926;
        QVector3D front;
        front.setX(cos(Yaw*PI/180.0) * cos(Pitch*PI/180.0));
        front.setY( sin(Pitch*PI/180.0));
        front.setZ(sin(Yaw*PI/180.0) * cos(Pitch*PI/180.0));
        front.normalize();
        Front = front;
        // also re-calculate the Right and Up vector
        Right = QVector3D::crossProduct(Front, WorldUp);
        // 标准化向量,因为向上或向下看得越多,向量的长度就越接近0,这会导致移动速度变慢。
        Right.normalize();
        Up    = QVector3D::crossProduct(Right, Front);
        Up.normalize();
    }
};
#endif

右击项目名称,选择添加现有文件

选择camera.h

更改myopenglwidget.h中的代码,包含摄像机类头文件以及创建其类对象;删除掉QVector3D类型的变量

#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QTimer>
#include <QTime>
#include "camera.h"

class MyOpenGLWidget : public QOpenGLWidget,QOpenGLFunctions_3_3_Core
{
    Q_OBJECT

public:
    explicit MyOpenGLWidget(QWidget *parent = nullptr);
    ~MyOpenGLWidget();

protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();

    void keyPressEvent(QKeyEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void wheelEvent(QWheelEvent *event);

private slots:
    void onTimeout();

private:
    QOpenGLShaderProgram m_shaderProgram;
    QOpenGLTexture *m_textureWall;
    QOpenGLTexture *m_textureSmile;
    QOpenGLTexture *m_textureSmall;

    float mixValue = 0.5;

    QTimer m_timer;
    QTime m_time;

    Camera m_camera;
};

#endif // MYOPENGLWIDGET_H

更改myopenglwidget.cpp中的代码

#include "myopenglwidget.h"
#include <QDebug>
#include <QKeyEvent>

unsigned int VBO; //顶点缓冲对象
unsigned int VAO; //顶点数组对象
unsigned int EBO; //元素缓冲对象

#define TIMEOUTMSEC 100

QPoint deltaPos;

MyOpenGLWidget::MyOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    setFocusPolicy(Qt::StrongFocus);
    setMouseTracking(true);

    m_camera.Position = QVector3D(0,0,3.0);

    connect(&m_timer, &QTimer::timeout, this, &MyOpenGLWidget::onTimeout);
    m_timer.start(TIMEOUTMSEC); //100ms
    m_time.start(); //开始计时
}

MyOpenGLWidget::~MyOpenGLWidget()
{
    if(m_timer.isActive())
        m_timer.stop();

    makeCurrent();
    glDeleteBuffers(1, &VBO);
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &EBO);
    doneCurrent();
}

void MyOpenGLWidget::onTimeout()
{
    update();
}

void MyOpenGLWidget::initializeGL()
{
    //初始化OpenGL函数
    initializeOpenGLFunctions();

    //创建VBO,并赋予ID
    glGenBuffers(1, &VBO);
    //绑定VBO对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    //顶点数据
    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
    };
    //把顶点数据复制到显存中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //创建VAO对象,并赋予ID
    glGenVertexArrays(1, &VAO);
    //绑定VAO对象
    glBindVertexArray(VAO);

    //创建EBO对象,并赋予ID
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    unsigned int indices[] = {
                               0, 1, 3, //第一个三角形
                               1, 2, 3 //第二个三角形
                             };
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    //位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0); //开启VAO管理的第一个属性值

    //纹理属性
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1); //开启VAO管理的第三个属性值

    //解绑VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    //解绑VAO
    glBindVertexArray(0);

    //创建一个程序对象
    m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shapes.vert");
    m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shapes.frag");
    bool success = m_shaderProgram.link();
    if(!success)
        qDebug()<<"ERR:" << m_shaderProgram.log();

    m_shaderProgram.bind();
    m_shaderProgram.setUniformValue("vertexColor", 0.0, 1.0, 0.0, 1.0);

    m_textureWall = new QOpenGLTexture(QImage(":/images/wall.jpg").mirrored()); //mirrored消除镜像
    m_shaderProgram.setUniformValue("textureWall", 0); //把纹理单元传给片段着色器中的采样器

    m_textureSmile = new QOpenGLTexture(QImage(":/images/awesomeface.png").mirrored()); //mirrored消除镜像
    m_shaderProgram.setUniformValue("textureSmile", 1); //把纹理单元传给片段着色器中的采样器

    m_textureSmall = new QOpenGLTexture(QImage(":/images/small.png").mirrored()); //mirrored消除镜像
    m_shaderProgram.setUniformValue("textureSmall", 2); //把纹理单元传给片段着色器中的采样器

    m_shaderProgram.setUniformValue("mixValue", mixValue);
}

void MyOpenGLWidget::resizeGL(int w, int h)
{
    Q_UNUSED(w);

    Q_UNUSED(h);
    //glViewport(0, 0, w, h);
}

QVector<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 MyOpenGLWidget::paintGL()
{
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置墨绿色背景
    glEnable(GL_DEPTH_TEST); //打开深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清空

    float time = m_time.elapsed()/1000.0;

    //观察矩阵
    QMatrix4x4 view; //创建单位矩阵
    view = m_camera.GetViewMatrix();
    m_shaderProgram.setUniformValue("view", view);

    //投影矩阵
    QMatrix4x4 projection; //创建单位矩阵
    projection.perspective(m_camera.Zoom, (float)width()/height(), 0.1, 100);
    m_shaderProgram.setUniformValue("projection", projection);

    //绘制
    m_shaderProgram.bind(); //激活程序对象
    glBindVertexArray(VAO); //绑定VAO
    m_textureWall->bind(0); //绑定激活纹理单元0
    m_textureSmile->bind(1); //绑定激活纹理单元1
    m_textureSmall->bind(2); //绑定激活纹理单元2

    foreach(auto item, cubePositions)
    {
        //模型矩阵
        QMatrix4x4 model; //创建单位矩阵
        model.translate(item); //移动
        model.rotate(time, 1.0f, 1.f, 0.0f); //绕向量(1.0f, 1.f, 0.0f)旋转
        m_shaderProgram.setUniformValue("model", model); //传给顶点着色器
        glDrawArrays(GL_TRIANGLES, 0, 36); //绘图
    }
}

void MyOpenGLWidget::keyPressEvent(QKeyEvent *event)
{
    //移动步长
    //float cameraSpeed = 2.5 * TIMEOUTMSEC / 1000.0;
    float deltaTime = TIMEOUTMSEC/1000.0;

    switch (event->key())
    {
        case Qt::Key_Up: mixValue += 0.1; break;
        case Qt::Key_Down: mixValue -= 0.1; break;
        case Qt::Key_W: m_camera.ProcessKeyboard(FORWARD, deltaTime); break;
        case Qt::Key_S: m_camera.ProcessKeyboard(BACKWARD, deltaTime); break;
        case Qt::Key_D: m_camera.ProcessKeyboard(RIGHT, deltaTime); break;
        case Qt::Key_A: m_camera.ProcessKeyboard(LEFT, deltaTime); break;
        default: break;
    }

    if(mixValue > 1.0)
        mixValue = 1.0;

    if(mixValue < 0.0)
        mixValue = 0.0;

    makeCurrent();
    m_shaderProgram.setUniformValue("mixValue", mixValue);
    doneCurrent();
    update();

    QOpenGLWidget::keyPressEvent(event);
}

void MyOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
    static QPoint lastPos(width()/2,height()/2); //上一帧鼠标的位置,初始化为屏幕中心

    auto currentPos = event->pos(); //当前帧的鼠标位置
    deltaPos = currentPos - lastPos; //当前帧和上一帧鼠标位置的偏移量
    lastPos = currentPos;

    m_camera.ProcessMouseMovement(deltaPos.x(), -deltaPos.y());

    update();
}

void MyOpenGLWidget::wheelEvent(QWheelEvent *event)
{
    m_camera.ProcessMouseScroll(event->angleDelta().y()/120);

    update();
}

运行程序,键盘WASD控制摄像机移动,鼠标控制摄像机转向,鼠标滚轮控制缩放

注:观看OpenGL中文官网(https://learnopengl-cn.github.io/)和阿西拜的现代OpenGL入门(https://ke.qq.com/course/3999604#term_id=104150693)学习OpenGL

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值