SpriteBlur类是cocos2d-x例程中的一个类,用于将一个精灵模糊。
由于项目需要模糊场景中的精灵,于是笔者把该类提取出来,未做任何修改用到自己的工程中。
实际应用发现奇怪的现象:
cocos2d-x_v3.0中提供的SpriteBlur,精灵模糊后基本是正常的,边缘的地方偶尔会闪出黑边。
cocos2d-x_v3.2中提供的SpriteBlur,精灵模糊后原本是透明像素的部分变成了黑色,即便模糊半径设置为0(即不模糊)也一样。
由于项目需要模糊场景中的精灵,于是笔者把该类提取出来,未做任何修改用到自己的工程中。
实际应用发现奇怪的现象:
cocos2d-x_v3.0中提供的SpriteBlur,精灵模糊后基本是正常的,边缘的地方偶尔会闪出黑边。
cocos2d-x_v3.2中提供的SpriteBlur,精灵模糊后原本是透明像素的部分变成了黑色,即便模糊半径设置为0(即不模糊)也一样。
请看下图
左侧是v3.2的效果,采样数设置的是3,模糊半径10。
右侧是v3.0的效果,采样数不能设置, 模糊半径10。
检查 SpriteBlur.h、SpriteBlur.cpp 中的代码,没有关于像素绘制的部分,而是将这部分工作交给了着色器“example_Blur.fsh”
......
void main(void)
{
vec3 col = blur(v_texCoord);
gl_FragColor = vec4(col, 1.0) * v_fragmentColor;
}
OK,问题就在给gl_FragColor赋值这一句,vec4(col, 1.0),强制把alpha值设置成了1,所以透明像素会"黑"。做如下修改:
gl_FragColor = vec4(col, texture2D(CC_Texture0, v_texCoord).a) * v_fragmentColor;
即将alpha值设置为当前纹理坐标处的alpha值,运行得到下面的效果:
Oz,头像边缘出现了锯齿和白边,为啥会这样呢?修改:
gl_FragColor = v_fragmentColor;
运行,头像的图片显示成了全白的长方形,白边也许就是因此而来。再看example_Blur.fsh中的blur函数,其中为每一个点的rgb值都进行了加权操作,于是笔者想,如果将alpha值也进行加权操作会怎么样呢?修改example_Blur.fsh:
……
vec4 blur(vec2);
void main(void)
{
vec4 col = blur(v_texCoord);
gl_FragColor = col * v_fragmentColor;
}
vec4 blur(vec2 p)
{
if (blurRadius > 0.0 && sampleNum > 1.0)
{
vec4 col = vec4(0);
vec2 unit = 1.0 / resolution.xy;
float r = blurRadius;
float sampleStep = r / sampleNum;
float count = 0.0;
for(float x = -r; x < r; x += sampleStep)
{
for(float y = -r; y < r; y += sampleStep)
{
float weight = (r - abs(x)) * (r - abs(y));
col += texture2D(CC_Texture0, p + vec2(x * unit.x, y * unit.y)) * weight;
count += weight;
}
}
return col / count;
}
return texture2D(CC_Texture0, p);
}
运行,得到下面的结果:
可以看到,效果与3.0中的效果基本一致了。
但是,还是不太理解,两种修改方法的效果怎么会不一样?高斯模糊应该只是重新分布rgb颜色值吧,怎么alpha也要进行加权操作才能正常?
另外,无论是3.2还是3.0,模糊以后,在任务头顶有一道明显的黑边,不知是何原因,有待探究。
最后附上完整的代码跟着色器文件:
#ifndef __prjHeads__SpriteBlur__
#define __prjHeads__SpriteBlur__
#include "cocos2d.h"
class SpriteBlur : public cocos2d::Sprite
{
public:
~SpriteBlur();
bool initWithTexture(cocos2d::Texture2D* texture, const cocos2d::Rect& rect);
void initGLProgram();
static SpriteBlur* create(const char *pszFileName);
void setBlurRadius(float radius);
void setBlurSampleNum(float num);
protected:
float _blurRadius;
float _blurSampleNum;
};
#endif /* defined(__prjHeads__SpriteBlur__) */
#include "SpriteBlur.h"
USING_NS_CC;
SpriteBlur::~SpriteBlur()
{
}
SpriteBlur* SpriteBlur::create(const char *pszFileName)
{
SpriteBlur* pRet = new SpriteBlur();
if (pRet && pRet->initWithFile(pszFileName))
{
pRet->autorelease();
}
else
{
CC_SAFE_DELETE(pRet);
}
return pRet;
}
bool SpriteBlur::initWithTexture(Texture2D* texture, const Rect& rect)
{
_blurRadius = 0;
if( Sprite::initWithTexture(texture, rect) )
{
#if CC_ENABLE_CACHE_TEXTURE_DATA
auto listener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom* event){
setGLProgram(nullptr);
initGLProgram();
});
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
#endif
initGLProgram();
return true;
}
return false;
}
void SpriteBlur::initGLProgram()
{
GLchar * fragSource = (GLchar*) String::createWithContentsOfFile(
FileUtils::getInstance()->fullPathForFilename("SpriteBlur.fsh").c_str()
)->getCString();
auto program = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, fragSource);
auto glProgramState = GLProgramState::getOrCreateWithGLProgram(program);
setGLProgramState(glProgramState);
auto size = getTexture()->getContentSizeInPixels();
getGLProgramState()->setUniformVec2("resolution", size);
getGLProgramState()->setUniformFloat("blurRadius", _blurRadius);
getGLProgramState()->setUniformFloat("sampleNum", 7.0f);
}
void SpriteBlur::setBlurRadius(float radius)
{
_blurRadius = radius;
getGLProgramState()->setUniformFloat("blurRadius", _blurRadius);
}
void SpriteBlur::setBlurSampleNum(float num)
{
_blurSampleNum = num;
getGLProgramState()->setUniformFloat("sampleNum", _blurSampleNum);
}
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform vec2 resolution;
uniform float blurRadius;
uniform float sampleNum;
vec4 blur(vec2);
void main(void)
{
vec4 col = blur(v_texCoord);
gl_FragColor = col * v_fragmentColor;
}
vec4 blur(vec2 p)
{
if (blurRadius > 0.0 && sampleNum > 1.0)
{
vec4 col = vec4(0);
vec2 unit = 1.0 / resolution.xy;
float r = blurRadius;
float sampleStep = r / sampleNum;
float count = 0.0;
for(float x = -r; x < r; x += sampleStep)
{
for(float y = -r; y < r; y += sampleStep)
{
float weight = (r - abs(x)) * (r - abs(y));
col += texture2D(CC_Texture0, p + vec2(x * unit.x, y * unit.y)) * weight;
count += weight;
}
}
return col / count;
}
return texture2D(CC_Texture0, p);
}