快速高斯模糊在暂停时

6
在中,我需要实现快速高斯模糊,以下是它应该的外观(我在App Store上找到了一些已经在Unity中完成了这样的模糊处理的游戏):

enter image description here

因此,当用户暂停游戏时,使用fadeIn-fadeOut模糊效果很好。

GPUImage已经拥有我需要的快速模糊效果,但我找不到适用于cocos2d-x的解决方案。

这里是使用GPUImage2的实时相机视图结果 - 在iPod Touch 5G上测试,即使在这个缓慢而老旧的设备上也运行得很快。

即使在像iPod Touch 5G这样非常缓慢的设备上,GPUImage中的模糊效果也非常快速。
正在寻找超快速高斯模糊解决方案,用于cocos2d-x。


2
我不了解cocos2d,但你可以轻松地通过以下步骤实现:1. 捕捉原始屏幕,2. 对于每一帧,使用前一帧作为输入,应用小半径高斯模糊。存储结果图片。在恢复时,以相反的顺序显示存储的图片。 - geza
2
如果您需要动画图像模糊,则可以使用可变宽度高斯模糊:对于小模糊,您可以直接将高斯模糊应用于输入以进行模糊处理。对于较大的模糊,请先缩小输入,然后对其应用较小的高斯模糊(2D高斯模糊是可分离的,因此不会很慢)。 - geza
{btsdaf} - Yakk - Adam Nevraumont
{btsdaf} - geza
你的问题仍然太宽泛了,关于代码的问题不能依赖于像Github这样的外部链接。需要在问题中提供重现问题所需的代码。 - user229044
@KAMIKAZE,你可以随时编辑你的问题,但不应该根据已经得到的答案进行编辑。你的问题需要保持不变,以便答案仍然有效。 - user229044
1个回答

24

通过学习 “Cocos2d-X中的后处理效果”“RENDERTEXTURE + BLUR”,我找到了下面的解决方案。

在Cocos2d-X中实现后处理效果的常见方法是使用图层。场景是一层,后处理是另一层,它以场景层作为输入。借助这种技术,后处理可以对渲染后的场景进行操作。

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

这意味着我们需要在Cocos2d-X中使用2个后处理图层。所以我们需要3层,一层是场景,另外两层是后处理:

// 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方法中,必须将后处理应用于场景(稍后我将描述如何设置uniforms):

// 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;
};

后处理.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;
}
着色器需要一个包含模糊算法偏移量的uniform变量 (u_blurOffset)。这是第一个模糊通道中两个像素间距离的X轴距离和第二个模糊通道中两个纹理元素间距离的Y轴距离。
模糊效果的强度由uniform变量 (u_blurStrength) 设置。其中0.0表示模糊关闭,1.0表示最大模糊程度。最大模糊效果由MAX_BLUR_WIDHT的值定义,它定义了在每个方向上查看的纹理元素的范围。因此,这或多或少是模糊半径。如果增加该值,模糊效果将增加,但性能会降低。如果减小该值,则模糊效果将减小,但性能将提高。幸运的是,由于近似的两次实现,性能和MAX_BLUR_WIDHT值之间的关系是线性的(而不是二次的)。
我决定避免预先计算高斯权重并将其传递到着色器中(高斯权重将取决于MAX_BLUR_WIDHTu_blurStrength)。相反,我使用了一个平滑的Hermite插值,类似于GLSL函数smoothstep
attribute 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;
}

模糊.frag

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);
}


完整的C++和GLSL源代码可以在GitHub上找到(实现可以通过bool HelloWorld::m_blurFast = false激活)。

查看预览:
preview

每个模糊半径单独使用着色器

高性能版本的高斯模糊算法是GPUImage-x介绍的解决方案。在这种实现中,为每个模糊半径创建了单独的模糊着色器。完整的 cocos2d-x 演示实现的源代码可以在GitHub上找到。该实现提供了两个变体,标准实现和优化实现,如链接中所述,可以通过 bool GPUimageBlur::m_optimized 进行设置。该实现为每个半径从0到 int GPUimageBlur::m_maxRadius 生成一个着色器和一个sigma float GPUimageBlur::m_sigma

查看预览:
preview

快速限制质量模糊

一个更加强大的解决方案,但明显质量非常低的是使用Optimizing Gaussian blurs on a mobile GPU中介绍的着色器。模糊不是动态的,只能开启或关闭:

update方法

// 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;
}

片段着色器

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;
}

查看预览:
在此输入图片描述


完整的C++和GLSL源代码可以在GitHub上找到(实现可以通过bool HelloWorld::m_blurFast进行切换)。


两层(帧缓冲区)逐步解决方案

这种解决方案的想法是对场景进行平滑、逐步、高质量的模糊处理。为此,需要一种弱但快速且高质量的模糊算法。模糊的精灵不会被删除,它将被存储到游戏引擎的下一次刷新中,并用作下一次模糊步骤的源。这意味着弱模糊的精灵再次变得模糊,因此比上一个更加模糊。这是一个渐进的过程,最终形成一个强大而准确的模糊精灵。
为了设置此过程,需要3个图层,即游戏图层和2个模糊图层(偶数和奇数)。

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个图层都是不可见的。

update方法中,一个图层被设置为可见状态。如果没有模糊,则游戏层是可见的。一旦开始模糊,游戏层将使用模糊着色器渲染到偶数层,游戏层变得不可见,而偶数层变得可见。在下一个循环中,偶数层将使用模糊着色器渲染到奇数层,偶数层变得不可见,而奇数层变得可见。这个过程会继续进行,直到模糊停止。同时,场景变得越来越模糊,质量高。 如果需要再次显示原始场景,则必须将游戏层设置为可见状态,并将偶数奇数层设置为不可见状态。

update方法:

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 );

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

顶点着色器

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;
}

片段着色器:

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;
}


C++和GLSL源代码的完整版本可以在GitHub上找到。

预览:
enter image description here


为什么“分离”X和Y的“近似”有效(高斯模糊是可分离的;唯一的损失来自四舍五入),以及为什么反复进行小模糊有效(高斯模糊核“相乘”成更大的高斯模糊核)。顺便说一下,你的“优化”版本似乎有bug,因为那不像我见过的任何高斯模糊。 - Yakk - Adam Nevraumont
{btsdaf} - Yakk - Adam Nevraumont
@Yakk 哦。当然你是对的。我必须修改我的答案。这个答案的一部分完全不重要且错误。所以我现在会移除它。 - Rabbid76
1
不错。但我怀疑优化模糊的低质量可能是由于采样函数中缺乏硬件插值造成的。优化模糊的数学似乎表明它没有质量损失;如果我们缺少插值,我会期望我们会得到像你上面的图像那样的伪影。我在cocos2d方面缺乏足够的技能来看到它将去哪里,我只是基于对链接优化代码正在做什么的黑盒理解。 (顺便说一句,这是摇滚巨星和超越!Rabbid76的好答案) - Yakk - Adam Nevraumont
1
@Rabbid76 我明白。家庭是最重要的事情。如果你突然有时间..我会一直在这里等待答案。我的游戏仍在开发中.. - KAMIKAZE
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接