課題
今回のプログラムでは,2つの物体を描画するようにします(今回提供するテンプレートにて実現済み). そのうち一方を「テスト物体」と呼んで, もう一方を「リファレンス物体」と呼ぶことにします. 上の例では,中央の大きいティーポットがテスト物体で, 右下の小さいティーポットがリファレンス物体です.
今回の課題では「テスト物体」と「リファレンス物体」について, テクスチャマッピングを次のように利用することにします.
- テスト物体: テクスチャの貼り方を変更可能とする
- リファレンス物体: テクスチャの貼り方は固定する
このように,テスト物体についてテクスチャの貼り方を制御可能にします. そのためにテクスチャ座標の自動生成機能を利用します. 一方でリファレンス物体についてはテクスチャの貼り方は固定しておくようにします. リファレンス物体へのテクスチャの貼り方は,固定であれば,任意で構いません.
このようにすることで,テクスチャマッピングの状態を比較できるようにします. 上の例でも,2つの物体について,同じ画像を使いつつ,テクスチャの貼り方は異なっていることが見て取れるでしょう.
事前準備
課題に取り組む前に,「仮想型端末(VDI)」で次の手続きを行ってください. 今回の課題のためにシステムの設定を一部更新します.
- PandAから次のファイルを「Home」にダウンロードしてください.
リソース > 01_設定ファイル > update_20220622.sh
-
端末(Terminal)で次のコマンドを実行してください.
$ sh update_20220622.sh ⏎
なお「$」は端末画面に最初から出ている「プロンプト」を表しています.これは入力しません.
このコマンドを実行すると,次のように表示されます.
+ cd /home/xxxxx/lib/ruby/cg (中略) Saving to: ‘gl_objects.rb’ gl_objects.rb 100%[===================>] 6.65K --.-KB/s in 0s 2022-06-20 13:06:25 (105 MB/s) - ‘gl_objects.rb’ saved [6807/6807] + /bin/rm -f update_20220622.sh
コマンドの実行後,ダウンロードしたファイル(update_20220622.sh)は削除されます.
なお,この「事前準備」は一度だけ行います. 2回以上行う必要はありません(2回以上行っても問題は起きません).
テンプレート
まずテンプレートプログラムをダウンロードしてください.
このテンプレートでは次のような操作ができるようになっています.
- [j]/[J]を押すことで,カメラ位置の経度が変わります.
- [k]/[K]を押すことで,カメラ位置の緯度が変わります.
- [w]/[W]を押すことで,カメラの原点からの距離が変わります.
- [r]を押すとカメラが最初の位置に戻ります.
- [o]/[O]を押すことで描画されるテスト物体,リファレンス物体が同時に変わります
- [m]/[M]を押すことでテスト物体,リファレンス物体のマテリアルが同時に変わります.
- [s]/[S]を押すことでリファレンス物体が拡大・縮小されます.
- [x]/[X],[y]/[Y],[z]/[Z]を押すことでリファレンス物体が移動します.
- [v]を押すことでリファレンス物体の描画が有効化/無効化されます.
- [q],[ESC]を押すとプログラムが終了します.
プログラムの実行方法
今回のプログラムは次のようにテクスチャ画像を指定して実行します.
具体的にはたとえば「4arrows.png」をテクスチャ画像に使うとして,次のように指定します.
テクスチャの貼り方の調整方法
今回の課題では,テンプレートに基づいて,テスト物体のテクスチャ座標の自動生成の式をプログラムで変更できるようにすることでテクスチャの貼り方を変えられるようにして下さい. 座標自動生成の式のパラメタは画面に表示するようにして下さい.
座標自動生成の式は次のメソッドで指定します.
### テクスチャ座標(s,t)の自動生成
# s座標の式の係数:(a_s, b_s, c_s, d_s)
# t座標の式の係数:(a_t, b_t, c_t, d_t)
# 世界座標系の点:(x,y,z)
# (x,y,z)に対応するs座標 ==> (通常) s = a_s・x + b_s・y + c_s・z + d
# (x,y,z)に対応するt座標 ==> (通常) t = a_t・x + b_t・y + c_t・z + d
#
# (例) s = x + y
# t = y + 2z + 0.5
GL.TexGen(GL::S,GL::OBJECT_PLANE,[1,1,0,0])
GL.TexGen(GL::T,GL::OBJECT_PLANE,[0,1,2,0.5])
テンプレートでは次のように設定しています(関係部分のみ抜粋).
display = Proc.new { : ### テクスチャ座標(s,t)の自動生成 # 式の係数:(a,b,c,d) (__s_plane,__t_planeは4要素の配列) # 点:(x,y,z)に対応するs座標 ==> (通常) s = ax + by + cz + d # (tについても同様) p [:s_plane,__s_plane] # [TEST] __s_planeを端末画面に表示(削除可能) p [:t_plane,__t_plane] # [TEST] __t_planeを端末画面に表示(削除可能) GL.TexGen(GL::S,GL::OBJECT_PLANE,__s_plane) GL.TexGen(GL::T,GL::OBJECT_PLANE,__t_plane) ### テスト物体の描画(現在選択されている物体の描画) __gl_objs.draw : }
このようにテスト物体に対して「__s_plane」と「__t_plane」によってテクスチャ座標の設定が定められています. __s_planeと__t_planeはいずれも4要素の配列です(4つの要素で係数a,b,c,dを指定しています). そのデータを確認するためのテスト用に,テンプレートを実行すると,端末画面に__s_planeと__t_planeのデータが表示されるようになっています(プログラムが完成したら,削除してください).
テンプレートでは,これらのデータは変更できるようにはなっていません. そこで,課題のプログラムではテクスチャ座標自動生成の式(__s_planeと__t_plane)をコールバックを使って適宜変更できるようにしてください.
すでに説明したとおり,リファレンス物体については,テクスチャ座標の指定は固定とします. 固定であれば,その設定は任意で構いません.
利用可能なテクスチャ画像
利用可能なテクスチャ画像のサンプルをPandAの「リソース」から取得できるようにしています.
修飾キー(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: 表示位置(世界座標)
# (※ ここで指定する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]
# x,y,zのそれぞれを正負の符号付きで小数点以下2桁まで取り込んで文字列str5を作る
# str5 ==> "v=(+1.23,+0.46,-2.00)"
str5 = "v=(%+.2f,%+.2f,%+.2f)" % [x,y,z]
いずれの場合も文字列"..."の中の「%」で始まる部分で指定された形式で,数値が当てはめられます.このとき小数点以下は(必要に応じて)四捨五入されます. 「%」で始まる部分以外は文字がそのまま入ります.
上の例に示している通り,当てはめる値が一つの場合は,(文字列につづく「%」の後に)そのままデータを指定します. 2つ以上の値を取り込む場合,データは配列で指定します. このとき配列の要素数とあてはめる値の個数が一致している必要があります. 次の例も参考にしてください.
ary = [1.2, 3, -4] # str6 ==> "(+1.200, +3.000, -4.000)" str6 = "(%+.3f, %+.3f, %+.3f)" % ary # ary自体が配列であることに注意
[注意] 文字表示のための追加設定
前回のシェーディングの課題でも説明したように, 表示する文字にもモデル・ビュー変換と投影変換が適用されます. Zバッファによる処理も行われます. シェーディング処理を行っている場合には,その影響も受けます. またテクスチャマッピングも影響を及ぼします. これらの効果を考慮しないと思わぬ結果になりえます.
そこで文字の表示処理の前後で次のような処理を行って下さい. こうすることで文字は2次元平面上に配置するものと考えることができます. また文字色はGL.Colorで指定できます.
GL.Disable(GL::DEPTH_TEST) # ZバッファをOFFにする
GL.Disable(GL::LIGHTING) # シェーディングをOFFにする(GL.Colorで文字の色を決めるようにする)
GL.Disable(GL::TEXTURE_2D) # テクスチャマッピングをOFFにする
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::TEXTURE_2D) # テクスチャマッピングをONにする
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)
オプション --- プログラムの拡張
余裕があれば,テクスチャ座標の設定以外にもテクスチャの設定も変更できるようにしてみて下さい. また前回のマテリアルの変更も合わせてできるようにすると,マテリアルとテクスチャがどのように処理されているかを見ることができるでしょう(これらの実現は必須ではありません).
テクスチャを切り替える方法については,立方体の各面に異なるテクスチャを貼るプログラムが参考になるでしょう.
図形オブジェクト,マテリアルの追加定義
テンプレートプログラム(textured_objects_template.rb)では, 新しい図形オブジェクト,あるいは新しいマテリアルを追加で定義できるようになっています. 課題には直接必要ありませんが,興味があれば試してみて下さい.
図形オブジェクトの追加
図形オブジェクトの追加は次のように行います.
図形オブジェクト名 = Proc.new {
### 図形の定義
# 図形を描画するコードをここに記述する
# Teapotの場合なら次のように書く.
GLUT.SolidTeapot(1.5)
}
__gl_objs.register(図形オブジェクト名)
マテリアルの追加
マテリアルの追加は次のように行います.
#
# [Ra,Ga,Ba] Ambient
# [Rd,Gd,Bd] Diffuse
# [Rs,Gs,Bs] Specular
# S Shininess/128.0 (つまり,S∈[0,1])
#
__gl_objs.register(GLMaterial.new([Ra,Ga,Ba],[Rd,Gd,Bd],[Rs,Gs,Bs],S))