레거시 Meta 아바타
이 애드온은 Fusion과 Meta 아바타를 통합하는 방법을 보여줍니다.
주요 목표는 다음과 같습니다:
- Fusion 네트워크 변수를 사용한 아바타 동기화
- Fusion Voice와의 립싱크 통합

Meta XR SDK
다른 샘플에서 사용된 OpenXR 플러그인 대신 Oculus XR 플러그인을 사용합니다.
Meta XR SDK는 스코프 레지스트리 https://npm.developer.oculus.com/를 통해 추가되었습니다.
자세한 내용은 Meta 공식 문서를 참조하세요.
Meta 레지스트리에서 설치되는 주요 패키지는 다음과 같습니다:
- Meta XR Core SDK
- Meta XR Platform SDK (Oculus 사용자 ID 및 Meta 아바타 로딩에 필요)
Oculus 리그 및 빌딩 블록
OpenXR 플러그인이 아닌 Oculus XR 플러그인을 기반으로 하기 때문에, 헤드셋 및 손 위치를 캡처하기 위한 별도의 리그가 필요합니다.
이 하드웨어 리그는 Meta 빌딩 블록을 통해 생성되었습니다.
- 해당 프리팹은
/Prefabs/Rig/BaseBuildingBlocks/[BuildingBlock] BaseRig
에 위치합니다. (MetaOVRHandsSynchronization
애드온 포함) - 동기화 컴포넌트를 포함한 실제 사용 프리팹은
/Prefabs/HardwareRig/[BuildingBlock] HardwareRigForMetaAvatar
에 위치합니다.

MetaAvatar
게임 오브젝트는 Meta 아바타 기능을 위한 모든 구성 요소를 포함합니다.
LipSync
:OVRAvatarLipSyncContext
컴포넌트를 포함하며, Oculus SDK가 제공하는 립싱크 설정용 컴포넌트입니다.BodyTracking
:SampleInputManager
컴포넌트를 포함하며,OvrAvatarInputManager
를 상속받아 하드웨어 리그 기반의 트래킹 입력을 처리합니다.AvatarManager
:OVRAvatarManager
를 통해 Meta 아바타 로딩을 담당합니다.
Runner
Runner
게임 오브젝트에 위치한 ConnectionManager
는 Photon Fusion 서버 연결을 처리하고, OnPlayerJoined
콜백 시 사용자 네트워크 프리팹을 생성합니다.
음성 스트리밍을 위해 Fusion Voice Client
가 필요하며, 해당 클라이언트는 Runner
하위에 위치한 Recorder
를 참조합니다.
Fusion과 Photon Voice 통합에 대한 자세한 내용은 다음 링크를 참고하세요:
Voice - Fusion Integration
Runner
는 MetaAvatarMicrophoneAuthorization
컴포넌트를 통해 마이크 권한을 요청하고, 권한이 부여되면 Recorder
오브젝트를 활성화합니다.
하위 오브젝트 Recorder
는 마이크 연결을 담당하며, AudioLipSyncConnector
컴포넌트를 통해 오디오 스트림을 OVRAvatarLipSyncContext
로 전달합니다.
사용자 스폰 네트워크 프리팹
OnPlayerJoined
콜백 시, ConnectionManager
는 NetworkRigWithOVRHandsMetaAvatar Variant
프리팹을 생성합니다.
프리팹 구성:
MetaAvatarSync
: 아바타를 무작위로 선택하고 네트워크로 스트리밍 처리NetworkedAvatarEntity
: Oculus의OvrAvatarEntity
를 상속한 클래스이며, 로컬 또는 원격 사용자에 따라 아바타를 구성합니다.
아바타 동기화
개요
MetaAvatarSync
클래스는 아바타 동기화의 중심 역할을 합니다.
ConfigureAsLocalAvatar()
메서드를 통해 로컬 사용자 프리팹에 다음 구성요소를 연결합니다:
- 립싱크:
OvrAvatarLipSyncContext
- 바디 트래킹:
SampleInputManager
이 데이터는 네트워크 변수를 통해 전송됩니다.
원격 사용자의 경우 ConfigureAsRemoteAvatar()
가 호출되어 수신된 데이터를 기반으로 아바타를 빌드 및 애니메이션 처리합니다.

