닐리 리깅 #3

11.29

드라이버를 이전하는 코드가 필요하다. 제약조건이야 블렌더의 기본기능인 symmetrize가 잘 복사해주는데 드라이버는 수동으로 해야 하기 때문이다. 지금까지는 수동으로 이동하고 있었으나 이것이 매우 번거롭다.

챗GPT에게 물어보니 엉뚱한 답만 내놓는다. 별 수 없이 인터넷 문서를 뒤졌으나, 드라이버를 새로 추가하는 법에 대한 설명뿐, 기존의 드라이버에 접근하는 방법을 쉽게 찾을 수가 없었다. 결국 뒤지고 뒤진 끝에 드라이버는 fcurve의 하위 클래스이긴 하지만, 액션의 fcurve와는 분리되어 있다는 사실을 깨달았다.

  • animation_data
    • action
      • fcurve
        • driver – 여길 뒤져봤자 아무 것도 안나온다.
  • animation_data
    • drivers
      • driver – 여길 뒤져야 한다.

이걸 알아내는데 1시간을 소비. 아오 빡쳐! 이걸 더 헛갈리게 하는 것이 drivers에 접근하면 리턴되는 값은fcurve클래스이다. 뭐 좋아. 발견했으니 이제 붙여야지. fcurve는 스크립트 짜면서 정말 피하고 싶은 요소중 하나이다. data_path와 array_index, group을 조합해서 본을 수동으로 찾아야 하기 때문이다. 방법이 없는 건 아닌데 신경쓸 것이 많다. 뭐 어떡해… 해야지..

드라이버를 수동으로 구성해주려면 각 항목에 대해 알아야 한다. 스택익스체인지에서 깔끔하게 정리해놓은 이미지를 발견할 수 있었다.

https://blender.stackexchange.com/questions/282140/how-can-i-add-and-configure-a-driver-through-a-script

실제로는 더 많은 정보가 필요하긴 했다. 우여곡절이 많았지만 어쨌거나 완성!

좌우의 본은 반전되어 적용되고 x,y,z등도 반전시킬 수 있도록 체크박스를 만들었다. 잘 작동되는 것 같다.버그가 없기를.

두께가 있는 모델링은 고민이 생긴다. 처음에는 가장 가까운 버텍스로 웨이트값을 이전하려고 했는데, 완벽하지가 않다. 노말 기준 계산이 아닌 구형 기준 계산이기 때문. 가장 좋은 것은 그냥 솔리디파이를 다시 주는 것인데, 이처럼 할 경우 반대쪽면에 손을 댔을 때(UV나 폴리곤최적화)가 문제가 된다.

하지만 얻는 것이 더 많으므로 이 쪽이 좋아보인다. 그렇다면 솔리디파이를 합치는 것은 리깅 이후로 미루어야 한다는 결론. 재작업은 불가피해 보인다. 머리가 나쁘면 몸이 고생이라더니 딱 그 꼴이다.

목요일부터는 가족여행으로 인해 한국에 없다. 여행다녀오면 기억이 모두 초기화되지 않을까..!

12.4

이렇게 자세한 리깅은 처음인지라 뭐하나 쉬운 게 없다. 리깅은 본설계와 웨이팅을 합친 과정인데, 설계가 끝난 후에도 웨이팅 과정에서 본을 수정할 일이 생긴다. 맥스는 이것이 꽤 번잡시러운데 블렌더는 이게 쉽다. 그나마 다행스러운 일이다.

드디어 팔과 코트를 합쳤다. 문제는 더 커졌다….

아우..맘에 안들어…

12.5

소매야 그럭저럭 됐는데 코트자락에 대한 좀 더 정확한 보간이 필요하다. 예상했던 가설들은 전부 빗나갔다. 실험을 해보자.

이것은 웨이트 이전용 프록시 메시이다. 이제 이걸

여기에 페이스 보간으로 웨이팅 이전을 한다.

바로 이 옵션이다. 그리고 아마추어를 씌우면 웨이팅값이 이전된다.

유니티 엔진은 영향받는 범위를 4버텍스 이하로 줄여야 한다. Limit Total을 걸어보자.

결과! 벌써부터 찌그러진다. 생각보다 더 민감하게 반응한다.

그렇다면 처음 세웠던 웨이팅방식으로 돌아가보자. 바로 그라데이션 방식이다.

이론적으로 영향버텍스가 4개를 넘지 않는다. 하지만 이걸 노말라이즈하면

엉뚱한 계산이 나온다.

여기까지 놓고 보면 결과는 명확하다. 마디마다 본을 늘리는 방법뿐이다.

기어이 이렇게 되었다.

팔을 들어올렸을 때의 처리는 자동화할 수 없다는 결론을 내렸다.

수동으로 보간하도록 하자.

12.6

지긋지긋한 코트 리깅을 끝내고 이제 나머지를 진행할 수 있다. 마음이 편하다.

12.7

머리카락은 정해진 공식이 있기에 어렵지 않다. 갈래가 좀 더 많으면 좋겠지만… 이미 코트에 너무 많은 본을 할애했기 때문에 더 이상 늘리는 것은 부담이 된다. (적다고는 하나 그래도 100개쯤 된다.)

머리카락이 모자를 뚫고 나오므로 머리카락 윗부분의 제어본을 하나 둔다.

이제 괜찮다.

..는 모자를 벗기면 이렇다.

이런 건 드라이브의 Distance를 사용해 자동화해주면 편하다.

이제 거의 끝나간다. 아직 속머리를 마무리해야 한다. 오늘은 타임오버

닐리 리깅 #2

11.25

더 예쁘게 표현될 수 없을까?를 고민하던 끝에 3축을 모두 계산에 넣는 방법이 떠올라 적용했다. 옆으로 다리를 벌렸을 때 옷이 공중에 떠 있는 문제가 있고 계산이 논리적이지도 않지만, 적어도 파묻히는 것보단 낫다.

덕분에 드라이버 코드는 좀 더 복잡해졌다. 이젠 진짜진짜 최종

드디어 다음과정으로 갈 수 있다.

11.26

블렌더의 스트레칭본은 뭔가 이상하다. 부모본의 스케일을 건드리는데 자식본이 별 반응이 없다.

내가 아는 지식선에서의 자식본이라면 이렇게 왜곡이 일어나야 한다. 그런데 이 왜곡이 일어나지 않는다. 그건 물론 좋은 일이지만, 대체 어찌 된 것일까.

블렌더의 본 관계도를 보면 스케일 상속유형을 정할 수가 있다. 그런데 여기에… None라는 항목을 선택하면

왜곡이 일어나지 않는다!! 개쩌는데?! 이거 엔진에 넘어갈까?

에이……..유니티는 개쩔지 않았다.

엔진 켠김에 닐리 치마도 잘 넘어가나 확인해두자.

다행이다. 이건 문제가 없다.애초에 본설계할 때 주변 본과 관련이 없도록 설계한 보람이 있다.

하지만 의문이 하나 더 남는다… 스트레칭 본은?!

에이…. 그럴 줄 알았어…스트레칭 본은 반드시 부모의 스케일이 1로 고정될 때만 사용하여야 한다.

치마에서 한바탕 고생을 한 덕분인지, 코트는 비교적 수월하게 느껴진다. 헬퍼본을 안쓰고 어떻게든 해보자싶었는데 헬퍼본 하나 심으니 모든 게 해결된다… 진작에 쓸 걸 그랬다.

11.27

..수월하긴 한데, 그게 쉽다는 뜻은 아니다.어질어질하다..

아이고야…

11.28

기본은 됐는데 몇가지 문제들이 눈에 띈다. 바로 저런 문제들.

팔을 위로 들었을 때 트위스트본이 꺾이는 문제가 있다. 내일 해결해보자.

트위스트본이 꺾이는 문제는 공간 행렬과 관련이 있다. 자세히는 모르지만 컴퓨터 그래픽스에서의 회전은 본의 공간이 회전하는 것이고 공간계산은 사인과 코사인 연산을 통해 이루어진다. 연산을 줄이기 위해서인지는 몰라도 180도를 넘어가면 이 값들을 마이너스로 처리를 하는데 이 과정에서 이런 뒤틀림이 일어난다.

부모본이야 상관이 없지만 자식본은 연결된 채 갑자기 같이 돌기 때문에 몹시 안좋은 결과를 가져온다. 그런데 이걸 어찌 풀어야 할까 고민하던 차에 회전값상속을 끌수가 있다는 사실을 알았다.

그럼 요래 움직이는데… 스케일은 안넘어갔지만 이건 넘어갈까?

오읭ㅇ?!! 이건 넘어간다?!

애니메이션 정보를 보니 키를 만들어서 보간하고 있다. 긍정적인 결과다. ARP에서도 잘 넘어갈까?

이럴수가! 아름답게 넘어간다!

그른데 아직 쓸 데가 없네… 음 일단 되는 걸 알았다는 사실로 만족하자.

팔의 메쉬가 튀는 원인은 알아냈다. 문제가 되는 버텍스들이 서로 다른 본에 반씩 걸쳐있는 것이 문제였다. 보통이라면 이것들이 크게 문제가 되진 않지만 회전제약을 걸어놓은 상태라 문제가 된다….라지만 그래도 문제가 되면 안될텐데?

뭐, 여튼 가설이 맞다면 웨이트를 제대로 잡으면 모두 수정되는 문제이긴 하다.

닐리 리깅

11.21

닐리의 리깅포인트는 미니스커트다. 로폴이었다면 대충 다리에 붙였을 것 같은데, 표현이 정밀해질수록 할 일이 많아진다.

첫시도는 실패. 구상은 좋았던 것 같은데, 생각보다 예쁜 모양이 나오지는 않았다.

이것도 실패

이것도 실패

이건 쓸만하다. 하지만 디테일잡기가 조금 어렵다.

스케일 + 드라이빙. 이게 가장 좋은 것 같다! 좀 더 자세히 파 보자.

러프한 구현.

본은 예상했던 대로 돌아가는데, 생각보다 완전하지 않다. 일단, 회전값이 예상대로 안들어온다. 다리의 첫번째 트위스트본은 다리를 바깥쪽으로 돌리면 완전히 90도 비틀리는데, x+z가 항상 90도라고 생각했던 것이 오판이었다.

그래서 이렇게 비스듬한 각도에서는 계산이 제대로 되지 않는다.

드라이버의 코드는 이렇다.

clamp(1 + 
(pi/2/sperate_count * offset_level + z) * (0.8/(pi/2/sperate_count)) + 
(pi/2/sperate_count * offset_level - x) * (0.8/(pi/2/sperate_count))
,0.2,1)

11.23

오늘의 깨달음. 오일러 회전방식이 애니메이션 뿐만 아니라 현재 상태에도 영향을 끼친다. 수잔의 XYZ축을 모두 45도로 설정한 후 각기 오일러 회전방식을 다르게 해주었더니 결과가 다르게 나왔다.

각방으로 연구를 해보았으나, 결국 완전한 방법은 깨닫지 못했다. 쿼터니온은 사람이 못알아듣는 값이고 결국 오일러값을 조절해야 하는데 이 값이 왜 이렇게 들어가는지 이해하지 못했다. 아득한 수준너머의 일이라고 생각하고 그냥 괜찮은 오일러값을 고르는데 집중해야 할 것 같다.

11.24

트위스트 본의 첫번째 본은 다리를 옆으로 뻗었을 때 분명 x축으로 90도가 돌아간다. 이는 앞으로 뻗었을 때와 옆으로 뻗었을 때의, 그리고 그 중간 보간과정에서 어느 축으로든 회전각의 합이 90도가 된다.라는 계산에서 시작한 작업이었지만, 실제로는 오일러 계산에 의해 선형적으로 보간되지 않는다.

하지만 그렇다 해도… 강제로 90도로 맞춰주면 되는 것 아닐까? 비율적으로는 z에서 x로 각의 비중을 넘겨줄 때 그 값이 비선형적이라 해도 어쨌거나 90도에 맞추어 재계산해주면 되는 것 아닐까? 싶어서 다시 스크립트를 짜려고 보니… 이제는 이 많은 내용을 한 줄에 표현하기가 불가능하다. 함수가 필요하다.

