在实际开发当中,常常需要对场景或者场景中的部分OBJ做一些特殊处理。而且这种处理往往是多方面的,很有可能一次处理根本不能满足要求。例如需要对A做高斯模糊处理,然后再做边缘检测。或者对场景中的A、B、C等多个物体同时做特效。这时候,你会发现需要对一个obj同时做2次以上的处理时,你会不知道该怎么办。因为obj做第一次处理后,直接输出到窗口的缓冲区去了,没办法得到第一次处理的结果,再进行第二次处理后才输出到显示缓冲区,显示到屏幕上。

        为什么会这样呢?因为OS的显示缓冲区是不受GL控制的,即GL是不能干涉和控制OS提供的缓冲区。为了解决这个问题,GL引进了FBO技术,即frame buffer obj。和os提供的frame buffer不同的是,FBO完全受GL控制。这便是Render to Texture的来源。意思是把结果渲染到受GL控制的FBO中,而这个FBO在某种意义上可以认为就是一个texture。这个texture可以直接输出到os提供的frame buffer缓冲区去,像图片一样显示在os的窗口上。

使用FBO技术的场景:

对一个obj同时做2次以上的处理;

对场景中多个物体做处理;

例如,场景中有A、B、C....H多个物体,需要对这些物体同时做shader特效或者变换。直接的方法是每次都遍历所有需要做特效处理的obj,一个一个地处理。obj数量越多,效率越低。另外一种方法是使用FBO,将所有需要处理的obj渲染到FBO中,将FBO中得到的texture做一次特效处理。然后再输出。高效直接。这就是经常说的Render to Texture典型的应用。

Cocos2d-x中提供的RenderTexture便是GL FBO的封装。而且有点“过头了”。直接把FBO的texture生成了一个sprite。一般情况下只需要提供FBO中texture的封装便可。如果你想直接控制FBO缓冲区的texture,不得不重写一个render 2 texture的封装。当然引擎这么做估计是考虑到不熟悉GL的小伙伴们,也能愉快地像使用sprite一样玩耍。如果你有不一样的需求,需要控制texture的时候,不得不吐槽,这不是画蛇添足吗!不过本人认为,引擎只需要提供基本的功能,尽可能地把更多的接口留给开发中来控制。过度的封装会丧失灵活性。

用过Cocos2d-x的RenderTexture的小伙伴们估计都不怎么高兴,尤其是Android平台的小伙伴们。因为引擎的源代码留下了这么句话:

// We have not found a way to dispatch the enter background message before the texture data are destroyed.

// So we disable this pair of message handler at present.

大概意思是说在Android平台上,目前还不能找到,一个有效地解决游戏切换到后台运行时导致FBO的texture丢失的问题。在3.0的引擎上,没有这句话。这句解释是在3.2-3.3版本才加上去的。我看到这句解释的时候,脑里冒出了3个单词:WTF。换句话说引擎不管Android的死活了,在Android别用RenderTexture。

在3.0的引擎上使用RenderTexture的时候,发现Android上确实存在这个问题,游戏从后台切换回来的时候,RenderTexture是黑的。也就是说FBO丢失了。像Texture2D一样,需要重新加载。但3.0的源代码在RenderTexture的构造函数里,明明注册了EVENT_COME_TO_BACKGROUND和EVENT_COME_TO_FOREGROUND事件。为何没有起作用。后来发现是没接收到EVENT_COME_TO_BACKGROUND,只能接收到EVENT_COME_TO_FOREGROUND。

其实这个问题还是比较好解决的,不知道引擎为什么在3.0之后的版本都直接屏蔽了这个问题,直接给出了上面那句话。

下面提供1种最直接的解决方法:

首先修改构造函数里注册事件的方式:引擎源代码

#if CC_ENABLE_CACHE_TEXTURE_DATA

// Listen this event to save render texture before come to background.

// Then it can be restored after coming to foreground on Android.

auto toBackgroundListener = EventListenerCustom::create(EVENT_COME_TO_BACKGROUND, CC_CALLBACK_1(RenderTexture::listenToBackground, this));

