View in English

  • 메뉴 열기 메뉴 닫기
  • Apple Developer
검색
검색 닫기
  • Apple Developer
  • 뉴스
  • 둘러보기
  • 디자인
  • 개발
  • 배포
  • 지원
  • 계정
페이지에서만 검색

빠른 링크

5 빠른 링크

비디오

메뉴 열기 메뉴 닫기
  • 컬렉션
  • 주제
  • 전체 비디오
  • 소개

WWDC25 컬렉션으로 돌아가기

  • 소개
  • 요약
  • 자막 전문
  • 코드
  • 몰입감 넘치는 앱을 위한 Metal 렌더링의 새로운 기능

    Compositor Services와 함께 몰입감 넘치는 앱을 위한 Metal 렌더링의 최신 개선 사항을 확인하세요. 앱의 상호작용 요소를 강조하기 위해 호버 효과를 추가하는 방법과 동적 렌더링 품질로 더 높은 fidelity를 렌더링하는 방법을 알아보세요. 새로운 점진적 몰입 스타일에 대해서 살펴보고 Metal 콘텐츠를 Mac에서 Vision Pro로 직접 렌더링하여 macOS 앱에 몰입형 경험을 가져오는 방법도 확인할 수 있습니다. 이 세션을 최대한 활용하려면 WWDC23에서 ‘몰입감 넘치는 앱을 위한 Metal 살펴보기'를 먼저 시청하는 것이 좋습니다.

    챕터

    • 0:00 - 서론
    • 1:58 - 새 렌더링 루프 API
    • 4:21 - 호버 효과
    • 10:50 - 동적 렌더링 품질
    • 14:44 - 점진적 몰입
    • 18:32 - macOS 공간 렌더링
    • 23:51 - 다음 단계

    리소스

    • Analyzing the performance of your Metal app
    • Optimizing GPU performance
    • Rendering hover effects in Metal immersive apps
      • HD 비디오
      • SD 비디오

    관련 비디오

    WWDC25

    • Metal 4 게임 심화 기능 알아보기
    • Metal 4 게임 알아보기
    • Metal 4 살펴보기
    • SwiftUI의 새로운 기능
    • visionOS 26의 새로운 기능
    • visionOS에서 공간 액세서리 입력 살펴보기

    WWDC24

    • visionOS에서 Metal 콘텐츠를 패스스루와 통합하여 렌더링하기

    WWDC23

    • 몰입형 앱을 위한 Metal 알아보기
  • 비디오 검색…

    안녕하세요, Apple 소프트웨어 엔지니어인 Ricardo입니다 오늘은 Apple Vision Pro에서 Metal로 몰입형 콘텐츠를 렌더링할 때 채택할 수 있는 새 기능을 소개하겠습니다 작년에는 Metal, Compositor Services, ARKit으로 visionOS에서 콘텐츠를 직접 렌더링해 몰입형 경험을 만드는 법을 알려 드렸죠

    Resolution Games의 Demeo처럼 완전히 몰입형 경험을 구현하거나 현실과 콘텐츠를 함께 보여주는 혼합 몰입형 스타일도 도입할 수 있습니다

    그리고 이제 개발자분들이 보내 주신 소중한 피드백 덕분에 visionOS의 Metal 렌더링은 흥미로운 새 기능을 지원합니다

    더욱 풍부한 세부 정보와 상호작용 효과를 앱과 게임에 추가할 수 있습니다 본 동영상에서는 새 Compositor Services 기능을 설명하겠습니다

    기능을 최대한 활용하려면 Compositor Services 프레임워크와 Metal 렌더링 기술을 잘 알고 있어야 합니다 이전에 이 기술을 사용한 적이 없다면 이전 동영상을 통해 알아볼 수 있습니다

    새 기능을 채택하려면 먼저 기존 렌더링 루프를 일부 변경해야 합니다 파이프라인의 유연성을 높이는 새 API 도입 방법을 알려 드리죠 해당 작업이 완료되면 호버 효과를 추가해 앱의 상호작용 요소를 강조 표시할 수 있습니다 렌더링된 콘텐츠의 해상도를 동적으로 조정할 수도 있습니다 사용자가 Digital Crown으로 몰입형 수준을 조절할 수 있는 새 점진적 몰입형 스타일이 도입되었습니다 Mac을 사용하면 몰입형 콘텐츠가 Vision Pro로 바로 렌더링이 되죠! 이 모든 과정은 새 렌더링 루프 API에서 시작됩니다 바로 시작하겠습니다

    Metal 몰입형 앱은 SwiftUI에서 시작되며 합성기 레이어를 포함하는 몰입형 공간을 만듭니다

    레이어는 렌더링 루프에서 사용할 레이어 렌더러 객체를 제공합니다 레이어 렌더러에서 프레임을 쿼리합니다 각 프레임에서 드로어블을 가져오죠, 드로어블에는 콘텐츠를 렌더링하는 데 사용하는 텍스처가 포함되어 있습니다 Metal 몰입형 앱을 이미 만든 경우 렌더링된 프레임마다 단일 드로어블을 쿼리합니다 올해 Compositor Services에는 드로어블 배열을 반환하는 새 쿼리 드로어블 기능이 있습니다 시스템 컨텍스트에 따라 배열에는 1개 또는 2개의 드로어블이 있죠 대부분의 경우 1개의 드로어블만 가져옵니다 그러나 Reality Composer Pro를 사용해 고화질의 동영상을 녹화할 때마다 프레임은 2개의 드로어블을 반환합니다 새 대상 속성을 사용해 드로어블을 식별할 수 있습니다 Vision Pro 디스플레이용으로는 .builtIn 값이 있고 녹화용으로는 .capture 값이 있습니다 고화질 비디오를 캡처하는 방법은 개발자 문서를 살펴보세요

    이 render frame 함수는 여러분에게 익숙할 것입니다 다음 프레임을 쿼리하고 장면 상태를 업데이트한 후 query drawable 함수를 호출합니다 그 후에는 입력에 가장 적절한 시점을 기다립니다 그리고 장면을 드로어블에 렌더링합니다

    이제 query drawable을 query drawables로 바꿉니다 배열이 비어 있지 않은지 확인하고 모든 드로어블에 장면을 출력하죠

    Xcode에는 몰입형 Metal 앱을 만드는 방법을 보여주는 편리한 템플릿이 포함되어 있어 매우 좋은 출발점이 되어 줍니다 확인하려면 visionOS 앱용 새 Xcode 프로젝트를 시작하고 몰입형 Space Renderer 팝업 메뉴에서 Metal 4를 선택하세요

    올해에도 Metal 3은 완벽하게 지원됩니다 Xcode 템플릿에서 “Metal” 옵션을 선택해 사용할 수 있죠

    최신 Metal 버전을 도입하는 방법은 “Discover Metal 4”에서 확인할 수 있습니다 새 query drawables 함수를 도입하면 올해의 새 기능을 모두 사용할 수 있습니다 장면의 상호작용 객체에 호버 효과를 추가하는 것처럼 말이죠

    이 기능으로 앱 사용자는 어떤 객체가 상호작용하는지 확인하고 동작의 대상을 예상할 수 있습니다 시스템은 사용자가 보는 객체를 동적으로 강조 표시합니다 예를 들어 퍼즐 게임은 플레이어가 선택하는 조각을 강조할 수 있죠

    여러 개의 3D 객체가 포함된 장면을 렌더링하는 앱을 상상해 보세요, 그 중 일부만 상호작용할 수 있죠 장면에서 상호작용 객체만 호버 효과가 적용되도록 하고 싶습니다 객체가 상호작용하지 않으면 추적되지 않고 호버 효과도 없습니다 그래서 저는 보통 렌더링합니다 반면에 객체가 상호작용하면 드로어블에 새 추적 영역을 추가합니다 등록하는 추적 영역에 고유한 객체 식별자를 할당해야 하는 점에 유의하세요

    그런 다음 객체에 호버 효과가 있어야 하는지 확인합니다 그렇지 않다면 추적 영역 렌더링 값을 드로우합니다 내 객체에 대한 핀치 동작을 감지하는 데 유용합니다 그러나 객체에 호버 효과가 있는 경우 렌더링 전에 추적 영역에서 호버 효과를 구성합니다

    코드에서 호버 효과를 사용하도록 레이어 렌더러를 구성해야 합니다 8비트 픽셀 형식을 사용하도록 추적 영역 텍스처를 설정합니다 최대 255개의 동시 상호작용 객체를 지원합니다 레이어 기능이 형식을 지원되는지 확인하고 구성에 설정합니다

    추적 영역 등록 코드에서 객체가 상호작용하는지 확인하고 그렇다면 객체 식별자의 드로어블에서 새 추적 영역을 등록합니다 객체의 수명 주기 동안 고유 식별자를 추적해야 합니다 그런 다음 객체에 호버 효과가 있는지 확인합니다 그렇다면 .automatic 속성을 사용해 추가합니다 즉, 사용자가 객체를 볼 때 시스템이 자동으로 호버 효과를 추가합니다 마지막으로 렌더링합니다

    Compositor Services로 드로어블은 앱이 콘텐츠를 렌더링하는 데 사용하는 다양한 텍스처를 제공합니다

    앱의 사용자에게 보여지는 색상 텍스처에 익숙하실 수도 있습니다 심도 텍스처는 어두운 색상일수록 뷰어로부터 더 먼 객체를 나타냅니다 시스템은 뷰어가 장면을 움직일 때 이 텍스처를 사용해 표시되는 콘텐츠의 정확도를 높입니다 올해는 장면에서 여러 상호작용 지역을 정의하는 추적 영역 텍스처도 있습니다 드로어블은 색상과 심도 텍스처를 제공합니다 이제 새 추적 영역 텍스처도 쿼리할 수 있습니다 그 안에 상호작용 객체에 해당하는 고유 영역을 드로우하죠 호버 효과를 사용하면 누군가가 장면의 상호작용 객체를 보면 시스템이 추적 영역 텍스처를 사용해 해당 지역을 찾고 일치하는 색상 텍스처 부분에 호버 효과를 적용합니다 여기서도 구성한 추적 영역이 포함된 object render 함수가 있죠 추적 영역 텍스처를 렌더링하려면 시스템에서 계산한 렌더링 값이 필요합니다, 이 값을 저장하기 위해 로컬 변수를 선언하고 해당 추적 영역에서 가져옵니다 객체가 상호작용하지 않으면 기본 nil 렌더링 값을 사용할 수 있죠 이 값을 draw 함수에 전달하고 프래그먼트 셰이더에 보냅니다

    셰이더 코드를 살펴보겠습니다

    프래그먼트 셰이더 출력에는 추적 영역 렌더링 값이 있으며, 인덱스 1의 색상 첨부에 매핑되어 있죠 거기서 추적 영역 텍스처를 설정했습니다 렌더링 값을 유니폼 구조에 바인딩했습니다 그리고 색상 값과 함께 셰이더 출력으로 반환합니다 이제 앱에 상호작용 호버 효과가 추가되었습니다 멀티샘플 안티 앨리어싱 또는 MSAA 사용 시 유의사항이 하나 더 있죠

    이 기술은 중간 단계의 고해상도 텍스처를 렌더링한 후 샘플링 영역에 걸쳐 색상 값을 평균화합니다 이것은 보통 대상 텍스처 저장 동작에서 multisample resolve 옵션을 사용해 수행됩니다 추적 영역 텍스처는 색상 픽셀을 분석하는 방식과 동일하게 분석할 수 없습니다 그렇게 하면 렌더링 값이 평균화되어서 추적 영역에 해당하지 않는 유효하지 않은 스칼라가 생성됩니다 색상에 multisample resolve를 사용하는 경우 추적 영역 텍스처에 대한 맞춤형 타일 확인자를 구현해야 합니다 don't care store 옵션과 맞춤형 타일 렌더링 파이프라인을 사용해 이 작업을 수행할 수 있습니다 좋은 전략은 MSAA 소스 텍스처의 샘플링 영역에서 가장 자주 표시되는 렌더링 값을 선택하는 것입니다 호버 효과에 대한 자세한 설명과 MSAA와 함께 사용하는 방법은 개발자 문서의 “Metal 몰입형 앱에서 호버 효과 렌더링”을 참고하세요

    추적 영역을 사용하면 이전보다 쉬운 방식으로 앱이 객체 상호작용을 처리할 수 있습니다 이제 공간 이벤트에는 null 가능 추적 영역 식별자가 포함됩니다 이 식별자를 사용해 장면 객체와 일치하는지 확인합니다 대상 객체를 찾으면 해당 객체에 대한 작업을 수행할 수 있습니다

    호버 효과를 통해 앱의 상호작용성을 향상시켰습니다 앱 사용자는 실행 가능한 객체와 핀치할 때 활성화될 객체를 명확하게 확인할 수 있습니다 그러면 그 어느 때보다 입력 이벤트 처리가 쉬워집니다! 이제 이전보다 더 높은 정확성으로 콘텐츠를 드로우할 수 있습니다 동적 렌더링 품질을 통해 콘텐츠 해상도를 조정할 수 있죠 장면의 복잡성에 따라 달라집니다

    먼저, 포비티드 렌더링이 어떻게 작동하는지 요약해보겠습니다 표준 비포비티드 텍스처에서 표면 전체에 픽셀이 균일하게 분포됩니다 포비티드 렌더링을 사용하면 중심 부분에 픽셀 밀도가 더 높은 텍스처에 드로우하도록 시스템이 지원합니다 이런 방식으로 앱은 계산과 전력 리소스를 사용해 콘텐츠 품질을 높입니다 사용자가 보고 있을 가능성이 가장 높은 위치에서 말이죠 올해는 포비티드 렌더링의 역동 적인 품질을 활용할 수 있습니다 이제 앱에서 렌더링된 프레임의 품질을 제어할 수 있습니다 먼저 앱에 적합한 최대의 렌더링 품질을 지정해야 합니다 그러면 앱의 렌더링 세션에 대한 상한선이 설정됩니다 그런 다음 표시한 콘텐츠 유형에 따라 선택한 범위 내에서 런타임 품질을 조정할 수 있습니다

    렌더링 품질을 높일수록 텍스처의 높은 관련성 영역이 확장되어 전체 텍스처 크기가 커집니다 참고로 품질을 높이면 앱이 더 많은 메모리와 전력을 사용하게 됩니다 텍스트나 사용자 인터페이스 요소를 렌더링하는 경우 더 높은 렌더링 품질을 설정하는 것이 좋습니다 복잡한 3D 장면을 표시하면 컴퓨팅 리소스로 제한될 수 있죠 앱이 원활하게 실행되려면 고품질 비주얼과 앱이 사용하는 전력량 사이에 균형을 맞춰야 합니다

    도구를 사용해 앱의 실시간 성능을 분석하고 Metal 디버거를 사용해 자세히 살펴보고 Metal 코드와 셰이더를 최적화할 수 있습니다 가장 복잡한 장면으로 앱을 프로파일링해 프레임을 일정한 속도로 렌더링할 수 있는 충분한 시간이 있는지 확인해야 합니다

    개발자 문서를 확인해 Metal 렌더링 앱의 최적화 방법에 대해 자세히 알아보세요

    이 코드 예제에서 앱을 프로파일링했고 앱 메뉴를 80% 품질 수준으로 렌더링하기로 했습니다 이렇게 하면 텍스트가 더 선명해 보입니다 복잡한 장면이므로 60% 품질로 게임 세계를 렌더링하려고 합니다 또한 최고 렌더링 품질이 적용된 계산 속성을 추가했으며 사용할 예정입니다 제 레이어 구성을 말씀드릴게요 동적 렌더링 품질은 포비에이션과만 사용될 수 있어서 활성화되어 있는지 확인합니다 그런 다음 최고 렌더링 품질을 계산된 속성의 값으로 설정합니다 콘텐츠에 적합한 최소값으로 설정해야 합니다 그렇지 않으면 앱이 불필요하게 많은 메모리를 사용하게 됩니다

    새 장면을 로드할 때 adjust render quality 함수를 호출합니다 렌더링 품질 조정은 포비에이션이 활성화된 경우에만 가능합니다 장면 유형을 전환하고 렌더링 품질을 그에 맞게 조정합니다

    품질 값 간의 전환은 즉시 되지 않고 약간의 시간이 필요합니다 이 시스템은 전환을 원활하게 처리합니다

    동적 렌더링 품질을 통해 세부적인 장면이 돋보일 것입니다 렌더링된 장면의 해상도가 높으면 세밀한 부분이 더 명확하게 표현됩니다 매우 복잡한 장면의 품질을 더 낮춰야 할 수도 있습니다 이제 콘텐츠에 맞게 앱의 렌더링 품질을 조정할 수 있습니다!

    올해부터 점진적 몰입형 포털 내에서 새 Metal 앱을 렌더링할 수 있습니다 점진적 몰입형 스타일을 사용해 앱 사용자는 Digital Crown을 돌려 몰입형 수준을 제어할 수 있습니다 이 모드는 실제 환경에 기반을 두어 복잡하고 움직임이 많은 장면을 보다 편안하게 볼 수 있도록 도움을 줍니다 점진적 몰입형 모드에서 Metal 앱을 볼 때 시스템은 현재 몰입형 수준의 콘텐츠만 렌더링합니다

    이 장면은 완전히 몰입형 상태로 렌더링한 게임의 예입니다 사용자가 Digital Crown을 조정한 후 동일한 장면이 부분 몰입형 상태로 렌더링된 모습입니다 두 장면을 비교하고 포털 외부에서 강조표시된 영역을 렌더링하지 않음으로써 컴퓨팅 능력의 소모를 줄일 수 있습니다 해당 부분이 보이지 않습니다 그러므로 렌더링할 필요가 없죠 새 API를 사용하면 시스템이 계산한 포털 스텐실로 콘텐츠를 가릴 수 있습니다 이 흰색 타원은 해당 포털 스텐실을 표시합니다 스텐실 버퍼는 렌더링된 장면에 마스크 기능을 합니다 이 기능을 통해 포털 내부의 콘텐츠만 렌더링할 수 있습니다 렌더링 중인 장면의 가장자리가 아직 매끄럽지 않습니다 페이드는 명령 버퍼의 마지막 단계로, 시스템에 의해 적용되며 결과적으로 사용자가 보게 되는 장면이 생성됩니다

    불필요한 콘텐츠 렌더링을 피하기 위해 스텐실을 사용하려면 먼저 합성기 레이어를 구성하세요 원하는 스텐실 형식이 레이어의 기능에서 지원되는지 확인한 후 구성에 설정합니다 포털 스텐실 마스크를 적용하려면 명령 버퍼를 사용해 드로어블에 렌더링 컨텍스트를 추가해야 합니다 스텐실에 마스크를 드로우하면 보이지 않는 픽셀이 렌더링되지 않습니다 명령 인코더를 직접 종료하는 대신 렌더링 컨텍스트를 통해 인코딩을 종료해야 합니다 그러면 포털 효과가 여러분의 콘텐츠에 효율적으로 적용됩니다 앱에서 SwiftUI로 몰입형 공간을 만들고 새 옵션으로 점진적 몰입형 스타일을 목록에 추가합니다 앱 사용자는 점진적 스타일과 전체 스타일을 전환할 수 있죠 이어서 레이어를 구성합니다 먼저, 점진적 몰입형 스타일은 계층화된 레이아웃에만 작동하는 점에 유의하세요 원하는 스텐실 형식을 픽셀당 최대 8비트로 지정합니다 기능이 이 형식을 지원하는지 확인한 후 구성에 설정합니다 MSAA를 사용하지 않으므로 샘플 수를 1로 설정합니다 해당 기술을 사용하는 경우 MSAA 샘플링 수로 설정합니다

    렌더러에서 렌더링 컨텍스트를 드로어블에 추가합니다 렌더링 명령에 사용할 동일한 명령 버퍼를 전달합니다 그런 다음 스텐실 첨부에서 포털 마스크를 드로우합니다 다른 스텐실 작업에서는 사용하지 않는 스텐실 값을 선택했습니다 렌더링 인코더에서 스텐실 참조 값을 설정합니다 렌더러가 현재 몰입형 수준 밖의 영역을 드로우하지 않습니다 장면 렌더링 후 드로어블 렌더링 컨텍스트에서 인코딩 종료 방식에 주목합니다

    점진적 몰입형 스타일을 사용하는 렌더러의 작동 예를 보려면 visionOS Metal 앱 템플릿에서 점진적 옵션을 선택하세요 이로써 포털 스타일의 Metal 앱을 빌드할 수 있습니다

    마지막으로 macOS 공간 렌더링을 살펴보겠습니다

    지금까지 Vision Pro에서 네이티브 몰입형 경험 빌드에 대해 말씀드렸습니다, 올해는 Mac의 성능을 사용해 몰입형 콘텐츠를 Vision Pro로 직접 렌더링하고 스트리밍할 수 있습니다 이 방법으로 기존 Mac 앱에 몰입형 경험을 추가할 수 있죠

    예를 들어, 3D 모델링 앱은 Vision Pro에서 장면을 직접 미리볼 수 있습니다 아니면 처음부터 몰입형 macOS 앱을 빌드할 수도 있습니다 그러면 Vision Pro의 전력 사용에 제한받지 않고 높은 계산 능력이 요구되는 복잡한 몰입형 경험을 만들 수 있습니다 Mac 앱에서 원격 몰입형 세션 시작하기는 매우 쉽습니다 macOS에서 Immersive Space를 열면 Vision Pro에서 연결을 허용하라는 메시지가 표시됩니다

    허용하면 Mac에서 렌더링된 몰입형 콘텐츠를 볼 수 있습니다

    일반적인 Mac 앱은 SwiftUI 또는 AppKit에서 빌드됩니다 두 프레임워크 중 하나를 사용해 Windows를 만들고 표시합니다 시스템은 Core Animation으로 윈도우 콘텐츠를 렌더링합니다 다양한 macOS 프레임워크를 도입해 앱 기능을 구현할 수 있죠 시스템은 사용자의 콘텐츠를 Mac 디스플레이에 표시합니다 Mac에서 지원하는 몰입형 경험을 빌드하려면, 익숙한 프레임워크를 그대로 사용해 몰입형 visionOS 앱을 만듭니다 먼저 새 Remote Immersive Space 장면 유형과 함께 SwiftUI를 사용하죠 그런 다음 Compositor Services 프레임워크를 채택합니다 ARKit과 Metal을 사용해 콘텐츠를 배치하고 렌더링합니다 그리고 시스템은 몰입형 장면을 Vision Pro에 직접 표시합니다 macOS Remote Immersive Space는 네이티브 visionOS 앱과 같이 Compositor Layer와 ARKit Session을 호스팅합니다 그리고 Vision Pro 디스플레이와 센서에 완벽하게 연결됩니다 ARKit Session을 visionOS에 연결하기 위해 세션 이니셜라이저에 전달하는 새 원격 기기 식별자 SwiftUI 환경 객체가 있습니다 Mac 몰입형 앱은 이렇게 구성되어 있습니다

    합성기 콘텐츠가 포함된 새 원격 몰입형 공간을 정의합니다 합성기 레이어 사용 방법을 보여드리겠습니다 Mac에서는 점진적 스타일과 완전 몰입형 스타일만 지원됩니다 Mac 앱의 인터페이스에서 새 supports remote scenes 환경 변수를 사용해 Mac에 이 기능이 있는지 확인합니다 원격 장면이 지원되지 않는 경우 메시지를 표시하도록 UI를 맞춤화할 수 있습니다 원격 장면이 지원되고 아직 몰입형 공간을 열지 않은 경우에는 실행할 수 있습니다 앱의 마지막 부분은 합성기 콘텐츠입니다 여기에는 합성기 레이어와 ARKit 세션이 있습니다 visionOS에서와 같은 방식으로 합성기 레이어를 만들고 사용하죠 새 원격 기기 식별자 SwiftUI 환경 객체에 액세스하고, ARKit 세션 이니셜라이저에 전달합니다 그러면 Mac의 ARKit 세션이 Vision Pro에 연결됩니다 마지막으로 일반적인 Metal 몰입형 앱에서처럼 렌더링 루프를 시작하죠

    ARKit과 월드 추적 제공자는 이제 macOS에서 사용 가능합니다 이를 통해 공간 내 Vision Pro 위치를 쿼리할 수 있습니다 네이티브 몰입형 앱에서와 같이 기기 위치를 사용해 렌더링하기 전에 장면과 드로어블을 업데이트합니다 macOS 공간 앱은 Mac에 연결된 입력 기기를 지원합니다 키보드와 마우스 제어를 사용할 수 있습니다 또는 게임패드를 연결하고 Game Controller 프레임워크를 사용해 입력을 처리할 수 있습니다 또한 레이어 렌더러에서 ‘onSpatialEvent’ 수정자를 사용해 몰입형 장면의 상호작용 요소에 핀치 이벤트를 사용할 수 있습니다

    올해부터 기존 AppKit나 UIKit 앱에서 SwiftUI 장면을 만들 수도 있죠 기존 Mac 앱에 새 몰입형 경험을 추가하는 좋은 방법입니다 자세한 내용은 “SwiftUI의 새로운 기능”에서 확인하세요

    렌더링 엔진은 보통 C나 C++로 구현됩니다 제가 설명한 모든 API는 C에서도 같은 기능을 제공합니다 Compositor Services 프레임워크의 C 타입은 ‘cp’ 접두사로 시작합니다 Core Foundation과 같은 익숙한 C 라이브러리와 유사한 패턴과 규칙을 사용합니다 ARKit의 경우 cDevice 속성은 C 호환 원격 기기 식별자를 제공합니다 create with device 함수를 사용해 C 프레임워크에 전달하고 ARKit Session을 초기화할 수 있습니다 이제 Vision Pro에서 몰입형 콘텐츠를 구동하기 위해 Mac을 사용할 모든 준비가 완료되었습니다

    저는 여러분이 새 기능을 통해 몰입형 앱을 어떻게 발전시키실지 정말 기대가 됩니다 상호작용, 높은 정확성 새 점진적 몰입형 스타일을 구현할 수 있습니다 새 macOS 공간 기능을 어떻게 활용할지 매우 궁금합니다 몰입형 앱을 한 단계 더 발전시키는 방법을 알아보려면 “visionOS에서 SwiftUI로 장면 설정하기”를 확인하세요 기타 플랫폼 개선 사항의 개요를 보려면 “visionOS의 새로운 기능”을 참고하세요 시청해 주셔서 감사합니다!

    • 0:01 - Scene render loop

      // Scene render loop
      
      extension Renderer {
          func renderFrame(with scene: MyScene) {
              guard let frame = layerRenderer.queryNextFrame() else { return }
      
              frame.startUpdate()
              scene.performFrameIndependentUpdates()
              frame.endUpdate()
      
              let drawables = frame.queryDrawables()
              guard !drawables.isEmpty else { return }
      
              guard let timing = frame.predictTiming() else { return }
              LayerRenderer.Clock().wait(until: timing.optimalInputTime)
              frame.startSubmission()
              scene.render(to: drawable)
              frame.endSubmission()
          }
      }
    • 5:54 - Layer configuration

      // Layer configuration
      
      struct MyConfiguration: CompositorLayerConfiguration {
          func makeConfiguration(capabilities: LayerRenderer.Capabilities,
                                 configuration: inout LayerRenderer.Configuration) {
              // Configure other aspects of LayerRenderer
      
              let trackingAreasFormat: MTLPixelFormat = .r8Uint
              if capabilities.supportedTrackingAreasFormats.contains(trackingAreasFormat) {
                  configuration.trackingAreasFormat = trackingAreasFormat
              }
          }
      }
    • 7:54 - Object render function

      // Object render function
      
      extension MyObject {
          func render(drawable: Drawable, renderEncoder: MTLRenderCommandEncoder) {
              var renderValue: LayerRenderer.Drawable.TrackingArea.RenderValue? = nil
              if self.isInteractive {
                  let trackingArea = drawable.addTrackingArea(identifier: self.identifier)
                  if self.usesHoverEffect {
                      trackingArea.addHoverEffect(.automatic)
                  }
                  renderValue = trackingArea.renderValue
              }
      		self.draw(with: commandEncoder, trackingAreaRenderValue: renderValue)
          }
      }
    • 8:26 - Metal fragment shader

      // Metal fragment shader
      
      struct FragmentOut
      {
          float4 color [[color(0)]];
          uint16_t trackingAreaRenderValue [[color(1)]];
      };
      
      fragment FragmentOut fragmentShader( /* ... */ )
      {
          // ...
      
          return FragmentOut {
              float4(outColor, 1.0),
              uniforms.trackingAreaRenderValue
          };
      }
    • 10:09 - Event processing

      // Event processing
      
      extension Renderer {
          func processEvent(_ event: SpatialEventCollection.Event) {
             let object = scene.objects.first {
                 $0.identifier == event.trackingAreaIdentifier
             }
             if let object {
                 object.performAction()
             }
         }
      }
    • 13:08 - Quality constants

      // Quality constants
      
      extension MyScene {
          struct Constants {
              static let menuRenderQuality: LayerRenderer.RenderQuality = .init(0.8)
              static let worldRenderQuality: LayerRenderer.RenderQuality = .init(0.6)
              static var maxRenderQuality: LayerRenderer.RenderQuality { menuRenderQuality }
          }
      }
    • 13:32 - Layer configuration

      // Layer configuration
      
      struct MyConfiguration: CompositorLayerConfiguration {
          func makeConfiguration(capabilities: LayerRenderer.Capabilities,
                                 configuration: inout LayerRenderer.Configuration) {
             // Configure other aspects of LayerRenderer
      
             if configuration.isFoveationEnabled {
                 configuration.maxRenderQuality = MyScene.Constants.maxRenderQuality
             }
      }
    • 13:57 - Set runtime render quality

      // Set runtime render quality
      
      extension MyScene {
          var renderQuality: LayerRenderer.RenderQuality {
              switch type {
              case .world: Constants.worldRenderQuality
              case .menu: Constants.menuRenderQuality
              }
          }
      }
      
      extension Renderer {
          func adjustRenderQuality(for scene: MyScene) {
              guard layerRenderer.configuration.isFoveationEnabled else {
                  return;
              }
              layerRenderer.renderQuality = scene.renderQuality
          }
      }
    • 16:58 - SwiftUI immersion style

      // SwiftUI immersion style
      
      @main
      struct MyApp: App {
          @State var immersionStyle: ImmersionStyle
      
          var body: some Scene {
              ImmersiveSpace(id: "MyImmersiveSpace") {
                  CompositorLayer(configuration: MyConfiguration()) { @MainActor layerRenderer in
                      Renderer.startRenderLoop(layerRenderer)
                  }
              }
              .immersionStyle(selection: $immersionStyle, in: .progressive, .full)
          }
      }
    • 17:12 - Layer configuration

      // Layer configuration
      
      struct MyConfiguration: CompositorLayerConfiguration {
          func makeConfiguration(capabilities: LayerRenderer.Capabilities,
                                 configuration: inout LayerRenderer.Configuration) {
              // Configure other aspects of LayerRenderer
              
              if configuration.layout == .layered {
                  let stencilFormat: MTLPixelFormat = .stencil8 
                  if capabilities.drawableRenderContextSupportedStencilFormats.contains(
                      stencilFormat
                  ) {
                      configuration.drawableRenderContextStencilFormat = stencilFormat 
                  }
                  configuration.drawableRenderContextRasterSampleCount = 1
              }
          }
      }
    • 17:40 - Render loop

      // Render loop
      
      struct Renderer {
          let portalStencilValue: UInt8 = 200 // Value not used in other stencil operations
      
          func renderFrame(with scene: MyScene,
                           drawable: LayerRenderer.Drawable,
                           commandBuffer: MTLCommandBuffer) {
              let drawableRenderContext = drawable.addRenderContext(commandBuffer: commandBuffer)
              let renderEncoder = configureRenderPass(commandBuffer: commandBuffer)
              drawableRenderContext.drawMaskOnStencilAttachment(commandEncoder: renderEncoder,
                                                                value: portalStencilValue)
              renderEncoder.setStencilReferenceValue(UInt32(portalStencilValue))
              
              scene.render(to: drawable, renderEncoder: renderEncoder)
      
              drawableRenderContext.endEncoding(commandEncoder: commandEncoder)
              drawable.encodePresent(commandBuffer: commandBuffer)
          }
      }
    • 20:55 - App structure

      // App structure
      
      @main
      struct MyImmersiveMacApp: App {
          @State var immersionStyle: ImmersionStyle = .full
      
          var body: some Scene {
              WindowGroup {
                  MyAppContent()
              }
      
              RemoteImmersiveSpace(id: "MyRemoteImmersiveSpace") {
                  MyCompositorContent()
              }
              .immersionStyle(selection: $immersionStyle, in: .full, .progressive)
         }
      }
    • 21:14 - App UI

      // App UI
      
      struct MyAppContent: View {
          @Environment(\.supportsRemoteScenes) private var supportsRemoteScenes
          @Environment(\.openImmersiveSpace) private var openImmersiveSpace
          @State private var spaceState: OpenImmersiveSpaceAction.Result?
      
          var body: some View {
              if !supportsRemoteScenes {
                  Text("Remote SwiftUI scenes are not supported on this Mac.")
              } else if spaceState != nil {
                  MySpaceStateView($spaceState)
              } else {
                  Button("Open remote immersive space") {
                      Task {
                          spaceState = await openImmersiveSpace(id: "MyRemoteImmersiveSpace")
                      }
                  }
              }
          }
      }
    • 21:35 - Compositor content and ARKit session

      // Compositor content and ARKit session
      
      struct MyCompositorContent: CompositorContent {
          @Environment(\.remoteDeviceIdentifier) private var remoteDeviceIdentifier
      
          var body: some CompositorContent {
              CompositorLayer(configuration: MyConfiguration()) { @MainActor layerRenderer in
                  guard let remoteDeviceIdentifier else { return }
                  let arSession = ARKitSession(device: remoteDeviceIdentifier)
                  Renderer.startRenderLoop(layerRenderer, arSession)
              }
          }
      }
    • 23:17 - C interoperability

      // Swift
      let remoteDevice: ar_device_t = remoteDeviceIdentifier.cDevice
Renderer.start_rendering(layerRenderer, remoteDevice)
      
      // C
      void start_rendering(cp_layer_renderer_t layer_renderer, ar_device_t remoteDevice) {
    ar_session_t session = ar_session_create_with_device(remoteDevice);
    // ...
}
    • 0:00 - 서론
    • visionOS의 Metal 렌더링은 Compositor Services와 함께 올해 흥미로운 새 기능을 선보입니다. 상호작용이 가능한 객체의 호버 효과, 렌더링된 콘텐츠의 동적 렌더링 품질, 완전히 새로워진 점진적 몰입형 스타일, macOS에서 바로 Vision Pro로 몰입형 콘텐츠를 렌더링하는 기능 등이 이러한 새로운 기능에 포함됩니다.

    • 1:58 - 새 렌더링 루프 API
    • 올해, visionOS의 렌더링 루프에 중요한 변화가 있었습니다. 이제 queryDrawables 객체는 단일 드로어블을 반환하는 대신 하나 또는 두 개의 드로어블 배열을 반환합니다. 두 번째 드로어블은 Reality Composer Pro로 고품질 비디오를 기록할 때마다 표시됩니다. Xcode에서 시작하는 데 도움이 되는 템플릿을 확인하세요. Metal 및 Metal 4가 모두 지원됩니다.

    • 4:21 - 호버 효과
    • 새로운 렌더링 루프 API를 채택하게 되면 상호작용이 가능한 객체에 호버 효과를 구현할 수 있습니다. 호버 효과를 사용하면 사람들은 상호작용하는 객체를 확인할 수 있고 사람들의 행동 대상을 예상할 수 있습니다. 시스템은 누군가 보는 객체를 동적으로 강조 표시합니다. 예를 들어, 퍼즐 게임은 플레이어가 선택하는 조각을 강조할 수 있습니다. 새로운 추적 영역 텍스처를 사용하면 장면에서 다양한 상호작용 영역을 정의하여 이를 구현할 수 있습니다. MSAA(멀티샘플 안티 앨리어싱)를 사용하는 경우, 염두에 두어야 할 몇 가지 추가 고려 사항이 있습니다.

    • 10:50 - 동적 렌더링 품질
    • 이제 이전보다 더 높은 정확성으로 콘텐츠를 드로우할 수 있습니다. 동적 렌더링 품질을 사용하면 장면의 복잡도에 따라 콘텐츠의 해상도를 조정할 수 있습니다. 시청자가 보고 있는 위치의 픽셀 밀도를 우선시하는 포비티드 렌더링을 기반으로 합니다. 최대 렌더링 품질을 설정한 다음 해당 범위 내에서 런타임 품질을 조정할 수 있습니다. 품질이 높을수록 텍스트와 UI 선명도는 개선되지만, 메모리와 전력 사용량이 늘어납니다. 품질과 성능 간의 균형을 맞추는 것이 중요합니다. Instruments 및 Metal 디버거와 같은 툴을 사용하여 적절한 균형을 찾으세요.

    • 14:44 - 점진적 몰입
    • 올해 새롭게 추가된 기능으로, 점진적으로 몰입형 포털 내에서 콘텐츠를 렌더링할 수 있습니다. 이를 통해 사람들은 Digital Crown을 회전시켜 몰입 수준을 제어합니다. 이를 통해 실제 환경에 익숙해지고 움직임이 있는 복잡한 장면을 시청할 때 더 편안하게 느낄 수 있습니다. 이를 구현하려면 시스템에 포털 경계 외부의 콘텐츠를 마스킹하기 위한 스텐실 버퍼를 제공하도록 요청합니다. 이 시스템은 포털의 가장자리에 페이드 효과를 적용하여 실제 환경과 렌더링된 환경 간 자연스럽게 전환하도록 합니다. 포털의 뷰 바깥 픽셀은 렌더링되지 않아 컴퓨팅 역량을 절약합니다. 구현 세부 사항이 공유됩니다.

    • 18:32 - macOS 공간 렌더링
    • macOS 공간 렌더링을 사용하면 Mac의 역량을 활용하여 몰입형 콘텐츠를 Apple Vision Pro로 직접 스트리밍할 수 있습니다. 이와 같은 새로운 기능을 사용하면 기존 Mac 앱을 실시간 3D 모델링 미리 보기와 같은 몰입형 경험으로 강화될 수 있습니다. 이제 ARKit과 worldTrackingProvider를 macOS에서 사용할 수 있습니다. 이를 통해 공간 내 Vision Pro의 위치를 쿼리할 수 있습니다. macOS RemoteImmersiveSpace는 네이티브 visionOS 앱처럼 CompositorLayer 및 ARKitSession을 호스팅합니다. Mac의 ARKit 세션을 Vision Pro에 연결하는 데 사용할 새로운 remoteDeviceIdentifier가 있습니다. 그리고 관련 API는 모두 C에서도 같은 기능을 제공합니다.

    • 23:51 - 다음 단계
    • Metal의 새로운 기능과 visionOS의 Compositor Services를 사용하면 앱과 게임에 더 나은 상호작용, 더 높은 정확성, 새로운 점진적 몰입형 스타일을 제공할 수 있습니다. 다음으로, ‘visionOS에서 SwiftUI로 장면 설정하기’ 및 ‘visionOS 26의 새로운 사항’을 확인하세요.

Developer Footer

  • 비디오
  • WWDC25
  • 몰입감 넘치는 앱을 위한 Metal 렌더링의 새로운 기능
  • 메뉴 열기 메뉴 닫기
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    메뉴 열기 메뉴 닫기
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    메뉴 열기 메뉴 닫기
    • 손쉬운 사용
    • 액세서리
    • 앱 확장 프로그램
    • App Store
    • 오디오 및 비디오(영문)
    • 증강 현실
    • 디자인
    • 배포
    • 교육
    • 서체(영문)
    • 게임
    • 건강 및 피트니스
    • 앱 내 구입
    • 현지화
    • 지도 및 위치
    • 머신 러닝 및 AI
    • 오픈 소스(영문)
    • 보안
    • Safari 및 웹(영문)
    메뉴 열기 메뉴 닫기
    • 문서(영문)
    • 튜토리얼
    • 다운로드(영문)
    • 포럼(영문)
    • 비디오
    메뉴 열기 메뉴 닫기
    • 지원 문서
    • 문의하기
    • 버그 보고
    • 시스템 상태(영문)
    메뉴 열기 메뉴 닫기
    • Apple Developer
    • App Store Connect
    • 인증서, 식별자 및 프로파일(영문)
    • 피드백 지원
    메뉴 열기 메뉴 닫기
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(영문)
    • News Partner Program(영문)
    • Video Partner Program(영문)
    • Security Bounty Program(영문)
    • Security Research Device Program(영문)
    메뉴 열기 메뉴 닫기
    • Apple과의 만남
    • Apple Developer Center
    • App Store 어워드(영문)
    • Apple 디자인 어워드
    • Apple Developer Academy(영문)
    • WWDC
    Apple Developer 앱 받기
    Copyright © 2025 Apple Inc. 모든 권리 보유.
    약관 개인정보 처리방침 계약 및 지침