오브젝트를 제자리에서 돌리려 했는데 원점을 중심으로 빙글빙글 돈다면 회전값보다 변환 순서를 먼저 확인합니다. 같은 회전과 이동을 사용해도 순서가 바뀌면 결과가 달라집니다.
여기서는 Unity의 Matrix4x4와 열벡터 표기를 기준으로 설명합니다. 다른 수학 라이브러리를 볼 때는 벡터를 왼쪽에 곱하는지 오른쪽에 곱하는지부터 확인해야 합니다.
점 하나로 순서 차이를 계산해 보자
로컬 점 (1,0,0)을 Z축으로 90도 회전한 뒤 X축으로 10만큼 옮긴다고 합시다.
- 회전 먼저: (1,0,0) → (0,1,0) → (10,1,0)
- 이동 먼저: (1,0,0) → (11,0,0) → (0,11,0)
첫 번째가 T * R * point, 두 번째가 R * T * point입니다. 점에서 가까운 오른쪽 연산부터 적용됩니다. 숫자 두 개만 비교해도 “자전하려 했는데 공전한다”는 증상을 설명할 수 있습니다.
Unity에서 두 결과를 나란히 출력하기
TransformOrderExample.cs를 오브젝트에 붙이면 Start에서 계산합니다.
using UnityEngine;
public sealed class TransformOrderExample : MonoBehaviour
{
private void Start()
{
Vector3 point = Vector3.right;
Matrix4x4 translation = Matrix4x4.Translate(new Vector3(10f, 0f, 0f));
Matrix4x4 rotation = Matrix4x4.Rotate(Quaternion.Euler(0f, 0f, 90f));
Vector3 rotateThenMove = (translation * rotation).MultiplyPoint3x4(point);
Vector3 moveThenRotate = (rotation * translation).MultiplyPoint3x4(point);
Debug.Log(rotateThenMove);
Debug.Log(moveThenRotate);
}
}
기대값은 각각 (10,1,0), (0,11,0)입니다. 부동소수점 계산 때문에 0이어야 할 성분이 아주 작은 값일 수 있습니다. 비교할 때는 각 성분의 완전 일치보다 두 결과 사이 거리의 허용 오차를 사용합니다.
TRS와 부모 Transform 연결하기
일반적인 로컬 변환은 T * R * S입니다. 크기를 적용하고, 회전한 뒤, 이동합니다. 부모가 있다면 자식의 월드 변환은 parentWorld * childLocal이 됩니다. 자식의 localPosition에 부모 위치를 수동으로 한 번 더 더하면 이동이 중복될 수 있습니다.
부모가 회전 또는 스케일을 가진 경우 parent.position + child.localPosition만으로는 월드 위치를 얻을 수 없습니다. 단순 덧셈은 부모의 회전이 없고 스케일이 1일 때만 맞습니다.
반대로 월드 점을 로컬로 되돌릴 때는 InverseTransformPoint를 사용합니다. 왕복 변환 후 원래 점과 가까운지 확인하면 좌표 공간을 섞었는지 찾기 좋습니다. 단, 스케일이 0인 축이 있으면 역변환이 정상적으로 정의되지 않습니다.
언제 직접 행렬을 만들까
게임 오브젝트를 배치하는 일이라면 Transform API로 충분합니다. 직접 행렬을 구성하는 예제는 셰이더 데이터나 커스텀 메시 좌표를 다룰 때 연산 순서를 확인하는 데 유용합니다. MultiplyPoint3x4는 일반적인 이동·회전·크기 변환에 쓰고, 원근 투영까지 같은 방식으로 처리하지는 않습니다.
화면 좌표까지의 변환에서는 클립 좌표 뒤에 필요한 나눗셈을 따로 다룹니다.
