공부/컴퓨터 그래픽스

OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문 Chapter 05 정점 처리

bokob 2024. 7. 5. 19:49

GPU는 폴리곤 메시를 입력받아서, 각각의 3차원 폴리곤을 스크린에 그려질 2차원 형태로 바꾸고, 이 2차원 폴리곤 내부를 차지하는 픽셀(pixel)들의 색상을 결정한다.

 

컬러 버퍼(color buffer)

  • 스크린에 나타날 픽셀 전체를 저장하는 메모리 공간
  • 주기적으로 스크린에 복사된다.

GPU 렌더링 파이프라인(GPU Rendering Pipline)

GPU에서 렌더링은 파이프라인 구조로 수행된다.

파이프라인은 한 단계의 결과물이 다음 단계의 입력으로 상용되는 일련의 데이터 처리 단계를 말하는 것이다.

 

programmable 단계

쉐이더(shader)는 프록그램과 동의어이다. 즉, GPU 렌더링을 위해서 정점 쉐이더(vertex shader)와 프래그먼트 쉐이더(fragment shader)라는 두 가지 프로그램을 작성해야 한다.

 

hard-wired 단계

래스터라이저(rasterizer)와 출력 병합기(output merger)는 하드웨어로 고정된 단계로, 정해진 연산을 수행한다.

 

정점 쉐이더(vertex shader)

  • 정점 배열에 저장된 모든 정점에 대해서 변환(transform)을 비롯한 다양한 연산 수행

래스터라이저(rasterizer)

  • 변환된 정점들로 삼각형들을 조립한 후, 각 삼각형 내부를 차지하는 프래그먼트(fragment) 생성
  • 한 프래그먼트는 컬러 버퍼의 한 픽셀을 갱신하는 데 필요한 데이터를 총칭하는 것

프래그먼트 쉐이더(fragment shader)

  • 래스터라이저가 출력한 프래그먼트를 입력으로 받아 라이팅과 텍스처링 등의 작업을 거쳐 색상 결정

출력 병합기(output merger)

  • 프래그먼트와 현재 컬러 버퍼에 저장된 픽셀 중 하나를 선택하거나 혹은 이들의 색을 결합하여 컬러 버퍼를 갱신

5.1 노멀의 월드 변환

각자의 오브젝트 공간에서 만들어진 물체를 단일한 월드 공간으로 모으는 것이 월드 변환의 역할

월드 변환은 정점 배열에 저장된 데이터 중 '위치'에 적용되었다. '노멀'에도 월드 변환을 적용할 수 있을까?

 

월드 변환이 아핀 변환들로만 구성되었다면 $\left[ L | t \right]$로 표기할 수 있다.

노멀 변환에 있어 $t$는 무시해도 된다. 벡터는 이동에 의해 영향 받지 않기 때문이다.

 

위 그림은 3차원 오브젝트 공간의 단면인 xy 평면을 보여주는데, (4, 1)과 (1, 4) 점을 잇는 선분은 xy평면에 수직인 삼각형의 단면을, n은 그 삼각형의 노멀을 나타낸다.

L을 2차원 축소확대 행렬이라 하고, $s_x = 0.5$, $s_y = 1$이라고 설정하자.

L에 의해 변환된 삼각형과 노멀은 서로 수직하지 않는다는 것을 보여준다. 따라서 노멀 변환을 위해 L을 사용할 수 없다.

L이 노멀에 적용했을 때 문제가 발생한 것은 '비균등 축소확대'였기 때문이다.

 

L이 비균등 축소확대를 포함하지 않는다면 즉, L이 회전, 이동, 균등 축소 확대 중 하나이거나 이들이 결합된 경우라면, 노멀에 L을 적용해도 된다.

 

알고리즘을 단순화하기 위해, L이 비균등 축소확대를 포함하건 말건 노멀 변환에는 무조건 $L^{-T}$를 사용하면 된다.

그리고 변환된 노멀의 길이를 1로 만들기 위해 정규화를 거친다.


참고

노멀 변환

삼각형 <p, q, r>에서 노멀 n은 p와 q를 잇는 벡터와 수직이다.

서로 수직한 두 벡터의 내적은 0이므로 다음과 같이 쓸 수 있다.

$n^T \left( q-p \right)$

 

위 식에서 $n$, $p$, $q$는 모두 열벡터이다.

