상세 컨텐츠

본문 제목

hJiggle 진행과정

Maya_Plugin_Development /hAnimation

by hwano 2014. 4. 29. 16:46

본문

 

 

http://www.cgcircuit.com/course/creating-a-custom-jiggle-deformer

지글 부분은 인터넷에 워낙 잘 나와있는 강좌가 있어서 이를 바탕으로 따라해보면서 공부해보려고 한다.

 

작동알고리즘을 한번 훑어보고 파이썬버젼의 프로토타입까지의 강좌를 본 후

c++ 부분은 스크립트를 copy하지 않고

 

http://cafe.naver.com/digitaldream/4724 등을 참고하여

강좌의 차례에 맞추어 직접 공부해가며 진행, 작성 해보려고 한다.

 

위 강좌가 하나씩 타이핑해가면서 강좌를 진행하는 것도 아니고 미리 죄다 작성해 놓은 상태에서

c++창을 위아래로 휙휙 옮겨가며 부분부분 설명을 하는데 말도 워낙빠르고 해서 따라할래도 따라할수가 없다.. 

 

비슷한 불만을 토로하는 리뷰가 있다. ㅎㅎ

This doesn't look like tutorial to me !I can read the code my self...35$ someone to read what is written already ...

or maybe this money are for the plugin..... 

 

 

 

강좌의 Jiggle 작동 수학 알고리즘

 

 

 

이 강좌에서 제시하는 알고리즘은 이러하다.

기본적으로 이 알고리즘이고 이것들을 Mesh의 각 point들 마다 반복해준다.

매트릭스 계산이 많이 들어가는게 아니래서 1000단위가 넘어가도 아마 무거울 이유는 딱히 없어보인다.

 

 

 

 

 

 

C++로 위 계산을 넣어 디포머를 만들어보자.

 

 

 

강좌에서 저 수학 계산식을 설명한 후 파이썬 으로 프로토타입의 지글 노드를 만드는데 각 포인트들을 움직이는 deformer단계의

지글이 아닌 transform 노드를 움직이는 노드이다. 때문에 골 포지션이 임의의 locator 같은 것으로 결정이 되었는데

버텍스마다의 goal을 어떻게 잡아야 할것인가?  

 

강좌의 경우는 이런식으로 풀었다. 우선 initialize가 되어 있는지 체크하고 되어 있지 않으면

map 두개를 만들어 현재 메쉬의 vertex들의 위치값을 저장한다.

그런 다음 시간차를 비교해서 작동하는 식으로 진행했다.    나는 어떻게 할까?  이 방식 그대로 진행해야되나?

 

( 하나 궁금한게 deformer 클래스에 private로 map을 만들어 저장한다는 건데 프레임이 바뀌거나 해도 계속 그 값들은 저장되어 있는거겠지?

  확인해보자 ) 

 

 

다른 방법을 찾아 보았다.

 

http://soup-dev.websitetoolbox.com/post/how-to-compare-previous-frame-to-current-frame-point-positions-6157715?trail

 

// get the input plug
MPlug pInGeo(thisMObject(), geoAttrMObject);

// get the current mesh
MObject geoCurr = pInGeo.asMObject();

// get the other frame/time you need:
MTime prevTime = currentTime + offset;

// create a MDGContext out of it:
MDGContext prevCtx(prevFrame);

// get another mobject of the geometry at this time (sort of a snapshot)
MObject geoPrev = pInGeo.asMObject(prevCtx);

 

위 페이지에 보면 샘플코드가 있다. 저런식으로 다른 time의 attribute로 접근할 수 있는거 같다.

 

STL로 저장을 해놓는게 빠를까 저런 명령어로 과거의 값을 가져오는게 더 빠를까? 

두 버젼을 테스트 해보기에는 내 역량이나 시간적 여유가 안될거 같고,  난 바로 불러오는 저 방법을 이용해서 제작해보려고 한다.

 

