前言
首先吐槽一下学校的计算机图形学课程是以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();
}
有几个文件没放,主要是绘制东西的代码,应该不是很重要。