Home Map Index Search News Archives Links About LF
[Top Bar]
[Bottom
Bar]
[Photo of the
Author]
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);
void glVertex2i(23L, 43L);
void glVertex3f(1.0F, 1.0F, 5.87220972F);

float vector[3];
void glVertex3fv(vector);

이런 모든 루틴들을 그냥 간단하게 glVertex*라고 부른다.

OpenGL에서 정점의 순서는 정점이 정의된 순서를 따른다. 정점의 순서는 glBegin(GLenum mode)glEnd() 사이에 정의된다.glVertex*문은 mode값에 따라 의미가 달라진다. 예를 들어보자.:
glBegin(GL_POINTS);
glVertex2f(0.0, 0.0);
glVertex2f(1.0, 0.0);
glVertex2f(0.0, 1.0);
glVertex2f(1.0, 1.0);
glVertex2f(0.5, 0.5);
glEnd();

2차원 좌표계에 5개의 점을 그리는 것이다. GL_POINTS는 OpenGL의 헤더파일인 <GL/gl.h>.에 정의된 표식중의 하나이다. 이용할 수 있는 모드가 많지만 여기서는 필요한 몇가지들만 살펴보기로 한다.

각 점들은 OpenGL의 상태변수중 색상버퍼에 해당하는 색으로 그려진다. 현재 색상을 바꾸려면 glColor*류의 루틴을 사용하면 된다; 색상을 선택하고 다루는 방법에 대해 할 말이 많지만 이 주제는 다른 기사에서 다룰 것이다. 간단히 살펴보면 0.0부터 1.0까지의 부동형실수 3개를 이용하여 RGB(적색-녹색-청색;Red-Green-Blue) 엔코딩방식을 표현할 수 있다.;
glColor3f(1.0, 1.0, 1.0); /* 흰색 */ 
glColor3f(1.0, 0.0, 0.0); /* 빨간색 */ 
glColor3f(1.0, 1.0, 0.0); /* 보라색 */ 
등등... 

내려받기: Makefile, 예제파일(example1.c), 예제파일(example2.c)

지금까지 배운 것을 이용하고 두개의 예제를 작성해 보도록 하자. 첫번째 예제는 카오스맵(일반적인 지도)에서 많은 궤도를 그려주는 간단한 OpenGL프로그램이다. 여러분이 지도를 만드는 방법이나 표준 지도에 대하여 잘 알지 못해도 별 문제가 되지 않는다. 간단히 말하자면 임의의 한 점을 취하고 이 점을 미리 정의된 식에 대입하여 새로운 점을 생성하여 다시 대입하는 형태로 지도를 그리는 것이다. 이때 사용하는 식은 다음과 같다.:

yn+1 = yn + K sin(xn)
xn+1 = xn + yn+1

표준맵은 입자가속기의 가속경로를 따라 회전하고 있는 전기를 띈 입자가 남기는 궤적과 가속기 단면을 지나는 궤적을 나타낸다. 표준맵이나 다른 맵들의 속성을 공부하는 것은 이온가속장치에서 방전된 입자의 안정성을 이해하기 위하여 물리학에서 중요하다. 표준맵은 각 파라메터의 몇몇 값때문에 아주 산뜻하다. 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들의 순서의 의미가 특별한 의미로 해석된다고 했다. 모드에 사용할 수 있는 값과 취할 수 있는 동작은 다음과 같다.:

  • GL_POINTS    n개의 정점을 각각 그린다. 
  • GL_LINES    연결되어 있지 않은 선을 그린다. 각 세그먼트는 v0과 v1, v2와 v3,...등등이다. 만약 n이 홀수라면 세그먼트를 구성하지 못한 vn-1은 무시된다. 
  • GL_POLYGON    꼭지점으로 정점 v0, v1,..,vn-1를 가지는 다각형을 그린다. 정점은 적어도 3개이상이어야 하며 만약 그렇지 않다면 아무것도 그려지지 않는다. 또한 다각형을 이루고 있는 변들끼리 교차해서는 안되며 (하드웨어알고리즘의 제한성으로 인하여) 반드시 다각형은 컨벡스여야만 한다. 
  • GL_TRIANGLES    정점 v0, v1, v2를 이용하여 삼각형을 그리고 v3, v4, v5등을 이용하여 삼각형을 계속 그린다. 만약 남아있는 점이 3개가 안된다면 무시된다.
  • GL_LINE_STRIP    v0에서 v1로, v1에서 v2로 와 같은 방식으로 선을 그린다. 마지막 vn-2에서 vn-1로 선을 그려 총 n-1개의 직선세그먼트를 그린다. 선을 그리는데 필요한 정점의 순서에 아무런 제약조건이 없으므로 서로 교차하는 직선도 그릴 수 있다.
  • GL_LINE_LOOP    GL_LINE_STRIP과 동일하지만 마지막에 vn-1에서 v0을 잇는 선을 그려서 폐루프를 형성한다.
  • GL_QUADS   정점 v0, v1, v2, v3를 이용하여 사각형을 그린다. 계속해서 v4, v5, v6, v7을 이용해서 그린다.
  • GL_QUAD_STRIP    사각형을 그리는데 사각형을 이루고 있는 네 정점의 순서는 v0, v1, v3, v2 을 이용해서 그린 다음 v2, v3, v5, v4을 이용해서 사각형을 그리는 순서이다.
  • GL_TRIANGLE_STRIP    삼각형을 그리는데 삼각형의 정점의 순서는 v0, v1, v2로 그리고, v2, v1, v3로 그리고, v2, v3, v4등으로 그리는 식이다. 이 순서는 삼각형이 제대로 위치를 가지도록 보장을 해주며 이를 이용하여 곡면/면의 일부를 나타내는데 사용된다.
  • GL_TRIANGLE_FAN    GL_TRIANGLE_STRIP와 비슷하다 그러나 삼각형을 그릴때 정점 v0, v1, v2을 이용하여 그리고, v0,v2, v3를 이용해서 그리고, v0, v3, v4을 이용해서 그리는 방식을 취하는 점에서 다르다. 모든 삼각형은 공통적으로 정점 v0를 가지고 있다.
