TOP   Ruby

OpenGL

RubyでOpenGLを使ってみます(公式HP)。使い方や紹介しているサイトは日本語でもたくさんあります。 検索してみるとよいでしょう。 インストールについてはこちら
環境は Linux Mint 17.2, Ruby 2.2.3、Gem は OpenGL 0.9.2 です。(2016/4/22)

OpenGLに関してはここがわかりやすいです。以下でも参考にしています。
GLUTに関しては、英文解説書の日本語訳が便利そうです。

なお、Gem の管理に Bundler を使っていない方は、以下のサンプルコードの「require 'bundler/setup'」をすべて消して下さい。


OpenGLを多少楽に使うラッパーモジュールのライブラリ「オパール Opal」を書きました。こちらもどうぞ。(2016/5/10)

「Opal」というのは既に有名なライブラリがあって名前がよくないですし、使い勝手も気に入らなくなってきたので、とりあえず「miniopengl.rb」というのを書き直しました。まだ開発中ですが、コードはここにあります。(2017/9/8)

目次

ちょっと気づいたこと

 検索して色いろなサイトのサンプルコードを動かしましたが、基本的にすべて動きます。 ただ、以前は Gem 'OpenGL' に 'Glut', 'Glu' ライブラリがバインディングされていたようですが、今はちがうようです('Gl' はバインディングされています)。 なので、その二つは Gem としてインストールする必要があります。また、コードで使われている場合はもちろん require せねばなりません。
 それから、これはよくわからないのですが、サンプルコードでエラーが出る場合は、「include Gl, Glu, Glut」を加えてやるとすべて動きました。これは書かなくても動くものもあるので、自分にはよくわかりません。 (※追記 よく考えたら、GL.Color3f() のようにドットを使った古い記法の場合は、includeは必要ないですね。glColor3f() のように新しい記法の場合は、includeが必要です。 まあ、どちらの書き方でも動きますが。)
 以上、御参考までに。(2016/4/23)

画面の表示

まずはウィンドウを表示させてみます。とりあえずここを参考にしています。

require 'bundler/setup'
require 'opengl'
require 'glut'
include Gl, Glut

def init
  glClearColor(1, 1, 1, 1)
end

display = proc do
  glClear(GL_COLOR_BUFFER_BIT)  #画面クリア
  glFlush                       #描画
end

glutInit
glutInitWindowSize(500, 500)
glutInitWindowPosition(200, 100)
glutCreateWindow
glutDisplayFunc(display)
init
glutMainLoop

黄色い正方形を描いてみます。display関数のところだけ書き換えます。

display = proc do
  glClear(GL_COLOR_BUFFER_BIT)
  glColor3d(1, 1, 0)       #黄色
  glBegin(GL_POLYGON)      #塗りつぶす
  glVertex2d(-0.8, -0.8)   #画面の範囲は縦横とも[-1, 1]
  glVertex2d(0.8, -0.8)
  glVertex2d(0.8, 0.8)
  glVertex2d(-0.8, 0.8)
  glEnd
  glFlush
end

glBegin(GL_POLYGON) のところを glBegin(GL_LINE_LOOP) に替えれば、枠だけの正方形を描きます。

上と同じことは次のように書けます。長方形を描く関数を使います。これも display関数のところだけ描き直します。

display = proc do
  glClear(GL_COLOR_BUFFER_BIT)
  glColor3f(1, 1, 0)
  glRectf(-0.8, 0.8, 0.8, -0.8)    #長方形
  glFlush
end

破線の三角形を描いてみます。ここを参考にしました。

display = proc do
  glClear(GL_COLOR_BUFFER_BIT)
  glEnable(GL_LINE_STIPPLE)  #破線を可能にする
  glLineStipple(1, 0xF0F0)   #破線のパターンの指定(0xF0F0の部分がそれ)
  glLineWidth(3)             #線の太さ
  glBegin(GL_LINE_LOOP)
    glColor3f(0, 1, 0)
    glVertex2f(0 , -0.9)
    glVertex2f(-0.9 , 0.9)
    glVertex2f(0.9 , 0.9)
  glEnd
  glFlush
end

頂点配列

頂点を配列を使って一気に指定します。これも display関数の部分だけ書きます。

