10.30 SLERP의 구현과 수치적 안정성

1. SLERP 구현의 중요 고려 사항

SLERP의 수학적 공식은 단순하지만, 수치적으로 안정적인 구현을 위해서는 몇 가지 중요한 고려 사항이 있다. 부호 이중성 처리, 작은 각도에서의 수치 안정성, 효율적 계산 등이 주요 이슈이다.

본 절에서는 SLERP의 견고한 구현을 위한 단계별 알고리즘과 고려 사항을 다룬다.

2. 기본 SLERP 알고리즘

2.1 수학적 공식

\mathrm{SLERP}(\mathbf{q}_0, \mathbf{q}_1, t) = \frac{\sin((1-t)\Omega)}{\sin\Omega}\mathbf{q}_0 + \frac{\sin(t\Omega)}{\sin\Omega}\mathbf{q}_1

여기서 \cos\Omega = \mathbf{q}_0\cdot\mathbf{q}_1이다.

2.2 단순 구현 (수치적 문제 있음)

function slerp_naive(q0, q1, t):
    cos_omega = dot(q0, q1)
    omega = acos(cos_omega)
    sin_omega = sin(omega)
    
    a = sin((1 - t) * omega) / sin_omega
    b = sin(t * omega) / sin_omega
    
    return a * q0 + b * q1

이 구현은 수치적 문제를 가지며, 실제 시스템에서 사용하기에 부적절하다.

3. 수치적 문제와 해결

3.1 문제 1: 부호 이중성

쿼터니언 \mathbf{q}-\mathbf{q}가 같은 회전을 나타내므로, 보간이 긴 경로를 따를 수 있다. 이는 시각적으로 부자연스러운 회전을 만든다.

3.2 해결 1: 부호 보정

내적의 부호를 확인하고, 음수이면 한 쿼터니언의 부호를 반전한다.

cos_omega = dot(q0, q1)
if cos_omega < 0:
    q1 = -q1
    cos_omega = -cos_omega

이후 \cos\Omega가 항상 양수이므로 \Omega \in [0, \pi/2]가 된다. 이는 짧은 경로를 보장한다.

3.3 문제 2: 작은 각도에서의 수치 불안정성

\Omega가 0에 가까우면(즉, 두 쿼터니언이 거의 같으면) \sin\Omega가 0에 가까워 분모가 매우 작아진다. 이는 수치 오차를 증폭시킨다.

3.4 해결 2: LERP로의 대체

작은 각도에서는 LERP와 SLERP의 차이가 무시할 수 있으므로, LERP로 대체하고 정규화한다.

if cos_omega > 0.9995:
    # 작은 각도 - LERP 사용
    result = (1 - t) * q0 + t * q1
    return normalize(result)

임계값 0.9995\Omega < 1.8°에 해당한다. 이 각도 이하에서는 LERP와 SLERP의 차이가 10^{-5} 이하로 무시할 수 있다.

3.5 문제 3: \arccos의 정확도

\cos\Omega1에 가까울 때 \arccos의 도함수가 발산하여 수치 오차가 크다.

3.6 해결 3: \mathrm{atan2} 사용

\arccos 대신 \mathrm{atan2}를 사용하여 더 안정적으로 \Omega를 계산할 수 있다.

sin_omega_approx = sqrt(1 - cos_omega * cos_omega)
omega = atan2(sin_omega_approx, cos_omega)

이는 \arccos보다 수치적으로 안정적이다.

3.7 문제 4: 부동 소수점 오차

부동 소수점 연산으로 \cos\Omega가 정확히 \pm 1을 약간 벗어날 수 있다. 이 경우 \arccos가 NaN을 반환한다.

3.8 해결 4: 클램핑

\cos\Omega[-1, 1]로 클램핑한다.

cos_omega = max(-1, min(1, cos_omega))

4. 견고한 SLERP 구현

위의 문제와 해결을 통합한 견고한 SLERP 알고리즘은 다음과 같다.

function slerp(q0, q1, t):
    # 1. 내적 계산
    cos_omega = dot(q0, q1)
    
    # 2. 부호 보정 (짧은 경로 선택)
    if cos_omega < 0:
        q1 = -q1
        cos_omega = -cos_omega
    
    # 3. 작은 각도 처리 (LERP로 대체)
    if cos_omega > 0.9995:
        result = (1 - t) * q0 + t * q1
        return normalize(result)
    
    # 4. 안전을 위한 클램핑
    cos_omega = min(1.0, cos_omega)
    
    # 5. 각도 계산 (atan2 사용 권장)
    sin_omega_sq = 1 - cos_omega * cos_omega
    sin_omega = sqrt(sin_omega_sq)
    omega = atan2(sin_omega, cos_omega)
    
    # 6. 계수 계산
    inv_sin_omega = 1.0 / sin_omega
    a = sin((1 - t) * omega) * inv_sin_omega
    b = sin(t * omega) * inv_sin_omega
    
    # 7. 결과 조합
    return a * q0 + b * q1

