Créer des extensions d’entrée de données d’image et de mouvement de l’appareil
En créant une extension d’entrée de données d’image et de mouvement de l’appareil, les développeurs peuvent étendre EasyAR Sense avec des implémentations de caméra personnalisées pour prendre en charge des appareils de tête spécifiques ou d’autres périphériques d’entrée. Le contenu suivant présente les étapes et les points à noter pour créer ce type d’extension.
Avant de commencer
- Comprendre les notions de base comme caméras, trames d’entrée.
- Lire Source de données de trame externe pour les détails d’interface nécessaires à la création d’une source de trame externe.
- Lire Données de trame d’entrée externes pour en savoir plus sur les données de trame caméra et les données de trame rendues.
Créer une classe de source de données de trame externe
- Si vous devez créer une extension d’entrée 6DoF, héritez de ExternalDeviceMotionFrameSource
- Si vous devez créer une extension d’entrée 3DoF, héritez de ExternalDeviceRotationFrameSource
Les deux sont des sous-classes de MonoBehaviour, et le nom du fichier doit correspondre au nom de la classe.
Par exemple, création d’une extension d’entrée 6DoF :
public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}
Lors de la création d’une extension HMD, vous pouvez utiliser le modèle com.easyar.sense.ext.hmdtemplate et le modifier à partir de ce modèle. Ce modèle est inclus dans le paquet compressé du plugin Unity téléchargé depuis le site EasyAR.
Définition de l’appareil
Redéfinissez IsHMD pour indiquer si l’appareil est un casque.
Par exemple, définissez-le à true sur un casque.
public override bool IsHMD { get => true; }
Redéfinissez Display pour définir l’affichage de l’appareil.
Par exemple, sur un casque, utilisez les informations de l’affichage HMD par défaut Display.DefaultHMDDisplay, ce qui définit la rotation de l’affichage à 0.
protected override IDisplay Display => easyar.Display.DefaultHMDDisplay;
Disponibilité
Redéfinissez IsAvailable pour définir si l’appareil est disponible.
Par exemple, l’implémentation dans RokidFrameSource est la suivante :
protected override Optional<bool> IsAvailable => Application.platform == RuntimePlatform.Android;
Si IsAvailable ne peut pas être déterminé lors de l’assemblage de la session, vous pouvez redéfinir la coroutine CheckAvailability() pour bloquer le processus d’assemblage jusqu’à ce que la disponibilité soit confirmée.
Origine de session
Redéfinissez OriginType pour définir le type d’origine défini par le SDK de l’appareil.
Si OriginType est Custom, vous devez aussi redéfinir Origin.
Par exemple, l’implémentation dans RokidFrameSource est la suivante :
protected override DeviceOriginType OriginType =>
#if EASYAR_HAVE_ROKID_UXR
hasUXRComponents ? DeviceOriginType.None :
#endif
DeviceOriginType.XROrigin;
Caméra virtuelle
Si OriginType est Custom ou None, vous devez redéfinir Camera pour fournir une caméra virtuelle.
Par exemple, l’implémentation dans RokidFrameSource est la suivante :
protected override Camera Camera => hasUXRComponents ? (cameraCandidate ? cameraCandidate : Camera.main) : base.Camera;
Caméra physique
Utilisez le type DeviceFrameSourceCamera pour redéfinir DeviceCameras afin de fournir les informations de caméra physique de l’appareil. Ces données sont utilisées lors de l’entrée des trames caméra. La création doit être terminée lorsque CameraFrameStarted vaut true.
Par exemple, l’implémentation dans RokidFrameSource est la suivante :
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;
}
Redéfinissez CameraFrameStarted pour fournir l’indicateur de début d’entrée des trames caméra.
Par exemple :
protected override bool CameraFrameStarted => started;
Démarrage et arrêt de la session
Redéfinissez OnSessionStart(ARSession) puis effectuez l’initialisation spécifique à l’AR. Veillez à appeler base.OnSessionStart en premier.
Par exemple :
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
StartCoroutine(InitializeCamera());
}
C’est l’endroit approprié pour ouvrir la caméra de l’appareil, par exemple une caméra RGB ou VST, surtout si elle n’est pas conçue pour rester ouverte en permanence. C’est aussi l’endroit adapté pour récupérer les données d’étalonnage qui ne changent pas pendant tout le cycle de vie. Il peut parfois être nécessaire d’attendre que l’appareil soit prêt ou que les données soient mises à jour avant de pouvoir les récupérer.
C’est également un bon endroit pour démarrer la boucle d’entrée des données. Vous pouvez aussi écrire cette boucle dans Update() ou dans une autre méthode, en particulier lorsque les données doivent être récupérées à un moment précis de l’ordre d’exécution Unity. N’entrez pas de données avant que la session soit prête.
Si nécessaire, vous pouvez aussi ignorer le processus de démarrage et vérifier les données à chaque mise à jour, selon vos besoins.
Par exemple, l’implémentation dans RokidFrameSource est la suivante :
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;
}
Redéfinissez OnSessionStop() et libérez les ressources, en veillant à appeler base.OnSessionStop.
Par exemple, l’implémentation dans RokidFrameSource est la suivante :
protected override void OnSessionStop()
{
base.OnSessionStop();
RokidExtensionAPI.RokidOpenXR_API_CloseCameraPreview();
started = false;
StopAllCoroutines();
cameraParameters?.Dispose();
cameraParameters = null;
deviceCamera?.Dispose();
deviceCamera = null;
}
Entrer les données de trame caméra
Après avoir obtenu la mise à jour des données de trame caméra, appelez HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) pour entrer les données de trame caméra.
Par exemple, l’implémentation dans RokidFrameSource est la suivante :
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);
}
}
Attention
N’oubliez pas d’appeler Dispose() après utilisation, ou de libérer Image, Buffer et les autres données associées via using. Sinon, il y aura une fuite mémoire grave, et l’obtention de buffer depuis le pool peut aussi échouer.
Entrer les données de trame de rendu
Après que les données de l’appareil sont prêtes, appelez HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) à chaque trame de rendu pour entrer les données de trame de rendu.
Par exemple, l’implémentation dans RokidFrameSource est la suivante :
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());
}