もう一度 Ruby で OpenGL してみる

前にも OpenGL してみたのだが、もう一度最初からやり直してみる。

モジュール 'MiniOpenGL' を書いてみる。で、こんな風にやりたいなと。
ガイドは下。
OpenGL“ü–å

二次元

■単なるウィンドウの表示。

require './miniopengl'

MiniOpenGL.app width: 400, height: 300, x: 300, y: 200, title: "OpenGL" do
  draw {display}
end

Tool#display は glFlush に相当し、強制的に描画します。それに対し、Tool#finish は glFinish に相当し、コマンドが完全に履行されるまで制御を返しません。
以下、require './miniopengl' は省略します。

■白地に黄色い正方形。

MiniOpenGL.app do
  clear_color(1, 1, 1)    #白
  
  draw do
    clear
    color(1, 1, 0)        #黄色
    block(GL_POLYGON) do      #多角形
      glVertex2d(-0.8, -0.8)  #左下
      glVertex2d( 0.5, -0.8)  #右下
      glVertex2d( 0.5,  0.5)  #右上
      glVertex2d(-0.8,  0.5)  #左上
    end
    display
  end
end

Tool#clear_color, Tool#color は色指定(最大値 1)、Tool#clear は画面クリア、Tool#block はブロックが glBegin と glEnd の間を意味します。
0.8 というのはウィンドウに対する割合です。ウィンドウの真ん中が原点です。なお、glVertex2d の 'd' は double のことです。'f' なら float です。

■緑の三角形。頂点配列。

MiniOpenGL.app do
  draw do
    vertex = [-0.9, 0.9, 0.9, 0.9, 0, -0.9]  #三角形の頂点を指定
    color(0, 1, 0)
    glLineWidth(3)                           #線の太さ
    draw_vertex(GL_LINE_LOOP, 2, vertex)     #頂点の処理。'2'は2次元(平面)ということ
    display
  end
end

Tool#draw_vertex は頂点の処理。

■簡単なアニメーション。

MiniOpenGL.app do
  i = -1
  draw {display}
  
  repeat(100) do
    color(0, 1, 0)
    line(i, 1, -i, -1)
    redisplay
    i += 0.05
    i = 1 if i > 1
  end
end

Tool#line は線の描画、Tool#repeart はブロック内をミリ秒単位での繰り返し、Tool#redisplay は再描画。

■三角形の回転。

MiniOpenGL.app do
  draw do
    block(GL_POLYGON) do
      color(1, 0, 0)
      glVertex2f(-0.9, -0.9)
      color(0, 1, 0)
      glVertex2f(0, 0.9)
      color(0, 0, 1)
      glVertex2f(0.9, -0.9)
    end
    display
  end
  
  repeat(50) do
    modelview             #モデルビュー変換
    rotate(2, 0, 1, 0)    #回転
    redisplay
  end
end

この場合、色は各頂点からなだらかに変位するように描画される。
Tool#rotate(angle, x, y, z) は、方向ベクトル (x, y ,z) の周りに angle だけ図形を回転させる(参照)。平行移動は Tool#translate、スケーリングは Tool#scale で行う。またはアフィン変換行列を使ってもよい。それは Tool#mload(導入)、Tool#mmap写像)で行う。

これらは Tool#modelview、Tool#projection のいずれかによって正反対になる。モデルビュー変換の方は物体(オブジェクト)を空間内で動かし、射影変換の方はカメラが空間内を移動する。

三次元

■立方体の回転。

L = 0.5
vertex = [[-L, -L, -L], [L, -L, -L], [L, L, -L], [-L, L, -L],
     [L, -L, L], [-L, -L, L], [-L, L, L], [L, L, L]]
edge = [[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6],
     [6, 7], [7, 4], [0, 5], [1, 4], [2, 7], [3, 6]] 

MiniOpenGL.app do
  clear_color(0, 0, 0.3)
  
  draw do
    clear
    color(0, 1, 0)
    draw_elements(GL_LINES, 3, vertex.flatten, edge.flatten)
    display
  end
  
  reshape do |w, h|
    viewport(0, 0, w, h)                  #(0, 0)は表示する画面の左上の隅の座標、(w, h)は画面の幅と高さ。これらは表示される実際のウィンドウの座標
    init_projection
    perspective(30, w / h.to_f, 3, 10)    #30は視野角、次の引数は画面のアスペクト比、(3, 10)は表示する奥行きが視点からの距離3〜10ということ
    init_modelview                        #look_at の前には必ず付ける
    look_at(3, 4, 5, 0, 0, 0, 0, 1, 0)
  end
  
  repeat(30) do
    modelview    #物体の方を回転させる
    rotate(5, 0, 1, 0)
    redisplay
  end
end