display = proc do
  vertex = [-0.9, 0.9, 0.9, 0.9, 0, -0.9]
  glClear(GL_COLOR_BUFFER_BIT)
  glEnableClientState(GL_VERTEX_ARRAY)     #配列を使うことを指定
  glVertexPointer(2, GL_FLOAT, 0, vertex)  #使う配列の名前は vertex
  glLineWidth(3)
  glBegin(GL_LINE_LOOP)
    glColor3f(0, 1, 0)
    3.times {|i| glArrayElement(i)}        #ひとつずつ頂点を指定していく
  glEnd
  glFlush
end

上の 7〜10 行を下のように書き直しても同じ結果です。直接に配列を指定したことになります。

  glColor3f(0, 1, 0)
  glDrawArrays(GL_LINE_LOOP, 0, 3)

ティーポット

このサイトからコピペです(感謝!)。よくみるティーポットの描画ですけれど、ライブラリにデータが入っているのですね。自分で打ち込むのではないのだ。そりゃそうか。

require 'bundler/setup'
require 'opengl'
require 'glut'
require 'glu'
 
display = proc do
  GL.Clear(GL::COLOR_BUFFER_BIT)
  GL.Color3f(0, 1, 0)
  GLUT.WireTeapot(1.0)
  GL.Flush
end
 