강좌 방법은 뭔가 원시적인거 같기도 하고,, 나중에 hConnect 노드들을 작성할 때도 어차피 필요한 부분이라서..

 

http://ewertb.soundlinker.com/api/api.009.php

 

이쪽에도 정보가 있다. 일정한 time과 dagpath를 집어 넣으면 포지션을 반환해준다.

문제는 frameCache노드가 버그도 많고 제대로 작동하지 않는 노드이다. 이 노드가 아마 내부적으로 이와 비슷한 방식으로 짜여져 있을거 같은데

custom으로 플러그인을 작성해도 여전히 제대로 작동이 안될거 같은 불안감이 든다.....      (  왠지 진짜 안될거 같아.. 불안해 )

 

 

 

 

 

 

MDGContext를 이용하여 다른 프레임의 위치값을 가져와 보자.

 

 

 

작동이 잘되는지 MDGContext를 테스트 해보자.

마야의 ghosting처럼 오브젝트를 잔상처럼 만들어보자.

 

compute 매서드가 아니고 deform매서드라 어떻게 하는지 모르겠다. 그냥

offset값을 만지면 과거나 미래 프레임의 모습을 보여줄수 있는지 학인해보자

 

기본적으로 테스트 해봤을때는 에러는 안난다.  다만 MDGContext를 써먹으로면 MPlug단에서

들어오는 값들의 time을 바꿔줘야 한다. 그래서 좀 코딩이 길어진다.

무슨말인고 하니 보통 MPxDeformerNode의 deform 안에서는 plug나 handle은 기본적인 기능으로 지원을 해주는데

 

그걸 못써먹고 다시 현재 오브젝트 -> inputAttrbute로 접근 -> 메쉬값이 들어오는 plug 찾음 -> MObject로 받음

뭐 이런식의 과정을 거쳐야 되서 조금더 까다롭다.

 

-------------------------------------------------------

 

뭐야 잘 작동하던게 세이브 후 다시 열면 작동을 안한다..  왓더..

역시나 ㅁㄴㅇㄻㄴㅇㄻㄶ  

 

이게 작동을 하지 않는건 정확히는 몰라도 버그일 가능성이 크다고 보는데

생각해 보면 MAP에 저장하는 방법을 택한 강좌의 플러그인의 경우는 어떻게 되는거지?

세이브 후 씬을 다시 불러와도 MAP의 데이터가 남아 있는걸까?..    다시 좀더 살펴봐야겠다.

 

 

 

 

 

 

맴버변수의 저장 확인

 

 

map 맴버변수에 이전 프레임들의 값을 저장해 놓는다.  그러면 프레임이 바뀌어도 값이 계속 저장되어 있다.

어떤 디포머를 적용했다. 그런다음 비져블을 꺼서 계산은 막더라도 그 계산과정에서 기록된 메모리는 계속

살아있다는 뜻이된다.

 

그럼 껏다 킨경우에는?    이경우에는 당연이 메모리가 날아간 다음이므로 저장되어 있지 않다.

때문에 지글 디포머의 움직임이 저장후 씬을 닫을 때 모습과 오픈했을때 모습은 다를 수 있다. 멤버변수를 이용하여

저장을 했다면 그 값은 메모리가 날아가면서 지워졌기 때문에. 

 

최초 씬을 열었을때 최초 실행을 위해 bool initial 이라는 변수를 만들었다.

만약 false이면 true로 바꿔주고 맴버변수에 그때의 프레임값을 입력해 두도록 하였다. 그런다음

프레임을 움직여 보면 처음 기록된 프레임 값이 계속 출력된다.  

 

만약 다른 프레임으로 옮기고 저장한 후 씬을 다시 열면 출력되는 프레임값은

씬을 열때마다 보여지는 첫 프레임이 되는 것이다.

 

 

 

 

 

 

강좌 플러그인 구동 방식 다시 분석

 

 

 

계산 순서를 하나씩 살펴보자.

 

1.    previousPoints 는 과거 프레임의 포인트들의 위치값이며 