$L$에 의해 변환된 $p$와 $q$를 각각 $p'$와 $q'$라고 표기하면 $\boldsymbol{L}p = p'$, $ \boldsymbol{L}q = q'$이다.

그러면, $p= \boldsymbol{L}^{-1}p'$, $q= \boldsymbol{L}^{-1}q'$가 된다.

$n^T \left( \boldsymbol{L}^{-1}q' - \boldsymbol{L}^{-1}p' \right) = 0$

식을 고쳐 쓰면 다음과 같다.

$n^T\boldsymbol{L}^{-1}\left(q'- p'\right)=0$

 

좌변을 전치(transpose) 형태로 바꾸면

$ \left( q' - p' \right)^{T} \left( \boldsymbol{L}^{-1} \right)^{T}n = 0$

 

위 식을 통해 L에 의해 변환된 삼각형의 변 $\left( q' - p' \right)와 \left( \boldsymbol{L}^{-1} \right)^{T}n$이 서로 수직임을 알 수 있다.

 

동일한 방식을 $p$와 $r$을 잇는 벡터에 적용하면 아래와 같은 식을 얻을 수 있다.

$\left(r'-p'\right)^{T} \left(\boldsymbol{L}^{-1}\right)^{T}n=0$

 

변 $\left( q' - p' \right)$와 $\left( r' - p' \right)$ 모두 $\left( \boldsymbol{L} ^{-1} \right)^{T} n$과 수직이다.

 

삼각형의 두 변에 수직인 벡터는 그 삼각형에 수직이므로 $\left( \boldsymbol{L}^{-1} \right)^{T}n$은 $L$에 의해 변환된 삼각형과 수직이다.

결국 $\left( \boldsymbol{L}^{-1} \right)^{T}$가 $n$에 적용되어야 하는 변환임을 알 수 있다.

 

$\left( \boldsymbol{L}^{-1} \right)^{T}$가 $ \boldsymbol{L}^T$와 곱해지면 단위 행렬이 된다.

$\left(\boldsymbol{L}^{-1}\right)^{T}\boldsymbol{L}^{T}=\left(\boldsymbol{L}\boldsymbol{L}^{-1} \right)^{T}=I^{T}=I$

 

위 식은 $\left( \boldsymbol{L}^{-1} \right)^T$가 $ \boldsymbol{L}^T$의 역행렬임을 말해준다. 따라서 $\left( \boldsymbol{L} ^{-1} \right)^T = \left( \boldsymbol{L} ^{T} \right)^{-1}$의 관계가 성립된다. 


5.2 뷰 변환

월드 변환이 완료되어 모든 물체가 월드 공간에 모아졌다고 가정하자.

월드 공간의 특정 영역을 스크린에 렌더링하기 위해서는 가상 카메라의 위치와 방향을 설정해야 한다.

5.2.1 카메라 공간

카메라의 위치와 방향은 EYE, AT, UP 파라미터를 통해 정의된다. 즉, 카메라 공간(camera space)은 이 파라미터를 이용해 정의된다.

  • EYE: 월드 공간에서의 카메라 위치
  • AT:  월드 공간에서 카메라가 바라보는 기준점
  • UP: 카메라의 상단이 가리키는 방향(대부분의 경우, UP은 월드 공간의 수직 방향인 y축으로 설정)

원점은 EYE에 놓여진다.

그리고 카메라 공간의 기저를 $\left\{u, v, n \right\}$으로 표기하자. 이는 직교정규 기저이다.

따라서 카메라 공간을 $\left\{u, v, n, \textbf{EYE}  \right\}$로 표기한다.

또, 월드 공간은 $\left\{e_1, e_2, e_3, \mathbf{O} \right\}$로 표기한다. $\mathbf{O}$는 원점을 의미

 

5.2.2 공간 이전과 뷰 행렬

정점은 월드 공간과 카메라 공간에서 다른 좌표를 가진다.

EYE의 월드 공간 좌표는 (18, 8, 0)이고, AT이 놓여진 주전자 주둥이 끝의 월드 공간 좌표는 (10, 2, 0)이다.

주둥이 끝은 카메라 공간의 -n축 위에 존재하므로, 이것의 u와 v좌표는 0이 된다.

