reckfellENGINE

Слои композиции OpenXR для UI

SubViewport, формы слоёв, проверенный ray-to-pixel helper, порядок и hole punching.

На этой странице

Слои композиции OpenXR для читаемого UI

Composition layer передаёт XR runtime отдельную поверхность для композиции, избегая дополнительного пересэмплирования через основную сцену для этой поверхности. Это может улучшить читаемость UI, но не гарантирует резкий текст при любом разрешении и расстоянии. SubViewport на обычной mesh остаётся допустимым более простым вариантом.

Сначала настройте запуск XR. Наличие класса не означает поддержку runtime. IsNativelySupported сообщает поддержку конкретного типа слоя, а не запущенную сессию, отправленный viewport или видимые пиксели. При отсутствии поддержки/в редакторе возможен fallback обычной mesh сцены с другими качеством и поведением spectator view.

Сначала сделайте плоскую панель

Текст
XROrigin3D
├── XRCamera3D
└── OpenXRCompositionLayerQuad
    └── SubViewport
        └── Control (UI root)
  1. Задайте положительный Size SubViewport в пикселях, например 800×400, и явно RenderTargetUpdateMode=Always. Назначьте его в LayerViewport слоя. Один viewport нельзя разделять между несколькими composition layers в дереве; сначала отсоедините его от текущего слоя.
  2. Разместите UI в пределах viewport. Для повторного использования сохраните иерархию UI как префаб; сам префаб не отправляет слой compositor.
  3. Задайте QuadSize как размеры поверхности, например 2×1 для соответствующего отношения 2:1. Разрешение в пикселях и размер поверхности — разные настройки. Проверка использовала WorldScale=1; текущий native setter передаёт QuadSize напрямую, поэтому пример не подтверждает совпадение размеров/pose при другом масштабе мира. Меняйте размеры поверхности, а не Entity Scale: в локальном и унаследованном transform не должно быть масштаба/skew.
  4. Разместите панель перед нужным зрителем, выберите читаемые расстояние и высоту. Фиксированная позиция относительно origin не является текущим положением головы автоматически. Пересматривайте размещение после перемещения/recenter.
  5. Для прозрачного UI включите SubViewport.TransparentBg=true и AlphaBlend=true. Учитывайте реальный фон UI: alpha слоя не убирает непрозрачный полноэкранный фон Control.

При назначении viewport режимы WhenVisible и WhenParentVisible меняются на Always с предупреждением. Другие режимы, например Disabled, автоматически подходящими не становятся; управляйте обновлением осознанно.

Слой под XRCamera3D становится head-locked, в том числе через промежуточные Entity. Без camera ancestor он world-locked. Выбирайте это явно: меню, следующее за головой, отличается от панели в комнате. LocalFloor сам не размещает меню перед переместившимся игроком.

Выбор формы

EntityПоверхность
OpenXRCompositionLayerQuadПлоский прямоугольник; начните с него для pointer UI.
OpenXRCompositionLayerCylinderВнутренняя сторона сегмента цилиндра; форму задают radius, central angle и aspect ratio.
OpenXRCompositionLayerEquirectСферический/equirectangular сегмент; нужны подходящие содержимое и угловая раскладка.

Поддержка runtime проверяется для каждой формы. FallbackSegments меняет разбиение изогнутой fallback mesh, а не разрешение native compositor. Helper ниже намеренно только для Quad; его плоское отображение не подходит изогнутым слоям.

Луч в координаты UI

Этот точный helper выполнен в установленном runtime 1.0.0. Он переводит world-space ray в назначенный viewport; не отправляет ввод и не проверяет перекрывающие объекты мира.

C#
using System;
using Reckfell;

