시스템과 이벤트
게임을 하다보면 클릭이나 키보드 입력을 통해서 특정 이벤트가 발생하도록 구현되어 있다. 따라서, 입력이 들어왔는지 안들어왔는지 항시 체크를 함으로써, 입력에 따른 이벤트 처리를 해야한다. 하지만 이를 무한 Loop를 돌려서 하기에는 비효율적이고 CPU의 Cost가 증가하게 된다. 따라서 이는 운영체제의 지원을 받음으로써 보다 효율적인 처리가 가능하다.
운영체제는 이벤트 큐라고 하는 기능을 지원한다. 큐는 자료구조로써 First In First Out로, 먼저들어온 작업을 먼저 처리한다는 자료구조이다. 따라서, 먼저 들어온 이벤트 입력을 먼저처리하는 기능이다. 그러면 우리는 무한Loop를 돌리지 않고 운영체제에 접근해서 이벤트 큐에 처리하고자 하는 입력을 넘겨줄 수 있는데, 이를 가능하게 하는 것이 GLUT이다. GLUT는 입력에 따른 함수를 콜백함수의 형태로 제공한다.
<콜백함수 리스트>
glutDisplayFunc : 디스플레이할 이벤트를 이벤트 큐에 저장하며 디스플레이 요청이 들어오면 이벤트를 가져온다.
glutMouseFunc : 마우스 입력에 따른 이벤트를 이벤트 큐에 저장하며 마우스 입력이 들어오면 이벤트를 가져온다.
glutKeyboardFunc : 키보드 입력에 따른 이벤트를 이벤트 큐에 저장하며 키보드 입력이 들어오면 이벤트를 가져다.
glutReshapeFunc : Window의 폭과 높이가 변경됨에 따른 이벤트를 이벤트 큐에 저장하며 윈도우 Size 변화에 따른 이벤트를 가져온다.
glutIdleFunc : 아무런 입력이 없을 때 발생하는 이벤트를 이벤트 큐에 저장하며 아무런 입력이 없을때 발생하는 이벤트를 가져온다.
예를 들어, glutKeyboardFunc(keyboardEvent)로 명령어를 날리면 이벤트 큐에 마우스 입력함수에 따른 이벤트를 저장해놓고, 입력이 들어오면 큐에서 가장 먼저 들어온 이벤트를 가져오게 된다.
OpenGL 실습코드 : 키보드콜백, 디스플레이콜백, reshape콜백
//4각형 그리기 예제
#include <OpenGL/OpenGL.h>
#include <GLUT/GLUT.h>
#include <iostream>
#define _WINDOW_WIDTH 300
#define _WINDOW_HEIGHT 300
int mode = 0;
void MyDisplay (void) //화면에 Display할 내용을 담고 있는 메서드
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0.5, 0.5, 0.5);
if(mode == 0){
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();
}else if(mode == 1){
glutSolidTeapot(0.6);
}
glFlush();
}
void MyKeyboard(unsigned char KeyPressed, int X, int Y){
switch (KeyPressed) {
case 'a':
mode = (mode+1)%2;
break;
case 'Q':
exit(0);
break;
case 'q':
exit(0);
break;
case 27:
exit(0); //"esc"의 아스키코드 값
break;
}
glutPostRedisplay();//MainLoop에 있는 MyDisplay를 다시한번 호출하라.
}
void MyReshape(int NewWidth, int NewHeight){
glViewport(0, 0, NewWidth, NewHeight);
GLfloat WidthFactor = (GLfloat)NewWidth / (GLfloat)_WINDOW_WIDTH;
GLfloat HeightFactor = (GLfloat)NewHeight / (GLfloat)_WINDOW_HEIGHT;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.0*WidthFactor, 1.0*WidthFactor, -1.0*HeightFactor, 1.0*HeightFactor, -1.0, 1.0);
}
int main(int argc, char *argv[])
{
glutInit(&argc, argv);//glut 초기화
glutInitDisplayMode(GLUT_RGB);
glutInitWindowSize(_WINDOW_WIDTH, _WINDOW_HEIGHT);
glutInitWindowPosition(0, 0);
glutCreateWindow("keyboardcallback Example");
glClearColor(1.0,1.0,1.0,1.0);
//콜백 함수 등록
glutDisplayFunc(MyDisplay);
glutKeyboardFunc(MyKeyboard);
glutReshapeFunc(MyReshape);
glutMainLoop();
return 0;
}
void glutInitDisplayMode(unsigned int mode)
아래는 미리 정의되어 있는 색상 모델(color model)의 상수이다.
- GLUT_RGBA 또는 GLUT_RGB - RGBA 창을 만듭니다. 이 값은 glut 가 사용하는 기본 색상 모드입니다.
- GLUT_INDEX - 인덱스 색상 모드로 설정합니다.
디스플레이 모드는 단일버퍼 창을 만들 것인지 아니면 이중버퍼 창을 만들 것인지 결정하는 것인데, 아래에 설정할 수 있는 상수가 있습니다.
- GLUT_SINGLE - 단일버퍼(single buffer) 창을 만든다.
- GLUT_DOUBLE - 이중버퍼(double buffer) 창을 만든다. 부드러운 에니메이션을 만들 때 필요합니다.
그리고 원한다면 특정 버퍼로 설정된 창을 만들 수 있습니다. 아래는 가장 많이 설정하는 버퍼입니다.:
- GLUT_ACCUM - 어큐뮬레이션 버퍼( The accumulation buffer )
- GLUT_STENCIL - 스텐실 버퍼 ( The stencil buffer )
- GLUT_DEPTH - 깊이 버퍼 ( The depth buffer )
void glutReshapeFunc(void(*func)(int width, int height))
윈도우의 크기가 바뀔 때 호출되는 콜백함수로, 인자에 함수를 넘겨주어 윈도우의 크기가 바뀔때마다 발생할 이벤트를 설정해준다.
일반적으로 처리하는 이벤트는 뷰포트 지정과 Projection행렬을 지정해주는 것이다.
void glViewport(GLint x, GLint y, GLsizei windth, GLsizei height)
(x,y)는 뷰포트의 왼쪽 아래 좌표이며 width, height는 폭과 높이이다. OpenGL에서는 윈도우의 좌표계도 좌상단이 아닌 좌하단이 원점이다.
glViewport(0,0,width,height) : 전체 윈도우를 사용한다.
glViewport(0,0,width/2,hegiht/2) : 좌하단을 사용한다.
glViewport(width/2,0,width/2,height/2) : 우하단을 사용한다.
glViewport(30,30,200,200) : 절대 크기를 사용한다.
void glMatrixMode(GLenum mode) - 변환 쪽에서 더 자세히 알아보기
만약 특정 좌표(1,1,1) 위치에 삼각형을 그린다고 한다면, 해당 좌표는 GL_MODELVIEW 매트릭스를 곱해서 실제적인 위치를 지정한다. 따라서 내부적인 위치 정보는 하나더라도 GL_MODELVIEW매트릭스가 변경된다면 전혀 다른 위치에 삼각형이 그려지게 된다. 이렇게 그려진 삼각형은 GL_PROJECTION매트릭스와 곱해서 화면상에 투영된다.
- GL_MODELVIEW : 이 매트릭스는 화면에 형상을 그리게되면 행렬을 곱해서 실제적 위치를 정한다
- GL_PROJECTION : 이 매트릭스는 해당 모델링을 최정적으로 어떻게 표시할지를 결정한다
- GL_TEXTURE : 텍스처를 적용하는 과정에서, 텍스처 좌표를 지정할 때 곱해진다.
void glLoadIdentity(void) - 변환 쪽에서 더 자세히 알아보기
현재 행렬을 단위 행렬로 만든다. 단위 행렬은 우하향 대각선 방향만 1이고 나머지 요소는 모두 0인 행렬로서 임의 행렬을 곱해도 원래 행렬이 계산되는 특수한 행렬이다. 곱셈의 1, 덧셈의 0과 같은 항등원으로서 연산을 해도 처음값이 유지된다. 현재 행렬을 단위 행렬로 만든다는 것은 행렬을 리셋한다는 뜻이며 이는 곧 어떠한 변환도 하지 않겠다는 뜻이다. 예를 들어서,
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
이 코드는 모델뷰 행렬을 리셋한다. display콜백 이전에 실행했을 때 적용했던 회전값을 무시하고 다시 설정하기 위해 리셋을 해야 한다. 이 리셋 코드가 없으면 회전이 계속 누적 적용되어 원하는 대로 회전되지 않는다. 그림을 그리기 전에 화면을 지우는 것과 마찬가지로 행렬을 사용하기 전에 리셋을 먼저 해야 한다.
void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal) - 변환 쪽에서 더 자세히 알아보기
좌우하상근원(LRBTNF) 순서대로 클리핑 영역을 설정한다. 이 영역이 평행하게 투영면에 비춰진다. 물체가 투영면에 평행하게 맺히므로 평행 투영이라고도 한다.
마우스콜백
OpenGL 실습코드 : 마우스콜백
#include <OpenGL/OpenGL.h>
#include <GLUT/GLUT.h>
#include <iostream>
#define _WINDOW_WIDTH 300
#define _WINDOW_HEIGHT 300
GLfloat r = 1.0 ,g = 1.0 ,b = 1.0;
GLint bef_x = -1 , bef_y = - 1; //마우스 커서 초기값(이전값)
int mode = 0;
void MyDisplay(){
glClear(GL_COLOR_BUFFER_BIT);
if(mode == 0){
glutWireSphere(0.3, 15, 15);
}else if(mode == 1){
glutWireTeapot(0.3);
}else if(mode == 2){
}
glFlush();
}
void MyReshape(int width, int height){
glViewport(0, 0, width, height);
GLfloat f_w = (GLfloat)width / (GLfloat)_WINDOW_WIDTH;
GLfloat f_h = (GLfloat)height / (GLfloat)_WINDOW_HEIGHT;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.0*f_w, 1.0*f_w, -1.0*f_h, 1.0*f_h, -1, 1);
}
void mouseBtn(int btn, int state, int x, int y){
if(btn == GLUT_LEFT_BUTTON && state == GLUT_UP){//왼쪽 마우스를 땔 때
mode = (mode+1)%2;
glutPostRedisplay();
}
}
void mouseDrag(int cur_x, int cur_y){
GLint dx, dy; //초기값과 바뀐값 차이
if(bef_x >= 0 || bef_y >= 0){
dx = abs(cur_x - bef_x);
dy = abs(cur_y - bef_y);
r = (r - 0.1)<0?0:r-0.1;
g = (g - 0.1)<0?0:g-0.1;
b = (b - 0.1)<0?0:b-0.1;
glColor3f(r, g, b);
glutPostRedisplay();
}
bef_x = cur_x;
bef_y = cur_y;
}
void mouseWindowEntry(int state){
if(state == GLUT_LEFT){
mode = 2;
}else if(state == GLUT_ENTERED){
mode = 1;
}
glutPostRedisplay();
}
int main(int argc, char ** argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB);
glutCreateWindow("title");
glutInitWindowSize(_WINDOW_WIDTH, _WINDOW_HEIGHT);
//콜백
glutDisplayFunc(MyDisplay);
glutReshapeFunc(MyReshape);
//마우스 콜백
glutMouseFunc(mouseBtn);
glutMotionFunc(mouseDrag);
glutEntryFunc(mouseWindowEntry);
glutMainLoop();
}
void glutMouseFunc(void(*func)(int button, int state, int x, int. y))
마우스 버튼 클릭 콜백함수
int button : GLUT_LEFT_BUTTON , GLUT_RIGHT_BUTTON, GLUT_MIDDLE_BUTTON
int state : GLUT_DOWN, GLUT_UP
void glutMotionFunc(void(*func)(int x, int y))
마우스 버튼이 눌려진 상태에서 움직일 때 이벤트 콜백함수
void glutPassiveMotionFunc(void(*func)(int x, int y))
마우스 버튼을 누르지 않은 상태로 움직일 때 이벤트 콜백함수
void glutEntryFunc(void(*func)(int state))
마우스 커서가 윈도우 내부에 있는지 바깥에 있는지 체크하는 콜백함수
int state : GLUT_ENTERED, GLUT_LEFT
메뉴 콜백
#include <OpenGL/OpenGL.h>
#include <GLUT/GLUT.h>
#include <iostream>
#define _WINDOW_WIDTH 300
#define _WINDOW_HEIGHT 300
GLfloat r = 1.0 ,g = 1.0 ,b = 1.0;
GLint bef_x = -1 , bef_y = - 1; //마우스 커서 초기값(이전값)
int mode = 0;
void MyDisplay(){
glClear(GL_COLOR_BUFFER_BIT);
if(mode == 0){
//..do nothing
}else if(mode == 1){
glutWireSphere(0.3, 15, 15);
}else if(mode == 2){
glutWireTorus(0.2, 0.5, 15, 15);
}else
glutWireTeapot(0.3);
glFlush();
}
void MyReshape(int width, int height){
glViewport(0, 0, width, height);
GLfloat f_w = (GLfloat)width / (GLfloat)_WINDOW_WIDTH;
GLfloat f_h = (GLfloat)height / (GLfloat)_WINDOW_HEIGHT;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.0*f_w, 1.0*f_w, -1.0*f_h, 1.0*f_h, -1, 1);
}
void mouseDrag(int cur_x, int cur_y){
GLint dx, dy; //초기값과 바뀐값 차이
if(bef_x >= 0 || bef_y >= 0){
dx = abs(cur_x - bef_x);
dy = abs(cur_y - bef_y);
r = (r - 0.1)<0?0:r-0.1;
g = (g - 0.1)<0?0:g-0.1;
b = (b - 0.1)<0?0:b-0.1;
glColor3f(r, g, b);
glutPostRedisplay();
}
bef_x = cur_x;
bef_y = cur_y;
}
void SelectMenu(int value){
//glutAddMenuEntry에서 지정해준 value값이 이 함수 매개변수로 들어옴
if(value == 2){
exit(0);
}
}
void SelectSubMenu(int value){
mode = value;
glutPostRedisplay();
}
int main(int argc, char ** argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB);
glutCreateWindow("title");
glutInitWindowSize(_WINDOW_WIDTH, _WINDOW_HEIGHT);
//콜백
glutDisplayFunc(MyDisplay);
glutReshapeFunc(MyReshape);
//마우스 콜백
glutMotionFunc(mouseDrag);
//서브메뉴 콜백
int subMenu_id = glutCreateMenu(SelectSubMenu);
glutSetMenu(subMenu_id);//현재 메뉴를 지정해줌
glutAddMenuEntry("Sphere", 1); //엔트리를 만들고, 지정된 현재메뉴의 콜백 함수의 인자로 value전달
glutAddMenuEntry("Torus", 2);
glutAddMenuEntry("Teaport", 3);
//메인메뉴 콜백
int mainMenu_id = glutCreateMenu(SelectMenu); //메인 메뉴를 만들어줌
glutSetMenu(mainMenu_id);//현재 메뉴를 지정해줌
glutAttachMenu(GLUT_RIGHT_BUTTON);//현재 메뉴는 마우스 오른쪽 클릭에 생성됌
glutAddSubMenu("3D Model", subMenu_id); //서브메뉴로 들어감, 서브메뉴 id 같이
glutAddMenuEntry("Exit", 2);//엔트리를 만들고, 지정된 현재메뉴의 콜백 함수의 인자로 value전달
glutMainLoop();
}
int glutCreateMenu(void(*func)(int value))
메뉴를 만드는 함수이다. 메뉴 콜백 함수 등록, 메뉴의 아이디를 리턴한다.
void glutSetMenu(int id)
여러 메뉴들 중에서 id를 이용하여 현재 메뉴를 설정함.(현재 메뉴로 설정하고 엔트리를 추가하면 현재 메뉴에 엔트리가 추가됨.)
void glutAddMenuEntry(char * name, int value)
현재 메뉴에 엔트리(텍스트:name, 값:value) 추가
int glutAttachMenu(int button)
특정 마우스 버튼을 클릭하면 클릭한 자리에 "현재 메뉴"로 설정한 메뉴가 생성됌.
int button : GLUT_LEFT_BUTTON, GLUT_RIGHT_BUTTON, GLUT_MIDDLE
void glutAddSubMenu(char * name, int menu)
현재 메뉴에서 서브메뉴(현재 메뉴에선 엔트리로 보임)를 추가하고 서브메뉴로 진입함.
https://m.blog.naver.com/PostView.naver?blogId=newpog&logNo=10002249715&proxyReferer=https:%2F%2Fwww.google.com%2F
google.com/webhp?hl=ko&sa=X&ved=0ahUKEwiK_dn3987uAhUOw4sBHa_hBUwQPAgI
'Computer Graphics' 카테고리의 다른 글
Chapter 05. 정점 배열과 3차원 도형 그리기 (0) | 2021.05.28 |
---|---|
Chapter04. 애니메이션과 더블버퍼링 (0) | 2021.05.28 |
Chapter02. OpenGL 시작 및 실습코드 (0) | 2021.05.26 |
Chapter 01-03. Graphics System & Model 상식 (0) | 2021.05.19 |
Chapter 01-02. 시작과 상식 (0) | 2021.05.19 |