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)描画終了
}
}
.....
}
※ 回転と平行移動演算は可換ではないので順番に注意。