스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Reality Composer Pro에서 대화식 3D 콘텐츠 만들기
Reality Composer Pro의 Timeline 뷰를 활용하여 생생한 3D 콘텐츠를 제작할 수 있습니다. 역운동학, 블렌드 모형, 골격 포즈 등을 사용하여 캐릭터, 물체, 배경이 상호작용하는 애니메이션 스토리를 제작하는 방법을 알아보세요. 또한 내장된 맞춤형 동작을 사용하고, 동작의 시퀀스를 설정하고, 트리거를 적용하고, 자연스러운 움직임을 구현하는 방법을 시연합니다.
챕터
- 0:00 - Introduction
- 2:54 - Introducing timelines
- 18:22 - Inverse kinematics
- 22:55 - Animation actions
- 27:23 - Blend Shapes animation
- 30:35 - Skeletal poses
리소스
관련 비디오
WWDC24
- 공간 컴퓨팅을 위해 3D 애셋 최적화하기
- 맞춤형 환경에서 더욱 몰입감 넘치는 미디어 시청 경험 만들기
- iOS, macOS, visionOS용 RealityKit API 알아보기
- RealityKit 디버거 자세히 알아보기
WWDC23
-
다운로드
안녕하세요 저는 Marin입니다 RealityKit 도구 엔지니어죠 오늘은 Reality Composer Pro의 새로운 기능인 Timelines를 소개하겠습니다 3D 콘텐츠를 생생하게 만드는 새로운 대화식 방법이죠 작년에 발표했던 Reality Composer Pro는 앱을 위한 3D 콘텐츠를 미리 보고 준비하는 과정을 간소화할 수 있는 도구입니다 여기 포함된 비주얼 편집기로 장면을 구성하고 애셋을 정리하고 상호작용과 피직스를 추가하고 ShaderGraph 편집기로 해당 요소들의 머티리얼까지 세밀하게 조정할 수 있습니다 RealityComposer Pro는 다양한 일을 할 수 있습니다 Reality Composer Pro가 처음이시거나 복습을 하고 싶으시다면 작년 WWDC의 이 세션들을 살펴보시는 것을 적극 추천드립니다 Reality Composer Pro로 RealityKit의 콘텐츠를 시각적으로 디자인하고 편집하고 미리볼 수 있습니다 씬을 구성하고 컴포넌트를 구성하고 복잡한 머티리얼 제작, 오디오 추가 여러 작업을 모두 할 수 있습니다 올해 새로 추가된 기능을 사용하면 타임라인 편집기로 애니메이션을 만들 수 있고 조명을 추가하고 환경 제작에 도움을 받을 수 있습니다 이 세션에서는 타임라인 편집기를 집중적으로 살펴보겠습니다 타임라인을 사용해 인터랙티브 앱을 만드는 방법과 RealityKit의 새로운 애니메이션 API를 보여드리겠습니다 함께 살펴보죠
다른 세션에서 Botanist 앱을 보셨을 것입니다 이 앱의 로봇을 가져와 재부팅해 우리만의 인터랙티브한 가상 환경을 만들겠습니다 이 앱에는 시들어가는 식물 3개가 있습니다 식물이 시들었을 때 식물을 탭하면 로봇이 식물로 이동해 물을 주도록 트리거할 수 있습니다 식물은 서서히 건강해지고 로봇은 다시 플랫폼 중앙으로 돌아갑니다 두 번째 로봇은 나비를 관찰하며 나비가 움직일 때 손을 뻗어 만지려고 합니다 이 세션에 링크된 샘플 프로젝트를 다운로드해 따라하실 수 있습니다 이 앱을 만들기 위해 먼저 Reality Composer Pro의 타임라인을 사용해 로봇을 식물에게 이동시키겠습니다
그런 다음 RealityKit에 탑재된 Full Body Inverse Kinematics 시스템으로 로봇이 손을 뻗어 식물에 물을 주게 만드는 방법을 보여드리겠습니다
그런 다음 애니메이션 동작을 코드로 작성하는 방법을 살펴보고 이를 사용해 로봇을 회전시키고 시작 위치로 다시 이동시켜볼 것입니다
다음으로 블렌드 셰이프 애니메이션으로 식물에 애니메이션을 적용하겠습니다
그리고 마지막으로 나비를 바라보는 두 번째 로봇의 스켈레탈 포즈 애니메이션을 추가할 것입니다
타임라인을 소개해 드리겠습니다 타임라인은 Reality Composer Pro의 새로운 기능으로 특정 순서나 시간에 실행할 동작을 시퀀싱할 수 있는 기능입니다 Reality Composer Pro를 사용하면 이러한 동작을 쉽게 편집하고 구성할 수 있습니다 왼쪽 패널에는 모든 타임라인의 목록이 있습니다
가운데는 기본 타임라인 편집기 오른쪽 패널에는 사용 가능한 모든 기본 동작 목록이 있습니다 타임라인을 만들었다면 트리거에 따라 재생되도록 타임라인을 시작할 수 있습니다
첫 번째 애니메이션입니다 이 경험에서 식물을 탭하면 로봇이 회전해 탭한 식물에 가도록 트리거됩니다 이 타임라인 시퀀스를 만든 방법을 보여드리겠습니다 여기, Reality Composer Pro에서 하단 패널에 Timelines라는 새 탭이 있습니다 중앙에는 Create Timeline 버튼이 있습니다 이 버튼을 선택해 타임라인을 만들겠습니다 왼쪽에는 사용 가능한 모든 타임라인 목록이 있고 가운데에 기본 타임라인 편집기가 있습니다 여기서 동작을 구성하고 타임라인에 순서를 지정합니다 오른쪽에 있는 목록은 내장된 모든 동작의 목록으로 타임라인에 쉽게 드래그 앤 드롭할 수 있습니다 타임라인의 이름을 MoveToPoppy로 변경하겠습니다 타임라인을 선택하고 더블 탭한 뒤 MoveToPoppy를 입력합니다
제일 먼저 로봇이 탭한 식물의 방향으로 돌아보도록 만들겠습니다 이를 위해 내장된 동작 목록에서 회전 동작을 사용하겠습니다 타임라인에 회전 동작을 드래그합니다
로봇을 회전시키고 싶으므로 속성 패널의 Target 필드에서 Choose를 선택하고 제 로봇 엔티티를 계층 구조 또는 뷰포트에서 찾아 선택합니다 선택을 마쳤다면 Done을 탭합니다
이제 로봇이 얼마나 많이 회전할지 구성해 볼 텐데요 속성 패널에서 회전수를 설정하면 됩니다 0.12로 설정하겠습니다
완전히 한 바퀴 도는 게 아니라 살짝만 돌면 되니까요 어떤 모습인지 볼까요? 타임라인 편집기 상단에 재생 버튼이 있는데 이 버튼을 탭하면 동작을 미리 볼 수 있습니다
좋아요, 로봇이 방향을 돌리는 게 식물을 향하는 것처럼 보이네요 로봇이 회전을 한 다음 움직이도록 만들겠습니다 Transform To 동작을 사용하겠습니다 내장된 동작 목록에서 Transform To 동작을 가져와서 편집기에 드래그 앤 드롭합니다 회전 동작 약 1초 후에 발생하도록 순서를 지정하겠습니다 이 동작의 대상을 제 로봇으로 하겠습니다 로봇을 식물 쪽으로 이동시킬 수 있을 것입니다 Choose를 선택하고 계층 구조에서 제 로봇을 찾습니다 그런 다음 Done을 탭합니다 이제 로봇이 이동할 방향의 시각적 신호를 볼 수 있습니다 현재 로봇은 플랫폼 중앙에 있습니다 원하는 위치가 아니네요 그럼 매니퓰레이터를 사용해 로봇의 목적지를 양귀비 식물 바로 앞으로 이동하겠습니다
좋습니다, 꽤 괜찮아 보여요 이제 Transform 동작의 지속 시간을 변경하겠습니다 기본 지속 시간인 1초에서 더 큰 숫자로 변경하겠습니다 이렇게 하면 로봇이 식물로 이동하는 데 시간이 좀 걸립니다 이 작업은 편집기에서 동작의 뒷쪽 끝을 드래그하면 됩니다
속성 패널에서 지속 시간을 설정할 수도 있습니다
동작을 드래그해 한 트랙에서 다른 트랙으로 옮길 수 있습니다 이렇게 하면 여러 동작을 동시에 실행하도록 시퀀싱할 수 있습니다 회전 동작을 나중에 사용할 새 트랙으로 드래그하겠습니다 이제 동작이 어떻게 보일지 미리 확인하겠습니다 재생 버튼을 누릅니다
좋습니다, 괜찮아 보여요 로봇이 식물을 향해 이동하는 동안 애니메이션과 오디오가 있다면 좋을 것 같습니다 이를 위해 다른 타임라인을 만들어 걷기 애니메이션과 오디오가 함께 나올 수 있게 조정하겠습니다 타임라인 목록 아래의 더하기(+) 버튼을 탭합니다 이 타임라인의 이름을 RobotMove로 하겠습니다 새 타임라인을 선택해 더블 탭하여 RobotMove를 입력합니다
애니메이션과 오디오가 재생되도록 새 컴포넌트 두 개를 사용합니다 AnimationLibraryComponent와 AudioLibraryComponent입니다 이 두 컴포넌트를 살펴보겠습니다 AnimationLibraryComponent는 애니메이션을 저장하고 애니메이션을 재생할 엔티티와 연관시키는 데 사용됩니다 리깅된 엔티티에 AnimationLibraryComponent를 추가하고 애니메이션 리소스를 AnimationLibraryComponent에 추가합니다 Reality Composer Pro를 사용하면 애니메이션을 쉽게 AnimationLibraryComponent에 추가할 수 있습니다 더하기(+) 버튼을 탭하고 프로젝트에서 애니메이션을 선택합니다 그러면 애니메이션 리소스가 해당 엔티티의 AnimationLibraryComponent에 추가됩니다 이 리소스는 나중에 코드에서 애니메이션을 재생하거나 Reality Composer Pro에서 타임라인을 사용할 때 사용할 수 있습니다
AudioLibraryComponent도 AnimationLibraryComponent와 작동 방식이 비슷합니다 AudioLibraryComponent는 오디오 리소스를 저장하고 이를 재생할 엔티티 연결에 사용합니다 엔티티에 AudioLibraryComponent를 추가한 다음 오디오 리소스를 AudioLibraryComponent에 추가합니다 오디오 리소스를 AudioLibraryComponent에 추가하려면 더하기(+) 버튼을 탭, 프로젝트에서 오디오 파일을 선택합니다 그러면 오디오 리소스가 해당 엔티티의 AudioLibraryComponent에 추가됩니다
이 리소스는 나중에 코드에서 오디오를 재생하거나 타임라인을 사용할 때 사용할 수 있습니다 Reality Composer Pro로 돌아가서 타임라인에 오디오와 애니메이션을 추가하겠습니다 RobotMove 타임라인에서 애니메이션 동작을 추가하기 위해 새 애니메이션 라이브러리 컴포넌트를 사용하겠습니다 미리 프로젝트 브라우저에 사용할 USD 애니메이션을 추가했습니다 이를 위해 계층 구조에서 로봇을 선택하겠습니다 속성 패널에서 애니메이션 라이브러리 컴포넌트가 이미 추가되어 있습니다 이 엔티티에 이미 USD 애니메이션이 연결되어 있기 때문입니다 Reality Composer Pro는 컴포넌트를 자동으로 추가하고 애니메이션 목록에 컴포넌트의 기본 애니메이션을 표시합니다 애니메이션 라이브러리 컴포넌트는 수동으로 추가할 수도 있습니다 속성 패널 하단의 Add Component 버튼을 선택하면 됩니다 기본 하위 트리 애니메이션을 선택 재생 버튼을 사용해 애니메이션을 미리 보거나 가위 아이콘을 사용해 클립으로 잘라낼 수 있습니다 애니메이션이 원하는 것보다 조금 길군요 두 클립으로 나눠서 더 짧은 애니메이션으로 만들고 연속 재생하겠습니다 이렇게 하려면 재생헤드를 잡고 첫 번째 조각을 만들 위치로 드래그합니다 가위 아이콘을 클릭합니다 그러면 두 개의 클립이 생깁니다 이제 끝부분만 사용할 수 있도록 다른 조각을 만들겠습니다 다시 두 번째 조각을 만들 위치로 재생헤드를 드래그하고 가위 아이콘을 클릭합니다
이제 연속으로 재생할 수 있는 클립 두 개가 생겼습니다 두 클립의 이름을 startWalk와 endWalk로 변경합니다
이제 RobotMove 타임라인을 선택 애니메이션을 연속해서 재생해보도록 하겠습니다 애니메이션 동작을 선택해 타임라인으로 드래그합니다
이 동작의 대상을 제 로봇으로 하기 위해 Choose를 선택한 다음 계층 구조에서 로봇을 선택합니다 그런 다음 애니메이션을 startWalk 클립으로 설정합니다
두 번째 클립의 다른 애니메이션을 타임라인으로 드래그합니다
첫 번째 클립 바로 뒤에 애니메이션이 실행되도록 합니다 이 동작도 로봇을 대상으로 하고
애니메이션을 endWalk 클립으로 설정합니다
애니메이션이 실행되는 동안 오디오 클립도 동시에 재생하고 싶습니다 이렇게 하기 위해 AudioLibraryComponent와 프로젝트에 미리 추가해둔 오디오 파일을 사용하겠습니다 계층 구조에서 로봇을 선택 속성 패널에서 Add Component 버튼을 선택해 새 컴포넌트를 추가하겠습니다 그리고 목록에서 Audio Library 컴포넌트를 선택합니다
더하기(+) 버튼을 탭하고
필요한 오디오 파일을 찾습니다 오디오 파일 두 개를 추가합니다 하나는 로봇이 회전할 때 하나는 로봇이 움직일 때 용입니다
이제 RobotMove 타임라인으로 돌아가 오디오 동작이 언제 실행될지 설정하겠습니다 오디오 동작을 타임라인으로 드래그하겠습니다 로봇이 오디오 사운드를 내도록 로봇을 이미터로 선택하고 방금 추가한 오디오 걷기 클립을 재생할 수 있도록 오디오 리소스를 walk로 설정하겠습니다 그리고 오디오 동작을 드래그해서 걷기 애니메이션이 실행되는 것과 동시에 재생되도록 합니다
좋아요, 재생하겠습니다
멋져요, 애니메이션과 오디오가 함께 재생되네요 이제 RobotMove 타임라인을 MoveToPoppy 타임라인에서 사용할 준비가 되었습니다 MoveToPoppy 타임라인을 선택하고 RobotMove 타임라인을 우클릭한 다음 Insert into Timeline 옵션을 선택합니다 이렇게 하면 RobotMove 타임라인이 MoveToPoppy 타임라인에 삽입됩니다 이제 다른 타임라인 안에서 재생되는 타임라인이 생겼습니다 이 중첩된 타임라인을 드래그해 원하는 지점으로 옮길 수 있습니다 애니메이션이 회전이 끝난 직후에 발생하도록 만들기 위해
애니메이션과 Transform 동작을 페어링해 애니메이션과 Transform이 동시에 발생하도록 하겠습니다 Transform 동작의 지속 시간을 변경해서 애니메이션이 시작되고 조금 후에 움직이기 시작하고 끝나기 조금 전에 움직임을 멈추도록 만들겠습니다 그리고 로봇이 회전할 때 재생할 오디오 클립을 추가하겠습니다 내장된 동작 목록에서 오디오 동작을 드래그해 회전 동작과 함께 발생하도록 배치합니다 로봇을 이미터로 설정하고 오디오 리소스를 회전 오디오로 설정하겠습니다
이제 미리보기로 어떤 모습인지 확인해 봅시다
멋지네요, 로봇이 식물로 이동하면서 애니메이션과 오디오가 재생됩니다 이제 타임라인을 만들었으니 어떻게 타임라인을 시작해 재생을 시작할까요? 두 가지 방법 중 하나를 사용할 수 있습니다 코드에서 RealityKit API를 사용해 타임라인의 애니메이션 리소스 인스턴스에서 entity.playAnimation을 호출할 수 있습니다 Reality Composer Pro의 UI를 사용해 코드를 작성하지 않고도 타임라인과 재생을 시작하는 새 Behaviors 컴포넌트를 추가할 수도 있습니다 Reality Composer Pro를 사용해 타임라인을 시작해 재생하려면 타임라인을 트리거하려는 엔티티에 Behaviors 컴포넌트를 추가해야 합니다 다양한 유형의 트리거를 사용해 타임라인을 시작해 재생할 수 있습니다 탭, 콜리전 엔티티가 씬에 추가될 때 또는 코드에 게시한 알림 기반의 트리거 등이 있습니다 Reality Composer Pro로 돌아가서 타임라인 실행을 시작할 수 있는 탭 제스처를 식물에 설정합니다 탭 제스처를 설정하기 위해 먼저 탭할 식물을 선택하겠습니다 여기서는 계층 구조에서 양귀비 식물을 선택하겠습니다 새 컴포넌트를 추가하겠습니다 속성 패널에서 Add Component 버튼을 탭하고 Behaviors 컴포넌트를 찾습니다 그런 다음 더하기(+) 버튼을 탭하고 탭 트리거를 선택합니다 사용자가 탭하면 식물이 반응하도록 하기 위해서입니다 식물을 탭하면 탭 제스처가 MoveToPoppy 타임라인을 재생하는 트리거를 만들기 위해 목록에서 MoveToPoppy 타임라인을 선택하겠습니다 이제 RealityView에게 탭 제스처에 반응하도록 지시해야 합니다 Xcode로 이동하겠습니다 RealityView에서 탭 제스처를 추가하겠습니다
탭 제스처는 targetedToAnyEntity가 됩니다 탭이 끝날 때 즉 손가락을 뗄 떼 우리에게 전달된 값에서 엔티티를 가져오고 엔티티에 탭 동작을 적용합니다 즉, 우리가 엔티티를 탭하면 우리의 경우 엔티티는 양귀비 식물이죠 Reality Composer에서 지정한 탭 동작 MoveToPoppy 타임라인을 적용할 것입니다 이제 빌드하고 실행해볼 수 있습니다 양귀비 식물을 탭하고 한번 보겠습니다
좋습니다! 식물을 탭하면 로봇이 회전하고 식물로 이동하네요 첫 번째 애니메이션을 만들고 사용자 입력에 따라 재생되도록 만드는 데 성공했습니다 정말 멋지네요! 이제 두 번째 애니메이션을 만듭니다 로봇이 식물에 도착하고 나면 로봇이 식물로 팔을 뻗도록 만들고 싶습니다 이를 위해 제가 할 일은 알림 동작을 추가하고 코드가 알림을 수신하는 것입니다 알림이 실행되면 로봇이 식물에 손을 뻗도록 맞춤형 코드를 작성하겠습니다 Reality Composer Pro로 돌아가서 알림 동작을 추가하겠습니다
내장된 동작 목록에서 알림 동작을 타임라인 끝에 끌어다 놓습니다
이 알림 동작의 대상을 양귀비로 지정하겠습니다 그런 다음 알림의 식별자를 ReachToPoppy로 설정합니다
이 문자열은 알림이 실행될 때 우리에게 전달될 문자열입니다 이제 Reality Composer Pro에서 알림을 설정했으니 이 알림을 수신할 코드를 ImmersiveView에 추가하겠습니다 Xcode의 ImmersiveView로 돌아가서 알림 이름 ReachToPoppy를 추가합니다 그런 다음 알림에 대한 퍼블리셔를 추가합니다
다음으로 RealityView에서 알림 수신을 처리하기 위해 onReceive를 사용합니다 출력에서 알림을 받으면 사용자 정보 딕셔너리에서 소스 엔티티를 가져온 다음 로봇이 식물에 손을 뻗는 동작을 시작하는 코드를 작성할 수 있습니다 함께 살펴보죠 손을 뻗는 애니메이션은 어떤 모습이어야 할까요? 로봇이 식물에 도착하면 팔을 내밀도록 하고 싶습니다
이를 위해 RealityKit의 Full Body Inverse Kinematics 시스템을 사용합니다 Inverse Kinematics는 운동 방정식을 사용해 리깅된 스켈레탈 구조에서 원하는 위치에 도달하기 위한 조인트의 동작을 결정합니다 목표 위치와 영향을 받는 조인트에 대한 제약 조건을 지정합니다 중간 조인트 위치는 자동으로 계산됩니다 일반적인 예를 들자면 손을 원하는 위치로 움직이면 팔꿈치가 해당 위치에 도달하도록 자동으로 조정됩니다 이를 통해 캐릭터의 움직임을 자연스럽게 표현할 수 있습니다 새로운 Inverse Kinematics API를 개략도를 통해 설명드린 다음 코드 내 사용법을 보여드리겠습니다 먼저 IKRig를 인스턴스화합니다 IKRig가 정의하는 것은 Inverse Kinematics의 작동 방식입니다 IKRig에 모델 스켈레톤과 반복 횟수 등의 구성을 지정하겠습니다 동일한 엔티티에서 애니메이션을 재생하는 경우 애니메이션에 가중치를 부여하고 IK에 가중치를 부여해야 솔버가 무엇을 재정의할지 알 수 있습니다 제약 조건을 추가하겠습니다 제약 조건은 조인트가 변경될 수 있는 방법을 제한합니다 이는 부자연스러운 움직임을 방지하는 데 사용해 팔꿈치가 잘못된 방향으로 구부러지는 등의 현상을 방지합니다
그런 다음 정의한 IKRig에서 IKResource를 생성합니다 IKResource는 IK 솔버가 처리용으로 사용하는 런타임 데이터입니다 IKResource에서 IKComponent를 생성하고 이를 엔티티에 추가하겠습니다 컴포넌트를 인스턴스화할 때 이 솔버를 설정하고 런타임에 매 프레임마다 업데이트할 수 있습니다
한 가지 중요한 점은 RealityKit의 IK 솔버가 전체 캐릭터 스켈레톤을 동시에 솔브한다는 것입니다 조인트 계층구조의 하위 집합까지 포함해서요 Inverse Kinematics를 사용해 코드를 작성하고 로봇이 식물에 손을 뻗게 합니다
Inverse Kinematics 솔버 설정을 위해 먼저 빈 릭을 초기화하고 모델 스켈레톤을 전달합니다 그리고 글로벌 릭 설정을 업데이트합니다 여러 번 시도해 보니 maxIterations를 30으로 설정하고 글로벌 포워드 키네마틱 가중치를 .02로 설정하는 것이 가장 낫네요 그리고 릭의 스켈레톤에 있는 조인트 이름을 참고하겠습니다 이 예에서는 로봇의 엉덩이, 가슴, 손을 움직여 로봇이 팔을 뻗는 애니메이션을 자연스럽게 표현해야 합니다
그런 다음 릭의 제약 조건을 정의하겠습니다 부모 제약 조건을 두 개 포인트 제약 조건을 하나 설정합니다 부모 제약 조건은 조인트의 위치와 방향을 제약합니다 포인트 제약 조건은 조인트의 위치를 제약합니다 다음으로 릭을 포함하는 리소스를 만들고 주어진 리소스로 IKComponent를 추가하겠습니다 매 프레임마다 IK 타깃을 업데이트하려면 씬에서 엔티티를 찾아 위치를 지정합니다
먼저 손이 닿을 위치를 구합니다 이 위치는 IKComponent가 있는 엔티티에 상대적이어야 합니다 그리고 reachPosition의 x, y, z 위치를 변경해 프레임마다 약간 달라지게 합니다
또한 각 프레임에서 왼손에 대한 제약 조건을 업데이트하고 싶습니다 따라서 엔티티에서 IKComponent를 가져옵니다 왼손 타깃 이동과 왼손의 위치를 설정합니다 값이 0이면 IK가 제약 조건에 영향을 미치지 않고 값이 1이면 IK가 제약 조건에 전적인 영향을 미칩니다
IK를 시작하고 끝낼 때 팔 애니메이션을 부드럽게 하려면 시간이 지남에 따라 위치가 IK에 의해 구동되는 정도와 기본 포즈에 의해 구동되는 정도에 대한 가중치를 늘리거나 줄입니다 그런 다음 업데이트된 값을 컴포넌트에 커밋합니다
코드를 실행해 확인하겠습니다
좋아요! 이 애니메이션의 목표를 달성했습니다 로봇이 팔을 뻗은 후 파티클 효과를 사용해 식물에 물을 줍니다 이제 로봇이 식물에 물을 주었으니 로봇이 다시 시작 위치로 돌아가도록 만들어 봅시다 애니메이션 동작을 사용하겠습니다 애니메이션 동작을 사용하면 동작이 발생하는 시기를 설정하고 일정한 기간 동안 동작을 함께 시퀀싱할 수 있습니다 이는 게임의 컷신 애니메이션과 유사하지만 애니메이션 동작을 사용하면 실시간으로 수행할 수 있습니다 이 동작은 일반적으로 캐릭터가 발걸음을 딛는 이벤트에서 많이 사용합니다 캐릭터의 발이 땅에 닿을 때마다 소리가 납니다 애니메이션 동작을 사용해 로봇을 플랫폼 중앙으로 다시 이동시킵니다
동작 API에는 기본 제공 동작과 사용자 지정 동작이 있습니다 기본 제공 동작은 여러 가지 내장형 동작으로 구성되어 있는데 SpinAction, PlayAnimationAction 등 쉽게 구성할 수 있는 동작들입니다 이것은 Reality Composer Pro의 타임라인 기능을 지원하는 기본 API입니다 기본 제공 동작에 포함되지 않는 다른 동작이 필요한 경우 맞춤형 동작을 사용할 수 있습니다 이를 통해 나만의 동작을 코드로 작성하고 타임라인에서 동기화할 수 있습니다 기본 제공 동작 API를 사용하려면 먼저 기본 제공 동작을 사용해 엔티티 애니메이션 정의를 만듭니다 그런 다음 해당 정의에서 애니메이션 리소스를 만듭니다 동시에 재생할 애니메이션 리소스를 그룹화합니다 그리고 재생할 애니메이션 리소스를 특정 순서대로 시퀀싱합니다 마지막으로 엔티티에서 애니메이션 재생을 호출합니다 코드에서 방법을 확인하겠습니다
여기 로직이 포함된 메서드가 몇 가지 있는데요 애니메이션을 정의하고 로봇의 회전, 이동, 정렬을 위한 애니메이션 리소스를 반환하는 역할을 합니다 이러한 애니메이션 리소스 인스턴스에 대한 로직은 기존 RealityKit API를 사용해 정의했습니다 이 애니메이션들을 함께 시퀀싱하겠습니다 RotateAnimation부터 시작해서 WalkAndMoveAnimation, AlignAtHomeAction 마지막으로 RobotTravelHomeCompleteAction입니다 그런 다음 기존 playAnimation API를 사용해 애니메이션을 순서대로 재생합니다
맞춤형 동작은 먼저 EntityAction의 자체 프로토콜 준수를 생성합니다 그런 다음 makeActionAnimation API로 AnimationResource를 생성합니다 그리고 애니메이션 리소스를 다른 리소스와 그룹화하거나 특정 순서로 실행되도록 시퀀싱합니다
마지막으로 엔티티에서 playAnimation을 호출합니다 그리고 런타임에 맞춤형 동작의 시작 및 종료 이벤트를 구독할 수 있습니다
코드에서 이러한 단계를 살펴보고 로봇이 식물에 물을 주고 나면 다시 시작 위치로 돌아가도록 만들겠습니다 맞춤형 동작 RobotMoveToHomeComplete을 만들어 집으로 이동 절차가 완료되면 이를 알리는 데 사용할 것입니다 그런 다음 인스턴스를 생성합니다 RobotMoveToHomeComplete EntityAction의 인스턴스죠 makeActionAnimation API를 사용해 AnimationResource를 생성합니다
한 가지 주목할 점은 사용자 지정 동작으로 여러 애니메이션을 시퀀싱하거나 그룹화할 수 있다는 것입니다 여기에는 애니메이션이 하나뿐이니 시퀀싱을 하지 않겠습니다
그리고 playAnimation API를 사용해 애니메이션 리소스를 재생합니다
EntityAction이 시작될 때 이벤트를 구독하도록 했습니다 구독 종료는 동작이 시작될 때 호출됩니다 시작 이벤트가 발생하면 로봇이 집 목적지에 도착했음을 알 수 있습니다 따라서 재생 컨트롤러를 중지해 애니메이션을 중지합니다 또한 이벤트도 구독하는데 RobotMoveToHomeComplete EntityAction이 종료된 경우에 구독합니다 종료가 호출되면 로봇을 .arrivedHome 상태로 트랜지션합니다
이제 이 시퀀스를 실행하겠습니다
좋아요, 잘 되고 있어요
식물 애니메이션에 집중하겠습니다 블렌드 셰이프 애니메이션입니다 블렌드 셰이프 애니메이션으로 한 포즈에서 다른 포즈로 부드럽게 트랜지션해 실사같은 움직임을 만들 수 있습니다 일반적으로 일련의 다른 셰이프들을 블렌딩해 캐릭터의 얼굴이나 몸을 애니메이션하는 데 사용됩니다 이 예제에서는 식물을 시든 상태에서 건강한 상태로 또는 그 반대로 애니메이션을 적용하겠습니다
블렌드 셰이프 애니메이션 API는 BlendShapeWeightsMapping BlendShapeWeightsComponent 및 애니메이션을 실행하는 두 가지 방법인 프로시저럴 또는 USD 애니메이션 사용으로 구성되어 있습니다 BlendShapeWeightsMapping로 블렌드 타깃에 연결된 가중치 설정이 가능합니다 엔티티에 블렌드 타깃을 설정하면 각 타깃에 연결된 가중치를 가질 수 있습니다 0~1로 설정할 수 있습니다 우리의 경우 가중치 1은 시든 식물이고 가중치 0.5는 그 사이의 상태 0은 식물이 건강함을 나타냅니다 시든 식물에서 건강한 식물로 애니메이션을 적용하기 위해 시간이 지남에 따라 값을 업데이트하겠습니다 블렌드 셰이프 애니메이션 API를 사용하려면 먼저 BlendShapeWeightsMapping을 정의한 다음 BlendShapeWeightsComponent를 매핑에서 생성하고 컴포넌트를 엔티티에 추가합니다 컴포넌트가 엔티티에 추가되면 BlendShapeWeightsComponent에 가중치 값을 쿼리하고 언제든지 업데이트할 수 있습니다 이제 프로시저럴로 FromToBy 또는 샘플링된 블렌드 셰이프 애니메이션을 만들 수 있습니다 USD 블렌드 셰이프 애니메이션을 재생할 수도 있습니다 기존 RealityKit playAnimation API를 사용해서 말이죠 이를 코드로 만들어 보겠습니다 먼저 엔티티 계층 구조에서 모델 컴포넌트가 있는 모델 엔티티를 찾습니다 해당 메시 리소스를 가져와 메시 리소스에서 BlendShapeWeightsMapping을 생성합니다 그런 다음 매핑에서 BlendShapeWeightsComponent를 생성합니다
블렌드 가중치 업데이트를 위해 BlendShapeWeightsComponent를 엔티티에서 가져옵니다
BlendShapeWeightsSet의 복사본으로 이 세트에 가중치 값을 할당하겠습니다
BlendShapeWeightsSet의 blendWeightIndex로 모든 가중치 값을 업데이트하고 건강한 식물을 나타내는 0으로 설정합니다 이 경우 식물의 상태를 건강한 상태로 유지하도록 설정합니다 건강한 상태에서 시든 상태는 이징 함수를 사용해 값을 점차적으로 늘리거나 줄입니다
그런 다음 새 가중치 값을 BlendShapeWeightsComponent에 할당해 적용되도록 합니다
코드를 실행하고 이 꽃들을 깨워 봅시다
꽃이 다시 살아나는 것을 보니 정말 기쁘네요 이제 두 번째 로봇이 무엇을 하고 있는지 확인해 봅시다
로봇이 Inverse Kinematics을 사용해 나비를 향해 손을 뻗고 있습니다 나비는 애니메이션 동작을 사용해 날아다니고 있습니다 Reality Composer Pro에서 타임라인을 사용해 생성하고 나비의 위치를 수정하는 맞춤형 컴포넌트를 사용한 동작이죠 로봇의 머리를 자세히 살펴보면 머리가 나비의 비행 경로를 따라 움직이는 것을 볼 수 있습니다 이 움직임을 구현하기 위해 새로운 스켈레탈 포즈 API를 사용합니다 일반적인 리깅된 3D 캐릭터는 서로 연결된 뼈대로 구성된 스켈레톤 구조로 구성됩니다 각 뼈대는 캐릭터 또는 오브젝트의 다른 부분에 해당합니다
캐릭터에 애니메이션을 적용하려면 조인트를 회전시켜 오브젝트가 여러 자세를 취하게 하고 애니메이션을 적용합니다 이는 일반적으로 캐릭터를 걷거나 뛰게 만드는 데 사용됩니다
스켈레탈 포즈 API는 새로운 SkeletalPosesComponent를 추가합니다 RealityKit에서 제작한 애니메이션을 사용할 수 있습니다 또는 런타임에 스켈레톤을 수정할 수 있는데 프로그래밍 방식으로 SkeletalPosesComponent를 쿼리하고 트랜스폼 업데이트 인터페이스로 수정이 가능합니다
스켈레탈 포즈 API를 사용해 로봇의 목뼈가 날아다니는 나비를 쳐다보는 것처럼 회전하도록 만들겠습니다
USD 파일에서 RealityKit으로 임포트된 모든 스키닝된 메시에는 이미 엔티티에 SkeletalPosesComponent가 첨부되어 있습니다 따라서 초기화할 필요 없이 엔티티에서 SkeletalPosesComponent를 가져오기만 하면 됩니다 조인트의 회전을 업데이트합니다 여기서 중요한 점은 조인트가 로컬 스페이스에 있다는 점입니다 모든 프레임에서 목 회전을 업데이트하기 위해서 RealityKit의 업데이트 함수에서 이 코드를 호출하겠습니다 RealityKit은 모든 시스템에서 프레임마다 업데이트 함수를 호출 그리고 업데이트된 값을 컴포넌트에 커밋합니다 전체 조인트 트랜스폼을 업데이트하거나 트랜슬레이션, 스케일, 회전만 업데이트할 수 있습니다 프레임마다 업데이트할 수 있습니다 코드를 실행하고 로봇이 나비를 바라보는 모습을 확인해 봅시다
여러분은 어떠실지 모르겠지만 전 가슴이 뛰네요
오늘 많은 내용을 다루었습니다 설명한 내용을 요약하겠습니다
Reality Composer Pro의 타임라인 기능을 사용해 타임라인에서 동작을 시퀀싱하고 트리거로 재생을 시작하는 방법을 배웠습니다 Full Body Inverse Kinematics API로 로봇이 오브젝트로 팔을 뻗도록 만들어 보았습니다 기본 제공 동작과 맞춤형 동작으로 동작을 시퀀싱하는 방법을 배우고 블렌드 셰이프 애니메이션으로 일련의 셰이프 간에 애니메이션을 적용해 보았습니다 그리고 조인트를 회전시켜 오브젝트가 다양한 자세를 취하게 하고 애니메이션도 적용해 봤습니다
이러한 기능 외에도 올해 Reality Composer Pro에는 흥미로운 기능이 많아졌습니다 Video Docking iOS 및 macOS에 배포하는 기능과 visionOS 배포, 환경 저작 라이팅 등이 있습니다
이러한 주제에 대해 자세히 알아보려면 ‘맞춤형 환경에서 더욱 몰입감 넘치는 미디어 시청 경험 만들기‘ 세션과 ‘iOS, macOS, visionOS용 RealityKit API 알아보기‘ 세션을 확인하세요 인터랙티브 3D 콘텐츠 제작에 도움이 될 새로운 기능으로 여러분이 무엇을 만들어 내실지 기대가 됩니다 감사합니다, 멋진 WWDC 되세요
-
-
20:31 - Setup IKComponent
// Setup IKComponent import RealityKit struct HeroRobotRuntimeComponent: Component { var rig = try? IKRig(for: modelSkeleton) rig.maxIterations = 30 rig.globalFkWeight = 0.02 let hipsJointName = "root/hips" let chestJointName = "root/hips/spine1/spine2/chest" let leftHandJointName = "root/hips/spine1/spine2/chest/…/L_arm3/L_arm4/L_arm5/L_wrist" rig.constraints = [ .parent(named: "hips_constraint", on: hipsJointName, positionWeight: SIMD3(repeating: 90.0), orientationWeight: SIMD3(repeating: 90.0)), .parent(named: "chest_constraint", on: chestJointName, positionWeight: SIMD3(repeating: 120.0), orientationWeight: SIMD3(repeating: 120.0)), .point(named: "left_hand_constraint", on: leftHandJointName, positionWeight: SIMD3(repeating: 10.0)) ] let resource = try? IKResource(rig: rig) modelComponentEntity.components.set(IKComponent(resource: resource)) }
-
21:33 - Update IKComponent
// Update IKComponent import RealityKit struct HeroRobotRuntimeComponent: Component { guard let reachTarget = sceneRoot.findEntity(named: "reachTargetName") else { return } var reachPosition = reachTarget.position(relativeTo: entity) let time = sin(simTime) reachPosition.x += (20.0 + 50.0 * time) reachPosition.y += (40.0 + 30.0 * abs(time)) reachPosition.z += (20.0 + 20.0 * abs(time)) guard let ikComponent = modelComponentEntity.components[IKComponent.self] else { return } var reachPosition = reachTarget.position(relativeTo: entity) ... var leftHandConstraint = ikComponent.solvers.first?.constraints["left_hand_constraint"] leftHandConstraint?.target.translation = reachPosition // A blendValue = 0 means no influence on the constraint. // A blendValue = 1 means full influence on the constraint. var blendValue = isEnabled ? (time / totalBlendTime) : (1.0 - time / totalBlendTime) leftHandConstraint?.animationOverrideWeight.position = blendValue modelComponentEntity.components.set(ikComponent) }
-
24:36 - Sequence and play animation actions
// Play Animation Actions import RealityKit struct HeroRobotRuntimeComponent: Component { let rotateAnimationResource = createRotateAnimationResource() let walkAndMoveAnimationGroup = createWalkAndMoveAnimationGroup() let alignAtHomeActionResource = createAlignAtHomeActionResource() let robotTravelHomeCompleteActionResource = createRobotTravelHomeCompleteAction() // Build a sequence of the rotate, move and align animations/actions to play. let moveHomeSequence = try? AnimationResource.sequence(with: [rotateAnimationResource, walkAndMoveAnimationGroup, alignAtHomeActionResource, robotTravelHomeCompleteActionResource]) // Play the move-to-home sequence. _ = robotEntity.playAnimation(moveHomeSequence) }
-
25:59 - Setup EntityActions
// Setup EntityActions import RealityKit struct HeroRobotRuntimeComponent: Component { struct RobotMoveToHomeComplete: EntityAction { var animatedValueType: (any AnimatableData.Type)? { nil } } let travelCompleteAction = RobotMoveToHomeComplete() let actionResource = try! AnimationResource.makeActionAnimation(for: travelCompleteAction, duration: 0.1) let _ = robotEntity.playAnimation(actionResource) }
-
26:39 - EntityAction subscription
// EntityAction subscription import RealityKit struct HeroRobotRuntimeComponent: Component { // Subscribe to know when the EntityAction has started. RobotMoveToHomeComplete.subscribe(to: .started) { event in if event.playbackController.entity != nil { event.playbackController.stop() } } // Possible states of the robot. public enum HeroRobotState: String, Codable { case available … case arrivedHome } // Subscribe to know when the EntityAction has ended. RobotMoveToHomeComplete.subscribe(to: .ended) { event in if let robotEntity = event.playbackController.entity, var component = robotEntity.components[HeroRobotRuntimeComponent.self] { component.setState(newState:.arrivedHome) } } }
-
29:17 - Setup BlendshapeWeightsComponent
// Setup BlendShapeWeightsComponent import RealityKit struct HeroPlantComponent: Component, Codable { guard let modelComponentEntity = findModelComponentEntity(entity: entity), let modelComponent = modelComponentEntity.components[ModelComponent.self] else { return } let blendShapeWeightsMapping = BlendShapeWeightsMapping(meshResource: modelComponent.mesh) // Create the blend shape weights component. entity.components.set(BlendShapeWeightsComponent(weightsMapping: blendShapeWeightsMapping)) }
-
29:38 - Update BlendshapeWeightsComponent
// Update BlendShapeWeightsComponent struct HeroPlantComponent: Component, Codable { guard let component = entity.components[BlendShapeWeightsComponent.self] else { return } var blendWeightSet = blendShapeComponent.weightSet // Update the weights in the BlendShapeWeightsSet for weightIndex in 0..<blendWeightSet[blendWeightsIndex].weights.count { blendWeightSet[blendWeightsIndex].weights[weightIndex] = 0.0 } // Assign the new weights to the blend shape component. for index in 0..<blendWeightSet.count { component?.weightSet[blendWeightsIndex].weights = blendWeightSet[index].weights } }
-
32:01 - Setup and update Skeletal Poses
// Update Skeletal Poses import RealityKit struct StationaryRobotRuntimeComponent: Component { guard var component = entity.components[SkeletalPosesComponent.self] else { return } let neckRotation = calculateRotation() component.poses.default?.jointTransforms[neckJointIndex].rotation = neckRotation }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.