Чуть меньше года назад увидела свет публикация, где мы рассказывали об учебно-лабораторном комплексе (УЛК) электропоезда ЭС1 «Ласточка», разработанном нашем университете. Тогда я обещал, что это будет не последняя публикация на данную тему, в частности грозился рассказать о проблемах создания трехмерной визуализации для подобного рода симуляторов и очертить основные подходы к их решению.
Прошедший год порадовал нас очередным релизом — УЛК высокоскоростного электропоезда ЭВС2 «Сапсан», который состоялся ещё в августе прошлого года. Сам по себе учебно-лабораторный комплекс данного электропоезда заслуживает отдельного рассказа, но в контексте этой публикации речь пойдет о наболевшем — проблеме создания адекватной подсистемы трехмерной визуализации, к решению которой наша команда подступалась с разных сторон около двух лет. Релиз симулятора «Сапсана» знаменателен (среди прочего) и тем, что определил вектор развития наших разработок в этой области.
1. Кратко об УЛК ЭВС2 «Сапсан»
Хочу подчеркнуть ещё раз (что я делаю с завидной периодичностью) что учебно-лабораторные комплексы подвижного состава, разрабатываемые в нашем университете, не предназначены для подготовки локомотивных бригад. Как справедливо заметил один из комментаторов предыдущей статьи, наши УЛК являются не тренажерами, а симуляторами, где основной упор делается на грамотную реализацию физики движения поезда и моделирование работы подсистем подвижного состава, обеспечивающих его движение и остановку. Не является исключением и симулятор «Сапсана», на котором решены следующие задачи:
- Реализована динамическая модель механической части поезда с учетом продольных сил и профиля пути
- Построена подробная компьютерная модель работы ключевых подсистем электропоезда: силовой электрической схемы, тягового электрического привода, пневматических и электропневматических тормозов
- Воспроизведены основные алгоритмы работы системы управления электропоездом на разных уровнях
Кроме того, учебно-лабораторный комплекс имеет в своем составе полноразмерный макет кабины электропоезда с основными органами управления и средствами отображения информации. В отличие от тренажера «Ласточки» эта кабина не изготавливалась нами самостоятельно, а была приобретена в 2015 году у одной известной в нашей стране конторы, занимающейся выпуском учебных тренажеров. Поэтому в процесс разработки симулятора сосредоточился на создании программного обеспечения.
Вид через лобовое стекло
Дисплей комплексного локомотивного устройства безопасности (КЛУБ-У). Красное «290» — это текущее ограничение скорости, получаемое из электронной карты КЛУБ-У. Пока здесь красуется предельная скорость, достигнутая «Сапсаном» на Октябрьской железной дороге. В будущем электронная карта будет реализована так, как это сделано в жизни
Главный дисплей «Интерфейс человек-машина»
Дисплей индикации состояния тормозной системы электропоезда
Задатчик скорости и контроллер тяги
Контроллер управления тормозами электропоезда
Тумблеры управления токоприемниками и аппаратами защиты (БВ/ГВ) — черные тумблеры около задатчика скорости
Интерфейс управления тренировкой — экран выбора маршрута движения
Экран управления громкостью аудиоэффектов
Счетчик пробега. С его появлением связана забавная история. Когда мы сдавали первый наш тренажер тепловоза 2ТЭ116, на наш вопрос когда будет подписан акт выполненных работ представитель заказчика отшутился: «Ну, давайте поступим как в жизни — при вводе нового локомотива в эксплуатацию он должен пройти 5000-километровый пробег. Вот пройдет...». Акт конечно был подписан намного раньше, но мы, оценив юмор ситуации, сделали подобный счетчик уже на тренажере «Ласточки». Счетчик можно сбросить в «0» введя сервисный пароль.
Правая вспомогательная панель с контрольными манометрами тормозной системы и клапаном экстренного торможения. Здесь установлены не все элементы присущие настоящему «Сапсану» — такой пульт был получен нами от поставщика
Поэтому, некоторые критичные для нас элементы управления были реализованы программно, в частности панель шунтирующих выключателей, управляемая с сенсорного дисплея
Разработка ПО подобного тренажера-симулятора вопрос очень широкий, и я постараюсь (в меру разумного) удовлетворить интерес читателей к этим вопросам в будущем (если таковой появится), но пока что, вернемся к основной теме статьи — трехмерной визуализации процесса движения поезда.
2. История вопроса и технологии прошлого
В комментариях к прошлой статье был задан вопрос, который, признаюсь честно, изрядно меня позабавил. Да, действительно, во многих, до сих пор эксплуатируемых сегодня тренажерах до сих пор применяется такой подход: снимается видео на реальном участке железной дороги, а затем прокручивается на тренажере со скоростью, пропорциональной скорости движения. Так делали только потому, что в те далекие времена, когда подобные тренажеры создавались, качество трехмерной графики, оставляло желать лучшего, причем это касалось и суровых графических станций на коммерческих юниксах, а уж о ПК и речи не шло. Поэтому, даже производители компьютерных игр, например вот этой, не гнушались использовать такой подход.
На сегодняшний день это не имеет смысла, потому что:
- Недостаточная частота смены кадров на низких скоростях движения поезда не обеспечивает нужной плавности обновления картинки. Заветные 25 fps мы будем иметь только при той скорости, на которой снималось видео из кабины машиниста. И этот фатальный недостаток ничем не преодолеть — ни съемкой скоростной камерой (сколько будет весить видео, снятое на 120 кадрах в секунду? То-то же...), ни программной генерацией промежуточных кадров. Последнее предпринималось нами с использованием технологии OpenCV, но к нормальным результатам не привело. Этот вопрос многократно прорабатывался со всех сторон и в итоге был сделан вывод, что затраты ресурсов на создание такой системы намного превышают разработку аналогичной системы, но на базе 3D-графики
- Трудности с плавной прокруткой видео назад. И даже учитывая, что они будут преодолены, то куда побегут бежавшие по перрону собаки, вздумай мы поехать задним ходом?
- Отсутствие всяческой «интерактивности». Как быть со сменой сигнала светофора, с движением стрелочных переводов, движением встречных и попутных поездов?
Поэтому все современные тренажеры и симуляторы создаются с применением интерактивной 3D-графики, благо сегодня нет никаких препятствий ни с программной, ни с аппаратной точки зрения.
Если с аппаратной точки зрения всё предельно ясно — монитор установленный вместо лобового стекла подключается к ПК с нормальной видеокартой (даже не самой топовой), то с программной точки зрения встает вопрос выбора технологии реализации задачи.
3. Графический движок против игрового движка или почему был выбран OpenSceneGraph
Могу ошибаться, но заранее предчувствую комментарии, в которых будет задаваться вполне закономерный вопрос, почему при анализе существующих технологий наш выбор не остановился на таких мастодонтах как Unity или Unreal Engine 4? Я отвечу на этот вопрос, более того, я обосную свой ответ.
Кратко — ни Unity ни Unreal Engine не удовлетворяют требованиям решаемой задачи. Более подробный ответ предусматривает, прежде всего перечисления тех требований, о которых идет речь. ТЗ, составленной нами на подсистему трехмерной визуализации, включает в себя (в порядке убывания значимости) следующие положения:
- Независимость процесса разработки ПО подсистемы визуализации и процесса создания ресурсов для неё. К ресурсам, в данном случае, относятся трехмерные модели, текстуры, а так же так называемые маршруты. Под маршрутом понимается совокупность объектов конфигурации и ресурсов, позволяющих видеоподсистеме отобразить желаемый участок железной дороги и обеспечить симуляцию движения поезда по нему. Сюда же стоит отнести и возможность смены маршрута без пересборки программной части видеоподсистемы
- Создание маршрутов неограниченной протяженности. Оговорюсь, что неограниченная протяженность в принципе недостижима по причине ограниченности аппаратных ресурсов. Данное требование следует понимать, что протяженность маршрута должна быть как минимум в пределах одно «плеча», то есть участка дороги между оборотными пунктами, а это, в зависимости от разных факторов достаточно большое расстояние, исчисляемое не одной сотней километров. Это требование накладывает необходимость обеспечивать динамическую загрузку/выгрузку ресурсов программы с достаточной плавностью при разумном потреблении памяти. И желательно, чтобы движок содержал такой функционал «из коробки»
- Удобная интеграция с используемым стеком технологий. Традиционно, в силу опять таки причин объективных, для разработки ПО УЛК ПС в нашей команде используется язык C++ с фреймворком Qt, в качестве IDE QtCreator, а в качестве системы контроля версий — Git. В качестве системной платформы УЛК ПС используется ОС на базе ядра Linux
Что же не так с Unity и UE? Что тот, что другой движки способны импортировать ресурсы совершенно разных форматов. Однако при сборке проекта происходит их необратимое преобразование во внутренний бинарный формат, делающее невозможным добавление и изменение ресурсов без повторной сборки проекта. Технологии типа prefabs и asset bundles, доступные в Unity не решают задачу, так как редактор движка — не лучшее место для создания железнодорожных локаций, из-за чего возникает потребность расширения редактора, что приводит к необходимости писать «движок внутри движка». Кроме того, создание префабов и бандлов невозможно без применения редактора Unity, а это, как показала практика, не слишком удобно, особенно для чистых моделлеров и левел-дизайнеров. Что же касается UE, и на этом и на других ресурсах за два года мной было задано достаточно вопросов, о том как отделить процесс сборки проекта от процесса добавления/изменения используемых им ресурсов, и адекватного ответа я не получил ни в документации, ни от «матерых» геймдевелоперов. Буду очень рад (без сарказма) если меня аргументировано натыкают носом во что-то, что было мной упущено.
Что касается второго требования, то и Unity и UE вроде как обеспечивают возможность создания динамически загружаемых локаций, но остается нераскрытым вопрос, каким образом подобные локации можно создавать независимо от редактора и без пересборки проекта? Выход один — писать «движок внутри движка», который будет загружать «сырую» (в любом из наперед заданном формате экспорта из 3D-редакторов) геометрию и текстуры, применять к ним все необходимые эффекты и позиционировать в пространстве опираясь на данные, описанные в стороннем, независимом от движка формате, который нужно ещё разработать и научить движок его интерпретировать.
В связи с вышеперечисленным возникает вопрос — если для решения поставленной задачи необходимо писать мощную программную прослойку над игровым движком, большая часть функциональности которого в рассматриваемой задаче просто не нужна, то зачем нужен игровой движок?
Может быть достаточно графического движка? Этот вопрос я задавал предыдущей команде, бравшейся за обсуждаемую проблему опираясь на Unity (и закономерно слившейся чуть позже). В ответ получил встречный вопрос: «А что предлагаете вы?», ответив на который в духе приведенного выше текста получил саркастическую улыбку оппонента.
Если обойтись без сарказма, то представленная задача является типичной задачей визуализации — здесь требуется только фреймворк для работы с графикой, так как и физика, и аудиоподсистема, опирающаяся на физику реализованы на серверной стороне. Я и моя команды пришли к пониманию этого факта двигаясь по инерции предыдущих разработчиков сначала в сторону Unity, через UE и попытки прикрутить графическую подсистему от оного из открытых железнодорожных симуляторов (OpenBVE, что кстати получилось, но стало временным костылем)
OpenSceneGraph является на сегодняшний день самым развитым (из открытых и бесплатных) графическим движком, ориентированным на C++ разработку. Он достаточно широко применяется за рубежом именно для технической трехмерной визуализации. Этот движок не обошли стороной и разного рода симулятора, наиболее известный из которых — FlightGear. Некогда существовал и железнодорожный симулятор на базе этого движка — Indra, от которого, впрочем, остались только унылые скриншоты по вышеприведенной ссылке и его дальнейшая судьба мне неизвестна.
В контексте решаемой задачи графический движок OSG обладает следующими положительными качествами:
- Кроссплатформенность, что делает возможным применить его в экосистеме GNU/Linux
- Язык разработки C++/STL, что дает возможность легко и непринужденно интегрировать его в устоявшийся в нашей команде технологический процесс разработки;
- Широчайший спектр поддерживаемых «из коробки» форматов ресурсов — 3D-геометрии и текстур за счет развитой системы плагинов. Простой и понятный интерфейс написания собственных плагинов для настройки менеджера ресурсов на нестандартные форматы, чем мы и воспользовались (об этом я напишу ниже);
- Система управления памятью на основе собственной модели умных указателей (собственный формат умных указателей используется исторически, в связи с тем что в начале разработки движка умных указатель в стандарте C++ не было);
- Гибкая модульная архитектура;
- Диспетчер объектов сцены, выполняющий динамическую загрузку объектов, обеспечивающий загрузку и отрисовку только тех объектов, которые попадают в пределы пирамиды отсечения (за счет класса osg::PagedLOD)
- Возможность интеграции с фреймворком Qt. Благодаря удобной модели «сигналы — слоты», предоставляемой Qt, существенно упрощающей и ускоряющей разработку на C++, мы широко применяем данный фреймворк для разработки ПО тренажерных комплексов. Соответственно, у нас накоплена значительная повторно используемая в разных проектах кодовая база, особенно это касается библиотеки межпроцессоной коммуникации на базе TCP-сокетов. Использовать возможности Qt и в проекте видеоподсистемы представляется вполне логичным решением;
- Достаточное для решаемой задачи качество картинки.
Потребовалось около полугода напряженного изучения возможностей OSG для того чтобы тщательно «прощупать почву» и найти подходы к решению поставленной задачи с помощью этого движка. То что родилось в итоге заслуживает отдельного разговора.
4. От архитектуры к рабочему прототипу
Видеоподсистема тренажеров подвижного состава (ВТПС) является клиентским приложением, буднично именуемым video3d-client и выполняет следующие функции:
- Запрос на подключение к серверной части тренажера, авторизация на сервере с последующим периодическим запросом идентификатора загружаемого маршрута, а затем и текущего положения поезда. При разрыве соединения со стороны сервера переход в режим ожидания возможности повторного подключения;
- Загрузка выбранного маршрута, организация динамического управления содержимым отрисовываемой сцены;
- Собственно рендеринг сцены в соответствии с текущим положением поезда на маршруте
Не то чтобы этот проект был opensource, однако с кодом полнофункциональной технологической демки вполне можно ознакомится здесь. Проект состоит из следующих модулей:
- filesystem — библиотека для работы с файловой системой, обеспечивает генерацию путей к конфигурационным файлам и ресурсам приложения
- library — кроссплатформенная реализация загрузчика динамических библиотек. В общем-то костыль, написаный в период, когда возможности интеграции с Qt (где есть готовый к бою модуль QLibrary) была ещё расплывчатой
- osgdb_dmd — плагин для загрузки моделей формата, специфичного для движка DGLEngine версии 1.1. Для чего это потребовалось я объясню чуть ниже
- route-loader — библиотека, обеспечивающая абстрактный интерфейс к загрузчику маршрутов. Предполагается возможность загрузки маршрутов произвольного формата
- tcp-connection — библиотека межпроцессного взаимодействия через TCP-сокеты
- viewer — основной исполняемый модуль программы
- zds-route-loader — плагин для загрузки маршрутов формата ZDSimulator
При проектировании ВТПС встал вопрос выбора: разрабатывать формат маршрутов самостоятельно, либо воспользоваться существующим форматом маршрутов, а так же готовыми маршрутами отечественных железных дорог для существующего железнодорожного симулятора. На счастье подвернулось решение — закрытый проприетарный продукт ZDSimulator, обладающий той особенностью, что он заточен под отечественный подвижной состав и специфику работы сети железных дорог. Несмотря на похвальбу авторов проекта, он имеет массу существенных недостатков, но при этом имеет простой и понятный формат маршрутов, находящихся в открытом доступе. На первом этапе было грех не воспользоваться имеющейся возможностью, при том что графическая часть симулятора основана на открытом движке DGLEngine. Беда в том, что данный движок хоть и развивается (текущее состояние проекта можно увидеть тут), но его текущая вторая версия несовместима с версией 1.1, на которой основан ZDSimulator. Исходники версии 1.1 утеряны, ссылки ведущие к ним давно протухли.
Тщательный поиск в вебархиве позволил найти утерянное и спасти, разместив DGLEngine v1.1 на Gtihub. Этот движок использует свой, специфический формат 3D-моделей. Имея исходники движка нетрудно было написать соответствующий плагин для OSG.
Таким образом задача создания ВТПС свелась к написанию программной части на движке OSG. В дальнейшем планируется разработка собственного формата маршрутов, так как текущий формат предусматривает движение только по главным путям и обладает рядом недостатков, не позволяющих воссоздать ряд сложных маршрутов.
Иерархия основных классов ВТПС представлена на следующей диаграмме
Иерархия классов загрузчика маршрутов выглядит так
Загрузчик любого другого формата маршрутов может быть написан как плагин, содержащий класс, наследующий от класса RouteLoader. При старте ВТПС ей передается путь к каталогу с маршрутом, определяется формат маршрута и динамически загружается соответствующий плагин, выполняющий далее остальную грязную работу.
Принципиально важным нюансом явилась интеграция движка OSG и Qt. Таковая интеграция существует и именуется osgQt. Эта библиотека не использована в данном проекте по двум причинам:
- Отсутствие необходимости в средствах управления окнами, предоставляемыми Qt. OSG имеет свою достаточно развитую систему управления окнами GUI и нет смысла городить GUI поверх другого GUI, так как osgQt предназначена прежде всего для интеграции вьювера OSG в GUI на базе Qt
- osgQt подвержена багу — некорректная работа с контекстом OpenGL, который она в некоторых случаях не может поделить между OSG и QGLWidget, из-за чего сцена отображается где угодно, только не на виджете Qt. Причем доискаться причин пока не удалось, поскольку на некоторых системах данный баг не проявляет себя.
Возникло понимание того, что интеграция с Qt нужна в части использования концепции «сигналы-слоты», для обеспечения взаимодействия с сетевой подсистемой tcp-connection, использующей Qt и являющейся стандартом де-факто в наших разработках. Опираться на систему сообщений OSG и заново писать TCP-клиент (да ещё и кроссплатформенный) очень не хотелось. Нашлось элегантное решение, опирающееся на то, что если мы хотим, чтобы один объект послал сигнал, инициирующий срабатывание слота у другого объекта мы должны выполнить три условия:
- Наследовать взаимодействующие классы от QObject
- Организовать цикл обработки сигналов
- Создать экземпляр класса QApplication (или QCoreApplication) существующий в памяти в процессе работы приложения
При этом вовсе ни в коем случае не следует выполнять вызов QApplication::exec(), запускающий штатный цикл обработки сигналов, достаточно организовать цикл в котором просто обрабатывать сигналы вызовом QApplication::processEvents(). В OSG таковой цикл имеется (тот цикл, в котором выполняется рендеринг) и имеется возможность создать обработчик событий, в котором обрабатывается событие osgGA::GUIEventAdapter::FRAME, генерируемое движком при отрисовке очередного кадра. Таким образом вся интеграция свелась к коду
qt-events.h
#ifndef QT_EVENTS_H
#define QT_EVENTS_H
#include <osgGA/GUIEventHandler>
#include <QtCore/QtCore>
class QtEventsHandler : public osgGA::GUIEventHandler
{
public:
QtEventsHandler(){}
virtual bool handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa);
protected:
};
#endif // QT_EVENTS_H
qt-events.cpp
#include "qt-events.h"
bool QtEventsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa)
{
switch (ea.getEventType())
{
case osgGA::GUIEventAdapter::FRAME:
{
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
break;
}
default:
break;
}
return false;
}
main.cpp
#include "main.h"
/*!
* fn
* brief Entry point
*/
//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
RouteViewer viewer(argc, argv);
if (viewer.isReady())
return viewer.run();
return 0;
}
после чего, классы унаследованные от QObject и его производных могут обмениваться сигналами до потери пульса.
Всё вышеперечисленной позволило за два месяца создать первый рабочий прототип ВТПС. Чтобы продемонстрировать что вышло в итоге, предлагаю следующую нарезку из опытных поездок по реальным маршрутам. Заранее прошу прощения за качество съемки — не разжились толковой техникой
Заключение и выводы
Главным выводом, по крайней мере для нашей команды стало то, что «серябрянной пули» не существует и в вопросах выбора технологии реализации проекта. Агрессивно продвигаемые на рынок игровые движки не всегда подходят для решения специфических задач, к каким относится визуализация результатов моделирования технических систем. А если и подходят, то не являются оптимальными с точки зрения усилий, потраченных на разработку и сопровождение проекта.
Обидно, что весьма неплохой, а главное свободный, графический движок OSG по факту не имеет в нашей стране сообщества. Дабы исправить эту проблему я пишу цикл статей здесь на ресурсе (там я собрал все ссылки на более менее адекватные источники информации, в том числе и на русском языке). Кроме того, в качестве документации, описывающей базовые принципы OSG могу предложить ещё и этот блог. Надеюсь что кому-то эта информация окажется полезной.
Что касается ВТПС, то работа в этом направлении продолжается. На очереди ещё масса важных задач, которые предстоит решить в ближайшем будущем.
Благодарю за внимание!
Центр развития инновационных компетенций РГУПС
Автор: maisvendoo