Introduction


이 섹션에서는 다양한 성능 관련 문제에 대해 설명합니다.
이 내용은 주로 프로그래머를 대상으로 합니다.

 

 

 

 

CPU 의존성


MagicaCloth는 Unity DOTS(Data-Oriented Technology Stack)를 사용합니다.
따라서 시뮬레이션 성능은 전적으로 CPU에 의존합니다.
반대로, GPU는 전혀 사용되지 않습니다.

또한, DOTS는 멀티스레딩을 지원하므로 CPU에 코어(스레드)가 많을수록 병렬로 실행되어 성능이 향상됩니다.

 

 

 

 

모바일 기기의 성능


그러나 Android/iPhone에서 사용할 때는 약간의 주의가 필요합니다.
모바일 기기의 CPU는 일반적으로 고성능 코어(big core)와 저전력 코어(small core)로 구성됩니다.
이를 Big-Little 구성이라고 합니다.
예를 들어, 8코어 CPU를 가진 단말기의 경우 대부분 Big4/Little4 등의 방식으로 나뉘어 있습니다.
이 경우, (4-4 코어)로 표현됩니다.

Unity는 DOTS를 Big 코어에서만 실행합니다.
따라서 위와 같은 기기의 경우, 8코어 중 오직 4개의 코어만 DOTS에 사용할 수 있습니다.
이 점을 유의하시기 바랍니다.

이 문제는 데스크톱 PC의 CPU에서는 발생하지 않습니다.

 

 

 

 

Check with Profiler


Unity의 프로파일러 기능을 사용하여 시뮬레이션 부하를 쉽게 확인할 수 있습니다.
프로파일러에서는 타임라인에 MagicaManager 블록으로 표시됩니다.
또한, Job 항목에서 멀티스레딩 상태를 확인할 수 있습니다.

 

 

 

 

Cloth Data의 생성 및 실행


MagicaCloth는 시뮬레이션을 수행하기 위해 다양한 데이터를 필요로 합니다.
이것을 Cloth Data라고 합니다.
Cloth Data는 요청에 따라 실행 시간에 동적으로 생성됩니다.

 

Cloth Data의 생성은 상당한 계산 작업을 필요로 하며, 보통 10ms에서 50ms 정도의 시간이 걸립니다.
이 생성 과정은 백그라운드 스레드에서 실행되므로 메인 스레드에는 영향을 거의 미치지 않습니다.
또한, 여러 개의 Cloth가 여러 스레드에서 생성되며 병렬로 실행됩니다.

 

하지만, 시뮬레이션은 이 Cloth Data가 완성될 때까지 기다려야 합니다.
이로 인해 캐릭터가 실제로 생성된 후 시뮬레이션이 시작되기까지 여러 프레임의 지연이 발생합니다.

 

 

 

 

Editor 실행 시 주의사항


MagicaCloth에서 사용하는 BurstJobSystem은 빌드할 때보다 에디터에서 실행할 때 더 많은 리소스를 소모합니다.
따라서 에디터에서 실행할 때의 프로파일러 내용은 빌드에서 실행할 때와 다를 수 있습니다.
이는 다음과 같은 요소들로 인해 발생합니다.

 

 

Burst JIT Compiler

Burst는 에디터에서만 런타임(Just-In-Time Compiler)에서 컴파일됩니다.
이 컴파일은 플레이가 시작된 후에 이루어지므로, MagicaCloth가 처음 사용될 때 컴파일 시간이 수백 ms 이상 걸릴 수 있습니다.
따라서 에디터 환경에서는 플레이 후 첫 번째 시뮬레이션이 시작되기 전에 상당한 지연이 발생합니다.


이 문제는 에디터 환경에서만 발생하며 빌드 시에는 발생하지 않습니다.

이 문제를 해결하려면 Enter Play Mode Options를 사용하십시오.
이 옵션은 PlayerSettingsEditor 탭에 있습니다.

 

Enter Play Mode를 사용하면, 반복적인 플레이 작업 후에도 Burst가 다시 JIT 컴파일되지 않습니다.

 

 

JobsDebugger 처리 부하

에디터에서는 JobsDebugger가 작업의 실행을 계속 모니터링합니다.
이로 인해 작업 실행 시간이 평소보다 길어지고, 작업 간에 부자연스러운 간격이 발생할 수 있습니다.
부하가 걱정되면 JobsDebugger를 끄는 것이 좋습니다.

 

 

 

 

SafeCheck 처리 부하

마찬가지로, 에디터 환경에서는 Burst의 안전성도 모니터링됩니다.
이 부하는 어느 정도 발생하므로, 걱정된다면 두 가지 체크를 끄는 것이 좋습니다..

 

 

 

 

이 두 체크를 끄면 오류는 더 이상 보고되지 않습니다.

하지만 위에서 설명한 대로 JobDebugger SafeCheck를 끄면 Burst/Jobs 오류가 표시되지 않습니다.
따라서 MagicaCloth가 제대로 작동하지 않는다고 생각되면 모든 체크를 다시 켜고 오류를 확인해보세요

 

 

빌드에서 테스트 권장

위에서 언급한 바와 같이, 에디터에서 실행할 때는 여러 모니터링으로 인해 시뮬레이션 성능이 떨어집니다.
그러나 릴리스 빌드는 모든 모니터링을 제거합니다.
따라서 실제 기기에서 실제 성능을 확인하려면 빌드를 통해 테스트하는 것이 가장 좋습니다.

 

 

 

 

빌드 시 주의사항


Burst AOT 설정

빌드할 때 Burst를 활성화하는 것을 잊지 마세요.
이는 PlayerSettings에서 Burst AOT Settings에서 설정할 수 있습니다.
이 옵션을 체크 해제하면 빌드가 Burst가 비활성화된 상태로 빌드됩니다.
기본적으로는 활성화되어 있습니다.

 

 

IL2CPP 권장

빌드할 때 IL2CPP를 사용하는 것을 강력히 추천합니다.
이유는 C#의 처리 속도가 Mono에 비해 크게 향상되기 때문입니다.

 

 

 

 

 

처리 부하 목록


이 섹션에서는 MagicaCloth의 기능 중 가장 처리 집약적인 기능에 대해 설명합니다.
이 많을수록 부하가 높아집니다.

 

천 데이터 생성 방법

Runtime build (default) ★★★ 런타임 생성은 사용될 때 천 데이터를 그 자리에서 생성합니다. 이로 인해 초기화 시 부하가 증가합니다. 천 데이터는 백그라운드에서 생성되지만, 이 과정은 CPU를 소모합니다.
Pre-build 프리 빌드는 천 데이터를 생성하여 에셋으로 만들어 편집 중에 미리 준비합니다. 이 방법은 초기화 부하를 크게 줄여줍니다. 또한, 백그라운드 처리도 없습니다.

 

 

천 타입

MeshCloth ★★★★ MeshCloth는 시뮬레이션 외에도 프록시 메쉬 스키닝과 렌더 메쉬에 다시 쓰는 작업이 포함되기 때문에 BoneCloth보다 훨씬 더 많은 부하를 요구합니다. 따라서 모바일 기기에서 사용할 때 성능에 주의해야 합니다.
BoneCloth BoneCloth는 매우 가벼운 타입입니다. 대부분의 경우, 대량으로 사용해도 문제가 발생하지 않습니다.

 

 

충돌 처리

Self Collision ★★★★★★★★★★ Self-collision은 모든 기능 중에서 가장 눈에 띄고 처리 부하가 큰 과정입니다. 따라서 기본적으로 CPU 코어가 많은 데스크톱 PC에서 사용을 권장합니다. 모바일 기기에서 사용할 경우, 프록시 메쉬의 꼭지점 수를 가능한 한 줄이고 성능에 주의해야 합니다.
Mutual Collision ★★★★★★ Mutual collision은 다른 대상과의 충돌만 결정하기 때문에 self-collision보다는 부하가 약간 적습니다. 하지만 프로세스는 self-collision과 다를 바 없으므로 성능에 주의해야 합니다.
Edge Collision ★★★★ Edge collisions는 점 충돌보다 몇 배 더 많은 부하를 요구합니다. 점 충돌에서 문제가 발생할 때만 사용하도록 하세요.
Point Collision ★★ Point collisions는 다른 충돌 판별 방식에 비해 처리 부하가 훨씬 적습니다.
Backstop Backstop은 계산이 적게 필요하기 때문에 가장 낮은 처리 부하를 가집니다. 따라서 걱정 없이 사용할 수 있습니다.

 

 

 

 

 

시뮬레이션 주파수 및 최대 업데이트 수


MagicaCloth의 시뮬레이션은 자체적인 시간 관리 방식으로 인해 Unity의 프레임 업데이트와 다른 타이밍에 실행됩니다.
이것은 프레임 속도와 관계없이 일정한 간격으로 실행됩니다.

 

이 일정한 간격을 시뮬레이션 주파수라고 합니다.
예를 들어, 주파수가 90이면 시뮬레이션은 1초에 1/90초마다 업데이트됩니다.
이는 Unity의 물리 엔진 업데이트(FixedUpdate)와 프레임 업데이트 간의 관계와 같습니다.
MagicaCloth는 초기 주파수로 90을 설정해 두었습니다.
즉, 시뮬레이션은 1초에 90번 업데이트됩니다.

 

또한, 한 프레임에서 실행할 수 있는 최대 시뮬레이션 횟수가 설정되어 있습니다.
이것은 과도한 부하로 인해 시뮬레이션이 무한히 반복되지 않도록 방지하는 안전 기능입니다.
MagicaCloth는 초기값으로 3번이 설정되어 있습니다.
최대 횟수 때문에 시뮬레이션이 생략되면, 위치는 보간(interpolation) 함수로 보충됩니다.
이 보간 함수는 간단하고 정확도가 떨어집니다.
따라서 시뮬레이션이 생략되면 아티팩트가 발생할 수 있다는 점을 유의해야 합니다.

 

 

주파수와 성능

시뮬레이션 주파수는 성능과 밀접하게 관련이 있습니다.
주파수를 낮추면 시뮬레이션을 적게 실행하게 되어 성능이 향상됩니다.
하지만 주파수는 시뮬레이션 정확도에 큰 영향을 미칩니다.
따라서 주파수를 낮추면 시뮬레이션의 정확도도 낮아지게 됩니다.
주파수와 시뮬레이션 정확도 사이에는 트레이드오프가 있다는 점을 염두에 두어야 합니다.

 

 

주파수 및 최대 업데이트 수 변경

주파수와 최대 업데이트 횟수는 두 가지 방법으로 변경할 수 있습니다.
변경은 언제든지 가능합니다.

 

 

API

스크립트를 통해 API로 변경할 수 있습니다.

 

 

MagicaSettings

MagicaSettings라는 전용 컴포넌트를 제공하여 시스템의 상태를 변경할 수 있습니다.
이 컴포넌트를 사용하면 코딩 없이 주파수와 최대 업데이트 수를 변경할 수 있습니다.
설정 방법에 대해서는 MagicaSettings 문서를 참조하십시오.

 

 

 

주파수의 운영 효과

주파수를 변경하면 시뮬레이션 동작에 약간의 변화가 생깁니다.
예를 들어, 주파수를 90으로 설정한 상태에서 이동을 조정한 후, 주파수를 30이나 150으로 설정하면 이동이 달라지고 완전히 동일하지 않게 됩니다.
이는 주파수를 변경하면 파라미터의 효과에 약간의 차이가 발생하기 때문입니다.
따라서 주파수 변경은 파라미터 재조정이 필요할 수 있습니다.

 

 

설정예시

다음은 몇 가지 설정 예시입니다.

 

성능 우선 설정

성능이 중요하다면 주파수를 60으로 설정하고 최대 업데이트를 2로 설정해 보세요.
정확도는 조금 떨어지지만 성능이 향상됩니다.

 

고정 프레임 속도 설정

게임이 60fps와 같은 고정된 프레임 속도로 실행될 경우, 주파수를 이에 맞게 설정하는 것도 효과적입니다.
예를 들어, 주파수를 60으로 설정하고 최대 업데이트 횟수를 1로 제한하면 한 프레임의 부하가 안정화됩니다.

 

또한, 게임이 30fps로 실행된다면 주파수를 60으로 설정하고 최대 업데이트 횟수를 2로 설정하면 도움이 됩니다.
이렇게 하면 한 프레임에서 시뮬레이션이 두 번 업데이트되어 30fps에서도 주파수 60의 정확도를 확보할 수 있습니다.

 