주둥이 끝과 EYE 사이의 거리는 10이다. 따라서, 카메라 공간에서 주둥이 끝의 좌표는 (0, 0, -10)이다.

월드 공간 좌표 (18, 8, 0)과 다르다.

 

이처럼 월드 공간의 모든 정점들이 카메라 공간으로 재정의되면 렌더링 알고리즘 설계하고 구현하는 것이 쉬워진다.

 

공간 이전(space change)

  • 하나의 공간에서 정의된 물체를 다른 공간으로 옮기는 것

월드 공간에서 카메라 공간으로의 이전은 카메라 좌표계 $\left\{u, v, n, \textbf{EYE}  \right\}$를 월드 좌표계 $\left\{e_1, e_2, e_3, \mathbf{O} \right\}$에 포개 놓는 과정으로 볼 수 있다.

즉, EYE를 먼저 O로 '이동'시키고 $\left\{u, v, n\right\}$을 $\left\{e_1, e_2, e_3\right\}$와 일치하도록 '회전'시키면 된다.

이 과정에서 모든 물체는 카메라 좌표계를 따라 같이 움직인다.

 

월드 공간에서 카메라 공간으로의 이전을 뷰 변환(view transform) 또는 카메라 변환(camera transform)이라 부른다.

뷰 변환의 첫 단계인 이동은 변위 벡터 O-EYE로 정의된다. O는 (0,0,0)이므로 변위 벡터는 $\left({-EYE}_{x}, {-EYE}_{y}, {-EYE}_{z}\right)$가 된다.

$ T = \begin{pmatrix}
1& 0& 0& -{EYE}_{x} \\
0& 1& 0& -{EYE}_{y} \\
0& 0& 1& -{EYE}_{z} \\
0& 0& 0& 1 \\
\end{pmatrix}$

EYE의 월드 공간 좌표는 (18, 8, 0)이 된다. 따라서 (10, 2, 0)에 위치한 주둥이 끝은 위 행렬 T에 의해 (-8, -6, 0)으로 이동

$T\begin{pmatrix} 10 \\ 2 \\ 0 \\ 1\end{pmatrix} =
\begin{pmatrix} 1& 0& 0& -18 \\ 0& 1& 0& -8 \\ 0& 0& 1& -0 \\ 0& 0& 0& 1 \end{pmatrix}
\begin{pmatrix} 10 \\ 2 \\ 0 \\ 1 \end{pmatrix} = 
\begin{pmatrix} -8 \\ -6 \\ 0 \\ 1 \end{pmatrix}$

이동에 의하여 월드 공간과 카메라 공간은 원점을 공유하게 된다.

따라서 기저 $\left\{u, v, n\right\}$은 회전을 통해 $\left\{e_1, e_2, e_3\right\}$에 포개질 수 있다.

 


복기

 

Chapter4에서 월드 공간의 기저와 오브젝트 공간의 기저와 같은 상태에서 임의의 축으로 회전하고 원래대로 다시 돌릴 때는 회전 행렬의 역행렬을 다시 적용해서 돌이켰다. 이 과정에서 두 공간의 기저는 다르게 된다.

회전 행렬의 역행렬을 다시 곱해서 원래대로 되돌릴 때, 이 역행렬은 원래 회전 행렬의 전치를 한 것과 같았다.


위의 복기를 생각하면서 이동 후의 상황을, 원래 카메라 좌표계에서 회전 행렬을 곱해 나타난 상황이라고 생각하자.

이제 곱할 회전 행렬을 R이라 하면 다음과 같다.

$R=\begin{pmatrix}
u_x& u_y& u_z& 0\\
v_x& v_y& v_z& 0\\
n_x& n_y& n_z& 0\\
0& 0& 0& 1\\
\end{pmatrix}$

이 회전을 통해 공간 이전이 완료된다. 공간 이전을 구성하는 이러한 회전을 기저 이전(basis change)라고 부른다.

이 뷰 변환을 구성하는 기저 이전은  $\left\{e_1, e_2, e_3\right\}$ 기준의 좌표 (-8, -6, 0)을  $\left\{u, v, n\right\}$ 기준의 좌표인 (0, 0, -10)으로 옮긴다.

 

뷰 변환을 한 번에 표기를 하면 다음과 같다.

$M_{view}$는 월드 공간의 모든 물체에 적용된다.

 