Tool#clr_matrix は行列の初期化、Tool#draw_elements は頂点と辺による描画、Tool#look_at は gluLookAt に相当し、視点(カメラ)の設定(参照)。
Tool#reshape はタイマーやウィンドウのサイズが変更された場合などに行われる再描画の際に呼ばれる。w, h はそれぞれウィンドウの横幅、高さ。Tool#viewport, Tool#perspective はそれぞれ glViewport, glPerspective そのまま。後者についてはここを参照。
Tool#init_projection, Tool#init_modelview は、それぞれ射影変換とモデルビュー変換の初期化。

■立方体を二つ描く。

L = 0.5
vertex = [[-L, -L, -L], [L, -L, -L], [L, L, -L], [-L, L, -L],
     [L, -L, L], [-L, -L, L], [-L, L, L], [L, L, L]]
edge = [[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6],
     [6, 7], [7, 4], [0, 5], [1, 4], [2, 7], [3, 6]] 

MiniOpenGL.app do
  clear_color(0, 0, 0.3)
  
  draw do
    clear
    color(0, 1, 0)
    init_modelview          #動かすのは物体(look_at の前には必ず付ける)
    look_at(3, 4, 5, 1, 0, 0, 0, 1, 0)
    draw_elements(GL_LINES, 3, vertex.flatten, edge.flatten)      #ひとつ目の立方体を描く
    display
    stack do                #行列をスタックして座標系を保護する
      translate(2, 0, 0)    #ひとつ目の立方体を平行移動する
      draw_elements(GL_LINES, 3, vertex.flatten, edge.flatten)    #原点にふたつ目の立方体を描く
      display
    end
  end
  
  reshape do |w, h|
    clear
    viewport(0, 0, w, h)
    init_projection
    perspective(30, w / h.to_f, 3, 10)
    init_modelview
    look_at(3, 4, 5, 1, 0, 0, 0, 1, 0)
  end
end

Tool#stack は行列をスタックして保護する。Tool#mpush, Tool#mpop を組み合わせてもよい。

■立方体の拡大して回転。ダブルバッファリングしている。スペースキーで終了。

L = 0.5
vertex = [[-L, -L, -L], [L, -L, -L], [L, L, -L], [-L, L, -L],
     [L, -L, L], [-L, -L, L], [-L, L, L], [L, L, L]]
edge = [[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6],
     [6, 7], [7, 4], [0, 5], [1, 4], [2, 7], [3, 6]]
ang = 0
r = 1

MiniOpenGL.app buffering: :on do    #ダブルバッファリングを可能にする
  clear_color(0, 0, 0.3)
  
  draw do
    clear
    init_modelview
    look_at(7, 3, 7, 0, 0, 0, 0, 1, 0)
    
    color(0.6, 0, 0)
    (-10..10).each do |i|     #xz平面の描画
      line3(i, 0, 10, i, 0, -10)
      line3(10, 0, i, -10, 0, i)
    end
    
    color(0, 1, 0)
    stack do
      scale(r, r, r)         #拡大
      rotate(ang, 1, 0, 0)   #回転
      draw_elements(GL_LINES, 3, vertex.flatten, edge.flatten)
    end
    swap_buffers    #描画バッファの交代(display の代わりにこれにすればよい)
  end
  
  reshape do |w, h|
    clear
    viewport(0, 0, w, h)
    init_projection
    perspective(30, w / h.to_f, 1, 20)
    init_modelview
    look_at(7, 3, 7, 0, 0, 0, 0, 1, 0)
  end
  
  repeat(50) do
    ang += 5
    r += 0.01
    r = 3 if r > 3
    redisplay
  end

  key_in do |key|
    exit if key == " "    #スペースキーで終了
  end
end

Tool#key_in はキー入力。Tool#line3 は三次元で一本の線を引く。
ダブルバッファリングはモジュールの引数に buffering: :on を追加し、Tool#display の代わりに Tool#swap_buffers にするだけでよい。描画バッファを交代させ、画面のちらつきを抑える。

■三角形の回転(三次元版)。

MiniOpenGL.app do
  draw do
    block(GL_POLYGON) do
      color(1, 0, 0)
      glVertex3d(-0.9, -0.9, 0)
      color(0, 1, 0)
      glVertex3d(0, 0.9, 0)
      color(0, 0, 1)
      glVertex3d(0.9, -0.9, 0)
    end
    display
  end
  
  reshape do |w, h|
    viewport(0, 0, w, h)
    init_projection
    perspective(30, w / h.to_f, 1, 20)
    init_modelview
    look_at(8, 2, 0, 0, 0.5, 0, -0.6, 0.8, 0)
  end
  
  repeat(50) do
    modelview
    rotate(2, 0, 1, 0)
    redisplay
  end
end


射影変換をきちんとやる:TODO (http://wisdom.sakura.ne.jp/system/opengl/gl12.html
ビューポート:TODO


※参考
OpenGL の gluLookAt と glLoadMatrix - Marginalia
OpenGL でごく単純なフライトシミュレータ - Marginalia