GL 프로그램의 예
#include <glut.h> // GLUT 라이브러리 사용하기 위함.
#include <gl.h> // GL 라이브러리 사용하기 위함.
#include <glu.h> // GLU 라이브러리 사용하기 위함.
void MyDisplay() { //Display 이벤트가 발생하면 불릴 콜백함수
glClear(GL_COLOR_BUFFER_BIT);// 프레임 버퍼를 초기화 하는 명령.
glBegin(GL_POLYGON); //아래 코드들이 다각형을 이루고 있음을 알림.
glVertex3f(-0.5, -0.5, 0.0);
glVertex3f(0.5, -0.5, 0.0);
glVertex3f(0.5, 0.5, 0.0);
glVertex3f(-0.5, 0.5, 0.0);
glEnd();
glFlush();
}
int main(int argc, const char * argv[]) {
glutCreateWindow("OpenGL Drawing Example"); //GLUT에게 새로운 윈도우 생성명령
glutDisplayFunc(MyDisplay); //MyDisplay함수를 display 이벤트에 대한 콜백 등록
glutMainLoop(); //이벤트별로 콜백함수를 등록했으니 이벤트 루프로 진입하라는 함수. 따라서 모든 GL 프로그램은 항상 이 함수로 끝이난다.
return 0;
}
일반적인 순서는 이렇다.
1- 윈도우 초기화 및 생성
: 원하는 윈도우 타입을 설정하고 초기화. 윈도우 관련이므로 GLUT 함수로 구성된다.
GLUT 역시 자체 변수를 사용하므로, 이 작업은 GLUT의 윈도우 관련 상태 변수 값을 설정하는 작업이다.
2- GL 상태 변수 설정
: 배경화면의 색, 광원의 위치 등 GL의 상태 변수 중 전체 프로그램을 통해 그 값이 변하지 않을 상태 변수 값 설정.
3- 콜백 함수 등록
4- 이벤트 처리 루프로 들어감
드라이버는 GL 명령어 하나하나를 받는 즉시 실행하지 않는다. 이렇게 하려면 GPU와 파이프라인 프로세서가 교신하는 시간이 길어지기 때문이다. 따라서 드라이버는 일정한 분량이 쌓이면 한꺼번에 실행한다.
glFlush() 함수는 드라이버에 더 이상 명령어를 쌓아 놓지말고 현재까지의 명령어를 실행하라는 것이다.
이는 렌더링을 위한 모든 함수가 정의 되었을 때 호출하는 것으로, 더 이상 함수에 의한 상태 변수 설정은 없으므로 이제 이 상태 변수를 기준으로 파이프라인 프로세스를 실행 하라는 것이다. 즉, 지금까지 쌓인 명령이 실행 되어야만 다음 명령이 의미가 있을 때 사용된다.
*GL에서 콜백 함수 사이에 어떤 값을 전달하는 데는 전역 변수가 사용된다.
예를 들어, 아이들 콜백에서 바꾼 변수값을 디스플레이 콜백으로 전달하려면 이를 전역변수로 선언해야 한다. 그 이유는 콜백 함수를 실행하는 것은 GLUT 이고, GLUT는 어떤 순간에 단 하나의 콜백 함수만을 실행하기 때문에 다른 콜백함수 에게 파라미터를 전달할 여지가 없기 때문이다.
콜백 프로그래밍
1) 리쉐이프 콜백
: 윈도우의 위치나 크기가 바뀔 때 불리고 파라미터로 바뀐 윈도우의 가로, 세로 값이 들어온다.
: 윈도우 크기 조절에 의한 왜곡을 방지하려면 glOrtho() 함수를 이용해야 한다.
void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
파라미터는 순서대로 좌, 우, 하, 상, 전, 후면의 위치를 나타낸다.
바뀐 윈도우의 종횡비와 이 함수의 파라미터의 종횡비를 맞춰주면 물체 왜곡을 막을 수 있다.
2) 키보드 콜백
: 키보드가 눌릴때 불리는 콜백이다. 파라미터로 눌린 키 값, 눌렸을 때의 마우스 포인터 x, y값이 넘어온다.
3) 마우스 콜백
: 마우스 버튼을 누를 때 또는 마우스가 움직일 때 발생한다. 파라미터로 눌린 마우스 버튼 종류 값, 해당 버튼이 눌러진 상태인지 아닌지, 이벤트 발생시 마우스의 x, y값이 넘어온다.
마우스를 누른 위치의 좌표계는 좌상단을 이용하고, 사각형을 그리는 GL 함수는 좌하단 좌표계를 이용한다. 즉, x값은 똑같지만 y값이 서로 달라진다.
4) 아이들 콜백
: glutMainLoop()은 프로그램을 무한 이벤트 루프로 가져가기 위한 함수이다. 만약 이벤트 큐에 이벤트가 없으면 아무 동작도 하지 않는다. 그러나, 이벤트 루프를 돌때 이벤트가 없을 때에 한해서 수행하고 싶은 동작이 있으면 아이들 콜백을 정의해서 수행할 수 있다.
애니메이션 작업에 이용하기도 한다. 즉, 애니메이션에 필요한 계산 일부를 아이들 콜백에서 행할 수도 있다.
GLfloat Delta = 0.0;
void MyDisplay() {
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_POLYGON);
glColor3f(0.0, 0.5, 0.8);
glVertex3f(-1.0 + Delta, -0.5, 0.0);
glVertex3f(0.0 + Delta, -0.5, 0.0);
glVertex3f(0.0 + Delta, 0.5, 0.0);
glVertex3f(-1.0 + Delta, 0.5, 0.0);
glEnd();
glutSwapBuffers();
}
void MyIdle() {
Delta = Delta + 0.001;
glutPostRedisplay();
}
int main(int argc, char** argv) {
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutInitWindowSize(300, 300);
glutInitWindowPosition(0, 0);
glutCreateWindow("OpenGL Drawing Example");
glClearColor(1.0, 1.0, 1.0, 1.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.0, 1.0, -1.0, 1.0, 1.0, -1.0);
glutDisplayFunc(MyDisplay);
glutIdleFunc(MyIdle);
glutMainLoop();
return 0;
}
이벤트 루프가 돌때 이벤트가 없을 경우에 한해서 MyIdle() 함수가 불리고 Delta 값이 증가한다. glutPostRedisplay()를 통해서 display콜백을 실행할 것을 요구한다. display 콜백이 불리면 MyDisplay() 함수에서 기존 사각형을 지우기 위해 glClear() 함수를 호출하여 프레임버퍼를 초기화하고, 새로운 사각형을 Back 프레임 버퍼에 그린 후 glutSwapBuffers() 함수를 호출하여 버퍼 스와핑을 명령한다. 그 결과 Back 프레임 버퍼가 프런트 프레임 버퍼가 되면서 새로운 사각형이 노출된다.
이 동작을 반복하게 되면서 사각형이 조금씩 움직이는 것 처럼 보이게 된다.
5) 타이머 콜백
: 아이들 콜백은 이벤트 큐에 이벤트가 없을 경우에만 실행 되기 때문에 정확한 타이밍을 조절하기가 어렵다. 반면, 타이머 콜백은 일정한 시간 간격이 지나면 불리는 콜백이다.
첫번째 파라미터로 얼마 후에 이벤트를 발생시킬 것인지 지정하고, 두번째 파라미터는 이벤트 발생시 불릴 함수를 지정하고, 세번째 파라미터는 이벤트 발생시 불릴 함수에 넘겨주고 싶은 파라미터가 있을경우 이용한다.
애니메이션과 더블 버퍼링
: 비디오 컨트롤러는 프레임 버퍼의 내용을 화면에 뿌리기 위한 요소다. 다른 이름으로 RAMDAC(Random Access Memory Digital to Analog Converter)이라 불리기도 한다.
즉, 비디오 컨트롤러는 프레임 버퍼의 디지털 데이터를 읽어서 해당 색에 맞는 전압(아날로그)으로 바꿔서 화면에 뿌린다.
그런데 GPU가 프레임 버퍼에 데이터를 입력하는 속도는 비교적 느리고 비디오 컨트롤러는 프레임 버퍼에 내용을 읽는 속도가 비교적 빠르기 때문에 프레임 버퍼를 하나만 두면 문제가 생긴다.
애니메이션을 구현할 때에 GPU가 프레임 버퍼에 다음 프레임을 쓰는 도중에 비디오 컨트롤러가 읽어서 화면에 뿌려버리면 이상한 화면이 보여질 수도 있다.
이를 방지하기 위해 "더블 버퍼링"을 사용한다.
더블 버퍼링은 2개의 버퍼(Front 프레임 버퍼, Back 프레임 버퍼)를 사용하는 것이다.
비디오 컨트롤러가 Front 프레임 버퍼를 읽어서 출력하는 동안 GPU는 Back 프레임 버퍼에 데이터를 쓰는 동작을 하고, GPU가 쓰기를 완전히 마치면 비디오 컨트롤러가 Back 프레임 버퍼로 스위칭하여 다시 읽고, GPU는 Front 프레임 버퍼로 스위칭 하여 다시 데이터를 쓴다.
더블버퍼를 사용하려면 다음 두 함수를 선언해야 한다.
void glutInitDisplayMode(unsigned int mode);
void glutSwapBuffers(void);
glutInitDisplayMode()는 이전에 glutInitDisplayMode(GLUT_RGB)에 의해 윈도우 모드를 초기화 했던 함수다. 그러나 이 함수는 윈도우 컬러 모드 뿐만 아니라 윈도우 버퍼 모드를 초기화 하는데도 사용된다. GPU가 더블 버퍼링을 지원한다면 이를 사용하기 위해 glutInitDisplayMode(GLUT_DOUBLE)이라고 초기화 해야 한다. 그렇지 않으면 기본값으로 적용되어 싱글 버퍼가 적용된다.
glutSwapBuffers() 함수는 front 프레임 버퍼와 Back 프레임 버퍼를 스위칭 하기 위한 것이다. 이 함수가 실행되면 묵시적으로 glFlush() 함수가 실행된다. 즉, 이전의 Front 프레임 버퍼에 가해질 명령을 모두 실행한 후에 버퍼 스위칭이 일어난다.
정점 배열
: GLUT 라이브러리 함수로 육면체를 그릴 수도 있지만, GL을 사용해서 직접 육면체를 그려보겠다.
육면체의 정점을 배열로 표현할 수 있다.
GLfloat MyVertices[8][3] = {
{ -0.25, -0.25, 0.25 },
{ -0.25, 0.25, 0.25 },
{ 0.25, 0.25, 0.25 },
{ 0.25, -0.25, 0.25 },
{ -0.25, -0.25, -0.25 },
{ -0.25, 0.25, -0.25 },
{ 0.25, 0.25, -0.25 },
{ 0.25, -0.25, -0.25 }
};
정점의 색도 배열로 표현할 수 있다.
GLfloat MyColors[8][3] = {
{ 0.2, 0.2, 0.2 },
{ 1.0, 0.0, 0.0 },
{ 1.0, 1.0, 0.0 },
{ 0.0, 1.0, 0.0 },
{ 0.0, 0.0, 1.0 },
{ 1.0, 0.0, 1.0 },
{ 1.0, 1.0, 1.0 },
{ 0.0, 1.0, 1.0 }
};
육면체를 구성하는 면 하나 하나는 GL_POLYGON으로 묘사될 수 있다. 예를 들어 정점 0, 3, 2, 1로 구성되는 면을 그리려면 다음과 같은 함수를 호출해야 한다.
glBegin(GL_POLYGON)
glColor3fv(MyColors[0]); glVertex3fv(MyVertices[0]);
glColor3fv(MyColors[3]); glVertex3fv(MyVertices[3]);
glColor3fv(MyColors[2]); glVertex3fv(MyVertices[2]);
glColor3fv(MyColors[1]); glVertex3fv(MyVertices[1]);
glEnd();
위처럼 다각형의 정점을 명시할 때는 순서에 주의한다.
위 그림에서 엄지 손가락 쪽이 시점이라고 생각해야 된다. 시점이 바라보는 표면이 되려면 시점에서 바라봤을 때 주먹이 말아지는 순서로 정점을 명시해야 된다.
즉, 0, 1, 2, 3으로 방향을 반대로 명시하게 되면 시점 기준으로 표면이 뒷면으로 바뀐다.
GLfloat MyVertices[8][3] = {
{ -0.25, -0.25, 0.25 },
{ -0.25, 0.25, 0.25 },
{ 0.25, 0.25, 0.25 },
{ 0.25, -0.25, 0.25 },
{ -0.25, -0.25, -0.25 },
{ -0.25, 0.25, -0.25 },
{ 0.25, 0.25, -0.25 },
{ 0.25, -0.25, -0.25 }
};
GLfloat MyColors[8][3] = {
{ 0.2, 0.2, 0.2 },
{ 1.0, 0.0, 0.0 },
{ 1.0, 1.0, 0.0 },
{ 0.0, 1.0, 0.0 },
{ 0.0, 0.0, 1.0 },
{ 1.0, 0.0, 1.0 },
{ 1.0, 1.0, 1.0 },
{ 0.0, 1.0, 1.0 }
};
GLubyte MyVertexList[24] = {
0, 3, 2, 1,
2, 3, 7, 6,
0, 4, 7, 3,
1, 2, 6, 5,
4, 5, 6, 7,
0, 1, 5, 4
};
void MyDisplay() {
glClear(GL_COLOR_BUFFER_BIT);
glFrontFace(GL_CCW);
glEnable(GL_CULL_FACE);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glColorPointer(3, GL_FLOAT, 0, MyColors);
glVertexPointer(3, GL_FLOAT, 0, MyVertices);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(30.0, 1.0, 1.0, 1.0);
for(GLint i = 0; i < 6; i++)
glDrawElements(GL_POLYGON, 4, GL_UNSIGNED_BYTE, &MyVertexList[4*i]);
glFlush();
}
int main(int argc, char** argv) {
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_RGB);
glutInitWindowSize(300, 300);
glutInitWindowPosition(0, 0);
glutCreateWindow("OpenGL Drawing Example");
glClearColor(1.0, 1.0, 1.0, 1.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
glutDisplayFunc(MyDisplay);
glutMainLoop();
return 0;
}
위 코드의 MyVertexList[24] 에서 여섯 개의 면이 각각 어떤 정점들에 의해 이루어지는지를 나타낸다.
glEnableClientState()에 의해 정점 배열 기능이 활성화 된다. GL의 정점 배열 기능은 정점 좌표뿐만 아니라 컬러, 컬러 인덱스, 법선 벡터, 텍스처 좌표 등에도 적용할 수 있다. 위 코드는 정점 컬러(GL_COLOR_ARRAY)와 정점 좌표(GL_VERTEX_ARRAY)에 적용했다.
glColorPointer()에 의해 실제 정점 색을 저장한 변수명인 MyColor를 제시한다. 마찬가지로 VertexPointer()에 의해 실제 정점 좌표를 저장한 변수명인 MyVertex를 제시한다. 여기서 파라미터 3은 배열 요소 하나가 3개의 요소로 이루어짐을, GL_FLOAT는 요소의 타입을 나타낸다. 0은 배열 요소 사이에 빈 공간이 없음을 의미한다.
최종적으로 육면체를 그리는 것은 glDrawElements() 함수다. 정점 리스트 변수인 MyVertexList가 파라미터로 넘겨지고 GL_POLYGON 파라미터는 그리고자 하는 목표를 나타낸다. 이 다각형 하나는 unsigned byte 타입으로 표시되는 4개의 인덱스로 구성된다. 마지막 파라미터인 &MyVertexList[4*i]는 정점 리스트의 시작 주소로, i 값이 변함에 따라 다각형을 구성하는 첫 인덱스의 시작 주소를 바꾸기 위한 것이다.
만약 glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, MyVertexList); 를 호출하면 for 루프 없이 하나의 함수 호출만으로 동일한 결과를 얻을 수 있다.
glFrontFace(GL_CCW)는 표면을 반시계 방향(CCW: Counter Clockwise) 순서로 나열하겠다는 것이고, GL_CW 시계 방향으로 표면을 지정하겠다는 뜻이다.
glEnable(GL_CULL_FACE)은 육면체를 바라보는 시점에 따라 보이지 않는 면이 존재한다. 이것을 굳이 생성할 필요없으니 제거하겠다는 함수이다.
glRotatef(30.0, 1.0, 1.0, 1.0)은 벡터(1.0, 1.0, 1.0)을 회전축으로 해서 육면체를 반시계 방향으로 30도 만큼 회전시키라는 명령이다. 회전 안시키면 정육면체를 정면으로 바라보면 사각형밖에 안보이기 때문이다.
디스플레이 리스트
: GL 함수의 실행 모드는 두가지로 분류된다.
: 직접 모드(Immediate Mode)는 물체를 화면에 그림과 동시에 물체 생성과 관련된 정보를 파기한다.
: 보류 모드(Retained Mode)는 정의된 물체 정보는 그대로 유지하고 재사용한다.
: 보류 모드는 디스플레이 리스트에 의해 이루어 진다. 물론, 벡터그래픽의 디스플레이 리스트와는 다른 의미를 지닌다.
'영상 처리 프로그래밍' 카테고리의 다른 글
[컴퓨터 그래픽스] 기하 변환 (1) | 2017.11.23 |
---|---|
[컴퓨터 그래픽스] 좌표계 (1) | 2017.11.23 |
[컴퓨터 그래픽스] OpenGL 기본(1) (0) | 2017.11.22 |
[컴퓨터 그래픽스] Introduction (0) | 2017.11.22 |
영상의 기하학적 변환 (0) | 2017.11.02 |