
UGameplayStatics::ProjectWorldToScreen함수는 언리얼 엔진에서 3D 월드 좌표를 현재 플레이어의 2D 뷰포트(스크린) 좌표로 변환하는 함수이다.
ProjectWorldToScreen 는 어떻게 동작하는걸까?이 글은 언리얼 엔진 5.6 기준으로 작성되었습니다.
// UGameplayStatics.h
static bool ProjectWorldToScreen(
const APlayerController* Player,
const FVector& WorldPosition,
FVector2D& ScreenPosition,
bool bPlayerViewportRelative
);
간단 예시:
bool GetActorScreenPosition(AActor* Actor, FVector2D& OutScreenPos)
{
if (!Actor) return false;
UWorld* World = Actor->GetWorld();
if (!World) return false;
// 플레이어 컨트롤러 가져오기
APlayerController* PC = World->GetFirstPlayerController();
if (!PC) return false;
// 월드 좌표 → 스크린 좌표 변환
const FVector WorldPos = Actor->GetActorLocation();
return UGameplayStatics::ProjectWorldToScreen(PC, WorldPos, OutScreenPos);
}
bPlayerViewportRelative = false가 기본값이다. FVector2D ScreenPos;
if (GetActorScreenPosition(SomeActor, ScreenPos))
{
UE_LOG(LogTemp, Log, TEXT("액터가 스크린에 존재함");
}
else
{
UE_LOG(LogTemp, Warning, TEXT("액터는 스크린 상에 존재하지 않음"));
}
먼저, 엔진 코드를 참고하면 ProjectWorldToScreen() 함수의 흐름은 이렇다.
LocalPlayer를 가져온다.ULocalPlayer* const LP = Player ? Player->GetLocalPlayer() : nullptr;
LocalPlayer를 가져와야 분할화면 상황(멀티로컬)에서 구분 가능FSceneViewProjectionData ProjectionData;
if (LP->GetProjectionData(LP->ViewportClient->Viewport, /*out*/ ProjectionData))
LocalPlayer 내부 GetProjectionData()함수를 이용해 값을 채운다.GetProjectionData() 내부 작업
1. ULocalPlayer::GetProjectionData 내부에서
ProjectionData.ViewOrigin = StereoViewLocation; // 카메라/눈 위치
ProjectionData.ViewRotationMatrix =
FInverseRotationMatrix(ViewInfo.Rotation) *
FMatrix(
FPlane(0,0,1,0), // UE 좌표계를 뷰공간 축으로 재배치
FPlane(1,0,0,0),
FPlane(0,1,0,0),
FPlane(0,0,0,1));
FInverseRotationMatrix는 카메라 회전의 역행렬을 곱해 월드를 카메라 기준으로 회전 시킨다.2. FMinimalViewInfo::CalculateProjectionMatrixGivenView()
GetProjectionData 마지막 부분에서 호출되는 함수ViewInfo 등을 사용해 Reversed-Z 투영 행렬을 만든다...!요약
FMatrix const ViewProjectionMatrix = ProjectionData.ComputeViewProjectionMatrix();
bool bResult = FSceneView::ProjectWorldToScreen(WorldPosition,
ProjectionData.GetConstrainedViewRect(), ViewProjectionMatrix, ScreenPosition);
FSceneView::ProjectWorldToScreen 는 내부적으로 FVector4 ClipSpace = ViewProjectionMatrix.TransformFVector4(WorldPos)ViewProjectionMatrix (뷰 행렬 × 투영 행렬)를 곱해 클립 공간 좌표를 만들고if (bPlayerViewportRelative)
{
ScreenPosition -= FVector2D(ProjectionData.GetConstrainedViewRect().Min);
}
간단하게 보자면
월드 좌표를 → 카메라 기준으로 보기 → 투영해서 납작하게 만들기 → 정규화 → 픽셀 좌표 변환한다 고 할 수 있다.
밑은 실제 행렬 적용 과정이다.
ProjectWorldToScreen 에서 사용하는 행렬 곱셈(좌표 변환) 과정
- 월드 좌표
어떤 오브젝트의 위치가 P_world 라고 하자.
- 뷰 (View Matrix)
카메라 위치를 (=ProjectionData.ViewOrigin), 카메라 회전을 라고 두면
R^{-1}(역회전)을 적용해 카메라가 원점에 있고 정면을 보는 좌표계로 옮겨준다.
- UE 내부에선
ViewRotationMatrix와-ViewOrigin을 합쳐 ViewMatrix를 만드는 걸로 이 작업을 수행
- 투영 (Projection Matrix)
투영 행렬 (=ProjectionData.ProjectionMatrix, FOV/종횡비/near/far 반영)를 곱한다.
- 3D 공간을 2D 평면에 투영함 (멀리 있는 건 작게, 가까운 건 크게)
- 퍼스펙티브 나눗셈 → NDC
- 는 카메라까지의 거리 정보라고 생각할 수 있다.
- 결과가 범위에 있으면 화면 안, 아니면 클리핑 대상으로 판단
- UE는 깊이에 Reversed-Z를 쓰지만(멀리 있을수록 작은 z), z는 독립적으로 깊이에 계산되는 값이므로 화면상 x·y 변환식에는 영향은 없다.
- 왼쪽그림이 눈으로 본 공간, 카메라는 원점에 위치해 -Z축을 향해 있음 (오른손 좌표계)
- 오른쪽 그림이 NDC, 정규화된 정육면체 범위를 보여준다. +Z축이 전방 (왼손 좌표계)
- 이 과정에서:
X좌표: [l, r] → [-1, 1]
Y좌표: [b, t] → [-1, 1]
Z좌표: [-n, -f] → [-1, 1] (NDC, 부호 반전 포함)
Reversed-Z
UE의 Reversed-Z는 멀리 있는 점일수록 z가 작아지고, 가까울수록 z=1에 가까워진다는 특성이다.
추가적으로,
FSceneView::ProjectWorldToScreen의 반환값(bool) 은 다음과 같이 계산된다.FPlane Result = ViewProjectionMatrix.TransformFVector4(FVector4(WorldPosition,1));
bool bIsInsideView = Result.W > 0.0f; // 카메라 "앞"에 있는가?W의 부호로 결정된다. (w >0이면 카메라 전방, w < 0이면 후방)FSceneView::ProjectWorldToScreen는 전방 여부만을 반영한다.즉, bool 값이 true 더라도 XY 범위와 z_ndc 범위까지 검사해야 진짜 화면 안에 보이는지를 알 수 있는 것이다.
- NDC → 픽셀(Screen) 좌표
ConstrainedViewRect는 “최종” 뷰 사각형으로,ConstrainedViewRect= 라고 하면- 위 식으로 NDC(-1..1) 를 실제 픽셀 사각형에 매핑한다.
- 참고: NDC (Normalized Device Coordinates)는 3D 좌표를 화면에 투영(projection)한 뒤, 정규화된 좌표계에 집어넣은 결과이다. 범위가 항상 -1 ~ +1 로 고정된다.
- 뷰포트 전체가 아니라 유효 영역(ConstrainedViewRect) 기준으로 매핑됨
- 좌표가 어긋나 보인다면 이 유효 영역 때문일 수 있음
bPlayerViewportRelative == true라면
- 스플릿 스크린에서 해당 플레이어 뷰포트의 (0,0) 을 원점으로 맞춰준다.
bPlayerViewportRelative위에서 말했다싶이, 좌표 원점을 어디로 잡을 것인지 결정해 주는 변수이다.
bPlayerViewportRelative = false (기본값) 이면
bPlayerViewportRelative = true 이면
각 플레이어 뷰포트의 좌상단을 원점 (0,0)으로 다시 맞춰준다. (분할 화면을 사용하는 경우를 예시로 들 수 있음)
예를 들어 1920*1080 화면에서

덧. It Takes Two 게임이 분할 화면을 사용한다.

ProjectWorldToScreen은 ProjectionData와 ConstrainedViewRect를 사용해 월드→스크린 투영을 수행하는 함수이다.bPlayerViewportRelative = true면 결과 좌표는 플레이어 뷰포트 사각형 로컬 기준. 분할화면에 적합하고, 평상시에는 사용할 일이 거의 없을 듯 함.
- Unreal Engine 5.6 소스코드
- ReversedZ: https://forums.unrealengine.com/t/ue4-z-reversed-projection-matrix/318330
- 투영 행렬: https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/opengl-perspective-projection-matrix.html
- NDC: https://www.songho.ca/opengl/gl_projectionmatrix.html
- Youtube
3Blue1Brown의 선형대수 - 행렬 관련 영상들