4.16

회전 보간과 리버스키의 알고리즘을 고쳤다. 리버스키는 자식본들의 키를 오프셋할 때, 좀 더 자연스럽게 움직일 수 있도록 0프레임 이전에 역방향 회전을 넣어주는 기능이다. 그런데 이것이 쿼터니온에 대한 이해가 부족할 때 제작한 거라 엉망으로 작동되고 있었다.
며칠 째 버그가 계속 나와서 작업 진행이 더디다. 실무에서는 이것들이 아티스트와 TA간의 연계작업이 되는데, 이 툴을 아티스트에게 던져줬다고 생각하면 끔찍하다.
더 이상의 버그가 나오질 않기를 바라는 마음에서 글을 새로 적었다. 기도합니다. 나무아미타불. 아멘
…
기도는 기도에서 그쳤다. 또 기능을 수정하고 추가하고…

애드온을 만들었는데도 작업이 꽤 어렵다. 특히 앉기처럼 몸과 다리가 밀착되면 특히 더 어려워진다. 사실 이건 답이 없다. 천하의 마블러스도 몸이 겹치면 구겨지고 난리인데, 본 베이스의 작업은 오죽할까.
4.17
현재는 본과 본사이의 선형 보간을 할 때 euler값을 더해 평균을 구하고 있었다. 그런데 이것이 서 있을 땐 문제가 없는데, 뒤집혀 있을 땐 문제가 됐다. 그래서 오늘도 챗GPT에게 물어봤는데…
이 코드의 문제는 회전값을 오일러 각도로 선형 보간하는 데 있을 것 같아요. 오일러 각도는 주로 한 축을 기준으로 회전하는 경우에만 유효하고, 다중 축 회전에서는 문제가 발생할 수 있어요. 특히 180도 회전과 같은 경우에는 오일러 각도가 다중 해석이 가능하기 때문에 문제가 될 수 있어요.
이 문제를 해결하기 위해선 여러 가지 방법이 있지만, 가장 간단한 방법은 쿼터니언(quaternion)을 사용하는 것입니다. 쿼터니언은 회전을 나타내는 더욱 강력한 방법으로, 다중 축 회전에서도 유효하고 180도 회전과 같은 경우에도 제대로 작동합니다.
다음은 쿼터니언을 사용하여 회전값을 보간하는 코드의 예시입니다:
pythonCopy codeimport bpy
from mathutils import Quaternion
def interpolate(bone):
location = [bone.location.x, bone.location.y, bone.location.z]
scale = [bone.scale.x, bone.scale.y, bone.scale.z]
rot1 = side_bones[0].matrix_channel.to_quaternion()
rot2 = side_bones[1].matrix_channel.to_quaternion()
# Interpolate quaternions
lerp_quat = rot1.slerp(rot2, 0.5)
# Convert quaternion to euler
lerp_euler = lerp_quat.to_euler()
bone.matrix = lerp_euler.to_matrix().to_4x4() @ bone.bone.matrix_local
bone.location.x = location[0]
bone.location.y = location[1]
bone.location.z = location[2]
bone.scale.x = scale[0]
bone.scale.y = scale[1]
bone.scale.z = scale[2]
bpy.context.view_layer.update()
위의 코드에서 rot1.slerp(rot2, 0.5) 부분은 쿼터니언의 선형 보간을 수행합니다. 이 방법을 사용하면 회전의 중간값을 구할 때 오일러 각도의 문제를 회피할 수 있습니다.
그리하여 오늘의 깨달음. 쿼터니온을 선형보간할 수 있구나! slerp를 사용하면 된다! 챗GPT는 대단해!그아아아(대흥분)
보간 코드는 여러번 말썽을 일으키는 코드였는데 쿼터니온 베이스라면 믿을 수 있을 것 같다. 앓던 이가 빠진 느낌이다.

1차 작업을 완료했다. 이제 엔진에 붙여보자.
엔진에 붙이기 위해 키를 정리해야 한다. 모든 모션은 프레임이 정해져 있기 때문에, 뒷프레임을 짤라낼 필요가 있다. 그렇지 않으면 유니티에서 일일이 숫자를 다시 기입해줘야 한다. 또한 프레임이 달라지면 유니티에서 설정해놓은 이벤트들이 어긋나기 때문에 기존의 프레임을 맞춰주는 편이 좋다. 그렇다면 작업공정이 안정된 지금은 세컨더리 애니메이션까지 한 번에 만들어주는 편이 좋을까…싶다가도 모션이 이상하면 어차피 수정해야 하기 때문에 지금 방식이 맞는 것 같다.
이 또한 기계적인 작업이기 때문에 스크립트에 기능을 추가했다.

