QT+OpenGL四之相機(jī)的移動(dòng)和旋轉(zhuǎn)

相機(jī)移動(dòng)主要針對(duì)于lookAt()矩陣,lookAt需要三個(gè)參數(shù)1.相機(jī)的位置2.相機(jī)看向的位置3.相機(jī)的縱軸向量。相機(jī)的移動(dòng)就圍繞著這3個(gè)參數(shù)進(jìn)行。其實(shí)我們可以簡(jiǎn)單的傳入不同參數(shù)來(lái)改變看向的位置,但是要是實(shí)現(xiàn)第一視覺(jué)的功能還需要算法支持。
首先掌握數(shù)學(xué)知識(shí):

1.向量的+/-運(yùn)算

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.向量的模長(zhǎng)

|A|=√(A.X2+A.Y2+A.Z2)

4.向量的點(diǎn)積又稱內(nèi)積

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θ
物理意義獲得第三個(gè)與A,B都垂直的向量,用于構(gòu)建坐標(biāo)系

6.向量的單位化

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

7.向量運(yùn)算類

.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);//向量矢量與標(biāo)量的乘積
    float operator*(OperatorVecter& v);//向量的內(nèi)積
    OperatorVecter operator=(OperatorVecter& v);
    float Magnitude();//向量模長(zhǎng)
    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.好了上面的數(shù)學(xué)知識(shí)了解到了我們就可以繼續(xù)構(gòu)建相機(jī)類

其實(shí)qt有自己的向量類QVector3D,用法和上面自己寫的差不多。

(1.概念:

上章也說(shuō)了初始位置在location(0,0,0)視點(diǎn)viewPoint(0,0,-1)相機(jī)縱軸為(0,1,0)

image.png

這里注意y軸其實(shí)是取世界坐標(biāo)系的y坐標(biāo)軸,他是不變的。所以這個(gè)坐標(biāo)系是個(gè)偽坐標(biāo)系,它不保證z軸和y軸一定要垂直。因?yàn)槲覀冎魂P(guān)注最后看向的點(diǎn),上章講得很明確,相機(jī)實(shí)際上是不移動(dòng)的(通過(guò)期望相機(jī)位置和視點(diǎn)影響世界中物體的位置達(dá)到看向不同位置的目的)見上一章視圖矩陣,這也是y軸和相機(jī)的位置不變的原因。于是我們就注重視點(diǎn),針對(duì)視點(diǎn)來(lái)運(yùn)算,反復(fù)構(gòu)建這個(gè)偽坐標(biāo)系,下面就來(lái)闡述這樣做的原因:
左右旋轉(zhuǎn):
左右旋轉(zhuǎn)好理解,我們只需繞y軸旋轉(zhuǎn),那如何繞y軸旋轉(zhuǎn)喃?相機(jī)不是不會(huì)動(dòng)嗎?實(shí)際上我們只需要知道y軸的坐標(biāo)和想要旋轉(zhuǎn)的角度就能通過(guò)算法計(jì)算出新的視點(diǎn),上一章說(shuō)的lookat()矩陣不就需要這個(gè)新的視點(diǎn)嗎,如果還想移動(dòng)他也需要期望相機(jī)的位置我們傳入不就可以了!這里說(shuō)的算法后面會(huì)講,因?yàn)樯舷滦D(zhuǎn)和左右旋轉(zhuǎn)用的都是同一個(gè)算法。
上下旋轉(zhuǎn):
上下旋轉(zhuǎn)其實(shí)多了點(diǎn)步驟,要根據(jù)上圖構(gòu)建這個(gè)偽坐標(biāo)系,這時(shí)我們能看見x軸,所以我們繞x軸旋轉(zhuǎn),同樣相機(jī)是不動(dòng)的,我們需要的是x軸的坐標(biāo)和想要旋轉(zhuǎn)的角度,注意x軸是計(jì)算出來(lái)的會(huì)變,同樣傳遞給lookat矩陣。lookat矩陣的構(gòu)建過(guò)程才是在構(gòu)建當(dāng)前的真正的3維坐標(biāo)系,但這個(gè)坐標(biāo)系是基于視線(相機(jī)位置和視點(diǎn)算出來(lái)的向量)來(lái)計(jì)算的也是假想出來(lái)的。(相機(jī)位置,和相機(jī)坐標(biāo)系雷打不動(dòng)哈哈?。?br> 奉上計(jì)算新視點(diǎn)位置的算法:
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:原來(lái)的視線方向可以用viewPoint-locaton獲得
不要問(wèn)為啥,問(wèn)就是特性。
有了視點(diǎn),y軸坐標(biāo)和location當(dāng)前位置是不是傳入lookat()矩陣結(jié)果就出來(lái)了相機(jī)也旋轉(zhuǎn)了。

(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);//旋轉(zhuǎn)算法
    void Yaw(float angle);//左右旋轉(zhuǎn)攝像機(jī)
    void Pitch(float angle);//上下旋轉(zhuǎn)攝像機(jī)
};

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);//構(gòu)建偽坐標(biāo)系x軸
    rightDirection.Normalize();
    RotateView(angle, rightDirection.x, rightDirection.y, rightDirection.z);
}

