В cocos2d-х Мне нужно реализовать быстрое размытие по Гауссу, и вот как это должно выглядеть (я только что нашел какую-то игру в App Store с уже сделанным таким размытием, в единстве):
Итак, приятно FadeIn—исчезать размытие, когда пользователь делает паузу в игре.
У GPUImage уже есть быстрое размытие, в котором я нуждаюсь, но я не могу найти решение для cocos2d-x.
код v1, когда это было (GPUImage v1) Задача
С
Вот результат просмотра камеры в реальном времени с использованием GPUImage2 — протестирован на iPod Touch 5G, и он работает быстро на этом медленном и старом устройстве.
Размытие в GPUImage работает очень быстро даже на очень медленных устройствах, таких как iPod Touch 5G.
Ищете решение с супер быстрым гауссовым размытием для cocos2d-x.
После изучения «Эффекты постобработки в 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.
Других решений пока нет …