Qt实现简单的漫游器

文章目录

  • 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  
#include  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);
}

Qt实现简单的漫游器_第1张图片

以上是最简单的实现版本。

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

#include 

// 移动方向枚举量. 是一种抽象,以避开特定于窗口系统的输入方法
// 我们这里是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 
#include 
#include 
#include 
#include 
#include 
#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 
#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
};

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();
}

完整代码链接

你可能感兴趣的:(qt,OpenGL,qt,开发语言)