Як згенерувати 3D-модель телефону в браузері

Інструкція: 3D-модель телефону в браузері з Three.js і CSG. Корпус із bevel, вирізи для кнопок і динаміків, Web Worker і швидке віднімання всіх отворів.

Дві згенеровані 3D-моделі телефону: одна спереду з темним дисплеєм, друга ззаду

Щоб згенерувати 3D-модель телефону в браузері, витягніть прямокутник із заокругленими кутами на глибину пристрою, а потім булевими операціями (CSG) виріжте передню панель, кнопки, динаміки й роз'єм. Увесь пристрій тоді описують кілька чисел у міліметрах, і нова модель означає зміну цих чисел, а не моделювання в 3D-програмі.

Це корисно всюди, де потрібно багато схожих пристроїв або де їх має налаштовувати користувач: конфігуратори, мокапи, прев'ю продуктів. Приклад із практики: Ecranify, інструмент для 3D-мокапів для скриншотів в App Store і Google Play, у якому немає жодної готової 3D-моделі. Нижче покроковий процес разом із вимірюванням, яке показує, де на обчисленнях можна заощадити найбільше.

Що вам знадобиться

  • Three.js для сцени, камер, світла і передусім геометрії. Геометрія в Three.js це mesh, тобто набір трикутників.
  • Бібліотека three-bvh-csg для булевих операцій над mesh. Вона пришвидшує їх просторовим індексом (BVH, bounding volume hierarchy), тому не перевіряє кожен трикутник проти кожного.
  • Опис пристрою як дані, наприклад JSON із розмірами корпусу, радіусом кутів, розміром bevel і списком елементів на боках. Усі позиції задавайте в міліметрах від центру пристрою.

Крок 1: Витягніть прямокутник у 3D

Намалюйте 2D-прямокутник із заокругленими кутами в реальному розмірі, наприклад 163,4 × 78 мм, і витягніть його (екструзія) на глибину 8,75 мм. Отримаєте пластину з гострими гранями. Таких телефонів не буває, тому під час екструзії увімкніть ще й bevel, тобто заокруглення ребра. Three.js робить обидва кроки за один раз.

Лише екструзіяКутаста пластина без заокруглених кутів і ребер
+ кути й bevelТа сама пластина із заокругленими кутами й ребрами
Та сама форма, два інші числа: радіус кута й розмір bevel.

Зверніть увагу: bevel додається назовні. Якщо потрібен точний зовнішній розмір, зменште 2D-форму та глибину на розмір bevel.

b = min(bevel, глибина / 2)
форма = заокруглений прямокутник(ширина - 2b, висота - 2b, радіус кута - b)
корпус = витягни(форма, глибина - 2b, bevel b)

Гладкість дуг (тесселяцію) визначає кількість сегментів, на які ділиться дуга. Більше сегментів означає гладшу форму й більше трикутників. Це число згодом визначить швидкість усього обчислення, тож ми повернемося до нього в кроці 7.

Крок 2: Виріжте заглиблення для дисплея

CSG (constructive solid geometry) складає тіла з простіших тіл за допомогою трьох операцій: об'єднання, різниця й перетин. Різниця працює як свердло: одне тіло забирає матеріал з іншого.

Дисплей телефону не врівень із рамкою, він лежить у неглибокому заглибленні. Щоб його зробити, створіть другу копію корпусу, меншу на 1,4 мм, залиште з неї лише передню частину завтовшки 0,7 мм (перетин із великим паралелепіпедом) і відніміть цю частину від корпусу. Потім покладіть у заглиблення темну площину дисплея.

Різальне тілоКорпус телефону із синім напівпрозорим різальним тілом на передній панелі
Після відніманняКорпус із вирізаним заглибленням і темним дисплеєм усередині
Синє тіло віднімається. Праворуч дисплей уже лежить у заглибленні.

Дві практичні деталі:

  • Паралелепіпед для перетину має бути справді великим. Йому достатньо обрізати глибину, але за шириною й висотою він має перевищувати весь пристрій. Не виводьте його з глибини: наприклад, 20-кратна глибина для тонкого високого телефону менша за його висоту, і вгорі та внизу залишиться необрізана рамка.
  • Збільште різальне тіло на кілька проміле. Коли два тіла мають точно однакову грань, CSG не може визначити, яка сторона всередині. Масштаб 1,002 цьому запобігає, і оком його не видно.

Крок 3: Підготуйте бічні елементи як свердла

Кожен елемент на боці робиться за тим самим рецептом з іншим свердлом. Кнопка, наприклад, складається з трьох тіл: заокруглений прямокутник, трохи глибший за кнопку, віднімається від рамки, край отвору отримує невелику фаску, а в отвір вставляється трохи менше тіло, яке злегка виступає з рамки.

СвердлаБік телефону з двома синіми різальними тілами для кнопок
Виріз і кнопкаБік телефону з вирізаними отворами й вставленими кнопками
Дві кнопки на правому боці до і після віднімання.

Динаміки це ряд маленьких циліндрів, роз'єм це заокруглений прямокутник. У кожен отвір вставте щось темне: тонкий диск у решітку, пластинку в роз'єм. Порожня дірка в тонкій оболонці виглядає як помилка, дірка з темним заповненням виглядає як залізо.

