[WWDC23] Meet ARKit for spatial computing

DAMIN KIM
daily-monster
Published in
19 min readFeb 7, 2024

안녕하세요 수요괴에에물 입니다! 😈😈 오늘은 제가 요즘 공부하고 있는 VisionOS 관련해서 Spatial computing에서 ARKit이 어떻게 쓰이는지 WWDC 세션을 듣고 이해가 안가는 부분은 내용을 조금씩 정리해봤습니다. 틀린 부분이 있을 수 있으니 댓글로 알려주시면 감사하겠습니다!

구체적인 코드부분은 어려워서 살짝 생략했고..ㅎㅎ 대략 이런식으로 쓰인다라고 가볍게 보시면 좋을 것 같습니다!

Overview

ARKit은 위와 같이
ARKitSession / DataProvider / Anchor 3가지의
Fundamental API로 나눌 수 있습니다.

Anchor

Anchor는 각각 유니크한 idtransform 속성을 가집니다.

세션에서는”일부 Anchor는 trackable한데 만약 trackable 한 앵커가 tracking 되지 않을때 그 앵커와 연결된 Virtual 콘텐츠를 hide 해줘야한다.” 라고 설명하는데 사실 이 의미가 어떤 의미인지 이해가 안 갔어요.

이것 저것 찾아보면서 제가 이해하기로는
ARKit이 실제 세계에 가상 콘텐츠를 배치하기 다양한 유형의 앵커가 있는데 이 중 trackable한 앵커( TrackableAnchor 프로토콜을 채택한 앵커)가 추적이 불가능해졌을 때는 시야에서 벗어나거나 조명이 충분하지 않은 경우 등의 상황 즉, 실제 환경에서도 눈에 안보여야 하는 상황이기 때문에 이때 해당 앵커와 연결된 모든 가상 콘텐츠도 숨겨져야 하는것입니다. 그렇지 않고 그대로 가상의 컨텐츠가 띄워져 있다면 어색하여 몰입감을 해칠 것입니다.

DataProvider

DataProvider는 이름 그대로 앵커의 값의 변화 같은 것을 관찰하고 있다가 값이 바뀔때 데이터를 제공해주고 다른 타입의 DataProvider는 다른 타입의 데이터를 제공합니다.(e.g. HandTrackingProvider, WorldTrackingProvider)

ARKitSession

ARKit의 기능들이 combine(결합) 된것임

ARKitSession여러개의 data provider들이 하나의 세션을 이룬다

세션이 실행되면 data provider가 data를 받기 시작해서 데이터의 종류에 따라 비동기적으로 데이터를 처리합니다!

Privacy

애플은 보안에 굉장히 신경을 쓰고 있기 때문에 ARKit 데이터에 접근하는것에 제약이 있습니다. (ARKit이 아무래도 카메라를 통해 민감한 정보들을 수집할 수 있어서 Privacy를 강조하는 것 같습니다.)

이런 ARKit의 데이터는 Shared Space에서는 접근을 못하고 Immersive space에서만 접근할 수 있습니다.

Immersive Space에서도 카메라 프레임은 클라이언트 쪽에 절대 보내지지 않음 = 접근 불가능 대신에 ARKit daemon 에 데이터가 전달되고 알고리즘에 의해 선별된 데이터만 요청시 전달 받을 수 있음!

world tracking, plane detection 등 이런 data 들은 사용자의 접근 허용이 필수로 필요하게 됩니다.
그래서 requestAuthorization이라는 권한 요청 API도 만들어져잇고 이때 authorizationStatus를 통해 allowed, denied 케이스를 통해 분기처리를 할 수 있겠죠?

World Tracking

worldTracking을 통해 실제 현실에 가상의 물체를 고정시킬수 있음
WorldTracking에서사용되는 것은 WorldTrackingProvider인데

가상 콘텐트를 위한 wordAnchors를 제공하고, 앱을 재시동해도 자동으로 WorldAnchor가 유지 될 수 있도록함
앵커를 제거하면 유지가 안되도록 할 수도 있음

WorldAnchor vs Non-WorldAnchor

월드 앵커가 있는 빨간색 큐브와 없는 파란색 큐브가 있고

내가 위치와 시선을 바꿔서 다시 큐브의 위치를 설정했을때
파란색 큐브는 내 위치(origin 앵커)에서 같은 위치로 다시 위치가 조정이 되고
빨간색 큐브는 내 위치와 관계 없이 공간상의 같은 위치로 고정이 되있습니다.

