TOP   Ruby

OpenGL(Module Opal)

目次

Module Opal

RubyGem 'OpenGL' のラッパー。いちいち書くのが面倒なところを処理します。Opal を使わない例と比べてみて下さい。(2016/5/10)

RubyGem 'OpenGL', 'Glu', 'Glut' のインストールが必要です。opal.rbと描画スクリプトを同じディレクトリに置いて下さい。

Opal.app のブロック変数(例えば g)が Toolクラスのオブジェクトになっています。Toolクラスのメソッドはだからすべて、g のインスタンス・メソッドというわけです。 これらは(そうしたければ)一切使わなくてもOKです(意味ないですけれど)。このモジュールを使う際は、関数はクロージャである proc(あるいは lambda でも可)を使うのがミソです。

なお、こちらにも Opal を用いたコード例があります。

opal.rb

require 'bundler/setup'    #Bundlerを使っていない人は必要ない
require 'opengl'
require 'glu'
require 'glut'
include Gl, Glu, Glut, Math

module Opal
  class Tool
    def initialize
      @rotate_v = [1, 0, 0]
      @clear_color = [0, 0, 0]
      @clear_color_alpha = 0
      @color_alpha = 1
      @depth_buffer_bit = 0
      @look_to = [0, 0, 0]
      @up = [0, 1, 0]
      @font = GLUT_BITMAP_TIMES_ROMAN_24
    end
    attr_accessor :clear_color, :color_alpha, :clear_color_alpha, :buffering, :depth, :depth_buffer_bit,
        :look_to, :up, :font
    
    def display=(prc)
      disp = proc do
        clear
        prc.call
        glutSwapBuffers unless @buffering.zero?
      end
      glutDisplayFunc(disp)
    end
    
    def reshape=(prc)
      prc1 = proc do |w, h|
        glViewport(0, 0, w, h)
        glMatrixMode(GL_PROJECTION)
        clr_matrix
        prc.call(w, h)
        glMatrixMode(GL_MODELVIEW)
      end
      glutReshapeFunc(prc1)
    end
    
    def mouse=(prc)
      glutMouseFunc(prc)
    end
    
    def passive_motion=(prc)
      glutPassiveMotionFunc(prc)
    end
    
    def motion=(prc)
      glutMotionFunc(prc)
    end
    
    def keyboard=(prc)
      glutKeyboardFunc(prc)
    end
    
    def idle=(prc)
      glutIdleFunc(prc)
    end
    
    def add_menu(str, value)
      glutAddMenuEntry(str, value)
    end
    
    def font=(a)
      @font = a
    end
    
    def print(str)
      str.bytes.each {|ch| glutBitmapCharacter(@font, ch)}
    end
    
    def rotate=(ar)
      @rotate_v = ar 
    end
    
    def rotate(ang)
      glRotated(ang, @rotate_v[0], @rotate_v[1], @rotate_v[2])
    end
    
    def look_at(x, y, z)
      l = sqrt(@up[0] ** 2 + @up[1] ** 2 + @up[2] ** 2)
      gluLookAt(x, y, z, @look_to[0], @look_to[1], @look_to[2], @up[0] / l, @up[1] / l, @up[2] / l)
    end
    
    def draw(c)
      glBegin(c)
      yield
      glEnd
    end
    
    def draw_vertex(c, dim, vtx)
      glEnableClientState(GL_VERTEX_ARRAY)
      glVertexPointer(dim, GL_FLOAT, 0, vtx)
      glBegin(c)
      (vtx.size / dim).times {|i| glArrayElement(i)}
      glEnd
    end
    
    def draw_elements(c, dim, vtx, id)
      glEnableClientState(GL_VERTEX_ARRAY)
      glVertexPointer(dim, GL_FLOAT, 0, vtx)
      glDrawElements(c, id.size, GL_UNSIGNED_BYTE, id)
    end
    
    def perspective(fovy = 60, aspect = 1, znear = 0.1, zfar = 20)
      glMatrixMode(GL_PROJECTION)
      clr_matrix
      gluPerspective(fovy, aspect, znear, zfar)
      glMatrixMode(GL_MODELVIEW)
    end
    
    def block
      glPushMatrix
      yield
      glPopMatrix
    end
    
    def timer(time, prc, value = 0)
      glutTimerFunc(time, prc, value)
    end
    
    def repeat(time, value = 0, &bk)
      a = proc do
        yield
        timer(time, a, value)
      end
      glutTimerFunc(time, a, value)
    end
    
    def redraw
      glutPostRedisplay
    end
    
    def color(r, g, b)
      glColor4f(r, g, b, @color_alpha)
    end
    
    def clear(a = GL_COLOR_BUFFER_BIT)
      glClear(a | @depth_buffer_bit)
    end
    
    def clr_matrix
      glLoadIdentity
    end
    
    def vtx2(x, y)
      glVertex2d(x, y)
    end
    
    def vtx3(x, y, z)
      glVertex3d(x, y, z)
    end
    
    def light(num , *args)
      glLightfv(eval("GL_LIGHT#{num}"), *args)
    end
    
    def light_enable(num = 0)
      glEnable(eval("GL_LIGHT#{num}"))
    end
    
    def line(x1, y1, z1, x2, y2, z2)
      draw(GL_LINES) do
        vtx3(x1, y1, z1)
        vtx3(x2, y2, z2)
      end
    end
    
    def polarView(distance, twist, elevation, azimuth)    #極座標
      #distance: 軌道半径(カメラと原点の距離)
      #twist: 照準線を中心とした回転角
      #elevation: yz平面でのカメラの回転角を、正のz軸から測ったもの(y軸を上として物体を見下ろす角度)
      #azimuth: y軸周りの回転角
      glTranslated(0, 0, -distance)
      glRotated(-twist, 0, 0, 1)
      glRotated(-elevation, 1, 0, 0)
      glRotated(-azimuth, 0, 1, 0)
    end
    
    def xyzAxis(colorx: [1, 0, 0], colory: [0, 1, 0], colorz: [0, 0, 1], length: 10, color: false)
      color(color[0], color[1], color[2]) if color    #(指定された)単一色
      draw(GL_LINES) do
        color(colorx[0], colorx[1], colorx[2]) unless color
        vtx3(0, 0, 0); vtx3(length, 0, 0)
        color(colory[0], colory[1], colory[2]) unless color
        vtx3(0, 0, 0); vtx3(0, length, 0)
        color(colorz[0], colorz[1], colorz[2]) unless color
        vtx3(0, 0, 0); vtx3(0, 0, length)
      end
    end
  end
  
  def self.app(title: "OpenGL", left: 200, top: 50, width: 300, height: 300, rgbm: GLUT_RGBA,  buffering: 0, depth: 0, culling: false)
    i = Tool.new
    i.buffering = buffering    #0ならばダブルバッファリングしない、GLUT_DOUBLEならする
    i.depth = depth            #0ならば隠面消去しない、GLUT_DEPTHならする
    i.depth_buffer_bit = GL_DEPTH_BUFFER_BIT unless depth.zero?
    glutInit
    glutInitDisplayMode(rgbm | i.buffering | i.depth)
    glutInitWindowPosition(left, top)
    glutInitWindowSize(width, height)
    glutCreateWindow(title)
    if culling    #trueならばカリングをする
      glEnable(GL_CULL_FACE)
      glCullFace(GL_BACK)
    end
    
    yield(i)
    glClearColor(i.clear_color[0], i.clear_color[1], i.clear_color[2], i.clear_color_alpha)
    glutMainLoop
  end
