在游戏中有时了为了突显前景元素,需要对背景进行一些模糊化处理,如下图所示
虽然cocos2d-x 3.x提供了新的用户模糊化的shader脚本,但根据实现方式的不同仍然会有很大区别。
方案0:对所有背景上所有精灵进行模糊化
最不可取的方案,多数情况下并不能达到预期效果,而且效率低,原因见以下两图:
第1张黑色背景是引擎示例中的效果,第2张是我在示例代码中加了一个白色背景后的效果。
这里的问题是,实际上3.x版本提供shader脚本无法很好的处理带有透明度的图片,由于示例默认都是黑色背景,因此无法看出问题,而在白色背景下,所有透明的地方都被处理成了黑色。相信现在游戏中即使背景也会有大量带有透明区域的图片,况且当我们我们模糊处理的时候经常也需要处理前景图片,那么这个问题完全不能忽视了。
我本人对shader脚本并不太熟悉,不过猜测应该可以靠修改shader脚本解决。但即使如此,对场景中多个精灵进行模糊化,效率如何呢?我们来看方案1。
方案1:截取屏幕生成一个精灵作为临时背景,并对此精灵进行模糊化
基本思路是使用渲染到纹理技术生成一个临时的图片精灵,将此精灵置于场景最上层(即Zorder高于其他精灵),成为一个假背景,而把需要突显的元素,再置于这个精灵之上。实现很容易,效果如图:
粗略看貌似没问题,但请注意左下角的帧率。实际上即使只对一个960x640尺寸的精灵进行模糊化,效率也非常低,长期保持在20帧以下。虽然这里我的运行环境是集成显卡,这也是造成帧率的低的原因之一,但台式机尚且如此,移动设备上很难表现的更好,况且移动设备还要耗电和发热问题,效率问题必须重视。于是便有了方案2。
方案2:用模糊化后的精灵生成一个普通精灵作为临时背景
效果如图:
帧数恢复到了60左右,经我在手机上测试,对1280x720的图片进行这样的处理,也几乎没有问题,仅仅会在生成模糊图的一刻会瞬间降到40帧左右,之后会迅速恢复至60帧。
以下是实现代码:
#include "HelloWorldScene.h"
USING_NS_CC;
//
//精灵模糊类
//
class SpriteBlur : public cocos2d::Sprite
{
public:
SpriteBlur();
~SpriteBlur();
static SpriteBlur* create(cocos2d::Sprite* pSprite, const float fRadius = 8.0f, const float fSampleNum = 8.0f);
bool initWithTexture(cocos2d::Texture2D* pTexture, const cocos2d::Rect& rRect);
void initGLProgram();
void setBlurRadius(float fRadius);
void setBlurSampleNum(float fSampleNum);
protected:
float fBlurRadius_;
float fBlurSampleNum_;
};
SpriteBlur::SpriteBlur()
: fBlurRadius_(0.0f)
, fBlurSampleNum_(0.0f)
{
}
SpriteBlur::~SpriteBlur()
{
}
SpriteBlur* SpriteBlur::create(cocos2d::Sprite* pSprite, const float fRadius, const float fSampleNum)
{
SpriteBlur* pRet = new (std::nothrow) SpriteBlur();
if (nullptr == pRet)
return nullptr;
pRet->fBlurRadius_ = fRadius;
pRet->fBlurSampleNum_ = fSampleNum;
if (pRet->initWithSpriteFrame(pSprite->getSpriteFrame()))
pRet->autorelease();
else
CC_SAFE_DELETE(pRet);
return pRet;
}
bool SpriteBlur::initWithTexture(cocos2d::Texture2D* texture, const cocos2d::Rect& rect)
{
if (Sprite::initWithTexture(texture, rect))
{
#if CC_ENABLE_CACHE_TEXTURE_DATA
auto listener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom* event){
setGLProgram(nullptr);
initGLProgram();
});
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
#endif
initGLProgram();
return true;
}
return false;
}
void SpriteBlur::initGLProgram()
{
GLchar * fragSource = (GLchar*)cocos2d::String::createWithContentsOfFile(
cocos2d::FileUtils::getInstance()->fullPathForFilename("shaders/blur.fsh").c_str())->getCString();
auto program = cocos2d::GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, fragSource);
auto glProgramState = cocos2d::GLProgramState::getOrCreateWithGLProgram(program);
setGLProgramState(glProgramState);
auto size = getTexture()->getContentSizeInPixels();
getGLProgramState()->setUniformVec2("resolution", size);
getGLProgramState()->setUniformFloat("blurRadius", fBlurRadius_);
getGLProgramState()->setUniformFloat("sampleNum", fBlurSampleNum_);
}
void SpriteBlur::setBlurRadius(float radius)
{
fBlurRadius_ = radius;
getGLProgramState()->setUniformFloat("blurRadius", fBlurRadius_);
}
void SpriteBlur::setBlurSampleNum(float num)
{
fBlurSampleNum_ = num;
getGLProgramState()->setUniformFloat("sampleNum", fBlurSampleNum_);
}
//
//精灵模糊化函数
//
cocos2d::RenderTexture* SpriteBlurer(cocos2d::Sprite* pSprite, const float fRadius = 8.0f, const float fSampleNum = 8.0f)
{
//模糊化的临时精灵
auto pSptBlur = SpriteBlur::create(pSprite, fRadius, fSampleNum);
pSptBlur->setRotationSkewX(180.0f);
pSptBlur->setPositionX(pSptBlur->getContentSize().width / 2);
pSptBlur->setPositionY(pSptBlur->getContentSize().height / 2);
//使用精灵尺寸初始化一个空的渲染纹理对象
cocos2d::RenderTexture* textureScreen =
cocos2d::RenderTexture::create(pSptBlur->getContentSize().width, pSptBlur->getContentSize().height);
//开始获取
textureScreen->beginWithClear(0.0f, 0.0f, 0.0f, 0.0f);
//遍历节点对象,填充纹理到texure中
pSptBlur->visit();
//结束获取
textureScreen->end();
return textureScreen;
}
//
//截屏函数
//
cocos2d::RenderTexture* ScreenShot(const bool bIsSave, std::function<void(cocos2d::RenderTexture*, const std::string&)> pFuncCallback)
{
//使用屏幕尺寸初始化一个空的渲染纹理对象
Size sizeWin = Director::getInstance()->getWinSize();
cocos2d::RenderTexture* textureScreen =
cocos2d::RenderTexture::create(sizeWin.width, sizeWin.height);
//清除数据并开始获取
textureScreen->beginWithClear(0.0f, 0.0f, 0.0f, 0.0f);
//遍历场景节点对象,填充纹理到texure中
cocos2d::Director::getInstance()->getRunningScene()->visit();
//结束获取
textureScreen->end();
//保存为PNG图
if (bIsSave)
{
static int s_iSerialNumber = 0;
textureScreen->saveToFile(
cocos2d::CCString::createWithFormat("ScreenShot_%04d.png", ++s_iSerialNumber)->getCString(),
cocos2d::Image::Format::PNG,
true,
pFuncCallback);
}
else
{
if (nullptr != pFuncCallback)
{
pFuncCallback(textureScreen, "");
}
}
return textureScreen;
}
//
//示例
//
Scene* HelloWorld::createScene()
{
auto scene = Scene::create();
auto layer = HelloWorld::create();
scene->addChild(layer);
return scene;
}
bool HelloWorld::init()
{
if ( !Layer::init() )
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
auto closeItem = MenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width/2 ,
origin.y + closeItem->getContentSize().height/2));
auto menu = Menu::create(closeItem, NULL);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);
auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);
label->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - label->getContentSize().height));
this->addChild(label, 1);
auto sprite = Sprite::create("HelloWorld.png");
sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
this->addChild(sprite, 0);
//
//背景模糊
//
//添加按键用于触发背景模糊
auto blurItem = MenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
[&](Ref* pSender){
//截取屏幕(不保存图片,不设置回调函数)
auto textureScreen = ScreenShot(false, nullptr);
//方案1:直接对背景精灵进行模糊化操作(效率低)
//使用截取后获得的截图精灵,生成一个带模糊效果的精灵
//SpriteBlur* spriteBlur = SpriteBlur::create(textureScreen->getSprite(), 8.0f, 8.0f);
//spriteBlur->setRotationSkewX(180.0f);
//方案2:将模糊化后的精灵保存成一张图片
//将截取的屏幕进行模糊化
auto textureBlur = SpriteBlurer(textureScreen->getSprite());
//将模糊化后的图片保存成一张图片
auto spriteBlur = Sprite::createWithSpriteFrame(textureBlur->getSprite()->getSpriteFrame());
spriteBlur->setPosition(Vec2(visibleSize.width, visibleSize.height));
Size size = Director::getInstance()->getWinSize();
spriteBlur->setRotationSkewX(180.0f);
spriteBlur->setPosition(Vec2(size.width / 2, size.height / 2));
//将此精灵覆盖在原有层之上,成为一个临时背景
this->addChild(spriteBlur, 4);
//添加一个新的图片作为前景元素以便进行比较
auto sprite = Sprite::create("HelloWorld.png");
sprite->setPosition(Vec2(size.width / 2, size.height / 2));
sprite->setScale(0.5f);
this->addChild(sprite, 5);
}
);
blurItem->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2));
menu->addChild(blurItem);
return true;
}
void HelloWorld::menuCloseCallback(Ref* pSender)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert");
return;
#endif
Director::getInstance()->end();
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
exit(0);
#endif
}
HelloWorldScene.h未做任何修改,因此此处不再贴出。
class SpriteBlur用于生成可被shader处理的精灵,注意将cocos2d-x的shader脚本拷入自己的项目中。得到的精灵每一帧都会进行像素渲染,可以动态调节模糊效果。构造函数中的除了需要模糊化的精灵之外,另外两个参数:fRadius为模糊半径,此参数越大,模糊效果越明显;fSampleNum为模糊采样,此参数越大,模糊效果越细腻。这两个参数越高,效率也就越低。并且当高过一定数值后,在手机上有可能出现异常,建议两个参数设置均不要超过8。
ScreenShot为截屏函数,相关教程很多,此处不再展开。
SpriteBlurer为精灵模糊化函数,将一个普通精灵传入,即可返回一个被模糊化处理后的纹理,使用此纹理即可生成普通精灵。即把用shader处理过的精灵截图,生成普通精灵,此精灵不再需要进行像素渲染,因此保证了效率。
更多推荐
(cocos2d-x) 背景模糊化以及效率优化
发布评论