/ email – joohangi1@naver.com / tel – 010.7249.2623
- VRM에 Mixamo 애니메이션 적용하는 툴 제작.
- VRM의 헤어 시뮬레이션과 블렌드 쉐이프 적용.
- ALEMBIC 방식을 차용한 애니메이션 방식 개발.
- 모든 애니메이션을 GPU 연산.
- 지오메트리 쉐이더에서 특수 이팩트 구현.
//


일본 미소녀 풍의 인디게임 제작 시 리소스에 비용상 많이 고민하게 된다. 이때 도달하게 되는 결론 중 하나가 VRM 파일을 높은 퀄리티로 무료로 제작할 수 있는 Vroid Studio를 사용하는 것이다. 하지만 이로써 제작한 VRM 파일은 Unity 게임엔진에서 작동하지 않기 때문에 UniVRM이라는 패키지를 유니티에 설치하여 Unity에서 VRM 파일을 임포트할 수 있다.
//



VRM 파일을 Unity에 임포트하였다면 두 번째 문제에 직면하게 되는데 그것은 VRM 파일에 어떻게 애니메이션을 적용할 것인가에 대한 문제이다. 필자는 몇 가지 방법을 시도해 보았는데 그중 가장 퀄리티가 높은 방식은 “Blender”라는 프로그램을 이용하여 파일 포맷을 변경하고, “CharacterCreator4”을 이용하여 모델링의 리깅을 프로그램에 기록하고, “ICrone8”을 이용하여 애니메이션을 적용하는 방식이다. 하지만 이 방식은 프로그램의 가격이 상당히 비싸다는 단점과 하나의 캐릭터를 작업할 때마다 많은 시간이 소요된다는 단점이 있다. 그리고 UniVRM이 가지는 큰 장점인 헤어 시뮬레이션을 포기해야 한다.
//

가장 작업량이 적은 방식은 Mixamo 내부 기능으로 리깅을 시도해 Mixamo 애니메이션을 이용하는 건데, 이 리깅 방식으로는 손가락 부분과 머리카락까지 애니메이션을 구현하지 못한다. Mixamo 애니메이션 자체의 퀄리티는 높지만, 새롭게 리깅하는 과정에서 이런 문제가 발생하는 것이다. 게임 출시를 염두에 둔다면 매우 치명적이라고 판단했다.
//

이런 단점들을 극복하여 VRM 전용 애니메이션 툴 제작을 결심하였다. 일단 2022.3 버전의 Unity URP 환경에 UniVRM이라는 패키지를 설치하였다. 믹사모 리깅 정보를 휴머로이드 아바타에 적용 시 애니메이션 동작에 오차가 발생하는 버그가 발생하여 Unity에서 직접 코드로 애니메이션 동기화를 시도하였다.
////


제작 계획은 유니티 내부에서 헤어 시뮬레이션을 살린 UniVRM의 애니메이션과 “Mixamo” 애니메이션 정보를 합성하는 것이다. 그 과정에서 VRM 기존의 리깅 정보를 살리는 것이 중요하다. 즉 “Mixamo”처럼 저퀄의 리깅 정보를 사용하는 방식이 아니다. 이 방식으로 작업 시 UNIV RM의 헤어 시뮬레이션과 손가락 애니메이션, “Mixamo” 애니메이션 자체의 높은 퀄리티까지 모두 살릴 수 있다.
//

유니티 씬에 VRM 모델링과 믹사모 모델링을 임포트하고 믹사모 모델링의 애니메이션 정보를 VRM 모델링에도 대입해 보았다. 위 영상을 참고해 보았을 때 VRM 모델링의 리깅 정보와 믹사모 리깅 정보가 다르기 때문에 바로 대입하기 힘들다는 것을 알 수 있다.
//



- 믹사모 순수 애니메이션 = 믹사모 애니메이션 행렬 x 믹사모 TPose 역행렬
- 최종 VRM 애니메이션 = 믹사모 순수 애니메이션 x VRM TPose 행렬.
//
이를 해결하기 위해 “Mixamo”에서 애니메이션 정보를 추출하기 위해 모델링의 TPose의 정보를 유니티에 가져왔다. 그리고 TPose의 뼈대 정보 값과 애니메이션이 적용된 메쉬의 뼈대 정보 값의 격차만큼 VRM 모델링에 적용시켜 준다. 즉 VRM 모델링의 리깅 정보와 “Mixamo”모델링의 리깅 정보가 달라도 TPose 상태에서 적용할 수 있는 순수한 애니메이션 “Mixamo”로부터 추출하여, 애초부터 TPose인 VRM 모델링에 적용시키는 것이다.
//

이로써 제작 시 몇 가지 문제가 발생하는데 첫 번째가 모델링의 발 높이가 지면과 맞지 않는 현상이다. 이는 믹사모 리깅 정보와 VRM 리깅 정보가 다르기 때문에 일어나는 또 다른 현상이다.
//