end

#ver 0.4.1

画面の表示

ウィンドウの表示。たったこれだけ。

require './opal'

Opal.app do |g|
  g.display = proc {glFlush}
end

白地に黄色い正方形。以下、require './opal' は書きません。

Opal.app do |g|
  display = proc do
    g.color(1, 1, 0)    #黄色
    g.draw(GL_POLYGON) do
      glVertex2d(-0.8, -0.8)
      glVertex2d(0.8, -0.8)
      glVertex2d(0.8, 0.8)
      glVertex2d(-0.8, 0.8)
    end
    glFlush
  end
  
  g.clear_color = [1, 1, 1]    #白
  g.display = display
end

同じのをあるいはこう。

Opal.app do |g|
  g.clear_color = [1, 1, 1]
  g.display = proc do
    g.color(1, 1, 0)
    glRectf(-0.8, 0.8, 0.8, -0.8)    #長方形
    glFlush
  end
end

破線の三角形。

Opal.app title: "Triangle", width: 500, height: 500 do |g|
  g.display = proc do
    glEnable(GL_LINE_STIPPLE)  #破線を可能にする
    glLineStipple(1, 0xF0F0)   #破線のパターンの指定(0xF0F0の部分がそれ)
    glLineWidth(3)             #線の太さ
    g.draw(GL_LINE_LOOP) do
      g.color(0, 1, 0)         #緑
      glVertex2f(0 , -0.9)
      glVertex2f(-0.9 , 0.9)
      glVertex2f(0.9 , 0.9)
    end
    glFlush
  end