https://docs.blender.org/manual/en/dev/animation/drivers/workflow_examples.html#driver-namespace

뜻이 있는 곳에 길이 있다고 했나! 드라이버엔 커스텀 함수를 포함할 수 있다. 네임스페이스 안에 함수를 포함시켜 사용하면 된다고 써 있다. 이제 지금까지 한 줄로 썼던 표현식을 함수화 시킬 수 있다.

def skirt_scaler(z, x, front, side, back, level):
    hpi = pi/2
    scale_front = (hpi/front*level+z) * (0.8/(hpi/front))
    scale_back = (hpi/back*level-z) * (0.8/(hpi/back))
    scale_side = (hpi/side*level-x) * (0.8/(hpi/side))
    
    z_scale = min(0, min(scale_front, scale_back))    
    x_scale = min(0, scale_side)
    result = 1 + z_scale + x_scale
    
    result = max(0.2, result)
    result = min(1, result)
    
    return result

그러니까… 이걸 한 줄에 쓰고 있었다. 어휴. 스크립트 표현식에서 편의상 제공하는 clamp는 정규블렌더 스크립트에선 사용할 수 없다. 어쩐지 math네임스페이스에도 없다. min/max로 짤라야 한다.

이제 여기서 z,x의 값을 비율 보간해 보자.

def skirt_scaler(z, x, front, side, back, level):
    hpi = pi/2    
    zx = abs(z)+abs(x)    
    extra = max(0, zx-hpi)
    z_sub = extra*(z/zx)
    x_sub = extra*(x/zx)
    
    scale_front = (hpi/front*level+z-z_sub) * (0.8/(hpi/front))
    scale_back = (hpi/back*level-z+z_sub) * (0.8/(hpi/back))
    scale_side = (hpi/side*level-x-x_sub) * (0.8/(hpi/side))
    
    z_scale = min(0, min(scale_front, scale_back))        
    x_scale = min(0, scale_side)
    
    result = 1 + z_scale + x_scale
    
    result = max(0.2, result)
    result = min(1, result)
    
    return result

망… 더 많이 파묻힌다…그렇게 쉬운 일이 아닌가보다.

하지만 어쨌거나 스크립트 표현식을 함수화할 수 있는 방법은 알아냈다.

이렇게 쓰던 걸

이렇게 깔끔하게 쓸 수 있다!

단점은 파일을 열 때마다 스크립트를 한 번 실행해주어야 한다는 것이다. 이것마저 귀찮다면 제작이 끝나고 애드온에 함수를 내장시켜놓으면 될 것이다.

하지만….

문제는 여기서 끝나지 않았다. 이번엔 본이 커튼처럼 뼈가 있는 부분외의 부분들에 볼륨이 나타나는 문제가 나타났다.

분명 이것은 본의 가닥이 많아지면 해결이 되는 문제이긴 하다. 하지만 그럼에도 완전히 해결되지는 않는다.

그렇다면 윤곽을 유지하기 위해 보더모양의 본을 설치하고 지금까지 만들었던 줄기본을 헬퍼로 쓰는것은 어떨까

러프한 구현결과…어? 괜찮을 듯? 보더에 단계를 둔다면?

훨씬 나은 결과!

게다가 가닥본들은 이제 헬퍼본들이기 때문에 엔진으로 넘기지 않아도 된다는 장점도 있다.

오늘의 깨달음.

드라이버는 본이 지워져도 캐시에 남아있다. 때문에 같은 이름의 본을 추가하면 드라이버가 그대로 전승된다. 이는 본의 제약조건을 복사할 때 매우 효과적인데, 드라이버가 지워질 것이 무서워서 symmetry를 사용하지 않고 일일이 수기로 복사해주는 번거로움을 줄여준다. 물론 캐시가 날아가면 못쓰므로 파일을 다시 열기 전에 작업을 끝내도록 하자.

버텍스 웨이트 툴에서 copy는 가장 나중에 선택한 점의 값을 기선택한 점에게 복사해주는 것이다. 스무스를 주지 못하는 웨이트를 잡아야 하는 특성상 옆의 값을 따와야 하는데, 이럴 때 좋다.

마침내, 기본은 거의 된 것처럼 보인다.

앨리스 리깅

11.10

본을 어디까지 얼마나 쓸 것인가? 는 늘 고민거리다. 어느 정도 틀이 잡힌 프로젝트라면 모를까, 처음 시작할 땐 적정수준이란 걸 가늠하기가 상당히 어렵다.

줄기를 늘릴까, 마디를 늘릴까

앞머리는 고정하는 편이 예쁘므로 마디가 없어도 될 줄 알았는데, 정작 해보니 생각보다 더 딱딱해 보인다.

대충 건드려본 결과물로도 분명한 차이가 있다.

그런데 앞머리의 움직임 패턴을 생각하면 또 마디가 없어도 될 것 같다.

11.17

기나긴 애드온 제작을 끝내고 다시 리깅작업으로. 그런데 이거 맞나? 싶다… 추가본만 100개가 넘는데…아무리 PC게임이라지만 게임 데이터가 이래도 되나…

어.. 진짜 이거 맞나? 벌써 어지러운데..

11.18

로폴 작업할 때까진 브러시 기능따위 왜 있는거야 생각했는데, 폴리곤이 많아지니 웨이팅 작업은 오히려 편하다. 인체공부를 안해뒀다면 꽤 괴로울 뻔 했다.

하지만… 그래도 쉽지 않다..

11.18

인내는 썼지만, 그 열매는 달았다. 잘 움직이는구나!

실전으로 들어가면 좀 더 사소한 것들이 발목을 잡는다. 이름지을 때 좌우첨자 고려를 안했더니 좌우반전이 안된다거나 하는 것들. 이게 왜 필요하냐면 블렌더는 좌우반전 본을 이름으로 구분한다. 이름에 .l이나 _l이 들어가 있다면 된다. 그리고 이게 없으면 본에 제약조건을 걸었을 때 본 심메트리가 안된다. 블렌더의 제약조건은 복붙이 안되는데, 심메트리를 이용해서 이를 해결할 수 있기 때문에 이 혜택(?)을 누리려면 본 이름 맨끝에 좌우첨자를 붙여주어야 한다.

