サンプルプログラム
まずサンプルプログラム(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点だけで実現できます.
- GLUT.SwapBuffersを使う
描画コールバックで,GL.Flushの代わりに,バッファの入れ換えを行うGLUT.SwapBuffersを実行します.
- GLUT.InitDisplayMode(GLUT::RGB|GLUT::DOUBLE)を指定する
プログラムの最初にGLUT.InitDisplayMode(GLUT::RGB|GLUT::DOUBLE)として,ダブルバッファリングを利用するように設定を行います. なおダブルバッファリングに必要なのはGLUT::DOUBLEですが,同時に利用する色のタイプ(ここではGLUT::RGB)も指定しています. 必要に応じて,さらに他の設定を追加する場合もあります.
なお,ダブルバッファリングを行わない場合は,1枚のバッファに順に描き込むことになります. アニメーションを行わない場合はダブルバッファリングはとくに必要ありません. これをシングルバッファリング(single buffering)といいます.