Через терни до хмар: створення хмарного сервісу для 3D проектування і дизайну приміщень на базі ядра C3D і WebGL

Нині в інтернетах тільки й говорять про хмарах, як вони нескінченні і прекрасні… про серверах, які вони там бачили… А ти? Ось і я вирішив поділитися з читачами своїм досвідом розробки онлайн сервісу проектування приміщень і інтер'єрів в 3D. Тут я постараюся розповісти про архітектуру проекту в цілому і про деталі реалізації.
image

Що таке хмарна система 3D проектування? Оскільки останнім часом термін «хмарні обчислення» дуже популярний і використовується до місця і не до місця, я почну з визначення. Хмарне 3D проектування в моєму розумінні і моєї реалізації — це така архітектура програмного забезпечення, при якій всі дані про 3D моделі і дії по її обробці розташовані на віддалених серверах (тобто в хмарах), а клієнтські пристрої запитують ту чи іншу частину даних або результатів розрахунків по мережі інтернет. Іншими словами, подібні системи відрізняються від класичних систем проектування тим, що виробляють більшість розрахункових операціях на серверах, а не на клієнтських пристроях і передають тільки невелику частину даних для візуалізації моделі та її параметрів клієнта. Архітектура подібних систем виявляється розділеної на тісно взаємодіють, але віддалено розміщені, серверну та клієнтську частини, що вимагає особливого підходу для забезпечення їх взаємодії непомітно для користувача продукту.
Наступного питання: які переваги має подібна архітектура? Безсумнівно, хмарна архітектура складніше класичної, в якій користувач, його дані та їх обробка знаходяться і виробляються в одному місці. Тим не менш, для мого проекту хмарна архітектура має ряд незаперечних переваг як з точки зору розробки, так і з точки зору використання, роблячи подібне ускладнення архітектури доцільним. Спробую їх сформулювати:
  • Клієнтським додатком є веб-браузер. У наш час це означає кросплатформеність програми та можливість користуватися сервісом з будь-якого пристрою.
  • Швидкий старт програми без установки знижує поріг входу для майбутніх користувачів.
  • Немає необхідності зберігати документи і переміщати їх між пристроями, оскільки всі дані одночасно доступні з усіх пристроїв.
  • Можливість одночасного редагування або перегляду окремих документів і цілих проектів декількома користувачами і зручна комунікація створюють єдине робоче простір між віддалено розташованими клієнтами.
  • Організація безперервного процесу розробки та миттєвої доставки оновлення, що дозволяє клієнтам використовувати останню версію і сприяє активному використанню технік екстремального програмування при розробці.
Зрозуміло, подібний підхід має і свої недоліки. З них я виділяю наступні:
  • Необхідність мати хороший інтернет-канал для комфортної роботи з додатком.
  • Ускладнення програмного забезпечення і, як наслідок, збільшення часу і вартості розробки.
  • Необхідність розгортання і подальшої підтримки мережевої інфраструктури, необхідної для роботи.
