포스트

Slerp

구면 선형 보간에 대해서 공부하고 유니티의 Slerp()에 대해서 정리했습니다.

Slerp

Unity의 Lerp()를 통해서 부드러운 이동, 회전, 애니메이션 등을 구현할 수 있다.
하지만 회전의 경우에는 문제를 보이는 경우가 있다.

이럴 때는 Lerp() 대신 Slerp()를 사용할 수 있다.
Slerp는 Spherically Linear Interpolation으로 구면 선형 보간이라고 한다.

구면 선형 보간은 두 회전 상태를 따라 일정한 속도로 회전을 부드럽게 연결하는 방법이다.



회전에서의 선형 보간의 문제점

지금부터 설명하는 내용들은 구체에서 원으로 차원을 낮춰서 설명하도록 하겠다.

벡터 $P_1$과 $P_2$가 존재하고, $P_1$을 $P_2$로 선형 보간하는 중이라고 해보자.
그렇다면 $P_1$과 $P_2$의 아핀 결합으로 생기는 결과 $n$은 $P_1$과 $P_2$로 만들어지는 직선 위에 생긴다.

여기서 일정한 속도의 부드러운 회전을 의도해서 구현하려고 한다고 하자.
그렇다면 $P_1$과 $P_2$ 사이를 보간하는 회전들이 일정한 간격을 유지하면서 보간해야 한다.
즉, 벡터들에 의해 나눠지는 선분들은 $a = b = c = d$를 만족해야 한다.

하지만 아핀 결합으로 생성된 벡터들을 정규화해서 호를 그려보면 벡터들에 의해 나눠지는 호의 길이가 일정하지 않다.

따라서 이는 구를 따라서 회전하는 것이 아닌 구를 가로 질러서 회전하므로 회전 속도가 일정하지 않음을 의미한다.
이를 해결하기 위해 구면 선형 보간을 사용한다.



구면 선형 보간의 유도

위에서 선형 보간이 회전에서 어떤 문제를 가지는지 보이면서 문제를 해결할 수 있는 방법에 대해서도 힌트를 남겼다.
$P_1$과 $P_2$의 아핀 결합 결과로 나온 벡터를 정규화해서 나눈 호의 길이가 동일해야 한다.


단위 벡터 $P_1$을 $x$축 위에 배치하고, $P_2$는 $P_1$에서 $\theta$만큼 회전했다고 하자.

$P_1 = (1,\; 0)$

$P_2 = (cos\theta,\; sin\theta)$

그렇다면 $P_1$과 $P_2$의 아핀 결합으로 생성되는 보간점 $r$은 원호를 따라 $t$만큼 이동한 것이다.

$r = (cos(t\cdot \theta),\; sin(t\cdot \theta))$


다시 $P_1$과 $P_2$의 아핀 결합으로 $r$이 생성되기 때문에 $r$은 다음과 같이 정의할 수 있다.

$r = nP_1 + mP_2$

즉, 위에서 구한 값을 이용하여 계산하면 다음과 같다.

$ n(1 ,\; 0) + m(cos\theta ,\; sin\theta) = (cos(t \cdot \theta),\; sin(t \cdot \theta))$

여기서 $m$을 구하면 다음과 같다.

$m\cdot sin\theta = sin(t \cdot \theta)$

$\therefore m = \cfrac{sin(t \cdot \theta)}{sin\theta}$

그리고 $m$을 이용해서 $n$을 구하면 다음과 같다.
포인트는 삼각함수의 덧셈 정리다.

$n + mcos\theta = cos(t \cdot \theta)$

$n = cos(t \cdot \theta) - mcos\theta$

$\;\;\;= cos(t \cdot \theta) - \cfrac{sin(t \cdot \theta)}{sin\theta}\cdot cos\theta$

$\;\;\;= \cfrac{cos(t \cdot \theta) \cdot sin\theta - sin(t \cdot \theta) \cdot cos\theta}{sin\theta}$

$\;\;\;= \cfrac{sin(\theta - t\theta)}{sin\theta} = \cfrac{sin((1-t)\theta)}{sin\theta}$

$\therefore n = \cfrac{sin((1-t)\theta)}{sin\theta}$


따라서 $P_1$과 $P_2$의 구면 선형 보간식은 다음과 같이 만들 수 있다.

$r = \cfrac{sin((1-t)\theta)}{sin\theta}P_1 + \cfrac{sin(t \cdot \theta)}{sin\theta}P_2$



구면 선형 보간의 구현

앞에서 구한 다음의 식을 코드로 나타내면 된다.

$r = \cfrac{sin((1-t)\theta)}{sin\theta}P_1 + \cfrac{sin(t \cdot \theta)}{sin\theta}P_2$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Quaternion Slerp(Quaternion begin, Quaternion end, double t)
{
    // 비율은 구간 [0, 1]으로 제한한다.
    t = Clamp01(t);

    // 두 회전 벡터를 정규화한다.
    begin = Normalize(begin);
    end = Normalize(end);

    // 두 벡터를 내적하여 얻은 벡터를 정규화한다.
    double d = std::clamp(Dot(begin, end), -1.0, 1.0);

    // 벡터가 뒤집혀 있으면 다시 뒤집는다.
    if(d < 0.0)
    {
        b *= -1.0;
        d = -d;
    }

    // 여기서부터 위의 공식이 사용된다.
    double theta = std::acos(d);
    double sin_theta = std::sin(theta);

    double w1 = std::sin((1.0 - t) * theta) / sin_theta;
    double w2 = std::sin(t * theta) / sin_theta;

    return Normalize(begin * w1 + end * w2);
}



Unity의 구면 선형 보간

Unity의 Quaternion은 구면 선형 보간을 하는 Slerp() 메서드를 제공한다.
또한 Vector3Slerp() 메서드를 제공한다.



마무리

Slerp()는 3차원 공간의 부드럽고 일정한 회전을 구현하는 데 사용된다.
Lerp()보다 무거워서 조금 느리긴 하지만 더 부드러운 회전이 가능하다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.