이 알고리즘은 모든 일반적 경우에서 안정적으로 작동한다.

5. 성능 최적화

5.1 사전 계산

같은 \mathbf{q}_0\mathbf{q}_1에 대해 여러 t 값으로 보간하는 경우, \Omega\sin\Omega를 한 번만 계산한다.

function precompute_slerp(q0, q1):
    cos_omega = dot(q0, q1)
    if cos_omega < 0:
        q1 = -q1
        cos_omega = -cos_omega
    
    if cos_omega > 0.9995:
        return (q0, q1, None, None)  # LERP 사용
    
    omega = acos(cos_omega)
    sin_omega = sin(omega)
    return (q0, q1, omega, sin_omega)

function evaluate_slerp(precomputed, t):
    q0, q1, omega, sin_omega = precomputed
    if omega is None:
        # LERP
        return normalize((1 - t) * q0 + t * q1)
    
    a = sin((1 - t) * omega) / sin_omega
    b = sin(t * omega) / sin_omega
    return a * q0 + b * q1

5.2 SIMD 최적화

쿼터니언의 4 성분 연산은 SIMD 명령으로 가속된다. Eigen 등의 라이브러리가 자동으로 활용한다.

5.3 룩업 테이블

t의 값이 미리 알려진 경우(예: 애니메이션의 프레임), \sin(t\Omega)를 룩업 테이블에 저장하여 삼각 함수 호출을 피할 수 있다.

6. 특수한 경우의 처리

6.1 동일한 쿼터니언

\mathbf{q}_0 = \mathbf{q}_1인 경우 \cos\Omega = 1이므로, 작은 각도 분기가 활성화되고 결과가 \mathbf{q}_0(또는 \mathbf{q}_1)이 된다. 이는 올바른 결과이다.

6.2 대척점

\mathbf{q}_1 = -\mathbf{q}_0인 경우 \cos\Omega = -1이다. 부호 보정 후 \cos\Omega = 1이 되고, 작은 각도 분기가 활성화된다. 그러나 이 경우 \mathbf{q}_0-\mathbf{q}_0이 같은 회전을 나타내므로 보간이 의미 없다(항상 같은 회전).

6.3 정확히 대척점 (부호 보정 후)

드문 경우지만, 부호 보정 후에도 두 쿼터니언이 여전히 대척점에 가까울 수 있다(즉, 회전 각이 180° 근처). 이 경우 측지선이 여러 개 존재할 수 있어 보간 결과가 모호할 수 있다.

6.4 완전한 180도 차이

\mathbf{q}_0\mathbf{q}_1이 정확히 180도 차이를 나타내면(\cos\Omega = 0), 측지선은 두 대원 중 하나로 선택될 수 있다. 이 경우 SLERP 공식이 여전히 작동하지만, 구체적으로 어느 경로가 선택되는지는 부호 관례에 따른다.

7. 테스트와 검증

7.1 끝점 검증

assert slerp(q0, q1, 0) == q0
assert slerp(q0, q1, 1) == q1  # 또는 -q1 (부호 보정 후)

7.2 단위 노름 검증

모든 t에 대해 결과가 단위 쿼터니언인지 확인한다.

for t in range(0, 1, 0.01):
    q = slerp(q0, q1, t)
    assert abs(norm(q) - 1) < 1e-10

7.3 일정한 각속도 검증

t가 균일하게 증가할 때 \mathbf{q}_0로부터의 각도가 균일하게 증가하는지 확인한다.

for t in range(0, 1, 0.01):
    q = slerp(q0, q1, t)
    angle = 2 * acos(abs(dot(q0, q)))
    # angle이 t에 선형적으로 증가해야 함

7.4 역방향 보간

\mathrm{SLERP}(\mathbf{q}_0, \mathbf{q}_1, t) = \mathrm{SLERP}(\mathbf{q}_1, \mathbf{q}_0, 1 - t)가 성립해야 한다.

7.5 대칭성

\mathrm{SLERP}(\mathbf{q}_0, \mathbf{q}_1, 0.5)\mathbf{q}_0\mathbf{q}_1에서 같은 거리에 있는지 확인한다.

8. 알고리즘의 변형

8.1 짧은 경로 vs. 긴 경로

