UE5에서 캐릭터 눈 깜빡임을 구현하는 가장 실용적인 방법

2026. 4. 2. 18:34·Dev./UE 언리얼 엔진
Blink Morph Target 트러블슈팅 Dev Log

 

캐릭터의 얼굴에 생기를 불어넣는 작업은 생각보다 까다롭다.

Eye Tracking, Facial Animation, Lip Sync...

선택지는 많지만, 어디서부터 시작해야 할지 막막한 경우가 대부분이다.

 

이 글은 그 출발점으로 눈 깜빡임(Blink)을 선택한 뒤, 최종 구현에 도달한 과정을 기록한 Dev Log다.

Unreal Engine 5.7 환경에서 Modify Curve 노드가 왜 문제를 일으켰는지,

Set Morph Target 방식이 왜 더 나은 선택이었는지를 구체적인 맥락과 함께 다룬다.

 

이 글을 끝까지 읽으면 세 가지를 얻는다.

  • 첫째, UE5 AnimInstance에서 Morph Target(메시의 형태를 변형하는 미리 정의된 변형 데이터)을 제어하는 구조를 이해할 수 있다.
  • 둘째, Modify Curve와 Set Morph Target 각각의 장단점과 선택 기준을 갖게 된다.
  • 셋째, 프로젝트에 바로 적용할 수 있는 Blink 시스템의 최소 구현 패턴을 알게 된다.

왜 Blink부터 시작했는가

기존에 참고한 데모 프로젝트(ABP_UE5_Demo_F)에는 눈 깜빡임, 시선 추적, 다양한 표정 등 여러 얼굴 표현이 포함되어 있었다.

 

이 중에서 첫 번째 이식 대상을 고르는 기준은 단순했다.

“구현 비용은 가장 작고, 체감 효과는 가장 큰 것.”

Eye Tracking(캐릭터의 눈동자가 카메라나 대상을 따라가는 기능)은 매력적이었지만,

본(Bone, 스켈레톤 안의 관절 하나하나) 이름이 여러 세트(aa_*, ac_*, an_*)로 혼재했고,

리그(Rig, 캐릭터의 뼈대 구조) 축이 머리와 동일하지 않았다.

 

시선의 움직임 자체가 워낙 미세해서, 구현 후에도 “안 보이는 건지, 안 움직이는 건지” 구분하는 데 시간이 더 들 상황이었다.

반면 Blink는 eyeBlinkLeft와 eyeBlinkRight, 단 두 개의 Morph Target만 0에서 1로 올렸다 내리면 된다.

비용 대비 캐릭터에 생기를 불어넣는 효과가 좋았다...!


이미 있던 것, 없던 것

프로젝트에는 이미 Presentation 시스템이라는 기반이 갖춰져 있다.

UGS_PresentationCameraComponent가 idle showcase(캐릭터가 대기 상태일 때 카메라가 자동으로 가까이 다가가는 연출) 진입과 이탈을 판정하고, UGS_PresentationPoseComponent가 머리·목·가슴의 카메라 상대 회전값을 계산했다.

 

UGS_SeekerAnimInstance는 이 값들을 ABP에 전달하는 브리지 역할을 이미 수행하고 있었다.

하지만 Blink 자체를 위한 런타임 값은 어디에도 없었다.

눈을 깜빡이게 만들 alpha 값을 생성하고 관리하는 로직을 처음부터 추가해야 했다.


C++ 쪽 구현: Blink Alpha 생성기

AnimInstance에 추가한 핵심 상태는 다섯 가지다.

PresentationBlinkAlpha는 현재 프레임에서의 눈 감김 정도를 0.0(완전히 뜬 상태)에서 1.0(완전히 감은 상태) 사이로 표현한다. BlinkTimeRemaining은 다음 깜빡임까지 남은 대기 시간이다.

CurrentBlinkDuration은 한 번의 깜빡임이 얼마나 걸리는지를 나타낸다.

BlinkElapsed는 현재 깜빡임이 시작된 이후 경과한 시간이다.

bBlinkActive는 지금 깜빡이는 중인지 아닌지를 나타내는 플래그다.

 

동작 방식은 직관적이다.

매 프레임 NativeUpdateAnimation()에서 UpdatePresentationBlink()를 호출한다.

평소에는 다음 깜빡임까지 랜덤으로 3~8초를 기다린다.

시간이 되면 깜빡임이 시작되고, alpha는 0 → 1 → 0 형태의 곡선을 그린다.