currentPoints 는 회색값이며 동시에 goalPoints값이기도 하다.

 

2.    velocity를 계산해서 움직여진 point값을 찾는다.  그래서 파란색값의 위치를 파악한다.

 

3.    그런 다음 다시 goalPoints로 최종값을 찾아낸다. ( 그림에는 표시되지 않았다. )

 

4.    그 최종값을 iterator에 return해 준다. 그리고 그 값을 previousPoints로 저장한다.

 

.....  아니네,,  잘못 이해했다.

previous와 current 두 프레임을 비교하는 방식이 아니라 3개의 프레임을 비교하는 방식이다.

 

previous는 2번째 이전 프레임

current는 1번째 이전 프레임

goal은 지금 현재의 프레임.

 

이렇게 봐야된다.  강좌에서의 변수 네이밍이 저렇게 되어있어서 햇갈렸다.

previous, current는 저장될 때를 기준으로해서 지어지는 이름이다.

 

 

 

정리를 하자면,

 

계산을 할 때

 

previous( 2프레임 이전 ), current( 1프레임 이전 ) 로 얻어진 newPos에서

goal( 현재프레임 )로 이동하는 값이 결과값.

 

저장을 할 때

 

previous( 계산할때의 current값을 저장 ), current( 마지막 결과 값을 저장 )을 저장한다.

 

생각보다 복잡하고 개념이 어렵네,,  왜 저렇게 돼야하는지 머리로 잘 이해가 안되지만,

막상 두 프레임만 가지고 코드를 작성해보면 잘 작동하지 않는다.

밑바닥부터 내가 알고리즘을 생각했으면 3프레임을 비교한다는 아이디어는 결국 떠올리지 못하고 끝났을듯.

 

수정해보니 잘 작동한다.

 

 

 

 

 

 

 

작동 트리거 수정

 

 

 

현재 테스트 하는 방식은 hJiggle을 적용한 오브젝트의 transform을 움직인게 아니고

포인트들을 cluster로 묶어서 움직였다. 그래서 그런지 클러스트의 움직임이 멈추면

플러그인의 작동도 멈춘다 ( 지글의 움직임 자체가 멈춘다 )

 

우선 다른 것 보다 localToWorldMatrix 이 계산을 넣어주자. 그래야

현재 transform 노드에 맞는 point들의 위치로 돌아 올때까지 디포머가 계속 작동할거 같다.

 

흠..  안돼네..   작동은 되는데 역시 transform이 변화할때만 지글이 작동된다.

트리거 역할을 하는게 뭔지 모르겠네..  

 

갑자기 생각해보니 강좌의 파이썬 버젼, 프로토타입 플러그인의 트리거는 뭘까

프로토타입 플러그인은 compute() 함수를 이용한 MPxNode 클래스 플로그인이다.

goal역할을 하는 locator의 position을 input으로 받는데 이 locator가 멈춰도 작동을 하는데>>?

 

살펴보니 time 인풋값을 하나 만들어서 계속 작동하게 하는 거였다. 

강좌 c++ 플러그인의 커맨드 부분을 보면 씬의 'time1' 노드와 연결하는 부분이 나온다.

 

// Connect time
  MPlug plugInTime = fnNode.findPlug(cvJiggle::aTime, false, &status);
  CHECK_MSTATUS_AND_RETURN_IT(status);
  MSelectionList list;
  status = MGlobal::getSelectionListByName("time1", list);
  MObject oTime;
  list.getDependNode(0, oTime);
  MFnDependencyNode fnTime(oTime, &status);
  CHECK_MSTATUS_AND_RETURN_IT(status);
  MPlug plugOutTime = fnTime.findPlug("outTime", false, &status);
  CHECK_MSTATUS_AND_RETURN_IT(status);
  dgMod.connect(plugOutTime, plugInTime);

 

그럼 시뮬레이션의 정지는 goal과 currentFrame의 위치를 비교하여 얻어낸

