【Cocos2d-x】图片描边的一种比较好的shader实现方法

转载: 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
  1. rt->begin();  
  2. for(int i = 0; i < 360; i += 15)  
  3. {  
  4.    float rad = CC_DEGREES_TO_RADIANS(i);  
  5.    m_label->setPosition(ccp(  
  6.    textureSize.width * 0.5f + sin(rad) * r,  
  7.    textureSize.height * 0.5f + cos(rad) * r));  
  8.    m_label->visit();  
  9. }  
  10. m_label->setColor(col);  
  11. m_label->setBlendFunc(originalBlend);  
  12. m_label->setPosition(ccp(textureSize.width * 0.5f, textureSize.height * 0.5f));  
  13. m_label->visit();  
  14. rt->end();  

这种方法可以作为一个比较好的解决方案,但是我认为这种方式在生成描边图片时,需要绘制多个图片,效率不是很好。


3. Cocos2d-x 3.x的ShaderTest有个描边的例子,是用shader实现的,不过那个程序有些不友好:描边宽度不是传像素进去,而是一个0~1的数字,是一个比例,就是说大的图片描边大,小的图片描边小,而且同一个图片不同位置的描边也宽窄不一(这在长比宽大很多的图片尤其明显),而且描边的颜色也深浅不一。



以下我分享一种我认为比较好的描边算法

在片段着色器里面,对于每个像素:1. 如果它是不透明的像素,则不管,维持原本颜色;2. 如果透明,是360度判断它四周有没有不透明的像素,如果有,则把它设成描边颜色,否则保持透明。

我为代码加了比较详细的注释,希望大家能理解微笑


stroke.fsh:描边片段着色器

[cpp]  view plain  copy
  1. varying vec4 v_fragmentColor; // vertex shader传入,setColor设置的颜色  
  2. varying vec2 v_texCoord; // 纹理坐标  
  3. uniform float outlineSize; // 描边宽度,以像素为单位  
  4. uniform vec3 outlineColor; // 描边颜色  
  5. uniform vec2 textureSize; // 纹理大小(宽和高),为了计算周围各点的纹理坐标,必须传入它,因为纹理坐标范围是0~1  
  6. uniform vec3 foregroundColor; // 主要用于字体,可传可不传,不传默认为白色  
  7. // 判断在这个角度上距离为outlineSize那一点是不是透明  
  8. int getIsStrokeWithAngel(float angel)  
  9. {  
  10.     int stroke = 0;  
  11.     float rad = angel * 0.01745329252; // 这个浮点数是 pi / 180,角度转弧度  
  12.     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的纹理坐标,而不是像素坐标  
  13.     if (a >= 0.5)// 我把alpha值大于0.5都视为不透明,小于0.5都视为透明  
  14.     {  
  15.         stroke = 1;  
  16.     }  
  17.     return stroke;  
  18. }  
  19.   
  20. void main()  
  21. {  
  22.     vec4 myC = texture2D(CC_Texture0, vec2(v_texCoord.x, v_texCoord.y)); // 正在处理的这个像素点的颜色  
  23.     myC.rgb *= foregroundColor;  
  24.     if (myC.a >= 0.5) // 不透明,不管,直接返回  
  25.     {  
  26.         gl_FragColor = v_fragmentColor * myC;  
  27.         return;  
  28.     }  
  29.     // 这里肯定有朋友会问,一个for循环就搞定啦,怎么这么麻烦!其实我一开始也是用for的,但后来在安卓某些机型(如小米4)会直接崩溃,查找资料发现OpenGL es并不是很支持循环,while和for都不要用  
  30.     int strokeCount = 0;  
  31.     strokeCount += getIsStrokeWithAngel(0.0);  
  32.     strokeCount += getIsStrokeWithAngel(30.0);  
  33.     strokeCount += getIsStrokeWithAngel(60.0);  
  34.     strokeCount += getIsStrokeWithAngel(90.0);  
  35.     strokeCount += getIsStrokeWithAngel(120.0);  
  36.     strokeCount += getIsStrokeWithAngel(150.0);  
  37.     strokeCount += getIsStrokeWithAngel(180.0);  
  38.     strokeCount += getIsStrokeWithAngel(210.0);  
  39.     strokeCount += getIsStrokeWithAngel(240.0);  
  40.     strokeCount += getIsStrokeWithAngel(270.0);  
  41.     strokeCount += getIsStrokeWithAngel(300.0);  
  42.     strokeCount += getIsStrokeWithAngel(330.0);  
  43.   
  44.     if (strokeCount > 0) // 四周围至少有一个点是不透明的,这个点要设成描边颜色  
  45.     {  
  46.         myC.rgb = outlineColor;  
  47.         myC.a = 1.0;  
  48.     }  
  49.   
  50.     gl_FragColor = v_fragmentColor * myC;  
  51. }  


我的utilShader.cpp:

