在实际开发当中,常常需要对场景或者场景中的部分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
}