립싱크

업데이트: 2022/10/06

립싱크 파라미터 특정

립싱크 효과를 사용하면 모델에 립싱크 동작을 적용할 수 있습니다.
립싱크 이펙트를 적용하기 위해서 다음의 처리를 실시합니다.

  • .model3.json 파일에 기술된 립싱크 효과의 값을 적용하는 파라미터와의 매칭
  • 음성 입력, 모션, 그 외의 방법으로 립싱크 이펙트에 수치를 전달

이 중 .model3.json 파일에 기술되는 립싱크 이펙트와 파라미터를 매칭시키는 정보는
ICubismModelSetting 인터페이스를 구현하는 CubismModelSettingJson 클래스를 활용하여 얻을 수 있습니다.

// C++
for (csmInt32 i = 0; i < _modelSetting->GetLipSyncParameterCount(); ++i)
{
   CubismIdHandle lipsyncParameter = _modelSetting->GetLipSyncParameterId(i);
}
// TypeScript
for(let i: number = 0; i < _modelSetting.getLipSyncParameterCount(); ++i)
{
    let lipSyncParameter = _modelSetting.getLipSyncParameterId(i);
}
// Java
for(int i = 0; i < _modelSetting.getLipSyncParameterCount(); i++) {
CubismId lipSyncParameter = _modelSetting.getLipSyncParameterId(i);
}

.model3.json 파일 안에 정의를 넣으려면 「눈 깜빡임 설정」을 확인하십시오.
Editor상에서 눈 깜빡임·립싱크 설정을 실시하고 나서 출력하면, .model3.json 파일에는 이하와 같이 기술됩니다.

{
    ... 생략 ...
       "Groups": [
                {
                        "Target": "Parameter",
                        "Name": "LipSync",
                        "Ids": [
                                "ParamMouthOpenY"
                        ]
                },
                {
                        "Target": "Parameter",
                        "Name": "EyeBlink",
                        "Ids": [
                                "ParamEyeLOpen",
                                "ParamEyeROpen"
                        ]
                }
        ]
}

립싱크를 수행하는 세 가지 방법

립싱크는 크게 이하의 3가지로 분류됩니다.

1. 실시간으로 음량을 취득하고 개폐도를 직접 지정하는 방식

음성 레벨을 어떠한 방법으로 취득해, 대상의 파라미터에 맞추어 척도를 맞춘 것을 지정함으로써
실시간 립싱크를 실현합니다.

// C++
csmFloat32 value = GetAudioLevelExampleFunction(); // 최근 음량 레벨을 취득합니다.

for (csmInt32 i = 0; i < _modelSetting->GetLipSyncParameterCount(); ++i)
{
    _model->AddParameterValue(_modelSetting->GetLipSyncParameterId(i), value, 0.8f);
}
// TypeScript
let value: number = getAudioLevelExampleFunction(); // 가장 최근의 음량 레벨을 취득합니다.

