課題
物体のマテリアルのパラメタをそれぞれキー操作で増減させて,表示させるティーポットに変更結果を適用するようにしてください. パラメタは次の表に示す通り10個あります. 具体的には,拡散反射(diffuse),鏡面反射(specular),環境光反射(ambient)がそれぞれR,G,Bで3成分ずつ,それに鏡面度(shininess)を加えて全部で10個です. 鏡面度は0-128の値で指定します.その他の成分は0-1で指定します.
拡散反射(diffuse):R,G,B | 0.0--1.0 |
鏡面反射(specular):R,G,B | 0.0--1.0 |
環境光反射(ambient):R,G,B | 0.0--1.0 |
鏡面度(shininess) | 0.0--128.0 |
- 適当なキー操作でパラメタの値を増減させるようにしてください。 10個のパラメタの操作方法は自由に指定してください.
- 増減のステップ幅は自由です.鏡面度以外は,0.1ずつ変えるか,0.01ずつ変えるぐらいが適当でしょう. 鏡面度のステップ幅は他と同じでなくても構いません.
- パラメタを増減させた後に値の下限と上限を越えないようにしてください.
まず下限については,0から減らそうとして操作しても0未満にならないように制限をかけるようにしてください.
これは値を変更して値が0未満になったときにはすぐに値を強制的に0に戻すような処理で実現できます.
STEP=0.1 x -= STEP # 「x = x - STEP」と同等 x = 0.0 if x < 0.0 # if修飾子(以下の文法事項を参照のこと)
上限についても同様に扱えます.
サンプルプログラム(テンプレート)
つぎにティーポットを描くサンプルプログラムを示します. このプログラムをテンプレートとして利用することで,課題のプログラムを作り上げることができるでしょう.
teapot.rb
このプログラムは次のように操作します.
- [j],[J]を押すことで,カメラ位置の経度が変わります.
- [k],[K]を押すことで,カメラ位置の緯度が変わります.
- [z],[Z]を押すことで,カメラの原点からの距離が変わります.
- [u],[U],[i],[I]を押すことで,光源の位置が変わります.
- [r]を押すとカメラが最初の位置に戻ります.
- [q],[ESC]を押すとプログラムが終了します.
文法事項
今回の課題で以下の式を新たに導入しています.
- case式
if式のバリエーションです.case key when 'j','J' : : when 'k','K' : : end
これは次のように書くのと同様です. case式の方がすっきりして,手間が少なくなります.if key == 'j' or key == 'J' : : elsif key == 'k' or key == 'K' : : end
- 条件演算子式
条件式に応じて評価される式を簡便に記述できます.# 条件式 ? 式1 : 式2 (key == 'j') ? DT : -DT
この式の値は「条件式」が正しいときには「式1」の値,正しくないときには「式2」の値になります. たとえば次のように書いた場合,「key=='j'」つまり[j]が押されたときにはdt=DT, そうでないときにはdt=-DTとなります.# key==[j]の場合: dt=DT,そうでない場合,dt=-DT dt = (key == 'j') ? DT : -DT
- if修飾子
「式 if 条件式」と記述すると,「条件式」の値が「真」のときのみ, 「式」が評価されます.このようにして条件分岐を行う仕組みを「if修飾子」といいます.# if修飾子 式 if 条件式
同等の処理は次のように記述できますが,if修飾子の方が記述がシンプルになります.if 条件式 式 end
if修飾子自体も「式 if 条件式」という構造の式です. 条件式の値が真のとき,if修飾子の値は「式」の値です. 条件式の値が偽のとき,if修飾子の値はnilです.
修飾キー(Shift,Ctrl,Alt)の活用
キーボード入力コールバックにおいて,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]
}
文字の表示
画面に文字(英数字,記号)を表示するには,次のようにdrawStringメソッドを利用します. 残念ながら日本語を表示することはできません. 以下の「注意:文字表示のための追加設定」も参照して下さい.
# 次のライブラリを利用する
require 'cg/bitmapfont'
:
display = Proc.new {
# x,y: 表示位置(世界座標; z = 0)
# (※ ここで指定する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(0.0,0.0,"message",GLUT::BITMAP_9_BY_15)
drawStringCont(" another message",GLUT::BITMAP_9_BY_15)
# __theta,__phiという(実数値)変数が定義されているとする.
# それぞれを全体6桁で小数点以下2桁で取り込んだ文字列を作る.
str = "(theta,phi)=(%6.2f %6.2f)" % [__theta,__phi]
# 生成された文字列strを表示する
drawString(-0.9,0.9,str,GLUT::BITMAP_TIMES_ROMAN_24)
}
数値を取り込んだ文字列を作る方法
上の例でも示している通り,(表示桁数を指定して)数値を取り込んだ文字列を作ることができます.
# サンプルデータ
x=1.234
y=0.456
z=2
# xの小数点以下を2桁までを文字列strを生成する
# str ==> "1.23"
str = "%.2f" % x
# xを(小数点をふくめて)全体で7桁,小数点以下3桁までで文字列str2を生成する
# str2 ==> " 1.234"
str2 = "%7.3f" % x
# xの小数点以下を2桁,yの小数点以下を3桁までを取り込んで文字列str3を作る
# str3 ==> "Data: [X=1.23] [Y=0.456]"
str3 = "Data: [X=%.2f] [Y=%.3f]" % [x,y]
# x,y,zのそれぞれを小数点以下2桁まで取り込んで文字列str4を作る
# str4 ==> "v=(1.23,0.46,2.00)"
str4 = "v=(%.2f,%.2f,%.2f)" % [x,y,z]
いずれの場合も文字列"..."の中の「%」で始まる部分で指定された形式で,数値が当てはめられます.このとき小数点以下は(必要に応じて)四捨五入されます. 「%」で始まる部分以外は文字がそのまま入ります.
上の例に示している通り,当てはめる値が一つの場合は,(文字列につづく「%」の後に)そのままデータを指定します. 複数の値を取り込む場合,データは配列で指定します.
[注意] 文字表示のための追加設定
文字の表示においてもモデル・ビュー変換と投影変換が適用されます. Zバッファによる処理も行われます. またシェーディング処理を行っている場合には,その影響も受けます. これらの効果を考慮しないと思わぬ結果になりえます.
そこでティーポットと同じ画面上に文字を表示するときには, 文字の表示処理の前後で次のような処理を行って下さい. こうすることで文字は2次元平面上に配置するものと考えることができます. また文字色はGL.Colorで指定できます.
GL.Disable(GL::DEPTH_TEST) # ZバッファをOFFにする
GL.Disable(GL::LIGHTING) # シェーディングをOFFにする(GL.Colorで文字の色を決めるようにする)
GL.PushMatrix() # 現在のモデル・ビュー変換行列の退避
GL.LoadIdentity() # モデル・ビュー行列の初期化
GL.MatrixMode(GL::PROJECTION) # 投影変換行列モードに設定
GL.PushMatrix() # 現在の投影変換行列の退避
GL.LoadIdentity() # 投影変換行列の初期化
# ここで文字の表示を行う
# -1 <= x <= 1,-1 <= y <= 1の範囲で二次元平面上に描くと考えればよい
# 色はGL.Colorで指定する
GL.PopMatrix() # 退避しておいた投影変換行列に戻す
GL.MatrixMode(GL::MODELVIEW) # モデル・ビュー行列モードに戻す
GL.PopMatrix() # 退避しておいたモデル・ビュー変換行列に戻す
GL.Enable(GL::LIGHTING) # シェーディングをONにする
GL.Enable(GL::DEPTH_TEST) # ZバッファをONにする
ここで使われているGL.PushMatrix,GL.PopMatrixについては「形状モデリング」で詳しく取り上げます.
[参考] 文字の表示位置の制御
drawString,drawStringContメソッドは,この実習用に用意したもので,OpenGLで標準に使えるものではありません(Ruby/OpenGLにも含まれていません).
なおdrawStringにつづいてdrawStringContを使うときに, drawStringContの直前でGL.Colorで色を変えても反映されないという不具合が分かっています. そこで文字列の途中で色を変える場合にはdrawStringContを使う代わりに(スマートではありませんが)drawStringで表示する位置を調整して下さい.
上のように二次元平面上に文字を描くように設定をしたとき, 描画される文字列の世界座標系(2次元座標系)でのサイズは,次のrasterSizeメソッドで知ることができるようになっています.
# rasterSize(obj,font,range,wsize,dir) # obj: 描画する文字列あるいは文字数 # font: 描画に用いるフォント # range: ウインドウの端から端までの世界座標系での長さ # wsize: ウインドウの大きさ # dir: BITMAP_DIM_WIDTH または BITMAP_DIM_HEIGHT WSIZE=800 # 以下の例で使っているウインドウサイズ # 描画する文字列の世界座標系での横幅を求める(BITMAP_DIM_WIDTH) # "hello!"という文字列を「GLUT::BITMAP_9_BY_15」で描画する # ウインドウの左端から右端までの世界座標系での長さは2.0だとする(-1≦x≦1) # ウインドウの大きさ(横幅)はWSIZE(=800)とする # 得られた値をswに代入する sw = rasterSize("hello!",GLUT::BITMAP_9_BY_15,2.0,WSIZE,BITMAP_DIM_WIDTH) # 描画する文字列の世界座標系での横幅を求める(BITMAP_DIM_WIDTH) # 「GLUT::BITMAP_HELVETICA_18」で12文字分描画する場合を考える # ウインドウの左端から右端までの世界座標系での長さは2.0だとする(-1≦x≦1) # ウインドウの大きさ(横幅)はWSIZE(=800)とする # 得られた値をswに代入する sw = rasterSize(12,GLUT::BITMAP_HELVETICA_18,2.0,WSIZE,BITMAP_DIM_WIDTH) # 描画する文字列の世界座標系での高さを求める(BITMAP_DIM_HEIGHT) # 「GLUT::BITMAP_HELVETICA_18」での縦方向で3文字分の高さを求める # ウインドウの上端から下端までの世界座標系での長さは2.0だとする(-1≦y≦1) # ウインドウの大きさ(高さ)はWSIZE(=800)とする # 得られた値をshに代入する sh = rasterSize(3,GLUT::BITMAP_HELVETICA_18,2.0,WSIZE,BITMAP_DIM_HEIGHT)