QT+OpenGL 摄像机

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

QT+OpenGL 摄像机

本篇完整工程见gitee:QtOpenGL 对应点的tag,由turbolove提供技术支持,您可以关注博主或者私信博主


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


摄像机原理

方向向量(Direction Vector)并不是最好的名字,因为它实际上指向从它到目标向量的相反方向


生成view矩阵的大致步骤

// 摄像机位置
QVector3D cameraPos = QVector3D(0.0, 0.0, 2.0);
// 摄像机方向
QVector3D cameraTarget = QVector3D(0.0, 0.0, 0.0);
QVector3D cameraDirection = QVector3D(cameraPos - cameraTarget);
cameraDirection.normalize();
// 右轴
QVector3D up = QVector3D(0.0, 1.0, 0.0);
QVector3D cameraRight = QVector3D::crossProduct(up, cameraDirection);
cameraRight.normalize();
// 上轴
QVector3D cameraUp = QVector3D::crossProduct(cameraDirection, cameraRight);
cameraRight.normalize();

使用QT的矩阵

QMatrix4x4 view;
view.lookAt(cameraPos, cameraTarget, QVector3D(0.0, 1.0, 0.0));

屏幕截图 2023-08-03 150933.png

其中R是右向量,U是上向量,D是方向向量P是摄像机位置向量

const float radius = 10.0;
float time = m_time.elapsed()/1000.0;
float camX = sin(time) *radius;
float camZ = cos(time) *radius;
view.lookAt(QVector3D(camX, 0.0, camZ), QVector3D(0.0, 0.0, 0.0), QVector3D(0.0, 1.0, 0.0));

通过上面的改造我们能实现一个沿着某个轴的旋转。


摄像机自由移动


摄像机移动

如果我们总是盯着原点,不太礼貌且枯燥,我们需要向前看。

cameraFront = QVector3D(0.0, 0.0, -1.0);
view.lookAt(cameraPos, cameraPos + cameraFront, QVector3D(0.0, 1.0, 0.0));
 switch (event->key())
 {
     case Qt::Key_W:
         cameraPos += cameraSpeed * cameraFront;
         break;
     case Qt::Key_A:
         cameraPos -= cameraSpeed * cameraRight;
         break;
     case Qt::Key_S:
         cameraPos -= cameraSpeed * cameraFront;
         break;
     case Qt::Key_D:
         cameraPos += cameraSpeed * cameraRight;
         break;
 }

摄像机旋转

为了能够改变视角,我们需要根据鼠标的输入改变canmeraFront向量


欧拉角: 俯仰角(Pitch), 偏航角(Yaw), 滚转角(Roll);

5c99c0e827f84c66a052e5eca78d4241.png

主要代码:(无滚转角)

void TurboOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
    static float yaw = -90;
    static float pitch = 0;
    static QPoint lastPos(width()/2, height()/2);
    auto curPos = event->pos();
    auto deltaPos = curPos - lastPos;
    lastPos = curPos;
    float sensitivity = 0.1f;
    deltaPos *= sensitivity;
    yaw += deltaPos.x();
    pitch -= deltaPos.y();
    float pi = 3.1415926;
    if(pitch > 89.0f) pitch = 89.0f;
    if(pitch < -89.0f) pitch = -89.0f;
    cameraFront.setX(cos(yaw*pi/180) * cos(pitch * pi/ 180));
    cameraFront.setY(sin(pitch*pi/180));
    cameraFront.setZ((yaw*pi/180) * cos(pitch*pi/180));
    cameraFront.normalize();
    update();
}

摄像机缩放

视野定义了可以看到场景中的多大范围。当视野变小时候,场景投影出来的空间就会减小,产生放大的感觉。我们会使用鼠标滚轮来放大物体。

QMatrix4x4 projection;
projection.perspective(fov, float(width() / height()), 0.1, 100);
shader_program_.setUniformValue("projection", projection);
void TurboOpenGLWidget::wheelEvent(QWheelEvent *event)
{
    if(fov >= 1.0 && fov <= 75.0)
        fov -= event->angleDelta().y()/120;
    if(fov <=1.0 ) fov = 1.0;
    if(fov >=75.0 ) fov = 75.0;
    update();
}

Camera封装

上述代码看看就可以,我们在实际使用的时候是不会这么写代码的,我们需要将摄像机封装成一个摄像机类,这里提供该类的基础版本的设计。


camera.h:

#ifndef QTOPENGL_CAMERA_H
#define QTOPENGL_CAMERA_H
#include<QMatrix4x4>
#include <vector>
#include <QOpenGLShaderProgram>
#define PI 3.141592653589793638
// 移动方向枚举量. 是一种抽象,以避开特定于窗口系统的输入方法
// 我们这里是WSAD
enum Camera_Movement {
    emFORWARD,
    emBACKWARD,
    emLEFT,
    emRIGHT
};
// 默认值
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:
    // constructor with vectors
    explicit 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);
    // constructor with scalar values
    Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch);
    // returns the view matrix calculated using Euler Angles and the LookAt Matrix
    QMatrix4x4 getViewMatrix();
    // 处理从任何类似键盘的输入系统接收的输入。接受摄像机定义枚举形式的输入参数(从窗口系统中提取)
    void processKeyboard(Camera_Movement direction, float deltaTime);
    // 处理从鼠标输入系统接收的输入。需要x和y方向上的偏移值。
    void processMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true);
    // 处理从鼠标滚轮事件接收的输入。仅需要在垂直车轮轴上输入
    void processMouseScroll(float yoffset);
    void setPosition(const QVector3D &position);
    QVector3D getPosition();
    void setFront(const QVector3D &front);
    QVector3D getFront();
    void setUp(const QVector3D &up);
    QVector3D getUp();
    void setRight(const QVector3D &right);
    QVector3D getRight();
    void setWorldUp(const QVector3D &worldUp);
    QVector3D getWorldUp();
    void setYaw(const float &yaw);
    float getYaw() const;
    void setPitch(const float &pitch);
    float getPitch() const;
    void setMovementSpeed(const float &movementSpeed);
    float getMovementSpeed() const;
    void setMouseSensitivity(const float &mouseSensitivity);
    float getMouseSensitivity() const;
    void setZoom(const float &zoom);
    float getZoom() const;
private:
    // 根据相机的(更新的)Euler角度计算前矢量
    void updateCameraVectors();
private:
    // 摄像机位置
    QVector3D m_position;
    // 前后移动值
    QVector3D m_front;
    // 上下移动值
    QVector3D m_up;
    // 左右移动值
    QVector3D m_right;
    QVector3D m_worldUp;
    // euler Angles
    float m_yaw;
    float m_pitch;
    // camera options
    float m_movementSpeed;
    float m_mouseSensitivity;
    float m_zoom;
};
#endif //QTOPENGL_CAMERA_H

camera.cpp:

#include "camera.h"
Camera::Camera(QVector3D position, QVector3D up, float yaw, float pitch)
        : m_front(QVector3D(0.0f, 0.0f, -1.0f)), m_movementSpeed(SPEED), m_mouseSensitivity(SENSITIVITY), m_zoom(ZOOM)
{
    m_position = position;
    m_worldUp = up;
    m_yaw = yaw;
    m_pitch = pitch;
    updateCameraVectors();
}
Camera::Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch)
        : m_front(QVector3D(0.0f, 0.0f, -1.0f)), m_movementSpeed(SPEED), m_mouseSensitivity(SENSITIVITY), m_zoom(ZOOM)
{
    m_position = QVector3D(posX, posY, posZ);
    m_worldUp = QVector3D(upX, upY, upZ);
    m_yaw = yaw;
    m_pitch = pitch;
    updateCameraVectors();
}
QMatrix4x4 Camera::getViewMatrix()
{
    QMatrix4x4 theMatrix;
    theMatrix.lookAt(m_position, m_position + m_front, m_up);
    return theMatrix;
}
void Camera::processKeyboard(Camera_Movement direction, float deltaTime)
{
    float velocity = m_movementSpeed * deltaTime;
    if (direction == emFORWARD)
        m_position += m_front * velocity;
    if (direction == emBACKWARD)
        m_position -= m_front * velocity;
    if (direction == emLEFT)
        m_position -= m_right * velocity;
    if (direction == emRIGHT)
        m_position += m_right * velocity;
}
void Camera::processMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch)
{
    xoffset *= m_mouseSensitivity;
    yoffset *= m_mouseSensitivity;
    m_yaw   += xoffset;
    m_pitch += yoffset;
    // 确保当投球超出边界时,屏幕不会翻转
    if (constrainPitch)
    {
        if (m_pitch > 89.0f)
            m_pitch = 89.0f;
        if (m_pitch < -89.0f)
            m_pitch = -89.0f;
    }
    // 使用更新的Euler角度更新前、右和上矢量
    updateCameraVectors();
}
void Camera::processMouseScroll(float yoffset)
{
    m_zoom -= (float)yoffset;
    if (m_zoom < 1.0f)
        m_zoom = 1.0f;
    if (m_zoom > 75.0f)
        m_zoom = 75.0f;
}
void Camera::updateCameraVectors()
{
    // calculate the new Front vector
    QVector3D front;
    front.setX(cos(m_yaw*PI/180.0) * cos(m_pitch*PI/180.0));
    front.setY( sin(m_pitch*PI/180.0));
    front.setZ(sin(m_yaw*PI/180.0) * cos(m_pitch*PI/180.0));
    front.normalize();
    m_front = front;
    // also re-calculate the Right and Up vector
    m_right = QVector3D::crossProduct(m_front, m_worldUp);
    // 标准化向量,因为向上或向下看得越多,向量的长度就越接近0,这会导致移动速度变慢。
    m_right.normalize();
    m_up = QVector3D::crossProduct(m_right, m_front);
    m_up.normalize();
}
QVector3D Camera::getPosition()
{
    return m_position;
}
void Camera::setPosition(const QVector3D &position)
{
    m_position = position;
}
void Camera::setFront(const QVector3D &front)
{
    m_front = front;
}
QVector3D Camera::getFront()
{
    return m_front;
}
void Camera::setUp(const QVector3D &up)
{
    m_up = up;
}
QVector3D Camera::getUp()
{
    return m_up;
}
void Camera::setRight(const QVector3D &right)
{
    m_right = right;
}
QVector3D Camera::getRight()
{
    return m_right;
}
void Camera::setWorldUp(const QVector3D &worldUp)
{
    m_worldUp = worldUp;
}
QVector3D Camera::getWorldUp()
{
    return m_worldUp;
}
void Camera::setYaw(const float &yaw)
{
    m_yaw = yaw;
}
float Camera::getYaw() const
{
    return m_yaw;
}
void Camera::setPitch(const float &pitch)
{
    m_pitch = pitch;
}
float Camera::getPitch() const
{
    return m_pitch;
}
void Camera::setMovementSpeed(const float &movementSpeed)
{
    m_movementSpeed = movementSpeed;
}
float Camera::getMovementSpeed() const
{
    return m_movementSpeed;
}
void Camera::setMouseSensitivity(const float &mouseSensitivity)
{
    m_mouseSensitivity = mouseSensitivity;
}
float Camera::getMouseSensitivity() const
{
    return m_mouseSensitivity;
}
void Camera::setZoom(const float &zoom)
{
    m_zoom = zoom;
}
float Camera::getZoom() const
{
    return m_zoom;
}


