暂停时快速高斯模糊

时间:2017-10-14 13:38:30

标签: c++ opengl-es shader cocos2d-x gaussianblur

cocos2d-x 中,我需要实现快速高斯模糊,这就是它应该是什么样子(我刚刚在App Store上发现了一些游戏,已经完成了这样的模糊,统一): / p>

enter image description here

因此,当用户暂停游戏时,很棒淡入淡出 - 淡出淡出模糊。

GPUImage已经有了我需要的快速模糊,但我找不到cocos2d-x的解决方案。

Here is result of live camera view using GPUImage2 - 在iPod Touch 5G上进行了测试,它可以在这款速度较慢的旧设备上快速运行。

即使在iPod Touch 5G等非常慢的设备上,GPUImage中的模糊效果也非常快 寻找具有超快速高斯模糊的cocos2d-x解决方案。

1 个答案:

答案 0 :(得分:18)

在研究"Post-Processing Effects in Cocos2d-X""RENDERTEXTURE + BLUR"后,我找到了以下解决方案。

在Cocos2s-X中实现后期处理效果的常用方法是实现图层。场景是一层,后期处理是另一层,它使用场景图层作为输入。使用此技术,后期处理可以操纵渲染的场景。

模糊算法在着色器中实现。在场景上应用模糊效果的常用方法是首先沿视口的X轴模糊,然后沿视口的Y轴进行第二次模糊(请参阅ShaderLesson5)。这是一个可接受的近似值,可以大大提高性能。

这意味着我们在Cocos2s-X中需要2个后期处理层。所以我们需要3层,一层用于场景,2层用于后期处理:

// scene (game) layer
m_gameLayer = Layer::create();
this->addChild(m_gameLayer, 0);

// blur X layer
m_blurX_PostProcessLayer = PostProcess::create("shader/blur.vert", "shader/blur.frag");
m_blurX_PostProcessLayer->setAnchorPoint(Point::ZERO);
m_blurX_PostProcessLayer->setPosition(Point::ZERO);
this->addChild(m_blurX_PostProcessLayer, 1);

// blur y layer
m_blurY_PostProcessLayer = PostProcess::create("shader/blur.vert", "shader/blur.frag");
m_blurY_PostProcessLayer->setAnchorPoint(Point::ZERO);
m_blurY_PostProcessLayer->setPosition(Point::ZERO);
this->addChild(m_blurY_PostProcessLayer, 2);

请注意,场景的精灵和资源必须添加到m_gameLayer

updated方法中,必须将后期处理应用到场景中(我稍后将描述制服的设置):

// blur in X direction

cocos2d::GLProgramState &blurXstate = m_blurX_PostProcessLayer->ProgramState();
blurXstate.setUniformVec2( "u_blurOffset", Vec2( 1.0f/visibleSize.width, 0.0 ) ); 
blurXstate.setUniformFloat( "u_blurStrength", (float)blurStrength );

m_blurX_PostProcessLayer->draw(m_gameLayer);

// blur in Y direction

cocos2d::GLProgramState &blurYstate = m_blurY_PostProcessLayer->ProgramState();
blurYstate.setUniformVec2( "u_blurOffset", Vec2( 0.0, 1.0f/visibleSize.height ) );
blurYstate.setUniformFloat( "u_blurStrength", (float)blurStrength );

m_blurY_PostProcessLayer->draw(m_blurX_PostProcessLayer);


为了管理后期处理,我实现了一个类PostProcess,我试图让事情变得尽可能简单:

PostProcess.hpp

#include <string>
#include "cocos2d.h"

class PostProcess : public cocos2d::Layer
{
private:
    PostProcess(void) {}
    virtual ~PostProcess() {}
public:
    static PostProcess* create(const std::string& vertexShaderFile, const std::string& fragmentShaderFile);
    virtual bool init(const std::string& vertexShaderFile, const std::string& fragmentShaderFile);
    void draw(cocos2d::Layer* layer);
    cocos2d::GLProgram      & Program( void )      { return *_program; }
    cocos2d::GLProgramState & ProgramState( void ) { return *_progState; }
private:
    cocos2d::GLProgram       *_program;
    cocos2d::GLProgramState  *_progState;
    cocos2d::RenderTexture   *_renderTexture;
    cocos2d::Sprite          *_sprite;
};

PostProcess.cpp

#include "PostProcess.hpp"

using namespace cocos2d;

