OpenGL Projection Matrix
Overview
컴퓨터 모니터는 2D 표면이다. OpenGL에 의해 렌더링되는 3D scene은 한 개의 2D image로서 컴퓨터 스크린에 사영되어야 한다. GL_PROJECTION matrix은 이 projection transformation을 위해 사용된다. 첫 번째, 그것은 모든 vertex data를 eye coordinate에서 clip coordinates로 변환한다. 그러고나서, 이러한 clip coordinates는 또한 clip coordinates의 w component로 나누어서 normalized device coordinates (NDC)로 변환된다.
그러므로, 우리는 두 clipping (frustum culling)과 NDC 변환이 GL_PROJECTION 행렬에 통합되도록 하는 것을 명심해야만 한다. 다음의 섹션은 6개의 파라미터들로 어떻게 그 projection matrix를 구성하는지를 설명한다 : left, right, bottom, top, near and far boundary values.
frustum culling (clipping)은 clip coordinates에서 수행된다, w_c로 나눠지기전에. 그 clip coordinates, x_c, y_c, z_c는 w_c와 비교되어서 테스트된다. 만약 어떤 clip coordinate가 -w_c보다 더 작다면, 또는 w_c보다 더 크다면, 그러고나서 그 정점은 버려질 것이다.
그러고나서, OpenGL은 clipping이 발생하는 polygon의 edges를 재구성할것이다.
Perspective Projection
원근 사영에서, 한 잘려진 pyramid frustum (eye coordinate)에서 한 3D point는 cube (NDC)로 매핑된다; x 좌표의 범위가 [l, r]에서 [-1, 1]가 되고, y 좌표 범위가 [b, t]에서 [-1, 1]로, z 좌표 범위가 [-n, -f]에서 [-1, 1] 이다.
eye coordinates가 오른손 좌표계로 정의되어 있는 것에 주의해라, 그러나 NDC는 왼손좌표계를 사용한다. 즉, 원점에 있는 카메라는 eye space에서 -Z축을 따라 보고있지만, 그것은 NDC에서 +Z축을 따라 보고있다.
glFrustum()은 오직 near and far 거리의 양수 값을 받아들이기 때문에, 우리는 GL_PROJECTION 행렬을 구성하는 동안 그것을 음수화 할 필요가 있다. OpenGL에서, eye space에 있는 한 3D 점은 near plane (projection plane)으로 사영된다. 그 다음의 그림은 eye space에 있는 한 점 (x_e y_e, z_e)가 near plane위의 (x_p, y_p, z_p)로 어떻게 사영되는지를 보여준다.
frustum의 top view로부터, eye space의 x좌표인 x_e는 x_p에 매핑되는데, 유사한 삼각형의 비율을 사용하여 계산된다;
{
Top View of Frustum에서, 삼각형 밑변들의 길이 (X축), 그리고 옆면들의 길이(Z축)의 비율을 통해서 x_p를 구한 것이다.
}
frustum의 side view로부터, y_p는 유사한 방식으로 또한 계산된다.
x_p와 y_p 둘 다 z_e에 의존한다는 것에 즤목해라; 그것들은 -z_e에 역으로 비례한다. 다시 말해서, 그것들은 둘 다 -z_e로 나눠진다. 그것은 GL_PROJECTION 행렬을 구성하는 첫 번째 단서이다. eye 좌표들이 GL_PROJECTION 행렬을 곱한 후에 변환된 후에, 그 CLIP 좌표들은 여전히 동차 좌표계이다. 그것은 마침내 clip 좌표의 w 컴포넌트로 나눠져서 NDC가 된다 (see more details on OpenGL Transformation)
그러므로, 우리는 clip coordinates의 w 컴포넌트를 -z_e로 설정할 수 있다. 그래서, GL_PROJECTION 행렬의 4번째 행이 (0, 0, -1, 0)이 된다.
다음으로, 우리는 x_p와 y_p를 선형 관계로 NDC의 x_n과 y_n으로 매핑한다; 즉, [l, r] => [-1. 1] and [b, t] => [-1. 1]
그러고나서, 우리는 x_p와 y_p를 위의 식에 대입한다
{ x_c }
{ y_c }
우리가 각 방정식의 두 항을 perspective division (x_c/w_c, y_c/w_c)를 위해 -z_E로 나눌 수 있게 만들었다는 것을 주목해라. 그리고 우리는 w_c를 -z_e로 미리 설정했고, 괄호 안의 항이 clip 좌표의 x_c와 y_c가 된다.
이러한 방정식으로부터 우리는 GL_PROJECTION 행렬의 첫 번째와 두 번째 행을 찾을 수 있다
이제 우리는 오직 GL_PROJECTION 행렬에서 풀어야 할 3번째 행만을 가진다. z_n을 찾는 것은 다른 것들과 조금 다르다. 왜냐하면 eye space에서 z_e는 항상 near plane에서 -n에 사영되기 때문이다. 그러나 우리는 clipping과 depth test를 위해서 unique z value가 필요하다. 게다가, 우리는 그것을 unproject (inverse transform)할 수 있어야 한다. 우리가 z는 x또는 y값에 의존하지 않는다는 것을 알기 때문에, 우리는 z_n과 z_e사이의 관계를 찾기 위해 w component를 빌린다. 그러므로, 우리는 GL_PROJECTION 행렬의 세 번째 행을 이렇게 정의할 수 있다.
eye space에서 w_e는 1이다. 그러므로, 그 방정식은 다음이 된다:
그 계수들 A와 B를 찾기 위해, 우리는 (z_e, z_n) 관계를 사용한다; (-n, -1)과 (-f, 1). 그리고 그것들을 위의 방정식에 넣는다.
A와 B에 대해 방정식을 풀기 위해, 첫 번째 방정식을 B에 대해 쓴다;
그리고 그 방정식을 두 번쨰 방정식에 대입하고 A에 대해서 풀어라
B를 찾기 위해 A를 첫 번째 방정식에 넣어라
우리는 A와 B를 찾았다. 그러므로, z_e와 z_n사이의 관계는 다음이 된다;
마침내, 우리는 GL_PROJECTION 행렬의 모든 성분들을 찾았다. 그 완전한 사영행렬은;
이 사영행렬은 일반 frustum을 위한 것이다. 만약 그 viewing volume이 대칭이라면, 즉, 그것이 r = -l 그리고 t = -b라면, 그러면 그것은 다음으로 간단하게 될 수 있다.
우리가 이동하기 전에, z_e와 z_e사이의 관계식을 다시 한 번 보자. 너는 그것이 유리 함수이고, z_e와 z_n사이의 비선형 관계라는 것을 보게된다. 그것은 near plane에서 high precision이 있다는 것을 의미하지만, far plane에서 little precision이 있다는 것을 의미한다. 만약 [-n, -f] 범위가 더 커진다면, 그것은 depth precision problem (z-fighting)을 발생시킨다; 즉 far plane 주변의 z_e의 작은 변화는 z_n 값에 영향을 미치지 않는다. n과 f 사이의 거리는 depth buffer precision problem을 가능한 최소화하기 위해 작아야 한다.
Orthographic Projection
GL_PROJECTION 행렬을 orthographic projection을 위해 구성하는 것은 perspective mode보다 더 간단하다.
eye space에 있는 모든 x_e, y_e, z_e 컴포넌트들 선형으로 NDC에 매핑된다. 우리는 그냥 rectangular volume을 cubew에 스케일링 할 필요가 있고, 그러고나서 그것을 원점으로 움직인다. 선형 관계를 사용하여 GL_PROJECTION의 원소들을 찾아보자.
w-component가 orthographic projection에 필요하지 않기 때문에, GL_PROJECTION 행렬의 4번째 행은 (0, 0, 0, 1)로 남는다. 그러므로, 완전한 orthographic projection을 위한 GL_PROJECTION 행렬은
만약 viewing volume이 대칭이라면, 그것은 더욱 간략하게 될 수 있다; 즉 r = -l, and t = -b이라면.
-----------------------------------------------------------------
Perspective Projection을 위해 간단히 정리하자면,
Eye Space -> Near Plane 사영 (삼각형 비율 이용)
-> Clip Space -> -z_e(w_c)로 나누어서 NDC
그리고 Clip Space에서 -w_c < x_c, y_c, z_c < w_c를 이용해 culling이 일어난다.
fovy를 제대로 이해하기 위해, 또한 Window Coordinate(Screen Coordinates)와 Projection Matrix 번역
http://www.songho.ca/opengl/gl_transform.html#matrix
Window Coordinates (Screen Coordinates)
NDC를 viewport transformation에 적용하여 스크린 좌표가 만들어진다. NDC는 렌더링 스크리엔 맞게 들어가기 위해 스케일링되고 평행이동 된다. 그 윈도우 좌표는 마침내 한 fragment가 되기위해 OpenGL pipeline의 rasterization process로 넘겨진다. glViewport() 명령어는 마지막 이미지가 매핑되는 렌더링 지역의 사각형을 정의하는데 사용된다. 그리고 glDepthRange()는 윈도우 좌표의 z 값을 결정하기 위해 사용된다. 위의 2개의 함수의 주어진 파라미터들로 윈도우 좌표들이 계산된다;
glViewport(x, y, w, h);
glDepthRange(n, f);
그 viewport transform 공식은 간단히 NDC와 윈도우 좌표 사이의 선형 관계에 의해 얻어진다;
Projection Matrix (GL_PROJECTION)
GL_PROJECTION 행렬은 frustum을 정의하기 위해 사용된다. 이 frustum은 어떤 오브제긑들 또는 어떤 오브젝트들의 부분들이 clipped out되어야 할 지를 결정한다. 또한, 그것은 3D scene이 screen에 어떻게 사영될지를 결정한다. (see more details how to construct projection matrix.)
OpenGL은 GL_PROJECTION transformation을 위해 두 개의 함수를 제공한다. glFrustum()은 perspective projection을 만들고, glOrtho()는 orthographic (parallel) projection을 만든다. 두 함수들은 6개의 clipping 평면을 명시하는 6개의 파라미터들을 요구한다; left, right, bottom, top, near and far planes. viewing frustum의 8개의 정점들은 다음의 이미지에서 보여질 수 있다.
far (back) plane의 정점들은 비슷한 삼각형의 비율로 계산될 수 있다, 예를들어 far plane의 왼쪽은;
orthographic projection에 대해, 이 비율은 1이 될 것이다. 그래서 far plane의 left, right, bottom and top values는 near plane과 같을 것이다.
너는 또한 gluPerspective()와 gluOrtho2D() 함수를 더 적은 파라미터들로 사용할지도 모른다. gluPerspective()는 오직 4개의 파라미터들을 요구한다; vertical field of view (FOV), the aspect ratio of width to height and the distances to near and far clipping planes. gluPerspective()에서 glFrustum()으로의 변환은 다음의 코드로 설명된다.
// This creates a symmetric frustum. // It converts to 6 params (l, r, b, t, n, f) for glFrustum() // from given 4 params (fovy, aspect, near, far) void makeFrustum(double fovY, double aspectRatio, double front, double back) { const double DEG2RAD = 3.14159265 / 180; double tangent = tan(fovY/2 * DEG2RAD); // tangent of half fovY double height = front * tangent; double width = height * aspectRatio; // params: left, right, bottom, top, near, far glFrustum(-width, width, -height, height, front, back); }
그러나, 만약 너가 비대칭 viewing volume을 만들 필요가 있다면, 너는 직접적으로 glFrustum()을 사용해야만 한다. 예를들어, 만약 너가 한 wide scene을 두 개의 인접한 스크린에 렌더링하고 싶다면, 너는 그 frustum을 2개의 비대칭 frustums으로(왼쪽과 오른쪽으로) 분해할 수 있다. 그러고나서, 각 frustum으로 그 scene을 렌더링한다.
댓글 없음:
댓글 쓰기