3D 캐릭터 표정 모프 타겟(Blendshape) AI 자동 리깅 파이프라인
Blender 4.2 LTS와 Unity 6를 기준으로 얼굴 랜드마크 분석부터 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는 기준 메시와 변형 메시의 정점 차이를 가중치로 합산하는 방식이다. 기준 메시를 , i번째 모프 타겟을 , 가중치를 라고 하면 최종 정점 위치는 다음과 같이 계산한다.
예를 들어 jawOpen, mouthSmile_L, mouthSmile_R, eyeBlink_L, eyeBlink_R를 각각 독립적인 모프 타겟으로 만들 수 있다. 문제는 jawOpen과 mouthSmile이 동시에 적용될 때 입술 중심이 과도하게 늘어나는 것처럼 개별 타겟의 선형 합만으로는 모든 조합을 표현하기 어렵다는 점이다.
이를 보완하는 것이 corrective shape다. 두 개 이상의 기본 가중치를 입력으로 받아 문제가 발생하는 영역만 다시 변형한다.
실제 제작에서는 모든 조합을 만들지 않고 다음처럼 영향이 큰 조합만 선택한다.
- 턱을 연 상태의 미소:
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과 같은 의미 있는 파라미터다.
예를 들어 입을 여는 정도는 입술 상단과 하단의 거리로 계산할 수 있다.
좌우 미소는 입꼬리의 수평 이동과 광대 방향의 이동을 함께 사용한다. 단일 랜드마크만 참조하면 얼굴 회전이나 입술 두께에 영향을 받기 쉽다.
실무에서는 다음과 같은 표정 파라미터를 먼저 정의한 뒤 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를 조합해야 한다.

영역 마스크와 좌우 대칭
각 모프 타겟에 모든 정점을 동일하게 적용하지 말고 얼굴 영역 마스크를 사용한다. 예를 들어 eyeBlink_L은 왼쪽 눈꺼풀과 주변 피부에만 영향을 주고 입술과 턱 영역에는 0에 가까운 가중치를 준다.
좌우 표정은 완전한 대칭만 사용하면 캐릭터가 기계적으로 보일 수 있다. 따라서 제작 단계에서는 좌우 대칭을 기준으로 생성한 뒤 런타임이나 애니메이션 레이어에서 작은 비대칭을 추가하는 것이 안전하다.
다음 오차를 자동 검사 항목으로 둘 수 있다.
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 렌더 결과 | 제작 승인 기준 설정 |
프레임 변동량은 다음처럼 계산할 수 있다.
단, 변동량이 항상 작아야 좋은 것은 아니다. 빠르게 눈을 감거나 크게 웃는 동작까지 지나치게 평활화하면 반응이 늦어진다. 따라서 자연스러운 빠른 동작과 노이즈를 구분할 수 있도록 표정별 필터 파라미터를 따로 관리해야 한다.
Blender에서 자동 생성 작업을 어떻게 배치할까?
Blender Python에서는 다음 순서로 배치 작업을 구성할 수 있다.
- 기준
.blend파일을 열고 얼굴 메시를 찾는다. - 표정 파라미터와 대상 Blendshape 이름이 담긴 JSON manifest를 읽는다.
- AI 추론 결과를 정규화하고 가중치 범위를 제한한다.
- 모프 타겟을 생성하거나 기존 타겟에 가중치를 적용한다.
- 대칭 오차와 충돌 검사 결과를 JSON 리포트로 저장한다.
- 승인된 결과만 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 → 엔진 검증의 단계를 분리하면 모델을 교체하거나 캐릭터를 추가해도 작업 구조를 유지할 수 있다.
처음부터 모든 표정을 자동 생성하려 하기보다 눈 깜빡임, 턱 열기, 좌우 미소처럼 핵심 셰이프부터 정의하고 명명 규칙과 품질 지표를 고정하는 것이 좋다. 이후 조합별 보정 셰이프와 런타임 필터를 추가하면 자동화 속도와 최종 표정 품질을 함께 확보할 수 있다.