성능 최우선 설정

성능을 최우선으로 한다면 주파수를 30으로 설정하고 최대 업데이트 횟수를 1로 설정해 보세요.
이 설정은 최대 성능을 발휘할 수 있습니다.
하지만 정확도가 크게 떨어지므로 매우 신중해야 합니다.
이 설정은 아티팩트보다 성능을 우선시하는 설정입니다.

 

 

 

 

 

컬링 시스템


컬링은 카메라에 표시되지 않거나 카메라에서 일정 거리 이상 떨어진 캐릭터들의 시뮬레이션을 중지시켜 성능을 향상시키는 기능입니다.

 

이 기능은 1인칭 FPS 게임이나 VR에서 성능을 크게 향상시킵니다.
컬링은 카메라 컬링거리 컬링의 두 가지 기능으로 구성됩니다.

https://www.youtube.com/watch?v=9uiXUocomVQ

 

 

https://www.youtube.com/watch?v=aP5ljTotmK4

 

자세한 내용은 컬링 시스템 문서를 참조하십시오.

 

 

 

 

캐릭터 배치


DOTS는 Transforms의 읽기 및 쓰기 작업에 멀티스레딩을 사용합니다.
하지만 이 혜택을 활용하려면 캐릭터 배치 방식에 주의해야 합니다.
DOTS에서는 Transform 처리가 계층 구조의 루트에 배치된 각 GameObject 그룹에 대해 멀티스레딩 방식으로 처리됩니다.

다음 예시에서, 모든 10개의 캐릭터가 루트에 배치되었으므로 각 캐릭터의 Transform 처리 과정은 여러 스레드에서 실행됩니다.
이것은 이상적인 배치입니다.

 

그러나 다음 예시에서는 모든 캐릭터가 "CharacterGroup" 객체의 자식으로 배치되어 있습니다.
이것은 매우 나쁜 예시로, Transform 처리가 전혀 멀티스레딩 방식으로 이루어지지 않습니다.
특히 많은 수의 캐릭터가 있을 경우 성능 저하가 두드러지게 나타날 수 있습니다.

 

 

 

 

 

 

시뮬레이션 작업


이 섹션에서는 천 시뮬레이션이 어떻게 수행되는지 설명합니다.
이 섹션은 시뮬레이션 프로세스가 최적화된 v2.14.0 이상 버전을 사용하고 있다고 가정합니다.

 

 

분할 작업

시뮬레이션 처리는 작업(job)이라는 처리 단위로 나누어져 실행됩니다.
이 작업들은 프로파일러에서 확인할 수 있습니다.

 

이미지에서 볼 수 있듯이 작업은 매우 작은 부분으로 나누어집니다.
이것은 각 처리 단계에서 데이터 동기화가 필요하기 때문입니다.
이 방법은 CPU 코어를 최대한 활용할 수 있다는 장점이 있지만, 작업 스케줄링 시간과 동기화 대기 시간 증가와 같은 단점도 있습니다.

 

 

배치 작업

따라서 v2.14.0부터는 작업을 나누지 않고 한 번에 처리하는 배치 작업을 추가하였습니다.
배치 작업에서는 하나의 MagicaCloth 컴포넌트 처리 작업이 하나의 작업으로 할당됩니다.
이렇게 하면 작업을 나누는 것에 비해 불필요한 동기화 시간이 제거되어 속도가 크게 향상됩니다.

 

하지만 단점도 존재합니다.
배치 작업은 작업을 나누지 않기 때문에 스레드별로 처리를 분배할 수 없습니다.따라서 처리 시간이 무겁게 드는 MagicaCloth 컴포넌트가 하나 있을 경우, 전체 처리 시간이 연장될 수 있습니다.

 

다음과 같이, 무거운 작업이 처리되는 동안 다른 CPU 코어는 완전히 유휴 상태가 됩니다.
이로 인해 CPU는 매우 비효율적이며, 작업이 분리된 작업으로 처리될 때보다 더 많은 시간이 소요됩니다.

 

 

분할 작업과 배치 작업 함께 사용하기

이 문제를 해결하기 위해, 우리는 경량 컴포넌트는 배치 작업으로 처리하고, 무거운 컴포넌트는 분할 작업으로 처리하는 하이브리드 시스템을 구현했습니다.
이렇게 하면 CPU 낭비를 없애고 코어를 최대한 활용할 수 있습니다.
배치 작업과 분할 작업은 병렬로 실행됩니다.
분할 작업은 다음 두 가지 조건에서 적용됩니다:

  • 프록시 메쉬의 꼭지점 수가 300개 이상인 경우
  • Self-collision 또는 Mutual collision을 사용하는 경우

이 조건 중 하나라도 만족하면 분할 작업이 사용됩니다.
프록시 메쉬의 꼭지점 수는 인스펙터에서 확인할 수 있습니다.

 

 

분할 작업 임계값 변경

분할 작업의 조건은 프록시 메쉬의 꼭지점 수가 300개 이상이어야 하지만, 이 기준은 변경할 수 있습니다.
이를 변경하는 두 가지 방법은 다음과 같습니다:

  • MagicaSettings
    코딩 없이 MagicaSettings 컴포넌트를 설치하여 변경할 수 있습니다.

이 변경은 런타임 중 언제든지 할 수 있으므로, 최대 효율성을 원한다면 플랫폼에 따라 임계값을 조정하는 것을 고려하세요.

 

 

 

 

 

기타


에디터 환경에서의 극단적인 성능 저하

지금까지 최종 사용자들로부터 에디터 환경에서 실행 시 성능이 매우 낮다는 보고를 받았습니다.
로드가 빌드할 때보다 수십 배 더 높은 것으로 나타났습니다.
하지만 이 문제는 재현되지 않으며, 일부 PC 환경에서만 발생하는 것으로 알려져 있습니다.
이 상황을 겪으셨다면, 프로젝트의 Library 폴더를 아래와 같이 삭제해 보세요:

 

일부 사용자들은 이 방법으로 문제를 해결했다고 보고했습니다.
Library 폴더는 프로젝트의 작업 데이터를 저장하며, 삭제하면 자동으로 다시 빌드됩니다.
하지만 Library 폴더를 삭제하기 전에 다음 단계를 따르세요:

  1. Unity 에디터 종료
  2. 프로젝트 백업
  3. Library 폴더 삭제
  4. Unity 에디터 재시작

'유니티 에셋 > Magica Cloth 2' 카테고리의 다른 글

의상 변경(Dress-up) 프로세스  (0) 2025.03.04
스케일 변경  (0) 2025.03.04
런타임 변경사항  (0) 2025.03.04
런타임 구성  (0) 2025.03.04
캐릭터 인스턴스화 (Character Instantiation)  (0) 2025.03.02

개요


이 섹션에서는 MagicaCloth를 다른 캐릭터에 적용하는 방법을 설명합니다.
이 방법을 사용하면 게임 캐릭터에 다양한 헤어스타일과 의상을 변경할 수 있습니다.
이 문서는 Unity에서 C# 프로그래밍에 익숙한 사용자를 대상으로 합니다.

 

 

 

 

 

샘플 씬


의상 변경(Dress-up) 프로세스를 위한 샘플 씬이 제공됩니다.
다음 폴더에서 씬을 찾을 수 있으며, 사용하는 렌더 파이프라인에 맞는 씬을 선택하여 테스트하세요.

 

샘플 씬 내 RuntimeDressUpDemo 오브젝트에 RuntimeDressUpDemo.cs가 포함되어 있습니다.
이 페이지에서는 해당 테스트 코드를 기반으로 설명합니다.

씬이 분리된 이유

  • 씬을 분리한 이유는 단순히 렌더링 머티리얼(Rendering Material)을 전환하기 위해서입니다.
  • 내부에 포함된 샘플 코드 자체는 모든 씬에서 동일합니다.

 

 

 

샘플 데이터


샘플 씬에서 사용되는 데이터에 대해 설명합니다.

Utc_sum_humanoid (Skeleton)

먼저 Utc_sum_humanoid (Skeleton)뼈대(Skeleton)만 있는 캐릭터입니다.

 

이 캐릭터는 Transform만 포함된 스켈레톤 캐릭터이며,
헤어(Hair), 의상(Clothing), MagicaCloth가 설정되지 않은 상태입니다.

 

이제 이 Utc_sum_humanoid (Skeleton)에 헤어와 의상을 추가할 것입니다.

 

 

Utc_sum_humanoid (Hair)

헤어 렌더러(Renderer)와 MagicaCloth가 설정된 프리팹(Prefab) 형태의 스켈레톤 캐릭터입니다.

 

이 프리팹은 전체 스켈레톤(Skeleton)을 포함하고 있습니다.
(사실, 필요한 GameObject만 포함하면 충분하지만, 샘플에서는 전체 뼈대를 포함하고 있습니다.)

 

 

Utc_sum_humanoid (Body)

의상 렌더러(Renderer)와 MagicaCloth가 설정된 프리팹(Prefab) 형태의 스켈레톤 캐릭터입니다.

 

이 프리팹은 전체 스켈레톤(Skeleton)을 포함하고 있습니다.
(사실, 필요한 GameObject만 포함하면 충분하지만, 샘플에서는 전체 뼈대를 포함하고 있습니다.)

 

 

 

 

의상 변경 방법

의상을 변경하려면 아래 단계를 따르세요.

  1. 의상 프리팹을 생성하고,
  2. MagicaCloth 초기화를 호출한 후
  3. MagicaCloth가 자동으로 빌드되지 않도록 중지합니다.
  4. 그다음 Renderer를 스켈레톤 아바타에 이식하고,
  5. MagicaCloth를 스켈레톤 아바타에 이식하며,
  6. 콜라이더 및 기타 항목을 스켈레톤 아바타에 이식한 후
  7. MagicaCloth 실행을 시작합니다.

(4)에서 Renderer를 이식하는 과정은 MagicaCloth 시스템과 무관하므로 다른 프로그램이나 에셋을 사용할 수도 있습니다.

 

 

 

 

의상 제거 방법


의상을 제거하려면 아래 절차를 따릅니다.

  1. Destroy()를 사용하여 Renderer를 제거하고,
  2. Destroy()를 사용하여 MagicaCloth를 제거하며,
  3. Destroy()를 사용하여 불필요한 콜라이더를 제거하고,
  4. Destroy()를 사용하여 원하지 않는 GameObject를 제거합니다.

기본적으로 MagicaCloth를 포함한 불필요한 GameObject를 Destroy()하면 되며, (1)의 Renderer 제거는 의상 변경 과정과 마찬가지로 다른 프로그램이나 에셋을 사용할 수도 있습니다.

 

 

 

 

예제


이 섹션에서는 샘플 씬 RuntimeDressUpDemo.cs에 대해 설명합니다.
위에서 설명한 의상 변경(Dress-up) 및 제거(Undress-up) 절차를 기반으로 코드를 살펴보면,
전체적인 동작을 쉽게 이해할 수 있을 것입니다.

// Magica Cloth 2.
// Copyright (c) 2023 MagicaSoft.
// https://magicasoft.jp
using System.Collections.Generic;
using UnityEngine;

namespace MagicaCloth2
{
  /// <summary>
  /// Dress-up sample.
  /// </summary>
  public class RuntimeDressUpDemo : MonoBehaviour
  {
    /// <summary>
    /// Avatar to change clothes.
    /// </summary>
    public GameObject targetAvatar;

    /// <summary>
    /// Hair prefab with MagicaCloth set in advance.
    /// </summary>
    public GameObject hariEqupPrefab;

    /// <summary>
    /// Clothes prefab with MagicaCloth set in advance.
    /// </summary>
    public GameObject bodyEquipPrefab;

    //=========================================================================================
    /// <summary>
    /// Bones dictionary of avatars to dress up.
    /// </summary>
    Dictionary<string, Transform> targetAvatarBoneMap = new Dictionary<string, Transform>();

    /// <summary>
    /// Information class for canceling dress-up.
    /// </summary>
    class EquipInfo
    {
      public GameObject equipObject;
      public List<ColliderComponent> colliderList;

      public bool IsValid() => equipObject != null;
    }
    EquipInfo hairEquipInfo = new EquipInfo();
    EquipInfo bodyEquipInfo = new EquipInfo();

    //=========================================================================================
    private void Awake()
    {
      Init();
    }

    void Start()
    {
    }

    void Update()
    {
    }