def reshape(w,h)
  GL.Viewport(0,0,w,h)
  GL.MatrixMode(GL::GL_PROJECTION)
  GL.LoadIdentity
  GLU.Perspective(30.0, w.to_f/h, 1.0, 100.0)
  GL.MatrixMode(GL::GL_MODELVIEW)
  GL.LoadIdentity
  GLU.LookAt(3.0, 4.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
end
 
GLUT.Init
GLUT.InitWindowSize(480,480)
GLUT.CreateWindow("Teapot")
GLUT.DisplayFunc(display)
GLUT.ReshapeFunc(method(:reshape).to_proc)
GL.ClearColor(0,0,0,1)
GLUT.MainLoop

ティーポットが回転します。今度はここからコピペです(感謝!)。

require "bundler/setup"
require "opengl"
require "glut"

display = proc do
  GL.Clear(GL::COLOR_BUFFER_BIT)
  GL.Color3f(0.0, 0.0, 1.0)
  GLUT.WireTeapot(0.5)
  GLUT.SwapBuffers
end

timer = proc do
  GL.Rotate(1.0, 0.0, 1.0, 0.0)
  GLUT.PostRedisplay
  GLUT.TimerFunc(10, timer, 0)
end

GLUT.Init
GLUT.CreateWindow("ruby-opengl test")
GLUT.DisplayFunc(display)
GL.ClearColor(0.5,0.5,0.5,0.0)
GLUT.TimerFunc(10, timer, 0)
GLUT.MainLoop

ここのサンプルコードでは、ティーポットがマウスでぐるぐる回転します。おもしろいので是非お試しあれ。

せっかくなのでコードをコピペさせてもらいましょう(感謝!)。これは勉強になりそうです。

#-----------------------------------------
# GLUTでティーポットを描画するだけのプログラム
# @author flipper
# @date   2009.03.12
#-----------------------------------------

require "bundler/setup"
require "opengl"
require "glut"
require "glu"
include Gl, Glu, Glut

class Teapot
    LIGHT_POSITION = [0.25, 1.0, 0.25, 0.0]
    LIGHT_DIFFUSE  = [1.0, 1.0, 1.0]
    LIGHT_AMBIENT  = [0.25, 0.25, 0.25]
    LIGHT_SPECULAR = [1.0, 1.0, 1.0]

    MAT_DIFFUSE   = [1.0, 0.0, 0.0]
    MAT_AMBIENT   = [0.25, 0.25, 0.25]
    MAT_SPECULAR  = [1.0, 1.0, 1.0]
    MAT_SHININESS = [32.0]
    
    def reshape(w,h)
        GL.Viewport(0,0,w,h)
    
        GL.MatrixMode(GL::GL_PROJECTION)
        GL.LoadIdentity
        GLU.Perspective(45.0, w.to_f/h.to_f, 0.1, 100.0)
    end

    def display
        GL.MatrixMode(GL::GL_MODELVIEW)
        GL.LoadIdentity
        GLU.LookAt(0.5, 1.5, 2.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
    
        GL.Lightfv(GL::GL_LIGHT0, GL::GL_POSITION, LIGHT_POSITION)
        GL.Lightfv(GL::GL_LIGHT0, GL::GL_DIFFUSE, LIGHT_DIFFUSE)
        GL.Lightfv(GL::GL_LIGHT0, GL::GL_AMBIENT, LIGHT_AMBIENT)
        GL.Lightfv(GL::GL_LIGHT0, GL::GL_SPECULAR, LIGHT_SPECULAR)

        GL.Materialfv(GL::GL_FRONT, GL::GL_DIFFUSE, MAT_DIFFUSE)
        GL.Materialfv(GL::GL_FRONT, GL::GL_AMBIENT, MAT_AMBIENT)
        GL.Materialfv(GL::GL_FRONT, GL::GL_SPECULAR, MAT_SPECULAR)
        GL.Materialfv(GL::GL_FRONT, GL::GL_SHININESS, MAT_SHININESS)
    
        GL.ClearColor(0.0, 0.0, 0.0, 1.0)
        GL.Clear(GL::GL_COLOR_BUFFER_BIT | GL::GL_DEPTH_BUFFER_BIT)

        GL.Rotate(@rotX, 1, 0, 0)
        GL.Rotate(@rotY, 0, 1, 0)
        GLUT.SolidTeapot(0.5)
    
        GLUT.SwapBuffers
    end

    def mouse(button,state,x,y)
        if button == GLUT::GLUT_LEFT_BUTTON && state == GLUT::GLUT_DOWN then
            @start_x = x
            @start_y = y
            @drag_flg = true
        elsif state == GLUT::GLUT_UP then
            @drag_flg = false
        end
    end

    def motion(x,y)
        if @drag_flg then
            dx = x - @start_x
            dy = y - @start_y
        
            @rotY += dx
            @rotY = @rotY % 360
        
            @rotX += dy
            @rotX = @rotX % 360
        end
        @start_x = x
        @start_y = y
        GLUT.PostRedisplay
    end

    def initialize
        @start_x = 0
        @start_y = 0
        @rotY = 0
        @rotX = 0
        @drag_flg = false
        GLUT.InitWindowPosition(100, 100)
        GLUT.InitWindowSize(300,300)
        GLUT.Init
        GLUT.InitDisplayMode(GLUT::GLUT_DOUBLE | GLUT::GLUT_RGB | GLUT::GLUT_DEPTH)
        GLUT.CreateWindow("Ruby de OpenGL")

        GL.Enable(GL::GL_DEPTH_TEST)
        GL.Enable(GL::GL_LIGHTING)
        GL.Enable(GL::GL_LIGHT0)
    
        GL.FrontFace(GL::GL_CW)
        GL.Enable(GL::GL_AUTO_NORMAL)
        GL.Enable(GL::GL_NORMALIZE)
        GL.Enable(GL::GL_DEPTH_TEST)
        GL.DepthFunc(GL::GL_LESS)
    
        GL.ShadeModel(GL::SMOOTH)
    
        GLUT.ReshapeFunc(method(:reshape).to_proc)
        GLUT.DisplayFunc(method(:display).to_proc)
        GLUT.MouseFunc(method(:mouse).to_proc)
        GLUT.MotionFunc(method(:motion).to_proc)
    end
    
    def start
        GLUT.MainLoop
    end
end

Teapot.new.start

マウス操作

マウスに四角がついていきます。今度はここからコピペです(感謝!)。

require "bundler/setup"
require "opengl"
require "glut"
include Glut, Gl

mouseX = 0
mouseY = 0
 
display = Proc.new do
  GL.Clear GL_COLOR_BUFFER_BIT
  # draw rect
  GL.Color3d 1.0, 1.0, 0.0
  GL.PushMatrix
  GL.Translatef mouseX, mouseY, 0
  GL.Begin GL_POLYGON
  GL.Vertex2d 0, 0
  GL.Vertex2d 0, 20
  GL.Vertex2d 20, 20
  GL.Vertex2d 20, 0
  GL.End
  GL.PopMatrix
 
  GL.Flush
  GLUT.SwapBuffers
end
 
resize = Proc.new do |w,h|
  GL.Viewport 0, 0, w, h
  GL.LoadIdentity
  GL.Ortho 0.0, w, h, 0.0, -1.0, 1.0
end
 
mouse_move = Proc.new do |x,y|
  mouseX = x
  mouseY = y
  GLUT.PostRedisplay
end
 
def init
  GL.ClearColor 0.0, 0.0, 0.0, 1.0
end
 
GLUT.Init
GLUT.InitDisplayMode GLUT_RGBA
GLUT.InitWindowSize 640, 480
GLUT.InitWindowPosition 200, 100
GLUT.CreateWindow "OpenGL:test"
GL.Clear GL_COLOR_BUFFER_BIT
GLUT.DisplayFunc display
GLUT.ReshapeFunc resize
GLUT.PassiveMotionFunc mouse_move
init
GLUT.MainLoop

ウィンドウをキャプチャしてBMPファイルに落とす

BMPファイルの作成について、ここが大変に参考になりました(感謝!)。 自分が作ったのは self.gl_capture メソッドです。ここも参照。

BitMapクラス。
opengl_getbitmap.rb

class BitMap
  def initialize(width, height, dpi = 96)
    @width = width
    @height = height
    @line_size = width * 3 + (4 - (width * 3) % 4) % 4
    @buf_size = @line_size * height
    @buf = ("\000" * @buf_size).encode('BINARY')
    @bit_count = 24
    @compression = 0  # 圧縮無し
    @size_image = 0
    @x_pix_per_meter = (39.375 * dpi).round
    @y_pix_per_meter = (39.375 * dpi).round
    @clr_used = 0
    @cir_important = 0
  end
  
  attr_accessor :buf
  attr_reader :width, :height

  # BMPファイルを出力する
  def write(filename)
    file_size = 14 + 40 + @buf_size
    data_offset = 14 + 40

    open(filename, "wb") do |f|
      f.print 'BM'
      f.print [file_size, 0, data_offset].pack("l*")
      f.print [40, @width, @height].pack("l*")
      f.print [1, @bit_count].pack("S*")
      f.print [@compression, @size_image,
               @x_pix_per_meter, @y_pix_per_meter,
               @clr_used, @cir_important].pack("l*")
      f.print @buf
    end
  end
  
  # 画面をキャプチャする
  def self.gl_capture(fname)
    x = glutGet(GLUT_WINDOW_WIDTH)
    y = glutGet(GLUT_WINDOW_HEIGHT)
    
    bitmap = BitMap.new(x, y)
    glPixelStorei(GL_PACK_ALIGNMENT, 1)
    imgdata = glReadPixels(0, 0, x, y, GL_BGR, GL_UNSIGNED_BYTE).unpack("H*")[0]    #画像の読み込み
    
    data = ""    #ここからデータの変換
    y.times do |j|
      (x * 3).times {|i| data += imgdata[6 * x * j + i * 2, 2].to_i(16).chr}
      data += "\x00" * ((4 - 3 * x % 4) % 4)
    end
    
    bitmap.buf = data
    bitmap.write(fname)
  end
end

サンプルコード。キャプチャしたいところ(glFlushの直前)で BitMap.gl_capture メソッドを呼び出します。引数は画像のファイル名です。

require 'bundler/setup'
require 'opengl'
require 'glut'
require "./opengl_getbitmap"
include Gl, Glut
 
def init
  glClearColor(1, 1, 1, 1)
end

display = proc do
  glClear(GL_COLOR_BUFFER_BIT)
  glColor3f(1, 1, 0)    #黄色
  glRectf(-0.8, 0.8, 0.8, -0.8)
  BitMap.gl_capture("sample1.bmp")
  glFlush
  
  glColor3f(0, 1, 0)    #緑色
  glRectf(-0.8, 0.8, 0.8, -0.8)
  BitMap.gl_capture("sample2.bmp")
  glFlush
end

glutInit
glutInitWindowSize(200, 200)
glutInitWindowPosition(200, 100)
glutCreateWindow
glutDisplayFunc(display)
init
glutMainLoop

3次元表示

ここを参考にしています。

require 'bundler/setup'
require 'opengl'
require 'glut'
require 'glu'
include Gl, Glu, Glut

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]] 