Нижній край телефону з двома рядами отворів динаміка й роз'ємом посередині
Отвори динаміка й роз'єм. Усе це відняті циліндри й заокруглені прямокутники.
для кожного елемента в списку бічних елементів:
    свердло = форма елемента (циліндр, заокруглений прямокутник, ...)
    розмісти свердло на ребрі за позицією в мм
    додай свердло до списку вирізів
    якщо елемент має заповнення: додай заповнення до сцени

Додати кнопку тоді означає додати запис у дані. Елементів швидко більшає: iPad Pro 13″ має 58 отворів динаміка, 4 кнопки, 2 антени й роз'єм, тобто понад 60 віднімань від одного корпусу.

Крок 4: Не свердліть передні деталі

Виріз камери, острівець у верхній частині дисплея й індикатор домашнього екрана пласкі. Різати для них геометрію марна робота. Нанесіть їх як decal, тобто зображення, спроєктоване на поверхню. Проєктуйте його на приховану площину на всю передню панель і задавайте позицію в тих самих міліметрах, що й усе інше. Виняток: кругла кнопка «Додому» старших iPhone. Вона має справжню глибину, яку видно збоку, тому проходить через CSG.

Крок 5: Перенесіть обчислення у Web Worker

JavaScript у браузері працює в одному потоці. Той самий потік рахує, малює сторінку й реагує на кліки. Поки працює CSG, сторінка зависає, і spinner не допоможе, бо йому потрібен той самий потік.

Тому запускайте всю збірку пристрою у Web Worker, тобто в окремому фоновому потоці. Worker отримує опис пристрою, повертає готові масиви вершин, і сторінка лишається плавною. Коли параметр змінюється, завершіть старий worker і запустіть новий, щоб він не дораховував застарілий стан.

Результат із worker з'являється не відразу, тому додайте миттєве прев'ю. Показуйте елемент, який редагується, як синього напівпрозорого «привида»: простий паралелепіпед або циліндр із кількома десятками трикутників, який можна збудувати в головному потоці одразу. Коли worker закінчить, привид зникне.

Крок 6: Відніміть усі отвори одразу

Найпряміший спосіб: віднімати елементи по черзі, отвір 1 від корпусу, потім отвір 2 від результату й так далі. Але кожне віднімання заново будує просторовий індекс усього корпусу, а це найдорожча частина обчислення. З десятками елементів час росте дуже швидко.

Краще спершу зібрати всі свердла для даного тіла, об'єднати їх в один mesh і відняти один раз.

вирізи = []
для кожного елемента: додай його свердло до вирізи
об'єднаний = об'єднай усі вирізи в один mesh
корпус = корпус мінус об'єднаний
якщо об'єднання не вдалося: віднімай вирізи по одному

Вимірювання на спрощеній копії процесу (Three.js 0.170, three-bvh-csg 0.0.17, Node 22, хмарний сервер із 2 ядрами Intel Xeon 2,1 GHz, медіана з 5 запусків):

Тіло Отвори По одному Одразу
телефон, 17 676 трикутників 25 2,47 с 0,26 с
планшет, 17 676 трикутників 58 5,55 с 0,89 с
планшет, 68 108 трикутників 58 22,8 с 3,1 с

Одна умова: об'єднання й одне віднімання дає той самий результат, що й послідовне віднімання, лише якщо свердла не перекриваються. Елементи розташовані в різних місцях, тож зазвичай це так. Але якщо користувач посуне два елементи один на одного, у місці перекриття може з'явитися дрібний дефект.

Крок 7: Тримайте кількість сегментів як параметр

Тесселяція, якої достатньо для показу, для CSG часто надто густа. З 64 сегментами на кут і 32 на bevel корпус має 68 108 трикутників. Половини (32 і 16) на ребрі 2 мм не видно, але корпус має лише 17 676 трикутників, і третій рядок таблиці показує, що це робить із часом.

64 і 32 сегментиКаркасна модель кута телефону з дуже густою сіткою трикутників
32 і 16 сегментівКаркасна модель того самого кута з удвічі меншою кількістю сегментів
Кут корпусу згори. Праворуч чверть трикутників.

Дрібним елементам із радіусом до 2 мм (отвори динаміка, гвинти) дайте половину сегментів, мініатюрам у галереї ще менше. Тому не задавайте якість як константу в коді, а робіть її параметром збірки.

Рендер на сервері без відеокарти

Якщо ту саму сцену потрібно рендерити й на сервері, наприклад як резерв для експорту, вона працює в Chrome без екрана (headless), який без відеокарти малює програмно. Там кожна неефективність множиться. В Ecranify найважчий планшет без описаних змін упирався в ліміт 15 хвилин. Зі спільними вирізами, меншими текстурами, нижчою тесселяцією й cache готової геометрії за hash опису пристрою рендер триває близько пів хвилини, а повторний близько 5 секунд. Подробиці з інтерактивними прикладами є в англомовній статті на ecranify.com.

Якщо ви розв'язуєте схожу задачу з 3D у браузері або потрібен застосунок на замовлення, напишіть нам.

← Більше в категорії Ecranify