文章目录
- Qt的OpenGL窗口
- GLSL的实现
- 摄像机类的实现
- 简单的漫游器
Qt的OpenGL窗口
Qt主要是使用QOpenGLWidget来实现opengl的功能。
QOpenGLWidget 提供了三个便捷的虚函数,可以重载,用来重新实现典型的OpenGL任务:
- paintGL:渲染OpenGL场景。widget 需要更新时调用。
- resizeGL:设置OpenGL视口、投影等。widget 调整大小(或首次显示)时调用。
- initializeGL:设置OpenGL资源和状态。第一次调用 resizeGL() / paintGL() 之前调用一次。
- 如果需要从paintGL()以外的位置触发重新绘制(典型示例是使用计时器设置场景动画),则应调用widget的update()函数来安排更新。
- 调用paintGL()、resizeGL()或initializeGL()时,widget 的OpenGL呈现上下文将变为当前。如果需要从其他位置(例如,在 widget 的构造函数或自己的绘制函数中)调用标准OpenGL API函数,则必须首先调用makeCurrent()。
- QOpenGLFunctions_X_X_Core 提供OpenGL X.X版本核心模式的所有功能。是对OpenGL函数的封装:
- initializeOpenGLFunctions:初始化OpenGL函数,将Qt里的函数指针指向显卡的函数。
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core> class MyOpenGLWidget : public QOpenGLWidget,QOpenGLFunctions_3_3_Core
{
Q_OBJECT
public:
explicit MyOpenGLWidget (QWidget *parent = nullptr);
protected:
virtual void initializeGL();
virtual void resizeGL(int w, int h);
virtual void paintGL();
};
void MyOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
}
void MyOpenGLWidget::resizeGL(int w, int h)
{
Q_UNUSED(w);
Q_UNUSED(h);
}
void MyOpenGLWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
以上是最简单的实现版本。
GLSL的实现
由于是最简单的漫游器,所以我们实现的版本只需要一个顶点着色器和一个片段着色器即可。
shapes.vert
#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 = aTexCoord;
}
shapes.frag
#version 330 core
out vec4 FragColor;
in vec2 texCoord;
uniform sampler2D texturewall;
uniform sampler2D texturesmile;
uniform sampler2D textureSmall;
uniform float ratio;
void main(){
FragColor = mix(texture(texturewall,texCoord),texture(texturesmile,texCoord),ratio);
}
摄像机类的实现
这边实现了一个可以对模型进行上下左右移动,移动视角,放大缩小的操作。
摄像机类初始化了几个变量
// 默认值
const float YAW = -90.0f;
const float PITCH = 0.0f;
const float SPEED = 2.5f;
const float SENSITIVITY = 0.1f;
const float ZOOM = 45.0f;
偏航角默认为90度,灵敏度主要用于控制鼠标移动时视角的变化量。
#ifndef CAMERA_H
#define CAMERA_H
#include<QMatrix4x4>
#include <vector>
// 移动方向枚举量. 是一种抽象,以避开特定于窗口系统的输入方法
// 我们这里是WSAD
enum Camera_Movement {
FORWARD,
BACKWARD,
LEFT,
RIGHT
};
// 默认值
const float YAW = -90.0f;
const float PITCH = 0.0f;
const float SPEED = 2.5f;
const float SENSITIVITY = 0.1f;
const float ZOOM = 45.0f;
// 一个抽象的camera类,用于处理输入并计算相应的Euler角度、向量和矩阵,以便在OpenGL中使用
class Camera
{
public:
// camera Attributes
QVector3D Position;
QVector3D Front;
QVector3D Up;
QVector3D Right;
QVector3D WorldUp;
// euler Angles
float Yaw;
float Pitch;
// camera options
float MovementSpeed;
float MouseSensitivity;
float Zoom;
// constructor with vectors
Camera(QVector3D position = QVector3D(0.0f, 0.0f, 0.0f), QVector3D up = QVector3D(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH) : Front(QVector3D(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
{
Position = position;
WorldUp = up;
Yaw = yaw;
Pitch = pitch;
updateCameraVectors();
}
// constructor with scalar values
Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(QVector3D(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
{
Position = QVector3D(posX, posY, posZ);
WorldUp = QVector3D(upX, upY, upZ);
Yaw = yaw;
Pitch = pitch;
updateCameraVectors();
}
// returns the view matrix calculated using Euler Angles and the LookAt Matrix
QMatrix4x4 GetViewMatrix()
{
QMatrix4x4 theMatrix;
theMatrix.lookAt(Position, Position + Front, Up);
return theMatrix;
}
// 处理从任何类似键盘的输入系统接收的输入。接受摄像机定义枚举形式的输入参数(从窗口系统中提取)
void ProcessKeyboard(Camera_Movement direction, float deltaTime)
{
float velocity = MovementSpeed * deltaTime;
if (direction == FORWARD)
Position += Front * velocity;
if (direction == BACKWARD)
Position -= Front * velocity;
if (direction == LEFT)
Position -= Right * velocity;
if (direction == RIGHT)
Position += Right * velocity;
}
// 处理从鼠标输入系统接收的输入。需要x和y方向上的偏移值。
void ProcessMouseMovement(float xoffset, float yoffset, bool constrainPitch = true)
{
xoffset *= MouseSensitivity;
yoffset *= MouseSensitivity;
Yaw += xoffset;
Pitch += yoffset;
// 确保当投球超出边界时,屏幕不会翻转
if (constrainPitch)
{
if (Pitch > 89.0f)
Pitch = 89.0f;
if (Pitch < -89.0f)
Pitch = -89.0f;
}
// 使用更新的Euler角度更新前、右和上矢量
updateCameraVectors();
}
// 处理从鼠标滚轮事件接收的输入。仅需要在垂直车轮轴上输入
void ProcessMouseScroll(float yoffset)
{
Zoom -= (float)yoffset;
if (Zoom < 1.0f)
Zoom = 1.0f;
if (Zoom > 75.0f)
Zoom = 75.0f;
}
private:
// 根据相机的(更新的)Euler角度计算前矢量
void updateCameraVectors()
{
// calculate the new Front vector
float PI=3.1415926;
QVector3D front;
front.setX(cos(Yaw*PI/180.0) * cos(Pitch*PI/180.0));
front.setY( sin(Pitch*PI/180.0));
front.setZ(sin(Yaw*PI/180.0) * cos(Pitch*PI/180.0));
front.normalize();
Front = front;
// also re-calculate the Right and Up vector
Right = QVector3D::crossProduct(Front, WorldUp);
// 标准化向量,因为向上或向下看得越多,向量的长度就越接近0,这会导致移动速度变慢。
Right.normalize();
Up = QVector3D::crossProduct(Right, Front);
Up.normalize();
}
};
#endif
简单的漫游器
漫游器的实现主要是通过Qt的窗口事件触发后将触发产生的位置偏量给摄像机类进行计算,从摄像机类中得到视图矩阵将模型的位置进行改变。
myopenglwidget.h
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QTimer>
#include <QElapsedTimer>
#include "camera.h"
class MyOpenGLWidget : public QOpenGLWidget,QOpenGLFunctions_3_3_Core
{
Q_OBJECT
public:
enum Shape
{
None,
Circle,
Rect,
Triangle
};
explicit MyOpenGLWidget(QWidget *parent = nullptr);
~MyOpenGLWidget();
void DrawShape(Shape shape);
void setWireFrameMode(bool enterWireframe);
void onTimeout();
protected:
virtual void initializeGL();
virtual void resizeGL(int w, int h);
virtual void paintGL();
signals:
private:
Shape m_shape;
QOpenGLShaderProgram shaderProgram;
QOpenGLTexture *textureWall;
QOpenGLTexture *textureSmile;
QOpenGLTexture *textureSmall;
QTimer *m_timer;
QElapsedTimer m_time;
Camera m_camera;
// QWidget interface
protected:
void keyPressEvent(QKeyEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
};
#endif // MYOPENGLWIDGET_H
myopenglwidget.cpp
#include "myopenglwidget.h"
#include <QImage>
#include <QKeyEvent>
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
};
QVector<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)
};
#define TIMEOUT 100
unsigned int indices[] = { // note that we start from 0!
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
unsigned int VBO, VAO ,EBO;
float ratio = 0.5;
QPoint deltaPos;
MyOpenGLWidget::MyOpenGLWidget(QWidget *parent)
: QOpenGLWidget{parent}
{
setFocusPolicy(Qt::StrongFocus);
setMouseTracking(true);
m_timer = new QTimer(this);
connect(m_timer,&QTimer::timeout,this,&MyOpenGLWidget::onTimeout);
m_timer->start(TIMEOUT);
m_time.start();
m_camera.Position = QVector3D(0.0,0.0,3.0);
}
MyOpenGLWidget::~MyOpenGLWidget()
{
if(!isValid()) return;
makeCurrent();
glDeleteBuffers(1,&VBO);
glDeleteBuffers(1,&EBO);
glDeleteVertexArrays(1,&VAO);
doneCurrent();
}
void MyOpenGLWidget::DrawShape(Shape shape)
{
m_shape=shape;
update();
}
void MyOpenGLWidget::setWireFrameMode(bool enterWireframe)
{
makeCurrent();
if(enterWireframe)
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
else
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
update();
doneCurrent();
}
void MyOpenGLWidget::onTimeout()
{
update();
}
void MyOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
//创建VBO和VAO对象,并赋予ID
glGenVertexArrays(1,&VAO);
glGenBuffers(1,&VBO);
//绑定VAO和VBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
//为当前绑定到target的缓冲区对象创建一个新的数据存储。
//如果data不是NULL,则使用来自此指针的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
//告知显卡如何解析缓冲里的属性值
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,5*sizeof(float),(void *)0);
//开启VAO管理的第一个属性值
glEnableVertexAttribArray(0);
//告知显卡如何解析缓冲里的属性值
glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,5*sizeof(float),(void *)(3*sizeof(float)));
//开启VAO管理的第三个属性值
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER,0);
bool success;
shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":Shaders/shapes.vert");
shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":Shaders/shapes.frag");
success = shaderProgram.link();
if(!success)
{
qDebug()<<"Error:"<<shaderProgram.log();
}
shaderProgram.bind();
shaderProgram.setUniformValue("ratio",ratio);
shaderProgram.setUniformValue("texturewall",0);
shaderProgram.setUniformValue("texturesmile",1);
shaderProgram.setUniformValue("textureSmall",2);
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
textureWall = new QOpenGLTexture(QImage(":Images/images/wall.jpg").mirrored());
textureSmile = new QOpenGLTexture(QImage(":Images/images/awesomeface.png").mirrored());
textureSmall = new QOpenGLTexture(QImage(":Images/images/small.png").mirrored());
glBindVertexArray(0);
}
void MyOpenGLWidget::resizeGL(int w, int h)
{
Q_UNUSED(w);
Q_UNUSED(h);
}
void MyOpenGLWidget::paintGL()
{
QMatrix4x4 model;
QMatrix4x4 view;
QMatrix4x4 projection;
view=m_camera.GetViewMatrix();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shaderProgram.bind();
projection.perspective(m_camera.Zoom,(float)width()/(height()),0.1,100.0);
shaderProgram.setUniformValue("projection", projection);
shaderProgram.setUniformValue("view", view);
glBindVertexArray(VAO);
//glDrawArrays(GL_TRIANGLES, 0, 3);
switch(m_shape)
{
case Rect:
textureWall->bind(0);
textureSmile->bind(1);
textureSmall->bind(2);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
foreach (auto item, cubePositions)
{
model.setToIdentity();
model.translate(item);
shaderProgram.setUniformValue("model", model);
glDrawArrays(GL_TRIANGLES,0,36);
}
break;
case None:
break;
case Triangle:
break;
case Circle:
break;
}
}
void MyOpenGLWidget::keyPressEvent(QKeyEvent *event)
{
float deltatime=TIMEOUT / 1000.0;
switch(event->key())
{
case Qt::Key_Up:ratio += 0.1;break;
case Qt::Key_Down:ratio -= 0.1;break;
case Qt::Key_W: m_camera.ProcessKeyboard(FORWARD,deltatime);break;
case Qt::Key_S: m_camera.ProcessKeyboard(BACKWARD,deltatime);break;
case Qt::Key_D: m_camera.ProcessKeyboard(RIGHT,deltatime);break;
case Qt::Key_A: m_camera.ProcessKeyboard(LEFT,deltatime);break;
}
if(ratio > 1) ratio = 1;
if(ratio < 0) ratio = 0;
makeCurrent();
shaderProgram.bind();
shaderProgram.setUniformValue("ratio",ratio);
update();
doneCurrent();
}
void MyOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
static QPoint lastPos(width()/2,height()/2);
auto currentPos=event->pos();
deltaPos=currentPos-lastPos;
lastPos=currentPos;
m_camera.ProcessMouseMovement(deltaPos.x(),-deltaPos.y());
update();
}
void MyOpenGLWidget::wheelEvent(QWheelEvent *event)
{
m_camera.ProcessMouseScroll(event->angleDelta().y()/120);
update();
}
完整代码链接