스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
iOS 또는 iPadOS 게임을 visionOS로 가져오기
iOS 또는 iPadOS 게임을 visionOS만의 특별한 경험으로 변환하는 방법을 알아보세요. 3D 프레임 또는 몰입형 배경을 사용하여 몰입감은 물론 재미 요소를 강화할 수 있습니다. 스테레오스코피 또는 머리 추적 기능으로 윈도우에 심도를 더해 플레이어가 더욱 몰입할 수 있게 해보세요.
챕터
- 0:00 - Introduction
- 1:42 - Render on visionOS
- 3:48 - Compatible to native
- 6:41 - Add a frame and a background
- 8:00 - Enhance the rendering
리소스
관련 비디오
WWDC24
- 매력적인 공간 비디오 및 사진 경험 빌드하기
- iOS, macOS, visionOS용 RealityKit API 알아보기
- RealityKit으로 공간 드로잉 앱 빌드하기
- visionOS에서 Metal 콘텐츠를 패스스루와 통합하여 렌더링하기
- visionOS의 게임 입력 방식 살펴보기
WWDC23
-
다운로드
안녕하세요 저는 RealityKit 및 visionOS 담당 소프트웨어 엔지니어 Olivier입니다 이 비디오에서는 iOS 또는 iPadOS 게임을 visionOS에서 더 실감 나는 경험으로 변환하는 방법을 다루겠습니다
포근한 느낌의 일상 및 농작 시뮬레이터 Wylde Flowers 게임을 예로 들겠습니다 이 게임의 iPad 버전은 이렇습니다
그리고 이것은 visionOS의 윈도우에서 호환되는 앱으로 실행되는 동일한 iPad 버전입니다
Wylde Flowers 최신 버전은 visionOS App Store에서 다운로드할 수 있으며 visionOS 관련 개선 사항이 포함되었습니다
예를 들어, 윈도우 주변에 3D 프레임이 있습니다
이 프레임은 게임플레이에 따라 바뀌기도 합니다 예컨대 플레이어가 정원에서 일을 하면 정원의 3D 모델과 추가 UI가 프레임에 표시됩니다
게임의 몰입감을 높이는 배경 요소도 있어 민들레가 떨어지거나 새들이 날고 지저귑니다
아울러 게임 뷰가 입체 영상으로 렌더링되므로 장면에 심도가 더해집니다
이러한 개선 사항으로 visionOS용 Wylde Flowers는 더욱 향상되었습니다 이번 비디오에서는 iOS 또는 iPadOS 게임에 이러한 개선 사항을 적용하는 법을 다루겠습니다
먼저 visionOS에서 사용할 수 있는 렌더링 기술을 살펴보겠습니다 그리고 iOS 앱을 네이티브 visionOS 앱으로 변환하는 방법을 알려 드리겠습니다 RealityKit 프레임과 배경을 게임에 추가하는 방법도 보여 드리겠습니다 끝으로 입체 영상, 머리 추적 VRR을 사용해 게임의 Metal 렌더링을 향상하는 방법을 알려 드리겠습니다
visionOS에서 사용할 수 있는 렌더링 기술부터 살펴보겠습니다 visionOS에서는 RealityKit과 Metal로 3D 렌더링을 할 수 있습니다
RealityKit은 프레임워크로 Swift에서 직접 사용하거나 Unity PolySpatial 같은 기술로 간접적으로 사용할 수 있으며 visionOS에서 같은 공유 공간에 여러 앱을 활성화합니다 예를 들어 RealityKit을 사용하면 볼륨 윈도우에서 콘텐츠를 렌더링할 수 있습니다 Game Room 및 LEGO Builder’s Journey가 그러한 예입니다 또는 RealityKit을 사용하여 콘텐츠를 Immersive Space에서 렌더링할 수도 있습니다 Super Fruit Ninja나 Synth Riders처럼요
visionOS에서 Metal을 사용해 직접 렌더링할 수도 있는데 맞춤형 렌더링 파이프라인이 필요한 경우 또는 게임을 RealityKit으로 포팅하는 것이 적합하지 않은 경우 그렇게 할 수 있습니다
visionOS에서 Metal을 사용해 렌더링하는 주요 모드는 2가지입니다
게임을 윈도우에서 호환 가능한 앱으로 실행할 수 있으며 이때 앱은 iPad에서와 매우 유사하게 작동합니다
Wylde Flowers의 iPad 버전이 바로 이러한 사례로 visionOS에서 호환 가능한 앱으로 실행됩니다
앱을 윈도우로 실행하면 공유 공간에서 다른 앱과 함께 실행할 수 있다는 큰 이점이 있습니다 Safari를 사용하거나 메시지를 보내면서 동시에 게임을 플레이할 수 있습니다
CompositorServices를 사용하여 게임을 완전 몰입형 앱으로 실행할 수 있으며 플레이어는 머리를 이용해 게임의 카메라를 제어합니다 예를 들어 ‘visionOS에서 Metal 콘텐츠를 패스스루와 통합하여 렌더링하기’ 비디오에서 샘플은 CompositorServices로 렌더링됩니다 해당 비디오에서 자세히 알아보세요
visionOS에서는 손쉽게 iPad 앱을 호환되는 앱으로 실행할 수 있습니다 CompositorServices를 사용하여 게임을 몰입형 앱으로 만들면 앱을 더 생생하게 만들 수 있지만 대대적인 재설계 및 재작업이 필요할 수 있습니다 플레이어가 카메라를 전적으로 제어할 수 있고 장면의 어떤 곳이든 볼 수 있기 때문이죠
이 비디오에서는 이 두 모드의 사이에 있는 여러 기술을 살펴보겠습니다 호환되는 앱으로 시작하고 점진적으로 기능을 추가하여 몰입감을 높이고 Vision Pro의 기능을 활용할 수 있는 방법을 보여 드리겠습니다
가장 쉬운 첫 단계는 visionOS에서 호환되는 앱으로 게임을 실행하는 방법입니다
iOS 게임의 예로 Metal Deferred Lighting 샘플을 사용하겠습니다 이 비디오는 iPad에서 실행되는 샘플을 보여 줍니다
이 샘플의 iOS 버전은 Developer 웹사이트 developer.apple.com에서 다운로드할 수 있습니다
먼저 iOS SDK로 앱을 컴파일한 다음 visionOS에서 호환되는 앱으로 실행해 보겠습니다
호환되는 앱은 visionOS의 윈도우에서 실행됩니다
visionOS에서는 터치 컨트롤과 게임 컨트롤러를 모두 사용할 수 있으므로 기본적으로 모든 플랫폼에서 공통된 경험을 제공합니다
게임 입력에 관한 자세한 내용은 ‘visionOS에서 게임 입력 살펴보기’ 비디오에서 확인할 수 있습니다
호환되는 앱은 visionOS에서 잘 작동하지만 visionOS SDK를 사용하기 위해 앱을 네이티브 앱으로 변환하겠습니다 앱의 빌드 설정으로 이동하여 iOS 대상을 선택합니다 현재 visionOS에서 iOS SDK를 사용하므로 Designed for iPad로 표시되어 있습니다
visionOS SDK로 앱을 컴파일하기 위해 Apple Vision을 지원 대상에 추가하겠습니다
컴파일 오류가 발생할 수 있지만 iOS용으로 제작된 앱이면 대부분의 코드가 컴파일됩니다
예를 들어 visionOS에는 Metal iOS 게임의 콘텐츠를 표시하는 여러 옵션이 있습니다
UI 뷰에 쉽게 통합할 수 있는 CAMetalLayer로 렌더링할 수 있습니다 새로운 LowLevelTexture API를 사용해 RealityKit 텍스처로 직접 렌더링해도 됩니다
CAMetalLayer로 시작하는 것이 더 쉬우면 그래도 되지만 가장 효과적으로 제어하려면 LowLevelTexture로 이동하는 편이 좋습니다
CAMetalLayer로 렌더링하려면 이를 포함하는 뷰를 생성하면 됩니다
그리고 CADisplayLink를 생성하여 프레임마다 콜백을 받을 수 있습니다
코드는 이렇게 작성합니다 UIView는 CAMetalLayer를 layerClass로 선언합니다 그런 다음 CAMetalDisplayLink를 생성해 렌더링 콜백을 가져옵니다
마지막으로 프레임마다 콜백에서 CAMetalLayer를 렌더링합니다
LowLevelTextures를 유사한 방식으로 사용할 수 있습니다 주어진 픽셀 형식과 해상도로 LowLevelTexture를 만들 수 있습니다 그런 다음 LowLevelTexture에서 TextureResource를 생성하고 RealityKit 장면 어디서든 사용할 수 있습니다 그리고 CommandQueue를 사용하여 MTLTexture를 통해 LowLevelTexture에 그릴 수 있습니다
코드로 작성하면 이렇습니다 이 코드는 LowLevelTexture를 생성하고 RealityKit 장면의 어디서든 사용할 수 있는 TextureResource를 만듭니다
그런 다음 프레임마다 텍스처를 그립니다
LowLevelTexture에 대한 자세한 내용은 ‘RealityKit으로 공간 드로잉 앱 빌드하기’ 비디오를 참조하세요 네이티브 visionOS 앱으로 게임을 변환했으므로, 이제 visionOS 관련 기능을 추가할 수 있습니다 예를 들어 게임 뷰 주변에 프레임을 추가하고 ImmersiveSpace에 배경을 추가해 앱의 몰입감을 높일 수 있습니다
Cut The Rope 3 게임에는 윈도우 주변에 동적 프레임이 있습니다
RealityKit은 프레임을 Metal은 게임을 렌더링합니다
앞서 보여드린 것처럼 게임을 텍스처로 렌더링하는 Metal 뷰가 있는 ZStack을 사용해 이 작업을 완료할 수 있으며 게임 주위에 3D 모델을 로드하는 RealityView를 사용하여 프레임을 생성할 수 있습니다
@State 변수를 사용해 동적인 프레임을 만들 수 있습니다 한 예로 Cut The Rope 3에서는 레벨에 따라 프레임이 변경됩니다 게임 뒤에 몰입형 배경을 추가할 수도 있습니다 Void-X 게임이 좋은 예시입니다 대부분의 게임플레이는 윈도우에서 이루어지지만 Void-X는 배경에 비와 번개를 추가하고 사방에서 날아다니는 3D 총알을 추가하여 몰입감을 높입니다
SwiftUI에서 ImmersiveSpace를 사용해 배경을 만들 수 있습니다
iOS 게임을 WindowGroup에 넣을 수도 있죠
SwiftUI @State 객체를 사용하여 윈도우와 ImmersiveSpace 간에 @State를 공유할 수도 있습니다
지금까지 게임 주변에 요소를 추가하는 방법을 보여 드렸습니다 이제 게임의 Metal 렌더링을 향상하는 데 사용할 수 있는 몇 가지 기술을 알려 드리겠습니다 먼저 입체 영상을 추가하여 게임에 심도를 더하는 방법을 보여 드리겠습니다 그런 다음 머리 추적을 추가하여 게임의 세계를 생생하게 선보이는 방법도 다룹니다 마지막으로 게임에 VRR을 추가하여 성능을 향상하는 방법을 보여 드리겠습니다
3D 영화와 비슷하게 게임에 입체 영상을 추가하여 장면에 심도 더할 수 있습니다
이 화면은 입체 영상으로 렌더링된 Deferred Lighting 샘플의 한 장면입니다 설명을 위해 빨간색과 청록색의 색조를 띠는 입체 영상을 보여 드리겠습니다 Vision Pro를 통해 보면 두 눈은 서로 다른 이미지를 보게 됩니다
입체 영상은 기본적으로 양쪽 눈에 서로 다른 이미지를 표시합니다 visionOS에서 이 방식을 구현하려면 RealityKit ShaderGraph 특히 CameraIndex 노드를 사용합니다
그 다음 각 눈에 서로 다른 이미지를 표시하여 입체 영상을 구현합니다
입체 영상의 심도 효과는 각 이미지에 있는 물체의 뷰 사이의 거리로 인해 발생하며 이를 패럴랙스라 합니다
패럴랙스로 인해 사람의 눈은 물체로부터 떨어진 거리에 따라 약간 모이게 됩니다 두 눈은 아주 먼 거리를 볼 때 평행을 이루고 가까운 물체를 바라볼 때는 모이게 되며 뇌는 이러한 정보를 수집하여 거리를 판단합니다 입체 영상은 이러한 원리로 장면에 심도를 더하며 마치 바로 앞에 실제 미니어처가 있거나 다른 세상으로 통하는 포털처럼 느끼게 합니다
네거티브 패럴랙스가 적용되면 물체는 이미지 앞쪽에 나타납니다 패럴랙스가 없으면 두 물체가 중첩되며 이 경우 2D 이미지와 마찬가지로 이미지 평면에 표시됩니다 포지티브 패럴랙스가 적용된 물체는 이미지 평면 뒤쪽에 나타납니다
이것은 정면에서 볼 때 입체 영상이 어떤 느낌인지를 표현한 것입니다
사실, 옆에서 본다면 윈도우에서 나오는 어떤 물체도 볼 수 없는데 콘텐츠가 단순히 직사각형 위에 표시되기 때문입니다
물체가 직사각형에서 밖으로 나오게 하고 싶다면 RealityKit, 새로운 포털 교차 API 같은 API를 사용하여 물체를 렌더링하면 됩니다 ‘iOS, macOS 및 visionOS용 RealityKit API 알아보기’ 비디오에서 포털 교차의 예를 살펴보세요
플레이어의 머리 위치를 사용하지 않으면 측면에서 볼 때 장면이 투사된 것처럼 보이게 됩니다 머리 위치를 사용하는 방법은 나중에 설명하겠습니다 이 그림은 입체 영상의 원리를 보여 주는 다이어그램입니다 물체는 두 광선의 교차점에서 인지됩니다 인지되는 심도는 두 이미지 사이의 패럴랙스에 따라 달라집니다 패럴랙스가 바뀌면 교차점의 위치도 바뀝니다
참고로 해당 입체 영상에서는 윈도우의 크기와 위치에 따라 인지되는 심도도 달라집니다 이미지는 바뀌지 않더라도 말이죠
‘매력적인 공간 사진 및 비디오 경험 구축하기’ 비디오에서 Vision Pro용 입체 영상을 만드는 방법을 자세히 살펴보세요
특히 피해야 할 상황 중 하나는 콘텐츠를 무한대 너머에 렌더링하는 것입니다 콘텐츠를 볼 때 사용자의 시선은 모이거나 평행을 이룹니다 콘텐츠가 무한대를 넘어가는 현상은 패럴랙스가 사용자의 눈 사이 간격보다 커질 때 발생하며 이때 광선은 발산하고 교차점이 사라지게 됩니다 실제 콘텐츠를 볼 때는 절대 발생하지 않는 현상으로 사용자에게 불편한 느낌을 줍니다 이 문제를 해결하는 한 가지 방법은 무한대 거리의 평면에 입체 영상을 표시하는 것입니다 예를 들어 이 그림은 윈도우 평면의 이미지 렌더링입니다 콘텐츠 중 일부는 이미지 평면 뒤쪽에 표시되고 일부는 이미지 평면의 앞쪽에 표시됩니다 공간 사진에서 포털을 통해 렌더링하듯, 무한대 거리의 이미지 평면에 콘텐츠를 렌더링하는 방법을 쓰면 패럴랙스가 0인 콘텐츠는 무한대 위치에 나타나고 다른 모든 콘텐츠는 네거티브 패럴랙스가 적용되어 그 앞쪽에 보이게 됩니다 이렇게 하면 모든 콘텐츠를 무한대보다 앞쪽에 표시할 수 있습니다
플레이어가 입체 영상의 강도를 쉽게 조정할 수 있도록 게임 설정에 슬라이더를 추가하는 것도 좋습니다 이 설정은 두 가상 카메라 사이의 거리를 변경하여 구현할 수 있습니다 입체 영상을 생성하려면 게임 루프를 각 눈에 렌더링되도록 업데이트해야 합니다 iOS의 Deferred Lighting 샘플의 게임 루프는 이렇습니다
이 샘플은 게임 상태와 애니메이션을 업데이트합니다 그런 다음 샘플에서 그림자 맵 같은 오프스크린 렌더링을 수행합니다 그러면 화면에 렌더링됩니다 끝으로 샘플에서 렌더링을 보여 줍니다
입체 영상에서는 각 눈에 대한 화면 렌더링을 복제해야 합니다 그리고 탁월한 성능을 위해 Vertex Amplification을 사용해 동일한 그리기 호출로 두 눈을 렌더링할 수 있습니다
개발자 문서에 Vertex Amplification에 관한 글이 있습니다
예를 들어 Deferred Lighting 샘플 코드를 저는 이렇게 수정했습니다 우선 그림자 패스를 한 번 인코딩합니다 그런 다음 각 뷰를 검토하고 적절한 카메라 매트릭스를 설정합니다 마지막으로 렌더링 명령을 해당 뷰의 색상 및 심도 텍스처로 인코딩합니다
입체 영상은 3D 영화나 공간 사진과 유사하게 장면에 심도를 더해 줍니다 게임을 다른 세계로 통하는 윈도우처럼 보이게 하려면 머리 추적을 추가할 수도 있습니다 예를 들어 이것은 머리 추적이 포함된 Deferred Lighting 샘플입니다 머리가 움직이면 카메라도 같이 움직입니다
ImmersiveSpace와 ARKit을 사용하여 플레이어 머리의 위치 정보를 얻을 수 있습니다 ARKit에서 프레임마다 머리 위치 정보를 가져와 렌더러에 전달하여 카메라를 제어할 수 있습니다
코드는 이렇게 작성합니다 먼저 ARKit을 가져옵니다 그러면 ARKitSession 및 WorldTrackingProvider가 생성됩니다 그리고 프레임마다 머리 변환을 쿼리합니다
또한 윈도우와 ImmersiveSpaces에는 visionOS에 대한 자체 좌표 공간이 있습니다 ARKit의 머리 변환은 ImmersiveSpace의 좌표 공간에 있습니다 윈도우에서 이 정보를 사용하려면 위치를 윈도우의 좌표 공간으로 변환하면 됩니다
코드로 작성하면 이렇습니다 ARKit에서 ImmersiveSpace의 좌표 공간에서의 머리 위치를 얻을 수 있습니다
그런 다음 visionOS 2.0의 이 최신 API를 사용하여 ImmersiveSpace 기준으로 윈도우에서 엔티티의 변환 매트릭스를 가져올 수 있습니다
이 행렬을 반전시키고 머리 위치를 윈도우 공간으로 변환할 수 있습니다 마지막으로 이 카메라 위치를 렌더러로 설정할 수 있습니다
좋은 결과를 얻으려면 머리 위치를 예측해야 합니다 렌더링은 발생 및 표시에 시간이 걸리므로 해당 시간의 추정치를 통해 머리 위치를 예측하여 렌더링이 최대한 최종 머리 위치와 일치하도록 해야 합니다 ARKit은 앱의 예상 렌더링 시간을 받으면 머리 예측을 수행합니다 샘플에서는 예측 presentationTime으로 33밀리초를 사용했으며 이는 90fps에서 3개 프레임에 해당합니다 게임이 실제 윈도우를 통해 렌더링되는 것처럼 보이도록 하려면 비대칭 투사 매트릭스도 빌드해야 합니다 고정 투사 매트릭스를 사용하면 윈도우 모양과 일치하지 않습니다 카메라 절두체가 윈도우를 통과하도록 해야 합니다 예를 들면 윈도우의 왼쪽과 오른쪽에 있는 벡터를 사용하여 투사 매트릭스를 빌드할 수 있습니다 투사 매트릭스를 이렇게 빌드하면 윈도우와 정렬된 가까운 클리핑 평면을 사용하여 물체와 윈도우 측면의 교차를 방지할 수 있다는 이점이 있습니다
코드는 이렇게 작성합니다 카메라 위치와 뷰포트의 3D 경계부터 시작합니다 카메라는 주어진 위치에서 -Z를 향하고 있습니다 그런 다음 뷰포트의 각 측면까지의 거리를 계산합니다 그리고 이러한 거리를 사용하여 비대칭 투사 매트릭스를 빌드합니다 이것이 머리 추적을 사용하여 게임의 세계를 생생하게 선보이는 방법도 다룹니다 입체 영상은 게임의 몰입감을 높여줍니다 하지만 렌더링에 드는 비용도 올라가는데 게임에서 렌더링해야 하는 조각도 2배로 늘기 때문입니다 Variable Rasterization Rates를 사용하면 게임의 렌더링 효율성을 향상하여 그러한 문제를 일부 해소할 수 있습니다 Variable Rasterization Rates는 화면 전체에 걸쳐 가변 해상도로 렌더링하는 Metal의 기능입니다
이 기능을 사용하여 주변의 해상도를 낮추고 중앙의 해상도를 높일 수 있습니다 머리 추적을 사용하는 경우 픽셀이 시야의 중앙에 있는지 아니면 주변에 있는지 알 수 있으므로 머리 변환에서 VRR 맵을 빌드할 수 있습니다 공유 공간에 있는 경우 머리 위치에 접근할 수 없으나 AdaptiveResolutionComponent를 사용하고 게임 뷰포트 위의 2D 그리드에 구성요소를 배치하여 VRR 맵을 빌드할 수 있습니다
AdaptiveResolutionComponent는 이 3D 위치에서 화면에 1m 정육면체가 차지하는 대략적인 크기를 픽셀 단위로 제공합니다 예를 들어 이 경우의 값은 1024에서 2048 픽셀로 변경됩니다
이 비디오에서는 카메라가 점점 더 가까워짐에 따라 AdaptiveResolutionComponent의 값이 어떻게 변경되는지 보여 줍니다
2D 그리드에서 수평 및 수직 VRR 맵을 추출할 수 있습니다 더 부드러운 결과를 얻으려면 각 VRR 맵을 보간하고 최종적으로 이를 Metal 렌더러에 전달하면 됩니다 마지막으로 콘텐츠가 VRR로 렌더링되면 VRR 맵을 반전시켜 디스플레이에 다시 매핑해야 합니다 Variable Rasterization Rates를 통해 카메라 변환에 맞춰 렌더링 해상도를 조정하여 게임 성능을 향상하는 방법입니다
이러한 개선 사항을 활용하면 visionOS 게임의 품질을 더 높일 수 있습니다 Wylde Flowers가 visionOS에서 멋진 모습을 선보이는 것처럼요
이 비디오에서는 iOS 게임을 어떻게 visionOS로 가져오는지 살펴보았습니다 그리고 프레임과 ImmersiveSpace를 추가하여 게임의 몰입감을 높이는 방법도 알아보았습니다 Metal 렌더러에 입체 영상과 머리 추적을 추가하여 게임을 다른 세계로 통하는 윈도우처럼 보이게 만드는 방법도 알아보았습니다 VRR을 사용한 성능 최적화 방법도 살펴봤습니다 이러한 기술을 사용하여 visionOS를 위해 iOS 게임을 향상해 보시기 바랍니다 여러분의 게임을 어서 Vision Pro에서 플레이할 수 있기를 기대합니다
-
-
5:44 - Render with Metal in a UIView
// Render with Metal in a UIView. class CAMetalLayerBackedView: UIView, CAMetalDisplayLinkDelegate { var displayLink: CAMetalDisplayLink! override class var layerClass : AnyClass { return CAMetalLayer.self } func setup(device: MTLDevice) { let displayLink = CAMetalDisplayLink(metalLayer: self.layer as! CAMetalLayer) displayLink.add(to: .current, forMode: .default) self.displayLink.delegate = self } func metalDisplayLink(_ link: CAMetalDisplayLink, needsUpdate update: CAMetalDisplayLink.Update) { let drawable = update.drawable renderFunction?(drawable) } }
-
6:20 - Render with Metal to a RealityKit LowLevelTexture
// Render Metal to a RealityKit LowLevelTexture. let lowLevelTexture = try! LowLevelTexture(descriptor: .init( pixelFormat: .rgba8Unorm, width: resolutionX, height: resolutionY, depth: 1, mipmapLevelCount: 1, textureUsage: [.renderTarget] )) let textureResource = try! TextureResource( from: lowLevelTexture ) // assign textureResource to a material let commandBuffer: MTLCommandBuffer = queue.makeCommandBuffer()! let mtlTexture: MTLTexture = texture.replace(using: commandBuffer) // Draw into the mtlTexture
-
7:06 - Metal viewport with a 3D RealityKit frame around it
// Metal viewport with a 3D RealityKit frame // around it. struct ContentView: View { @State var game = Game() var body: some View { ZStack { CAMetalLayerView { drawable in game.render(drawable) } RealityView { content in content.add(try! await Entity(named: "Frame")) }.frame(depth: 0) } } }
-
7:45 - Windowed game with an immersive background
// Windowed game with an immersive background @main struct TestApp: App { @State private var appModel = AppModel() var body: some Scene { WindowGroup { // Metal render ContentView(appModel) } ImmersiveSpace(id: "ImmersiveSpace") { // RealityKit background ImmersiveView(appModel) }.immersionStyle(selection: .constant(.progressive), in: .progressive) } }
-
13:11 - Render to multiple views for stereoscopy
// Render to multiple views for stereoscopy. override func draw(provider: DrawableProviding) { encodeShadowMapPass() for viewIndex in 0..<provider.viewCount { scene.update(viewMatrix: provider.viewMatrix(viewIndex: viewIndex), projectionMatrix: provider.projectionMatrix(viewIndex: viewIndex)) var commandBuffer = beginDrawableCommands() if let color = provider.colorTexture(viewIndex: viewIndex, for: commandBuffer), let depthStencil = provider.depthStencilTexture(viewIndex: viewIndex, for: commandBuffer) { encodePass(into: commandBuffer, color: color, depth: depth) } endFrame(commandBuffer) } }
-
13:55 - Query the head position from ARKit every frame
// Query the head position from ARKit every frame. import ARKit let arSession = ARKitSession() let worldTracking = WorldTrackingProvider() try await arSession.run([worldTracking]) // Every frame guard let deviceAnchor = worldTracking.queryDeviceAnchor( atTimestamp: CACurrentMediaTime() + presentationTime ) else { return } let transform: simd_float4x4 = deviceAnchor .originFromAnchorTransform
-
14:22 - Convert the head position from the ImmersiveSpace to a window
// Convert the head position from the ImmersiveSpace to a window. let headPositionInImmersiveSpace: SIMD3<Float> = deviceAnchor .originFromAnchorTransform .position let windowInImmersiveSpace: float4x4 = windowEntity .transformMatrix(relativeTo: .immersiveSpace) let headPositionInWindow: SIMD3<Float> = windowInImmersiveSpace .inverse .transform(headPositionInImmersiveSpace) renderer.setCameraPosition(headPositionInWindow)
-
15:05 - Query the head position from ARKit every frame
// Query the head position from ARKit every frame. import ARKit let arSession = ARKitSession() let worldTracking = WorldTrackingProvider() try await arSession.run([worldTracking]) // Every frame guard let deviceAnchor = worldTracking.queryDeviceAnchor( atTimestamp: CACurrentMediaTime() + presentationTime ) else { return } let transform: simd_float4x4 = deviceAnchor .originFromAnchorTransform
-
15:47 - Build the camera and projection matrices
// Build the camera and projection matrices. let cameraPosition: SIMD3<Float> let viewportBounds: BoundingBox // Camera facing -Z let cameraTransform = simd_float4x4(AffineTransform3D(translation: Size3D(cameraPosition))) let zNear: Float = viewportBounds.max.z - cameraPosition.z let l /* left */: Float = viewportBounds.min.x - cameraPosition.x let r /* right */: Float = viewportBounds.max.x - cameraPosition.x let b /* bottom */: Float = viewportBounds.min.y - cameraPosition.y let t /* top */: Float = viewportBounds.max.y - cameraPosition.y let cameraProjection = simd_float4x4(rows: [ [2*zNear/(r-l), 0, (r+l)/(r-l), 0], [ 0, 2*zNear/(t-b), (t+b)/(t-b), 0], [ 0, 0, 1, -zNear], [ 0, 0, 1, 0] ])
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.