OpenGL - 5 OpenGL 기본 툴 -(1)
05_01 그래픽 입력 장치
키보드와 달리 커서를 사용하여 화면의 일정 위치를 가리키는 것들을 ‘포인팅 장치’라 부른다.
ex ) 마우스, 조이스틱, 트랙볼, 스페이스 볼
커서 이동 방향의 가지 수를 자유도(DOF:Degree of Freedom)이라 한다. 예를 들어서,
조이스틱이나 트랙볼은 2차원 평면의 상하 좌우 4방향으로 움직일 수 있기 때문에 자유도가 4인 장치다.
물리적 입력 장치(Phyiscal Input Devices)는 상대 입력(Relative Input)과 절대 입력(Absolute Input)에 의해 구분된다.
상대 입력 : 좌표 (2,3)에 있던 마우스를 (3,4)로 옮기면 컴퓨터로 입력되는 것은 (3-2,4-3) = (1,1)이다.
위의 수식을 계산하는 것은 운영체제의 몫이다.
절대 입력 : 좌표 (3,4)로 이동한다면 (3,4)가 운영체제에 입력된다. ex) 태블릿
터치 패널(Touch Panel) 또는 터치 스크린(Touch Screen)은 마우스 대신 손 끝 터치로 입력할 수 있는 장치이다.
고속도로 휴게실의 관광 안내 화면이나 대형 건물 입구의 안내 화면 등이 이러한 터치 패널이라 볼 수 있다.
터치 패널에는 2가지 방식이 있는데, 광학 패널(Optical Panel)로서 막을 누르지 않아도 위치가 감지된다.
화면 위 테두리에서 밑으로 열을 쏘는데, 손가락이 그 부분을 가리면 빛이 전달되지 않아서 그 위치를 잡는 방식이다
전기 패널(Electrical Panel)에서는 외피 바로 뒤에 얇고 투명한 내피가 손가락과 닿으면 전류가 발생하여 좌표를 알 수 있게 한다. 그러나 이러한 터치 패널 입력은 손가락을 사용하기 때문에 정확한 위치 입력은 어렵다.
SGI사의 그래픽 전용 워크스테이션에 사용되는 버튼 박스(Button Box)와 다이얼 박스(Dial Box)이다.
버튼 박스는 매크로를 정의하여 각 버튼에 할당된 것이다. 다이얼은 각각 별도의 기능을 수행하는 회전식 불륨 스위치형 박스이다. 이는 일종의 아날로그 장치로, 매우 빠른 실시간대 디스플레이 기능을 갖추고 있다. 각 다이얼은
물체의 회전, 이동, 확대 등 각 제각기의 기능이 있다.
05_02 입력모드
입력 장치와 응용 프로그램 사이에 어떤 방법으로 상호 작용이 일어나는지에 따라서 입력 모드(Input Modes)를 나눈다. 먼저 메저(Measure)와 트리거(Trigger)를 구분할 필요가 있다.
메저(Measure)는 입력 장치가 응용 프로그램에 넘겨주는 값이다.
트리거(Trigger)는 메저를 가져가라는 신호로, 일종의 방아쇠(Trigger)를 당기는 행위로 볼 수 있다.
예를 들어
1
2
int x;
scanf(%d,&x);
에서 내가 3 이라고 쳤으면 3은 메저값이고, 엔터는 트리거이다.
마우스를 어떤 위치에 갖다놓고 클릭을 가했다면 그 위치의 좌표 (x,y)는 메저이고, 클릭은 트리거이다.
트리거에 의해 메저가 응용 프로그램에 전달되는 데는 메저가 이미 저장되어 있음을 전제로 한다.
메저 프로세스(Measure Process)는 메저를 인식하여 저장하는 과정으로서 일반적으로 장치를 초기화할 때
실행되기 시작한다. 예를 들어 윈도우 시스템에서 처음 시스템을 커서 마우스를 초기화한 이후에는 응용프로그램이
그 값을 요구하든 말든 항상 현재의 마우스 위치 좌표가 추적되어 시스템 버퍼(System Buffer)에 저장된다.
이러한 메저 프로세스와 트리거와의 관계를 기준으로 입력 모드를 세 가지로 구분할 수 있다.
리퀘스트 모드(Request Mode)는 프로그램이 실행 중 메저를 요구하는 방식이다. 이 경우 메저 프로세스는
자신이 인식한 메저 값을 프로그램에 전달한다. 단, 이러한 전달은 트리거 신호가 프로세서로부터 자신에게 들어오는 순간에 이루어진다. 예를들어, “C”프로그램에서 scanf()함수는 Enter에 의해 트리거될때까지 프로그램을 대기신다.
리퀘스트 모드를 적용하기 위한 명령어는 다음과 같다.
Request_Locator (Device_ID,&Measure); Device_ID는 물리적 장치의 아이디로, 예를들어 마우스를 아이디 1번, 키보드를 2번 등.. 이런 값을 말한다.
Measure변수는 함수로 통해 가져온 메저값을 넣는 변수이다.
샘플 모드(Sample Mode) 또는 직접 모드(Immediate Mode)라고도 부르는 이 방식에서는 사용자 트리거가
불필요하다. 프로그램이 메저 값을 요구하면 메저 프로세스는 무조건 현재의 메저 값을 제공한다.
따라서 사용자로서는 프로그램의 해당 함수가 실행되기 전에 미리 필요한 메저 데이터를 입력한 상태여야한다.
위의 리퀘스트 모드와 비교를 하자면,
물체를 회전한다고 하면 리퀘스트 모드에서는 “회전” 버튼을 누르고 마우스를 물체에 가져다 놓으면 메저 프로세스는 일단 해당 물체의 아이디를 인식한다. 그 후, 마우스를 누르는 순간, 즉 트리거가 일어난 순간 메저가 프로세스에 전달된다.
샘플 모드에서는 회전될 물체를 미리 마우스로 눌러 선택한다. 이후 “회전” 메뉴를 선택하여, 프로그램이 자동으로
이미 들어와 있는 메저(물체 아이디)값을 가져온다.
샘플모드 명령어는 다음과 같다.
1
2
Sample_Loactor (Device_ID, &Measure);
```
리퀘스트 모드나 샘플 모드에서는 프로그램이 Device_ID로 지정한 물리적 장치 이외의 어떤 입력 장치로부터의
입력도 무시된다. 사용자는 프로그램이 실행 중에 요구하는 장치에서만 입력을 행사할 수 있다. 이러한 점은 문제가되는데, 예를들어 비행기를 조종한다하면 조이스특, 버튼, 스위치 등 다양한 물리적 입력장치가 어떠한 상황이든 유기적으로 연결되어 대응되어야 하는데, 위의 두 방식을 사용하면 문제가 생길수 밖에 없다.
이벤트모드(Event Mode)에서는 사용자가 선택한 입력 장치가 우선권을 쥔다. 아무 때나 사용자가 임의로 선택한
입력 장치를 사용하여 입력 데이터를 프로그램에 전달할 수 있고, 프로그램은 이러한 요구에 맞추어 해당 작업을
수행해야 한다. 이 경우 트리거되는 순간 하나의 이벤트가 발생한 것으로 본다.
입력 장치별로 여러개의 이벤트가 발생할 수 있기 때문에 이러한 이벤트를 순차적으로 처리하기 위해서는
이벤트 큐(Event Queue)를 사용해야 한다.
이벤트 모드를 사용하는 시스템을 이벤트 구동 시스템(Event-Driven System)이라 한다.
마우스, 키보드, 트랙 볼에 각각 이벤트를 발생시키면 발생 순서대로 이벤트 레코드(Event Record)가
이벤트 큐(Event Queue)에 삽입된다. 이벤트 레코드는 이벤트를 발생시킨 장치의 아이디, 메저 등으로 구성된다.
응용 프로그램은 주기적으로 이벤트 큐를 검사하는데, 큐가 비어있으면 다른 일을 하거나 기다린다.
콜백 함수(Callback Function)는 응용 프로그램이 이벤트를 처리하는 방법을 말한다. 이는 이벤트 처리기(Event
Handler)라고도 불리는데, 이벤트 타입별로 수행해야 할 내용을 함수로 나타낸 것이다.
예를 들어 마우스 버튼이 클릭되었을 때 응용 프로그램이 수행해야 할 함수 내용에 대한 소스 코드를 작성하는 식이다.
MS 윈도우 운영체제도 일종의 이벤트 구동 시스템이다. 윈도우 프로그램의 시작점은 WinMain()이라는 함수로,
이 함수는 새로운 윈도우를 만드는 즉시, 이벤트를 처리하는 루프로 들어간다.
우리는 이러한 이벤트 처리를 응용 프로그램에 의해 직접 실행할 수도 있지만, GLUT API를 이용하면 더 쉬워진다.
05_03 지엘 프로그램의 예
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<freeglut.h> //freeglut 헤더 포함
//main함수부터 보세요.
/*
지엘 프로그램의 필수 요소를 모두 갖추고 있다는 점에서 지엘 프로그래밍의 기본 틀이다.
*/
void MyDisplay()
{
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_POLYGON);
// Begin과 End 사이에 제시된 정점들이 다각형을 이루고 있음을 의미한다.
glVertex3f(-0.5, -0.5, 0.0);
// 윈도우 한 가운데를 (0.0, 0.0, 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();
// 원래 드라이버는 명령어를 쌓아놓고 처리한다. 명령어마다 파이프라인 프로세서와 서로 교신해야 하는 시간 부담이 따르기 때문이다.
}
// glFlush()는 이러한 드라이버 행위를 원치 않을 때 사용한다. 명렁어를 쌓아두지않고 현재까지 쌓인 명령어 모두를 무조건 프로세서에 전달하도록 강제한다.
int main(int argc, char** argv)
{
glutInit(&argc, argv);
// freeglut를 사용시 이렇게 초기화를 해줘야한다.
glutCreateWindow("OpenGL Drawing Exmaple");
// GLUT에게 새로운 윈도우를 생성하라는 명령이다. 안의 문자열은 윈도우창 타이틀 이름이다.
glutDisplayFunc(MyDisplay);
// "MyDisplay" 라는 함수를 디스플레이 이벤트에 대한 콜백 함수로 등록한 것이다. 화면 디스플레이 이벤트가 발생할 때 실제로는 어떤 함수를 호출해야 하는지를 등록하는 함수이다.
// 이 부분에서는 키보드나 마우스 등 입력 이벤트별로 콜백 함수를 등록할 수 있다. 콜백 함수는 실행 함수가 아니기에 어떤 순서로 등록해도 무방하다.
glutMainLoop();
// main()함수의 마지막은 항상 glutMainLoop()로 끝나야한다. 이벤트 루프를 돌려야하기 때문이다. 그러므로, 이 명령이 실행되기 전 이벤트별로 콜백 함수를 등록해놔야한다.
return 0;
}
장히 보기가 까다롭습니다.. 밑에 github 주소를 냄겨놓을테니 열어보는것을 추천합니다.
지엘 프로그램은 윈도우 기능 및 입출력 제어에 있어서 GLUT 라이브러리를 이용한다.
지엘 프로그램과 드라이버 사이에서 인터페이스 역할을 하므로, 프로그래머는 드라이버를 직접 상대하는 대신
GLUT에게 필요한 일을 시키면 된다.
프로그래머가 필요한 콜백 함수를 등록하고 콜백 함수에 원하는 내용을 채워 놓으면 이에 대한 호출은
GLUT가 알아서 처리한다. 이를 위해 GLUT는 프로그래머가 등록한 함수를 콜백 테이블(Callback Table)형태로 저장하며 이 테이블에서는 이벤트 타입별로 불러야 할 콜백 함수명이 저장된다.(ex 위의 사진)
이후, GLUT는 드라이버로부터 받은 이벤트 레코드를 참고해 이벤트 타입을 판단하고 테이블을 검색하여 그에 맞는
콜백 함수를 호출한다. 이때 이벤트 레코드의 메저는 파라미터 형태로 콜백 함수에 전달된다.
일반적으로 큐에 이벤트가 하나도 없으면, 운영체제는 현재 프로그램 외에 다른 일을 실행하지만,
만약 프로그램에서 아이들 콜백 함수(Idle Callback Function)를 정의하면 아이들 콜백 함수가 자동 호출되어
실행된다. (뒤에서 다룸) 이 함수는 주로 이벤트가 없는 시간을 활용하여 필요한 계산을 하는 데 사용된다.
05_04 윈도우와 뷰 포트
지엘 프로그램이 실행되면서 파이프라인 변환 프로세스를 따라가면서 기준 좌표계가 바뀌고, 그때마다
새로운 좌표계를 기준으로 정점 좌표가 바뀐다.
모델좌표는 물체별로 모델링에 편하게 설정된 좌표계
전역 좌표는 개별 물체를 모았을 때 이를 한꺼번에 아우를 수 있는 좌표계
시점 좌표는 물체를 바라보는 시점을 기준으로 표현한 좌표계
절단 좌표는 시점으로부터 보이지 않는 물체를 잘라내기 편하게 설정한 좌표계
까지는 뒤에서 다루고
정규 좌표계와 화면 좌표계에 대해 알아볼 것이다. 이 부분이 최종적으로 그림을 화면에 뿌리는 단계,
즉 GLUT의 윈도우 기능이 관여하는 단계이기 때문이다.
모든 3차원 물체는 렌더링 결과 2차원 화면에 뿌려져야 한다. 즉, 어느 순간에 3차원 좌표에서 2차원 좌표로의
변환이 필요하다. 이 변환은 절단 좌표계에서 정규 좌표계로 넘어가오면서 이루어진다.
여기서 정규 좌표는 1을 기준으로 하는 2차원 좌표다.
위의 그림은 3차원 좌표에 정의된 물체를 화면에 뿌리는 과정인데,
여러 변환 단계를 거쳐 정규 좌표계에서 (0.5, 0.5)로 변환된다고 가정해보자.
정규화라고 하는데, 이러한 정규화를 거치면 모든 정점 좌표는 1보다 작은 값으로 바뀐다.
위의 사진처럼 우상단이 (1.0, 1.0)이고, 좌하단이 (-1.0, -1.0) 중앙은 (0.0, 0.0)으로 바뀌는걸 알 수 있다.
화면 좌표는 위의 사진처럼 화소 단위로 좌표를 표시한다.
하지만, 화면 좌표는 위의 사진처럼 좌상단이 (0,0)으로 정규좌표와 다르므로 주의해야한다. 소프트웨어나 컴파일러 등에 따라 달라질 수도 있다.
정규 좌표에서 화면 좌표로 바꾸는 계산식은 1024 * 768 모니터 기준으로
x좌표는 1023 * ((정규 좌표) + 1.0 ) * 0.5 가 된다.
뷰포트의 왜곡현상이라고 있는데, 간단히 뷰포트의 종횡비가 안맞아서
사진을 위로 길게 늘리면 위로 늘여져서 깨지고, 세로로 길게 늘리면 세로로 길게 늘려져서 깨지는 현상을 말한다.
밑에서 왜곡현상을 보겠다.
위의 코드가 간단한 이유는 기본 상태 변수를 적용했기 때문이다.
즉, GL의 상태 변수를 설정하는 명령 또는 GLUT 상태 변수를 설정하는 명령을 모두 생략했기 때문이다.
생략된 명령을 추가하여 그 의미를 파악해보겠다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<freeglut.h>
void MyDisplay()
{
glClear(GL_COLOR_BUFFER_BIT);
// GL 상태 변수를 설정한다. 프레임 버퍼를 초기화 하라는 뜻이다.
glViewport(100, 80, 200, 100);
// viewport의 위치와 크기 값이다. 이 위치는 !!좌하단!!에서 부터 시작한다. 이는 GL과 GLUT가 사용하는 좌표가 다르기 때문이다. (x,y,width,height) x,y는 좌표, width, height는 폭과 높이다. 또한 해당 폭과 높이를 지정해줘서 왜곡 현상을 방지한다.
glColor3f(1.0, 1.0, 1.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();
glFlush();
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
// GLUT window 초기화 함수 - GLUT 라이브러리를 초기화하고 윈도우 운영체제와 연결하는 하나의 세션을 형성한다.
glutInitDisplayMode(GLUT_RGB);
// 윈도우의 기본 컬러 모드를 RGB 모드로 설정하기 위한 것이다.
glutInitWindowSize(300, 300);
// 윈도우 폭을 300(첫 파라미터), 높이를 300화소로 하라는 것이다.
glutInitWindowPosition(0, 0);
// GLUT의 화면 좌표계는 운영체제의 화면 좌표계를 따른다. 이 함수는 만든윈도우의 좌상단을 좌표(0,0) 화면 좌표계의 원점에 위치 시키라는 것이다.
glutCreateWindow("OpenGL Sample Drawing");
// window창을 만들라는 뜻이다.
glClearColor(0.0, 0.0, 0.0, 1.0);
// 프로그램 전반에 걸쳐 적용되어야 할 GL의 상태 변수가 정의된다. 수시로 바뀌어야 할 때는 디스플레이 콜백 함수인 MyDisplay()에서 정의하면 된다.
// 안의 파라미터 값은 초기화 색이라는 상태 변수의 값을 설정한다. 각 첫번째 파라미터 부터 R, G, B, Alpha(불투명도) 값이다.
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
glutDisplayFunc(MyDisplay);
glutMainLoop();
return 0;
}
주석을 따로 안단 함수는 뒤에서 나올 예정입니다.
++ 이 외에 사용자 편의를 위해 이미 모델링된 몇가지 물체를 제공합니다.
정육면체, 원구, 원한체, 원뿔, 정4면체… 등 따로 정의된 명령어가 있으므로 참고하시고 여기에 따로 적지는 않겠습니다.
++왜곡현상
위의 코드에서
//glViewport(100, 80, 200, 100);
따로 크기를 지정해주지 않는다면 왜곡현상이 일어납니다.
초기 실행화면
이런식으로 1:1 비율이 안맞는 것을 왜곡 현상이라고 합니다.
위의 소스코드를 주석처리 안하고 종횡비를 맞춘 소스를 넣은 경우
1
glViewport(0, 0, 300, 300);