SDF 파일 포맷 이해
SDF(Simulation Description Format)는 로봇 및 물리 기반 시뮬레이션을 정의하기 위한 XML 기반의 파일 포맷이다. 주로 로봇의 구조와 물리적 특성을 기술하는 데 사용되며, 가제보(Gazebo)와 같은 시뮬레이션 환경에서 널리 사용된다. Unity는 SDF 포맷을 직접적으로 지원하지 않지만, SDF 파일을 Unity로 변환하는 방법을 사용하여 로봇 모델을 가져올 수 있다.
SDF 파일의 주요 구성 요소는 다음과 같다:
<world>
: 시뮬레이션 환경을 정의한다. 중력, 시간 단계, 조명 및 기타 환경 설정을 포함한다.<model>
: 로봇이나 물체의 물리적 모델을 정의한다. 모델은 링크와 조인트로 구성된다.<link>
: 모델 내의 개별 파트를 정의하며, 각 파트는 물리적 특성(질량, 관성, 충돌 모양 등)을 갖는다.<joint>
: 링크 사이의 조인트를 정의한다. 조인트는 회전축, 이동축 및 연결된 링크를 명시한다.
SDF 파일의 구조적 변환
Unity로의 변환을 위해서는 SDF 파일의 각 구성 요소를 Unity의 구조체로 매핑해야 한다. 주요 변환 대상은 로봇의 각 링크와 조인트이다.
링크 변환
SDF의 <link>
는 Unity의 GameObject
에 해당한다. SDF에서 로봇의 각 링크는 Unity에서 다음과 같은 구성 요소로 변환된다:
- 질량: Unity의
Rigidbody
에서 질량 값을 설정한다. SDF에서는<inertial>
태그를 통해 링크의 질량과 관성 모멘트를 정의할 수 있다. 이를 Unity에서Rigidbody.mass
와 관성 텐서 값으로 매핑한다.
SDF 파일에서의 질량 설정:
xml
<link name="base_link">
<inertial>
<mass>1.0</mass>
<inertia>
<ixx>0.1</ixx>
<iyy>0.1</iyy>
<izz>0.1</izz>
</inertia>
</inertial>
</link>
Unity에서는 이 질량 정보를 다음과 같이 적용한다:
csharp
Rigidbody rb = gameObject.AddComponent<Rigidbody>();
rb.mass = 1.0f;
rb.inertiaTensor = new Vector3(0.1f, 0.1f, 0.1f);
링크의 관성 변환
SDF에서 정의된 관성 행렬은 Unity의 Rigidbody.inertiaTensor
에 매핑된다. SDF에서 관성 텐서는 3 \times 3 대칭 행렬로 주어진다. 이는 Unity에서 축에 맞는 관성 텐서 값으로 변환된다.
SDF에서의 관성 행렬은 다음과 같이 정의된다:
이 중 대각 성분인 \mathbf{I_{xx}}, \mathbf{I_{yy}}, \mathbf{I_{zz}}를 Unity의 Rigidbody.inertiaTensor
로 변환한다. Unity에서는 직교 좌표계로만 처리하므로, 대각 성분만 사용한다.
SDF에서 정의된 관성 텐서:
<inertia>
<ixx>0.1</ixx>
<iyy>0.1</iyy>
<izz>0.1</izz>
</inertia>
Unity에서 적용 예시:
rb.inertiaTensor = new Vector3(0.1f, 0.1f, 0.1f);
조인트 변환
SDF에서 정의된 <joint>
는 Unity에서 ConfigurableJoint
또는 HingeJoint
로 변환할 수 있다. 두 조인트 시스템의 변환은 다음과 같은 기본 절차로 진행된다:
- 조인트 타입: SDF의
revolute
,prismatic
,fixed
등의 조인트는 Unity에서 적절한 조인트 타입으로 변환된다. 예를 들어,revolute
조인트는 Unity의HingeJoint
로 변환된다.
SDF의 revolute
조인트 정의:
xml
<joint name="joint1" type="revolute">
<parent>link1</parent>
<child>link2</child>
<axis>
<xyz>0 0 1</xyz>
</axis>
</joint>
Unity에서의 조인트 설정:
csharp
HingeJoint joint = gameObject.AddComponent<HingeJoint>();
joint.axis = new Vector3(0, 0, 1);
조인트 제약
SDF에서 조인트의 회전 각도나 이동 거리 제한이 있다면, Unity에서 이를 설정해야 한다. HingeJoint
의 경우, 각도 제한을 설정할 수 있다. SDF의 <limit>
태그는 Unity의 limits
속성으로 변환된다.
SDF에서의 조인트 제약 설정:
<limit>
<lower>-1.57</lower>
<upper>1.57</upper>
</limit>
Unity에서의 변환:
joint.limits = new JointLimits {
min = -90,
max = 90
};
URDF와 SDF 간의 차이점
SDF와 URDF는 유사한 목적을 가진 파일 포맷이지만, Unity에서의 변환 작업 시 고려해야 할 몇 가지 차이점이 있다. 이 차이점을 이해하는 것이 변환 과정에서 오류를 최소화하는 데 중요하다.
- SDF의 계층 구조: SDF는 더 복잡한 계층 구조를 지원하며, 여러 개의
<model>
을 포함할 수 있다. URDF는 하나의 로봇에 대한 정의만을 포함하는 반면, SDF는 하나의 파일 내에서 여러 로봇을 정의하거나 서로 다른 환경 요소를 포함할 수 있다. 따라서 Unity에서 변환할 때 각 모델을 개별Prefab
으로 처리하는 것이 필요할 수 있다. - 물리 엔진의 설정 차이: SDF는 URDF보다 더 세밀한 물리 엔진 설정을 제공한다. 예를 들어, SDF는 각 모델의 물리적 속성(예: 마찰력, 감쇠 등)을 더 구체적으로 정의할 수 있다. 이와 같은 세부 물리 속성을 Unity로 변환할 때는 Unity의
PhysicMaterial
과 같은 요소를 적절히 활용해야 한다.
변환 과정 자동화
SDF 파일에서 Unity로의 변환을 수동으로 하는 것은 매우 비효율적일 수 있으므로, 이 과정을 자동화하는 스크립트를 작성하는 것이 유리한다. Python이나 C#을 사용하여 SDF 파일을 파싱한 뒤, Unity의 오브젝트로 변환하는 자동화 도구를 만들 수 있다.
Python을 통한 SDF 파싱
SDF 파일을 파싱하기 위해 Python의 xml.etree.ElementTree
모듈을 사용할 수 있다. 이 모듈을 사용하면 SDF 파일을 트리 구조로 읽어들여, 각 노드를 순차적으로 파싱할 수 있다.
SDF 파일을 읽고 Unity 변환용 데이터를 추출하는 Python 코드 예시:
import xml.etree.ElementTree as ET
def parse_sdf(sdf_file):
tree = ET.parse(sdf_file)
root = tree.getroot()
for model in root.findall('model'):
model_name = model.get('name')
print(f"Model: {model_name}")
for link in model.findall('link'):
link_name = link.get('name')
mass = link.find('inertial/mass').text
print(f" Link: {link_name}, Mass: {mass}")
# 조인트 처리
for joint in model.findall('joint'):
joint_name = joint.get('name')
parent = joint.find('parent').text
child = joint.find('child').text
print(f" Joint: {joint_name}, Parent: {parent}, Child: {child}")
parse_sdf('robot.sdf')
이 파싱 코드를 통해 추출된 정보를 Unity에서 사용할 수 있는 포맷으로 변환하는 C# 스크립트와 연결할 수 있다.
Unity에서 SDF 데이터 적용
파싱된 SDF 데이터를 Unity에 적용하려면, 파싱 결과를 C#으로 가져와서 Unity 오브젝트를 생성하는 스크립트를 작성해야 한다. 예를 들어, 각 링크에 해당하는 GameObject
를 생성하고, 물리 속성을 적용할 수 있다.
C#에서 Python 파싱 결과를 사용하는 예시:
using UnityEngine;
public class RobotLoader : MonoBehaviour
{
public void CreateLink(string linkName, float mass)
{
GameObject link = new GameObject(linkName);
Rigidbody rb = link.AddComponent<Rigidbody>();
rb.mass = mass;
// 다른 물리 속성 추가
}
public void CreateJoint(string jointName, string parentName, string childName)
{
GameObject parent = GameObject.Find(parentName);
GameObject child = GameObject.Find(childName);
HingeJoint joint = child.AddComponent<HingeJoint>();
joint.connectedBody = parent.GetComponent<Rigidbody>();
// 다른 조인트 속성 추가
}
}
이처럼 SDF 파일에서 로봇의 링크와 조인트를 자동으로 생성하고 Unity에서 물리적으로 시뮬레이션할 수 있는 환경을 만들 수 있다.
충돌 메쉬 및 시각적 메쉬 처리
SDF에서 각 링크는 충돌 메쉬와 시각적 메쉬를 별도로 지정할 수 있다. Unity로 변환할 때, 이를 Unity의 MeshCollider
와 MeshRenderer
로 적절히 처리해야 한다. 충돌 메쉬는 물리적 상호작용을 위한 것이며, 시각적 메쉬는 렌더링을 위한 것이다.
충돌 메쉬 변환
SDF의 충돌 메쉬는 <collision>
태그 아래에 정의된다. 이를 Unity의 MeshCollider
로 변환할 수 있다. 충돌 메쉬가 복잡할 경우, 물리 성능을 고려하여 간소화하는 작업이 필요할 수 있다.
SDF 충돌 메쉬 정의:
<collision name="base_collision">
<geometry>
<mesh>
<uri>model://robot/meshes/base_collision.dae</uri>
</mesh>
</geometry>
</collision>
Unity에서의 변환 예시:
MeshCollider collider = link.AddComponent<MeshCollider>();
collider.sharedMesh = Resources.Load<Mesh>("Meshes/base_collision");
시각적 메쉬 변환
SDF의 시각적 메쉬는 <visual>
태그 아래에 정의된다. 이는 Unity에서 MeshRenderer
와 MeshFilter
를 사용하여 로봇의 외형을 렌더링하는 데 사용된다.
SDF 시각적 메쉬 정의:
<visual name="base_visual">
<geometry>
<mesh>
<uri>model://robot/meshes/base_visual.dae</uri>
</mesh>
</geometry>
</visual>
Unity에서의 변환 예시:
MeshFilter meshFilter = link.AddComponent<MeshFilter>();
meshFilter.mesh = Resources.Load<Mesh>("Meshes/base_visual");
MeshRenderer renderer = link.AddComponent<MeshRenderer>();
renderer.material = Resources.Load<Material>("Materials/RobotMaterial");
이와 같이, SDF에서 정의된 시각적 및 충돌 메쉬를 각각 적절한 Unity 구성 요소로 변환할 수 있다.