Qt и OpenGL: не удается инициализировать openGL VBO или VAO после первого вызова paintGL?

Вопрос или проблема

Я пишу программу, используя Qt5 и OpenGL. Это интерактивная 3D-среда, которая позволяет пользователю в любой момент импортировать несколько триангулированных мешей (среди прочего). Изначально я использовал фиксированный конвейер функций, но узнал о шейдерах и хотел бы перейти на них.

После того, как я преобразовал свой код для использования шейдеров, я столкнулся с странной проблемой, которую не могу решить, и пример кода здесь воспроизводит её минимально, как я мог:

#include <QApplication>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QTimer>
#include <QMatrix4x4>
#include <QVector3D>
#include <QVector>

#include <iostream>

#define TEMP_ERR_CHK { GLenum err = glGetError(); std::cout << __FILE__ << " " << __LINE__ << ":   OpenGL error code: " << (err) << std::endl; }

class Mesh
{
    public:

    Mesh(QOpenGLFunctions_3_3_Core* context_in)
    {
        context = context_in;
        init_vbos();
    }

    void init_vbos()
    {
        std::cout << "Я ИНИЦИАЛИЗИРУЮ VBO МЕША" << std::endl;
        vertices = {0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f};
        colors   = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f};
        normals  = {0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f};

        // Создаем VAO и VBO
        context->glGenVertexArrays(1, &vao);
        context->glBindVertexArray(vao);

        // VBO для позиции
        context->glGenBuffers(1, &vboPositions);
        context->glBindBuffer(GL_ARRAY_BUFFER, vboPositions);
        context->glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GLfloat), vertices.data(), GL_STATIC_DRAW);
        context->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
        context->glEnableVertexAttribArray(0);

        // VBO для цвета
        context->glGenBuffers(1, &vboColors);
        context->glBindBuffer(GL_ARRAY_BUFFER, vboColors);
        context->glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(GLfloat), colors.data(), GL_STATIC_DRAW);
        context->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
        context->glEnableVertexAttribArray(1);

        // VBO для нормалей
        context->glGenBuffers(1, &vboNormals);
        context->glBindBuffer(GL_ARRAY_BUFFER, vboNormals);
        context->glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(GLfloat), normals.data(), GL_STATIC_DRAW);
        context->glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
        context->glEnableVertexAttribArray(2);

        context->glBindVertexArray(0);

    }

    void draw()
    {
        std::cout << "рисование" << std::endl;
        TEMP_ERR_CHK
        context->glBindVertexArray(vao);
        TEMP_ERR_CHK
        context->glDrawArrays(GL_TRIANGLES, 0, 36);
        context->glBindVertexArray(0);  
    }

    private:
    GLuint vao, vboPositions, vboColors, vboNormals;
    QVector<GLfloat> vertices, colors, normals;
    float rotationAngle;
    QOpenGLFunctions_3_3_Core* context;
};

class OpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{

public:
    OpenGLWidget(QWidget* parent = nullptr)
        : QOpenGLWidget(parent), program(nullptr), rotationAngle(24.0f) {}

    virtual void mousePressEvent(QMouseEvent* event) override final
    {
        std::cout << "AAAA" << std::endl;   
        mesh_to_draw = new Mesh(this); //РАЗМЕЩЕНИЕ 0
        this->update();
    }

