cg6_opengl

免許法認定公開講座:
コンピュータグラフィックス
第6回 3次元グラフィックス 演習
九州工業大学 情報工学部
システム創成情報工学科 尾下 真樹
演習内容
• 基本的な3次元グラフィックスのプログラムを
作成
– OpenGL を使ったポリゴン描画
– 視点操作
– アニメーション
参考書
• 最低限の関数は資料で説明
• OpenGLの定番の本(高い)
– OpenGLプログラミングガイド(赤本), 12,000円
– OpenGLリファレンスマニュアル(青本), 8,300円
• 共に、ピアソン・エデュケーション出版
• 入門書
– OpenGL入門, 3,000円
• エドワード・エンジェル 著、 滝沢 徹・牧野 祐子 訳
• ピアソン・エデュケーション出版
• 赤本の廉価版的位置づけ
演習環境
• C言語
– 開発環境として Borland C++ を使用
– プログラムの記述にはテキストエディタを使用
• OpenGL + GLUT ライブラリ
– OpenGL
• Zバッファ法によるポリゴン描画
– GLUT
• OpenGLを使ったプログラムを簡単に作成する補助ライブラリ
• まずは、サンプルプログラムをコンパイルしてみま
しょう!
サンプルプログラム・データ
• opengl.cpp
– OpenGL&GLUTを使ったサンプルプログラム
• bitmap.h, bitmap.cpp
– BMP画像の読み書きのための関数のヘッダファ
イルとソースファイル (後で使用)
• kyushu.bmp
– サンプルのテクスチャ画像(後で使用)
サンプルプログラムのコンパイル
• コンパイル
– bcc32 -DWIN32 opengl.cpp
– コンパイルに成功すると、
opengl.exe が生成される
• 動作確認
– opengl.exe を実行してみる
– 地面+1枚のポリゴンを描画
– マウスの右ドラッグで視点回転
コンパイルの手順(1)
• サンプルプログラム一式を適当なフォルダに
置く
– ここでは、 仮に
D:\opengl
に置くとする
• コマンド プロンプトを起動
– スタートメニューから、スタート → プログラム →
アクセサリ → コマンド プロンプト を選択
コンパイルの手順(2)
• コマンドプロンプト上で、プログラムソースを
置いたディレクトリに移動し、コンパイル
– ディレクトリ名は、適宜、自分がファイルを置いた
位置に書き換える
– Dir と入力すると、ファイル一覧が確認できる
① プログラムソースを置いたディレ
クトリに移動
1行目 Dドライブに移動
2行目 指定ディレクトリに移動
② コンパイルを実行
プログラムの修正
• テキストエディタを使って、プログラムを書き
換える
• 「サクラエディタ」 を使った
プログラムの書き換え
– opengl.cpp を右クリックし、
「SAKURAで開く」
• プログラムを変更したら、再度コンパイルし
て実行 (再コンパイルを忘れないこと!)
OpenGL & GLUT入門
OpenGL
• OpenGL
– 現在、最も広く使われている3次元API
• C言語を始め、いろんな言語から使える
– ポリゴンの描画、Zバッファなどの3次元描画に
必要な機能を提供
– ウィンドウ生成やマウス・キーボード入力などの
処理の機能は持たない
• これらは、OSやウィンドウシステム固有の機能なので、
各環境に応じたAPIを使って記述する必要がある
• 実装が大変、環境ごとに実装する必要がある
GLUT
• OpenGL Utility Toolkit (GLUT)
– ウィンドウ生成やイベント処理などの環境依存
の部分を共通化したライブラリ
• OpenGL標準ではないがかなり広く普及している
– 内部に各OS用のコードを含んでいるため、一度
プログラムを作ればいろんな環境で動く
– 機能が限定されている代わりに非常にシンプル
– とりあえずOpenGLを使いたい場合に適している
• OpenGL と GLUT を混同しないように注意
ウィンドウシステムでのプログラミング
• ウィンドウシステムと協調して動作するプログラムを
作成する必要がある
– ウィンドウシステム
• Windows などの、グラフィカルなインターフェースを持つシステム
– ウィンドウ管理やマウス操作などはシステムが処理
– ユーザプログラムは、初期化処理を行った後は処理を
ウィンドウシステムに移す
– ウィンドウシステムは、画面の再描画やマウスの操作な
どのイベントが起こるたびにユーザプログラムに処理を一
時的に戻す (イベントドリブン型)
イベントドリブン型プログラム
コンソール・プログラム
処
理
の
流
れ
ウィンドウ・プログラム(イベントドリブン)
ユーザ・プログラム
ユーザ・プログラム
初期化処理
初期化処理
ウィンドウシステム
描画
メイン処理
マウス処理
アニメーション処理
終了処理
終了処理
入力待ち処理
GLUTのイベントモデル
• イベントループとコールバック
– イベントが起こった時にそのイベントを処理する
関数をあらかじめ登録しておく
– プログラムは初期化が終わったら、GLUTに処
理を移す
– マウス操作などのイベントが起こったらあらかじ
め登録した関数が呼ばれる(コールバック)
GLUTのイベントモデル
これらの処理をコールバック
関数としてGLUTに登録
ユーザ・プログラム
GLUT
初期化処理
描画
マウス処理
アニメーション処理
終了処理
入力待ち処理
OpenGLの関数名
• gl~ で始まる関数
– OpenGLの標準関数
• glu~ で始まる関数
– OpenGL Utility Library の関数
– OpenGLの関数を内部で呼んだり、引数を変換
したりすることで、使いやすくした補助関数
• glut~ で始まる関数
– GLUT(OpenGL Utility Toolkit)の関数
– 正式にはOpenGL標準ではない
OpenGLの関数名
• 同じ機能で、微妙に違う名前の関数がある
– 例: glVertex3f(x, y, z), glVertex3d(x, y, z)
• f は引数が float 型であることを表す
• d は引数が double 型であることを表す
– C言語なので、関数のオーバーロード(同じ名前で引数が異
なる関数)はサポートしていない
• 必要に応じて使い分ける
サンプルプログラムの構成
• グローバル変数の定義
• コールバック関数
–
–
–
–
–
display()
reshape()
mouse()
motion()
idle()
• initEnvironment()
• main()
コールバック関数
• display()
– 再描画が必要な時に呼ばれる
– 地面と 1枚のポリゴンを描画
• reshape()
– ウィンドウサイズ変更時に呼ばれる
– ウィンドウサイズに応じて視界、ビューポート変換の設定
• mouse()
– マウスのボタンが押されたとき、離されたときに呼ばれる
– 右ボタンの押下状態を記録
• motion()
– マウスがウィンドウ上でドラッグされたときに呼ばれる
– 右ドラッグに応じて視点の回転角度を変更
• idle()
– 処理が空いた時に定期的に呼ばれる
サンプルプログラムの構成
ユーザ・プログラム
main()関数
initEnvironment()関数
初期化処理
display()関数
描画
mouse()関数
motion()関数
マウス処理
idle()関数
アニメーション処理
main()関数
終了処理
GLUT
glutMainLoop()
入力待ち処理
GLUTの初期化(メイン関数)
• GLUTの初期化
–
–
–
–
–
–
glutInit()
glutInitDisplayMode()
glutInitWindowSize()
glutInitWindowPosition()
glutCreateWindow()
glutMainLoop()
– 各関数の説明は省略
コールバック関数の設定(メイン関数)
• コールバック関数の設定
–
–
–
–
–
–
関数の引数として関数を渡す(特殊な使い方)
glutDisplayFunc()
glutReshapeFunc()
glutMouseFunc()
glutMotionFunc()
glutIdelFunc()
描画のための設定(初期化関数)
• initEnvironment()関数
–
–
–
–
描画に必要な最低限の設定
光源情報の設定
機能の有効化 (色指定、隠面消去、背面消去)
背景色の設定
– 各関数の説明は省略
• 光源情報などは変更しなくても構わない
描画処理
• dysplay()関数
–
–
–
–
–
–
–
画面のクリア(glClear()関数)
変換行列の設定(ワールド座標系→カメラ座標系)
光源位置の設定
地面のポリゴンの描画
変換行列の設定(モデル座標系→カメラ座標系)
ポリゴンの描画
描画画面を表示(glSwapBuffers()関数)
• 変換行列の設定とポリゴン描画については、
後で詳しく説明
ウィンドウサイズ変更時の処理
• reshape() コールバック関数
– ウィンドウ内の描画範囲を設定
• glViewport()関数
• ここでは、画面全体に描画を行うように設定
– 射影行列の設定
• gluPerspective()関数
• ここでは、標準な射影になるよう設定 (視野角 45°)
– どちらも、描画を行う上で欠かせない設定
マウス操作時の処理
• マウス操作のコールバック関数
– mouse()関数
• マウスのボタンが、押されたとき、ま
たは、離されたときに呼ばれる
– motion()関数
• マウスのボタンが押された状態で、
マウスが動かされたときに定期的に
呼ばれる
• ボタンが押されない状態で、マウス
が動かされたときに呼ばれる関数も
ある(今回は使用しない)
アイドル時の処理
• 描画やマウス入力を処理する必要がないと
きに定期的に呼ばれる関数
– 物体の位置・向きを少しずつ変化させるといった、
アニメーションを実現するために利用できる
– サンプルプログラムでは、何も処理を行っていな
い(今後処理を追加)
サンプルプログラムの構成(確認)
ユーザ・プログラム
main()関数
initEnvironment()関数
初期化処理
display()関数
描画
mouse()関数
motion()関数
マウス処理
idle()関数
アニメーション処理
main()関数
終了処理
GLUT
glutMainLoop()
入力待ち処理
描画処理の詳しい説明
• 描画関数(display()関数)の詳しい説明
– 変換行列の設定
– ポリゴンの描画
変換行列の設定
• OpenGLは、内部に変換行列を持っている
– モデルビュー変換行列
– 射影変換行列
• 両者は別に扱った方が便利なので、別々に設定でき
るようになっている
• プログラムから OpenGLの関数を呼び出す
ことで、変換行列を変更できる
座標変換(復習)
• モデル座標系からスクリーン座標系への変換
y
y
z
モデル座標系
ワールド座標系
x
z
x
y
y
x
スクリーン
座標系
カメラ座標系
z
x
z
変換行列の設定
• 設定を行う変換行列の指定
– glMatrixMode()
– どの変換行列を変更するのかを指定する
• 変換行列の設定
– glLoadIdentity()
– glTranslate()、glRotate()
– その他の設定関数
変換行列の指定
• glMatrixMode( mode )
– 設定する変換行列を指定する
– GL_MODELVIE
• モデルビュー変換
(モデル座標系からカメラ座標系への変換)
– GL_PROJECTION
• 射影変換
(カメラ座標系からスクリーン座標系への変換)
変換行列の変更
• glLoadIdentity()
– 単位行列で初期化
• glTranslate( x, y, z )
– 平行移動変換をかける
• glRotate( angle, x, y, z )
– 指定した軸周りの回転変換をかける
– angle は、1回転を360として指定
変換行列の変更
• 変換行列は順番に右側にかけられていく
– プログラムで後から記述した変換行列の方が、
実際には先に計算される