5.3 오른손 좌표계와 왼손 좌표계

3차원 카테시안 좌표계는 오른손 좌표계와 왼손 좌표계로 나뉜다.

오른손 좌표계의 경우, 오른손 네 손가락이 x축에서 y축으로 움직일 때 엄지손가락이 z축을 가리키게 된다.

왼손 좌표계의 경우 동일한 규칙이 왼손에 적용된다.

Direct3D는 왼손 좌표계를 사용하지만, OpenGl과 OpenGl ES는 오른손 좌표계를 사용한다.

 

오른손 좌표계의 물체를 왼손 좌표계로 포팅하면 다음과 같다.

예를 들어, OpenGL에서 Direct3D로 포팅하는 경우에 해당한다.

물체 정점 및 카메라를 그대로 사용했다. 물체의 경우 왼손 좌표계의 -z축 방향에 놓이게 된다.

결과는 좌우가 바뀌어서 나오게 된다.

 

이 문제를 해결하는 방법은 포팅 시 물체와 카메라 파라미터의 z 좌표의 부호를 바꾸면 된다.

포팅 뿐 아니라, OpenGL 내부에서도 이러한 작업을 해주는 경우도 있다.

 

5.4 투영 변환

EYE, AT, UP을 카메라 외부 파라미터라고 본다면, 카메라의 렌즈를 선택하고 줌인/줌아웃을 조절하는 것은 내부 파라미터라고 정의할 수 있다.

이제 월드 공간은 고려하지 않아도 되므로 카메라 공간 기저 $\left(u, v, n \right)$을 버리고 카메라 공간 축을 익숙한 x, y, z로 표기하자.

5.4.1 뷰 프러스텀(View Frustum)

카메라의 시야(field of view; fow)는제한되어 있기 때문에 카메라 공간의 모든 물체를 스크린에 담아낼 수 없다.

뷰 볼륨(view volume)

  • 카메라의 가시 영역, 이는 fovy, aspect, n, f 네 가지 파라미터를 사용해 결정된다.

  • fovy(field of view y-axis): y축 기준의 시야각을 말한다. 위 그림에서 주전자 위의 원통은 시야각 바깥에 있으므로 보이지 않는다.
  • aspect: 뷰 볼륨의 종횡비(가로세로 비율)를 말하는데

fovy와 aspect에 의해 정의된 뷰 볼륨은 꼭지점을 원점에 두고 -z축을 중심축으로 가진 무한한 피라미드가 된다.

  • n: 원점으로부터 전방 평면(near plane)까지의 거리
  • f: 원점으로부터 후방 평면(far plane)까지의 거리

fovy와 aspect에 의해 정의된 무한한 크기의 뷰 볼륨은 z축에 수직인 전방 평면 z=-n과 후방 평면 z=-f에 의해 절단되어 유한한 크기의 뷰 볼륨으로 바뀐다. 이를 뷰 프러스텀(view frustum) 혹은 절두체라고 부른다.

 

뷰 프러스텀 바깥에 놓인 물체는 보이지 않는 것으로 처리된다.

전후방 평면은 실세계의 카메라 작동 원리와 모순되지만, 계산 효울성을 위해 도입된 개념이다.

뷰 프러스텀 바깥에 놓인 물체는 대개 미리 걸러져서 GPU 파이프라인에 들어가지 못하게 한다.

 

위의 주전자처럼 일부가 후방 평면과 교차하는 일이 있을 수 있다.

이처럼 폴리곤이 뷰 프러스텀과 교차할 경우, 이 폴리곤은 잘라져서 프러스텀 안쪽에 놓인 부분만 GPU 파이프라인의 다음 단계로 넘어가게 된다. 이렇게 폴리곤을 자르는 작업을 클리핑(clipping)이라 한다.

이는 래스터라이저(GPU 파이프라인의 두 번째 단계)에 의해 수행된다.

 

5.4.2 투영 행렬

실제 클리핑은 카메라 공간에서 뷰 프러스텀을 이용해 수행되지 않는다. 뷰 프러스텀을 투영 변환 후 수행한다.

투영 변환(projection transform)

  • 피라미드 모양의 뷰 프러스텀을 좌표계의 주축에 나란한 2 x 2 x 2 크기의 정육면체 뷰 볼륨으로 변형하는 것 

