---
title: "외적: 왼쪽인가 오른쪽인가"
description: "내적은 앞뒤만 알려 주고 왼쪽과 오른쪽은 가리지 못합니다. 외적은 두 벡터에 모두 수직인 벡터를 만들고, 바닥 위의 두 방향이면 그 y 부호가 왼쪽과 오른쪽을 알려 줍니다. 수직 · 법선 · 왼손 좌표계부터 작은 숫자로 따라갑니다."
author: "유니얼"
pubDate: 2026-11-05
category: "game-dev/math"
tags: ["Unity", "기초"]
canonical: https://blog.unialgames.com/posts/math-cross/
---
포탑이 적을 향해 천천히 돌게 만들려면, 매 프레임 **왼쪽으로 돌지 오른쪽으로 돌지**를 정해야 합니다.
적이 앞에 있는지는 내적으로 알 수 있습니다. 그런데 내적은 왼쪽 45 도와 오른쪽 45 도에 똑같은 값을 줍니다.
그대로 쓰면 포탑이 반대쪽으로 한 바퀴를 돌아서 갑니다.

이때 쓰는 것이 **외적**입니다. 외적은 **벡터**, 곧 방향과 길이를 가진 화살표 두 개를 받아 **두 벡터에 모두 수직인 벡터**를 돌려줍니다.
수직은 바닥과 기둥처럼 직각(90°)으로 만나는 것입니다. 오른쪽과 위는 수직입니다.
바닥 위의 두 방향을 외적하면 결과는 위나 아래를 가리키고, 그 부호가 왼쪽과 오른쪽을 가릅니다.

> **`Vector3.Cross(앞, 적 방향)` 의 y 가 0 보다 크면 적은 오른쪽, 작으면 왼쪽, 0 이면 정면이나 정 뒤입니다.
> 외적의 길이는 두 벡터가 평행할수록, 곧 한 직선 위에 나란히 놓일수록 0 에 가까워지고, 결과의 방향은 넣는 순서를 바꾸면 뒤집힙니다.**

## 내적만으로는 왼쪽과 오른쪽을 못 가린다

**내적**은 x 끼리, y 끼리, z 끼리 곱해서 더한 숫자이고, 길이 1 인 두 방향이면 사이각의 cos 입니다.
cos 는 각도를 넣으면 숫자를 돌려주는 함수로, 직각삼각형에서 밑변 ÷ 빗변 입니다. 0° 면 1, 90° 면 0 입니다.
그런데 cos 는 각의 크기만 보기 때문에 왼쪽으로 45 도든 오른쪽으로 45 도든 같은 값이 나옵니다.

3D 벡터는 숫자 셋 (x, y, z) 로 적고, 유니티에서는 x 가 오른쪽, y 가 위, z 가 앞입니다. 그래서 오른쪽으로 1 은 (1, 0, 0), 앞으로 1 은 (0, 0, 1) 입니다.
앞 (0, 0, 1) 을 보고 있을 때입니다.

| 적 방향 | `Dot(앞, 적)` | 실제 위치 |
| --- | --- | --- |
| (0.71, 0, 0.71) | 0.71 | 오른쪽 앞 |
| (−0.71, 0, 0.71) | 0.71 | 왼쪽 앞 |

내적은 "앞에서 45 도 벗어났다" 까지만 압니다. 어느 쪽으로 벗어났는지는 다른 계산이 필요합니다.

## 외적은 두 벡터에 수직인 벡터다

외적은 3D 벡터 둘로 정의됩니다. 식은 이렇습니다.

```text
a × b = ( a.y × b.z − a.z × b.y,
          a.z × b.x − a.x × b.z,
          a.x × b.y − a.y × b.x )
```

읽는 요령은 **각 자리를 자기를 뺀 나머지 두 자리로 만든다**는 것입니다. x 자리는 y 와 z 로, y 자리는 z 와 x 로, z 자리는 x 와 y 로 만듭니다.
직접 계산할 일은 거의 없고 `Vector3.Cross(a, b)` 가 해 줍니다. 중요한 것은 결과가 **a 에도 수직이고 b 에도 수직**이라는 성질입니다.