아바타 모드
MetaAvatarSync
는 다음 두 가지 모드를 지원합니다:
- UserAvatar: 사용자의 Meta 아바타를 로드
- RandomAvatar: 무작위 Meta 아바타를 로드
프리팹 생성 시 다음과 같은 코드로 아바타를 결정합니다:
C#
public override void Spawned()
{
base.Spawned();
if (Object.HasInputAuthority)
{
LoadLocalAvatar();
}
else
{
if (!avatarConfigured)
{
ConfigureAsRemoteAvatar();
}
}
changeDetector = GetChangeDetector(ChangeDetector.Source.SnapshotFrom);
// Trigger initial change if any
OnUserIdChanged();
ChangeAvatarIndex();
}
async void LoadLocalAvatar()
{
if (avatarMode == AvatarMode.UserAvatar)
{
// Make sure to download the user id
ConfigureAsLocalAvatar();
UserId = await avatarEntity.LoadUserAvatar();
}
else
{
ConfigureAsLocalAvatar();
AvatarIndex = UnityEngine.Random.Range(0, 31);
avatarEntity.LoadZipAvatar(AvatarIndex);
}
}
AvatarIndex
가 네트워크 변수이므로, 값이 변경되면 ChangeDetector
를 통해 모든 클라이언트에서 자동으로 동기화됩니다.
C#
[Networked]
public int AvatarIndex { get; set; } = -1;
ChangeDetector changeDetector;
C#
public override void Render()
{
base.Render();
foreach (var changedPropertyName in changeDetector.DetectChanges(this))
{
if (changedPropertyName == nameof(UserId)) OnUserIdChanged();
...
}
}
아바타 데이터
SampleInputManager
는 하드웨어 리그의 움직임을 추적하며, 로컬 사용자의 경우 NetworkedAvatarEntity
에 참조됩니다.
매 프레임 LateUpdate()
에서 데이터를 캡처합니다:
C#
private void LateUpdate()
{
// Local avatar has fully updated this frame and can send data to the network
if (Object.HasInputAuthority)
{
CaptureAvatarData();
}
}
CaptureLODAvatar
메서드는 아바타 엔티티의 스트림 버퍼를 가져와 AvatarData
라는 네트워크 변수에 복사합니다.
용량은 1200으로 제한되어 있으며, 이는 중간 또는 높은 LOD 수준의 Meta 아바타를 스트리밍 하기에 충분한 크기입니다.
(실제 구성에서는 메모리 낭비를 피하기 위해 필요한 데이터 크기에 맞게 조정하는 것이 좋습니다)
참고로, 이 샘플에서는 단순화를 위해 중간 LOD만 스트리밍 됩니다.
버퍼 크기인 AvatarDataCount
역시 네트워크를 통해 동기화됩니다.
C#
[Networked, Capacity(1200)]
public NetworkArray<byte> AvatarData { get; }
[Networked]
public uint AvatarDataCount { get; set; }
따라서 아바타 스트림 버퍼가 업데이트되면, 원격 사용자들에게 해당 변경 사항이 전달되고, 수신된 데이터를 원격 사용자를 나타내는 네트워크 리그에 적용하게 됩니다.
C#
public override void Render()
{
base.Render();
foreach (var changedPropertyName in changeDetector.DetectChanges(this))
{
...
if (changedPropertyName == nameof(AvatarData)) ApplyAvatarData();
}
}
사용자 정의 Meta 아바타 로드
로컬 사용자 아바타 로드
AvatarMode.UserAvatar
모드로 설정 시, LoadUserAvatar()
를 통해 Meta 사용자 계정의 아바타를 불러옵니다:
C#
/// <summary>
/// Load the user meta avatar based on its user id
/// Note: _deferLoading has to been set to true for this to be working
/// </summary>
public async Task<ulong> LoadUserAvatar()
{
// Initializes the OVR PLatform, then get the user id
await FinOculusUserId();
if(_userId != 0)
{
// Load the actual avatar
StartCoroutine(Retry_HasAvatarRequest());
}
else
{
Debug.LogError("Unable to find UserId.");
}
return _userId;
}
이 메서드는 Meta 계정의 사용자 ID를 반환하며, 이 값은 UserId
네트워크 변수에 저장되어 모든 클라이언트 간에 동기화됩니다.
C#
[Networked]
public ulong UserId { get; set; } = 0;
사용자 아바타를 로드하기 위해서는 다음 사항을 반드시 유의해야 합니다:
- 플레이어 프리팹의
NetworkedAvatarEntity
컴포넌트에서Defer Loading
옵션을true
로 설정해야 합니다.
이 설정은 아바타가 시작 시 자동으로 로드되는 것을 방지합니다.
원격 사용자 아바타 로드
UserId
를 수신하면, 원격 클라이언트는 해당 ID에 연동된 아바타를 자동으로 다운로드하게 됩니다.
C#
public override void Render()
{
base.Render();
foreach (var changedPropertyName in changeDetector.DetectChanges(this))
{
if (changedPropertyName == nameof(UserId)) OnUserIdChanged();
...
}
}
void OnUserIdChanged()
{
if(Object.HasStateAuthority == false && UserId != 0)
{
Debug.Log("Loading remote avatar: "+UserId);
avatarEntity.LoadRemoteUserCdnAvatar(UserId);
}
}
Meta 아바타 접근 설정
Meta 아바타를 로드하려면 Unity 메뉴의 Oculus > Platform > Edit Settings
에서 App Id
를 입력해야 합니다.


