Написание пользовательского Qt 3D аспекта - часть 1

Qt 3D, OpenGL, C++, Qt

Введение

Qt 3D имеет гибкую и расширяемую архитектуру, которая позволяет нам легко добавлять в нее свои новые функциональные возможности, не нарушая при этом существующие свойства. Функциональность Qt 3D разделена на так называемые аспекты, каждый из которых инкапсулирует конкретную предметную область, такую как рендеринг (Render Aspect), ввод (Input Aspect) или анимация (Animation Aspect).

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

Обзор

Приложение, которое мы соберем, выглядит так. Это - очень простая Qt 3D-сцена, и окно, показывающее текущую среднюю частоту кадров.

Существует несколько частей, которые нам нужно учитывать при добавлении нового функционала:

  • Аспект - отвечает за организацию любых заданий, требующих выполнения каждого фрейма, и за управление хранением любых внешних объектов.
  • Компоненты - предоставляются вашей библиотекой или приложением, которые в совокупности придают конечному объекту новое поведение. Компоненты - это основной API, который вам нужно создавать.
  • Узлы (Nodes на рисунке) - как и для компонентов, за исключением того, что подклассы QNode обычно предоставляют вспомогательные данные, требуемые компонентом. Например QAnimationClipLoader является узлом, который предоставляет ключевые данные анимации кадров компоненту QClipAnimator.
  • Бэкэнд-узлы (Backend Nodes на рисунке) - Бэкэнд-копии для любых внешних компонентов или узлов, предоставляемых вашей библиотекой / приложением. Это объекты, которые обычно обрабатываются заданиями, запущенными в пуле потоков, как это продиктовано самим аспектом.
  • Mapper - Пользовательские карты, которые регистрируются с учетом этого аспекта и отвечают за создание, выборку и уничтожение внутренних узлов по требованию. Mapper используется QAbstractAspect и QAspectEngine для синхронизации времени жизни внешних и внутренних объектов.
  • Задания - создаются и планируются аспектом, обрабатывают базовые узлы. Задания могут также отправлять события во внешние узлы и компоненты, если свойства изменяются.
  • Арбитр изменений - отвечает за доставку событий между фронтэнд и бэкэнд объектами. С ним ничего не нужно делать, но имейте в виду его существование.

Добавление аспекта

Написание исходного аспекта действительно тривиально. Просто унаследуйте QAbstractAspect и зарегистрируйте его с помощью QAspectEngine:

customaspect.h

class CustomAspect : public Qt3DCore::QAbstractAspect
{
    Q_OBJECT
public:
    explicit CustomAspect(QObject *parent = nullptr)
        : Qt3DCore::QAbstractAspect(parent)
    {}
 
protected:
    QVector<Qt3DCore::QAspectJobPtr> jobsToExecute(qint64 time) override
    {
        qDebug() << Q_FUNC_INFO << "Frame time =" << time;
        return {};
    }
};

main.cpp

int main(int argc, char **argv)
{
    QGuiApplication app(argc, argv);
    Qt3DExtras::Quick::Qt3DQuickWindow view;
 
    view.engine()->aspectEngine()->registerAspect(new CustomAspect);
 
    view.setSource(QUrl("qrc:/main.qml"));
    view.show();
    return app.exec();
}

В QAbstractAspect есть виртуальные методы, которые вы можете переопределить для инициализации и удаления, если они вам понадобятся. Однако для этого простого примера все, что нам нужно сделать, это реализовать виртуальный метод jobsToExecute(). Позже мы используем его, чтобы запланировать выполнение задания в пуле потоков на каждый кадр, но сейчас мы просто выводим отладочный текст, чтобы вернуть пустой вектор (нет заданий готовых к выполнению). Обратите внимание, что эти виртуальные функции будут вызываться только после регистрации аспекта с помощью QAspectEngine (см. Выше), чтобы он был частью моделирования. Мы вернемся и завершим этот аспект чуть позже.

Компонент FpsMonitor

Мы хотим добавить функционал, который бы выводил информацию о средней частоте кадров, усредненной по заданному числу кадров. Мы можем написать API-интерфейс примерно так:

fpsmonitor.h

class FpsMonitor : public Qt3DCore::QComponent
{
    Q_OBJECT
    Q_PROPERTY(int rollingMeanFrameCount READ rollingMeanFrameCount WRITE setRollingMeanFrameCount NOTIFY rollingMeanFrameCountChanged)
    Q_PROPERTY(float framesPerSecond READ framesPerSecond NOTIFY framesPerSecondChanged)
 
public:
    explicit FpsMonitor(Qt3DCore::QNode *parent = nullptr);
 
    float framesPerSecond() const;
    int rollingMeanFrameCount() const;
 
public slots:
    void setRollingMeanFrameCount(int rollingMeanFrameCount);
 
signals:
    void framesPerSecondChanged(float framesPerSecond);
    void rollingMeanFrameCountChanged(int rollingMeanFrameCount);
 
private:
    float m_framesPerSecond;
    int m_rollingMeanFrameCount;
};

Обратите внимание, что мы используем декларативный API на основе свойств, чтобы этот класс можно было легко использовать как из QML, так и из C ++. Свойство rollingMeanFrameCount является обычным свойством чтения и записи, а реализация функций setter и getter полностью стандартная. Это свойство будет использоваться для управления количеством кадров, по которым мы вычисляем скользящую среднюю частоту кадров. Свойство framesPerSecond - это свойство только для чтения, которое позже будет установлено из задания, которое мы будем писать для обработки компонентов FpsMonitor в пуле потоков Qt 3D .