end

頂点配列

頂点を配列を使って一気に指定します。

Opal.app title: "Polygon", width: 500, height: 500 do |g|
  g.display = proc do
    vertex = [-0.9, 0.9, 0.9, 0.9, 0, -0.9]
    g.color(0, 1, 0)
    glLineWidth(3)
    g.draw_vertex(GL_LINE_LOOP, 2, vertex)
    glFlush
  end
end

ティーポット

Opal.app title: "Teapot", width: 400, height: 400 do |g|
  g.display = proc do
    g.color(0, 1, 0)
    glutWireTeapot(1)
    glFlush
  end
  g.reshape = proc {|w, h| gluPerspective(30, w.to_f / h, 1, 100)}
  gluLookAt(3, 4, 5, 0, 0, 0, 0, 1, 0)
end

ティーポットが回転します。先の例とは少し変えてあるので、おもしろいですよ。

Opal.app title: "Teapot", width: 400, height: 400, buffering: GLUT_DOUBLE do |g|
  timer = proc do
    g.rotate(1)
    g.redraw
    g.timer(10, timer)
  end
  g.rotate = [0, 1, 1]
  
  g.display = proc do
    g.color(1, 0, 1)
    glutWireTeapot(0.5)
  end
  g.timer(10, timer)
end

マウス操作

マウスに四角がついていきます。

Opal.app title: "Mouse Move", width: 640, height: 480, buffering: GLUT_DOUBLE do |g|
  mouseX = mouseY = 0
  
  g.display = proc do
    g.color(1, 1, 0)
    glPushMatrix
    glTranslatef(mouseX, mouseY, 0)
    glRectf(0, 0, 20, 20)
    glPopMatrix
  end
  g.reshape = proc {|w, h| glOrtho(0, w, h, 0, -1, 1)}
  g.passive_motion = proc do |x, y|
    mouseX = x
    mouseY = y
    g.redraw
  end
end

3次元表示

ぐるぐる回転させてみる。だいぶコードが短くなったと思います。

gl = nil
vertex = [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0],
     [1, 0, 1], [0, 0, 1], [0, 1, 1], [1, 1, 1]]
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]].flatten
vertex = vertex.map {|v| [v[0] - 0.5, v[1] - 0.5, v[2] - 0.5]}.flatten

timer = proc do
  gl.rotate(10)
  gl.redraw
  gl.timer(100, timer)
end

init = proc do
  glEnable(GL_BLEND)
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  gl.clr_matrix
  gluLookAt(3, 4, 5, 0, 0, 0, 0, 1, 0)
end


