---
title: "오일러 각: eulerAngles 가 넣은 값과 다르게 나오고 짐벌 락이 생기는 이유"
description: "x 를 100 으로 넣은 회전을 읽으면 (80, 180, 180) 이 나옵니다. 오일러 각은 세 축으로 차례로 세 번 도는 것이라 같은 자세를 여러 숫자로 쓸 수 있고, x 가 90 이 되면 두 축이 같은 일을 해서 회전 방향 하나를 잃는 짐벌 락이 생깁니다. 축과 회전부터 따라갑니다."
author: "유니얼"
pubDate: 2026-11-05
category: "game-dev/math"
tags: ["Unity", "기초"]
canonical: https://blog.unialgames.com/posts/math-euler/
---
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 으로 돈 자세입니다. 앞과 위 두 방향이 정해지면 자세 하나가 정해지므로, 화살표 둘로 자세를 나타냅니다.
파란 화살표가 오브젝트의 앞, 청록 화살표가 오브젝트의 위입니다.

<x-vec>
<script type="application/json">
{
  "label": "(100, 0, 0) 과 (80, 180, 180)",
  "space": "3d",
  "grid": 1,
  "view": [-2, -1.7, 2.2, 1.9],
  "dwell": 2.4,
  "cases": [
    {
      "name": "x 100 한 번",
      "steps": [
        {
          "caption": "회전 없음. 앞은 z, 위는 y 입니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, 0, 1], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0, 1, 0], "rigid": true, "color": "accent2", "label": "위" }
          ]
        },
        {
          "caption": "x 50. 앞이 아래로 50 도 숙여졌습니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, -0.77, 0.64], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0, 0.64, 0.77], "rigid": true, "color": "accent2", "label": "위" }
          ]
        },
        {
          "caption": "x 100. 바로 아래를 지나 조금 뒤를 봅니다. 위 화살표는 앞쪽으로 넘어갔습니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, -0.98, -0.17], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0, -0.17, 0.98], "rigid": true, "color": "accent2", "label": "위" }
          ]
        }
      ]
    },
    {
      "name": "z 180 → x 80 → y 180",
      "steps": [
        {
          "caption": "회전 없음.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, 0, 1], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0, 1, 0], "rigid": true, "color": "accent2", "label": "위" }
          ]
        },
        {
          "caption": "z 로 180 도. 앞은 그대로이고 위가 아래로 뒤집혔습니다. 물구나무선 셈입니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, 0, 1], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0, -1, 0], "rigid": true, "color": "accent2", "label": "위" }
          ]
        },
        {
          "caption": "x 로 80 도. 앞이 아래로 80 도 숙여집니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, -0.98, 0.17], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0, -0.17, -0.98], "rigid": true, "color": "accent2", "label": "위" }
          ]
        },
        {
          "caption": "y 로 90 도까지.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0.17, -0.98, 0], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [-0.98, -0.17, 0], "rigid": true, "color": "accent2", "label": "위" }
          ]
        },
        {
          "caption": "y 로 180 도. 앞과 위가 x 100 한 번 했을 때와 정확히 같은 자리에 섰습니다. 숫자 셋이 전부 다른데 같은 자세입니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, -0.98, -0.17], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0, -0.17, 0.98], "rigid": true, "color": "accent2", "label": "위" }
          ]
        }
      ]
    }
  ]
}
</script>
</x-vec>

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

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

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

```csharp
// 매 프레임 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 을 오가며 아래를 넘어가지 못합니다.
**각도를 직접 들고 있다가 매번 새로 만들어** 넣으면 됩니다. 읽어 와서 고치지 않습니다.

```csharp
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 일 때입니다.
주황 화살표가 오브젝트의 오른쪽입니다.