_eventDispatcher->addEventListenerWithSceneGraphPriority(toBackgroundListener, this);

auto toForegroundListener = EventListenerCustom::create(EVENT_COME_TO_FOREGROUND, CC_CALLBACK_1(RenderTexture::listenToForeground, this));

_eventDispatcher->addEventListenerWithSceneGraphPriority(toForegroundListener, this);

#endif

修改为:

#if CC_ENABLE_CACHE_TEXTURE_DATA

// Listen this event to save render texture before come to background.

// Then it can be restored after coming to foreground on Android.

// auto toBackgroundListener = EventListenerCustom::create(EVENT_COME_TO_BACKGROUND, CC_CALLBACK_1(RenderTexture::listenToBackground, this));

// //_eventDispatcher->addEventListenerWithSceneGraphPriority(toBackgroundListener, this);

//_eventDispatcher->addEventListenerWithFixedPriority(toBackgroundListener, 1);

auto toForegroundListener = EventListenerCustom::create(EVENT_COME_TO_FOREGROUND, CC_CALLBACK_1(RenderTexture::listenToForeground, this));

//_eventDispatcher->addEventListenerWithSceneGraphPriority(toForegroundListener, this);

_eventDispatcher->addEventListenerWithFixedPriority(toForegroundListener, 1);

#endif

addEventListenerWithSceneGraphPriority和addEventListenerWithFixedPriority的区别在于:后者允许“事件穿透”。即使后者不处于当前运行场景中,也能接收到事件的通知。

如果引擎不允许“事件穿透”,这便是设计上的一个缺陷。(曾经问过,不处于运行场景中的节点,如何接收事件通知的小伙伴们知道怎么办了吧。)

其次,修改listenToForeground函数的实现:重新建立FBO和texture

void RenderTexture::listenToForeground(EventCustom *event)

{

#if CC_ENABLE_CACHE_TEXTURE_DATA

// -- regenerate frame buffer object and attach the texture

auto texture = new (std::nothrow) Texture2D();

Texture2D* textureCopy;

void *data = nullptr;

do {

// textures must be power of two squared

int powW = 0;

int powH = 0;

if (Configuration::getInstance()->supportsNPOT()) {

powW = _fullviewPort.size.width;

powH = _fullviewPort.size.height;

}else {

powW = ccNextPOT(_fullviewPort.size.width);

powH = ccNextPOT(_fullviewPort.size.width);

}

auto dataLen = powW * powH * 4;

data = malloc(dataLen);

CC_BREAK_IF(! data);

memset(data, 0, dataLen);

if (texture) {

texture->initWithData(data, dataLen, (Texture2D::PixelFormat)_pixelFormat, \

powW, powH, _fullviewPort.size);

}else {

break;

}

GLint oldRBO;

glGetIntegerv(GL_RENDERBUFFER_BINDING, &oldRBO);

if (Configuration::getInstance()->checkForGLExtension("GL_QCOM")) {

textureCopy = new (std::nothrow) Texture2D();

if (textureCopy) {

textureCopy->initWithData(data, dataLen, (Texture2D::PixelFormat)_pixelFormat, \

powW, powH, _fullviewPort.size);

}else {

texture->release();

break;

}

}

_texture = texture;

_texture->setAliasTexParameters();

if (_textureCopy) {

_textureCopy = textureCopy;

_textureCopy->setAliasTexParameters();

}

/* It is already deallocated by android */

//glDeleteFramebuffers(1, &_FBO);

//_FBO = 0;

glGenFramebuffers(1, &_FBO);

glBindFramebuffer(GL_FRAMEBUFFER, _FBO);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture->getName(), 0);

CCASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, \

"Could not attach texture to framebuffer");

_sprite->setTexture(_texture);

_sprite->setFlippedY(true);

_texture->release();

_sprite->setBlendFunc(BlendFunc::ALPHA_PREMULTIPLIED);

glBindRenderbuffer(GL_RENDERBUFFER, oldRBO);

glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO);

} while (0);

CC_SAFE_FREE(data);

#endif

}