velocity가 0이면 정지하도록 하면 되겠다..  ( 수학적 0은 항상 오류의 위험이 있으므로 0.001보다 적어지면 스톱하도록 했다 )

 

 

 

 

 

오류수정 및 속도 테스트

 

 

 

여기까지 하고 속도 테스트를 해보려고 한다.

디폴트 스피어를 하나 만들고 hJiggle을 적용했는데 스피어의 segment를 늘리거나 하면 에러가 나버린다.

이 오류 해결하고 가자. MAP에서 MPointArray의 setLength를 잡아주었는데 이 값이 달라지니 오류가 나는거다.

그러니 포인트갯수를 담아둘 MAP을 또하나 만들어야 된다.

deform()함수가 작동하는데 이전 포인트갯수가 다르면 initial을 false로 만들어주어 다시 initial을 하도록 해주면 될거다.

 

 

이 후 세그먼트를 150/150으로 나누어 ( 버텍스 개수 22352 ) 속도 테스트를 해보니 25.9 fps가 나온다.

아직까지는 어느정도 적절한 속도가 나오는듯.

 

 

 

 

 

멀티스레딩 적용

 

 

 

이제 각 attribute 별 weight map도 만들어 넣어야 되고 할게 많지만

여기서 바로 multi threading을 적용시켜 보려고 한다.    더 복잡해 지면 더 이해가 안가고 어려워 질것 같아

아주 단순한 상태일 때 적용시켜 보려고 함.

 

---------------------

 

TBB가 더 최신인거 같아 적용해 볼라는데 분명 쉽다는데 전혀 감도 못잡겠다....

책까지 서점가서 더럽게 비싸게 주고 사왔는데 발번역이라 별 도움이 안됨. 그냥

openMP는 강좌도 있고 자료도 더 많고 한거 같으니 우선 openMP먼저 적용해 볼까 한다. 아흑 공부하기 싫어

 

 

강좌 내용을 훑어보니

#pragma omp parallel for private( 변수들 ) 문장 하나만

주요 계산식이 들어가 있는 for문 앞에 적어주면 작동은 한다.  다른 내용도 많겠지만 우선 for문을 돌리는건

다른거 없이 저것만 적어도 되는듯.

 

가로 안에는 각각의 스레딩(?)마다 달라지는

변수들이 들어가면된다. 예를 들어 각 포인트마가의 goal은 다를텐데 그런 변수를 주욱 적어주면 된다.

 

 

서브디비젼을 300/300 정도로 더욱 올리고 ( 버텍스 개수 89702 ) 테스트 해보았다.

 

단일 스레딩

 

멀티 스레딩

 

뭐 기대했던것 만큼 빨라지지는 않는다..  openMP가 평균 20fps 정도 나오지만 움직임이 많은 곳에선 훨씬더 느려지기도 한다.

제대로 작동하지 않는가 싶어. cpu사용량을 확인했는데 단일 스레딩일 경우 15%를 사용하고 openMP를 적용하면

100%로 올라가는걸 보면 제대로 작동하는 것은 맞다.

 

CPU가 무려 12개인 컴퓨터인데 겨우 2.5배 정도 빨라지는 정도에서 멈추는건 좀 문제가 있는거 같다.

병렬분배가 제대로 되지 않고 있다는 얘기인데 openMP를 잘모르니..  그냥 넘어갈수 밖에..

 

 

 

 

 

 

중간 중간 느려지는 버그

 

 

 

위 상태에서 segment를 그다지 높히지 않은 상태에서 플레이를 해봐도

중간 중간 느려지는 부분이 있다. 평균 80fps정도가 나오는 가벼운 상태에서도 중간중간 렉이 걸리듯

느려지는 부분이 있는데 원인을 못찾겠다.

 

각 버텍스마다 이전 프레임과 현재의 프레임과의 위치로 벡터를 구해 벡터의 크기가 일정크기 이하이면

