본문으로 건너뛰기
뒤로가기

[Vulkan] 좌표계의 Y축은 왜 아래를 향할까?

이 글은 Vulkan을 학습하는 개발자의 관점에서, Vulkan 좌표계의 Y축이 아래를 향하는 이유와 원리를 사실 위주로 정리한 기록입니다.


1. 정점의 여정: 그래픽스 파이프라인의 좌표 변환

하나의 정점이 3D 모델 파일에서부터 최종 화면의 픽셀이 되기까지, 여러 단계의 좌표계 변환을 거칩니다. 이 전체 과정에서 어떤 부분은 개발자가 직접 제어하고, 어떤 부분은 GPU가 자동으로 처리하는지 이해하는 것이 중요했습니다.

단계담당 주체결과 공간설명
① 모델 변환셰이더 or CPU모델 공간 → 월드 공간모델 매트릭스로 변환
② 뷰 변환셰이더 or CPU월드 공간 → 뷰(카메라) 공간카메라 기준으로 변환
③ 투영 변환버텍스 셰이더뷰 공간 → 클립 공간gl_Position에 투영 행렬 곱셈
④ 원근 나누기GPU 자동클립 공간 → NDC 공간(x, y, z) / w 연산
⑤ 뷰포트 변환GPU 자동NDC 공간 → 프레임버퍼 좌표NDC를 실제 픽셀 위치로 매핑

제가 가졌던 핵심 질문, “Y축은 언제 뒤집히는가?”에 대한 답은 GPU가 자동으로 처리하는 마지막 두 단계, 즉 NDC뷰포트 변환 에 있었습니다.


2. 명세에서 찾은 좌표계의 단서

Vulkan 명세 29.7장과 29.9장에서 이 핵심 과정을 찾을 수 있었습니다.

클립 공간(Clip Space)과 NDC: 수학적 Y-up 공간

버텍스 셰이더의 최종 출력(gl_Position)이 존재하는 클립 공간과, 이후 GPU에 의해 자동으로 w로 나누어져 생성되는 정규화된 장치 좌표(NDC) 는 물리적인 화면과 분리된 순수한 수학적 공간입니다. 명세 29.7장에 따르면 NDC의 범위는 다음과 같습니다.

1xd11yd10zd1-1 \le x_d \le 1 \\ -1 \le y_d \le 1 \\ 0 \le z_d \le 1

이 정의를 통해 NDC 공간 자체는 Y값이 증가할수록 위로 향하는 전통적인 Y-up 좌표계의 형태라는 것을 알 수 있습니다.

뷰포트 변환: 결정적인 좌표 매핑

제가 파악한 문제의 핵심은, 이 추상적인 Y-up NDC 공간을 실제 픽셀로 이루어진 구체적인 창(Window) 공간 으로 변환하는 뷰포트 변환(Viewport Transform) 단계였습니다.

Vulkan 명세 공식과 그 의미

Vulkan 명세 29.9장 “Controlling the Viewport”는 변환 공식을 다음과 같이 정의합니다.

여기서 px=width2p_x = \frac{width}{2} 이고, ox=x+width2o_x = x + \frac{width}{2} 입니다. (VkViewportx, width 필드에 해당)

이 공식이 제가 흔히 사용하던 형태와 동일한지 증명해 보았습니다.

  1. 명세 공식 xf=pxxd+oxx_f = p_x \cdot x_d + o_x 에 각 항목을 대입합니다.
xf=(width2)xd+(x+width2)x_f = (\frac{width}{2}) \cdot x_d + (x + \frac{width}{2})
  1. 항의 순서를 바꾸고 width2\frac{width}{2} 로 묶어줍니다.
xf=xdwidth2+width2+xx_f = x_d \cdot \frac{width}{2} + \frac{width}{2} + x xf=(xd+1)width2+xx_f = (x_d + 1) \cdot \frac{width}{2} + x
  1. 이제 변수 이름을 제가 알아보기 쉬운 형태로 바꾸면 (xfxfbx_f \rightarrow x_{fb}, xdxndcx_d \rightarrow x_{ndc}, xxoffsetx \rightarrow x_{offset}), 제가 알던 공식과 완벽히 일치함을 알 수 있습니다.
xfb=(xndc+1)×width2+xoffsetx_{fb} = (x_{ndc} + 1) \times \frac{width}{2} + x_{offset}

Y축 또한 동일한 방식으로 유도됩니다. 이처럼 두 공식은 표현 방식만 다를 뿐, 수학적으로는 100% 동일합니다. 이제 이 공식을 바탕으로 변환 과정을 분석해 보았습니다.

예시: 1920x1080 뷰포트의 기본 변환

가로 1920, 세로 1080 픽셀 크기의 전체 화면을 사용하는 뷰포트가 있다고 가정해 보았습니다. (width=1920, height=1080, x_offset=0, y_offset=0)

대응법: 음수 뷰포트 높이(Negative Viewport Height)

Vulkan 1.1부터는 이 Y축 반전에 대응할 수 있는 흥미로운 방법이 핵심 기능으로 포함된 것을 알게 되었습니다. 바로 뷰포트의 높이를 음수로 설정 하는 것입니다.

VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = (float)swapchainExtent.height; // y 오프셋을 높이로 설정
viewport.width = (float)swapchainExtent.width;
viewport.height = -(float)swapchainExtent.height; // 높이를 음수로 설정
// ...

이 설정은 뷰포트 변환 공식의 Y축 반전을 수학적으로 상쇄시킵니다.

예시: 음수 높이를 적용한 1920x1080 뷰포트 변환

음수 뷰포트 설정을 적용해 보았습니다. (height=-1080, y_offset=1080)


3. 앞면 판별 규칙: 부호 있는 면적과 외적

Vulkan 명세 30.12.1장 “Basic Polygon Rasterization”은 폴리곤의 앞면(front-face)을 판별하는 규칙을 정의합니다. 그 기준은 프레임버퍼 좌표계(framebuffer coordinates) 에서 계산된 폴리곤의 부호 있는 면적(signed area)입니다.

GPU는 프레임버퍼 좌표계의 정점들을 순회하는 방향을 고려하여 면적을 계산하고, 그 결과의 부호(양수/음수) 를 통해 와인딩 순서를 판별합니다.

vkCmdSetFrontFace 설정은 이 계산 결과를 어떻게 해석할지 GPU에게 알려주는 규칙입니다. 이 모든 과정은 Y축이 이미 뒤집힌 창 좌표계에서 일어나므로, NDC에서 CCW였던 삼각형은 CW로, CW였던 삼각형은 CCW로 최종 판별됩니다.


학습을 마치며

이번 학습을 통해 “Vulkan은 Y-down이다”라는 말이 단순히 외워야 할 규칙이 아니라, “Vulkan의 좌표계 시스템과 뷰포트/프레임버퍼 좌표계 시스템 사이의 상세한 매커니즘의 결과” 라는 것을 이해하게 되었습니다. 이 글은 제가 Vulkan 명세를 찾아보며 그 과정을 수학적으로 따라가 본 기록입니다.


이 게시물은 학습한 내용을 바탕으로 초안을 작성한 뒤, LLM의 도움을 받아 내용을 검수하고 다듬어 완성되었습니다.


공유하기:

이전 글
[Vulkan] MoltenVK 종료 시 메모리 할당 메시지 분석
다음 글
[Vulkan] WSI: GLFW를 이용한 창과 서피스 연결