Hand Tracking: отслеживание рук

Babylon.js предоставляет мощные инструменты для работы с 3D-графикой в браузере, включая интеграцию с WebXR для виртуальной и дополненной реальности. Одной из ключевых возможностей WebXR является hand tracking, позволяющий отслеживать положение и жесты рук пользователя в пространстве. Это открывает новые возможности для взаимодействия с 3D-сценой без использования контроллеров.

Подключение WebXR и активация Hand Tracking

Для начала необходимо создать базовую сцену Babylon.js и включить поддержку WebXR. Основные шаги:

const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);

const camera = new BABYLON.ArcRotateCamera("Camera", Math.PI/2, Math.PI/2, 2, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);

const light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(1, 1, 0), scene);

// Включение WebXR
const xrHelper = await scene.createDefaultXRExperienceAsync({
    uiOptions: { sessionMode: 'immersive-vr' },
    optionalFeatures: true
});

// Активация отслеживания рук
xrHelper.input.onControllerAddedObservable.add((controller) => {
    if (controller.inputSource.hand) {
        console.log("Hand tracking доступен");
    }
});

Ключевой момент: WebXR hand tracking работает только на устройствах и браузерах с поддержкой WebXR Hand Input API, например, на устройствах серии Oculus Quest.

Получение данных о костях руки

Каждая рука в Babylon.js представлена объектом WebXRHand, который содержит коллекцию костей (joints). Каждая кость имеет позицию и ориентацию в пространстве сцены.

xrHelper.input.onControllerAddedObservable.add((controller) => {
    if (controller.inputSource.hand) {
        const hand = controller.inputSource.hand;

        hand.joints.forEach((jointName) => {
            const joint = hand.get(jointName);
            console.log(`Кость ${jointName} координаты:`, joint.getPosition());
        });
    }
});

Основные кости:

  • wrist — запястье
  • thumb-metacarpalthumb-tip — большой палец
  • index-finger-metacarpalindex-finger-tip — указательный палец
  • Аналогично для остальных пальцев

Каждая кость возвращает объект WebXRJoint, содержащий методы:

  • getPosition() — возвращает позицию в пространстве сцены
  • getRotationQuaternion() — возвращает ориентацию кости в виде кватерниона

Визуализация рук

Для отладки или создания визуальных эффектов можно использовать MeshBuilder для построения сфер или цилиндров на каждой кости:

const spheres = {};
hand.joints.forEach((jointName) => {
    const sphere = BABYLON.MeshBuilder.CreateSphere(jointName, { diameter: 0.02 }, scene);
    spheres[jointName] = sphere;
});

scene.onBeforeRenderObservable.add(() => {
    hand.joints.forEach((jointName) => {
        const joint = hand.get(jointName);
        const pos = joint.getPosition();
        spheres[jointName].position.copyFrom(pos);
    });
});

Рекомендация: при визуализации всех костей можно использовать разные цвета для каждого пальца для лучшего восприятия.

Определение жестов рук

Базовый подход к распознаванию жестов — использование позиций пальцев относительно запястья. Например, для распознавания сжатого кулака:

function isFist(hand) {
    const wrist = hand.get("wrist").getPosition();
    let closed = true;

    const fingers = ["index-finger-tip", "middle-finger-tip", "ring-finger-tip", "pinky-finger-tip", "thumb-tip"];
    fingers.forEach(finger => {
        const tip = hand.get(finger).getPosition();
        if (BABYLON.Vector3.Distance(tip, wrist) > 0.07) {
            closed = false;
        }
    });
    return closed;
}

scene.onBeforeRenderObservable.add(() => {
    xrHelper.input.controllers.forEach(controller => {
        if (controller.inputSource.hand && isFist(controller.inputSource.hand)) {
            console.log("Кулак сжат");
        }
    });
});

Важно: точные пороги расстояний нужно подбирать под конкретное устройство и масштаб сцены.

Интерактивное управление объектами

Hand tracking позволяет напрямую взаимодействовать с объектами сцены без контроллеров:

const box = BABYLON.MeshBuilder.CreateBox("box", { size: 0.1 }, scene);

scene.onBeforeRenderObservable.add(() => {
    xrHelper.input.controllers.forEach(controller => {
        if (controller.inputSource.hand) {
            const indexTip = controller.inputSource.hand.get("index-finger-tip").getPosition();
            if (box.intersectsPoint(indexTip)) {
                box.scaling.setAll(1.5);
            } else {
                box.scaling.setAll(1);
            }
        }
    });
});

Такой подход позволяет создавать жестовые интерфейсы, где объекты реагируют на приближение пальцев, сжатие кулака или раскрытие ладони.

Оптимизация и производительность

  • Отслеживание каждой кости требует значительных ресурсов, особенно на мобильных устройствах.
  • Для снижения нагрузки можно обновлять визуализацию не каждый кадр, а через каждые 2–3 кадра.
  • Минимизировать количество создаваемых мешей, используя объединённые или инстанцированные объекты.
  • Проверять наличие hand-tracking перед выполнением сложных вычислений, чтобы избежать ошибок на устройствах без поддержки.

Дополнительные возможности

  • Интеграция с Physics Engine Babylon.js позволяет создавать реалистичные взаимодействия: захват объектов, столкновения пальцев с предметами.
  • Возможность комбинировать hand tracking с XR controller input для гибридных интерфейсов.
  • Использование WebXR Features для расширения отслеживания: например, добавление оптической распознаваемости жестов.

Hand tracking в Babylon.js открывает путь к естественным и интуитивным интерфейсам, позволяя строить приложения, где руки пользователя становятся полноценным инструментом управления и взаимодействия с 3D-пространством.