[DirectX] DirectX 9 버전 랜더링 파이프 라인 알아보기!!

 다이렉트x를 이용하여 간단한 프로그램을 만들기 전 일단 다이렉트x의 동작 원리에 대해서 알아보는게 더 중요할 것 같다.

 흔히 다이렉트x는 3d 랜더링을 위해 많이 사용하므로, 3d에 중점을 두고 설명하겠다. (물론 2d 랜더링도 가능하다. 왜냐하면 UI로 사용하는 화면들은 다 2d 화면이기 때문이다. 하지만 다이렉트x에서 중점을 맞추는 부분은 3d 구성 부분 이므로 3d를 기준으로 말하겠다. )

 3d 화면을 만든다고 쳐도, 실제로 우리 모니터에 표시 되는 것은 2d 이다. 뭐 물론 요즘 3d 모니터니 해도 그 것은 논외로 치고, 현재로서는 2d 모니터라고 보는 것이 맞겠다. (그리고 그 3d가 이 3d랑은 의미가 다르다 -_- )

 그래서 3d 랜더링 파이프라인을 한마디로 정의하면, 3d의 화면을 우리가 보는 2d 화면으로 변환해주는 일련의 과정들 이라고 볼 수 있다.

 파이프라인이라는 의미는 순서대로 변환을 거쳐 오기 때문에, 파이프의 입구로 들어와서 출구로 배출 되는 느낌이기에 그렇게 부른다.

 먼저, 3d 오브젝트가 필요하다. 그래서 우리는 흔히, 맥스 같은 프로그램을 이용해서 3d 모델링을 한다. 그렇게 모델링을 하면, 중점을 기준으로 어느 위치라고 하는 상대적인 위치에 버텍스들이 존재하게 된다.

 여기서 버텍스라고 함은, 모델링을 이루는 기본 폴리곤의 한 점, 즉 포인터다. 폴리곤은 그 점 3개가 모인 삼각형, (혹은, 사각형이라고도 하던데 이 부분은 정확히 몰라서 패스) 그리고 그 폴리곤들이 모이면 하나의 메쉬, 그리고 메쉬가 모이면 오브젝트라고.. 보통 부른다. (하지만 아직, 메쉬와 오브젝트의 기준을 잘 모르겠다... 둘다 폴리곤의 덩어리인데, 어떻게 부르냐의 차이인지도... 하지만 정확히 단어의 뜻을 찾자면, 오브젝트 라는 의미가 맞지 않나 생각된다. )

 그렇게 모델링이 있다면, 기준점을 기준으로 각 위치 값이 생긴다.

(대충 그림판으로 그리는 거라 이상해도 이해를....)

위의 중점을 모델의 피봇 좌표라고 봤을 떄 그 피봇은 0,0,0 이다. (그림은 2d이지만 3d 모델이라고 생각하면 좌표는 3개이다.) 그 점 A는 중점에 비해 y가 +1 만큼 된 0,1,0 의 좌표에 존재한다. 점 b는 x 로 1 y 로는 -0.5정도의 위치에 있다. 그래서 좌표는 1, -0.5, 0 이 된다. 이것이 바로 오브젝트의 로컬 좌표이다.

즉, 로컬 좌표란, 원점이 되는 좌표를 중심으로 각 버텍스의 좌표라고 생각하면된다. 이 것이 결정되는 것이 랜더링 파이프 라인의 첫 단계이다.

 그 로컬 좌표를 지정된 월드 좌표에 곱한다. 이 월드 좌표라는 것은 흔히 생각하는 우리 실세계의 3d 좌표라고 생각하면된다. 예를들면 책이 있다. 그 책은 그 책의 중심이 있고 각 꼭지점, 8개의 점으로 이루어져 있다. 그리고 그 책은 현재 우리가 사는 세계의 특정위치에 존재한다. 그렇다면 그 특정위치에 그 책의 중점을 옮기면 자연스럽게 나머지 8개의 점들은 따라오게 되어있다. 

 