bool PostProcess::init(const std::string& vertexShaderFile, const std::string& fragmentShaderFile)
{
    if (!Layer::init()) {
        return false;
    }

    _program = GLProgram::createWithFilenames(vertexShaderFile, fragmentShaderFile);
    _program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_COLOR, GLProgram::VERTEX_ATTRIB_POSITION);
    _program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_COLOR);
    _program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORD);
    _program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD1, GLProgram::VERTEX_ATTRIB_TEX_COORD1);
    _program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD2, GLProgram::VERTEX_ATTRIB_TEX_COORD2);
    _program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD3, GLProgram::VERTEX_ATTRIB_TEX_COORD3);
    _program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_NORMAL, GLProgram::VERTEX_ATTRIB_NORMAL);
    _program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_BLEND_WEIGHT, GLProgram::VERTEX_ATTRIB_BLEND_WEIGHT);
    _program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_BLEND_INDEX, GLProgram::VERTEX_ATTRIB_BLEND_INDEX);
    _program->link();

    _progState = GLProgramState::getOrCreateWithGLProgram(_program);

    _program->updateUniforms();

    auto visibleSize = Director::getInstance()->getVisibleSize();

    _renderTexture = RenderTexture::create(visibleSize.width, visibleSize.height);
    _renderTexture->retain();

    _sprite = Sprite::createWithTexture(_renderTexture->getSprite()->getTexture());
    _sprite->setTextureRect(Rect(0, 0, _sprite->getTexture()->getContentSize().width,
    _sprite->getTexture()->getContentSize().height));
    _sprite->setAnchorPoint(Point::ZERO);
    _sprite->setPosition(Point::ZERO);
    _sprite->setFlippedY(true);
    _sprite->setGLProgram(_program);
    _sprite->setGLProgramState(_progState);
    this->addChild(_sprite);

    return true;
}

void PostProcess::draw(cocos2d::Layer* layer)
{
    _renderTexture->beginWithClear(0.0f, 0.0f, 0.0f, 0.0f);
    layer->visit();
    _renderTexture->end();
}

PostProcess* PostProcess::create(const std::string& vertexShaderFile, const std::string& fragmentShaderFile)
{
    auto p = new (std::nothrow) PostProcess();
    if (p && p->init(vertexShaderFile, fragmentShaderFile)) {
        p->autorelease();
        return p;
    }
    delete p;
    return nullptr;
}


着色器需要一个unifor,其中包含模糊算法的偏移量(u_blurOffset)。这是第一个模糊过程沿X轴的2个像素之间的距离,以及第二个模糊过程沿Y轴的2个纹素之间的距离。
模糊效果的强度由均匀变量(u_blurStrength)设置。其中0.0表示模糊关闭,1.0表示最大模糊。最大模糊效果由MAX_BLUR_WIDHT的值定义,MAX_BLUR_WIDHT定义了在每个方向上查看的纹素的范围。所以这或多或少是模糊半径。如果增加该值,模糊效果将增加,但性能会下降。如果减小该值,模糊效果将会降低,但您将获得性能提升。由于近似的2遍实现,性能与MAX_BLUR_WIDHT的值之间的关系值得线性(而不是二次)。
我决定避免预先计算高斯权重并将它们传递给着色器(高斯权重取决于u_blurStrengthattribute vec4 a_position; attribute vec2 a_texCoord; attribute vec4 a_color; varying vec4 v_fragmentColor; varying vec2 v_texCoord; void main() { gl_Position = CC_MVPMatrix * a_position; v_fragmentColor = a_color; v_texCoord = a_texCoord; } )。相反,我使用了与GLSL函数Hermite interpolation类似的平滑smoothstep

blur.vert

varying vec4 v_fragmentColor;
varying vec2 v_texCoord;

uniform vec2  u_blurOffset;
uniform float u_blurStrength;

#define MAX_BLUR_WIDHT 10

void main()
{
    vec4 color   = texture2D(CC_Texture0, v_texCoord);

    float blurWidth = u_blurStrength * float(MAX_BLUR_WIDHT);
    vec4 blurColor  = vec4(color.rgb, 1.0);
    for (int i = 1; i <= MAX_BLUR_WIDHT; ++ i)
    {
        if ( float(i) >= blurWidth )
            break;

        float weight = 1.0 - float(i) / blurWidth;
        weight = weight * weight * (3.0 - 2.0 * weight); // smoothstep

        vec4 sampleColor1 = texture2D(CC_Texture0, v_texCoord + u_blurOffset * float(i));
        vec4 sampleColor2 = texture2D(CC_Texture0, v_texCoord - u_blurOffset * float(i));
        blurColor += vec4(sampleColor1.rgb + sampleColor2.rgb, 2.0) * weight; 
    }

    gl_FragColor = vec4(blurColor.rgb / blurColor.w, color.a);
}