A
  x   x 
   
 y    y 
  z   z 
    
 1   1 
A  A1A2 A3
An
サンプルプログラムの変換行列
• サンプルプログラムのシーン設定
– カメラと水平面の角度(仰角)は camera_ptich
– カメラと中心の間の距離は 15
– ポリゴンを(0,1,0)の位置に描画
z
x
y
15
y
camera_pitch
z
x
(0,1,0)
サンプルプログラムの変換行列
• モデル座標系 → カメラ座標系 への変換行列
1

0
0

0
0 0 0  1
0
0

1 0 0  0 cos  camera_pitch   sin  camera_pitch 
0 1 15  0 sin  camera_pitch  cos  camera_pitch 

0 0 1 
0
0
 0
ワールド座標系→カメラ座標系
0  1

0  0
0  0

1 
 0
0 0 0   x   x 
    
1 0 1   y   y 

0 1 0   z   z 
    
0 0 1   1   1 
モデル座標系→ワールド座標系
– x軸周りの回転
– 2つの平行移動変換の位置に注意
• 中心から15離れるということは、回転後の座標系で
カメラを後方(z軸)に15下げることと同じ
変換行列の変更のプログラム
• マウス入力処理(motion()関数)
// 右ボタンのドラッグ中であれば、マウスの移動量に応じて視点を回転
if ( drag_mouse_r )
{
// マウスの縦移動に応じてX軸を中心に回転
camera_pitch -= ( my - last_mouse_y ) * 1.0;
if ( camera_pitch < -90.0 )
camera_pitch = -90.0;
else if ( camera_pitch > 0.0 )
camera_pitch = 0.0;
}
// 今回のマウス座標を記録
last_mouse_x = mx;
last_mouse_y = my;
// 再描画の指示を出す(描画のコールバック関数が呼ばれる)
glutPostRedisplay();
変換行列の設定のプログラム
• 描画処理(display()関数)
// 変換行列を設定(ワールド座標系→カメラ座標系)
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0.0, 0.0, - 15.0 );
glRotatef( - camera_pitch, 1.0, 0.0, 0.0 );
// 地面を描画(ワールド座標系で頂点位置を指定)
・・・・・・
// 変換行列を設定(モデル座標系→カメラ座標系)
glTranslatef( 0.0, 1.0, 0.0 );
// ポリゴンを描画(モデル座標系で頂点位置を指定)
・・・・・・
その他の変換行列の設定方法
• 変換行列の設定関数
– glLookAt()
• カメラの位置と注視点の位置から変換行列を設定
– glLoadMatrix(), glMustMatrix()
• 配列を使って変換行列を設定 or かける
• 射影行列の設定関数
– glPerspective(), glFrustrum(), glOrth()
• 1つ目の関数はサンプルプログラムで使用
• 今回はこれらの関数の説明は省略
ポリゴンの描画
• glBegin( type ) ~ glEnd() を使用
– glBegin( プリミティブの種類 )
• この間にプリミティブを構成する頂点データを記述
– glEnd()
• プリミティブの種類
– GL_POINTS(点)、GL_LINES(線分)、
GL_TRIANGLES(三角面)、GL_QUADS(四
角面)、GL_POLYGON(ポリゴン)、他
頂点データの指定
• glColor3f( r, g, b )
– これ以降の頂点の色を設定
• glNormal3f( nx, ny, nz )
– これ以降の頂点の法線を設定
• glVertex3f( x, y, z )
– 頂点座標を指定
– 色・法線は、最後に指定したものが使用される
サンプルポリゴンの描画(1)
• 地面のポリゴン
– ワールド座標系で頂点位置・法線を指定
– 真上(0,1,0)を向き、水平方向の長さ10の四角形
// 地面を描画
glBegin( GL_POLYGON );
glNormal3f( 0.0, 1.0, 0.0 );
glColor3f( 0.5, 0.8, 0.5 );
glVertex3f( 5.0, 0.0, 5.0 );
glVertex3f( 5.0, 0.0,-5.0 );
glVertex3f(-5.0, 0.0,-5.0 );
glVertex3f(-5.0, 0.0, 5.0 );
glEnd();
サンプルポリゴンの描画(2)
• ポリゴン
– モデル座標系で頂点位置・法線を指定
glBegin( GL_TRIANGLES );
glColor3f( 0.0, 0.0, 1.0 );
glNormal3f( 0.0, 0.0, 1.0 );
glVertex3f(-1.0, 1.0, 0.0 );
glVertex3f( 0.0,-1.0, 0.0 );
glVertex3f( 1.0, 0.5, 0.0 );
glEnd();
(-1,1,0)
y
(1,0.5,0)
x
z
(0,-1,0)
サンプルプログラムの座標系
1