    //=========================================================================================
    public void OnHairEquipButton()
    {
      if (hairEquipInfo.IsValid())
        Remove(hairEquipInfo);
      else
        Equip(hariEqupPrefab, hairEquipInfo);
    }

    public void OnBodyEquipButton()
    {
      if (bodyEquipInfo.IsValid())
        Remove(bodyEquipInfo);
      else
        Equip(bodyEquipPrefab, bodyEquipInfo);
    }

    //=========================================================================================
    /// <summary>
    /// Create an avatar bone dictionary in advance.
    /// </summary>
    void Init()
    {
      Debug.Assert(targetAvatar);

      // Create all bone maps for the target avatar
      foreach (Transform bone in targetAvatar.GetComponentsInChildren<Transform>())
      {
        if (targetAvatarBoneMap.ContainsKey(bone.name) == false)
        {
          targetAvatarBoneMap.Add(bone.name, bone);
        }
        else
        {
          Debug.Log($"Duplicate bone name :{bone.name}");
        }
      }
    }

    /// <summary>
    /// Equip clothes.
    /// </summary>
    /// <param name="equipPrefab"></param>
    /// <param name="einfo"></param>
    void Equip(GameObject equipPrefab, EquipInfo einfo)
    {
      Debug.Assert(equipPrefab);

      // Generate a prefab with cloth set up.
      var gobj = Instantiate(equipPrefab, targetAvatar.transform);

      // All cloth components included in the prefab.
      var clothList = new List<MagicaCloth>(gobj.GetComponentsInChildren<MagicaCloth>());

      // All collider components included in the prefab.
      var colliderList = new List<ColliderComponent>(gobj.GetComponentsInChildren<ColliderComponent>());

      // All renderers included in the prefab.
      var skinList = new List<SkinnedMeshRenderer>(gobj.GetComponentsInChildren<SkinnedMeshRenderer>());

      // First stop the automatic build that is executed with Start().
      // And just in case, it does some initialization called Awake().
      foreach (var cloth in clothList)
      {
        // Normally it is called with Awake(), but if the component is disabled, it will not be executed, so call it manually.
        // Ignored if already run with Awake().
        cloth.Initialize();

        // Turn off auto-build on Start().
        cloth.DisableAutoBuild();
      }

      // Swap the bones of the SkinnedMeshRenderer.
      // This process is a general dress-up process for SkinnedMeshRenderer.
      // Comment out this series of processes when performing this process with functions such as other assets.
      foreach (var sren in skinList)
      {
        var bones = sren.bones;
        Transform[] newBones = new Transform[bones.Length];

        for (int i = 0; i < bones.Length; ++i)
        {
          Transform bone = bones[i];
          if (!targetAvatarBoneMap.TryGetValue(bone.name, out newBones[i]))
          {
            // Is the bone the renderer itself?
            if (bone.name == sren.name)
            {
              newBones[i] = sren.transform;
            }
            else
            {
              // bone not found
              Debug.Log($"[SkinnedMeshRenderer({sren.name})] Unable to map bone [{bone.name}] to target skeleton.");
            }
          }
        }
        sren.bones = newBones;

        // root bone
        if (targetAvatarBoneMap.ContainsKey(sren.rootBone?.name))
        {
          sren.rootBone = targetAvatarBoneMap[sren.rootBone.name];
        }
      }

      // Here, replace the bones used by the MagicaCloth component.
      foreach (var cloth in clothList)
      {
        // Replaces a component's transform.
        cloth.ReplaceTransform(targetAvatarBoneMap);
      }

      // Move all colliders to the new avatar.
      foreach (var collider in colliderList)
      {
        Transform parent = collider.transform.parent;
        if (parent && targetAvatarBoneMap.ContainsKey(parent.name))
        {
          Transform newParent = targetAvatarBoneMap[parent.name];

          // After changing the parent, you need to write back the local posture and align it.
          var localPosition = collider.transform.localPosition;
          var localRotation = collider.transform.localRotation;
          collider.transform.SetParent(newParent);
          collider.transform.localPosition = localPosition;
          collider.transform.localRotation = localRotation;
        }
      }

      // Finally let's start building the cloth component.
      foreach (var cloth in clothList)
      {
        // I disabled the automatic build, so I build it manually.
        cloth.BuildAndRun();
      }

      // Record information for release.
      einfo.equipObject = gobj;
      einfo.colliderList = colliderList;
    }

    /// <summary>
    /// Removes equipped clothing.
    /// </summary>
    /// <param name="einfo"></param>
    void Remove(EquipInfo einfo)
    {
      Destroy(einfo.equipObject);
      foreach (var c in einfo.colliderList)
      {
        Destroy(c.gameObject);
      }

      einfo.equipObject = null;
      einfo.colliderList.Clear();
    }
  }
}

 

 

Transform 딕셔너리 생성

먼저, 스켈레톤 아바타(Skeletal Avatar)의 Transform 딕셔너리를 생성합니다.
이 딕셔너리는 이름(Name)을 키(Key)로 사용합니다.
이 딕셔너리는 본(Bone) 교체 작업을 수행하는 데 사용됩니다.

/// <summary>
/// Bones dictionary of avatars to dress up.
/// </summary>
Dictionary<string, Transform> targetAvatarBoneMap = new Dictionary<string, Transform>();

/// <summary>
/// Create an avatar bone dictionary in advance.
/// </summary>
void Init()
{
  Debug.Assert(targetAvatar);

  // Create all bone maps for the target avatar
  foreach (Transform bone in targetAvatar.GetComponentsInChildren<Transform>())
  {
    if (targetAvatarBoneMap.ContainsKey(bone.name) == false)
    {
      targetAvatarBoneMap.Add(bone.name, bone);
    }
    else
    {
      Debug.Log($"Duplicate bone name :{bone.name}");
    }
  }
}

 

 

MagicaCloth 초기화 및 자동 빌드 중지

먼저, MagicaCloth 컴포넌트의 초기화를 수동으로 호출하는 것이 중요합니다.
반드시 본(Bone) 교체 작업을 수행하기 전에 초기화해야 합니다.

다음으로, 자동 Cloth 빌드 작업을 중지(Pause)해야 합니다.

// Swap the bones of the SkinnedMeshRenderer.
// This process is a general dress-up process for SkinnedMeshRenderer.
// Comment out this series of processes when performing this process with functions such as other assets.
foreach (var sren in skinList)
{
  var bones = sren.bones;
  Transform[] newBones = new Transform[bones.Length];

  for (int i = 0; i < bones.Length; ++i)
  {
    Transform bone = bones[i];
    if (!targetAvatarBoneMap.TryGetValue(bone.name, out newBones[i]))
    {
      // Is the bone the renderer itself?
      if (bone.name == sren.name)
      {
        newBones[i] = sren.transform;
      }
      else
      {
        // bone not found
        Debug.Log($"[SkinnedMeshRenderer({sren.name})] Unable to map bone [{bone.name}] to target skeleton.");
      }
    }
  }
  sren.bones = newBones;

  // root bone
  if (targetAvatarBoneMap.ContainsKey(sren.rootBone?.name))
  {
    sren.rootBone = targetAvatarBoneMap[sren.rootBone.name];
  }
}

 

이 과정은 MagicaCloth와 직접적인 관련이 없습니다.
즉, 이 단계에서는 원하는 방식으로 처리할 수 있으며, 다른 의상 변경(Dress-up) 에셋을 사용할 수도 있습니다.

 

 

MagicaCloth 컴포넌트 이식(Porting)

MagicaCloth를 스켈레톤 아바타(Skeletal Avatar)에 이식(Implant) 합니다.
이 과정은 SkinnedMeshRenderer의 본 교체 방식과 동일하게 내부 본(Bone)을 교체하는 방식으로 수행됩니다.

본 교체는 미리 생성한 Transform 딕셔너리(Transform Dictionary)를 사용하여 진행됩니다.

// Here, replace the bones used by the MagicaCloth component.
foreach (var cloth in clothList)
{
  // Replaces a component's transform.
  cloth.ReplaceTransform(targetAvatarBoneMap);
}

 

 

콜라이더 이식(Transplantation of Collider)

MagicaCloth에서 콜라이더(Collider)를 사용하고 있다면,
이를 스켈레톤 아바타(Skeletal Avatar)로 함께 이식해야 합니다.

// Move all colliders to the new avatar.
foreach (var collider in colliderList)
{
  Transform parent = collider.transform.parent;
  if (parent && targetAvatarBoneMap.ContainsKey(parent.name))
  {
    Transform newParent = targetAvatarBoneMap[parent.name];

    // After changing the parent, you need to write back the local posture and align it.
    var localPosition = collider.transform.localPosition;
    var localRotation = collider.transform.localRotation;
    collider.transform.SetParent(newParent);
    collider.transform.localPosition = localPosition;
    collider.transform.localRotation = localRotation;
  }
}

 

 

 

MagicaCloth 실행 시작

마지막으로, MagicaCloth를 빌드(Build)하고 실행(Run)합니다.

// Finally let's start building the cloth component.
foreach (var cloth in clothList)
{
  // I disabled the automatic build, so I build it manually.
  cloth.BuildAndRun();
}

 

 

의상 제거(Release)

의상 변경이 더 이상 필요하지 않을 경우, 다음과 같이 해제합니다.
불필요한 모든 GameObject를 **Destroy()**하여 제거하면 됩니다.

/// <summary>
/// Removes equipped clothing.
/// </summary>
/// <param name="einfo"></param>
void Remove(EquipInfo einfo)
{
  Destroy(einfo.equipObject);
  foreach (var c in einfo.colliderList)
  {
    Destroy(c.gameObject);
  }

  einfo.equipObject = null;
  einfo.colliderList.Clear();
}

 

 

 

 

 

주의 사항(Notes)


이 예제 코드가 모든 가능성을 포함하는 것은 아닙니다.
따라서, 이 사례는 **참고용(Reference Only)**으로만 사용해야 합니다.

예를 들어, 의상 프리팹(Dress-up Prefab)에는 존재하지만, 스켈레톤 아바타(Skeletal Avatar)에는 존재하지 않는 GameObject
이 방식으로는 자동으로 이식되지 않습니다.
이러한 경우에는 추가적인 처리가 필요합니다.

끝.

'유니티 에셋 > Magica Cloth 2' 카테고리의 다른 글

성능(Performance)  (0) 2025.03.04
스케일 변경  (0) 2025.03.04
런타임 변경사항  (0) 2025.03.04
런타임 구성  (0) 2025.03.04
캐릭터 인스턴스화 (Character Instantiation)  (0) 2025.03.02

개요


이 섹션에서는 스케일(Scale) 변경 방법을 설명합니다.
편집 중 및 실행 중 스케일 변경에는 몇 가지 제한 사항이 있습니다.

 

 

 

 

 

제한 사항


먼저, MagicaCloth2에서는 균일한 스케일(Uniform Scaling)만 지원됩니다.
즉, X, Y, Z 축을 개별적으로 조정하는 것은 불가능하며,
전체적으로 2배, 3배, 0.5배 등 동일한 비율로 스케일 조정해야 합니다.

 

편집(Editing) 중 스케일 제한

  • 편집 시에는 양수의 균일한 스케일만 허용됩니다.
  • 음수(-) 스케일은 사용할 수 없습니다.
  • Inspector에서 잘못된 스케일이 적용된 경우 경고 메시지가 표시됩니다.

 

실행(Running) 중 스케일 제한

  • 실행 중에는 균일한 스케일 변경이 자유롭게 가능합니다.
  • v2.8.0부터 음수(-) 스케일도 지원됩니다.
    • 이를 이용하면 캐릭터 전체를 반전(Invert)할 수 있습니다.
    • 주로 2D 게임에서 유용하게 사용됩니다.
  • 단, 음수 스케일은 한 개의 축(X, Y, Z 중 하나)에서만 적용할 수 있습니다.

 

 

 

 

 

 

기본 스케일(Base Scale) 개념


런타임에서 Cloth 컴포넌트가 초기화될 때의 스케일 값을 **기본 스케일(Base Scale)**이라고 합니다.
MagicaCloth는 런타임 중 어떤 스케일 변경을 하더라도 기본 스케일 및 흔들림 동작(Swaying Behavior)이 유지되도록 설계되었습니다.

예를 들어, 다음과 같이 캐릭터 세 명이 있다고 가정합니다.

  • 가운데 캐릭터는 기본 스케일(1.0x)
  • 왼쪽 캐릭터는 1.5배 크기(1.5x)
  • 오른쪽 캐릭터는 0.7배 크기(0.7x)

 