Opal.app title: "3d" do |g|
  gl = g
  init.call
  g.rotate = [0, 1, 0]
  
  g.display = proc do
    g.color(0, 1, 0)
    g.draw_elements(GL_LINES, 3, vertex, edge)
    glFlush
  end
  g.reshape = proc {|w, h| gluPerspective(30, w / h.to_f, 3, 10)}
  g.timer(100, timer)
end

正四面体の面に色をつけて回転。隠面消去とダブルバッファリングとカリングを行っています。

gl = nil
A = Math.sqrt(3) / 2
vertex = [[0, A / 2, 0], [-0.5, -A / 2, 0], [0.5, -A / 2, 0], [0, 0, A]]
face = [[0, 1, 2], [0, 3, 2], [0, 1, 3], [1, 2, 3]]
color = [[1, 1, 1], [1, 0, 0], [0, 1, 0], [0, 0, 1]]

display = proc do
  gl.draw(GL_TRIANGLES) do
    face.each_with_index do |ar, j|
      gl.color(color[j][0], color[j][1], color[j][2])
      ar.each {|i| glVertex3dv(vertex[i])}
    end
  end
end

timer = proc do
  gl.rotate(15)
  gl.redraw
  gl.timer(100, timer)
end

init = proc do
  glEnable(GL_DEPTH_TEST)    #深度テストは有効にする(隠面消去を有効にする)
  gl.clr_matrix
  gluLookAt(4, 0, 1, 0, 0, A / 2, 0, 0, 1)
end


Opal.app title: "3d", buffering: GLUT_DOUBLE, depth: GLUT_DEPTH, culling: true do |g|
  gl = g
  init.call
  g.rotate = [0, 0, 1]
  
  g.display = display
  g.reshape = proc {|w, h| gluPerspective(30, w / h.to_f, 3, 10)}
  g.timer(100, timer)
end

init = proc do .. の部分だけ下のように変えると、(色の)混合処理ができます。つまり、後ろが透けて見えます。

init = proc do
  glEnable(GL_BLEND)      #混合処理を可能にします
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  gl.color_alpha = 0.8    #アルファ値(透け具合)
  glEnable(GL_DEPTH_TEST)
  gl.clr_matrix
  gluLookAt(4, 0, 1, 0, 0, A / 2, 0, 0, 1)
end

全般的に下を参考にしました。
http://www.wakayama-u.ac.jp/~wuhy/GSS/index.html

空間内の移動

キー入力によって空間内を移動します。常に原点方向を向きます。f で前進、b で後退、l で(原点を中心に)左回転、r で右回転です。

gl = nil

MM = 3; MS = 0.25
r = 3; theta = 0; step = 0.05
x = z = 0
vertex = [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0],
     [1, 0, 1], [0, 0, 1], [0, 1, 1], [1, 1, 1]]
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]].flatten
vertex = vertex.map {|v| [v[0] - 0.5, v[1] - 0.5, v[2] - 0.5]}.flatten

display = proc do
  gl.color(1, 0, 0)
  (-MM).step(MM, MS) do |i|
    gl.line(i, -0.5, MM, i, -0.5, -MM)
    gl.line(MM, -0.5, i, -MM, -0.5, i)
  end
  gl.color(0, 1, 0)
  gl.draw_elements(GL_LINES, 3, vertex, edge)
end

pers = proc do
  gluLookAt(r * cos(theta), 0, r * sin(theta), 0, 0, 0, 0, 1, 0)
end

keyboard = proc do |key|
  case key
  when "f"
    r -= step
    r += step if r < 0.5
  when "b"
    r += step
  when "l"
    theta += step
  when "r"
    theta -= step
  when " "
    exit
  end
  gl.clr_matrix
  pers.call
  gl.redraw
end


Opal.app width: 500, height: 500, buffering: GLUT_DOUBLE do |g|
  gl = g
  
  g.perspective
  g.clr_matrix
  pers.call
   
  g.display = display
  g.keyboard = keyboard
end



inserted by FC2 system