(本文是LearnOpenGL的学习笔记,教程中文翻译地址https://learnopengl-cn.github.io/(备用地址https://learnopengl-cn.readthedocs.io/zh/latest/),写于 2020-2-2 ,并在 2021-8-29 进行了更新)
0.前言上一篇笔记记录了坐标系统:https://blog.csdn.net/gongjianbo1992/article/details/104131776,本文将学习Camera摄像机。
1.如何实现观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右测的向量以及一个指向它上方的向量。我们实际上创建了一个三个单位轴相互垂直的、以摄像机的位置为原点的坐标系。
由于这里面知识点较多,理论请参照教程:https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/
这里面重点为LookAt矩阵和欧拉角。
现在我们用LookAt来生成我们的view矩阵了:
//观察矩阵
QMatrix4x4 view;
//3个相互垂直的轴和一个定义摄像机空间的位置坐标
//第一组参数:eyex, eyey,eyez 相机在世界坐标的位置
//第二组参数:centerx,centery,centerz 相机镜头对准的物体在世界坐标的位置
//第三组参数:upx,upy,upz 相机向上的方向在世界坐标中的方向,(0,-1,0)就旋转了190度
//void QMatrix4x4::lookAt(const QVector3D &eye, const QVector3D ¢er, const QVector3D &up)
view.lookAt(cameraPosition,cameraFront,cameraUp);
shaderProgram.setUniformValue("view", view);
欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:
俯仰角是描述我们如何往上或往下看的角,可以在第一张图中看到。第二张图展示了偏航角,偏航角表示我们往左和往右看的程度。滚转角代表我们如何翻滚摄像机,通常在太空飞船的摄像机中使用。每个欧拉角都有一个值来表示,把三个角结合起来我们就能够计算3D空间中任何的旋转向量了。
(对于教程的移动速度这一需求,我直接略过了,目前还用不到)
(此外,本节用到的一些数学知识,我还得再补补,不然没法理解)
2.实现代码(项目git链接:https://github.com/gongjianbo/OpenGLwithQtWidgets.git)
我的GLCamera类实现效果(左边图片右边GIF):
GLCamera类代码:
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//摄像机(观察矩阵)
//QOpenGLWidget窗口上下文
//QOpenGLFunctions访问OpenGL接口,可以不继承作为成员变量使用
class GLCamera
: public QOpenGLWidget
, protected QOpenGLFunctions_3_3_Core
{
public:
explicit GLCamera(QWidget *parent = nullptr);
~GLCamera();
protected:
//【】继承QOpenGLWidget后重写这三个虚函数
//设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
void initializeGL() override;
//渲染OpenGL场景,每当需要更新小部件时使用
void paintGL() override;
//设置OpenGL视口、投影等,每当尺寸大小改变时调用
void resizeGL(int width, int height) override;
//按键操作,重载Qt的事件处理
void keyPressEvent(QKeyEvent *event) override;
//鼠标操作,重载Qt的事件处理
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
private:
void calculateCamera();
QMatrix4x4 getViewMatrix();
private:
//着色器程序
QOpenGLShaderProgram shaderProgram;
//顶点数组对象
QOpenGLVertexArrayObject vao;
//顶点缓冲
QOpenGLBuffer vbo;
//纹理(因为不能赋值,所以只能声明为指针)
QOpenGLTexture *texture1{ nullptr };
QOpenGLTexture *texture2{ nullptr };
//定时器,做箱子旋转动画
QTimer timer;
int rotate{ 0 };
//操作View,我这里为了展示没有封装成单独的Camera类
//Camera Attributes
QVector3D cameraPos{ 0.0f, 0.0f, 3.0f };
QVector3D cameraFront{ 0.0f, 0.0f, -1.0f };
QVector3D cameraUp{ 0.0f, 1.0f, 0.0f };
QVector3D cameraRight{ 1.0f, 0.0f, 0.0f };
//Euler Angles
//偏航角如果是0.0f,指向的是 x轴正方向,即右方向,所以向里转90度,初始方向指向z轴负方向
float eulerYaw{ -90.0f }; //偏航角,绕y左右转
float eulerPitch{ 0.0f }; //俯仰角,绕x上下转
//Camera options
float cameraSpeed{ 0.5f }; //移动速度
float cameraSensitivity{ 0.1f }; //鼠标拖动灵敏度
float projectionFovy{ 45.0f }; //透视投影的fovy参数,视野范围
//鼠标位置
QPoint mousePos;
};
#include "GLCamera.h"
#include
#include
GLCamera::GLCamera(QWidget *parent)
: QOpenGLWidget(parent)
{
connect(&timer,&QTimer::timeout,this,[this](){
rotate+=2;
if(isVisible()){
update();
}
});
timer.setInterval(50);
setFocusPolicy(Qt::ClickFocus); //默认没有焦点
calculateCamera();
}
GLCamera::~GLCamera()
{
//initializeGL在显示时才调用,释放未初始化的会异常
if(!isValid())
return;
//QOpenGLWidget
//三个虚函数不需要makeCurrent,对应的操作已由框架完成
//但是释放时需要设置当前上下文
makeCurrent();
vbo.destroy();
vao.destroy();
delete texture1;
delete texture2;
doneCurrent();
}
void GLCamera::initializeGL()
{
//为当前上下文初始化OpenGL函数解析
initializeOpenGLFunctions();
//着色器代码
//in输入,out输出,uniform从cpu向gpu发送
//因为OpenGL纹理颠倒过来的,所以取反vec2(aTexCoord.x, 1-aTexCoord.y);
const char *vertex_str=R"(#version 330 core
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec2 inTexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec2 texCoord;
void main()
{
gl_Position = projection * view * model * vec4(inPos, 1.0);
texCoord = vec2(inTexCoord.x, 1-inTexCoord.y);
})";
const char *fragment_str=R"(#version 330 core
in vec2 texCoord;
uniform sampler2D texture1;
uniform sampler2D texture2;
out vec4 fragColor;
void main()
{
fragColor = mix(texture(texture1, texCoord), texture(texture2, texCoord), 0.2);
})";
//将source编译为指定类型的着色器,并添加到此着色器程序
if(!shaderProgram.addCacheableShaderFromSourceCode(
QOpenGLShader::Vertex,vertex_str)){
qDebug()release();
shaderProgram.release();
}
void GLCamera::resizeGL(int width, int height)
{
glViewport(0, 0, width, height);
}
void GLCamera::keyPressEvent(QKeyEvent *event)
{
event->accept();
//横向是移动,不是绕0点
switch (event->key()) {
case Qt::Key_W: //摄像机往上,场景往下
cameraPos -= QVector3D::crossProduct(cameraFront, cameraRight).normalized() * cameraSpeed;
break;
case Qt::Key_S: //摄像机往下,场景往上
cameraPos += QVector3D::crossProduct(cameraFront, cameraRight).normalized() * cameraSpeed;
break;
case Qt::Key_A: //摄像机往左,场景往右
cameraPos -= QVector3D::crossProduct(cameraFront, cameraUp).normalized() * cameraSpeed;
break;
case Qt::Key_D: //摄像机往右,场景往左
cameraPos += QVector3D::crossProduct(cameraFront, cameraUp).normalized() * cameraSpeed;
break;
case Qt::Key_E: //远
cameraPos -= cameraFront * cameraSpeed;
break;
case Qt::Key_Q: //近
cameraPos += cameraFront * cameraSpeed;
break;
default:
break;
}
update();
}
void GLCamera::mousePressEvent(QMouseEvent *event)
{
event->accept();
mousePos = event->pos();
}
void GLCamera::mouseReleaseEvent(QMouseEvent *event)
{
event->accept();
}
void GLCamera::mouseMoveEvent(QMouseEvent *event)
{
event->accept();
int x_offset = event->pos().x()-mousePos.x();
int y_offset = event->pos().y()-mousePos.y();
mousePos = event->pos();
//y轴的坐标是从下往上,所以相反
//我这里移动的是摄像机,所以场景往相反方向动
eulerYaw += x_offset*cameraSensitivity;
eulerPitch -= y_offset*cameraSensitivity;
if (eulerPitch > 89.0f)
eulerPitch = 89.0f;
else if (eulerPitch < -89.0f)
eulerPitch = -89.0f;
calculateCamera();
update();
}
void GLCamera::wheelEvent(QWheelEvent *event)
{
event->accept();
//fovy越小,模型看起来越大
if(event->delta() < 0){
//鼠标向下滑动为-,这里作为zoom out
projectionFovy += cameraSpeed;
if(projectionFovy > 90)
projectionFovy = 90;
}else{
//鼠标向上滑动为+,这里作为zoom in
projectionFovy -= cameraSpeed;
if(projectionFovy < 1)
projectionFovy = 1;
}
update();
}
void GLCamera::calculateCamera()
{
//角度转弧度
//2021-8-29 之前没转为弧度制,导致初始位置不对
const float yaw = qDegreesToRadians(eulerYaw);
const float pitch = qDegreesToRadians(eulerPitch);
QVector3D front;
front.setX(std::cos(yaw) * std::cos(pitch));
front.setY(std::sin(pitch));
front.setZ(std::sin(yaw) * std::cos(pitch));
cameraFront = front.normalized();
}
QMatrix4x4 GLCamera::getViewMatrix()
{
QMatrix4x4 view; //观察矩阵
view.lookAt(cameraPos, cameraPos+cameraFront, cameraUp);
return view;
}
3.参考
LearnOpenGL:https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/
博客:https://blog.csdn.net/z136411501/article/details/79939695