이미지 및 기기 모션 데이터 입력 확장 생성
이미지 및 기기 모션 데이터 입력 확장을 생성함으로써, 개발자는 EasyAR Sense에 대한 사용자 정의 카메라 구현을 확장하여 특정 헤드셋 장치나 다른 입력 장치를 지원할 수 있습니다. 다음 내용은 이미지 및 기기 모션 데이터 입력 확장을 생성하는 단계와 주의 사항을 소개합니다.
시작하기 전에
- 카메라, 입력 프레임 등의 기본 개념을 이해하세요.
- 외부 프레임 데이터 소스를 생성하는 데 필요한 상세 인터페이스 설명은 외부 프레임 데이터 소스를 참고하세요.
- 카메라 프레임 데이터와 렌더링 프레임 데이터에 대해 알아보려면 외부 입력 프레임 데이터를 읽어보세요.
외부 프레임 데이터 소스 클래스 생성
- 6DoF 장치 입력 확장을 생성하려면, ExternalDeviceMotionFrameSource 상속
- 3DoF 장치 입력 확장을 생성하려면, ExternalDeviceRotationFrameSource 상속
이들은 모두 MonoBehaviour 의 하위 클래스이며, 파일 이름은 클래스 이름과 동일해야 합니다.
예를 들어, 6DoF 장치 입력 확장 생성:
public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}
HMD 확장 생성 시, com.easyar.sense.ext.hmdtemplate 템플릿을 사용하여 수정할 수 있습니다. 이 템플릿은 EasyAR 웹사이트에서 다운로드한 Unity 플러그인 압축 파일 내에 있습니다.
장치 정의
IsHMD를 재정의하여 장치가 헤드 마운트 디스플레이(HMD)인지 정의합니다.
예를 들어, HMD에서는 true로 설정합니다.
public override bool IsHMD { get => true; }
Display를 재정의하여 장치의 디스플레이를 정의합니다.
예를 들어, HMD에서 기본 디스플레이 Display.DefaultHMDDisplay 정보를 사용하며, 이는 디스플레이 회전을 0으로 정의합니다.
protected override IDisplay Display => easyar.Display.DefaultHMDDisplay;
사용 가능성
IsAvailable을 재정의하여 장치의 사용 가능 여부를 정의합니다.
예를 들어, RokidFrameSource에서의 구현 방식은 다음과 같습니다:
protected override Optional<bool> IsAvailable => Application.platform == RuntimePlatform.Android;
IsAvailable이 세션 조립 시점에 판단할 수 없는 경우, 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;
Physical camera
DeviceFrameSourceCamera 타입을 사용하여 DeviceCameras를 재정의하여 디바이스 물리적 카메라 정보를 제공합니다. 이 데이터는 입력 카메라 프레임 데이터 시 사용됩니다. 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;
세션 시작 및 중지
OnSessionStart(ARSession)를 재정의한 후 AR 전용 초기화 작업을 수행합니다. base.OnSessionStart를 먼저 호출해야 합니다.
예시:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
StartCoroutine(InitializeCamera());
}
이는 디바이스 카메라(예: RGB 카메라 또는 VST 카메라 등)를 여기에 열기에 적합한 위치입니다. 특히 이러한 카메라가 항상 켜져 있도록 설계되지 않은 경우 더욱 그렇습니다. 또한 수명 주기 동안 변경되지 않는 보정 데이터를 가져오기에 적절한 위치이기도 합니다. 때로는 디바이스가 준비되거나 데이터가 업데이트될 때까지 기다려야 할 수 있습니다.
또한 데이터 입력 루프를 시작하기에 적절한 위치입니다. 필요에 따라 Update() 또는 다른 메소드에서 이 루프를 작성할 수도 있습니다. 특히 데이터를 Unity 실행 순서의 특정 시점에 가져와야 하는 경우에 유용합니다. 세션이 준비(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);
}
}
주의
사용 후 Image, Buffer 및 기타 관련 데이터를 Dispose() 실행하거나 using 메커니즘을 통해 해제하는 것을 잊지 마십시오. 그렇지 않으면 심각한 메모리 누수가 발생할 수 있으며, 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());
}
다음 단계
- 헤드셋 확장 패키지 생성하기