3D 캐릭터 표정 모프 타겟(Blendshape) AI 자동 리깅 파이프라인

Blender 4.2 LTS와 Unity 6를 기준으로 얼굴 랜드마크 분석부터 Blendshape 생성, 보정 셰이프, 런타임 검증까지 AI 표정 리깅 파이프라인을 정리합니다.

✓ 복사 완료!
챕터애니메이션 & 리깅
난이도중급
상태● 검토 완료
대상 엔진
Unity
최종 업데이트
3D 캐릭터 표정 모프 타겟(Blendshape) AI 자동 리깅 파이프라인

핵심 요약

3D 캐릭터 표정 모프 타겟 AI 자동 리깅은 참조 영상이나 FACS 라벨에서 얼굴 랜드마크를 추출하고 이를 표정 파라미터와 Blendshape 가중치로 변환하는 작업이다. 핵심은 AI가 만든 표정을 그대로 사용하는 것이 아니라 기준 포즈, 토폴로지, 좌우 대칭, 보정 셰이프, 런타임 클램프를 포함한 검증 단계까지 자동화하는 데 있다.

이 글의 기준 구현은 Blender 4.2 LTS의 Python 3.11 API, Unity 6(6000.0)의 SkinnedMeshRenderer, ONNX Runtime 기반 추론 파이프라인이다.

3D 표정 자동 리깅에서 가장 어려운 문제는 무엇인가?

얼굴 표정 자동화는 단순히 여러 개의 표정 모델을 만드는 작업이 아니다. 하나의 캐릭터 메시가 눈썹, 눈꺼풀, 입술, 볼, 턱의 움직임을 서로 간섭하지 않고 조합해야 한다.

특히 다음 문제가 반복적으로 발생한다.

문제원인자동화 전략
입술과 턱이 함께 무너짐모프 타겟 간 영향 범위 중복영역 마스크와 corrective shape 사용
좌우 표정이 다르게 보임비대칭 토폴로지 또는 학습 데이터 편향좌우 미러링과 대칭 오차 검사
눈꺼풀이 안구를 뚫음안구 위치와 eyelid shape의 기준 불일치충돌 거리 검사와 보정 셰이프 생성
표정이 떨림프레임별 AI 추론값의 노이즈EMA 또는 One Euro Filter 적용
엔진에서 표정 이름이 연결되지 않음DCC와 런타임의 명명 규칙 불일치Blendshape manifest를 단일 기준으로 관리
모든 표정 조합이 자연스럽지 않음개별 모프 타겟만 제작조합별 corrective shape와 가중치 제한 적용

AI는 랜드마크에서 표정 변화를 빠르게 추정할 수 있지만 메시의 실제 변형 품질까지 보장하지는 않는다. 따라서 자동 리깅 파이프라인은 생성 모델과 규칙 기반 검증기를 함께 구성해야 한다.

Blendshape와 모프 타겟은 어떤 원리로 작동하는가?

Blendshape는 기준 메시와 변형 메시의 정점 차이를 가중치로 합산하는 방식이다. 기준 메시를 VbaseV_{base}, i번째 모프 타겟을 TiT_i, 가중치를 wiw_i라고 하면 최종 정점 위치는 다음과 같이 계산한다.

V(w)=Vbase+∑i=1nwi(Ti−Vbase),0≤wi≤1V(w) = V_{base} + \sum_{i=1}^{n} w_i(T_i - V_{base}), \quad 0 \leq w_i \leq 1

예를 들어 jawOpen, mouthSmile_L, mouthSmile_R, eyeBlink_L, eyeBlink_R를 각각 독립적인 모프 타겟으로 만들 수 있다. 문제는 jawOpen과 mouthSmile이 동시에 적용될 때 입술 중심이 과도하게 늘어나는 것처럼 개별 타겟의 선형 합만으로는 모든 조합을 표현하기 어렵다는 점이다.

