본문 바로가기

GameDevelopmentDiary/UnrealDiary

언리얼4 립싱크(Text To Speech) 구현 방법 Part3

1. Viseme

이전에 텍스트를 목소리로 변환해 보았다. 이제 립싱크를 위해서는 그 목소리에서 특정 값을 추출해야한다. 우리가 추출할 값을 보통 Viseme 값이라고 한다. 

위키백과(https://en.wikipedia.org/wiki/Viseme)

<Viseme 간략한 설명>

  • MS 음성 서비스에서 제공하는 REST API 경우 Viseme 값을 추출하는 기능이 지원되지 않는다.
  • 언리얼에서 사용할 AzureTTS 플러그인은 REST API를 이용하기 때문에 또 다른 플러그인이 필요하다.

 

2. OVRLipSync

2.1. OVRLipSync - USoundBase to Visemes

처음에 플러그인을 모두 준비했겠지만, 만약 안 되어 있다면 마저 다운받고 실행해주자. 그리고 OVRLipSync 플러그인의 테스트에 사용할 대사파일을 하나 준비하자. ( LOL: https://goodcow.tistory.com/58 )

언리얼 엔진4에는 기본적으로 오큘러스 플러그인이 설치치어 있어, 마켓플레이스에 들르지 않아도 관련 플러그인들을 사용할 수 있다. 다만, OvrLipSync 플러그인은 직접 사이트에서 다운로드 받아야한다. ( https://developer.oculus.com/downloads/package/oculus-lipsync-unreal/ )

  • Viseme 값을 불러오기 위해서는 OVLipSyncPlaybackActor 컴포넌트와 OVRLipSyncFrameSequence 클래스가 필요하다.

<일반 오디오 에셋을 프레임 시퀀스로 변경 가능>

 

  • OVRLipSync 컴포넌트는 자신과 연결 된 시퀀스를 통해 지속적으로 Viseme 값들을 추출한다.
    Start 노드에 오디오 액터와 시퀀스를 입력하면 컴포넌트와 시퀀스가 연결 되고, 오디오 재생 시 OnVisemes Ready 이벤트가 지속적으로 호출 된다.

<Start 노드에 OVLipSyncFrameSequence 등록>

 

  • 이제 Visime 값을 어디에 사용할까? 립싱크 대상으로 사용할 StaticMesh 컴포넌트도 OVRLipSyncPlaybackActor 컴포넌트와 연결하자.
  • Assign Visemes to Morph Targets 노드가 이 연결을 담당한다.
    블루프린트에서는 등록만해도 괜찮지만, C++에서는 바인딩 후 BeginPlay 쯤에서 바인딩한 함수를 1회 실행할 필요가 있다. 

<Viseme 이름과 값으로 원하는 StaticMesh 컴포넌트를 모핑>


2.2. OVRLipSync - Animation with Viseme
Viseme 값을 어디에(Static Mesh Morphing) 사용할지는 알았다. 이제 어떻게 사용할까?
립싱크할 얼굴의 애니메이션 블루프린트를 열고 Viseme 값을 가져오자.

<애니메이션 블루프린트에 사용할 Viseme 값을 가져와서 등록> 

 

  • 그리고 메타휴먼의 ABP를 복사해서 사용한다면 다음과 같은 노드를 발견할 수 있을텐데, 없으면 하나 생성해고, 커브 변경 노드의 인풋에 캐시를 연결해주자.

<기본 포즈의 캐시데이터 기록>
<기본 포즈 캐시를 배분> 

 

  • 이렇게 기본 캐시데이터가 필요한 이유는 이후 생성될 '커브수정' 노드의 기본 포즈 때문이다. 만약 아무 인풋 없이 커브를 수정한다면, 
    더보기
    <인풋이 없는 경우1>
    <인풋이 없는 경우2>
  • 목이 분리되어버린다.


  • OVRLipSync 플러그인에서 제공하는 Viseme 데이터는 영문음 기준으로 15개의 실수(float) 배열이다.
    sil(묵음), PP, FF, TH, DD, kk, CH, SS, nn, RR, aa, E, ih, oh, ou
    설명문 링크: https://developer.oculus.com/documentation/unreal/audio-ovrlipsync-viseme-reference

 

  • 사용할 Viseme 값에서 불러온 값이 커브값에 그대로 쓰이면 모자라거나 과할 수 있으니 중간중간 변수를 추가해준다. 다음은 사례의 일부이다.

<LipSync 목적의 애님그래프 일부>

  • 자연스러운 입 모양을 찾아서 적당한 변수값을 붙여주는 일이 매우 지난한 일일 수 있다. 아무래도 영어 위주의 Viseme 값이기에 한국어로는 더 어려웠다. 다음 링크는 도움이 되었던 튜토리얼이다. (https://www.youtube.com/watch?v=18ZzMLvqpS4&ab_channel=FracturedFantasy)

  • 여기까지 작업을 마쳤다면 오디오 컴포넌트를 사용해서 목소리를 들어보자.

<'Play 노드'에 연결 된 Audio 컴포넌트는 'Start 노드의 InAudioComponent 매개변수와 동일>

  • 이제 플레이 버튼을 누르면 캐릭터가 대사를 치는 모습을 볼 수 있다.
<삶과 죽음의 순환은 계속된다>

 

  • 지금은 파일을 통해 립싱크를 했지만, 다음에는 입력한 텍스트를 사용해 대사를 읊어보자. 그냥 'Runtime audio importer 사용하면 되겠네.' 싶을 수 있다. 하하하, 당연히 그렇게는 안 될 것이다.