もう一度 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