페이지

2021년 3월 16일 화요일

Unity Animation Rigging 사용시 주의점

 Two Bone IK Constraint 를 적용할때

Tip, Mid, Root 에 해당하는 Bone들을 알맞게 설정해야 애니메이션이 정상적으로 작동된다. 그러므로 설정시 주의깊게 체크해서 입력하도록 하자. 



특히나 Tip 에 해당하는 Bone을 보통 Bone 뼈대의 맨 끝 ( 마지막 자식 (예) Bone.003_end )을 설정하고 "Auto Setup from Tip Transform" 메뉴를 선택해서 Mid, Root의 뼈대를 자동으로 설정하게되는 경우가 있는데, 이를 조심해야한다.  Bone.003_end 를 Tip으로 하고 Auto Setup from Tip Transform으로 할 경우, Mid : Bone.003, Root: Bone.002 로 잘못 설정된다.


2족 보행 애니메이션 리깅을 예를 들자면, 

아래 그림에서처럼 Bone.003_end 는 발끝을 의미한다.  다리의 뼈대가 허벅지(Root), 종아리(Mid), 발 이 3가지로 크게 구성될수 있는데, Rigging시 발보다는 발끝 ( 이름뒤에 end가 붙는다) 을 Tip으로 설정해야 한다. Mid : 종아리, Root: 허벅지 로 설정해야한다.




그래서 이 모델에서는

Root : Bone.001

Mid: Bone.002

Tip : Bone.003_end 

가 된다.


2021년 3월 8일 월요일

Unity C# Job System - tips and troubleshooting

  • Job내에 있는 Static data에 접근하지 말라

예를들어 Job내에서 MonoBehaviour 멤버에 접근하면 크래쉬를 야기 시킨다.


  • Flush scheduled batches

다른 Job들이 수행중일때(executing), 즉, 끝나지 않은 상태에서 또 다른 대기 Job들을 JobHandle.ScheduleBatchedJobs 으로 호출하면,  대기순서를 무시하고 바로 실행된다( Execute )

디폴트로, Job들은 Schedule 함수호출로 큐에 놓여져만 있다.  이는 작업자 스레드가 큐에 있는 Job들을 하나씩 가져와서 실행하게 해준다. 
Job 시스템은 ScheduleBatchedJobs를 사용자가 호출하지 않는다면,  현재 수행중인 Job이 끝날때까지  job 수행을 하지 않는다. 왜냐하면, 또 다른 작업자 스레드를 강제로 키는 것은 성능에 않좋기 때문이다. 그래서 많은 양의 Job들이 스케줄되어 있더라도, 순차적으로 스케쥴된 Job들이 수행되게 기다려라. 

Note:  ECS 에서는 batch가 암묵적으로 flush된다. ScheduleBatchedJobs를 호출할 필요가 없다.


  • NativeContainer 데이타를 바로 변경하지 말라

 Job내에서 ref return 이 안되므로, NativeContainer를 참조형 변경을 하지 말라. 항상 복사본을 만들어서 변경시켜라
예)

nativeArray[0]++; // failed


var temp = nativeArray[0];
temp++;
nativeArray[0] = temp;    // success


  • 소유권을 얻기위해서는 JobHandle.Complete 호출하라

메인스레드에서 소유권을 얻으며, safety system이 상태를 리셋시켜서 메모리누수를 방지한다.


  • Schedule, Complete를 메인 스레드에서 사용해라

  • Schedule, Complete를 적절한 시간에 사용해라

Schedule() 함수를 호출하고 Job 결과값이 필요하기 전에는 Complete()함수를 호출하지 말아라. 다른 Job의 수행 결과를 기다릴 필요가 없을때 Schedule() 함수를 호출하는것이 좋은 습관이다. 예를 들어 1 frame latency일 경우에, 한 frame이 끝날때 즈음에 Schedule() 을 호출하고 다음 frame 의 시작시기에 그 결과 데이타를 사용하도록하자.


  • NativeContainer 타입은 readonly로 설정하라

Job은 초기에 NativeContainer에 대한 읽기, 쓰기권한을 갖고 있다. 성능향상을 위해서 ReadOnly만을 설정해라.


  • 데이타 의존성을 체크해라

유니티 프로파일러에, 메인스레드에 "WaitForJobGroup" 문구가 나온다면, 이는 작업 스레드의 Job이 완료되기를 Unity가 기다리고 있다는 의미.
이는 사용자가 어디선가 작업이 끝내기를 기다리는 선작업이 있다는 것으로 , 의존성 데이타를 사용하고 있다는 의미이다. JobHandle.Complete 를 찾아라. 메인스레드가 기다리게 만드는 데이타 의존성을 찾아야 한다. 


  • Debugging jobs

Job은 Run() 함수를 갖고 있다. Schedule() 함수 콜 위치에서 사용할수 있다. 이는 메인스레드의 Job을 바로 실행케하는 것이다. 이는 Debugging 목적으로만 쓰인다.


  • Job내에서 managed memory를 생성하지 말아라

이는 매우 느려지게 하는 원인이다. job은 성능향상을 위해서 Unity Burst Compiler( managed code )를 이용할 수 없다.





2021년 3월 3일 수요일

Unity C# Job System 5

 ParallelFor jobs

IJob 들은 코어당 하나의 Job (Execute )을 수행한다. 그에 반해 IJobParallelFor 은 하나의 코어에 여러개의 Job들을 수행할수 있다. 

Note: ParallelFor Job은 IJobParallelFor 인터페이스를 구현한 structure이다. 



