OpenGL 활용/1장 - Basic

1장 4절 - Animation by Idling

BeH 2013. 1. 21. 00:54

Animation by Idling

 

 

 

OpenGL  에서 이미지의 움직임과 동작을 주기 위한 방법으로 glutIdleFunc() glutTimerFunc() callback 함수가 있다.  함수의 syntax 는 다음과 같다.

 

void glutIdleFunc( void (*func)(void) );

void glutTimerFunc( unsigned int millisec, void (*func)(int value), int value );

 

참고 Site:    glutIdleFunc()    http://www.opengl.org/resources/libraries/glut/spec3/node63.html

                    glutTimerFunc()     http://www.opengl.org/resources/libraries/glut/spec3/node64.html

 

 

간단한 Animation 을 해보기 위해 다음의 함수를 main() 함수에 등록하고,  idle() 함수 와 Timer() 함수를 만들어 보자.

 

추가 전역변수:

 

 GLfloat   speed = 1.0;

 GLboolean rot_status = TRUE;

 

main() 함수:

 

           .

           .

      // init GLUT and create Window
      glutInit( &argc, argv );
      glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
      glutInitWindowSize( wwidth, wheight );
 
          .

           . 
      // handling keyboard input events
      glutKeyboardFunc( keyboard  );
      glutSpecialFunc( special );
      glutIdleFunc( idle );
   // Timer( 0 ); // glutTimerFunc( 1, Timer, 0 );

 

      // enter GLUT event processing cycle
      glutMainLoop();

           .

           .

 

diaplay() 함수:  마지막의 glFlush(); 를 주석처리 해주고 대신

 

            .

            .

   // glFlush();        // 주석처리
      glutSwapBuffers();
}

 

keyboard() 함수:  삼각형의 회전 속도의 변화를 주기위하여 다음을 더한다.

 

             .

             .
         case '+':
                  if (magfac <= 2.0f) magfac += 0.02;
                  break;
         case '-':
                  if (magfac >= 0.04) magfac -= 0.02;
                  break;
         case '>':
                  speed += 1.0f;
                  break;
         case '<':
                  speed -= 1.0f;
                  break;

         case ' ': // space bar
                  rot_status = !rot_status;
                  if (rot_status) glutIdleFunc( idle );
                  else            glutIdleFunc( NULL );
                  break;
         case 27:

                  exit(0);
                  break;

            .

            .

 

idle() 함수:

 

 void  idle( void )
{
      zrot += speed * 0.025f;
      if (zrot > 360.0f) zrot -= 360.0f;
      glutPostRedisplay(); // tell GLUT that the display has changed
}

 

Timer() 함수:

 

 void  Timer( int value )
{
      zrot += speed * 1.0f;
      if (zrot > 360.0f)  zrot -= 360.0f;
      glutPostRedisplay(); // tell GLUT that the display has changed
     
      // tell GLUT to call update again in 25 milliseconds
      glutTimerFunc( 25, Timer, 0 );
}

 

main() 에 있는 glutInitDisplayMode() 의 Mode 가 GLUT_SINGLE 에서 GLUT_DOUBLE 로 변화되었음을 볼 수 있다.  그리고 display() 함수의 마지막을 glFlush() 대신 glutSwapBuffers() 를 사용하였다.   이는 GLUT_DOUBLE 로 이중 Buffer 를 사용할여 앞, 뒤의 Buffer 를 서로 Swapping 하면서 교환하라는 명령을 주기 위함이다. 

한 Buffer 에서 지웠다 그리기를 계속 반복하면 깜박거림이 발생하여 눈에 거슬리므로 두 개의 Buffer 를 교대로 사용한다.  초기화시에 GLUT_DOUBLE  flag 를 전달하면 OpenGL 은 화면에 출력하는 Front Buffer 와 뒤에서 그리는 작업을 하는 Back Buffer, 두 개의 Buffer 를 준비한다.  glutSwapBuffers() 에 의해  Front Buffer 와 Back Buffer 가 일시에 교체됨으로써 Back Bufff  에 미리 준비해 놓은 그림이 바로 나타난다.  순간적으로 교체되므로 깜박거림은 전혀 느낄 수 없다.  이 과정을 계속 반복하면 animation 이 되는 것이다.  Buffer 를 교체하는 것 자체가 출력이므로 glFlush() 는 호출하지 않아도 상관없다.  위 에서 speed 는 전역변수로 rotation 속도를 조절하기 위한 변수이다.  '<'  와 '>' key 로 크기를 조절하게 하였다.

 

idle() 함수는 단지 idling 시간만을 지정해 주고,  glutPostRedisplay() 함수를 불러 display 할 내용을 변화시키도록 한다.

위 두개의 함수의 차이를 보면 Timer() 함수는 함수내에서 다시 재귀 (recursive) 형태로 자신을 다시 호출한다는 것이다.  이유는 glutTimerFunc() 은 지정된 시간후에 지정된 함수를 딱 한번 호출한다.  주기적으로 계속 호출하려면 callback 함수에서 자신을 다시 호출해야 한다. 지속적으로 호출되는 방식에 비해 약간 불편하지만 매 호출시마다 다음 주기를 가변적으로 결정할 수 있다는 면에서 활용성은 오히려 더 높다.     더 이상 Timer() 호출이 필요없으면 callback 을 재등록하지 않으면 된다.   함수의 동작형태는 millisec 후에 Timer() 함수를 호출하며 인수로 value를 전달한다.   value는 Timer callback 으로 전달되어 작업 시간의 거리를 지시하는데,  Timer 의 용도가 하나뿐이라면 아무 값이나 주어도 상관없다.   callback 을 등록해 놓으면 millisec 후에 callback 함수가 호출된다.    main() 에서는 다만 시동만 걸어줄 뿐이고 다음 타이머 호출 시점은 callback 함수가 자체적으로 결정할 수 있다.

 