일반적으로 짧은 경로를 선택한다. 특정 응용에서 긴 경로가 필요하면 부호 보정을 생략한다.

8.2 시간 매개변수의 비선형

매개변수 t를 비선형 함수로 변환하여 가속/감속 효과를 만들 수 있다.

function slerp_with_ease_in_out(q0, q1, t):
    # t를 ease-in-out으로 변환
    t_eased = 3 * t * t - 2 * t * t * t
    return slerp(q0, q1, t_eased)

8.3 경로 조절

SLERP는 측지선을 따르지만, 제어점을 추가하여 경로를 조절할 수 있다. 이는 Squad(10.31)로 일반화된다.

9. 라이브러리의 SLERP

9.1 Eigen

Eigen::Quaterniond q = q0.slerp(t, q1);

Eigen의 slerp 함수는 부호 보정과 작은 각도 처리를 포함한다.

9.2 SciPy

from scipy.spatial.transform import Slerp
slerp_obj = Slerp([0, 1], Rotation.from_quat([q0, q1]))
q = slerp_obj(t).as_quat()

9.3 ROS의 tf2

tf2::Quaternion q = q0.slerp(q1, t);

이러한 라이브러리의 구현은 대체로 견고하지만, 세부 사항은 라이브러리마다 다르다.

10. 부동 소수점 정밀도의 영향

10.1 단일 정밀도 (float)

32-bit float에서는 약 7 자리수의 정확도를 가진다. 대부분의 그래픽스 응용에서 충분하다.

10.2 더블 정밀도 (double)

64-bit double에서는 약 15 자리수의 정확도를 가진다. 자세 추정과 정밀 응용에 권장된다.

10.3 임계값 조정

정밀도에 따라 작은 각도 임계값을 조정한다. 더블 정밀도에서는 더 작은 임계값을 사용할 수 있다.

11. SLERP와 다른 보간법의 비교

11.1 SLERP vs. LERP

특성SLERPLERP
단위 노름보존미보존
일정한 각속도보장보장 안 함
측지선아니오
계산 비용중간낮음

11.2 SLERP vs. NLERP

특성SLERPNLERP
단위 노름보존보존
일정한 각속도보장보장 안 함
측지선예 (같은 경로)
계산 비용중간낮음

NLERP는 SLERP와 같은 경로를 따르지만 매개변수화가 다르다. 일정한 각속도가 필요하면 SLERP, 그렇지 않으면 NLERP가 선택된다.

12. 실시간 구현의 고려

12.1 계산 예산

게임 엔진 등 실시간 시스템에서 SLERP는 매 프레임마다 많이 호출될 수 있다. 각 호출의 비용이 중요하다.

12.2 최적화 전략

  1. 중복 계산 방지 (사전 계산)
  2. SIMD 활용
  3. 룩업 테이블 사용
  4. 작은 각도에서 LERP 대체

12.3 병렬화

여러 SLERP 계산을 병렬로 수행할 수 있다. GPU나 멀티코어 CPU에서 효율적이다.

13. 시각화를 통한 검증

SLERP의 정확성을 시각화로 확인할 수 있다.

13.1 D 회전 시각화

S^2 상의 단위 벡터(예: 기준 축 (1, 0, 0))를 SLERP된 쿼터니언으로 회전한 결과를 그린다. t가 증가함에 따라 벡터가 구면 위의 매끄러운 경로를 그려야 한다.

13.2 각속도 플롯

t에 대한 회전 각 그래프가 직선이어야 한다. 이는 일정한 각속도의 증거이다.

14. 결론

SLERP의 견고한 구현은 부호 보정, 작은 각도 처리, 수치 안정성 등 여러 요소를 고려해야 한다. 위에서 제시한 알고리즘은 대부분의 응용에서 잘 작동하며, 검증된 라이브러리(Eigen, SciPy 등)의 사용이 권장된다. 성능이 매우 중요한 응용에서는 NLERP가 대안으로 고려될 수 있다.

15. 참고 문헌

  • Shoemake, K. (1985). “Animating Rotation with Quaternion Curves.” SIGGRAPH Computer Graphics, 19(3), 245–254.
  • Kuipers, J. B. (1999). Quaternions and Rotation Sequences. Princeton University Press.
  • Diebel, J. (2006). “Representing Attitude: Euler Angles, Unit Quaternions, and Rotation Vectors.” Stanford University Technical Report.
  • Sola, J. (2017). “Quaternion Kinematics for the Error-State Kalman Filter.” arXiv:1711.02508.
  • Watt, A., & Watt, M. (1992). Advanced Animation and Rendering Techniques. Addison-Wesley.

version: 1.0