◆◆ SFML で OpenGL を使う ◆◆
SFML2.3 非公式日本語翻訳
今回のテーマは OpenGL そのもの、ではなく、SFML を、OpenGL の実行環境として使う方法について、です。
両者を合わせて使う方法についても触れます。
さて、OpenGL の素敵なところは何かと言えば、いろんな OS で使えるところですよね。
でも OpenGL 単体ではアプリケーションを作れない。
ウィンドウが必要。レンダリングコンテキストが必要。入出力が必要。などなど。
そうなってくると結局、OS に依存したメンドくさいコーディングも必要になってきてしまいます。
だけど、そんな厄介ごとは SFML のウィンドウモジュールが一網打尽にしてくれます。
さぁ、面倒なことは SFML に任せて、思う存分 OpenGL で踊ろうではありませんか。
ヘッダーとライブラリを設定する
OpenGL のヘッダーファイルは OS によって違ってます。
ですが、SFML を使えばそんなことは気にせずに一発でインクルード完了です。
#include <SFML/OpenGL.hpp>
このヘッダーは、あくまでも OpenGL そのものの関数をインクルードしているだけです。
OpenGL のエクステンションはインクルードしてません。
SFML は 内部で OpenGL のエクステンションもロードしているので、ヘッダーもインクルードしてくれている、と見せかけて、してません。
それはあくまでも内部実装のヒミツなのであります。
ユーザーの立場としては、エクステンションは他の外部ライブラリと同じように、個別に取り扱うことになってます。
ヘッダーファイルをインクルードしたら、次はライブラリをリンクしましょう。
ヘッダーに関しては OS ごとの違いを気にしなくてもよかったのですが、ライブラリの場合は気にしないとイケません
(Windows の場合は "opengl32"、Linux の場合は "GL"、などなど)。
OpenGL の関数名には頭に "gl" というプレフィックスがついてます。
このことを覚えておくと、リンカエラーが出たときにヒントになります。
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 はできるだけ近い値に修正します。
たとえば、4x の アンチエイリアシング が高すぎる場合は 2x を試します。それでもダメなら 0 にします。
実際のところどんな値に設定されたのかは、getSettings() 関数で事後に確認できます。
sf::ContextSettings settings = window.getSettings();
std::cout << "depth bits:" << settings.depthBits << std::endl;
std::cout << "stencil bits:" << settings.stencilBits << std::endl;
std::cout << "antialiasing level:" << settings.antialiasingLevel << std::endl;
std::cout << "version:" << settings.majorVersion << "." << settings.minorVersion << std::endl;
SFML では バージョン3.0以上の OpenGL をサポートしてます(グラフィックドライバが対応してれば)。
SFML2.3 からは 3.2+ のプロファイルのコンテキストと、デバッグフラグのオン/オフもサポートされてます。前方互換プロファイルはまだ未対応です。
デフォルトでは、SFML は 前方互換のプロファイルで 3.2+ のコンテキストを作ります。
なぜかというと、SFML はグラフィックモジュールの内部でレガシーな OpenGL の機能を使ってるからです。
OpenGL と SFML のグラフィックモジュールを混ぜて使うときには、コアプロファイルのコンテキストを使わないようにしてください。
グラフィックモジュールが動作しなくなってしまいます。
OS X では、SFML は コアプロファイルだけを使った 3.2+ をサポートしてます。
OpenGL と SFML のグラフィックモジュールを混ぜて使うときには、レガシーなコンテキストを使わなければいけません。つまり OpenGL2.1 になるということです。
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つのウィンドウに描画したい場合は、
どっちのウィンドウがアクティブなのかを、描画する前に設定してあげる必要があります。
setActive()関数を使いましょう。
// 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フレーム終了。display() はレンダリング関数なので、このとき、コンテキストがアクティブである必要がある。
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 と SFML のグラフィックスモジュールを一緒に使う
ここまで、OpenGL を SFML のウィンドウモジュールと一緒に使うやり方を解説してきました。わりと簡単でしたよね?
なぜ簡単だったかというと、ウィンドウモジュールはそもそも OpenGL で描画を行うための土台だからです。描画機能はない。
描画機能はグラフィックモジュールの担当です。そしてその内部では 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、などの関数があります。
そしてさらに、描画前に SFML の状態もリストアする必要があります。resetGLStates()関数を使いましょう。
glDraw...
glPush...
window.resetGLStates();
window.draw(...);
glPop...
glDraw...
マニュアル操作でセーブ/リストアすれば、必要なものだけ管理できるし、不必要なドライバコールを抑えられます。