앞 a = (0, 0, 1) 과 오른쪽 b = (1, 0, 0) 의 외적입니다.

| 자리 | 계산 | 값 |
| --- | --- | --- |
| x | 0 × 0 − 1 × 0 | 0 |
| y | 1 × 1 − 0 × 0 | **1** |
| z | 0 × 0 − 0 × 1 | 0 |

결과는 (0, 1, 0), **위**입니다. 앞과도 수직이고 오른쪽과도 수직입니다.

축이 아닌 벡터도 마찬가지입니다. 오른쪽 앞 a = (1, 0, 1) 과 위 b = (0, 1, 0) 의 외적입니다.

| 자리 | 계산 | 값 |
| --- | --- | --- |
| x | 0 × 0 − 1 × 1 | −1 |
| y | 1 × 0 − 1 × 0 | 0 |
| z | 1 × 1 − 0 × 0 | 1 |

결과 (−1, 0, 1) 이 정말 수직인지는 내적이 0 인지로 확인합니다. a 와는 (−1) × 1 + 0 × 0 + 1 × 1 = 0, b 와는 0 + 0 + 0 = 0 입니다. 둘 다 수직입니다.

바닥에 놓인 두 방향은 y 가 0 입니다. 식에 a.y = 0, b.y = 0 을 넣으면 x 자리는 0 × b.z − a.z × 0 = 0, z 자리는 a.x × 0 − 0 × b.x = 0 입니다.
그래서 바닥 위 두 방향의 외적은 **y 만 남습니다.** 바닥에 수직인 방향은 위 아니면 아래뿐이라는 말과 같습니다.

## y 의 부호가 왼쪽과 오른쪽이다

앞 a = (0, 0, 1) 과 바닥 위의 적 방향 b = (b.x, 0, b.z) 를 식에 넣으면, y 자리는 `1 × b.x − 0 × b.z = b.x` 입니다.
적이 오른쪽(x 가 양수)에 있으면 y 가 양수, 왼쪽에 있으면 음수입니다.

포탑이 다른 쪽을 봐도 같습니다. 오른쪽 (1, 0, 0) 을 보는 포탑에게 오른손 쪽은 뒤 (0, 0, −1) 입니다. 이 둘의 외적 y 는 0 × 0 − 1 × (−1) = **1**, 양수라서 오른쪽입니다.
이렇게 y 자리 하나만 떼어 쓰는 계산을 **2D 외적**이라고 부르기도 합니다.

적이 내 주위를 도는 동안 내적과 외적의 y 는 이렇게 바뀝니다. 위에서 내려다본 그림이라 가로가 x, 세로가 z 이고, 외적은 화면 밖으로 나오거나 들어가는 방향이라 숫자로만 적었습니다.

