Реалізація 3DoF-трекінгу голови в мобільному VR-додатку

TRUETECH займається розробкою, підтримкою та обслуговуванням мобільних додатків iOS, Android, PWA. Маємо великий досвід та експертизу для публікації мобільних додатків до популярних маркетів Google Play, App Store, Amazon, AppGallery та інші.

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1735 послуг
Реалізація 3DoF-трекінгу голови в мобільному VR-додатку
Середній
~3-5 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    495

Реалізація 3DoF Head Tracking у мобільних VR-додатках

3DoF (три степені свободи) — це обертання: pitch (кивок), yaw (поворот), roll (нахил голови). Смартфон знає орієнтацію через IMU — акселерометр та гіроскоп. Об'єднати дані двох датчиків у стабільну орієнтацію без накопичення drift — ось де вся складність.

IMU Fusion: чому не можна використовувати лише гіроскоп

Гіроскоп вимірює кутову швидкість з високою точністю та низьким шумом. Інтегруємо за часом — отримуємо кут повороту. Проблема: числове інтегрування накопичує помилку. За кілька хвилин гіроскоп "дрейфує" на кілька градусів — віртуальний горизонт змінюється.

Акселерометр у статиці показує на центр Землі — це абсолютна орієнтація. Проблема: при русі акселерометр не розрізняє гравітацію від прискорення, його дані шумні.

Рішення — Complementary Filter або Madgwick/Mahony фільтр:

// Android: спрощений Complementary Filter
class ComplementaryFilter(val alpha: Float = 0.98f) {
    private var pitch = 0f
    private var roll = 0f

    fun update(gyroDelta: FloatArray, accel: FloatArray, dt: Float) {
        // Кут з гіроскопа (швидко, точно короткострокова)
        val gyroPitch = pitch + gyroDelta[0] * dt
        val gyroRoll  = roll  + gyroDelta[1] * dt

        // Кут з акселерометра (повільно, абсолютна орієнтація)
        val accelPitch = Math.toDegrees(Math.atan2(accel[1].toDouble(), accel[2].toDouble())).toFloat()
        val accelRoll  = Math.toDegrees(Math.atan2(-accel[0].toDouble(), accel[2].toDouble())).toFloat()

        // Мішаємо: 98% гіроскопа + 2% акселерометра
        pitch = alpha * gyroPitch + (1f - alpha) * accelPitch
        roll  = alpha * gyroRoll  + (1f - alpha) * accelRoll
    }
}

alpha = 0.98 — стандартне значення. При швидких рухах голови тимчасово знижуємо alpha (більше довіри акселерометру), при повільних — підвищуємо.

Android SensorManager: читаємо IMU правильно

На Android IMU читається через SensorManager. Два варіанти: TYPE_ROTATION_VECTOR (уже дає fusion з системи) або сирі TYPE_GYROSCOPE + TYPE_ACCELEROMETER + кастомний fusion.

TYPE_GAME_ROTATION_VECTOR — спеціально для ігор: не використовує магнітометр (компас), тому не залежить від металевих об'єктів поруч. Для VR — найкращий вибір:

val sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
val gameRotationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR)

sensorManager.registerListener(object : SensorEventListener {
    override fun onSensorChanged(event: SensorEvent) {
        // event.values: [x, y, z, w] quaternion
        val rotationMatrix = FloatArray(16)
        SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
        // Застосовуємо до camera transform
        updateCameraRotation(rotationMatrix)
    }
    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}, gameRotationSensor, SensorManager.SENSOR_DELAY_FASTEST) // ~500Hz

SENSOR_DELAY_FASTEST — критично для VR. SENSOR_DELAY_GAME (~50Hz) дає помітне запізнення при швидких поворотах.

iOS: CoreMotion та CMMotionManager

На iOS все простіше: CMMotionManager.deviceMotion вже містить fusion з акселерометра, гіроскопа та магнітометра. Attitude повертає як CMAttitude з roll, pitch, yaw або quaternion:

let motionManager = CMMotionManager()
motionManager.deviceMotionUpdateInterval = 1.0 / 60.0 // 60Hz мінімум, краще 90+

motionManager.startDeviceMotionUpdates(
    using: .xArbitraryZVertical, // не залежить від магнітного північного
    to: .main
) { [weak self] motion, error in
    guard let motion else { return }
    let quaternion = motion.attitude.quaternion
    self?.cameraNode.orientation = SCNQuaternion(
        x: Float(quaternion.x),
        y: Float(quaternion.y),
        z: Float(quaternion.z),
        w: Float(quaternion.w)
    )
}

xArbitraryZVertical — reference frame без залежності від магнітного північного. Початковий напрямок довільний, що правильно для VR: користувач сам обирає куди дивитися при запуску.

Latency: головний враг комфорту

Motion-to-photon latency — час від руху голови до оновлення картинки на екрані. Поріг комфорту: < 20ms. Типовий pipeline:

  • IMU → sensor event: 1–3ms
  • Sensor event → camera rotation update: 1–5ms (залежить від thread scheduling)
  • Camera rotation → render: 8–16ms (один кадр при 60–120 FPS)
  • Render → display: 8–16ms (display latency)

Загалом легко виходить 20–40ms. ATW (Asynchronous TimeWarp) у Cardboard SDK бере останній rendered кадр та перепроецирує його з урахуванням нової орієнтації — віртуально знижує motion-to-photon latency без зменшення render time.

Recenter (скидання орієнтації)

Користувач повернувся боком або встав — його "прямо вперед" змінилася. Recenter встановлює поточну орієнтацію голови як нульову:

// iOS
func recenter() {
    referenceAttitude = motionManager.deviceMotion?.attitude.copy() as? CMAttitude
}

// У update: застосовуємо delta щодо reference
func updateCamera() {
    guard let current = motionManager.deviceMotion?.attitude,
          let reference = referenceAttitude else { return }
    current.multiply(byInverseOf: reference)
    // використовуємо current.quaternion як camera rotation
}

Recenter зазвичай прив'язаний до кнопки Cardboard або до спеціального жесту (струс пристрою).

Робочий процес

Вибір IMU API: системний rotation vector проти сирих гіроскопа/акселерометра з кастомним fusion.

Реалізація читання датчиків з мінімальною latency, на виділеному потоці.

Синхронізація орієнтації з рендером, налаштування recenter.

Тестування drift: 10-хвилинна сесія без recenter, оцінка накопленої помилки.

Інтеграція з ATW через Cardboard SDK.

Оцінка часу

Базовий 3DoF head tracking через системний rotation vector — 1–2 дні. Кастомна реалізація з власним fusion, latency оптимізацією, recenter — 3–5 днів.