public static class QuadUiRay
{
    // Maps a world-space ray to pixels; does not send input or test occluding objects.
    public static bool TryGetPixel(OpenXRCompositionLayerQuad layer,
        Vector3 origin, Vector3 direction, out Vector2 pixel)
    {
        ArgumentNullException.ThrowIfNull(layer);
        pixel = default;
        if (!layer.IsInsideTree() || !layer.IsVisibleInTree()) return false;
        var viewport = layer.LayerViewport;
        if (viewport == null || viewport.Size.X <= 0 || viewport.Size.Y <= 0) return false;
        if (layer.QuadSize.X <= 0 || layer.QuadSize.Y <= 0) return false;
        if (!origin.IsFinite() || !direction.IsFinite() || direction.LengthSquared() < 0.000001f) return false;
        // Native ray projection expects no scale/shear, including inherited transforms.
        var basis = layer.GlobalTransform.Basis;
        if (!basis.IsEqualApprox(basis.Orthonormalized())) return false;
        var uv = layer.IntersectsRay(origin, direction.Normalized());
        if (!uv.IsFinite() || uv.X < 0 || uv.X > 1 || uv.Y < 0 || uv.Y > 1) return false;
        var size = viewport.Size;
        pixel = new Vector2(Mathf.Clamp(uv.X * size.X, 0, size.X - 1),
            Mathf.Clamp(uv.Y * size.Y, 0, size.Y - 1));
        return true;
    }
}

Вызывайте QuadUiRay.TryGetPixel с global position контроллера и forward direction, обычно -controller.GlobalTransform.Basis.Z, только после проверки текущих tracking data pose. true даёт локальные пиксели viewport. UV Y уже направлена вниз; повторно переворачивать её не нужно. Native IntersectsRay возвращает (-1,-1) при промахе. Пересечение Quad не является physics raycast и не отсекает панель за стеной.

Для рабочего pointer-компонента храните hover, последний pixel и pressed state. Отправляйте motion в SubViewport через PushInput с inLocalCoords=true. Левое нажатие создавайте только при переходе up→down, release — down→up; согласованно ведите ButtonMask. При потере tracking/focus, выходе луча, закрытии панели или выходе компонента отправьте release в последний валидный pixel, затем очистите pressed/hover. Иначе GUI button или drag может остаться зажатым. Удержание trigger не является новым click каждый кадр. Эти случаи маршрутизации событий/focus не выполнялись проверкой mapping.

Не вызывайте Free/Dispose заимствованного LayerViewport или provider tracker из helper. Их временем жизни управляет сцена/компонент-владелец. Перед удалением отдельно принадлежащего вам viewport отсоедините LayerViewport; до очистки остановите отправку событий.

Порядок и hole punching

SortOrder по умолчанию 1, основная projection имеет порядок 0. Положительный слой может закрыть физически более близкие объекты сцены: порядок композиции не равен обычному depth test.

Чтобы объекты сцены перекрывали панель, используйте EnableHolePunch=true вместе с отрицательным SortOrder, например -1. Native compositor рисует UI позади основной projection, а hole-punch mesh сцены обеспечивает отверстие/поведение глубины. Неотрицательный порядок вызывает configuration warning. Также проверьте AlphaBlend и путь прозрачности основного viewport: один флаг не подтверждает корректное перекрытие в runtime.

Native-composited слой может быть виден только в шлеме и отсутствовать на настольном spectator view. При необходимости сделайте отдельное spectator-представление; desktop capture не доказывает картинку шлема. Fallback, native composition и hole-punch пути нужно оценивать отдельно.

Диагностика

СимптомЧто проверить
Панель не обновляетсяLayerViewport, положительный Size и явный Always update mode.
Ошибка назначенияТот же SubViewport уже используется другим слоем в дереве.
UI растянутОтношение сторон pixel Size viewport и QuadSize.
Луч не попадает в видимую панельТекущую pose, направление world ray, видимость и scale/shear всех предков.
Click смещён или отражён по вертикалиЛокальные пиксели, актуальный Size viewport и отсутствие второго переворота UV Y.
GUI остался зажатRelease/cancel при выходе луча, потере tracking/focus и очистке.
Панель закрывает более близкую рукуSortOrder и hole-punch: native composition не является обычной mesh сцены.
Нет UI в desktop captureРеальный native/fallback путь и отдельный spectator UI.

Границы проверки

Windows headless с XR off выполнил 20 проверок: точный helper для центра/углов/промахов, translation/rotation, некорректных rays/size, видимости, локального/унаследованного масштаба, назначения viewport и свойств. Hole-punch проверен только как metadata, без рендера перекрытий. OpenXR session, native layer submission, изображение/читаемость шлема, controller/GUI input routing, hit testing изогнутых форм, spectator capture и export не приняты как проверенные. Source, Runtime и SDK не менялись.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.