게임 개발

월드 좌표에서 화면 좌표까지: 머리 위 UI가 어긋나는 이유

모델·월드·뷰·클립·화면 좌표와 원근 나눗셈을 구분합니다. WorldToScreenPoint로 UI를 붙일 때 카메라 뒤와 Canvas 좌표를 확인하는 순서입니다.

CodingJoa 수정 2026.09.22 3분 정도 걸립니다
짙은 남색 배경에 로컬 좌표, 월드 좌표, 뷰 좌표, 클립 좌표, 화면 좌표 다섯 단계가 나란히 놓인 변환 흐름도. 각 단계마다 초록색 정육면체와 x, y, z 축이 그려져 있고 단계 사이 화살표에는 Model, View, Projection, Viewport라는 이름이 적혀 있다

캐릭터 머리 위에 이름표를 붙이는데 해상도를 바꾸면 위치가 어긋날 때가 있습니다. 월드 위치를 화면 위치로 바꾼 것까지는 맞지만, 화면 픽셀을 Canvas의 로컬 위치처럼 사용했을 수 있습니다. 이 문제를 이해하려면 좌표 변환의 마지막 단계까지 봐야 합니다.

클립 좌표는 아직 화면 픽셀이 아니다

열벡터 표기에서 정점은 다음 순서로 변환됩니다.

local point → Model → world → View → view → Projection → clip
clip.xyz / clip.w → normalized device coordinates → viewport → screen pixels

Model 행렬은 오브젝트의 배치, View 행렬은 카메라 기준 변환, Projection 행렬은 투영을 담당합니다. 원근 투영 뒤에는 w로 나누는 단계가 필요합니다. 이 나눗셈 때문에 먼 물체가 작게 보입니다. “로컬부터 화면까지 전부 행렬 곱”이라고만 설명하면 이 단계가 빠집니다.

동차 좌표의 네 번째 성분은 이동과 투영을 같은 계산 틀에서 다루기 위한 표현입니다. 게임 월드가 물리적으로 4차원이라는 뜻은 아닙니다. 아핀 변환에서 점의 w를 1, 방향의 w를 0으로 두면 이동 성분이 점에만 적용되는 이유도 볼 수 있습니다.

이름표를 붙이는 최소 예제

다음 WorldLabel.cs는 Screen Space - Overlay Canvas 전용입니다. Label의 부모 RectTransform을 Label Parent에, 이름표를 Label에 지정하고 Label 앵커는 중앙으로 설정합니다. Target은 캐릭터 머리 위치의 Transform입니다.

using UnityEngine;

public sealed class WorldLabel : MonoBehaviour
{
    [SerializeField] private Camera worldCamera;
    [SerializeField] private Transform target;
    [SerializeField] private RectTransform labelParent;
    [SerializeField] private RectTransform label;

    private void LateUpdate()
    {
        if (worldCamera == null || target == null || labelParent == null || label == null)
            return;

        Vector3 screenPoint = worldCamera.WorldToScreenPoint(target.position);
        bool inFront = screenPoint.z > worldCamera.nearClipPlane;
        label.gameObject.SetActive(inFront);
        if (!inFront)
            return;

        if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
            labelParent, screenPoint, null, out Vector2 localPoint))
        {
            label.localPosition = new Vector3(localPoint.x, localPoint.y, 0f);
        }
    }
}

이 컴포넌트는 Label 자체가 아니라 계속 활성 상태인 별도 관리자에 붙입니다. Label에 붙이면 카메라 뒤에서 자신을 비활성화한 뒤 다시 켤 Update가 돌지 않습니다.

Overlay Canvas에서는 변환 함수의 카메라 인자에 null을 넣습니다. Screen Space - Camera 또는 World Space Canvas로 바꾸려면 해당 Canvas를 렌더링하는 카메라를 사용하도록 코드를 바꿔야 합니다.

결과를 확인할 순서

  1. 카메라 정면의 캐릭터를 좌우로 이동합니다. 이름표가 같은 방향으로 따라가야 합니다.
  2. 게임 뷰 해상도와 Canvas Scaler 설정을 바꿉니다. 픽셀을 그대로 대입했을 때 생기는 오차가 없어야 합니다.
  3. 캐릭터를 카메라 뒤로 이동합니다. 이름표가 반대쪽 화면에 나타나지 않고 숨겨져야 합니다.
  4. Label의 부모 크기와 pivot을 바꿉니다. localPosition 변환이 올바른 부모를 기준으로 하는지 확인합니다.

예제는 화면 밖으로 나간 이름표를 가장자리에 고정하거나 다른 물체에 가려졌는지 검사하지 않습니다. 그런 기능은 화면 범위 검사와 가림 판정을 추가해야 합니다. WorldToScreenPoint가 값을 반환했다는 것만으로 실제 화면에 보이는 것은 아닙니다.

참고 API

이 글 공유하기 X Facebook 네이버

이 글은 기술 설명과 참고 자료, 글쓴이의 판단을 담고 있습니다. 예제와 실제 사용 사례의 적용 조건은 본문을 참고해주세요. 도구의 버전·가격·동작은 시간이 지나면 달라질 수 있으니, 따라 하실 때는 공식 문서를 함께 확인해주세요. 자세한 내용은 이용약관에 정리해두었습니다.