[OpenGL] 基本相机移动旋转控制

前言

        首先吐槽一下学校的计算机图形学课程是以OGL入门的,以至于早期的代码积累都是在OGL上的,直到最近才克服自己的惰性,开始计划升级自己的技术栈。在此期间也认真考虑过要不要升级到DirectX11,花了几个月大致扫完了一本相关的书,但目前看到dx接口命名风格依旧有着浓厚的win32气息就动力不足:),所以打算先把步子迈的小一点,先把OGL升级到高版本的OpenGL,以免自己对图形学产生抵触的心理。

        在做升级工作之前,先看了几本书做铺垫,之后做了几个简单的shader demo来提升一下自信。刚开始学OpenGL时,写的相机控制代码还是充满一堆三角函数的,虽然能跑但是看着非常乱,算起来也很痛苦。大学专业课也没有线代,导致一直没有建立起使用矩阵的概念。使用矩阵运算的技能点还是我在工作之后点的,在任务紧迫的排期下,自己强行把结果求出来的。现在,先简单升级了一下相机控制的代码,虽然用的还是欧拉角啊哈哈。

基本功能

环境:Qt + OpenGL

键盘WASD移动相机
右键旋转相机
滚轮推进相机 (zoom)

 

 

 

 

相机移动

          相机的视图矩阵主要是维护眼睛位置和视点位置,移动时将眼睛和视点位置沿着相对于视线的特定方向向量移动即可。假设步长为step,视线方向为ViewDir,那么向前运动的计算为:

lookAtLocation -= step * ViewDir;    
​​​​​​​eyeLocation -= step * ViewDir;

        这里取+=或者-=和viewdir的求法有点关系(从眼睛发出还是指向眼睛)。

        向左运动的计算为:

QVector3D upDir(0, 1, 0);
QVector3D U = QVector3D::crossProduct(upDir, ViewDir);

lookAtLocation -= step * U;
eyeLocation -= step * U;

                                   移动相机

相机旋转

         相机旋转主要是维护一个视线向量,通过旋转视线向量,然后求出变换后的眼睛位置或者视点位置。这里有两种情况:

       (1) 一般游戏里使用的,固定眼睛位置,视点位置发生变化。类似于人的头部左右上下旋转看到的景象。此时根据眼睛位置和变换后的视线向量来求解视点位置即可。(之后的例子按此来计算)

       (2) 有些软件中可能使用到的,固定视点位置,眼睛位置发生变化。类似于绕着一个物体360度旋转观察。此时根据视点位置和变换后的视线向量来求解眼睛位置即可。

                  旋转相机环绕四周(情况1)

代码

        以下代码有更新,主要更新是将前后左右的相机移动改成不受当前视线的高度(即y方向不参与运算)影响的。

cameramanager.h

#ifndef CAMERAMANAGER_H
#define CAMERAMANAGER_H
#include <QVector3D>
#include <QMatrix4x4>

class Camera
{
private:
    QVector3D DefaultViewDir;
    QVector3D ViewDir = QVector3D(0, 0, 1);
    float rotateX = 0.0f;
    float rotateY = 0.0f;

    QVector3D eyeLocation = QVector3D(0, 1, 4);
    QVector3D lookAtLocation = QVector3D(0, 0, 0);
    QMatrix4x4 viewMatrix;

    void MoveStep(QVector3D& dir, QVector3D step);
    Camera();

public:
    static Camera* camera;
    static Camera* Inst() {
        if(!camera) {
            camera = new Camera();
        }
        return camera;
    }

    void SetRotateXY(float x, float y);
    float GetRotateX() { return rotateX; }
    float GetRotateY() { return rotateY; }

    void MoveLeft(float step);
    void MoveRight(float step);
    void MoveFront(float step);
    void MoveBack(float step);
    void MoveUp(float step);
    void MoveDown(float step);
    void Zoom(float step);

    void UpdateViewMatrix();
    const QMatrix4x4& GetViewMatrix() { return viewMatrix; }
    const QVector3D& GetCameraPos() { return eyeLocation; }
    const QVector3D& GetViewDir() { return ViewDir; }
};

#endif // CAMERAMANAGER_H

cameramanager.cpp

#include "cameramanager.h"
#include "rendercommon.h"
#include <algorithm>

Camera* Camera::camera = nullptr;

Camera::Camera()
{
    UpdateViewMatrix();

    ViewDir = eyeLocation - lookAtLocation;
    DefaultViewDir  = ViewDir;
}

void Camera::SetRotateXY(float x, float y)
{
    rotateX = x;
    rotateY = y;

    QMatrix4x4 rotation;
    rotation.rotate(rotateX, QVector3D(1,0,0));
    rotation.rotate(rotateY, QVector3D(0,1,0));

    ViewDir = DefaultViewDir * rotation;
    lookAtLocation = eyeLocation - ViewDir;
}

void Camera::UpdateViewMatrix()
{
    QVector3D upDir(0, 1, 0);

    QVector3D N = eyeLocation - lookAtLocation;
    QVector3D U = QVector3D::crossProduct(upDir, N);
    QVector3D V = QVector3D::crossProduct(N, U);

    N.normalize();
    U.normalize();
    V.normalize();

    viewMatrix.setRow(0, {U.x(), U.y(), U.z(), -QVector3D::dotProduct(U, eyeLocation)}); // x
    viewMatrix.setRow(1, {V.x(), V.y(), V.z(), -QVector3D::dotProduct(V, eyeLocation)}); // y
    viewMatrix.setRow(2, {N.x(), N.y(), N.z(), -QVector3D::dotProduct(N, eyeLocation)}); // z
    viewMatrix.setRow(3, {0, 0, 0, 1});
}

