今回のエンジニアブログを担当する安藤です。
cocos2d-xでオフスクリーンレンダリングする手法とちょっとしたテクニックをご紹介したいと思います。
●オフスクリーンレンダリングとは
フレームバッファを描画サーフェスにレンダリングせずフレームバッファやテクスチャなどにレンダリングするレンダリング手法です。今回はcocos2d-xの機能にあるCCRenderTextureを使ってお手軽にオフスクリーンレンダリングを実装してみたいと思います。
では、実際に実装します。
実装はいたってシンプルでCCRenderTextureのbeginWithClear()/end()メソッドの間に描画処理(visit)を挟むだけです。
CCSprite* pSprite = CCSprite::create("HelloWorld.png");// add "HelloWorld" splash screen" pSprite->retain(); // addしないオブジェクトはかならずrelease()しよう CCSize win_size = CCDirector::sharedDirector()->getWinSize(); // CCRenderTexture生成 CCRenderTexture* pRenderTexture = CCRenderTexture::create(win_size.width, win_size.height); pRenderTexture->setPosition(ccp(win_size.width/2, win_size.height/2)); this->addChild(pRenderTexture); // オフスクリーンレンダリング pRenderTexture->beginWithClear(0,0,1,1); for(int i=0;i<1000;i++){ pSprite->setPosition(ccp(rand()%(int)win_size.width,rand()%(int)win_size.height)); pSprite->setScale(0.1f); pSprite->visit(); } pRenderTexture->end();
オフスクリーンレンダリングした画像を描画した結果です。
初期化時に1000個のspriteを一枚のテクスチャにまとめて表示しています。
私の環境ではFPSは60を指しています。
1000個のspriteを素直にaddChild()して描画してみました。
私の環境ではFPSは20まで落ちます。
主な使い方としては重たいエフェクトの前に静的なSpriteをCCRenderTextureにレンダリングすることで高速化を計ったりします。
● 導入例 pushScene()先で前のシーンを表示する
cocos2d-xの仕様でpushScene()したらpush前のシーンが表示されません。
事前にCCRenderTextureに書き込んでおいて背景に描画することで解決します。
以下がソースコードになります。
HelloWorldScene.h
#include "cocos2d.h" class HelloWorld : public cocos2d::CCLayer { public: // Method 'init' in cocos2d-x returns bool, instead of 'id' in cocos2d-iphone (an object pointer) virtual bool init(); // there's no 'id' in cpp, so we recommend to return the class instance pointer static cocos2d::CCScene* scene(); // a selector callback void menuCloseCallback(CCObject* pSender); // preprocessor macro for "static create()" constructor ( node() deprecated ) CREATE_FUNC(HelloWorld); };
HelloWorldScene.cpp
#include "HelloWorldScene.h" #include "SimpleAudioEngine.h" #include "Scene2.h" using namespace cocos2d; using namespace CocosDenshion; CCScene* HelloWorld::scene() { // 'scene' is an autorelease object CCScene *scene = CCScene::create(); // 'layer' is an autorelease object HelloWorld *layer = HelloWorld::create(); // add layer as a child to scene scene->addChild(layer); // return the scene return scene; } // on "init" you need to initialize your instance bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !CCLayer::init() ) { return false; } ///////////////////////////// // 2. add a menu item with "X" image, which is clicked to quit the program // you may modify it. // add a "close" icon to exit the progress. it's an autorelease object CCMenuItemImage *pCloseItem = CCMenuItemImage::create( "CloseNormal.png", "CloseSelected.png", this, menu_selector(HelloWorld::menuCloseCallback) ); pCloseItem->setPosition( ccp(CCDirector::sharedDirector()->getWinSize().width - 20, 20) ); // create menu, it's an autorelease object CCMenu* pMenu = CCMenu::create(pCloseItem, NULL); pMenu->setPosition( CCPointZero ); this->addChild(pMenu, 1); ///////////////////////////// // 3. add your codes below... // add a label shows "Hello World" // create and initialize a label CCLabelTTF* pLabel = CCLabelTTF::create("Scene1", "Thonburi", 34); // ask director the window size CCSize size = CCDirector::sharedDirector()->getWinSize(); // position the label on the center of the screen pLabel->setPosition( ccp(size.width / 2, size.height / 2-40) ); // add the label as a child to this layer this->addChild(pLabel, 1); // add the sprite as a child to this layer // スプライトを2000個描画してギリギリの負荷をかける for(int i=0;i<2000;i++){ CCSprite* sprite=CCSprite::create("Icon.png"); float x = sprite->getContentSize().width*(i%80)*0.1f; float y = sprite->getContentSize().height*(i/80)*0.1f; sprite->setPosition(ccp(x,y)); sprite->setScale(0.1f); this->addChild(sprite, 0); } return true; } void HelloWorld::menuCloseCallback(CCObject* pSender) { CCSize win_size = CCDirector::sharedDirector()->getWinSize(); // CCRenderTexture生成 CCRenderTexture* pRenderTexture = CCRenderTexture::create(win_size.width, win_size.height); pRenderTexture->setPosition(ccp(win_size.width/2, win_size.height/2)); // オフスクリーンレンダリング pRenderTexture->beginWithClear(0,0,1,1); this->visit(); pRenderTexture->end(); CCDirector::sharedDirector()->pushScene(Scene2::scene(pRenderTexture)); }
Scene2.h
#include "cocos2d.h" class Scene2 : public cocos2d::CCLayer { public: // Method 'init' in cocos2d-x returns bool, instead of 'id' in cocos2d-iphone (an object pointer) virtual bool init(); // there's no 'id' in cpp, so we recommend to return the class instance pointer static cocos2d::CCScene* scene(cocos2d::CCRenderTexture* pRenderTexture); // preprocessor macro for "static create()" constructor ( node() deprecated ) CREATE_FUNC(Scene2); };
Scene2.cpp
#include "Scene2.h" using namespace cocos2d; CCScene* Scene2::scene(CCRenderTexture* pRenderTexture) { // 'scene' is an autorelease object CCScene *scene = CCScene::create(); // 'layer' is an autorelease object Scene2 *layer = Scene2::create(); layer->addChild(pRenderTexture,0); // add layer as a child to scene scene->addChild(layer); // return the scene return scene; } // on "init" you need to initialize your instance bool Scene2::init() { if ( !CCLayer::init() ) { return false; } CCLabelTTF* pLabel = CCLabelTTF::create("Scene2", "Thonburi", 34); CCSize size = CCDirector::sharedDirector()->getWinSize(); pLabel->setPosition( ccp(size.width / 2, size.height / 2+40) ); this->addChild(pLabel,2); // 同じくスプライトを2000個描画して処理落ちしていないか確認 for(int i=0;i<2000;i++){ CCSprite* sprite=CCSprite::create("Icon.png"); float x = sprite->getContentSize().width*(i%80)*0.1f; float y =-sprite->getContentSize().height*(i/80)*0.1f; sprite->setPosition(ccp(x,y+size.height)); sprite->setScale(0.1f); this->addChild(sprite, 1); } return true; }
Push前のシーンです。Spriteを2000個表示してギリギリの処理負荷を掛けてます。
Push後のシーンです。さらにSpriteを2000個表示しても処理が落ちていないのが確認できます。
pushScene()を行ったシーンは更新も描画もされることがないので、 pushされたシーンはpush前のシーン処理を気にせずコードを書けるメリットがあります。
お手軽とはいえ癖のあるクラスです。少しではありますがTIPSをまとめてみました。
● 3Dオブジェクトを描画した場合、Zが破損しているDepth Bufferを保存するよう引数を追加する必要があります。
// CCRenderTexture生成 CCRenderTexture* pRenderTexture = CCRenderTexture::create(win_size.width, win_size.height,kCCTexture2DPixelFormat_RGBA8888,GL_DEPTH_COMPONENT16); // オフスクリーンレンダリング pRenderTexture->beginWithClear(0,0,1,1,kCCTexture2DPixelFormat_RGBA8888,GL_DEPTH_COMPONENT16);
● 描画したRenderTextureがチラツキとめり込みを起こす DepthTextをfalseにするとなおります。
CCDirector::sharedDirector()->setDepthTest(false);
● 毎フレーム更新して使用したい 低端末で処理落ちを起こします。特にAndroid2.3系でその処理落ちが顕著に現れます。