QT+OpenGL四之相机的移动和旋转

相机移动主要针对于lookAt()矩阵,lookAt需要三个参数1.相机的位置2.相机看向的位置3.相机的纵轴向量。相机的移动就围绕着这3个参数进行。其实我们可以简单的传入不同参数来改变看向的位置,但是要是实现第一视觉的功能还需要算法支持。
首先掌握数学知识:

1.向量的+/-运算

A+B=(A.X+B.X,A.Y+B.Y,A.Z+B.Z)
A-B=(A.X-B.X,A.Y-B.Y,A.Z-B.Z)

2.向量与矢量积

A*scale=(A.X*scale,A.Y*scale,A.Z*scale)

3.向量的模长

|A|=√(A.X²+A.Y²+A.Z²)

4.向量的点积又称内积

A*B=(A.X*B.X+A.Y*B.Y+A.Z*B.Z)
A*B= |A| * |B| * cosθ
物理意义求出夹角(欧拉角)

5.向量叉乘

A×B=(A.Y*B.Z-A.Z*BY,A.Z*B.X-A.X*B.Z,A.X*B.Y-A.Y*B.X)
A×B=|A| * |B| * sinθ
物理意义获得第三个与A,B都垂直的向量,用于构建坐标系

6.向量的单位化

A.Normalize=(A.X/|A|,A.Y/|A|,A.Z/|A|)

7.向量运算类

.h

#pragma once
class OperatorVecter
{
public:
    float x, y, z;
    OperatorVecter(float x, float y, float z);
    OperatorVecter operator+(OperatorVecter& v);
    OperatorVecter operator-(OperatorVecter& v);
    OperatorVecter operator*(float scale);//向量矢量与标量的乘积
    float operator*(OperatorVecter& v);//向量的内积
    OperatorVecter operator=(OperatorVecter& v);
    float Magnitude();//向量模长
    void Normalize();
};
OperatorVecter cross(OperatorVecter v1, OperatorVecter v2);//向量的叉乘

.cpp

#include "stdafx.h"
#include "OperatorVecter.h"
OperatorVecter::OperatorVecter(float x, float y, float z) {
    this->x = x;
    this->y = y;
    this->z = z;
}
OperatorVecter OperatorVecter::operator+(OperatorVecter& v) {
    return OperatorVecter(x+v.x,y+v.y,z+v.z);
}
OperatorVecter OperatorVecter::operator-(OperatorVecter& v) {
    return OperatorVecter(x - v.x, y - v.y, z - v.z);
}
OperatorVecter OperatorVecter::operator*(float scale) {
    return OperatorVecter(x * scale, y * scale, z * scale);
}
float OperatorVecter::operator*(OperatorVecter& v) {
    return x * v.x + y * v.y + z * v.z;
}
OperatorVecter OperatorVecter::operator=(OperatorVecter& v){
    x = v.x;
    y = v.y;
    z = v.z;
}
float OperatorVecter::Magnitude() {
    return sqrtf(x * x + y * y + z * z);
}
void OperatorVecter::Normalize() {
    float length = Magnitude();
    x /= length;
    y /= length;
    z /= length;
}
OperatorVecter cross(OperatorVecter v1, OperatorVecter v2) {
    return OperatorVecter(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x);
}

8.好了上面的数学知识了解到了我们就可以继续构建相机类

其实qt有自己的向量类QVector3D,用法和上面自己写的差不多。

