【OpenGL】Cocos2d-xでLive2DのモデルをSpriteにオフスクリーンレンダリング【メモ】

[2016/04/11 投稿]
Live2D SDK for OpenGL のサンプルプロジェクトに、モデルをオフスクリーンでレンダリングする機能追加のメモ。

###環境
- Live2D SDK：2.0.10
- Cocos2d-x：3.5
- プロジェクト：/sample_cocos2dx3.5/Simple

###コード
※コメントは追加部分のみ、元々あったコメントは全て削除。

```cpp:SampleLive2DSprite.cpp
#include "SampleLive2DSprite.h"
#include "base/CCDirector.h"
#include "util/UtSystem.h"

#include "graphics/DrawProfileCocos2D.h"
#include "platform/CCFileUtils.h"

using namespace live2d;
USING_NS_CC;
const char* TEXTURES[] ={
	"texture_00.png" ,
	"texture_01.png" ,
	"texture_02.png" ,
	NULL
} ;

// 追加
cocos2d::CCRenderTexture *renderTexture;
cocos2d::CCSprite *sprite;
GLuint color_buffer;
GLuint frame_buffer;

SampleLive2DSprite::SampleLive2DSprite()
: live2DModel(nullptr)
{
}

SampleLive2DSprite::~SampleLive2DSprite()
{
	delete live2DModel;
	Director::getInstance()->getTextureCache()->removeTextureForKey(std::string(TEXTURES[0]));
	Director::getInstance()->getTextureCache()->removeTextureForKey(std::string(TEXTURES[1]));
	Director::getInstance()->getTextureCache()->removeTextureForKey(std::string(TEXTURES[2]));

}

bool SampleLive2DSprite::init()
{
	if (DrawNode::init())
	{
		if (live2DModel != nullptr)
		{
			delete live2DModel;
			live2DModel = nullptr;
		}
		
		const char* MODEL = "haru.moc" ;

		unsigned char* buf;
		ssize_t bufSize;
		buf = FileUtils::getInstance()->getFileData(MODEL,"rb", &bufSize);
		
		live2DModel = Live2DModelOpenGL::loadModel( buf,bufSize ) ;
		free(buf);

		auto chacheMgr = Director::getInstance()->getTextureCache();
		for( int i = 0 ; TEXTURES[i] != NULL ; i++ )
		{
			Texture2D* texture = chacheMgr->addImage(TEXTURES[i]);

			Texture2D::TexParams texParams = { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE };
			texture->setTexParameters(texParams);
			texture->generateMipmap();

			int glTexNo = texture->getName() ;

			live2DModel->setTexture( i , glTexNo ) ;
		}

		float w = Director::getInstance()->getWinSize().width;
		float h = Director::getInstance()->getWinSize().height;
		float scx = 2.0 / live2DModel->getCanvasWidth() ;
		float scy = -2.0 / live2DModel->getCanvasWidth() * (w/h);
		float x = -1 ;
		float y = 1 ;
		float matrix []= {
			scx , 0 , 0 , 0 ,
			0 , scy ,0 , 0 ,
			0 , 0 , 1 , 0 ,
			x , y , 0 , 1
		} ;
		
		live2DModel->setMatrix(matrix) ;
		
		live2DModel->setPremultipliedAlpha( true );


		CCSize size = CCDirector::sharedDirector()->getWinSize();

		// Spriteの作成
		// 画面の2/3のサイズで中央に配置
		renderTexture = RenderTexture::create(size.width/3*2, size.height/3*2);
		renderTexture->setPosition(ccp(size.width/2, size.height/2));
		addChild(renderTexture);
		sprite=renderTexture->getSprite();

		// 不透明度の変更をするなら予め設定しておく（デフォルトだと乗算済アルファで描画されるため、半透明時に描画がおかしくなる）
		sprite->setBlendFunc(BlendFunc::ALPHA_NON_PREMULTIPLIED);


		// 前処理
		GLint oldFBO;
		glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFBO);
		
		// フレームバッファオブジェクト作成
		color_buffer = sprite->getTexture()->getName();
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		glGenFramebuffers(1, &frame_buffer);
		glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_buffer, 0);

		// 後処理
		glBindFramebuffer(GL_FRAMEBUFFER, oldFBO);
		glBindTexture(GL_TEXTURE_2D, 0);
	}

	return true;
}

void SampleLive2DSprite::draw(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t flags)
{
	
	// 前処理
	GLint oldFBO;
	glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFBO);
	
	// オフスクリーンへの描画を開始
	glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer);

	// オフスクリーン部分がわかりやすいよう背景色を変更
	glClearColor(0,30,255,255);
	glClear(GL_COLOR_BUFFER_BIT );


	kmGLPushMatrix();
	kmGLLoadMatrix(&transform);
 
	
	double t = (UtSystem::getUserTimeMSec()/1000.0) * 2 * M_PI  ;
	double cycle=3.0;
	double value=sin( t/cycle );

	live2d::DrawProfileCocos2D::preDraw();
	
	live2DModel->update() ;
	live2DModel->draw() ;
	
	live2d::DrawProfileCocos2D::postDraw() ;

	kmGLPopMatrix();

	

	// オフスクリーンへの描画を終了
	glBindFramebuffer(GL_FRAMEBUFFER, oldFBO);

	// わかりやすいように画面を別の色でクリア。
	glClearColor(0,0,0,255); 
	
	// 不透明度を設定 値は0～255の範囲
	sprite->setOpacity((value+1) / 2 * 255);

	// Spriteへ描画
	sprite->draw(renderer, transform, flags);
}

SampleLive2DSprite* SampleLive2DSprite::createDrawNode()
{
	SampleLive2DSprite *ret = new SampleLive2DSprite();
	if (ret && ret->init())
	{
		ret->autorelease();
		return ret;
	}
	else
	{
		CC_SAFE_DELETE(ret);
		return nullptr;
	}
}

```

###実行結果
![cocos2dx_offscreen.png](https://qiita-image-store.s3.amazonaws.com/0/79621/1ef5c6fe-2742-9263-634e-6114f876feb4.png)




###参考リンク
[2010年12月07日 [OpenGL] フレームバッファオブジェクトの使い方あげいん](http://marina.sys.wakayama-u.ac.jp/~tokoi/?date=20101207)
[cocos2dx3.2＠Live2Dでオフスクリーンレンダリング](http://d.hatena.ne.jp/t_takasaka/20140928) （本稿はほぼこのページの内容まんま）