ウィンドウモジュールのチュートリアルは読んだかな?
SFMLを使うと、とっても簡単に OpenGL のウィンドウを開いてイベントをハンドリングできるんだったよね。
でもでも、何にも描画できないよ? うえーん。
OpenGL の描画 API を使えって? あんなのややこしくってイヤだよ。うえーん。
泣かないで! SFML には OpenGL よりもカンタンに2Dのモノを描画する機能があるんだよー。
新しいお友達を紹介します。sf::RenderWindowちゃんです。
SFMLのグラフィックモジュールで生成したものを描画するには、こっちを使ってあげる必要があります。
この子は今まで一緒にお勉強してきた sf:Windowクラスを継承して作られてて、全部の機能を受け継いでるので、
ウィンドウモジュールのチュートリアルでお勉強したことは、全部同じように使えますよ。
(ウィンドウ生成とか、イベントハンドリングとか、フレームレートとか、OpenGLと混ぜ混ぜとか)
sf::RenderWindowちゃんは賢い子なので、描画なんてお茶の子さいさいです。
今日はそんな sf::RenderWindowちゃんの得意技の中から2つを見せてもらいます。
その2つとは、clear関数と、draw関数です。
どんな関数かというと、2つとも名前の通りで、
clear関数はウィンドウ全体をクリアします。何色でクリアして欲しいのか、引数で指定することもできます。
draw関数は引数にどんなものでも入れて描画できるんだ。
じゃあ、RenderWindowちゃん、ちょっとみんなに見せてあげてくれるかな?
#include <SFML/Graphics.hpp>
int main()
{
// ウィンドウ生成
sf::RenderWindow window(sf::VideoMode(800, 600), "My window");
// ウィンドウが開いている間、プログラムを続ける
while (window.isOpen())
{
// タマってるイベントを全部お世話する。
sf::Event event;
while (window.pollEvent(event))
{
// 閉じる要求検知。ウィンドウを閉じる。
if (event.type == sf::Event::Closed) {
window.close();
}
}
// ウィンドウを黒でクリア
window.clear(sf::Color::Black);
// なんでも描画
// window.draw(...);
// 1フレーム終了
window.display();
}
return 0;
}
描画の前に clear関数を呼ぶのは、お約束です。
じゃないと、以前のフレームでお絵かきしてた内容の上に重ね描きすることになってしまいます。
ちなみに、ウィンドウ全体を覆い隠すようなものを描画する場合だけは別かもしれませんね。
そういう場合は clearで塗りつぶしてあげなくても、全部のピクセルが描き換わるので clear関数を呼ばなくっても大丈夫です。
(パフォーマンスはあんまり変わらないんですけどね)
display関数を呼ぶのもお約束です。
これを呼ぶことで、それまでに draw関数で描画された内容がウィンドウに表示されます。
というか、つまり、draw関数って、実は直接描画してるのではなくって、背後のバッファに描画してるのですね。
で、display関数が呼ばれたときに、そのバッファの内容が画面にコピーされる、ということです。
いわゆるダブルバッファリングというやつです。
この「clear → draw → display」の描画パターンは鉄板です。他の作戦を試そうなんて思わないでね。
たとえば、前のフレームからピクセル状態を維持しておくとか、
ピクセルを消去するとか、1度描画した後で display関数を何度も呼ぶとか。
ダブルバッファリングのところで、おかしなことになるですよー。
というか、これは本当なんですけど、ハイカラなグラフィックチップセットや API でも
「clear → draw → display」の描画パターンが使われてて、メインループの1フレームごとに全部1度クリアされるようになってます。
重くなってしまいそうな気がするかもですけど、
たとえば1000個のスプライトを1秒間に60回描画したとしても、キミのマシンは全然余裕です。何しろ何百万枚ものポリゴンを扱えるぐらいですからね。
そんなわけで、キミのメインループは描画する準備ができたよ!
何をどんなふうに描画しようかな? わくわく。
SFMLでは4種類の物体が描画できます。
そのうちの3つは、もう使える状態になってます(スプライト、文字、図形)。
残りの1つは、なんというか「頂点のリスト」で、これを使うと独自の形の物体を作って描画できます。
さて、この4つの物体ですが、共通のインターフェイスもあるんですが、
個別のお約束もあるので、それぞれ別のページでお勉強することとしましょう。
SFML ではウィンドウに直接描画するだけじゃなく、テクスチャにも描画できます。
その場合、sf::RenderWindow の代わりに、sf::RenderTexture クラスを使います。
sf::RenderWindowちゃんも sf::RenderTextureちゃんも、sf::RenderTargetちゃんの親戚なので、
同じ描画メソッドを継承してます。
// 500x500 の レンダリング用テクスチャを生成
sf::RenderTexture renderTexture;
if (!renderTexture.create(500, 500))
{
// error...
}
// 同じ関数を使って描画
renderTexture.clear();
renderTexture.draw(sprite); // spriteに限らず何でも描画
renderTexture.display();
// ターゲットテクスチャを取得(ここに何かが描画されている)
const sf::Texture& texture = renderTexture.getTexture();
// ウィンドウに描画
sf::Sprite sprite(texture);
window.draw(sprite);
getTexture関数で得られるのは、読み込み専用のテクスチャです。使うだけ。変更不可。
使う前に変更したいときは、別の sf::Texture のインスタンスにコピーしてください。
さて、sf::RenderTexture は sf::RenderWindowと同じ関数を持ってて、ビュー(視点・2Dカメラ)や OpenGL をいじれます
(なので、詳しくはそっちのチュートリアルを読んでね)。
OpenGLを使ってテクスチャに描画するとき、深度バッファを持つことができます。
create関数の3番目の引数に true を指定します。
// 深度バッファを有効にする
renderTexture.create(500, 500, true);
SFML ではマルチスレッドで描画することもできます。
特に何も設定してあげる必要はないんですが、注意点が1つだけ。
別のスレッドで描画を行う前に、ウィンドウを非活性にしておく必要があります。
ウィンドウ(というか OpenGL のコンテキスト)は、複数のスレッドで同時にアクティブにはできないからです。
void renderingThread(sf::RenderWindow* window)
{
// 描画ループ
while (window->isOpen())
{
// …… 描画 ……
// 1フレーム終了
window->display();
}
}
int main()
{
// ウィンドウ生成(OSの仕様上、ウィンドウはメインスレッドで生成しておくのが無難)
sf::RenderWindow window(sf::VideoMode(800, 600), "OpenGL");
// OpenGLコンテキストを非活性にする
window.setActive(false);
// 描画スレッドを起動
sf::Thread thread(&renderingThread, &window);
thread.launch();
// メインループ
while (window.isOpen())
{
...
}
return 0;
}
上のサンプルによると、描画スレッドの中では、ウィンドウがアクティブかどうかを気にしなくてもいいみたいですね。
SFMLさんはいい子なので、必要とあらば自動的にいい感じにしてくれるのです。
ウィンドウの生成とイベントハンドリングは、メインスレッドで行うようにしましょう。
ウィンドウモジュールのところでお勉強したように、
世間には気難しい OS さんもいらっしゃいますからね?