void Camera::MoveStep(QVector3D& dir, QVector3D step)
{
    dir.setX(dir.x() + step.x());
    dir.setZ(dir.z() + step.z());
}

void Camera::MoveLeft(float step)
{
    QVector3D upDir(0, 1, 0);
    QVector3D U = QVector3D::crossProduct(upDir, ViewDir);
    MoveStep(lookAtLocation, -step * U);
    MoveStep(eyeLocation, -step * U);

    UpdateViewMatrix();
}

void Camera::MoveRight(float step)
{
    QVector3D upDir(0, 1, 0);
    QVector3D U = QVector3D::crossProduct(upDir, ViewDir);

    MoveStep(lookAtLocation, step * U);
    MoveStep(eyeLocation, step * U);

    UpdateViewMatrix();
}

void Camera::MoveFront(float step)
{
    MoveStep(lookAtLocation, -step * ViewDir);
    MoveStep(eyeLocation, -step * ViewDir);

    UpdateViewMatrix();
}

void Camera::MoveBack(float step)
{
    MoveStep(lookAtLocation, step * ViewDir);
    MoveStep(eyeLocation, step * ViewDir);

    UpdateViewMatrix();
}

void Camera::MoveUp(float step)
{
    eyeLocation.setY(eyeLocation.y() + step);
    lookAtLocation.setY(lookAtLocation.y() + step);
    UpdateViewMatrix();
}

void Camera::MoveDown(float step)
{
    eyeLocation.setY(eyeLocation.y() - step);
    lookAtLocation.setY(lookAtLocation.y() - step);
    UpdateViewMatrix();
}

void Camera::Zoom(float step)
{
    lookAtLocation -= step * ViewDir;
    eyeLocation -= step * ViewDir;

    UpdateViewMatrix();
}

mainwidget.h

#ifndef MAINWIDGET_H
#define MAINWIDGET_H


#include <QOpenGLWidget>

#include "geometryengine.h"
#include "rendercommon.h"
#include "skybox.h"
#include <QOpenGLFunctions>

class GeometryEngine;


class MainWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT

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

protected:
    void mousePressEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
    void mouseReleaseEvent(QMouseEvent* event) override;
    void wheelEvent(QWheelEvent* event) override;

    void keyPressEvent(QKeyEvent* event) override;
    void initializeGL() override;
    void resizeGL(int w, int h) override;
    void paintGL() override;


private:
    SkyBox skyBox;
    float mx = 0,my = 0,ax = 0,ay = 0;
    bool isDown = false;
};

#endif // MAINWIDGET_H
#include "mainwidget.h"
#include "cameramanager.h"
#include <QMouseEvent>
#include <math.h>

MainWidget::MainWidget(QWidget *parent) :
    QOpenGLWidget(parent)
{

}

MainWidget::~MainWidget()
{

}

void MainWidget::keyPressEvent(QKeyEvent* event)
{
    const float step = 0.6f;
    if(event->key() == Qt::Key_W)
    {
        Camera::Inst()->MoveFront(step);
        update();
    }
    else if(event->key() == Qt::Key_S)
    {
        Camera::Inst()->MoveBack(step);
        update();
    }
    else if(event->key() == Qt::Key_A)
    {
        Camera::Inst()->MoveLeft(step);
        update();
    }
    else if(event->key() == Qt::Key_D)
    {
        Camera::Inst()->MoveRight(step);
        update();
    }
    else if(event->key() == Qt::Key_Q)
    {
        Camera::Inst()->MoveUp(step);
        update();
    }
    else if(event->key() == Qt::Key_E)
    {
        Camera::Inst()->MoveDown(step);
        update();
    }
}

void MainWidget::initializeGL()
{
    initializeOpenGLFunctions();

    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    skyBox.Create(":/front.jpg",":/back.jpg",":/left.jpg",":/right.jpg",":/top.jpg",":/bottom.jpg");

}


void MainWidget::mousePressEvent(QMouseEvent *e)
{
    isDown = true;
    float x = e->x();
    float y = e->y();
    mx = x;
    my = y;

    update();
}

void MainWidget::mouseMoveEvent(QMouseEvent *e)
{
    if(isDown)
    {
        float x = e->x();
        float y = e->y();

        ay += (x - mx) / 5.0f;
        mx = x;

        ax += (y - my) / 5.0f;
        my = y;

        // clamp(ax,-70,70)
        if(ax >= 70) ax = 70;
        else if(ax <= -70) ax = -70;

        Camera::Inst()->SetRotateXY(ax, ay);
        Camera::Inst()->UpdateViewMatrix();
        update();
    }
}

void MainWidget::mouseReleaseEvent(QMouseEvent *e)
{
    isDown = false;
}

void MainWidget::wheelEvent(QWheelEvent* event)
{
    float numDegrees = (float)event->delta() / 50;
    Camera::Inst()->Zoom(numDegrees);
    update();
}

void MainWidget::resizeGL(int w, int h)
{
    float aspect = float(w) / float(h ? h : 1);
    const qreal zNear = 2.0, fov = 45.0;
    RenderCommon::Inst()->GetProjectMatrix().setToIdentity();
    RenderCommon::Inst()->GetProjectMatrix().perspective(fov, aspect, zNear, RenderCommon::Inst()->GetZFarPlane());
}

void MainWidget::paintGL()
{
    glClearColor(0,0,0,1);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    skyBox.Render();
}

有几个文件没放,主要是绘制东西的代码,应该不是很重要。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值