暂停时的快速高斯模糊
Fast Gaussian blur at pause
在 cocos2d-x 中,我需要实现快速高斯模糊,它应该是这样的(我刚刚在 App Store 上找到了一些已经完成的游戏模糊,统一):
所以,当用户暂停游戏时,fadeIn-fadeOut 模糊效果很好。
GPUImage 已经有我需要的快速模糊,但我找不到 cocos2d-x 的解决方案。
v1 code when it was (GPUImage v1) Objective
C
- GPUImage-x C++ 版本
Here is result of live camera view using GPUImage2 - 在 iPod Touch 5G 上测试过,它在这个缓慢而陈旧的设备上运行速度很快。
GPUImage 中的模糊处理速度非常快,即使在非常慢的设备(如 iPod Touch 5G)上也是如此。
寻找 cocos2d-x 超快高斯模糊的解决方案。
经过研究"Post-Processing Effects in Cocos2d-X" and "RENDERTEXTURE + BLUR",我得出了以下解决方案。
在Cocos2s-X中实现post处理效果的常用方法是实现图层。场景是一层,一个post进程是另一层,它以场景层作为输入。使用这种技术,post 进程可以操纵渲染的场景。
模糊算法是在着色器中实现的。对场景应用模糊效果的一种常见方法是首先沿视口的 X 轴进行模糊,然后沿视口的 Y 轴进行第二次模糊(参见 ShaderLesson5)。这是一个可以接受的近似值,可以显着提高性能。
这意味着,我们在 Cocos2s-X 中需要 2 个 post 进程层。所以我们需要 3 层,一层用于场景,2 层用于 post 进程:
// 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
的方法中,post的流程要应用到场景中(后面会介绍制服的设置):
// 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);
对于 post 流程的管理,我实施了 class 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 pass 实现。
我决定避免预先计算高斯权重并将它们传递给着色器(高斯权重将取决于 MAX_BLUR_WIDHT
和 u_blurStrength
)。相反,我使用了平滑的 Hermite interpolation similar to the GLSL function smoothstep
:
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. In this implementation a separated blur shader for each blur radius is created. The source code of the full cocos2d-x demo implementation can be found at GitHub 中提出的解决方案。该实现提供了 2 个变体,标准实现和优化实现,如 link 中的实现,可以通过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` 方法中,一层设置为状态 visible。如果没有模糊,则游戏层是可见的。模糊开始后,游戏层将使用模糊着色器渲染到 even 层。游戏层变得不可见,even层变得可见。在下一个循环中,even 层使用模糊着色器渲染到 odd 层。 even层不可见,odd层可见。这个过程一直持续到模糊停止为止。同时,场景变得越来越模糊,质量很高。
如果要再次显示原场景,那么游戏层已经设置为可见,偶数和奇数层必须设置为不可见。
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.
上找到
查看预览:
在 cocos2d-x 中,我需要实现快速高斯模糊,它应该是这样的(我刚刚在 App Store 上找到了一些已经完成的游戏模糊,统一):
所以,当用户暂停游戏时,fadeIn-fadeOut 模糊效果很好。
GPUImage 已经有我需要的快速模糊,但我找不到 cocos2d-x 的解决方案。
v1 code when it was (GPUImage v1) Objective C
- GPUImage-x C++ 版本
Here is result of live camera view using GPUImage2 - 在 iPod Touch 5G 上测试过,它在这个缓慢而陈旧的设备上运行速度很快。
GPUImage 中的模糊处理速度非常快,即使在非常慢的设备(如 iPod Touch 5G)上也是如此。
寻找 cocos2d-x 超快高斯模糊的解决方案。
经过研究"Post-Processing Effects in Cocos2d-X" and "RENDERTEXTURE + BLUR",我得出了以下解决方案。
在Cocos2s-X中实现post处理效果的常用方法是实现图层。场景是一层,一个post进程是另一层,它以场景层作为输入。使用这种技术,post 进程可以操纵渲染的场景。
模糊算法是在着色器中实现的。对场景应用模糊效果的一种常见方法是首先沿视口的 X 轴进行模糊,然后沿视口的 Y 轴进行第二次模糊(参见 ShaderLesson5)。这是一个可以接受的近似值,可以显着提高性能。
这意味着,我们在 Cocos2s-X 中需要 2 个 post 进程层。所以我们需要 3 层,一层用于场景,2 层用于 post 进程:
// 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
的方法中,post的流程要应用到场景中(后面会介绍制服的设置):
// 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);
对于 post 流程的管理,我实施了 class 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 pass 实现。
我决定避免预先计算高斯权重并将它们传递给着色器(高斯权重将取决于 MAX_BLUR_WIDHT
和 u_blurStrength
)。相反,我使用了平滑的 Hermite interpolation similar to the GLSL function smoothstep
:
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. In this implementation a separated blur shader for each blur radius is created. The source code of the full cocos2d-x demo implementation can be found at GitHub 中提出的解决方案。该实现提供了 2 个变体,标准实现和优化实现,如 link 中的实现,可以通过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` 方法中,一层设置为状态 visible。如果没有模糊,则游戏层是可见的。模糊开始后,游戏层将使用模糊着色器渲染到 even 层。游戏层变得不可见,even层变得可见。在下一个循环中,even 层使用模糊着色器渲染到 odd 层。 even层不可见,odd层可见。这个过程一直持续到模糊停止为止。同时,场景变得越来越模糊,质量很高。 如果要再次显示原场景,那么游戏层已经设置为可见,偶数和奇数层必须设置为不可见。
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.
查看预览: