Реализация 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-трекинга головы в мобильном VR-приложении

3DoF (three degrees of freedom) — это вращение: pitch (кивок), yaw (поворот), roll (наклон головы). Смартфон знает, куда направлен, через IMU — акселерометр и гироскоп. Объединить данные двух сенсоров в стабильную ориентацию без дрейфа — вот где кроется вся сложность.

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 vs сырые гироскоп/акселерометр с кастомным fusion.

Реализация чтения сенсоров с минимальной latency, на выделенном потоке.

Синхронизация ориентации с рендером, настройка recenter.

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

Интеграция с ATW через Cardboard SDK.

Ориентиры по срокам

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