Реклама

Написание пользовательского 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г.

Реклама

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
  • JaJay
  • 17 декабря 2017 г. 5:16

C++ - Тест 002. Константы

  • Результат 58 баллов
  • Очки рейтинга -2
  • JaJay
  • 17 декабря 2017 г. 4:55

C++ - Тест 001. Первая программа и типы данных

  • Результат 93 баллов
  • Очки рейтинга 8
  • JaJay
  • 17 декабря 2017 г. 4:48

C++ - Тест 001. Первая программа и типы данных

  • Результат 66 баллов
  • Очки рейтинга -1
Последние комментарии
  • EVILEG
  • 7 декабря 2017 г. 9:47

Django - Урок 011. Добавление комментариев на сайт с Django

Визуальный пример чего? комментариев? При ответе на конкретный комментарий рядом с ником отвечающего будет стрелочка и указание ник другого пользователя. Который будет ссылкой на коммента...

  • Bernar
  • 7 декабря 2017 г. 9:24

Django - Урок 011. Добавление комментариев на сайт с Django

есть визуальный пример ?

  • EVILEG
  • 6 декабря 2017 г. 11:30

Django - Урок 011. Добавление комментариев на сайт с Django

Да, так будет даже лучше, я на сайте уже обновил до такого вида код Вот это уже не нужно if request.method == 'POST': Поскольку Вы и так используете метод post, то есть эта про...

  • Bernar
  • 6 декабря 2017 г. 11:19

Django - Урок 011. Добавление комментариев на сайт с Django

сделал немного по другому class EArticleView(View): template_name = 'knowledge/article.html' comment_form = CommentForm def get(self, request, *args, **kwargs): ...

Сейчас обсуждают на форуме
  • EVILEG
  • 16 декабря 2017 г. 17:23

Пауза в многопоточности

QFuture, который возвращается QtConcurrent::map имеет методы pause() и resume() и теоретически должен поддерживать этот функционал. Но для Qt...

  • Миша
  • 15 декабря 2017 г. 11:26

Как найти в QVector макс и мин

Спасибо

  • Galant
  • 14 декабря 2017 г. 19:58

LPT

Понял! Спасибо!

  • EVILEG
  • 14 декабря 2017 г. 13:38

QCustomPlot можно ли построить прерывистую линию на одном графике?

Во-первых: В pro файле проект по идее достаточно указать следующий define для включения возможности рендеринга через OpenGL DEFINES += QCUSTOMPLOT_USE_OPENGL И во вторых:...

  • EVILEG
  • 13 декабря 2017 г. 8:05

В многопоточности выполнять действие только в одном из потоков

Статическиe методs QThread::currentThread(); и QThread::currentThreadId() могут возвращать указатель на поток и его handle id соответственно. Можете попробовать через как...