# 시뮬레이션 시간(Clock)과 속도 제어

Isaac Sim에서 시뮬레이션 시간을 다루는 것은 물리 엔진, 센서 데이터, 제어 알고리즘, ROS2 통신 모두에 중요한 영향을 미친다. 특히 시뮬레이션 내에서 동작하는 로봇의 움직임이 실제와 달라지지 않도록 하려면 시간의 흐름을 정확히 이해하고 설정해야 한다. Isaac Sim은 내부적으로 물리 시뮬레이션을 계산하기 위해 별도의 타임스텝(time step) 개념을 운영하며, 사용자는 이를 기반으로 시뮬레이션 시간의 배속(speed factor)이나 물리 연산 주기를 조절할 수 있다.

또한 ROS2와 연동하여 사용할 때, 시뮬레이션이 퍼블리시(publish)하는 시간 정보와 ROS2의 Clock이 긴밀하게 동기화되어야 한다. Isaac Sim 내에서의 “시뮬레이션 시간”과 호스트 컴퓨터가 제공하는 “실시간(wall clock time)” 사이에 차이가 발생할 수 있기 때문이다. 이 장에서는 Isaac Sim의 시뮬레이션 시계와 속도 제어 방법을 기초부터 고급 개념까지 폭넓게 다루어 본다.

#### Isaac Sim에서의 시뮬레이션 시간 개념

Isaac Sim의 시뮬레이션 시간은 실제 시간과 반드시 일치하지 않는다. 시뮬레이션은 내부 물리 엔진이 요구하는 각종 계산량, CPU/GPU 성능, 장면(Scene) 복잡도 등 다양한 요인에 의해 느려지거나 빨라질 수 있다. 이에 대응하기 위해 Isaac Sim은 다음 두 가지 시간을 구분한다.

시뮬레이션 시간은 물리 세계에서 가상의 시간이 얼마나 흘렀는지를 나타낸다. 이를 $t\_\text{sim}$이라 하자.

실시간은 호스트 시스템의 벽시계 시간을 의미하며, $t\_\text{wall}$이라 하자.

이 두 시간은 일반적으로 관계식으로 표현할 수 있다.

$$
\begin{align} t\_\text{sim} = \alpha \cdot t\_\text{wall}  \end{align}
$$

여기서 $\alpha$는 ‘시뮬레이션 속도 비율(scaling factor)’로서, 실제 시간 대비 시뮬레이션 시간을 어느 정도로 진행시킬지를 결정한다. 예를 들어 $\alpha = 1$이면 실제 시간과 동일하게 시뮬레이션 시간이 흐르고, $\alpha = 2$이면 시뮬레이션 시간이 실제 시간의 두 배로 빨리 흐른다.

물리엔진이 실제로 계산되는 타임 스텝 물리 프레임 간격 $\Delta t\_\text{phys}$ 역시 중요하다. 이는 Isaac Sim이 한 프레임(frame)을 계산할 때마다 시뮬레이션 시간을 얼마만큼 진전시키는지를 의미한다. 예를 들어 60FPS로 동작하도록 설정한 경우, 한 프레임이 완성될 때마다 $\Delta t\_\text{phys} = \frac{1}{60}$초가 시뮬레이션 시간에서 증가한다.

#### 기본 물리 파이프라인과 시계 업데이트

Isaac Sim에서 한 프레임이 업데이트되는 대략적인 과정을 간단히 살펴보면 다음과 같다.