한 번의 깜빡임은 0.09~0.16초 안에 끝나며, 눈이 감기는 구간(closing phase)이 전체 duration의 35%를 차지한다. 사람은 눈을 감을 때보다 뜰 때가 약간 더 느리기 때문이다. 깜빡임이 끝나면 다시 랜덤 interval을 설정하고 대기한다.

 

차단 조건도 걸었다.

캐릭터가 빈사 상태(bIsDying)이거나 전신 몽타주가 재생 중(bIsFullBodySlotActive)일 때는 Blink를 억제한다.

죽어가는 캐릭터가 규칙적으로 눈을 깜빡이면 부자연스러울 것이고, 전신 애니메이션이 이미 눈 영역을 제어하고 있다면 충돌이 일어날 수 있기 때문이다.


첫 번째 시도: Modify Curve 노드 — 그리고 실패

처음 선택한 방법은 ABP_Seeker의 AnimGraph안에서 Modify Curve 노드를 사용하는 것이었다.

 

구상은 깔끔했다. Modify Curve 노드의 Curve Map에 eyeBlinkLeft = 1.0, eyeBlinkRight = 1.0을 설정하고, Alpha 핀에 C++에서 만든 Get_PresentationBlinkAlpha()를 연결한다. alpha가 0이면 curve 영향 없음, 1이면 눈 완전히 감김. 원리상 완벽했다.

 

그런데 두 가지 문제가 연달아 발생했다.

  • 첫 번째 문제는 UE 5.7의 편집 UX였다. Modify Curve 노드의 디테일 패널에서 Curve Map 엔트리를 직접 추가하려 했지만, 기대한 방식으로 항목 추가가 자연스럽게 드러나지 않았다. 결국 Map<Name, Float> 타입의 변수를 별도로 만들어 바인딩하는 우회 경로를 시도했다.
  • 두 번째 문제는 thread-safe 경고였다. Curve Map을 변수 바인딩으로 연결하자, 엔진이 thread-safe 위반 경고를 발생시켰다. UE5의 AnimGraph는 워커 스레드에서 평가되는데, 이 맥락에서 thread-safe가 보장되지 않는 데이터에 접근하면 엔진이 경고를 띄운다. 구조상 불가능한 것은 아니었지만, 프로젝트의 기존 패턴과 맞지 않았고 깔끔하지도 않았다.

결론은 명확했다. Modify Curve 방식은 버린다.


두 번째 시도: EventGraph + Set Morph Target — 그리고 한 번 더 실패

다음으로 전환한 방식은 ABP_Seeker의 EventGraph에서 직접 Set Morph Target을 호출하는 것이었다.

Blink는 morph 두 개만 건드리면 되니, AnimGraph의 복잡한 파이프라인을 거칠 필요가 없다.

 

그런데 여기서도 한 번 삐끗했다.

처음에는 OnUpdate 이벤트에서 Get Owning Component → Set Morph Target을 호출하려 했다.

 

문제는 OnUpdate가 thread-safe 그래프라는 것이었다.

이 영역은 순수한 계산만 허용되는 공간이고, Get Owning Component나 Set Morph Target 같은 게임 오브젝트 접근 함수는 게임 스레드에서만 호출할 수 있다. 당연히 오류가 발생했다.

 

핵심은 이것이다. ABP에는 두 종류의 업데이트 경로가 있다. OnUpdate(thread-safe 영역, 계산 전용)와 Blueprint Update Animation(게임 스레드 영역, 오브젝트 접근 가능).

 

Blink처럼 컴포넌트에 직접 값을 써야 하는 작업은 반드시 후자에서 해야 한다.


최종 구조: 이렇게 정리했다

최종 구현은 ABP_Seeker의 EventGraph 두 곳에 나뉘어 들어간다.

 

Blueprint Initialize Animation 단계에서는 Get Owning Component로 소유 컴포넌트를 가져와 SkeletalMeshComponent로 캐스팅(Cast, 특정 타입으로 변환하는 것)한 뒤, 그 결과를 BlinkMeshComp라는 변수에 저장한다. 매 프레임마다 컴포넌트를 찾는 낭비를 피하기 위해, 초기화 시점에 한 번만 캐시하는 것이다.

 