이 경우, 크기가 다르더라도 흔들리는 방식은 모두 동일합니다.
즉, Cloth가 초기화될 때 설정된 기본 스케일이 매우 중요합니다.

 

 

 

 

 

스케일 변경 시 주의 사항


캐릭터의 스케일을 실시간으로 변경할 때 몇 가지 제한 사항이 있습니다.
하지만 v2.13.0 이후 버전에서는 이를 해결할 수 있으며, 사전 빌드(Pre-Building)를 사용하면 제한을 우회할 수 있습니다.
자세한 내용은 Character Instantiation(캐릭터 인스턴스화) 문서를 참고하세요.

 

다음 제한 사항은 v2.13.0 이전의 런타임 빌드에서만 적용됩니다.

 

 

초기화 전에 스케일을 변경할 수 없음

런타임 중 스케일 변경은 Cloth 컴포넌트가 초기화된 후에만 가능합니다.
초기화 전에 스케일을 변경하면 예기치 않은 동작이 발생할 수 있습니다.
Cloth 컴포넌트는 기본적으로 Start()에서 초기화되며, 이때 참조 스케일(Reference Scale)이 기록됩니다.
따라서, 특정 이유로 Start() 이전에 스케일을 변경하려면 수동으로 초기화를 호출해야 합니다.
이를 위해 Initialize() 메서드를 사용하세요.

 

 

 

씬에 미리 배치할 때의 주의 사항

예를 들어, Cloth 컴포넌트가 포함된 캐릭터 프리팹 A가 있다고 가정합니다.
이 프리팹 A를 처음부터 스케일을 2배로 설정하여 씬에 배치하면 Cloth가 올바르게 작동하지 않습니다.
이는 초기화 시점에서 기본 스케일(Base Scale)이 결정되기 때문입니다.
즉, 스케일이 2배로 설정된 상태에서 초기화되면, 이 스케일이 기본 스케일로 인식되어 Cloth 동작이 예상과 다를 수 있습니다.

이 문제를 방지하려면:

  1. 씬에 배치할 때 원래 크기(1.0x)로 배치합니다.
  2. Cloth 컴포넌트를 초기화한 후(Initialize() 실행 후), 스크립트를 통해 스케일을 변경합니다.

'유니티 에셋 > Magica Cloth 2' 카테고리의 다른 글

성능(Performance)  (0) 2025.03.04
의상 변경(Dress-up) 프로세스  (0) 2025.03.04
런타임 변경사항  (0) 2025.03.04
런타임 구성  (0) 2025.03.04
캐릭터 인스턴스화 (Character Instantiation)  (0) 2025.03.02

개요


MagicaCloth2는 런타임 중 파라미터 변경을 지원합니다.
변경 방법은 두 가지가 있습니다.

  1. 스크립트를 사용하여 변경
  2. 애니메이션 키(Animation Key)를 사용하여 변경

그러나 애니메이션을 통해 제어할 수 있는 파라미터는 제한적이며,
스크립트를 사용하면 모든 파라미터에 접근하여 변경할 수 있습니다.

 

 

 

 

스크립트를 통한 변경 절차


다음 단계를 따라 MagicaCloth의 파라미터를 변경할 수 있습니다.

  1. MagicaCloth 구성 요소의 SerializeData 클래스 가져오기
  2. SerializeData의 내용을 변경하기
  3. MagicaCloth 구성 요소의 SetParameterChange() 호출하기

MagicaCloth의 모든 수정 가능한 파라미터는 SerializeData 클래스에 포함되어 있으며,
이 클래스를 직접 수정하여 설정을 변경할 수 있습니다.

하지만 모든 파라미터를 변경할 수 있는 것은 아닙니다.
변경 가능 여부는 소스 코드의 주석(Comment)을 통해 확인할 수 있습니다.

/// [OK] Runtime changes.
/// [NG] Export/Import with Presets

 

마지막으로, SetParameterChange()를 호출하여 시스템에 변경 사항을 알립니다.
사용된 API에 대한 자세한 내용은 ScriptingAPI 페이지를 참조하세요.

 

 

 

파라미터 변경 예제 (1)

using MagicaCloth2;
using UnityEngine;

public class RuntimeParameterTest : MonoBehaviour
{
  public MagicaCloth cloth;
  public ColliderComponent col;

  bool sw = false;

  void Start()
  {

  }

  void Update()
  {
    if (cloth == null || col == null)
      return;

    // Check if MagicaCloth is running
    if (cloth.IsValid() == false)
      return;

    if (Time.frameCount % 100 == 0)
    {
      sw = !sw;
      UpdateParameter();
    }
  }

  /// <summary>
  /// Parameter change.
  /// </summary>
  void UpdateParameter()
  {
    // It manages all parameters that cloth.SerializeData can change at runtime.
    var sdata = cloth.SerializeData;
    if (sw)
    {
      // add collider.
      sdata.colliderCollisionConstraint.colliderList.Add(col);

      // gravity on
      sdata.gravity = 5.0f;
    }
    else
    {
      // remove collider
      sdata.colliderCollisionConstraint.colliderList.Remove(col);

      // gravity off
      sdata.gravity = 0.0f;
    }

    // change notification
    cloth.SetParameterChange();
  }
}

 

이 예제에서는 100프레임마다 콜라이더를 추가/제거하고 중력을 변경합니다.
이 방법을 사용하면 모든 파라미터 변경이 가능합니다.
다른 파라미터도 이 방식으로 변경할 수 있습니다.

 

 

 

애니메이션을 통한 변경 절차


일부 파라미터는 애니메이션 키(Animation Key)로 노출되어 있습니다.
애니메이션에 이러한 키를 추가하고 조작하면 파라미터를 변경할 수 있습니다.

 

애니메이션 키를 사용하여 변경하는 경우, 스크립트에서처럼 SetParameterChange()를 별도로 호출할 필요가 없습니다.

'유니티 에셋 > Magica Cloth 2' 카테고리의 다른 글

의상 변경(Dress-up) 프로세스  (0) 2025.03.04
스케일 변경  (0) 2025.03.04
런타임 구성  (0) 2025.03.04
캐릭터 인스턴스화 (Character Instantiation)  (0) 2025.03.02
바람 셋팅(Wind Setting)  (0) 2025.03.02

개요


MagicaCloth2는 런타임 구성을 완전히 지원합니다.
여기서는 스크립트를 통해 런타임 중 MagicaCloth 구성 요소를 생성하는 방법을 설명합니다.
이 문서는 Unity에서 C# 프로그래밍에 익숙한 독자를 대상으로 합니다.

 

 

 

 

샘플 씬


MagicaCloth2의 런타임 구성을 위한 샘플 씬이 제공됩니다.
아래 폴더에서 사용할 렌더 파이프라인에 맞는 씬을 선택하여 테스트할 수 있습니다.

 

이 샘플 씬에는 RuntimeBuildDemo라는 오브젝트에 RuntimeBuildDemo.cs라는 테스트 코드가 포함되어 있습니다.
이 페이지에서는 해당 테스트 코드의 내용을 추출하여 설명합니다.

씬이 분리된 이유는 단순히 렌더링 머티리얼을 전환하기 위함이며, 내부에 포함된 샘플 코드는 동일합니다.

 

 

 

 

구성 절차


다음 단계에 따라 MagicaCloth를 빌드할 수 있습니다.

  1. MagicaCloth 구성 요소 생성
  2. 파라미터 설정
  3. 옷감(Cloth) 데이터 생성 및 실행 시작

예제와 함께 각 단계를 설명하겠습니다.
또한 구성 시 주의해야 할 몇 가지 사항이 있습니다.

 

정점 속성 지정

MagicaCloth에서는 어떤 정점(vertex)이 움직이고 어떤 정점이 움직이지 않는지를 명확하게 지정해야 합니다.
이를 정점 속성(Vertex Attributes)이라고 합니다.

에디터에서는 정점 페인트 기능을 사용하여 수동으로 속성을 설정합니다.
그러나 이 방법은 런타임 구성에서는 사용할 수 없습니다.

따라서 런타임에서 빌드할 때는 옷감 유형에 따라 여러 가지 방법으로 정점 속성을 지정해야 합니다.
이러한 방법들을 예제를 통해 설명하겠습니다.

 

런타임 지연

MagicaCloth2는 런타임 중 옷감 데이터를 생성합니다.
이 과정은 별도의 스레드에서 수행되므로 메인 스레드에 미치는 영향은 적습니다.
그러나 데이터 구축에는 몇 프레임의 시간이 소요됩니다.

즉, 구성 요소를 빌드한 후 실제로 옷감 시뮬레이션이 시작되기까지 몇 프레임의 지연이 발생합니다.

 

 

 

 

예제


여기서는 런타임 구성 예제를 소개합니다.
이 예제는 샘플 씬 RuntimeBuildDemo.cs에서 발췌된 것이므로, 데모 씬도 함께 참고하는 것이 좋습니다.
사용된 API에 대한 자세한 내용은 ScriptingAPI 페이지를 확인하세요.

코드에서 "character"는 캐릭터의 GameObject를 의미합니다.
또한, "gameObjectContainer"는 이름을 통해 특정 GameObject를 가져올 수 있도록 하는 간단한 클래스입니다.

 

 

BoneCloth 구성 예제 (1)

/// <summary>
/// BoneCloth construction example (1).
/// Set all parameters from a script.
/// </summary>
void SetupHairTail_BoneCloth()
{
  if (character == null)
    return;

  var obj = new GameObject("HairTail_BoneCloth");
  obj.transform.SetParent(character.transform, false);

  // add Magica Cloth
  var cloth = obj.AddComponent<MagicaCloth>();
  var sdata = cloth.SerializeData;

  // bone cloth
  sdata.clothType = ClothProcess.ClothType.BoneCloth;
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HairTail_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HairTail_00_B").transform);

  // setup parameters
  sdata.gravity = 3.0f;
  sdata.damping.SetValue(0.05f);
  sdata.angleRestorationConstraint.stiffness.SetValue(0.15f, 1.0f, 0.15f, true);
  sdata.angleRestorationConstraint.velocityAttenuation = 0.6f;
  sdata.tetherConstraint.distanceCompression = 0.5f;
  sdata.inertiaConstraint.particleSpeedLimit.SetValue(true, 3.0f);
  sdata.colliderCollisionConstraint.mode = ColliderCollisionConstraint.Mode.None;

  // start build
  cloth.BuildAndRun();
}

 

이 예제에서는 BoneCloth를 생성하고 모든 파라미터를 스크립트에서 직접 설정합니다.
가장 중요한 부분은 SerializeData 클래스로, 이 클래스에 포함된 속성을 조정하여 Cloth의 동작을 정의합니다.

 

이 예제에서는 별도의 정점 속성(Vertex Attributes)을 지정하지 않았습니다.
하지만 BoneCloth 및 BoneSpring에서는 지정된 루트 본(root bone)의 Transform이 자동으로 고정(Fixed) 속성으로 설정되며,
나머지는 변형(Translation) 속성으로 설정되므로 생략할 수 있습니다.

마지막으로 BuildAndRun()을 호출하여 Cloth 데이터를 별도의 스레드에서 생성하고 시뮬레이션을 자동 시작합니다.

 

 

BoneCloth 구성 예제 (2)

/// <summary>
/// BoneCloth construction example (2).
/// Copy parameters from an existing component.
/// </summary>
void SetupFrontHair_BoneCloth()
{
  if (character == null || frontHairSource == null)
    return;

  var obj = new GameObject("HairFront_BoneCloth");
  obj.transform.SetParent(character.transform, false);

  // add Magica Cloth
  var cloth = obj.AddComponent<MagicaCloth>();
  var sdata = cloth.SerializeData;

  // bone cloth
  sdata.clothType = ClothProcess.ClothType.BoneCloth;
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HairFront_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HairSide2_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HairSide_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HairFront_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HairSide2_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HairSide_00_B").transform);

  // Normal direction setting for backstop
  sdata.normalAlignmentSetting.alignmentMode = NormalAlignmentSettings.AlignmentMode.Transform;
  sdata.normalAlignmentSetting.adjustmentTransform = gameObjectContainer.GetGameObject("HeadCenter").transform;

  // setup parameters
  // Copy from source settings
  sdata.Import(frontHairSource, false);

  // start build
  cloth.BuildAndRun();
}

 