Архітектура проекту
При виборі архітектури я намагався врахувати можливість масштабування в майбутньому і розділив проект на частини таким чином, щоб було легко розпаралелити найнавантаженіші частини. При розрахунку я використовував наступні припущення, засновані на наявному досвіді по розробці CAD і уточнені при створенні прототипу:
Для завантаження середньостатистичної квартири з меблями мені потрібно приблизно 25 Мб нестиснутих геометричних даних і додаткових атрибутів (5 Мб стислих) + 10Мб текстур. Час генерації даних від 0.2 сек. до 5 сек. (у найбільш складних випадках). Я планую обмежити обсяг моделі на рівні 3-5 млн трикутників.
Під час роботи з проектування плану та розстановці різних виробів користувачем, на одну операцію (вставка і редагування виробів, регенерація плану) припадає в середньому 100 — 500 Кб вихідного трафіку. Час виконання кожної операції на сервері в середньому становить 0,1-0.5 сек.
Активність користувачів знаходиться на рівні відкриття однієї моделі в хвилину або виконання 5 — 10 операцій редагування в хвилину.
Виходячи з цього, стало зрозуміло, що утримати на одному сервері більше сотні активних користувачів буде проблематично, тому потрібно буде забезпечити обробку геометричних запитів на різних серверах, і якимось чином розподіляти 3D моделі між ними.
У виборі засобів розробки я спочатку був пов'язаний кількома обмеженнями. По-перше, використання в якості геометричного ядра C3D і високі вимоги по швидкості геометричних розрахунків при роботі з моделлю зумовило використання С++ на стороні сервера. По-друге, запуск клієнтської частини на браузері також звузило вибір мов до тих, що підтримують компіляцію в JavaScript.
В результаті на даному етапі проект складається з чотирьох незалежних частин: два внутрішніх (backend) сервісу та два зовнішніх Web програми. Головний внутрішній сервіс відповідає за геометричне моделювання та розрахунки. Він написаний на с++ З використанням бібліотек C3D і Qt Core. Допоміжний сервіс відповідає за управлінням файлами, каталогами користувачів і обробкою текстур. Він написаний на ASP.NET Core. Веб додатки розділені аналогічно. Одне відповідає безпосередньо за моделювання і написано на TypeScript + WebGL, а друге надає інтерфейс користувача для управління проектами та каталогами користувача і написано на зв'язці Angular 2 + TypeScript. Взаємодія між клієнтської і серверної частинами йде з допомогою простих HTTP запитів. В частині, що відповідає за інтерактивне моделювання приміщень, використовуються WebSocket з'єднання, яким передаються стислі бінарні дані. Щоб уникнути дублювання коду між серверними сервісами вони також обмінюються необхідною інформацією по HTTP протоколу.
Серверна частина
Головна родзинка проекту — це комбінація серверної та клієнтської частини, що відповідає за геометричне моделювання, візуалізацію та збереження історії редагування моделей. До цієї частини проекту пред'являються високі вимоги по продуктивності, споживання оперативної пам'яті, розпаралелювання і масштабованості, т. к. геометричне моделювання саме по собі досить витратна обчислювальна завдання, а виконання запитів побудови моделей від безлічі активних користувачів ускладнює її ще більше. У якості «серця» системи для виконання задач геометричного моделювання на сервері було обрано ядро C3D від компанії C3D Labs, причини вибору якого описані в моїй попередній статті «Ядерні технології в CAD» [1]. Для реалізації функціоналу з управління комплексними 3D проектами була розроблена власна система зберігання даних 3D моделі, в основу якої взято ієрархічна ECS (Entity Component System), популяризированная розробниками ігор. Вона представляє собою деревоподібну структуру моделі, що складається з різних елементів(сутностей), де у кожного елемента є різні набори даних (компоненти), такі як геометричні параметри, BREP оболонки, трикутні сітки, призначені для користувача дані і т. п. Для того щоб система відповідала необхідним вимогам, її реалізація має цілий ряд відмінних особливостей:
При завантаженні моделі завантажується лише її структура, а всі її дані (компоненти) зберігаються в NoSQL базі даних і автоматично завантажуються в оперативну пам'ять при зверненнями до компонентів, а також автоматично вивантажуються з пам'яті по мірі необхідності. Це дозволяє працювати на сервері з тисячами одночасно відкритих моделей при невеликих витратах оперативної пам'яті.
Всередині компонентів зберігаються зв'язку на компоненти в інших сутності у вигляді пари «ID сутності — ТИП компонента». При операціях копіювання елементів моделі всі елементи отримують нові ID старого ID і випадкового коду операції за допомогою симетричної хеш-функції, тому в сутності замість підміни всіх змінених ID всередині компонентів запам'ятовується код перетворення старих ID нові. Це дозволяє копіювати дані компонентів простим і швидким побайтным копіюванням, не втрачаючи посилальної цілісності в структурі моделі. В результаті можна копіювати величезні моделі без читання структури їх компонентів, що, в свою, чергу забезпечує миттєве копіювання великих збірок межах проектованого приміщення.
Завдяки попередньому механізму реалізований спеціальний компонент-транзакція, в якому автоматично зберігається історія зміни структури моделі під час того, як різні команди редагують її вміст. Поділ сутності на відносно невеликі компоненти дозволило поставити «слухач» на звернення до кожного компоненту, і відслідковувати, тим самим, його зміна автоматично. Це дозволяє зберігати всю історію змін моделі і повертатися до будь-якого моменту її створення, навіть зробленому багато місяців тому (оскільки вся історія зберігається також в компонентах, які без необхідності не завантажуються в оперативну пам'ять). З точки зору розробника це означає наявність у геометричній моделі аналога транзакцій, аналогічних існуючим в СУБД.
Версійність кожного елемента і компонента моделі забезпечує швидке формування спеціальних файлів-патчів, в яких міститься інформація про те, які сутність і компоненти потрібно скорегувати на клієнтської моделі, щоб синхронізувати її з версією на сервері. Укупі з використанням бінарної версії протоколу WebSocket це забезпечує ефективну синхронізацію даних моделі на всіх підключених клієнтів у реальному часі.
На практиці подібна система працює досить швидко, забезпечуючи час обробки більшості запитів до моделі в межах 5-50 мс. Однак у системи утворилося вузьке місце: відкриття моделі вимагає передачі всіх даних для її візуалізації, що у разі масивних моделей вимагає безлічі звернень до БД для отримання даних компонентів, призводячи до значних затримок, що досягає декількох секунд на моделях з десятків тисяч елементів. Подолати цю проблему дозволило кешування файлів-патчів Redis. Оскільки патчі не втрачають своєї актуальності (патчі з версії 0 до версії сто + патч до версії 100 до версії 200 еквіваленти єдиного патчу з версії 0 до версії 200), це легко вирішує проблему инвалидации кешу: його можна оновлювати у фоновому режимі, не піклуючись про втрати актуальності даних.
Клієнтська частина
Проектування клієнтської частини почалося з вибору движка для візуалізації. Були перепробувані майже всі популярні WebGL движки, проте зупинитися ні на одному з них не вдалося з наступних причин:
  • Слабка підтримка CAD режимів візуалізації, таких як видалення невидимих ліній і обмальовка силуетів криволінійних поверхонь.
  • Слабке розвиток інструментів для якісного виведення тексту невеликих розмірів в 3D режимі в поєднанні з малювання ліній довільної товщини (ця комбінація потрібна для промальовування планів на 3D моделі)
  • Відсутність ефективної техніки batching. Моделі найчастіше складаються з десятків тисяч невеликих елементів з різними матеріалами, і користувач може змінити будь-який об'єкт в будь-який момент часу, тому необхідні ефективні техніки динамічного склеюванню маленьких об'єктів у великі вершинні буфери у відеопам'яті для досягнення прийнятного рівня продуктивності.
  • Необхідність написання специфічних компонентів управління камерою, накладання матеріалів і анімації, т. к. запропоновані «коробкові» варіанти не підходять під потреби системи проектування.
В результаті аналізу прийшло розуміння того, що написати свій «велосипед» буде і швидше, і краще, і значно легше підтримки. За основу була обрана чудова бібліотека https://twgljs.org
Ось приклади відтворення плану приміщення і 3D виду на WebGL:
image
image
Наступним питанням був вибір мови програмування і платформи в цілому. У мене вже був досвід розробки JavaScript додатки розміром близько 10 тисяч рядків. Виходячи з цього досвіду, ідея розробки на JavaScript щось більшого особисто мені вселяла благоговійний жах. Черговий реліз TypeScript і той факт, що за ним стоїть Андерс Хейлсберг, зумовило вибір мови. Вибір платформи для Web упав на Angular 2 ( який тепер уже 4): мені і так належало зібрати проект з чималої кількості різношерстих бібліотек, а збирати свій комбайн для Web-додатки не було ні найменшого бажання. Хотілося мати саме framework, в якому «все включено». Розвинені можливості відкладеного завантаження модулів системи, ефективна кодогенерация (AOT) і можливості інтернаціоналізації тільки зміцнили мій вибір. Єдине, що мене бентежить на даний момент — це відсутність локалізації повідомлень у вихідних файлах, але я щиро сподіваюся, що вже до четвертої версії вони реалізують цю функціональність))
Реалізація задумів
Проект почався з реалізації на С++ прототипу структури майбутньої моделі та експериментальної візуалізації OpenGL. Після декількох місяців налагодження я зайнявся перекладом програми на клієнт-серверну модель. Спочатку я зробив це таким чином: написав суміщений REST+WebSocket сервер на C# і підключив геометричний сервіс, як динамічну бібліотеку з C-інтерфейсом для роботи з моделями, яка буде викликатися для геометричних запитів. Крайнє незручність налагодження такого гібридного програми і непотрібні накладки при копіюванні даних з C++ в З, а потім і в C# змусили мене шукати альтернативні рішення. В кінці кінців я включив WebSocket сервер всередину С++ частини і маршрутизировал всі запити до нього через проксі-сервер. При цьому для аутентифікації клієнтів геометричний сервіс робить внутрішні запити до основного REST-сервісу.
Наступним етапом стала реалізація алгоритму синхронізації моделі, змінною на сервері, з моделлю, яка відображається на клієнті. Початкові ідеї стеження сервером за станом клієнта, або відправлення клієнтом свого поточного стану на сервер перед синхронізацією довелося відкинути, як не дуже надійні і складні в реалізації. Зупинився я на наступній реалізації: в кожному компоненті зберігається цілочисельна версія компонента. Таким чином, версія моделі в цілому визначається максимальною версією серед всіх її компонентів і компонентів дочірніх сутностей. При синхронізації клієнт відправляє на сервер запит, що містить версію моделі, у відповідь на який сервер відправляє дані всіх компонентів, версія яких старше клієнтської версії. Це забезпечило синхронізацію деревоподібної моделі між клієнтами і сервером з мінімально можливим трафіком (запит синхронізації — одне число, а у відповіді містяться лише змінені компоненти).
Після написання прототипів клієнтської і серверної частин я зайнявся пошуком оптимального формату даних для передачі геометричної моделі між клієнтом і сервером. У цьому форматі я хотів мати наступні можливості:
  • Зручність запису і читання формату як з C++, так і з TypeScript коду
  • Підтримка схеми даних
  • Мінімально можливий час пакування та розпакування даних
  • Передача чисел з плаваючою крапкою без втрати точності
  • Ефективність роботи при середньому розмірі пакету даних в діапазоні 100Кб — 10Мб
  • Версійність формату для зручності розвитку в майбутньому і можливості поетапного поновлення різних частин системи
