qt+opengl 三维物体加入摄像机
1 在前几期的文章中,我们已经实现了三维正方体的显示了,那我们来实现让物体的由远及近,和由近及远。这里我们需要了解一个概念摄像机。
1.1 摄像机定义:在世界空间中位置、观察方向、指向右侧向量、指向上方的向量。如下图所示:
1.2 实现一个摄像机
首先定义一个摄像机位置,然后获取一个摄影机指向原点的向量,并且获取指向摄像机的单位向量(只关心摄像机的方向,不关心其长度).代码如下所示:
-
QVector3D cameraPos(0, 0, 3)
-
QVector3D cameraTarget(0, 0, 0);
-
QVector3D cameraDirection( (cameraTarget- cameraPos).normalized() )
1.3 摄像机位置
摄像机位置是在世界空间中一个指向摄像机位置的向量。右手坐标系:大拇指指向正x轴方向,食指指向正y轴方向,中指指向正z轴方向,z正轴从屏幕指向自己。
通过改变摄像机的位置就可以实现物体的远近效果。
在qt opengl 函数里面我们有函数实现了
void QMatrix4x4::lookAt(const QVector3D &eye, const QVector3D ¢er, const QVector3D &up)
//获取lookAt矩阵.
//eye: 设置摄像机(眼睛)位置,比如QVector3D(0,0,3)
//center:表示摄像机(眼睛)正在看的视图的中心,比如原点QVector3D(0,0,0)
//up:眼睛的上向量,一般为QVector3D(0,1,0),通过该向量就能获取到眼睛的右轴和上轴
这里我们只需要设置相应的参数就可以了
eye : 就是我们眼睛的位置,这里我们设置Z轴正方向 QVector3D(0, 0, 3);
center : 就是我们以什么地方为中心点观察物体,如QVector3D(0, 0, 0);原点。
up:眼睛的上向量,一般为QVector3D(0,1,0),通过该向量就能获取到眼睛的右轴和上轴
好了,那么我们现在来修改glsl语句和代码实现。
#include "glwidget.h"
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QMouseEvent>
#include <QDateTime>
#include <QtMath>
static const char *vertexShaderSource =
"#version 330\n"
"layout (location = 0) in vec4 vertex;\n"
"layout (location = 1) in vec4 texCoord;\n"
"out vec4 texc;\n"
"uniform mat4 matrix;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"
"void main(void)\n"
"{\n"
" gl_Position = projection*view* matrix * vertex;\n"
" texc = texCoord;\n"
"}\n";
static const char *fragmentShaderSource =
"#version 330\n"
"uniform sampler2D texture;\n"
"in vec4 texc;\n"
"void main(void)\n"
"{\n"
" gl_FragColor = texture2D(texture, texc.st);\n"
"}\n";
GLWidget::GLWidget():QOpenGLWidget()
,m_xRos(0)
,m_yRos(0)
{
cam = QVector3D(m_xRos, m_yRos, m_zRos);
cameraPos = QVector3D(0, 0, 3); 近---远
// cameraPos = QVector3D(0, 0, 30);远---近
timer = new QTimer;
timer->setInterval(20);
connect(timer,&QTimer::timeout,this,[=]{
qDebug()<<"timeout"<<nCount<<m_xRos;
rotateBy(2 * 16, +2 * 16, -1 * 16);
});
}
GLWidget::~GLWidget()
{
makeCurrent();
vbo.destroy();
for (int i = 0; i < 6; ++i)
delete textures[i];
delete program;
doneCurrent();
}
QSize GLWidget::minimumSizeHint() const
{
return QSize(400, 400);
}
QSize GLWidget::sizeHint() const
{
return QSize(400, 400);
}
void GLWidget::rotateBy(int xAngle, int yAngle, int zAngle)
{
float cameraSpeed = 0.2;
cameraPos += cameraSpeed * QVector3D(0, 0, 1);//由远到近
//cameraPos -= cameraSpeed * QVector3D(0, 0, 1);//由近到远
xRot += xAngle;
yRot += yAngle;
zRot += zAngle;
update();
}
void GLWidget::setClearColor(const QColor &color)
{
clearColor = color;
update();
}
void GLWidget::initializeGL()
{
vertices = {
// ---- 位置---- - 纹理坐标 - ---- 颜色 ----
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,//1
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,//2
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,//3
-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,//4
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,//5
-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,
};
initializeOpenGLFunctions();
// makeObject();
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
//#define PROGRAM_VERTEX_ATTRIBUTE 0
//#define PROGRAM_TEXCOORD_ATTRIBUTE 1
program = new QOpenGLShaderProgram;
program->addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShaderSource);
program->addShaderFromSourceCode(QOpenGLShader::Fragment,fragmentShaderSource);
program->link();
program->bind();//激活Program对象
vbo.create();
vbo.bind(); //绑定到当前的OpenGL上下文,
vbo.allocate(vertices.constData(), vertices.count() * sizeof(GLfloat));
//初始化VAO,设置顶点数据状态(顶点,法线,纹理坐标等)
vao.create();
vao.bind();
for (int j = 0; j < 6; ++j)
{
textures[j] = new QOpenGLTexture(QImage(QString(":/cube%1.png").arg(j + 1)).mirrored());
textures[j]->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear,QOpenGLTexture::Linear);
textures[j]->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::ClampToEdge);
textures[j]->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::ClampToEdge);
}
program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 5 * sizeof(float)); //设置aPos顶点属性
program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 2, 5 * sizeof(float)); //设置aColor顶点颜色
program->enableAttributeArray(0); //使能aPos顶点属性
program->enableAttributeArray(1); //使能aColor顶点颜色
program->setUniformValue("texture", 0);
QMatrix4x4 projection;
projection.perspective(60,(float)width()/height(),0.1,100);//构建透视矩阵,这个可以是固定写法
program->setUniformValue("projection", projection);
// vao.release();
// vbo.release();
}
void GLWidget::paintGL()
{
//glClearColor(clearColor.redF(), clearColor.greenF(), clearColor.blueF(), clearColor.alphaF());
glEnable(GL_DEPTH_TEST);
glClearColor(0.1f,0.5f,0.7f,1.0f); //设置清屏颜色
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
QMatrix4x4 m;
//m.ortho(-0.5f, +0.5f, +0.5f, -0.5f, 4.0f, 15.0f);
m.translate(0.0f, 0.0f, 0.0f);
m.rotate(xRot / 16.0f, 1.0f, 0.0f, 0.0f);
m.rotate(yRot / 16.0f, 0.0f, 1.0f, 0.0f);
m.rotate(zRot / 16.0f, 0.0f, 0.0f, 1.0f);
m.scale(0.5);
program->setUniformValue("matrix", m);
// QMatrix4x4 view;
// view.translate(0.0f,0.0f,-3.0f);
// program->setUniformValue("view", view);
QMatrix4x4 view;
view.lookAt(cameraPos, QVector3D(0, 0, 0), QVector3D(0, 1, 0));
//view.lookAt(cameraPos, cameraPos+QVector3D(0,0,-1), QVector3D(0,1,0));
program->setUniformValue("view", view);
for (int i = 0; i < 6; ++i) {
textures[i]->bind();
glDrawArrays(GL_TRIANGLE_FAN, i * 4, 4);
}
}
void GLWidget::resizeGL(int width, int height)
{
this->glViewport(0,0,width,height); //定义视口区域
}
void GLWidget::mousePressEvent(QMouseEvent *event)
{
lastPos = event->pos();
timer->start();
}
void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
int dx = event->x() - lastPos.x();
int dy = event->y() - lastPos.y();
if (event->buttons() & Qt::LeftButton) {
rotateBy(8 * dy, 8 * dx, 0);
} else if (event->buttons() & Qt::RightButton) {
rotateBy(8 * dy, 0, 8 * dx);
}
lastPos = event->pos();
}
void GLWidget::mouseReleaseEvent(QMouseEvent * /* event */)
{
emit clicked();
}
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QTimer>
#include <QOpenGLTexture>
#include <QOpenGLShaderProgram>
QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram);
QT_FORWARD_DECLARE_CLASS(QOpenGLTexture)
class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
//using QOpenGLWidget::QOpenGLWidget;
GLWidget();
~GLWidget();
QSize minimumSizeHint() const override;
QSize sizeHint() const override;
void rotateBy(int xAngle, int yAngle, int zAngle);
void setClearColor(const QColor &color);
signals:
void clicked();
protected:
void initializeGL() override;
void paintGL() override;
void resizeGL(int width, int height) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private:
QColor clearColor = Qt::black;
QPoint lastPos;
int xRot = 0;
int yRot = 0;
int zRot = 0;
QOpenGLTexture *textures[6] = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
QOpenGLShaderProgram *program = nullptr;
QOpenGLBuffer vbo;
QVector<float> vertices;
QOpenGLVertexArrayObject vao;
QTimer* timer;
float m_xRos = 0.0f;
float m_yRos = 0.0f;
float m_zRos = 3.0f;
QVector3D cam;
int nCount=0;
QVector3D cameraPos;
QVector3D cameraTarget;
QVector3D cameraDirection;
};
#endif
运行下代码是不是就是可以由远到近和由近到远了呢.
我们初始化
cameraPos = QVector3D(0, 0, 3); cameraPos += cameraSpeed * QVector3D(0, 0, 1);//由近到远
这样写就是近--远了呢
cameraPos = QVector3D(0, 0, 30); cameraPos -= cameraSpeed * QVector3D(0, 0, 1);//由远到近了呢
我们这样写是固定了摄像机观察的点是QVector3D(0, 0, 0),可是有个问题是不是当我们设置eye为
cameraPos = QVector3D(0, 0, 30);的时候,运行出来显示出来的效果是由远到近,然后又近到远了呢?为什么?因为
cameraPos -= cameraSpeed * QVector3D(0, 0, 1);这个值减少到负方向了?
所以我们修改下程序,修改center(摄像机观察的点)让观察点随着摄像机变化,是不是就没有由远到近 ,然后又由近到远呢?上代码
#include "glwidget.h"
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QMouseEvent>
#include <QDateTime>
#include <QtMath>
static const char *vertexShaderSource =
"#version 330\n"
"layout (location = 0) in vec4 vertex;\n"
"layout (location = 1) in vec4 texCoord;\n"
"out vec4 texc;\n"
"uniform mat4 matrix;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"
"void main(void)\n"
"{\n"
" gl_Position = projection*view* matrix * vertex;\n"
" texc = texCoord;\n"
"}\n";
static const char *fragmentShaderSource =
"#version 330\n"
"uniform sampler2D texture;\n"
"in vec4 texc;\n"
"void main(void)\n"
"{\n"
" gl_FragColor = texture2D(texture, texc.st);\n"
"}\n";
GLWidget::GLWidget():QOpenGLWidget()
,m_xRos(0)
,m_yRos(0)
{
cam = QVector3D(m_xRos, m_yRos, m_zRos);
cameraPos = QVector3D(0, 0, 3);
timer = new QTimer;
timer->setInterval(20);
connect(timer,&QTimer::timeout,this,[=]{
qDebug()<<"timeout"<<nCount<<m_xRos;
rotateBy(2 * 16, +2 * 16, -1 * 16);
});
}
GLWidget::~GLWidget()
{
makeCurrent();
vbo.destroy();
for (int i = 0; i < 6; ++i)
delete textures[i];
delete program;
doneCurrent();
}
QSize GLWidget::minimumSizeHint() const
{
return QSize(400, 400);
}
QSize GLWidget::sizeHint() const
{
return QSize(400, 400);
}
void GLWidget::rotateBy(int xAngle, int yAngle, int zAngle)
{
float cameraSpeed = 0.2;
cameraPos -= cameraSpeed * QVector3D(0, 0, -1);//由远到近
//cameraPos += cameraSpeed * QVector3D(0, 0, -1);//由近到远
xRot += xAngle;
yRot += yAngle;
zRot += zAngle;
update();
}
void GLWidget::setClearColor(const QColor &color)
{
clearColor = color;
update();
}
void GLWidget::initializeGL()
{
vertices = {
// ---- 位置---- - 纹理坐标 - ---- 颜色 ----
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,//1
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,//2
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,//3
-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,//4
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,//5
-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,
};
initializeOpenGLFunctions();
// makeObject();
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
//#define PROGRAM_VERTEX_ATTRIBUTE 0
//#define PROGRAM_TEXCOORD_ATTRIBUTE 1
program = new QOpenGLShaderProgram;
program->addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShaderSource);
program->addShaderFromSourceCode(QOpenGLShader::Fragment,fragmentShaderSource);
program->link();
program->bind();//激活Program对象
vbo.create();
vbo.bind(); //绑定到当前的OpenGL上下文,
vbo.allocate(vertices.constData(), vertices.count() * sizeof(GLfloat));
//初始化VAO,设置顶点数据状态(顶点,法线,纹理坐标等)
vao.create();
vao.bind();
for (int j = 0; j < 6; ++j)
{
textures[j] = new QOpenGLTexture(QImage(QString(":/cube%1.png").arg(j + 1)).mirrored());
textures[j]->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear,QOpenGLTexture::Linear);
textures[j]->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::ClampToEdge);
textures[j]->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::ClampToEdge);
}
program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 5 * sizeof(float)); //设置aPos顶点属性
program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 2, 5 * sizeof(float)); //设置aColor顶点颜色
program->enableAttributeArray(0); //使能aPos顶点属性
program->enableAttributeArray(1); //使能aColor顶点颜色
program->setUniformValue("texture", 0);
QMatrix4x4 projection;
projection.perspective(60,(float)width()/height(),0.1,100);//构建透视矩阵,这个可以是固定写法
program->setUniformValue("projection", projection);
// vao.release();
// vbo.release();
}
void GLWidget::paintGL()
{
//glClearColor(clearColor.redF(), clearColor.greenF(), clearColor.blueF(), clearColor.alphaF());
glEnable(GL_DEPTH_TEST);
glClearColor(0.1f,0.5f,0.7f,1.0f); //设置清屏颜色
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
QMatrix4x4 m;
//m.ortho(-0.5f, +0.5f, +0.5f, -0.5f, 4.0f, 15.0f);
m.translate(0.0f, 0.0f, 0.0f);
m.rotate(xRot / 16.0f, 1.0f, 0.0f, 0.0f);
m.rotate(yRot / 16.0f, 0.0f, 1.0f, 0.0f);
m.rotate(zRot / 16.0f, 0.0f, 0.0f, 1.0f);
m.scale(0.5);
program->setUniformValue("matrix", m);
// QMatrix4x4 view;
// view.translate(0.0f,0.0f,-3.0f);
// program->setUniformValue("view", view);
QMatrix4x4 view;
view.lookAt(cameraPos, cameraPos+QVector3D(0,0,-1), QVector3D(0,1,0));
program->setUniformValue("view", view);
for (int i = 0; i < 6; ++i) {
textures[i]->bind();
glDrawArrays(GL_TRIANGLE_FAN, i * 4, 4);
}
}
void GLWidget::resizeGL(int width, int height)
{
this->glViewport(0,0,width,height); //定义视口区域
}
void GLWidget::mousePressEvent(QMouseEvent *event)
{
lastPos = event->pos();
timer->start();
}
void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
int dx = event->x() - lastPos.x();
int dy = event->y() - lastPos.y();
if (event->buttons() & Qt::LeftButton) {
rotateBy(8 * dy, 8 * dx, 0);
} else if (event->buttons() & Qt::RightButton) {
rotateBy(8 * dy, 0, 8 * dx);
}
lastPos = event->pos();
}
void GLWidget::mouseReleaseEvent(QMouseEvent * /* event */)
{
emit clicked();
}
我们运行程序,最后点击下屏幕,是不是实现了呢?