이 예제에서는 외부의 다른 Cloth 구성 요소인 "frontHairSource"에서 파라미터를 가져와 적용합니다.
이를 통해 파라미터 설정을 보다 간편하게 할 수 있습니다.

 

그러나 일부 파라미터는 가져올 수 있고, 일부는 가져올 수 없습니다.
이와 관련된 사항은 소스 코드의 주석으로 문서화되어 있습니다.

/// [OK] Runtime changes.
/// [NG] Export/Import with Presets

 

이 예제에서는 normalAlignmentSetting이 가져올 수 없는 항목이기 때문에 수동으로 설정하고 있습니다.

 

 

BoneCloth 구성 예제 (3)

/// <summary>
/// BoneCloth construction example (3).
/// Load parameters from saved presets.
/// </summary>
void SetupRibbon_BoneCloth()
{
  if (character == null || string.IsNullOrEmpty(ribbonPresetName))
    return;

  var obj = new GameObject("Ribbon_BoneCloth");
  obj.transform.SetParent(character.transform, false);

  // add Magica Cloth
  var cloth = obj.AddComponent<MagicaCloth>();
  var sdata = cloth.SerializeData;

  // bone cloth
  sdata.clothType = ClothProcess.ClothType.BoneCloth;
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HeadRibbon_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HeadRibbon_00_B").transform);

  // setup parameters
  // Load presets from the Resource folder.
  // Since presets are in TextAssets format, they can also be used as asset bundles.
  var presetText = Resources.Load<TextAsset>(ribbonPresetName);
  sdata.ImportJson(presetText.text);

  // start build
  cloth.BuildAndRun();
}

 

이 예제에서는 **프리셋 파일(Json)**로부터 파라미터를 가져옵니다.
파라미터는 Json 형식으로 외부로 내보낼 수 있으며,
이는 단순한 텍스트 파일이므로 Resources 폴더 또는 AssetBundle에 배치하여 런타임 중 로드할 수 있습니다.

 

 

BoneCloth의 정점 속성(Vertex Attribute) 설정

BoneCloth를 구성할 때, 루트 본(root bone)은 자동으로 고정(Fixed) 속성으로 설정되며,
그 외의 본들은 자동으로 이동(Move) 속성으로 설정됩니다.
따라서 기본적으로 별도의 설정이 필요하지 않습니다.

그러나 ClothSerializeData2의 boneAttributeDict를 사용하여 수동으로 설정할 수도 있습니다.
boneAttributeDict는 Transform과 속성(Vertex Attribute) 쌍으로 이루어진 딕셔너리(Dictionary)이며, 다음과 같이 지정할 수 있습니다.

// Transform Attribute
var sdata2 = cloth.GetSerializeData2();
sdata2.boneAttributeDict.Add(bone1, VertexAttribute.Fixed);
sdata2.boneAttributeDict.Add(bone2, VertexAttribute.Invalid);
sdata2.boneAttributeDict.Add(bone3, VertexAttribute.Move);

 

 

MeshCloth 구성 예제 (1)

/// <summary>
/// MeshCloth construction example (1).
/// Reads vertex attributes from a paintmap.
/// </summary>
void SetupSkirt_MeshCloth()
{
  if (character == null || skirtPaintMap == null)
    return;

  // skirt renderer
  var sobj = gameObjectContainer.GetGameObject(skirtName);
  if (sobj == null)
    return;
  Renderer skirtRenderer = sobj.GetComponent<Renderer>();
  if (skirtRenderer == null)
    return;

  // add Magica Cloth
  var obj = new GameObject("Skirt_MeshCloth");
  obj.transform.SetParent(character.transform, false);
  var cloth = obj.AddComponent<MagicaCloth>();
  var sdata = cloth.SerializeData;

  // mesh cloth
  sdata.clothType = ClothProcess.ClothType.MeshCloth;
  sdata.sourceRenderers.Add(skirtRenderer);

  // reduction settings
  sdata.reductionSetting.simpleDistance = 0.0212f;
  sdata.reductionSetting.shapeDistance = 0.0244f;

  // paint map settings
  // *** Paintmaps must have Read/Write attributes enabled! ***
  sdata.paintMode = ClothSerializeData.PaintMode.Texture_Fixed_Move;
  sdata.paintMaps.Add(skirtPaintMap);

  // setup parameters
  sdata.gravity = 1.0f;
  sdata.damping.SetValue(0.03f);
  sdata.angleRestorationConstraint.stiffness.SetValue(0.05f, 1.0f, 0.5f, true);
  sdata.angleRestorationConstraint.velocityAttenuation = 0.5f;
  sdata.angleLimitConstraint.useAngleLimit = true;
  sdata.angleLimitConstraint.limitAngle.SetValue(45.0f, 0.0f, 1.0f, true);
  sdata.distanceConstraint.stiffness.SetValue(0.5f, 1.0f, 0.5f, true);
  sdata.tetherConstraint.distanceCompression = 0.9f;
  sdata.inertiaConstraint.depthInertia = 0.7f;
  sdata.inertiaConstraint.movementSpeedLimit.SetValue(true, 3.0f);
  sdata.inertiaConstraint.particleSpeedLimit.SetValue(true, 3.0f);
  sdata.colliderCollisionConstraint.mode = ColliderCollisionConstraint.Mode.Point;

  // setup collider
  var lobj = new GameObject("CapsuleCollider_L");
  lobj.transform.SetParent(gameObjectContainer.GetGameObject("Character1_LeftUpLeg").transform);
  lobj.transform.localPosition = new Vector3(0.0049f, 0.0f, -0.0832f);
  lobj.transform.localEulerAngles = new Vector3(0.23f, 16.376f, -0.028f);
  var colliderL = lobj.AddComponent<MagicaCapsuleCollider>();
  colliderL.direction = MagicaCapsuleCollider.Direction.Z;
  colliderL.SetSize(0.082f, 0.094f, 0.3f);

  var robj = new GameObject("CapsuleCollider_R");
  robj.transform.SetParent(gameObjectContainer.GetGameObject("Character1_RightUpLeg").transform);
  robj.transform.localPosition = new Vector3(-0.0049f, 0.0f, -0.0832f);
  robj.transform.localEulerAngles = new Vector3(0.23f, -16.376f, -0.028f);
  var colliderR = robj.AddComponent<MagicaCapsuleCollider>();
  colliderR.direction = MagicaCapsuleCollider.Direction.Z;
  colliderR.SetSize(0.082f, 0.094f, 0.3f);

  sdata.colliderCollisionConstraint.colliderList.Add(colliderL);
  sdata.colliderCollisionConstraint.colliderList.Add(colliderR);

  // start build
  cloth.BuildAndRun();
}

 

이 예제에서는 MeshCloth를 구성하고, 정점 속성을 페인트 맵(Paint Map)으로 지정합니다. 페인트 맵은 설정한 렌더러와 동기화되어야 한다는 점에 유의하세요. 즉, 렌더러의 개수와 페인트 맵의 개수는 동일해야 합니다.

 

또한 두 개의 캡슐 콜라이더(Capsule Collider)를 생성하여 콜라이더 리스트에 추가했습니다. 콜라이더 생성은 Unity에서 일반적인 컴포넌트를 생성하는 방식과 동일합니다.

 

그 외의 과정은 BoneCloth 예제와 동일합니다.

 

 

MeshCloth의 정점 속성 직접 지정

BoneCloth와 달리 MeshCloth에서는 정점 속성을 명확하게 지정해야 합니다. 이를 생략할 수는 없습니다.
MeshCloth의 정점 속성을 지정하는 방법은 두 가지가 있습니다. 첫 번째는 MeshCloth 구성 예제 (1)에서 설명한 페인트 맵(Paint Map)을 사용하는 방법, 두 번째는 정점 배열(Vertex Array)과 동일한 개수의 속성 배열을 준비하여 지정하는 방법입니다.

페인트 맵 대신 정점 속성 배열을 사용하려면 ClothSerializeData2의 vertexAttributeList를 사용하세요.
먼저, vertexAttributeList의 요소 개수는 MeshCloth에 등록된 렌더러(Renderer) 개수와 일치해야 합니다.
그리고 각 요소는 해당 렌더러의 메쉬 정점(Vertex) 개수와 동일해야 합니다.

 

다음은 페인트 맵 대신 vertexAttributeList를 사용하는 예제입니다.
이 예제는 하나의 렌더러가 있는 경우를 가정합니다.

// add vertex attribute
var sdata2 = cloth.GetSerializeData2();
var attributes = new VertexAttribute[VertexCount];

// Initialize with movement attributes
for(int i = 0; i < VertexCount; i++)
  attributes[i] = VertexAttribute.Move;

// Making a specific vertex a fixed attribute
attributes[0] = VertexAttribute.Fixed;
attributes[7] = VertexAttribute.Fixed;
attributes[21] = VertexAttribute.Fixed;

// Registering vertex attributes
sdata2.vertexAttributeList.Add(attributes);

개요 (Overview)


MagicaCloth가 포함된 캐릭터를 런타임에 인스턴스화하면, MagicaCloth는 자동으로 초기화되며 작동을 시작합니다.

따라서 일반적으로 별도의 작업이 필요하지 않습니다.

그러나 초기화 과정에서 특정 상황에 따라 캐릭터의 자세 등에 제한이 가해질 수 있으며, 이는 MagicaCloth의 버전과 빌드 방식에 따라 다음 세 가지 유형으로 분류됩니다.

  • v2.13.0 이전 버전의 런타임 빌드
  • v2.13.0 이후 버전의 런타임 빌드
  • 사전 빌드(Pre-built)

이제 각 방식의 차이점과 인스턴스화 내부 프로세스를 자세히 설명하겠습니다.

 

 

 

 

 

런타임 빌드와 사전 빌드 (Run-time and Pre-build)


MagicaCloth는 두 가지 방식으로 작동합니다: **런타임 빌드(Runtime Construction)**와 사전 빌드(Pre-construction).
일반적으로 런타임 빌드가 많이 사용됩니다.

 

 

런타임 빌드 (Runtime Construction)

런타임 빌드 방식에서는 Cloth 데이터가 런타임에 즉석에서 생성됩니다.
이 데이터 생성에는 평균적으로 10ms~30ms가 소요되며,
이 작업은 백그라운드에서 실행되므로 게임 진행에는 영향을 미치지 않습니다.
그러나 이 과정으로 인해 인스턴스화 후 Cloth 시뮬레이션이 실제로 시작되기까지 몇 프레임의 지연이 발생합니다.

 

 

사전 빌드 (Pre-built)

사전 빌드 방식에서는 Cloth 데이터를 편집 단계에서 미리 생성하여 에셋(Asset)으로 저장합니다.
이 방식은 런타임에 즉시 시뮬레이션을 시작할 수 있다는 장점이 있습니다.
그러나 에셋 데이터의 증가Cloth 데이터를 수동으로 빌드해야 하는 추가 작업이 필요하다는 단점이 있습니다.
자세한 내용은 사전 빌드(Pre-building) 문서를 참고하세요.

 

 

 

 

 

빌드 방식별 장단점 (Advantages and Disadvantages of Different Build Methods)


본격적인 설명에 앞서, 각 빌드 방식의 장점과 단점을 간략하게 정리합니다.
특히, v2.13.0 이후 런타임 빌드는 성능이 크게 개선되어 이전의 단점이 많이 줄어들었습니다.

  장점(Pros) 단점(Cons)
v2.13.0 이전 런타임 빌드 MagicaCloth 버전 변경, 메쉬 또는 본(Bone) 구조 변경에도 영향 없음.
파라미터가 변경되어도 특별한 작업 없이 적용 가능.
초기화 부하가 높음.
데이터 빌드 시간이 필요하여 시뮬레이션 시작 전 지연 발생.
캐릭터가 편집 당시의 자세 및 스케일로 초기화되어야 함.
v2.13.0 이후 런타임 빌드 MagicaCloth 버전 변경, 메쉬 또는 본 구조 변경에도 영향 없음.
파라미터 변경 시 별도의 작업이 필요 없음.
초기화 시 캐릭터의 자세와 스케일이 자유로움.
초기화 부하가 낮음.
데이터 빌드 시간이 필요하여 시뮬레이션 시작 전 지연 발생.
사전 빌드 (Pre-built) 초기화 비용이 낮고, 시뮬레이션이 즉시 시작됨.
초기화 시 캐릭터의 자세와 스케일이 자유로움.
Cloth 데이터를 생성하는 추가 작업 필요.
MagicaCloth 버전, 메쉬, 본 구조가 변경되면 Cloth 데이터를 다시 생성해야 함. Cloth 파라미터가 변경될 때마다 데이터를 다시 빌드 해야 함.

 

 

 

 

 

