OpenGL本身没有摄像机(Camera)的概念,但我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机,产生一种我们在移动的感觉,而不是场景在移动。
要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右侧的向量以及一个指向它上方的向量。
摄像机位置:
获取摄像机位置很简单。摄像机位置简单来说就是世界空间中一个指向摄像机位置的向量。不要忘记正z轴是从屏幕指向你的,如果我们希望摄像机向后移动,我们就沿着z轴的正方向移动。
QVector3D cameraPos = QVector3D( 0.0f, 0.0f, 2.0f);//摄像机位置
摄像机方向:
这里指的是摄像机指向哪个方向。现在我们让摄像机指向场景原点:(0, 0, 0)。用场景原点向量减去摄像机位置向量的结果就是摄像机的指向向量。由于我们知道摄像机指向z轴负方向,但我们希望方向向量(Direction Vector)指向摄像机的z轴正方向。如果我们交换相减的顺序,我们就会获得一个指向摄像机正z轴方向的向量:
cameraTarget = QVector3D( 0.0f, 0.0f, 0.0f);//摄像机看到的位置
cameraDirection = QVector3D(cameraPos - cameraTarget);//摄像机的方向
cameraDirection.normalize();
右轴:
它代表摄像机空间的x轴的正方向。为获取右向量我们需要先使用一个小技巧:先定义一个上向量(Up Vector)。接下来把上向量和第二步得到的摄像机方向向量进行叉乘。两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量(如果我们交换两个向量叉乘的顺序就会得到相反的指向x轴负方向的向量):
up = QVector3D(0.0f, 1.0f, 0.0f);
cameraRight = QVector3D::crossProduct(up,cameraDirection);//两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量
cameraRight.normalize();
上轴:
把右向量和摄像机方向向量进行叉乘得到摄像机的上向量:
cameraUp = QVector3D::crossProduct(cameraDirection,cameraRight);
Look At:
使用矩阵的好处之一是如果你使用3个相互垂直(或非线性)的轴定义了一个坐标空间,你可以用这3个轴外加一个平移向量来创建一个矩阵,并且你可以用这个矩阵乘以任何向量来将其变换到那个坐标空间。
QTime gtime;
QMatrix4x4 view;
float radius = 10.0f; //圆半径
float time = gtime.elapsed()/1000.0;
float camx = sin(time) * radius;
float camz = cos(time) * radius;
view.lookAt(QVector3D(camx,0.0,camz),cameraTarget,up);
参数简介:
一个摄像机位置,一个目标位置和一个表示世界空间中的上向量的向量(我们计算右向量使用的那个上向量)。
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include
#include
#include
#include
#include
#include
#include
class MyOpenGLWidget : public QOpenGLWidget,public QOpenGLFunctions_3_3_Core
{
public:
MyOpenGLWidget(QWidget *parent = nullptr);
protected:
virtual void initializeGL();
virtual void paintGL();
virtual void resizeGL(int w, int h);
private:
QOpenGLTexture *m_wall;
QOpenGLTexture *m_face;
QOpenGLShaderProgram *m_program;
QVector3D cameraPos;
QVector3D cameraTarget;
QVector3D cameraDirection;
QVector3D up;
QVector3D cameraRight;
QVector3D cameraUp;
};
#endif // MYOPENGLWIDGET_H
#include "myopenglwidget.h"
#include
#include
#include
#include
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
};
GLuint indices[] = {
0, 1, 3,
1, 2, 3
};
//顶点着色器语言
const GLchar* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"layout (location = 1) in vec2 texCoord;\n"
"out vec2 outTexCoord;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"
"void main()\n"
"{\n"
"gl_Position = projection * view * model * vec4(position,1.0);\n"
"outTexCoord = texCoord;\n"
"}\n\0";
//片段着色器语言
//texture函数会使用之前设置的纹理参数对相应的颜色值进行采样
//mix按一定的比例,混合两个纹理颜色
const GLchar* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"
"uniform sampler2D ourTexture1;\n"
"uniform sampler2D ourTexture2;\n"
"in vec2 outTexCoord;\n"
"void main()\n"
"{\n"
"color = mix(texture(ourTexture1, outTexCoord),texture(ourTexture2, vec2(outTexCoord.x, outTexCoord.y)),0.45);\n"
"}\n\0";
GLuint VBO, VAO,EBO;
GLuint shaderProgram;
QTimer *timer;
QTime gtime;
QVector 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)
};
MyOpenGLWidget::MyOpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
timer = new QTimer();
timer->start(50);
connect(timer,&QTimer::timeout,[=]{
update();
});
gtime.start();
cameraPos = QVector3D( 0.0f, 0.0f, 2.0f);//摄像机位置
cameraTarget = QVector3D( 0.0f, 0.0f, 0.0f);//摄像机看到的位置
cameraDirection = QVector3D(cameraPos - cameraTarget);//摄像机的方向
cameraDirection.normalize();
up = QVector3D(0.0f, 1.0f, 0.0f);
cameraRight = QVector3D::crossProduct(up,cameraDirection);//两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量
cameraRight.normalize();
cameraUp = QVector3D::crossProduct(cameraDirection,cameraRight);
}
void MyOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
m_program = new QOpenGLShaderProgram();
m_program->addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShaderSource);
m_program->addShaderFromSourceCode(QOpenGLShader::Fragment,fragmentShaderSource);
m_program->link();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);//绑定VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO);//顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把顶点数据复制到缓冲的内存中GL_STATIC_DRAW :数据不会或几乎不会改变。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glBindVertexArray(0);//解绑VAO
m_wall = new QOpenGLTexture(QImage("./container.jpg").mirrored());
m_face = new QOpenGLTexture(QImage("./awesomeface.png").mirrored());
m_program->bind();
m_program->setUniformValue("ourTexture1",0);
m_program->setUniformValue("ourTexture2",1);
//设置投影透视矩阵
QMatrix4x4 projection;
projection.perspective(60,(float)( width())/(height()),0.1,100);
m_program->setUniformValue("projection",projection);
}
void MyOpenGLWidget::paintGL()
{
glClearColor(0.2f,0.3f,0.3f,1.0f);
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
QMatrix4x4 model;
QMatrix4x4 view;
//随时间变化的x和z坐标
float radius = 10.0f;
float time = gtime.elapsed()/1000.0;
float camx = sin(time) * radius;
float camz = cos(time) * radius;
view.lookAt(QVector3D(camx,0.0,camz),cameraTarget,up);
m_program->bind();
glBindVertexArray(VAO);//绑定VAO
m_wall->bind(0);
m_face->bind(1);
//设置观察矩阵
m_program->setUniformValue("view",view);
foreach(auto pos , cubePositions)
{
model.setToIdentity();
model.translate(pos);
//model.rotate(time,1.0f,5.0f,3.0f);
//设置模型矩阵
m_program->setUniformValue("model",model);
glDrawArrays(GL_TRIANGLES,0,36);
}
}
void MyOpenGLWidget::resizeGL(int w, int h)
{
}
现在lookat函数变成了这样,我们首先将摄像机位置设置为之前定义的cameraPos。方向是当前的位置加上我们刚刚定义的方向向量。这样能保证无论我们怎么移动,摄像机都会注视着目标方向。
QMatrix4x4 view;
view.lookAt(cameraPos,cameraPos + cameraFront,cameraUp);
键盘事件:
void MyOpenGLWidget::keyPressEvent(QKeyEvent *event)
{
qDebug()<key();
cameraSpeed = 2.5 * 100 / 1000.0;
switch (event->key()) {
case Qt::Key_W:{
cameraPos += cameraSpeed * cameraFront;
}
break;
case Qt::Key_S:{
cameraPos -= cameraSpeed * cameraFront;
}
break;
case Qt::Key_A:{
cameraPos -= cameraSpeed * cameraRight;
}
break;
case Qt::Key_D:{
cameraPos += cameraSpeed * cameraRight;
}
break;
default:
break;
}
update();
}
当我们按下WASD键的任意一个,摄像机的位置都会相应更新。如果我们希望向前或向后移动,我们就把位置向量加上或减去方向向量。如果我们希望向左右移动,我们使用叉乘来创建一个右向量(Right Vector),并沿着它相应移动就可以了。这样就创建了使用摄像机时熟悉的横移(Strafe)效果。
只用键盘移动没什么意思。特别是我们还不能转向,移动很受限制。
为了能够改变视角,我们需要根据鼠标的输入改变cameraFront向量。
有兴趣的可以去了解一下欧拉角。这里直接上代码。
float PI = 3.1415926;
QPoint deltaPos;
void MyOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
static float yaw = -90;
static float pitch = 0;
//上一次的位置
static QPoint lastPos(width()/2,height()/2);
//当前的位置
auto currentPos = event->pos();
//
deltaPos = currentPos-lastPos;
lastPos = currentPos;
//灵敏度
float sensitivity = 0.1f;
deltaPos *= sensitivity;
yaw += deltaPos.x();
pitch -= deltaPos.y();
if(pitch > 89.0f)
pitch = 89.0f;
if(pitch < -89.0f)
pitch = -89.0f;
cameraFront.setX(cos(yaw*PI/180.0) * cos(pitch *PI/180));
cameraFront.setY(sin(pitch*PI/180));
cameraFront.setZ(sin(yaw*PI/180) * cos(pitch *PI/180));
cameraFront.normalize();
update();
}
我们说视野(Field of View)或fov定义了我们可以看到场景中多大的范围。当视野变小时,场景投影出来的空间就会减小,产生放大(Zoom In)了的感觉。我们会使用鼠标的滚轮来放大。
我们现在在每一帧都必须把透视投影矩阵上传到GPU,但现在使用fov变量作为它的视野:
projection.perspective(fov,(float)( width())/(height()),0.1,100);
滚轮事件:
void MyOpenGLWidget::wheelEvent(QWheelEvent *event)
{
if(fov >= 1.0f && fov <= 75.0f)
fov -= event->angleDelta().y()/120;
if(fov <= 1.0f)
fov = 1.0f;
if(fov >= 75.0f)
fov = 75.0f;
update();
}
6.完整源码
https://download.csdn.net/download/wzz953200463/87887281https://download.csdn.net/download/wzz953200463/87887281