스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
visionOS에서 Metal 콘텐츠를 패스스루와 통합하여 렌더링하기
visionOS를 위한 Metal 경험을 확장하세요. 패스스루를 통해 렌더링된 콘텐츠를 사용자의 실제 환경에 통합하기 위한 모범 사례를 살펴보고, 렌더링된 콘텐츠를 실제 환경에 맞춰 배치하고 추적 가능한 앵커 예측(Trackable Anchor Prediction) 기능으로 지연 시간을 줄이는 방법 등을 확인해 보세요.
챕터
- 0:00 - Introduction
- 1:49 - Mix rendered content with surroundings
- 9:20 - Position render content
- 14:49 - Trackable anchor prediction
- 19:22 - Wrap-up
리소스
- Forum: Graphics & Games
- How to start designing assets in Display P3
- Improving Rendering Performance with Vertex Amplification
- Interacting with virtual content blended with passthrough
- Metal Developer Resources
- Rendering a Scene with Deferred Lighting in Swift
- Rendering at Different Rasterization Rates
관련 비디오
WWDC24
WWDC23
-
다운로드
안녕하세요 반갑습니다 Apple 엔지니어 Pooya입니다 오늘은 visionOS의 강력한 성능을 모두 활용할 수 있도록 완전 몰입형 스타일을 넘어 혼합 몰입형으로 Metal을 확장하겠습니다 무엇보다도 Metal을 혼합 몰입형으로 확장할 때 이미 친숙한 툴과 프레임워크를 사용하게 될 겁니다
작년에는 ARKit과 Metal, Compositor Services를 활용해 고유의 렌더링 엔진으로 완전 몰입형 경험을 만들어 봤습니다 올해는 Metal을 사용하여 혼합 몰입형 스타일로 렌더링해 현실과 상상의 경계를 구분하기 어려울 정도로 구현하는 방법을 보여 드리겠습니다
이미 Compositor Services로 RealityKit 렌더링 대신 Metal과 ARKit을 사용한 렌더링 엔진을 구현하여 사용 중인 분도 있을 겁니다 Compositor Services로 렌더링 세션을 생성하고 Metal API로 멋진 모습의 프레임을 렌더링하며 ARKit으로 공간과 손 동작을 추적할 수 있습니다 오늘 세션에서는 Metal과 ARKit, CompositorServices API를 사용해 혼합 몰입형 경험을 제공하는 애플리케이션의 제작 방법을 살펴보겠습니다 첫 번째 단계는 앱의 렌더링 콘텐츠를 자연스럽게 실제 환경과 혼합하는 것입니다 그런 다음 렌더링 콘텐츠를 실제 환경에 어울리도록 배치하는 방법을 알아보겠습니다 끝으로, 추적 가능한 앵커 위치의 올바른 예측 시간을 구하고 사용하는 방법을 알아보겠습니다 예를 들어 사람의 손 그리고 손과 상호작용하는 가상 물체를 추적할 때 사용됩니다 먼저 렌더링 콘텐츠를 주변 환경에 자연스럽게 혼합해 보겠습니다 Compositor Services API와 Metal API Metal 렌더링 기술 관련 경험이 있다면 오늘 세션을 진행할 때 큰 도움이 될 것입니다 해당 분야의 경험이 없다면 이전 동영상을 참고해 보세요 developer.apple.com/kr에서 코드 샘플과 문서를 확인하셔도 됩니다 시작하겠습니다
혼합 몰입형 스타일에서는 렌더링된 콘텐츠와 사용자 주변의 실제 환경이 모두 표시됩니다 몇 가지 단계를 거쳐 이 효과를 최대한 사실적으로 만들 수 있습니다 먼저 그릴 수 있는 텍스처를 올바른 값으로 정리합니다 해당 값은 완전 몰입형 스타일에 사용한 값과 다를 것입니다 그런 다음 렌더링 파이프라인에서 올바른 색상 및 심도 값이 나오는지 확인합니다 이를테면 알파 값이 미리 곱해진 P3 색상 공간 등 visionOS에 적합한 결과인지 확인합니다
그런 다음 ARKit에서 제공하는 장면 정보를 통해 실제 환경에 렌더링 콘텐츠를 앵커로 고정하고 물리 시뮬레이션을 수행합니다 마지막으로 앱 경험에 어울리는 팔 가시성 유형을 지정합니다 앱을 혼합 몰입형 스타일로 전환하는 첫 단계는 아주 간단합니다 immersionStyle을 설정할 때 mixed 옵션을 추가하면 됩니다 기본적으로 SwiftUI는 윈도우 장면을 생성하며 앱의 첫 장면이 ImmersiveSpace인 경우에도 그렇습니다 애플리케이션 장면 매니페스트에 PreferredDefaultSceneSessionRole 키를 추가하여 기본 장면 동작을 수정할 수 있습니다 Compositor Space Content가 있는 공간을 사용하는 경우 CPSceneSessionRoleImmersiveApplication을 사용해야 합니다 애플리케이션 장면의 InitialImmersionStyle 키도 변경할 수 있습니다 혼합 몰입형으로 실행하려면 UIImmersionStyleMixed를 사용해야 합니다
렌더링 루프 시작 부분에서 그릴 수 있는 텍스처를 정리해야 합니다 정리 색상 값은 렌더링할 몰입형 스타일마다 다릅니다 일반적인 렌더링 루프입니다 앱은 그릴 수 있는 새 텍스처를 확보하고 로드 및 정리 작업으로 파이프라인 상태를 구성하고 GPU 워크로드를 인코딩하고 그릴 텍스처를 표시한 다음 커밋합니다
인코딩 단계에서는 가장 먼저 색상 및 심도 텍스처를 정리합니다 텍스처의 모든 픽셀이 렌더링되지 않을 수 있기 때문이죠 심도 값은 항상 0으로 정리해야 합니다 하지만 색상 텍스처의 올바른 값은 몰입형 스타일에 따라 다릅니다 완전 몰입형 스타일에서는 색상 텍스처를 (0,0,0,1)로 정리합니다 혼합 몰입형에서는 모두 0으로 정리합니다 코드는 이런 형태이며 이 예시에서는 renderPassDescriptor를 만들고 색상과 심도 텍스처를 정의했습니다 그런 다음 로드 및 저장 동작을 조정해 렌더링을 시작하기 전에 텍스처를 정리했습니다 색상 및 심도를 모두 0으로 정리했습니다 이 예시에서는 혼합 몰입형 스타일을 사용하므로 색상 텍스처의 알파 값을 0으로 설정했습니다
그런 다음 앱이 지원되는 규칙으로 색상 값을 렌더링하는지 확인합니다
아마도 익숙하실 알파 블렌딩은 두 텍스처를 결합하여 배경과 전경을 사실적으로 만드는 데 사용합니다 visionOS 색상 파이프라인에서는 미리 곱한 알파 색상 규칙을 사용합니다 셰이더의 각 색상 채널을 Compositor Services에 전달하기 전에 알파 값을 곱한다는 의미입니다
visionOS 색상 파이프라인은 P3 디스플레이 색상 공간을 사용합니다 앱의 렌더링 콘텐츠와 패스스루 사이의 색상 일관성을 맞추기 위해 애셋은 디스플레이 P3 색상 공간으로 제작되어야 합니다 developer.apple.com/kr의 문서에서 자세한 내용을 참고하세요
Compositor Services는 색상과 심도 텍스처를 모두 사용하여 합성 연산을 수행합니다 여기에서 알아야 내용으로 Compositor Services는 심도 텍스처가 reverse-Z 규칙을 따를 것으로 예상합니다 특정 픽셀의 렌더링 콘텐츠는 알파 채널 값이 0보다 크고 원근 심도 값이 유효하면 디스플레이에 표시됩니다 시차 효과를 방지하려면 렌더러가 모든 픽셀 전반에 올바른 심도 값을 부여해야 합니다 또한 시스템 성능을 향상하려면 알파 값이 0인 모든 픽셀에 대해 렌더러가 심도 값으로 0을 전달해야 합니다 그런 다음 ARKit에서 제공하는 데이터로 렌더링 콘텐츠에 장면 이해 정보를 반영합니다 혼합 몰입형 스타일에서는 렌더링 콘텐츠가 사용자의 주변 환경과 함께 표시됩니다 경험의 사실성을 높이기 위해 장면 이해 정보를 앱 렌더링 로직에 통합할 수 있습니다 ARKit으로 렌더링 콘텐츠를 실제 환경의 물체와 표면에 고정할 수 있으며 실제 물체와 렌더링 콘텐츠 사이의 사실적인 상호작용을 위한 물리 시뮬레이션을 수행할 수 있고 실제 물체에 뒤에 있는 렌더링 콘텐츠는 표시하지 않을 수도 있습니다 visionOS의 ARKit에 대한 자세한 내용은 ‘공간 컴퓨팅을 위한 ARKit 알아보기’를 참고하세요 거의 완료되었네요! 마지막 단계는 visionOS가 사람의 손과 팔을 표시하도록 팔 가시성을 지정하는 겁니다 혼합 몰입형 스타일에서 팔 가시성을 설정하는 방법은 3가지입니다 첫 번째는 Visible입니다 Visible을 선택하면 물체의 심도와 관계없이 팔이 항상 가장 위에 표시됩니다
두 번째는 Hidden입니다 이 모드에서는 팔이 항상 렌더링 콘텐츠에 가려집니다 마지막으로 Automatic입니다 이 모드에서는 손의 가시성이 렌더링 콘텐츠의 심도에 따라 바뀌게 됩니다 손은 물체 앞에 있는 경우 표시되고 심도가 늘어나면서 페이드아웃됩니다 이 작업은 시스템이 자동으로 처리합니다 어떻게 작동하는지 자세히 알아보겠습니다 한 사용자가 헤드셋을 착용하고 혼합 몰입형 스타일을 실행한다고 상상하세요 장면에는 2개의 물체가 렌더링되어 있습니다 빨간 구와 노란 정육면체입니다 사용자의 손도 시야 안에 있습니다 장면을 렌더링한 다음 그릴 수 있는 심도와 색상 텍스처는 이렇습니다 또한 중요한 점은 Compositor Services API에서 예상하는 심도 텍스처는 reverse-Z 값입니다 팔 가시성을 Automatic으로 지정한 경우 Compositor Services가 텍스처에 설정한 심도 값을 사용합니다 프레임워크가 이 값을 사용하여 손이 물체 앞에 있으니 완전히 표시해야 하는지 아니면 물체 뒤나 안에 있어 부분적으로 가려야 하는지 파악합니다
코드는 이렇습니다 upperLimbVisibility로 앱에서 사용할 팔 가시성 모드를 시스템에 전달합니다 저는 Automatic으로 설정했습니다 렌더링 콘텐츠를 주변의 실제 환경에 시각적으로 혼합한 다음에는 콘텐츠를 환경에 어울리게 배치하는 것이 중요합니다 그러려면 먼저 콘텐츠를 세계 좌표 공간에서 정규화된 기기 좌표 공간으로 변환합니다 장면 이해도 투영 행렬을 활용해 콘텐츠를 더 효과적으로 배치하는 방법을 보여 드리겠습니다 그런 다음 어떤 정규화된 기기 좌표 규칙이 Compositor Services 프레임워크 입력을 지원하는지 살펴보겠습니다 끝으로 여러분의 렌더링 엔진에 사용할 수 있는 대안 규칙을 소개하겠습니다 Vision Pro의 위치와 방향을 기준으로 물체를 렌더링할 때 3D 세계 공간의 콘텐츠를 정규화된 기기 좌표 공간이라는 2.5D 공간으로 변환합니다 이를 ProjectionViewMatrix라고 합니다 ProjectionViewMatrix는 ProjectionMatrix와 ViewMatrix의 조합입니다 ViewMatrix는 2개의 변환으로 나눌 수 있습니다 먼저 기존 공간을 기기 공간으로 변환하는 deviceFromOrigin이 있고 다음으로 기기 공간을 뷰 공간으로 변환하는 viewFromDevice가 있습니다
visionOS API와 일치하도록 ProjectionViewMatrix는 ProjectionMatrix에 originFromDevice와 deviceFromView의 곱의 역을 곱한 것과 같습니다 deviceFromView 변환은 Compositor Services API의 cp_view_get_transform을 호출해 처리됩니다 렌더링된 뷰를 기기 공간으로 변환한 행렬이 반환됩니다 originFromDevice의 경우 ARKit API에서 ar_anchor_get_origin_from_anchor를 호출한 결과를 통해 제공됩니다 올해부터는 실제 물체를 바탕으로 렌더링 콘텐츠를 더 효과적으로 배치할 수 있도록 장면 이해도 투영 행렬을 사용할 수 있습니다 이 행렬은 카메라 고유 정보와 프레임당 실시간 장면 이해 정보를 바탕으로 실제 물체와 렌더링되는 콘텐츠를 더 잘 배치하여 혼합 몰입형 스타일에서 사람들의 경험을 개선합니다 애플리케이션이 Compositor Services에서 혼합 몰입형 스타일을 요청하는 경우 반드시 이 새 API를 사용해야 합니다 콘텐츠를 세계 공간에서 텍스처 좌표 공간으로 변환하면 앱이 렌더링 후 그릴 수 있는 색상과 심도 텍스처는 이렇습니다 텍스처 공간에서 X축은 왼쪽에서 오른쪽으로 Y축은 위에서 아래로 증가합니다 예상되는 깊이 값은 reverse-Z 규칙을 따릅니다
더 자세히 살펴보면 그릴 수 있는 텍스처는 화면과 물리적 공간에서 이렇게 표시됩니다 여기에서 visionOS는 포비티드 색상 파이프라인을 사용합니다 이는 물리적 공간의 크기가 화면 공간 텍스처 크기보다 작다는 것을 보여 줍니다 화면 공간은 사람들이 값을 보는 공간이고 물리적 공간은 메모리에 실제 값이 저장되는 공간입니다
포비티드 렌더링의 작동 방식을 자세히 알아보려면 developer.apple.com/kr의 ‘Rendering at different rasterization rates’를 참고하시기 바랍니다 Compositor Services가 받는 정규화된 기기 좌표 공간에 대해 더 자세히 알아보겠습니다 정규화된 기기 좌표 공간에는 3개의 축이 있습니다 가로 X, 세로 Y 수직 방향의 감기는 순서이며 모두 렌더러의 방향에 영향을 줍니다
Compositor Services가 받는 색상과 심도 텍스처의 렌더링에 사용되는 정규화된 기기 좌표 공간은 모두 X축이 왼쪽에서 오른쪽으로 Y축이 아래에서 위로 감기는 순서가 앞에서 뒤입니다 하지만 렌더링 엔진이 중간 텍스처를 다른 정규화된 기기 좌표 공간으로 렌더링할 수 있습니다 Compositor Services는 장면 이해도 투영 행렬을 다양한 규칙으로 제공하여 Y축이나 감기는 순서를 뒤집을 수 있습니다 결국 렌더링 엔진이 최종적으로 그릴 텍스처를 Compositor Services 규칙에 맞춰야 합니다 이제 코드 샘플을 통해 visionOS API로 ProjectionViewMatrix를 작성하는 방법을 구체적으로 보겠습니다 특정 표시 시간의 deviceAnchor를 가져온 후 deviceAnchor를 Compositor Services API에 전달합니다
주어진 deviceAnchor로 그릴 수 있는 텍스처의 뷰를 반복하여 뷰당 projectionViewMatrix를 계산할 수 있습니다 먼저 해당하는 뷰를 가져옵니다 그런 다음 ARKit API에서 originFromDevice 변환을 가져옵니다 그 후 Compositor Services API에서 deviceFromView 변환을 가져옵니다 이 둘로 viewMatrix를 만들어 원점에서의 뷰로 변환합니다 computeProjection API를 호출하고 해당 뷰의 projection 행렬을 구하고 이때 Compositor Services 규칙인 .rightUpBack 공간을 맞춥니다 마지막으로 projection과 viewMatrix 변환을 곱해 projectionViewMatrix를 구합니다 중요한 점은 애플리케이션에서 모든 계산을 프레임마다 수행하고 이전 프레임의 변환 결과를 재사용하면 안 됩니다 렌더링 콘텐츠를 원하는 대로 배치했으니 이제 사람들이 상호작용할 때 동작을 고려해 봐야 합니다 앵커는 실제 세계에서의 위치와 방향을 나타냅니다 추적 가능한 앵커는 세션이 진행되는 동안 시스템이 추적 상태를 파악하고 놓칠 수도 있는 엔티티 목록입니다 추적 가능한 앵커를 사용하여 렌더링 콘텐츠를 배치할 수 있습니다 예를 들어 사람의 손은 추적 가능한 앵커 엔티티입니다 추적 가능한 앵커를 설정하려면 먼저 ARKit 연결을 승인받습니다 세션을 사용하여 액세스하려는 데이터의 유형에 대한 승인을 요청합니다 그런 다음 제공자를 선택합니다 데이터 제공자를 통해 앵커 변경사항 등의 업데이트를 받고 관찰할 수 있습니다 최종적으로는 ARKit API에서 추적 가능한 앵커를 가져옵니다 렌더링 루프 안에서, 프레임마다 먼저 렌더러에서 로직을 업데이트하고 Compositor Services에 데이터를 제출합니다 렌더링 루프를 더 자세히 살펴보겠습니다 두 단계로 구성됩니다 먼저 Update 단계가 있으며 여기에는 시뮬레이션 로직 상호작용이나 시뮬레이션 물리가 있습니다 이 단계는 일반적으로 CPU 측에서 처리됩니다 추적 가능한 앵커와 기기 위치 정보가 모두 있어야 시뮬레이션 로직을 올바르게 처리할 수 있습니다 두 번째는 Submission 단계로 최종 결과가 텍스처로 렌더링됩니다 이 단계는 GPU 측에서 처리됩니다 앵커 위치 예측 함수의 정확도는 예측 쿼리가 호출되었을 때 프레임 표시 시점이 가까울수록 높아집니다 앵커 더 나은 결과를 위해 앵커와 기기 위치를 다시 쿼리해 가능한 한 결과의 정확도를 높입니다 이 다이어그램은 타이밍이 프레임 섹션에 미치는 영향을 보여 줍니다 Compositor Services에서는 네 가지 타이밍을 이용할 수 있습니다 프레임 타임라인은 애플리케이션에서 수행하는 작업을 나타냅니다
최적 입력 시간은 앱이 상호작용이나 물리 시뮬레이션 등 주요하지 않은 작업을 완료하는 시간입니다 Update 단계 초반에 앱이 추적 가능한 앵커와 앱 앵커를 모두 쿼리합니다 최적 입력 시간 직후가 지연 시간에 중요한 입력을 쿼리하고 프레임 렌더링을 시작하기에 가장 적합한 시기입니다 렌더링 데드라인은 프레임 렌더링의 CPU 및 GPU 작업이 완료되어야 하는 시간입니다 추적 가능한 앵커 시간은 카메라가 주변 환경을 확인하는 시간입니다 추적 가능한 앵커의 경우 이때 앵커 예측을 해야 합니다 마지막으로 표시 시간에는 프레임이 디스플레이에 표시됩니다 이 시간에는 기기 앵커 예측이 수행되어야 합니다 기기 시야 내의 장면을 상상해 보세요 사용자의 손 위에 빨간 구체가 놓여 있습니다 주변에는 노란 정육면체도 있습니다 렌더링이 완료되면 기기 디스플레이에 이렇게 표시됩니다 이 노란 정육면체처럼 추적 가능한 앵커가 없는 모든 물체는 표시 시간에 기기의 위치를 쿼리해 기기를 기준으로 물체의 변환을 계산할 수 있습니다 그러나 빨간 구체처럼 추적 가능한 앵커가 있는 모든 렌더링 콘텐츠는 추적 가능한 앵커 위치와 기기 위치를 모두 사용합니다 추적 가능한 앵커 예측의 경우 추적 가능한 앵커 시간을 이용합니다 코드 샘플을 보겠습니다 렌더링 루프에서 머리 위치와 추적 가능한 앵커 위치를 가져오기 전에 애플리케이션이 주요하지 않은 워크로드를 수행해야 합니다 그래야 예측 정확도가 향상됩니다 중요하지 않은 작업을 완료한 후 애플리케이션이 최적 시간 이후 워크로드를 시작합니다 앵커 의존 워크로드를 시작할 때 먼저 presentationTime과 trackableAnchorTime을 frameTiming 데이터에서 가져옵니다
그런 다음 타임스탬프를 초 단위로 변환합니다
presentationTime을 사용하여 deviceAnchor를 쿼리합니다 그리고 trackableAnchorTime을 사용하여 trackableAnchor를 쿼리합니다 그 후 trackableAnchor가 추적되면 위치 기준 로직을 수행합니다 ARKit API에 대해 자세히 알아보려면 ‘ARKit으로 향상된 공간 컴퓨팅 경험 제작하기’ 비디오를 참고하세요 지금까지 살펴본 모든 도구로 visionOS에서 멋진 혼합 몰입형 경험을 제작할 수 있습니다 Compositor Services와 Metal로 렌더링 루프를 구성하고 3D 콘텐츠를 표시할 수 있습니다 마지막으로 ARKit을 사용하여 인터랙티브한 경험을 만들 수 있습니다 자세한 알아보려면 이 동영상을 참고하세요 시청해 주셔서 감사합니다!
-
-
3:07 - Add mixed immersion
@main struct MyApp: App { var body: some Scene { ImmersiveSpace { CompositorLayer(configuration: MyConfiguration()) { layerRenderer in let engine = my_engine_create(layerRenderer) let renderThread = Thread { my_engine_render_loop(engine) } renderThread.name = "Render Thread" renderThread.start() } .immersionStyle(selection: $style, in: .mixed, .full) } } }
-
4:43 - Create a renderPassDescriptor
let renderPassDescriptor = MTLRenderPassDescriptor() renderPassDescriptor.colorAttachments[0].texture = drawable.colorTextures[0] renderPassDescriptor.colorAttachments[0].loadAction = .clear renderPassDescriptor.colorAttachments[0].storeAction = .store renderPassDescriptor.colorAttachments[0].clearColor = .init(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) renderPassDescriptor.depthAttachment.texture = drawable.depthTextures[0] renderPassDescriptor.depthAttachment.loadAction = .clear renderPassDescriptor.depthAttachment.storeAction = .store renderPassDescriptor.depthAttachment.clearDepth = 0.0
-
9:08 - Set Upper Limb Visibility
@main struct MyApp: App { var body: some Scene { ImmersiveSpace { CompositorLayer(configuration: MyConfiguration()) { layerRenderer in let engine = my_engine_create(layerRenderer) let renderThread = Thread { my_engine_render_loop(engine) } renderThread.name = "Render Thread" renderThread.start() } .immersionStyle(selection: $style, in: .mixed, .full) .upperLimbVisiblity(.automatic) } } }
-
13:37 - Compose a projection view matrix
func renderLoop { //... let deviceAnchor = worldTracking.queryDeviceAnchor(atTimestamp: presentationTime) drawable.deviceAnchor = deviceAnchor for viewIndex in 0...drawable.views.count { let view = drawable.views[viewIndex] let originFromDevice = deviceAnchor?.originFromAnchorTransform let deviceFromView = view.transform let viewMatrix = (originFromDevice * deviceFromView).inverse let projection = drawable.computeProjection(normalizedDeviceCoordinatesConvention: .rightUpBack, viewIndex: viewIndex) let projectionViewMatrix = projection * viewMatrix; //... } }
-
18:27 - Trackable anchor prediction
func renderFrame() { //... // Get the trackable anchor and presentation time. let presentationTime = drawable.frameTiming.presentationTime let trackableAnchorTime = drawable.frameTiming.trackableAnchorTime // Convert the timestamps into units of seconds let devicePredictionTime = LayerRenderer.Clock.Instant.epoch.duration(to: presentationTime).timeInterval let anchorPredictionTime = LayerRenderer.Clock.Instant.epoch.duration(to: trackableAnchorTime).timeInterval let deviceAnchor = worldTracking.queryDeviceAnchor(atTimestamp: devicePredictionTime) let leftAnchor = handTracking.handAnchors(at: anchorPredictionTime) if (leftAnchor.isTracked) { //... }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.