v2.13.0 이전 런타임 빌드 (Runtime Build for v2.13.0 and Earlier)


v2.13.0 이전 버전에서는 캐릭터가 편집 당시와 동일한 자세로 초기화되어야 합니다.
일반적으로 이는 A 포즈 또는 T 포즈입니다.
초기화 시 편집할 때와 다른 자세가 적용되면 버텍스 속성 데이터가 어긋나 시뮬레이션이 정상적으로 작동하지 않을 수 있습니다.

특히 다음 항목은 편집할 때와 동일해야 합니다.

  • 캐릭터 자세 (Character Posture, Animation Pose)
  • 캐릭터 스케일 (Character Scale)

좌표(Position) 및 회전(Rotation)은 변경해도 문제가 없습니다.
만약 인스턴스화 직후 캐릭터의 자세를 변경하려면, 변경 전에 수동으로 초기화를 호출해야 합니다.
이에 대한 방법은 문서의 마지막에서 설명됩니다.

 

 

 

 

 

v2.13.0 이후 런타임 빌드 (Runtime Build for v2.13.0 and Later)


v2.13.0부터는 편집 시 캐릭터의 자세가 자동으로 저장됩니다.
이 데이터를 **초기화 데이터(Initialization Data)**라고 합니다.

이전에는 편집할 때와 초기화할 때 자세가 동일해야 했지만,
v2.13.0 이후 버전에서는 이 제한이 제거되었습니다.
즉, 초기화 전에 캐릭터의 자세나 스케일을 변경하는 것이 가능합니다.

 

 

초기화 데이터 확인 (Check the Initialization Data)

컴포넌트가 초기화 데이터를 포함하고 있는지 여부는 Inspector의 Info 창에서 확인할 수 있습니다.

[Init Data]가 True이면, 해당 컴포넌트가 초기화 데이터를 보유하고 있음을 의미합니다.

 

Inspector의 Info 창에서는 런타임에서 초기화 데이터가 사용되었는지도 표시됩니다.

"Success"라고 표시되면, 빌드가 완료될 때 초기화 데이터가 올바르게 사용된 것입니다.

숫자와 메시지가 표시되면, 오류가 발생한 것이므로 초기화 데이터를 다시 생성해야 합니다.

 

 

 

초기화 데이터 생성 (Creating Initialization Data)

초기화 데이터는 자동으로 생성되므로 기본적으로 별도의 작업이 필요하지 않습니다.
그러나 어떤 이유로 초기화 데이터가 올바르게 생성되지 않은 경우, 다음 방법을 사용하여 수동으로 생성할 수 있습니다.

Vertex Paint를 한 번 실행한 후 즉시 종료하면 초기화 데이터가 생성됩니다.

 

또한, 컴포넌트 메뉴에서 "Rebuild InitData"를 선택하면 초기화 데이터를 강제로 다시 빌드할 수 있습니다.

 

 

 

v2.13.0 이전 컴포넌트에서 초기화 데이터를 활성화하는 방법

v2.13.0 이전의 컴포넌트에는 초기화 데이터가 포함되어 있지 않지만, 이를 쉽게 호환 가능하게 만들 수 있습니다.
아래 방법 중 하나를 수행하면 됩니다.

  • 캐릭터가 프리팹(Prefab)으로 구성된 경우, 한 번 Prefab 모드로 들어가기
  • 캐릭터를 씬(Scene)에 한 번 배치하기

위 방법을 수행하면 새로운 초기화 데이터가 자동으로 생성됩니다.

 

 

 

 

초기화 위치 변경 (Change the Initialization Location)


초기화는 기본적으로 MonoBehaviour의 Start()에서 수행되지만, 이를 Awake()에서 실행하도록 변경할 수 있습니다.
이 설정을 변경하려면 MagicaSettings 컴포넌트를 사용하면 됩니다.

 

자세한 내용은 MagicaSettings 컴포넌트 설명서를 참고하세요.

또한, 스크립팅 API를 통해 동일한 변경을 직접 적용할 수도 있습니다.
이를 위해 SetInitializationLocation() 메서드를 사용합니다.

MagicaManager.SetInitializationLocation(MagicaManager.InitializationLocation.Awake);

 

 

 

 

 

수동 초기화 (Manual Initialization)


초기화를 수동으로 호출할 수도 있습니다.
이를 위해 Initialize() 메서드를 사용합니다.

특히 v2.13.0 이전 버전에서는 캐릭터의 자세(Posture)나 스케일(Scale)을 변경하기 전에 반드시 Initialize()를 호출해야 합니다.

다음은 캐릭터를 인스턴스화한 후, 수동으로 초기화를 호출하고 스케일을 변경하는 예제 코드입니다.

var obj = Instantiate(prefab);

// MagicaCloth Initialize
var clothList = obj.GetComponentsInChildren<MagicaCloth>();
foreach (var cloth in clothList)
  cloth.Initialize();

// Scale
obj.transform.localScale = new Vector3(2, 2, 2);

이렇게 하면 초기화가 완료된 후 캐릭터의 속성을 안전하게 변경할 수 있습니다.

'유니티 에셋 > Magica Cloth 2' 카테고리의 다른 글

런타임 변경사항  (0) 2025.03.04
런타임 구성  (0) 2025.03.04
바람 셋팅(Wind Setting)  (0) 2025.03.02
BoneCloth 고급 설정 (BoneCloth Advanced Settings)  (0) 2025.03.02
자가 충돌 설정 (Self-Collision Setting)  (1) 2025.03.02

 

 

 

 

개요 (Overview)


바람 생성 기능을 사용하면 Cloth 시뮬레이션에 바람을 추가하여 보다 현실적인 표현이 가능합니다.

이는 MagicaWindZone 컴포넌트를 씬(Scene)에 배치하여 바람이 생성될 영역을 지정함으로써 제어됩니다.

또한 [Wind] 패널에서 설정을 조정하여 MagicaCloth 컴포넌트에 미치는 바람의 영향을 변경할 수도 있습니다.

 

 

 

 

 

샘플 씬 (Sample Scene)


바람 기능을 위한 샘플 씬이 제공되며, 해당 샘플은 다음 폴더에서 확인할 수 있으며, 자신의 렌더 파이프라인(Render Pipeline)에 맞는 씬을 선택하여 테스트하면 됩니다.

 

샘플 씬의 WindDemo에는 WindDemo.cs가 첨부되어 있으며, 이는 바람 기능을 테스트하는 코드입니다.

씬이 각각의 렌더 파이프라인에 따라 분리되어 있는 이유는 렌더링 머티리얼(Material)을 변경하기 위함이며, 포함된 샘플 코드는 모두 동일합니다.

 

 

윈드 존 설정 (Wind Zone Settings)


바람을 생성하려면 씬에서 바람의 범위를 지정해야 하며, 이를 위해 MagicaWindZone 컴포넌트를 사용합니다.
주의: Unity의 기본 WindZone 컴포넌트는 MagicaCloth에 영향을 미치지 않으므로, 반드시 전용 MagicaWindZone 컴포넌트를 추가해야 합니다.

MagicaWindZone 컴포넌트는 오른쪽 클릭 메뉴를 통해 추가할 수 있습니다.

 

 

바람 영역(Wind Zone)은 [Mode] 설정에 따라 여러 유형으로 나뉘며,

 

각 모드에 따라 설정 항목이 다르므로 이를 설명합니다.

 

 

글로벌 방향 (Global Direction)

 

 

씬 전체에 영향을 미치는 방향성 바람으로, 범위 제한이 없습니다.

Main 바람 속도 (m/s)
Turbulence 바람 난류(Turbulence) 비율
Direction Angle X 바람 방향 X축 각도 (Deg)
Direction Angle Y 바람 방향 Y축 각도 (Deg)
Is Addition 추가 방식(Addition Style) 플래그

 

 

 

구형 방향 (Sphere Direction)

 

 

구형 영역 내에서만 영향을 미치는 방향성 바람입니다.

Radius 바람이 영향을 미치는 영역 반경
Main 바람 속도 (m/s)
Turbulence 바람 난류(Turbulence) 비율
Direction Angle X 바람 방향 X축 각도 (Deg)
Direction Angle Y 바람 방향 Y축 각도 (Deg)
Is Addition 추가 방식(Addition Style) 플래그

 

 

박스 방향 (Box Direction)

 

 

 

박스 형태의 영역 내에서만 영향을 미치는 방향성 바람입니다.

Box Size 바람이 영향을 미치는 영역의 XYZ 크기
Main 바람 속도 (m/s)
Turbulence 바람 난류(Turbulence) 비율
Direction Angle X 바람 방향 X축 각도 (Deg)
Direction Angle Y 바람 방향 Y축 각도 (Deg)
Is Addition 추가 방식(Addition Style) 플래그

 

 

 

구형 방사형 (Sphere Radial)

 

 

구형 영역 내에서 중심에서 바깥쪽으로 퍼지는 방사형 바람입니다.

방사형 바람은 중심에서 바깥 방향으로 발생하므로 별도의 방향 지정이 필요 없습니다.

[Attenuation] 설정을 통해 중심에서 바깥쪽으로의 바람 강도 감쇠 곡선을 지정할 수 있습니다.

Radius 바람이 영향을 미치는 영역 반경
Main 바람 속도 (m/s)
Turbulence 바람 난류(Turbulence) 비율
Attenuation 바람의 감쇠(Attenuation) 곡선
Is Addition 추가 방식(Addition Style) 플래그

 

 

바람 존 우선순위 (Wind Zone Priority)

여러 개의 바람 존(Wind Zone)이 하나의 Cloth 컴포넌트에 겹쳐질 경우, 볼륨(Volume)이 가장 작은 바람 존만 활성화됩니다.
그러나 예외적으로 Global Direction 존은 볼륨 ∞(무한)으로 간주되어 가장 낮은 우선순위를 가집니다.
단, 이후 설명할 [IsAddition] 플래그가 활성화된 경우, 이 우선순위 규칙에서 제외됩니다.

 

 

IsAddition 플래그 (IsAddition Flag)

[IsAddition] 플래그를 활성화하면, 해당 바람 존이 '추가적(Additive)'으로 처리됩니다.

추가적 바람 존은 기존의 바람 존 우선순위 처리에서 제외되며, 동시에 여러 개의 바람이 Cloth에 영향을 줄 수 있습니다.

이 경우 바람이 하나에서 다른 것으로 전환되는 것이 아니라, 여러 개의 바람이 합산되어 적용됩니다.

단, 하나의 Cloth 컴포넌트에는 최대 3개의 추가적 바람(Additional Wind)만 적용할 수 있습니다.

 

 

 

 

MagicaCloth 설정 (MagicaCloth Settings)

각 Cloth 컴포넌트마다 바람 효과를 개별적으로 조정할 수 있으며, 이를 위해 [Wind] 패널을 사용합니다.

 

 

Influence 전체적인 바람 영향력 비율을 조정합니다. 값을 낮추면 바람의 영향을 약화시킬 수 있으며, 1.0(100%) 이상의 값도 설정 가능합니다.
Frequency 흔들림의 주기를 조정합니다. 값이 클수록 흔들림의 속도가 증가하며, 1.0(100%) 이상의 값도 설정 가능합니다.
Turbulence 바람의 난류(Turbulence) 비율을 조정합니다. 값이 클수록 흔들림이 더욱 불규칙해지며, 1.0(100%) 이상의 값도 설정 가능합니다.
Noise Blend 사인(Sine) 파형과 노이즈(Noise) 파형의 혼합 비율을 조정합니다. 두 파형은 이후 설명됩니다.
Synchronization 동기화 비율을 조정합니다. 1.0으로 설정하면 Cloth의 모든 부분이 동일한 방식으로 움직이며, 0.0으로 설정하면 각각 독립적으로 움직입니다.
Depth Weight 깊이 효과를 조정합니다. 값을 높이면 시작점(예: 허리 근처)의 버텍스가 바람의 영향을 덜 받도록 하여 스커트, 앞머리 등의 안정성을 향상할 수 있습니다.
Moving Wind 움직임에 따른 바람 효과를 자동으로 생성합니다. 값이 클수록 움직임에 따른 바람 효과가 더욱 강해집니다.

 

 

 