<x-vec>
<script type="application/json">
{
  "label": "적이 돌 때 Dot 과 Cross 의 y",
  "view": [-2, -2, 2, 2],
  "dwell": 2.4,
  "steps": [
    {
      "caption": "적이 바로 앞. 외적의 y 는 0 입니다. 두 방향이 평행하면 외적은 0 이 됩니다.",
      "items": [
        { "id": "c", "type": "circle", "at": [0, 0], "r": 1, "color": "muted" },
        { "id": "f", "from": [0, 0], "to": [0, 1], "label": "앞", "labelAt": [-0.35, 1.3] },
        { "id": "e", "from": [0, 0], "to": [0, 1], "color": "muted" },
        { "id": "t", "type": "text", "at": [0, -1.45], "color": "muted", "label": "Dot 1 · Cross.y 0" }
      ]
    },
    {
      "caption": "오른쪽 앞. 외적의 y 가 +0.71, 양수라서 오른쪽입니다. 내적 0.71 은 앞쪽이라는 뜻입니다.",
      "items": [
        { "id": "c", "type": "circle", "at": [0, 0], "r": 1, "color": "muted" },
        { "id": "f", "from": [0, 0], "to": [0, 1], "label": "앞", "labelAt": [-0.35, 1.3] },
        { "id": "e", "from": [0, 0], "to": [0.71, 0.71], "color": "accent2" },
        { "id": "t", "type": "text", "at": [0, -1.45], "color": "accent2", "label": "Dot 0.71 · Cross.y +0.71 오른쪽" }
      ]
    },
    {
      "caption": "바로 오른쪽. 외적의 y 가 가장 큰 +1. 내적은 0 이라 앞도 뒤도 아닙니다.",
      "items": [
        { "id": "c", "type": "circle", "at": [0, 0], "r": 1, "color": "muted" },
        { "id": "f", "from": [0, 0], "to": [0, 1], "label": "앞", "labelAt": [-0.35, 1.3] },
        { "id": "e", "from": [0, 0], "to": [1, 0], "color": "accent2" },
        { "id": "t", "type": "text", "at": [0, -1.45], "color": "accent2", "label": "Dot 0 · Cross.y +1 오른쪽" }
      ]
    },
    {
      "caption": "오른쪽 뒤. 외적은 여전히 양수라 오른쪽, 내적은 음수라 뒤쪽입니다. 둘을 같이 보면 네 방향이 다 갈립니다.",
      "items": [
        { "id": "c", "type": "circle", "at": [0, 0], "r": 1, "color": "muted" },
        { "id": "f", "from": [0, 0], "to": [0, 1], "label": "앞", "labelAt": [-0.35, 1.3] },
        { "id": "e", "from": [0, 0], "to": [0.71, -0.71], "color": "accent2" },
        { "id": "t", "type": "text", "at": [0, -1.45], "color": "accent2", "label": "Dot −0.71 · Cross.y +0.71 오른쪽" }
      ]
    },
    {
      "caption": "왼쪽 뒤. 외적의 y 가 음수로 바뀌었습니다. 이제 왼쪽입니다.",
      "items": [
        { "id": "c", "type": "circle", "at": [0, 0], "r": 1, "color": "muted" },
        { "id": "f", "from": [0, 0], "to": [0, 1], "label": "앞", "labelAt": [-0.35, 1.3] },
        { "id": "e", "from": [0, 0], "to": [-0.71, -0.71], "color": "warn" },
        { "id": "t", "type": "text", "at": [0, -1.45], "color": "warn", "label": "Dot −0.71 · Cross.y −0.71 왼쪽" }
      ]
    },
    {
      "caption": "왼쪽 앞. 내적은 오른쪽 앞과 똑같은 0.71 인데, 외적의 y 는 −0.71 로 부호가 반대입니다. 내적이 못 가린 것을 외적이 가립니다.",
      "items": [
        { "id": "c", "type": "circle", "at": [0, 0], "r": 1, "color": "muted" },
        { "id": "f", "from": [0, 0], "to": [0, 1], "label": "앞", "labelAt": [-0.35, 1.3] },
        { "id": "e", "from": [0, 0], "to": [-0.71, 0.71], "color": "warn" },
        { "id": "t", "type": "text", "at": [0, -1.45], "color": "warn", "label": "Dot 0.71 · Cross.y −0.71 왼쪽" }
      ]
    }
  ]
}
</script>
</x-vec>

포탑 코드는 이렇게 됩니다.

```csharp
Vector3 toEnemy = enemy.position - turret.position;   // 도착 − 출발
toEnemy.y = 0f;                                        // 바닥 위에서만 본다

float side = Vector3.Cross(turret.forward, toEnemy).y;

// 양수면 오른쪽, 음수면 왼쪽으로 돈다. Sign 은 양수면 1, 음수면 -1 을 돌려준다
// Time.deltaTime 은 지난 프레임이 걸린 초. 곱하면 1초에 turnSpeed 도씩 돈다
float turn = Mathf.Sign(side) * turnSpeed * Time.deltaTime;
turret.Rotate(0f, turn, 0f);
```

