欢迎访问宙启技术站
智能推送

使用QOpenGLWidget()实现对象拾取功能

发布时间:2024-01-02 10:14:57

QOpenGLWidget是Qt框架为了方便开发者在Qt中使用OpenGL而提供的一个OpenGL容器类,用于在Qt界面中显示OpenGL渲染的内容。对象拾取(Object Pick)是OpenGL中的一项功能,用于检测用户在屏幕上点击的点是否处于渲染场景中的某一个物体上,并返回该物体的编号或者其他相关信息。

下面是一个使用QOpenGLWidget实现对象拾取功能的例子:

首先,我们需要创建一个继承自QOpenGLWidget的自定义类,例如MyOpenGLWidget。在这个类中,我们需要重写initializeGL()、paintGL()和mousePressEvent()等函数。

class MyOpenGLWidget : public QOpenGLWidget
{
    Q_OBJECT

public:
    MyOpenGLWidget(QWidget *parent = nullptr);
    ~MyOpenGLWidget();

protected:
    void initializeGL() override;
    void paintGL() override;
    void mousePressEvent(QMouseEvent *event) override;

private:
    GLuint m_objectVao;
    GLuint m_objectVbo;
    GLuint m_objectEbo;
    std::vector<float> m_vertices;
    std::vector<unsigned int> m_indices;
    int m_selectedObject;

    void createObjects();
    void renderObjects();
    int pickObject(int x, int y);
};

在initializeGL()函数中,我们需要初始化OpenGL相关的状态和资源。在本例中,我们生成一个简单的立方体物体用于演示。我们使用OpenGL的核心模式,并生成顶点数组对象(VAO)、顶点缓冲对象(VBO)和索引缓冲对象(EBO)。

void MyOpenGLWidget::initializeGL()
{
    initializeOpenGLFunctions();

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    createObjects();
}

在paintGL()函数中,我们进行渲染操作。在本例中,我们只简单地绘制一个立方体。

void MyOpenGLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    renderObjects();
}

createObjects()函数用于生成渲染场景中的物体的顶点数据和索引数据。在本例中,我们使用6个面来构造一个简单的立方体。

void MyOpenGLWidget::createObjects()
{
    m_vertices = {
        // 前面
        -0.5f, -0.5f, 0.5f,
         0.5f, -0.5f, 0.5f,
         0.5f,  0.5f, 0.5f,
        -0.5f,  0.5f, 0.5f,
        // 后面
        -0.5f, -0.5f,-0.5f,
         0.5f, -0.5f,-0.5f,
         0.5f,  0.5f,-0.5f,
        -0.5f,  0.5f,-0.5f
    };

    m_indices = {
        // 前面
        0, 1, 2,
        0, 2, 3,
        // 后面
        4, 7, 6,
        4, 6, 5,
        // 上面
        3, 2, 6,
        3, 6, 7,
        // 下面
        0, 4, 5,
        0, 5, 1,
        // 左面
        0, 3, 7,
        0, 7, 4,
        // 右面
        1, 5, 6,
        1, 6, 2
    };

    glGenVertexArrays(1, &m_objectVao);
    glBindVertexArray(m_objectVao);

    glGenBuffers(1, &m_objectVbo);
    glBindBuffer(GL_ARRAY_BUFFER, m_objectVbo);
    glBufferData(GL_ARRAY_BUFFER, m_vertices.size() * sizeof(float), m_vertices.data(), GL_STATIC_DRAW);

    glGenBuffers(1, &m_objectEbo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_objectEbo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof(unsigned int), m_indices.data(), GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

    glBindVertexArray(0);
}

renderObjects()函数用于渲染场景中的物体。在本例中,我们只绘制一个立方体,并根据用户选择高亮显示当前选中的物体。

void MyOpenGLWidget::renderObjects()
{
    glBindVertexArray(m_objectVao);

    for (unsigned int i = 0; i < m_indices.size() / 3; i += 3)
    {
        if (i / 3 == m_selectedObject)
            glColor3f(1.0f, 1.0f, 0.0f);
        else
            glColor3f(1.0f, 1.0f, 1.0f);

        glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, (void*)(i * sizeof(unsigned int)));
    }

    glBindVertexArray(0);
}

在mousePressEvent()函数中,我们获取用户点击的坐标,并调用pickObject()函数进行拾取操作。拾取操作的思路是根据点击的坐标从渲染场景的帧缓冲区中读取像素值,并通过像素值反推出对应的物体编号。

void MyOpenGLWidget::mousePressEvent(QMouseEvent *event)
{
    int x = event->x();
    int y = height() - event->y();

    m_selectedObject = pickObject(x, y);

    update();
}

pickObject()函数用于执行拾取操作。在函数中,我们首先创建一个与窗口大小相同的帧缓冲区,并将其绑定为当前的渲染目标。然后,我们调用renderObjects()函数绘制场景中的物体,并读取对应位置的像素值,判断是否与物体对应。最后,我们解除帧缓冲区的绑定,并返回拾取到的物体编号。

int MyOpenGLWidget::pickObject(int x, int y)
{
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    GLuint colorBuffer;
    glGenRenderbuffers(1, &colorBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, colorBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width(), height());
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBuffer);

    GLuint depthBuffer;
    glGenRenderbuffers(1, &depthBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width(), height());
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuffer);

    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    {
        qDebug() << "Failed to create frame buffer object";
        return -1;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    renderObjects();

    unsigned char pixel[4];
    glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);

    int objectIndex = -1;

    if (pixel[0] == 255 && pixel[1] == 255 && pixel[2] == 255)
    {
        objectIndex = 0;
    }
    else if (pixel[0] == 255 && pixel[1] == 255 && pixel[2] == 0)
    {
        objectIndex = 1;
    }
    // 其他物体的判断

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glDeleteRenderbuffers(1, &colorBuffer);
    glDeleteRenderbuffers(1, &depthBuffer);
    glDeleteFramebuffers(1, &framebuffer);

    return objectIndex;
}

以上就是使用QOpenGLWidget实现对象拾取功能的示例。在本例中,我们使用一个简单的立方体作为场景中的物体,通过鼠标点击拾取物体并高亮显示选中的物体。当然,实际应用中可能需要更加复杂的场景和物体模型,需要根据具体