課題
もくじ
テンプレートファイル
次のテンプレートを利用してください. このテンプレートではカメラと光源を配置済みです. キーボードでカメラを動かす仕組みも定義してあります. あわせて物体のマテリアルを簡便に設定する仕組み,テクスチャマッピングを設定する仕組みも導入しています. 詳しくはテンプレートで確認してみてください.
このテンプレートプログラムでは,画像ファイルを指定して実行したとき,それをテクスチャ画像として利用するようになっています.
プログラムの説明書
今回の課題では,テンプレートの最初の「=begin」から「=end」の間を次のように変更して, 「所属」「氏名」「学生番号」と①〜⑤を記入した上で提出してください.
=begin
所属:
氏名:
学生番号:
①プログラムの概要
②プログラムの操作方法
③自己評価(100点満点)
④工夫した点,アピールしたい点など
⑤さらに改善・拡張が考えられる点など
感想など(任意)
=end
技術情報
プログラムに利用可能な技術要素などを紹介します.
サンプルプログラム(ベジェ曲面)
OpenGLでのベジェ曲面を簡易的に扱う仕組みを導入した曲面の設定と描画処理方法を示したサンプルプログラムを示します. テクスチャマッピングも適用可能にしています. 詳しくはファイルを参照してください.
利用可能な図形
形状を設計するために利用可能な図形を紹介します. またサンプルプログラムを示します.
GLUTの図形
GLUTライブラリでは,以下のような図形が提供されています. これらを組み合わせたり,あるいは適宜スケーリングすることで, さらに複雑な図形を構築することもできます.
# トーラス
# 中心=原点,z軸に垂直
# inner トーラス内部の円の直径(太さを決める)
# outer 中心の空洞部の円の半径(大きさを決める)
# sides 放射方向の分割数
# stacks 放射方向と直交する方向の分割数
GLUT.SolidTorus(inner,outer,sides,stacks)
GLUT.WireTorus(inner,outer,sides,stacks)
# 球
# 中心=原点
# radius 半径
# slices 経線方向の分割数
# stacks 緯線方向の分割数
GLUT.SolidSphere(radius,slices,stacks)
GLUT.WireSphere(radius,slices,stacks)
# 円錐
# 中心軸=z軸,底面:z=0,頂点=(0,0,height)
# base 底面の半径
# height 高さ
# slices 経線方向の分割数
# stacks 緯線方向の分割数
GLUT.SolidCone(base,height,slices,stacks)
GLUT.WireCone(base,height,slices,stacks)
# 四面体
# 原点中心
GLUT.SolidTetrahedron()
GLUT.WireTetrahedron()
# 立方体
# 原点中心
# size: 大きさ
GLUT.SolidCube(size)
GLUT.WireCube(size)
# 八面体
# 原点中心
GLUT.SolidOctahedron()
GLUT.WireOctahedron()
# 十二面体
# 原点中心
GLUT.SolidDodecahedron()
GLUT.WireDodecahedron()
# 二十面体
# 原点中心
GLUT.SolidIcosahedron()
GLUT.WireIcosahedron()
GLUの図形
GLUライブラリは,二次曲面(quadric surface)として定義されている図形を提供します.
GLU図形を利用するには,予め二次曲面オブジェクトを用意する必要があります. また二次曲面の描画スタイルなどを指定しておく必要があります.
# 二次曲面オブジェクトの生成
quadric = GLU.NewQuadric()
# 二次曲面の描画スタイル
# quadric 二次曲面オブジェクト
# style 描画スタイル(GLU::POINT, GLU::LINE, GLU::FILL, GLU::SILHOUETTE)
GLU.QuadricDrawStyle(quadric,style)
# 二次曲面の法線処理モード
# quadric 二次曲面オブジェクト
# mode 法線モード(GLU::NONE, GLU::FLAT, GLU::SMOOTH)
GLU.QuadricNormals(quadric,mode)
二次曲面オブジェクトは一つ用意すれば,それをすべてのGLU図形の描画のために何度でも使うことができます. つまり二次曲面ごとに二次曲面オブジェクトを個別に用意する必要はありません. 利用できる図形を以下に示します.
# 球面
# quadric 二次曲面オブジェクト
# radius 半径
# slices 経線方向の分割数
# stacks 緯線方向の分割数
GLU.Sphere(quadric,radius,slices,stacks)
# 円柱面(中心軸=z軸,底面はz=0,上面はz=height)
# quadric 二次曲面オブジェクト
# base 底面側の半径
# top 上面側の半径
# height 高さ
# slices 経線方向の分割数
# stacks 緯線方向の分割数
GLU.Cylinder(quadric,base,top,height,slices,stacks)
# 円盤(孔あき;z軸に垂直)
# quadric 二次曲面オブジェクト
# inner 内側の半径(これが0なら孔はなくなる)
# outer 外側の半径
# slices 放射方向の分割数
# rings 円盤を構成する同心円の数
GLU.Disk(quadric,inner,outer,slices,rings)
# 扇型(孔あき;z軸に垂直)
# quadric 二次曲面オブジェクト
# inner 内側の半径(これが0なら孔はなくなる)
# outer 外側の半径
# slices 放射方向の分割数
# rings 円盤を構成する同心円の数
# start 開始位置(度)
# angle 中心角(度)
GLU.PartialDisk(quadric,inner,outer,slices,rings,start,angle)
サンプルプログラム(GLUT,GLU図形)
次にGLUTとGLUで提供される図形を扱ったサンプルプログラムを示します.文字の表示
画面に文字(英数字,記号)を表示するには,次のようにdrawStringメソッドを利用します. 残念ながら日本語を表示することはできません.
次のライブラリを利用する
require "cg/bitmapfont"
:
display = Proc.new {
# x,y: 表示位置(世界座標系)
# str: 表示する文字列
# font: フォント(以下のいずれかを指定する)
# GLUT::BITMAP_9_BY_15
# GLUT::BITMAP_8_BY_13
# GLUT::BITMAP_TIMES_ROMAN_10
# GLUT::BITMAP_TIMES_ROMAN_24
# GLUT::BITMAP_HELVETICA_10
# GLUT::BITMAP_HELVETICA_12
# GLUT::BITMAP_HELVETICA_18
drawString(x,y,str,font)
# 直前に書いた文字列の続きに別の文字列を表示する場合は,
# 次のメソッドを利用する.
drawStringCont(str2,font)
}
なおdrawString,drawStringContメソッドは,この実習用に用意したもので,OpenGLで標準に使えるものではありません(Ruby/OpenGLにも含まれていません).
文字表示のための設定
描画するシーンとは独立に文字をウインドウに固定して表示する際には,シェーディング処理,テクスチャマッピング処理を一時的に無効化した上で,モデル・ビュー変換行列,投影変換行列を(退避してから)初期化することで,2次元平面に直接描画できるように設定を変更します. このような設定変更を簡単に行えるようにするために,今回は,次のように「MGLUtils.picture_mode」を使えるようにしています.
# MGLUtils.picture_mode { ... }
# { ... }の内部では2Dでシェーディング,テクスチャを無効化して描画
MGLUtils.picture_mode {
GL.Color(1.0,0.75,0.125)
drawString(-0.95,0.9,'Hello, World!',GLUT::BITMAP_TIMES_ROMAN_24)
}
[参考] 文字の表示位置の制御
drawStringにつづいてdrawStringContを使うときに, drawStringContの直前でGL.colorで色を変えてもそれが反映されません. そこで文字列の途中で色を変える場合にはdrawStringContを使う代わりに(スマートではありませんが)表示する位置を調整しつつdrawStringを使ってください. 以前の課題のように2次元平面上に文字を描くように設定をしたとき,描画される文字列の世界座標系でのおおよそのサイズは,次のrasterSizeメソッドで知ることができます.
## 世界座標系での文字列の(おおよその)幅/高さを得る
# obj: 対象とする文字列または文字数
# font: 利用するフォント
# range: 画面の左端から右端(上端から下端)までの距離(世界座標)
# pixels: ウインドウのサイズ(ピクセル数での幅/高さ)
# dir: BITMAP_DIM_WIDTH(幅を得る),BITMAP_DIM_HEIGHT(高さを得る)
rasterSize(obj,font,range,width,dir)
# "S plane: "という文字列のGLUT::BITMAP_9_BY_15での(おおよその)幅
# 画面の左端から右端までの距離=2.0
# ウインドウの幅=600ピクセル
# wに世界座標系での文字列の幅(x方向の長さ)が代入される
w = rasterSize("S plane: ",GLUT::BITMAP_9_BY_15,2.0,600,BITMAP_DIM_WIDTH)
# GLUT::BITMAP_TIMES_ROMAN_24の1文字の(おおよその)高さ(最初の引数=1で文字数を指定している)
# 画面の上端から下端までの距離=2.0
# ウインドウの高さ=800ピクセル
# hに世界座標系での文字列の高さ(y方向の長さ)が代入される
h = rasterSize(1,GLUT::BITMAP_TIMES_ROMAN_24,2.0,800,BITMAP_DIM_HEIGHT)
桁数の制御
数値の表示桁数を指定したい場合には次のようにするとよいでしょう.
# xの小数点以下を2桁までを文字列strにする str = "%.2f" % x # xを(小数点をふくめて)全体で6桁,小数点以下2桁までで文字列str2にする str2 = "%6.2f" % x # red,green,blueのそれぞれに正負の符号を付けて小数点以下2桁まで並べてcolor=(...)と表示する. str3 = "color=(%+.2f,%+.2f,%+.2f)" % [red,green,blue] # あとは生成した文字列(str,str2,str3)を適宜drawStringで表示する
色の指定
描画する物体の色(材質)を決めるには課題08で作成したプログラムを利用するとよいでしょう.
背景色
またGL.Colorで設定する色のサンプルを示すのに次のプログラムも利用できます.
操作方法は次の通りです.
[r],[R]: 赤成分の増減([r]:増やす,[R]:減らす) [g],[G]: 緑成分の増減([g]:増やす,[G]:減らす) [b],[B]: 青成分の増減([b]:増やす,[B]:減らす) ※ [ALT]を押しながら操作すると,より細かな単位で色を変更できます. [q],[ESC]: 終了
なおウインドウの背景色はMGLUtils.bgcolorで指定します.
# 背景色の設定(red,green,blue)
MGLUtils.bgcolor(0.4,0.4,1.0)
テクスチャ用画像
利用可能なテクスチャ画像のサンプルについてはPandAで確認してください.
特殊なキーの扱い
カーソルキー[↓][↑]やファンクションキー[F1]...[F12],ホームキー[Home]などの通常の文字以外のキー入力はキーボードコールバック(KeyboardFunc)では扱えません. それらをインタラクションに利用するには,特殊キー用のコールバックを別途作成して,それを「SpecialFunc」として登録します.
# 特殊なキー入力をあつかうコールバック special = Proc.new { |key,x,y| case key when GLUT::KEY_LEFT # [←] # [←]を押したときの処理 when GLUT::KEY_RIGHT # [→] # [→]を押したときの処理 when GLUT::KEY_UP # [↑] # [↑]を押したときの処理 when key == GLUT::KEY_DOWN # [↓] # [↓]を押したときの処理 when key == GLUT::KEY_F1 # [F1] # [F1]を押したときの処理 when key == GLUT::KEY_F2 # [F2] # [F2]を押したときの処理 end } : : GLUT.SpecialFunc(special) # 特殊キー用の入力コールバック登録 : GLUT.Mainloop()
キーボードモディファイアの取得
キーボード入力コールバックにおいて,GLUT.GetModifiersというメソッドで, [Shift],[Ctrl],[Alt]キーが押されているかどうかをチェックできます.
- 注意
仮想型端末を利用しているため,キーの組み合わせ次第では想定通り動作しなかったり,予想外の挙動が示したりする場合がありますので注意してください. たとえばWWWブラウザのショートカットキーと競合する場合が問題になります.
mod = GLUT.GetModifiers() if (mod & GLUT::ACTIVE_ALT)!=0 # # [Alt]が押されている場合の処理 # end if (mod & GLUT::ACTIVE_CTRL)!=0 # # [Ctrl]が押されている場合の処理 # end if (mod & GLUT::ACTIVE_SHIFT)!=0 # # [Shift]が押されている場合の処理 # end
なお[Shift],[Ctrl]を押しながらキーを押すとイベントとして発生するkeyが変化する場合がありますので注意してください. たとえば[Shift]+[a]の場合,keyは"A"に変わります(小文字→大文字). また[Ctrl]+[x]の場合,keyは「24番の文字」になります. これは「key.ord == 24」で判定できます(他の判定方法もあります).
when key case 'a' # [a]の場合 case 'A' # [A](=[Shift]+[a])の場合 case 'x' # [x]の場合 end when key.ord case 0x1b # [ESC]の場合 case 0x18 # 0x18 == 24 # [Ctrl]+[x]の場合 end
(一時的に)キーのデータを端末に表示させてみれば,どういうイベントが発生しているのかを知ることができます.
keyboard = Proc.new { |key,x,y|
# keyとその番号(key.ord)を端末に表示する
p [:key,key,:ord,key.ord]
}
タイマーコールバック
タイマーコールバックは指定した時間が経過したときに処理を実行する仕組みを提供します. タイマーコールバックはGLUT.TimerFuncで登録します. タイマーコールバックが1回実行されると登録は解除されるようになっています. タイマーコールバックの内部で再度タイマーコールバックを登録すれば 一定間隔で同一の処理を繰り返し実行することができます.
INTERVAL=500 # 実行間隔(msec) timer = Proc.new { |v| # メッセージを表示(vの値を表示する) puts "call timer with #{v}" # 再度タイマーをセット # timerにはv+1を渡す GLUT.TimerFunc(INTERVAL,timer,v+1) } keyboard = Proc.new { |key,x,y| case key when 't' # タイマーをセット # INTERVALミリ秒後にtimerを実行する.timerに0を渡す GLUT.TimerFunc(INTERVAL,timer,0) end }
タイマーコールバックを使ったサンプルプログラムを示します.
乱数
プログラムにランダム性を取り入れるには,次のメソッドを利用します.
rand(n) # 1,...,n-1のいずれかの整数をランダムに返す(nは正の整数とする)
rand(1..n) # 1,...,nのいずれかの整数をランダムに返す(nは正の整数とする)
rand(0) # 0以上1未満の数をランダムに返す