추가로 실제로 이름을 지어보니 언더바를 많이 써서 넘버링 사이의 첨자를 쩜으로 고쳤다. 이는 옛날 개발자의 습성이기도 한데, 고대의 개발자들은 파일명에 공백이 없어야 한다는 오래된 규칙 때문에 빈칸을 기피하는 경향이 있다. (…) 비슷한 이유로 파일명을 숫자로 시작하지도 않는다. 뭐 지금은 둘 다 된다. 게다가 윈도우 파일명과 블렌더 내의 본 이름규칙과는 다르므로 사실 2개는 아무런 관계가 없다.

참고로 본의 이름에 쩜을 추가하면 언리얼에선 이를 언더바로 치환한다. 공백은 확인해보진 않았지만, 아마 치환당하지 않을까 싶다.

leg_helper를 왜 넣었는지 기억이 났다! Autorig Pro의 본 구조상 종아리의 본은 허벅지의 자식본이 아니다. 아마도 IK때문에 root에 연결되어 있는 것 같다. 때문에 종아리가 접힐 때 살이 눌려 양쪽으로 살집이 튀어나오는 처리를 하기 위해 넣은 bulge본의 드라이버를 위해 다리의 굽힘 정도를 알 수 있는 헬퍼본이 필요했었다.

리깅이 끝났어!이제 시작이다!

하지만 삽질이 또 한가득었기 때문에 다른 캐릭터의 리깅부터 진행해보는 게 좋겠다

11.20

리깅이 끝난 줄 알았는데, 포즈를 잡았을 때 다리의 본이 예쁘게 정렬되지 않는 문제가 있었다. 기존엔 트위스트본이 2관절이었는데, 본쓰는 김에 좀 더 쓰면 어떠리..싶어서 관절을 4개로 늘렸다. 결과는 대만족!이렇게 쉽게 해결될 녀석이었나! 다른 캐릭터의 리깅은 아직 진행전이니, 팔에도 적용해 놓는 편이 좋겠다.

레퍼런스 모델에서 얼굴을 수정했을 경우, 꽤나 많은 걸 수정해야 한다. 이걸 손으로 하면 시간이 너무 오래걸리기 때문에 스크립트가 필요하다. 각 드라이버를 돌면서 깨진 타겟을 다시 연결해주고, 머티리얼에 걸린 드라이버도 재정의된 셰이프키를 연결해준다. 드라이버에 접근하는 건 매우 까다롭다. 꼭꼭 숨어있기 때문이다. 짧은 코드지만, 이걸 쓰는데는 2시간이 걸렸다.

obj = bpy.context.object

for fcurve in obj.data.shape_keys.key_blocks.data.animation_data.drivers:
    for variables in fcurve.driver.variables:
        variables.targets[0].id = bpy.data.objects['Girl_rig']
        
obj.data.shape_keys.name = 'FaceKey'        
target_material_names = ['Face', 'Mouth', 'Eye']

for i, material in enumerate(obj.data.materials):
    for name in target_material_names:
        if name in material.name:
            mat = bpy.data.materials.get(name)
            obj.data.materials[i] = mat

            for fcurve in mat.node_tree.nodes.data.animation_data.drivers:
                for variables in fcurve.driver.variables:
                    if variables.targets[0].id_type == 'KEY':
                        variables.targets[0].id = obj.data.shape_keys  
                        
face_obj = bpy.data.objects.get('Face')

if face_obj:
    obj.location = face_obj.location    
    obj.name = 'Face'
    delete(face_obj)    

  

이제 얼굴의 모양을 고치면 그냥 들고 오면 된다.

11.21

카메라를 멀리서 볼 때 캐릭터의 인상이 약해서 아이셰도우를 추가했는데 성형키로 인해 아이셰도우의 모양이 눈매와 맞지 않는 문제가 있었다. 이를 위해선 얼굴의 셰이프키를 고쳐야 하고, 그걸 가져오면 부모관계 및 드라이버가 모두 깨진다. 위의 코드는 이를 쉽게 처리할 수 있는 스크립트가 필요했기 때문에 작성한 코드다. 이걸 손으로 하면 지금까지 했던 캐릭터들에게 모두 같은 일을 해주어야 하므로 족히 1시간은 걸릴 일이다. 실수도 잦다!

눈가장자리로 갈수록 눈썹이 붉어지는 건 잘못딸려오긴 했는데.. 그냥 예쁘므로 두기로 하자. 이전 프로젝트에서도 얼굴을 새로 업데이트하는 처리를 했었지만, 이 방법은 그 전보다 세련됐다. 왜 전엔 이렇게 하지 못했을까…

앨리스를 작업하며 했던 삽질을 또 다른 캐릭터에게도 적용했다. 데이터가 복잡하니 할 일도 많다.

오늘의 깨달음

ctrl+f2를 누르면 일괄 이름변경을 할 수 있다. 전에도 알고는 있었으나 사용법이 알기 어려워 안썼는데 특정 키워드를 지우거나 바꾸기엔 매우 좋다.

내일은 정말로 닐리 리깅에 들어가보도록 하자.

애니메이션 애드온의 제작 #2

11.15

경직구간 설정. 충돌은 아니지만 충돌을 처리하기 위해 만들었다. 다른 기능과는 다르게 본 단위로 움직이므로 가장 마지막 단계에서 처리하는 편이 좋겠다. 원리는 단순한데, 시작과 끝의 보간을 없애 그래프를 평평하게 만든다. 완전히 자연스러운 애니메이션이라고 보기는 힘들지만, 설정된 값으로 최대한 쉽게 애니메이션을 만들 수 있다. 어려울 줄 알았는데, 의외로 쉽게 풀렸다. 오굳

첫프레임 보간비율을 추가. 어쩐지 여기 항목 점점 늘어난다(…) 이 값은 키를 물려줬을 때, 키를 미는 과정에서 0프레임에 키가 비는 현상을 보간해준다.

더불어 오일러와 쿼터니온 중 안쓰는 커브를 삭제하는 기능도 추가. 데이터가 좀 더 깔끔해졌다.