그 다음 계산을 멈추도록 했다.  오브젝트를 여러개 복사해서 움직임이 없는 구간을 플레이해 보면

fps가 1/3정도 떨어진다. 뭔가 계속 계산을 하고 있는것 같다.

 

segment를 3/3으로 나누고 움직임을 줬는데도 중간중간 매우 느려지는 부분이 있다. 뭐야 뭐야뭐야 뭐야 뭐야 뭐얌 ㅜ야

 

중간에 openMP부분을 지우고 작동을 해보면 속도의 변화가 없이 비록 느리지만 안정적이게 플레이되고

openMP구문을 넣으면 중간중간 버벅거리고 속도의 편차가 매우 커지는걸 보면

openMP때문인거 같아 보이는데,,     이걸 어떻게 수정해야 되낟..  난감하네

 

 

그래서 openMP관련 글들을 조금 더 읽어보고 있다.

 

http://himskim.egloos.com/3271423     - 설명이 잘되어 있는 블로그.

친절한 설명 매우매우 감사합니다.

 

openMP에 관한 몇가지 정보들

 

#pragma omp parallel for private( goal, testVector ) 이 뒤에

#pragma omp parallel for private( goal, testVector ) schedule( guided )

이런식으로 작동방법을 덧붙일 수 있다.

 

schedule에는

static, dynamic, guided가 있다.

static은 기본 방법으로 반복횟수를 thread수로 나누어 순차적으로 계산한다.

dynamic은 반복횟수를 chunk size로 나눈 독립된 작업으로 생각하고 각 작업이 끝난 thread들이 순차적으로 job을 가져가게 한다. 일찍 끝난 놈이 다음 일을 빨리 가져간다.

guided는 dynamic과 유사하지만 chunk size를 처음에는 크게 시작하여, 점점 chunk size를 줄여나간다.

 

 

----------------------------------------------------------------------------------------------

 

 

이것저것 테스트 해보다가

threads라는 attribute를 만들어서 플러그인이 사용하는 tread의 갯수를 조절해 보았다.

뒤에 num_threads( int )를 붙여주면 된다.

 

#pragma omp parallel for private( goal, testVector ) schedule( guided ) num_threads( int ) 요런식으로

 

스피어를 50/50으로 나누고 hJiggle을 적용한다음 32개로 복사하여서

움직임이 없는구간에서의 속도를 테스트 했다. 움직임이 없는 구간은 이전 프레임과 현제 프레임의 포인트 위치가

똑같은지 확인하는 계산에서 끝이 난다.

 

시스템상의 최대 threads는 12.

 

threads 1  -  평균 10fps정도

threads 6  -  평균 23fps정도

threads 10  -  평균 22fps정도

threads 12  -  평균 3fps~20fps를 요동침

 

재밌는 현상이 나온다. 어느정도 threads가 올라갈수록 성능의 향상이 있다가

더이상 threads가 늘어도 속도가 늘지 않고 최대치까지 가자 오히려 버벅되기 시작했다.

 

인텔스레딩빌딩블록책 19페이지에 비슷한 설명이 있다.

- 프로세서 코어의 개수를 아무리 늘려도 더 이상 속도 증가 효과가 나지 않을때 조정성의 한계가 있다고 말한다.

  프로세서 코어를 강제로 추가 해도 오히려 성능이 떨어지기 쉽다.

   TBB에는 이 문제를 방지하기 위해 데이터 분할을 합리적인 수준으로 제한해 주는 grain 크기의 개념이 있다. -

 

난 openMP니까 grain은 우선 패스.  openMP에도 저런게 있을텐데,, 우선은

적당한 퍼포먼스를 위해서 thread 수를 플러그인 작성시에는 4개정도로 고정시키는게 좋을것 같다.

 

 

 

-------------------------------------------------------------------------------------------------------

 

thread 수를 4로 고정하고 위에서 했던 segment 300/300의 속도 테스트 해보자.  24~25fps 왔다갔다 한다.