[cpp]  view plain  copy
  1. const char* shaderNameStroke = "ShjyShader_Stroke";  
  2.   
  3. namespace utilShader  
  4. {  
  5. // 传入描边宽度(像素为单位),描边颜色,图片大小,获得GLProgramState  
  6.     cocos2d::GLProgramState* getStrokeProgramState( float outlineSize, cocos2d::Color3B outlineColor, cocos2d::Size textureSize, cocos2d::Color3B foregroundColor/* = cocos2d::Color3B::WHITE*/ )  
  7.     {  
  8.         auto glprogram = GLProgramCache::getInstance()->getGLProgram(shaderNameStroke);  
  9.         if (!glprogram)  
  10.         {  
  11.             std::string fragmentSource = FileUtils::getInstance()->getStringFromFile(FileUtils::getInstance()->fullPathForFilename("shaders/stroke.fsh"));  
  12.             glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, fragmentSource.c_str());  
  13.             GLProgramCache::getInstance()->addGLProgram(glprogram, shaderNameStroke);  
  14.         }  
  15.           
  16.         auto glprogramState = GLProgramState::create(glprogram);  
  17.           
  18.         glprogramState->setUniformFloat("outlineSize", outlineSize);  
  19.         glprogramState->setUniformVec3("outlineColor", Vec3(outlineColor.r / 255.0f, outlineColor.g / 255.0f, outlineColor.b / 255.0f));  
  20.         glprogramState->setUniformVec2("textureSize", Vec2(textureSize.width, textureSize.height));  
  21.         glprogramState->setUniformVec3("foregroundColor", Vec3(foregroundColor.r / 255.0f, foregroundColor.g / 255.0f, foregroundColor.b / 255.0f));  
  22.   
  23.         return glprogramState;  
  24.     }  
  25. }  



调用的地方:

[cpp]  view plain  copy
  1. Sprite* spr = Sprite::create("elephant1_Diffuse.png");  
  2. spr->setPosition(200, 200);  
  3. spr->setGLProgramState(utilShader::getStrokeProgramState(5, Color3B::GREEN, spr->getContentSize()));  
  4. this->addChild(spr, 1);  

效果:


效果还算是比较好的,经测试,此算法在安卓多个机型上也表现良好。


这样一套完整的描边算法就完成了,如果描述有不当之处,或有更优方法,欢迎吐槽。



-------------------------------------------------------------------------------------------------------------------------------------------------------------------

谢谢complex_ok的吐槽,应该预先计算好sin和cos值,无需每次计算。优化后的 stroke.fsh 如下:

[cpp]  view plain  copy
  1. varying vec4 v_fragmentColor;  
  2. varying vec2 v_texCoord;  
  3. uniform float outlineSize;  
  4. uniform vec3 outlineColor;  
  5. uniform vec2 textureSize;  
  6. uniform vec3 foregroundColor;  
  7.   
  8. 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};  
  9. const float sinArray[12] = {0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5};  
  10.   
  11. int getIsStrokeWithAngelIndex(int index)  
  12. {  
  13.     int stroke = 0;  
  14.     float a = texture2D(CC_Texture0, vec2(v_texCoord.x + outlineSize * cosArray[index] / textureSize.x, v_texCoord.y + outlineSize * sinArray[index] / textureSize.y)).a;  
  15.     if (a >= 0.5)  
  16.     {  
  17.         stroke = 1;  
  18.     }  
  19.   
  20.     return stroke;  
  21. }  
  22.   
  23. void main()  
  24. {  
  25.     vec4 myC = texture2D(CC_Texture0, vec2(v_texCoord.x, v_texCoord.y));  
  26.     myC.rgb *= foregroundColor;  
  27.     if (myC.a >= 0.5)  
  28.     {  
  29.         gl_FragColor = v_fragmentColor * myC;  
  30.         return;  
  31.     }  
  32.   
  33.     int strokeCount = 0;  
  34.     strokeCount += getIsStrokeWithAngelIndex(0);  
  35.     strokeCount += getIsStrokeWithAngelIndex(1);  
  36.     strokeCount += getIsStrokeWithAngelIndex(2);  
  37.     strokeCount += getIsStrokeWithAngelIndex(3);  
  38.     strokeCount += getIsStrokeWithAngelIndex(4);  
  39.     strokeCount += getIsStrokeWithAngelIndex(5);  
  40.     strokeCount += getIsStrokeWithAngelIndex(6);  
  41.     strokeCount += getIsStrokeWithAngelIndex(7);  
  42.     strokeCount += getIsStrokeWithAngelIndex(8);  
  43.     strokeCount += getIsStrokeWithAngelIndex(9);  
  44.     strokeCount += getIsStrokeWithAngelIndex(10);  
  45.     strokeCount += getIsStrokeWithAngelIndex(11);  
  46.   
  47.     bool stroke = false;  
  48.     if (strokeCount > 0)  
  49.     {  
  50.         stroke = true;  
  51.     }  
  52.   
  53.     if (stroke)  
  54.     {  
  55.         myC.rgb = outlineColor;  
  56.         myC.a = 1.0;  
  57.     }  
  58.   
  59.     gl_FragColor = v_fragmentColor * myC;  
  60. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值