Архитектура Qt Quick 3D
Qt Quick 3D расширяет Qt Quick, чтобы поддерживать рендеринг 3D-контента. Он добавляет обширную функциональность, включая несколько новых публичных импортов QML, а также новую внутреннюю сцену и рендерер. Этот документ описывает архитектуру Qt Quick 3D от публичного API до деталей работы рендеринга.
Обзор модуля
Qt Quick 3D состоит из нескольких модулей и плагинов, которые предоставляют дополнительные 3D API, а также средства для подготовки и импорта существующих 3D-активов.
Импорты QML
- QtQuick3D - Основной импорт, содержащий все основные компоненты Qt Quick 3D
- QtQuick3D.Effects - Библиотека встроенных компонентов эффектов пост-обработки
- QtQuick3D.Helpers - Библиотека дополнительных компонентов, которые могут быть использованы для проектирования и отладки 3D-сцен.
Библиотеки C++
-
QtQuick3D - Единственный публичный модуль C++. Содержит определения всех типов, экспонируемых в импорте QML QtQuick3D, а также несколько C++ API
- QQuick3DGeometry - Подкласс для создания процедурных данных мешей
- QQuick3DTextureData - Подкласс для создания процедурных данных текстур
- QQuick3D::idealSurfaceFormat - используется для получения идеального формата поверхности
-
QtQuick3DAssetImport- Внутренняя и закрытая библиотека для помощи в импорте активов и преобразовании активов в QML. -
QtQuick3DRuntimeRender- Внутренняя и закрытая библиотека, содержащая узлы пространственной сцены и рендерер. -
QtQuick3DUtils- Внутренняя и закрытая библиотека, используемая как общая библиотека утилит всеми другими модулями C++.
Плагины AssetImporters
Инструменты импорта активов реализованы с использованием архитектуры на основе плагинов. Плагины, поставляемые с Qt Quick 3D, расширяют функциональность библиотеки и инструмента импорта активов, Balsam.
- Assimp - Этот плагин использует стороннюю библиотеку libAssimp для преобразования 3D-активов в форматах обмена 3D в компоненты QML Qt Quick 3D.
- UIP - Этот плагин позволяет преобразовывать проекты, созданные с помощью Qt 3D Studio, в компоненты QML Qt Quick 3D.
Как Qt Quick 3D вписывается в стек графики Qt
На приведенной выше диаграмме показано, как Qt Quick 3D вписывается в более крупный стек графики Qt. Qt Quick 3D работает как расширение 2D API Qt Quick, и при использовании 3D-элементов сцены совместно с View3D сцена будет рендериться через Qt Rendering Hardware Interface (RHI). RHI будет переводить API-вызовы в правильные вызовы API рендеринга аппаратного обеспечения для данной платформы. На приведенной выше диаграмме показаны доступные варианты для каждой платформы. Если явное определение родного бэкенда отсутствует, то Qt Quick по умолчанию использует разумный родной бэкенд для рендеринга на каждой платформе.
Интеграция между компонентами Qt Quick 3D стека и стеком Qt Quick описаны ниже в следующих разделах.
Интеграция 3D в 2D
Отображение 3D-контента в 2D является основной целью API Qt Quick 3D. Основным интерфейсом для интеграции 3D-контента в 2D является компонент View3D.
Компонент View3D работает как любой другой класс, производный от QQuickItem, с контентом и реализует виртуальную функцию QQuickItem::updatePaintNode. Qt Quick вызывает updatePaintNode для всех «грязных» элементов в иерархии сцены Qt Quick во время фазы синхронизации. Это включает в себя 3D-элементы, управляемые View3D, которые также проходят свою фазу синхронизации в результате вызова updatePaintNode.
Метод updatePaintNode компонента View3D выполняет следующие действия:
- Настройка рендерера и целевого объекта рендеринга, если они еще не существуют
- Синхронизация элементов в 3D-сцене через SceneManager
- Обновление любых «динамических» текстур, которые были рендерены Qt Quick (Путь текстуры 2D в 3D ниже)
Однако рендеринг 3D-сцены не происходит в методе updatePaintNode компонента View3D. Вместо этого updatePaintNode возвращает подкласс QSGNode, содержащий рендерер Qt Quick 3D, который будет рендерить 3D-сцену во время фазы предобработки процесса рендеринга Qt Quick.
Подключение того, как Qt Quick 3D будет рендерить, зависит от того, какой View3D::renderMode используется:
Оффскрин
По умолчанию для View3D используется режим Оффскрин. При использовании режима оффскрин View3D становится поставщиком текстур, создавая оффскрин-поверхность и рендеря на неё. Эта поверхность может быть отображена как текстура в Qt Quick и отрисована с помощью QSGSimpleTextureNode.
Эта модель очень похожа на то, как уже работают QSGLayerNodes в Qt Quick.
Подложка
При использовании режима Подложка 3D-сцена непосредственно рендерится на QQuickWindow, содержащий View3D. Рендеринг происходит в результате сигнала QQuickWindow::beforeRenderPassRecording(), что означает, что все остальное в Qt Quick будет отрисовано поверх 3D-контента.
Перекрытие
При использовании режима Перекрытие 3D-сцена непосредственно рендерится на QQuickWindow, содержащий View3D. Рендеринг происходит в результате сигнала QQuickWindow::afterRenderPassRecording(), что означает, что 3D-контент будет отрисован поверх всего остального контента Qt Quick.
Инлайн
Режим рендеринга Инлайн использует QSGRenderNode, что позволяет напрямую рендерить на целевой объект рендеринга Qt Quick без использования оффскрин-поверхности. Это достигается путем вставки команд рендеринга в процессе 2D-рендеринга сцены Qt Quick.
Этот режим может быть проблематичным, потому что использует тот же буфер глубины, что и рендерер Qt Quick, и значения z имеют совершенно разный смысл в Qt Quick и Qt Quick 3D.
Интеграция 2D в 3D
При рендеринге 3D-сцены часто возникает необходимость встраивать 2D-элементы в 3D. Существует два разных способа интеграции 2D-контента в 3D-сцены, каждый со своим путем к экрану.
Прямой путь
Прямой путь используется для рендеринга 2D-контента Qt Quick так, как если бы он существовал как плоский элемент в 3D-сцене. Например, рассмотрим следующее определение сцены:
Node {
Text {
text: "Hello world!"
}
} Что происходит здесь: когда дочерний компонент устанавливается на пространственный узел типа QQuickItem, он сначала оборачивается QQuick3DItem2D, который просто является контейнером, добавляющим 3D-координаты к 2D-элементу. Это устанавливает базовое 3D-преобразование для того, как все последующие 2D-дочерние элементы отрисовываются, чтобы они правильно отображались в 3D-сцене.
Когда настает время рендеринга сцены, QSGNodes этих 2D-элементов передаются рендереру Qt Quick для генерации соответствующих команд рендеринга. Поскольку команды выполняются встроеным способом и учитывают текущее 3D-преобразование, они рендерятся точно так же, как и в 2D-рендерере, но отображаются как будто они были отрисованы в 3D.
Недостатком этого подхода является то, что никакие данные освещения 3D-сцены не могут быть использованы для затенения 2D-контента, поскольку 2D-рендерер Qt Quick не имеет понятия об освещении.
Путь текстуры
Путь текстуры использует 2D сцену Qt Quick для создания динамического контента текстуры. Рассмотрим следующее определение текстуры:
Texture {
sourceItem: Item {
width: 256
height: 256
Text {
anchors.centerIn: parent
text: "Hello World!"
}
}
} Этот подход работает так же, как и элементы Layer в Qt Quick, в том смысле, что все рендерится на оффскрин-поверхность размером с верхний элемент, а затем эта оффскрин-поверхность может быть использована как текстура, которую можно повторно использовать в других местах.
Эта текстура может затем быть использована материалами в сцене для рендеринга контента Qt Quick на элементах.
Синхронизация сцены
Управление сценой
Управление сценой в Qt Quick 3D отвечает за отслеживание пространственных элементов в 3D-сцене и за обеспечение обновления узлов сцены, соответствующих элементам, во время фазы синхронизации. В Qt Quick эта роль выполняется QQuickWindow для 2D-случая. Управление сценой является основным интерфейсом между передними узлами и объектами сцены бэкенда.
Каждый элемент View3D будет иметь как минимум один Scene Manager, так как один создается и ассоциируется с встроенным корнем сцены при создании. Когда пространственные узлы добавляются как дочерние элементы View3D, они регистрируются в управлении сценой View3D. При использовании импортированной сцены создается второй SceneManager (или ссылается на него, если он уже существует) для управления узлами, которые не являются непосредственными дочерними элементами View3D. Это необходимо, потому что, в отличие от View3D, импортированная сцена не существует на QQuickWindow, пока на неё не сослались. Дополнительный SceneManager гарантирует, что активы, принадлежащие импортированной сцене, создаются как минимум один раз на каждом QQuickWindow, в котором они используются.
Хотя управление сценой является внутренним API, важно знать, что оно отвечает за вызов updateSpatialNode для всех объектов, которые были помечены как грязные вызовом метода update().
Синхронизация фронтенд/бэкенд
Цель синхронизации заключается в обеспечении соответствия состояний, заданных на фронтенде (Qt Quick), состоянию на бэкенде (рендерер пространственной сцены Qt Quick). По умолчанию фронтенд и бэкенд работают в разных потоках: фронтенд в основном потоке Qt, а бэкенд в потоке рендеринга Qt Quick. Фаза синхронизации — это то, где основной поток и поток рендеринга могут безопасно обмениваться данными. На этой стадии управление сценой вызовет updateSpatialNode для каждого грязного узла в сцене. Это либо создаст новый узел бэкенда, либо обновит существующий для использования рендерером.
Пространственная графика сцены Qt Quick
Qt Quick 3D предназначен для использования той же схемы разделения фронтенда/бэкенда, что и Qt Quick: объекты фронтенда управляются движком Qt Quick, в то время как объекты бэкенда содержат данные состояния для отрисовки сцены. Объекты фронтенда наследуют от QObject и доступны движку Qt Quick. Элементы в файлах исходного кода QML напрямую отображаются на объекты фронтенда.
По мере обновления свойств этих объектов фронтенда создается один или несколько узлов бэкенда и помещаются в иерархию сцены. Поскольку отрисовка 3D-сцен включает в себя гораздо больше данных состояния, чем отрисовка 2D-сцен, существует отдельный набор специализированных узлов сцены для представления состояния объектов 3D-сцены. Эта иерархия сцен называется Qt Quick Spatial Scene Graph.
И объекты фронтенда, и узлы бэкенда можно разделить на два класса. Первые — пространственные, в том смысле, что они существуют где-то в 3D-пространстве. На практике это означает, что каждый из этих типов содержит матрицу преобразования. Для пространственных элементов существенны отношения родитель-потомок, поскольку каждый дочерний элемент наследует преобразование своих родительских элементов.
Другой класс элементов — ресурсы. Элементы ресурсов не имеют положения в 3D-пространстве, а являются просто данными состояния, которые используются другими элементами. Между этими элементами может быть отношение родитель-потомок, но оно не имеет другого значения, кроме владения.
В отличие от 2D-иерархии сцены в Qt Quick, пространственная иерархия сцены предоставляет узлы ресурсов пользователю напрямую. Например, в Qt Quick, хотя QSGTexture является общедоступным API, нет QQuickItem, который напрямую раскрывает этот объект. Вместо этого пользователь должен либо использовать элемент Image, который описывает, откуда берется текстура, а также как ее отрисовывать, либо написать код на C++, чтобы работать с самим QSGTexture. В Qt Quick 3D эти ресурсы напрямую доступны в QML API. Это необходимо, потому что ресурсы являются важной частью состояния сцены. К этим ресурсам могут обращаться многие объекты в сцене: например, множество материалов могут использовать одну и ту же текстуру. Также можно изменять свойства текстуры во время выполнения, что напрямую повлияет на то, как текстура сэмплируется, например.
Пространственные объекты
Все пространственные объекты являются подклассами компонента Node, который содержит свойства, определяющие положение, вращение и масштаб в 3D-пространстве.
Объекты ресурсов
Объекты ресурсов являются подклассами компонента Object3D. Object3D — это просто подкласс QObject со специальными вспомогательными средствами для использования с менеджером сцены. Объекты ресурсов имеют ассоциации родитель-потомок, но они в основном полезны для управления ресурсами.
View3D и слои отрисовки
Что касается разделения фронтенда/бэкенда, View3D является точкой разделения с точки зрения пользователя, потому что View3D определяет, какой контент сцены нужно отрисовывать. В Qt Quick Spatial Scene Graph корневым узлом сцены, которая будет отрисовываться, является узел Layer. Узлы Layer создаются View3D с помощью комбинации свойств View3D и свойств SceneEnvironment. При отрисовке сцены для View3D именно этот узел Layer передается рендереру для отрисовки сцены.
Отрисовка сцены
Настройка целевого объекта отрисовки
Первый шаг процесса отрисовки — определение и настройка целевого объекта отрисовки сцены. В зависимости от установленных свойств в SceneEnvironment фактический целевой объект отрисовки будет разным. Первое решение — отрисовывать ли контент непосредственно на поверхность окна или на внеэкранную текстуру. По умолчанию View3D будет отрисовывать на внеэкранную текстуру. При использовании эффектов пост-обработки отрисовка на внеэкранную текстуру обязательна.
После определения целевого объекта отрисовки сцены устанавливаются некоторые глобальные состояния.
- размер окна — если отрисовка на окне
- область просмотра — размер области сцены, подлежащей отрисовке
- прямоугольник обрезки — подмножество окна, к которому должна быть обрезана область просмотра
- цвет очистки — цвет, которым необходимо очистить целевой объект отрисовки, если требуется.
Подготовка к отрисовке
Следующий этап отрисовки — этап подготовки, где рендер производит внутреннюю работу, чтобы понять, что нужно отрисовать для данного кадра, и что все необходимые ресурсы доступны и актуальны.
Сам этап подготовки имеет две фазы: высокоуровневую подготовку, определяющую, что нужно отрисовать и какие ресурсы необходимы; и низкоуровневую подготовку, которая использует RHI для фактической настройки конвейеров и буферов отрисовки, а также настройки зависимостей отрисовки основного прохода сцены.
Высокоуровневая подготовка к отрисовке
Цель этой фазы — извлечь состояние пространственной иерархии сцены в нечто, что может быть использовано для создания команд отрисовки. Общий обзор заключается в том, что рендер создает списки комбинаций геометрии и материалов для отрисовки с точки зрения одной камеры с набором состояний освещения.
Сначала определяется общее состояние всех элементов. Если SceneEnvironment определяет lightProbe, то проверяется, загружена ли карта окружения, связанная с текстурой light probe, и если нет, загружается или генерируется новая карта окружения. Генерация карты окружения будет представлять собой набор проходов для свертки исходной текстуры в куб-карту. Эта куб-карта будет содержать как информацию о зеркальном отражении, так и об освещенности, используемую для затенения материалов.
Далее рендеру необходимо определить, какую камеру использовать в сцене. Если активная камера не определена явно в View3D, используется первая доступная камера в сцене. Если в сцене нет камер, то контент не отрисовывается, и рендер прекращает работу.
После определения камеры можно рассчитать матрицу проекции для этого кадра. Вычисление выполняется на этом этапе, так как каждый рендер-объект должен знать, как его спроецировать. Это также означает, что теперь можно рассчитать, какие рендер-объекты следует отрисовать. Начиная со списка всех рендер-объектов, мы удаляем все объекты, которые не видны, потому что они либо отключены, либо полностью прозрачны. Затем, если отсечение по конусу обзора включено для активной камеры, каждый рендер-объект проверяется на предмет полного расположения вне обзора конуса обзора камеры, и если это так, он удаляется из списка рендер-объектов.
В дополнение к проекции камеры также вычисляется направление камеры, поскольку это необходимо для расчетов освещения в коде затенения.
Если в сцене есть узлы света, они собираются в список, длина которого соответствует максимальному доступному количеству источников света. Если в сцене существует больше узлов света, чем поддерживает рендер, любые дополнительные узлы света сверх этого предела игнорируются и не влияют на освещение сцены. Можно указать область действия узлов освещения, но обратите внимание, что даже при установке области действия состояние освещения каждого источника света по-прежнему передается каждому материалу, который использует освещение, но для источников света, не находящихся в области действия, яркость устанавливается в 0, поэтому на практике эти источники света не влияют на освещение этих материалов.
Теперь, с, надеемся, более коротким списком рендер-объектов, каждый из этих элементов должен быть обновлен, чтобы отразить текущее состояние сцены. Для каждого рендер-объекта мы проверяем, загружен ли подходящий материал, и если нет, создается новый. Материал — это комбинация шейдеров и конвейера отрисовки, и он необходим для создания вызова отрисовки. Кроме того, рендер проверяет, загружены ли все необходимые ресурсы для отрисовки рендер-объекта, например, геометрия и текстуры, установленные на модели. Ресурсы, которые еще не загружены, загружаются здесь.
Затем список рендер-объектов сортируется в 3 списка.
- Непрозрачные элементы: они сортируются спереди назад, или, другими словами, от элементов, которые ближе к камере, к элементам, которые дальше от камеры. Это делается для использования преимущества аппаратного отсечения по окклюзии или раннего определения z в шейдере фрагмента.
- 2D-элементы: это элементы QtQuick, которые отрисовываются движком Qt Quick.
- Прозрачные элементы: они сортируются сзади вперёд, или, другими словами, от элементов, которые дальше от камеры, к элементам, которые ближе к камере. Это делается, потому что прозрачные элементы должны быть смешаны со всеми элементами, которые находятся за ними.
Низкоуровневая подготовка к отрисовке
Теперь, когда все, что необходимо учитывать для этого кадра, определено, можно заняться вспомогательными элементами и зависимостями основного прохода отрисовки. Первое, что делается на этом этапе, — отрисовка всех предварительных проходов, необходимых для основного прохода.
- Render DepthPass - Некоторые функции, такие как Screen Space Ambient Occlusion и Shadowing, требуют предварительного прохода по глубине. Этот проход включает в себя рендеринг всех непрозрачных элементов в текстуру глубины.
- Render SSAOPass - Цель прохода Screen Space Ambient Occlusion заключается в генерации текстуры затенения окружающей среды. Эта текстура используется позже материалами для затемнения определённых областей при затенении.
- Render ShadowPasses - Каждый свет в сцене, у которого включено затенение, вносит вклад в дополнительный проход затенения. Рендерер использует две различные техники затенения, поэтому в зависимости от типа света будут разные проходы. При рендеринге теней от направленного света сцена рендерится в 2D-текстуру непрозрачности, исходя из комбинации направления направленного света и размера камеры. При рендеринге теней от точечного или прожекторного света текстура непрозрачности света представляет собой куб, отображающий вклад непрозрачности относительно каждого направления грани света.
- Render ScreenTexture - Этот проход будет выполняться только при использовании CustomMaterial, которому требуется текстура экрана, которая может использоваться для таких методов рендеринга, как преломление. Этот проход работает как проход по глубине, но вместо этого рендерит все непрозрачные элементы в цветную текстуру.
После завершения рендеринга зависимостей остальные проходы подготавливаются, но не рендерятся. Эта подготовка включает в себя преобразование состояния, собранного на этапе подготовки высокого уровня, в графические примитивы, такие как создание/обновление значений унифицированных буферов, связывание сэмплеров с зависимыми текстурами, настройку для связывания ресурсов шейдера и всё остальное, необходимое для создания состояния конвейера, необходимого для выполнения вызова отрисовки.
Отрисовка сцены
Теперь, когда подготовка завершена, выполняется отрисовка элементов, входящих в основной контент сцены.
- Clear Pass - Это не совсем проход, но в зависимости от того, какой backgroundMode установлен для SceneEnvironment, могут происходить разные действия. Если режим фона прозрачный или цветной, то буфер цвета очищается либо прозрачностью, либо указанным цветом. Если же режим фона установлен на SkyBox, то будет выполнен проход, который рендерит SkyBox с точки зрения камеры, который также заполнит буфер начальными данными.
- Opaque Pass - Далее все непрозрачные элементы будут отрисованы. Это просто включает настройку состояния конвейера и выполнение команды отрисовки для каждого элемента в порядке списка, так как они уже отсортированы на этом этапе.
- 2D Pass - Если в сцене есть какие-либо 2D-элементы, то вызывается рендерер Qt Quick для генерации команд рендеринга, необходимых для рендеринга этих элементов.
- Transparent Pass - И наконец, прозрачные элементы сцены рендерятся по одному так же, как и непрозрачные элементы.
Это завершает рендеринг сцены.
Последовательная обработка
Если включена какая-либо функциональность пост-обработки, можно предположить, что результатом рендеринга сцены является текстура, которая является входным параметром для этапа пост-обработки. Все методы пост-обработки являются дополнительными проходами, которые работают с этой входной текстурой сцены.
Все шаги этапа пост-обработки являются необязательными, и если их нет, то выход рендеринга сцены используется в качестве конечного целевого объекта.
Эффекты пост-обработки
Этот первый шаг пост-обработки заключается в выполнении всех пользовательских эффектов пост-обработки. Они определяются в упорядоченном списке свойством SceneEnvironment::effects. Способ работы заключается в том, что каждый эффект пост-обработки является частью цепочки, при этом выход предыдущего эффекта является входом для следующего. Первый эффект в этой цепочке получает входные данные непосредственно от выходных данных шага рендеринга сцены. Также эффекты могут получать доступ к текстуре глубины, выводимой шагом рендеринга сцены.
Каждый эффект в этом процессе может состоять из нескольких подпроходов, что означает, что можно рендерить контент в промежуточные буферы. Ожидается, что конечный проход многопроходного эффекта выведет одну текстуру, содержащую данные цвета, которые будут использоваться следующими шагами этапа пост-обработки.
Временная и прогрессивная антиалиасинг
Шаги временного и прогрессивного антиалиасинга можно выборочно включить, установив свойства в SceneEnvironment. Хотя это не строго часть этапа пост-обработки, фактические результаты временного и прогрессивного антиалиасинга реализуются на этапе пост-обработки.
Временная антиалиасинг выполняется, когда сцена активно обновляется. При включении активная камера делает очень небольшие корректировки направления камеры для каждого кадра при отрисовке сцены. Текущий кадр затем смешивается с ранее отрисованным кадром для сглаживания отрисованного изображения.
Прогрессивная антиалиасинг выполняется только когда сцена не обновляется. При включении выполняется обновление, и текущее состояние сцены рендерится с очень небольшими корректировками направления активной камеры. До 8 кадров накапливаются и смешиваются с заранее определёнными весами. Это позволяет сгладить неанимированную сцену, но влечёт за собой затраты производительности, потому что для каждого обновления будет отрисовано несколько дополнительных кадров.
Антиалиасинг сверхдискретизации (SSAA)
Антиалиасинг сверхдискретизации — это грубый способ сглаживания сцены. Он работает путём рендеринга в текстуру, которая является кратным значением запрошенного размера сцены, а затем последующим её сжатием до целевого размера. Например, если запрошен 2X SSAA, то сцена будет рендериться в текстуру, которая в 2 раза больше предполагаемого размера, а затем сжиматься в этом этапе. Это может сильно повлиять на производительность и использование ресурсов, поэтому следует избегать этого метода, если это возможно. Также размер View3D может быть слишком большим для использования этого метода, поскольку текстура, необходимая для этого метода, может быть больше, чем поддерживается графическим оборудованием.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.1/qtquick3d-architecture.html