뷰포트
고친 사람 github-actions[bot]
뷰포트는 화면에서 그림이 나타날 사각형을 정해 줍니다. 웹에서는 브라우저 창 안에서 페이지가 보이는 영역을 이렇게 부릅니다. 3D 그래픽스에서는 계산이 끝난 장면을 화면의 어느 사각형에 옮겨 그릴지를 이렇게 부릅니다. 두 분야 모두 어디에 얼마만 한 크기로 보여 줄지를 정한다는 뜻은 같습니다.
쉽고 빠른 이해
무슨 일을 하는 물건인가 — 그림을 내보낼 화면 속 사각형을 정합니다. 브라우저 창에서 페이지가 보이는 영역이 뷰포트입니다. 게임 화면을 반으로 나눠 두 사람이 따로 보면 뷰포트가 두 개입니다.
왜 이렇게 하나 — 그리는 쪽은 창이 얼마나 큰지 모른 채 계산을 시작합니다. 뷰포트가 있어야 그 결과를 지금 창의 크기와 위치에 맞춰 놓을 수 있습니다.
어떻게 도나
- 웹에서는 브라우저가 창 안의 보이는 영역을 기준 삼아 페이지를 배치합니다
- 페이지가 창보다 길면 스크롤이 그 영역을 페이지 위에서 옮깁니다
- 3D 그래픽스에서는 계산이 끝난 장면을 뷰포트 사각형에 맞게 늘이거나 줄입니다
언제 쓰나 — 웹에서 요소를 화면 크기에 맞출 때 뷰포트를 기준으로 삼습니다. 부모 요소에 맞출 때는 부모 기준의 비율 값을 씁니다. 3D 그래픽스에서 그림을 줄이지 않고 화면 일부에만 그리려면 뷰포트 대신 시저 테스트를 씁니다.
대가 — 3D 그래픽스에서는 뷰포트의 가로세로 비율이 장면을 계산할 때 쓴 비율과 다르면 그림이 찌그러집니다. 웹에서는 창 크기가 바뀔 때마다 배치를 다시 계산합니다.
상세
뷰포트의 뜻을 세운 뒤 웹 브라우저와 3D 그래픽스에서 각각 무엇을 가리키는지 봅니다. 끝에서는 두 분야의 차이와 글에서 어느 뜻인지 가르는 단서를 짚습니다.
뷰포트는 출력 화면 위에서 그림이 놓일 사각형입니다. 사각형은 시작 위치와 가로·세로 크기로 정해집니다. 모니터가 1920×1080 픽셀이어도 브라우저 창이 왼쪽 절반만 차지하면, 웹 페이지의 뷰포트는 그 절반 크기의 사각형입니다.
두 분야에서 가리키는 사각형
같은 뜻이 두 분야에서 조금 다른 물건에 붙어 있습니다. 세 가지 축으로 나란히 놓으면 이렇습니다.
| 분야 | 뷰포트가 가리키는 사각형 | 사각형 안에 들어가는 그림 | 사각형을 키우면 |
|---|---|---|---|
| 웹 브라우저 | 창 안에서 페이지가 보이는 영역 | 페이지의 일부 | 페이지가 더 많이 보입니다 |
| 3D 그래픽스 | 결과 이미지 위에서 장면을 옮겨 그릴 영역 | 계산된 장면 전체 | 같은 장면이 더 크게 늘어납니다 |
마지막 칸이 두 분야를 가르는 대목입니다. 웹의 뷰포트는 넓은 페이지를 내다보는 창입니다. 3D 그래픽스의 뷰포트는 장면 전체를 끼워 넣는 틀입니다. 왜 이렇게 갈리는지는 아래 「크기를 바꾸면 갈리는 결과」에서 다룹니다.
웹 브라우저의 뷰포트
브라우저는 문서를 끝없이 넓은 평면 위에 배치합니다. 이 평면을 캔버스라고 부릅니다. 창 안의 사각형은 그 평면의 일부만 비춥니다. 이 사각형이 뷰포트입니다.
문서가 뷰포트보다 길면 브라우저는 스크롤 막대를 붙입니다. 스크롤은 문서를 움직이는 일이 아닙니다. 뷰포트를 문서 위에서 옮겨 다른 부분을 비추는 일입니다.
브라우저는 페이지의 요소 하나하나에 사각형 영역을 하나씩 줍니다. 이 사각형을 요소의 상자라고 합니다. 상자는 서로 겹겹이 들어가 있습니다. 안쪽 상자는 바깥 상자의 폭에 맞춰 자기 폭을 정합니다.
맨 바깥 상자의 폭은 뷰포트와 크기가 같은 사각형을 기준으로 정합니다. 이 사각형이 초기 컨테이닝 블록입니다. 뷰포트와는 크기만 같은 별개의 사각형입니다. 스크롤해도 따라 움직이지 않고 페이지 맨 위에 머뭅니다.
뷰포트 크기는 이렇게 배치의 출발점이 됩니다. 뷰포트가 없으면 브라우저는 페이지를 몇 픽셀 폭으로 배치할지 정할 기준이 없습니다. 창 크기를 바꾸면 이 기준이 바뀌므로 페이지 배치를 다시 계산합니다. 창을 좁히면 한 줄에 들어가던 글이 두 줄로 접히는 것이 그 결과입니다.
CSS(Cascading Style Sheets, 웹 페이지의 모양을 정하는 규칙 언어)에는 뷰포트 크기를 직접 읽는 수단이 셋 있습니다. 모두 요소의 크기나 위치를 부모 상자가 아니라 화면에 맞추고 싶을 때 씁니다.
| 수단 | 하는 일 | 쓰는 예 |
|---|---|---|
뷰포트 단위 vw · vh |
뷰포트 너비·높이의 100분의 1을 한 단위로 씁니다 | 첫 화면을 꽉 채우는 배너 |
| 미디어 쿼리 | 뷰포트 폭에 따라 다른 규칙을 적용합니다 | 좁은 화면에서 메뉴를 한 줄로 접기 |
고정 위치 지정 position: fixed |
요소를 뷰포트에 붙여 스크롤해도 움직이지 않게 합니다 | 화면 위에 늘 떠 있는 상단 바 |
세 수단은 기준이 화면이라는 점이 같습니다. 반대로 요소를 부모 상자의 크기에 맞추려면 이 수단들을 쓰지 않고 부모 기준의 비율 값을 씁니다.
휴대폰 브라우저의 두 뷰포트
휴대폰 브라우저에는 뷰포트가 둘 있습니다. 둘이 필요한 까닭은 손가락으로 벌려 화면을 확대하는 핀치 줌 때문입니다. 확대할 때마다 배치를 다시 계산하면 글이 계속 새로 접혀 읽을 수가 없습니다.
레이아웃 뷰포트는 배치의 기준이 되는 사각형입니다. 확대해도 크기가 바뀌지 않으므로 배치가 흔들리지 않습니다.
비주얼 뷰포트는 지금 화면에 보이는 부분입니다. 확대하면 비주얼 뷰포트가 작아집니다. 그러면 레이아웃 뷰포트 안의 좁은 영역만 크게 보입니다.
휴대폰 브라우저는 데스크톱용으로 만든 페이지가 깨지지 않도록 레이아웃 뷰포트를 화면보다 넓게 잡습니다. 그리고 그 넓은 배치를 축소해서 보여 줍니다. 휴대폰에 맞춰 만든 페이지는 HTML(HyperText Markup Language, 웹 문서의 뼈대를 적는 언어) 문서의 <head> 안에 아래 한 줄을 넣어 이 동작을 끕니다.
<meta name="viewport"
content="width=device-width, initial-scale=1">
width=device-width 는 레이아웃 뷰포트의 폭을 기기 화면 폭에 맞추라는 뜻입니다. initial-scale=1 은 처음 열 때 확대도 축소도 하지 말라는 뜻입니다. 이 줄이 없는 페이지를 휴대폰으로 열면 데스크톱 폭의 배치가 작게 줄어들어 글자가 깨알처럼 보입니다.
3D 그래픽스의 뷰포트
3D 그래픽스에서 뷰포트는 계산과 화면 사이의 마지막 연결 단계를 맡습니다. 장면의 좌표가 어떤 모양으로 뷰포트에 도착하는지부터 봅니다. 그다음 뷰포트가 그 좌표를 픽셀 위치로 바꾸는 식을 봅니다.
장면을 그리는 계산은 GPU(Graphics Processing Unit, 그래픽 처리 장치) 위의 여러 단계를 거칩니다. 이렇게 차례로 이어진 단계 전체를 그래픽스 파이프라인이라고 부릅니다.
파이프라인 맨 앞의 정점 셰이더는 도형의 꼭짓점마다 위치를 계산하는 작은 프로그램입니다. 정점 셰이더는 보통 꼭짓점 좌표에 투영 행렬을 곱합니다. 투영 행렬은 카메라에 보이는 범위를 뒤에 나올 좌표 상자에 맞춰 넣는 계산입니다.
다음 단계인 클리핑은 카메라에 보이는 범위 밖으로 나간 부분을 잘라 냅니다. 화면에 안 나올 부분을 미리 버려 뒤 단계가 헛일을 하지 않게 합니다.
남은 꼭짓점 좌표는 원근 나누기를 거칩니다. 원근 나누기는 x·y·z 좌표를 꼭짓점마다 딸린 넷째 값 w로 나누는 계산입니다. 투영 행렬은 멀리 있는 점일수록 w를 크게 만들어 둡니다. 나눈 뒤에는 먼 것이 작아집니다.
나눈 결과는 화면 크기와 상관없는 좌표 상자 안의 값이 됩니다. 이 좌표를 정규화 장치 좌표라고 부릅니다. 가로와 세로가 각각 -1에서 1 사이에 들어옵니다. 좌표 상자에는 앞뒤 축도 있습니다. 이 축의 값은 카메라에서 얼마나 먼지를 나타냅니다.
좌표 상자는 창이 몇 픽셀인지 모릅니다. 덕분에 같은 계산을 어느 크기의 창에든 쓸 수 있습니다. 대신 마지막에 이 상자를 실제 픽셀 사각형으로 옮기는 단계가 필요합니다.
그 단계가 뷰포트 변환입니다. 좌표 상자가 옮겨 갈 사각형이 뷰포트입니다.
flowchart TD
A["정점 셰이더 · 투영 행렬을 곱해 꼭짓점 위치 계산"] --> K["클리핑 · 보이는 범위 밖을 잘라 냄"]
K --> B["원근 나누기 · 좌표를 w 로 나눔"]
B --> C["정규화 장치 좌표 · -1 에서 1 사이 좌표 상자"]
C --> D["뷰포트 변환 · 좌표 상자를 픽셀 사각형으로"]
D --> E["래스터화 · 도형을 픽셀로 채움"]
그림의 맨 아래 래스터화는 도형이 덮는 픽셀을 골라 칠할 준비를 하는 단계입니다. 뷰포트 변환이 끝나야 도형이 어느 픽셀 위에 있는지 알 수 있으므로 래스터화는 그 뒤에 옵니다.
그래픽스 API(Application Programming Interface, 프로그램이 기능을 불러 쓰는 창구)에는 뷰포트를 네 값으로 넘깁니다. 사각형 시작 모서리의 x·y 위치와 너비·높이입니다.
깊이 버퍼는 픽셀마다 거기 그린 것이 카메라에서 얼마나 먼지 적어 두는 메모리입니다. 가까운 것이 먼 것을 가리게 할 때 씁니다. 많은 API가 뷰포트에 깊이의 최솟값·최댓값 두 값을 더 받습니다. 좌표 상자의 앞뒤 좌표를 이 두 값 사이로 옮겨 깊이 버퍼에 적습니다.
아래는 너비 800, 높이 600 짜리 뷰포트에서 좌표 상자의 점 셋이 어디로 가는지 계산한 것입니다. 왼쪽 위를 시작 모서리로 잡았습니다. nx · ny 는 정규화 장치 좌표입니다. sx · sy 는 화면 픽셀 위치입니다.
뷰포트 x=0 y=0 너비=800 높이=600
sx = x + (nx + 1) / 2 * 너비
sy = y + (1 - ny) / 2 * 높이
(nx, ny) = ( 0, 0) // (400, 300)
(nx, ny) = ( 1, 1) // (800, 0)
(nx, ny) = (-1, -1) // ( 0, 600)
좌표 상자의 한가운데는 사각형의 한가운데로 갑니다. 좌표 상자의 오른쪽 위 모서리는 사각형의 오른쪽 위 모서리로 갑니다.
좌표 상자의 세로축은 위로 커집니다. 화면의 세로축은 아래로 커집니다. sy 식에서 부호를 뒤집는 까닭입니다. 시작 모서리와 두 축의 방향을 정하는 약속은 API마다 다릅니다. 뒤집지 않는 API도 있습니다.
한 화면을 나눠 쓰는 뷰포트
뷰포트는 결과 이미지 전체일 필요가 없습니다. 한 화면 안에 뷰포트를 여럿 두는 일이 흔합니다. 게임의 분할 화면과 미니맵이 그 예입니다.
텔레비전 뉴스를 보면 화면 구석의 작은 사각형 안에 수어 통역사가 나옵니다. 큰 화면 하나를 두 영상이 나눠 씁니다. 영상마다 어느 구석에 얼마만 한 크기로 나올지가 미리 정해져 있습니다.
통역사가 나오는 그 구석 사각형이 뷰포트에 해당합니다. 결과 이미지를 담는 메모리를 렌더 타깃이라고 합니다. 뷰포트는 그 안의 일부 사각형이어도 됩니다.
두 사람이 한 화면으로 하는 게임의 분할 화면이 그렇게 만들어집니다. 뷰포트를 왼쪽 절반으로 잡고 첫째 사람의 카메라로 장면을 그립니다. 그다음 뷰포트를 오른쪽 절반으로 바꾸고 둘째 사람의 카메라로 한 번 더 그립니다.
flowchart TD
subgraph T["렌더 타깃 한 장"]
L["왼쪽 뷰포트 · 첫째 사람 카메라"]
R["오른쪽 뷰포트 · 둘째 사람 카메라"]
end
두 뷰포트가 렌더 타깃 한 장을 나눠 씁니다. 화면 구석에 작게 뜨는 미니맵도 같은 방법입니다. 큰 장면을 다 그린 뒤, 뷰포트를 구석의 작은 사각형으로 바꾸고 위에서 내려다본 카메라로 한 번 더 그립니다.
크기를 바꾸면 갈리는 결과
웹과 3D 그래픽스는 뷰포트 크기를 바꿨을 때 반대로 움직입니다. 웹은 더 많이 보여 줍니다. 3D 그래픽스는 같은 것을 더 크게 보여 줍니다. 차이는 뷰포트에 무엇을 맞추느냐에서 옵니다.
확대하지 않은 상태에서 웹 브라우저는 페이지의 한 픽셀을 뷰포트의 한 픽셀에 대응시킵니다. 뷰포트가 커지면 그만큼 페이지가 더 많이 보입니다.
3D 그래픽스는 좌표 상자 전체를 뷰포트에 맞춰 늘입니다. 좌표 상자 안에 든 장면은 뷰포트가 크든 작든 전부 들어갑니다. 뷰포트가 커지면 같은 장면이 더 크게 그려질 뿐입니다.
이 차이에서 3D 그래픽스 쪽 대가가 생깁니다. 좌표 상자는 가로와 세로가 같은 범위입니다. 이 상자를 가로로 긴 뷰포트에 옮기면 장면이 가로로 늘어납니다.
이것을 막는 쪽이 투영 행렬입니다. 가로로 긴 화면이면 투영 행렬이 장면을 가로로 미리 좁혀 좌표 상자에 넣습니다. 뷰포트가 가로로 늘이면 원래 비율로 돌아옵니다. 창 크기가 바뀌면 뷰포트와 투영 행렬의 비율을 함께 고쳐야 그림이 찌그러지지 않습니다.
뷰포트를 줄이면 장면 전체가 작게 들어갑니다. 일부가 잘려 나가지는 않습니다. 장면 크기를 바꾸지 않고 화면의 일부에만 그리려면 시저 테스트를 씁니다. 시저 테스트는 정해 둔 사각형 밖의 픽셀을 버립니다.
어느 뜻인지 가르는 단서
글에서 뷰포트를 만나면 주변 낱말로 분야를 가립니다. CSS·스크롤·미디어 쿼리·휴대폰 화면이 같이 나오면 웹의 뷰포트입니다. 정점·셰이더·렌더 타깃·좌표 변환이 같이 나오면 3D 그래픽스의 뷰포트입니다.
관련 항목
뷰포트가 속하는 상위 분야
그래픽스 · 렌더링 · 브라우저 · 레이아웃 · CSS
웹 뷰포트가 비추는 문서 평면과 보기 영역
캔버스 · 레이아웃 뷰포트 · 비주얼 뷰포트 · 스크롤 · 핀치 줌 · 장치 픽셀 비율
웹 뷰포트 크기에 기대는 배치 규칙
뷰포트 단위 · 미디어 쿼리 · 고정 위치 지정 · 초기 컨테이닝 블록 · 메타 뷰포트 태그 · 반응형 웹 디자인
뷰포트 변환 앞뒤로 좌표가 지나가는 처리 단계
그래픽스 파이프라인 · 정점 셰이더 · 클리핑 · 원근 나누기 · 뷰포트 변환 · 래스터화
뷰포트가 이어 주는 좌표계
클립 공간 · 정규화 장치 좌표 · 화면 좌표 · 윈도 좌표
그래픽스 뷰포트와 함께 맞춰 두는 그리기 설정
렌더 타깃 · 스왑 체인 · 백 버퍼 · 깊이 버퍼 · 시저 테스트 · 투영 행렬 · 화면비
뷰포트를 설정하는 그래픽스 API
Direct3D · OpenGL · Vulkan · Metal · WebGL · WebGPU
여러 뷰포트로 짜는 화면 구성
분할 화면 · 미니맵 · 화면 속 화면
다른 이름: viewport