위 영상과 같이 이를 가장 낮은 높이를 지닌 발등을 기준으로 높이를 직접 코드로 동기화해 준다.
//
두번째 문제는 위 영상과 같이 믹사모 모델링과 VRM 모델링이 서로 팔다리 비율이 달라 일어나는 모델링 뚫림 현상이다.
//



이를 해결하기 위한 방안으로 VRM 모델링의 팔다리 뼈대에 가해지는 애니메이션 비율을 조절한다.
//

드디어 VRM 모델링의 애니메이션들을 베이킹할 차례이다. 베이킹을 하기 위해 믹사모 모델링의 “Animator Component”에서 Frame 관련 정보들을 추출한다. 따로 유니티에서 제공해주지 않으니 직접 계산해야 한다.
////

해당 믹사모 모델링에서 추출한 최대 Frame은 17 Frame이다. 믹사모 모델링에서 도출한 최대 Frame, 현재 Frame을 체크해가면서 VRM 모델링의 정점들을 계산한다. 하지만 이때 녹화하는 애니메이션 정점들은 믹사모 모델링이 아닌 VRM 모델링의 정점들이다. 믹사모에서 Frame을 추출한 것은 단순히 Frame을 체크하기 위한 용도인 것이다. 왜냐하면 VRM 모델링은 직접 필자가 코드로 뼈대를 움직인 것일 뿐, 별도의 애니메이터클립을 가지고 있지 않기 때문이다.
//

또한 모델링 크기나 위치 등등 추가적으로 보정한 부분들이 있다면 VRM 모델링의 월드행렬을 반드시 적용하자.
//

그리고 Unity환경에서 코드로 모델링을 작성할 경우 크게 실수하는 부분이 있다. Mesh 내부 배열에 지속적으로 접근하면 안된다. 이렇게 코드를 짜면 Unity 실행 시 엄청 버벅이거나 최악의 경우 멈춰버린다.
//

직접 제대로 배열을 선언하여 접근해야 해야 버벅임 없이 잘 실행된다. 결과적으로 믹사모 애니메이션의 17 Frame 전부 안전하게 녹화되었다.
//


해당 VRM 모델링 메쉬는 2개로 나뉘어져 있기 때문에 하나의 메쉬로 합쳐서 작업할 것이다. 그러기 위해 해당 UV가 쉐이더 내부에서 A텍스처를 쓸지 B텍스처를 쓸지 햇깔리는 상황이 발생한다. 이를 정확히 분리하기 위해 UDIM을 구현해준다.
//

이렇게 녹화된 데이터들을 바탕으로 지오메트리 쉐이더에서 애니메이션을 구현할 것이다. 이러한 지오메트리 쉐이더 스키닝 방식을 설명하기에 앞서 일단 이 스키닝 방식을 고안한 배경부터 설득해보고자 한다.
//
현재 유니티, 언리얼에서 제공해주는 스키닝 방식은 정점들을 CPU에서 연산된다. 그렇게 함으로써 개발자들은 애니메이션 처리된 정점들의 정보들에 접근할 수 있게 된다. 만약 정점들이 쉐이더에서 연산되었다면 GPU에서 연산되기 때문에 개발자들이 애니메이션 처리된 정점들의 정보들에 접근할 수 없게 될 것이다. 반면 CPU에서 정점을 연산하는 방식은 매우 큰 성능 저하를 가져오게 된다.
//

그렇기 때문에 VFX 업계에서는 GPU에서 애니메이션 정보를 처리하는 버텍스 애니메이션 텍스처 기술(VAT)을 이용하게 된다. 하지만 버텍스 수를 표현하기에 VAT는 제약이 크기 때문에 보통 10000개 이상의 폴리곤을 지닌 캐릭터를 구현하지 못한다. 왜냐하면 게임 업계에서는 보통 2048 * 2048 이상의 텍스처를 사용하지 않기 때문이다. 만약 사용한다 하더라도 메모리 소모가 심해서 기존의 방식대로 CPU에서 연산하는 것이 나을 수도 있다.
//

또 다른 방식으로는 DirectX에서 구현하는 MatrixPalette 방식 차용해 GPU 스키닝을 시도하는 방법이 있다. 하지만 뼈대의 갯수 제한이 생기게 된다. 또한 매 프레임 뼈대의 애니메이션 값들을 넘겨주어야 하기 때문에 경우에 따라서는 좀 더 성능저하가 일어날 수 있다.
//

때문에 오랜기간 GPU스키닝에 대하여 연구해왔는데 최근 지오메트리 쉐이더 스키닝을 고안했다. 특히 Alembic과 Geometry Shader의 궁합이 좋기 때문에 서로의 단점을 보완해 구현할 것이다. 다만 지금부터 서술할 방식은 기존에 없는 방식이고 필자가 직접 고안한 것이니 생소할 수 있을 것이다.
//