(1.概念:

上章也说了初始位置在location(0,0,0)视点viewPoint(0,0,-1)相机纵轴为(0,1,0)

image.png

这里注意y轴其实是取世界坐标系的y坐标轴,他是不变的。所以这个坐标系是个伪坐标系,它不保证z轴和y轴一定要垂直。因为我们只关注最后看向的点,上章讲得很明确,相机实际上是不移动的(通过期望相机位置和视点影响世界中物体的位置达到看向不同位置的目的)见上一章视图矩阵,这也是y轴和相机的位置不变的原因。于是我们就注重视点,针对视点来运算,反复构建这个伪坐标系,下面就来阐述这样做的原因:
左右旋转:
左右旋转好理解,我们只需绕y轴旋转,那如何绕y轴旋转喃?相机不是不会动吗?实际上我们只需要知道y轴的坐标和想要旋转的角度就能通过算法计算出新的视点,上一章说的lookat()矩阵不就需要这个新的视点吗,如果还想移动他也需要期望相机的位置我们传入不就可以了!这里说的算法后面会讲,因为上下旋转和左右旋转用的都是同一个算法。
上下旋转:
上下旋转其实多了点步骤,要根据上图构建这个伪坐标系,这时我们能看见x轴,所以我们绕x轴旋转,同样相机是不动的,我们需要的是x轴的坐标和想要旋转的角度,注意x轴是计算出来的会变,同样传递给lookat矩阵。lookat矩阵的构建过程才是在构建当前的真正的3维坐标系,但这个坐标系是基于视线(相机位置和视点算出来的向量)来计算的也是假想出来的。(相机位置,和相机坐标系雷打不动哈哈!)
奉上计算新视点位置的算法:
tempX(Cosθ + x * x * (1 - Cosθ ), x * y * (1 - Cosθ ) - z * Sinθ, x * z * (1 - Cosθ ) + y * Sinθ)
newDirection.x = tempX * viewDirection
tempY(x * y * (1 - Cosθ ) + z * Sinθ, Cosθ + y * y * (1 - Cosθ ), y * z * (1 - Cosθ ) - x * Sinθ)
newDirection.y = tempY * viewDirection
tempZ(x * z * (1 - Cosθ ) - y * Sinθ, y * z * (1 - Cosθ ) + x * Sinθ, Cosθ + z * z * (1 - Cosθ ))
newDirection.z = tempZ * viewDirection
newViewPoint = location + newDirection
viewDirection:原来的视线方向可以用viewPoint-locaton获得
不要问为啥,问就是特性。
有了视点,y轴坐标和location当前位置是不是传入lookat()矩阵结果就出来了相机也旋转了。

(2.上代码:

Camera.h

#pragma once
#include "OperatorVecter.h"
class Camera
{
public:
    Camera();
    OperatorVecter location, viewPoint, worldY;
    void RotateView(float angle,float  currentAxis_x,float currentAxis_y,float currentAxis_z);//旋转算法
    void Yaw(float angle);//左右旋转摄像机
    void Pitch(float angle);//上下旋转摄像机
};

Camera.cpp

#include "stdafx.h"
#include "Camera.h"
Camera::Camera() :location(0.0,0.0,0.0),viewPoint(0.0,0.0,-1.0),worldY(0.0,1.0,0.0){

}
void Camera::RotateView(float angle, float  currentAxis_x, float currentAxis_y, float currentAxis_z) {
    OperatorVecter viewDirection = viewPoint - location;
    OperatorVecter newViewDirection(0.0,0.0,0.0);
    float cos = cosf(angle);
    float sin = sinf(angle);
    OperatorVecter tempX(cos + currentAxis_x * currentAxis_x * (1 - cos), currentAxis_x * currentAxis_y * (1 - cos) - currentAxis_z * sin, currentAxis_x * currentAxis_z * (1 - cos) + currentAxis_y * sin);
    newViewDirection.x = tempX * viewDirection;
    OperatorVecter tempY(currentAxis_x * currentAxis_y * (1 - cos) + currentAxis_z * sin, cos + currentAxis_y * currentAxis_y * (1 - cos), currentAxis_y * currentAxis_z * (1 - cos) - currentAxis_x * sin);
    newViewDirection.y = tempY * viewDirection;
    OperatorVecter tempZ(currentAxis_x * currentAxis_z * (1 - cos) - currentAxis_y * sin, currentAxis_y * currentAxis_z * (1 - cos) + currentAxis_x * sin, cos + currentAxis_z * currentAxis_z * (1 - cos));
    newViewDirection.z = tempZ * viewDirection;
    viewPoint = location + newViewDirection;
}
void Camera::Yaw(float angle) {
    RotateView(angle, worldY.x, worldY.y, worldY.z);
}
void Camera::Pitch(float angle) {
    OperatorVecter viewDirection = viewPoint - location;
    viewDirection.Normalize();
    OperatorVecter rightDirection = cross(viewDirection, worldY);//构建伪坐标系x轴
    rightDirection.Normalize();
    RotateView(angle, rightDirection.x, rightDirection.y, rightDirection.z);
}

先让相机旋转起来

OpenglWegdit中
章节是连续的接着前面纹理绑定的代码
因为QT中事件都被很好的封装了,所以鼠标事件直接重写


image.png

使用:

void MyOpenglWegdit::mousePressEvent(QMouseEvent* event) {
    if (event->buttons() == Qt::RightButton) {
       mousePoint = event->globalPos();
       tempPoint = mousePoint;
       this->setCursor(Qt::BlankCursor); //隐藏鼠标 
       isMousePressed = true;
    }
}
void MyOpenglWegdit::mouseMoveEvent(QMouseEvent* event) {
    if (isMousePressed == true) {
     float deltX= (float)(event->globalPos().x() - tempPoint.x());
     float deltY = (float)(event->globalPos().y() - tempPoint.y());
     tempPoint = event->globalPos();
     float angleX = deltX / 1000.0f;
     float angleY = deltY / 1000.0f;
     camera.Yaw(-angleX);
     camera.Pitch(-angleY);
    }
}
void MyOpenglWegdit::mouseReleaseEvent(QMouseEvent* event) {
    if (isMousePressed ==true) {
        isMousePressed = false;
        QCursor::setPos(mousePoint);
        this->setCursor(Qt::ArrowCursor);  //显示正常鼠标 
    }
}
//因为章节是连续的这里就不给初始化函数了,可以去QT+OpenGL二去看
void MyOpenglWegdit::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     QMatrix4x4 v;
     v.lookAt(QVector3D(camera.location.x, camera.location.y, camera.location.z),
         QVector3D(camera.viewPoint.x, camera.viewPoint.y, camera.viewPoint.z),
         QVector3D(camera.worldY.x, camera.worldY.y, camera.worldY.z));
     mvMat = v * mMat;
    shaderProgram.setUniformValue(mvLoc, mvMat);//修改统一变量
    mTexture->bind(mTexture->textureId());
    //glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
    glDrawArrays(GL_TRIANGLES,0,36);
   mTexture->release();
   QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
}

注意:

为什么偏移量要除以1000,因为sinθ有个特点,当θ足够小的时候,在sinθ中
偏移量x和θ几乎相等。我们就可以把偏移量当成角度来看。
参数角度取相反数的原因,因为我们要达到鼠标右移,场景就会向左移动的目的。
动态效果


1.gif

相机的移动(并非相机移动,而是构建算法去影响矩阵,从而影响场景中物体的位置):

image.png

我们发现移动相机实际上还要移动视点。也就是说要移动两个东西。这里只讨论前后移动,左右移动同理。
这样就有了算法:
物理知识:x=vt
t:每一帧消耗的时间,v速度,x位移
forward:
OperatorVecter delta = viewDirection * x;
location = location + delta;
viewPoint = viewPoint + delta;
backward:
OperatorVecter delta = viewDirection * x;
location = location - delta;
viewPoint = viewPoint - delta;
leftward:
OperatorVecter delta = rightDirection * x;x轴向量
location = location - delta;
viewPoint = viewPoint - delta;
rightward:
OperatorVecter delta = rightDirection * x;
location = location + delta;
viewPoint = viewPoint + delta;
当然左右移动需要x轴所以还是需要构建伪坐标系。
所以我们接着需要计算出x

void MyOpenglWegdit::updataDetlaTime() {//获取每次渲染所需时间detlaTime ,速度v可以自己定义
    static int lastTime = 0; int temp;
    QDateTime time = QDateTime::currentDateTime();   //获取当前时间
    int currTime = time.toMSecsSinceEpoch();
    temp = lastTime == 0 ? 0: currTime - lastTime;
    detlaTime =(float)temp/1000.0;
    lastTime = currTime;
}

这样就能算出x
上代码
camera.h添加

    void move(float time,int direction, float velocity = 10.0);//相机移动默认速度为10.0
    enum MyEnum//匹配前后左右
    {
        forward = 0,
        backward=1,
        leftward=2,
        rightward=3
    };

camera.cpp添加

void Camera::move(float time,int direction, float velocity) {
    float x = velocity * time;
    OperatorVecter viewDirection = viewPoint - location;
    viewDirection.Normalize();
    OperatorVecter rightDirection = cross(viewDirection, worldY);//构建伪坐标系x轴
    rightDirection.Normalize();
    switch (direction)
    {
    case forward: {
        OperatorVecter delta = viewDirection * x;
        location = location + delta;
        viewPoint = viewPoint + delta; break;
    }
    case backward: {
        OperatorVecter delta = viewDirection * x;
        location = location - delta;
        viewPoint = viewPoint - delta; break;
    }
    case leftward: {
        OperatorVecter delta = rightDirection * x;
        location = location - delta;
        viewPoint = viewPoint - delta; break;
    }
    case rightward: {
        OperatorVecter delta = rightDirection * x;
        location = location + delta;
        viewPoint = viewPoint + delta; break;
    }
  }

QOpenGLWidget.h添加

float detlaTime;
void keyPressEvent(QKeyEvent* event);//键盘事件
private:
void updataDetlaTime();//设置每次绘画的间隔时间

QOpenGLWidget.cpp添加

MyOpenglWegdit::MyOpenglWegdit(QWidget*parent)
    : QOpenGLWidget(parent)
{
    this->grabKeyboard();//构造函数初始化开启键盘事件
}
void MyOpenglWegdit::keyPressEvent(QKeyEvent* event) {
    switch (event->key())
    {
    case Qt::Key_W: {camera.move(detlaTime, Camera::forward); break;}
    case Qt::Key_S: {camera.move(detlaTime, Camera::backward); break;}
    case Qt::Key_A: {camera.move(detlaTime, Camera::leftward); break;}
    case Qt::Key_D: {camera.move(detlaTime, Camera::rightward); break;}
    }
}
void MyOpenglWegdit::updataDetlaTime() {//在绘画函数paintGL中调用
    static int lastTime = 0; int temp;
    QDateTime time = QDateTime::currentDateTime();   //获取当前时间
    int currTime = time.toMSecsSinceEpoch();
    temp = lastTime == 0 ? 0: currTime - lastTime;
    detlaTime =(float)temp/1000.0;
    lastTime = currTime;
}

效果动态图:


2.gif

目录

VSC++2019+QT+OpenGL
QT+OpenGL一之绘制立方体(三角形图元)
QT+OpenGL二之纹理贴图
QT+OpenGL三之矩阵简解
QT+OpenGL四之相机的移动和旋转
QT+OpenGL五之绘制不同的模型(vao,vbo机制)
QT+OpenGL六之天空盒
QT+OpenGL七之使用EBO
QT+OPenGL八之模型准备
QT+OPenGL九之模型解码
QT+OPenGL十之光照模型
QT+OPenGL十一之漫反射和镜面反射贴图
QT+OPenGL十二之定向光
QT+OPenGL十三之真正的点光源和聚光灯
QT+OPenGL十四之多光源混合的问题
QT+OPenGL十五之深度缓冲区
QT+OPenGL十六之模板缓冲区
QT+OPenGL十七帧缓冲区(离屏渲染)
QT+OPenGL十八抗锯齿
QT+OPenGL十九镜面反射效率调整
QT+OPenGL二十Gamma校正

你可能感兴趣的:(QT+OpenGL四之相机的移动和旋转)