display = proc do
  glClear(GL_COLOR_BUFFER_BIT)

  glColor3d(0, 1, 0)
  glBegin(GL_LINES)
  edge.each do |e|
    glVertex3dv(vertex[e[0]])
    glVertex3dv(vertex[e[1]])
  end
  glEnd
  glFlush
end

resize = proc do |w, h|
  glViewport(0, 0, w, h)
  glMatrixMode(GL_PROJECTION)  #マトリックスモードを投影変換にする
  glLoadIdentity               #変換行列の初期化
  gluPerspective(30, w / h.to_f, 3, 10)   #透視投影
  glMatrixMode(GL_MODELVIEW)              #マトリックスモードをモデルビューにする
  glLoadIdentity()                        #変換行列の初期化
  gluLookAt(3, 4, 5, 0, 0, 0, 0, 1, 0)    #目の位置
end

def init
  glEnable(GL_BLEND)
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  glClearColor(0, 0, 0, 0)
end

glutInit
glutInitDisplayMode(GLUT_RGBA)
glutInitWindowPosition(200, 50)
glutInitWindowSize(300, 300)
glutCreateWindow("3d")
init
glutDisplayFunc(display)
glutReshapeFunc(resize)
glutMainLoop

ぐるぐる回転させてみる。ここを参考にしました。