Blueprint Update Animation 단계에서는 매 프레임 다음 순서로 실행된다. 먼저 BlinkMeshComp가 유효한지 확인한다. 유효하면 C++에서 만든 Get_PresentationBlinkAlpha()를 호출해 현재 alpha 값을 가져온다. 이 값을 0.0~1.0 범위로 클램프(Clamp, 값을 지정된 범위 안에 가두는 것)한 뒤, Set Morph Target을 두 번 호출한다. 한 번은 eyeBlinkLeft, 한 번은 eyeBlinkRight. 두 호출 모두 같은 alpha 값을 사용하므로, 양쪽 눈이 동시에 같은 정도로 감기고 열린다.

 

이 구조의 데이터 흐름을 정리하면 이렇다. C++ AnimInstance가 매 프레임 blink alpha를 생성하고, ABP EventGraph가 그 값을 읽어 메시에 직접 적용한다. AnimGraph는 건드리지 않는다.


왜 이 방식이 더 나은가

성능. 게임 스레드에서 morph target 두 개의 weight를 갱신하는 비용은 사실상 무시할 수 있다. AnimGraph의 복잡한 파이프라인을 경유하지 않으므로, 기존 애니메이션 성능에 영향을 주지 않는다.

 

안정성. AnimGraph의 thread-safe 제약을 완전히 우회한다. Blueprint Update Animation은 게임 스레드에서 실행되므로, 컴포넌트 접근이나 morph target 설정에 아무런 제약이 없다. thread-safe 경고가 발생할 여지 자체가 없다.

 

디버깅 용이성. Morph target 이름과 alpha 값만 확인하면 된다. eyeBlinkLeft, eyeBlinkRight라는 이름이 메시에 존재하는지, alpha 값이 0~1 범위에서 올바르게 생성되는지 — 이 두 가지만 체크하면 문제를 추적할 수 있다.

 

물론 단점도 있다. EventGraph 쪽 로직이 늘어나고, 이 구조는 완전한 facial animation 시스템이라기보다는 blink 전용 예외 경로에 가깝다. Eye tracking 같은 확장 기능을 나중에 붙이려면 별도 설계가 필요하다.

 

'Dev. > UE 언리얼 엔진' 카테고리의 다른 글

UE5 Kawaii Physics로 머리카락 물리를 구현하며 마주친 문제  (0) 2026.03.27
[캐릭터] Ears 모프 타겟을 바꿨는데 에디터에서 왜 안보이지?  (0) 2026.03.27
UE5 이동속도 동기화와 모션매칭 안정화 트러블슈팅  (0) 2026.03.25
AI와 함께 Unreal Engine 플러그인을 만드는 워크플로우 — 삽질을 시스템으로 바꾸는 법  (0) 2026.03.19
UE5 Motion Matching 5편 :: 다른 프로젝트에 이 시스템을 옮기는 방법  (0) 2026.03.15
'Dev./UE 언리얼 엔진' 카테고리의 다른 글
  • UE5 Kawaii Physics로 머리카락 물리를 구현하며 마주친 문제
  • [캐릭터] Ears 모프 타겟을 바꿨는데 에디터에서 왜 안보이지?
  • UE5 이동속도 동기화와 모션매칭 안정화 트러블슈팅
  • AI와 함께 Unreal Engine 플러그인을 만드는 워크플로우 — 삽질을 시스템으로 바꾸는 법
raindrovvv
raindrovvv
raindrovvv 님의 블로그 입니다.
  • raindrovvv
    raindrovvv 님의 블로그
    raindrovvv
  • 전체
    오늘
    어제
    • 분류 전체보기 (170) N
      • Dev. (163) N
        • AI 인공지능 (27)
        • UE 언리얼 엔진 (81) N
        • Unity 유니티 (0)
        • Wwise 와이즈 (7)
        • 게임 네트워크 (8)
        • 그래픽스 Graphics (22)
        • 프로젝트 (8)
        • 기타 개발 관련 (10)
      • Computer Science (0)
        • 하드웨어 HW (0)
        • 소프트웨어 SW (0)
        • 통신 (0)
        • 데이터 (0)
      • 블로그 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    unrealengine
    Git
    AI
    에이전트
    Wwise
    그래픽스
    게임개발
    TA
    깃
    생산성
    Unreal
    바이브코딩
    언리얼엔진
    UE
    dev
    네트워크
    트러블슈팅
    devlog
    언리얼
    인디게임
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
raindrovvv
UE5에서 캐릭터 눈 깜빡임을 구현하는 가장 실용적인 방법
상단으로

티스토리툴바