Créer une extension d'entrée d'images et de données de mouvement d'appareil
Par la création d'une extension d'entrée d'images et de données de mouvement d'appareil, les développeurs peuvent étendre EasyAR Sense avec une implémentation personnalisée de caméra, permettant ainsi la prise en charge de casques de réalité virtuelle spécifiques ou d'autres dispositifs d'entrée. Le contenu suivant décrit les étapes et les considérations pour créer une telle extension.
Avant de commencer
- Comprendre les concepts de base tels que caméras, images d'entrée.
- Lire Source de données d'images externes pour connaître les interfaces détaillées nécessaires à la création d'une source de données d'images externes.
- Lire Données d'images d'entrée externes pour comprendre les données d'images de caméra et les données d'images de rendu.
Créer une classe de source de données de trame externe
- Si vous avez besoin de créer une extension d'entrée de périphérique 6DoF, héritez de ExternalDeviceMotionFrameSource
- Si vous avez besoin de créer une extension d'entrée de périphérique 3DoF, héritez de ExternalDeviceRotationFrameSource
Elles sont toutes deux des sous-classes de MonoBehaviour, et le nom du fichier doit correspondre au nom de la classe.
Par exemple, pour créer une extension d'entrée de périphérique 6DoF :
public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}
Lors de la création d'une extension pour casque de réalité virtuelle, vous pouvez utiliser le modèle com.easyar.sense.ext.hmdtemplate et le modifier. Ce modèle est inclus dans l'archive du plugin Unity téléchargée depuis le site web d'EasyAR.
Définition de l'appareil
Remplacez IsHMD pour définir si l'appareil est un casque de réalité virtuelle.
Par exemple, définissez sur true sur les casques de réalité virtuelle.
public override bool IsHMD { get => true; }
Remplacez Display pour définir l'affichage de l'appareil.
Par exemple, sur les casques de réalité virtuelle, les informations d'affichage par défaut Display.DefaultHMDDisplay sont utilisées, ce qui définit la rotation de l'affichage à 0.
protected override IDisplay Display => easyar.Display.DefaultHMDDisplay;
Disponibilité
Remplacez 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 remplacer la co-routine CheckAvailability() pour bloquer le processus d'assemblage jusqu'à ce que la disponibilité soit établie.
Session origine
Remplacez OriginType pour définir le type d'origine défini par le SDK d'appareil.
Si OriginType est Custom, vous devez également remplacer 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, il faut remplace 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 remplacer DeviceCameras afin de fournir des informations sur les appareils photo physiques de l'appareil. Ces données sont utilisées lors de la saisie des trames de la caméra. Doit être complété lorsque CameraFrameStarted est 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;
}
Remplacez CameraFrameStarted pour fournir l'indicateur de début de saisie de la trame de la caméra.
Par exemple :
protected override bool CameraFrameStarted => started;
Session démarrage et arrêt
Surchargez OnSessionStart(ARSession) puis effectuez l'initialisation spécifique à la RA. Assurez-vous d'appeler d'abord base.OnSessionStart.
Par exemple :
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
StartCoroutine(InitializeCamera());
}
C'est l'endroit approprié pour activer la caméra du périphérique (par exemple, caméra RVB ou caméra VST), surtout si ces caméras ne sont pas conçues pour rester constamment actives. C'est également l'endroit idéal pour obtenir des données d'étalonnage qui ne changeront pas pendant tout le cycle de vie. Parfois, il peut être nécessaire d'attendre que le périphérique soit prêt ou que les données soient mises à jour avant que ces données ne puissent être obtenues.
C'est aussi un bon endroit pour démarrer une boucle d'entrée de données. Vous pouvez également écrire cette boucle dans Update() ou d'autres méthodes, surtout si les données doivent être acquises à un moment précis de l'ordre d'exécution d'Unity. N'envoyez pas de données avant que la session soit prête (ready).
Si nécessaire, vous pouvez ignorer le processus de démarrage et vérifier les données à chaque mise à jour, cela dépend entièrement des besoins spécifiques.
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;
}
Surchargez OnSessionStop() et libérez les ressources. Assurez-vous d'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;
}
Entrée des données de trame de caméra
Après avoir obtenu la mise à jour des données de trame de la caméra, appelez HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) pour entrer les données de trame de la 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'exécuter Dispose() ou de libérer via des mécanismes comme using pour Image, Buffer et d'autres données associées après utilisation. Sinon, une fuite de mémoire grave se produira et l'obtention du tampon dans le pool de tampons peut échouer.
Entrer les données de trame de rendu
Une fois les données de l'appareil 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());
}
Prochaines étapes
- Créer un Kit d'extension pour casque