이게 어떤식으로 가능하냐면

방안을 스캔해서 (특정 위치에 점들이 찍힘)

공간상에 World Anchor를 가상의 콘텐츠와 함께 위치시킨다

근데 사실 각 앵커는 특정한 아이디만을 가지고 있고 가상 콘텐트의 데이터든 가지고 있지 않습니다. 우리는 앵커의 이 아이디와 가상의 콘텐츠를 매핑해줘야 합니다.

만약 방에서 썼다가 다른 곳을 장소를 옮겨서 쓰고 또 다시 방으로 들어왔을때 기존의 맵에서 위치를 인식해가지고 전에 사용했던 앵커가 그대로 위치될 수 있게 만들어줍니다.

Device pose

pose란 앱의 원점에 대한 기기의 방향과 위치를 의미하며 이머시브한 공간에서 직접 metal 이나 CompositorService 렌더링을 할때 필요한데 요청 쿼리가 상대적으로 비싸다.

이부분도 무슨말인지 이해가 어려워서 다시 정리하자면
ARKit를 사용해 장치의 위치와 방향(포즈)을 추적할 수 있고, 이는 앱이 장치의 실제 위치와 방향에 맞게 가상 컨텐츠를 정확하게 표시하도록 돕습니다. WorldTrackingProvider를 통해 이 pose 정보를 얻어, Metal과 같은 그래픽 라이브러리를 사용해 가상 컨텐츠를 렌더링할 때 필요한 데이터를 제공합니다. 그러나 pose 쿼리는 자원을 많이 사용하기 때문에 되도록 필요할때만 쓰는게 좋다고 합니다.

어떻게 ARKit에서 CompositorService로 렌더링하는지 예제를 보면은

이런식으로 c언어를 이용한 예제를 설명해주더라구요…
갑작스러운 c언어공격에 이부분은 정리를 하지 못했습니다ㅠ
다음에 쓸 일이 생기면 공부해봐야될 것 같아요ㅎㅎ

이부분에 대한 좀 더 자세한 정보를 얻고 싶다면 아래 세션을 참고해주세요!

  • Discover Metal for immersive apps — WWDC23
  • Optimize app power and performance for spatial computing — WWDC23

Scene understanding

Plane detection
plane detection provider
는 ARKit이 인식한 수평 혹은 수직의 평면에(표면) 정보를 제공하고 각 평면은 PlaneAnchor로 제공되고 이 앵커는 평면에 콘텐트를 배치하거나 low fidelity (= 낮은 퀄리티)의 물리 시뮬레이션을 돌릴때 적합하다고 합니다.

Plane Anchor는 그림과 같은 속성을 가지고 있습니다.
Anchor니까 id, transform은 당연히 가지고 있고 alignment, geometry, classification이 있습니다.

alignment는 평면이 테이블 평면같은 수평(horizontal)인지, 창문 평면같은 수직(vertical)인지 의미하고
geometry는 평면의 모양을 의미합니다. 평면이여도 여러가지 모양이 있을 수 있겠죠?plane의 classification(분류)은 다음과 같은데요

Plane detection을 통해 위 그림처럼 벽, 바닥, 천장, 테이블, 의자(앉는 부분), 창, 문 처럼 평면들을 각각 분류를 할 수 있다고 해요. 이를 이용해서 특정 평면에 앵커를 달 수 있을 것 같습니다.

만약 특정한 평면으로 분류가 안된다면 상황에 따라 .unknown, .undetermined, .notAvalable로 표시가 된다고 합니다.

Scene geometry

폴리곤을 이용해 공간을 측정하는데 폴리곤은 입체 공간을 파악하기 위해 쪼개 놓은 다각형들이라고 보시면 될것 같습니다.

Scene geometry에서는 SceneReconstructionProvider라고 하는 Provider를 통해 데이터를 제공합니다.

Mesh geomtry는 MeshAnchor의 형태로 제공이되며, PlaneAnchor처럼 콘텐츠 배치에 유용하고, MeshAnchor의 경우에는 높은 high-fidelity 물리 시뮬레이션에 적합하다고 하네요.

각 MeshAnchor는 geometry를 가지고

이 geometry는 꼭짓점(vertice), 법선(normal), 면(face) 그리고 face당 하나의 classification들을 가지고 있습니다.

Mesh face 또한 다양하게 분류될 수 있으며 Mesh gemetry로 이중에 하나도 인식이 안되었을때는 none으로 설정되게됩니다.

Image tracking