이를 보완하는 것이 corrective shape다. 두 개 이상의 기본 가중치를 입력으로 받아 문제가 발생하는 영역만 다시 변형한다.

C=f(wjawOpen,wmouthSmileL,wmouthSmileR)C = f(w_{jawOpen}, w_{mouthSmile_L}, w_{mouthSmile_R})

실제 제작에서는 모든 조합을 만들지 않고 다음처럼 영향이 큰 조합만 선택한다.

  • 턱을 연 상태의 미소: jawOpen_mouthSmile
  • 눈을 감은 상태의 미소: eyeBlink_smile
  • 눈썹을 올린 상태의 찡그림: browUp_browDown
  • 입술을 오므린 상태의 턱 내림: jawOpen_mouthPucker

AI 자동 리깅 파이프라인은 어떻게 구성할까?

전체 흐름은 입력 데이터 정규화, 표정 파라미터 추론, 메시 변형, 품질 검증, 엔진 내보내기로 나눈다.

flowchart TD
  A["참조 영상 또는 FACS 라벨"] --> B["얼굴 랜드마크 추출"]
  B --> C["머리 회전과 스케일 정규화"]
  C --> D["표정 파라미터 추론"]
  D --> E["Blendshape 가중치 변환"]
  E --> F["Blender 모프 타겟 생성"]
  F --> G["대칭·충돌·범위 검증"]
  G --> H["Corrective Shape 생성"]
  H --> I["FBX 또는 glTF 내보내기"]
  I --> J["Unity 6 런타임 테스트"]

1단계: 기준 메시와 명명 규칙을 고정한다

AI 추론보다 먼저 기준 메시의 상태를 고정해야 한다. 기준 포즈가 프레임마다 달라지면 AI가 계산한 가중치가 정확해도 결과 메시가 흔들린다.

권장 기준은 다음과 같다.

항목권장 기준
기준 포즈중립 표정, 입 다문 상태, 눈 정면
좌표계캐릭터 정면을 +Z 또는 프로젝트 규칙으로 고정
메시모든 표정에서 정점 수와 인덱스 순서 유지
스케일실제 단위 또는 프로젝트 단위로 통일
명명 규칙eyeBlink_L, eyeBlink_R, jawOpen처럼 의미 기반 사용
저장 형식원본 .blend, 교환용 FBX 또는 glTF 분리

얼굴 랜드마크는 캐릭터마다 얼굴 크기와 카메라 거리가 다르므로 눈 사이 거리나 코 길이를 기준으로 정규화한다. 머리 회전도 먼저 제거해야 한다. 그렇지 않으면 고개를 돌린 동작을 눈썹 움직임으로 잘못 해석할 수 있다.

2단계: 얼굴 랜드마크를 표정 파라미터로 변환한다

랜드마크 모델은 눈꼬리, 눈꺼풀, 입술 모서리, 코, 턱 끝 등의 위치를 반환한다. 하지만 엔진에서 필요한 것은 좌표가 아니라 blink, smile, jawOpen과 같은 의미 있는 파라미터다.

예를 들어 입을 여는 정도는 입술 상단과 하단의 거리로 계산할 수 있다.

jawOpen=clamp(d(lipUpper,lipLower)−dneutraldopen−dneutral,0,1)jawOpen = clamp\left(\frac{d(lipUpper, lipLower) - d_{neutral}}{d_{open} - d_{neutral}}, 0, 1\right)

좌우 미소는 입꼬리의 수평 이동과 광대 방향의 이동을 함께 사용한다. 단일 랜드마크만 참조하면 얼굴 회전이나 입술 두께에 영향을 받기 쉽다.

실무에서는 다음과 같은 표정 파라미터를 먼저 정의한 뒤 Blendshape에 매핑한다.

