Qt で OpenGL を使用する方法について述べる
※ 筆者は OpenGL を使い始めたばかりであり、本稿はなんらかの間違いを含んでいるかもしれない。
間違いを見つけた場合は、優しく指摘してくださるとありがたい。
OpenGL は OpenGL モジュールに含まれているので、OpenGL モジュールを有効にしなくてはいけない。
プロジェクト作成時に「OpenGL Library」にチェックを入れる。
既に作成しているプロジェクトに OpenGL を組み込む方法:
VisualStudio Qt-Addin:Qt>Qt Project Settings を実行し、Qt Module タブで「OpenGL Library」にチェックを入れる。
OpenGL を使用するには QGLWidget の派生クラスを定義する。
上記ではクラス名を GLWidget にし、親クラスを QGLWidit に、コンストラクタ引数を QWidget * に設定している。
このクラスは OpenGL の機能を持つ QWidget 派生クラスなので、中に3D画像を表示することが簡単に出来る。
宣言ファイルを開き、下記のように、initializeGL(), resizeGL(int, int), paintGL() の3つのメソッドを宣言します。
これらは QGLWidit の protected な仮想関数です。
class GLWidget : public QGLWidget { Q_OBJECT public: GLWidget(QWidget *parent = 0); ~GLWidget(); protected: void initializeGL(); // OpenGL 初期化 void resizeGL(int, int); // ウィジットリサイズ時のハンドラ void paintGL(); // 描画処理 };
それぞれの仮想関数の実装は以下のようにします。
void GLWidget::initializeGL() { qglClearColor(Qt::lightGray); // 背景色指定 }
initializeGL() では OpenGL の初期化を行います。
この例では qglClearColor(const QColor &) を用いて、ビューの背景色(クリアカラー)を指定しています。
OpenGL には glClearColor3b(uchar R, uchar G, uchar B) 等がある(3b の部分は引数の個数・タイプを表す)ので、
それらで色指定も可能ですが、qglClearColor() の方が、Qt の定義済みカラーなども使用できて便利だと思います。
void GLWidget::resizeGL(int width, int height) { // ビューポートの指定 glViewport(0, 0, width, height); }
resizeGL(int width, int height) は、ビューがリサイズされた時にコールされるハンドラです。
glViewport(x, y, width, height) により、OpenGL にビューサイズを指定します。
#include <GL/glu.h> ..... void GLWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT); // カラーバッファをクリア glMatrixMode( GL_PROJECTION ); // 射影行列 glLoadIdentity(); // 変換行列を初期化 gluPerspective(30.0, // 視野角 (double)width() / (double)height(), // アスペクト比 1.0, 100.0); // 視野範囲(近距離・遠距離) gluLookAt( 3.0, 4.0, 5.0, // 視点位置 0.0, 0.0, 0.0, // 目標位置 0.0, 1.0, 0.0); // 上方向 glMatrixMode( GL_MODELVIEW ); // モデルビュー行列 glLoadIdentity(); // 変換行列を初期化 qglColor(Qt::black); // 描画色指定 glBegin(GL_LINES); // 線分(GL_LINES)描画開始 glVertex3d(0.0, 0.0, 0.0); // 線分座標指定 glVertex3d(0.0, 1.0, 0.0); ..... glEnd(); // 線分(GL_LINES)描画終了 glFlush(); // OpenGL の命令をフラッシュ }
paintGL() は OpenGL の描画を行うメソッドです。
最初に glClear(GL_COLOR_BUFFER_BIT) で、ビューをクリアします。 クリア色は initializeGL() で qglClearColor() または glClearColorXX() で指定した色が使用されます。
OpenGL の描画は図形の3次元頂点座標を、モデルビューと射影行列を使って座標変換することで、ビューの2次元座標に変換します。
変換行列を操作する関数は、どの変換行列でも共通なので、glMatrixMode() を使ってどの変換行列かを指定します。
glLoadIdentity() は、変換行列を単位行列に初期化するものです。
gluPerspective() は、視野角度、画面のアスペクト比、視野範囲(近距離・遠距離)を指定するものです。
gluLookAt() は図形を見る視点、目標位置、視線の上方向を指定します。
以上で射影行列の設定は終わりです。
ついで、glMatrixMode(GL_MODELVIEW) によりモデルビュー行列を選択し、glLoadIdentity() で初期化します。
以上で、このあと指定する3次元座標値をビューの2次元座標に正しく変換することができるようになります。
qglColor(const QColor &) は描画する図形の色を指定します。
glBegin(...) はそれ以降に指定する頂点座標の図形種別を指定するものです。
GL_POINTS | 点 |
GL_LINES | 直線 |
GL_LINE_STRIP | 折れ線 |
GL_LINE_LOOP | 閉じた折れ線 |
GL_TRIANGLES | 三角形 |
GL_QUADS | 四角形 |
GL_TRIANGLE_STRIP | 1辺を共有した三角形群(帯状) |
GL_TRIANGLE_FAN | 1辺を共有した三角形群(扇状) |
GL_QUAD_STRIP | 1辺を共有した四角形群 |
GL_POLYGON | 凸多角形 |
glVertexXX で頂点座標を指定します。
glEnd() で描画を終了し、glFlush() で OpenGL の命令をフラッシュします。
enum { A = 0, B, C, D, E, F, G, H, }; GLdouble vertexCube[][3] = { {-1.0,-1.0,-1.0 }, // A { 1.0,-1.0,-1.0 }, // B { 1.0, 1.0,-1.0 }, // C {-1.0, 1.0,-1.0 }, // D {-1.0,-1.0, 1.0 }, // E { 1.0,-1.0, 1.0 }, // F { 1.0, 1.0, 1.0 }, // G {-1.0, 1.0, 1.0 } // H }; int edge[][2] = { { A, B }, { B, C }, { C, D }, { D, A }, { E, F }, { F, G }, { G, H }, { H, E }, { A, E }, { B, F }, { C, G }, { D, H }, }; void GLWidget::paintGL() { ..... glBegin(GL_LINES); // 線分(GL_LINES)描画開始 for (int i = 0; i < 12; ++i) { glVertex3dv(vertexCube[edge[i][0]]); glVertex3dv(vertexCube[edge[i][1]]); } glEnd(); // 線分(GL_LINES)描画終了 glFlush(); // OpenGL の命令をフラッシュ }
void GLWidget::initializeGL() { qglClearColor(Qt::lightGray); // 背景色指定 glEnable(GL_DEPTH_TEST); // for 印面消去 }
int face[][4] = { {A, B, C, D}, {A, E, F, B}, {B, F, G, C}, {C, G, H, D}, {D, H, E, A}, {E, F, G, H}, }; void GLWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // バッファをクリア .... glBegin(GL_QUADS); // 四角形(GL_QUADS)描画開始 double col[][3] = { {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {1.0, 1.0, 0.0}, {0.0, 0.0, 1.0}, {1.0, 0.0, 1.0}, {0.0, 1.0, 1.0}, }; for (int i = 0; i < 6; ++i) { glColor3f(col[i][0], col[i][1], col[i][2]); glVertex3dv(vertexCube[face[i][0]]); glVertex3dv(vertexCube[face[i][1]]); glVertex3dv(vertexCube[face[i][2]]); glVertex3dv(vertexCube[face[i][3]]); } glEnd(); // 四角形(GL_QUADS)描画終了 glFlush(); // OpenGL の命令をフラッシュ }
OpenGL でアニメーションを行うには、実際のアニメーションと同じように、
少しずつ変化した画像を短い時間間隔で順に表示します。
そのためには、タイマーオブジェクトを使って、1秒間に数10フレーム表示するようにします。
class MainWindow : public QMainWindow { protected slots: void onTimer(); private: QTimer *m_timer; };
MainWindow::MainWindow(QWidget *parent, Qt::WFlags flags) : QMainWindow(parent, flags) { m_glWidget = new GLWidget; setCentralWidget(m_glWidget); m_timer = new QTimer(); connect(m_timer, SIGNAL(timeout()), this, SLOT(onTimer())); m_timer->start(20); // 50fps } void MainWindow::onTimer() { m_glWidget->rotate(5); // 5度回転 }
class GLWidget : public QGLWidget { ..... public: void rotate(int); // 立方体を回転 private: int m_r; // 回転角度 };
GLWidget::GLWidget(QWidget *parent) : QGLWidget(parent) { m_r = 0; } void GLWidget::rotate(int d) { m_r = (m_r + d) % 360; update(); // ウィジットを無効化して再描画 }
void GLWidget::paintGL() { ..... glRotated((double)m_r, 0.0, 1.0, 0.0); glBegin(GL_LINES); // 線分(GL_LINES)描画開始 ..... }
画面の適当なところにスライダーを配置。
とりあえず、ツールバーに配置してみた。
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); ui.mainToolBar->addWidget(m_sliderEyeY = new QSlider); m_sliderEyeY->setOrientation(Qt::Horizontal); m_sliderEyeY->setMinimum(0); m_sliderEyeY->setMaximum(10*10); setCentralWidget(m_glWidget = new GLWidget()); connect(m_sliderEyeY, SIGNAL(valueChanged(int)), m_glWidget, SLOT(eyeYChanged(int))); ..... }
GLWidget に視点Y座標を保持するメンバ変数、視点Y座標指定スロット追加
class GLWidget : public QGLWidget { ..... public slots: void eyeYChanged(int); private: int m_r; double m_eyeY; }; void GLWidget::eyeYChanged(int y) // y = [0, 100] { m_eyeY = (double)y / 5 - 10.0; update(); }
描画メソッドで、視点Y座標を参照
// 描画処理 void GLWidget::paintGL() { ..... gluLookAt( 6.0, m_eyeY, 10.0, // 視点位置 0.0, 0.0, 0.0, // 目標位置 0.0, 1.0, 0.0); // 上方向 ..... }
複数の同形オブジェクトを配置する時、glBegin() 〜 glEnd() 間で、座標値をずらしながら glVertex3d() を記述しても良いが、 座標指定は同一とし、座標変換で同形オブジェクトを配置する方法もあるぞ。
void GLWidget::paintGL() { ..... for(int z = -4; z < 5; ++z) { for(int x = -4; x < 5; ++x) { glLoadIdentity(); // 変換行列を初期化 glTranslated((double)x*3, 0.0, -(double)z*3); glRotated((double)m_r, 0.0, 1.0, 0.0); // Y軸方向について回転 glBegin(GL_QUADS); // 四角形(GL_QUADS)描画開始 for (int i = 0; i < 6; ++i) { glColor3f(col[i][0], col[i][1], col[i][2]); glVertex3dv(vertexCube[face[i][0]]); glVertex3dv(vertexCube[face[i][1]]); glVertex3dv(vertexCube[face[i][2]]); glVertex3dv(vertexCube[face[i][3]]); } glEnd(); // 四角形(GL_QUADS)描画終了 } } ..... }
※ 回転と平行移動演算は可換ではないので順番に注意。