【Cocos2d-x】图片描边的一种比较好的shader实现方法
2017-07-18 09:17
489 查看
转载: http://blog.csdn.net/u011281572/article/details/44999609
图片描边需求如下:
1. 可指定描边宽度
2. 可指定描边颜色3. 可用于字体
图片描边我所知道的方式有以下几种:
1. Cocos2d-x 3.x中,字体用FreeType库,字体描边可以用FreeType自带的描边功能,实际效果没测过,但只能用于字体。
2. 用RenderTexture,方法大概是把原图做一圈的偏移,渲染到同一张纹理上,他们相隔中心点的距离都是r,最后再把自己渲染到中间,核心代码大概这样:
[cpp] view
plain copy
rt->begin();
for(int i = 0; i < 360; i += 15)
{
float rad = CC_DEGREES_TO_RADIANS(i);
m_label->setPosition(ccp(
textureSize.width * 0.5f + sin(rad) * r,
textureSize.height * 0.5f + cos(rad) * r));
m_label->visit();
}
m_label->setColor(col);
m_label->setBlendFunc(originalBlend);
m_label->setPosition(ccp(textureSize.width * 0.5f, textureSize.height * 0.5f));
m_label->visit();
rt->end();
这种方法可以作为一个比较好的解决方案,但是我认为这种方式在生成描边图片时,需要绘制多个图片,效率不是很好。
3. Cocos2d-x 3.x的ShaderTest有个描边的例子,是用shader实现的,不过那个程序有些不友好:描边宽度不是传像素进去,而是一个0~1的数字,是一个比例,就是说大的图片描边大,小的图片描边小,而且同一个图片不同位置的描边也宽窄不一(这在长比宽大很多的图片尤其明显),而且描边的颜色也深浅不一。
以下我分享一种我认为比较好的描边算法:
在片段着色器里面,对于每个像素:1. 如果它是不透明的像素,则不管,维持原本颜色;2. 如果透明,是360度判断它四周有没有不透明的像素,如果有,则把它设成描边颜色,否则保持透明。
我为代码加了比较详细的注释,希望大家能理解
stroke.fsh:描边片段着色器
[cpp] view
plain copy
varying vec4 v_fragmentColor; // vertex shader传入,setColor设置的颜色
varying vec2 v_texCoord; // 纹理坐标
uniform float outlineSize; // 描边宽度,以像素为单位
uniform vec3 outlineColor; // 描边颜色
uniform vec2 textureSize; // 纹理大小(宽和高),为了计算周围各点的纹理坐标,必须传入它,因为纹理坐标范围是0~1
uniform vec3 foregroundColor; // 主要用于字体,可传可不传,不传默认为白色
// 判断在这个角度上距离为outlineSize那一点是不是透明
int getIsStrokeWithAngel(float angel)
{
int stroke = 0;
float rad = angel * 0.01745329252; // 这个浮点数是 pi / 180,角度转弧度
float a = texture2D(CC_Texture0, vec2(v_texCoord.x + outlineSize * cos(rad) / textureSize.x, v_texCoord.y + outlineSize * sin(rad) / textureSize.y)).a; // 这句比较难懂,outlineSize * cos(rad)可以理解为在x轴上投影,除以textureSize.x是因为texture2D接收的是一个0~1的纹理坐标,而不是像素坐标
if (a >= 0.5)// 我把alpha值大于0.5都视为不透明,小于0.5都视为透明
{
stroke = 1;
}
return stroke;
}
void main()
{
vec4 myC = texture2D(CC_Texture0, vec2(v_texCoord.x, v_texCoord.y)); // 正在处理的这个像素点的颜色
myC.rgb *= foregroundColor;
if (myC.a >= 0.5) // 不透明,不管,直接返回
{
gl_FragColor = v_fragmentColor * myC;
return;
}
// 这里肯定有朋友会问,一个for循环就搞定啦,怎么这么麻烦!其实我一开始也是用for的,但后来在安卓某些机型(如小米4)会直接崩溃,查找资料发现OpenGL es并不是很支持循环,while和for都不要用
int strokeCount = 0;
strokeCount += getIsStrokeWithAngel(0.0);
strokeCount += getIsStrokeWithAngel(30.0);
strokeCount += getIsStrokeWithAngel(60.0);
strokeCount += getIsStrokeWithAngel(90.0);
strokeCount += getIsStrokeWithAngel(120.0);
strokeCount += getIsStrokeWithAngel(150.0);
strokeCount += getIsStrokeWithAngel(180.0);
strokeCount += getIsStrokeWithAngel(210.0);
strokeCount += getIsStrokeWithAngel(240.0);
strokeCount += getIsStrokeWithAngel(270.0);
strokeCount += getIsStrokeWithAngel(300.0);
strokeCount += getIsStrokeWithAngel(330.0);
if (strokeCount > 0) // 四周围至少有一个点是不透明的,这个点要设成描边颜色
{
myC.rgb = outlineColor;
myC.a = 1.0;
}
gl_FragColor = v_fragmentColor * myC;
}
我的utilShader.cpp:
[cpp] view
plain copy
const char* shaderNameStroke = "ShjyShader_Stroke";
namespace utilShader
{
// 传入描边宽度(像素为单位),描边颜色,图片大小,获得GLProgramState
cocos2d::GLProgramState* getStrokeProgramState( float outlineSize, cocos2d::Color3B outlineColor, cocos2d::Size textureSize, cocos2d::Color3B foregroundColor/* = cocos2d::Color3B::WHITE*/ )
{
auto glprogram = GLProgramCache::getInstance()->getGLProgram(shaderNameStroke);
if (!glprogram)
{
std::string fragmentSource = FileUtils::getInstance()->getStringFromFile(FileUtils::getInstance()->fullPathForFilename("shaders/stroke.fsh"));
glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, fragmentSource.c_str());
GLProgramCache::getInstance()->addGLProgram(glprogram, shaderNameStroke);
}
auto glprogramState = GLProgramState::create(glprogram);
glprogramState->setUniformFloat("outlineSize", outlineSize);
glprogramState->setUniformVec3("outlineColor", Vec3(outlineColor.r / 255.0f, outlineColor.g / 255.0f, outlineColor.b / 255.0f));
glprogramState->setUniformVec2("textureSize", Vec2(textureSize.width, textureSize.height));
glprogramState->setUniformVec3("foregroundColor", Vec3(foregroundColor.r / 255.0f, foregroundColor.g / 255.0f, foregroundColor.b / 255.0f));
return glprogramState;
}
}
调用的地方:
[cpp] view
plain copy
Sprite* spr = Sprite::create("elephant1_Diffuse.png");
spr->setPosition(200, 200);
spr->setGLProgramState(utilShader::getStrokeProgramState(5, Color3B::GREEN, spr->getContentSize()));
this->addChild(spr, 1);
效果:
效果还算是比较好的,经测试,此算法在安卓多个机型上也表现良好。
这样一套完整的描边算法就完成了,如果描述有不当之处,或有更优方法,欢迎吐槽。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
谢谢complex_ok的吐槽,应该预先计算好sin和cos值,无需每次计算。优化后的 stroke.fsh 如下:
[cpp] view
plain copy
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform float outlineSize;
uniform vec3 outlineColor;
uniform vec2 textureSize;
uniform vec3 foregroundColor;
const float cosArray[12] = {1, 0.866, 0.5, 0, -0.5, -0.866, -0.1, -0.866, -0.5, 0, 0.5, 0.866};
const float sinArray[12] = {0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5};
int getIsStrokeWithAngelIndex(int index)
{
int stroke = 0;
float a = texture2D(CC_Texture0, vec2(v_texCoord.x + outlineSize * cosArray[index] / textureSize.x, v_texCoord.y + outlineSize * sinArray[index] / textureSize.y)).a;
if (a >= 0.5)
{
stroke = 1;
}
return stroke;
}
void main()
{
vec4 myC = texture2D(CC_Texture0, vec2(v_texCoord.x, v_texCoord.y));
myC.rgb *= foregroundColor;
if (myC.a >= 0.5)
{
gl_FragColor = v_fragmentColor * myC;
return;
}
int strokeCount = 0;
strokeCount += getIsStrokeWithAngelIndex(0);
strokeCount += getIsStrokeWithAngelIndex(1);
strokeCount += getIsStrokeWithAngelIndex(2);
strokeCount += getIsStrokeWithAngelIndex(3);
strokeCount += getIsStrokeWithAngelIndex(4);
strokeCount += getIsStrokeWithAngelIndex(5);
strokeCount += getIsStrokeWithAngelIndex(6);
strokeCount += getIsStrokeWithAngelIndex(7);
strokeCount += getIsStrokeWithAngelIndex(8);
strokeCount += getIsStrokeWithAngelIndex(9);
strokeCount += getIsStrokeWithAngelIndex(10);
strokeCount += getIsStrokeWithAngelIndex(11);
bool stroke = false;
if (strokeCount > 0)
{
stroke = true;
}
if (stroke)
{
myC.rgb = outlineColor;
myC.a = 1.0;
}
gl_FragColor = v_fragmentColor * myC;
}
图片描边需求如下:
1. 可指定描边宽度
2. 可指定描边颜色3. 可用于字体
图片描边我所知道的方式有以下几种:
1. Cocos2d-x 3.x中,字体用FreeType库,字体描边可以用FreeType自带的描边功能,实际效果没测过,但只能用于字体。
2. 用RenderTexture,方法大概是把原图做一圈的偏移,渲染到同一张纹理上,他们相隔中心点的距离都是r,最后再把自己渲染到中间,核心代码大概这样:
[cpp] view
plain copy
rt->begin();
for(int i = 0; i < 360; i += 15)
{
float rad = CC_DEGREES_TO_RADIANS(i);
m_label->setPosition(ccp(
textureSize.width * 0.5f + sin(rad) * r,
textureSize.height * 0.5f + cos(rad) * r));
m_label->visit();
}
m_label->setColor(col);
m_label->setBlendFunc(originalBlend);
m_label->setPosition(ccp(textureSize.width * 0.5f, textureSize.height * 0.5f));
m_label->visit();
rt->end();
这种方法可以作为一个比较好的解决方案,但是我认为这种方式在生成描边图片时,需要绘制多个图片,效率不是很好。
3. Cocos2d-x 3.x的ShaderTest有个描边的例子,是用shader实现的,不过那个程序有些不友好:描边宽度不是传像素进去,而是一个0~1的数字,是一个比例,就是说大的图片描边大,小的图片描边小,而且同一个图片不同位置的描边也宽窄不一(这在长比宽大很多的图片尤其明显),而且描边的颜色也深浅不一。
以下我分享一种我认为比较好的描边算法:
在片段着色器里面,对于每个像素:1. 如果它是不透明的像素,则不管,维持原本颜色;2. 如果透明,是360度判断它四周有没有不透明的像素,如果有,则把它设成描边颜色,否则保持透明。
我为代码加了比较详细的注释,希望大家能理解
stroke.fsh:描边片段着色器
[cpp] view
plain copy
varying vec4 v_fragmentColor; // vertex shader传入,setColor设置的颜色
varying vec2 v_texCoord; // 纹理坐标
uniform float outlineSize; // 描边宽度,以像素为单位
uniform vec3 outlineColor; // 描边颜色
uniform vec2 textureSize; // 纹理大小(宽和高),为了计算周围各点的纹理坐标,必须传入它,因为纹理坐标范围是0~1
uniform vec3 foregroundColor; // 主要用于字体,可传可不传,不传默认为白色
// 判断在这个角度上距离为outlineSize那一点是不是透明
int getIsStrokeWithAngel(float angel)
{
int stroke = 0;
float rad = angel * 0.01745329252; // 这个浮点数是 pi / 180,角度转弧度
float a = texture2D(CC_Texture0, vec2(v_texCoord.x + outlineSize * cos(rad) / textureSize.x, v_texCoord.y + outlineSize * sin(rad) / textureSize.y)).a; // 这句比较难懂,outlineSize * cos(rad)可以理解为在x轴上投影,除以textureSize.x是因为texture2D接收的是一个0~1的纹理坐标,而不是像素坐标
if (a >= 0.5)// 我把alpha值大于0.5都视为不透明,小于0.5都视为透明
{
stroke = 1;
}
return stroke;
}
void main()
{
vec4 myC = texture2D(CC_Texture0, vec2(v_texCoord.x, v_texCoord.y)); // 正在处理的这个像素点的颜色
myC.rgb *= foregroundColor;
if (myC.a >= 0.5) // 不透明,不管,直接返回
{
gl_FragColor = v_fragmentColor * myC;
return;
}
// 这里肯定有朋友会问,一个for循环就搞定啦,怎么这么麻烦!其实我一开始也是用for的,但后来在安卓某些机型(如小米4)会直接崩溃,查找资料发现OpenGL es并不是很支持循环,while和for都不要用
int strokeCount = 0;
strokeCount += getIsStrokeWithAngel(0.0);
strokeCount += getIsStrokeWithAngel(30.0);
strokeCount += getIsStrokeWithAngel(60.0);
strokeCount += getIsStrokeWithAngel(90.0);
strokeCount += getIsStrokeWithAngel(120.0);
strokeCount += getIsStrokeWithAngel(150.0);
strokeCount += getIsStrokeWithAngel(180.0);
strokeCount += getIsStrokeWithAngel(210.0);
strokeCount += getIsStrokeWithAngel(240.0);
strokeCount += getIsStrokeWithAngel(270.0);
strokeCount += getIsStrokeWithAngel(300.0);
strokeCount += getIsStrokeWithAngel(330.0);
if (strokeCount > 0) // 四周围至少有一个点是不透明的,这个点要设成描边颜色
{
myC.rgb = outlineColor;
myC.a = 1.0;
}
gl_FragColor = v_fragmentColor * myC;
}
我的utilShader.cpp:
[cpp] view
plain copy
const char* shaderNameStroke = "ShjyShader_Stroke";
namespace utilShader
{
// 传入描边宽度(像素为单位),描边颜色,图片大小,获得GLProgramState
cocos2d::GLProgramState* getStrokeProgramState( float outlineSize, cocos2d::Color3B outlineColor, cocos2d::Size textureSize, cocos2d::Color3B foregroundColor/* = cocos2d::Color3B::WHITE*/ )
{
auto glprogram = GLProgramCache::getInstance()->getGLProgram(shaderNameStroke);
if (!glprogram)
{
std::string fragmentSource = FileUtils::getInstance()->getStringFromFile(FileUtils::getInstance()->fullPathForFilename("shaders/stroke.fsh"));
glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, fragmentSource.c_str());
GLProgramCache::getInstance()->addGLProgram(glprogram, shaderNameStroke);
}
auto glprogramState = GLProgramState::create(glprogram);
glprogramState->setUniformFloat("outlineSize", outlineSize);
glprogramState->setUniformVec3("outlineColor", Vec3(outlineColor.r / 255.0f, outlineColor.g / 255.0f, outlineColor.b / 255.0f));
glprogramState->setUniformVec2("textureSize", Vec2(textureSize.width, textureSize.height));
glprogramState->setUniformVec3("foregroundColor", Vec3(foregroundColor.r / 255.0f, foregroundColor.g / 255.0f, foregroundColor.b / 255.0f));
return glprogramState;
}
}
调用的地方:
[cpp] view
plain copy
Sprite* spr = Sprite::create("elephant1_Diffuse.png");
spr->setPosition(200, 200);
spr->setGLProgramState(utilShader::getStrokeProgramState(5, Color3B::GREEN, spr->getContentSize()));
this->addChild(spr, 1);
效果:
效果还算是比较好的,经测试,此算法在安卓多个机型上也表现良好。
这样一套完整的描边算法就完成了,如果描述有不当之处,或有更优方法,欢迎吐槽。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
谢谢complex_ok的吐槽,应该预先计算好sin和cos值,无需每次计算。优化后的 stroke.fsh 如下:
[cpp] view
plain copy
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform float outlineSize;
uniform vec3 outlineColor;
uniform vec2 textureSize;
uniform vec3 foregroundColor;
const float cosArray[12] = {1, 0.866, 0.5, 0, -0.5, -0.866, -0.1, -0.866, -0.5, 0, 0.5, 0.866};
const float sinArray[12] = {0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5};
int getIsStrokeWithAngelIndex(int index)
{
int stroke = 0;
float a = texture2D(CC_Texture0, vec2(v_texCoord.x + outlineSize * cosArray[index] / textureSize.x, v_texCoord.y + outlineSize * sinArray[index] / textureSize.y)).a;
if (a >= 0.5)
{
stroke = 1;
}
return stroke;
}
void main()
{
vec4 myC = texture2D(CC_Texture0, vec2(v_texCoord.x, v_texCoord.y));
myC.rgb *= foregroundColor;
if (myC.a >= 0.5)
{
gl_FragColor = v_fragmentColor * myC;
return;
}
int strokeCount = 0;
strokeCount += getIsStrokeWithAngelIndex(0);
strokeCount += getIsStrokeWithAngelIndex(1);
strokeCount += getIsStrokeWithAngelIndex(2);
strokeCount += getIsStrokeWithAngelIndex(3);
strokeCount += getIsStrokeWithAngelIndex(4);
strokeCount += getIsStrokeWithAngelIndex(5);
strokeCount += getIsStrokeWithAngelIndex(6);
strokeCount += getIsStrokeWithAngelIndex(7);
strokeCount += getIsStrokeWithAngelIndex(8);
strokeCount += getIsStrokeWithAngelIndex(9);
strokeCount += getIsStrokeWithAngelIndex(10);
strokeCount += getIsStrokeWithAngelIndex(11);
bool stroke = false;
if (strokeCount > 0)
{
stroke = true;
}
if (stroke)
{
myC.rgb = outlineColor;
myC.a = 1.0;
}
gl_FragColor = v_fragmentColor * myC;
}
相关文章推荐
- 左侧图片 右侧块的实现方法---解决3像素bug的一种解决方案,不用浮动用绝对定位和margin-left
- php实现图片添加描边字和马赛克的方法
- ListView异步加载图片是非常实用的方法,凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,下面就说实现方法,先贴上主方法的代码:
- Android 自定义View修炼-Android实现圆形、圆角和椭圆自定义图片View(使用BitmapShader图形渲染方法)
- 一种基于im客户端实现消息图片管理的方法和装置
- 将图片转化为文本的一种实现方法
- php实现图片添加描边字和马赛克的方法
- cocos2d-x 利用CCLabelTTF制作文字描边与阴影效果的实现方法
- cocos2d-x 2.2.0 图片选中聚焦 ,图片描边 CCClippingNode 实现
- Android自定义“图片+文字”控件四种实现方法之 二--------个人最推荐的一种
- 3.顶点外扩方法实现的描边shader
- cocos2d-x之字体描边效果shader实现
- cocos2d-x 2.2.0 图片选中聚焦 ,图片描边 CCClippingNode 实现
- cocos2d-x 利用CCLabelTTF制作文字描边与阴影效果的实现方法
- js两种实现网页图片滚动的方法 和一种切换的方法
- Android自定义“图片+文字”控件四种实现方法之 二--------个人最推荐的一种
- cocos2d-x 利用CCLabelTTF制作文字描边与阴影效果的实现方法
- cocos2d-x 利用CCLabelTTF制作文字描边与阴影效果的实现方法
- Android自定义“图片+文字”控件实现方法之 --------个人最推荐的一种