blur.frag

bool HelloWorld::m_blurFast = false


完整的C ++和GLSL源代码可以在GitHub找到(实现可以由bool GPUimageBlur::m_optimized激活)。

查看预览:
preview


每个模糊半径的单独着色器

高斯模糊算法的高性能版本是GPUImage-x提出的解决方案。在该实现中,为每个模糊半径创建分离的模糊着色器。完整 cocos2d-x 演示实现的源代码可以在GitHub找到。该实现提供了2种变体,标准实现和优化实现,如链接中的实现,可以int GPUimageBlur::m_maxRadius设置。该实现为从0到float GPUimageBlur::m_sigma和sigma update的每个半径生成着色器。

查看预览:
preview


快速限制质量模糊

更强大的解决方案,但明显质量非常低,将使用Optimizing Gaussian blurs on a mobile GPU处提供的着色器。模糊不是动态的,只能打开或关闭:

// blur pass 1 cocos2d::GLProgramState &blurPass1state = m_blurPass1_PostProcessLayer->ProgramState(); blurPass1state.setUniformVec2( "u_blurOffset", Vec2( blurStrength/visibleSize.width, blurStrength/visibleSize.height ) ); m_gameLayer->setVisible( true ); m_blurPass1_PostProcessLayer->draw(m_gameLayer); m_gameLayer->setVisible( false ); // blur pass 2 cocos2d::GLProgramState &blurPass2state = m_blurPass2_PostProcessLayer->ProgramState(); blurPass2state.setUniformVec2( "u_blurOffset", Vec2( blurStrength/visibleSize.width, -blurStrength/visibleSize.height ) ); m_blurPass1_PostProcessLayer->setVisible( true ); m_blurPass2_PostProcessLayer->draw(m_blurPass1_PostProcessLayer); m_blurPass1_PostProcessLayer->setVisible( false ); 方法

attribute vec4 a_position;
attribute vec2 a_texCoord;

varying vec2 blurCoordinates[5];

uniform vec2  u_blurOffset;

void main()
{
    gl_Position     = CC_MVPMatrix * a_position;

    blurCoordinates[0] = a_texCoord.xy;
    blurCoordinates[1] = a_texCoord.xy + u_blurOffset * 1.407333;
    blurCoordinates[2] = a_texCoord.xy - u_blurOffset * 1.407333;
    blurCoordinates[3] = a_texCoord.xy + u_blurOffset * 3.294215;
    blurCoordinates[4] = a_texCoord.xy - u_blurOffset * 3.294215;
}

Vetex着色器

varying vec2 blurCoordinates[5];

uniform float u_blurStrength;

void main()
{
    vec4 sum = vec4(0.0);
    sum += texture2D(CC_Texture0, blurCoordinates[0]) * 0.204164;
    sum += texture2D(CC_Texture0, blurCoordinates[1]) * 0.304005;
    sum += texture2D(CC_Texture0, blurCoordinates[2]) * 0.304005;
    sum += texture2D(CC_Texture0, blurCoordinates[3]) * 0.093913;
    sum += texture2D(CC_Texture0, blurCoordinates[4]) * 0.093913;
    gl_FragColor = sum;
}

片段着色器

bool HelloWorld::m_blurFast

查看预览:
enter image description here