표정 파라미터주로 사용하는 랜드마크Blendshape 예시
눈 깜빡임위·아래 눈꺼풀eyeBlink_L, eyeBlink_R
턱 열기윗입술, 아랫입술, 턱 끝jawOpen
미소좌우 입꼬리, 광대mouthSmile_L, mouthSmile_R
입술 오므리기입술 외곽mouthPucker
눈썹 올리기눈썹과 눈 위쪽 랜드마크browUp_L, browUp_R
찡그리기눈썹 안쪽과 미간browDown_L, browDown_R

3단계: AI 출력값을 Blendshape 가중치로 매핑한다

AI 모델이 직접 정점 위치를 출력하게 만들 수도 있지만 제작 파이프라인에서는 의미 기반 파라미터를 출력하는 방식이 관리하기 쉽다. 모델을 교체해도 캐릭터별 매핑 레이어를 유지할 수 있기 때문이다.

권장 구조는 다음과 같다.

  • AI 모델: 랜드마크 또는 영상 프레임을 표정 파라미터로 변환
  • 매핑 레이어: 표정 파라미터를 캐릭터별 Blendshape 이름으로 변환
  • 규칙 레이어: 최소·최대값, 상호 배제, 좌우 대칭 규칙 적용
  • 보정 레이어: 특정 조합에서 corrective shape 가중치 추가

가중치 변환은 단순한 1:1 연결보다 커브와 우선순위를 포함하는 편이 안정적이다.

from dataclasses import dataclass

@dataclass
class ShapeRule:
    source: str
    target: str
    scale: float = 1.0
    offset: float = 0.0
    minimum: float = 0.0
    maximum: float = 1.0


def remap(value: float, rule: ShapeRule) -> float:
    result = value * rule.scale + rule.offset
    return max(rule.minimum, min(rule.maximum, result))

rules = [
    ShapeRule("blink_left", "eyeBlink_L", scale=1.0),
    ShapeRule("blink_right", "eyeBlink_R", scale=1.0),
    ShapeRule("jaw_open", "jawOpen", scale=0.9, maximum=0.95),
    ShapeRule("smile_left", "mouthSmile_L", scale=1.1),
    ShapeRule("smile_right", "mouthSmile_R", scale=1.1),
]

여기서 maximum=0.95처럼 캐릭터별 상한을 두면 극단적인 AI 오검출이 얼굴을 비정상적으로 변형하는 것을 막을 수 있다.

눈과 입이 무너지는 문제는 어떻게 보정할까?

얼굴은 눈, 입, 코, 볼이 좁은 영역에 밀집되어 있어 모프 타겟 간 충돌이 자주 발생한다. 이를 해결하려면 모프 타겟을 기능별 영역으로 나누고 corrective shape를 조합해야 한다.

3D 캐릭터 얼굴의 기본 표정 모프 타겟과 영향 영역을 보여주는 작업 화면

영역 마스크와 좌우 대칭

각 모프 타겟에 모든 정점을 동일하게 적용하지 말고 얼굴 영역 마스크를 사용한다. 예를 들어 eyeBlink_L은 왼쪽 눈꺼풀과 주변 피부에만 영향을 주고 입술과 턱 영역에는 0에 가까운 가중치를 준다.

좌우 표정은 완전한 대칭만 사용하면 캐릭터가 기계적으로 보일 수 있다. 따라서 제작 단계에서는 좌우 대칭을 기준으로 생성한 뒤 런타임이나 애니메이션 레이어에서 작은 비대칭을 추가하는 것이 안전하다.

다음 오차를 자동 검사 항목으로 둘 수 있다.

Esym=1N∑i=1N∥ML(vi)−Mirror(MR(vi))∥E_{sym} = \frac{1}{N} \sum_{i=1}^{N} \left\|M_L(v_i) - Mirror(M_R(v_i))\right\|

E_sym이 임계값을 초과하면 좌우 정점 대응이나 랜드마크 매핑을 확인한다.

충돌 거리 검사

