古い端末では DrawCall 数の重要さをひしひしと感じる今日このごろ。お元気でしょうか。
この記事では、そんなときに遭遇した不思議な RenderTexture にまつわる不思議な現象とその対策についてメモしたものです。
GlobalZOrder を弄ると RenderTexture に描画されない…?
GlobalZOrder は親子関係を無視して描画順を入れ替えることができるので、プレイヤーノードの中の HP バーだけ前面に表示したい場合や、必ず裏側に回る背景、ポップアップウインドウを実装するために、GlobalZOrder を使うと思います。
(親要素だけにGlobalZOrder を指定するだけじゃ子要素以降には適用されないので、親子関係を考慮して微妙に値を足しつつ再帰的に GlobalZOrder を設定する必要がある面倒臭さがあるのですが。)
自分の場合、裏側に回ったポップアップウインドウを RenderTexture に焼き付けてまとめる処理を書いていました。
これらをスクリーン上に描画する際は問題無いと思います。
ですが、これを RenderTexture に描画しようとすると、おかしな現象が発生します。
例えば、次のようなコードがあったとします。
// RenderTexture の準備
auto *renderTexture = cocos2d::RenderTexture::create(200, 300);
addChild(renderTexture);
// RenderTexture の内容を確認するため中央に寄せておく
auto &screenSize = cocos2d::Director::getInstance()->getOpenGLView()->getDesignResolutionSize();
renderTexture->setPosition(cocos2d::Vec2(screenSize.width * 0.5f, screenSize.height * 0.5f));
// RenderTexture に描画したい Sprite の準備
auto *sprite = cocos2d::Sprite::create("hoge.png");
sprite->setPosition(cocos2d::Vec2(100.0f, 150.0f));
sprite->setGlobalZOrder(100.0f);
// レンダリング対象は visit を呼び出す直前に isVisible == true であれば
// 適当な場所に addChild されていても問題ない。
container->addChild(sprite);
// RenderTexture に描画
renderTexture->begin();
sprite->visit();
renderTexture->end();
// RenderTexture には焼きこんだので、結果がわかりやすいように非表示にしておく。
sprite->setVisible(false);
このコードを実行すると、hoge.png が一瞬画面にちらつくように表示された後、RenderTexture には何も描画されずに画面上に何も表示されなくなると思います。
原因
この現象は cocos2d-x 3.x から描画処理を非同期に行うようになったことに関係してます。
まず、RenderTexture の GlobalZOrder はデフォルトの 0.0f なので、begin 命令及び end 命令の優先度はデフォルトのままです。
ところが、sprite の GlobalZOrder は 100.0f に設定されているため、通常より前面に描画するように、つまり GlobalZOrder が 100.0f 未満のものが処理された後に sprite の visit()
内部で発行された描画命令が走ります。
すると実行順は
- RenderTexture への描画開始
- RenderTexture への描画終了
- spriteの描画
となってしまいます。
このため、spriteの描画の時点では画面が描画対象となっている状態なので sprite が描画されてしまいました。
対処方法
RenderTexture への描画開始/終了命令が先に来ているのが原因なので、spriteの描画の前に RenderTexture への描画開始命令を、spriteの描画の後に RenderTexture への描画終了命令が来るようにすることで解決できます。
そのために、RenderTexture への描画処理を以下のように書き換えることでうまくいきます。
// RenderTexture に描画
renderTexture->setGlobalZOrder(-65536.0f);
renderTexture->begin();
sprite->visit();
renderTexture->setGlobalZOrder(65536.0f);
renderTexture->end();
このようにすると、RenderTexture への描画命令の順番が強制的に変更され無事に
- RenderTexture への描画開始
- spriteの描画
- RenderTexture への描画終了
の順で実行されるようになります。
今回は 65535/-65536 という値を使いましたが、もしかしたら std::numeric_limits::max や infinity も使えるかもしれません。
それでは良い RenderTexture ライフを。