이제 루핑키를 처리할 수 있다.

그것까진 좋은데, 키가 너무 많다. 사용하는 키는 20프레임인데, 이를 만들기 위해 양쪽으로 40프레임씩이 더 붙어야 한다. 유니티가 이걸 어떻게 받아들이는지 확인해보자.

맙소사. 마이너스 프레임을 0으로 친다. 키 최적화 기능이 필요하다.

키 최적화는 단순히 앞뒤로 짜르면 되겠지 싶었는데, 키를 지우며 리스트가 동적으로 변해 오류를 냈다. 이를 해결하려면 키를 뒤에서부터 지워야 한다. for~in reverse()를 사용하면 된다. 이걸 알아내느라 한참 걸렸다.

캐릭터가 다리를 움직여 치마가 들리면 불규칙한 움직임이 일어난다. 회전 보간은 이럴 때 사용하기 위한 기능이다. 원래 계획에 있었던 것이지만, 구현은 예상보다 더 괜찮은 결과를 보여줬다. 흐뭇.

지금까지 한 것과, 앞으로 할 것들. 거..거의 끝나간다!

11.16

월드축 기준으로 붙이기는 어느 정도 원형을 보존해주기는 하지만, 약간 아쉬울 때가 많았다. 섞기를 만들자.

0.5비율로 섞었을 때

월드 0.7 : 로컬 0.3일 때

코드 리팩토링을 몇 번이나 한 보람이 있다. 흑흑. 뒤로 갈수록 구현이 쉬워진다.

이제 남은 건 이렇게 2개이다.

초기값으로 보간은 alt+r(초기값으로 되돌리기)이 너무 극단적이라 중간값을 쓰고 싶을 때 사용한다. 대략 1/3정도씩 줄어든다.

회전키는 사람손으로 부드럽게 잡기는 어렵다. 이걸 잘 처리하기 위해선 원형커브를 만들고 룩앳을 달아서 그걸 쫓도록 하는 아주 번거로운 과정을 거쳐야 하는데, 그냥 회전값에 sin, cos을 더하면 될 일 아닌가? 싶어서 러프하게 구현해봤더니 잘 된다. 해당 키는 8각을 그리는 키인데, 좀 더 부드러운 곡선을 원한다면 각을 늘려주면 된다. 좋아. 회전 프로퍼티를 넣어보자…싶었는데 타임오버. 오늘은 여기까지

11.17

생각보다 설정할 것들이 많아 카테고리를 따로 뺐다. 대부분 예상했던대로 잘 동작했으나, 계산을 오일러 기반으로 하고 있기 때문에 짐벌락 현상이 발생한다! 때때로 아주 과한 애니메이션에 부적합할 수 있다. 이걸 해결한 게 쿼터니온인데, 그건 컴터만 알아듣는 숫자고. 내가 할 수 있는 건 여기까지인 것 같다.

다음으로 할 일은 그룹본이 아닌 본에게 키를 복사하는 기능이다.

…는 enum만 추가하니 쉽게 됐다.

야호.

회전키 제작과 루프키 버그 수정. 또한 그룹본이 아닌 본에도 애니메이션을 줄 수 있도록 한다. 예상했던 기능들은 이제 모두 구현했다. 이제 미뤄왔던 애드온 기능을 추가하고 마무리 하면 되겠다.

오늘의 깨달음:

  • matrix_basis는 부모를 기준으로 한 상대적인 회전행렬을 리턴하지만, 이걸 오일러로 바꾸어 숫자를 조작한다고 해서 반드시 축대로 정확히 움직이는 것은 아니다. 아마도 오일러 공식 때문인 것으로 보인다.
  • 이를 해결하려면 bpy.ops.transform.rotate()를 사용해야 한다.

애니메이션 애드온의 제작 #1

11.8

날짜를 강조해서 쓰기 위해 마크업 문법 “###”(제목표시줄3)를 첫머리에 쓰면 저런 도움말이 뜬다. 그 다음 날짜를 쓰면 저 문구가 사라지며 내용이 써지는 식이다. 내가 늘상 헤딩하고 있는 건 맞는데, 블로그가 그렇게 말하니 기분나쁘네!? 야이..

리깅은 제약조건과의 싸움이다. 리깅의 기본이 되는 IK/FK도 제약조건(Constraint)이 만들어낸 마술이다. 하지만 이러한 제약조건들은 단일 모션에는 적합할지 몰라도 범용적으로 사용하긴 어렵다. 제약조건이 복잡해 질수록 의도치 않은 문제가 생긴다. 컨트롤 본이 늘어나는 것은 덤이다. 가뜩이나 복잡한 릭이 더 복잡해진다. 때문에 게임 실무에서는 이걸 잘 안쓴다. 본이 몸을 뚫든 말든 스프링본을 돌리고 베이킹해서 끝낸다. 하지만 타임라인에 지저분한 키가 남는다. 이게 마음에 안드는 이들은 그냥 손으로 잡는다. 하지만 품이 너무 많이 든다.

세컨더리 애니메이션을 아름답게 처리할 수 있는 방법이 없을까? 리깅 전에 이걸 먼저 해결해야 할 것 같다.

먼저 테스트 모델을 제작한다.

애니메이션 스크립트는 가장 건들기 싫은 부류의 스크립트다. 블렌더의 애니메이션 시스템도 복잡하거니와 챗GPT도 엉뚱한 답만 내놓기 때문에 스스로 생각해야 하기 때문이다.

블렌더의 애니메이션 데이터는 Action이 들고 있다. 본이 아니다. 때문에 액션에서 커브를 찾아 매칭시켜야 한다.

  • armature
    • animation_data
      • action
        • fcurve
          • keyframe_point

우리가 애니메이션을 만들면 action에 fcurve를 추가하게 된다. 블렌더는 이 때 해당 본의 이름으로 data_path를 생성하고 인덱스를 부여해서 커브를 구성하게 된다.

그런데 문제가 있다. fcurve가 복사가 안된다!

키프레임도 안된다! 되는 게 뭐야!

그렇다며언…으음 모두 수동으로 구성해주는 방법뿐이다. 어쩔 수 없지

