WebXR: запуск в браузере и ограничения
Асинхронный запуск, пространства отсчёта, ввод и реальные ограничения Web-экспорта.
WebXR: запуск в браузере и ограничения
WebXR подключает веб-приложение к XR-устройству, поддерживаемому браузером. Это отдельная цель экспорта, а не запуск Windows-сборки внутри браузера. Наличие WebXRInterface в SDK не подтверждает работающую браузерную сессию.
Ограничение C# сейчас: установлен SDK 1.0.0, но его плагин экспорта C# не реализует цель Web. Выбор Web export не превращает настольный C#-проект в WebXR-приложение. Отдельно нужны нативный веб-шаблон и проект с конфигурацией скриптов/runtime, которую этот шаблон поддерживает. Статья не подтверждает успешный браузерный экспорт Reckfell на C#.
Что нужно до подключения шлема
- Веб-сборка с модулем WebXR и подходящий проект. Модулю нужны OpenGL и включённый XR; на Web параметр
proxy_to_pthreadотключает его. - Рендерер Compatibility. Флаги веб-платформы отключают RenderingDevice; настольные настройки Vulkan/Forward+ не являются инструкцией для браузера.
- Браузер и устройство с поддержкой нужного режима.
inline,immersive-vrиimmersive-arзапрашиваются отдельно; поддержка одного не подтверждает остальные. - Безопасный контекст страницы и разрешение браузера. Запускайте immersive-сессию действием пользователя по кнопке. Для встроенной страницы также проверьте XR permissions policy родительского сайта. См. спецификацию WebXR.
Обычная страница должна оставаться доступной при отсутствии или отказе XR. Сначала сделайте небольшую сцену и рабочий 2D-интерфейс входа/выхода, затем добавляйте тяжёлый рендеринг.
Правильная последовательность сессии
Один компонент должен управлять запуском, подписками и восстановлением viewport. Не запускайте процедуры OpenXR и WebXR одновременно для одного origin.
- Найдите зарегистрированный интерфейс с именем
WebXRчерез XRServer. Его отсутствие означает, что текущая сборка/цель не предоставляет интерфейс; не создавайте абстрактный WebXRInterface вручную. - Подпишитесь на SessionSupported, SessionStarted, SessionEnded и SessionFailed до запроса или старта. Сохраните прежний Viewport.UseXR для восстановления.
- Вызовите IsSessionSupported для нужного режима, например
immersive-vr. Метод не возвращает bool поддержки: режим и результат позже передаёт SessionSupported. Пока ждёте, отключите кнопку входа; сопоставляйте полученный режим со своим запросом. - До старта задайте SessionMode, RequiredFeatures, OptionalFeatures и RequestedReferenceSpaceTypes. В API движка required/optional features — строки через запятую, не JSON-массивы.
- В обработчике пользовательского нажатия кнопки вызовите Initialize напрямую. Не ждите перед ним постороннюю асинхронную работу. Пока запрос выполняется, исключите повторный запуск.
- Включите Viewport.UseXR после SessionStarted, затем прочитайте ReferenceSpaceType и EnabledFeatures. true из Initialize или IsInitialized не доказывает принятие сессии браузером: native-код ставит initialized до завершения асинхронного запроса.
- При SessionFailed или SessionEnded восстановите прежний флаг viewport, покажите обычный интерфейс и разрешите осознанный повтор. Сохраните сообщение ошибки. При выходе компонента-владельца удалите подписки; не вызывайте Dispose у заимствованного зарегистрированного интерфейса.
Ранняя валидация может вызвать SessionFailed ещё внутри Initialize. Подписки и состояние fallback должны быть готовы заранее. Не перезаписывайте состояние обработчика ошибки только потому, что Initialize вернул управление.
В текущем JS-мосте запрос поддержки обрабатывает успешное завершение, но не содержит rejection handler для isSessionSupported(). При отклонённом browser promise событие SessionSupported может не прийти. Добавьте тайм-аут/ошибку ожидания в интерфейс; тайм-аут означает запрос не завершён, а не доказанное отсутствие поддержки. Это ограничение исходников не исправлялось в движке.
Пространство отсчёта и features
По умолчанию интерфейс использует SessionMode=inline и RequestedReferenceSpaceTypes=local; immersive-режим нужно задать явно.
| Настройка | Практический смысл |
|---|---|
RequiredFeatures=local-floor | При старте обязательно получить floor-space feature. Используйте, только если без неё игра работать не может. |
OptionalFeatures=local-floor | Запросить поддержку пола, не делая её обязательной. |
RequestedReferenceSpaceTypes=local-floor,local | Пробовать пространства по порядку; второе — fallback, а не второй одновременно действующий origin. |
| ReferenceSpaceType | Реально выбранное пространство после SessionStarted; может отличаться от первого запроса. |
| EnabledFeatures | Сообщённый список принятых features. Один optional-запрос не разрешает механику, которой эта feature необходима. |
Если выбрано local, не считайте Y=0 измеренным физическим полом. Разделяйте настройку стоя и сидя. Браузер может оценивать высоту пола; игровому содержимому всё равно нужен подходящий origin. Запрос browser feature также не доказывает наличие всех связанных API в Reckfell.
Путь рендеринга
Текущий код умеет переходить с XRWebGLBinding/projection layers на XRWebGLLayer: когда layers отключены, binding отсутствует/не создаётся или нет createProjectionLayer. Native-старт также отключает layers-путь при отсутствии GLES multiview. Прежнее общее утверждение, что любая stereo-сессия требует multiview, не описывает этот fallback.
Наличие fallback-кода не гарантирует совместимость устройства. Проверяйте фактический stereo-вывод в целевом браузере/устройстве. DisableWebXRLayers выбирает fallback до запуска, а не уровень качества. Статья не подтверждает пиксели, сглаживание или производительность шлема.
Ввод контроллеров
При standard mapping используются имена ниже; nonstandard mapping получает общие имена button_0/axis_0. Не каждый источник ввода предоставляет все кнопки или grip pose.
| Имена | Значение |
|---|---|
| trigger_click, grip_click | Bool, получаемый текущим кодом из button pressure > 0; не отдельный browser pressed flag. |
| trigger, grip | Давление кнопки, сообщённое браузером. |
| touchpad_click, thumbstick_click, ax_button, by_button | Стандартные bool кнопок, когда устройство их предоставляет. |
| touchpad_x/y, thumbstick_x/y | Скалярные оси; при standard mapping движок инвертирует Y. |
| touchpad, thumbstick | Vector2, создаются при наличии соответствующей пары осей. |
| default, aim | Aim transform активного источника ввода. |
| grip | Grip transform, только если он предоставлен источником. |
Перед взаимодействием проверяйте активность источника и актуальные tracking data pose. Gaze или screen input не становится физическим ручным контроллером автоматически. Совпадение отдельных имён не означает загрузку или полную идентичность OpenXR action map.
Диагностика
| Симптом | Что проверить |
|---|---|
| Нет WebXR interface в настольном редакторе | Ожидаемое различие платформ: конкретный интерфейс регистрируется под WEB_ENABLED. |
| Не проходит C# Web export | Поддержку цели текущим C# exporter, а не геометрию XR-сцены. |
| Кнопка входа зависла в ожидании | Режим запроса, консоль браузера, разрешение/policy и ограничение rejection при проверке поддержки. |
| Initialize успешен, затем ошибка сессии | Сообщение SessionFailed: отказ запроса, WebGL compatibility или исчерпанные варианты reference space. |
| Неверная высота игрока | Реальный ReferenceSpaceType и origin; local не гарантирует пол. |
| Нет grip или кнопок | Активность источника, mapping и данные устройства; не придумывайте отсутствующие элементы управления. |
Границы проверки
Реальный JS-мост движка прошёл 16 проверок в Node с явными mock браузера, сессии, GL и таймеров. Адаптирована только привязка регистрации библиотеки Emscripten; тела функций не менялись. Проверены результат запроса поддержки, немедленная отправка запроса сессии, разбор features, порядок fallback пространств, выбор layers-пути, ошибки старта и очистка JS-состояния.
Это не проверка браузерного экспорта или шлема. Native callback integration, реальные permissions/user activation, WASM build, выполнение C# Web, пиксели, контроллеры и производительность не проверены. C++ lifecycle, условия сборки модуля/платформы, ограничения C# export и mapping ввода сверены с текущими исходниками. Source, Runtime и SDK движка не менялись.