for(let i: number = 0; i < _modelSetting.getLipSyncParameterCount(); ++i)
{
    _model.addParameterValue(_modelSetting.getLipSyncParameterId(i), value, 0.8);
}
// Java
float value = getAudioLevelExampleFunction(); // 가장 최근의 음량 레벨을 취득합니다
for(int i = 0; i < _modelSetting.getLipSyncParameterCount(); i++){
_model.addParameterValue((_modelSetting.getLipSyncParameterId(i), value, 0.8f);
}

Native(C++)의 CubismModel::Update 함수, Web(TypeScript), Java의 CubismModel.update 함수보다 앞에 Native(C++)의 CubismModel::SetParameterValue 함수, Web(TypeScript), Java의 CubismModel.setParameterValue 함수 및 Native(C++)의 CubismModel::AddParameterValue 함수, Web(TypeScript), Java의 CubismModel.addParameterValue 함수의 제2인수에 직접 0~1의 값을 설정함으로써 입이 열리는 정도를 제어할 수 있습니다.

iPhone/Android2.3 이상(※)은 재생 중인 음량을 실시간으로 취득할 수 있습니다.
취득한 재생 중의 음량의 값을 0..1의 범위로 가공해, 그 값을 상기의 명령으로 설정하면 립싱크시킬 수 있습니다.
(표준 파라미터 설정대로 입의 개폐는 0에서 1의 파라미터로 작성하기 때문)

설정하는 값은 0 미만, 혹은 1 이상이라도 오류가 되지는 않지만, 그 경우 립싱크가 올바르게 동작하지 않는 경우가 있습니다.
(※): Android2.2 이하의 경우 실행 시에 재생 중인 볼륨을 취득할 수 없습니다.
다른 플랫폼에서 실시간으로 볼륨을 취득할 수 있는지 여부는 오디오 재생 라이브러리에 따라 다릅니다.

iPhone에서의 취득 방법: AVAudioPlayer 클래스
Android에서의 취득 방법: Visualizer 클래스

2. 립싱크용 정보를 가진 모션을 사용하는 방식

Editor에서의 작업으로 모션 자체에 음성의 움직임을 도입하는 방법입니다.
모션에 립싱크의 모션을 넣는 방법은 「BGM이나 음성을 사용한 장면 생성」을 확인해 주십시오.
재생 전에 Native(C++)의 CubismMotion::SetEffectIds 함수 또는 Web(TypeScript), Java의 CubismMotion.setEffectIds 함수를 사용해 립싱크, 눈 깜빡임의 파라미터군을 세팅하면
CubismMotion 인스턴스의 파라미터 갱신 처리 시에 대상 파라미터로 바꾼 후 모션을 재생합니다.

// C++
    // .model3.json에 기술된 눈 깜빡임용 파라미터 불러오기
    csmVector<CubismIdHandle> eyeBlinkIds;
    csmInt32 eyeBlinkCount = _modelSetting->GetEyeBlinkParameterCount();
    for (csmInt32 i = 0; i < eyeBlinkCount; i++)
    {
        eyeBlinkIds.PushBack(_modelSetting->GetEyeBlinkParameterId(i));
    }

    // .model3.json에 기술된 립싱크용 파라미터 불러오기
    csmVector<CubismIdHandle> lipSyncIds;
    csmInt32 lipSyncCount = _modelSetting->GetLipSyncParameterCount();
    for (csmInt32 i = 0; i < lipSyncCount; i++)
    {
        lipSyncIds.PushBack(_modelSetting->GetLipSyncParameterId(i));
    }

    // 모션 파일 불러오기
    csmByte* buffer;
    csmSizeInt size;
    csmString path = "example.motion3.json";
    buffer = CreateBuffer(path.GetRawString(), &size);
    CubismMotion* tmpMotion = static_cast<CubismMotion*>(LoadMotion(buffer, size, name.GetRawString()));
    DeleteBuffer(buffer, path.GetRawString());

    // 가져온 모션 파일에 눈 깜빡임용 파라미터와 립싱크용 파라미터를 등록
    tmpMotion->SetEffectIds(eyeBlinkIds, lipSyncIds);
// TypeScript
    // .model3.json에 기술된 눈 깜빡임용 파라미터 불러오기
    let eyeBlinkIds: csmVector<CubismIdHandle> = new csmVector<CubismIdHandle>();
    let eyeBlinkCount: number = _modelSetting.getEyeBlinkParameterCount();
    for(let i: number = 0; i < eyeBlinkCount; i++)
    {
        eyeBlinkIds.pushBack(_modelSetting.getEyeBlinkParameterId(i));
    }

    // .model3.json에 기술된 립싱크용 파라미터 불러오기
    let lipSyncIds: csmVector<CubismIdHandle> = new csmVector<CubismIdHandle>();
    let lipSyncCount: number = _modelSetting.getLipSyncParameterCount();
    for(let i: number = 0; i < lipSyncCount; i++)
    {
        lipSyncIds.pushBack(_modelSetting.getLipSyncParamterId(i));
    }

    // 모션 파일 불러오기
    let path: string = fileName;
    path = this._modelHomeDir + path;

    fetch(path).then(
        (response) =>
        {
            return response.arrayBuffer();
        }
    ).then(
        (arrayBuffer) =>
        {
            let buffer: ArrayBuffer = arrayBuffer;
            let size = buffer.byteLength;
            let tmpMotion = <CubismMotion>this.loadMotion(buffer, size, name);
            deleteBuffer(buffer, path);

            // 불러오기의 일환으로 모션 파일에 눈 깜빡임용 파라미터와 립싱크용 파라미터를 등록
            motion.setEffectIds(this._eyeBlinkIds, this._lipSyncIds);
        }
    );
// Java
    // .model3.json에 기술된 눈 깜빡임용 파라미터 불러오기
    List<CubismId> eyeBlinkIds;
    int eyeBlinkCount = _modelSetting.getEyeBlinkParameterCount();
    for (int i = 0; i < eyeBlinkCount; i++) {
        eyeBlinkIds.add(_modelSetting.getEyeBlinkParameterId(i));
    }
    // .model3.json에 기술된 립싱크용 파라미터 불러오기
    List<CubismId> lipSyncIds;
    int lipSyncCount = _modelSetting.getLipSyncParameterCount();
    for (int i = 0; i < lipSyncCount; i++) {
        lipSyncIds.add(_modelSetting.getLipSyncParameterId(i));
    }
    // 모션 파일 불러오기
    byte[] buffer;
    String path = "example.motion3.json";
    buffer = createBuffer(path);
    CubismMotion tmpMotion = loadMotion(buffer);
    // 가져온 모션 파일에 눈 깜빡임용 파라미터와 립싱크용 파라미터를 등록
    tmpMotion.setEffectIds(eyeBlinkIds, lipSyncIds);

3. 립싱크용 정보 전용 모션 사용 방법

Native:

2에서 취급한 모션을 전용으로 취급하는 모션 매니저를 준비해, 입만 컨트롤하는 방법입니다.
몸이나 머리의 동작 모션과 립싱크를 나누고 싶을 때 유용합니다.

// C++
    /**
     * @brief 사용자가 실제로 사용하는 모델의 구현 클래스<br>
     *         모델 생성, 기능 컴포넌트 생성, 업데이트 처리 및 렌더링을 호출한다.
     *
     */
    class LAppModel : public Csm::CubismUserModel
    {
    /*생략*/  

    private:    
        CubismMotionManager*    _mouthMotionManager; // <<< 추가
    };
// C++
    void LAppModel::Update()
    {
 /*생략*/  

        //-----------------------------------------------------------------
        _model->LoadParameters(); // 마지막으로 저장된 상태를 로드
        if (_motionManager->IsFinished())
        {
            // 모션 재생이 없으면 대기 모션 중에서 랜덤 재생
            StartRandomMotion(MotionGroupIdle, PriorityIdle);
        }
        else
        {
            const csmFloat32 playSpeed = pow(2, (csmFloat32)_motionSpeed / 10.0);
            motionUpdated = _motionManager->UpdateMotion(_model, deltaTimeSeconds * playSpeed); // 모션 업데이트
        }
        _mouthMotionManager->UpdateMotion(_model, deltaTimeSeconds); // <<< 추가
        _model->SaveParameters(); // 상태 저장
        //-----------------------------------------------------------------

/*생략*/  

    }
// C++
	_mouseMotionManager->StartMotionPriority(lipsyncMotion, autoDelete, priority);

Web:

2에서 취급한 모션을 전용으로 취급하는 모션 매니저를 준비해, 입만 컨트롤하는 방법입니다.
몸이나 머리의 동작 모션과 립싱크를 나누고 싶을 때 유용합니다.

// TypeScript
    export class LAppModel extends CubismUserModel {
/*생략*/

        _mouthMotionManager: CubismMotionManager; // <<< 추가
    }
// TypeScript
	public update(): void
    {
/*생략*/

        //--------------------------------------------------------------------------
        this._model.loadParameters();   // 마지막으로 저장된 상태를 로드
        if(this._motionManager.isFinished())
        {
            // 모션 재생이 없으면 대기 모션 중에서 랜덤 재생
            this.startRandomMotion(LAppDefine.MotionGroupIdle, LAppDefine.PriorityIdle);
            
        }
        else
        {
            motionUpdated = this._motionManager.updateMotion(this._model, deltaTimeSeconds);    // 모션 업데이트
        }
		_mouthMotionManager.udpateMotion(_model, deltaTimeSeconds); // <<< 추가
        this._model.saveParameters(); // 상태 저장
        //--------------------------------------------------------------------------

/*생략*/
    }
// TypeScript
	_mouseMotionManager.startMotionPriority(lipSyncMotion, autoDelete, priority);

Java :

2에서 취급한 모션을 전용으로 취급하는 모션 매니저를 준비해, 입만 컨트롤하는 방법입니다.
몸이나 머리의 동작 모션과 립싱크를 나누고 싶을 때 유용합니다.

// Java
/**
 * 사용자가 실제로 사용하는 모델의 구현 클래스
 * 모델 생성, 기능 컴포넌트 생성, 업데이트 처리 및 렌더링을 호출한다
 */
public class LAppModel extends CubismUserModel {
    // 생략

    private CubismMotionManager _mouthMotionManager; // <<< 추가
}
// Java
public void update() {
/*생략*/
    // -----------------------------
    _model.loadParameters(); // 마지막으로 저장된 상태를 로드
    if (_motionManager.isFinished()) {
        // 모션 재생이 없으면 대기 모션 중에서 랜덤 재생
        startRandomMotion(LAppDefine.MotioNGroup.IDLE.getId(), LAppDefine.Priority.IDLE.getPriority());
    } else {
        final float playSpeed = Math.pow(2, _motionSpeed / 10);
        isMotionUpdated = _motionManager.updateMotion(_model, deltaTimeSeconds * playSpeed); // 모션 업데이트
    }  
    _mouthMotionManager.updateMotion(_model, deltaTimeSeconds); // <<< 추가
    _model.saveParameters(); // 상태 저장
    // -----------------------------
/*생략*/
}
// Java
_mouseMotionManager.startMotionPriority(lipSyncMotion, autoDelete, priority);
이 기사에 관한 의견 및 요청사항을 보내 주시기 바랍니다.