러프한 구현. 이렇게 움직인 다음, 스크립트를 돌리면

오프셋된 키가 만들어진다.

오일러 테스트. 생각보다 잘 작동한다. 오오

아직은 비루한 메뉴ㅋㅋ

스프링 베이킹과는 달리, 키를 망치지 않는다. 그냥 전이할 뿐이다.

가능성이 보이니 계속해 보자. 현재 코드는 차일드가 1개뿐일 때만을 계산한다. 더 아래에 있는 차일드에게도 전이하도록 해야 한다.

…는 그냥 되고 있었는데 코드를 잘못 봐서 조기리턴되고 있었다.

바람이 치마를 훑고 가기 위해선 키를 밀어야 한다. 하는 김에 트랜스폼 3형제를 따로 밀 수 있도록 구현했다. 그렇다면 자식본에 회전값이 아니라, 선택한 값을 모두 물려주는 메뉴로 바꿀 수도 있겠다. 이것도 구현했다. 본격적인 사용은 좀 더 나중이 될 것이다.

다음은 순환 본의 처리. 치마 중 하나를 움직인 후 나머지 본에 모두 같은 값을 붙이는 기능이 필요하다. 월드와 로컬로 나뉘어야 하는데 이게 매우 어려워 보인다. 일단 자리부터 깔자

옆본 보간은 본을 하나만 제어했을 경우 나머지 본을 알아서 제어해 줄 수 있게 만들 예정이다. 뜻대로 잘 되면 좋겠다.먼저 키 따라하기부터 만들어보자.

11.10

오늘의 깨달음 :

  • 본 정보의 matrix는 월드 기준 행렬, matrix_basis는 부모 기준 행렬이다.
  • .to_quaternion()메서드는 해당 본의 회전정보를 쿼터니온으로 뽑아준다.
  • 여기에 .to_matrix()를 붙이면 행렬화 해주고
  • 여기에 .to_4x4()를 붙이면 4×4행렬로 만들어준다.
  • 이제 이걸 붙이고 싶은 본의 역행렬과 곱해주면 본의 회전정보를 같게 만든다.
  • 이걸 다시 .to_quaternion()으로 뽑아서 회전값에 넣어주면 된다.
a = bpy.context.selected_pose_bones[0]
b = bpy.context.selected_pose_bones[1]
        
m = a.matrix.to_quaternion().to_matrix().to_4x4()        
b.rotation_quaternion = (b.matrix.inverted() @ m).to_quaternion()

이렇게 되면 2개의 본은 완전히 같은 회전값을 가진다. 하지만 내가 하고 싶은 건 상대적 월드 회전값이다. 예를 들어 A본을 45도 돌리면 B본도 45도 돌아야 한다. 로컬기준 회전은 그냥 값을 복사하면 되지만, 월드기준으로 돌아야 하는 것은 꽤 까다롭다. 이걸 알아내는데 꼬박 하루를 보냈는데, 풀지 못했다. 그런데 저녁먹고 밤되서 다시 해보니 10분만에 됐다.(!?)

저녁의 깨달음:

  • 본 정보의 matrix_chennel은 원본 대비 회전값을 월드좌표 기준으로 저장한 행렬이다. 이를 이용해서 다른 본도 같은 회전값을 갖게 할 수 있다.
  • 포즈본이 아닌 데이터본 정보의 matrix_local은 월드좌표 기준 행렬이다. 즉, 초기값이다. alt+rgs누르면 돌아가는 그 값
  • 회전값만 뽑은 행렬을 원본 행렬과 곱해주면 위치가 어긋난다. 하지만 여기서 다시 회전값만 뽑으면 잘 된다. 내가 헛갈린 부분이 이 부분인데, 행렬곱은 만능으로 쓸 수 있는 공간 전환 마법이 아니었다.

하지만 그럼에도 불구하고 문제를 완전히 해결하진 못했다. 결국 이것 또한 삽질이었다.

11.12

컴퓨터 그래픽스에서 오브젝트의 회전은 공간이 회전하는 것이다. 공간은 행렬로 이루어져 있는데, 그동안 공부를 기피해왔었다. 어려웠기 때문이다.

하지만 원하는 걸 하기 위해선 행렬을 알아야 했고, 3일동안 공부를 해서 쓸만큼은 이해했는데, 뭔가 여전히 잘 작동하지 않는다. 그래서 챗GPT에게 물어봤더니… 와. 금방 알려줬다(…) 처음엔 왜 안알려줬어??!! 어쩌면 난 질문을 하기 위해 공부한 것일 지도 모르겠다.

import bpy
from math import radians
from mathutils import Matrix

def rotate_bone(pose_bone, angles_deg):
    # angles_deg: [x_angle, y_angle, z_angle]

    # 각도를 라디안으로 변환
    angles_rad = [radians(angle) for angle in angles_deg]

    # X, Y, Z 축을 기준으로 회전하는 행렬 생성
    rotation_matrices = [
        Matrix.Rotation(angles_rad[0], 4, 'X'),
        Matrix.Rotation(angles_rad[1], 4, 'Y'),
        Matrix.Rotation(angles_rad[2], 4, 'Z')
    ]

    # 회전 행렬들을 결합
    combined_matrix = rotation_matrices[2] @ rotation_matrices[1] @ rotation_matrices[0]

    # 현재 본의 전역 행렬을 가져옴
    world_matrix = pose_bone.bone.matrix

    # 결합된 회전 행렬을 전역 행렬에 적용
    pose_bone.bone.matrix = combined_matrix @ world_matrix

    # 화면 업데이트
    bpy.context.view_layer.update()

# 테스트를 위해 활성 포즈 객체와 본을 얻음
pose = bpy.context.active_pose_bone

# X, Y, Z 축을 기준으로 각각 45도씩 회전
rotate_bone(pose, [45.0, 45.0, 45.0])

코드에 문제가 아주 없는 건 아닌데, 그건 사소한 문제다. 핵심은 다 들어있다. 3일 밤낮으로 고민하던 게 이렇게 쉽고 허무하게…흑흑