위의 중점은 현재 x,y,z 축을 기준으로 x 축으로 1만큼, z 축으로 1만큼 이동했다. 그렇다면 점A의 현재위치는 중점을 기준으로 0,1,0 의 좌표에 있었지만 자동적으로 1,1,1 의 위치로 이동된 것이다. 이 것이 바로 월드 변환이다. 이 곳에서 스케일, 회전, 위치이동을 모두 계산한다.

 실제 로컬 좌표에 연산하는 행렬 값은 크기조정, 회전, 위치 값을 연산한 행렬을 한번에 곱한다. 하지만 스칼라 값의 연산은 곱셈공식에 의하여 앞뒤가 바뀌어도 상관없지만, 행렬은 그 곱하는 순서에 따라서 값이 달라지게 된다. 그렇기 때문에 연산순서를 신경써야한다. 

 보통 연산 순서는 S(스케일)*R(로테이션)*T(트랜스레이트) 순으로 곱한다. SRT로 외우면 헷갈리지 않는다. (단, opengl은 연산순서나 이런것들이 반대, 다이렉트x 용으로 외우면 된다.) 이 것이 무슨 영향을 끼치냐고 하면, 회전 연산이 로컬 좌표에 연산되면 로컬 축이 변한다. 그 상태에서 T(위치이동)이 생기면 전혀 엉뚱한 방향으로 이동이 된다는 의미이다. 즉, 움직이고 거기서 돌아가는 것이랑 돌고난 이후에 다른 방향으로 움직이느냐는 다른 의미이다.

 곱하는 순서에 따라서 자전과 공전이 된다. (자전은 제자리에서 돌기, 공전은 중점을 기준으로 도는 것이다. 지구의 자전으로 낮밤이 나뉘고, 공전으로 1년 365의 계절이 생긴다.

 이렇게 월드좌표로 움직였다고 보면, 이제 뷰 스페이스 연산을 하면된다. 그 스페이스라는 개념은 아까 로컬좌표도 로컬 스페이스에 존재하는 버텍스들, 월드 좌표도 월드 스페이스에 존재하는 버텍스 좌표, 이런식으로 연산을 거치면서 위치 이동이 되는 중이라고 보면된다. 뷰 스페이스로 연산하는 것은, 쉽게 말해 우리 눈에 보이는 위치로 움직이는 것이다.

 또 다른 사람들은 이런 랜더링의 카메라 연산을 비유할 때 그냥 일반 카메라에 비유한다. 일반 카메라로 사진을 찍는다면, 찍고싶은 피사체가 있다. 그 피사체는 우리가 아까 말한 오브젝트가 된다. 그럼 그 피사체는 각자의 외각선에 로컬좌표로 설정되고, 중점에 따라 같이 움직인다. 그리고 그 움직이던 피사체나 정지된 피사체는 우리의 실세계의 어느 위치에 있다. 이 것이 월드좌표이다. 이때 위치는 같지만 그 물체가 회전하느냐 크기가 변하느냐에 따라서 모양은 천차만별이다. 그렇게 찍고싶은 대상이 정해지면, 이제 뷰 연산이 바로 카메라다. 현재 카메라의 위치가 있고, 그 카메라가 바라보는 방향이 있고, 그 2가지 만으로는 2가지의 형태가 나오니까 업 벡터가 하나더 필요하다.. ( 현재 위치고 바라보는 방향 벡터만 가지고는 위 아래 양방향 모두 가능하기 때문에 정확히 해주어야 정확한 화면을 얻을 수 있다. ) 그렇게 되었을 때 이제 카메라가 사진을 찍으면 그 장면이 필름에 기록된다. ( 이 부분도 나중에 설명이 되겠지만, 이 것이 바로 프로젝션 행렬과 뷰포트 변환이다. )

 그리하여 우리도 카메라의 현재위치, 바라보는 방향, 업벡터 이렇게 3개의 값이 필요하다.


 사진이 점점 더 지저분해지기는 하는데, 이제 저 카메라 라고 적힌 위치가 카메라의 위치고 그 곳에서 바라보는 저 시야가 바로 lookat(바라보는 방향이다. 업 벡터는 현재 위치상 0,1,0 이라고 생각하면된다. (2d라서 정확히 표현이 안된다. )
 그리고, 이해의 편의상 저렇게 범위를 표현했지만, 실제로는 어디서 어디까지인지는 프로젝션을 거쳐야 알 수 있다. 흔히 우리가 카메라에 렌즈에 따라서 같은 위치지만 넓게도, 좁게도, 둥글게도 나오듯이 그런 렌즈의 역할은 프로젝션 연산에서 수행된다.

 여기까지 오면 거의 기본적인 변환은 다 끝났다. 아 하지만 현재 그림에서 원래 월드를 기준으로 카메라의 위치만 그렸기 때문에 월래 월드 좌표랑 같다고 생각할 수 있지만, 현재 개념상 그런 것이지 좌표는 분명 바뀌어 있다. 하지만 이해하기 편하도록 저렇게 그린 것이다.

 그리고 이제 조명 연산과, 컬링, 클리핑 연산이 들어간다. 사실 조명 연산은 어느 위치에서든 미리 해줄 수도 있다. 하지만, 그렇게 되면 연산량이 많기 때문에 보통 뷰 연산 이후에 한다. 그리고 컬링과 클리핑은 뷰 연산이 끝나야 할 수 있는 연산이기 때문에 보통 뷰 연산 이후에 이 것들을 처리한다. 클리핑은 프로젝션이후에 가능하다.

 조명 연산은 말그대로 조명 효과를 계산하는 것이다. 조명의 종류는 3가지가 있는데, 엠비언트, 디퓨즈, 스펙큘러 가 있다. 그리고 자체적으로 포인터 형 조명, 스포트 라이트 같은 조명 효과도 줄 수 있다. (포인터형 조명은 백열등을 생각하면되고, 스포트라이트는 손전등을 생각하면된다. 이 것들은 엠비언트 라이트의 범주에 속한다고 생각되는데, 정확한 것은 아니다. ) 보통 엠비언트, 디퓨즈 스펙큘러는 텍스쳐와 연관되어서 계산되는 경우가 많다.

 그리고 랜더링 연산속도에 영향을 주는 컬링과 클리핑이 남았는데, 컬링 연산은 말그대로 안보이는 부분을 삭제하여 더 이상 연산하지 않는 것이다. 랜더링 파이프라인에 뷰 연산까지 끝냈다면, 이제 안보이는 뒷면과 앞면으로 나누어 지는데 뒷면을 제거해 버리면, 연산양이 줄어들게 되므로, 속도를 위해서는 꼭 컬링 처리를 해야한다. (하지만, 뒷면이 필요한 상황도 있으므로, 사용하기 나름. 어떤 원리인지만 알고 있다면, 응용은 쉬울 것이다. )

 그리고 클리핑은 아까 봤던 뷰의 범위에 없는 오브젝트들을 제거하는 것이다. 월드 상에서는 그렸지만, 카메라 위치와 뷰 연산과 프로젝션연산까지 끝나면, 실제로 절두체 범위에 들어오지 않는 오브젝트 들이 생긴다. 그 오브젝트들은 차후에 연산을 해도 화면에 나오지 않으므로, 클리핑 연산으로 제거한다.
 

위의 빨간 범위를 제거하는 것이 클리핑이고, 현재 보이지는 않지만, 저 안쪽으로 더 가는 삼각형의 뒷면을 제거하는 것이 컬링이다. 즉, 이렇게 까지 연산이 진행되면 현재 점A 부분만 남게 되고 B는 사라지게된다.
 ( 물론, 그렇게 되면 화면에 남는 부분이 없어서 점만 보이게 되지만, 이 것은 예로 드는 것이기 때문에 이해만 하면 된다.)

 이제 마지막, 프로젝션과 뷰 포트 연산, 그리고 레스터라이즈 연산만 남았다.
프로젝션 연산은 아까 카메라 범위를 설명하면서 했던 것처럼 카메라로 사진을 필름에 찍을 때, 좌우의 폭과, 가까이 있는 것은 크게, 멀리 있는 것은 점점 작게 찍히는 것을 말한다.


 위의 그림을 보면 파란선이 카메라에 가까운 near 절두체고, 자주색 선이 카메라에서 먼 far 절두체 범위라고 보면된다. 즉 화면에 찍히는 것은 파란선과 자주선의 사이 영역만 찍힌다. 그리고 가까울 수록 오브젝트는 크게 멀수록 작게 찍히게 된다. 이 영역은 직접 지정해 줄 수가 있는데, 너무 가까운 거리까지 지정하면 카메라 앞에 어떤 물체가 너무 가깝게 붙어도 그 곳에 가려서 보이지 않은 경우가 있기 때문에 적당한 조절이 필요하고, 또 far 절두체가 너무 벌리까지 범위가 되어버리면 연산량이 많아지기 떄문에 적당한 범위에서 끊어야 적정 프레임이 나올 수 있다. 그리고 아까 말햇던 클리핑 연산도 여기서 수행된다.

 보통 게임을 하다보면, 상대방 케릭터에 너무 접근하면, 케릭터가 뻥 뚤리는 경우가 있다. 이 것은 그 near에 범위보다 더 가까이 접근해서 케릭터의 버텍스를 그리지 않다보니 생기는 현상이 되겠다. 

 또 게임을 만들 때 보통 멀어진 산 같은 곳에 안개가 있는 경우는, 일정 거리를 벗어나면, 자동으로 그 범위에 포그를 생성하고 뒷 배경에는 산 같은 가까이 다가가도 일정 크기가 되는 배경을 플랜에 붙이거나 하늘(스카이 돔)을 설치해서 먼가 멀리 까지 배경이 있는 듯한 훼이크를 주기도 한다.
 
 그리고 이 프로젝션 연산을 어떻게 하느냐에 따라서, 볼록렌즈 같은 효과, 원근법 무시 랜더링, 등등 여러가지 효과도 줄수있다. 

 이제 이 연산이 모두 끝나면, 이제 뷰포트 연산이 남았다. 뷰포트가 우리가 궁극적으로 하려고 했던, 바로.. 3d 위치들을 우리가 있는 2d 화면좌표에 밀어넣는 연산이다. 즉 -1~1 범위에있던 프로젝션된 버텍스들의 좌표를 실제 화면좌표 0~1280(?), 1920(?) 등의 각자 화면 좌표에 맞도록 위치를 잡아주는 작업이다.

 아까 이야기를 하다가 그냥 넘어왔는데, 사실 랜더링 한번의 과정은 한번 시작되면 끝날 때 까지 쭉 이어진다. 그런데 지금은 한두가지 모델링만 가지고 이야기를 하니까. 엄청 빨리 될 것 같지만, 실제로 여러가지 오브젝트에 배경에 여러가지 효과가 들어가면, 속도가 많이 느려진다. 그렇게 때문에 중간중간에 이런 연산 속도의 향상을 위한 테크닉들이 들어간다. 가령 z 버퍼 sort 라고 해서 현재 바라보고 있는 z축을 기준으로 오브젝트들을 줄 세워서 정렬을 시켜서, 앞쪽에 오브젝트를 먼저 그리면, 뒷쪽 오브젝트는 안그려지는 부분은 생략이 가능하다. 그래서 오브젝트를 앞에서 부터 먼저 찍어나가면 속도에 이득이 있다. 하지만 이 것이 안먹히는 것들이 있는데 바로 알파오브젝트다. 알파 오브젝트는 뒤의 배경색과 +/ 연산을 통해서 색이 결정된다. 그렇기 때문에 뒷 배경이 있어야 진짜 알파색을 구할 수 있다. 그렇기 때문에 알파 오브젝트는 반대로 뒤에서 그린다. 그래서 이것을 바로 알파 소팅 이라고 한다. 이렇듯 랜더링에 관해서 속도의 이득을 위해 여러가지 테크닉들이 있다. 이 것들을 모두 적재적소에 잘 써야 할 것이다.

 이제 진짜 마지막으로 레스터라이즈 연산이 남았다. 이 것은 궁극적으로 이제 화면 좌표에 딱 맞게 정해진 각 폴리곤들의 픽셀값을 집어넣는 연산이다. 즉 아까 설정된 조명, 텍스쳐 그리고 버텍스 위치 등등을 종합하여 각 위치의 마지막 픽셀 색을 결정하는 것이다. 그래서 같은 오브젝트라도 조명이 들어간 것과 아닌 것의 마지막 픽셀의 색은 차이가 나므로, 그림자 효과 등이 들어가고 안들어 가고 하는 것들이 되는 것이다..

 자.. 이게 기본적인 랜더링의 과정이다.. 이렇게 글로 쓰고 보니 실로 엄청난 과정들인 것 같은데.. 이런 과정들이 보통 초당 60프레임정도... 일어나고, 만약 제한폭없이 간단한 랜더링만 한다면 몇백번은 훌쩍 나오는 것이 요즘 컴퓨터의 성능이다. 하지만.. 그렇다고 최적화나 이런 테크닉들을 배제하고 개발한다면, 나중에 30프레임도 나오지 않아서 뚝뚝 끊기는 프로그램 화면을 보게 될 수도 있으므로, 항상 염두해 두고 코딩을 해야할 것이다.

[DirectX] DirectX 9 버전 랜더링 파이프 라인 알아보기!!

Posted by 바람처럼..
|