    ~OpenGLWidget() {
    }

protected:
    void initializeGL() override {
        initializeOpenGLFunctions();
        std::cout << "Я ИНИЦИАЛИЗИРУЮ OPENGL" << std::endl;
        glEnable(GL_DEPTH_TEST);

        // Инициализация шейдерной программы
        program = new QOpenGLShaderProgram(this);
        program->addShaderFromSourceCode(QOpenGLShader::Vertex,
            R"(
            #version 330 core
            layout(location = 0) in vec3 position;
            layout(location = 1) in vec3 color;
            layout(location = 2) in vec3 normal;

            out vec3 fragColor;
            out vec3 fragNormal;
            out vec3 fragPosition;

            uniform mat4 model;
            uniform mat4 view;
            uniform mat4 projection;

            void main() {
                fragColor = color;
                fragNormal = normalize(mat3(transpose(inverse(model))) * normal);
                fragPosition = vec3(model * vec4(position, 1.0f));
                gl_Position = projection * view * vec4(fragPosition, 1.0f);
            })");

        program->addShaderFromSourceCode(QOpenGLShader::Fragment,
            R"(
            #version 330 core
            in vec3 fragColor;
            in vec3 fragNormal;
            in vec3 fragPosition;
            out vec4 fragColorOut;

            uniform vec3 lightPosition;
            uniform vec3 viewPosition;

            void main() {
                // Простая модель освещения Фонга
                vec3 ambient = 0.1 * fragColor;
                vec3 norm = normalize(fragNormal);
                vec3 lightDir = normalize(lightPosition - fragPosition);
                float diff = max(dot(norm, lightDir), 0.0);
                vec3 diffuse = diff * fragColor;
                vec3 viewDir = normalize(viewPosition - fragPosition);
                vec3 reflectDir = reflect(-lightDir, norm);
                float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
                vec3 specular = 0.5 * spec * vec3(1.0);

                fragColorOut = vec4(ambient + diffuse + specular, 1.0);
            })");

        program->link();

        // mesh_to_draw = new Mesh(this); //РАЗМЕЩЕНИЕ 1
    }

    void paintGL() override {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        program->bind();

        // Настройка матриц трансформации
        QMatrix4x4 model;
        model.rotate(rotationAngle, 0.0f, 1.0f, 0.0f);  // Вращение вокруг оси Y

        QMatrix4x4 view;
        view.lookAt(QVector3D(0.0f, 0.0f, 3.0f), QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0f, 1.0f, 0.0f));

        QMatrix4x4 projection;
        projection.perspective(45.0f, width() / float(height()), 0.1f, 100.0f);

        program->setUniformValue("model", model);
        program->setUniformValue("view", view);
        program->setUniformValue("projection", projection);

        program->setUniformValue("lightPosition", QVector3D(1.0f, 1.0f, 1.0f));
        program->setUniformValue("viewPosition", QVector3D(0.0f, 0.0f, 3.0f));

        if (mesh_to_draw) mesh_to_draw->draw();

        program->release();
    }

    void resizeGL(int w, int h) override {
        glViewport(0, 0, w, h);
    }

private:
    QOpenGLShaderProgram* program;
    Mesh* mesh_to_draw = nullptr;
    float rotationAngle;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    OpenGLWidget widget;
    widget.setWindowTitle("Вращающийся куб");
    widget.resize(800, 600);
    widget.show();
    return app.exec();
}

Представленный выше код содержит класс меша, который хранит данные о вершинах, нормалях и цветах, а также VBO и VAO, которые я использую для его рендеринга. Есть функция, которая инициализирует эти объекты.

Существует класс виджета OpenGL, который обрабатывает рендеринг и наследуется от QOpenGLWidget и QOpenGLFunctions_3_3_Core. Код содержит два закомментированных местоположения: Местоположение 0 и Местоположение 1. Эти местоположения представляют разные точки, которые инициализируют различные ресурсы меша. Инициализация в местоположении 1 дает ожидаемый результат. Я вижу треугольник на экране, и все в порядке. Проблема в том, что мне нужно инициализировать меш внутри функции initializeGL, которая вызывается в начале программы. Я хочу иметь возможность добавлять меш в произвольное время во время выполнения. Чтобы смоделировать это, я добавил mousePressEvent, который инициализирует меш (местоположение 0). Однако, когда я инициализирую таким образом, я не вижу ничего на экране, и OpenGL дает мне ошибку 1282 (недопустимая операция). Похоже, что, инициализировав VAO после первого вызова рисования (или что-то в этом роде), VAO больше не действителен.

Для полноты картины вот терминальные выводы для каждого случая:
Местоположение 1:

Я ИНИЦИАЛИЗИРУЮ OPENGL
Я ИНИЦИАЛИЗИРУЮ VBO МЕША
рисование
src/main.cpp 64:   OpenGL error code: 0
src/main.cpp 66:   OpenGL error code: 0
рисование
src/main.cpp 64:   OpenGL error code: 0
src/main.cpp 66:   OpenGL error code: 0

(треугольник отображается правильно)

Местоположение 0:

Я ИНИЦИАЛИЗИРУЮ OPENGL
AAAA
Я ИНИЦИАЛИЗИРУЮ VBO МЕША
рисование
src/main.cpp 64:   OpenGL error code: 0
src/main.cpp 66:   OpenGL error code: 1282

(ничего на экране)

Как мне это исправить?

Ответ или решение

Проблема и Решение: Инициализация VBO и VAO в Qt с использованием OpenGL

Введение

Ваша ситуация связана с использованием OpenGL в контексте Qt, где возникает ошибка 1282 (неверная операция) при попытке инициализировать VAO и VBO после первого вызова paintGL. Это довольно распространенная проблема, и причина её заключается в порядке инициализации объектов OpenGL в контексте графической отрисовки.

Описание Проблемы

Вы описали две локации в вашем коде:

  1. Локация 1 — инициализация Mesh в функции initializeGL.
  2. Локация 0 — инициализация Mesh при событиях мыши (например, нажатие кнопки мыши), что приводит к ошибке OpenGL при первом вызове метода draw.

Ошибки возникают потому, что OpenGL имеет определенные ограничения на то, когда и как могут быть созданы VBO и VAO. Все операции с объектами OpenGL должны выполняться во время контекста OpenGL, и вам необходимо гарантировать, что VBO и VAO инициализируются до любого вызова прорисовки.

Анализ Причины Ошибки

Когда вы вызываете init_vbos() в обработчике событий мыши, в момент вызова draw() контекст OpenGL может быть недоступен или находиться в состоянии, когда предыдущие отрисовки еще не завершены. Это приводит к ошибке 1282 (неверная операция), так как VAO, созданный после первой отрисовки, имеет состояние, несовместимое с текущим контекстом.

Решение Проблемы

Для успешной инициализации VAO и VBO после первого вызова paintGL, следует применить следующий подход:

  1. Инициализация в контексте OpenGL: Убедитесь, что любые вызовы, которые создают VAO и VBO, происходят в пределах контекста OpenGL. Вы можете использовать метод update() для инициирования нового цикла отрисовки.

  2. Отложенная Инициализация: Вместо того, чтобы создавать новый объект Mesh в событии мыши, создайте его только после того, как контекст OpenGL будет готов к работе. Для этого можно добавить специальное событие или использовать таймер:

    void mousePressEvent(QMouseEvent* event) override final
    {
        std::cout << "AAAA" << std::endl;
        QTimer::singleShot(0, [this]() {
            mesh_to_draw = new Mesh(this); // Теперь инициализация будет в контексте OpenGL
            update();
        });
    }
  3. Переинициализация VAO: Если объект VAO или VBO был инициализирован, убедитесь, что они корректно удаляются или переинициализируются. OpenGL не позволяет повторную инициализацию VAO без его связывания и разрывного распределения.

  4. Убедитесь в правильной последовательности вызовов: Всегда обязательно выполняйте glBindVertexArray() и всю логику рендеринга в paintGL().

Заключение

Проблема, с которой вы столкнулись, требует строгого соблюдения порядка инициализации и использования контекста OpenGL. Убедитесь, что все объекты и ресурсы создаются и инициализируются в корректной последовательности и с соблюдением условий контекста, чтобы избежать ошибок. Применение вышеописанных рекомендаций должно запустить ваш проект корректно, и вы сможете продолжить разрабатывать интерактивную 3D среду в Qt с использованием OpenGL.

Оцените материал
Добавить комментарий

Капча загружается...