오일러 각: eulerAngles 가 넣은 값과 다르게 나오고 짐벌 락이 생기는 이유
Esc

    오일러 각: eulerAngles 가 넣은 값과 다르게 나오고 짐벌 락이 생기는 이유

    –Unity기초

    Inspector 에서 Rotation 의 X 를 100 으로 넣고 스크립트로 transform.eulerAngles 를 읽어 보면 (80, 180, 180) 이 나옵니다. eulerAngles.x 를 매 프레임 1 씩 올리는 코드는 90 근처에서 멈춰 버립니다.

    둘 다 오일러 각의 성질 때문입니다. 오일러 각은 회전 하나를 “z 로 몇 도, x 로 몇 도, y 로 몇 도” 처럼 세 번 차례로 도는 것으로 적습니다. 이렇게 적으면 같은 자세를 여러 숫자 조합으로 쓸 수 있고, 특정 각도에서는 두 숫자가 같은 일을 해서 축 하나를 잃습니다.

    유니티의 오일러 각은 z → x → y 순서로 월드 축을 기준으로 돕니다. 같은 자세를 쓰는 조합이 여럿이라, 유니티는 넣은 값이 아니라 자기가 고른 조합을 돌려줍니다. x 가 ±90 이면 y 와 z 가 같은 축으로 돌아서 회전 방향 하나를 잃습니다(짐벌 락). 그래서 회전은 내부에서 쿼터니언으로 들고 있습니다.

    오일러 각은 세 번 차례로 도는 것이다

    유니티는 방향을 세 축으로 잽니다. 축은 방향을 재는 기준선이고, x 가 오른쪽, y 가 위, z 가 앞입니다. “y 축으로 돈다” 는 위아래로 선 막대를 꽂고 팽이처럼 돈다는 뜻입니다. 돌 때 움직이지 않는 이 막대를 회전축이라 합니다. 꼬치에 꽂은 고기를 돌리면 꼬치가 회전축입니다. 얼마나 돌았는지는 도(°) 로 셉니다. 한 바퀴가 360°, 반 바퀴가 180°, 직각이 90° 입니다. 각도가 양수일 때 어느 쪽으로 도는지는 유니티에서 이렇게 정해져 있습니다(왼손 엄지를 축의 + 쪽으로 세우면 나머지 손가락이 감기는 쪽).

    회전 양수로 돌리면
    x 축으로 앞이 아래로 숙여집니다
    y 축으로 위에서 내려다볼 때 시계 방향. 앞이 오른쪽으로 돕니다
    z 축으로 뒤에서 볼 때 반시계 방향. 위가 왼쪽으로 기웁니다

    Inspector 의 Rotation 칸에 넣는 세 숫자가 오일러 각 (x, y, z) 이고, 이렇게 읽습니다. 적는 순서는 x, y, z 이지만 도는 순서는 z, x, y 입니다. 여기서 축은 월드 축입니다. 장면 전체에 고정된 x · y · z 라서 물체가 돌아도 그대로입니다.

    순서 도는 축 사람 말로
    1 월드 z 축으로 z 도 고개를 옆으로 갸웃 (roll)
    2 월드 x 축으로 x 도 고개를 숙이거나 듦 (pitch)
    3 월드 y 축으로 y 도 좌우로 돌아봄 (yaw)

    x 를 양수로 돌리면 앞이 아래로 숙여집니다. x 가 90 이면 바로 아래를, 100 이면 아래를 지나 조금 뒤를 봅니다.

    물체가 어느 쪽을 보고 어느 쪽이 위인지를 자세라 합니다. 순서가 정해져 있어야 하는 이유는, 회전은 순서를 바꾸면 결과가 달라지기 때문입니다. “앞으로 숙인 다음 왼쪽으로 돌기” 와 “왼쪽으로 돈 다음 앞으로 숙이기” 는 다른 자세입니다.

    물체에 붙어 함께 도는 x · y · z, 곧 물체의 오른쪽 · 위 · 앞을 자기 축이라 합니다. 같은 결과를 자기 축 기준으로 말하면 y → x → z 순서로 돈 것과 같습니다.

    같은 자세를 쓰는 숫자가 여럿이다

    아래는 x 를 100 으로 넣은 자세와, z 180 → x 80 → y 180 으로 돈 자세입니다. 앞과 위 두 방향이 정해지면 자세 하나가 정해지므로, 화살표 둘로 자세를 나타냅니다. 파란 화살표가 오브젝트의 앞, 청록 화살표가 오브젝트의 위입니다.

    같은 자세를 (100, 0, 0) 으로도 (80, 180, 180) 으로도 쓸 수 있습니다. 360 을 더하거나 빼도 같은 자세이니 조합은 끝없이 많습니다.

    유니티는 회전을 오일러 각으로 저장하지 않습니다. 쿼터니언(축과 각도로 회전 하나를 나타내는 네 숫자)으로 저장해 두고, eulerAngles 를 읽을 때마다 거기서 조합 하나를 계산해 돌려줍니다. 그 계산은 x 를 위아래 90 도 안쪽(0 ~ 90, 또는 음수 쪽을 270 ~ 360 으로 적은 것)에서 고르기 때문에, 100 을 넣으면 80 이 들어간 다른 조합으로 돌아옵니다. (Inspector 는 사람이 넣은 숫자를 따로 기억해 두었다가 보여 주기도 해서, 스크립트로 읽은 값과 다르게 보일 수 있습니다.)

    그래서 이런 코드가 깨집니다.

    // 매 프레임 eulerAngles 를 읽어서 x 만 1 올린다
    Vector3 e = transform.eulerAngles;
    e.x += 1f;
    transform.eulerAngles = e;

    프레임은 화면에 그리는 그림 한 장이고, 스크립트의 Update 는 한 장마다 한 번 불립니다. 프레임마다 값은 이렇게 바뀝니다.

    프레임 읽은 값 1 을 더해 넣은 값 실제 자세
    1 (89, 0, 0) (90, 0, 0) 바로 아래
    2 (90, 0, 0) (91, 0, 0) 아래를 1° 지남
    3 (89, 180, 180) (90, 180, 180) 다시 바로 아래
    4 (90, 0, 0) (91, 0, 0) 2 와 같음. 여기서부터 되풀이

    91 을 넣은 자세를 읽으면 같은 자세의 다른 조합 (89, 180, 180) 이 돌아옵니다. 거기에 1 을 더하면 다시 바로 아래(90)입니다. 바로 아래를 읽으면 유니티는 (90, 0, 0) 쪽 조합을 돌려주니, 2 와 3 이 끝없이 되풀이됩니다. x 는 89 와 90 을 오가며 아래를 넘어가지 못합니다. 각도를 직접 들고 있다가 매번 새로 만들어 넣으면 됩니다. 읽어 와서 고치지 않습니다.

    float pitch;   // 내가 들고 있는 각도
    
    void Update()
    {
        // Time.deltaTime 은 지난 그림 한 장이 걸린 초
        // 30 에 곱하면 "1초에 30도" 가 된다
        pitch += 30f * Time.deltaTime;
    
        transform.rotation = Quaternion.Euler(pitch, 0f, 0f);
    }

    한 축으로 조금씩 돌리는 것이 목적이면 transform.Rotate(1f, 0f, 0f) 처럼 지금 회전에 더하는 함수를 써도 됩니다.

    x 가 90 이면 y 와 z 가 같은 일을 한다

    원래 y 와 z 는 다른 일을 합니다. y 는 좌우로 돌아보기, z 는 옆으로 갸웃하기입니다. 그런데 x 가 90 이라 앞이 바로 아래를 보고 있으면, 둘이 똑같이 움직입니다. 아래는 x 가 0 일 때와 90 일 때입니다. 주황 화살표가 오브젝트의 오른쪽입니다.

    왜 이렇게 되는지는 순서에 있습니다. z 회전은 x 회전보다 먼저 적용됩니다. z 회전은 처음 자세에서 하니, 오브젝트 자신의 앞 축(처음엔 월드 z 와 같은 축)을 중심으로 돈 셈입니다.

    그다음 x 90 이 그 앞 축을 아래(−y)로 눕힙니다. 앞 축을 중심으로 돌았던 효과가 앞 축과 함께 누워서, 결국 위아래 축(y)을 중심으로 돈 것과 같은 모양이 됩니다. y 회전은 원래부터 위아래 축으로 도는 것이라, 이제 둘이 같은 일을 합니다. 그래서 x 가 90 일 때는 (90, 45, 0) 과 (90, 0, −45) 가 같은 자세이고, (90, 30, −15) 도 같은 자세입니다. y − z 만 의미가 있습니다.

    오일러 각 자세
    (90, 45, 0) 아래를 보고, 위가 오른쪽 앞
    (90, 0, −45) 같음
    (90, 30, −15) 같음 (30 − (−15) = 45)

    짐벌은 고리 셋을 안쪽으로 겹쳐 끼워, 고리마다 축 하나씩 따로 돌게 만든 장치입니다. 카메라 손떨림 방지대나 옛 비행기 나침반이 이렇게 생겼습니다. 오일러 각의 세 숫자가 이 고리 셋과 같습니다. 바깥 고리가 y, 가운데 고리가 x, 안쪽 고리가 z 입니다. 가운데 고리(x)를 90° 돌리면 안쪽 고리가 바깥 고리와 같은 면에 눕습니다. 이제 바깥 고리를 돌려도 안쪽 고리를 돌려도 같은 쪽으로만 돕니다. 고리가 실제로 잠긴 것은 아니고, 돌릴 수 있는 방향 하나가 사라진 것입니다. 이것을 짐벌 락(lock, 잠김)이라 부릅니다.

    축이 셋이던 것이 둘로 줄었으니, x 가 90 인 자리에서는 월드 z 축을 중심으로 도는 회전을 y 나 z 를 바꿔서는 만들 수 없습니다.

    x 가 90 일 때 오브젝트의 위는 월드 z 를 향합니다. 그러니 “월드 z 축으로 돌기” 는 “오브젝트가 자기 위 축으로 좌우를 돌아보기” 이고, 이 움직임이 막힌 것입니다. x 까지 함께 크게 바꿔야만 그 자세에 갈 수 있습니다. 비행기나 우주선처럼 아무 방향으로나 도는 물체를 오일러 각으로 조종하면, 위나 아래를 똑바로 볼 때 조종이 이상해지는 이유입니다. 1인칭 슈팅 게임의 카메라가 위아래를 ±89 도에서 막아 두는 것도 이 자리에 닿지 않게 하려는 것입니다.

    오일러 각은 언제 써도 되나

    쓰기 좋은 곳 피할 곳
    Inspector 에서 사람이 회전을 넣을 때 eulerAngles 를 읽어서 고쳐 다시 넣기
    한두 축만 쓰는 회전 (2D 의 z, 좌우만 도는 포탑의 y) 아무 방향으로나 도는 물체 (비행기, 우주선)
    들고 있는 각도로 매번 새로 만들기 (Quaternion.Euler(pitch, yaw, 0)) 두 자세 사이를 각도 숫자로 조금씩 섞기. 350° 에서 10° 로 숫자를 섞으면 20° 면 될 것을 340° 거꾸로 돕니다
    저장하거나 화면에 보여 줄 숫자 위나 아래를 똑바로 보는 자세 근처

    정리

    • 유니티의 오일러 각은 z → x → y 순서로 월드 축을 기준으로 돕니다
    • 같은 자세를 쓰는 숫자 조합이 여럿이라, eulerAngles 는 넣은 값이 아니라 유니티가 고른 조합을 돌려줍니다
    • 유니티는 회전을 쿼터니언으로 저장하고, 오일러 각은 읽을 때마다 계산합니다
    • 각도는 직접 들고 있다가 Quaternion.Euler 로 매번 새로 만듭니다. 읽어서 고치지 않습니다
    • x 가 ±90 이면 y 와 z 가 같은 축으로 돌아 회전 방향 하나를 잃습니다. 고리 셋을 겹친 짐벌에서 두 고리가 같은 면에 눕는 것과 같아 짐벌 락이라 부릅니다