学习笔记2--与OpenGL窗口中的立方体进行简单的交互

版权声明:本文为博主原创文章,欢迎转载。 https://blog.csdn.net/u014328804/article/details/58256774

对于任何应用程序,交互都是必须的,本文将在上一篇笔记的基础上,再添加些交互功能。

添加的交互功能是较为常见的:让OpenGL窗口中的渲染的立方体响应鼠标事件,实现对立方体的旋转、放缩、移动

要实现以上交互功能,主要解决两个问题:一个是界面如何响应鼠标输入事件,这主要重写Qt中窗口部件的mouseMoveEventwheelEventmousePressEvent等类似的函数来实现对鼠标事件的捕捉;另一个是如何将捕捉到的鼠标事件体现到OpenGL渲染的立方体上,这主要用到坐标变换、OpenGL的坐标系统(局部坐标、世界坐标、观察坐标、裁剪坐标、屏幕坐标)、摄像机以及坐标系统和摄像机的关系。

本文不打算介绍关于Qt和OpenGL的相关知识,对于Qt可以查看帮助文档,对于OpenGL中关于桌边系统和摄像机知识,参考:https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/

本例的程序将继续在上一篇笔记的基础上添加:

openglwindow.h:仅需添加需要重写的虚函数的声明即可;

#ifndef OPENGLWINDOW_H
#define OPENGLWINDOW_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>

class openglwindow : public QOpenGLWidget,
                     protected QOpenGLFunctions_3_3_Core
{
    Q_OBJECT

public:
    openglwindow(QWidget *parent = 0);
    ~openglwindow();

    void initializeGL();
    void resizeGL(int width, int height);
    void paintGL();

protected:
    void mouseMoveEvent(QMouseEvent *event);
    void wheelEvent(QWheelEvent *event);
    void mousePressEvent(QMouseEvent *event);

};

#endif // OPENGLWINDOW_H

openglwindow.cpp:

(1) 添加头文件(包括鼠标事件、glm库用于坐标变换);

(2) 定义鼠标事件先关的一些全局变量;

(3) 改变着色器源码,添加uniform变量,用来实现传递转换矩阵;

(4) 对openglwindow.h中的鼠标事件虚函数进行重写(具体见源代码);

(5) 在paintGL函数中定义三个矩阵,用来存储观察矩阵、投影矩阵和模型矩阵,并将该值导入到着色器中的uniform变量中;

(6) 最后别忘了添加update函数到paintGL函数中,调用该函数实现将鼠标事件的信息实时传入到着色器中。

#include "openglwindow.h"
#include <iostream>
#include <QMouseEvent>

//OpenGL Mathematics 用来进行数学变换
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

/*******************************************************************************
 * 鼠标操作的一些设置
 ******************************************************************************/

//相机位置及朝向,用来构造ViewMatrix,进行“世界空间”到“观察空间”的转换
glm::vec3 cameraPos    = glm::vec3(0.0f, 0.0f, 3.0f);   //相机位置
glm::vec3 worldCentrol = glm::vec3(0.0f, 0.0f, 0.0f);   //世界坐标原点,相机始终朝向这个方向
glm::vec3 cameraUp     = glm::vec3(0.0f, 1.0f, 0.0f);   //相机的顶部始终朝向y轴

//构建ModelMatrix,进行“局部空间”到“世界空间”的转换
glm::vec3 transVec     = glm::vec3(0.0f, 0.0f, 0.0f);   //局部坐标在世界坐标中的平移量

GLfloat yaw   = 0.0f;   //偏航角
GLfloat pitch = 0.0f;   //俯仰角
GLfloat lastX = 0;      //光标上次x值
GLfloat lastY = 0;      //光标上次y值

/*******************************************************************************
 * 着色器、着色器程序、VAO(顶点数组对象)、VBO(顶点缓冲对象)、EBO(索引缓冲对象)
 ******************************************************************************/

const GLuint NumVertexShader = 1;       //顶点着色器的数目
const GLuint NumFragmentShader = 1;     //片段着色器的数目
const GLuint NumShaderProgram = 1;      //着色器程序的数目
const GLuint NumVAO = 1;                //VAO的数目
const GLuint NumVBO = 1;                //VBO的数目
const GLuint NumEBO = 1;                //EBO的数目