클립 공간(clip space)

  • 투영 변환 후, 물체가 놓이는 공간

 

실제 카메라에서 3D 물체는 COP를 통과해서 2D 평면에 투영된다.

컴퓨터 그래픽스에서의 카메라는 COP의 한 점으로 축소해서 동일한 초점 거리를 사용하는 가상 이미지 평면에 투영한다.

 

뷰 프러스텀은 원점에 위치한 카메라로 수렴하는 투영선(projection lines)들의 집합으로 볼 수 있다.

뷰 프러스텀과 원점 사이에 놓여 있으면서 z축에 수직인 가상의 투영 평면(projection plane)을 생각해보자.

뷰 프러스텀의 투영선은 투영 평면에 영상을 형성할 것이다.

 

하나의 투영선 위에 존재하는 모든 3차원 점은 투영 평면의 한 점에 맺히게 된다.

두 선분 $l_1$과 $l_2$를 보면, 3차원 공간에서는 $l_1$이 $l_2$보다 길지만 투영 평면에서 이들은 동일한 길이를 가진다.

즉, 멀리 있는 물체가 작게 보이게 되는 원근법이 나타난다.

 

위 사진에서 우측에 그려진 단면을 보면, 투영 변환의 결과로 모든 투영선들이 서로 평행해져 z축에 나란해졌다.

즉, 단일한 투영 방향을 가지게 되었다.

사진에서 뷰 프러스텀 앞부분에 있는 폴리곤은 상대적으로 커진 반면, 뒷부분에 있는 폴리곤은 상대적으로 작아진 것이 보인다.

결과적으로 선분 ${l_1}'$과 ${l_2}'$는 동일한 길이를 가지게 되고, 단일한 투영 방향을 따라 이들은 동일한 길이로 보이게 된다.

주의할 점은, 투영 변환은 3차원 공간 물체를 2차원 투영 평면에 실제로 투영하는 것이 아니라, 3차원 공간 내에서 원근법을 구현한다는 것이다.  

 

투영 변환은 정점 쉐이더가 수행하는 마지막 연산이다. 투영 변환된 물체는 하드웨어로 고정된 래스터라이저로 들어가게 된다.

래스터라이저는 2 x 2 x 2 크기의 정육면체 뷰 볼륨을 확대하여 위 gif에서 우측 면이 스크린에 딱 맞도록 확대한다.

이 확대는 실제로 물체에 적용되는데, 확대된 물체는 z축을 따라 스크린에 투영된다. 따라서 ${l_1}'$과 ${l_2}'$는 스크린에서 동일한 길이를 가지게 된다.

 

뷰 프러스텀을 2 x 2 x 2 크기의 정육면체로 변형하는 투영 행렬은 다음과 같다.

투영 행렬의 중요한 특징 중 하나는 아핀 변환을 위한 행렬과 달리 마지막 행이 (0 0 0 1)이 아니라는 것이다.

 

클립 공간의 물체는 래스터라이저로 입력된다.

클립 공간은 오른손 좌표계이다. 그런데 래스터라이저는 모든 물체가 왼손 클립 공간에서 정의되어 있다는 가정에서 설계되어 있다. 따라서 래스터라이저로 진입하기 위하여 오른손 좌표계를 왼손 좌표계로 변환해야 한다.

이를 위해 정점 z좌표의 부호를 변경하면 된다. 

 

오른손 -> 왼손 좌표계 변환을 위한 투영 행렬

카메라 공간의 정점 v는 기존의 투영 행렬을 사용해서 v'로 변환되는데, v'의 z좌표는 투영 행렬의 세 번째 행이 결정한다.

따라서 v'의 z좌표 부호를 바꾸기 위해서는 기존 투영 행렬의 세 번째 행의 모든 원소 부호를 바꾸면 된다.

 

5.4.3 투영 행렬의 유도

투영 행렬은 뷰 프러스텀을 2 x 2 x 2 크기의 정육면체로 변환한다.

