[CG実習 > アニメーション]

アニメーション

アニメーションとは,一般には動画(動きのある一連の映像)を意味します. アニメーションは,少しずつ変化する画像を次々と表示していくことで実現されます. ここでは,CGの重要な要素であるアニメーション機能をもったプログラムをOpenGLで作成する方法について説明します.

サンプルプログラム

まずサンプルプログラム(windmill.rb)を見て下さい. 各行の左端の数字は,説明のために付けた行の番号で,プログラムにはこの行番号は含まれません.

このプログラムでは,風車を回転させるアニメーションを行います. [r]を押すと,風車が回転します. もう一度押すと停止します. [q]あるいは[ESC]を押すとプログラムが終了します.


001  # coding: utf-8
002  require "opengl"
003  require "glu"
004  require "glut"
005  
006  ##
007  ## 定数
008  ##
009  DTHETA = 0.02*Math::PI # 回転角単位
010  WING_W = 0.7           # 風車の羽根のサイズ(長さ)
011  WING_H = 0.3           # 風車の羽根のサイズ(幅)
012  SLEEP_TIME = 0.01      # アニメーションスピード調整パラメタ
013  WSIZE = 400            # ウインドウサイズ
014  
015  ##
016  ## 状態変数
017  ##
018  __theta = 0       # 風車の角度
019  __anim_on = false # アニメーションON/OFF(最初は偽:OFF)
020  
021  display = Proc.new {
022  
023    # 背景のクリア
024    GL.Clear(GL::COLOR_BUFFER_BIT)
025  
026    # 風車の描画
027    GL.Begin(GL::QUADS)
028  
029      cos_t = Math.cos(__theta)
030      sin_t = Math.sin(__theta)
031  
032      x0 = WING_W*cos_t
033      y0 = WING_W*sin_t
034      x1 = -WING_H*sin_t
035      y1 = WING_H*cos_t
036      x2 = x0 + x1
037      y2 = y0 + y1
038  
039      x3 = -WING_W*sin_t
040      y3 = WING_W*cos_t
041      x4 = -WING_H*cos_t
042      y4 = -WING_H*sin_t
043      x5 = x3 + x4
044      y5 = y3 + y4
045  
046      GL.Color(1.0,0.7,0.0)
047      GL.Vertex(0.0,0.0)
048      GL.Vertex(x0,y0)
049      GL.Vertex(x2,y2)
050      GL.Vertex(x1,y1)
051  
052      GL.Color(1.0,0.4,0.0)
053      GL.Vertex(0.0,0.0)
054      GL.Vertex(x3,y3)
055      GL.Vertex(x5,y5)
056      GL.Vertex(x4,y4)
057    
058      GL.Color(1.0,0.7,0.0)
059      GL.Vertex(0.0,0.0)
060      GL.Vertex(-x0,-y0)
061      GL.Vertex(-x2,-y2)
062      GL.Vertex(-x1,-y1)
063  
064      GL.Color(1.0,0.4,0.0)
065      GL.Vertex(0.0,0.0)
066      GL.Vertex(-x3,-y3)
067      GL.Vertex(-x5,-y5)
068      GL.Vertex(-x4,-y4)
069  
070    GL.End()
071  
072    #  GL.Flush()      # SINGLEバッファの場合
073    GLUT.SwapBuffers() # 2枚のバッファの交換
074  }
075  
076  #### アイドルコールバック ########
077  idle = Proc.new {
078    sleep(SLEEP_TIME)          # SLEEP_TIMEだけ待つ
079    __theta += DTHETA           # 風車の角度の更新
080    GLUT.PostRedisplay()       # displayコールバックを(後で適宜)呼び出す
081  }
082  
083  #### キーボード入力コールバック ########
084  keyboard = Proc.new { |key,x,y| 
085    # [r]でアニメーション開始/停止
086    if key == 'r'
087      if __anim_on           ## アニメーションON(ON->真)の場合
088        GLUT.IdleFunc(nil)   # idleコールバックの登録削除(→アニメーション停止)
089        __anim_on = false    # アニメーションOFF(ON->偽)
090      else                   ## アニメーションOFFの場合
091        GLUT.IdleFunc(idle)  # idleコールバックの登録(→アニメーション開始)
092        __anim_on = true     # アニメーションON(ON->真)
093      end
094    # [q]か[ESC]の場合は,終了する.
095    elsif key == 'q' or key.ord == 0x1b
096      exit 0
097    end
098  }
099  
100  ##############################################
101  # main
102  ##############################################
103  GLUT.Init()
104  GLUT.InitDisplayMode(GLUT::RGB|GLUT::DOUBLE) # ダブルバッファを利用するための設定
105  GLUT.InitWindowSize(WSIZE,WSIZE) 
106  GLUT.CreateWindow("Windmill")
107  GLUT.DisplayFunc(display)        
108  GLUT.KeyboardFunc(keyboard)      
109  GL.ClearColor(0.4,0.4,1.0,1.0)   
110  GLUT.MainLoop()