require 'bundler/setup'
require 'opengl'
require 'glut'
require 'glu'
include Gl, Glu, Glut

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

display = proc do
  glClear(GL_COLOR_BUFFER_BIT)
  glColor3d(0, 1, 0)
  glBegin(GL_LINES)
  edge.each do |e|
    glVertex3dv(vertex[e[0]])
    glVertex3dv(vertex[e[1]])
  end
  glEnd
  glFlush
end

resize = proc do |w, h|
  glViewport(0, 0, w, h)
  glMatrixMode(GL_PROJECTION)
  glLoadIdentity
  gluPerspective(30, w / h.to_f, 3, 10)
  glMatrixMode(GL_MODELVIEW)
end

timer = proc do
  glRotated(10, 0, 1, 0)          #回転
  glutPostRedisplay               #再表示
  glutTimerFunc(100, timer, 0)    #100ミリ秒後にコールバック関数を呼び出す
end

def init
  glEnable(GL_BLEND)
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  glClearColor(0, 0, 0, 0)
  glLoadIdentity
  gluLookAt(3, 4, 5, 0, 0, 0, 0, 1, 0)
end

glutInit
glutInitDisplayMode(GLUT_RGBA)
glutInitWindowPosition(200, 50)
glutInitWindowSize(300, 300)
glutCreateWindow("3d")
init
glutDisplayFunc(display)
glutReshapeFunc(resize)
glutTimerFunc(100, timer, 0)
glutMainLoop

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

require 'bundler/setup'
require 'opengl'
require 'glut'
require 'glu'
include Gl, Glu, Glut

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
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)    #画面を消去するときは Z バッファも消去する
  glColor3d(0, 1, 0)
  glBegin(GL_TRIANGLES)
  face.each_with_index do |ar, j|
    glColor3dv(color[j])
    ar.each {|i| glVertex3dv(vertex[i])}
  end
  glEnd
  glutSwapBuffers    #ダブルバッファリングによる画面の入れ替え
  glFlush
end

resize = proc do |w, h|
  glViewport(0, 0, w, h)
  glMatrixMode(GL_PROJECTION)
  glLoadIdentity
  gluPerspective(30, w / h.to_f, 3, 10)
  glMatrixMode(GL_MODELVIEW)
end

timer = proc do
  glRotated(15, 0, 0, 1)
  glutPostRedisplay
  glutTimerFunc(100, timer, 0)
end

def init
  glEnable(GL_BLEND)
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  glClearColor(0, 0, 0, 0)
  glEnable(GL_DEPTH_TEST)    #深度テストは有効にする(隠面消去を有効にする)
  glEnable(GL_CULL_FACE)     #カリングを有効に
  glCullFace(GL_BACK)        #裏面を表示しない
  glLoadIdentity
  gluLookAt(4, 0, 1, 0, 0, A / 2, 0, 0, 1)
end

glutInit
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)    #隠面消去を行うために、Zバッファを用意する
glutInitWindowPosition(200, 50)
glutInitWindowSize(300, 300)
glutCreateWindow("3d")
init
glutDisplayFunc(display)
glutReshapeFunc(resize)
glutTimerFunc(100, timer, 0)
glutMainLoop

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




inserted by FC2 system