눈꺼풀과 안구의 최소 거리를 검사하면 눈을 감을 때 안구를 뚫는 오류를 빠르게 찾을 수 있다. 거리 값이 임계값보다 작으면 눈꺼풀의 이동량을 줄이거나 eyeBlink_corrective 셰이프를 추가한다.

또한 입술 안쪽은 치아와 혀에 쉽게 관통하므로 jawOpen과 mouthSmile 조합에서 별도의 내부 마스크를 적용하는 것이 좋다.

눈꺼풀과 안구 사이의 충돌 검사를 시각화한 디버그 뷰

AI 표정 리깅을 엔진에서 어떻게 검증할까?

DCC 툴에서 모프 타겟이 정상적으로 보여도 엔진에서는 다음 문제가 나타날 수 있다.

  • FBX 내보내기 과정에서 Blendshape 이름이 변경됨
  • 가중치가 0~100 또는 0~1 범위로 다르게 해석됨
  • 본 애니메이션과 표정 모프가 같은 버텍스를 중복 변형함
  • 업데이트 순서에 따라 눈꺼풀과 안구 위치가 어긋남
  • 프레임별 추론 노이즈로 표정이 떨림

Unity 6에서는 SkinnedMeshRenderer.SetBlendShapeWeight로 가중치를 적용할 수 있다. 이 예제는 0~100 범위로 가져온 Blendshape를 가정하므로 프로젝트 내부의 0~1 값을 엔진 호출 시 100배 한다. 실제 가중치 범위는 메시의 Blendshape 프레임에 정의된 값에 맞춰 변환해야 한다.

using UnityEngine;

public sealed class FaceBlendshapeDriver : MonoBehaviour
{
    [SerializeField] private SkinnedMeshRenderer faceMesh;
    [SerializeField] private float smoothing = 12f;

    private float currentJawOpen;
    private int jawOpenIndex;

    private void Awake()
    {
        jawOpenIndex = faceMesh.sharedMesh.GetBlendShapeIndex("jawOpen");
    }

    public void ApplyJawOpen(float normalizedValue, float deltaTime)
    {
        if (jawOpenIndex < 0) return;

        float target = Mathf.Clamp01(normalizedValue);
        currentJawOpen = Mathf.Lerp(
            currentJawOpen,
            target,
            1f - Mathf.Exp(-smoothing * deltaTime)
        );

        faceMesh.SetBlendShapeWeight(jawOpenIndex, currentJawOpen * 100f);
    }
}

Mathf.Lerp에 고정된 보간값을 사용하는 대신 시간 기반 지수 보간을 사용하면 프레임 속도가 달라도 비슷한 반응성을 유지할 수 있다. 실제 프로젝트에서는 blink, smile, jawOpen 각각에 필터를 적용하고 급격한 변화에는 별도의 공격·릴리즈 시간을 둘 수 있다.

표정 자동화 품질은 어떤 지표로 측정할까?

눈으로만 확인하면 표정 품질을 일관되게 비교하기 어렵다. 최소한 다음 지표를 자동 리포트로 남기는 것이 좋다.

지표계산 대상활용 목적
랜드마크 오차예측 위치와 기준 위치AI 추론 정확도 확인
좌우 대칭 오차미러링 후 정점 차이비대칭 오류 탐지
충돌 횟수안구·치아·혀와 피부의 교차관통 오류 확인
가중치 범위 위반0 미만 또는 1 초과 값런타임 안정성 확인
프레임 변동량연속 프레임 가중치 차이떨림과 노이즈 확인
조합별 아티팩트주요 corrective pose 렌더 결과제작 승인 기준 설정

프레임 변동량은 다음처럼 계산할 수 있다.

Jtemporal=1T−1∑t=2T∥wt−wt−1∥1J_{temporal} = \frac{1}{T-1} \sum_{t=2}^{T} \left\|w_t - w_{t-1}\right\|_1

단, 변동량이 항상 작아야 좋은 것은 아니다. 빠르게 눈을 감거나 크게 웃는 동작까지 지나치게 평활화하면 반응이 늦어진다. 따라서 자연스러운 빠른 동작과 노이즈를 구분할 수 있도록 표정별 필터 파라미터를 따로 관리해야 한다.

Blender에서 자동 생성 작업을 어떻게 배치할까?

Blender Python에서는 다음 순서로 배치 작업을 구성할 수 있다.

  1. 기준 .blend 파일을 열고 얼굴 메시를 찾는다.
  2. 표정 파라미터와 대상 Blendshape 이름이 담긴 JSON manifest를 읽는다.
  3. AI 추론 결과를 정규화하고 가중치 범위를 제한한다.
  4. 모프 타겟을 생성하거나 기존 타겟에 가중치를 적용한다.
  5. 대칭 오차와 충돌 검사 결과를 JSON 리포트로 저장한다.
  6. 승인된 결과만 FBX 또는 glTF로 내보낸다.

manifest는 다음처럼 버전 관리할 수 있다.

{
  "character": "hero_a",
  "schemaVersion": 1,
  "mesh": "Face",
  "units": "normalized_0_1",
  "shapes": {
    "blink_left": "eyeBlink_L",
    "blink_right": "eyeBlink_R",
    "jaw_open": "jawOpen",
    "smile_left": "mouthSmile_L",
    "smile_right": "mouthSmile_R"
  },
  "correctives": [
    {
      "name": "jawOpen_mouthSmile",
      "inputs": ["jawOpen", "mouthSmile_L", "mouthSmile_R"],
      "threshold": 0.65
    }
  ]
}

manifest를 별도 파일로 두면 Blender, Unity, Unreal Engine 5.x에서 같은 명명 규칙을 공유할 수 있다. 이름을 코드에 직접 작성하면 모델이나 캐릭터가 늘어날수록 누락된 셰이프를 찾기 어려워진다.

자주 묻는 질문(FAQ)

AI가 만든 Blendshape를 바로 게임에 사용해도 될까?

권장하지 않는다. 정점 수, 기준 포즈, 좌우 대응, 안구·치아 관통, 엔진의 가중치 범위를 먼저 검증해야 한다. AI는 생성과 추정 시간을 줄이는 도구이며 최종 품질 승인기는 아니다.

본 리깅과 Blendshape 중 무엇을 선택해야 할까?

턱과 머리처럼 큰 회전은 본 리깅이 효율적이고 입술·눈꺼풀·볼처럼 미세한 피부 변형은 Blendshape가 유리하다. 일반적인 얼굴 시스템은 본과 Blendshape를 함께 사용하는 하이브리드 구성이 안정적이다.

표정이 떨릴 때 가장 먼저 확인할 것은 무엇인가?

랜드마크 정규화와 머리 회전 제거를 먼저 확인한다. 그다음 AI 출력에 EMA, One Euro Filter 또는 공격·릴리즈 기반 평활화를 적용하고 마지막으로 엔진의 프레임 업데이트 순서를 점검한다.

정리

3D 캐릭터 표정 모프 타겟 AI 자동 리깅의 핵심은 AI 모델 자체가 아니라 데이터 계약과 검증 파이프라인이다. 랜드마크 → 표정 파라미터 → Blendshape 가중치 → corrective shape → 엔진 검증의 단계를 분리하면 모델을 교체하거나 캐릭터를 추가해도 작업 구조를 유지할 수 있다.

처음부터 모든 표정을 자동 생성하려 하기보다 눈 깜빡임, 턱 열기, 좌우 미소처럼 핵심 셰이프부터 정의하고 명명 규칙과 품질 지표를 고정하는 것이 좋다. 이후 조합별 보정 셰이프와 런타임 필터를 추가하면 자동화 속도와 최종 표정 품질을 함께 확보할 수 있다.