Unity

[Unity] 1. 좌표계 변환 및 원리

Vfly 2026. 1. 13. 20:50

이번 기회에 한번 Unity를 공부해보도록 하겠다.

 

가장 먼저 정리해볼 내용은 Direct 배울때도 잘 이해가 되지 않았던 "좌표계 변환" 에 대한 내용을 한번 제대로 정리해보록 하겠다.

 

왜 좌표계 변환이 필요한가?

유니티 뿐만 아니라 게임을 만들다 보면 누구나 한번쯤은 "좌표계의 늪"에 빠지게 될거라고 생각한다.

 

분명히 캐릭터에게 "앞으로 이동해라" 라고 코드를 짰는데 위로 승천한다거나 시계방향으로 회전하라 했는데 갑자기 공중제비를 돌거나 하는 정신나갈것 같은 상황들이 종종 발생할것이다.

 

이런 문제는 거의 99% 월드 공간(World Space)로컬 공간(Local Space)의 차이를 명확히 하지않아서 발생하는 문제일것이다.

 

  1. 월드 공간(World Space) - 절대 좌표계
    • 정의: 씬(Scene) 전체를 기준으로 하는 절대적인 좌표 시스템.
    • 원점(0, 0, 0): 씬의 중심. Hierarchy 창의 최상위에 해당.
    • 축 방향:
      • X축: 오른쪽(+) / 왼쪽(-)
      • Y축: 위(+) / 아래(-)
      • Z축: 앞(+) / 뒤(-) (유니티는 왼손 좌표계 사용)
    • 특징: 모든 오브젝트가 공유하는 공통의 기준점이다. 오브젝트가 회전하거나 이동해도 월드 공간의 축은 절대 변하지 않는다
  2. 로컬 공간(Local Space) - 상대 좌표계
    • 정의: 해당 오브젝트 자신을 원점으로 삼는 상대적인 좌표 시스템.
    • 원점(0, 0, 0): 오브젝트의 피벗(Pivot) 위치.
    • 축 방향:
      • X축: 오브젝트의 오른쪽 방향 (transform.right)
      • Y축: 오브젝트의 위쪽 방향 (transform.up)
      • Z축: 오브젝트의 앞쪽 방향 (transform.forward)
    • 특징: 오브젝트가 45도 회전하면, 로컬 공간의 축도 함께 45도 회전합니다. 즉, "나의 앞"이 계속 바뀐다.

유니티는 우리를 위해 transform.Translate(), TransformDirection(), InverseTransformPoint() 같은 편리한 마법의 함수들을 제공한다고 한다.

 

이 함수들을 사용하면 복잡한 수식 없이도 "내 기준으로 오른쪽으로 이동해", "적이 내 시야 왼쪽에 있어?" 같은 기능을 쉽게 구현할 수 있다고 한다.

 

사실 지금 듣고 있는 강의에서는 "있는 기능 그냥 갔다 써도 충분하다" 라고 말하기는 하지만 Direct를 배웠을때 완전히 100% 이해하지 못하고 넘어갔던 부분에 대해서 이번 기회에 한번 제대로 이해하고  싶어서 이 글을 작성하게 되었다.

 


​유니티에서의 좌표 변환 함수들

일단 위치와 관련된 대표적인 함수 2개가 있다

  • transform.Translate()
  • transform.TransformDirection() , transform.InverseTransformDirection()

이번에는 Translate() 와 TransformDirection() 에 대해 알아보도록 하겠다.

 

transform.Translate()

이 함수는 직접적으로 오브젝트의 위치를 변경해주는 함수다.

if (Keyboard.current.wKey.isPressed)
{
    transform.Translate(Vector3.forward * Time.deltaTime * _speed);
}
if (Keyboard.current.sKey.isPressed)
{
    transform.Translate(Vector3.back * Time.deltaTime * _speed); 
}
if (Keyboard.current.aKey.isPressed)
{
    transform.Translate(Vector3.left * Time.deltaTime * _speed);
}
if (Keyboard.current.dKey.isPressed)
{
    transform.Translate(Vector3.right * Time.deltaTime * _speed);
}

 

위의 코드를 예시로 들자면 WASD 방향키로 이동하는 로직이다.

 

만약에 Translate( Vector3.forward ) 같은 경우 즉 ( 0, 0, 1 ) 를 넣으면 알아서 로컬 공간 기준으로 z 축 방향으로 1만큼(앞으로) 이동하게 된다.

 

다시 말해 캐릭터가 45도 회전해 있다면 회전한 방향 그대로 앞으로 이동하게 된다.

 

하지만 실제로는 내부적으로 (0,0,1)를 변환을 해서 위치가 이동하게 될것이다.

 

 

transform.TransformDirection()

이 함수는 실제로 오브젝트를 움직이는 함수는 아니다. 단지 "내 기준의 방향이 월드 좌표로는 어디야?"를 계산해서 알려주는 함수다.

if (Keyboard.current.wKey.isPressed)
{
    transform.position += transform.TransformDirection(Vector3.forward * Time.deltaTime * _speed);
}
if (Keyboard.current.sKey.isPressed)
{
    transform.position += transform.TransformDirection(Vector3.back * Time.deltaTime * _speed);
}
if (Keyboard.current.aKey.isPressed)
{
    transform.position += transform.TransformDirection(Vector3.left * Time.deltaTime * _speed);
}
if (Keyboard.current.dKey.isPressed)
{
    transform.position += transform.TransformDirection(Vector3.right * Time.deltaTime * _speed);
}

 