<x-vec>
<script type="application/json">
{
  "label": "y 를 돌릴 때와 z 를 돌릴 때",
  "space": "3d",
  "grid": 1,
  "view": [-2, -1.7, 2.2, 1.9],
  "dwell": 2.4,
  "cases": [
    {
      "name": "x = 0",
      "steps": [
        {
          "caption": "x 0. 앞을 보고 서 있습니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, 0, 1], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0, 1, 0], "rigid": true, "color": "accent2", "label": "위" },
            { "id": "r", "from": [0, 0, 0], "to": [1, 0, 0], "rigid": true, "color": "warn", "label": "오른쪽" }
          ]
        },
        {
          "caption": "y 를 45 로. 위 화살표는 그대로이고 앞과 오른쪽이 바닥 위에서 돕니다. 좌우로 돌아보기입니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0.71, 0, 0.71], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0, 1, 0], "rigid": true, "color": "accent2", "label": "위" },
            { "id": "r", "from": [0, 0, 0], "to": [0.71, 0, -0.71], "rigid": true, "color": "warn", "label": "오른쪽" }
          ]
        },
        {
          "caption": "y 를 다시 0 으로 돌려놓습니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, 0, 1], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0, 1, 0], "rigid": true, "color": "accent2", "label": "위" },
            { "id": "r", "from": [0, 0, 0], "to": [1, 0, 0], "rigid": true, "color": "warn", "label": "오른쪽" }
          ]
        },
        {
          "caption": "이번엔 z 를 −45 로. 앞은 그대로이고 위와 오른쪽이 기울어집니다. 갸웃하기라서 y 와 전혀 다른 움직임입니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, 0, 1], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0.71, 0.71, 0], "rigid": true, "color": "accent2", "label": "위" },
            { "id": "r", "from": [0, 0, 0], "to": [0.71, -0.71, 0], "rigid": true, "color": "warn", "label": "오른쪽" }
          ]
        }
      ]
    },
    {
      "name": "x = 90",
      "steps": [
        {
          "caption": "x 90. 앞이 바로 아래를 봅니다. 위 화살표는 앞쪽(z)으로 누웠습니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, -1, 0], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0, 0, 1], "rigid": true, "color": "accent2", "label": "위" },
            { "id": "r", "from": [0, 0, 0], "to": [1, 0, 0], "rigid": true, "color": "warn", "label": "오른쪽" }
          ]
        },
        {
          "caption": "y 를 45 로. 앞은 아래를 본 채로, 위와 오른쪽이 바닥 위에서 돕니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, -1, 0], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0.71, 0, 0.71], "rigid": true, "color": "accent2", "label": "위" },
            { "id": "r", "from": [0, 0, 0], "to": [0.71, 0, -0.71], "rigid": true, "color": "warn", "label": "오른쪽" }
          ]
        },
        {
          "caption": "y 를 다시 0 으로 돌려놓습니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, -1, 0], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0, 0, 1], "rigid": true, "color": "accent2", "label": "위" },
            { "id": "r", "from": [0, 0, 0], "to": [1, 0, 0], "rigid": true, "color": "warn", "label": "오른쪽" }
          ]
        },
        {
          "caption": "z 를 −45 로. y 를 45 로 했을 때와 똑같이 바닥 위에서 돕니다. x 가 90 이면 y 와 z 가 같은 축으로 돌아서, 세 숫자로 갈 수 있는 방향이 둘로 줄었습니다. 이것이 짐벌 락입니다.",
          "items": [
            { "id": "f", "from": [0, 0, 0], "to": [0, -1, 0], "rigid": true, "label": "앞" },
            { "id": "u", "from": [0, 0, 0], "to": [0.71, 0, 0.71], "rigid": true, "color": "accent2", "label": "위" },
            { "id": "r", "from": [0, 0, 0], "to": [0.71, 0, -0.71], "rigid": true, "color": "warn", "label": "오른쪽" }
          ]
        }
      ]
    }
  ]
}
</script>
</x-vec>

왜 이렇게 되는지는 순서에 있습니다. 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 가 같은 축으로 돌아 회전 방향 하나를 잃습니다. 고리 셋을 겹친 짐벌에서 두 고리가 같은 면에 눕는 것과 같아 짐벌 락이라 부릅니다