Ми реалізуємо мобільні додатки для перегляду 360°-панорам, які виходять далеко за межі простого статичного фото. Це або відео в equirectangular-форматі з декодуванням 4K/8K в реальному часі, або інтерактивні віртуальні тури з точками інтересу (hotspots), переходами між локаціями та аудіогідом. Технічні вимоги високі: потрібне декодування відео або керування набором тайлів з плавним переходом. Спеціалізуємося на таких рішеннях для iOS та Android, використовуючи Swift, Kotlin, Metal та OpenGL ES. Досвід — понад 10 років у мобільній розробці, десятки реалізованих проєктів з 360°-контентом.
Як забезпечити плавний рендеринг 360°-відео на iOS?
360°-відео в equirectangular-форматі на iOS декодується через AVPlayer з AVPlayerLayer, але AVPlayerLayer рендерить тільки в 2D. Для сферичної проєкції потрібен AVPlayerItemVideoOutput + Metal або SceneKit. Згідно з документацією Apple, AVPlayerItemVideoOutput.copyPixelBuffer(forItemTime:itemTimeForDisplay:) блокує викликаючий потік на час декодування кадру. На iPhone 12 декодування 4K H.265 займає 8–15ms — це половина бюджету кадра при 60fps. Виклик на CADisplayLink-callback в main thread дає видимі просадки.
Правильно: CADisplayLink тільки запускає Metal render pass, копіювання CVPixelBuffer — в окремій DispatchQueue(qos: .userInteractive), результат передається в Metal через CVMetalTextureCacheCreateTextureFromImage.
let displayLink = CADisplayLink(target: self, selector: #selector(renderFrame)) displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 60, maximum: 120, preferred: 120) @objc func renderFrame() { videoDecodeQueue.async { [weak self] in guard let pixelBuffer = self?.videoOutput.copyPixelBuffer( forItemTime: self!.playerItem.currentTime(), itemTimeForDisplay: nil ) else { return } self?.metalRenderer.render(pixelBuffer: pixelBuffer) } } Такий підхід гарантує плавний рендеринг навіть на старих пристроях. Для Android аналогічне рішення будується на TextureView + MediaCodec з поверхневим викликом OpenGL.
Чому тайлова панорама краща для віртуальних турів?
Для високоякісних статичних панорам (готелі, нерухомість, музеї) використовують не єдине зображення, а тайли за аналогією з картами: кілька рівнів деталізації, розбитих на сітку. При наближенні (зменшенні FOV) підвантажуються тайли більш високої роздільної здатності.
Стандарт — Krpano tile format або Marzipano. Для мобільного рендеру використовуємо власний Metal/OpenGL ES пайплайн або PanoramaGL (Android). Тайли завантажуються через URLSession з пріоритетами: спочатку видимий квадрант сфери, потім сусідні 4 тайли (prefetch).
LRU-кеш для тайлів — обов'язково. Без нього при навігації по віртуальному туру (10+ локацій × 6 граней куба × 4 рівні деталізації) пам'ять зростає до 800 МБ за 15 хвилин. Наш досвід показує, що оптимізація кешу знижує споживання до 120 МБ.
| Параметр | Відео-панорама | Тайлова панорама |
|---|---|---|
| Роздільна здатність | Обмежена декодером (4K~8K) | Практично безлімітна (сітка тайлів) |
| Продуктивність | Залежить від апаратного декодування | Лінійно від числа тайлів на екрані |
| Інтерактивність | Обмежена (тільки панорама) | Hotspots, переходи, аудіогід |
| Розмір на диску | 500 МБ на 5 хвилин 4K відео | ~50 МБ на локацію (10 тайлів) |
Hotspots та інтерактивність
Hotspot — це точка в 3D-просторі сфери, яка при рендерингу перетворюється на 2D-елемент на екрані (іконка, підказка, кнопка переходу). Конвертація зі сферичних координат (yaw/pitch) у screen coordinates:
func sphericalToScreen(yaw: Float, pitch: Float, cameraYaw: Float, cameraPitch: Float, fov: Float, screenSize: CGSize) -> CGPoint? { // Матричне перетворення: сферичні → декартові → проєкція камери → NDC → screen let direction = SIMD3<Float>( cos(pitch) * sin(yaw), sin(pitch), cos(pitch) * cos(yaw) ) // ... view matrix × projection matrix → clip space → viewport } Якщо dot(direction, cameraForward) < 0 — hotspot за камерою, не рендеримо.
Анімація hotspot-появи при потраплянні в FOV — fade-in через CABasicAnimation, не через SwiftUI-анімацію (SwiftUI overlay поверх Metal SCNView дає 2–3ms layout pass на кожний кадр). Використовуємо власний рендер-пайплайн, що мінімізує overhead.
Гіроскоп та компас
CMDeviceMotion через CMMotionManager дає quaternion орієнтації пристрою з частотою до 100 Hz. Перетворюємо в Euler-кути для камери сцени, застосовуємо фільтр Калмана для згладжування джиттера. Без фільтра гіроскопний дрейф на старих пристроях (~0.3° на секунду) за 5 хвилин відводить «північ» на 15°.
Північний напрямок при старті — з CLLocationManager.heading, прив'язуємо нульовий yaw до магнітної/істинної півночі. Гарантуємо точність відстеження на всіх iPhone та iPad з гіроскопом.
Що входить в роботу
В рамках проєкту ми надаємо:
- детальну технічну документацію та архітектурну схему рендер-пайплайну;
- доступ до репозиторію з кодом та інструментом для збірки;
- інструкції з інтеграції з бекендом та публікації в сторах;
- 3 місяці технічної підтримки після здачі.
Процес роботи
- Аналітика та збір вимог: обговорюємо цілі, типи контенту, цільові пристрої.
- Проектування архітектури рендер-пайплайну (Metal/OpenGL ES).
- Розробка модуля завантаження та кешування тайлів.
- Інтеграція гіроскопа, компаса та сенсорного керування.
- Реалізація hotspots з анімаціями та переходами.
- Оптимізація продуктивності під цільові пристрої.
- Тестування на реальних пристроях та симуляторах.
- Підготовка документації та інструкцій.
- Супровід при публікації в App Store та Google Play.
- Технічна підтримка протягом 3 місяців після здачі.
Додатково про кешування тайлів
Для кешу використовуємо LRU-стратегію з обмеженням у 100 МБ. Кожний тайл важить в середньому 100-300 КБ. При підвантаженні нової локації попередньо завантажуємо всі тайли поточного рівня, а також тайли суміжної локації, якщо вона визначена картою переходів. Це виключає лаги при навігації.
Строки розробки
| Компонент | Строки |
|---|---|
| Базова відео-панорама з Metal-рендером (iOS) | 2–3 тижні |
| Тайлова панорама з кешуванням (iOS/Android) | 3–5 тижнів |
| Віртуальний тур з 10+ локаціями, hotspots, аудіогідом | 6–10 тижнів |
| Повний цикл (проектування, розробка, тестування, деплой) | 8–14 тижнів |
Вартість розраховується індивідуально після аналізу вимог. Оцінимо проєкт протягом 2 робочих днів. Зв'яжіться з нами для оцінки вашого проєкту — ми проаналізуємо вимоги та запропонуємо оптимальне рішення. Отримайте консультацію, щоб обговорити деталі.







