第 6 章 マウスとキーボード
6.1 ウィンドウとビューポート
6.1.3 ビューポートの設定方法
glViewport() によるビューポートの設定を描画のループの前でしか行なっていなければ、その
後のウィンドウのサイズの変更がビューポートに反映されません。かといって、描画のループの 中で描画のたびにウィンドウのサイズを調べてビューポートを設定するのは、ウィンドウのサイ ズの変更がそれほど頻繁に行われる処理ではないことを考えると、無駄が多い気がします。
そこで、ウィンドウのサイズが変更された時だけ glViewport() を実行して、ビューポートの設 定を行うようにします。glfwSetWindowSizeCallback() 関数を用いれば、ウィンドウのサイズが変 更されたときに実行する関数 (コールバック関数) を設定することができます。
l ウィンドウ処理のクラス化
この処理を追加するために、まず GLFW によるウィンドウに関する処理を次のようなクラス にまとめます。クラス名は Window とします。
// ウィンドウ関連の処理 class Window
{
// ウィンドウのハンドル GLFWwindow *const window;
x y
-1 0 1
-1 1
正規化デバイス座標系 デバイス座標系のビューポート クリッピング
public:
// コンストラクタ
Window(int width = 640, int height = 480, const char *title = "Hello!") : window(glfwCreateWindow(width, height, title, NULL, NULL))
{
if (window == NULL) {
// ウィンドウが作成できなかった
std::cerr << "Can't create GLFW window." << std::endl;
exit(1);
}
// 現在のウィンドウを処理対象にする glfwMakeContextCurrent(window);
// GLEW を初期化する
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK) {
// GLEW の初期化に失敗した
std::cerr << "Can't initialize GLEW" << std::endl;
exit(1);
}
// 垂直同期のタイミングを待つ glfwSwapInterval(1);
}
// デストラクタ virtual ~Window() {
glfwDestroyWindow(window);
}
// ウィンドウを閉じるべきかを判定する int shouldClose() const
{
return glfwWindowShouldClose(window);
}
// カラーバッファを入れ替えてイベントを取り出す void swapBuffers()
{
// カラーバッファを入れ替える glfwSwapBuffers(window);
// イベントを取り出す glfwWaitEvents();
} };
glfwCreateWindow() 関数の戻り値として得られるウィンドウのハンドルは、このクラスのメン
バ変数 window に保持するものとし、コンストラクタでこれに値を設定します。また、それに続
いて glfwMakeContextCurremt() 関数やGLEW の初期化、glfwSwapInterval() 関数もコンストラク
タ内で実行します。なお、このままでは GLEW の初期化や glfwSwapInterval(1) がウィンドウを 開くたびに実行されますが、これらはウィンドウを開いた直後に一度だけ実行すれば十分です。
デストラクタではウィンドウを閉じる glfwDestroyWindow() 関数を実行します。ただし、この プログラムはウィンドウを閉じた後すぐに終了するので、現状では必ずしも必要ありません。
void glfwDestroyWindow(GLFWwindow *const window)
指定されたウィンドウとそのコンテキストを破棄します。この関数が実行されると、このウィ ンドウに設定されているコールバック関数は呼び出されなくなります。
window
破棄するウィンドウのハンドル。
このほか、このクラスのメンバ関数として、glfwWindowShouldClose() 関数の戻り値を返す
shouldClose() や、バッファを入れ替えてイベントを取り出す swapBuffers() も定義しておきます。
l ウィンドウのサイズ変更時に実行する関数の登録
ウィンドウのサイズを変更した時に実行する関数は、glfwSetWindowSizeCallback() 関数を使っ て次のように設定します。実行する関数名は、ここでは resize() とします。引数の window は対 象のウィンドウのハンドルです。
glfwSetWindowSizeCallback(window, resize);
GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow *const window, GLFWwindowsizefun cbfun)
指定されたウィンドウのサイズが変更されたときに実行するコールバック関数を指定します。
戻り値として以前に設定されていたコールバック関数のポインタか、コールバック関数が設定 されていなければ NULL を返します。
window
サイズ変更時にこのコールバック関数を呼び出すのウィンドウのハンドル。
cbfun
実行する関数のポインタ。NULL なら現在設定されているコールバック関数を削除します。
引数 cbfun に設定する関数は、次の形式で定義します。ここでは関数名を resize() とします。
この関数の引数 window には、この関数の呼び出しの原因となったウィンドウのハンドルが与え られます。width と height には、それぞれサイズ変更後のウィンドウの幅と高さが与えられます。
void resize(GLFWwindow *const window, int width, int height) {
《省略》
}
この処理を先ほど定義したクラス Window に組み込みます。resize() を静的メンバ関数として このクラスに追加し、コンストラクタで glfwSetWindowSizeCallback() 関数を実行して、これをコ ールバック関数として登録します。resize() ではウィンドウ全体をビューポートに設定します。メ ンバ関数をコールバック関数に使う場合は、静的メンバ関数である必要があります。
この関数で行うビューポートの設定などの処理は、一般に最初に Window を開いたときにも実 行すべきものです。したがって、コンストラクタの最後でも resize() を呼び出します。
// ウィンドウ関連の処理 class Window
{
// ウィンドウのハンドル GLFWwindow *const window;
public:
// コンストラクタ
Window(int width = 640, int height = 480, const char *title = "Hello!") : window(glfwCreateWindow(width, height, title, NULL, NULL))
{
《省略》
// 垂直同期のタイミングを待つ glfwSwapInterval(1);
// ウィンドウのサイズ変更時に呼び出す処理の登録 glfwSetWindowSizeCallback(window, resize);
// 開いたウィンドウの初期設定 resize(window, width, height);
}
// デストラクタ virtual ~Window() {
glfwDestroyWindow(window);
}
// ウィンドウを閉じるべきかを判定する int shouldClose() const
{
return glfwWindowShouldClose(window);
}
// カラーバッファを入れ替えてイベントを取り出す void swapBuffers()
{
// カラーバッファを入れ替える glfwSwapBuffers(window);
// イベントを取り出す glfwWaitEvents();
}
// ウィンドウのサイズ変更時の処理
static void resize(GLFWwindow *const window, int width, int height) {
// ウィンドウ全体をビューポートに設定する glViewport(0, 0, width, height);
} };
l ソースプログラム main.cpp の変更点
前述の Window クラスの定義を別のソースファイル (ヘッダファイル) に作成して main.cpp
の冒頭で #include するか、以下のように main() 関数の前に置きます。そして glfwWindowHint() による設定の後の処理を、そのインスタンス (window) の生成に置き換えます。これで図形を表 示するウィンドウが作成されます。GLEW の初期化はこのインスタンスの生成時に行われますの で、この部分からは削除します。また、ループの継続条件やダブルバッファリングの処理を、そ れぞれそのインスタンスのメンバ関数である shouldClose() と swapBuffers() に置き換えます。
《省略》
// ウィンドウ関連の処理 class Window
{
《省略》
}
int main() {
《省略》
// OpenGL Version 3.2 Core Profile を選択する glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// ウィンドウを作成する Window window;
// 背景色を指定する
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
// プログラムオブジェクトを作成する
const GLuint program(loadProgram("point.vert", "pv", "point.frag", "fc"));
// 形状データを作成する
const Object object(createRectangle());
// ウィンドウが開いている間繰り返す
while (window.shouldClose() == GL_FALSE) {
// ウィンドウを消去する
glClear(GL_COLOR_BUFFER_BIT);
// シェーダプログラムの使用開始
glUseProgram(program);
// 図形を描画する
glBindVertexArray(object.vao);
glDrawArrays(GL_LINE_LOOP, 0, object.count);
// カラーバッファを入れ替えてイベントを取り出す window.swapBuffers();
} }