스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
RealityKit 오디오로 공간 컴퓨팅 앱 향상하기
RealityKit 오디오로 공간 컴퓨팅 경험을 향상해 보세요. 공간 음향을 활용하여 생생하고 몰입감 넘치는 3D 경험을 만드는 방법을 소개합니다. 주변 오디오 및 잔향부터 3D 콘텐츠에 개성을 더하는 실시간 절차적 오디오(Procedural Audio)까지, 다양한 RealityKit 오디오 API를 활용하여 더욱 매력적인 앱을 만들어 보세요.
챕터
- 0:00 - Introduction
- 2:01 - Spatial audio
- 11:43 - Collisions
- 16:57 - Reverb presets
- 18:24 - Immersive music
- 20:41 - Mix groups
리소스
관련 비디오
WWDC24
-
다운로드
안녕하세요, 저는 James이며 RealityKit 오디오 팀에서 일하고 있습니다 오늘은 RealityKit의 새로운 오디오 API를 소개하고자 합니다 이 API는 공간 컴퓨팅 경험의 상호 작용 수준과 몰입감을 높여 주죠 제 동료 Yidi는 실제 환경 속에서 사용자가 가상 우주선을 조종할 수 있는 게임을 만들었습니다 실제 환경뿐만 아니라 맞춤형 가상 환경도 구현 가능하며 심지어는 포털을 통해 우주로 갈 수도 있습니다 이 앱에서는 새로운 SpatialTrackingService API를 사용하여 우주선을 조작하는 손 제스처를 유연하게 추적할 수 있고
힘 효과와 같은 새로운 물리 기능을 사용하여 갈수록 위험천만해지는 소행성 지대를 만들 수 있으며
물리 법칙이 적용된 연결 부위로 소중한 화물을 실은 트레일러를 부착해 너무 격렬하게 비행하면 흔들리게 할 수 있습니다 또한 새로운 포털 교차 구성요소를 사용하여 우주선이 포털을 통해 우주에 갔다가 돌아오게 할 수 있습니다
각각의 새로운 기능은 가상과 현실 사이의 역동적 상호 작용을 촉진합니다
‘iOS, macOS 및 visionOS용 RealityKit API 알아보기’ 세션을 확인하여 이 게임이 제작된 과정에 대해 자세히 알아보세요 이 세션에서는 이 게임의 몰입감과 상호 작용 수준을 더욱 높일 수 있도록 RealityKit 오디오 기능을 사용합니다 그 방법을 살펴봅시다
이 세션에 연결된 샘플 프로젝트를 다운로드하여 따라해 볼 수 있습니다 먼저 오디오 파일 재생과 실시간 생성 오디오를 사용하여 우주선에 공간 음향을 추가하겠습니다 다음으로는 가상 물체 간의 충돌과 가상 물체와 실제 물체 간의 충돌에 대한 공간 음향을 추가합니다 그런 다음 우주선에서 새로운 우주로 가는 몰입감 있는 환경에 리버브를 적용합니다 재생할 음악에 주변 오디오를 사용하여 분위기를 연출하고 마지막으로는 오디오 믹스 그룹을 사용하여 사용자가 자신의 취향대로 사운드를 조정할 수 있도록 합니다 우주선 본체의 오디오부터 알아보겠습니다 이 우주선에는 본체의 시각적 요소가 반영된 다층 사운드 디자인이 적용되어 있습니다
우주선에는 2개의 엔진이 있습니다 각 엔진에는 비행운 배기가스, 터빈이 있습니다 시각적 측면에서 비행운은 입자 효과입니다 스로틀의 오르내림과 관계없이 방출되는 효과죠 반면에 배기가스는 우주선의 스로틀을 올릴수록 길어집니다 터빈은 보이지 않으며 우주선 본체에 감싸져 있지만 사운드 디자인으로 안 보이는 물체의 존재감을 드러낼 수 있습니다 비행운의 경우 날카롭지 않은 소음이 재생되며 스로틀의 오르내림과 상관없이 일정한 음량이 유지됩니다 배기가스의 경우 더 박력 있는 소음이 재생되며 스로틀을 올릴수록 음량이 커집니다 터빈의 경우 사운드가 동적으로 생성됩니다 매개변수가 스로틀에 따라 달라지죠 소리를 잠깐 들어 보겠습니다
먼저 비행운 오디오 재생 소리를 듣겠습니다 비행운이 사용자에게서 멀어지면 소리가 조금 더 조용하고 탁해지죠 다음으로, 배기구를 열고 스로틀을 조작하겠습니다 스로틀을 올리면 소리가 어떻게 커지는지 들어보세요
이 효과를 어떻게 코딩하는지 알아봅시다
앱 번들에서 비행운 오디오 파일을 로드한 후 Entity.playAudio를 사용해 이 파일을 엔진 엔티티에서 바로 재생합니다
RealityKit에서 재생되는 오디오에는 공간 음향이 기본 적용됩니다 단 두어 줄의 코드로 오디오가 6DoF로 렌더링되어 음량과 음색이 사용자에 따라 또는 오디오 소스의 이동이나 회전에 따라 업데이트됩니다 물리 기반의 거리 감쇠 효과가 적용되어 오디오 소스가 사용자에게서 멀어지면 자연스럽게 조용해집니다
마지막으로 공간 음향 소스에는 리버브가 적용됩니다 리버브는 실제 환경에서 실시간으로 시뮬레이션되죠
한 가지 유의할 점은 공간 기반으로 재생되는 오디오는 공간화되기 전에 단일 Mono 채널로 믹스다운된다는 것입니다 모노 파일을 작성하는 것이 좋습니다 예상치 못한 믹스다운 아티팩트가 공간 렌더링 시 추가되지 않도록 말이죠
비행운 오디오 파일은 짧으며 튀어나오는 소리나 클릭 없이 매끄럽게 반복되도록 작성됩니다 AudioFileResource.Configuration에서 shouldLoop 속성을 설정하여 이 짧은 파일을 지속적으로 발생하는 소음인 것처럼 재생할 수 있습니다
두 엔진에 모두 짧은 소리가 반복되는 똑같은 파일을 재생하고 있습니다 shouldRandomizeStartTime 속성을 설정하여 두 엔진 모두에서 소리가 나면 파일의 다양한 위치에서 재생이 시작됩니다 반복 재생과 재생 시작 시간 무작위화를 함께 사용하면 풍부한 오디오 경험을 제공하면서 리소스를 효율적으로 사용할 수 있습니다 시끄러운 비행운 오디오 파일은 매끄럽게 반복되도록 작성되는데 이는 재생이 갑자기 시작된다는 뜻이기도 합니다 오디오 페이드인으로 오디오 경험을 개선할 수 있습니다 먼저 AudioPlaybackController를 저장한 후 참고합니다 이것은 Entity.playAudio 메서드에서 반환되죠
AudioPlaybackController는 엔티티에서 재생되는 오디오 소스의 특정 사운드 이벤트 인스턴스에 사용되는 리모컨 같은 역할을 합니다 Entity.playAudio 또는 Entity.prepareAudio를 호출할 때마다 AudioPlaybackController가 반환되므로 필요에 따라 이를 저장한 후 구성을 하려는 경우나 런타임 시 재생, 일시 정지, 중지 등 전송 제어가 필요한 경우 참고하세요 gain을 -.infinity로 설정합니다 AudioPlaybackController의 gain 속성에는 상대 데시벨 단위가 사용됩니다 여기서 -.infinity는 무음이며 0은 표준 음량입니다 모든 사운드는 최대 0데시벨로 조정됩니다
AudioPlayback Controller.fade(to:duration:) 메서드를 호출하여 1초간 0데시벨로 페이드인합니다
비행운 오디오가 재생되지만 사용자가 우주선을 조종할 때 자연스럽고 역동적인 소리를 내려고 합니다 예를 들어, 우주선의 엔진이 사용자 쪽을 향하고 있을 때는 엔진 사운드가 세세하게 들리고 엔진이 사용자의 반대편을 향하고 있을 때는 사운드가 감쇠되고 탁해져야 합니다 SpatialAudioComponent와 지향성으로 엔진의 공간 렌더링을 구성하겠습니다
먼저 audioSource만 나타내는 새로운 Entity()를 추가합니다 다음으로 audioSource를 엔진의 자식으로 추가한 후 engine이 아닌 audioSource에서 오디오를 재생합니다
audioSource 엔티티에 SpatialAudioComponent를 설정하고 SpatialAudioComponent에서는 지향성을 집중도가 0.25인 빛줄기 패턴으로 설정합니다
지향성은 오디오가 공간 음향 소스에서 전파되는 방식을 정의합니다 빛줄기 지향성 패턴은 0~1의 범위에서 작용하며 여기서 0은 전방향입니다 모든 사운드가 전 방향에서 똑같은 음색으로 전파되죠 1은 꽉 찬 빛줄기 패턴입니다 앞에서 뒤로 갈수록 사운드가 조용하고 탁해지죠
마지막으로는 audioSource를 회전하여 사운드가 배기가스가 나오는 엔진 입구에서 발생하게 합니다 이제 우주선이 회전하면 음량과 음색의 변화가 들립니다
SpatialAudioComponent를 사용하면 공간 음향 소스의 방향 특성을 설정할 수 있으며 엔티티에서 발생하는 모든 사운드 이벤트의 음량도 설정할 수 있습니다 구성요소 프로토콜을 준수하는 SpatialAudioComponent를 사용하여 간편하게 오디오 소스의 음량을 맞춤형 시스템의 컨텍스트에 맞게 동적으로 조절할 수 있습니다 예를 들어, 단일 throttle 값에 따라 우주선의 물리적 특성, 그래픽, 오디오가 결정됩니다 throttle이 0이면 배기음이 무음이 되며 throttle이 1이면 음량이 최대가 되므로 배기음은 이 두 극단 값 사이에서 보간됩니다 업데이트할 때마다 throttle 값을 읽는 맞춤형 오디오 시스템이 있으며 이 시스템에서는 선형인 throttle 값에서 로그 데시벨로 값을 매핑한 후 SpatialAudioComponent의 gain 속성을 데시벨 값으로 업데이트합니다 우주선 오디오가 점점 더 역동적으로 구현되고 있습니다 우주선이 비행할 때 지향성 패턴으로 인해 변화하는 음량과 음색을 들어보고 스로틀을 올렸다가 내렸을 때 시끄러워졌다가 조용해지는 배기음을 들어봅니다 파란색 불빛 뒤편에서 한 쌍의 터빈이 소리를 내죠 눈에 보이지는 않지만 회전이 빨라지고 느려지는 소리가 들립니다 스로틀을 올리고 내릴 때마다 말입니다
격납고 보기에서 우주선의 터빈을 돌리면 사운드를 들어볼 수 있죠 스로틀에 따라 사운드의 주파수가 변하는 것을 확인할 수 있습니다
이를 어떻게 코딩하는지 알아보죠
먼저 엔진 중 하나를 나타내는 Entity를 취하는 메서드를 사용한 후 Entity.playAudio 메서드의 변형을 호출합니다 이 Entity에서 오디오 소스를 재생하는 대신 콜백을 제공하겠습니다 오디오 시스템에서 렌더링되는 버퍼에 직접 샘플을 기록할 수 있도록 말이죠 Entity.playAudio의 이 변형은 AudioGeneratorController를 반환합니다 오디오를 계속 스트리밍하려면 AudioGeneratorController를 앱에서 유지해야 합니다
AudioGeneratorController로 작성되는 오디오 버퍼는 엔티티에서 재생되는 오디오 리소스처럼 동작합니다 공간 음향으로 기본 재생되지만 공간 음향, 주변 음향 채널 음향 구성요소를 엔티티에 구성하여 버퍼의 렌더링을 제어할 수 있습니다 오디오 파일을 재생할 때와 마찬가지로 말이죠
앱에는 AudioUnitTurbine이라는 맞춤형 audioUnit이 있습니다 이 audioUnit에는 렌더 블록에 실시간 안전 코드가 포함된 Objective-C 인터페이스가 있습니다 AudioUnitTurbine에는 0~1 범위의 단일 throttle 값이 있으며 audioUnit 구현부에서는 이 값을 사용하여 여러 오실레이터를 구동합니다 먼저, audioUnit을 인스턴스화합니다 다음으로 AudioGeneratorController 구성을 준비합니다 그런 다음 audioUnit의 출력용 format을 만들고 audioUnit.outputBusses에서 이를 설정합니다 audioUnit의 렌더 리소스를 할당하고 audioUnit.internalRenderBlock을 캡처합니다 다음으로 오디오 생성기 구성을 사용하여 AudioGeneratorController를 구성하고
마지막으로 오디오 생성기의 콜백에서 제공되는 오디오 데이터를 기반으로 audioUnit.internalRenderBlock을 호출합니다
AudioGeneratorController가 설정된 실시간 오디오를 사용하면 Apple에서 제공하는 Audio Unit 맞춤형 자체 Audio Units 또는 자체 오디오 엔진의 출력에서 오디오를 전달할 수 있습니다 이 오디오는 RealityKit과 visionOS에서 제공하는 광선 추적된 공간 음향에 의해 렌더링됩니다 사운드를 어떤 식으로 만들든 공유된 공간의 음향은 물론 시스템의 다른 사운드와 일관된 혼합, 프로그레시브 완전 몰입형 공간의 음향을 적용할 수 있습니다
RealityKit에서 재생되는 오디오에는 공간 음향이 기본 적용되며 기본 동작이 최대한 자연스럽도록 적절한 길이를 설정했습니다 물론 우주선이 비행하며 내는 소리는 현실과 비현실 사이에 있습니다 물리적 반응에 약간의 재미를 줄 수 있죠 예를 들어 우주선이 사용자에게 가까이 있으면 선실의 창문을 통해 나지막하게 흘러나오는 음악을 들을 수 있습니다 한번 들어 보시죠
기본 공간 음향 구성요소 매개변수를 사용하는 이 오디오 트랙을 재생하면 재생되는 소리는 항상 스테레오로 들립니다 공간 음향 구성요소의 거리 감쇠를 사용자화하여 원하는 효과를 구현할 수 있습니다
거리 감쇠를 사용자화하면 사용자가 소스에 아주 가까이 있는 경우에만 사운드가 들리게 할 수 있습니다 소스가 멀리 있는 경우에 사운드가 들리게 사용자화할 수도 있죠 롤오프 인수 1은 공간 음향 소스의 기본값으로 감쇠가 자연스럽습니다 롤오프 인수를 2로 설정하면 사운드가 자연스러운 상태의 2배 속도로 감쇠되며 롤오프 인수가 0.5면 사운드가 자연스러운 상태의 0.5배 속도로 감쇠됩니다 이제 오디오의 거리 감쇠를 구성해 보겠습니다
여기서는 distanceAttenuation을 롤오프 4로 구성하여 우주선이 사용자에게 아주 가까이 있을 때만 특정 사운드가 들리게 합니다 이 공간 음향 소스의 gain은 줄이겠습니다 사용자에게 아주 가까이 있을 때만 우주선의 음악이 들리도록 말이죠 다음으로는 콘텐츠의 엔티티가 서로 충돌할 때 사운드를 발생시키는 방법에 대해 살펴보겠습니다 먼저 소리를 들어 보겠습니다 우주선이 콘크리트 바닥, 나무 상자 금속 기둥 또는 유리와 부딪힐 때 주의해서 들어 보세요
우주선은 소행성과 같이 움직이는 가상의 물체는 물론 스튜디오 환경의 시멘트 바닥 나무 육면체, 금속 기둥과 같은 가상의 물체와도 충돌할 수 있습니다 혼합 몰입형 환경에서 우주선은 사용자 주변의 실제 물체로부터 재구성된 메시와 충돌할 수 있습니다 이러한 충돌의 영향력을 더욱더 높여 보겠습니다 앱에서 실제 물질의 정보를 바탕으로 오디오를 통합해서요
먼저 CollisionEvents에 응답해야 합니다 .Began 충돌 이벤트를 처리할 메서드를 작성하여 두 엔티티 간에 일어난 충돌에 대한 정보를 제공합니다 이미 로드된 AudioFileResource를 가져온 후 첫 번째 엔티티가 충돌 시 이를 재생합니다
시작이 좋지만 이 상태로는 두 유형의 물체가 충돌할 때마다 동일한 사운드가 똑같은 음량으로 재생됩니다 서로 충돌하는 강도와 상관없이요 AudioFileGroupResource를 사용하여 구현부를 다듬겠습니다 AudioFileGroupResources는 오디오 파일 리소스의 배열로부터 구성됩니다 Entity.playAudio를 AudioFileGroupResource에서 호출할 때마다 오디오 파일 리소스를 무작위로 선택하여 재생합니다 충돌 오디오로 사운드 세트를 로드할 수 있습니다 동일한 사운드 이벤트를 미묘하게 변형한 것이죠
그러면 두 물체가 충돌할 때마다 소리가 자연스럽게 달라집니다
우주선과 소행성은 매우 역동적입니다 이들은 빠르고 느리게 움직일 수 있으므로 충돌할 때에는 충돌 사운드의 크기가 충돌의 영향을 잘 나타내야 합니다
비행운 오디오를 페이드인할 때 했던 것처럼 Entity.playAudio를 호출하여 AudioPlaybackController를 가져온 후 AudioPlaybackController의 gain을 두 엔티티의 속도에서 계산된 수준으로 설정합니다
이렇게 하면 두 물체가 고속으로 정면 충돌할 때 충돌 사운드가 강력하고
이들이 저속으로 충돌하면 충돌 사운드가 적절히 감쇠됩니다
현재 구현부에서는 충돌하는 두 물체에 동일한 사운드 세트를 사용하고 있습니다 우주선이 소행성 컴퓨터 화면 또는 식물과 충돌하는 경우 충돌에 휘말린 두 물질에 맞는 사운드를 낼 수 있습니다
먼저 앱에서 상호 작용할 것으로 예상되는 사운드 세트를 정의합니다 예를 들면 우주선은 플라스틱으로 구성되어 있고 소행성은 암석으로 구성되어 있습니다 스튜디오 환경의 표면은 모두 콘크리트, 나무, 금속 유리 등의 물질로 레이블이 지정되어 있습니다
앱에서 AudioMaterial의 enum과 맞춤형 AudioMaterialComponent를 정의합니다 이것은 엔티티에 AudioMaterial을 저장하죠 충돌 사운드를 내고자 하는 모든 가상 물체에 AudioMaterialComponent를 설정합니다 예를 들어 소행성에는 암석 오디오 머티리얼을 설정하고 소행성에는 플라스틱 오디오 머티리얼을 설정합니다
두 가상 물체가 충돌하면 먼저 이들에게 설정된 audioMaterials를 읽으려고 시도한 다음 이미 로드되고 두 audioMaterials에 의해 색인화된 AudioFIleGroupResources 세트에서 선택합니다 그 결과 우주선과 소행성 간에 발생하는 충돌 사운드는 플라스틱과 암석의 정보를 바탕으로 하며 우주선과 이 테이블 간의 충돌은 플라스틱과 나무의 정보를 바탕으로 합니다
이제 구현부에서 가상 물체가 서로 충돌할 때 발생하는 오디오를 지원합니다 가상 스튜디오 환경의 표면이 모두 물질로 레이블이 지정되어 있으므로 맞춤형 충돌 사운드를 제공할 수 있습니다 우주선이 이러한 표면 또는 물체와 충돌할 때 말이죠 하지만 가상 공간에만 국한될 필요는 없습니다 완전히 똑같은 동작을 실제 공간에 있는 표면과 물체에 추가할 수 있습니다 실제 환경에서 ARKit 장면 재구성 메시를 사용할 수 있습니다 이 기능은 메시 분류 유형을 제공하므로 이를 통해 공간 내 다양한 표면과 물체를 구분한 후 우주선이 이들과 충돌할 때 재생할 사운드를 연결하면 됩니다 이 동작을 구현하는 방법을 알아보죠
메시에서 동적 물체와 충돌하게 되는 면을 결정해야 합니다 다음으로, 이 특정 면의 메시 분류를 읽은 후 메시 분류를 맞춤형 오디오 머티리얼 열거형에 매핑합니다 이제 가상 물체와 실제 물체의 두 물질에 적절한 사운드를 재생할 수 있습니다 이러한 동적인 물체가 사용자 주변의 실제 환경과 충돌 시 어떤 소리가 나는지 들어 보죠 우주선이 다양한 유형의 물체와 충돌할 때 주의 깊게 들어 보세요 충돌 사운드가 다르죠 예를 들어 우주선이 테이블, iMac 또는 의자와 충돌할 때 나는 소리를 들어 보세요
좋아요! 현실 환경에서 우주선을 조종하는 것도 정말 재미있습니다 이 스튜디오 몰입형 환경에서 소행성 사이로 우주선을 조종하는 것처럼 말이죠
이 가상 공간에 입장하면 가상 물체가 맞춤형 이미지 기반의 조명으로 제각기 다르게 빛납니다 오디오에 대해서도 동일한 작업을 수행해야 합니다 가상 오디오 소스에서 나오는 소리가 지금 현재 있는 공간이 아닌 가상 환경에서 나는 소리처럼 들리도록 해야 하죠 새로운 ReverbComponent와 리버브 프리셋을 사용하여 새로운 음향 환경을 선사할 수 있습니다 ReverbComponent를 엔티티 계층 구조의 어느 지점에 배치하기만 하면 되죠 한 번에 하나의 리버브만 활성화됩니다 작업 흐름에 따라 이 콘텐츠를 Reality Composer Pro의 장면으로 작성할 수 있습니다 아니면 저처럼 RealityKit API를 사용해도 됩니다 visionOS 1에서 모든 공간 음향 사운드의 리버브는 현실에서 실시간으로 시뮬레이션된 음향으로 적용됩니다 이는 혼합 몰입형 환경 사용 사례에 적합합니다 사용자가 비주얼 패스스루를 통해 실제 환경을 볼 수 있는 경우죠 혼합 몰입형 환경 사용 사례에서의 오디오는 그 모습대로 들려야 합니다 사용자의 공간에 있는 것처럼 말이죠
visionOS 2의 새로운 리버브 프리셋을 사용하면 점진적이고 완전한 몰입형 공간으로 사용자에게 다른 세계를 선보일 수 있습니다 점진적인 몰입형 환경에서는 몰입도에 따라 사용자의 현실 음향이 리버브 프리셋과 블렌딩됩니다 완전한 몰입형 환경의 공간 음향 소스에는 ReverbComponent에 설정된 프리셋으로만 리버브가 적용됩니다 올해 WWDC에서 진행된 Jonathan의 세션 ‘맞춤형 환경에서 더욱 몰입감 넘치는 미디어 시청 경험 만들기’를 참고하여 대상 비디오 샘플에서 리버브를 Reality Composer Pro 패키지로 작성하는 방법을 알아보세요 이제 주변 음악을 재생하여 분위기를 연출하는 방법을 알아보겠습니다
앱의 특정 단계에 진입하면 특정 분위기에 어울리는 다양한 음악이 재생됩니다
예를 들어 자유 조종 단계에서는 공중에 떠 있는 것 같은 느낌의 은은하고 우주적인 음악이 들리지만 작업 단계로 넘어가면 음악이 힘 있게 재생됩니다
이 우주선의 중요한 점은 사용자의 정면뿐 아니라 전방위적으로 비행할 수 있다는 것입니다 사용자 뒤로도 새로운 행성이 생성될 수 있죠 그러려면 사용자가 환경 주위를 둘러봐야 합니다 우주선이 어디로 가든 음악이 항상 나오기를 원하며 음악이 뚜렷한 전방 지향성을 가지기를 원하지 않습니다 앱의 공간감을 저해할 수 있으니까요 또한 오디오 파일 채널의 방향이 환경의 특정 위치에 고정되도록 구현하려 합니다 음악이 마치 사용자의 머릿속에서 흘러나오는 것처럼 느끼지 않도록 말이죠
쿼드라포닉이라는 채널 레이아웃을 사용하여 4개의 채널로 구성된 음악 트랙을 만들었습니다 쿼드라포닉 채널 레이아웃은 4개의 채널을 사용자를 중심으로 등거리로 분산해 주므로 시네마틱 프레젠테이션을 위해 디자인된 다른 멀티채널 레이아웃에서 볼 수 있는 두드러지는 전방 지향성이 없습니다 채널 전체에 오디오 신호가 고르게 분포되도록 음악을 제작하여 사용자의 시선이 어디에 있든 균형 잡힌 믹스를 느낄 수 있도록 했습니다 또한 엔진의 비행운 사운드 및 배기음과 마찬가지로 음악 트랙이 튀거나 딸깍거리는 소리 없이 매끄럽게 반복되도록 했습니다 RealityKit을 사용하여 이 음악 트랙을 앱에 통합하는 방법을 알아보겠습니다
RealityKit에 로드된 멀티채널 오디오 파일의 경우 채널 레이아웃이 해당 파일에 기록되어 있어야 하며 그렇지 않으면 RealityKit가 오디오 파일의 개별 채널을 올바른 각도에서 렌더링할 수 없습니다 기본값인 preload가 아닌 .stream으로 loadingStrategy를 설정하겠습니다 스트림 로딩 전략은 디스크에서 오디오 데이터를 스트리밍하여 실시간으로 디코딩하는 반면 프리로드 로딩 전략은 로딩 프로세스의 일환으로 오디오 데이터를 로드하고 디코딩합니다 스트림 로딩 전략은 메모리 사용량을 줄이지만 추가 지연 시간이 발생할 수 있습니다 예를 들어 충돌 사운드처럼 엄격한 지연 시간 요구 사항이 있는 주변 음악은 아니지만 주변 음악 파일은 상당히 크므로 이 사용 사례에는 스트림 로딩 전략이 가장 적합하죠 그래서 음악 재생 엔티티에 AmbientAudioComponent()를 설정합니다 주변 오디오 소스는 3DoF로 렌더링됩니다 소스 회전과 머리 회전은 인식되지만 이동은 인식되지 않습니다 이제 앱에는 여러 가지 오디오 카테고리가 있습니다 기분에 따라 우주선의 역동적인 오디오 충돌 사운드, 음악을 한꺼번에 듣는 것이 좋을 때도 있고 음악만 듣는 것이 좋을 때도 있습니다 앱의 오디오 믹서를 사용하면 각 카테고리의 오디오 레벨을 개별적으로 제어할 수 있으므로 비행의 느낌을 만끽하면서 완벽한 분위기를 연출할 수 있습니다 사용 방법을 알아보겠습니다
우선 음악을 내립니다
다음은 행성음을 내립니다
마지막으로는 우주선 사운드를 내립니다
이제 행성음을 다시 올리고
믹스를 음악으로 채웁니다
이제 이를 코딩하는 방법을 알아보겠습니다 먼저 음악 오디오 리소스를 로딩합니다 AudioFileResource 구성에서 mixGroupName 속성을 ‘Music’으로 설정합니다 이것이 앱의 모든 음악 리소스의 레벨을 조작하는 오디오 믹서에서 사용자가 조정할 수 있는 믹스 그룹의 이름이 됩니다 다음에는 AudioMixGroupsComponent를 저장하는 Entity를 생성합니다 믹스 그룹에 대한 레벨이 업데이트되면 AudioMixGroup을 생성하고 gain을 설정합니다 마지막으로 AudioMixGroupsComponent를 구성하고 이를 audioMixerEntity에 설정합니다 UI를 업데이트하여 AudioMixGroupsComponent를 업데이트할 수 있습니다 여기에서 한 것과 같죠 AudioMixGroupsComponent는 구성요소 프로토콜을 준수하므로 맞춤형 RealityKit 시스템에서도 업데이트할 수 있습니다
훌륭합니다, 오디오 믹스 그룹을 사용하면 이제 앱에서 발생하는 다양한 사운드를 제어하여 분위기에 맞는 경험을 연출할 수 있습니다
공간 음향 소스와 실시간으로 생성된 오디오를 사용하여 우주선에 생동감을 불어넣고 가상과 현실의 사이의 상호 작용을 촉진하는 충돌 사운드를 제작했습니다 촉진합니다 그런 다음 리버브 프리셋을 통해 이러한 공간 사운드를 몰입형 환경으로 가져왔습니다 마지막으로는 오디오 믹스 그룹을 사용하여 앱에서 나오는 음악과 사운드의 적절한 믹스를 만들었습니다 직접 이 앱을 사용해 보시기 바랍니다 RealityKit 오디오로 만드신 결과물을 기대하겠습니다
-
-
3:11 - Play vapor trail audio
// Vapor trail audio import RealityKit func playVaporTrailAudio(from engine: Entity) async throws { let resource = try await AudioFileResource(named: "VaporTrail") engine.playAudio(resource) }
-
4:02 - Make vapor trail audio playback more dynamic
// Vapor trail audio import RealityKit func playVaporTrailAudio(from engine: Entity) async throws { let resource = try await AudioFileResource( named: "VaporTrail", configuration: AudioFileResource.Configuration( shouldLoop: true, shouldRandomizeStartTime: true ) ) let controller: AudioPlaybackController = engine.playAudio(resource) controller.gain = -.infinity controller.fade(to: .zero, duration: 1) let audioSource = Entity() audioSource.orientation = .init(angle: .pi, axis: [0, 1, 0]) audioSource.components.set( SpatialAudioComponent(directivity: .beam(focus: 0.25)) ) engine.addChild(audioSource) let controller = audioSource.playAudio(resource) }
-
7:10 - Exhaust audio
// Exhaust audio import RealityKit func updateAudio(for exhaust: Entity, throttle: Float) { let gain = decibels(amplitude: throttle) exhaust.components[SpatialAudioComponent.self]?.gain = Audio.Decibel(gain) } func decibels(amplitude: Float) -> Float { 20 * log10(amplitude) }
-
8:17 - Turbine audio
// Turbine audio import RealityKit var turbineController: AudioGeneratorController? func playTurbineAudio(from engine: Entity) { let audioUnit = try await AudioUnitTurbine.instantiate() let configuration = AudioGeneratorConfiguration(layoutTag: kAudioChannelLayoutTag_Mono) let format = AVAudioFormat( standardFormatWithSampleRate: Double(AudioGeneratorConfiguration.sampleRate), channelLayout: .init(layoutTag: configuration.layoutTag)! ) try audioUnit.outputBusses[0].setFormat(format) try audioUnit.allocateRenderResources() let renderBlock = audioUnit.internalRenderBlock turbineController = try engine.playAudio(configuration: configuration) { isSilence, timestamp, frameCount, outputData in var renderFlags = AudioUnitRenderActionFlags() return renderBlock(&renderFlags, timestamp, frameCount, 0, outputData, nil, nil) } }
-
11:28 - Setting distance attenuation and gain
import RealityKit func configureDistanceAttenuation(for spaceshipHifi: Entity) { spaceshipHifi.components.set( SpatialAudioComponent( gain: -18, distanceAttenuation: .rolloff(factor: 4) ) ) }
-
12:36 - Loudness variation
// Loudness variation import RealityKit func handleCollisionBegan(_ collision: CollisionEvents.Began) { let resource: AudioFileGroupResource // … let controller = collision.entityA.playAudio(resource) controller.gain = relativeLoudness(for: collision) }
-
14:44 - Defining audio materials
// Audio materials import RealityKit enum AudioMaterial { case none case plastic case rock case metal case drywall case wood case glass case concrete case fabric } struct AudioMaterialComponent: Component { var material: AudioMaterial }
-
14:53 - Setting audio materials
// Setting Audio Materials asteroid.components.set( AudioMaterialComponent(material: .rock) ) spaceship.components.set( AudioMaterialComponent(material: .plastic) )
-
15:04 - Handling collision audio
// Audio materials import RealityKit func handleCollisionBegan(_ collision: CollisionEvents.Began) { guard let audioMaterials = audioMaterials(for: collision), let resource: AudioFileGroupResource = collisionAudio[audioMaterials] else { return } let controller = collision.entityA.playAudio(resource) controller.gain = relativeLoudness(for: collision) }
-
17:18 - Reverb presets
// Reverb presets import Studio func prepareStudioEnvironment() async throws { let studio = try await Entity(named: "Studio", in: studioBundle) studio.components.set( ReverbComponent(reverb: .preset(.veryLargeRoom)) ) rootEntity.addChild(studio) }
-
20:05 - Immersive music
// Immersive music import RealityKit func playJoyRideMusic(from entity: Entity) async throws { let resource = try await AudioFileResource( named: “JoyRideMusic”, configuration: .init( loadingStrategy: .stream, shouldLoop: true ) ) entity.components.set(AmbientAudioComponent()) entity.playAudio(resource) }
-
21:57 - Using AudioMixGroup with a RealityKit entity
// Audio mix groups import RealityKit let resource = try await AudioFileResource( named: “JoyRideMusic”, configuration: .init( loadingStrategy: .stream, shouldLoop: true, mixGroupName: “Music” ) ) var audioMixerEntity = Entity() func updateMixGroup(named mixGroupName: String, to level: Audio.Decibel) { var mixGroup = AudioMixGroup(name: mixGroupName) mixGroup.gain = level let component = AudioMixGroupsComponent(mixGroups: [mixGroup]) audioMixerEntity.components.set(component) }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.