◆◆ SFML で OpenGL を使う ◆◆
非公式日本語翻訳
今回のテーマは OpenGLそのもの、ではなく、SFMLを、OpenGLへのインターフェイスとして使う方法について、です。
両者を合わせて使う方法についても触れます。
さて、OpenGLの素敵なところは何かっていうと、いろんな OS 上で使えるところですよね。
でも OpenGL単体ではアプリケーションを作れない。
ウィンドウが必要。レンダリングコンテキストが必要。入出力が必要。などなど。
そうなってくると結局、OSに依存したメンドくさいコーディングも必要になってきてしまいます。
そこで! SFMLのウィンドウモジュールの出番です。
SFMLのウィンドウ上で OpenGLとキャッキャウフフする方法について見ていきましょう。
OpenGLのウィンドウを作る
SFMLは内部でOpenGLを使ってるので、特に妙技を凝らさなくても、すでにOpenGLを実行できる状態になってます。
sf::Window window(sf::VideoMode(800, 600), "OpenGL");
// ほら簡単!
glEnable(GL_TEXTURE_2D);
...
丸投げすぎて気持ち悪いって思うキミのために、sf::Windowクラスのコンストラクタの引数で、
内部で使うOpenGLの設定を変更できるようになってます。
引数は sf::ContextSettings 構造体です。この構造体で、下記の項目を定義します。
- depthBits:深度バッファを使うときのピクセルごとのビット数( 0 で無効)
- stencilBits:ステンシルバッファを使うときのピクセルごとのビット数( 0 で無効)
- antialiasinfLevel:マルチサンプリングのレベル
- majorVersion と minorVersion:使いたいOpenGLのバージョン
sf::ContextSettings settings;
settings.depthBits = 24;
settings.stencilBits = 8;
settings.antialiasingLevel = 4;
settings.majorVersion = 3;
settings.minorVersion = 0;
sf::Window window(sf::VideoMode(800, 600), "OpenGL", sf::Style::Default, settings);
SFMLでは バージョン3.0以上のOpenGLをサポートしてます(グラフィックドライバが対応してれば)。
でもフラグを自分で設定することはできません。
つまり、デバッグコンテキスト、または前方互換コンテキストは作れないということです。
実はSFMLは内部的に "compatibility" フラグでコンテキストを作ってます。
なんでかというと、古い非推奨の機能を使ってる箇所があるからです。
この点は近いうちに改善しようと思ってます。フラグもAPIに出していこうと思ってます。
OS X の話。SFML は OpenGL3.2 のコアプロファイルを使ったコンテキストの作成をサポートしています。
ただし、そのコンテキストは SFML のグラフィックスモジュールと互換性がありません。
グラフィックスモジュールを使いたい場合はデフォルトのバージョン(2.1)のコンテキストを使うようにしてください。
※ 訳注 ※
この箇所、正直なところ私にはどういうことなのかよくわからないのですが、とある情報スジによると、Mac OS X に標準で搭載されている GLUT は
OpenGL2.1 しか使うことができず、OpenGL3.2 Core Profile の使用は公式にはサポートされていない、とのこと。おそらく、その件についての注意書きなのだと思います。
(参考:
床井研究室 様)
つまり、Mac OS X で OpenGL を使いつつ SFML のグラフィックスモジュールも同時に使いたい場合は、
settings.majorVersion = 2;
settings.minorVersion = 1;
として、コンテキストを作成しましょう、ということでしょうか。
なにぶん、Mac を所有していないため動作確認ができません。
どなたか、Mac をお使いの方、「できたよ!」などの情報をくださるとうれしいです。
SFMLでOpenGLを使うサンプル
SFMLを使った場合、OpenGLのプログラムはこんな感じです。
#include <SFML/Window.hpp>
#include <SFML/OpenGL.hpp>
int main()
{
// ウィンドウ生成
sf::Window window (
sf::VideoMode(800, 600),
"OpenGL",
sf::Style::Default,
sf::ContextSettings(32)
);
window.setVerticalSyncEnabled(true);
// ……画像とかをロードしたり、OpenGLを初期設定したり……
// メインループ
bool running = true;
while (running)
{
// イベントハンドリング
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
// プログラム終了
running = false;
}
else if (event.type == sf::Event::Resized)
{
// ウィンドウのサイズが変更されたとき、ビューポートを設定しなおす
glViewport(0, 0, event.size.width, event.size.height);
}
}
// バッファをクリア
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// ……描画……
// フレーム終了(初期状態でダブルバッファリングするようになっています)
window.display();
}
// ……リソースを解放……
return 0;
}
このサンプルでは、メインループの while の終了条件に windos.isOpen() を使ってませんね?
プログラムが終了するまで、ウィンドウが開いたままになっている必要があるからです。
最後のループと終了処理の時点で、OpenGLコンテキストが有効じゃないといけないので、こういうふうにしてます。
わからないことがあったら、SFML SDK の中に "OpenGL" と "Window" のサンプルがあるので、
面倒くさがらずに見ておいてくださいませ。詳しく書いてあるので、たぶん絶対役に立つと思いますよー。
複数の OpenGLウィンドウ
OpenGLのウィンドウを1つ扱うのも複数扱うのも、大して変わりありません。
覚えておかなきゃいけないことは少しだけ。
1つ目。
OpenGLの機能はその時点でアクティブなコンテキスト上で行われます(つまりアクティブなウィンドウ上で)。
なので、1つのプログラムで2つのウィンドウに描画したい場合は、
どっちのウィンドウがアクティブなのかを、描画する前に設定してあげる必要があります。
// window1 をアクティブにする
window1.setActive(true);
// …… window1 に描画する処理 ……
// window2 をアクティブにする
window2.setActive(true);
// …… window2 に描画する処理 ……
1つのスレッドでアクティブにできるコンテキスト(=ウィンドウ)は1つだけです。
なので、あるウィンドウをアクティブにする前に、
そのときアクティブなウィンドウを非アクティブに設定してあげる
必要はありません。
自動的に非アクティブになってくれます。OpenGLがそういう仕様になってます。
2つ目。
SFMLで生成されたOpenGLのコンテキストは、リソースを共有します。
たとえば、テクスチャや頂点バッファをあるコンテキストで作成したら、他のコンテキストでも使えます。
ウィンドウを生成しなおすとき、OpenGLのリソースを全部作り直す必要もありません。
ウィンドウのないOpenGL
たまに、ウィンドウがなくて、つまりOpenGLコンテキストがなくて、でもOpenGLの関数を使いたい、っていうときもありますよね。
たとえば別のスレッドでテクスチャをロードしたときとか、ウィンドウを生成する前とか。
sf::Contextクラスを使うと、ウィンドウレスのコンテキストを作れます。
このクラスのインスタンスを生成すれば、コンテキストを取得できます。
int main()
{
sf::Context context;
// …… OpenGLのリソースをロード……
sf::Window window(sf::VideoMode(800, 600), "OpenGL");
...
return 0;
}
別スレッドでレンダリング
マルチスレッドの使い道でよくあるのが、あるスレッド(メイン)でウィンドウとイベントを管理して、
レンダリングは別のスレッドで行う、というパターンです。
こういうふうにするとき、注意事項が1つ。
あるコンテキスト(ウィンドウ)が、あるスレッドでアクティブになっているとき、
そのコンテキストを別のスレッドでアクティブにすることはできません。
つまり、レンダリング用のスレッドを立ち上げる前に、ウィンドウを非アクティブにしておく必要がある、ということです。
void renderingThread(sf::Window* window)
{
// ウィンドウ(コンテキスト)をアクティブにする
window->setActive(true);
// レンダリングループ
while (window->isOpen())
{
// ……描画……
// 1フレーム終了。このとき、コンテキストがアクティブである必要がある。
window->display();
}
}
int main()
{
// ウィンドウ生成(OSの仕様上、ウィンドウはメインスレッドで生成しておくのが無難)
sf::Window window(sf::VideoMode(800, 600), "OpenGL");
// ウィンドウ(コンテキスト)を非活性にする
window.setActive(false);
// レンダリングスレッドを起動
sf::Thread thread(&renderingThread, &window);
thread.Launch();
// メインループ
while (window.isOpen())
{
...
}
return 0;
}
OpenGLとグラフィックスモジュールを一緒に使う
ここまで、OpenGLをSFMLのウィンドウモジュールと一緒に使うやり方を解説してきました。わりと簡単でしたよね?
でもグラフィックスモジュールと一緒に使うのは、ちょっと複雑です。
グラフィックモジュールも内部でOpenGLを使ってるので、お互いに衝突しないように気をつけてあげないといけません。
SFMLのグラフィックモジュールについてよく知らないキミは、ここで1つだけ覚えておいてください。
sf::Windowクラスは sf::RenderWindowクラスに置き換え可能です。
sf::RenderWindowクラスは sf::Windowクラスを継承してて、SFML専用の描画機能が追加されたものです。
さて、SFMLとOpenGLが衝突しないようにするには、
両者を切り替えるたびに、状態をセーブ/リストアするしかありません。
- OenGL で描画
- OpenGL の状態をセーブ
- SFML で描画
- OpenGL の状態をリストア
- OpenGL で描画
……
SFML には pushGLStates関数、popGLStates関数、というのがあってですね、
これを使うと SFMLがキミのためにあれこれ頑張ってくれます。
glDraw...
window.pushGLStates();
window.draw(...);
window.popGLStates();
glDraw...
SFMLさんは、キミがどんなOpenGLコードを書いたか知る由もないので、
毎回、OpenGLの状態全部をセーブ/リストアします。
小規模なプロジェクトならこれでもいいけれども、規模が大きくなると、遅くなってしまいます。
その場合は、マニュアル操作で OpenGLの状態をセーブ/リストアするとよいです。
glPushAttrib/glPopAttrib、 glPushMatrix/glPopMatrix、などの関数があります。
glDraw...
glPush...
window.resetGLStates();
window.draw(...);
glPop...
glDraw...
マニュアル操作でセーブ/リストアすれば、必要なものだけ管理できるし、不必要なドライバコールを抑えられます。