Image Tracking은 현실의 2D이미지를 인식하는 것으로 이미지 트래킹이 사용하는 Provider는 ImageTrackingProvider 입니다.

ReferenceImage의 Set을 통해 이미지를 인식할 수 있는데

ReferenceImage를 생성하는 방법은 3가지로

  1. asset catalog에 있는 AR resource group에서 load하는 것
  2. CVPixelBuffer init을 이용하는 것
  3. CGImage init을 이용하는 것

이렇게 ReferenceImage를 가지고 이미지 감지에 성공했다면
감지된 이미지는 ImageAnchor로 제공이 됩니다.
사전에 제공된 이미지를 사용하니까 알고있는 content에 배치를 할때, 고정적인 이미지에 배치할 때 유용합니다. (e.g. 영화 포스터를 레퍼런스로 만들어 포스터 위에 콘텐츠를 배치할 수 있겠네요)

이미지 앵커는 TrackableAnchor를 채택한 trackable한 앵커이고
인식한 이미지의 대략적인 사이즈를 estimatedScaleFactor를 통해 알 수 있다고 합니다.

Hand Tracking

자 다음은 Hand Tracking 입니다. Hand Tracking은 WWDC23에서 추가된 새로운 기능입니다.

Hand Tracking에 사용되는 Provider는 HantrackingProvider으로
감지된 손은 HandAnchor로 제공됩니다.

이 HandAnchor는 trackable하고 skeleton과 chirality(카이랄리티, 비대칭성이라는 과학용어)라는 속성을 가지고 있습니다.
여기서 chirality를 통해 손이 왼손인지, 오른손인지를 알 수 있습니다.
HandAnchor의 transform은 손목(wrist)의 transform이고 이는 앱 origin의 상대적인 위치를 나타냄

skeleton은 관절의 이름으로 구성됩니다(JointName)

Joint 구조체의 각 속성을 살펴보면
parentJoint: 현재 Joint에 연결된 부모 관절
name: 관절의 이름
localTransform: 부모 관절에 대한 상대적인 transform
rootTransform: 뿌리 관절에 대한 상대적인 transform
isTracked: 관절의 추적 여부

Hand tracking에서 각 관절을 enum을 만들어 놨어요…ㅎㅎ
0, 25라고 적혀있는 부분이 뿌리 관절인 손목 관절입니다.
이제 이 뿌리관절에 연결되어 있는 관절이 부모-자식의 관계를 이룹니다.
1,5,10,15,20의 부모관절은 뿌리 관절이 되는거겠죠

손에다가 콘텐츠를 올리거나 커스텀 제스쳐를 감지하는데 유용하겠네요
그리고 HandAnchor를 2가지 방법으로 받을 수 있는데 첫 번째는 업데이트를 주기적으로 확인하는 방법이고, 두 번째는 HandAnchors가 사용 가능해질 때 비동기적으로 앵커를 받는 방법입니다.

첫 번째 방법은 앱이 적극적으로 최신 상태를 확인하는 반면, 두 번째 방법은 새로운 HandAnchors가 감지되면 자동으로 앱에 알림을 보내는 방식입니다.

Example

App에서는 ARKit 데이터에 접근해야 하기위해 ImmersiveSpace로 Scene에서 구성해줬습니다. RealityView에서는 ViewModel에서 엔터티를 content에 추가하였습니다.

ViewModel에는
ARKitSession, HandTrackingProvider, SceneReconstructionProvider가 정의되어 있고, 우리가 생성할 모든 콘텐츠를 담은 Entity가 있고
Scene과 Collider map이 있습니다.

또한 다양한 함수를 제공하는데요 앱 속에서 쓰면서 확인해볼게요

setupContentEntity는 맵의 모든 손가락 엔터티들을 ViewModel의 contentEntity의 자식으로 추가한뒤 contentEntity를 리턴합니다.

Session initialization

세션 초기화는 3개의 task 중 하나로 실행됩니다.

첫 번째 task는 runSession함수를 호출하고 두개의 provider(HandTrackingProvider, SceneReconstructionProvider)와 함께 세션을 실행합니다.

그리고 세션이 시작되면 Anchor를 업데이트 받게 됩니다.

fingertip collider를 생성하고 업데이트 해볼게요,

이 task는 손 업데이트를 처리합니다.

비동기 시퀀스에서 provider에 대한 업데이트를 반복합니다.
- handAnchor가 추적되는지 확인하고
- fingertip 의 관절을 얻어서
- 관절도 추적되는지 확인합니다.

