[CG実習 >  CG実習 課題(2021) >  自由制作(最終課題) ]

[課題13]: 自由制作(最終課題)

課題

これまでに学習した内容を活かした3次元CG作品を作成し, またプログラムの説明をプログラム内に記述した上で提出して下さい. アニメーション,インタラクションの機能を入れることを条件として, テーマは自由です.

もくじ

プログラムの説明書

プログラムの説明書は,次のような形式でプログラムの先頭に挿入して下さい.


=begin

所属:     都学部1回生  
氏名:     左京太郎       
学生番号: 0123456789   

ここにプログラムの説明を書く

=end

次の項目を含めるようにしてください.

テンプレートファイル

次のテンプレートを利用して構いません. カメラと光源を適当に配置しています. またキーボードでカメラを動かす仕組みを入れてあります.

技術情報

自由制作で使う可能性のある技術要素をいくつか紹介します.

GLUT図形オブジェクト

GLUTライブラリでは,以下のような図形オブジェクトが定義されています. これらを組み合わせたり,あるいは適宜スケーリングすることで, さらに複雑なオブジェクトを構築することもできます.

次に曲面オブジェクトに関するサンプルプログラムを示します.



  # ティーポット
  # size: 大きさ
  GLUT.SolidTeapot(size)
  GLUT.WireTeapot(size)

  # トーラス
  # 中心=原点,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)として定義されている図形オブジェクトが用意されています. 次にサンプルプログラムを示します(GLUT図形オブジェクトのサンプルと同じです).

二次曲面を利用する際には,二次曲面オブジェクトが必要となります. また予め二次曲面の描画スタイルなどを指定しておく必要があります.



  # 二次曲面オブジェクトの生成
  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)

  # 二次曲面のテクスチャ処理モード
  # quadric 二次曲面オブジェクト
  # mode    テクスチャON/OFF (GL::TRUE, GL::FALSE)
  GLU.QuadricTexture(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)


文字の表示

画面に文字(英数字,記号)を表示するには,次のように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)
}

文字を表示する際に,必要であれば, 以前の課題で行ったように,一時的に変換行列を初期化して,シェーディング,テクスチャ,Zバッファを無効化します. 具体的な処理の記述については課題08課題09のプログラムを参考にしてください.

なおdrawString,drawStringContメソッドは,この実習用に用意したもので,OpenGLで標準に使えるものではありません(Ruby/OpenGLにも含まれていません).

[参考] 文字の表示位置の制御

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)

   # "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]: 終了

なおウインドウの背景色はGL.ClearColorで指定します.


  # 背景色の設定(red,green,blue,alpha) 
  GL.ClearColor(0.4,0.4,1.0,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]キーが押されているかどうかをチェックできます.


  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) # n>0のとき,0〜n-1のいずれかの整数をランダムに返す.
  rand(0) # [0,1)区間の値をランダムに返す


[CG実習 >  CG実習 課題(2021) >  自由制作(最終課題) ]