`Rotate` 의 y 에 양수를 넣으면 위에서 볼 때 시계 방향, 곧 오른쪽으로 돕니다. 외적 y 의 부호와 방향이 맞습니다.
`Mathf.Sign` 은 0 에도 1 을 돌려주므로, 정면을 맞춘 뒤에도 조금씩 오른쪽으로 돌며 떨 수 있습니다. 실제로는 `side` 가 아주 작으면 돌지 않게 하거나 남은 각도만큼만 돌게 막습니다.
부호만 쓰니까 `toEnemy` 를 정규화(길이를 1 로 맞추기)할 필요는 없습니다. 외적의 길이는 두 벡터의 길이를 곱한 만큼 커지지만, 부호는 바뀌지 않습니다.

## 순서를 바꾸면 방향이 뒤집힌다

외적은 **넣는 순서가 결과를 바꿉니다.** a × b 와 b × a 는 길이는 같고 방향이 정반대입니다.
식에서 각 자리가 "앞 × 뒤 − 뒤 × 앞" 꼴이라, a 와 b 를 맞바꾸면 빼는 순서가 바뀌어 부호만 뒤집힙니다.
보통 곱셈은 2 × 3 = 3 × 2 이지만 외적은 그렇지 않습니다.

<x-vec>
<script type="application/json">
{
  "label": "앞과 오른쪽, 순서만 바꿔 외적",
  "space": "3d",
  "grid": 1,
  "view": [-1.6, -1.5, 2, 1.6],
  "cases": [
    {
      "name": "Cross(앞, 오른쪽)",
      "steps": [
        {
          "caption": "a 는 앞 (0, 0, 1), b 는 오른쪽 (1, 0, 0). 둘 다 바닥 위에 있습니다.",
          "items": [
            { "id": "a", "from": [0, 0, 0], "to": [0, 0, 1], "label": "a 앞" },
            { "id": "b", "from": [0, 0, 0], "to": [1, 0, 0], "color": "accent2", "label": "b 오른쪽" }
          ]
        },
        {
          "caption": "a × b 는 (0, 1, 0), 위입니다. 바닥에 놓인 두 방향 모두와 수직입니다.",
          "code": "Vector3.Cross(Vector3.forward, Vector3.right)   // (0, 1, 0)",
          "items": [
            { "id": "a", "from": [0, 0, 0], "to": [0, 0, 1], "label": "a 앞" },
            { "id": "b", "from": [0, 0, 0], "to": [1, 0, 0], "color": "accent2", "label": "b 오른쪽" },
            { "id": "c", "from": [0, 0, 0], "to": [0, 1, 0], "color": "warn", "label": "위 (0, 1, 0)" }
          ]
        }
      ]
    },
    {
      "name": "Cross(오른쪽, 앞)",
      "steps": [
        {
          "caption": "같은 두 방향을 순서만 바꿨습니다. a 는 오른쪽, b 는 앞.",
          "items": [
            { "id": "a", "from": [0, 0, 0], "to": [1, 0, 0], "color": "accent2", "label": "a 오른쪽" },
            { "id": "b", "from": [0, 0, 0], "to": [0, 0, 1], "label": "b 앞" }
          ]
        },
        {
          "caption": "결과는 (0, −1, 0), 아래입니다. 길이는 같고 방향만 정반대가 됐습니다.",
          "code": "Vector3.Cross(Vector3.right, Vector3.forward)   // (0, -1, 0)",
          "items": [
            { "id": "a", "from": [0, 0, 0], "to": [1, 0, 0], "color": "accent2", "label": "a 오른쪽" },
            { "id": "b", "from": [0, 0, 0], "to": [0, 0, 1], "label": "b 앞" },
            { "id": "c", "from": [0, 0, 0], "to": [0, -1, 0], "color": "warn", "label": "아래 (0, −1, 0)" }
          ]
        }
      ]
    }
  ]
}
</script>
</x-vec>

다른 축끼리도 같은 규칙입니다.