그리고 검지의 fingertip의 transform을 앱의 원점을 기준으로 계산합니다.
(원점-손목, 손목-검지 계산하고 둘을 곱해서 원점에서 검지 거리 계산)
그리고 fingerEntity 맵에서 모델 엔터티 transform 값을 업데이트

이 맵에서 fingerTip Model Entity를 생성하는 함수

이 ModelEntity 익스텐션의 createFingertip 함수에서는 Model Entity를 Collistion Shape이 있는 5mm의 Sphere로 만들어집니다.

불투명도 컴포넌트를 추가해서 이 엔터티를 숨깁니다. (관절 엔터티는 보기 안좋으니까..?)
그러나 디버깅을 위해서 숨기지 않고 표시해서 제대로 작동하는지 확인해보는 것도 좋습니다.

entity의 opacity가 0으로 되어있는 거를 임시로 1로 바꿔서 직접 확인해봅시다.

위 코드에서 추가했던 것처러 Sphere모양의 모델 엔터티가 있는걸 볼 수 있음

사진을 보면 트래킹 되는 콘텐트를 손이 부분적으로 가리고 있는걸 볼 수 있습니다.
이게 Hand occlusion이라고 가상 콘텐트 위에 손이 올라 올 수 있도록 하는것으로
기본적으로 enabled 되어있다고 합니다. 저 콘텐트를 좀 더 잘보이게 하기 위해 이걸 끌 수도 있어요

upperLimbVisibility를 hiden시키면

이렇게 손 위치와 무관하게 위에 콘텐츠가 계속 올라와 있어 더 잘 보이게 할 수도 있어요

이번에는 scene collider를 추가해보겠습니다.
scene collider는 physics와 gesture target으로도 사용합니다.

provider anchor update의 비동기 시퀀스에서 작업을 반복하여 meshAnchor로부터 ShapeResource를 생성한 다음
Anchor의 event의 added 케이스에서
Model Entity 만들어 해당 엔터티에 meshAnchor의 transform을 적용하고 collision에 shape을 넣어 CollisionComponent를 추가하고 PhysicsBodyComponent와 InputTargetComponent를 추가하면
이 collider가 gesture target이 되게 됩니다.

마지막으로 맵에 위에서 만든 새 entity를 추가하고
contentEntity에서 새 entity를 자식으로 추가합니다.

엔터티를 업데이트 하기 위해서는 맵에서 추가한 엔터티를 가져와서 transform과 collision shape을 업데이트해주고

삭제시에는 맵을 이용해서 부모 엔터티 (contentEntity)로부터 삭제하고, 맵에서도 removeValue로 값을 지우면 됩니다.

제스처로 정육면체 추가하기

App에서 SpatialTapGesture를 통해 손가락으로 탭했을 때 어떤 엔터티가 탭 되었는지 알 수 있고 탭이 완료시 onEnded에서 value를 받아와 global 좌표에서 scene 좌표로 변환하여 3D locatoin을 받게 됩니다.

탭 했을때 전달 받은 3D locaiton에 sphere를 추가하면 이렇게 sphere가 테이블 위에 추가된게 보입니다.

그러면 큐브를 추가하기 위해 addCube 메서드를 실행시 탭한 위치로 location3D를 넘겨줍니다. 큐브를 추가하려면 먼저 배치 위치를 계산해야하는데 여기서는 tapLocation의 20cm위로 지정해줬고, 큐브를 Model Entity로 생성하여 계산된 배치 위치로 포지션을 지정해줍니다.

그리고 InputTargetComponent를 통해 어떤 유형의 제스처에 반응할지 설정해줍니다. 위 코드에서는 indirect inputType만 허용하도록 했습니다. (fingertip collider를 통해서 직접적인 인터랙션을 제공할 수 있기 때문에)

그 다음 PhysicsBodyComponent를 커스텀해서 물리적 상호작용을 개선해줬습니다.

마지막으로 entity를 content 엔터티에 추가합니다.

우리가 손으로 탭할때마다 scene collider나 cube가 tap location위에 추가되고
물리 시스템이 큐브를 scene collider 위로 떨어트립니다.
그리고 hand collider가 큐브와 충돌할 수 있게 만들어줍니다.

더 자세한 건 아래 세션들을 참고하시면 됩니다.
아래 세션에 대한 내용도 추가로 정리할 예정입니다!

  • Build spatial experiences with RealityKit — WWDC23
  • Evolve your ARKit app for spatial experiences — WWDC23

Reference

--

--