GLuint IDVertexShader[NumVertexShader];
GLuint IDFragmentShader[NumFragmentShader];
GLuint IDShaderProgram[NumShaderProgram];
GLuint IDVAO[NumVAO];
GLuint IDVBO[NumVBO];
GLuint IDEBO[NumEBO];

/*******************************************************************************
 * 着色器源码
 ******************************************************************************/

const GLchar *vertexShaderSource =
        "#version 330 core\n"
        "layout(location = 0) in vec3 vPosition;\n"
        "layout(location = 1) in vec3 vColor;\n"
        "uniform mat4 model;\n"
        "uniform mat4 view;\n"
        "uniform mat4 projection;\n"
        "out vec3 Color;\n"
        "void main()\n"
        "{\n"
            "gl_Position = projection * view * model * vec4(vPosition, 1.0);\n"
            "Color = vColor;\n"
        "}\n";

const GLchar *fragmentShaderSource =
        "#version 330 core\n"
        "in vec3 Color;\n"
        "out vec4 fColor;\n"
        "void main()\n"
        "{\n"
            "fColor = vec4(Color, 1.0f);\n"
        "}\n";

openglwindow::openglwindow(QWidget *parent)
    :QOpenGLWidget(parent)
{
    //设置OpenGL的版本信息
    QSurfaceFormat format;
    format.setRenderableType(QSurfaceFormat::OpenGL);
    format.setProfile(QSurfaceFormat::CoreProfile);
    format.setVersion(3,3);
    setFormat(format);
}

openglwindow::~openglwindow()
{

}