| 식 | 결과 | 순서를 바꾸면 |
| --- | --- | --- |
| `Cross(위, 앞)` | (1, 0, 0) 오른쪽 | (−1, 0, 0) 왼쪽 |
| `Cross(오른쪽, 위)` | (0, 0, 1) 앞 | (0, 0, −1) 뒤 |

어느 쪽이 나오는지 손으로 확인하는 방법이 **왼손 법칙**입니다. 유니티가 **왼손 좌표계**를 쓰기 때문입니다.
좌표계는 숫자 셋을 어느 쪽으로 읽을지 정한 약속입니다. 왼손 엄지를 오른쪽(x), 검지를 위(y)로 세우면 중지가 앞(z), 곧 화면 안쪽을 가리키는데, 유니티 축이 이 배치입니다. 오른손으로 같은 자세를 하면 중지가 나를 향하고, 그쪽을 z 로 삼는 것이 오른손 좌표계입니다.
왼손을 펴서 손가락들이 a 를 가리키게 하고, 손가락을 b 쪽으로 감아쥐면 엄지가 가리키는 쪽이 a × b 입니다.

식 자체는 어느 엔진이나 수학책이나 같습니다. `Cross((1, 0, 0), (0, 1, 0))` 은 어디서나 (0, 0, 1) 입니다.
달라지는 것은 그 (0, 0, 1) 이 화면 안쪽인지(왼손 좌표계) 나를 향하는지(오른손 좌표계)입니다.
그래서 오른손 좌표계로 그린 자료에서 "외적이 화면 밖으로 나온다" 고 설명한 그림은, 유니티에서는 반대 방향으로 읽어야 맞습니다.

## 길이는 평행할수록 0 이다

외적의 길이는 `a 의 길이 × b 의 길이 × sin(사이각)` 입니다.
sin 은 cos 의 짝으로, 직각삼각형에서 **높이 ÷ 빗변** 입니다. 반지름 1 인 원에서 각 θ 만큼 돈 점의 세로 위치와 같습니다. 그래서 0 도와 180 도(눕혀서 높이가 없음)에서 0, 90 도(똑바로 섬)에서 1 입니다.
위 그림에서 적이 정면이나 정 뒤일 때 외적이 0, 바로 옆일 때 가장 컸던 이유입니다.

| 두 벡터 | 사이각 | 외적의 길이 |
| --- | --- | --- |
| 수직 | 90° | 길이 × 길이 (가장 큼) |
| 비스듬히 | 45° | 길이 × 길이 × 0.71 |
| 평행 | 0° 또는 180° | **0** |

이 길이는 두 벡터를 두 변으로 하는 **평행사변형**(마주 보는 변끼리 평행한 사각형. 직사각형도 그중 하나)의 넓이와 같습니다.
(2, 0, 0) 과 (0, 0, 3) 을 외적하면 (0, −6, 0), 길이 6 이고, 가로 2 세로 3 인 직사각형의 넓이도 6 입니다. 삼각형은 그 절반이라, 메시 삼각형의 넓이를 구할 때 이 길이 ÷ 2 를 씁니다.

외적은 **두 방향이 평행한지 확인**하는 데도 씁니다. 결과가 영벡터, 곧 길이가 0 이라 방향이 없는 (0, 0, 0) 에 가까우면 두 방향은 같은 직선 위에 있습니다.
평행하면 둘에 수직인 방향이 하나로 정해지지 않아서 외적이 0 이 되는 것입니다.
카메라가 바로 아래를 내려다보면 카메라의 앞과 월드의 위가 평행해져, 외적으로 오른쪽 축을 만드는 코드가 영벡터를 받게 됩니다. 이런 자리는 평행인지 먼저 확인합니다.

## 면의 방향을 구할 때

외적의 원래 쓰임은 **면의 법선**을 구하는 것입니다. 법선은 면에 수직으로 선 방향으로, 바닥이면 위 (0, 1, 0) 입니다. 면이 어느 쪽을 보는지 알아야 빛을 받는 밝기와 앞뒷면을 정할 수 있습니다.
삼각형 세 꼭짓점 A, B, C 가 있으면 두 변을 벡터로 만들어 외적합니다.