System 이 초당 60 frame 을 refresh 한다고 가정하면,  가장 빠른 frame 율은 60 fps 이다.  따라서 1/60 초 이내에 frame 이 정리되고 다시 그려져야 부드럽게 움직이는 animation  이 된다.   그림이 복잡하여 한 frame 을 그리는데 1/45 초가 걸린다면,  frame 율은 30 fps 가 될 것이다.  이 경우 graphic 은 1/30 - 1/45 = 1/90 초의 frame 당 시간 (sec per frame) 의 idle 이 요구된다.    만일 Back Buffer 에서 각각의 그림들이 그려지는 시간이 일정하지 않다면,  어떤 frame 은 시간을 좀더 많이 쓰고, 어떤 frame 은 시간을 적게 쓰게 되는 현상들이 발생할 것이다.  이러한 현상들에 의해 부드러운 Animation 진행과정이 방해 받게된다.

 

프로그램을 컴파일한 후 실행하면서, 앞 절, keyboard 프로그램 실행시 Test 해 보았던 것들을 해보면, 어떤 변화가 일어나는지를 볼 수 있을 것이다.  

 

 

 

 

응용 Program:

 

무한정 움직이는 1개의 당구공을 생각해 보자.  당구공은 당구대의 벽면에 대한 입사각에 대하여 같은 반사각으로 튕겨진다.  이 원리를 적용하여 공의 위치를 속도에 따른 움직이는 거리를 계산하여 glTranslate() 함수를 사용,  공이 Table 내에서 계속 움직이도록 프로그램 한것이다.  

 

우선 몇개의 전역변수를 정의 하였다.

 

 #define   angle   -30.0          // launching angle

 

// set window size & image size coordinates & image view mode
GLsizei   wwidth = 640, wheight = 320; // initial window width & height
GLfloat   ix1 = 0.0, ix2 =131.0,       // set size of table
          iy1 = 0.0, iy2 = 67.0,
          iz1 =-3.0, iz2 =  3.0;       // depends on radius of ball
GLfloat   radii = 3.0, dt = 3.0;       // radius of ball & table edge thickness
GLfloat   xtr = 20.0, ytr = 10.0;      // initial ball location
GLfloat   speed = 0.02, xdeg = angle, ydeg = angle; // initial ball speed & launching angle
GLfloat   red = 1.0, green = 0.0, blue = 0.0, alpha = 1.0;  // initial ball color

 

다음 display() 함수를 다음과 같이 수정한다.

 

             .

             .

     // clear all color & depth buffers
      glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT );


      drawTable();

 

      /*  Translate & rotate the image  */
      glPushMatrix();                   // Save the transformations performed
      glTranslatef( xtr, ytr, 0.0 );    // move ball to initial position

 

      /*  Draw circular ball  */
      glColor4f( red, green, blue, alpha );
      glBegin( GL_POLYGON);
            glVertex3f( 0.0, 0.0, 0.0 );
            for (int i = 0; i <= 360; i += 10) {
                  GLdouble  degree = (GLdouble)i * (PI/180.0);
                  glVertex3f( radii*cos(degree), radii*sin(degree), 0.0 );
            }
      glEnd();
      glPopMatrix();     // Restore the transformations

 

      sprintf( info, "x=%.1f, y=%.1f, launching angle=%.3f", xtr, ytr, angle );
      glutSetWindowTitle( info );

 

      glutSwapBuffers();
}

 

위에서 drawTable(); 함수의 내용을 지금 설명에는 조금 이른감이 있어 아래 Source Code 에 포함 되어있는것을  사용하기로 한다.  

 

다음 idle() 함수 또는 Timer() 함수를 다음과 같이 작성한다.

 

void  idle( void )
{
      GLfloat  xmin = ix1 + radii + dt, xmax = ix2 - radii - dt;
      GLfloat  ymin = iy1 + radii + dt, ymax = iy2 - radii - dt;


      if (xdeg > +360.0) xdeg -= 360.0;
      if (xdeg < -360.0) xdeg += 360.0;
      if (ydeg > +360.0) ydeg -= 360.0;
      if (ydeg < -360.0) ydeg += 360.0;

 

      xtr += speed * cos( xdeg * PI/180.0 );
      ytr += speed * sin( ydeg * PI/180.0 );

 

      if (ytr >= ymax)  ydeg = -ydeg;
      if (xtr >= xmax)  xdeg += 180.0;
      if (ytr <= ymin)  ydeg = -ydeg;
      if (xtr <= xmin)  xdeg += 180.0;

 

      glutPostRedisplay(); // tell GLUT that the display has changed
}

 

ball 의 table 을 향한 입사각과 반사각에 대한 계산이므로 조금만 깊이 살펴보면 원리를 이해할 수 있을것이다.

 

그외 reshape() keyboard() special() 함수를 약간 수정하여 컴파일 후 실행배보면 재미있는 결과를 볼것이다.
실행할 때 선택사항은 다음과 같다.

 

'>'  와  '<' key           -   공의 속도를 변화.

'arrow'  keys            -   공의 입사각의 변화

'r',  'g', 'b', 'w' key     -   공의 색상 변화

'F11' & 'F11'            -   Toggle fullsize window

 

 

 

 

 

 

 

Source Code:         01_4 - animation.zip         01_4 - moving_ball.zip