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_textureW
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值