사인파(Sine Wave)와 노이즈파(Noise Wave)

바람의 변동을 표현하기 위해 사인(Sine) 파형노이즈(Noise) 파형 두 가지를 사용합니다.

사인(Sine) 파형은 일정한 주기로 바람이 변동하므로 일본 애니메이션과 같은 장면 연출에 적합합니다. 하지만 랜덤성이 부족한 단점이 있습니다.

 

노이즈(Noise) 파형은 불규칙한 주기를 가지며 보다 자연스럽고 현실적인 움직임을 생성할 수 있습니다.

 

이 두 가지 파형은 [Noise Blend] 값을 조정하여 혼합할 수 있습니다.

 

 

 

움직이는 바람 (Moving Wind)

[Moving Wind] 값을 증가시키면 캐릭터의 움직임에 따라 자동으로 바람이 생성됩니다.

 

  • 움직이는 바람 효과는 관성(Inertia) 매개변수의 영향을 받습니다.
  • 즉, 관성(Inertia) 설정을 통해 이동 영향이 감소하면, 움직이는 바람 효과도 함께 줄어듭니다.

 

 

튜닝 팁 (Tuning Tips)


  • 바람 존(Wind Zone)의 바람 속도는 최대 30m/s까지 설정 가능하지만, 너무 강한 바람은 충돌 감지 시 튜널링(Tunneling) 문제를 유발할 수 있으므로 주의해야 합니다.
  • 특정 Cloth 컴포넌트에서 바람 효과를 줄이고 싶다면 [Influence] 값을 조정하세요.
  • 스커트가 허리에서 너무 많이 흔들리지 않도록 하려면 [Depth Weight] 값을 증가시키세요.
  • 머리카락이 불규칙하게 흔들리도록 하려면 [Synchronization] 값을 낮추세요.
  • 흔들림의 랜덤성을 조정하려면 [Noise Blend]와 함께 [Synchronization] 값도 조정하세요.
  • 움직이는 바람(Moving Wind) 효과는 관성(Inertia) 매개변수 감소의 영향을 받습니다.
  • 강한 돌풍 효과를 만들고 싶다면, [Sphere Radial] 모드로 설정하고 [IsAddition] 플래그를 활성화하는 것이 효과적입니다.

BoneCloth 메쉬 연결 (BoneCloth Mesh Connection)


BoneCloth에는 등록된 Transform을 기반으로 자동으로 메쉬를 생성하는 기능이 있습니다.

이 기능을 사용하면 Transform 간의 연결이 더욱 강해지고, 형태 유지 및 충돌 감지가 강화됩니다.

또한, 엣지가 수평으로 연결되므로 엣지 충돌(Edge Collision)과 함께 사용하면 충돌 감지를 더욱 강화할 수 있습니다.

 

BoneCloth의 [Connection Mode]에서 설정을 변경할 수 있으며,

 

현재 세 가지 연결 방식이 제공됩니다.

 

예제로, 10개의 루트 본(Root Bones)으로 구성된 스커트를 기준으로 각 연결 방식의 특징을 설명합니다.

 

 

자동 메쉬(Auto Mesh)

 

인접한 Transform을 자동으로 연결합니다.

그러나 Transform의 배치에 따라 형태가 자연스럽지 않을 수도 있습니다.

 

 

순차 루프 메쉬(Sequential Loop Mesh)

 

Root Bones의 등록 순서에 따라 수평으로 연결됩니다.

따라서 Root Bones가 등록된 순서가 중요하며, 루트 본의 시작과 끝이 루프 형태로 연결됩니다.

 

 

순차 비루프 메쉬(Sequential Non-Loop Mesh)

 

Root Bones의 등록 순서에 따라 수평으로 연결됩니다.

따라서 Root Bones가 등록된 순서가 중요하며, 루트 본의 시작과 끝이 서로 연결되지 않습니다.

 

 

 

 

 

Transform 보간율 (Transform Interpolation Rate)


BoneCloth는 각 Transform의 회전 자세를 조정할 수 있으며,

 

이는 각 Transform이 최종적으로 부모 또는 자식 방향으로 얼마나 보간될지를 결정하는 비율입니다.

 

 

Root Rotation (루트 회전 조정)

 

고정된 Transform의 회전 조정을 담당하며, 0.0으로 설정하면 원래 자세에서 회전하지 않으며, 1.0으로 설정하면 자식 Transform의 방향으로 회전합니다.

자식이 여러 개일 경우 평균값이 적용되며, 0.5로 설정하면 부모와 자식 사이의 중간 위치로 회전합니다.

 

Rotational Interpolation (회전 보간 조정)

 

움직이는 Transform의 회전 조정을 담당하며,

0.0으로 설정하면 부모 Transform의 방향으로 회전하고,

1.0으로 설정하면 자식 Transform의 방향으로 회전합니다.

자식이 여러 개일 경우 평균값이 적용되며,

0.5로 설정하면 부모와 자식 사이의 중간 위치로 회전합니다.

단말 Transform(즉, 자식이 없는 Transform)은 항상 부모 방향으로 회전합니다.

 

 

 

자가 충돌(Self-Collision) 및 상호 충돌(Mutual Collision)


여기서는 두 가지 충돌 감지 기능인 자가 충돌과 상호 충돌에 대해 설명합니다.

두 기능은 유사하지만 용도가 다릅니다.

 

 

자가 충돌(Self-Collision)이란?

자가 충돌은 천 자체가 스스로와 충돌할 수 있도록 하는 기능입니다.

이 기능을 사용하면 천이 자체적으로 겹쳐지더라도 관통하지 않도록 설정할 수 있습니다.

 

 

상호 충돌(Mutual Collision)이란?

상호 충돌은 서로 다른 천 간의 충돌을 감지하는 기능입니다.

이 기능을 사용하면 각각 별도로 설정된 천들이 서로 충돌할 수 있도록 만들 수 있습니다.

 

 

 

 

 

사용 시 주의사항(Precautions for Use)


이 두 가지 충돌 기능은 강력하지만, 사용 시 몇 가지 주의할 점이 있습니다.

현재도 지속적으로 개선 및 연구가 진행 중입니다.

 

 

베타 버전(Beta Version)

자가 충돌 기능은 현재 베타 버전입니다.

따라서 향후 기능이 변경될 가능성이 높습니다.

 

 

높은 처리 부하(Processing Load)

자가 충돌은 매우 높은 연산 비용이 필요한 기능입니다.

따라서 데스크톱 PC 또는 고성능 콘솔 기기에서 사용하는 것이 권장됩니다.

이 비용은 프록시 메쉬의 버텍스 수에 정확히 비례하므로, 모바일 기기에서 사용할 경우 프록시 메쉬의 버텍스 수를 최대한 줄이는 것이 필수적입니다.

 

 

정확도 문제(Accuracy Problem)

현재 자가 충돌 기능의 정확도가 높지 않습니다.

특히 고속으로 움직이면 천이 서로 얽힐 가능성이 있으며, 하지만 내부적으로 얽힘을 감지하고 풀어주는 내장 메커니즘이 포함되어 있습니다.

 

 

진동 문제(Vibration Problem)

현재 자가 충돌 기능은 진동이 발생할 가능성이 큽니다.

이 문제는 프록시 메쉬의 구조에 크게 영향을 받으며, 메쉬의 형태에 따라 사용하기 어려울 수도 있습니다.

 

 

 

 

 

자가 충돌 설정(Self-Collision Settings)


자가 충돌은 [Self Collision] 패널에서 설정할 수 있으며, 이전의 다른 제약 조건보다 설정이 간단합니다.

 

자가 충돌에는 Self Mode와 Surface Thickness 두 가지 속성이 사용됩니다.

 

 

Self Mode(자가 충돌 모드)

현재 설정할 수 있는 모드는 Full Mesh뿐입니다.

Full Mesh는 Point-Triangle(점-삼각형) 및 Edge-Edge(모서리-모서리) 충돌 감지를 수행합니다.

 

추후에 더 다양한 모드가 추가될 가능성이 있습니다.

 

 

Surface Thickness(표면 두께)

Surface Thickness는 충돌 감지의 두께를 의미하며, 기본 단위는 미터(m)입니다.

자가 충돌은 콜라이더 충돌 감지에서 사용하는 버텍스 반지름을 사용하지 않고 오직 Surface Thickness 값만으로 충돌이 해결됩니다.

 

중요 사항: Surface Thickness는 충돌하는 두 버텍스의 값이 합산되어 적용되므로, 예를 들어 Surface Thickness를 0.005(m)로 설정하면 실제 충돌 두께는 0.01(m)이 됩니다.

또한, 이 두께 값은 상호 충돌과 공유됩니다.

 

두께가 클수록 충돌 감지가 더 강력해지지만, 너무 크게 설정하면 버텍스가 진동할 수 있습니다.

 

 

 

 

 

상호 충돌 설정 (Mutual Collision Setting)


상호 충돌은 [Self Collision] 패널에서도 설정할 수 있으며, 설정 과정이 자가 충돌보다 조금 더 복잡합니다.

 

Sync Mode

상호 충돌은 Sync Mode, Surface Thickness, Cloth Mass를 활용하여 설정됩니다.

자가 충돌과 마찬가지로 현재 설정할 수 있는 모드는 Full Mesh뿐이며, Full Mesh 모드는 상대 천(Cloth)에 대해 Point-Triangle 및 Edge-Edge 충돌 판정을 수행합니다.

 

모드를 Full Mesh로 변경하면, Sync Partner(동기화 파트너)를 설정할 수 있으며, 이 파트너가 충돌 대상이 됩니다.

 

예를 들어, 흰색과 초록색 두 개의 천이 있다고 가정할 때, 이 두 개의 천은 서로 독립적인 Cloth 컴포넌트입니다.

 

 

상호 충돌을 적용하려면 두 천 모두에 충돌을 설정할 필요는 없으며, 한쪽 천에만 상호 충돌을 설정하면 됩니다.

 

이 예제에서는 초록색 천을 설정하여 흰색 천과 충돌하도록 하며, 초록색 천의 설정에서 Sync Mode를 Full Mesh로 변경하고, Sync Partner로 흰색 천을 등록하면 됩니다.

 

반면, 흰색 천에는 Sync Mode와 Sync Partner를 설정할 필요가 없습니다.

 

이처럼 한 천에는 하나의 상호 충돌만 설정할 수 있으므로, 한 천에 대해 여러 개의 상호 충돌을 적용하려면, 어떤 천에 설정할지 신중히 고려해야 합니다.

 

 

Surface Thickness

Surface Thickness는 충돌 감지의 두께를 의미하며, 기본 단위는 미터(m)입니다.

이 설정은 자가 충돌(Self Collision)과 공유되므로, 자세한 사항은 자가 충돌 설명을 참고하면 됩니다.

 

중요한 점은, 충돌하는 두 천의 두께 값이 합산되어 계산된다는 것이며, 따라서 양쪽 천의 두께 값을 적절하게 설정하는 것이 중요합니다.

 

 

Cloth Mass

Cloth Mass는 상호 충돌 시 천의 무게를 결정하는 값으로, 값이 높을수록 천이 무거워집니다.

 

무거운 천과 가벼운 천이 충돌하면, 가벼운 천이 더 많이 움직이고, 무거운 천은 적게 움직이게 됩니다.

 

예를 들어, 액세서리와 스커트가 충돌할 때, 스커트의 무게를 더 무겁게 설정하면, 액세서리가 스커트를 불필요하게 밀어내는 현상을 방지할 수 있습니다.

따라서, 일반적으로 내부 층(layer)에 있는 천이 더 무거울수록 움직임이 더 안정적입니다.

'유니티 에셋 > Magica Cloth 2' 카테고리의 다른 글

바람 셋팅(Wind Setting)  (0) 2025.03.02
BoneCloth 고급 설정 (BoneCloth Advanced Settings)  (0) 2025.03.02
백스톱 (Backstop)  (0) 2025.03.02
버텍스 페인팅 방법  (0) 2025.03.02
매개변수 설정  (0) 2025.03.02

 

 

 

소개 (Introduction)


백스톱은 버텍스의 노멀 방향을 기준으로 진입할 수 있는 거리를 제한하는 일종의 충돌 감지 방식입니다.

이를 사용하면 앞머리가 머리 안으로 침범하는 것을 쉽게 방지할 수 있으며, T셔츠나 기타 의류가 신체 내부로 들어가는 것을 막을 수도 있습니다.

