Создание расширений ввода изображений и данных о движении устройства
Создавая расширения ввода изображений и данных о движении устройства, разработчики могут расширять EasyAR Sense пользовательскими реализациями камеры для поддержки определенных головных дисплеев или других устройств ввода. Ниже описаны шаги и важные замечания по созданию расширений ввода изображений и данных о движении устройства.
Перед началом
- Ознакомьтесь с базовыми понятиями, такими как камеры и входные кадры.
- Прочитайте Внешний источник данных кадров, чтобы узнать подробные требования к интерфейсам для создания внешнего источника данных кадров.
- Прочитайте Данные внешнего входного кадра, чтобы узнать о данных кадра камеры и данных кадра рендеринга.
Создание класса внешнего источника данных кадров
- Если нужно создать расширение ввода устройства 6DoF, наследуйте ExternalDeviceMotionFrameSource
- Если нужно создать расширение ввода устройства 3DoF, наследуйте ExternalDeviceRotationFrameSource
Оба класса являются подклассами MonoBehaviour, а имя файла должно совпадать с именем класса.
Например, создание расширения ввода устройства 6DoF:
public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}
При создании расширения для головного дисплея можно использовать шаблон com.easyar.sense.ext.hmdtemplate и изменять его как основу. Этот шаблон находится в архиве Unity-плагина, загруженном с сайта EasyAR.
Определение устройства
Переопределите IsHMD, чтобы определить, является ли устройство головным дисплеем.
Например, на головном дисплее задайте true.
public override bool IsHMD { get => true; }
Переопределите Display, чтобы определить дисплей устройства.
Например, на головном дисплее по умолчанию используется информация дисплея Display.DefaultHMDDisplay, что задает поворот дисплея равным 0.
protected override IDisplay Display => easyar.Display.DefaultHMDDisplay;
Доступность
Переопределите IsAvailable, чтобы определить, доступно ли устройство.
Например, реализация в RokidFrameSource выглядит так:
protected override Optional<bool> IsAvailable => Application.platform == RuntimePlatform.Android;
Если IsAvailable нельзя определить во время сборки session, можно переопределить корутину CheckAvailability(), чтобы заблокировать процесс сборки до тех пор, пока доступность не будет определена.
Источник координат session
Переопределите OriginType, чтобы определить тип источника координат, заданный SDK устройства.
Если OriginType равен Custom, также нужно переопределить Origin .
Например, реализация в RokidFrameSource выглядит так:
protected override DeviceOriginType OriginType =>
#if EASYAR_HAVE_ROKID_UXR
hasUXRComponents ? DeviceOriginType.None :
#endif
DeviceOriginType.XROrigin;
Виртуальная камера
Если OriginType равен Custom или None, нужно переопределить Camera, чтобы предоставить виртуальную камеру.
Например, реализация в RokidFrameSource выглядит так:
protected override Camera Camera => hasUXRComponents ? (cameraCandidate ? cameraCandidate : Camera.main) : base.Camera;
Физическая камера
Переопределите DeviceCameras с типом DeviceFrameSourceCamera, чтобы предоставить информацию о физических камерах устройства. Эти данные используются при вводе данных кадра камеры. Создание должно быть завершено, когда CameraFrameStarted равно true.
Например, реализация в RokidFrameSource выглядит так:
private DeviceFrameSourceCamera deviceCamera;
protected override List<FrameSourceCamera> DeviceCameras => new List<FrameSourceCamera> { deviceCamera };
{
var imageDimensions = new int[2];
RokidExtensionAPI.RokidOpenXR_API_GetImageDimensions(imageDimensions);
size = new Vector2Int(imageDimensions[0], imageDimensions[1]);
deviceCamera = new DeviceFrameSourceCamera(CameraDeviceType.Back, 0, size, new Vector2(50, 50), new DeviceFrameSourceCamera.CameraExtrinsics(Pose.identity, true), AxisSystemType.Unity);
started = true;
}
Переопределите CameraFrameStarted, чтобы предоставить признак начала ввода кадров камеры.
Например:
protected override bool CameraFrameStarted => started;
Запуск и остановка session
Переопределите OnSessionStart(ARSession) и выполните инициализацию, специфичную для AR. Нужно убедиться, что сначала вызывается base.OnSessionStart.
Например:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
StartCoroutine(InitializeCamera());
}
Это подходящее место для открытия камеры устройства, например RGB-камеры или VST-камеры, особенно если эти камеры не предназначены для постоянной работы. Также это подходящее место для получения калибровочных данных, которые не меняются в течение всего жизненного цикла. Иногда перед получением этих данных может потребоваться дождаться готовности устройства или обновления данных.
Кроме того, это подходящее место для запуска цикла ввода данных. Этот цикл также можно написать в Update() или других методах, особенно если данные нужно получать в определенный момент порядка выполнения Unity. Не вводите данные до готовности session (ready).
При необходимости можно также пропустить процесс запуска и выполнять проверку данных при каждом обновлении; это полностью зависит от конкретных требований.
Например, реализация в RokidFrameSource выглядит так:
private IEnumerator InitializeCamera()
{
yield return new WaitUntil(() => (RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() >= RokidTrackingStatus.Detecting && (RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() < RokidTrackingStatus.Tracking_Paused);
var focalLength = new float[2];
RokidExtensionAPI.RokidOpenXR_API_GetFocalLength(focalLength);
var principalPoint = new float[2];
RokidExtensionAPI.RokidOpenXR_API_GetPrincipalPoint(principalPoint);
var distortion = new float[5];
RokidExtensionAPI.RokidOpenXR_API_GetDistortion(distortion);
var imageDimensions = new int[2];
RokidExtensionAPI.RokidOpenXR_API_GetImageDimensions(imageDimensions);
size = new Vector2Int(imageDimensions[0], imageDimensions[1]);
var cameraParamList = new List<float> { focalLength[0], focalLength[1], principalPoint[0], principalPoint[1] }.Concat(distortion.ToList().GetRange(1, 4)).ToList();
cameraParameters = CameraParameters.tryCreateWithCustomIntrinsics(size.ToEasyARVector(), cameraParamList, CameraModelType.OpenCV_Fisheye, CameraDeviceType.Back, 0).Value;
deviceCamera = new DeviceFrameSourceCamera(CameraDeviceType.Back, 0, size, new Vector2(50, 50), new DeviceFrameSourceCamera.CameraExtrinsics(Pose.identity, true), AxisSystemType.Unity);
RokidExtensionAPI.RokidOpenXR_API_OpenCameraPreview(OnCameraDataUpdate);
started = true;
}
Переопределите OnSessionStop() и освободите ресурсы. Нужно убедиться, что вызывается base.OnSessionStop.
Например, реализация в RokidFrameSource выглядит так:
protected override void OnSessionStop()
{
base.OnSessionStop();
RokidExtensionAPI.RokidOpenXR_API_CloseCameraPreview();
started = false;
StopAllCoroutines();
cameraParameters?.Dispose();
cameraParameters = null;
deviceCamera?.Dispose();
deviceCamera = null;
}
Ввод данных кадра камеры
После получения обновления данных кадра камеры вызовите HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) для ввода данных кадра камеры.
Например, реализация в RokidFrameSource выглядит так:
private static void OnCameraDataUpdate(IntPtr ptr, int dataSize, ushort width, ushort height, long timestamp)
{
if (!instance) { return; }
if (ptr == IntPtr.Zero || dataSize == 0 || timestamp == 0) { return; }
if (timestamp == instance.curTimestamp) { return; }
instance.curTimestamp = timestamp;
RokidExtensionAPI.RokidOpenXR_API_GetHistoryCameraPhysicsPose(timestamp, positionCache, rotationCache);
var pose = new Pose
{
position = new Vector3(positionCache[0], positionCache[1], -positionCache[2]),
rotation = new Quaternion(-rotationCache[0], -rotationCache[1], rotationCache[2], rotationCache[3]),
};
// NOTE: Use real tracking status when camera exposure if possible when writing your own device frame source.
var trackingStatus = ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus()).ToEasyARStatus();
var size = instance.size;
var pixelSize = instance.size;
var pixelFormat = PixelFormat.Gray;
var yLen = pixelSize.x * pixelSize.y;
var bufferBlockSize = yLen;
var bufferO = instance.TryAcquireBuffer(bufferBlockSize);
if (bufferO.OnNone) { return; }
var buffer = bufferO.Value;
buffer.tryCopyFrom(ptr, 0, 0, bufferBlockSize);
using (buffer)
using (var image = Image.create(buffer, pixelFormat, size.x, size.y, pixelSize.x, pixelSize.y))
{
instance.HandleCameraFrameData(instance.deviceCamera, timestamp * 1e-9, image, instance.cameraParameters, pose, trackingStatus);
}
}
Осторожно
Не забывайте после использования вызывать Dispose() или освобождать Image , Buffer и другие связанные данные через такие механизмы, как using. Иначе возникнут серьезные утечки памяти, а получение buffer из buffer pool также может завершаться ошибкой.
Ввод данных кадра рендеринга
После готовности данных устройства в каждом кадре рендеринга вызывайте HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) для ввода данных кадра рендеринга.
Например, реализация в RokidFrameSource выглядит так:
protected void LateUpdate()
{
if (!started) { return; }
if ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() < RokidTrackingStatus.Detecting) { return; }
if ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() >= RokidTrackingStatus.Tracking_Paused) { return; }
InputRenderFrameMotionData();
}
private void InputRenderFrameMotionData()
{
var timestamp = RokidExtensionAPI.RokidOpenXR_API_GetCameraPhysicsPose(positionCache, rotationCache);
var pose = new Pose
{
position = new Vector3(positionCache[0], positionCache[1], -positionCache[2]),
rotation = new Quaternion(-rotationCache[0], -rotationCache[1], rotationCache[2], rotationCache[3]),
};
if (timestamp == 0) { return; }
HandleRenderFrameData(timestamp * 1e-9, pose, ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus()).ToEasyARStatus());
}