세번째 예제프로그램에서 GL_LINES와 GL_POLYGON을 이용한 다른 애니메이션프로그램을 만들어보기로 하자. 프로그램을 컴파일한 다음 소스코드를 살펴보면서 이것이 어떤 역할을 하고 있는지 살펴보자. 이 소스코드는 예제2(example2.c)와 매우 비슷하다. 화면의 그림은 매우단순한 진자를 나타낸다. 이 애니메이션은 이상적인 진자의 움직임을 흉내낼 것이다. 애니메이션중의 한장면은 다음과 같다.:
 

먼저 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();
};

    
    

연습

지금까지 배운 내용을 이용하여 다음 연습과제들을 해보기 바란다.:

  • 예제1(example1.c)을 이용하여 다른 맵들을 생성해 보라. 이를 위하여 먼저 도서관에 가서 혼돈(카오스;Chaos)이론과 프랙탈(Fractals)이론에 관한 책을 한권 골라서 보라. 아마 책에 많은 예들이 있을 것이다. 파라메터값과 좌표계의 값을 바꾸어 보고 여러가지 맵을 적용해 보라. 재미있는 경험이 될 것이다.
  • 예제2(example2.c)에서 긱 점들에 색깔을 입혀보자. 예를 들자면  (Physics Review Letters Vol 63, (1989) 1226을 참고하여) 궤도의 안정성에 따라 서로 다른 색상을 설정해보라. 아마 혼동형역을 지나가는 궤적은 붉은 빛을 띨 것이다. 예를 들어 안정된 섬 근처에서는 푸른 빛을 띨 것이다. 만약 여러분이 이 효과를 낼 수 있는 코드를 작성할 수 있다면 우리가 예로 들었던 맵들의 프랙털속성이 더욱 명확해 질 것이다. 물론 미분방정식을 배우지 않더라도 그 주제에 대한 지식이 조금 늘어날 것이다. 만약 여러분이 컴퓨터 그래픽에서 맵핑과 프랙털을 이용하고싶다면 미분방정식을 배우는 것이 충분히 가치가 있는 일이다.
  • 예제3(example3.c)을 이용하여 여러형태의 선을 이용하여 디스크 를 그리는 프로그램을 작성해 보라. GL_LINES, GL_TRIANGLES,등등을 이용해 보라. 어떻게 보이는가. 디스크를 생성하는 코드를 최적화 해보라. 만약 sin과 cos을 이용하지 않는다면 각 프레임에서 동일한 디스크를 그리기 위한 시간이 많이 걸릴 것이다.  여러분은 이 정보를 배열의 형태로 저장할 수 있다. 다각형을 이용하여 서랍상자나 다이어몬드 또는 진자의 끝에 무언가를 그려보라. 또 제각각 따로 움직이거나 서로 충돌하는 두 진자를 그리는 프로그램을 작성하는 것도 좋은 예가 될 것이다.

다은 연재에서는

이 글에서 다루고자 하는 내용은 끝났다. 다각형에 대하여 더 이야기 해볼것들이 많다. 다음호(1998년 3월호)에서는 계속해서 다각형을 살펴보고 여러분이 이미 익숙해진 명령들을 좀 더 자세히 다루어보고 모델링하는것까지 살펴보겠다.


더 자세히 알고 싶으시면:



번역 : 이주호


본 웹사이트는 Miguel Angel Sepulveda씨에 의해 관리됩니다.
© 1998 Miguel Angel Sepúlveda

LinuxFocus 1998