레이블이 OpenGL인 게시물을 표시합니다. 모든 게시물 표시
레이블이 OpenGL인 게시물을 표시합니다. 모든 게시물 표시

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() 등이 있다.

2015. 2. 7.

[OpenGL]사각형 그리기

 OpenGL을 사용하여 사각형을 그려보자. 단순하지만 기초적으로 알고 지나가야할 GLUT 함수들이 있다. 이런 함수에 대해서 언급하고자 한다.


사각형 그리기
 OpenGL 설치 및 설정에서 본 코드이다. 메인함수 내에 존재하는 코드는 모두 GLUT 함수로 구성되었다. 이 함수들은 앞으로도 계속 사용되므로 알아보고 넘어가자.
  • glutCreateWindow("OpenGL Draw Rect"): GLUT에게 새로운 윈도우를 생성하라는 명령이다. 사각형이 그려지는 윈도우가 바로 이 함수에 의해 만들어진다. 인자로 전달되는 문자열은 윈도우 상단의 타이틀바(title bar)에 나타난다.
  • glutDisplayFunc(MyDisplay): 이 함수는 'MyDisplay'라는 함수를 디스플레이 이벤트에 대한 콜백 함수로 등록한 것이다. 이 함수의 정의를 찾아보면 다음과 같다. extern void APIENTRY glutDisplayFunc(void (*func)(void)). 매개변수를 함수 포인터로 되어있다. 즉 매개변수로 전달한 함수는 디스플레이 이벤트마다 호출된다. 디스플레이 이벤트는 별일없으면 계속 발생한다.
  • glutMainLoop(): OpenGL의 메인함수는 항상 이 함수로 끝난다. 이 함수의 역할은 이벤트 루프를 돌리는 것이다. 이벤트별로 콜백 함수 등록을 모두 마쳤으니 이벤트 루프로 진입하라는 의미이다. 모든 지엘 프로그램은 항상 glutMainLoop() 함수로 끝난다.
 결국 윈도우를 하나 생성해서 디스플레이 이벤트 때마다 어떤 함수를 호출할지(무엇을 그릴지) 정하고 이벤트 루프를 돌린다.


디스플레이 콜백 함수

 콜백 함수(Callback Function)는 응용 프로그램이 이벤트를 처리하는 방법을 말한다. 이는 이벤트 처리기(Event Handler)라도고 하는데, 이벤트 타입별로 프로그램이 수행해야 할 내용을 함수로 나타낸 것이다. 예를 들어 키보드에서 'W' 키를 누르면 바라보는 물체가 확대되도록 한다던지, 'esc'키를 누르면 현재 응용 프로그램을 종료하도록 한다던지 등의 내용이 바로 콜백 함수에 정해진다. 즉 디스플레이 이벤트는 별일이 없다면 매 프레임마다 발생할 것이고, 콜백 함수로 정해진 MyDisplay() 함수가 반복적으로 호출될 것이다. 이제 화면을 실제로 그리는 MyDisplay() 함수를 살펴보자.
 위 코드는 디스플레이 콜백 함수인 MyDisplay() 함수이다. 실제로 화면에 그리는 내용이 이 함수에 구현되어 있다.

  • glBegin(GL_POLYGON): gl에게 그리기를 시작함을 알리는 함수이다. 무엇을 그릴지에 대한 정보는 인자로 전달된다. POLYGON을 그린다고 gl에 알린다. 그러면 다음에 제시되는 정점(vertex) 좌표들로 기하물체가 그려지게 된다. 여기서는 4개의 정점 좌표로 사각형을 그리고 있다. 참고로 정점 좌표는 윈도우의 한 가운데를 (0.0, 0.0, 0.0)으로 정의된 좌표계로 표시된다. 기하물체를 구성하는 정점 좌표 설정이 끝나면 glEnd() 함수를 호출한다.
  • glEnd(): 기하물체를 구성하는 정점 좌표에 대한 설정이 끝났다는 것을 gl에 알린다. 그러면 glBegin() 함수와 glEnd() 함수 사이에 있는 정점 정보들로부터 하나의 기하물체를 완성하고 그릴 수 있게 된다.
  • glFlush(): 그래픽 명령어를 처리하려면, 이 명령어를 수행하는 GPU(그래픽 프로세서)는 렌더링 파이프라인(Rendering Pipeline) 상의 프로세서와 교신해야 한다. 이는 I/O를 발생시키는 것이므로 하나의 명령어를 파이프라인으로 처리하는 것이 아니라 여러 명령어를 일정 분량 쌓아두었다가 한꺼번에 파이프라인 프로세서에 전달하는 것이 유리하다. 즉 명령어를 쌓아두었다가 파이프라인 프로세서에 전달하는 방식으로 처리된다. 하지만 glFlush() 함수는 더이상 명령어를 쌓지말고 지금가지 쌓인 명령어를 모두 프로세서에 바로 전달하도록 강제하는 함수이다. 렌더링을 위한 준비가 완료된 후 호출하는 함수로 이 함수를 통해 명령어를 GPU에 전달하지 않으면 아무것도 그려지지 않는다. 실제로 이 함수를 주석으로 처리하고 실행해보고, 주석을 해제하고 실행해보도록 하자.


입력 콜백 함수

 glutMainLoop() 함수는 이벤트별로 콜백 함수 등록을 다 마치고 이벤트 루프로 진입하라는 명령이었다. 위의 코드에서는 디스플레이 콜백 함수만 존재한다. OpenGL에서는 GLUT 라이브러리를 통해서 윈도우 및 입출력을 제어할 수 있다. GLUT를 사용하여 프로그래머는 필요한 콜백 함수를 등록하고, 해당 콜백 함수에 원하는 내용을 구현하면 된다. 이벤트가 발생하면 해당 콜백 함수를 호출하는 것은 GLUT가 알아서 처리한다. 프로그래머가 등록한 콜백 함수는 콜백 테이블(Callback Table)에 저장되고, GLUT는 이벤트가 발생하면 이 테이블을 참조하여 콜백 함수를 호출한다. 이벤트는 큐(Queue)를 통해 관리된다. GLUT는 이벤트 루프를 돌면서 큐에 이벤트가 있으면 가장 먼저 발생한 이벤트를 가져와서 테이블을 참조하여 함수를 호출한다.

 그렇다면 이번에는 입력 콜백 함수를 만들어보자. 디스플레이 콜백 함수를 등록하는 glutDisplayFunc() 함수와 마찬가지로 입력 콜백 함수를 등록하는 GLUT 함수도 당연히 있다. glutKeyboardFunc(void (*func)(unsigned char key, int x, int y)) 함수가 바로 입력 콜백 함수를 등록하는 함수이다.

 'Q' 키를 누르면 프로그램 실행을 종료하는 키보드 이벤트를 만들고 싶다면 어떻게 해야할 것인가? 먼저 'Q' 키를 눌렀을 때 프로그램을 종료하도록 하는 코드를 콜백 함수에 넣어야 한다. glutKeyboardFunc() 함수의 인자를 보면 unsigned char 한 개와 int 두 개를 매개변수로 하는 함수의 포인터이다. 여기서 char가 바로 어떤 키를 눌렀는지를 의미한다. 아래의 코드는 'Q' 키를 누르면 종료되는 콜백 함수이다.


 이 외에도 마우스 콜백, 메뉴 콜백, 타이머 콜백 등이 있다. 이런 콜백 함수를 정의하고 GLUT를 사용하여 등록해주면 된다.


정리하면

  • 특정 이벤트가 발생하면 실행되는 함수를 콜백 함수가로 부른다.
  • OpenGL에서 이벤트는 큐로 관리되며 이벤트 루프를 돌면서 이벤트를 처리한다.
  • 콜백 함수를 작성하고, 이 함수를 GLUT를 통해서 등록하면 이벤트 처리는 GLUT에서 알아서 해준다.

[OpenGL]설치 및 설정

 그래픽스를 좀 공부해보고자 한다. DirectX가 아닌 OpenGL로 선택했다. 선택했으면 먼저 설치를 해야겠다.


OpenGL 설치

 OpenGL은 3차원 물체를 만들고 변경하며 출력하기 위한 모든 기능을 제공한다. 하지만 OpenGL 자체에는 입력이나 윈도우 처리 기능이 없다. 즉 키보드 입력, 마무스 입력이라던지 윈도우 생성, 윈도우 크기 변경 등을 위한 기능은 없다. 그래서 이런 기능을 실행하기 위해서는 GLU 라이브러리와 GLUT(OpenGL Utility Toolkit) 라이브러리가 필요하다. 즉 OpenGL의 모든 기능은 OpenGL, GLU, GLUT 세 개의 라이브러리가 필요하다. 각각의 라이브러리는 프로그램 실행 시 필요한 .dll 파일, 소스와의 링크를 위한 .lib 파일, 컴파일을 위한 헤더인 .h 파일로 구성되어 있다. 

 그럼 세 개의 라이브러리를 설치하자. 그런데 윈도우 운영체제와 Visual C가 설치되어 있으면 OpenGL과 GLU 라이브러리는 이미 설치되어 있다. 우리는 GLUT 라이브러리만 다운로드해서 설치하면 된다. 다운받을 수 있는 곳은 공식 사이트에서 받을 수 있다.

 GLUT 라이브러리가 압축된 파일을 다운받은 후, 압축을 풀자. 압축을 풀어서 glut32.dll, glut32.lib, glut.h파일만 복사해주면 된다. 복사할 위치는 다음과 같다.
  • glut32.dll: C:\Windows\System32
  • glut32.lib: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\lib
  • glut.h: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\GL
 경로에 Microsoft Visual Studio 12.0 부분은 자신의 Visual Studio에 맞게 선택해주면 된다.

 만약 OpenGL, GLU 라이브러리가 설치되어 있지 않다면 해당 라이브러리를 다운받은 후, .dll, .lib, .h 파일들을 각각 위의 경로(GLUT 라이브러리 파일들을 복사한 경로)와 동일한 곳으로 나눠 복사하면 된다.

  
설치 후 설정

 먼저 Visual Studio를 실행해서 win32 console application으로 프로젝트를 만들자. 그리고 다음 코드를 입력하여 실행시켜 보자. 
 실행결과는 다음과 같다.

여기까지 성공했다면 OpenGL 설치 및 설정이 완료되었다. 혹시 inlcude가 제대로 되지 않는다면 라이브러리 파일의 위치를 다시 확인해보자. 그리고 #include <GL\glut.h>를 #include <glut.h>로 변경해보자. 자신이 라이브러를 복사할 때, GL 폴더를 만들지 않고 바로 복사했을 수도 있다. 라이브러리 경로만 잘 설정했다면 별일없이 작동할 것이다.