可以在GitHub上找到完整的C ++和GLSL源代码(可以通过m_gameLayer = Layer::create(); m_gameLayer->setVisible( false ); this->addChild(m_gameLayer, 0); // blur layer even m_blur_PostProcessLayerEven = PostProcess::create("shader/blur_fast2.vert", "shader/blur_fast2.frag"); m_blur_PostProcessLayerEven->setVisible( false ); m_blur_PostProcessLayerEven->setAnchorPoint(Point::ZERO); m_blur_PostProcessLayerEven->setPosition(Point::ZERO); this->addChild(m_blur_PostProcessLayerEven, 1); // blur layer odd m_blur_PostProcessLayerOdd = PostProcess::create("shader/blur_fast2.vert", "shader/blur_fast2.frag"); m_blur_PostProcessLayerOdd->setVisible( false ); m_blur_PostProcessLayerOdd->setAnchorPoint(Point::ZERO); m_blur_PostProcessLayerOdd->setPosition(Point::ZERO); this->addChild(m_blur_PostProcessLayerOdd, 1); 切换实现)。


具有两层(帧缓冲区)的渐进式解决方案

这个解决方案的想法是,做一个平滑,渐进,高质量的场景模糊。为此,需要一种弱但快速且高质量的模糊算法。模糊精灵不会被删除,它将被存储用于游戏引擎的下一次刷新,并用作下一个模糊步骤的源。这意味着弱模糊的精灵,再次变得模糊,因此它比最后一个模糊一点。这是一个渐进的过程,以强烈而精确的模糊精灵结束 要设置此过程需要3层,游戏层和2个模糊层(偶数和奇数)。

update

请注意,最初所有3个图层都是不可见的。

在update`方法中,一层设置为state visible 。如果没有模糊,则游戏图层可见。一旦模糊开始,游戏图层将使用模糊着色器渲染到偶数图层。游戏图层变得不可见,偶数图层变得可见。在下一个循环中,使用模糊着色器将偶数图层渲染到奇数图层。 偶数图层变为不可见,奇数图层变为可见。该过程继续直到模糊停止。同时,场景变得越来越强,质量越来越高。 如果原始场景必须再次显示,则游戏图层必须设置为可见,并且偶数奇数图层必须设置为不可见。

bool even = (m_blurTick % 2) == 0; if ( m_blur ) { cocos2d::GLProgramState &blurFaststate1 = m_blur_PostProcessLayerEven->ProgramState(); blurFaststate1.setUniformVec2( "u_texelOffset", Vec2( 1.0f/visibleSize.width, 1.0f/visibleSize.height ) ); cocos2d::GLProgramState &blurFaststate2 = m_blur_PostProcessLayerOdd->ProgramState(); blurFaststate2.setUniformVec2( "u_texelOffset", Vec2( -1.0f/visibleSize.width, -1.0f/visibleSize.height ) ); if ( m_blurTick == 0 ) { m_gameLayer->setVisible( true ); m_blur_PostProcessLayerEven->draw(m_gameLayer); } else if ( even ) { m_blur_PostProcessLayerEven->draw(m_blur_PostProcessLayerOdd); } else { m_blur_PostProcessLayerOdd->draw(m_blur_PostProcessLayerEven); } ++m_blurTick; } else m_blurTick = 0; m_gameLayer->setVisible( !m_blur ); m_blur_PostProcessLayerEven->setVisible( m_blur && even ); m_blur_PostProcessLayerOdd->setVisible( m_blur && !even ); 方法

attribute vec4 a_position;
attribute vec2 a_texCoord;

varying vec2 blurCoordinates[9];

uniform vec2 u_texelOffset;

void main()
{
    gl_Position     = CC_MVPMatrix * a_position;

    blurCoordinates[0] = a_texCoord.st + vec2( 0.0,  0.0) * u_texelOffset.st;
    blurCoordinates[1] = a_texCoord.st + vec2(+1.0,  0.0) * u_texelOffset.st;
    blurCoordinates[2] = a_texCoord.st + vec2(-1.0,  0.0) * u_texelOffset.st;
    blurCoordinates[3] = a_texCoord.st + vec2( 0.0, +1.0) * u_texelOffset.st;
    blurCoordinates[4] = a_texCoord.st + vec2( 0.0, -1.0) * u_texelOffset.st;
    blurCoordinates[5] = a_texCoord.st + vec2(-1.0, -1.0) * u_texelOffset.st;
    blurCoordinates[6] = a_texCoord.st + vec2(+1.0, -1.0) * u_texelOffset.st;
    blurCoordinates[7] = a_texCoord.st + vec2(-1.0, +1.0) * u_texelOffset.st;
    blurCoordinates[8] = a_texCoord.st + vec2(+1.0, +1.0) * u_texelOffset.st;
}

着色器是一个简单而精确的3 * 3模糊着色器:

Vetex着色器

varying vec2 blurCoordinates[9];

void main()
{
    vec4 sum = vec4(0.0);
    sum += texture2D(CC_Texture0, blurCoordinates[0]) * 4.0;
    sum += texture2D(CC_Texture0, blurCoordinates[1]) * 2.0;
    sum += texture2D(CC_Texture0, blurCoordinates[2]) * 2.0;
    sum += texture2D(CC_Texture0, blurCoordinates[3]) * 2.0;
    sum += texture2D(CC_Texture0, blurCoordinates[4]) * 2.0;
    sum += texture2D(CC_Texture0, blurCoordinates[5]) * 1.0;
    sum += texture2D(CC_Texture0, blurCoordinates[6]) * 1.0;
    sum += texture2D(CC_Texture0, blurCoordinates[7]) * 1.0;
    sum += texture2D(CC_Texture0, blurCoordinates[8]) * 1.0;
    sum /= 16.0; 
    gl_FragColor = sum;
}

片段着色器

{{1}}


同样,可以在GitHub上找到完整的C ++和GLSL源代码。

查看预览:
enter image description here