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));
其中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);
主要代码:(无滚转角)
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; }