출처: 뿡뿡이형(https://bbungprogram.tistory.com/25) 투영행렬 유도와 ndc (tistory.com)

뷰 프러스텀 안의 한 정점의 좌표 (x, y, z)가 투영 변환을 거쳐 (x', y', z')가 되었다면 x', y', z'의 범위는 모두 [-1, 1] 이어야 한다.

 

뷰 프러스텀 단면 그래프 출처: 코승호딩(https://velog.io/@bangseungho/Chapter-2-%EC%A0%95%EC%A0%90-%EC%B2%98%EB%A6%AC)
x', y' 유도 출처: 코승호딩(https://velog.io/@bangseungho/Chapter-2-%EC%A0%95%EC%A0%90-%EC%B2%98%EB%A6%AC)

 

위 그래프는 피라미드 모양의 뷰 프러스텀의 단면을 보여주는 것이다. v로 표기된 정점의 좌표는(y, z)로 표기 되었다.

z축에 수직인 투영 평면을 생각해보면 여기에 투영된 v는 v'로, v'의 좌표는 (y', z')로 표기할 수 있다.

투영 평면의 z좌표가 $-\cot{\frac{fovy}{2}}$이라면, y'의 범위는 [-1, 1]로 제한된다. 이는 제약조건을 만족한다.

y'는 닮은꼴 삼각형(similar triangles)을 이용해 구할 수 있다.

$y'=\cot{\frac{fovy}{2}}\cdot\frac{y}{z}$

만약 x축 기준의 시야각 fovx가 주어진다면, y' 계산을 위해서 사용한 것과 마찬가지로 x'를 구할 수 있다.

$x'=\cot{\frac{fovx}{2}}\cdot\frac{x}{z}$

시야각 fovx와 fovy를 활용한 종횡비 aspect 계산 출처: 코승호딩(https://velog.io/@bangseungho/Chapter-2-%EC%A0%95%EC%A0%90-%EC%B2%98%EB%A6%AC)

 

$\cot{\frac{fovy}{2}}$는 D로 aspect는 A로 표기하면 투영 변환된 점 v'는 다음과 같이 표현할 수 있다.

$v' = \left(x', y', z', 1 \right) = \left(-\frac{D}{A}\cdot\frac{x}{z}, -D\frac{y}{z}, z', 1 \right)\rightarrow \left( -\frac{D}{A}x, -Dy, -zz', -z \right)$

$\rightarrow$ 앞뒤는 동일한 카테시안 좌표에 해당하는 두 동차 좌표이다.

맨 우항의 원소 모두 x, y, z의 선형조합(linear combination)이므로, 다음과 같은 행렬을 얻을 수 있다. (-zz는 z''로 표기)

위 행렬이 투영 행렬이다.

 

카메라 공간의 (x, y, z) 좌표가 (x', y', z')로 투영 변환될 때, z'는 x와 y에 무관하게 변환된다.

카메라 공간에서 z축에 수직인 평면을 생각해보면, 이 평면은 투영 변환 이후에도 여전히 z축과 수직을 유지한다.

즉, 투영 변환된 평면의 모든 점들은 변환 전의 (x, y) 좌표에 무관하게 동일한 z' 값을 가진다. 또한, z''는 -zz'로 정의되었으므로, z'' 역시 x와 y에 무관하다.

따라서 투영 행렬의 세 번째 행은 $\begin{pmatrix}0 & 0 & m_3 & m_4 \\ \end{pmatrix}$로 단순화할 수 있고 v' 좌표를 다음과 같이 정리할 수 있다.

 

 

투영 변환은 z 범위 [-f, -n]을 [-1, 1]로 전환 출처: 코승호딩(https://velog.io/@bangseungho/Chapter-2-%EC%A0%95%EC%A0%90-%EC%B2%98%EB%A6%AC)

$z' = -m_3-\frac{m_4}{z}$

투영 변환은 -f, -n을 각각 -1과 1로 변환한다. 이 값들을  대입한다.

$-1 = -m_3 + \frac{m_4}{f}$

$1 = -m_3 + \frac{m_4}{n}$

$m_3$와 $m_4$에 대해 풀면 아래를 얻는다.

$m_3=\frac{f+n}{f-n}$

$m_4=\frac{2nf}{f-n}$

 

투영 행렬의 세 번째 행의 $m_3$와 $m_4$에 대입하고, D와 A를 각각 $\cot{\frac{fovy}{2}}$와 $aspect$로 다시 되돌리면 다음과 같은 행렬을 얻는다.

세 번째 행의 부호를 바꾸면 최종 투영 행렬이 얻어진다.

 

 

출처

[OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문]을 보고 공부하고 정리한 내용