개요


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);

+ Recent posts