(cocos2d-x) 背景模糊化以及效率优化

编程知识 更新时间:2023-05-03 02:44:28

在游戏中有时了为了突显前景元素,需要对背景进行一些模糊化处理,如下图所示

虽然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处理过的精灵截图,生成普通精灵,此精灵不再需要进行像素渲染,因此保证了效率。

 此方案的缺点:SpriteBlurer函数生成的纹理,既不能再次调整模糊程度的强弱,也不能改变显示内容。但如果作为一个背景来用,这个方案已经足够了。 

更多推荐

(cocos2d-x) 背景模糊化以及效率优化

本文发布于:2023-04-30 04:34:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/1923790698a1765c98e8ba7424fe1aec.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:模糊   效率   背景   cocos2d

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!

  • 113315文章数
  • 28753阅读数
  • 0评论数