스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
공간 컴퓨팅을 위한 ARKit 알아보기
ARKit의 추적과 씬 이해 기능을 사용해 몰입형 앱과 게임에 완전히 새로운 세계를 개발할 방법을 알아보세요. visionOS와 ARKit이 만나 어떤 식으로 사람의 주변 환경을 이해하는 앱을 만들 수 있게 되는지 알려 드립니다. 전 과정에서 프라이버시가 보호됩니다. ARKit API의 최신 업데이트 내용을 살펴보고, 앱에서 손 추적과 씬 지오메트리를 활용하는 방법을 보여드립니다.
리소스
관련 비디오
WWDC23
-
다운로드
♪ 감미로운 인스트루멘탈 힙합 ♪ ♪ 안녕하세요 저는 라이언입니다 저는 코너입니다 이번 세션에서 소개할 내용은 공간 컴퓨팅을 위한 ARKit입니다 ARKit가 새로운 플랫폼에서 수행하는 중대한 역할과 다음 세대의 앱을 빌드할 때 이를 어떻게 활용하면 될지를 말씀드리겠습니다 ARKit는 정교한 컴퓨터 비전 알고리즘으로 주변 세계와 여러분의 움직임에 대한 이해를 구축합니다 iOS 11에 처음 도입된 이 기술은 개발자들이 굉장한 증강 현실 경험을 만들 수단이었습니다 손안에서 사용할 수 있는 도구였죠 이 플랫폼에서 성숙한 ARKit는 완전한 시스템 서비스가 되었습니다 새로운 실시간 Foundation으로 처음부터 새로 빌드되었죠 ARKit는 전체 운영 체제에 깊이 짜여 들어가 윈도우와 하는 상호 작용부터 몰입형 게임 플레이까지 모든 걸 구현합니다 이 과정의 일환으로 저희는 API를 전면적으로 정비했습니다 새로운 디자인은 iOS에서 알게 된 것에 공간 컴퓨팅만의 요구 사항을 더한 결과물입니다 분명 여러분도 좋아하실 겁니다 ARKit는 강력한 기능을 다양하게 제공합니다 이런 기능을 결합해서 놀라운 일을 이룰 수 있죠 가상 콘텐츠를 탁자에 배치하는 예가 있겠네요 콘텐츠가 실재하는 것처럼 손을 뻗어 만지는 건 물론이고 콘텐츠와 현실의 상호 작용도 볼 수 있습니다 정말이지 마법 같은 경험이죠 이 새로운 플랫폼에서 ARKit로 해낼 수 있는 작업을 살짝 맛보셨으니 이야기할 주제를 설명하겠습니다 저희 API의 기본적인 개념과 빌딩 블록을 먼저 개괄할 겁니다 이어서 월드 추적을 파고들 텐데 이는 현실을 기준 삼아 가상 콘텐츠를 배치하는 데 필수입니다 다음으로는 씬 이해 기능을 살펴봅니다 유용한 주변 환경 정보를 제공하는 기능이죠 이후에는 최신 기능을 소개합니다 새로 추가된 손 추적이라는 흥미로운 기능을 활용하면 손을 기준으로 가상 콘텐츠를 배치하거나 다른 유형의 사용자 지정 상호 작용을 빌드할 수 있습니다 마지막으로는 처음 이야기로 돌아와서 이 기능을 실제로 적용하는 법을 살펴보겠습니다 조금 전에 보여 드린 영상 속 코드를 검토할게요 그럼 시작해 봅시다 새로운 API는 면밀하게 만들어졌습니다 신형 Swift와 고전적인 C 언어라는 강력한 두 가지 종류로요 모든 ARKit 기능은 이제 따로 제공됩니다 개발자에게 최대한 유연성을 부여해서 경험을 빌드하는 데 필요한 기능을 고를 수 있게 했습니다 ARKit 데이터 접근은 프라이버시를 최우선으로 해서 설계되었습니다 프라이버시 보호를 위한 안전장치를 확립하면서 개발자를 위해 단순성도 유지하죠 이 API는 기본적인 빌딩 블록 세 가지로 구성됩니다 세션과 데이터 공급자 그리고 앵커죠 앵커부터 시작해서 세션으로 거슬러 가겠습니다 앵커는 현실의 위치와 방향을 나타냅니다 모든 앵커는 고유 식별자와 트랜스폼을 지니죠 어떤 유형의 앵커는 추적도 가능합니다 추적 가능한 앵커가 추적되지 않는다면 그 앵커로 앵커링한 가상 콘텐츠를 숨겨야 합니다 데이터 공급자는 개별 ARKit 기능을 나타냅니다 데이터 공급자로는 앵커 변경 같은 데이터 업데이트를 폴링하거나 관측할 수 있습니다 데이터 공급자 유형에 따라 제공하는 데이터 유형도 달라지죠 세션은 ARKit 기능이 결합된 집합을 나타냅니다 특정 경험을 위해 같이 사용하면 좋죠 세션을 실행하려면 데이터 공급자 집합을 제공합니다 세션이 실행되면 데이터 공급자가 데이터를 받기 시작하죠 업데이트는 데이터 유형에 따라 다른 주파수에서 비동기적으로 들어옵니다 이제 프라이버시로 넘어가서 앱이 ARKit 데이터에 접근하는 방식을 살펴보죠 프라이버시는 기본 인권입니다 저희 핵심 가치 중 하나이기도 하고요 ARKit 아키텍처와 API는 프라이버시 보호를 위해 세심하게 설계되었습니다 ARKit가 여러분 주변 세계에 대해 이해를 구축할 수 있게 이 기기에는 카메라와 다른 유형의 센서가 많습니다 카메라 프레임처럼 이런 센서로 얻은 데이터는 클라이언트 공간으로 절대 전송되지 않습니다 센서의 데이터는 대신 ARKit 데몬으로 전송되어 저희 알고리즘으로 안전하게 처리됩니다 이 알고리즘으로 생성된 결과 데이터는 신중하게 큐레이션된 후 여러분의 앱처럼 데이터를 요청하는 클라이언트로 전달됩니다 ARKit에 접근하기 위한 선결 조건이 몇 가지 있는데요 우선 여러분의 앱이 Full Space에 진입해야 합니다 Shared Space에 있는 앱으로는 ARKit가 데이터를 전송하지 않죠 둘째, 일부 유형의 ARKit 데이터는 접근 권한을 요구합니다 권한이 부여되지 않으면 그 유형의 데이터는 여러분의 앱에 전송되지 않죠 ARKit는 이 작업이 수월해지도록 권한을 편리하게 처리하는 권한 설정 API를 제공합니다 접근하려는 유형의 데이터에는 세션으로 권한 설정을 요청할 수 있습니다 이 작업을 하지 않으면 필요할 경우 세션을 실행할 때 ARKit가 자동으로 권한 요청을 띄웁니다 여기서는 손 추적 데이터에 접근을 요청하겠습니다 단일 요청에서 함께 필요한 모든 권한 설정 타입을 배치로 만들 수 있는데요 권한 설정 결과가 나오면 이를 반복하면서 각 권한 설정 타입의 상태를 확인합니다 권한이 부여되었다면 상태는 allowed가 됩니다 사용자가 접근을 거부한 데이터를 제공하는 데이터 공급자로 세션을 실행하려 하면 세션 실패라는 결과가 나오죠 이제 ARKit가 이 플랫폼에서 지원하는 각 기능을 더 자세히 살펴봅시다 우선 월드 추적을 보죠 월드 추적으로는 현실에 가상 콘텐츠를 앵커링할 수 있습니다 ARKit가 여섯 단계의 자유도로 기기 움직임을 추적하고 각 앵커를 업데이트하면 콘텐츠는 주변 환경 기준으로 동일한 위치에 계속 남아 있습니다 월드 추적에 쓰이는 DataProvider는 WorldTrackingProvider입니다 여기에는 몇 가지 중요한 기능이 있습니다 WorldAnchor를 추가할 수 있는데 이게 ARKit로 업데이트되면 기기가 움직이는 동안에도 주변 환경 기준으로 계속 고정되어 있습니다 WorldAnchor는 가상 콘텐츠 배치에 필수적인 도구입니다 추가한 WorldAnchor는 앱이 실행되고 재부팅될 때 모두 자동으로 지속됩니다 이 동작이 여러분이 빌드하는 경험에 적절하지 않으면 앵커는 사용이 끝나서 지속할 필요가 없어지는 대로 삭제해 주면 됩니다 어떤 경우에는 지속성을 쓸 수 없다는 걸 꼭 알아 두세요 WorldTrackingProvider로는 앱의 원점 기준으로 기기 자세를 구할 수도 있습니다 Metal로 렌더링을 직접 진행한다면 이는 꼭 필요하죠 우선 WorldAnchor가 무엇이며 왜 사용해야 하는지를 더 자세히 살펴봅시다 WorldAnchor는 TrackableAnchor로 트랜스폼을 취하는 이니셜라이저가 있습니다 트랜스폼이란 앱의 원점 기준으로 앵커를 배치할 위치와 방향이죠 앵커링되지 않은 가상 콘텐츠와 앵커링된 콘텐츠의 차이를 시각화할 예제를 준비했습니다 정육면체가 두 개 있습니다 왼쪽의 파란 정육면체는 WorldAnchor로 업데이트되지 않고 오른쪽의 빨간 정육면체는 WorldAnchor로 업데이트됩니다 두 정육면체는 모두 앱이 실행되었을 때 앱의 원점 기준으로 배치되었습니다 기기가 움직여도 두 정육면체는 배치된 위치에 남아 있죠 크라운을 눌러서 잡고 있으면 앱의 중심을 조정할 수 있습니다 중심을 조정할 때는 여러분의 현재 위치로 앱의 원점이 이동할 겁니다 앵커링되지 않은 파란 정육면체는 앱의 원점 기준으로 상대 배치를 유지하며 위치가 조정됩니다 반면 앵커링된 빨간 정육면체는 현실을 기준으로 고정된 채 남아 있습니다 WorldAnchor 지속성이 어떻게 작동하는지를 살펴보죠 기기 움직임에 따라서 ARKit는 주변 환경의 맵을 빌드합니다 추가된 WorldAnchor는 저희가 맵에 인서트해서 자동으로 지속해 드리는데요 WorldAnchor 식별자와 트랜스폼만 지속됩니다 가상 콘텐츠를 비롯한 다른 데이터는 포함되지 않죠 가상 콘텐츠에 연동한 WorldAnchor 식별자 매핑을 유지할지는 여러분의 결정입니다 맵은 위치 기반이므로 여러분이 기기를 집에서 사무실로 옮기는 등 새로운 위치로 이동시키면 집의 맵은 언로드되고 다른 맵이 사무실 위치로 인식됩니다 이 새로운 위치에 추가한 앵커는 모두 그 맵에 들어갑니다 하루를 마치고 사무실을 떠나 집으로 갈 때면 ARKit가 사무실에서 계속 빌드한 그 맵은 거기에 배치된 다른 앵커와 함께 언로드됩니다 재차 말씀드리는데 이 맵은 앵커와 함께 자동으로 지속되고 있습니다 집에 돌아오면 ARKit가 위치 변경을 인식하죠 저희는 이 위치의 기존 맵을 확인해 위치 재인식 과정을 시작합니다 그 맵을 찾으면 그걸로 위치가 인식되어서 앞서 집에 추가해 둔 모든 앵커가 다시 추적됩니다 기기 자세로 넘어가죠 WorldAnchor를 추가하고 삭제하는 것 외에도 WorldTrackingProvider를 사용하면 기기의 자세를 알 수 있습니다 자세란 앱의 원점에 대한 기기의 위치와 방향이죠 Metal과 CompositorServices로 완전 몰입형 공간에서 직접 렌더링한다면 자세 쿼리는 필수입니다 이 쿼리는 상대적으로 비싼데요 콘텐츠 배치처럼 유형이 다른 앱 로직에 기기 자세를 쿼리할 때는 주의해 주세요 단순화한 렌더링 예제를 빠르게 살펴보며 어떻게 ARKit에서 CompositorServices로 기기 자세를 줄 수 있는지를 보여 드리겠습니다 세션을 유지할 Renderer 구조체와 월드 추적 공급자와 최신 자세가 있는데요 Renderer를 초기화할 때는 먼저 세션을 생성합니다 이어서 각 프레임을 렌더링할 때 기기 자세 쿼리에 사용할 월드 추적 공급자를 생성하죠 이제 넘어가서 필요한 데이터 공급자로 세션을 실행할 수 있습니다 이 경우에는 월드 추적 공급자만 사용하겠습니다 render 함수에 할당되지 않게 자세도 만들어 줍니다 이제 render 함수로 넘어갑시다 프레임률로 호출해 볼 텐데요 CompositorServices의 drawable로 타깃 렌더링 시간을 페칭합니다 다음으로는 타깃 렌더링 시간으로 기기의 자세를 쿼리합니다 성공적으로 수행되면 앱의 원점에 대한 자세의 트랜스폼을 추출할 수 있습니다 콘텐츠 렌더링에 사용할 트랜스폼이죠 마지막으로는 합성할 프레임을 제출하기 전에 drawable의 자세를 설정해서 해당 프레임에 콘텐츠를 렌더링할 때 어떤 자세를 사용할지를 컴포지터에 알립니다 직접 하는 렌더링 작업을 더 알고 싶다면 Metal을 사용한 몰입형 앱 제작을 집중적으로 다루는 세션을 시청해 주세요 추가로 공간 컴퓨팅의 성능과 관련해서 고려할 사항을 알 수 있는 훌륭한 세션도 있으니 같이 확인해 보시기를 추천합니다 다음으로는 씬 이해를 살펴봅시다 씬 이해는 여러분의 주변 환경을 여러 방식으로 알려 주는 기능의 범주입니다 평면 감지부터 봅시다 평면 감지는 ARKit가 현실에서 감지한 수평과 수직 표면의 앵커를 제공합니다 평면 감지에 쓰이는 DataProvider 타입은 PlaneDetectionProvider입니다 주변 환경에서 감지된 평면은 PlaneAnchor의 형태로 제공됩니다 PlaneAnchor는 콘텐츠를 원활히 배치하는 데 쓰이죠 가상 객체를 탁자에 배치할 때처럼요 평면은 물리 시뮬레이션에도 쓸 수 있습니다 이때는 바닥이나 벽 같은 평평한 기본 지오메트리면 충분합니다 각 PlaneAnchor에는 정렬이 들어 있습니다 이는 수평 혹은 수직 여부 및 평면의 지오메트리와 시맨틱 분류입니다 평면은 바닥이나 탁자처럼 다양한 유형의 표면으로 분류될 수 있습니다 특정 표면을 식별하는 게 불가능하다면 제공된 분류는 상황에 따라서 unknown이나 undetermined 혹은 not available로 표시됩니다 이제 씬 지오메트리로 넘어가 봅시다 씬 지오메트리는 현실의 모양을 추정하는 다각형 메시가 있는 앵커를 제공합니다 씬 지오메트리가 쓰는 DataProvider 타입은 SceneReconstructionProvider죠 ARKit가 주변 세계를 스캔하면 주변 환경은 세분화 메시로 재구성되어 MeshAnchor의 형태로 여러분에게 제공됩니다 PlaneAnchor처럼 MeshAnchor 역시 원활한 콘텐츠 배치에 사용될 수 있습니다 가상 콘텐츠가 단순한 평면이 아닌 객체와 상호 작용해야 하는 상황에서 물리 시뮬레이션의 충실도를 높일 수도 있는데요 각 MeshAnchor는 메시 지오메트리를 포함합니다 이 지오메트리에는 꼭짓점, 법선, 면과 면당 하나씩인 시맨틱 분류가 있습니다 메시 면은 다양한 유형의 객체로 분류될 수 있죠 특정 객체를 식별할 수 없다면 제공되는 분류는 none이 됩니다 마지막으로 이미지 추적을 살펴봅시다 이미지 추적으로는 현실의 2D 이미지를 감지할 수 있는데요 이미지 추적에 쓰이는 DataProvider 타입은 ImageTrackingProvider입니다 ImageTrackingProvider는 감지하려는 ReferenceImage의 집합으로 설정합니다 ReferenceImage를 생성할 방법은 몇 가지가 있습니다 프로젝트 에셋 카탈로그의 AR 리소스 그룹에서 로드하는 게 한 가지 방법이죠 아니면 CVPixelBuffer나 CGImage를 제공해 ReferenceImage를 직접 초기화할 수도 있습니다 이미지가 감지되면 ARKit는 ImageAnchor를 제공합니다 ImageAnchor는 콘텐츠를 정적으로 배치된 알려진 이미지에 배치할 때 쓸 수 있죠 예를 들어 영화 포스터 옆에 영화 정보를 보여 줄 수 있습니다 ImageAnchor는 TrackableAnchor로 스케일 팩터를 포함하는데요 이 팩터는 감지된 이미지의 크기가 여러분이 지정한 물리적 크기 및 앵커가 상응하는 ReferenceImage와 비교했을 때 어떤지를 나타냅니다 이제 새로운 기능인 손 추적을 소개하고 예제를 설명할 수 있게 코너에게 자리를 넘기겠습니다 반갑습니다 함께 살펴볼 손 추적은 ARKit에 추가된 최신 기능입니다 손 추적이 제공하는 앵커에는 손 각각의 스켈레톤 데이터가 들어 있습니다 손 추적에 쓰이는 DataProvider 타입은 HandTrackingProvider입니다 감지된 손은 HandAnchor 형태로 제공되는데요 HandAnchor는 TrackableAnchor입니다 HandAnchor에는 스켈레톤과 카이랄성이 있죠 왼손인지 오른손인지는 카이랄성으로 알 수 있습니다 HandAnchor의 트랜스폼은 앱 원점에 대한 손목의 트랜스폼입니다 스켈레톤을 구성하는 건 이름으로 쿼리될 수 있는 관절이죠 관절에 포함되는 건 부모 관절과 이름 및 부모 관절에 대한 localTransform과 뿌리 관절에 대한 rootTransform입니다 끝으로 각 관절은 Bool을 포함하는데 이는 관절의 추적 여부를 나타냅니다 손 스켈레톤에서 가능한 관절을 모두 열거했는데요 관절 계층의 부분집합을 찬찬히 살펴봅시다 손의 뿌리 관절은 손목입니다 각 손가락의 첫 번째 관절은 손목을 부모로 삼습니다 예를 들어 0은 1의 부모가 되죠 이전 손가락 관절이 다음 관절의 부모가 되는 겁니다 1이 2의 부모가 되고 이런 관계가 이어지죠 손 기준으로 콘텐츠를 배치하거나 사용자 지정 제스처를 감지할 때 HandAnchor를 사용할 수 있습니다 HandAnchor는 두 가지 방법으로 받을 수 있는데 업데이트를 폴링하거나 사용이 가능할 때 앵커를 비동기적으로 받는 방법입니다 비동기 업데이트는 잠시 후 Swift 예제로 살펴볼 테니 앞에서 봤던 Renderer에 손 앵커 폴링을 추가해 봅시다 업데이트된 구조체 정의입니다 hand tracking provider를 추가했고 왼손과 오른손 앵커도 넣었죠 업데이트된 init 함수에서 hand tracking provider를 새로 만들고 실행하는 공급자 목록에 이를 추가한 다음 폴링에 필요한 왼손 앵커와 오른손 앵커를 만들었습니다 렌더 루프에 할당되지 않도록 이를 미리 생성했다는 데 주목하세요 구조체의 업데이트와 초기화를 마쳤으니 get_latest_anchors를 render 함수에 호출할 수 있죠 공급자와 먼저 할당한 손 앵커를 패스합니다 사용 가능한 최신 데이터가 저희 앵커에 채워질 겁니다 최신 앵커가 채워지면 저희 경험에 이 데이터를 사용할 수 있습니다 정말 멋지죠 앞서 보여 드린 예제로 돌아갈 차례입니다 ARKit와 RealityKit의 기능을 결합해서 이 경험을 빌드했는데요 씬 지오메트리가 물리용 콜라이더와 제스처로 사용되었고 정육면체 엔티티와 직접 상호 작용 하는 데는 손 추적이 사용되었습니다 이 예제를 빌드한 과정을 함께 살펴봅시다 우선 앱의 구조체와 뷰 모델을 확인합니다 이어서 ARKit 세션을 초기화하고요 다음으로는 손끝 콜라이더와 씬 재구성 콜라이더를 추가합니다 끝으로 제스처가 있는 정육면체를 어떻게 추가하는지 살펴볼게요 바로 시작해 보죠 저희 앱 TimeForCube입니다 SwiftUI 앱과 씬 설정은 표준적인 편입니다 씬에는 ImmersiveSpace를 선언합니다 ARKit 데이터에 접근하려면 Full Space로 이동해야 하니 ImmersiveSpace는 필수죠 ImmersiveSpace에서는 저희 뷰 모델의 콘텐츠를 보여 줄 RealityView를 정의합니다 저희 앱의 로직 대부분은 뷰 모델에 있습니다 빠르게 살펴보죠 뷰 모델이 잡고 있는 것은 ARKit 세션과 저희가 사용할 데이터 공급자 및 저희가 생성하는 다른 엔티티가 모두 담길 콘텐츠 엔티티와 씬 및 손 콜라이더 맵입니다 뷰 모델은 저희가 앱에서 호출할 다양한 함수도 제공하죠 콘텍스트 속 각각을 앱에서 검토해 봅시다 처음으로 호출할 함수는 RealityView의 생성 클로저 내에서 contentEntity를 설정합니다 RealityView 콘텐츠에 이 엔티티를 추가해서 뷰 모델이 뷰 콘텐츠에 엔티티를 추가할 수 있게 하죠 setupContentEntity는 맵의 모든 손가락 엔티티를 contentEntity에 자식으로 추가한 뒤 이를 반환합니다 훌륭하군요 세션 초기화로 넘어갑시다 세션 초기화는 세 task 중 하나로 실행됩니다 첫 번째 task는 runSession 함수를 호출합니다 이 함수는 두 공급자와 함께 단순히 세션을 실행하죠 세션이 실행되고 있으면 앵커 업데이트를 받기 시작합니다 정육면체와 상호 작용 할 때 사용할 손끝 콜라이더를 생성하고 업데이트해 봅시다 이건 손 업데이트를 처리하는 task입니다 이 함수는 공급자 앵커 업데이트의 비동기 시퀀스에서 반복됩니다 손 앵커가 추적되는지 확인하고 검지 손끝 관절을 얻어서 관절 자체도 추적되는지를 점검합니다 이어서 검지 끝의 트랜스폼을 앱 원점 기준으로 연산하죠 마지막으로 업데이트와 트랜스폼 설정이 필요한 손가락 엔티티가 무엇인지 찾습니다
손가락 엔티티 맵으로 돌아가 보죠 ModelEntity로 확장해서 손마다 엔티티를 생성하는데요 충돌 모양이 있는 5mm 구가 이 확장으로 만들어집니다 키네마틱 물리 보디 컴포넌트를 추가하고 불투명도 컴포넌트를 추가해서 이 엔티티를 숨깁니다 저희 사용 예에서는 숨기지만 손끝 엔티티를 시각화해서 전부 기대대로 작동하는지 확인하는 것도 좋겠죠 불투명도를 잠시 1로 설정하고 엔티티가 제자리에 있는지 점검합시다 좋네요 손끝에 바로 구가 보입니다 저희 손이 구의 일부를 가린다는 데 주목하세요 손 어클루전이라는 이름의 이 시스템 기능으로 가상 콘텐츠 위에 사람의 손이 보이게 됩니다 기본적으로 활성화되지만 조금 더 선명한 구를 보고 싶다면 손 어클루전 표시 여부를 설정할 수 있습니다 upperLimbVisibility라는 setter를 씬에 사용하면 되죠 팔다리 표시 여부를 hidden으로 설정하면 손 위치와 무관하게 구의 전체 형태가 보일 겁니다 저희 예제에서는 팔 표시 여부를 기본값으로 두고 불투명도를 다시 0으로 설정하겠습니다 깔끔하네요 이제 씬 콜라이더를 추가합니다 물리에도 쓰고 제스처 타깃으로도 사용하죠 저희 모델에 함수를 호출하는 task입니다 공급자 앵커 업데이트의 비동기 시퀀스에서 작업을 반복하고 MeshAnchor에서 ShapeResource를 생성한 다음 앵커 업데이트 이벤트에서 전환하는 겁니다 앵커를 추가하려면 새 엔티티를 만들어서 그 트랜스폼을 설정하고 충돌과 물리 보디 컴포넌트를 추가한 뒤에 입력 타깃 컴포넌트를 추가하면 됩니다 그러면 이 콜라이더가 제스처 타깃이 되죠 마지막으로 맵에 새 엔티티를 추가합니다 이건 콘텐츠 엔티티의 자식입니다 엔티티를 업데이트하려면 맵에서 회수한 다음 트랜스폼과 충돌 컴포넌트 모양을 업데이트합니다 삭제하려면 해당하는 엔티티를 부모와 맵에서 삭제하면 되죠 손 콜라이더와 씬 콜라이더를 갖췄으니 제스처로 정육면체를 추가할 수 있습니다 아무 엔티티나 타깃팅해서 SpatialTapGesture를 추가하면 RealityView 콘텐츠에서 엔티티가 탭되었는지를 알 수 있습니다 탭이 완료되면 글로벌 좌표에서 씬 좌표로 변환하는 3D 위치를 받습니다 이 위치를 시각화해 봅시다 탭 위치에 구를 추가하면 이렇게 보입니다 이제 이 위치 기준으로 정육면체를 추가하라고 뷰 모델에 전달합니다 정육면체를 추가하려면 먼저 배치 위치를 계산합니다 탭한 위치에서 20cm 위죠 이어서 정육면체를 생성하고 계산한 배치 위치로 그 위치를 설정합니다 InputTargetComponent를 추가해서 엔티티가 어떤 유형의 제스처에 반응할지를 설정합니다 사용 예의 정육면체에는 간접 입력만 허용하겠습니다 직접적인 상호 작용은 손끝 콜라이더가 제공할 테니까요 사용자 지정 매개변수로 PhysicsBodyComponent를 추가해서 물리 상호 작용을 조금 더 개선합니다 정육면체를 콘텐츠 엔티티에 추가하면 끝입니다 드디어 정육면체의 시간이군요 마지막으로 예제를 처음부터 이어서 살펴봅시다 씬 콜라이더나 정육면체를 탭할 때마다 탭 위치 위에 새 정육면체가 추가됩니다 물리 시스템에 의해 정육면체가 씬 콜라이더 위로 떨어지고 손 콜라이더로 정육면체와 상호 작용이 가능합니다 RealityKit를 더 알아보려면 공간 컴퓨팅을 위한 RealityKit 사용을 소개하는 세션을 확인해 보세요 iOS에서 이 플랫폼으로 가져오고 싶은 ARKit 경험이 이미 있다면 이 주제만을 다루는 세션에서 더 많은 내용을 안내받으실 수 있습니다 여러분이 ARKit의 새 버전을 사용하시게 되어 저희 팀 모두가 아주 들떠 있습니다 흥미로운 새 플랫폼에 여러분이 만들어 낼 획기적인 앱들이 정말 기대됩니다 시청해 주셔서 감사합니다 ♪
-
-
5:20 - Authorisation API
// Privacy // Authorization session = ARKitSession() Task { let authorizationResult = await session.requestAuthorization(for: [.handTracking]) for (authorizationType, authorizationStatus) in authorizationResult { print("Authorization status for \(authorizationType): \(authorizationStatus)") switch authorizationStatus { case .allowed: // All good! break case .denied: // Need to handle this. break ... } } }
-
10:20 - World Tracking Device Pose Render Struct
// World tracking // Device pose #include <ARKit/ARKit.h> #include <CompositorServices/CompositorServices.h> struct Renderer { ar_session_t session; ar_world_tracking_provider_t world_tracking; ar_pose_t pose; ... }; void renderer_init(struct Renderer *renderer) { renderer->session = ar_session_create(); ar_world_tracking_configuration_t config = ar_world_tracking_configuration_create(); renderer->world_tracking = ar_world_tracking_provider_create(config); ar_data_providers_t providers = ar_data_providers_create(); ar_data_providers_add_data_provider(providers, renderer->world_tracking); ar_session_run(renderer->session, providers); renderer->pose = ar_pose_create(); ... }
-
10:21 - World Tracking Device Pose Render function
// World tracking // Device pose void render(struct Renderer *renderer, cp_layer_t layer, cp_frame_t frame_encoder, cp_drawable_t drawable) { const cp_frame_timing_t timing_info = cp_drawable_get_frame_timing(drawable); const cp_time_t presentation_time = cp_frame_timing_get_presentation_time(timing_info); const CFTimeInterval target_render_time = cp_time_to_cf_time_interval(presentation_time); simd_float4x4 pose = matrix_identity_float4x4; const ar_pose_status_t status = ar_world_tracking_provider_query_pose_at_timestamp(renderer->world_tracking, target_render_time, renderer->pose); if (status == ar_pose_status_success) { pose = ar_pose_get_origin_from_device_transform(renderer->pose); } ... cp_drawable_set_ar_pose(drawable, renderer->pose); ... }
-
16:00 - Hand tracking joints
/ Hand tracking @available(xrOS 1.0, *) public struct Skeleton : @unchecked Sendable, CustomStringConvertible { public func joint(named: SkeletonDefinition.JointName) -> Skeleton.Joint public struct Joint : CustomStringConvertible, @unchecked Sendable { public var parentJoint: Skeleton.Joint? { get } public var name: String { get } public var localTransform: simd_float4x4 { get } public var rootTransform: simd_float4x4 { get } public var isTracked: Bool { get } } }
-
17:00 - Hand tracking with Render struct
// Hand tracking // Polling for hands struct Renderer { ar_hand_tracking_provider_t hand_tracking; struct { ar_hand_anchor_t left; ar_hand_anchor_t right; } hands; ... }; void renderer_init(struct Renderer *renderer) { ... ar_hand_tracking_configuration_t hand_config = ar_hand_tracking_configuration_create(); renderer->hand_tracking = ar_hand_tracking_provider_create(hand_config); ar_data_providers_t providers = ar_data_providers_create(); ar_data_providers_add_data_provider(providers, renderer->world_tracking); ar_data_providers_add_data_provider(providers, renderer->hand_tracking); ar_session_run(renderer->session, providers); renderer->hands.left = ar_hand_anchor_create(); renderer->hands.right = ar_hand_anchor_create(); ... }
-
17:25 - hand tracking call in render function
// Hand tracking // Polling for hands void render(struct Renderer *renderer, ... ) { ... ar_hand_tracking_provider_get_latest_anchors(renderer->hand_tracking, renderer->hands.left, renderer->hands.right); if (ar_trackable_anchor_is_tracked(renderer->hands.left)) { const simd_float4x4 origin_from_wrist = ar_anchor_get_origin_from_anchor_transform(renderer->hands.left); ... } ... }
-
18:00 - Demo app TimeForCube
// App @main struct TimeForCube: App { @StateObject var model = TimeForCubeViewModel() var body: some SwiftUI.Scene { ImmersiveSpace { RealityView { content in content.add(model.setupContentEntity()) } .task { await model.runSession() } .task { await model.processHandUpdates() } .task { await model.processReconstructionUpdates() } .gesture(SpatialTapGesture().targetedToAnyEntity().onEnded({ value in let location3D = value.convert(value.location3D, from: .global, to: .scene) model.addCube(tapLocation: location3D) })) } } }
-
18:50 - Demo app View Model
// View model @MainActor class TimeForCubeViewModel: ObservableObject { private let session = ARKitSession() private let handTracking = HandTrackingProvider() private let sceneReconstruction = SceneReconstructionProvider() private var contentEntity = Entity() private var meshEntities = [UUID: ModelEntity]() private let fingerEntities: [HandAnchor.Chirality: ModelEntity] = [ .left: .createFingertip(), .right: .createFingertip() ] func setupContentEntity() { ... } func runSession() async { ... } func processHandUpdates() async { ... } func processReconstructionUpdates() async { ... } func addCube(tapLocation: SIMD3<Float>) { ... } }
-
20:00 - function HandTrackingProvider
class TimeForCubeViewModel: ObservableObject { ... private let fingerEntities: [HandAnchor.Chirality: ModelEntity] = [ .left: .createFingertip(), .right: .createFingertip() ] ... func processHandUpdates() async { for await update in handTracking.anchorUpdates { let handAnchor = update.anchor guard handAnchor.isTracked else { continue } let fingertip = handAnchor.skeleton.joint(named: .handIndexFingerTip) guard fingertip.isTracked else { continue } let originFromWrist = handAnchor.transform let wristFromIndex = fingertip.rootTransform let originFromIndex = originFromWrist * wristFromIndex fingerEntities[handAnchor.chirality]?.setTransformMatrix(originFromIndex, relativeTo: nil) }
-
21:20 - function SceneReconstruction
func processReconstructionUpdates() async { for await update in sceneReconstruction.anchorUpdates { let meshAnchor = update.anchor guard let shape = try? await ShapeResource.generateStaticMesh(from: meshAnchor) else { continue } switch update.event { case .added: let entity = ModelEntity() entity.transform = Transform(matrix: meshAnchor.transform) entity.collision = CollisionComponent(shapes: [shape], isStatic: true) entity.physicsBody = PhysicsBodyComponent() entity.components.set(InputTargetComponent()) meshEntities[meshAnchor.id] = entity contentEntity.addChild(entity) case .updated: guard let entity = meshEntities[meshAnchor.id] else { fatalError("...") } entity.transform = Transform(matrix: meshAnchor.transform) entity.collision?.shapes = [shape] case .removed: meshEntities[meshAnchor.id]?.removeFromParent() meshEntities.removeValue(forKey: meshAnchor.id) @unknown default: fatalError("Unsupported anchor event") } } }
-
22:20 - add cube at tap location
class TimeForCubeViewModel: ObservableObject { func addCube(tapLocation: SIMD3<Float>) { let placementLocation = tapLocation + SIMD3<Float>(0, 0.2, 0) let entity = ModelEntity( mesh: .generateBox(size: 0.1, cornerRadius: 0.0), materials: [SimpleMaterial(color: .systemPink, isMetallic: false)], collisionShape: .generateBox(size: SIMD3<Float>(repeating: 0.1)), mass: 1.0) entity.setPosition(placementLocation, relativeTo: nil) entity.components.set(InputTargetComponent(allowedInputTypes: .indirect)) let material = PhysicsMaterialResource.generate(friction: 0.8, restitution: 0.0) entity.components.set(PhysicsBodyComponent(shapes: entity.collision!.shapes, mass: 1.0, material: material, mode: .dynamic)) contentEntity.addChild(entity) } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.