하지만 적어도 이번 삽질 덕분에 행렬이 어떻게 생겼는지 조금은 이해하게 됐다. 이동값은 오른쪽끝에, 회전값은 3×3공간에 사인과 코사인값으로 나누어져 들어가 있고, 스케일은 대각선을 곱한다. 블렌더에서 제공되는 본의 행렬은 4가지가 있다. world, basis, channel, local

어쨌거나 이제야 다음 단계로 넘어갈 수 있게 됐다. 어휴.. 3년은 늙은 것 같다…

11.13

아휴… 난잡해… 하지만 그림이라도 없으면 카테고리 분류가 너무 안된다. 아이콘 리스트는 여기

기능은 모두 완료되지는 않았다. 하지만, 가장 어려웠던 건 해결했다. 이제 그냥 차근차근 코드 리팩토링과 함께 진행만 하면된다.

11.14

드디어 도메인이 연결됐다! 이제 https://ix9.net으로 접속할 수 있다.

블렌더의 포즈본 시스템은 어찌된 건지, 본을 선택하면 순서대로 리스트에 들어가지 않는다. 굳이 뒤죽박죽 섞는다! 왜! 그것 때문에 본의 선택 순서를 장담할 수가 없다. 그래서 한 번에 본을 잡고 뭔가를 처리하는 것에 제약이 많이 걸린다.

이 애드온은 세컨더리 애니메이션에 사용할 요령으로 제작했다. 머리카락이나 치마같이 흩날리는 애니메이션을 제작할 경우에 손으로 잡는 키를 최소화하기 위해 쓰일 것이다. 그런데 생각보다 이해가 쉽지 않고, 벌써 손이 안가는 메뉴들도 생겼다.(…) 며칠간 좀 더 손을 봐야 할 것으로 보인다.

부모본의 값들을 자식에게 물려줄 때 배율이 좀 더 강했으면 좋겠다. 싶어서 배율항목을 추가하고 값에 이를 곱해주면 되겠지. 싶었는데… 쿼터니온은 값이 오른다고 회전이 2배가 되는 것이 아니다.

그래서 키를 물려줄 때 오일러로 변환 후 다시 쿼터니온으로 변환하는 번잡시러운 과정을 거쳐야 한다. 그럼 쿼터니온 공식을 직접 조작하는 게 좋지 않을까 싶어서 챗GPT에게 물어보니

뭐라는 거야? ^^

내장함수가 왜 존재하는 지 알 것 같았다.

여차저차 구현 후 적용. 이것은 기존의 1배율 물려주기

이것이 배율을 높여서(1.4배) 물려주기이다. 확실히 더 자연스럽다. 그런데 이게 버그다… 의도했던 움직임이 아니다.

이것이 의도했던 순차적 오프셋. 그런데 버그느낌이 더 좋다! 의도적으로 키간격을 축소시키는 기능을 추가해보자.

정작 해보니 어느 상황에나 잘 맞아떨어지진 않는다. 상황봐가며 적당히 사용하도록 하자.

번호순으로 키 밀기기능도 완료. 회전 애니메이션 시에 용이하게 쓰일 것으로 기대한다.

모델링들을 모아보자.

11.8

왼쪽부터 앨리스 – 닐리 – 달래 – 오로라.

리깅에 들어가기 전에 달래의 디테일을 손보려 했는데, 모아놓고 보니 오히려 앨리스가 복잡해 보이는 현상이 있었다. 디테일을 추가하기로 한 계획은 취소하고, 치마 앞단의 무늬만 바꾸고 마무리.

폴리곤은 5만 전후. 앨리스와 오로라가 좀 많다. 정말 신기한 게, 폴리곤을 원없이 쓰자! 10만쓰자! 라고 생각하고 시작한 작업인데 5만개를 쓰니 더 이상을 쓸 곳이 없다. 팔을 8각으로 만드는 습성처럼 정해진 비율의 폴리곤작업이 몸에 베어있다보니, 폴리곤이 늘어나는 것도 한계가 있는 것이다. 물론 여기서 섭디를 한 번 더 주면 20만까지 늘어나지만, 이미 충분히 마음에 든다.

마지막 캐릭터인 오로라를 작업하며 모델링 삽질은 많았지만 셰이더 이슈가 없었다. 이젠 모델링 제작 프로세스는 완전히 정착되었다 봐도 좋을 것 같다.

이제 리깅이다. 모델링도 복잡했지만, 리깅은 더할 것이다. 심기일전하고 내일부터 데이터청소를 시작으로 앨리스 리깅에 들어가 보자.

11.9

정리만으로 하루가 다 갔다. 캐릭터 하나당 체크해야 할 사항이 이만-큼이나 된다.

  • 얼굴을 제외한 모든 오브젝트를 하나로 합친다.
  • 이 과정에서 버텍스 컬러가 문제를 일으킬 수 있으니 꼼꼼히 보자
    • 채널이름은 Attribute를 사용한다.
    • 특히 스킨셰이더 B채널은 눈에 띄지 않기 때문에 빠뜨릴 가능성이 높다.
  • 커스텀 노말을 간과하기 쉬우니, 샤프데이터 제대로 들어가 있나 꼭 확인
  • UV채널이 2개인지 확인
  • Outline그룹을 제외한 모든 버텍스 그룹을 삭제
  • Apply를 해주자.
  • 카피해서 Nude_01로 복장을 가져온다. Face와 Body외의 오브젝트는 모두 날린다.
  • 재빨리 저장부터 하자.
  • 릭이 있는 콜렉션(Girl)으로 Body를 옮겨준다.
  • 키 스케일이 1.0이 아니라면
    • 릭의 스케일을 줄인다.
    • 릭 스케일을 Apply한다.
    • 이러면 얼굴 컨트롤러의 위치가 어긋난다. 1/스케일만큼 다시 곱해준다.
    • 얼굴을 Parents와 분리한다. 이 때 트랜스폼을 유지하지 않아야 위치가 제대로 잡힌다.
    • 얼굴의 스케일을 리셋한다.(Apply가 아니라 1.0으로 되돌리는 것이다. 주의!)
    • 키 보정치만큼 올려주거나 내려준다.
    • 다시 얼굴을 본에 붙인다.
  • 레퍼런스 본 수정모드로 들어가 힐만큼 키를 올린다.
  • 3번레이어의 본들을 모두 잡고 키만큼 올린다.
  • 이렇게 되면 겨드랑이 본의 길이가 맞지 않는다. 제약조건에 들어가 Original Length옆의 X를 눌러 맞춰준다.
  • 바인딩한다.
  • 메쉬의 아마추어 모디파이어에서 Preserve Volume을 제거한다.
  • Body에서 버텍스 그룹을 데이터 이전한다. 제대로 이전되었는지는 손가락으로 확인한다.
  • Body는 이제 수명을 다 했으므로 삭제한다.
  • 머티리얼의 이름에서 .001을 떼고, 셰이더를 Nude_01에 내장된 것으로 교체한다.
  • 리커시브 후에 셰이더 노드창에서 Add-Group을 확인해 쓰레기 노드가 있는지 살피자.
  • 아웃라이너에서 .001머티리얼을 삭제한다.
  • .001이미지가 있다면 리맵해주자.
  • 얼굴 성형 후에 키를 잠근다.
  • 머리색깔과 눈색깔을 캐릭터에 맞게 바꾼다.
  • 이제 커스텀본을 심으며 개별 캐릭터의 세팅을 한다.