アイドルコールバックによるアニメーションの実現

アイドルコールバック(idle callback)を利用することで,簡易的にアニメーションを実現することができます. アイドルコールバックは,他に処理するイベントがないときに処理される特殊なコールバックです. アイドルコールバックは,GLUT.IdleFuncで登録します.

サンプルプログラムでは,アイドルコールバックで,アニメーションの時間調整 (sleep)を行ったのち,状態変数__thetaを少しづつ変化させて,GLUT.PostRedisplayを呼び出して,描画を行うようになっています. ここで,__thetaは,風車の表示角度を表すデータです. 描画コールバック(display)では,この角度に基づいて風車を描くようになっています.


076  #### アイドルコールバック ########
077  idle = Proc.new {
078    sleep(SLEEP_TIME)          # SLEEP_TIMEだけ待つ
079    __theta += DTHETA           # 風車の角度の更新
080    GLUT.PostRedisplay()       # displayコールバックを(後で適宜)呼び出す
081  }

アイドルコールバックの登録と削除

アイドルコールバックは,登録された後は常に有効になりますが, サンプルプログラムでは,常にアニメーションを行うのではなく, [r]キーを押すことでアニメーションをON/OFFするようにしています.

アイドルコールバックを削除すれば,アニメーションの実行を停止させることができます. もう一度アイドルコールバックを登録すれば,アニメーションが再開されます. つまり,キー入力に連動して,アイドルコールバックの登録/削除を行うことでアニメーションの制御ができるわけです.

サンプルプログラムでは,キーボード入力コールバック(keyboard)でそのような処理を行っています. コールバックを削除するには,登録メソッドにnilを渡します. また,アニメーションの現在の状態(ONかOFFか)を状態変数__anim_onで管理しています.


083  #### キーボード入力コールバック ########
084  keyboard = Proc.new { |key,x,y| 
085    # [r]でアニメーション開始/停止
086    if key == 'r'
087      if __anim_on           ## アニメーションON(ON->真)の場合
088        GLUT.IdleFunc(nil)   # idleコールバックの登録削除(→アニメーション停止)
089        __anim_on = false    # アニメーションOFF(ON->偽)
090      else                   ## アニメーションOFFの場合
091        GLUT.IdleFunc(idle)  # idleコールバックの登録(→アニメーション開始)
092        __anim_on = true     # アニメーションON(ON->真)
093      end
094    # [q]か[ESC]の場合は,終了する.
095    elsif key == 'q' or key.ord == 0x1b
096      exit 0
097    end
098  }

ダブルバッファリング

ウインドウにはプログラムの処理に従って,順に描画が進められていきます. ウインドウへの描画の途中経過が逐一画面に表示されてしまうとちらつきの原因となる場合があります. これを防ぐには,途中経過を表示しないで1枚の画像が完成してから一度に表示を行うようにします. これを実現するための仕組みがダブルバッファリング(double buffering)です.

一般にバッファ(buffer)とは,一時的にデータを保存しておく場所のことです. ダブルバッファリングとは,表バッファ(front buffer)と裏バッファ(back buffer)の2枚を用いて,滑らかなアニメーション表示を実現するための機能です. 表示されるのは,表バッファだけです. アニメーションを行う際には,(表示されない)裏バッファに描画していって,描画が終わったときに,表バッファと裏バッファを入れ換えます. こうすることで,描画の途中経過を表に出すことなく,滑らかにアニメーションを行うことが可能になります.

ダブルバッファリングを行うのは簡単です. 次の2点だけで実現できます.

  1. GLUT.SwapBuffersを使う
    描画コールバックで,GL.Flushの代わりに,バッファの入れ換えを行うGLUT.SwapBuffersを実行します.

  2. GLUT.InitDisplayMode(GLUT::RGB|GLUT::DOUBLE)を指定する
    プログラムの最初にGLUT.InitDisplayMode(GLUT::RGB|GLUT::DOUBLE)として,ダブルバッファリングを利用するように設定を行います. なおダブルバッファリングに必要なのはGLUT::DOUBLEですが,同時に利用する色のタイプ(ここではGLUT::RGB)も指定しています. 必要に応じて,さらに他の設定を追加する場合もあります.

なお,ダブルバッファリングを行わない場合は,1枚のバッファに順に描き込むことになります. アニメーションを行わない場合はダブルバッファリングはとくに必要ありません. これをシングルバッファリング(single buffering)といいます.

[CG実習 > アニメーション]