예를 들어 바닥에 누운 삼각형 A (0, 0, 0), B (0, 0, 1), C (1, 0, 0) 이 있습니다. 위에서 보면 A 에서 앞으로 B, 오른쪽으로 C 입니다.

| 단계 | 계산 | 결과 |
| --- | --- | --- |
| 변 1 | B − A | (0, 0, 1) |
| 변 2 | C − A | (1, 0, 0) |
| 법선 | `Cross(B − A, C − A)` | (0, 1, 0) 위 |

꼭짓점을 적는 순서를 바꾸면 법선이 뒤집힙니다.

<x-vec>
<script type="application/json">
{
  "label": "같은 삼각형, 꼭짓점 순서만 바꾸기",
  "space": "3d",
  "grid": 1,
  "view": [-1.4, -1.5, 2, 1.6],
  "cases": [
    {
      "name": "A → B → C",
      "steps": [
        {
          "caption": "바닥에 누운 삼각형. 위에서 내려다보면 A 에서 B(앞), 다시 C(오른쪽)로 도는 순서는 시계 방향입니다.",
          "items": [
            { "id": "e1", "type": "line", "from": [0, 0, 0], "to": [0, 0, 1], "color": "muted" },
            { "id": "e2", "type": "line", "from": [0, 0, 1], "to": [1, 0, 0], "color": "muted" },
            { "id": "e3", "type": "line", "from": [1, 0, 0], "to": [0, 0, 0], "color": "muted" },
            { "id": "pa", "type": "point", "at": [0, 0, 0], "color": "text", "label": "A", "labelAt": [-0.25, -0.2, 0] },
            { "id": "pb", "type": "point", "at": [0, 0, 1], "color": "text", "label": "B", "labelAt": [-0.25, 0, 1.2] },
            { "id": "pc", "type": "point", "at": [1, 0, 0], "color": "text", "label": "C", "labelAt": [1.2, -0.2, 0] }
          ]
        },
        {
          "caption": "첫 변 B − A = (0, 0, 1), 둘째 변 C − A = (1, 0, 0).",
          "items": [
            { "id": "e2", "type": "line", "from": [0, 0, 1], "to": [1, 0, 0], "color": "muted" },
            { "id": "s1", "from": [0, 0, 0], "to": [0, 0, 1], "label": "B − A" },
            { "id": "s2", "from": [0, 0, 0], "to": [1, 0, 0], "color": "accent2", "label": "C − A" },
            { "id": "pa", "type": "point", "at": [0, 0, 0], "color": "text", "label": "A", "labelAt": [-0.25, -0.2, 0] }
          ]
        },
        {
          "caption": "Cross(B − A, C − A) = (0, 1, 0). 법선이 위, 곧 위에서 보는 사람 쪽을 향합니다. 이 면은 위에서 보면 앞면입니다.",
          "items": [
            { "id": "e2", "type": "line", "from": [0, 0, 1], "to": [1, 0, 0], "color": "muted" },
            { "id": "s1", "from": [0, 0, 0], "to": [0, 0, 1], "label": "B − A" },
            { "id": "s2", "from": [0, 0, 0], "to": [1, 0, 0], "color": "accent2", "label": "C − A" },
            { "id": "n", "from": [0, 0, 0], "to": [0, 1, 0], "color": "warn", "label": "법선 위" },
            { "id": "pa", "type": "point", "at": [0, 0, 0], "color": "text", "label": "A", "labelAt": [-0.25, -0.2, 0] }
          ]
        }
      ]
    },
    {
      "name": "A → C → B",
      "steps": [
        {
          "caption": "같은 삼각형을 A, C, B 순서로 적었습니다. 위에서 보면 반시계 방향입니다.",
          "items": [
            { "id": "e1", "type": "line", "from": [0, 0, 0], "to": [0, 0, 1], "color": "muted" },
            { "id": "e2", "type": "line", "from": [0, 0, 1], "to": [1, 0, 0], "color": "muted" },
            { "id": "e3", "type": "line", "from": [1, 0, 0], "to": [0, 0, 0], "color": "muted" },
            { "id": "pa", "type": "point", "at": [0, 0, 0], "color": "text", "label": "A", "labelAt": [-0.25, -0.2, 0] },
            { "id": "pb", "type": "point", "at": [0, 0, 1], "color": "text", "label": "B", "labelAt": [-0.25, 0, 1.2] },
            { "id": "pc", "type": "point", "at": [1, 0, 0], "color": "text", "label": "C", "labelAt": [1.2, -0.2, 0] }
          ]
        },
        {
          "caption": "이번 첫 변은 C − A = (1, 0, 0), 둘째 변은 B − A = (0, 0, 1). 외적에 넣는 순서가 바뀌었습니다.",
          "items": [
            { "id": "e2", "type": "line", "from": [0, 0, 1], "to": [1, 0, 0], "color": "muted" },
            { "id": "s1", "from": [0, 0, 0], "to": [0, 0, 1], "label": "B − A" },
            { "id": "s2", "from": [0, 0, 0], "to": [1, 0, 0], "color": "accent2", "label": "C − A" },
            { "id": "pa", "type": "point", "at": [0, 0, 0], "color": "text", "label": "A", "labelAt": [-0.25, -0.2, 0] }
          ]
        },
        {
          "caption": "Cross(C − A, B − A) = (0, −1, 0). 법선이 아래로 뒤집혔습니다. 위에서 보는 카메라에게 이 면은 뒷면이라 그려지지 않습니다.",
          "items": [
            { "id": "e2", "type": "line", "from": [0, 0, 1], "to": [1, 0, 0], "color": "muted" },
            { "id": "s1", "from": [0, 0, 0], "to": [0, 0, 1], "label": "B − A" },
            { "id": "s2", "from": [0, 0, 0], "to": [1, 0, 0], "color": "accent2", "label": "C − A" },
            { "id": "n", "from": [0, 0, 0], "to": [0, -1, 0], "color": "warn", "label": "법선 아래" },
            { "id": "pa", "type": "point", "at": [0, 0, 0], "color": "text", "label": "A", "labelAt": [-0.25, -0.2, 0] }
          ]
        }
      ]
    }
  ]
}
</script>
</x-vec>