目录
相关文章
|
4月前
QT4.7版本的OPENGL的3D旋转模型例子
QT4.7版本的OPENGL的3D旋转模型例子
|
9月前
QT+OpenGL鼠标操作和模型控制
光线追踪法 从鼠标投射 3D 射线, 通过摄像机,进入场景,然后检查该光线是否与某个对象相交。
121 0
|
2月前
|
Linux API iOS开发
【Qt 渲染引擎】一文带你了解qt的三种 渲染引擎,包括栅格引擎(Raster)、OpenGL 和本地绘图系统
【Qt 渲染引擎】一文带你了解qt的三种 渲染引擎,包括栅格引擎(Raster)、OpenGL 和本地绘图系统
37 0
|
2月前
|
机器学习/深度学习 API vr&ar
Qt, OpenCV与OpenGL协同作战:图像处理与三维图形界面的完美结合
Qt, OpenCV与OpenGL协同作战:图像处理与三维图形界面的完美结合
118 4
|
2月前
Qt+OpenGL 打砖块游戏
Qt+OpenGL 打砖块游戏
21 0
|
3月前
|
数据可视化 API vr&ar
qt“五彩斑斓“ opengl
qt“五彩斑斓“ opengl
|
9月前
|
异构计算
QT+OpenGL高级数据和高级GLSL
● OpenGL中的缓冲区 对象管理特定的GPU内存 ● 在将缓冲区绑定到特定的缓冲区目标时候赋予它意义 ● OpenGL在内部会保存每个目标(缓冲区)的引用,并且根据目标以不同的方式处理缓冲区。
98 0
QT+OpenGL高级数据和高级GLSL
|
9月前
|
存储 异构计算
QT+OpenGL深度测试
在前面的文章中,我们渲染了一个3D箱子,并且运用了深度缓冲来防止阻挡的面渲染到其他面的前面。 现在大部分的GPU都提供一个叫做提前深度测试(Early Depth Testing)的硬件特性。提前深度测试允许深度测试在片段着色器之前运行。只要我们清楚一个片段永远不会是可见的(它在其他物体之后),我们就能提前丢弃这个片段。
72 0
|
9月前
|
存储
QT+OpenGL开始3D
顶点坐标起始于局部空间,它在之后会变为世界坐标,观察坐标,裁减坐标,并最后以屏幕坐标的形式结束。
55 0
|
9月前
|
算法
QT+OpenGL高级光照 Blinn-Phong和Gamma校正
冯氏光照:视线与反射方向之间的夹角不小于90度,镜面光分量会变成0.0(不是很合理,会有清晰的分界线) Blinn-Phone模型采用了半程向量,即光线与视线夹角一般方向上的一个单位向量。当半程向量与法线向量越接近,镜面光分量就越大。
62 0