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\Omega가 1에 가까울 때 \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
| 특성 | SLERP | LERP |
|---|---|---|
| 단위 노름 | 보존 | 미보존 |
| 일정한 각속도 | 보장 | 보장 안 함 |
| 측지선 | 예 | 아니오 |
| 계산 비용 | 중간 | 낮음 |
11.2 SLERP vs. NLERP
| 특성 | SLERP | NLERP |
|---|---|---|
| 단위 노름 | 보존 | 보존 |
| 일정한 각속도 | 보장 | 보장 안 함 |
| 측지선 | 예 | 예 (같은 경로) |
| 계산 비용 | 중간 | 낮음 |
NLERP는 SLERP와 같은 경로를 따르지만 매개변수화가 다르다. 일정한 각속도가 필요하면 SLERP, 그렇지 않으면 NLERP가 선택된다.
12. 실시간 구현의 고려
12.1 계산 예산
게임 엔진 등 실시간 시스템에서 SLERP는 매 프레임마다 많이 호출될 수 있다. 각 호출의 비용이 중요하다.
12.2 최적화 전략
- 중복 계산 방지 (사전 계산)
- SIMD 활용
- 룩업 테이블 사용
- 작은 각도에서 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