MouthMovement (Cocos Creator)
업데이트: 2023/03/14
개요
MouthMovement는 립싱크 파라미터의 현재 값에 개폐 상태 값을 적용하는 기능입니다.
모션으로 설정된 립싱크용의 커브나 재생하고 있는 음성 파일로부터 실시간으로 샘플링한 값 등을 모델에 적용하는 것이 가능합니다.
모델에 립싱크 파라미터를 설정하는 방법에 대해서는 여기를 참조하세요.
MouthMovement에서 설정하는 것은 입의 개폐 상태뿐입니다.
입의 모양을 모음에 맞추는 것과 같은 조작을 할 수는 없습니다.
Cocos Creator에서 립싱크용 파라미터를 지정하려면 Cubism 에디터에서 모델로 설정해 두는 것 외에도 Cocos Creator에서 사용자가 임의로 지정할 수 있습니다.
Cubism SDK for Cocos Creator의 MouthMovement는 세 가지 요소로 구성됩니다.
- 파라미터 지정용 컴포넌트
- 각 파라미터에 값을 적용하는 컴포넌트
- 2에서 적용하는 값 조작
1. 파라미터 지정용 컴포넌트
MouthMovement에 사용할 파라미터를 지정하려면 CubismMouthParameter를 사용합니다.
CubismMouthParameter는 Component를 상속받은 컴포넌트로,
[Prefab 루트]/Parameters/ 아래에 배치된 Node에 연결하여 사용합니다.
이것이 연결된 Node와 같은 ID의 파라미터를 립싱크용 파라미터로서 취급합니다.
모델 자체에 립싱크용 파라미터가 설정되어 있는 경우 가져오기 시에 그 파라미터의 Node에 CubismMouthParameter가 연결됩니다.
CubismMouthParameter는 참조처를 취득하기 위한 마커로 사용하고 있으므로, 내부에서는 아무것도 처리를 실시하고 있지 않고 데이터도 가지고 있지 않습니다.
2. 각 파라미터에 값을 적용하는 컴포넌트
각 파라미터에 개폐 값을 적용하려면 CubismMouthController를 사용합니다.
이것은 Component를 상속받은 컴포넌트이며, 사용할 때 Cubism의 Prefab 루트에 연결됩니다.
초기화 시 Prefab에 연결된 모든 CubismMouthParameter에 대한 참조를 가져옵니다.
실행 중에 눈 깜빡임용 파라미터를 추가/삭제한 경우 CubismMouthController.refresh()를 호출해 참조를 다시 취득합니다.
public refresh() { const model = CoreComponentExtensionMethods.findCubismModel(this); // Fail silently... if (model == null || model.parameters == null) { return; } // Cache destinations. const tags = ComponentExtensionMethods.getComponentsMany( model.parameters, CubismMouthParameter ); this.destinations = new Array(tags.length); for (let i = 0; i < tags.length; ++i) { this.destinations[i] = tags[i].getComponent(CubismParameter); } // Get cubism update controller. this.hasUpdateController = this.getComponent(CubismUpdateController) ! = null; } ... protected start() { // Initialize cache. this.refresh(); }
CubismMouthController는 매 프레임의 lateUpdate() 타이밍에 CubismMouthParameter로 마킹된 파라미터에 CubismMouthController.MouthOpening의 값을 적용합니다.
protected onLateUpdate(deltaTime: number) { // Fail silently. if (!this.enabled || this.destinations == null) { return; } // Apply value. CubismParameterExtensionMethods.blendToValueArray( this.destinations, this.blendMode, this.mouthOpening ); }
MouthOpening으로 설정하는 값은 0.0f~1.0f 범위입니다.
CubismMouthController는 이 값을 대상 파라미터에 CubismMouthController.blendMode에서 설정된 계산 방식으로 적용합니다.
이 MouthOpening의 값을 외부에서 조작하여 모델의 입을 열고 닫을 수 있습니다.
@property({ type: CCFloat, slide: true, range: [0.0, 1.0, 0.01] }) public mouthOpening: number = 1.0;
3. 2에서 적용하는 값의 조작
「2. 각 파라미터에 값을 적용하는 컴포넌트」에서 설명한 대로 CubismMouthController.mouthOpening의 값을 조작하여 립싱크용 파라미터에 값을 적용할 수 있습니다.
Cubism SDK for Cocos Creator에서는 다음 세 가지 방법으로 이 값을 조작할 수 있습니다.
- 모션으로 값 조작
- 주기적으로 값 조작
- AudioClip에서 샘플링하여 값 조작
또 유저 측에서 이 값을 조작하는 처리를 구현함으로써 립싱크의 속도나 타이밍 등을 독자적으로 커스터마이즈 할 수 있습니다.
Tips
CubismMouthController.mouthOpening을 조작하는 컴포넌트의 실행 순서가 CubismMouthController보다 나중인 경우 의도한 동작이 되지 않을 가능성이 있습니다.
만약 동작에 문제가 생겼다면 유저 측에서 명시적으로 컴포넌트의 실행 순서를 제어하여 회피할 수 있습니다.
Cubism SDK for Cocos Creator는 각 컴포넌트의 실행 순서를 CubismUpdateController로 제어하므로 이를 활용할 수도 있습니다.
또한 상기 3종류의 설정 방법은 각각이 같은 값을 조작하고 있기 때문에, 궁리 없이 하나의 모델에 공존시키는 것은 어렵습니다.
모션으로 값 조작
눈 깜빡임용 파라미터를 설정한 모델을 사용하여 Cubism의 Animator에서 모션을 만들 경우 눈 깜빡임용 커브를 설정할 수 있습니다.
눈 깜빡임용 커브가 설정된 .motion3.json을 Cocos Creator 프로젝트로 가져온 경우 Animation에는 CubismMouthController.mouthOpening의 값을 대상으로 커브가 생성됩니다.
그 때문에, AnimationClip을 Animator 컴포넌트 등에서 재생하면 CubismMouthController.mouthOpening의 값이 조작됩니다.
주기적으로 값 조작
주기적으로 립싱크의 값을 조작하려면 CubismAutoMouthInput을 사용합니다.
CubismAutoMouthInput은 사인파로 립싱크 값을 산출하고 설정하는 컴포넌트입니다.
CubismAutoMouthInput을 사용하려면 Cubism의 Prefab 루트에 연결합니다.
CubismAutoMouthInput에는 하나의 설정 항목이 있습니다.
- Timescale
사인파의 주기가 바뀝니다.
@property(CCFloat) public Timescale: number = 10.0;
lateUpdate(deltaTime: number) { // Fail silently. if (this.Controller == null) { return; } // Progress time. this.T += deltaTime * this.Timescale; // Evaluate. this.Controller.mouthOpening = Math.abs(Math.sin(this.T)); }
오디오에서 샘플링하여 값 조작
Cocos Creator에서 재생되는 오디오에서 립싱크 값을 설정하려면 CubismAudioMouthInput을 사용합니다.
CubismAudioMouthInput은 AudioSource에서 취득한 재생 중인 오디오 정보에서 샘플링하여 실시간으로 립싱크 값을 생성하고 설정합니다.
CubismAudioMouthInput을 사용하려면 Cubism의 Prefab 루트에 연결합니다.
protected update(deltaTime: number) { const { audioInput, samples, target, sampleRate, gain, smoothing } = this; // 'Fail' silently. if (audioInput == null || target == null || samples == null || sampleRate == 0) { return; } const { trunc, sqrt } = Math; const { currentTime } = audioInput; const pos = trunc(currentTime * this.sampleRate); let length = 256; switch (this.samplingQuality) { case CubismAudioSamplingQuality.veryHigh: length = 256; break; case CubismAudioSamplingQuality.maximum: length = 512; break; default: length = 256; break; } // Sample audio. let total = 0.0; for (let i = 0; i < length; i++) { const sample = samples.getData((pos + i) % samples.length); total += sample * sample; } // Compute root mean square over samples. let rms = sqrt(total / length) * gain; // Clamp root mean square. rms = math.clamp01(rms); // Smooth rms. const output = MathExtensions.Float.smoothDamp( this.lastRms, rms, this.velocityBuffer, smoothing * 0.1, undefined, deltaTime ); rms = output[0]; this.velocityBuffer = output[1]; // Set rms as mouth opening and store it for next evaluation. target.mouthOpening = rms; this.lastRms = rms; }
CubismAudioMouthInput에는 네 가지 설정 항목이 있습니다.
@property(AudioSource) public audioInput: AudioSource | null = null; @property({ type: Enum(CubismAudioSamplingQuality) }) public samplingQuality: CubismAudioSamplingQuality = CubismAudioSamplingQuality.high; @property({ type: CCFloat, slide: true, range: [1.0, 10.0, 0.01] }) public gain: number = 1.0; @property({ type: CCFloat, slide: true, range: [0.0, 1.0, 0.01] }) public smoothing: number = 0.0;
- audioInput
샘플링하는 AudioSource의 참조입니다. - samplingQuality
샘플링하는 음성의 정밀도입니다. - gain
샘플링한 값의 배율입니다.
1로 1배, 값을 크게 할수록 립싱크의 값도 커집니다. - smoothing
샘플링한 값의 스무딩 양입니다.
값이 클수록 립싱크 값이 매끄럽게 변합니다.