레이블이 glPopMatrix()인 게시물을 표시합니다. 모든 게시물 표시
레이블이 glPopMatrix()인 게시물을 표시합니다. 모든 게시물 표시

2015. 2. 10.

[OpenGL]작은 태양계 만들기

 CTM을 활용하여 간단한 태양계를 만들어 보려고 한다. 태양, 지구, 달로 구성된 단순한 태양계이다. 그리고 CTM에서와 다른 점은 이번에는 물체들이 움직여야 한다는 점이다. 물체를 움직이게 하는 방법은 여러 가지가 있다. 어떤 방법을 사용해도 문제없다. 그럼 본격적으로 태양계를 만들어 보자.


태양 만들기
 먼저 간단하게 태양을 생성해보자. 색상을 지성하고, 좌표는 월드 좌표계의 원점이다. 그리고 glPushMatrix() 함수를 통해 현재의 CTM을 스택에 푸시하였다. 
 다음은 지구를 만들자. 지구는 태양을 중심으로 Day만큼 공전하고, Time만큼 자전한다. 이를 어떻게 구현해야 할까? 먼저 태양을 중심으로 지구를 회전시켜야 한다. 지구의 좌표계가 아닌 태양의 좌표계로 지구를 Day만큼 회전시키자. 그 다음 지구는 태양으로부터 7만큼 x축 방향으로 떨어져있다. 그리고 지구의 자전을 구현해야 한다. 지구의 자전은 태양이 아닌 태양으로부터 7만큼 떨어진 지구의 좌표계를 중심으로 이루어져야 한다. 그래서 glTranslate() 함수 후에 glRaotatef() 함수를 호출해야 한다. 만약 glRaotatef() 함수와 glTranslate() 함수의 순서를 자꾸면 현재와 전혀 다른 결과가 나타날 것이다. 왜 그런 결과가 나타나는지에 대해서도 설명할 수 있어야 한다.
 만약 위의 코드를 실행하면 어떤 결과가 나오겠는가? 먼저 태양으로부터  x축으로 7만큼 좌표계가 이동한다. 그리고 그 좌표계를 중심으로 Day, Time만큼 회전한다. 이전 코드와 어떤 점이 다른지 알겠는가? 만약 Day의 값이 증가한다고 생각해보자. 앞의 경우는 좌표계를 회전시킨 후에 이를 7만큼 이동시킨다. 결국 Day 값이 증가하면 할수록 지구는 태양을 공전하게 된다. 하지만 이번 경우는 태양을 중심으로 x축 만큼 7 이동시킨다. 이동시키는 방향은 일정하다. 앞의 경우는 좌표계를 Day 값에 따라 좌표계가 회전하였고, 회전한 좌표축을 기준으로 x축 방향으로 이동했으므로 지구는 결과적으로 공전하는 것처럼 보인다. 하지만 이번 경우는 x축 방향으로의 이동은 언제나 동일하다. 태양의 좌표계는 현재 고정된 상태이기 때문이다. 이동 후에 지구의 y축 방향으로 회전한다. 결국 지구는 제자리서 돌게 된다. 자전만 구현된다.
 이제 달을 만들어보자. 달은 지구를 중심으로 일정거리 떨어지고 지구의 좌표계를 중심으로 공전하면 된다. 즉 Time을 기준으로 회전하면 된다. 코드를 살펴보자. 지구의 좌표계가 Time만큼 회전한다. 그리고 회전한 좌표계를 중심으로 x축 방향으로 이동한다. 결과적으로 달은 지구를 중심으로 공전하게 된다.


키보드 이벤트 함수
 이 함수는 간단하다. 'd' 키를 누르면 Day값이 증가하고, 't' 키를 누르면 Time값이 증가한다. 앞에서 말했듯이 Day는 지구의 공전, Time은 지구의 자전을 나타내기 위해 사용한 변수이다.


정리하면

  • OpenGL은 물체의 변환보다는 좌표계의 변환으로 이해하자.
  • 변환함수를 사용하면 이 결과는 누적되어 CTM으로 보관된다. glPushMatrix() 함수를 통해서 특정시점의 CTM을 스택에 저장할 수 있다.

2015. 2. 8.

[OpenGL]CTM

 그래픽스에서 모델에 변환 행렬을 곱해서 모델을 이동시키거나, 확대 및 축소시키거나, 회전시킨다. 이동, 확대 및 축소, 회전 등은 모두 행렬으로 이루어진다. 이런 행렬들은 누적되어 유지된다. 그리고 곱한 행렬들은 스택에 push하거나 pop할 수 있다.