아까 녹화한 데이터들을 점정들의 시멘틱에 저장해 애니메이션을 재생할 것이다. 1개의 정점에서 총 4개의 추가 애니메이션 정보를 저장하는 것이 가능하다. 또한 마지막 시멘틱에 프레임 재생에 관한 정보를 저장한다. 여기서 발생하는 단점은 1개의 정점이 4 Frame의 애니메이션 밖에 저장할 수 없다는 것이다.
//


이 단점을 해결하기 위한 방식으로 Alembic 방식을 차용한다. 메모리 용량이 더 커지더라도 대신 애니메이션을 위한 정점들을 추가한다. 아까 시멘틱에 애니메이션 정보들을 상당히 압축해 두었기 때문에 기존 Alembic 파일만큼 극단적으로 커지지는 않는다.
//

Unity 내부에서는 indexFormat이 uint16으로 세팅되어 있는데 이 세팅으로는 배열 인덱스를 일정 수준까지 밖에 구현할 수 없다. 인덱스 버퍼가 6만대까지만 작동하길래 자료형을 계속 의심해봤는데 구글링해도 안나와서 직접 하나하나 찾을 수 밖에 없었다.
//

현재 HLSL로 작성하고 있기 때문에 기본적인 툰 쉐이더를 간단히 HLSL코드로 작성해두었다.
//

귀찮을 수 있지만 어쩔 수 없이 랜더링 파이프라인 구현을 위한 행렬도 직접 적용해주어야 한다.
//

//
녹화된 17 Frame이 정상적으로 지오메트리에서 재생되었지만 1가지 큰 단점이 발생하게 된다. 버텍스 쉐이더에서 처리되는 애니메이션도 동일한 문제가 발생하게 되겠지만 노말에 대한 추가 애니메이션 처리이다. 위 영상의 오른쪽 팔을 살펴보면 쉐이더의 명암이 애니메이션이 재생됨에 따라 일정 간격으로 애니메이션 품질이 끊기는 것을 볼 수 있다.
//


- 잘못된 예시
- -> Normal = Cross(Normalize(PT0 – PT1), Normalize(PT0 – PT2));
또한 Normal Smoothing을 고려하지 않고 지오메트리 쉐이더에서 노말을 재생성하게 되면 왼쪽 그림과 같은 대참사가 일어나게 된다. 구글링을 통해 지오메트리 쉐이더의 노말 생성 문제를 해결하려 할 경우, 위와 같은 잘못된 해결책만 제시하니 주의하여야 한다.
//


행렬을 구현한 뒤 수식을 바탕으로 노말의 애니메이션을 처리해준다. 삼각형의 모든 정점 정보를 알 수 있는 것은 지오메트리 쉐이더만의 장점이기 때문에 VAT 방식으로는 Unity에서 구현하기 힘든 노말처리도 해결가능하다. 위 영상과 같이 애니메이션에 따라 노말도 자연스럽게 구현되었다.
//

결론적으로 믹사모의 손가락 애니메이션과 VRM의 헤어 시뮬레이션, 블랜드 쉐이프를 살리고자 직접 툴을 제작해 녹화하고 지오메트리 쉐이더로 애니메이션을 최적화 방식을 작성해 보았다. 위 영상과 같이 헤어 시뮬레이션과 손가락 애니메이션이 잘 구현됨을 볼 수 있다.
//

현재 필자는 모바일 환경에서 게임을 제작중인데 애니메이션 최적화가 매우 신경쓰고 있다. 이유는 제페토 월드를 예시로 들 수 있는데 플레이어가 일정 수가 넘어가버리면 상당히 렉이 걸리는 것을 경험할 수 있다. 이는 캐릭터 애니메이션의 연산을 CPU에서 담당하기 때문이기도 하다.
//

즉 이 최적화 문제를 해결하기 위해 리깅 정보가 있는 메쉬 (Skinned Mesh, Skeletal Mesh) 대신 리깅 정보가 없는 메쉬(Static Mesh)로 제작하였고 애니메이션 정보를 시멘틱에 저장해 지오메트리 쉐이더로 연산했다. 그렇게 되면 애니메이션이 없는 메쉬와 동일한 CPU 연산량을 가지게 된다. 모바일 포팅 시 가장 문제가 되는 최적화 문제 중 하나를 해결해 주는 셈이다. 또한 추가적으로 최적화가 어려워 일반적으로 모바일에서 구현하기 힘든 헤어 시뮬레이션과 블랜드 쉐이프도 구현이 가능하다.
//

이 과정에서 하필 왜 지오메트리 쉐이더로 최적화했냐는 질문을 할 수도 있다. 지오메트리 쉐이더가 매우 까다롭고 어려운 기술이라 제대로 쓸 수 있는 사람도 거의 없을 뿐이지 보통 쉐이더로도 해결하기 힘든 많은 것들이 해결가능하다.
//

이래나 저래나 과정이 좀 길었지만 이제 다 만든 모델링을 Unity GUI를 구현 후 Export해서 테스트해 보았다. 다행히 외부 뷰어에서도 잘 열린다. 일본 미소녀 풍의 게임을 제작하기 위한 캐릭터 애니메이션 툴을 만들어 보았다.
//