{% @mermaid/diagram content="flowchart TB
A(장면 설정<br>또는 이벤트 발생) --> B(물리 엔진<br>시뮬레이션 진행)
B --> C(시뮬레이션 시간<br>t\_sim 업데이트)
C --> D(렌더링 및<br>결과 반영)
D --> A" %}

물리 엔진이 한 번 호출될 때마다 Isaac Sim은 내부에서 현재 설정된 타임 스텝($\Delta t\_\text{phys}$)만큼 시뮬레이션 시간을 증가시키고, 이후 렌더링 과정을 통해 장면을 업데이트한다. 이 시점에서 $t\_\text{sim}$이 실제 시간 $t\_\text{wall}$과 일치하는지는 시스템 성능과 시뮬레이션 스케일 팩터에 따라 달라진다.

#### 시뮬레이션 속도 제어

Isaac Sim은 시뮬레이션 속도를 제어하기 위해 크게 두 가지 접근 방식을 제공한다.

하나는 물리 계산 주기를 조정하는 것이고, 다른 하나는 시뮬레이션 시간을 재생성(Playback)할 때 배속을 설정하는 것이다. $\Delta t\_\text{phys}$는 보통 물리 엔진이 안정적으로 동작할 수 있는 범위 내에서만 조정한다. 그 외에 $\alpha$ 값을 조절하거나, 내부적으로 제공되는 ‘Real Time Scale’ 등을 사용해 실제 시간 대비 가상 시간을 조정한다.

가장 기본적인 제어 파라미터로는 다음과 같은 것이 존재한다.

* 시뮬레이션 타임스텝: 물리 계산 주기인 $\Delta t\_\text{phys}$.
* 재생 속도 배율: 배속 계수 $\alpha$.

이를 적절히 조합하여 사용함으로써, 특정 시나리오에서는 느리게(slow motion) 시뮬레이션하고 싶거나, 반대로 빠르게 진행하여 결과만 빠르게 확인할 수도 있다.

#### $\Delta t\_\text{phys}$와 수치 해석

물리 시뮬레이션에서 $\Delta t\_\text{phys}$를 어떻게 설정하느냐에 따라 시뮬레이션 품질이 크게 달라진다. 다음 예시를 통해 물리 시뮬레이션에서의 간단한 해석 모델을 살펴본다.

1차 근사 방식(Euler)으로 로봇의 상태를 갱신한다고 할 때, 이산 시간 스텝 $k$에서의 상태 $\mathbf{x}*k$와 다음 스텝의 상태 $\mathbf{x}*{k+1}$ 관계는

$$
\begin{align} \mathbf{x}\_{k+1} &= \mathbf{x}\_k + \dot{\mathbf{x}}*k \Delta t*\text{phys}  \end{align}
$$

와 같이 나타낼 수 있다. 여기서 $\dot{\mathbf{x}}\_k$는 $k$ 시점에서의 상태 변화율(속도나 가속도에 의해 결정되는 값)이다. 물리 시뮬레이션은 매우 다양한 상호 작용(충돌, 마찰, 합력 등)을 계산하기 때문에 보다 정교한 적분법(Semi-Implicit Euler, Runge-Kutta 등)을 쓰지만, 기본 원리는 위와 유사하다.

따라서 $\Delta t\_\text{phys}$가 너무 크면 빠른 충돌이나 복잡한 역학 조건을 놓칠 수 있으며, 너무 작으면 계산량이 커져서 시뮬레이션이 과도하게 느려질 수 있다. Isaac Sim이 권장하는 범위 내에서 이 값을 세심하게 조정하는 것이 중요하다.

#### 예시: Python 스크립트를 통한 타임스텝 조정

Isaac Sim에서 Python API를 사용해 타임스텝을 직접 제어하고 싶은 경우 다음과 같은 코드를 작성할 수 있다. 이 코드는 가상 씬(Scene)을 초기화한 뒤 원하는 타임스텝을 설정한다.

```python
import omni
from omni.isaac.core.utils.stage import get_current_stage

# Isaac Sim 엔진을 초기화하는 과정 (예시)
async def setup_simulation():
    # 현재 Stage(USD 파일) 불러오기
    stage = get_current_stage()
    
    # 물리 설정(PhysicsScene) 경로 가져오기
    physics_scene_path = "/World/PhysicsScene"
    physics_scene = stage.GetPrimAtPath(physics_scene_path)
    
    # 타임 스텝을 1/60초로 설정 (60FPS)
    # 만일 1/30초(30FPS)로 설정하고 싶다면 (1/30)로 변경
    physics_scene.GetAttribute("physics:timeStepsPerSecond").Set(60.0)

    # 추가적으로 'realTimeScale' 값 설정도 가능
    # physics_scene.GetAttribute("physics:enableRealTime").Set(True)
    # physics_scene.GetAttribute("physics:realTimeScale").Set(0.5)  # 실제 시간의 0.5배로 느리게

# 메인 루프 실행
async def main():
    await setup_simulation()
    # 시뮬레이션 진행
    # ... (로봇 스폰, 조작 등)
```

physics:timeStepsPerSecond 같은 속성은 실제로 물리엔진이 1초 동안 몇 번의 계산을 수행할지 결정해 준다. 예를 들어 60.0으로 설정할 경우 $\Delta t\_\text{phys} = \frac{1}{60}$초이 된다. realTimeScale을 함께 사용하면, $\Delta t\_\text{phys}$는 일정하게 유지하되 시뮬레이션 시간을 원하는 배율로만 진행하게 된다.

#### ROS2 시간 동기와 시뮬레이션 시간

Isaac Sim은 ROS2와 통신할 때, 센서 토픽이나 TF(Transform) 메시지 등에 시간 정보를 포함해서 퍼블리시한다. 이때 Isaac Sim 내부 시계($t\_\text{sim}$)를 그대로 ROS2에 넘겨줄 수도 있고, 시뮬레이션 시간과 ROS2 Clock을 동기화해서 사용하도록 설정할 수도 있다.

ROS2 Humble에서 사용되는 Clock은 크게 ‘System Time’, ‘ROS Time’ 등으로 구분되며, Isaac Sim이 ROS Time을 사용한다면, 시뮬레이션 시간이 ROS2 노드들 간에 동일하게 공유된다. 이를 위해서는 ROS2 Bridge에서 “Use Simulation Clock” 옵션을 활성화하고, Isaac Sim에서 퍼블리시하는 clock 토픽을 ROS2가 구독(subscribe)할 수 있도록 설정하면 된다.

만약 Isaac Sim이 매우 빠르게(또는 느리게) 진행된다면, ROS2 기반으로 작성된 알고리즘은 $t\_\text{sim}$에 기반하여 처리하기 때문에, 외부 시스템의 실제 시간과는 다른 결과가 나올 수 있다. 이는 시뮬레이션 검증에 필요한 경우 유용하지만, 실제 시간과 동일하게 동작하는지 확인하려면 별도로 실시간 동기화 설정을 확인해야 한다.

#### 시뮬레이션 시간을 활용한 제어 예시 (Python)

다음은 Isaac Sim에서 현재 시뮬레이션 시간을 불러와서 로봇 제어 알고리즘에 반영하는 단순 예시다. 가령, 로봇의 조인트를 시뮬레이션 시간에 따라 사인 파 형태로 움직이고 싶다고 하자.

```python
import math
import time

class RobotController:
    def __init__(self, robot):
        self.robot = robot

    def update(self, sim_time):
        # 시뮬레이션 시간을 인자로 받아서, 로봇 조인트를 사인 곡선으로 제어
        angle = math.sin(sim_time)
        self.robot.set_joint_position("joint_1", angle)

async def main_loop():
    controller = RobotController(my_robot)
    while True:
        # Isaac Sim으로부터 현재 시뮬레이션 시간(초 단위) 읽기 (가정)
        sim_time = get_simulation_time_in_seconds()  
        
        # 로봇 제어
        controller.update(sim_time)
        
        # step 함수 혹은 프레임 업데이트
        await omni.kit.app.get_app().next_update_async()
```

여기서 get\_simulation\_time\_in\_seconds()는 단순 예시 함수로, 실제 구현에서는 Isaac Sim의 API나 ROS 토픽 등을 통해 시뮬레이션 시간을 얻어올 수 있다. 만일 realTimeScale을 2.0으로 설정했다면, 시뮬레이션 시간이 실제 시간 대비 두 배 빠르게 증가하므로, 이 제어 코드는 더 빠르게 변화를 일으키게 된다.

#### 고급 물리 시뮬레이션에서의 시간 관리

Isaac Sim은 다양한 물리 효과(관성, 마찰, 충돌, 유연체, 유체 등)를 높은 정확도로 시뮬레이션할 수 있도록 최적화되어 있다. 이와 같은 상황에서 시뮬레이션 시간을 관리하는 것은 단순히 배속을 설정하는 것 이상의 의미를 갖는다. 물리 연산이 복잡해질수록 실제 계산 시간(실시간)과 가상 물리 시간(시뮬레이션 시간) 사이에 큰 편차가 생길 수 있기 때문이다.

가상 장면에서 객체 간 충돌이 많이 발생하거나, 복잡한 조인트(articulation) 구조를 가진 로봇들이 다수 존재하면, 한 프레임의 물리 연산 시간이 크게 증가한다. 이때 소프트웨어가 프레임마다 할당된 시뮬레이션 시간을 $\Delta t\_\text{phys}$만큼 증가시키되, 실제로는 $\Delta t\_\text{phys}$를 처리하기 위해 훨씬 더 많은 실시간이 소요될 수 있다. 결과적으로 $t\_\text{sim}$과 $t\_\text{wall}$ 사이의 비율이 크게 달라진다.

Isaac Sim은 내부적으로 물리 스레드(physics thread)와 렌더링 스레드(rendering thread)를 분리하여 최대한 병렬화하려 하지만, 하드웨어 스펙이나 작업 부하에 따라 시뮬레이션 속도는 달라지게 된다. 사용자는 적절한 타임스텝 크기를 설정하거나, realTimeScale 값을 활용하여 시뮬레이션 안정성을 유지하면서도 원하는 시간 축으로 동작하도록 관리할 수 있다.

#### 비실시간(Non-realtime) 시뮬레이션과 후처리

Isaac Sim에서의 비실시간 시뮬레이션은 실제 시간보다 느리거나 빠르게 흘러가며, 어떤 경우에는 정지(freeze) 상태에서 여러 물리 매개변수를 조정한 뒤 다시 재생(Resume)할 수도 있다. 이러한 비실시간 특성은 실험적, 연구적 용도로 매우 유용하다. 예를 들어 로봇이 특정 충돌 조건에 놓였을 때 시뮬레이션을 중지한 뒤, 각종 센서 데이터나 연산 중간값을 분석하고, 다시 재생시켜 결과를 확인할 수 있다.

이 과정을 반복하면서 얻은 데이터는 후처리(Post-processing)나 기계학습(특히 시뮬레이션에서 생성된 대규모 학습 데이터) 등에 활용된다. 이때 시뮬레이션 시간과 실시간을 분리해서 관리하면, Isaac Sim 내부에서 기록된 로그나 ROS2 토픽의 Timestamp 등이 뒤섞이지 않고 일관성 있게 유지된다.

#### 멀티 로봇 시뮬레이션에서의 시간 동기

Isaac Sim은 단일 로봇뿐 아니라 다수의 로봇을 동시에 시뮬레이션할 수도 있다. 멀티 로봇 시나리오에서는 여러 로봇이 상호 작용하는 물리 환경, 충돌, 센서 데이터 등을 동시에 처리하게 되는데, 여기서 시간 동기가 중요한 역할을 한다. 모든 로봇이 동일한 시뮬레이션 Clock을 참조해야 각 로봇 간 이벤트 발생 시점이 일관성을 갖게 되며, ROS2 통신 역시 한 Clock을 기반으로 메시지를 송수신할 수 있다.

Isaac Sim은 내부에서 단일한 시뮬레이션 시간($t\_\text{sim}$)을 유지하면서도, 각 로봇의 컨트롤 루프가 별도의 스레드나 별도의 노드로 동작할 수 있다. 이를 위해서는 로봇마다 개별적으로 물리 엔진을 실행하는 것이 아니라, 중앙에서 시뮬레이션 시간을 진전시키며 각 로봇의 상태를 순차적으로(또는 병렬적으로) 업데이트하는 구조를 활용한다.

#### UI를 통한 시간 제어

Isaac Sim은 스크립트 API뿐 아니라 그래픽 사용자 인터페이스(GUI)를 통해서도 시뮬레이션 시간을 제어할 수 있다. 일반적으로 Isaac Sim 상단 메뉴나 시뮬레이션 패널(Play, Pause, Stop 등)을 통해 실행 상태를 바꾸거나 재생 속도(Playback Speed)를 조절할 수 있다. PhysicsScene 설정 창을 열면 timeStepsPerSecond, enableRealTime, realTimeScale 등의 파라미터를 직접 입력할 수도 있다.

또한 Isaac Sim의 기능 중 하나인 OmniGraph를 통해, 노드 기반으로 시간 흐름을 제어하거나 특정 이벤트 시점에 시뮬레이션을 정지/재개하는 로직을 구성할 수도 있다. 이 방식은 프로그래밍 없이 블록 다이어그램 형태로 직관적인 시간 컨트롤을 구현한다는 장점이 있다.

#### 대규모 시나리오에서의 성능 고려

시뮬레이션에서 가장 중요한 점은 일정한 타임스텝(또는 목표 프레임 레이트)을 유지하면서 안정적으로 물리를 계산할 수 있느냐다. 만일 물리 계산 부하가 너무 커서 타임스텝 간격보다 실제 계산 시간이 더 길어지면, 결과적으로 시뮬레이션이 느려진다. 이를 해소하기 위해서는 물리 엔진 파라미터 조정, GPU 병렬화 사용, 장면 최적화(폴리곤 수나 콜라이더(Collider) 단순화 등) 등이 필요하다.

Isaac Sim은 NVIDIA의 RTX GPU를 활용하는 OptiX 기반 경로 추적이나 PhysX 엔진 가속, 클라우드 기반 시뮬레이션 확장 등 다양한 성능 향상 기법을 지원한다. 이러한 기법들을 활용할 때도, 시뮬레이션 시간 스케일 $\alpha$와 물리 타임스텝 $\Delta t\_\text{phys}$는 적절히 설정해야 한다. 지나치게 큰 $\Delta t\_\text{phys}$로 설정하면 충돌 감지가 부정확해질 수 있고, 지나치게 작은 값은 연산 부하를 크게 증가시켜 실시간 동작이 어려워질 수 있다.

#### ROS2 Clock 동기화의 세부 설정

ROS2에서 시뮬레이션 시간을 동기화하려면, Isaac Sim이 퍼블리시하는 /clock 토픽(기본적으로 ROS2 Humble에서 지원되는 시뮬레이션 시간 토픽)을 활용한다. 시스템 전체를 이 토픽 기반으로 동작하도록 하려면 ROS2 노드들이 다음과 같이 구성되어 있어야 한다.

* ROS2 노드들은 use\_sim\_time 파라미터를 True로 설정하여, 시뮬레이션 시간(ROS Time)을 참조한다.
* Isaac Sim은 시뮬레이션 시간의 변화를 /clock에 주기적으로 퍼블리시한다.

이때 ROS2 노드들의 주기적 타이머, 메시지 Timestamp, TF 브로드캐스트 등의 모든 시간 값이 시뮬레이션 시간에 종속된다. 시뮬레이션을 정지하면, ROS2 노드들도 시간이 정지된 것으로 인식하고, 타이머 콜백 등이 동작하지 않게 된다. 배속을 빠르게 돌리면 ROS2 관점에서 동일한 시뮬레이션 시간이 훨씬 빠르게 지나가기 때문에, 센서 데이터나 제어 명령이 매우 빠른 주기로 발생하는 모습을 볼 수 있다.

#### 동적 타임스텝과 어댑티브(Adaptive) 물리

일부 복잡한 다물체 동역학 시뮬레이션(Multi-body dynamics)에서는 ‘어댑티브 타임스텝(Adaptive Time Stepping)’을 활용하기도 한다. 충돌이나 빠른 운동이 발생할 때 $\Delta t\_\text{phys}$를 자동으로 줄여서 더 세밀하게 계산하고, 상대적으로 운동이 단조로운 구간에서는 타임스텝을 다시 늘리는 방식이다. PhysX나 다른 물리 엔진에서 이를 지원하는지 여부는 엔진 버전과 설정에 따라 달라진다.

Isaac Sim은 일반적으로 고정된 타임스텝을 권장하지만, 사용자가 필요한 경우에는 커스텀 방식을 통해 어댑티브 솔버를 적용할 수 있다. 다만 Isaac Sim GUI나 기본 Python API에서 바로 설정할 수 있는 범위를 넘어가는 고급 기능일 수 있으므로, 물리 엔진의 확장 옵션이나 커스텀 플러그인 형태로 접근해야 한다.

#### 멀티 프로세스 / 멀티 GPU 시뮬레이션과 시간 동기

대규모 시나리오를 위해 멀티 프로세스나 멀티 GPU 분산 시뮬레이션을 구성할 때도 시간 동기가 중요하다. Isaac Sim은 멀티 GPU를 활용해 렌더링 성능을 높이거나, 클라우드 환경에서 여러 노드를 동원해 대규모 에이전트를 시뮬레이션할 수 있다. 이때는 단일 물리 엔진이 여러 컴퓨팅 리소스를 병렬로 활용하는지, 혹은 여러 물리 엔진 인스턴스를 별도로 띄워서 로봇 집단을 분산 시뮬레이션하는지에 따라 시간 동기 방안이 달라진다.

단일 엔진 기반의 멀티 GPU는 기본적으로 한 시뮬레이션 Clock을 사용하므로 문제가 적다. 반면 여러 엔진 인스턴스가 분산 환경에서 각각 독립적으로 물리를 계산한다면, ROS2 토픽 또는 별도의 동기화 메시지를 통해 $t\_\text{sim}$을 맞춰야 한다. 이 경우에는 ROS2의 /clock 토픽을 중앙에서 생성한 뒤 각 엔진에 주입하는 방식이 필요할 수 있다.

#### 추가 예시: 멀티 로봇 시뮬레이션에서의 시계 확인 (C++)

다음 예시는 Isaac Sim에서 여러 로봇을 동시에 시뮬레이션한다고 가정하고, C++ ROS2 노드가 /clock 토픽을 구독하여 시뮬레이션 시간을 확인하는 단순 코드다.

```cpp
#include <rclcpp/rclcpp.hpp>
#include <rosgraph_msgs/msg/clock.hpp>

class ClockSubscriber : public rclcpp::Node {
public:
    ClockSubscriber()
    : Node("clock_subscriber")
    {
        subscription_ = this->create_subscription<rosgraph_msgs::msg::Clock>(
            "/clock",
            10,
            std::bind(&ClockSubscriber::clockCallback, this, std::placeholders::_1)
        );
    }

private:
    void clockCallback(const rosgraph_msgs::msg::Clock::SharedPtr msg) {
        // Isaac Sim이 퍼블리시하는 시뮬레이션 시간
        auto sim_time = msg->clock;
        RCLCPP_INFO(this->get_logger(), "Simulation time: %d.%09d",
                    sim_time.sec, sim_time.nanosec);
    }

    rclcpp::Subscription<rosgraph_msgs::msg::Clock>::SharedPtr subscription_;
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<ClockSubscriber>());
    rclcpp::shutdown();
    return 0;
}
```

이 코드는 ROS2가 활성화된 상태에서 Isaac Sim을 실행하고, 시뮬레이션 Clock 퍼블리시가 설정되어 있으면, 그 시간을 실시간으로 출력한다. 시뮬레이션이 일시 정지되면 Timestamp가 갱신되지 않아 멈추게 되며, 재생 속도를 높이면 빠른 간격으로 시각이 증가하는 모습을 볼 수 있다.

#### 관련 파라미터 동적 조절

Isaac Sim은 실행 도중에도 스크립트나 UI를 통해 timeStepsPerSecond, realTimeScale, enableRealTime 등을 동적으로 변경할 수 있다. 시뮬레이션이 진행 중인 상태에서 타임스텝을 바꾸면, 그 즉시 프레임 계산 방식이 바뀌어 물리가 달라지므로, 테스트나 디버깅 용도로는 유용하지만 실제 환경을 모사하는 관점에서는 다소 위험할 수 있다. 따라서 시간 파라미터 변경으로 인해 로봇의 동작이 갑작스럽게 달라지는 부분을 주의해야 한다.

#### 디버깅과 로그 활용

Isaac Sim에서 시뮬레이션 시간과 속도를 제어하며 디버깅할 때, 로그(logging) 기능을 잘 활용하면 상황을 명확히 파악할 수 있다. 시뮬레이션이 특정 타이밍에 이상 동작을 보이거나, 로봇의 동작이 예기치 않은 방식으로 전개될 때, 시뮬레이션 시간을 기준으로 정보를 남겨놓으면 문제의 재현과 분석이 쉬워진다. Python 스크립트 또는 C++ 코드에서 $t\_\text{sim}$, $t\_\text{wall}$, 그리고 센서나 액추에이터의 상태를 함께 로그로 남기면, 추후에 비교 분석할 때 유용하다.

ROS2와 연동하는 경우에는 rosbag2를 사용하여 /clock 토픽과 함께 모든 센서 토픽들을 녹화할 수 있다. 이때, 녹화된 rosbag을 재생(ros2 bag play)할 때 시뮬레이션 시간이 어떻게 기록되어 있는지를 확인하면, Isaac Sim에서의 시간 축이 ROS2 메시지 Timestamp에 정확히 반영되었는지 검증 가능하다. Isaac Sim에서 물리 엔진을 일시 정지하면 /clock도 같이 멈추므로, bag 파일에도 시간이 멈춘 구간이 생길 수 있다.

#### 스텝 모드(Step by Step) 시뮬레이션

Isaac Sim을 활용하다 보면, 스텝 모드로 시뮬레이션을 한 프레임씩 진행하고 싶을 때가 있다. 예컨대 충돌 시점 직전의 상황을 자세히 분석한다거나, 특정 알고리즘이 한 프레임마다 어떤 연산을 하는지 추적하기 위해서다. 이를 위해서는 시뮬레이션을 Pause 상태로 전환한 뒤, 한 프레임씩 수동으로 ‘Step Physics’ 명령을 주면 된다. UI에서 제공하는 버튼을 누르거나, Python 스크립트로 다음과 같이 한 프레임씩 업데이트할 수 있다.

```python
import omni

async def step_physics_frames(num_frames=1):
    for _ in range(num_frames):
        await omni.kit.app.get_app().next_update_async()
```

num\_frames만큼 반복해서 호출하면, 각 반복마다 Isaac Sim 물리 엔진이 한 스텝을 계산하고, $t\_\text{sim}$이 $\Delta t\_\text{phys}$ 만큼 증가한다. 이 과정을 통해 디버깅 시점마다 상태를 확인하거나, 센서 데이터를 캡처하는 스크립트를 함께 호출할 수 있다.

#### 시뮬레이션 시간 리셋

Isaac Sim에서 장면을 재설정하거나 시나리오를 처음부터 다시 실행하고자 할 때, $t\_\text{sim}$을 0으로 되돌려 시작해야 할 수 있다. 일반적으로는 새로운 Scene을 불러오면 시뮬레이션 시간이 초기화되지만, 동일한 Stage 상에서 물리 엔진을 재시작(restart)하는 경우에는 별도의 리셋 호출이 필요하다.

ROS2 관점에서 시뮬레이션 Clock이 리셋된 경우, rosbag2 녹화 파일에는 Timestamp가 갑작스레 0으로 되돌아가는 구간이 생길 수 있다. 이는 나중에 분석할 때 한 개의 bag 파일 안에 두 개의 시나리오가 기록된 형태가 될 수도 있으므로, 장면을 완전히 재시작하면 bag 파일도 새롭게 시작하거나, 시뮬레이션 시간 리셋을 메시지로 기록해 둬야 혼동을 줄일 수 있다.

#### 실시간 제어 vs. 오프라인 시뮬레이션

Isaac Sim을 이용하는 패턴은 크게 두 가지로 나뉜다.

하나는 실제 로봇 제어에 준하는 실시간 시뮬레이션으로, 가능하면 $t\_\text{sim}$이 $t\_\text{wall}$과 동일하게 흘러가길 원한다. 예를 들어, 원격 지능형 로봇 운영을 위한 HIL(hardware-in-the-loop) 시뮬레이션을 구성하거나, 조이스틱 등의 입력 장치로 로봇을 직접 조종할 때에는 실시간성을 보장하는 것이 중요하다. 이 경우 realTimeScale=1.0 근처로 맞추고, $\Delta t\_\text{phys}$가 시스템 성능에 맞춰 안정적으로 유지되어야 한다.

다른 하나는 대량의 실험이나 학습 데이터를 생성하기 위한 오프라인 시뮬레이션이다. 이때는 시뮬레이션이 실제 시간보다 빠르든 느리든 상관없이, 가능한 한 많은 에피소드(episode)를 정확하게 수집하는 것이 목적이다. Isaac Sim 성능이 허락한다면 realTimeScale을 2 이상으로 높여 빠르게 시뮬레이션하고, CPU/GPU가 과부하에 걸리면 자동으로 실제 시간보다 느리게 돌아가더라도 $t\_\text{sim}$은 의도한 배속대로 증가한다. 이렇게 생성된 데이터는 학습 알고리즘에서 $t\_\text{sim}$을 기준으로 사용하게 되므로, 실제 시간과는 무관하게 일관된 타임스탬프가 유지된다.

#### Isaac Sim에서의 시간 및 속도 제어 팁

디테일한 물리 계산이 필요한 경우 $\Delta t\_\text{phys}$를 줄여 높은 해상도로 시뮬레이션하고, 전체 시뮬레이션을 빠르게 재생하기 위해 realTimeScale을 키울 수 있다.

시뮬레이션을 잠시 멈추고 싶은 경우 Pause 버튼 또는 enableRealTime 옵션을 끄는 방법을 쓸 수 있다. 이후 Step Physics 방식으로 프레임 단위로 진행하면서 문제 현상을 정밀하게 살펴보는 식이다.

ROS2 환경에서 시뮬레이션 시간을 감지할 때에는 반드시 /clock 토픽을 구독해서 시간 정보를 받아야 한다. 시스템 시간이 아니라 시뮬레이션 시간을 사용하도록 설정했는지, 혹은 둘을 혼용하고 있지는 않은지 확인해야 혼란이 줄어든다.

#### Isaac Sim과 ROS2의 TF 시간 관계

ROS2 로봇 시스템에서 TF(Transform) 메시지는 /tf, /tf\_static 토픽으로 퍼블리시되며, 이 메시지에도 헤더 타임스탬프가 포함된다. Isaac Sim에서 로봇 링크들의 위치나 자세를 TF로 퍼블리시하려면, 보통 ROS2 Bridge가 USD Stage 내의 각 링크를 관찰하고, 해당 위치 정보와 $t\_\text{sim}$을 바탕으로 TF 메시지를 생성한다.

이 과정에서 시뮬레이션 시간이 제대로 입력되지 않으면, TF 메시지의 Timestamp가 실제 로봇에 비해 크게 앞서거나 뒤처져서 TF 트리에서 에러가 발생할 수 있다. RViz2 같은 시각화 도구를 사용하면, “TF 메시지 timestamp not in cache” 또는 “Transform from base\_link to something is not available” 같은 오류가 나타날 수 있다. 이는 주로 시뮬레이션 시간을 ROS2가 잘못 인식한 경우거나, Isaac Sim이 /clock 토픽을 보내지 않거나, TF 메시지를 퍼블리시할 때 헤더를 올바르게 설정하지 않은 경우 발생한다.

#### 고급 API: OmniGraph와 확장 플러그인

Isaac Sim은 OmniGraph라는 노드 기반 시스템을 통해 로직을 시각적으로 구성할 수 있다. OmniGraph 노드에는 시간 정보를 처리하거나, 이벤트 시점에 시뮬레이션 타임스텝과 상호 작용하는 블록들이 존재한다. 이를 통해 특정 이벤트(예: 충돌 감지, 센서 트리거 발생) 시점에 시뮬레이션을 일시 정지하거나, 타임스텝을 변경하는 고급 흐름 제어가 가능하다.

또한 Isaac Sim의 확장(Extension) 형태로 Python/C++ 코드를 작성해 더 세밀하게 물리 엔진 및 시뮬레이션 시간을 다룰 수 있다. 이러한 확장은 Isaac Sim 내장 API와 PhysX API를 직접 호출할 수 있도록 노출하므로, 사용자가 원하는 커스텀 물리 파이프라인이나 타임 매니지먼트 로직을 구축할 수 있다.

#### 예시: Octave 기반 데이터 후처리

Isaac Sim과 ROS2로 시뮬레이션을 진행한 뒤, /clock 및 센서 데이터가 함께 rosbag2에 기록되었다고 하자. 이를 CSV 형식으로 변환한 후, Octave에서 시간에 따른 로봇 조인트 각도 변화를 분석하는 간단한 예시를 살펴본다.

```octave
% 가정: bag2->csv 변환 과정에서 time, joint1, joint2 ... 와 같은 컬럼이 만들어졌다고 하자
data = csvread('robot_joint_states.csv');

% 첫 번째 열이 시뮬레이션 시간(초)이라고 가정
t_sim = data(:, 1);
joint1 = data(:, 2);
joint2 = data(:, 3);

% 간단히 그래프를 그려본다
plot(t_sim, joint1, 'r', t_sim, joint2, 'b');
xlabel('Simulation time (s)');
ylabel('Joint angles (rad)');
title('Joint State over Simulation Time');
legend('joint1','joint2');
```

이 스크립트는 시뮬레이션 시간이 0초부터 어떻게 변했는지, 그리고 각 조인트가 어떤 경로를 그렸는지를 시각적으로 확인하게 해준다. 실제 시간인 $t\_\text{wall}$과는 별개로 시뮬레이션 Clock 기준의 Timestamp가 저장되어 있기 때문에, 시뮬레이션이 일시 정지된 구간, 배속이 달라진 구간 등도 그래프에서 그대로 반영된다.

***

Isaac Sim에서 시뮬레이션 시간과 속도 제어는 물리 엔진의 타임스텝과 배속 설정, 그리고 ROS2와의 Clock 동기화로 구성된다. 시뮬레이션 자체는 실제 시간에 종속되지 않으며, 시스템 성능과 설정에 따라 달라지는 계산 부하에 의해 다양하게 동작할 수 있다. 이러한 유연성 덕분에 Isaac Sim은 실시간 제어나 대규모 오프라인 시뮬레이션, 디버깅 등 여러 목적에 부합하는 방식으로 사용할 수 있다.

#### 결정론적(Deterministic) 시뮬레이션과 시드(Seed) 관리

Isaac Sim의 물리 계산은 여러 알고리즘적 요소와 하드웨어 병렬 연산의 영향으로 인해 완전한 결정론성을 보장하지 않을 수 있다. 특히 충돌 감지, 랜덤 샘플링(예: 마찰계수, 노이즈 추가), GPU 병렬 연산 등에 따라 매 시뮬레이션마다 아주 미세한 차이가 발생할 수 있다. 그러나 연구 및 테스트 목적으로 “동일한 입력에 대해 동일한 결과”가 필요하다면, 가능한 범위 내에서 결정론적 시뮬레이션을 구성해야 한다.

대표적인 방법으로는 Isaac Sim과 물리 엔진(PhysX)의 랜덤 시드(seed) 값을 고정하는 것이 있다. Isaac Sim 확장(Extension) 설정이나, PhysX 고급 설정에서 랜덤 시드를 명시적으로 지정해두면, 같은 하드웨어와 같은 환경에서 재실행했을 때 최대한 동일한 시뮬레이션 결과를 재현할 확률이 높아진다. 다만 GPU 연산의 미세한 순서 변화나 병렬 스레드 스케줄링에 따른 오차까지 완전히 제거하기는 어렵다.

시뮬레이션 시간이 동일하게 진행되더라도, 내부 엔진이 실제로 충돌을 계산하는 순서가 달라지면 수치 해석 결과가 달라질 수 있다. 이를 최소화하기 위해 타임스텝 $\Delta t\_\text{phys}$를 작게 유지하고, 병렬 처리 옵션을 제한하거나 CPU 기반 설정을 쓰는 등 다양한 제한을 걸 수도 있지만, 이는 시뮬레이션 속도를 떨어뜨릴 수 있다.

#### 물리 엔진 내부 시간 계산과 Isaac Sim API

PhysX를 비롯한 물리 엔진은 내부적으로 “서브스텝(substep)”이라는 개념을 사용하기도 한다. 한 프레임($\Delta t\_\text{phys}$) 안에서 충돌 감지나 적분(Integration)을 여러 번 나누어 수행해 정확도를 높이거나 안정성을 확보하는 기법이다. 예를 들어 한 프레임이 1/60초라면, 내부적으로 이를 2\~4번의 서브스텝으로 세분화해서 더 촘촘히 충돌을 탐지한다.

Isaac Sim의 Python API나 GUI를 통해 설정할 수 있는 timeStepsPerSecond, realTimeScale 등은 물리 엔진 전체 프레임에 대한 설정이므로, 서브스텝의 개수나 적분 알고리즘은 별도의 파라미터를 통해 조정한다. 서브스텝을 많이 주면 충돌이나 빠른 움직임에 대한 정확도가 올라가지만, 당연히 계산 부하가 증가해 $t\_\text{wall}$ 대비 $t\_\text{sim}$이 느려질 수 있다.

#### 이벤트 기반(Discrete Event) 시뮬레이션 고려

일반적으로 Isaac Sim은 연속 시간 기반 물리 시뮬레이션을 수행한다. 그러나 일부 응용에서는 로봇의 충돌이나 센서 트리거 같은 이벤트 중심의 논리를 중요하게 다룬다. 이때 이벤트가 발생하는 시점에만 시뮬레이션을 세밀하게 계산하고, 그 외 시점에는 생략하거나 크게 스킵(skipped)하고 싶을 수 있다.

Isaac Sim에서는 이러한 이벤트 기반 스킵을 직접 지원하지는 않지만, 스크립트나 OmniGraph 논리를 통해 “특정 이벤트가 감지될 때까지 빠르게(또는 건너뛰며) 진행하라” 같은 로직을 구성할 수 있다. 예컨대 다음과 같은 순서를 고려해 볼 수 있다.

* 로봇이 일정 거리 내로 접근하기 전까지는 큰 타임스텝(예: 1/10초)으로 시뮬레이션.
* 특정 조건(거리 임계값 이하)이 충족되는 순간, 타임스텝을 1/100초로 변경해 더 세밀하게 물리를 계산.
* 이벤트가 종료되면 다시 타임스텝을 복원.

이와 같은 접근은 실제 물리 엔진에서 정확도가 유지되는 한도에서만 가능하다. 너무 급격하게 타임스텝을 바꾸면 수치 해석적 불연속이 생길 수 있어 주의가 필요하다.

#### 실시간 계측(Profiling)과 Real-time Factor 확인

Isaac Sim은 시뮬레이션을 진행하면서, 현재 실제 프레임 렌더링/물리 엔진 처리 시간이 얼마나 걸리는지 UI 상단 혹은 “Profiler” 창에서 확인할 수 있다. 이 값이 크다면, $t\_\text{wall}$이 $t\_\text{sim}$보다 훨씬 빠르게 증가하고 있다는 의미이므로, 실시간성이 떨어지고 있음을 짐작할 수 있다.

$\text{Real-time Factor(RTF)}$라는 개념을 정의해 본다면,

$$
\begin{align} \text{RTF} = \frac{\Delta t\_\text{sim}}{\Delta t\_\text{wall}}  \end{align}
$$

한 프레임 동안 $t\_\text{sim}$이 증가한 양과 실제로 걸린 벽시계 시간($t\_\text{wall}$)의 비율을 말한다. 예를 들어 RTF가 0.5면 시뮬레이션 시간이 실제 시간보다 2배 느리게 흐르고, RTF가 2.0이면 2배 빠르게 흐른다. Isaac Sim에서 realTimeScale = 1.0으로 설정했더라도, 하드웨어가 처리하지 못하면 실제 RTF가 1 미만으로 떨어질 수 있다. 반대로 장면이 간단하고 GPU 성능이 뛰어나다면 1보다 훨씬 큰 값이 나올 수도 있다.

이를 모니터링하면서 원하는 RTF를 얻도록 장면 최적화, 타임스텝 조정, 물리 파라미터 튜닝 등을 진행한다. 특히 실시간 로봇 제어(HIL 시뮬레이션)에서는 RTF가 1 근처를 유지하는 것이 중요하다. 너무 낮으면 제어 신호가 제때 전달되지 않고, 너무 높으면 실제 로봇보다 빠른 세계에서 제어 루프가 동작하게 되므로 오차가 발생한다.

#### 네트워크 지연과 분산 시뮬레이션

Isaac Sim은 로컬 PC 내부에서만 시뮬레이션을 수행할 수도 있지만, 클라우드 서버에서 시뮬레이션을 돌리고 원격지에서 ROS2 메시지를 주고받기도 한다. 이 경우 네트워크 지연(latency)에 의해 $t\_\text{sim}$과 분산 노드의 $t\_\text{wall}$ 사이에 추가 오차가 생길 수 있다. ROS2 QoS 설정 또는 /clock 토픽 버퍼링 등을 통해 이 지연을 최소화하거나, 동기화 알고리즘을 적용해야 한다.

여러 대의 Isaac Sim 프로세스가 서로 다른 물리 세계를 시뮬레이션하면서 동일한 ROS2 네트워크에 연결될 수도 있는데, 이때 각 프로세스는 독립된 시뮬레이션 시간을 가진다. 만일 로봇 그룹 간 상호 작용이 필요한 시나리오라면, 하나의 중앙 시뮬레이션 Clock을 기준으로 여러 프로세스가 동작하도록 설계해야 한다. 그렇지 않으면 토픽 간 타임스탬프가 서로 뒤섞이거나, 메시지가 과거 시점으로 도착하는 등 혼란이 발생한다.

#### Reinforcement Learning(RL) 시나리오에서의 시간

Isaac Sim은 강화학습 시뮬레이션 환경을 구성하는 데도 자주 활용된다. RL 알고리즘 관점에서, 시뮬레이션 시간은 곧 에이전트(agent)가 경험하는 에피소드의 타임스텝이다. Isaac Sim에서 realTimeScale을 크게 높여 에피소드를 빠르게 생성할 수 있으며, 물리엔진이 감당 가능한 범위 내에서 $\Delta t\_\text{phys}$를 유지하면 더욱 정확한 물리를 얻을 수 있다.

RL에서는 종종 “프레임 스킵(frame skip)” 기법을 사용해 에이전트가 매 프레임마다 행동을 내놓지 않고, 몇 프레임에 한 번씩 행동을 수행하도록 한다. Isaac Sim에서 스크립트나 OmniGraph 노드로 이를 구성할 수 있으며, 보상 함수 역시 시뮬레이션 시간(또는 물리 스텝 수)에 기반해 정의하게 된다.

이처럼 RL 시나리오에서는 실제 시간은 전혀 중요하지 않으며, $t\_\text{sim}$을 기준으로 보상과 상태 변화를 누적하는 것이 핵심이다. 오프라인 시뮬레이션으로 수백\~수천 시간을 빠르게 쌓은 뒤, 실제 로봇으로 옮길 때만 실시간 혹은 실제 벽시계 시간을 고려하면 된다.

\--- 노트

Isaac Sim에서의 시뮬레이션 시간(Clock) 및 속도 제어는 단순한 배속 설정이 아니라, 물리 엔진의 타임스텝, ROS2와의 시간 동기, 분산 시뮬레이션, 네트워크 지연, 결정론성 등 다양한 요소가 얽혀 있다. 사용자는 시뮬레이션 목표(실시간 제어, 오프라인 대량 실험, 정밀 물리 등)에 따라 적절한 설정을 선택해야 하며, 성능 모니터링과 디버깅 로그를 통해 시간을 세밀하게 관리할 수 있다.