CTM(Current Transformation Matrix)

 어떤 기하물체 A가 있다고 가정하자. A는 현재 모델 좌표계(Local Coordinate)로 정의되어 있다. 이를 월드 좌표계로 변환하기 위해서는 이동 행렬(Translation Matrix)를 모델에 곱해주면 된다. 그리고 A의 크기를 두 배로 만들기 위해서 크기 변환 행렬(Scale Matrix)를 곱해주면 된다. 지금 상태에서 물체 A에 가해진 변환은 이동 행렬과 크기 변환 행렬이다. OpenGL은 현재 변환 상태를 보관한다. 즉 지금까지 변환 행렬들을 누적해서 곱한 복합 행렬이 바로 현재 변환 행렬(CTM)이 된다. CTM을 그림으로 살펴보자.

 위 그림에서 모델 A는 이동 변환, 회전 변환, 이동 변환이 차례대로 가해진 상태이다. 이 변환 행렬들을 곱한 결과가 바로 CTM으로 저장된다. 결과적으로 모델 A에 CTM만 곱해주면 위의 세 가지 행렬을 차례로 곱한 결과와 동일한 결과를 얻을 수 있다. CTM을 이용하면 현재까지 변환된 변환 결과를 유지할 수 있다는 점이다. 만약 모델 A로부터 x축으로 10만큼 이동한 모델 B를 구현하려면 어떻게 해야할까? 모델 A에 곱하진 변환 행렬들을 다 곱한 후에 translate(10, 0)을 해주면 된다. 하지만 이는 비효율적이다. 모델 A에 곱해진 행렬들의 결과는 이미 CTM에 저장되어 있기 때문이다. CTM에 translate(10, 0)을 곱해준 결과가 모델 B의 위치가 된다. CTM은 이런 장점을 가지고 있다.


CTM의 한계

 CTM을 사용하면 현재까지의 변환을 저장할 수 있다. 그렇다면 CTM 이전의 행렬은 어떻게 알 수 있을까? 가장 최근에 CTM에 곱해진 행렬의 역행렬을 CTM에 곱하면 직전 단계의 CTM이 된다. 특정 시점의 CTM을 알기 위해서는 그 시점 이후에 곱해진 행렬들을 알아야 한다. 그리고 그 행렬들의 복합 행렬의 역행렬을 CTM에 곱해주어야 한다. 이는 매우 불편한 작업이다. 특정 시점의 변환 행렬들을 모두 기억하고 관리하는 것은 어렵다. CTM는 현재 상태의 변환에 대한 복합 행렬만을 보관할 수 있다는 한계가 있다. 이런 한계를 극복하기 위해서 필요한 것이 바로 특정 시점의 복합 행렬을 보관하기 위한 자료구조이다. OpenGl에서는 스택(stack)을 사용한다. 자신이 저장하고 싶은 시점의 복합 행렬을 glPushMatrix() 함수를 통해 스택에 저장한다. 그래고 glPopMatrix() 함수를 사용해서 변환 행렬을 보관하는 스택에 가장 위에 저장된 행렬을 반환한다. 

 자신이 지정한 특정 시점의 변환 행렬을 glPushMatrix() 함수로 저장하고, glPopMatrix() 함수로 접근해서 사용하면 된다.

 현재 변환 행렬을 위한 스택에 glPushMatrix() 함수를 사용해서 M1을 저장하였다. M1행렬은 CTM에 저장되어 있는 상태이다. 이후 glTranslate(), glRotate(), glScale() 함수 등을 사용하여 CTM에 추가적으로 변환이 가해졌다. 그리고 이를 glPushMatrix()를 통해 스택에 저장하였다. 이 상태는 그림의 M2이다. 이후 glPopMatrix() 함수를 사용하여 최상위의 M2를 pop하여 CTM을 M1으로 변경하였다.
 실행결과는 다음과 같다.


 위의 코드는 흰색, 노란색, 녹색, 빨간색 순서로 glPushMatrix() 함수를 사용하였다. glTranslate() 함수의 변환 결과가 스택에 누적되었다. 스택에 행렬들이 어떻게 누적되어서 렌더링되는지 생각해보자. 그리고 생각의 결과가 렌더링 결과와 일치하는지 살펴보자. 그리고 녹색 원의 아래쪽에 파란색원을 추가하고 다른 원들의 위치를 현재와 같은 상태를 유지하기 위해서 어떻게 해야할까? 코드를 직접 수정해보자.
 우리가 만들어야 하는 결과는 다음과 같다.


 녹색 원까지의 변환을 glPushMatrix() 함수를 통해 스택에 저장하였다. 그리고 녹색 원의 y축 방향으로 아래로 파란색 원을 추가하였다. 하지만 우리가 원하는 형태는 녹색 원 오른쪽에 빨간 원이 있는 모습이다. 그렇게 하기 위해서는 녹색 원이 추가된 상태의 변환 행렬로 돌아가야 한다. 하지만 현재 스택 최상위에 있는 변환 행렬은 파란 원을 추가했을 때의 CTM이다. 그래서 glPoPMatrix()를 호출하여야 한다. 그 후 빨간색 원을 추가하면 녹색원을 중심으로 우측에 생성되게 된다.


정리하면

  • OpenGL에서 현재까지의 변환은 CTM이라는 행렬에 저장된다.
  • CTM의 한계를 극복하기 위해서 CTM을 보관하기 위한 스택이 사용된다.
  • 변환 행렬을 스택으로 관리하기 위해 사용되는 함수는 glPushMatrix(), glPopMatrix() 등이 있다.