【Cocos2d-x】マスク処理の悩ましい問題

この記事は

cocos2d-xでたまーに使うマスク処理。
意外にこいつが爆弾を抱えています。
これについて調査した結果をまとめました。

v3.2の内容です。最新のv3.3では試してません。
またGL周りの記述については、明るくないため間違いが多分にあると思います。
ご了承ください。

追記:
※v3.3からは、より問題が少ないClippingRectangleNodeというクラスがv3.3が追加されたらしいです。
制限もあるようですが、これはうれしいニュースです。
もうちょっと詳しく言うとステンシルバッファに依存していない版ClippingNodeだそうです。
Cocos2d-xなどの開発TIPSを公開しているブログCocoa部のおさむさん、情報ありがとうございました!

ClippingNodeの悩ましい問題

Cocos2d-xでマスク処理を行う場合、通常ClippingNodeというクラスを使います。
実はコイツ、機種依存性の高い挙動を困ったちゃんなクラスです。
またRenderTextureによる動的テクスチャ処理を採用している場合、更に爆弾を抱え込むことになります。

ClippingNodeとステンシルバッファ

ClippingNodeはどうやらOpenGLのステンシルバッファというものを使用して実現しているようです。
そしてステンシルバッファはフレームバッファにアタッチして使われています。

フレームバッファというのはCocos2d-xの描画先のキャンバスのようなものだと思えばいいと思います。
iOSならばCCEGLView、AndroidならばGLSurfaceViewがCocos2d-xの描画の土台となっているようです。
フレームバッファのみだと描画しかできないけど追加でステンシルバッファをアタッチすることでマスクの機能が使えるイメージで良いかと思います。

ご存知の通り、iOSはある程度デバイス構成が決まっているため調べた限り期待通りの挙動を示しますが
Androidの方はメーカーによって構成がバラバラです。
でAndroidで影響をモロに受ける端末というかプロセッサが存在します。

それがTegra3です。

Tegra3プロッセッサ搭載AndroidでのClippingNode

GL_DEPTH24_STENCIL8_OESバッファタイプ

iOSやAndroidの(おそらく)大部分の機種では、深度バッファとステンシルバッファが結合されたバッファが使用できるようです。
これがGL_DEPTH24_STENCIL8_OESと呼ばれるバッファタイプです。(たぶん深度バッファ24bit+ステンシル8bitの構成の特殊なバッファタイプを示すもの)

iOSのCCEGLViewではデフォルトでこのバッファを作成するように指定されており、現在主要となってる端末/OS上では問題なく使用できます。

が、Androidの方はチップセットに基づいた最適なGLSurfaceViewを返す使用となっているため
生成バッファはメーカーやプロセッサに左右されるようです。

Tegra3の仕様

  • Tegra3搭載端末はデフォルトでステンシルバッファを確保しない。
  • Tegra3は32bitの深度バッファをサポートしていない(調べたところ24bit?)
  • なのでGL_DEPTH24_STENCIL8_OESのバッファタイプは使えない

何が起こる

結論から申しますと、この端末でClippingNodeを使うと描画正常にされず真っ白になります。

解決策は?

この解決方法については既に記事にされている方がいますので下記にご紹介します。
感謝!

cocos2dxのClippingNodeが変になったときの対処(for android)… – 杏z 学習帳

ハマケン100%開発: cocos2d-x Androidプロジェクトの場合はStencilバッファを明示的に有効化しておかないとクリッピングできない事がある

で、この解決方法なのですが、詰まるところsetEGLConfigChooserというAPIを用いて
通常最適化されるはずのGLSurfaceViewの生成するフレームバッファの設定を強制的に書き換え8bitのステンシルビットを確保している訳です。

参考:Tegra3の搭載端末

NVIDIA Tegra – Wikipedia
に載っています。

Nexus7(2012)がありますね…。

RenderTextureを利用する上での問題

もしRenderTextureでClippingNodeの描画をしないゲームを作成予定ならば、このsetEGLConfigChooserでClippingNodeを利用することができます。
が、RenderTextureを利用する場合、もうちょっと問題が複雑になります。

RenderTextureとClippingNode(GL_DEPTH24_STENCIL8_OESが使える環境)