짜르기는 현재 선택한 프레임을 기준으로 새 키를 찍고 이전 혹은 이후 프레임을 날려준다. 그런데 이 코드를 작성하며 깨달은 사실. 키를 순차적으로 지우면 리스트가 동적이라 결과가 변한다. 때문에 거꾸로 지워야 한다.
for fcurve in action.fcurves:
if group_name in fcurve.data_path:
for keyframe in reversed(fcurve.keyframe_points):
if ((side == 'left' and keyframe.co.x < frame_current) or
(side == 'right' and keyframe.co.x > frame_current)):
fcurve.keyframe_points.remove(keyframe)
for문을 돌 때 반드시 reversed()로 돌도록 하자.
하지만 이 방법도 문제가 있는게, 중간에 있는 키프레임을 지우면 같은 문제가 생긴다. 결국 키프레임을 별도의 리스트로 저장해서 해결해야 한다.

이제 엔진에 적용 중. 닐리의 발 잡기는 앨리스를 위해 만든 것이라고 해도 과언이 아닌 것!
고생한 것 치고는 퀄리티가 성에 차질 않는다. 모션사이가 생각보다 많이 끊겨 보이는데…이것만 보간할 수 있는 방법이 없을까?..
4.17
유니티의 애니메이터는 레이어라는 개념이 있다. 주로 FPS등에서 상하체를 분리해 애니메이션을 조합하는 용도로 사용된다. 이것을 이용하면 되지 않을까? 속성으로 레이어 마스크를 배우고, 블렌딩을 적용해본 결과… 블렌딩 자체가 스테이트가 투명하게 관리되지 않아 오래전에 발생한 문제가 발생했다. FSM을 직접 만들기로 결심했었던 바로 그 문제. 블렌딩이 2-3개 겹치면 모션이 엉망이 된다.
이를 해결하려면 스테이트가 겹치지 않게 하면 된다. 그런데 잠깐…애니메이션은 대체로 격렬한 움직임을 보인다. 애니메이션이 튀는 구간은 거의 모두 Idle로 돌아오는 구간이다. CrossFade()를 Idle에만 적용한다면 어떨까.

성공적! 거의 튀지 않는다.
….인줄 알았는데, 블렌딩이 빠르게 될 때의 시간이 보간될 때 여전히 문제를 일으킨다. 으.어…
…
결국 CrossFade()를 포기. 조사해 본 바로는 블렌딩을 바로 취소할 수 있는 방법은 없다. 결국 다시 Play()로 돌리고 끊김을 완화하기 위해선 다시 끝프레임을 Idle로 맞춰주는 방법을 택할 수 밖에 없다.
…
문제가 하나 더 있는데, 잡기에서 Idle로 돌아올 때 방향이 바뀌는 문제이다. 이건 어차피 예전에도 한번 바꾸고 싶긴 했다. 각오하고 해보자.

이 기능은 예전에 한 번 실패했던 오퍼레이터이다. 몸통은 어렵지 않은데 양쪽 팔이 굉장히 번잡시러워서 포기했던 기억이 난다. 구르고 굴러 돌아오니 그냥 데이터 패스를 맞교환해주면 되는 일이었다. 하지만 여전히 손가락이나 IK등은 번잡시럽기 때문에, 큰 줄기만 뒤집어놓고 세부수정을 하는 편이 좋겠다.
이런 저런 삽질 끝에 정한 방향성
- 애니메이션은 CrossFade()대신 Play()를 사용.
- 대기로 이어지는 애니메이션의 경우, 맨끝프레임을 Idle의 첫프레임으로 보간
- 방향이 달라지는 잡기의 반전
데이터가 커지면 작은 변화 하나도 부담되는 작업량이 된다. 차근차근 진행해보자.
블렌더 특성 상 ChildOf컴포넌트가 2개이상 붙은 본은 익스포트 시 프레임 지연이 일어난다. 게다가 IK가 정상작동하지 않는 문제도 있다. 이렇게 되면 결국 파일을 분리하는 것이 속편하다. 잡기용 파일은 별도로 분리해두자.
4.19
세컨더리 작업이 어려울 거라 생각했지만 예상보다 파장이 컸다. 이대로 작업을 진척시키기는 어렵다. 규칙을 확실하게 정립해 두어야 한다.
- 순차적 키밀기가 마냥 좋지만은 않다. 과유불급. 사용 시 주의하자.
- 첫 본이 가급적 90도를 넘지 않게 애니메이션 하자.
- 두번째 본이 충돌에 오작동을 일으키는 경우가 있다. 팔의 컬리전은 사용하지 말 것
- 짧은 길이는 단순 블렌딩한다. 그걸로 충분하다.
- 추가로 표정을 애니메이션 하자.