오로라 모델링 #2

11.2

생긴 것에 비해 본 구조는 단순하다. 그냥 사람 팔과 같다. 하지만 어깨 관절부는 구형 움직임으로 디자인을 변경하는 편이 좋겠다. 움직임이는데 생각보다 제약이 크다.

이거 그리면서도 어디서 봤나 했더니 그거였구나. 나와라! 가제트 팔!

꽤 많이 썼다고 생각했지만, 이래봤자 폴리곤이 5만개 좀 넘는 수준. 이 중 로봇팔은 만개도 안된다. 기계류는 언제나 폴리곤을 들인 것에 비해 더 좋은 효과를 보여주는 것 같다.

11.3

다시 돌아온 매핑 시즌.

11.4

아휴.. 얕봤는데 진짜 쉬운 게 하나도 없는 캐릭터네ㅋㅋ

후드티를 끝내고 싶었는데 x매듭리본이 복병이었다.

11.5

어제 작업이 마음에 안들었기 때문에 결국 다시 했다. 이번 작업은 마음에 든다.

아이고 멀었네. 손 마이 간다. 마이 가.

11.6

가방 매핑 중 타임오버

11.7

가방은 처음부터 끝까지 말썽이었다.어제 작업이 마음에 안들어서 결국 눈을 없애고 다시 디자인.

겨우겨우 상반신이 끝났다.

오늘의 블렌더 팁 : Shift + Numpad 4, 6(좌우)키를 누르면 카메라를 Roll기준으로 회전시킬 수 있다.

어쩐지 유독 어려웠던 모델이었지만 결과는 마음에 든다. 오로라 모델링 완료.

리깅 전에 계속 눈에 거슬렸던 달래 모델링을 조금 손보고 가도록 하자.

FSM을 만들어보자. #3

11.1

선입력 시스템을 추가. 조작감이 한결 부드러워졌다. 겉으로 변한 것은 없지만 많은 리팩토링이 있었다.

아따따뚜겐!

하지만 그럼에도 불구하고 두번째 기술이 자꾸 씹히는 현상이 있었다. 이유인즉, 스테이트가 배뀔 때 기존 입력이 초기화되는데, 사람이 키를 누르는 속도는 이보다 빠르다. 키를 누르다가 초기화 당해버리는 것이다

그래서 선입력 시스템을 갈아엎어 2개까지 받도록 만들어서 해결했다.처음엔 3개까지 받았지만, 이러니 너무 예약된 움직임이 많아서 게임하는데는 방해가 됐다.

11.3

스파게티코드가 되지 않기 위해 열심히 리팩토링 중. 오늘은 박치기를 구현.(↓→A)

11.6

  • 장풍 구현. 커맨드는 마데꾸.
  • 적이 점프할 때 맞으면 날아가는 구현. 이건 점프로직을 바꿔놔서 수월하게 진행.

방어도 구현했다.

….생각보다 예상했던 작업이 일찍 끝났다. 으으음. 이제 뭐하지…?

오로라 모델링 #1

10.25

오로라의 컨셉은 조금 변했다. 원래 가방에서 나오는 것은 각종 총기 및 무기였고, 이에 따라 원거리 캐릭터로 구상했었으나, 잡기 캐릭터가 필요하다는 생각에 노선을 변경했다. 본체는 약하지만 뒷팔이 힘쎄고 강하니까! 하지만 뜻밖에 가방의 이미지가 너무 안잡혀서 몇 번을 갈아엎었는지 모른다.

10.26

이제 모델링에 들어가보자.

모델링을 할 때 명심할 점. 흐름 먼저. 모양은 나중에.

이번에는 러프 모델링을 우선 뽑아본다. 비율보기가 상당히 좋다. 진작에 이렇게 할 걸!

디테일 작업 중.

10.28

누드모델이 힐을 신은 키가 기준이 되다보니 생각보다 많이 줄였다. 무려 145cm.

아마 등장인물 중(추후 등장할 사천왕을 포함해서) 가장 작은 캐릭터가 될 것이다.

10.29~30

감기때문에 작업을 못했다. 아이고오

10.31

이거 전에 오로라 작업할 때도 느낀 거긴 한데… 뒷면 잘 뵈지도 않을 거에 디자인을 쓸데없이 많이 해놔서 손해보는 느낌이다. 시간만 많이 들고.

아톰다리 뒷모습 실루엣이 예쁘게 잘 나온 것아 뿌듯하다. 애니할 때 어떻게든 뒷테를 보여줘야겠다.

로봇팔을 만들자.

티스토리는 글을 쓰고 그림을 붙이는 게 자연스러웠는데, 워드프레스는 반대로 하는 편이 보기에 좋다. 익숙해지자.

11.01

손가락을 만드는 것은 늘 어렵다. 뜻밖에 고전중.

손설계가 꽤 어렵다.

11.2

러프모델링은 대강 끝났으니 이대로 정리를 해보자.

영차영차 정리 중

손을 정리하자.