스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
향상된 RoomPlan 살펴보기
RoomPlan의 흥미로운 업데이트를 살펴보세요. MultiRoom 지원이 추가되고 방 표현이 개선되었습니다. 보다 자세히 영역을 스캔하고, 여러 방을 캡처하고, 개별 스캔을 하나의 큰 구조로 병합하는 방법을 알려 드립니다. 또한 RoomPlan 결과물을 기존의 3D 모델 라이브러리에 결합하는 워크플로와 모범 사례를 공유합니다.
리소스
관련 비디오
WWDC23
WWDC22
-
다운로드
♪ ♪
안녕하세요, 저는 알렉스입니다 비디오 엔지니어링 팀 소속이죠 오늘 제 동료 앤트완과 함께 알려 드릴 내용은 RoomPlan의 새 기능입니다 RoomPlan은 ARKit 기반의 정교한 기계 학습 알고리즘으로 벽면과 창문 문, 개구부를 비롯해 방을 정의하는 물체를 감지합니다 RoomCaptureView API를 사용하면 스캐닝 경험을 앱에 바로 통합할 수 있죠 스캐닝이 끝나면 앱에서 생성된 3D 모델을 표시하고 USDZ 파일로 내보낼 수 있습니다 이 세션에서는 RoomPlan의 새로운 기능을 알아보겠습니다 사용자 지정 ARSession을 사용하여 RoomPlan을 ARKit와 결합하는 새로운 방법부터 시작하죠 그다음 MultiRoom 지원을 알아보겠습니다 새로운 MultiRoom API를 통해 각각의 방 스캔을 하나의 큰 구조로 병합할 수 있죠 그러고 나서 RoomCaptureView의 VoiceOver 지원을 소개하겠습니다 마지막으로 RoomPlan 표현의 개선 사항을 알아보겠습니다 새로운 워크플로를 위해 강화한 내보내기 기능도요
그럼 사용자 지정 ARSession 지원부터 시작하죠
아까 말씀드렸듯 RoomPlan은 스캔 시에 ARKit의 정보를 활용하여 벽과 창문, 문 개구부, 기타 물체를 감지합니다 이를 위해 RoomCaptureSession이 기본 ARSession과 함께 실행되죠 iOS 17부터는 RoomPlan이 ARWorldTrackingConfiguration으로 사용자 지정 ARSession을 사용할 수 있습니다 이를 통해 RoomPlan과 ARKit의 사용을 동일한 워크플로 내에서 결합하는 새 방법이 생겼죠 예시를 살펴봅시다 사용자 지정 ARSession으로 RoomPlan을 쓰는 한 가지 방법은 RoomPlan 결과를 ARKit scene 지오메트리 및 평면 감지와 결합해 가상 콘텐츠와 현실 세계의 지오메트리 사이에 몰입감 있는 상호 작용을 구현하는 것입니다 또한 ARKit의 고품질 이미지 캡처를 통해 공간의 사진 표현을 수집해서 RoomPlan으로 부동산 목록을 더 풍부하게 만들 수도 있죠 기존 AR 경험의 일부로 RoomPlan을 사용하는 경우 기존 ARAnchors를 방해하지 않고 RoomPlan의 결과를 결합할 수 있습니다 이는 사용자 지정 ARSession 사용 예의 일부에 불과합니다 몇 가지 코드를 통해 RoomPlan에 사용자 지정 ARSession을 전달하는 법을 알아보죠 다음은 기존 RoomPlan의 init 및 stop 함수입니다 다음은 사용자 지정 ARSession을 init 함수에 전달하는 방법이죠 ARWorldTrackingConfiguration을 쓰는 사용자 지정 ARSession은 RoomCaptureSession 내에서 인식됩니다 또한 stop 함수에 포함된 새로운 옵션으로 기본 ARSession의 중단 여부를 결정할 수 있죠 Bool을 false로 지정하면 RoomCaptureSession이 멈춘 후에도 ARSession을 계속 실행할 수 있습니다 다음 섹션에서는 ARSession을 사용해서 여러 스캔을 하나의 큰 구조로 병합하는 등 새로운 워크플로를 만드는 법을 알아보죠 MultiRoom 지원을 살펴보겠습니다 MultiRoom 지원이란 뭘까요? 기존 RoomPlan에서는 단일 스캔으로 방 한 개의 3D 모델을 얻을 수 있었습니다 한 집의 여러 방을 스캔했다고 가정해 보죠 식당과 주방 거실, 복도, 침실 등요 여러 스캔을 병합하는 데에는 몇 가지 어려움이 따르죠 첫째, 스캔이 각각 독립적인 좌표계를 지닙니다 월드 좌표의 원점과 방향이 방마다 다르다는 거죠 둘째, 수동으로 스캔을 병합하더라도 벽이 중복되어 버립니다 물체도 중복될 수 있고요
먼저 서로 다른 좌표계 문제를 해결해 보겠습니다 이때 필요한 건 모든 스캔을 동일한 좌표계에서 수행하는 것이죠 여러 방을 스캔할 때 추천하는 접근 방식은 두 가지입니다 첫째, 지속적 ARSession 사용하기 둘째, ARSession 재현지화 사용하기죠 지속적 ARSession 사용법부터 살펴보겠습니다 기존 RoomPlan에서는 RoomCaptureSession이 멈추면 ARSession도 일시 중지됩니다 스캔마다 다른 좌표계를 지니죠 새 API에서는 stop 함수에 새로운 인수가 추가되어 pauseARSession을 false로 설정할 수 있습니다 이렇게 하면 ARSession이 다음 스캔이나 그다음 스캔까지 계속 실행됩니다 우리가 ARSession을 다시 중지할 때까지 계속되죠
이 접근 방식을 쓰면 동일한 ARSession을 여러 스캔에 실행할 수 있습니다 그러면 모든 스캔이 공통된 월드 좌표계를 지니게 되죠 코드에서 이 작업을 살펴보겠습니다 다음 예시는 지속적 ARSession으로 RoomCaptureSession을 실행합니다 RoomCaptureSession.run으로 첫 스캔을 시작합니다 여기가 중요한 부분인데요 RoomCaptureSession.stop의 새 API에서 pauseARSession을 false로 설정합니다 그러면 ARSession이 다음 스캔에서도 계속 실행되죠 그런 다음 동일한 roomCaptureSession 인스턴스로 두 번째 스캔을 진행합니다 그러고는 두 번째 스캔을 중단합니다 그 결과, 첫 번째와 두 번째 스캔 결과가 동일한 좌표계를 지니게 되죠 한 좌표 공간에서 개별적으로 방을 스캔하는 또 다른 접근 방식은 ARSession 재현지화입니다 이 방식은 각기 다른 시간에 방을 스캔하는 경우 가장 적합합니다 하루나 일주일 뒤에 같은 위치를 재방문할 때죠 이 작업 방식을 살펴봅시다 단일 스캔으로 얻은 단일 방의 3D 모델입니다 RoomCaptureSession을 멈추고 ARSession을 일시 중지하면 다음 스캔의 재현지화를 위해 디스크에 ARWorldMap을 저장해야 합니다 ARSession이 중단된 상태에서 기존 스캔을 계속하려면 중단된 ARSession에서 ARWorldMap을 로드하여 스캔을 재개할 수 있죠
ARWorldMap을 쓰면 이전 스캔의 환경에 대해 재현지화할 수 있고 연이은 스캔이 모두 동일한 좌표계를 공유하게 됩니다 재현지화를 사용한 스캐닝 워크플로의 샘플 코드를 살펴보죠 먼저 첫 번째 스캔을 실행합니다 그런 다음 스캔을 멈추고 ARSession을 일시 중지합니다 재현지화가 작동하려면 ARSession이 일시 정지된 채로 ARWorldMap을 저장해야 합니다
두 번째 스캔을 실행하기 전에 이전 ARWorldMap을 복원해야 하는데요 우선 ARWorldMap을 로드합니다 로드한 ARWorldMap을 ARWorldTrackingConfiguration .initialWorldMap에 할당합니다 그런 다음 ARSession을 실행해 재현지화를 수행합니다 재현지화가 완료되면 이전 ARSession이 로드되죠 현 월드 좌표가 이전 월드 좌표와 일치합니다 그러면 두 번째 스캔을 실행합니다 마지막으로 두 번째 스캔을 종료합니다 이러한 과정을 거치면 첫 번째와 두 번째 스캔 결과가 동일한 3D 좌표계를 갖죠 여러 스캔을 동일한 3D 좌표계에 연결하는 두 가지 접근 방식을 알아봤습니다 이번에는 새로운 MultiRoom API를 사용해 스캔을 하나의 결합된 구조로 합치는 방법을 알아보죠 각 스캔에 RoomBuilder API를 실행하면 개별적인 CapturedRoom을 생성할 수 있습니다 아까 보여드렸듯 지속적 ARSession과 ARSession 재현지화를 쓰면 모든 CapturedRoom이 같은 3D 월드 공간에 있게 되죠 다음 RoomBuilder 출력에는 세 가지 CapturedRoom이 있습니다 새로 도입된 병합 API인 StructureBuilder로 이를 하나의 큰 구조로 병합할 수 있죠 바로 CapturedStructure입니다 이번에는 StructureBuilder API의 샘플 코드를 살펴보죠 StructureBuilder API를 사용해 여러 스캔을 병합하는 방법입니다 먼저 구성 옵션을 지닌 StructureBuilder 인스턴스를 생성합니다 그리고 Array를 생성해 이전에 스캔한 여러 CapturedRoom을 로드합니다 그런 다음 StructureBuilder API를 호출하여 병합 결과물인 capturedStructure를 얻습니다 이렇게 하면 capturedStructure를 USDZ 파일로 내보낼 수 있죠 다음은 CapturedStructure의 정의입니다 우선 방이라는 프로퍼티가 있습니다 CapturedRoom 인스턴스의 어레이죠 다음은 병합된 벽과 문 창문, 개구부, 물체의 프로퍼티입니다 마지막은 USDZ 파일로 내보내는 함수입니다
MultiRoom을 실제로 사용해 보죠 저희가 제공하는 샘플 앱에서 StructureBuilder API로 여러 스캔을 병합하고 USDZ 파일로 내보낼 수 있습니다 USDZ 파일은 iOS와 macOS에서 미리보기 할 수 있죠 Blender 등의 디지털 콘텐츠 제작 툴로 USDZ 파일을 로드해서 섬세히 작업할 수도 있습니다
3D 모델을 다듬고 나면 더욱 멋진 결과물을 얻을 수 있죠
마지막으로 최상의 MultiRoom 경험을 위한 MultiRoom 지원 관련 고려 사항을 알아보겠습니다 MultiRoom은 1층짜리 집에 가장 적합합니다 침실 1-4개와 거실 주방, 식당을 포함하는 일반적인 형태의 집에요 개별 방을 스캔 및 병합할 때 권장하는 최대 총면적은 2,000제곱피트 혹은 약 186제곱미터입니다 또한 50럭스 이상의 밝은 조명을 권장합니다 그래야 RoomPlan이 명확한 비디오 스트림과 AR 추적 성능으로 스캔할 수 있죠 이제 앤트완이 iOS 17 RoomPlan의 다른 개선 사항을 알려 드리겠습니다 고마워요, 알렉스 지금부터 접근성을 알아보죠 렌더링이라고 하면 일반적으로 시각적 형태를 떠올리지만 이런 형태는 저시력자에게 그다지 유용하지 않습니다 올해 RoomPlan에 추가된 음성 피드백은 VoiceOver가 활성화된 경우 기기가 스캔 지침을 제공하고 보이는 내용을 설명합니다 시작하려면 기기를 움직이세요 벽 밑 가장자리에 카메라를 맞추세요 벽난로, 벽 창문 이번에는 RoomPlan이 방에서 수집할 수 있는 새 정보와 그 정보의 렌더링을 알아보죠 RoomPlan으로 다양한 방을 쉽게 스캔할 수 있습니다 하지만 지금까지는 한정된 방 환경만 정확히 표현할 수 있다는 제약이 있었죠 앞으로는 RoomPlan이 더 다양한 방 유형을 지원합니다 기울어진 벽과 곡면 벽뿐만 아니라 식기세척기와 오븐, 싱크대와 같은 주방 요소도 인식할 수 있죠 또한 RoomPlan은 특정 범주의 물체 구성을 감지하도록 개선됐습니다 예를 들어 소파의 종류는 다양한데요 1인용에서 L 자형 단순한 형태의 소파 모두 RoomPlan의 새 버전은 감지할 수 있습니다 RoomPlan을 소개하면서 RoomPlan이 스캔할 수 있는 두 가지 요소를 말씀드렸죠 표면과 물체입니다 여기에 방 내부 영역을 묘사하는 새 요소를 추가했습니다 바로 구역입니다 이제 벽을 다각형으로 묘사할 수 있어서 기울어진 벽이나 기둥이 있는 벽처럼 특이한 벽도 처리할 수 있습니다 지금까지는 곡면 벽과 창문이 데이터 전용 API에 포함됐지만 이제 RoomCaptureView의 최종 결과물에서도 곡면 벽을 렌더링할 수 있죠 표면 범주 외에도 바닥 범주가 추가되었는데요 이 또한 다각형으로 묘사할 수 있습니다 또한 물체의 새로운 속성은 한 범주 내의 다양한 구성을 더 잘 묘사할 수 있죠 이제 표면과 물체는 새로운 부모 변수를 갖습니다 부모의 식별자를 포함하는 변수죠 예를 들어 창문의 부모는 벽이고 의자의 부모는 식탁이며 식기세척기의 부모는 수납장입니다 예시를 통해 이러한 개선 사항을 자세히 살펴보죠 구역은 방이나 집의 각기 다른 영역을 묘사하며 다음 중 하나의 레이블을 갖습니다 livingRoom, bedroom, bathroom kitchen, diningRoom이죠 위치와 층이 정해져 있습니다
특수한 벽은 polygonCorner 변수를 통해 다각형으로 렌더링됩니다 바닥은 스캔 중에는 직사각형으로 표시되고 스캔이 종료되면 다각형으로 다듬어집니다 식기세척기와 오븐, 싱크대의 부모도 렌더링에 나타나죠 RoomPlan을 도입했을 때 범주를 사용해서 물체를 묘사했는데요 이 표현 방식은 제한적이었습니다 의자를 예로 들어 보죠 이 범주에는 여러 종류의 의자가 있습니다 스툴과 식탁용 의자 사무용 의자 등이 있죠 용도는 모두 다릅니다 물체를 더 잘 나타내기 위해 속성을 추가했습니다 이 예시에서는 속성을 통해 스캔 내용을 더욱 정확히 이해할 수 있죠 RoomPlan API에서는 다형적인 열거형 어레이를 통해 속성을 사용할 수 있습니다 하지만 열거형은 속성을 이해하는 최적의 방법은 아닙니다 이어서 더 매력적인 표현을 위한 새로운 방법을 살펴보겠습니다 앞으로는 스캔에서 수집된 새로운 정보와 함께 속성도 내보낸 결과물에 포함할 수 있습니다 내보내는 데이터 종류에 두 가지 추가 사항이 있습니다 USDZ 노드에서 메타데이터를 찾는 파일과 내보낸 USDZ에 모델을 추가하는 구조입니다 메시로 방을 내보내면 표면과 물체에 대한 노드 트리를 포함하는 USDZ를 생성하는데 올해 추가된 구역 그룹은 구역 중심을 포함합니다 하지만 이렇게 할 경우 많은 스캔 정보를 잃게 됩니다 벽과 물체의 치수와 더불어 물체의 속성까지 말이죠 이제 방을 내보낼 때 매핑 파일을 생성할 수 있는데요 이는 String과 UUID의 인코딩된 딕셔너리로서 USDZ의 고유한 노드 이름과 고유하게 식별된 CapturedRoom 엘리먼트를 잇는 다리 역할을 합니다
RoomPlan API에서 어떻게 전개되는지 살펴봅시다 RoomPlan의 이전 버전에서는 방을 내보내는 방식이 두 가지였죠 export 함수를 통해 USDZ로 내보내거나 CapturedRoom 구조를 인코딩하는 JSON이나 Plist로 내보내는 거였습니다 iOS 17부터는 export 함수에서 메타데이터 URL을 지정해 방을 USDZ에 매핑할 수 있고 내보낸 두 가지 정보를 연결할 수 있습니다 이렇게 하면 스캔된 방을 렌더링할 때 표면이나 물체에 대한 추가 정보를 쿼리할 수 있죠 새로운 매핑 파일뿐만 아니라 ModelProvider도 도입하여 박스로 표현되던 물체를 스캔된 속성과 일치하는 모델로 대체할 수 있게 했습니다 이 작업의 목표는 물체를 3D 모델에 연결하는 것입니다 구체적으로는 URL에 연결하는 거죠 이렇게 하면 물체를 나타내던 바운딩 박스를 더욱 매력적이고 올바른 렌더링으로 대체할 수 있습니다 이를 위해 RoomPlan에 새로운 구조가 제공됩니다 바로 ModelProvider죠 ModelProvider는 범주와 속성 집합을 Model URL에 매핑합니다 범주와 속성을 지닌 물체에 상응하는 모델 URL을 ModelProvider에 요청할 수 있죠 방 전체에 일반화해 봅시다 방은 구역과 표면 범주 및 속성을 지닌 물체의 모음이죠 export 함수에서 지정된 ModelProvider 인스턴스는 3D Model URL을 방의 물체에 각각 연결할 수 있습니다 이제 모델을 연결하는 구조가 준비됐으니 ModelProvider로 작업하는 법을 알아보죠 ModelProvider를 채우는 방법은 다양합니다 데이터베이스에서 속성 집합과 일치하는 모델을 가져오거나 기존 카탈로그에 주석을 달면 되죠 오늘은 아주 간단한 예제를 통해 직접 작은 에셋 카탈로그를 만드는 법을 알려 드릴게요 카탈로그 생성과 사용은 네 단계를 거칩니다 우선 RoomPlan이 지원하는 범주와 속성을 파싱하고 각 범주와 속성 집합에 모델을 연결합니다 그러고 나서 ModelProvider를 인스턴스화하죠 마지막으로 이를 통해 방을 내보냅니다 몇 가지 모델을 사용해서 속성을 찾고 ModelProvider를 생성하는 법을 알아봅시다 먼저 RoomPlan이 지원하는 모든 범주를 반복합니다 그런 다음 지원되는 범주마다 폴더를 생성합니다 나중에 각 폴더에 모델을 추가할 수 있죠 이제 각 범주에 RoomPlan이 지원하는 속성 조합을 요청합니다 지원되는 속성 집합마다 폴더를 생성하세요 각 폴더에는 범주나 속성 집합에 상응하는 3D 모델을 추가할 수 있습니다 이제 카탈로그 콘텐츠가 준비됐으니 인덱스 파일을 생성할 차례입니다 카탈로그 인덱스를 처리하는 구조의 예시입니다 엘리먼트 어레이를 포함하며 각 어레이는 범주나 속성 집합을 포함하죠 각각의 엘리먼트는 상응하는 모델의 경로를 참조합니다 이제 인덱스 파일을 plist로 저장하고 카탈로그를 번들로 저장할 수 있습니다 모델이 있는 방을 내보내거나 ModelProvider를 통해 물체와 모델을 연결할 때마다 이 카탈로그 번들을 사용해서 ModelProvider를 생성할 수 있습니다 카탈로그의 범주와 속성을 반복하고 상응하는 모델 URL을 찾으면 되죠 속성이 없는 경우 모델 URL을 범주에 연결하거나 모델 URL을 속성 집합에 연결하면 되죠 마지막 단계는 export 함수를 호출하여 출력 URL과 ModelProvider 인스턴스 .model 옵션을 지정하는 것입니다 그러면 스캔과 일치하는 3D 모델을 지닌 USDZ를 얻을 수 있죠 수준을 향상하고 싶은 경우 USDZ를 Blender 등의 DCC 툴로 가져가서 조명과 그림자를 추가하면 단 몇 분 만에 더욱 현실적인 결과물을 얻을 수 있습니다 여러분이 멋진 결과물을 만들 수 있게 미리 채워진 카탈로그를 샘플 코드에 추가했습니다 마무리해 주세요, 알렉스 고마워요, 앤트완 오늘 다룬 내용을 정리해 볼까요? 사용자 지정 ARSession 지원으로 사용 예가 새로워졌습니다 예를 들어 스캔 시 고화질 이미지와 비디오를 캡처해 부동산 목록의 품질을 향상할 수 있죠 또한 여러 방을 스캔할 수 있고 새로운 StructureBuilder API로 집 전체의 3D 모델을 병합할 수 있습니다 저시력 사용자의 스캐닝 경험을 개선하기 위해 RoomCaptureView 사용 시 RoomPlan이 VoiceOver를 지원합니다 RoomPlan의 새로운 물체 속성을 통해 스캔한 방을 더욱 정확하게 표현할 수 있습니다 마지막으로 새로운 내보내기 API를 사용하여 사용자 지정 카탈로그의 3D 모델을 상응하는 스캔 물체에 할당할 수 있습니다 iOS 17의 RoomPlan을 알아봤습니다 여러분의 작품을 기대하겠습니다
-
-
3:00 - RoomPlan with custom ARSession
// RoomCaptureSession public class RoomCaptureSession { // Init: ARSession is an optional input for RoomCaptureSession public init(arSession: ARSession? = nil) { ... } // Stop: pauseARSession is used for whether to continue ARSession experience public func stop(pauseARSession: Bool = true) { ... } }
-
5:50 - MultiRoom support with Continuous ARSession
// Continuous ARSession // start 1st scan roomCaptureSession.run(configuration: captureSessionConfig) // stop 1st scan with continuing ARSession roomCaptureSession.stop(pauseARSession: false) // start 2nd scan roomCaptureSession.run(configuration: captureSessionConfig) // stop 2nd scan (pauseARSession = true by default) roomCaptureSession.stop()
-
7:30 - MultiRoom capture with loading ARWorldMap
// Capture with loading ARWorldMap // load ARWorldMap let arWorldMap = try NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self, from: data) // run ARKit relocalization let arWorldTrackingConfig = ARWorldTrackingConfiguration() arWorldTrackingConfig.initialWorldMap = arWorldMap roomCaptureSession.init() roomCaptureSession.arSession.run(arWorldTrackingConfig, options: []) // Wait for relocalization to complete // start 2nd scan roomCaptureSession.run(configuration: captureSessionConfig) // stop 2nd scan roomCaptureSession.stop()
-
9:40 - StructureBuilder
// StructureBuilder // create structureBuilder instance let structureBuilder = StructureBuilder(option: [.beautifyObjects]) // load multiple capturedRoom results to capturedRoomArray var capturedRoomArray: [CapturedRoom] = [] // run structureBuilder API to get capturedStructure let capturedStructure = try await structureBuilder.capturedStructure(from: capturedRoomArray) // export capturedStructure to usdz try capturedStructure.export(to: destinationURL)
-
10:11 - CapturedStructure
// CapturedStructure public struct CapturedStructure: Codable, Sendable { public var rooms: [CapturedRoom] public var walls: [Surface] public var doors: [Surface] public var windows: [Surface] public var openings: [Surface] public var objects: [Object] public var floors: [Surface] public var sections: [Section] public func export(to url: URL, metadataURL: URL? = nil, modelProvider: ModelProvider? = nil, exportOptions: USDExportOptions = .mesh) throws }
-
19:20 - Parse attributes and categories to create folder hierarchy
// Parse attributes and categories to create folder hierarchy for category in CapturedRoom.Object.Category.allCases { let url = generateFolderURL(category: category, attributes: []) FileManager.default.createDirectory(at: url, withIntermediateDirectories: true) for attributes in category.supportedCombinations { let url = generateFolderURL(category: category, attributes: attributes) FileManager.default.createDirectory(at: url, withIntermediateDirectories: true) } }
-
20:00 - Create a Catalog index
// Create a Catalog index struct RoomPlanCatalog: Codable { let categoryAttributes: [RoomPlanCatalogCategoryAttribute] } struct RoomPlanCatalogCategoryAttribute: Codable { enum CodingKeys: String, CodingKey { case folderRelativePath case category case attributes case modelFilename } let category: CapturedRoom.Object.Category let attributes: [any CapturedRoomAttribute] let folderRelativePath: String private(set) var modelFilename: String? = nil func encode(to encoder: Encoder) throws { … } }
-
20:15 - Create a Catalog bundle
// Create a Catalog bundle let catalog = RoomPlanCatalog(categoryAttributes: categoryAttributes) let plistEncoder = PropertyListEncoder() let data = try plistEncoder.encode(catalog) let catalogURL = inputURL.appending(path: "catalog.plist") try data.write(to: catalogURL) let fileWrapper = try FileWrapper(url: inputURL) try fileWrapper.write(to: outputURL, options: [.atomic, .withNameUpdating], originalContentsURL: nil)
-
20:22 - Instantiate a Model Provider from a Catalog
// Instantiate a Model Provider from a Catalog for categoryAttribute in catalog.categoryAttributes { guard let modelFilename = categoryAttribute.modelFilename else { continue } let folderRelativePath = categoryAttribute.folderRelativePath let modelURL = url.appending(path: folderRelativePath).appending(path: modelFilename) if categoryAttribute.attributes.isEmpty { try modelProvider.setModelFileURL(modelURL, for: categoryAttribute.category) } else { try modelProvider.setModelFileURL(modelURL, for: categoryAttribute.attributes) } }
-
20:47 - Exporting a captured room to usdz with models
// Exporting a captured room to usdz with models try capturedRoom.export(to: outputURL, modelProvider: modelProvider, exportOptions: .model)
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.