0
0

0
0 0 0  1
0
0

1 0 0  0 cos  camera_pitch   sin  camera_pitch 
0 1 15  0 sin  camera_pitch  cos  camera_pitch 

0 0 1 
0
0
 0
ワールド座標系→カメラ座標系
z
x
0 0 0   x   x 
    
1 0 1   y   y 

0 1 0   z   z 
    
0 0 1   1   1 
モデル座標系→ワールド座標系
y
15
0  1

0  0
0  0

1 
 0
y
camera_pitch
z
x
(0,1,0)
サンプルプログラムのまとめ
• サンプルプログラムの構成
– メイン関数と各コールバック関数
– 初期化処理
– 各コールバック関数の役割
• 変換行列の設定 (display()関数)
• ポリゴンの描画 (display()関数)
サンプルプログラムの構成(確認)
ユーザ・プログラム
main()関数
initEnvironment()関数
初期化処理
display()関数
描画
mouse()関数
motion()関数
マウス処理
idle()関数
アニメーション処理
main()関数
終了処理
GLUT
glutMainLoop()
入力待ち処理
サンプルプログラムの拡張
サンプルプログラムの拡張
• 資料に従って、サンプルプログラムを少しず
つ拡張しながら、OpenGLの使い方を学習
• サンプルプログラムを修正するときは、自分
でわざわざ打ち込まなくとも、コピー&ペース
を活用すると早い
• ただし、各修正にどのような意味があるのか、
きちんと理解しながら進めることが重要
– 理解しないままただコピーすると、間違えて違う
ところを修正してしまう可能性が高い
プログラム拡張の流れ
• 簡単なアニメーションの追加
• より複雑なポリゴンモデルの描画
• 変換行列を使った視点操作
• 変換行列を使ったアニメーション
• テクスチャマッピング
簡単なアニメーション
ポリゴンの回転の変換行列
• 1枚のポリゴンを y軸を中心として回転させる
1

