뷰 공간
고친 사람 github-actions[bot]
뷰 공간은 3차원 장면의 모든 점을 카메라에서 본 위치로 바꿔 적는 좌표 공간입니다. 카메라를 한가운데에 두고, 카메라가 바라보는 쪽을 앞으로 삼습니다. 이렇게 적어 두면 점마다 카메라 앞으로 얼마나 먼지가 좌표 하나로 드러납니다. 화면에 무엇이 어떤 크기로 보일지는 이 좌표에서 출발해 계산합니다.
쉽고 빠른 이해
뷰 공간은 장면 속 물건의 위치를 카메라 입장에서 다시 적어 둔 것입니다. 방 구석을 기준으로 적힌 탁자 위치가 "카메라 앞으로 5, 오른쪽으로 1" 로 바뀝니다.
이렇게 바꾸지 않으면 화면을 그릴 때마다 카메라가 어디서 어느 쪽을 보는지를 계산에 끌고 다녀야 합니다. 카메라 기준으로 옮겨 두면 멀고 가까움이 좌표 하나로 나옵니다. 그 뒤의 계산은 카메라가 어디 있든 같은 식을 씁니다.
어떻게 도나:
- 장면 전체를 카메라 위치만큼 반대로 옮겨서 카메라를 한가운데에 오게 합니다
- 카메라가 돌아간 만큼 장면을 반대로 돌려서 카메라 정면을 축 하나에 맞춥니다
- 두 동작을 행렬 하나로 묶어 두고 장면의 모든 점에 곱합니다
대가도 있습니다. 카메라가 움직이면 이 행렬을 다시 만들고 모든 점을 다시 옮겨야 합니다. 카메라가 보는 쪽을 + 로 잡을지 − 로 잡을지는 관례가 둘입니다. 투영 행렬과 이 공간의 관례가 어긋나면 물체가 화면에서 사라집니다.
카메라와 상관없이 화면 귀퉁이에 늘 붙어 있는 글자나 버튼은 이 공간을 거치지 않습니다.
상세
길을 알려 주는 말투는 두 가지입니다. 하나는 "시청 앞 사거리 북쪽 모퉁이"처럼 누구에게나 같은 주소로 말합니다. 다른 하나는 "지금 보는 쪽으로 열 걸음 가서 오른쪽"처럼 듣는 사람이 선 곳과 바라보는 쪽을 기준으로 말합니다. 같은 가게를 가리켜도 뒤의 말투는 듣는 사람이 몸을 돌리면 달라집니다.
뷰 공간은 3차원 장면을 뒤의 말투로 적는 좌표 공간입니다. 이 절은 장면 속 점 하나를 따라갑니다. 그 점이 이 공간에서 어떤 좌표를 갖는지, 어떻게 옮겨 오는지, 왜 굳이 옮기는지를 차례로 봅니다.
좌표 공간이 이어지는 차례
3차원 물체는 작은 삼각형을 이어 붙여 나타냅니다. 삼각형의 꼭짓점을 그래픽스에서는 정점이라고 부릅니다. 물체를 화면에 그리는 일은 정점마다 화면 위치를 구하는 데서 시작합니다.
같은 점이라도 무엇을 기준으로 재느냐에 따라 좌표 값이 달라집니다. 기준점과 축 방향을 정해 둔 좌표의 틀을 좌표 공간 또는 좌표계라고 부릅니다. 아래에 나오는 「~ 좌표계」와 「~ 공간」은 모두 이것입니다. 그래픽스는 정점을 한 좌표 공간에서 다음 좌표 공간으로 여러 번 옮겨 가며 화면 위치를 구합니다.
출발점은 모델 좌표계입니다. 물체 자신을 기준으로 잰 좌표라서, 의자를 방 어디에 두든 의자 다리 끝의 좌표는 바뀌지 않습니다.
물체를 장면 안에 놓으면 월드 좌표계로 넘어갑니다. 장면 전체가 함께 쓰는 좌표입니다. 앞의 비유에서 주소로 말하는 쪽이 이것입니다.
뷰 공간은 그다음입니다. 월드 좌표를 카메라 기준으로 다시 잰 좌표입니다.
뷰 공간 다음은 클립 공간입니다. 화면 밖으로 나갈 부분을 잘라 내는 단계의 좌표입니다.
좌표 공간을 넘어갈 때마다 행렬을 하나씩 곱합니다. 월드 좌표계로 옮기는 행렬이 모델 행렬입니다. 뷰 공간으로 옮기는 행렬은 뷰 행렬입니다.
클립 공간으로 옮기는 행렬은 투영 행렬입니다. 3차원 장면이 화면에 어떤 모양으로 비칠지를 정합니다. 원근을 주는 투영 행렬은 멀리 있는 것이 작게 보이도록 좌표를 바꿉니다.
flowchart TD
A["모델 좌표계 · 물체 자신 기준"] -->|"모델 행렬"| B["월드 좌표계 · 장면 전체 기준"]
B -->|"뷰 행렬"| C["뷰 공간 · 카메라 기준"]
C -->|"투영 행렬"| D["클립 공간 · 화면 밖을 잘라 내는 단계"]
뷰 공간은 장면을 다루는 쪽과 화면을 다루는 쪽 사이에 놓입니다. 그 앞까지는 물체를 어디에 둘지를 다룹니다. 그 뒤부터는 무엇이 어떤 크기로 보일지를 다룹니다.
카메라에 붙은 세 축
뷰 공간의 원점은 카메라가 있는 곳입니다. 축 셋은 카메라의 몸에 붙어 있습니다. 하나는 카메라의 오른쪽, 하나는 위쪽, 하나는 카메라가 바라보는 쪽을 가리킵니다.
이 절의 예는 오른쪽을 x, 위쪽을 y, 바라보는 쪽을 z 로 둡니다. 그러면 뷰 공간의 점 (x, y, z) 는 카메라에서 오른쪽으로 x, 위로 y, 앞으로 z 만큼 떨어진 점으로 읽힙니다.
(1, 0, 5) 는 카메라 정면에서 오른쪽으로 1, 앞으로 5 떨어진 점입니다. (0, 0, -3) 은 z 가 음수라 카메라 뒤쪽에 있는 점입니다. 카메라에 안 찍힌다는 것을 좌표만 보고 알 수 있습니다.
z 값을 깊이라고 부릅니다. 깊이는 카메라 정면 방향으로만 잰 거리입니다. 카메라에서 점까지 곧게 잰 거리와는 다릅니다. (1, 0, 5) 의 깊이는 5 이고, 곧게 잰 거리는 옆으로 비낀 만큼 5 보다 조금 깁니다.
월드 좌표를 뷰 공간으로 옮기는 뷰 행렬
예로 쓸 카메라는 월드의 (10, 0, 0) 에 서 있습니다. 이 카메라로 월드의 점 (11, 2, 15) 와 (15, 0, 0) 을 뷰 공간으로 옮겨 봅니다. 옮기는 동작은 둘입니다. 카메라 위치만큼 빼는 것과, 카메라가 돌아간 만큼 반대로 돌리는 것입니다.
카메라를 움직이는 것과 장면 전체를 반대로 움직이는 것은 화면에서 구별되지 않습니다. 카메라가 오른쪽으로 한 걸음 가면 화면 속 물건은 모두 왼쪽으로 한 걸음 밀려 보입니다. 뷰 행렬은 이 성질을 씁니다. 카메라를 원점에 묶어 두고 장면을 반대로 움직입니다.
첫 동작은 평행 이동입니다. 모든 점에서 카메라의 월드 좌표를 뺍니다. 카메라는 원점에 옵니다. 다른 점에는 카메라와의 차이만 남습니다.
먼저 카메라가 월드의 +z 쪽을 보는 경우입니다. 카메라가 돌아가 있지 않아서 빼기만 하면 뷰 공간 좌표가 됩니다.
const cam = [10, 0, 0]; // 카메라 위치
const p = [11, 2, 15]; // 월드 좌표
const view = p.map((a, i) => a - cam[i]);
view; // [1, 2, 15]
점은 카메라에서 오른쪽으로 1, 위로 2, 앞으로 15 떨어져 있습니다. 월드 좌표만으로는 카메라 위치와 방향을 함께 따져야 앞뒤를 알 수 있었습니다. 뷰 공간 좌표는 z 가 15 라서 앞이라는 것이 보입니다.
둘째 동작은 회전입니다. 이번에는 같은 카메라가 월드의 +x 쪽으로 몸을 돌려 서 있다고 해 봅시다. 장면을 반대로 돌려서 카메라가 보는 쪽을 뷰 공간의 z 축에 맞춥니다.
월드의 점 (15, 0, 0) 에서 먼저 카메라 위치를 빼면 (5, 0, 0) 이 됩니다. 카메라가 보는 +x 쪽을 z 축에 맞춰 돌리면 (0, 0, 5) 가 됩니다. 카메라 정면으로 5 떨어진 점입니다. 월드 좌표에서는 x 가 달랐던 점이, 뷰 공간에서는 깊이로 드러납니다.
뷰 행렬은 이 두 동작을 하나로 묶은 행렬입니다. 카메라도 장면 속 물체처럼 월드에 놓는 행렬로 나타낼 수 있습니다. 뷰 행렬은 그 행렬이 한 일을 거꾸로 되돌립니다. 그래서 카메라를 놓는 행렬의 역행렬이 곧 뷰 행렬입니다.
평행 이동과 회전을 행렬 하나에 담으려면 점에 넷째 성분을 붙입니다. 회전은 3×3 행렬 곱으로 됩니다. 평행 이동은 더하기라서 3×3 곱으로는 안 됩니다.
점을 (x, y, z, 1) 로 적으면 4×4 행렬 곱 안에서 더하기까지 할 수 있습니다. 이렇게 성분을 하나 늘린 좌표가 동차 좌표입니다.
뷰 행렬을 손으로 조립하는 일은 드뭅니다. 카메라 위치, 바라볼 점, 카메라의 위쪽 방향 셋을 받아 뷰 행렬을 만들어 주는 함수를 씁니다. 이런 함수는 대개 lookAt 이라는 이름을 갖습니다.
위쪽 방향까지 받는 까닭이 있습니다. 카메라 위치와 바라볼 점만으로는 카메라가 옆으로 얼마나 기울었는지가 안 정해집니다. 위쪽 방향을 주어야 화면의 위가 어느 쪽인지 정해집니다.
카메라 기준으로 옮기는 까닭
이 소절은 뷰 공간을 거치지 않으면 무엇이 번거로운지를 봅니다. 월드 좌표에서 바로 화면 위치를 구하는 경우와 견줍니다.
멀리 있는 것을 작게 그리는 원근 투영은 점의 가로·세로 위치를 깊이로 나눕니다. 월드 좌표에는 깊이가 따로 없습니다. 점마다 카메라 위치를 빼고 카메라 방향으로 재는 계산을 거쳐야 깊이가 나옵니다. 뷰 공간에서는 z 가 곧 깊이라서 그 계산이 이미 끝나 있습니다.
카메라에 담기는 범위는 꼭대기를 잘라 낸 피라미드 모양입니다. 카메라 쪽이 좁습니다. 먼 쪽은 넓습니다. 이 입체가 뷰 절두체입니다.
뷰 공간에서 절두체는 카메라가 어디로 가든 z 축을 가운데 두고 늘 같은 모양으로 놓입니다. 그래서 투영 행렬은 이 절두체 모양만 알면 만들 수 있습니다. 카메라가 월드 어디에 있는지는 몰라도 됩니다.
이렇게 두 행렬이 맡는 일이 갈립니다. 카메라가 움직이면 뷰 행렬만 다시 만듭니다. 카메라가 담는 각도인 시야각이나 화면의 가로세로 비율이 바뀌면 투영 행렬만 다시 만듭니다. 한쪽이 바뀌어도 다른 쪽은 손대지 않습니다.
투영 말고도 카메라가 원점에 있다는 것 하나로 짧아지는 계산이 있습니다. 아래 표가 그런 계산입니다.
| 계산 | 뷰 공간에서 짧아지는 까닭 |
|---|---|
| 원근 투영 | 나눌 깊이가 z 에 담겨 있다 |
| 매끈한 표면의 반짝임 | 점에서 카메라로 향하는 방향이 점 좌표의 부호를 뒤집은 것이다 |
| 반투명한 물체의 정렬 | 먼 것부터 그리려면 z 만 견주면 된다 |
| 안개 | 멀수록 흐리게 칠할 거리를 z 에서 얻는다 |
표의 둘째 줄을 풀어 둡니다. 매끈한 표면의 반짝이는 점은 보는 방향에 따라 위치가 바뀝니다. 그래서 이 계산에는 점에서 카메라로 향하는 방향이 필요합니다. 뷰 공간에서는 카메라가 원점이라, 원점에서 점 좌표를 뺀 값이 그 방향입니다.
개발자가 정하는 공간
GPU(Graphics Processing Unit, 그래픽 처리 장치)는 그래픽 계산을 맡는 전용 칩입니다. 정점의 좌표를 바꾸는 계산도 GPU 에서 돕니다. 그 계산은 개발자가 정하는 부분과 GPU 가 알아서 하는 부분으로 나뉩니다.
GPU 가 알아서 하는 것은 클립 공간 좌표를 받은 뒤의 일입니다. 화면 밖으로 나간 부분을 잘라 냅니다. 남은 삼각형은 화면에 찍습니다.
클립 공간 좌표를 만들어 GPU 에 넘기는 일은 정점 셰이더가 맡습니다. 정점 셰이더는 정점마다 한 번씩 도는 작은 프로그램입니다. 이 프로그램은 개발자가 작성합니다.
모델 행렬, 뷰 행렬, 투영 행렬을 차례로 곱하는 것도 정점 셰이더 안에서 개발자가 하는 일입니다. 그래서 뷰 공간은 GPU 가 강제하는 공간이 아닙니다. 개발자들이 따르는 약속입니다.
약속이라 단계를 합치기도 합니다. 모델 행렬과 뷰 행렬을 미리 곱해 두면 월드 좌표계를 건너뛰고 뷰 공간으로 바로 갑니다. 이렇게 합친 행렬을 모델뷰 행렬이라고 부릅니다.
정점 셰이더는 화면을 한 번 그릴 때마다 모든 정점에 대해 돕니다. 카메라가 움직이는 장면이면 뷰 행렬이 그때마다 새로 만들어집니다. 모든 정점의 뷰 공간 좌표도 그때마다 새로 나옵니다.
카메라와 상관없는 것은 이 공간을 거치지 않기도 합니다. 화면 귀퉁이에 늘 붙어 있는 글자나 버튼이 그렇습니다. 이런 것은 뷰 행렬 없이 화면 좌표로 바로 그립니다.
카메라가 보는 방향의 부호
뷰 공간의 축 방향을 정하는 관례는 둘로 갈립니다. 갈리는 것은 카메라가 z 축의 + 쪽을 보느냐 − 쪽을 보느냐입니다. 앞의 예는 모두 + 쪽을 따랐습니다.
두 관례는 좌표계를 어느 손으로 잡느냐에서 갈립니다. 오른손의 엄지를 x, 검지를 y 로 두면 중지가 가리키는 쪽이 z 입니다. 이렇게 잡은 좌표계가 오른손 좌표계입니다.
오른손 좌표계에서 x 를 오른쪽, y 를 위쪽으로 두면 z 는 화면에서 보는 사람 쪽으로 튀어나옵니다. 화면 앞에 앉은 그 사람이 곧 카메라입니다. 그래서 오른손 좌표계로 뷰 공간을 잡으면 카메라는 화면 안쪽, 곧 z 축의 − 쪽을 봅니다.
카메라 앞의 점은 z 가 음수입니다. 깊이는 z 에 마이너스를 붙여야 나옵니다.
왼손으로 같은 일을 하면 z 가 화면 안쪽을 가리킵니다. 이렇게 잡은 왼손 좌표계에서는 카메라가 z 축의 + 쪽을 봅니다. 앞의 (1, 0, 5) 는 이쪽 관례의 좌표입니다. 오른손 좌표계로는 같은 점을 (1, 0, -5) 로 적습니다.
투영 행렬도 뷰 공간과 같은 관례로 만들어야 합니다. 어긋나면 카메라 앞의 물체가 카메라 뒤에 있는 것으로 판정됩니다. 그 물체는 클립 공간에서 잘려 나가 화면에 안 나옵니다.
화면은 텅 빕니다. 오류 메시지도 뜨지 않습니다. 그래서 원인을 찾기 어렵습니다.
관련 항목
뷰 공간이 속하는 상위 분류
그래픽스 · 3차원 그래픽스 · 렌더링 · 그래픽스 파이프라인 · 좌표 변환 · 좌표 공간
한 점이 화면까지 거치는 좌표 공간
모델 좌표계 · 월드 좌표계 · 클립 공간 · 정규화 장치 좌표 · 화면 좌표 · 윈도 좌표
뷰 공간을 드나드는 행렬
뷰 행렬 · 모델 행렬 · 모델뷰 행렬 · 투영 행렬 · LookAt 행렬 · 변환 행렬
뷰 행렬을 이루는 계산
평행 이동 · 회전 행렬 · 역행렬 · 동차 좌표 · 행렬 곱 · 쿼터니언 · 오일러 각
뷰 공간과 행렬을 적는 관례
오른손 좌표계 · 왼손 좌표계 · 행 우선 순서 · 열 우선 순서
뷰 공간의 카메라가 담는 범위를 정하는 투영
카메라 · 원근 투영 · 직교 투영 · 뷰 절두체 · 시야각 · 근평면 · 원평면
뷰 공간 좌표로 하는 계산
퐁 반사 모델 · 정반사광 · 알파 블렌딩 · 안개 효과 · 절두체 컬링 · 깊이 버퍼
뷰 행렬을 곱하는 프로그램과 장치
뷰 공간을 다루는 그래픽 라이브러리
다른 이름: view space · eye space · camera space · 눈 공간 · 카메라 공간 · 카메라 좌표계 · 눈 좌표계