메커니즘이 단순하고 처리 부하가 적으며, 신체 내부로의 침투를 강력하게 방지할 수 있는 시스템입니다.

 

백스톱은 [Movement Limit] 패널에서 설정할 수 있습니다. 이 가이드는 먼저 백스톱의 원리를 설명한 후, 앞머리와 스커트 두 가지 예제를 통해 사용 방법을 설명합니다.

 

 

 

 

구조 (Structure)


백스톱의 원리는 간단합니다.

프록시 메쉬의 각 버텍스가 노멀 방향을 기준으로 이동할 수 있는 범위를 제한하는 방식입니다.

 

 

위 그림은 백스톱의 설정 속성과 그 한계를 보여줍니다. 프록시 메쉬의 버텍스와 노멀은 현재의 이동 위치가 아닌, 애니메이션 포즈(Origin)를 기준으로 합니다.

첫 번째 제한은 Max Distance(최대 거리)입니다. 버텍스는 원점(Origin)을 중심으로 반경 Max Distance 내에서만 이동할 수 있습니다.

 

두 번째 제한은 Backstop Collision(백스톱 충돌)입니다. 버텍스는 Backstop Radius(백스톱 반경)를 갖는 구체 바깥쪽으로만 이동할 수 있으며, 이 구체는 원점에서 Backstop Distance(백스톱 거리)만큼 떨어진 위치에 생성됩니다.

 

이 두 가지 제한으로 인해 버텍스는 Max Distance 내에서만 이동할 수 있으며, 동시에 Backstop Collision 바깥쪽에서만 존재할 수 있습니다. 이 제한은 둘 다 적용할 수도 있고, 하나만 사용할 수도 있습니다.

 

 

 

 

앞머리 설정 예제 (Setting Example of Bangs)


앞머리를 예로 들어 백스톱을 사용하여 앞머리가 머리 안으로 침범하는 것을 방지하는 방법을 설명합니다.

 

BoneCloth는 앞머리가 자연스럽게 흔들리도록 사전에 설정되어 있습니다.

 

 

노멀 방향 확인 (Check Normal Direction)

먼저 현재 프록시 메쉬의 노멀 방향을 확인해야 합니다.

Gizmo Panel에서 Axis를 Normal로 설정하면 확인할 수 있습니다.

 

초록색 선이 노멀 방향을 나타냅니다.

가시성을 위해 버텍스 구는 숨겨져 있습니다.

 

BoneCloth는 단순히 Transform의 Y축을 사용하기 때문에 이 모델에서는 방향이 엉망입니다.

이 상태에서는 백스톱을 사용할 수 없습니다.

 

 

노멀 조정 (Normal Adjustment)

노멀 방향을 조정해야 합니다.

이상적으로는 모든 노멀이 머리 바깥쪽을 향해야 합니다.

노멀 축(Normal Axis)과 노멀 정렬(Normal Alignment) 속성을 사용하여 조정합니다.

 

Normal Alignment를 Transform으로 설정한 후,

 

머리 중심에 위치한 Transform을 등록합니다.

만약 머리 중심에 Transform이 없다면 새로운 GameObject를 생성하여 등록하는 것이 좋습니다.

 

Transform 모드에서는 지정한 Transform 좌표를 중심으로 노멀 방향이 방사형으로 변경됩니다.

이 변경은 프록시 메쉬에 적용되며, 원래의 Transform이나 Mesh에는 영향을 주지 않습니다.

 

결과적으로 모든 노멀이 머리 바깥쪽을 향하도록 조정되었으며, 이제 백스톱을 설정할 준비가 되었습니다.

 

 

백스톱 설정 (Backstop Setting)

이제 침범할 수 있는 거리를 설정하면 됩니다. 이번에는 다음과 같이 설정했습니다.

 

곡선을 사용하여 시작점에서 끝점까지 0cm에서 5cm까지 침범할 수 있도록 설정했습니다. 이번에는 Max Distance를 사용하지 않았습니다.

Stiffness는 반발력의 강도를 나타냅니다. 값을 낮추면 버텍스가 조금 더 부드럽게 되돌아갑니다.

 

 

완료 (Complete)

실행해 봅시다!

 

*캐릭터를 좌우로 움직였습니다.
Collider를 사용하지 않고도 머리카락이 머리 안으로 침범하지 않도록 설정할 수 있었습니다.

 

 

 

 

스커트 설정 예제 (Skirt Setting Example)


이번에는 스커트를 예로 들어 고급 백스톱 설정 방법을 설명합니다.

백스톱은 주로 버텍스가 신체를 관통하는 것을 방지하는 역할을 합니다. 그러나 신체는 항상 애니메이션되며 다양한 자세 변화를 겪기 때문에 앞머리 설정보다 더 복잡한 작업이 필요합니다.

 

 

애니메이션 자세의 중요성 (Importance of Animation Posture)

여기서 애니메이션 자세가 중요한 요소가 됩니다. 애니메이션 자세란, MagicaCloth를 사용하지 않고 Transform 또는 Mesh가 애니메이션을 위해 변형되는 포즈를 의미합니다.

 

예를 들어, 아래 캐릭터의 스커트는 왼쪽과 오른쪽 다리에 스킨이 적용된 상태입니다.
이 경우, 스커트는 다리를 따라 움직이며 애니메이션되므로, 백스톱을 설정할 수 있습니다.

 

하지만 다음 캐릭터의 경우, 스커트가 다리에 스킨되지 않은 상태입니다.
이 상태에서는 애니메이션이 실행될 때 다리가 스커트를 통과하게 되며, 백스톱을 설정할 수 없습니다.

 

백스톱은 애니메이션 포즈에 제한을 가하는 시스템이므로, 백스톱이 제대로 작동하려면 Transform 또는 Mesh가 애니메이션을 따라가야 합니다.

 

 

애니메이션 자세 조정 (Animation Posture Adjustment)

위 문제를 해결하는 가장 좋은 방법은 스킨 가중치를 설정하여 메쉬가 다리를 따라가도록 만드는 것입니다.
또는 스커트에 본(Bone)을 추가하고, 달리기, 점프, 공격 등의 액션별로 본 애니메이션을 제어하여 자세를 어느 정도 조절하는 방법도 있습니다.
이 과정은 일반적으로 Blender와 같은 외부 툴을 사용하는 아티스트가 수행합니다.

 

이렇게 아티스트가 제작한 애니메이션 자세에 Cloth Simulation을 추가하는 방식이, 신체 침투를 방지하는 가장 견고한 방법입니다.
이 방법을 가장 추천합니다.

 

하지만 이 방법을 사용할 수 없는 경우, 이후 설명할 커스텀 스키닝 기능(Custom Skinning Feature)을 대안으로 사용할 수도 있습니다.

 

 

애니메이션 자세 확인 (Check Animation Posture)

현재 애니메이션 자세를 Gizmo를 통해 시각적으로 확인할 수 있습니다.
다음과 같이 애니메이션 자세 확인 기능을 활성화하면 확인할 수 있습니다.

 

예를 들어, 샘플에 포함된 UnityChanKAGURA의 스커트는 다리에 스킨되지 않았습니다.
Gizmo를 확인하면 이를 확인할 수 있습니다.

 

 

커스텀 스키닝 사용 (Using Custom Skinning)

만약 아티스트가 직접 수정하는 것이 불가능한 경우, 커스텀 스키닝(Custom Skinning) 기능을 대안으로 사용할 수 있습니다.
이 기능은 프록시 메쉬를 지정한 본(Bone)에 자동으로 스킨하는 기능입니다.
이미 애니메이션 포즈가 올바르게 설정되어 있다면 이 기능을 사용할 필요는 없습니다.

 

Custom Skinning 패널에서 체크를 활성화하고, Skinning Bones에 필요한 본을 등록합니다.
KAGURA의 스커트의 경우, 가슴에서 양쪽 다리까지의 본을 등록했습니다.

 

이 등록된 본은 Gizmo에서 노란색 구체(Yellow Sphere)로 표시됩니다.

 

이 상태에서 실행하면 커스텀 스키닝을 통해 프록시 메쉬가 다리를 따라가도록 설정됩니다.


이제 백스톱이 정상적으로 작동하게 됩니다.
이 기능은 BoneCloth에서도 사용할 수 있습니다.

 

하지만 커스텀 스키닝은 단순한 대체 기능일 뿐이므로 정확도가 낮을 수 있습니다.

 

 

유효한 버텍스 페인트 (Paint Valid Vertices)

백스톱은 버텍스 단위로 기능을 켜거나 끌 수 있습니다.
이는 패널의 파란색 펜 마크(Blue Pen Mark)를 클릭하여 버텍스 페인트를 실행하면 설정할 수 있습니다.

 

예를 들어, 특정 메쉬 부분에서 백스톱이 작동하지 않도록 설정하고 싶을 때 사용할 수 있습니다.

 

 

백스톱 설정 (Backstop Setting)

이제 앞머리 설정 예제와 동일한 방식으로 백스톱을 설정하면 됩니다.
이번에는 KAGURA의 스커트에 커스텀 스키닝을 적용합니다.

효과를 쉽게 확인하기 위해 침범 제한 거리를 0cm로 설정했습니다.

 

완료 (Complete)

실행해 봅시다!

 

이번 설정은 Collider를 전혀 사용하지 않고 백스톱만으로 제어되는 방식입니다.
백스톱이 다리의 움직임을 제한하며, 버텍스가 다리 안으로 침범하는 것을 막는 것을 확인할 수 있습니다.
또한 허리 주변에서도 버텍스 침범이 발생하지 않는 것을 확인할 수 있습니다.

 

여기에 Collider를 추가하고 보강하면, 아무리 강한 움직임을 줘도 스커트가 다리를 관통하지 않도록 제어할 수 있습니다.

 

 

 

 

애니메이션 자세 비율 (Animation Posture Ratio)


이 항목은 백스톱과 직접적인 관련은 없지만, 흔들림을 제어하는 데 중요하기 때문에 설명합니다.

 

지금까지 애니메이션 자세에 대해 설명했으며, MagicaCloth는 내부적으로 초기 자세와 애니메이션 자세를 모두 유지하고 있습니다.

백스톱은 항상 애니메이션 자세를 기준으로 계산됩니다.

 

그러나 일부 제약에서는 초기 자세 또는 애니메이션 자세를 기준으로 복원할지를 지정할 수 있으며, 이를 애니메이션 자세 비율이라고 합니다.

기본 패널의 Animation Pose Ratio 항목에서 설정할 수 있습니다.

 

이 값이 1.0에 가까울수록 복원력이 애니메이션 자세를 기준으로 계산됩니다.

기본값은 0.0이며, 이 경우 복원력은 초기 자세를 기준으로 계산됩니다.

즉, 초기 자세로 복원할 것인지, 현재 애니메이션 자세로 복원할 것인지에 대한 비율을 설정하는 값입니다.

 

비율이 0.0이면 초기 자세로 돌아가려는 복원력이 작용하고, 비율이 1.0이면 현재 애니메이션 자세로 돌아가려는 복원력이 작용합니다.

비율이 0.5이면 초기 자세와 애니메이션 자세를 1:1로 블렌딩하여 복원됩니다.

 

 

영향을 받는 제약 (Affected Constraints)

이 애니메이션 자세 비율의 영향을 받는 제약은 세 가지입니다.

  1. 각도 복원(Angle Restoration)
  2. 각도 제한(Angle Limit)
  3. 형상 복원(Shape Restoration)

 

 

설정 가이드라인 (Setting Guidelines)

기본적으로 0.0으로 설정하는 것이 무난합니다.

대부분의 경우 옷의 움직임이 더 안정적으로 유지되기 때문입니다.

 

그러나 애니메이션 자세가 크게 변형되는 경우에는 비율을 증가시키는 것이 더 안정적일 수 있습니다.

또한 애니메이션 자세가 아티스트에 의해 완전히 제어되는 경우, 높은 값을 설정하는 것이 더 안정적입니다.

이 경우, 기본적으로 0.5부터 시작하여 움직임을 보면서 조정하는 것이 좋습니다.

'유니티 에셋 > Magica Cloth 2' 카테고리의 다른 글

BoneCloth 고급 설정 (BoneCloth Advanced Settings)  (0) 2025.03.02
자가 충돌 설정 (Self-Collision Setting)  (1) 2025.03.02
버텍스 페인팅 방법  (0) 2025.03.02
매개변수 설정  (0) 2025.03.02
충돌 감지 설정  (0) 2025.03.02

+ Recent posts