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