Вопрос или проблема
Я пишу программу, используя 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 — инициализация
Mesh
в функцииinitializeGL
. - Локация 0 — инициализация
Mesh
при событиях мыши (например, нажатие кнопки мыши), что приводит к ошибке OpenGL при первом вызове методаdraw
.
Ошибки возникают потому, что OpenGL имеет определенные ограничения на то, когда и как могут быть созданы VBO и VAO. Все операции с объектами OpenGL должны выполняться во время контекста OpenGL, и вам необходимо гарантировать, что VBO и VAO инициализируются до любого вызова прорисовки.
Анализ Причины Ошибки
Когда вы вызываете init_vbos()
в обработчике событий мыши, в момент вызова draw()
контекст OpenGL может быть недоступен или находиться в состоянии, когда предыдущие отрисовки еще не завершены. Это приводит к ошибке 1282 (неверная операция), так как VAO, созданный после первой отрисовки, имеет состояние, несовместимое с текущим контекстом.
Решение Проблемы
Для успешной инициализации VAO и VBO после первого вызова paintGL
, следует применить следующий подход:
-
Инициализация в контексте OpenGL: Убедитесь, что любые вызовы, которые создают VAO и VBO, происходят в пределах контекста OpenGL. Вы можете использовать метод
update()
для инициирования нового цикла отрисовки. -
Отложенная Инициализация: Вместо того, чтобы создавать новый объект
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(); }); }
-
Переинициализация VAO: Если объект VAO или VBO был инициализирован, убедитесь, что они корректно удаляются или переинициализируются. OpenGL не позволяет повторную инициализацию VAO без его связывания и разрывного распределения.
-
Убедитесь в правильной последовательности вызовов: Всегда обязательно выполняйте
glBindVertexArray()
и всю логику рендеринга вpaintGL()
.
Заключение
Проблема, с которой вы столкнулись, требует строгого соблюдения порядка инициализации и использования контекста OpenGL. Убедитесь, что все объекты и ресурсы создаются и инициализируются в корректной последовательности и с соблюдением условий контекста, чтобы избежать ошибок. Применение вышеописанных рекомендаций должно запустить ваш проект корректно, и вы сможете продолжить разрабатывать интерактивную 3D среду в Qt с использованием OpenGL.