![]() Miguel Angel Sepúlveda 필자 소개: 1993년에 워싱턴대학(미국)을 졸업했다. 스페인, 이스라엘, 일본, 미국에서 과학연구 분야에서 일한다. 커널 0.98쯤부터 리눅스를 알게 되었다. (첫눈에 반했다) 현재는 자유 시간에 리눅스 포커스의 편집장으로 일한다. 필자에게 연락하기 들어가는 글점찍기선과 다각형 그리기연습다음 연재에는 |
OpenGL프로그래밍:간단한 다각형 랜더링요약: OpenGL프로그래밍 첫 연재로 간단한 랜더링작업에 대하여 알아봅니다. 들어가는 글이 글은 2차원/3차원 그래픽의 산업계표준인 OpenGL에 관한 연재기사중 첫번째 글이다. ('OpenGL이란 무엇인가?'라는 글도 함께 있다.) 이 글을 읽는 여러분은 여러분의 C개발플랫폼에 이미 익숙하고 GLUT라이브러리에 약간의 지식을 가지고 있다고 생각하고 설명하겠다.(만약 처음 접한다면 리눅스포커스의 "GLUT프로그래밍"이란 글을 먼저 읽기 바란다.) 필자는 리눅스에서 자유롭게 사용할 수 있는 환상적인 OpenGL의 동작을 보여주는 Mesa라이브러리를 이용하라고 추천한다. Mesa를 하드웨어적으로 지원하는 제품도 이미 나오고 있다.(관련 글인 '3Dfx 그래픽카드'를 읽어보라.) 새로운 OpenGL 명령어는 그 기능을 예제를 통하여 수행되는 모습을 볼 수 있을 것이다. 물론 우리도 이렇게 할 것이다. 이 연재의 마지막에는 완전히 OpenGL로 쓰여진 게임 시뮬레이션의 소스코드를 보게 될 것이다. 글을 시작하기에 앞서 먼저 필자는 과학자이므로 OpenGL을 사용한 경험들 대다수는 실제 입자나 고전적 시스템을 시뮬레이션하기 위한 도구로 사용했던 경우라는 것을 밝혀둔다. 그래서 그런지 필자가 쓰는 글의 예제들은 좀 그런쪽으로 치우친 감도 없지않아 있다 ;-) 여러분이 다른 종류의 예를 보고싶다면 필자에게 알려주기 바란다. OpenGL은 대부분 3차원 그래픽이나 환상적인 특수효과, 현실과 유사한 광원효과를 가진 복잡한 형상등과 관련되어 있다. 그러나 OpenGL은 2차원 그래픽을 렌더링해주는 기능도 있다. 복잡한 3차원 투시법이나 모델 랜더링, 광원효과, 카메라 위치 등등을 배우기 전에 2차원에서 할 수 있는 것들중에도 여러분이 알아두어야 할 것이 많다는 점을 명심해야 한다. 많은 과학/공학 응용프로그램은 2차원에서 랜더링되어진다. 그래서 간단한 2차원 애니메이션을 만드는 방법을 살펴보기로 하자. 점 찍기OpenGL은 점,선, 다각형과 같은 몇개의 기하학적 요소만을 가지고 있다. 이러한 기하학적 요소들은 그들제각각의 정점들로 표현된다. 한 점(vertex)은 2개 또는 3개의 실수값으로 나타내는데, 직교좌표계에서 2차원 점은 (x,y)로, 3차원 점은 (x, y, z)로 표현된다. 직교좌표계는 가장 일반적인 좌표계이다. 그러나 컴퓨터 그래픽분야에서는 4개의 실수값 (x, y, z, w)로 표현되는 호모지니어스 좌표계역시 자주 사용된다. 먼저 3차원 랜더링의 몇가지 기본 개념을 살펴보고 다시 이야기 하도록 하자. OpenGL에서 모든 기하학적 물체는 일정한 순서를 가진 정점들의 집합으로 표현되기 때문에 정점을 선언하는 루틴이 있다. 그 사용법은 다음과 같다.: void glVertex{234}{sifd}[v](TYPE coords); 위와 같은 표기법에 익숙해져야할 필요가 있다. 중괄호는 각 기능을 담당하는 함수명의 일부이다. 위의 함수의 경우 파라메터로 2,3,4 중 하나를 취할 수 있으며 또한 short, long, float, double 형의 데이터 타입까지 결정해 줄 수 있다. 선택사항으로 이 파라메터들을 벡터형식으로 제공할 수도 있는데, 이 경우에는 v-형식의 루틴들을 사용할 수 있다. 지금까지 이야기한 것들을 실제로 사용한 몇가지 예이다.: void glVertex2s(1, 3);
float vector[3];
이런 모든 루틴들을 그냥 간단하게 glVertex*라고 부른다. OpenGL에서 정점의 순서는 정점이 정의된 순서를 따른다.
정점의 순서는 glBegin(GLenum mode)과 glEnd() 사이에
정의된다.glVertex*문은 mode값에 따라 의미가 달라진다.
예를 들어보자.:
2차원 좌표계에 5개의 점을 그리는 것이다. GL_POINTS는 OpenGL의 헤더파일인 <GL/gl.h>.에 정의된 표식중의 하나이다. 이용할 수 있는 모드가 많지만 여기서는 필요한 몇가지들만 살펴보기로 한다. 각 점들은 OpenGL의 상태변수중 색상버퍼에 해당하는 색으로 그려진다.
현재 색상을 바꾸려면 glColor*류의 루틴을 사용하면 된다;
색상을 선택하고 다루는 방법에 대해 할 말이 많지만 이 주제는 다른 기사에서
다룰 것이다. 간단히 살펴보면 0.0부터 1.0까지의 부동형실수 3개를 이용하여
RGB(적색-녹색-청색;Red-Green-Blue) 엔코딩방식을 표현할 수 있다.;
지금까지 배운 것을 이용하고 두개의 예제를 작성해 보도록 하자. 첫번째 예제는 카오스맵(일반적인 지도)에서 많은 궤도를 그려주는 간단한 OpenGL프로그램이다. 여러분이 지도를 만드는 방법이나 표준 지도에 대하여 잘 알지 못해도 별 문제가 되지 않는다. 간단히 말하자면 임의의 한 점을 취하고 이 점을 미리 정의된 식에 대입하여 새로운 점을 생성하여 다시 대입하는 형태로 지도를 그리는 것이다. 이때 사용하는 식은 다음과 같다.: yn+1 = yn + K sin(xn)
표준맵은 입자가속기의 가속경로를 따라 회전하고 있는 전기를 띈 입자가 남기는 궤적과 가속기 단면을 지나는 궤적을 나타낸다. 표준맵이나 다른 맵들의 속성을 공부하는 것은 이온가속장치에서 방전된 입자의 안정성을 이해하기 위하여 물리학에서 중요하다. 표준맵은 각 파라메터의 몇몇 값때문에 아주 산뜻하다. K는 카오스정도를 나타내준다. 또한 물리학에는 별로 관심이 없지만 괜찮은 그래픽코드를 개발하고자 하는 사람들에게 맵과 그 속성을 살펴볼만한 것이다. 또한 텍스쳐, 화염불꽃, 나무, 지형등등을 생성하는 많은 알고리즘은 이 프랙털 맵에 기초하고 있다. 예제1(example1.c)의 소스를 살펴보자: #include <GL/glut.h> #include <math.h> const double pi2 = 6.28318530718; void NonlinearMap(double *x, double *y){ static double K = 1.04295; *y += K * sin(*x); *x += *y; *x = fmod(*x, pi2); if (*x < 0.0) *x += pi2; }; void winInit(){ /* 좌표계를 설정한다. */ gluOrtho2D(0.0, pi2, 0.0, pi2); }; void display(void){ const int NumberSteps = 1000; const int NumberOrbits = 100; const double Delta_x = pi2/(NumberOrbits-1); int step, orbit; glColor3f(0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0, 1.0, 1.0); for (orbit = 0; orbit < NumberOrbits; orbit++){ double x, y; y = 3.1415; x = Delta_x * orbit; glBegin(GL_POINTS); for (step = 0; step < NumberSteps; step++){ NonlinearMap(&x, &y); glVertex2f(x, y); }; glEnd(); }; for (orbit = 0; orbit < NumberOrbits; orbit++){ double x, y; x = 3.1415; y = Delta_x * orbit; glBegin(GL_POINTS); for (step = 0; step < NumberSteps; step++){ NonlinearMap(&x, &y); glVertex2f(x, y); }; glEnd(); }; }; int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA); glutInitWindowPosition(5,5); glutInitWindowSize(300,300); glutCreateWindow("Standard Map"); winInit(); glutDisplayFunc(display); glutMainLoop(); return 0; }위의 코드에 사용된 glut* 루틴의 사용법을 이해하려면 먼저 GLUT 프로그래밍기사를 읽기 바란다. 이 프로그램은 단일버퍼와 RGB모드를 가진 그래픽창에 그림을 띄운다. 지도를 그리는 콜백함수의 이름은 display()이다.: 먼저 배경색(검은색)을 선택한다.; glClear(GL_COLOR_BUFFER_BIT) 현재값으로 색상버퍼를 초기화시킨 다음 glColor함수를 이용하여 흰색을 선택한다. 다음 NonlinearMap()함수를 호출하여 여러번 반복하여 GL_POINTS모드로 glVertex*에 정의되는 점들을 화면에 그린다. 정말 간단하다. 윈도우 초기화에 사용된 winInit()루틴을 살펴보자. 이 루틴에는 겨우 한줄로 된 OpenGL유틸리티 툴킷의 함수gluOrtho2D()가 사용되었다. 이 함수는 좌표계를 2차원 직교좌표계로 설정한 것이다.이 함수에는 "최소 x, 최대 x, 최소 y, 최대 y좌표값"이 전달된다. 참고: 필자는 그림이 어떻게 그려지는지를 살펴보기 위하여 단일모드 윈도우와 매우 많은 점들을 이용하여 그리도록 하였다. 크기가 크거나 시간이 많이 걸리는 그림, 그리고 OpenGL루틴에 따라 여러분의 화면에 그림이 그려지는데 오래 걸리는 것들은 주로 단일모드를 사용하는 것이 일반적이다. 아래 그림은 예제 1을 통해 볼 수 있는 그림이다.: ![]() 두번째 프로그램, 예제 2(example2.c)를 살펴보자.: #include <GL/glut.h> #include <math.h> const double pi2 = 6.28318530718; const double K_max = 3.5; const double K_min = 0.1; static double Delta_K = 0.01; static double K = 0.1; void NonlinearMap(double *x, double *y){ /* 표준 맵 */ *y += K * sin(*x); *x += *y; /* 각도 x는 2파이로 나눈 값이다.*/ *x = fmod(*x, pi2); if (*x < 0.0) *x += pi2; }; /* 콜백함수: 입력없이 무엇을 할 것인가 */ void idle(void){ /* 확률적 파라메터를 증가시킨다.*/ K += Delta_K; if(K > K_max) K = K_min; /* 화면을 다시 그린다.*/ glutPostRedisplay(); }; /* 그래픽 창(윈도우)를 초기화 한다.*/ void winInit(void){ gluOrtho2D(0.0, pi2, 0.0, pi2); }; /* 콜백함수: 화면을 다시 그릴때 어떻게 할 것인가?*/ void display(void){ const int NumberSteps = 1000; const int NumberOrbits = 50; const double Delta_x = pi2/(NumberOrbits-1); int step, orbit; glColor3f(0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0, 1.0, 1.0); for (orbit = 0; orbit < NumberOrbits; orbit++){ double x, y; y = 3.1415; x = Delta_x * orbit; glBegin(GL_POINTS); for (step = 0; step < NumberSteps; step++){ NonlinearMap(&x, &y); glVertex2f(x, y); }; glEnd(); }; for (orbit = 0; orbit < NumberOrbits; orbit++){ double x, y; x = 3.1415; y = Delta_x * orbit; glBegin(GL_POINTS); for (step = 0; step < NumberSteps; step++){ NonlinearMap(&x, &y); glVertex2f(x, y); }; glEnd(); }; glutSwapBuffers(); }; int main(int argc, char **argv) { /* GLUT초기화 */ glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); glutInitWindowPosition(5,5); glutInitWindowSize(300,300); /*창을 하나 연다. */ glutCreateWindow("Order to Chaos"); /* 창을 초기화 한다. */ winInit(); /* 콜백함수를 등록한다. */ glutDisplayFunc(display); glutIdleFunc(idle); /* 이벤트처리기를 실행한다.*/ glutMainLoop(); return 0; }이 프로그램은 예제 1(example1.c)을 으용하여 만들어졌다. 예제1과 큰 차이점은 윈도우를 이중버퍼모드로 연다는 것과 맵 파라메터인 K가 프로그램이 실행되는 동안 계속 바뀐다는 것이다. 새로운 콜백함수인 idle()이 GLUT의 glutIdleFunc()이벤트 처리기로 등록되었다.; 이 함수는 사용자의 입력이 없을때 이벤트처리기가 실행을 하는 함수이다. idle()콜백함수는 애니메이션프로그래밍에 아주 적절하다. 예제 2에서 이 함수는 맵 파라메터의 값을 조금씩 바꿔주는 기능을 한다. idle()함수의 마지막에 있는 glutPostResDisplay()함수는 이전창의 초기화를 그대로 유지한 채로 윈도우를 다시 그리기 위한 유용한 GLUT명령이다. 일반적으로 그냥 display()를 한번더 호출하는 것보다 훨씬 효율적이다. display()함수의 뒤에 사용된 glutSwapBuffers()함수도 유심히 살펴볼만한 함수이다. 이 예제에서 이중버퍼모드로 윈도우가 초기화되었기 때문에 히든버퍼로 랜더링의 방향을 지정할 수 있다. ;이 경우 그려지는 동안 사용자는 그려지는 장면을 볼 수 없다. 그러나 그림(한 프레임)이 완전히 그려지면 glutSwapBuffers()함수로 인하여 히든버퍼가 바로 보여지게 된다. 이 때 현재 그려져 있던 버퍼는 히든 버퍼로 전환된다. 이 기법을 이용하여 애니메이션을 부드럽게 할 수 있는 것이다. 아래 그애니메이션이 진행되는 동안의 몇몇 화면이다.: ![]() ![]() ![]() 중요! 밑줄 쫘악~ : The display() 콜백함수는 항상 idle()이전에 한번 실행된다. 여러분은 이것을 염두에 두고 애니메이션 기능을 프로그램할때는 display()와 idel()에서 실행될 기능들을 결정하여야 할 것이다. 선과 다각형 그리기예제 3(example3.c) 앞에서 glBegin(GLenum mode)에는 다양한 모드가 존재하며 이에 따라 정점v0, v1,v2, v3,v4,... vn-1들의 순서의 의미가 특별한 의미로 해석된다고 했다. 모드에 사용할 수 있는 값과 취할 수 있는 동작은 다음과 같다.:
![]() 먼저 idle()콜백함수는 클럭작동시간을 그대로 유지시켜준다(time변수를 업데이트 해가면서) display()함수에서는 흰 진자줄과 빨간 진자추 2개의 물체를 그려준다. 진자추의 움직임은 xcenter와 ycenter에 대한 공식에 의하여 구한다.: void display(void){ static double radius = 0.05; const double delta_theta = pi2/20; double xcenter , ycenter; double x, y; double theta = 0.0; double current_angle = cos(omega * time); glColor3f(0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0, 1.0, 1.0); /*진자의 줄을 그린다. */ glColor3f(1.0, 1.0, 1.0); glBegin(GL_LINES); glVertex2f(0.0, 0.0); xcenter = -cord_length * sin(current_angle); ycenter = -cord_length * cos(current_angle); glVertex2f(xcenter, ycenter); glEnd(); /* 진자추를 그린다. */ glColor3f(1.0, 0.0, 0.0); glBegin(GL_POLYGON); while (theta <= pi2) { x = xcenter + radius * sin(theta); y = ycenter + radius * cos(theta); glVertex2f(x, y); theta += delta_theta; }; glEnd(); glutSwapBuffers(); }; 연습지금까지 배운 내용을 이용하여 다음 연습과제들을 해보기 바란다.:
다은 연재에서는이 글에서 다루고자 하는 내용은 끝났다. 다각형에 대하여 더 이야기 해볼것들이 많다. 다음호(1998년 3월호)에서는 계속해서 다각형을 살펴보고 여러분이 이미 익숙해진 명령들을 좀 더 자세히 다루어보고 모델링하는것까지 살펴보겠다. 더 자세히 알고 싶으시면:
번역 : 이주호
|
본 웹사이트는 Miguel Angel Sepulveda씨에 의해 관리됩니다. © 1998 Miguel Angel Sepúlveda LinuxFocus 1998 |