実は自分が今制作しているゲームキャラクター&ポップではRenderTextureを多用する設計にしています。
RenderTextureを使用すると大幅にドローコールが減らせる部分があるため描画パフォーマンスを改善できるからです。(これについては別記事にする予定です)
このアプリの中でRenderTextureにClippingNodeを描画しようとして問題にぶちあたりました。

RenderTextureの実装を見たところ、内部で本体とは別のフレームバッファを作成しています。
そしてこのフレームバッファ、普通にするとステンシルバッファは作成されないのです。

つまり本体ではClippingNodeが描画されてるのに、RenderTextureでvisitすると真っ白になる挙動を見せます。

で、これに関する回避策は

RenderTexture::create(width, height, Texture2D::PixelFormat:: RGBA8888, GL_DEPTH24_STENCIL8_OES)

とGL_DEPTH24_STENCIL8_OESを指定してあげることです。
深度バッファの指定をGL_DEPTH24_STENCIL8_OESにした場合、Cocos2d-xが内部的に切り分けてステンシルバッファ対応版のRenderTextureを生成してくれます(ただし一部端末を除いた危険な解決方法)

RenderTextureとClippingNode(GL_DEPTH24_STENCIL8_OESが使えない環境)

勘の良い方ならお気づきだと思います。
Tegra3ってGL_DEPTH24_STENCIL8_OESサポートしてねぇんじゃねぇの?と。

はい、その通りです。
この指定でTegra3搭載端末で動かすとクラッシュします。
例えば個人アプリ等で割り切れるのならば、この指定でもありだと思います。
業務などで切り捨てられない方は別の方法で対応する必要があります。

自分は最初、切り分ける方向で対応しようと思いました。が、GL_DEPTH24_STENCIL8_OESが利用できるかどうかを調べる方法は結局見つけられませんでした。

対応方法は色々考えられますが、今回は自分が使った方法をご紹介します。

BlendFunc+RenderTextureで対応だ!!

調べたところClippingNodeを使わないマスク処理がありました。
それがBlendFuncでの対応です。

下記シミュレータで
SourceをGL_ZERO
DestinationをGL_ONE_MINUS_SRC_ALPHA もしくは GL_SRC_ALPHA
にして実行してみてください。

Anders Riggelsen – Visual glBlendFunc and glBlendEquation Tool

スクリーンショット 2015-01-05 17.29.09

かなりマスクに近い挙動になると思います。

この黒い部分はRGBA(0, 0, 0, 1)ではなくRGBA(0, 0, 0, 0)で塗られています。
これを普通に本体の描画ノード上にやってしまうと、背面の描画が全部RGBA(0, 0, 0, 0)になってしまうため
背景の黒色が見えてしまい、結局黒塗りしているのと変わりません。

ですが、この描画をRenderTexture上で行った上でaddChildすると
この黒の下に背面の描画がきっちりとされます。
RenderTextureは本体とは別FrameBufferなため、このような処理が出来る訳です。

ただしRenderTextureを確保する=メモリを圧迫しドローコールも増えます。
自分はこのRenderTextureを更にRenderTexture上に描画して
動的テクスチャを作成しSpriteFrameで切り出していました。

※この方法については、ソースも含めて後日別記事にしようと思います。

マスク処理を考えるためのフローチャート

マスク部分が静的である→[YES]・・・マスクした画像を別途用意する
↓[NO]
マスク部分はRenderTexture(RT)で描画しない→[YES]・・・setEGLConfigChooserで対応
↓[NO]
マスク部分をRTで描画、非対応端末は切り捨てる・・・GL_DEPTH24_STENCIL8_OES指定で生成
↓[NO]
今回紹介したBlendFunc、もしくは他の方法での対応が必要

と、ともかくマスク部分がゲームの肝になっている。
どうしても動的に生成しなければならない理由がある。
というケース以外は使わない方向で考えると幸せかもしれません。

おまけ:captureScreenについて

v3.2のcaptureScreenはRenderTextureとは別の処理になっていそうなので
おそらく問題なく動作すると思います。
スクショを撮りたいがためにRenderTextureを使うのならばcaptureScreenを使うと良いと思います。

コメントを残す

メールアドレスが公開されることはありません。