先讓相機(jī)旋轉(zhuǎn)起來(lái)

OpenglWegdit中
章節(jié)是連續(xù)的接著前面紋理綁定的代碼
因?yàn)镼T中事件都被很好的封裝了,所以鼠標(biāo)事件直接重寫


image.png

使用:

void MyOpenglWegdit::mousePressEvent(QMouseEvent* event) {
    if (event->buttons() == Qt::RightButton) {
       mousePoint = event->globalPos();
       tempPoint = mousePoint;
       this->setCursor(Qt::BlankCursor); //隱藏鼠標(biāo) 
       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);  //顯示正常鼠標(biāo) 
    }
}
//因?yàn)檎鹿?jié)是連續(xù)的這里就不給初始化函數(shù)了,可以去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);//修改統(tǒng)一變量
    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,因?yàn)閟inθ有個(gè)特點(diǎn),當(dāng)θ足夠小的時(shí)候,在sinθ中
偏移量x和θ幾乎相等。我們就可以把偏移量當(dāng)成角度來(lái)看。
參數(shù)角度取相反數(shù)的原因,因?yàn)槲覀円_(dá)到鼠標(biāo)右移,場(chǎng)景就會(huì)向左移動(dòng)的目的。
動(dòng)態(tài)效果


1.gif

相機(jī)的移動(dòng)(并非相機(jī)移動(dòng),而是構(gòu)建算法去影響矩陣,從而影響場(chǎng)景中物體的位置):

image.png

我們發(fā)現(xiàn)移動(dòng)相機(jī)實(shí)際上還要移動(dòng)視點(diǎn)。也就是說(shuō)要移動(dòng)兩個(gè)東西。這里只討論前后移動(dòng),左右移動(dòng)同理。
這樣就有了算法:
物理知識(shí):x=vt
t:每一幀消耗的時(shí)間,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;
當(dāng)然左右移動(dòng)需要x軸所以還是需要構(gòu)建偽坐標(biāo)系。
所以我們接著需要計(jì)算出x

void MyOpenglWegdit::updataDetlaTime() {//獲取每次渲染所需時(shí)間detlaTime ,速度v可以自己定義
    static int lastTime = 0; int temp;
    QDateTime time = QDateTime::currentDateTime();   //獲取當(dāng)前時(shí)間
    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);//相機(jī)移動(dòng)默認(rèn)速度為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);//構(gòu)建偽坐標(biāo)系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();//設(shè)置每次繪畫的間隔時(shí)間

QOpenGLWidget.cpp添加

MyOpenglWegdit::MyOpenglWegdit(QWidget*parent)
    : QOpenGLWidget(parent)
{
    this->grabKeyboard();//構(gòu)造函數(shù)初始化開啟鍵盤事件
}
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() {//在繪畫函數(shù)paintGL中調(diào)用
    static int lastTime = 0; int temp;
    QDateTime time = QDateTime::currentDateTime();   //獲取當(dāng)前時(shí)間
    int currTime = time.toMSecsSinceEpoch();
    temp = lastTime == 0 ? 0: currTime - lastTime;
    detlaTime =(float)temp/1000.0;
    lastTime = currTime;
}

效果動(dòng)態(tài)圖:


2.gif

目錄

VSC++2019+QT+OpenGL
QT+OpenGL一之繪制立方體(三角形圖元)
QT+OpenGL二之紋理貼圖
QT+OpenGL三之矩陣簡(jiǎn)解
QT+OpenGL四之相機(jī)的移動(dòng)和旋轉(zhuǎn)
QT+OpenGL五之繪制不同的模型(vao,vbo機(jī)制)
QT+OpenGL六之天空盒
QT+OpenGL七之使用EBO
QT+OPenGL八之模型準(zhǔn)備
QT+OPenGL九之模型解碼
QT+OPenGL十之光照模型
QT+OPenGL十一之漫反射和鏡面反射貼圖
QT+OPenGL十二之定向光
QT+OPenGL十三之真正的點(diǎn)光源和聚光燈
QT+OPenGL十四之多光源混合的問(wèn)題
QT+OPenGL十五之深度緩沖區(qū)
QT+OPenGL十六之模板緩沖區(qū)
QT+OPenGL十七幀緩沖區(qū)(離屏渲染)
QT+OPenGL十八抗鋸齒
QT+OPenGL十九鏡面反射效率調(diào)整
QT+OPenGL二十Gamma校正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容