Быстрое размытие по Гауссу при паузе

В cocos2d-х Мне нужно реализовать быстрое размытие по Гауссу, и вот как это должно выглядеть (я только что нашел какую-то игру в App Store с уже сделанным таким размытием, в единстве):

введите описание изображения здесь

Итак, приятно FadeInисчезать размытие, когда пользователь делает паузу в игре.

У GPUImage уже есть быстрое размытие, в котором я нуждаюсь, но я не могу найти решение для cocos2d-x.

Вот результат просмотра камеры в реальном времени с использованием GPUImage2 — протестирован на iPod Touch 5G, и он работает быстро на этом медленном и старом устройстве.

Размытие в GPUImage работает очень быстро даже на очень медленных устройствах, таких как iPod Touch 5G.
Ищете решение с супер быстрым гауссовым размытием для cocos2d-x.

6

Решение

После изучения «Эффекты постобработки в Cocos2d-X» а также «RENDERTEXTURE + BLUR», Я пришел к следующему решению.

Распространенным способом достижения эффектов постобработки в Cocos2s-X является реализация слоев. Сцена — это один слой, а постобработка — это другой слой, который использует слой сцены в качестве входных данных. С помощью этой техники пост-процесс может манипулировать визуализированной сценой.

Алгоритм размытия реализован в шейдере. Обычный способ применения эффекта размытия на сцене — это размытие сначала вдоль оси X окна просмотра, а во втором проходе вдоль оси Y окна просмотра (см. ShaderLesson5). Это приемлемые приближения, которые дают огромный прирост производительности.

Это означает, что нам нужно 2 слоя постобработки в Cocos2s-X. Итак, нам нужно 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;
}

Шейдеру нужен унифор, который содержит смещение для алгоритма размытия (u_blurOffset). Это расстояние между 2 пикселями вдоль оси X для первого прохода размытия и расстоянием между 2 пикселями вдоль оси Y для второго прохода размытия.
Сила эффекта размытия задается единой переменной (u_blurStrength). Где 0.0 означает, что размытие отключено, а 1.0 означает максимальное размытие. Максимальный эффект размытия определяется значением MAX_BLUR_WIDHT, который определяет диапазон текселей, на которые смотрят в каждом направлении. Так что это более или менее радиус размытия. Если вы увеличите это значение, эффект размытия будет увеличиваться, что отрицательно скажется на потере производительности. Если вы уменьшите значение, эффект размытия уменьшится, но вы выиграете производительность. Соотношение между производительностью и стоимостью MAX_BLUR_WIDHT к счастью, линейный (а не квадратичный), из-за приблизительной реализации 2 прохода.
Я решил избежать предварительного вычисления весов Гаусса и передачи их шейдеру (вес Гаусса будет зависеть от MAX_BLUR_WIDHT а также u_blurStrength). Вместо этого я использовал гладкую Эрмитова интерполяция похож на функцию GLSL 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-х. В этой реализации создается отдельный шейдер размытия для каждого радиуса размытия. Исходный код полный cocos2d-х демонстрационная реализация может быть найдена в GitHub.Реализация предоставляет 2 варианта: стандартную реализацию и оптимизированную реализацию, такую ​​как реализация в ссылке, которую можно настроить с помощью bool GPUimageBlur::m_optimized, Реализация генерирует шейдер для каждого радиуса от 0 до int GPUimageBlur::m_maxRadius и сигма float GPUimageBlur::m_sigma,

Смотрите превью:
предварительный просмотр

Быстрое ограниченное качество размытия

Гораздо более мощное решение, но с очевидно, очень низкое качество, будет использовать шейдер, представленный на Оптимизация размытия по Гауссу на мобильном графическом процессоре. Размытие не является динамическим и может быть только включено или выключено:

update Methode:

// 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 shader:

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 Methode:

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 shader:

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.

Смотрите превью:
введите описание изображения здесь

17

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]