2D에 익숙해져 있는 우리에게 3D는 무척 접근하기 힘든 분야입니다. 저는 아직도 어려워하고 있으며, 아마 앞으로도 그렇게 쉽지만은 않을 것 같습니다.
제가 3D를 공부하기 위해 맨땅에 헤딩을 하고 있을 때, 가장 어려웠던 것 중에 하나가 어떻게 3D가 만들어질 수 있느냐는 것이었습니다. 처음에 저는 3D가 어떻게 구성되는지도 모르는채 3D책을 사서 공부했습니다. 결과는 대실패였습니다. 어느 책에서도 그래픽 파이프라인에 대하여 자세하고 구체적인 설명을 보여주지 않았으니까요. 사실 책 쓰는 사람 입장으로 보면 그 부분은 책으로 써서 설명하기가 무척 곤란한 부분일 것이라고 생각은 됩니다만, 2D에 익숙해져 있는 사람에게 갑자기 '카메라가 뭐고, Local 좌표계와 World 좌표계가 어떻게, View Matrix가 어떻게 벡터가 어떻고, Projection이 어떻다'라고 설명하는 것은 마치 초등학생을 데려다가 미적분학에 대하여 강의하는 것과 같다고 생각됩니다. 특히나 2D에 익숙해져 있는 사람에게는 사실 3D로 전환하기 위한 마음에 준비가 전혀 안되있습니다(의욕은 넘치지만 실질적으로 책을 읽어도 무슨 말을 하는지를 모르죠). 그러기 때문에 제가 두번째 모임에서 발표하고자 했던 것이 '그래픽 파이프라인'을 쉽게 대충 이런 과정을 거쳐야만 3D 프로그램을 만들 수 있다라는 내용이었습니다. 그리고 초심자가 어려워하는 것 중 또다른 하나는 용어입니다. 카메라, 월드, 뷰포트, 동차좌표등등의 용어들은 초심자를 가장 크게 좌절시키는 요인입니다. 제가 저런 용어들 때문에 삽질했던 시간을 생각하면 분노가 생길 정도니까요. 간혹 어디에 질문을 하면 또다른 모르는 용어로 설명을 합니다. 그러니 답변을 봐도 모르기는 매한가지죠.
그래서 이번 두번째 스터디에서 제가 대충 간단한 그래픽 파이프라인에 대한 설명을 드렸습니다. 그것을 정리하고자 글로써 남기는 것입니다.
우리가 2D에서 했던 내용은 아래의 그림에서와 같이 이미지 자신의 좌표계(Local)를 화면의 좌표계(Back Buffer)로 기준점을 옮기는 짓을 한 것입니다.
http://xs50.xs.to/pics/05411/graphicspipeline1.gif
2D에서의 작업도 Local 좌표의 Object를 World 좌표로 옮기는 작업이다.
즉 이미지가 300*400의 이미지가 있었다고 하죠. 그리고 화면 버퍼(Back Buffer)가 800*600이었다고 하죠. 이제 제가 저 이미지를 화면에 그리려고 합니다. 그런데 화면의 (30, 40)의 위치에 저 이미지를 찍으려고 합니다. 그러면 Blt() 같은 함수를 사용하여 (30, 40)에 저 이미지를 축소나 확장을 하지 말고 찍으라고 명령을 줄 것입니다.
위 과정을 다른 말로 해보면 이미지 좌표계(Local)의 기준점을 화면 좌표계(World)의 (30, 40)으로 옮겨놓은 것입니다. 즉 Local -> World로의 좌표 변환이 이루어 졌다고 볼 수 있죠. 만약 이미지를 확대 또는 축소하여 화면에 찍는 작업도 거의 똑같은 작업입니다. 즉 Local -> World로 변환하기 전에 이미지를 확대나 축소를 하고, 그리고 그것을 World로 보냅니다. 회전도 마찬가지겠지요.
어쨋든 위의 내용을 정리해보면 우리는 이미 2D에서 좌표변환이라는 것을 사용해왔다는 사실을 알 수 있습니다. 단지 1 ~ 2번의 변환 과정이 있었기 때문에 잘 인식하지 못했지만, 좌표 변환 과정이 있었다는 사실은 분명합니다. 2D에서는 이것이 전부이므로 '2D의 그래픽 파이프라인'이라고 명명해도 별 문제는 없습니다.
3D를 생각해보죠. 3D는 당연히 3개의 좌표를 가지고 있어야 할 것입니다. (x, y, z)라는 좌표가 있어서 저 좌표가 하나의 점(point)를 의미하게 됩니다. 사실 3D 책들은 많은 경우 벡터(vector)와 점(point)을 모두 그냥 벡터라고 사용하고 있습니다만, 설명을 위하여 점과 벡터를 나누어 생각하도록 하겠습니다. 우리가 3차원의 물체를 만들고자 한다면 3차원 점들의 집합을 만들어야 합니다. 간단히 육면체 하나의 좌표를 쭈욱 써보도록 하겠습니다.
(0, 0, 0)
(1, 0, 0)
(0, 1, 0)
(1, 1, 0)
(0, 0, 1)
(1, 0, 1)
(0, 1, 1)
(1, 1, 1)
위 8개의 점들을 이으면 정육면체가 나오게 됩니다. 아래 제가 그림으로 그려두었습니다.
http://xs50.xs.to/pics/05411/graphicspipeline.gif
이제 정육면체 하나를 정의하였습니다. 이것을 Local 좌표계의 정육면체라고 부르겠습니다. 즉 우선은 정육면체의 기준점을 잡고 그 기준점으로부터 정육면체를 정의한 것입니다. 기준점은 당연히 (0, 0, 0)이겠지요. 이제 정육면체를 World의 (a, b, c)의 위치로 옮기겠습니다. 다시 이야기하면 정육면체의 기준점을 특정한 좌표로 옮기는 것입니다. 즉 좌표 변환이 되겠지요. 이 좌표 변환은 우리가 2D에서 했던 작업과 동일합니다. 좌표 개수가 하나 더 추가되었다는 점만 다를 뿐이죠. 그리고 확대와 축소도 똑같이 할 수 있습니다. 회전도 마찬가지입니다. 지금까지의 과정은 2D에서 Local -> World의 그래픽 파이프라인과 다를바가 없습니다.
이제 '3D의 물체를 보기 위해서는 어떤 것들이 필요한가'라는 점을 생각해봅시다. 우리가 살고 있는 세계를 일반적으로 3차원이라고 합니다(사실은 3차원이 아닙니다만)
우리가 3차원에서 살고 있다는 사실을 어떻게 아십니까? 그리고 우리가 보고 있는 것이 정말로 3차원입니까?
혹시 카메라를 가지고 사진을 많이 찍어보신 분들은 아시겠지만, 카메라를 통해서 사진을 찍으면 사진은 2D로 나오게 됩니다. 우리 눈이 하는 일을 대신하게 한 것이 카메라이므로 다시 생각해보면 우리가 실제로 보고 있는 장면은 3차원이 아니라 2차원이라는 것을 알 수 있습니다. 그럼에도 불구하고 우리는 3차원이라는 사실을 인식할 수 있는 이유는 우리가 움직이고 있기 때문입니다(참고로 태어난지 1~3개월 사이의 아이들은 우리가 3차원에 살고 있다는 사실을 모른다고 합니다.)
어떤 물체를 볼 때 내가 뒤로 움직여서 물체를 보는 것과 물체를 멀리 밀어서 보는 것과 차이가 있습니까?
이제 3차원에서 어떤 물체를 본다는 것은 2차원의 장면의 흐름을 보고 있다는 것과 같고, 어떤 물체를 본다는 것은 '나를 중심으로 물체를 본다'는 것과 같다는 사실을 알게 되었습니다. 즉 컴퓨터로 3차원을 만들기 위해서는 눈이라는 것이 필요합니다. 눈이 점 (0, 0, 0)에 있었고 벡터(0, 0, 1)의 방향을 바라본다고 가정해 봅시다. 그러면 점 (0, 0, -1)의 위치에 있는 물건은 보일까요? 당연히 보이지 않겠죠?
위에서 우리는 물건을 Local -> World로의 좌표변환을 수행을 했습니다. 이제 해야하는 것은 눈으로부터의 '물건이 얼마나 멀리 있는가'를 아는 일입니다. 즉 눈의 좌표계로 변환이 필요한 것입니다. 눈을 기준으로 물건의 각 좌표들이 얼마나 떨어져 있는가를 계산해야 합니다. 이런 좌표 변환을 View 변환이라고 합니다.
Local -> World -> View
여기까지 오게 되면 이제 남은 것은 3D의 좌표를 2D로 바꾸는 일입니다. 혹시 생각나실런지 모르겠습니다만, '사영'이라는 것을 우리는 중고등학교 때 배운 적이 있습니다. 사영은 보통 그림자를 계산하는 방법으로 나왔었는데요. 지금 한번 찾아보시면 이해하는데 많은 도움을 받을 수 있습니다.
사영은 영어로 Projection이라고 합니다. 2D에서 사영을 거치면 1D로 가게 됩니다. (어떤 분들께서는 조금 기울어진 직선에 어떤 벡터를 사영하면 2D가 아니냐고 말씀하시는 분도 계시지만, 그것은 이미 기울어진 직선 자체를 기저로 잡은 것이므로 1D가 됩니다.) 사영은 위에서 그림자라고 이야기했던 것처럼 2D에서는 '하나의 직선에 어떤 벡터의 그림자를 드리우는 방법으로 벡터를 사영시켰다'라고 말합니다. 3D에서는 어떤 평면에 그림자를 찍는 것입니다. 조금 더 쉽게 설명을 하면 카메라를 가지고 찍는 것입니다. 그러면 어떤 물체가 카메라의 필름의 어느 위치로 가게 되겠죠. 이것을 사영이라고 말하는 것입니다.
Local -> World -> View -> Projection
이제 카메라에 찍혔으니 모든 것이 끝났습니다. 사실은 레스터라이징 과정이 남아 있습니다. 레스터 라이징은 말이 조금 어렵지만, 실제로 화면을 그리는 과정을 레스터라이징이라고 합니다(실제로 레스터 라이징이 바로 화면에 그리지는 않습니다. 그리고 그렇게 단순한 기능만을 하는 것은 아닙니다만, 다른 것들은 설명을 하기에 복잡하고 알고리즘도 많이 들어가 있습니다. 더욱 중요한 것은 우리가 건드리는 작업이 아니라는 점입니다. 그래서 그냥 레스터 라이징은 벡버퍼에 그림을 그리는 것이라고 이해하고 있어도 별 문제는 되지 않습니다.).
위의 과정이 대표적인 3D에서 그래픽 파이프라인입니다. 파이프라인이라고 하는 이유를 대충 아시겠죠? 물건의 한점 한점이 모두 Local -> World -> View -> Projection의 과정을 거치게 되므로 Pipeline과 비슷하다고 하여 붙인 이름입니다.
제가 위에서 설명한 것은 조금 이해하기 힘든 부분도 있을 것 같습니다. 그런 경우는 OpenGL Programming Guide, IT EXPERT 3D 게임 프로그래밍, DirectX9을 이용한 3D Game Programming 입문등의 책을 통해서 정보를 얻으시면 많은 도움을 얻을 수 있습니다. 그리고 조금 더 자세한 그래픽 파이프라인에 대한 이해를 하고 싶으시면 홍릉과학출판사의 '컴퓨터 그래픽스'와 정보문화사의 'Real Time Rendering'이라는 책을 보시면 자세히 나와있습니다. 하지만 쉽지 않습니다.
그리고 제가 위에서 설명한 그래픽 파이프라인은 그래픽 파이프라인의 전부를 설명한 것이 아닙니다(사실 저도 전부를 모릅니다.). 대표적으로 빠진 것들이 컬링, 동차 절단 공간으로의 클리핑, 스텐실 테스트, 깊이 버퍼 테스트 등이 있습니다. 하지만 그런 것들은 천천히 이해해가도 별 문제는 없는 것입니다.