struct IncrementByDeltaTimeJob: IJobParallelFor {

    public NativeArray<float> values;
    public float deltaTime;

    public void Execute(int index){
        float temp = values[index];
        temp += deltaTime;
        values[index] = temp;
    }
}


Scheduling ParallelFor Jobs

이를 사용할때는 NativeArray 데이타 길이를 정해야 한다.  코어는 한정적이고 처리해야할  IJobParallelFor의 Execute들이 많다.이럴때는 몇개의 Job들을 묶어서 하나의 Batch 로 만든다. 이들 여러개의 Batch들을 Job Queue에 넣고 NativeJob 을 생성한다. Native Job System은 각 core당 NativeJob Queue로부터 NativeJob들을 꺼내와서 Batch들을 수행한다.



만약 다른 NativeJob들 보다 먼저 batch수행을 끝낸것 이 있다면, 그것은 현재 남아 있는 다른 NativeJob의 batch들을 가져와서 처리한다. 한번에 남아있는 batch들의 반정도 양만 가져온다. 
이를 최적화 하기위해서, batch count를 시스템에 맞게 적절히 조절할 필요가 있다. Batch count가 너무 낮으면,  core에 비해 처리해야할 Queue 가 많아지며, Batch count가 너무 높으면 한 core에서 수행할 작업자 스레드의 양이 증가한다. 

An example of scheduling a ParallelFor job

// Job adding two floating point values together
public struct MyParallelJob: IJobParallelFor {
    [ReadOnly]
    public NativeArray<float> a;
    [ReadOnly]
    public NativeArray<float> b;
    public NativeArray<float> result;

    public void Execute(int i ){
        result[i] = a[i] + b[i];
    }
}


// Main Thread

NativeArray<float> a = new NativeArray<float>(2, Allocator.TempJob);
NativeArray<float> b = new NativeArray<float>(2, Allocator.TempJob);
NativeArray<float> result = new NativeArray<float>(2, Allocator.TempJob);

a[0] = 1.1;
b[0] = 2.2;
a[1] = 3.3;
b[1] = 4.4;

MyParallelJob jobData = new MyParallelJob();
jobData.a = a;
jobData.b = b;
jobData.result = result;

// Schedule the job with one Execute per index in the results array and only 1 item per processing batch
JobHandle handle = jobData.Schedule(result.Length, 1);

// Wait for the job to complete
handle.Complete();

// Free the memory allocated by the arrays
a.Dispose();
b.Dispose();
result.Dispose();

ParallelForTransform jobs

ParallelFor job의 또 다른 타입. 이는 GameObject의 Transform만을 다루기 위해 나왔다.


2021년 3월 1일 월요일

Unity C# Job System 4

 JobHandle and dependencies

Job의 Schedule() 메서드를 호출하면 JobHandle을 반환한다. 이것으로 다른 Job들과 의존성으로 사용할수 있다. 한개의 Job이 다른 Job의 결과에 의존적이라면, 첫번째 Job의 JobHandle을 두번째 Job의 Schedule 메서드에 변수로 넣는다. 


JobHandle firstJobHandle = firstJob.Schedule();

secondJob.Schedule(firstJobHandle);


Combining dependencies

만약 Job이 많은 의존성들을 갖는다면, JobHandle.CombineDependencies  메서드를사용하여 그것들을 병합하라. 

NativeArray<JobHandle> handles = new NativeArray<JobHandle>(numJobs, Allocator.TempJob);
JobHandle jb = JobHandle.CombineDependencies(handles);

Waiting for jobs in the main thread

 메인스레드에서 Job 수행이 끝날때까지 기다리게 하고 싶으면, Jobhandle을 사용해라. 이는 Complete() 메서드를 호출하면 된다. 이점이 안전하게 Job이 실행중에도, 메인스레드에서 NativeContainer를 접근할수 있게 해준다.

Note 

Job은 사용자가 schedule 한다고 해서 실행되지 않는다. 사용자가 메인스레드내에서 Job을 기다린다면, 그리고 Job 이 사용중인 nativeContainer Data를 접근하려 한다면, JobHandle.Complete()를 호출하면 된다.  This method flushed the jobs from memory cache and starts the process of execution. Complete메서드를 호출하는 것은 Job의 소유권을 메인스레드에 넘겨주는 것이다. 그리고 메인스레드에서 다시 NativeContainer type 에 안전하게 접근할수 있다. 
만약 데이타에 접근할 필요가 없다. 명시적으로 배치를 flush할수 있다. 이는 JobHandle.ScheduleBatchedJobs 를 호출 하면 된다. 이는 성능에 않좋은 함수이다. 


//Job adding two floating point values together
public struct MyJob:IJob {
    public float a;
    public float b;
    public NativeArray<float> result;

    public void Execute(){
        result[0] = a + b;
    }
}

//Job adding one to a value 
public struct AddOneJob:IJob  {
    public NativeArray<float> result;
    public void Execute(){
        result[0] = result[0] + 1;
    }
}

NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);

MyJob job1 = new MyJob();
job1.a = 10;
job1.b = 10;
job1.result = result;
JobHandle job1Handle = job1.Schedule();

AddOneJob job2 = new AddOneJob();
job2.result = result;
JobHandle job2Handle = job2.Schedule(job1Handle);

// Wait for job2 to complete
job2Handle.Complete();

// All copies of the NativeArray point to the same memory, you can access the result in your copy of the NativeArray
float aPlusB = result[0];

// Free the memory allocated by the result array
result.Dispose();