Meta 대시보드의 API > App Id
에서 확인 가능하며, Data use checkup
항목에서 User Id
, User profile
, Avatars
접근 권한이 설정되어 있어야 합니다.

사용자 아바타 테스트
개발 중 로컬 Meta 계정에 연결된 아바타를 확인하려면, 로컬 사용자 계정이 Oculus 플랫폼 설정에 입력된 App Id
와 연결된 조직의 구성원이어야 합니다.
Quest와 데스크톱 빌드 간의 크로스 플랫폼 환경에서 아바타를 확인하려면, 해당 Quest 앱과 Rift 앱이 하나의 그룹으로 묶여 있어야 합니다.
이 설정 방법은 다음 문서의 "Group App IDs Together" 섹션에서 확인할 수 있습니다: Configuring Apps for Meta Avatars SDK
립싱크
마이크 초기화는 Photon Voice Recorder
에 의해 수행됩니다.
OVRHardwareRig
에 포함된 OvrAvatarLipSyncContext
는 오디오 버퍼를 직접 전달받는 방식으로 설정되어 있으며,
이를 위해 별도의 클래스가 Recorder
의 오디오 데이터를 가로채어 OvrAvatarLipSyncContext
로 전달합니다. 아래는 이 처리 방식의 상세 내용입니다.
Recorder
클래스는 읽어들인 오디오 버퍼를 IProcessor
인터페이스를 구현한 클래스에 전달할 수 있습니다.
커스텀 오디오 프로세서를 만드는 방법에 대한 자세한 내용은 다음 문서를 참고하세요: Photon Voice - FAQ
이러한 프로세서를 음성 연결에 등록하려면, VoiceComponent
를 상속받은 AudioLipSyncConnector
클래스를 Recorder
와 동일한 GameObject에 추가해야 합니다.
이로 인해 PhotonVoiceCreated
및 PhotonVoiceRemoved
콜백을 수신할 수 있으며, 연결된 음성에 후처리 프로세서를 등록할 수 있게 됩니다.
이때 연결되는 후처리 프로세서는 IProcessor<float>
또는 IProcessor<short>
를 구현한 AvatarAudioProcessor
입니다.
플레이어가 연결되면 MetaAvatarSync
컴포넌트는 Recorder
오브젝트 내의 AudioLipSyncConnector
를 찾아
해당 프로세서의 lipSyncContext
필드에 OvrAvatarLipSyncContext
를 설정합니다.
이 설정이 완료되면, Recorder
에서 AvatarAudioProcessor.Process
콜백이 호출될 때마다
오디오 버퍼가 OvrAvatarLipSyncContext.ProcessAudioSamples()
로 전달되어, 아바타 모델에 립싱크가 실시간으로 적용됩니다.
이 방식 덕분에, 립싱크 정보도 아바타의 다른 바디 데이터와 함께 MetaAvatarSync
의 LateUpdate()
단계에서 RecordStreamData_AutoBuffer
를 통해 스트리밍 됩니다.
의존성
- Meta Avatars SDK (com.meta.xr.sdk.avatars) 24.1.1 + 샘플 씬
- Meta Avatars SDK Sample Assets (com.meta.xr.sdk.avatars.sample.assets) 24.1.1
- Meta XR Core SDK (com.meta.xr.sdk.core) 62.0.0
- Meta XR Platform SDK (com.meta.xr.sdk.platform) 62.0.0
- Photon Voice SDK
- MetaOVRHandsSynchronization 애드온
데모
데모 씬은 Assets\Photon\FusionAddons\MetaAvatar\Demo\Scenes\
폴더에 있습니다.
지원되는 토폴로지
- 공유 모드
변경 로그
버전 2.0.1:
- Meta Avatar com.meta.xr.sdk.avatars 24.1.1 패키지 24.1.1 호환성 추가
버전 2.0.0: 최초 릴리즈