상세 컨텐츠

본문 제목

Unreal Engine 좌표변환 이해하기(1)

언리얼엔진

by zmo 2025. 10. 20. 17:36

본문

오늘 학습할 내용으로는 언리얼 엔진에서 좌표를 표현하는 방식과 학습할 때 좌표를 표현하는 방식이 서로 다르기 때문에 사용해야할 좌표변환에 대해서 알아보려고 한다. 사실 이전에 학습했던 포인트 클라우드 내용으로도 어느정도 눈치챌수 있는 내용이지만 정확하게 기준 좌표계부터 벡터의 여러가지 변환에 대해 어떤 행렬을 사용하는지 알아보자

 


좌표변환 이해하기

  • 회전 행렬은 단순히 "좌표축을 새롭게 정의하는 것"이다.
  • 예를 들어, Z축을 기준으로 90도 회전한다는 것은 "X축이 Y축으로, Y축이 -X축으로 이동한다"는 뜻이다.
  • 따라서 회전 행렬은 공간의 기준 좌표계를 다시 잡아주는 도구라 할 수 있다.
  • 회전 행렬은 벡터의 길이와 각도를 보존하면서 방향만 바꾸는 변환이다.
  • 기하학적으로는 좌표계 자체를 회전시켜 벡터를 새 좌표계에서 표현하는 것과 같다.
  • 언리얼 엔진에서는 직관적인 FRotator나 수학적으로 안정적인 FQuat로 회전을 다루지만, 근본적으로는 회전 행렬을 기반으로 한다.

예시 코드 작성해보기

FRotator Rot(0, 90, 0); // Yaw 90도 회전
FVector Forward(1, 0, 0);

FVector Rotated = Rot.RotateVector(Forward);

UE_LOG(LogTemp, Warning, TEXT("Rotated Vector: %s"), *Rotated.ToString());
// 결과: (0, 1, 0)

2D 회전 행렬

R(θ) = [ cosθ   -sinθ ]
       [ sinθ    cosθ ]

3D 회전 행렬

  • X축 회전
Rx(θ) = [ 1     0       0  ]
        [ 0   cosθ   -sinθ ]
        [ 0   sinθ    cosθ ]

  • Y축 회전
Ry(θ) = [ cosθ   0   sinθ ]
        [   0    1     0  ]
        [ -sinθ  0   cosθ ]

  • Z축 회전
Rz(θ) = [ cosθ  -sinθ   0 ]
        [ sinθ   cosθ   0 ]
        [   0     0     1 ]

3D 그래픽스, 로보틱스, 게임 엔진(언리얼/유니티 등)에서 물체의 회전을 표현할 때 흔히 오일러 각(Euler Angle) 또는 회전 행렬(Rotation Matrix) 를 사용한다고 한다.

하지만 이 두 방식에는 단점이 있는데

  • 오일러 각짐벌락(Gimbal Lock) 문제가 발생 (특정 각도에서 자유도가 줄어들어 회전이 막히는 현상)
  • 회전 행렬 → 3×3 행렬이라 메모리 사용량이 크고, 부동소수점 오차 누적으로 인해 직교성이 깨지면 정상적인 회전이 되지 않음

이를 해결하기 위해 사용되는 것이 쿼터니언이다.

쿼터니언은 4차원 복소수 기반 회전 표현 방식으로, 짐벌락을 피하면서 안정적이고 효율적으로 회전을 계산할 수 있다.

쿼터니언의 정의

쿼터니언은 실수부 하나와 허수부 세 개로 이루어진 수 이다.

q=w+xi+yj+zk

여기서

  • w: 실수부 (scalar part)
  • (x,y,z): 허수부 (vector part)
  • i,j,k: 서로 다른 3개의 허수 단위

즉, 쿼터니언은 4개의 실수 (w, x, y, z) 로 표현된다.

회전용 단위 쿼터니언은 이렇게 정의된다.

q=cos(θ/2)+(xi+yj+zk)sin(θ/2)

여기서θ는 회전 각도, (x,y,z)는 회전축 단위벡터이다.

"어떤 축을 기준으로 얼마만큼 돌릴지"를 압축적으로 담아둔 수학적 표현이 바로 쿼터니언.

 

Homogeneous Coordinates (동차 좌표계)의 활용

동차 좌표계(Homogeneous Coordinates)는 이동(Translation), 회전(Rotation), 투영(Projection) 같은 다양한 기하학적 변환을 하나의 행렬 곱으로 통합하기 위해 사용된다. 고등학교 기하학 수업에서 학습한 투영과 같은 원리이기에 상대적으로 쉽게 이해할수 있을 것이다.

기존의 3D 좌표 (x, y, z)는 동차 좌표계에서 (x, y, z, 1)로 확장된다.

이 마지막 성분 w(1) 는 “스케일링”과 “투영”을 처리하는 데 중요한 역할을 한다.

단순히 좌표를 움직이는 방법이라고 생각하자.

동차 좌표계를 활용한 변환 코드 알아보기

아래 코드는 하나의 점 (1,2,3)을

1)이동, 2) 회전, 3) 투영하는 과정이다.

import numpy as np

# 1. 원래 3D 점 (동차 좌표로 표현: w=1)
point = np.array([1, 2, 3, 1])  # (x=1, y=2, z=3)

# 2. 이동 변환 (x+3, y+2, z+5)
T = np.array([
    [1, 0, 0, 3],
    [0, 1, 0, 2],
    [0, 0, 1, 5],
    [0, 0, 0, 1]
])

# 3. z축 회전 (90도)
theta = np.radians(90)
Rz = np.array([
    [np.cos(theta), -np.sin(theta), 0, 0],
    [np.sin(theta),  np.cos(theta), 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1]
])

# 4. 원근 투영 행렬 (focal length = 2, d=5)
f, d = 2, 5
P = np.array([
    [f, 0, 0, 0],
    [0, f, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 1/d, 0]
])

# 5. 전체 변환 (투영 회전 이동)
point_transformed = P @ Rz @ T @ point

# 6. 동차 좌표 → 3D 복원 (w로 나눔)
point_final = point_transformed[:3] / point_transformed[3]

print("원래 점 (world):", point[:3])
print("변환 후 동차 좌표:", point_transformed)
print("투영 후 최종 좌표 (카메라 평면):", point_final)

실행 결과 예시

원래 점 (world): [1 2 3]
변환 후 동차 좌표: [ -2.    4.    8.    2.6]
투영 후 최종 좌표 (카메라 평면): [-0.769  1.538  3.077]

코드 해석

  1. 원래 점은 ((1,2,3)).
  2. 먼저 ((+3,+2,+5)) 만큼 이동.
  3. 이어서 z축 기준으로 90° 회전.
  4. 마지막으로 원근 투영을 적용하여 카메라 평면에 사상.

최종적으로 점은 화면 좌표 (-0.769, 1.538) 근처에 나타나게 된다.

핵심 포인트

  • 동차 좌표를 쓰면 이동, 회전, 투영을 전부 행렬 곱으로 처리 가능하다.
  • (w) 좌표는 투영 후 결과를 복원하는 데 사용된다.
  • 이 방식은 그래픽스 파이프라인, 로보틱스, 컴퓨터 비전에서 표준적으로 사용된다.

관련글 더보기