void openglwindow::initializeGL()
{
    //初始化OpenGL函数
    initializeOpenGLFunctions();

    //设置全局变量
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    /******************************* 顶点着色器创建 *******************************/
    /* 第一个顶点着色器 */
    IDVertexShader[0] = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(IDVertexShader[0], 1, &vertexShaderSource, nullptr);
    glCompileShader(IDVertexShader[0]);
    //检查编译是否出错
    GLint success;
    GLchar infoLog[512];
    glGetShaderiv(IDVertexShader[0], GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(IDVertexShader[0], 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    /******************************* 片段着色器创建 *******************************/
    /* 第一个片元着色器 */
    IDFragmentShader[0] = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(IDFragmentShader[0], 1, &fragmentShaderSource, nullptr);
    glCompileShader(IDFragmentShader[0]);
    //检查编译是否出错
    glGetShaderiv(IDFragmentShader[0], GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(IDFragmentShader[0], 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    /********************************* 链接着色器 *********************************/
    /* 第一个着色器程序 */
    IDShaderProgram[0] = glCreateProgram();
    glAttachShader(IDShaderProgram[0], IDVertexShader[0]);
    glAttachShader(IDShaderProgram[0], IDFragmentShader[0]);
    glLinkProgram(IDShaderProgram[0]);
    //检查链接错误
    glGetProgramiv(IDShaderProgram[0], GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(IDShaderProgram[0], 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }

    //删除着色器对象(生成着色器程序之后不再需要)
    glDeleteShader(IDVertexShader[0]);
    glDeleteShader(IDFragmentShader[0]);

    /******************************** 设置顶点数据 ********************************/
    //彩色正方体
    GLfloat vertices[] =
    {
        -0.5f, -0.5f,  0.5f, 1.0f, 0.0f, 0.0f,
         0.5f, -0.5f,  0.5f, 0.0f, 1.0f, 0.0f,
         0.5f,  0.5f,  0.5f, 0.0f, 0.0f, 1.0f,
        -0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 0.0f,
        -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f,
         0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 1.0f,
         0.5f,  0.5f, -0.5f, 0.8f, 0.5f, 0.2f,
        -0.5f,  0.5f, -0.5f, 0.2f, 0.8f, 0.5f
    };
    GLuint indices[] =
    {
        0, 1, 2, 2, 3, 0,   //前
        4, 5, 6, 6, 7, 4,   //后
        0, 4, 7, 7, 3, 0,   //左
        1, 5, 6, 6, 2, 1,   //右
        0, 4, 5, 5, 1, 0,   //上
        3, 7, 6, 6, 2, 3    //下
    };

    /****************************************************************************/
    /**************************** VAO\VBO\顶点属性指针 ****************************/
    /****************************************************************************/

    /* 创建相关对象 */
    glGenVertexArrays(NumVAO, IDVAO);
    glGenBuffers(NumVBO, IDVBO);
    glGenBuffers(NumEBO, IDEBO);

    /* 显示立方体 */
    glBindVertexArray(IDVAO[0]);    //开始记录状态信息

    glBindBuffer(GL_ARRAY_BUFFER, IDVBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IDEBO[0]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3*sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glBindVertexArray(0);           //结束记录状态信息
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);   //在VAO后解绑,是为了不让VAO把解绑EBO的信息包含进入
    /* 显示立方体 */

    /* 固定属性区域 */
    glEnable(GL_DEPTH_TEST);        //开启深度测试
}

void openglwindow::paintGL()
{
    //清理屏幕
    glClear(GL_COLOR_BUFFER_BIT);

    //实现参数的刷新
    update();

    //渲染彩色正方体
    glUseProgram(IDShaderProgram[0]);

    glm::mat4 view;
    glm::mat4 projection;
    glm::mat4 model;
    GLint modelLoc = glGetUniformLocation(IDShaderProgram[0], "model");
    GLint viewLoc = glGetUniformLocation(IDShaderProgram[0], "view");
    GLint projLoc = glGetUniformLocation(IDShaderProgram[0], "projection");
    view = glm::lookAt(cameraPos, worldCentrol, cameraUp);
    projection = glm::perspective(glm::radians(45.0f), 4.0f / 3.0f, 0.1f, 100000.0f);
    model = glm::translate(model, transVec);
    model = glm::rotate(model, glm::radians(pitch), glm::vec3(1.0f, 0.0f, 0.0f));   //按住左键,上下拖动鼠标让立方体绕x轴旋转
    model = glm::rotate(model, glm::radians(yaw), glm::vec3(0.0f, 1.0f, 0.0f));     //按住左键,左右拖动鼠标让立方体绕y轴旋转
    glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
    glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));

    glBindVertexArray(IDVAO[0]);
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);

    //强制刷新缓冲区,保证命令被执行
    glFlush();
}

void openglwindow::resizeGL(int width, int height)
{
    //未使用
    Q_UNUSED(width);
    Q_UNUSED(height);
}

void openglwindow::mouseMoveEvent(QMouseEvent *event)
{
    //鼠标左键用来实现对物体的旋转功能
    if(event->buttons() == Qt::LeftButton)
    {
        //计算yaw,pitch值的改变量
        GLfloat xoffset = event->x() - lastX;
        GLfloat yoffset = event->y() - lastY;
        lastX = event->x();
        lastY = event->y();

        GLfloat sensitivity = 0.4f;     //旋转时的灵敏度
        xoffset *= sensitivity;
        yoffset *= sensitivity;

        yaw   += xoffset;
        pitch += yoffset;

        //可以用来设置俯仰角的上下界
        if (pitch > 89.0f)
            pitch = 89.0f;
        if (pitch < -89.0f)
            pitch = -89.0f;
    }
    //鼠标右键用来实现对移动物体(即局部坐标在世界坐标中的移动)
    else if(event->buttons() == Qt::RightButton)
    {
        //计算x,y方向的偏移量
        GLfloat xoffset = event->x() - lastX;
        GLfloat yoffset = event->y() - lastY;
        lastX = event->x();
        lastY = event->y();

        GLfloat sensitivity = 0.01f;    //移动时的灵敏度
        xoffset *= sensitivity;
        yoffset *= sensitivity;

        //仅需在x-y平面内移动即可
        transVec += glm::vec3(xoffset, -yoffset, 0.0f);
    }
}

//滚轮实现对物体的放大缩小,摄像机距离远近(放大缩小)
void openglwindow::wheelEvent(QWheelEvent *event)
{
    GLfloat sensitivity = 0.0005f;
    cameraPos *= (1.0f - event->delta() * sensitivity);
}

void openglwindow::mousePressEvent(QMouseEvent *event)
{
    //记录点击光标时的位置
    lastX = event->x();
    lastY = event->y();
}
运行结果:


现在可以看到在上一篇中绘制的立方体的全貌了。

编译环境及版本:Win10企业版+Qt 5.7.1(MSVC 2015, 32bit)+OpenGL3.3 Core Profile

阅读更多
换一批

没有更多推荐了,返回首页