0
0

0
0  1
0
0

1 0 0  0 cos  camera_pitch   sin  camera_pitch 
0 1 15  0 sin  camera_pitch  cos  camera_pitch 

0 0 1 
0
0
 0
0 0
ワールド座標系→カメラ座標系
0  1

0  0
0  0

1 
 0
0 0 0   cos  theta_cycle 

1 0 1
0
0 1 0    sin  theta_cycle 

0 0 1  
0
0 sin  theta_cycle  0   x   x 
    
1
1
0   y   y 

1 cos  theta_cycle  0   z   z 
    
0
0
1   1   1 
モデル座標系→ワールド座標系
– 変換行列に y軸周りの回転を追加することで実現
y
(0,1,0)
theta_cycle
z
x
ポリゴンの回転のための変数
• 変数定義(先頭)、変数の変化(idle()関数)
// アニメーションのための変数
float theta_cycle = 0.0;
void idle( void )
{
// theta_cycle を 0~360 まで繰り返し変化させる
// (360まで来たら0に戻る)
theta_cycle += 1.0;
if ( theta_cycle > 360 )
theta_cycle -= 360;
// 再描画の指示を出す(描画関数が呼ばれる)
glutPostRedisplay();
}
ポリゴンの回転の追加
• 変換行列の設定(display()関数)
// 変換行列を設定(ワールド座標系→カメラ座標系)
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0.0, 0.0, - 15.0 );
glRotatef( - camera_pitch, 1.0, 0.0, 0.0 );
// 地面を描画(ワールド座標系で頂点位置を指定)
・・・・・・
// 変換行列を設定(モデル座標系→カメラ座標系)
glTranslatef( 0.0, 1.0, 0.0 );
glRotatef( theta_cycle, 0.0, 1.0, 0.0 );
// ポリゴンを描画(モデル座標系で頂点位置を指定)
裏面のポリゴンの描画
• 裏面が描画されない(背面消去のため)ので、
裏向きのポリゴンの描画を追加
glBegin( GL_TRIANGLES );
glColor3f( 0.0, 0.0, 1.0 );
glNormal3f( 0.0, 0.0, 1.0 );
glVertex3f(-1.0, 1.0, 0.0 );
glVertex3f( 0.0,-1.0, 0.0 );
glVertex3f( 1.0, 0.5, 0.0 );
glColor3f( 1.0, 0.0, 0.0 );
glNormal3f( 0.0, 0.0,-1.0 );
glVertex3f(-1.0, 1.0, 0.0 );
glVertex3f( 1.0, 0.5, 0.0 );
glVertex3f( 0.0,-1.0, 0.0 );
glEnd();
頂点の順序、法線の向きが
逆になる点に注目
修正箇所のまとめ
• 変数定義(先頭)
• 変数の変化(idle()関数)
• 変換行列の設定(display()関数)
• 裏向きのポリゴンの描画を追加(display()
関数)
ポリゴンモデルの描画
より複雑なポリゴンモデルの描画
• 四角すいの描画
– 三角面の集合として描画
• 配列を使った四角すいの描画
– ポリゴンデータを配列に格納
• 配列を使った直方体の描画
– 四角面の集合として描画
– ポリゴンデータを配列に格納
四角すいの描画
• 四角すいを構成する頂点と三角面
三角面
y
法線
{ V0, V3, V1 } { 0.0, 0.53, 0.85 }
V0 (0.0, 0.8, 0.0)
{ V0, V2, V4 } { 0.0, 0.53, -0.85 }
{ V0, V1, V2 } { 0.85, 0.53, 0.0 }
V4
V3
x
V2
V1 (1.0, -0.8, 1.0)
z
{ V0, V4, V3 } { -0.85, 0.53, 0.0 }
{ V1, V3, V2 } { 0.0, -1.0, 0.0 }
{ V4, V2, V3 } { 0.0, -1.0, 0.0 }
面の法線の計算方法
• ポリゴンの2辺の外積から計算できる
N
N=(V3 - V1)×( V2 - V1)
V1
長さが 1 になるよう正規化
V3
V2
ポリゴンモデルの描画方法
• いくつかの描画方法がある
– プログラムからOpenGLに頂点データを与えるのにいろ
いろなやり方がある
• 主な描画方法 (今回は簡単な2通りのみを扱う)
–
–
–
–
–
glVertex() 関数に直接頂点座標を記述
頂点データの配列を使う方法
頂点配列を使う方法
頂点データとインデックスの配列を使う方法
頂点配列とインデックス配列を使う方法
• OpenGLの頂点配列の機能を使うことで、より高速
に描画できる(今回は扱わない)
方法1 最も基本的な描画方法
• サンプルプログラムと同様の描画方法
– glVertex() 関数の引数に直接頂点座標を記述
– ポリゴン数×各ポリゴンの頂点数の数だけ
glVertex()関数を呼び出す
四角すいの描画(1)
• 四角すいを描画する新たな関数を追加
void renderPyramid()
{
glBegin( GL_TRIANGLES );
// +Z方向の面
glNormal3f( 0.0, 0.53, 0.85 );
glVertex3f( 0.0, 1.0, 0.0 );
glVertex3f(-1.0,-0.8, 1.0 );
glVertex3f( 1.0,-0.8, 1.0 );
・・・・・・
glEnd();
}
四角すいの描画(2)
• 描画関数から四角すいの描画関数を呼び出し
– 修正の場所を間違えないように注意
– renderPyramid()関数では色は使用されていない
ので、呼び出す前に色を設定している
void display( void )
{
・・・・・・
// 中心に四角すいを描画((0,1,0) に移動)
glTranslatef( 0.0, 1.0, 0.0 );
glColor3f( 1.0, 0.0, 0.0 );
renderPyramid();
・・・・・・
}
この描画方法の問題点
• 問題点
– 同じ頂点が共通して使われている
• プログラムが長くなる
• モデルデータの修正がしにくい
– モデルデータがプログラムとして記述されている
ので、ファイルから動的に読み込んだりするよう
なことができない
• 解決方法
– モデルデータを配列に格納する
方法2 配列を使った描画方法
• 頂点・ポリゴンのデータを配列に格納
• 描画関数では、配列のデータを順に参照し
ながら描画
• 必要な配列(サンプルプログラムの例)
– 頂点座標(x,y,z)×頂点数
– 三角面を構成する頂点番号(v0,v1,v2)×三角
面数
– 三角面の法線(x,y,z)×三角面数
三角面インデックス
• 頂点データの配列と、三角面インデックスの
配列に分けて管理する
三角面インデックス
面1 面2 面3 面4 面5 面6
面1 面2 面3 面4 面5 面6
何番目の頂点データを
使うかという情報
頂点データ(座標, 法線, 色など)
※ 頂点の重複がある
※ 頂点の重複がなくなる
頂点データ(座標, 法線, 色など)
配列を使った四角すいの描画(1)
• 配列データの定義
const int num_pyramid_vertices = 5; // 頂点数
const int num_pyramid_triangles = 6; // 三角面数
// 角すいの頂点座標の配列
float pyramid_vertices[ num_pyramid_vertices ][ 3 ] = {
{ 0.0, 1.0, 0.0 }, { 1.0,-0.8, 1.0 }, { 1.0,-0.8,-1.0 }, ・・・・・・
};
// 三角面インデックス(各三角面を構成する頂点の頂点番号)の配列
int pyramid_tri_index[ num_pyramid_triangles ][ 3 ] = {
{ 0,3,1 }, { 0,2,4 }, { 0,1,2 }, { 0,4,3 }, { 1,3,2 }, { 4,2,3 }
};
// 三角面の法線ベクトルの配列(三角面を構成する頂点座標から計算)
float pyramid_tri_normals[ num_pyramid_triangles ][ 3 ] = {
{ 0.00, 0.53, 0.85 }, // +Z方向の面
・・・・・・
配列を使った四角すいの描画(2)
• 配列データを参照しながら三角面を描画
void renderPyramid()
{
int i, j, v_no;
glBegin( GL_TRIANGLES );
for ( i=0; i<num_pyramid_triangles; i++ )
{
glNormal3f( pyramid_tri_normals[i][0],・・[i][1],・・ [i][2] );
for ( j=0; j<3; j++ )
{
v_no = pyramid_tri_index[ i ][ j ];
glVertex3f( pyramid_vertices[ v_no ][0], ・・・[ v_no ][1], ・・・
}
}
glEnd();
}
直方体の描画
• 別のポリゴンモデル(直方体)の描画
– 1枚は空欄にしているので、各自、適切な頂点番
号を考えて追加する
四角面
法線
(-0.4, 1.0, -0.2)
V7
V3
{ V2, V3, V1 , V0 } { 0.0, 0.0, 1.0 }
V6
{ V7, V6, V4 , V5 } { 0.0, 0.0, -1.0 }
y
V2 (0.4, 1.0, 0.2)
{ V2, V0, V4 , V6 } { 1.0, 0.0, 0.0 }
{ V3, V7, V5 , V1 } { -1.0, 0.0, 0.0 }
V5
V1
z
V4
x
V0 (0.4, 0.0, 0.2)
{ V3, V2?
, V6 , V7 } { 0.0, 1.0, 0.0 }
{ V0, V1, V5 , V4 } { 0.0, -1.0, 0.0 }
配列を使った直方体の描画(1)
• 配列データの定義
– 四角面を使うので、各面の頂点数が4個になる
const int num_cube_vertices = 8;
const int num_cube_quads = 6;
// 頂点数
// 四角面数
// 頂点座標の配列
float cube_vertices[ num_cube_vertices ][ 3 ] = {
{ 0.4, 0.0, 0.2 }, // 0
・・・・・・
{-0.4, 1.0,-0.2 }, // 7
};
// 四角面インデックス(各四角面を構成する頂点の頂点番号)の配列
int cube_index[ num_cube_quads ][ 4 ] = {
{ 2,3,1,0 }, { 7,6,4,5 }, { 2,0,4,6 }, { 3,7,5,1 },
{ 3,2,6,7 }, { 0,1,5,4 }
};
配列を使った直方体の描画(2)
• 配列データの定義(続き)
– 今回は各面の色も指定する
// 四角面の法線ベクトルの配列(四角面を構成する頂点座標から計算)
float cube_normals[ num_cube_quads ][ 3 ] = {
{ 0.00, 0.00, 1.00 },
{ 0.00, 0.00,-1.00 },
・・・・・・
{ 0.00,-1.00, 0.00 } };
// 四角面のカラーの配列
float cube_colors[ num_cube_quads ][ 3 ] = {
{ 0.00, 1.00, 0.00 },
{ 1.00, 0.00, 1.00 },
・・・・・・
{ 1.00, 1.00, 0.00 } };
配列を使った直方体の描画(3)
• 配列データを参照しながら四角面を描画
void renderCube()
{
int i, j, v_no;
glBegin( GL_QUADS );
for ( i=0; i<num_cube_quads; i++ )
{
glNormal3f( cube_normals[i][0],・・[i][1],・・ [i][2] );
glColor3f( cube_colors[i][0], ・・・[i][1], ・・・[i][2] );
for ( j=0; j<4; j++ )
{
v_no = cube_index[ i ][ j ];
glVertex3f( cube_vertices[v_no][0], ・・[v_no][1], ・・[v_no][2] );
}
}
glEnd();
}
配列を使った直方体の描画(4)
• 描画関数から直方体の描画関数を呼び出し
– 適切な位置(移動の変換行列)を設定
– 色の指定は不要
void display( void )
{
・・・・・・
// 中心に直方体を描画
glTranslatef( 0.0, 0.0,
? 0.0 );
glColor3f( 1.0, 0.0, 0.0 );
renderCube();
・・・・・・
}
変換行列を使った視点操作
視点操作の拡張
• 左ドラッグで距離を操作できるように拡張
1

0
0

0
0 0
0
0
0
 1

1 0
0
 0 cos  camera_pitch   sin  camera_pitch 
0 1 camera_distance  0 sin  camera_pitch  cos  camera_pitch 

 0
0 0
1
0
0

ワールド座標系→カメラ座標系
z
y
x
camera_pitch
z
x
0 0 0   x   x 
    
1 0 1   y   y 

0 1 0   z   z 
    
0 0 1   1   1 
モデル座標系→ワールド座標系
camera_distance
y
0  1

0  0
0  0

1 
 0
(0,1,0)
視点操作の拡張
• プログラムの修正箇所(多いので注意)
– 左ボタンの押下状態を記録する変数を追加
– カメラと原点の距離を記録する変数を追加
– mouse()関数に、左ボタンの押下状態を更新す
る処理を追加
– motion()関数に、左ドラッグに応じて
camera_distance を変更する処理を追加
• 一定値以上は近づかないように制限
– display()関数を、camera_distance に応じて変換
行列を設定するように変更
変換行列によるアニメーション
変換行列によるアニメーション
• 変換行列を組み合わせることで、さまざまな
運動を実現できる
• idle()関数
– 運動を表す媒介変数の変化を記述
• dysplay()関数
– 媒介変数の値に応じて、回転角度や移動距離
を設定
アニメーションに使用する変数
• 資料に従って、いくつかの媒介変数を追加
– theta_cycle
• 0~360 へ単調増加、360 になったら 0 に戻る
– theta_repeat
• 0~180 の間を往復、180 になったら減少を始める
• theta_cycle から計算できる
– move
• 0~1 の間を加速度つきで往復
• 1に近づくと速度が減少
– theta_cycle2, theta_repeat2
アニメーションの例
• 一定速度で回転運動
• 一定位置で回転運動
• 一定速度で回転運動(常に正面を向く)
• 一定速度で往復回転運動
• 一定速度で上下に往復移動運動
• 加速度つきで上下に往復移動運動
• 複数の物体の運動の組み合わせ
例1:一定速度で回転運動
• 移動→回転の順に適用
– 移動にも回転が適用されるので、半径1.5で回転





M
 cos  theta_cycle 

0

   sin  theta_cycle 


0

0 sin  theta_cycle  0   1

1
1
00
1 cos  theta_cycle  0   0

0
0
1   0
ワールド座標系→カメラ座標系
0 

1 0 0 
0 1 1.5 

0 0 1 
0 0
 x   x 
   
 y   y 
 z   z 
   
1  1 
モデル座標系→ワールド座標系
y
z
x
例2:一定位置で回転運動
• 回転→移動の順に適用(順序を逆)
– 常に同じ位置に移動するので、その場で回転





M
1

0
0

0

0   cos  theta_cycle 

1 0 0 
0
0 1 1.5    sin  theta_cycle 

0 0 1  
0
0 0
y
z
x
0 sin  theta_cycle  0   x   x 
    
1
1
0   y   y 

1 cos  theta_cycle  0   z   z 
    
0
0
1   1   1 
例3:一定速度で回転運動2
• 常に正面を向くようにするためには?
– 最初に逆方向に回転しておくことで、次の回転を
キャンセル (移動にのみ回転がかかる)





M
 cos  theta_cycle 

0

   sin  theta_cycle 


0

0 sin  theta_cycle  0   1

1
1
00
1 cos  theta_cycle  0  0

0
0
1   0
0   cos  theta_cycle 

1 0 0 
0
0 1 1.5    sin  theta_cycle 

0 0 1  
0
0 0
y
z
x
0 sin  theta_cycle  0   x   x 
    
1
1
0   y   y 

1 cos  theta_cycle  0   z   z 
    
0
0
1   1   1 
例4:一定速度で往復回転運動
• 変換行列は例1と同じ、異なる変数を使用
– 変数の変化(idle()関数) と 変換行列の設定
(display()関数)の組み合わせが重要





M
 cos  theta_repeat 

0

   sin  theta_repeat 


0

0 sin  theta_repeat  0   1

1
1
00
1 cos  theta_repeat  0   0

0
0
1   0
y
z
x
0 

1 0 0 
0 1 1.5 

0 0 1 
0 0
 x   x 
   
 y   y 
 z   z 
   
1  1 
例5:一定速度で上下に往復移動
• 回転だけではなく、位置に変数を使用するこ
ともできる
– めり込みを避けるために y座標値を +1 している





M
1

0
0


0
0 0
0


1 0 theta_repeat /180  1

0 1
0

0 0
1

y
z
x
 x   x 
   
 y   y 
 z   z 
   
1  1 
例6:加速度つきで上下に往復運動
• 変数の変化を工夫することで、移動速度を
変化させるようなこともできる
– ここでは三角関数の絶対値を利用
– 0~360 を 0~2πに変換している
void idle( void )
{
// move を 0~1 の間で反復変化させる
//(三角関数を用いることで、一定速度でなはなく、
// 0 の近くで速度が小さく
// 180 の近くで速度が大きくなるように変化させる)
move = fabs( sin( theta_cycle * 3.1415926 / 180.0 ) );
}
例7:複数の物体の運動
• それぞれ異なる変換行列を使用して描画















 x   x 
   
 y   y 
 z   z 
   
1  1 
M





M
 cos  theta_cycle 

0

   sin  theta_cycle 


0

M
 cos  theta_cycle2 

0

   sin  theta_cycle2 


0

0 sin  theta_cycle  0   1

1
1
00
1 cos  theta_cycle  0   0

0
0
1   0
0 

1 0 0 
0 1 1.5 

0 0 1 
0 0
0 sin  theta_cycle2  0   1

1
1
00
1 cos  theta_cycle2  0   0

0
0
1   0
0 0 0

1 0 0
0 1 3

0 0 1 
 x   x 
   
 y   y 
 z   z 
   
1  1 
 x   x 
   
 y   y 
 z   z 
   
1  1 
変換行列の退避・復元
• 現在の変換行列を記録しておき、後から復
元することができる
– 記録した変換行列はスタックに記録される
• glPushMatrix()
– 現在の変換行列の退避
– スタックに積む
• glPopMatrix()
– 最後に退避した変換行列の回復
– スタックから取り出す
変換行列の退避・復元の例
• ワールド座標系からカメラ座標系への
変換行列を設定




 World →
 Camera 




• 地面を描画
• 行列を退避
• 物体1からワールド座標系への変換行列
• 物体1を描画




 World →
 Camera 




• 物体2を描画


Obj1 → 
World 






 World →
 Camera 




• 行列を回復
• 物体2からワールド座標系への変換行列










 World →
 Camera 






Obj2 →
 World








変換行列の退避・復元の例
• プログラムの例
void display( void )
{
// ここまででワールド座標系からカメラ座標系への変換設定
// 地面を描画
// 例7:2つの物体を描画(異なる周期で往復回転運動)
glPushMatrix();
glRotatef( theta_cycle2, 0.0, 1.0, 0.0 );
glTranslatef( 0.0, 0.0, 3.0 );
renderCube();
glPopMatrix();
glRotatef( theta_cycle, 0.0f, 1.0f, 0.0f );
glTranslatef( 0.0f, 0.0f, 1.5f );
renderCube();
}
テクスチャマッピング
テクスチャマッピング
• 地面にテクスチャマッピングを適用して描画
– 各頂点に、テクスチャ座標(u,v)を指定
v
(0.0, 1.0)
u
(0.0, 0.0)
(1.0, 1.0)
(1.0, 0.0)
テクスチャマッピングのための修正
• プログラムの修正箇所
– テクスチャ画像を格納する変数を追加
– テクスチャ画像の読み込み処理、テクスチャマッ
ピングの設定処理、を追加
• 画像の読み込みには bitmap.cpp の関数を使用
– 地面にテクスチャマッピングを行う処理を追加
• テクスチャマッピングの有効化
• 各頂点にテクスチャ座標を設定
• コンパイル方法(bitmap.cppを一緒にコンパイル)
– bcc32 -DWIN32 opengl.cpp bitmap.cpp
レポート課題
• 課題1 ポリゴンモデルの描画
• 課題2 視点操作の拡張
• 課題3 アニメーションの追加
• 作成したプログラムのソースファイルを提出
– 締め切り 12月16日(土)17:00
• 基本的には今日中に終わらせて提出することを想定
– レポート(文章)は提出しなくとも良い
– bitmap.cpp, bitmap.h やテクスチャ画像はつけなくて良い
– ファイル名を st??.cpp (自分のアカウント名) として提出
課題1 ポリゴンモデルの描画
• 矢印(初心者マーク)の描画するプログラム
を作成せよ
V10
V4
y V6
V8
V2
V0
V11
V7
V5
V3
V1
z
V9
x
課題1 ポリゴンモデルの描画
• 矢印(初心者マーク)のポリゴンデータ
– 三角面 or 四角面の配列、描画関数を作成
– 頂点の順番は変えても良い V0 { 頂点座標
0.0, 0.8, 0.2 }
V10
V4
y V6
V8
V2
V0
V11
V7
V5
V3
V1
z
V9
x
V1 { 0.0, 0.0, 0.2 }
V2 { 0.4, 1.0, 0.2 }
V3 { 0.4, 0.2, 0.2 }
V4 {-0.4, 1.0, 0.2 }
V5 {-0.4, 0.2, 0.2 }
V6 { 0.0, 0.8,-0.2 }
V7 { 0.0, 0.0,-0.2 }
V8 {-0.4, 1.0,-0.2 }
V9 {-0.4, 0.2,-0.2 }
V10 { 0.4, 1.0,-0.2 }
V11 { 0.4, 0.2,-0.2 }
課題1のヒント(1)
• 直方体と同様に頂点・四角面の配列を定義
const int num_leaf_vertices = ? ;
const int num_leaf_quads = ? ;
// 頂点数
// 四角面数
// 頂点座標の配列
float leaf_vertices[ num_leaf_vertices ][ 3 ] = {
};
?
// 三角面インデックス(各三角面を構成する頂点の頂点番号)の配列
int cube_index[ num_leaf_quads ][ 4 ] = {
};
?
// 四角面の法線ベクトルの配列(四角面を構成する頂点座標から計算)
float cube_normals[ num_cube_quads ][ 3 ] = {
};
?
// 四角面のカラーの配列
float cube_colors[ num_cube_quads ][ 3 ] = {
?
};
課題1のヒント(2)
• 描画関数も直方体と同じ(変数名を変更)
void renderLeaf()
{
int i, j, v_no;
glBegin( GL_QUADS );
for ( i=0; i<num_leaf_quads; i++ )
{
glNormal3f( cube_normals[i][0],・・[i][1],・・ [i][2] );
glColor3f( cube_colors[i][0], ・・・[i][1], ・・・[i][2] );
for ( j=0; j<4; j++ )
{
v_no = cube_index[ i ][ j ];
glVertex3f( cube_vertices[v_no][0], ・・[v_no][1], ・・[v_no][2] );
}
}
glEnd();
}
課題2 視点操作の拡張
• 左右に右ドラッグすると、視点が横に回転す
る(方位角が変化する)機能を追加せよ
– 変数を追加(camera_yaw)
– 変数の操作を追加(motion()関数)
– 変換行列の計算を追加(display()関数)
課題2のヒント
• 先に変換行列を考えてからプログラム作成
1

0
0

0
0 0
0
0
0
 1

1 0
0
 0 cos  camera_pitch   sin  camera_pitch 
0 1 camera_distance  0 sin  camera_pitch  cos  camera_pitch 

 0
0 0
1
0
0

ワールド座標系→カメラ座標系
y軸周りの回転 をどこかに追加
 cos  camera_yaw 

0

  sin  camera_yaw 

0

0 sin  camera_yaw  0 

1
0
0
0 cos  camera_yaw  0 

0
0
1 
追加する場所が違うと、正しく
動作しないので注意!
z
0

0
0  

1 
W





 x   x 
   
 y   y 
 z   z 
   
1  1 
モデル座標系→ワールド座標系
y
camera_distance
y
x
camera_pitch
z
camera_yaw
x
課題3 アニメーションの追加
• 指定されたアニメーションを実現
– 物体1~物体6 の運動を実現
– サンプルプログラムを参照
• opengl_report.exe
– 変数にはサンプルプログラム
と同じものを使って良い
– まずは各物体の変換行列を
考えてから、プログラムを作
成すると良い
課題3 アニメーションの追加
1. 中心で移動しないまま、一定速度で回転運動
2. 物体1の周囲を一定速度で回転運動、物体1と同
じ方向を向いている
3. 物体2の真上で上下に放物反復移動、常に正面
を向いている
4. 物体3の真上で左右に反復回転運動、物体3と同
じ方向を向いている
5. 物体1の正面で前後に等速反復移動、常に正面
を向いている