Використаний в експериментах JSON довелося відкинути відразу, а далі був вибір між MessagePack, Google Protocol Buffers, Apache Thrift, BSON і аналогічних їм бібліотек. Мій вибір зупинився на Google Protocol Buffers внаслідок кращої продуктивності, гарного стиснення і зручного кодогенератора. Важливим фактором також стала поширеність бібліотеки і надія, що вона не буде закинута в довгостроковій перспективі. В результаті я використовую рідний protobuf на С++, protobufjs — для читання і запису на клієнті, proto2typescript — для використання єдиної схеми між C++ і TypeScript. Крім того, дані додатково стискуються zlib при передачі через WebSockets. Це схема дозволила дуже комфортно і швидко передавати всі необхідні дані з моделі.
PS: не так давно натрапив на бібліотеку FlatBuffers від того ж розробника, і в мене з'явилася думка, що цей варіант буде ще краще, проте часу спробувати цю бібліотеку абсолютно не вистачає, крім того, підтримка TypeScript поки відсутня в основній гілці.
Після ладу формату даних і перших експериментів над кожною частиною системи стало приблизно зрозуміло, як буде функціонувати сервіс в цілому. Крім цього були оцінені вузькі місця і зроблені начерки варіантів масштабування в майбутньому. Потім були створені перші варіанти програми, що показують роботу зв'язки «дія користувача — запит до сервера моделювання — візуалізація дії користувача». На цьому етапі настало перше розчарування: така схема роботи забезпечує оновлення даних у стилі «потягнув за маркер, відпустив мишку, об'єкт перебудувався». Це було занадто повільно для інтерактивного відображення дій користувача при переміщенні курсору.
Цей результат вимагає переосмислити кордон між серверною і клієнтською частиною і зробити клієнт більшим, а також продублювати функціонал між сервером і клієнтом таким чином, щоб клієнт проводив попередні розрахунки для інтерактивної полігональної візуалізації, а сервер виробляв фінальні дії над BREP моделлю і синхронізував всіх клієнтів між собою. Це змусило мене глибоко задуматися про проект WebAssembly, який теоретично дозволив би мати єдину кодову базу для клієнтської і серверної частини і оперативно керувати виконанням розрахунків між клієнтом і сервером, перерозподіляючи навантаження по мірі необхідності. Але поки це все мрії…
Наступним етапом стала реалізація повноцінного WebGL рендера. Поки його можливості досить скромні, однак і над ними довелося неабияк попотіти. Перерахую основні моменту реалізації:
На поточному етапі для відтворення використовую класичний Forward-rendering і кілька проходів. На перспективу планую реалізувати затінення на основі Screen Space Ambient Occlusion або Scalable Ambient Obscurance.
Для досягнення прийнятної продуктивності склеиваю дрібні об'єкти у великі буфери вершин в глобальній системі координат і відправляю в відеокарту. При зміні об'єктів всі необхідні буфери знову перераховуються на процесорі. Це може виглядати дико, але відправляти матрицю об'єкта в додаткових атрибутах ще дорожче.
Відображення в 3D ліній товщиною відмінною від 1 пікселя вкрай в WebGL нетривіальна. Реалізував її через малювання двох трикутників, всі вершини яких лежать на одній лінії, а товщина зберігається в атрибутах — тангенсных векторах. Кінцеві вершини трикутників розраховуються в вершинном шейдере шляхом перекладу точок в систему координат екрана (для обліку співвідношення ширини і висоти екрана), збільшення необхідної товщини ліній і перекладу в нормалізовані координати. Згладжування ліній реалізується через альфа-канал у фрагментном шейдере.
Побудова тексту зроблена через техніку SDF, опублікованій компанією Valve. Для підготовки шрифтів використовувалася утиліта Ієрогліф від libgdx. Отриманий мною результат — задовільний: при розмірі шрифту 14-16 пікселів текст виглядає непогано, якщо ж розмір менше, і текст розташований під гострим кутом до площини екрану, то він практично ніяк. Можливо, я просто не вмію готувати SDF, але втративши багато часу, кардинального поліпшення результатів отримати не вдалося. В перспективі планую спробувати цю техніку: http://wdobbie.com/post/gpu-text-rendering-with-vector-textures/
Хочу також зазначити, що підтримка WebGL в сучасних браузерах відмінна порівняно з OpenGL під Windows. Це, мабуть, завдяки проекту Angle, эмулирующему виклики WebGL через DirectX. Код без милиць відмінно працює навіть під IE 11. З іншого боку, в додатку, оперують великими обсягами даних зі складними структурами, гостро стоїть проблема витоків пам'яті, з якими боротися дуже непросто.
Епілог
Наступним кроком у розробці стала реалізація предметної області програми — моделювання будівель, планування приміщень і різних конструктивних елементів, розміщення предметів інтер'єру і створення каталогів користувача. Звичайно ж, потребують уваги і часу безліч супутніх веб-сервісу речей у вигляді авторизації і аутентифікації, забезпечення резервного копіювання, масштабування різних частин, безперервної інтеграції всього процесу розробки. У цих напрямках попереду чималий фронт роботи, перш ніж проект можна буде відкрити для публічного користування. Незважаючи на це, проведена робота дозволила мені придбати величезний досвід. Сподіваюся, мої вигадки будуть корисними комусь із читачів. Якщо хтось має досвід і готовий, у свою чергу, поділитися порадами щодо реалізації веб-сервісу, мені буде дуже цікаво їх почути. Пишіть в приват, або залишайте коментарі тут.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.