OpenGL в Qt 5.1 – Часть 1 и 2

в 23:18, , рубрики: c++, KDAB, OpenGL, qt, Qt Software, Анимация и 3D графика, перевод, уроки, метки: , , , , ,

Эта статья является перевод статьи OpenGL in Qt 5.1 — Part 1 и Part 2

Часть 1


Эта статья является первой в серии. Она покажет, как использовать OpenGL в Qt 5. В этой статье будет краткий экскурс в историю поддержки OpenGL в Qt. Затем мы перейдем к описанию первой части новых возможностей, которые вошли в Qt 5.1. Последующие статьи будут содержать описание большего количества возможностей, а так же будут приведены несколько простых примеров того, насколько легко можно использовать Qt для работы с OpenGL.

(Очень) Краткая история Qt и OpenGL

Qt имеет длинную историю поддержки рисования с помощью OpenGL. Большинство разработчиков Qt имеют представления о QGLWidget и, возможно, о некоторых OpenGL-based движках. Это позволяет работать с «сырым» OpenGL или использовать удобства которые предоставляет API QPainter. В дополнение к этому, Qt предоставляет несколько полезных оберток вокруг различных OpenGL объектов: QGLShaderProgram, QGLFramebufferObject, QGLBufferи пр.

При проектировании Qt 5, эти QGL* классы были помечены как «законченные» («done») и новейшие QOpenGL* классы стали частью модуля QtGui (примечание переводчика — раньше был QtOpenGL). Причиной этих изменений является то, что новая визуализация в Qt Quick 2 основана на OpenGL, и в настоящее время является основной частью графических предложений Qt. Кроме того, новые QOpenGL* классы могут быть использованы в качестве непосредственной замены старых QGL* классов. Сейчас рекомендуется использовать QOpenGL* классы из библиотеки QtGui.

Qt 5.0 предоставляет (в основном) то же подмножество функциональности OpenGL которое предоставлял Qt 4.8. Это требовалось, в том числе, для функциональных возможностей, необходимых в Qt Quick 2. В дополнение к функциональности Qt 4.8, Qt 5.0 предоставляет средства для легкого создания собственных окн и контекстов OpenGL на любой платформе. Теперь не требуется возиться с особенностями различных платформ, чтобы создать контекст, который может поддерживать OpenGL Core profile. Вы можете просто использовать QOpenGLContext и спасти себя от седины!

В Qt 5.1 наши приключения начинаются с демонстрации все большей и большей функциональности OpenGL так, чтобы сделать использование OpenGL и Qt простым, элегантный и, надеюсь, веселым! С этой целью, KDAB вложила значительные средства для расширение границ Qt и OpenGL.

Функции, функции всюду!

OpenGL представляет из себя, грубо говоря, что-то вроде боли, при работе с различными платформами. Одна из главных причин этой боли является необходимость назначить адрес точки входа динамически, во время выполнения, а не во время сборки (когда это в состоянии сделать линковщик). Например, на Microsoft Windows, адрес любой функции, введенной в OpenGL начиная с версии 1.1, должен быть назначен во время выполнения. То есть это нужно сделать почти для всех функции, используемых в современном OpenGL!

Для решения этих проблем Qt предоставил пару полезных утилиты: QOpenGLContext::GetProcAddress() и QOpenGLFunctions. Первый может быть использован для ручного назначения точек входа, в то время как последний является классом, методы которого являются отображением общего подмножества функций OpenGL 2 и OpenGL ES 2. Эти помощники хороши, настолько, насколько это возможно. Проблема в том, что QOpenGLFunctions ограничен в предоставляемых методах (подмножества OpenGL 2 и OpenGL ES 2). А ручное назначение точек входа очень утомительная работа, чреватая ошибками. В качестве альтернативы можно использовать функции из сторонних библиотек, решающие эти проблемы, таких как Glew или GLee. Правда эти решения тоже вызывают проблемы для получения требуемой функциональности и удобной работой с Qt (например, учет порядка хидеров).

Используйте QOpenGLContext::versionFunctions()! Это скромная небольшая функция — ваш проводник в «утопию» адресов (точек входа) функций OpenGL:) Эта функция может быть использована для получения указателя на объект (с методами для каждой функции), в требуемой версии и профиле OpenGL. Давайте взглянем на простой пример. Скажем, у нас есть подкласс QWindow, который осуществляет рендер. Мы хотим создать OpenGL 4.3 Core profile и пользоваться всеми предоставляемыми функциями. Сделать это очень просто:

Window::Window(QScreen * screen) : QWindow(screen) {
  // Скажем Qt что мы используем OpenGL для этого окна
  setSurfaceType(OpenGLSurface);

  // Специфицируем формат и создаем платфоро-зависимый сюрфейс
  QSurfaceFormat format;
  format.setDepthBufferSize(24);
  format.setMajorVersion(4);
  format.setMinorVersion(3);
  format.setSamples(4);
  format.setProfile(QSurfaceFormat::CoreProfile);
  setFormat(format);
  create();

  // Создаем OpenGL контекст
  m_context = new QOpenGLContext;
  m_context->setFormat(format);
  m_context->create();

  // Сделаем контекст текущим для этого окна
  m_context->makeCurrent(this);
  // Получить объект функции и назначить все точки входа
  // m_funcs объявлен как: QOpenGLFunctions_4_3_Core * m_funcs
  m_funcs = m_context->versionFunctions();
  
  if (!m_funcs) {
    qWarning("Could not obtain OpenGL versions object");
    exit(1);
  }
   
  m_funcs->initializeOpenGLFunctions();
}

С этого момента мы можем просто использовать функции-члены объекта QOpenGLFunctions_4_3_Core. Например:

  // Установить Vertex Attrib Divisor
  // Который используется с instanced rendering
  // (введен в OpenGL 3.3)
  m_funcs->glVertexAttribDivisor(pointLocation, 1);

  // Процесс отправки через compute shader
  // (введенный в OpenGL 4.3)
  m_funcs->glDispatchCompute(512 / 16, 512 / 16, 1);

Как мы видим, достаточно легко иметь всю функциональность OpenGL в своих руках для любой платформы, которая поддерживает его. Кроме того, QOpenGLContext, QOpenGLFunctions_4_3_Core и подобные им классы минимизируют работу для использования функций путем распределение бекэндов, содержащих действующие указатели на функции. Так же, этот подход автоматически заботится о платформо-зависимых адресах функций (например, при использовании нескольких потоков и контекстов или нескольких GPU). Код для этих классов генерируется автоматически, с помощью вспомогательной утилиты, так что его очень легко обновлять при релизе новой версии OpenGL.

Расширения OpenGL (OpenGL Extensions)

OpenGL имеет популярный механизм расширений, позволяющий поставщикам ввести новую или экспериментальную функциональность и API, чтобы проверить, полезны ли они и хорошо ли они продуманны. К сожалению, если расширение вводит новые функции, то для них так же требуется указать адрес как и для других OpenGL функций (как указано выше).

Для использования OpenGL расширений, нужно пройти два этапа. Qt помогает с обоими этапами:

Этап 1:

Проверьте, что текущая реализация поддерживает требуемое расширение. Если расширение вводит новый API, укажите точки входа. Чтобы проверить, что расширение поддерживается, нужно использовать метод QOpenGLContext::hasExtension(). Кроме того, чтобы получить полный список поддерживаемых расширений можете использовать OpenGLContext::extensions():

// Очередь расширений
QList extensions = m_context->extensions().toList();
std::sort(extensions);
qDebug() << "Supported extensions (" << extensions.count() <<")";
  
foreach (const QByteArray &extension, extensions) {
  qDebug() << "    " << extension;
}

Этап 2:

На втором этапе нам потребовалось бы использовать нашего старого друга — метод QOpenGLContext::GetProcAddress(). В Qt 5.1 за это отвечает модуль QtOpenGLExtensions. Этот модуль является статической библиотекой и содержит класс для каждого расширения OpenGL (которое вводит новое API) из реестра Khronos'а. Чтобы использовать расширение OpenGL используйте код, подобный следующему:

  // Проверка поддержки расширения
  if (!m_context->hasExtension(QByteArrayLiteral("GL_ARB_instanced_arrays")) {
    qFatal("GL_ARB_instanced_arrays is not supported");
  }

  // Создаем экземпляр вспомогательного класса и разрешаем использование требуемой функции
  QOpenGLExtension_ARB_instanced_arrays * m_instanceFuncs = new QOpenGLExtension_ARB_instanced_arrays();
  m_instanceFuncs->initializeOpenGLFunctions();

  // Вызов функции расширения
  m_instanceFuncs->glVertexAttribDivisorARB(pointLocation, 1);

Как и для основных функции OpenGL, код для расширений — генерируемый и, вследствие этого, легко обновим в будущем.

Часть 2


Vertex Array Objects

Qt имеет QOpenGLBuffer (до этого был QGLBuffer), чтобы помочь управлять различными типами буферных объектов OpenGL, таких как per-vertex attribute data и element index buffers. OpenGL также имеет специальных контейнер, тип которого именуется Objects Vertex Array (VAOs), помогающий в работе с наборов vertex buffer object(VBO).

KDAB добавил код для Qt 5.1, который инкапсулирует несколько VAO с классом QOpenGLVertexArrayObject. Связывание экземпляра этого класса говорит OpenGL «запомнить» любое состоянии спецификации вершин, которое вы хотели бы потом установить. Мы можем позже восстановить требуемые спецификации состояния очень быстро, просто повторно связывая сам VAO. Это позволяет нам очень быстро переключаться между состояниями вершин для «объектов», которые мы хотим отрисовать в нашей функции рендеринга:

Примечание переводчика

На мой взгляд вместо «связывания», уместнее было бы использовать «обрусевший» глагол «биндить», который, возможно, привычнее слышать разработчикам.
void Scene::initialize() {
  // Предположим что мы имеем текущий QOpenGLContext и
  // m_shaderProgram экземпляр QOpenGLShaderProgram

  // Создаем VAO для рендеринга первого объекта
  m_vao1 = new QOpenGLVertexArrayObject( this );
  m_vao1->create();
  m_vao1->bind();

  // Установка нескольких VBO и IBO (используем QOpenGLBuffer для хранения данных,
  // указание формата, указание применения и т.д.). Это будет "запомнено"
  // с текущим связанным VAO
  m_positionBuffer.create();
  m_positionBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
  m_positionBuffer.bind();
  m_positionBuffer.allocate(positionData,
                            vertexCount * 3 * sizeof(float));
  
  m_shaderProgram.enableAttributeArray("vertexPosition");
  m_shaderProgram.setAttributeBuffer  ("vertexPosition", GL_FLOAT, 0, 3);

  m_colorBuffer.create();
  m_colorBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
  m_colorBuffer.bind();
  m_colorBuffer.allocate(colorData,
                         vertexCount * 3 * sizeof(float));
  
  m_shaderProgram.enableAttributeArray("vertexColor");
  m_shaderProgram.setAttributeBuffer  ("vertexColor", GL_FLOAT, 0, 3);

  // Повторить для буферов нормалей, текстурных координат,
  // касательных, ...
  ...

  // Создаем VAO для отображения второго объекта
  m_vao2 = new QOpenGLVertexArrayObject(this);
  m_vao2->create();
  m_vao2->bind();

  // Установка нескольких VBO и IBO для следующего объекта
  ...

  // "Промыть и повторить" для других объектов (см. примечание ниже)
  m_skyBoxVAO = new QOpenGLVertexArrayObject(this);
  ...
}

void Scene::render() {
  // Очищаем буферы
  m_funcs->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // Связываем шейдер для первого набор объектов
  m_phongShaderProgram->bind();
  ...

  // Переключаемся на данные о вершинах первого объекта и рисуем его
  m_vao1->bind();
  m_funcs->glDrawElements(...);

  // Переключаемся на данные о вершинах второго объекта и рисуем его
  m_vao2->bind();
  m_funcs->glDrawElements(...);

  // Мб поменять шейдер и/или текстуру и т.д.
  // И отрисовать остальные объекты
  m_skyboxShaderProgram->bind();
  ...
  m_skyboxVAO->bind();
  m_funcs->glDrawElements(...);
  ...
}
Примечание переводчика

«Промыть и повторить» — саркастический метафора, указывающая на повторение инструкций. Имеет корни от фразы «wash, rinse, repeat», которая указанна на упаковках большинства марок шампуней.

VAO были введены с версии OpenGL 3, но они требуются для OpenGL версии старше 3.1 с Core Profile. Кроме того, VAO доступны как расширения GL_ARB_vertex_array_object или GL_OES_vertex_array_object в OpenGL 2 и OpenGL ES 2 соответственно. Класс QOpenGLVertexArrayObject будет использовать основную функциональность (если это возможно) или обращаться в случае необходимости к расширению (если таковое имеется).

Использование VAO может существенно упростить код визуализации и увеличить производительность, т.к. драйвер OpenGL будет делать меньше проверок корректности (чем если бы делал больше количество операций с буфером).

Part 3,4,5 coming soon...PS: Большая просьба, все стилистические и грамматические ошибки, а так же неточности перевода сообщать через ЛС. Все буду править по мере поступления.

Автор: DisaDisa

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js