(本文是LearnOpenGL的学习笔记,教程中文翻译地址https://learnopengl-cn.github.io/)
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类代码:
#ifndef GLCAMERA_H
#define GLCAMERA_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include <QTimer>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
class GLCamera : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{
Q_OBJECT
public:
explicit GLCamera(QWidget *parent = nullptr);
~GLCamera();
protected:
//设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
void initializeGL() override;
//渲染OpenGL场景,每当需要更新小部件时使用
void paintGL() override;
//设置OpenGL视口、投影等,每当尺寸大小改变时调用
void resizeGL(int width, int height) override;
//鼠标操作,重载Qt的事件处理
void keyPressEvent(QKeyEvent *event) override;
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=nullptr;
//箱子旋转角度
int _rotate=0;
//操作View,我这里为了展示没有封装成单独的Camera类
//Camera Attributes
QVector3D _cameraPosition{0.0f,0.0f,3.0f};
QVector3D _cameraFront{-_cameraPosition};
QVector3D _cameraUp{0.0f,1.0f,0.0f};
QVector3D _cameraRight{};
QVector3D _cameraWorldUp{_cameraUp};
// Euler Angles
//偏航角如果是0.0f,指向的是 x轴正方向,即右方向,所以向里转90度,初始方向指向z轴负方向
//(这里有个问题,教程是90,但是算出来整体向右偏移了)
float _Yaw=-89.5f; //x偏航角
float _Pitch=0.0f; //y俯仰角
// Camera options
float _Speed=2.0f;
float _Sensitivity=0.01f;
float _Zoom=45.0f; //缩放加限制范围
//
QPoint _mousePos;
};
#endif // GLCAMERA_H
#include "GLCamera.h"
#include <QDebug>
GLCamera::GLCamera(QWidget *parent)
: QOpenGLWidget(parent)
{
_timer=new QTimer(this);
connect(_timer,&QTimer::timeout,this,[this](){
_rotate+=2;
update();
});
_timer->setInterval(50);
setFocusPolicy(Qt::ClickFocus); //默认没有焦点
calculateCamera();
}
GLCamera::~GLCamera()
{
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 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0f);
TexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
})";
const char *fragment_str=R"(#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
})";
//将source编译为指定类型的着色器,并添加到此着色器程序
if(!_shaderProgram.addCacheableShaderFromSourceCode(
QOpenGLShader::Vertex,vertex_str)){
qDebug()<<"compiler vertex error"<<_shaderProgram.log();
}
if(!_shaderProgram.addCacheableShaderFromSourceCode(
QOpenGLShader::Fragment,fragment_str)){
qDebug()<<"compiler fragment error"<<_shaderProgram.log();
}
//使用addShader()将添加到该程序的着色器链接在一起。
if(!_shaderProgram.link()){
qDebug()<<"link shaderprogram error"<<_shaderProgram.log();
}
//VAO,VBO(盒子六个面,一个面两个三角)
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
_vao.create();
_vao.bind();
//QOpenGLVertexArrayObject::Binder vaoBind(&_vao);
_vbo=QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
_vbo.create();
_vbo.bind();
_vbo.allocate(vertices,sizeof(vertices));
// position attribute
int attr = -1;
attr = _shaderProgram.attributeLocation("aPos");
//setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)
_shaderProgram.setAttributeBuffer(attr, GL_FLOAT, 0, 3, sizeof(GLfloat) * 5);
_shaderProgram.enableAttributeArray(attr);
// texture coord attribute
attr = _shaderProgram.attributeLocation("aTexCoord");
_shaderProgram.setAttributeBuffer(attr, GL_FLOAT, sizeof(GLfloat) * 3, 2, sizeof(GLfloat) * 5);
_shaderProgram.enableAttributeArray(attr);
// texture 1
//直接生成绑定一个2d纹理, 并生成多级纹理MipMaps
_texture1 = new QOpenGLTexture(QImage(":/box.jpg"), QOpenGLTexture::GenerateMipMaps);
if(!_texture1->isCreated()){
qDebug() << "Failed to load texture";
}
// set the texture wrapping parameters
// 等于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
_texture1->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
_texture1->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
// set texture filtering parameters
//等价于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
_texture1->setMinificationFilter(QOpenGLTexture::Linear);
_texture1->setMagnificationFilter(QOpenGLTexture::Linear);
// texture 2
//直接生成绑定一个2d纹理, 并生成多级纹理MipMaps
_texture2 = new QOpenGLTexture(QImage(":/face.png"), QOpenGLTexture::GenerateMipMaps);
if(!_texture2->isCreated()){
qDebug() << "Failed to load texture";
}
// set the texture wrapping parameters
// 等于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
_texture2->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
_texture2->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);//
// set texture filtering parameters
//等价于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
_texture2->setMinificationFilter(QOpenGLTexture::Linear);
_texture2->setMagnificationFilter(QOpenGLTexture::Linear);
_shaderProgram.bind();
_shaderProgram.setUniformValue("texture1", 0);
_shaderProgram.setUniformValue("texture2", 1);
_shaderProgram.release();
_timer->start();
}
//绘制多个盒子
static QVector3D cubePositions[] = {
QVector3D( 0.0f, 0.0f, 0.0f),
QVector3D( 2.0f, 5.0f, -15.0f),
QVector3D(-1.5f, -2.2f, -2.5f),
QVector3D(-3.8f, -2.0f, -12.3f),
QVector3D( 2.4f, -0.4f, -3.5f),
QVector3D(-1.7f, 3.0f, -7.5f),
QVector3D( 1.3f, -2.0f, -2.5f),
QVector3D( 1.5f, 2.0f, -2.5f),
QVector3D( 1.5f, 0.2f, -1.5f),
QVector3D(-1.3f, 1.0f, -1.5f)
};
void GLCamera::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
//每次渲染迭代之前清除深度缓冲,否则前一帧的深度信息仍然保存在缓冲中
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//Z缓冲(Z-buffer),也被称为深度缓冲(Depth Buffer)
glEnable(GL_DEPTH_TEST);
//纹理单元的应用
glActiveTexture(GL_TEXTURE0);
_texture1->bind();
glActiveTexture(GL_TEXTURE1);
_texture2->bind();
_shaderProgram.bind();
_vao.bind();
/*//观察矩阵
QMatrix4x4 view;
//3个相互垂直的轴和一个定义摄像机空间的位置坐标
//第一组参数:eyex, eyey,eyez 相机在世界坐标的位置
//第二组参数:centerx,centery,centerz 相机镜头对准的物体在世界坐标的位置
//第三组参数:upx,upy,upz 相机向上的方向在世界坐标中的方向,(0,-1,0)就旋转了190度
view.lookAt(_cameraPosition,
_cameraPosition+_cameraFront,
_cameraUp);
_shaderProgram.setUniformValue("view", view);*/
_shaderProgram.setUniformValue("view", getViewMatrix());
//透视投影
QMatrix4x4 projection;
projection.perspective(_Zoom, 1.0f * width() / height(), 0.1f, 100.0f);
_shaderProgram.setUniformValue("projection", projection);
for (unsigned int i = 0; i < 10; i++) {
//计算模型矩阵
QMatrix4x4 model;
//平移
model.translate(cubePositions[i]);
//这样每个箱子旋转的速度就不一样
float angle = (i + 1.0f) * _rotate;
//旋转
model.rotate(angle, QVector3D(1.0f, 0.3f, 0.5f));
//传入着色器并绘制
_shaderProgram.setUniformValue("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
_vao.release();
_texture1->release();
_texture2->release();
_shaderProgram.release();
}
void GLCamera::resizeGL(int width, int height)
{
glViewport(0, 0, width, height);
}
void GLCamera::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_W: //上
_cameraPosition-=_cameraWorldUp * _Speed;
break;
case Qt::Key_S://下
_cameraPosition+=_cameraWorldUp * _Speed;
break;
case Qt::Key_A: //左
_cameraPosition+=_cameraRight * _Speed;
break;
case Qt::Key_D: //右
_cameraPosition-=_cameraRight * _Speed;
break;
case Qt::Key_E://近
_cameraPosition+=_cameraFront * _Speed;
break;
case Qt::Key_Q: //远
_cameraPosition-=_cameraFront * _Speed;
break;
default:
break;
}
/*switch (event->key()) {
case Qt::Key_W: //前
_cameraPosition+=_cameraFront*0.5f;
break;
case Qt::Key_S: //后
_cameraPosition-=_cameraFront*0.5f;
break;
case Qt::Key_A: //左
_cameraPosition-=QVector3D::crossProduct(_cameraFront, _cameraUp)
.normalized()*0.5f;
break;
case Qt::Key_D: //右
_cameraPosition+=QVector3D::crossProduct(_cameraFront, _cameraUp)
.normalized()*0.5f;
break;
default:
break;
}*/
update();
QWidget::keyPressEvent(event);
}
void GLCamera::mousePressEvent(QMouseEvent *event)
{
_mousePos=event->pos();
QWidget::mousePressEvent(event);
}
void GLCamera::mouseReleaseEvent(QMouseEvent *event)
{
QWidget::mouseReleaseEvent(event);
}
void GLCamera::mouseMoveEvent(QMouseEvent *event)
{
int x_offset=event->pos().x()-_mousePos.x();
int y_offset=event->pos().y()-_mousePos.y();
_mousePos=event->pos();
_Yaw -= x_offset*_Sensitivity;
_Pitch += y_offset*_Sensitivity;
if (_Pitch > 89.0f)
_Pitch = 89.0f;
else if (_Pitch < -89.0f)
_Pitch = -89.0f;
calculateCamera();
update();
QWidget::mouseMoveEvent(event);
}
void GLCamera::wheelEvent(QWheelEvent *event)
{
if(event->delta()<0){
_Zoom+=_Speed;
if(_Zoom>90)
_Zoom=90;
}else{
_Zoom-=_Speed;
if(_Zoom<1)
_Zoom=1;
}
update();
QWidget::wheelEvent(event);
}
void GLCamera::calculateCamera()
{
QVector3D front;
front.setX(cos(_Yaw) * cos(_Pitch));
front.setY(sin(_Pitch));
front.setZ(sin(_Yaw) * cos(_Pitch));
_cameraFront = front.normalized();
_cameraRight = QVector3D::crossProduct(_cameraFront, _cameraWorldUp).normalized();
_cameraUp = QVector3D::crossProduct(_cameraRight, _cameraFront).normalized();
}
QMatrix4x4 GLCamera::getViewMatrix()
{
QMatrix4x4 view; //观察矩阵
view.lookAt(_cameraPosition,_cameraPosition+_cameraFront,_cameraUp);
return view;
}
3.参考
LearnOpenGL:https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/
博客:https://blog.csdn.net/z136411501/article/details/79939695