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ファイルの作成について、ここが大変に参考になりました(感謝!)。
自分が作ったのは 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
ここを参考にしています。
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