Создание небольшого тестового приложения

Прежде чем мы сможем использовать наш пользовательский компонент из QML, мы должны зарегистрировать тип с помощью системы типов QML. Это простой однострочный код, который мы можем добавить к нашей основной функции:

main.cpp

qmlRegisterType<FpsMonitor>("CustomModule", 1, 0, "FpsMonitor");
rootContext->setContextProperty("_window", &view);

где мы также воспользовались возможностью экспортировать окно в QML-контекст (это нам понадобится в определенный момент).

Как только это будет сделано, мы можем импортировать модуль CustomModule QML и использовать компонент FpsMonitor так же, как любой из встроенных типов.

main.qml

Entity {
    components: [
        FpsMonitor {
            rollingMeanFrameCount: 20
            onFramesPerSecondChanged: {
                var fpsString = parseFloat(Math.round(framesPerSecond * 100) / 100).toFixed(2);
                _window.title = "CustomAspect: FPS = " + fpsString
                        + " (" + rollingMeanFrameCount + " frame average)"
            }
        }
    ]
}

Конечно, на данный момент FpsMonitor не делает ничего, кроме установки значения свойства rollingMeanFrameCount. Как только мы закончим бэкэнд, приведенный выше код обновит заголовок окна, чтобы показать текущую среднюю частоту кадров и количество кадров, используемых для ее расчета.

В следующей части мы реализуем соответствующий бэкэнд узел для FpsMonitor и убедимся, что он создается и уничтожается по требованию и настраивает связь между интерфейсом и бэкэндом.

Статья написана: Sean Harmer | Суббота, Ноябрь 25, 2017г.

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
How to become an author?

Contribute to the evolution of the EVILEG community.

Learn how to become a site author.

Learn it
Donate

Good day, Dear Users!!!

I am Evgenii Legotckoi, developer of EVILEG. And it is my hobby project, which helps to learn programming another programmers and developers

If the site helped you, and you want also support the development of the site, than you can donate by following ways

PayPalYandex.Money
Timeweb

Let me recommend you the excellent hosting on which EVILEG is located.

For many years, Timeweb has been proving his stability.

For projects on Django I recommend VDS hosting

View Hosting Timeweb
n
June 5, 2020, 2:28 a.m.
n1k0m1

Qt - Test 001. Signals and slots

  • Result:0points,
  • Rating points-10
s
June 3, 2020, 1:56 a.m.
silo1995

C++ - Тест 003. Условия и циклы

  • Result:35points,
  • Rating points-10
AP
June 2, 2020, 9:11 p.m.
Aleksej Pikenin

C++ - Test 005. Structures and Classes

  • Result:75points,
  • Rating points2
Last comments
June 5, 2020, 1:39 a.m.
Evgenij Legotskoj

Qt/C++ - Tutorial 091. How to write a custom delegate controlling the highlighting of a row in a table

По-моему, смысла в этом нет особого. Если делегат будет игнорировать настройки таблицы, то это приведёт ещё к большему непониманию, что вообще происходит, для программиста, который после вас буд…
June 5, 2020, 1:34 a.m.
IscanderChe

Qt/C++ - Tutorial 091. How to write a custom delegate controlling the highlighting of a row in a table

Сижу, размышляю: можно ли переписать делегата так, чтобы независимо от настроек строк выделялись строки?
June 5, 2020, 1:31 a.m.
Evgenij Legotskoj

Qt/C++ - Tutorial 091. How to write a custom delegate controlling the highlighting of a row in a table

Понятно. Я не обратил внимания на то, что там было в старом коде по настройкам строк :)
June 5, 2020, 1:27 a.m.
IscanderChe

Qt/C++ - Tutorial 091. How to write a custom delegate controlling the highlighting of a row in a table

Разобрался. У вас изначально в проекте были вот эти настройки: ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);ui->tableView->setSelectionMode(QAbstractItemVie…
June 4, 2020, 11:10 a.m.
IscanderChe

Qt/C++ - Tutorial 091. How to write a custom delegate controlling the highlighting of a row in a table

Полностью скопировал пример - всё правильно работает. Значит, где-то у меня ошибки в тестовом проекте. Буду разбираться. Извините за беспокойство. :)
Now discuss on the forum
June 5, 2020, 6:13 a.m.
IscanderChe

Фильтр для QtableView sql

Добрый день. Для такой фильтрации необходимо использовать QSortFilterProxyModel. В оффдоках есть хороший пример.
MA
June 4, 2020, 2:46 a.m.
Mihail A

Qt- C++ QTableView подсветить строку

Спасибо.
f
June 3, 2020, 1:49 a.m.
fryn3

Можно ли сделать в QML таблицу как в Excel?

edi-tableview - нашел пока такое выглядит коряво, посмотрим что можно сделать
June 2, 2020, 2:46 a.m.
Evgenij Legotskoj

Медиа файлы Google Firebase

Картинки можете попробовать сжимать через QPixmap, там есть возможность установки scaleFactor, через него можете устанавливать нужные параметры. А что касается конвертации видео, то лучше п…
June 2, 2020, 2:01 a.m.
Evgenij Legotskoj

Перехват обращения к локальным файлам QWebEngineView

В вашем случае вполне адекватное решение. Так сказать меньше зло. В противном случае пришлось бы очень много переписывать и перепиливать.
About
Services
© EVILEG 2015-2020
Recommend hosting TIMEWEB