꼭짓점을 적는 순서를 **와인딩 순서**라고 부릅니다. 유니티는 **카메라에서 볼 때 시계 방향으로 이어지는 면을 앞면**으로 보고, 기본으로 뒷면은 그리지 않습니다.
메시를 코드로 만들 때 꼭짓점 순서를 반대로 넣으면 면이 통째로 안 보이는 것이 이 때문입니다.
`Mesh.RecalculateNormals()` 도 삼각형마다 이 외적으로 면의 방향을 구하고, 꼭짓점을 같이 쓰는 면들의 방향을 모아 꼭짓점의 법선을 만듭니다.

## 정리

- 내적은 왼쪽 45 도와 오른쪽 45 도에 같은 값을 줘서 좌우를 못 가립니다
- 외적 `Vector3.Cross(a, b)` 는 a 와 b 에 모두 수직인 벡터입니다. 수직인지는 내적이 0 인지로 확인합니다
- 바닥 위 두 방향의 외적은 y 만 남고, `Cross(앞, 적).y` 가 양수면 오른쪽, 음수면 왼쪽입니다
- 내적의 부호(앞뒤)와 외적 y 의 부호(좌우)를 같이 보면 네 방향이 다 갈립니다
- 순서를 바꾸면 방향이 뒤집힙니다. 유니티는 왼손 좌표계라 왼손 법칙으로 확인합니다
- 외적의 길이는 sin(사이각) 에 비례해서, 평행하면 0 입니다. 길이는 평행사변형의 넓이이고, 평행 확인에도 씁니다
- 삼각형 두 변의 외적이 면의 법선이고, 꼭짓점 순서(와인딩)가 앞면을 정합니다. 유니티는 시계 방향이 앞면입니다