위의 코드는 Translate와 동일하게 WASD로 캐릭터를 이동시키는 로직이다. 하지만 Translate를 쓰는 대신, 직접 위치(transform.position)에 벡터를 더해주는 방식을 사용했다.

여기서 핵심이 되는 함수가 바로 transform.TransformDirection이다.

이 함수는 인자로 넣어준 벡터를 '나의 로컬 공간' 기준으로 해석했을 때, 그것이 '월드 공간'에서는 어떤 방향인지 계산해주는 함수다.

처음에는 이 말이 무슨 뜻인지 바로 와닿지 않을 수 있다. 그림으로 예시를 들어보자.

 

여기서 transform.TransformDirection(Vector3.forward)를 호출하면 어떤 일이 벌어질까?

  1. 입력: Vector3.forward는 단순히 (0, 0, 1)이라는 숫자 덩어리다.
  2. 질문: 이 함수에게 (0, 0, 1)을 넘겨주는 것은 다음과 같이 물어보는 것과 같다.
    • 야, 내(파란색 네모) 기준으로 앞쪽(0, 0, 1)이 월드 지도상으로는 어디를 가리키고 있어?"
  3. 상황: 파란색 네모는 현재 월드 기준 Y축으로 45도 회전해 있는 상태다.
  4. 계산: "음... 내 기준으로 앞쪽(Local Z)은 월드 기준으로 보면 북동쪽(World NE)을 향하고 있네!"
  5. 결과: 함수는 월드 좌표계 기준의 방향 벡터 (0.707, 0, 0.707)을 반환한다.

이제 이 결과값을 transform.position에 더해주면 어떻게 될까?

 

transform.position은 월드 좌표이므로, 여기에 방금 구한 '월드 기준 북동쪽 벡터'를 더해주면 파란색 네모는 자신이 바라보는 방향(대각선)으로 정확하게 이동하게 된다.

 

 

행렬 연산

지금까지 앞에서 transform.TransformDirection(Vector3.forward ... ) 가 "내 기준 앞쪽을 월드 기준으로 바꿔주는 함수" 라는 것일 직관적으로 봤다.

 

하지만 여기서 멈춘다면 재미가 없으니 내부적으로 무슨 계산을 하는지 한번 자세히 알아보도록 하자.

 

 

A좌표계에서의 M( x, y )의 의미

먼저 위의 그림과 같은 상황이라고 가정해보자. A좌표계의 기저벡터 ( u, v ) , B좌표계의 기저벡터 (U, V)가 있을때, 점 M을 표현하는 방법에 대해 알아보자.

 

먼저 A 좌표계에서 M의 의미는 "A의 기저 u,v를 얼마나 섞어서 M을 만들었는가"를 의미한다.

 

즉 아래 식이 정의 그 자체다.

 

 

B의 기저를 A의 언어로 표현

좌표계를 바꾸려면 먼저 "B의 기저벡터 U, V가 A에서는 어떤 벡터로 보이는지"를 알아야한다. 이를 가장 표준적인 방식으로 아래와 같다.

여기서 (a,b)는 "벡터 U를 A기저 (u,v)로 표현했을 때의 좌표, (c,d)는 벡터 V를 A기저 (u,v)로 표현했을때의 좌표" 라고 생각하면 편하다.

 

 

점 M( x, y )를 B좌표계로 표현

그 다음 이제 같은 점 M을 B좌표계로도 표현해 보자.

 

B좌표계에서 M의 좌표를 ( X, Y )라고 두면 아래의 식과 같다.

여기서 위에서 구한 U, V 를 바로 위의 식에 대입하면

그런데 이미 위에서 M = xu + yv 였으므로, 같은 벡터를 같은 기저 (u, v)로 표현했을 때 계수는 같아야한다. 따라서 다음과 같은 연립방정식을 얻게 된다.

 

자 그러면 이거를 행렬로 표현하게 되면 다음과 같다.

여기서 이제 중요 점은, 행렬의 첫번째 열이 "U를 A 기준으로 본 좌표" , 두번째 열이 "V를 A기준으로 본 좌표"라는 점이다.

 

즉 B의 기저벡터를 A기준 좌표로 표현한뒤 그 좌표들을 열로 세워 만든 행렬이 B좌표를 A좌표로 바꿔주는 변환행렬이 되는것이다.

 

물론 반대의 경우 A기준 좌표를 B기준 좌표로 알고싶다면

아래와 같이 역행렬을 곱해주게되면 된다.

 

 

 

3D 좌표계에서 변환행렬이 의미하는것은?

그럼 여기까지 읽었을때 아니 그래서 행렬이 의미하는게 뭔데? Unity 3D에서는 뭔 상관인데? 싶을 수 있다.

 

앞에서 2D 기준에서 설명을 했지만 3D에서도 다를게 없다 단지 축이 하나 더 생길뿐이다.

이런 형태일텐데 각 열이 Right, Up, Forward 벡터를 의미하게된다. 왜냐하면 변환행렬을 구성하는 요소가 사실상 기저벡터들이기 때문이다.

 

나중에는 SRT 변환 행렬 ( 크기 , 회전 , 위치 ) 에 대한 행렬연산도 한번 다뤄보도록 하겠다.