免許法認定公開講座: コンピュータグラフィックス 第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 00 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 00 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 00 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 00 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 00 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の正面で前後に等速反復移動、常に正面 を向いている
© Copyright 2025 ExpyDoc