相機(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)

這里注意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 * viewDirectiontempY(x * y * (1 - Cosθ ) + z * Sinθ, Cosθ + y * y * (1 - Cosθ ), y * z * (1 - Cosθ ) - x * Sinθ)newDirection.y = tempY * viewDirectiontempZ(x * z * (1 - Cosθ ) - y * Sinθ, y * z * (1 - Cosθ ) + x * Sinθ, Cosθ + z * z * (1 - Cosθ ))newDirection.z = tempZ * viewDirectionnewViewPoint = location + newDirectionviewDirection:原來(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)事件直接重寫

使用:
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)效果

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

我們發(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)圖:

目錄
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校正