暂停时的快速高斯模糊[关闭]

Posted

技术标签:

【中文标题】暂停时的快速高斯模糊[关闭]【英文标题】:Fast Gaussian blur at pause [closed] 【发布时间】:2018-03-26 11:48:43 【问题描述】:

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

所以,当用户暂停游戏时,fadeIn-fadeOut 模糊效果很好。

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

v1 code when it was (GPUImage v1) 目标 C

v2 code when is now Swift(GPUImage v2)斯威夫特

GPUImage-xC++版

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

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

【问题讨论】:

我不知道 cocos2d,但你可以很容易地做到这一点:1. 捕获原始屏幕,2. 为每一帧应用小半径高斯模糊,使用前一帧作为输入。存储生成的图片。在恢复时,以相反的顺序显示存储的图片。 如果您需要动画图像模糊,那么:您可以使用可变宽度的高斯模糊:对于小模糊,您可以通过简单地对其应用高斯模糊来模糊输入。对于较大的模糊,首先缩小输入,然后对其应用较小的高斯模糊(2d 高斯模糊是可分离的,因此不会很慢)。 @geza 有趣的是,数学上最体面的质量下采样包括模糊输入然后点采样(诚然不是高斯模糊)。对于更大的模糊,您可以计算模糊的内核,对图像和内核进行 FFT,逐点相乘,然后对结果进行反向 FFT(在高斯半径中为 O(1),而不是 O(n),所以当您的模糊需要超过几十个像素大小时开始实用)。 @Yakk:是的,如果您的输入图像有很多高频内容,那么用于缩小的简单框过滤器可能不够用。但通常,对于实时移动应用程序,box filter 是可以的(很多年前,我已经尝试过 truncated sinc 过滤器,大多数时候,没有视觉差异,所以不值得麻烦+额外的计算成本)。而且我从未见过有人使用 FFT 进行游戏的实时模糊 :)。缩小 + 高斯模糊通常是真实高斯模糊的一个非常好的近似值(至少,它在视觉上和真实的一样吸引人)。 你的问题还是太笼统了,关于代码的问题不能依赖外部链接,比如你的 Github repo。重现您的问题所需的代码需要放在问题中。 【参考方案1】:

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

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

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

这意味着,我们在 Cocos2s-X 中需要 2 个后期处理层。所以我们需要 3 层,1 层用于场景,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_WIDHTu_blurStrength)。相反,我使用了类似于 GLSL 函数 smoothstep 的平滑 Hermite interpolation:

blur.vert

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;

blur.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 激活实现)。

查看预览:

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

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

查看预览:

快速有限质量模糊

一个更强大但质量明显非常低的解决方案是使用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 );

Vetex 着色器

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`方法中,一层设置为状态可见。如果没有模糊,则游戏层可见。一旦开始模糊,游戏层就会使用模糊着色器渲染到 even 层。游戏层变得不可见,而 even 层变得可见。在下一个循环中,even 层被渲染到 odd 层,带有模糊着色器。 even 层变得不可见,而 odd 层变得可见。这个过程一直持续到模糊停止。同时,场景变得越来越模糊,质量很高。 如果要再次显示原始场景,则游戏图层已设置为可见,evenodd 图层必须设置为不可见。

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 模糊着色器:

Vetex 着色器

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 上找到。

查看预览:

【讨论】:

快速描述为什么分离 X 和 Y 的“近似”有效(高斯模糊是可分离的;唯一的损失是四舍五入),以及为什么反复进行小模糊有效(高斯模糊内核“成倍增加”) "变成更大的高斯模糊内核)。顺便说一句,您的“优化”似乎有错误,因为这看起来不像我见过的任何高斯模糊。 @Yakk 你的意思是如果我删除优化的那我会改进答案吗? @Rabbid76 不,我的意思是我怀疑优化的有一个实现错误。也许在您的测试套件或您的实现中。我的意思是,它看起来像只对角线的环形效果,而不是高斯模糊。根据我的经验,错误的内核使得高斯模糊变成对角线模糊似乎比该网站的优化模糊那么糟糕更有可能。链接页面上显示的示例没有相同类型的工件。 不错。但我怀疑优化模糊的质量较低可能是因为采样函数中缺少硬件插值。优化模糊的数学似乎表明它没有质量损失。我希望如果我们缺少插值,我们会得到像您上面的图像一样的伪影。我在 cocos2d 方面缺乏足够的技能来了解它的发展方向,我只是基于对链接优化代码正在做什么的黑盒理解。 (顺便说一句,这是摇滚明星和超越!很好的答案 Rabbid76) @Rabbid76 我明白这一点。家庭是最重要的。如果你突然有时间..我会一直在这里等待答案。我的游戏还在开发中..

以上是关于暂停时的快速高斯模糊[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

最快的高斯模糊不起作用

android 高斯模糊怎么还原

android能对背景做高斯模糊么

【iOS开发】生成高斯模糊效果背景

比较中值模糊和高斯模糊对高斯噪声处理效果

webgl智慧楼宇发光效果算法系列之高斯模糊