Cocos2d-lua 初识shader之四:描边

原理:对于所有透明的像素点,遍历该像素点周围的所有像素点,当有任意一个像素点非透明时,就将该像素点置为描边颜色。

PS.在网上读到一位前辈写的方法是“遍历所有不透明的像素点四周,当有透明像素点时,将该像素点设置为描边颜色”(思路相反),这样的做法会有些缺憾,结尾会放出比较图。

  1. local vert = [[  
  2.     attribute vec4 a_position;   
  3.     attribute vec2 a_texCoord;   
  4.     attribute vec4 a_color;   
  5.     #ifdef GL_ES    
  6.     varying lowp vec4 v_fragmentColor;  
  7.     varying mediump vec2 v_texCoord;  
  8.     #else                        
  9.     varying vec4 v_fragmentColor;   
  10.     varying vec2 v_texCoord;    
  11.     #endif      
  12.     void main()   
  13.     {  
  14.         gl_Position = CC_PMatrix * a_position;   
  15.         v_fragmentColor = a_color;  
  16.         v_texCoord = a_texCoord;  
  17.     }  
  18. ]]  
  19.   
  20. local frag = [[  
  21.     #ifdef GL_ES   
  22.     precision mediump float;   
  23.     #endif   
  24.     varying vec4 v_fragmentColor;   
  25.     varying vec2 v_texCoord;   
  26.     uniform vec2 my_size;                   // 纹理大小,纹理坐标范围为0-1  
  27.     void main(void)   
  28.     {   
  29.         vec2 unit = 1.0 / my_size.xy;       // 单位坐标  
  30.         float step = 30.0;                  // 30度  
  31.         float width = 2.5;                  // 描边宽度  
  32.         float a_limit = 0.4;                // 透明度限制,低于该值即视为透明  
  33.         vec4 border = vec4(1.0,1.0,1.0,1.0);// 边框颜色  
  34.   
  35.         vec4 color1 = texture2D(CC_Texture0, v_texCoord);  
  36.         gl_FragColor = color1;  
  37.         if(color1.a >= a_limit)  
  38.         {  
  39.             return;                         // 非透明像素点不做处理  
  40.         }  
  41.         // 遍历四周所有像素点  
  42.         for(float i = 0.0; i < 360.0; i += step)  
  43.         {  
  44.             // 当前角度的偏移坐标  
  45.             vec2 offset = vec2(cos(i) * unit.x, sin(i) * unit.y) * width;  
  46.             // 当前像素点偏移坐标下的像素点  
  47.             vec4 color2 = texture2D(CC_Texture0, v_texCoord + offset);   
  48.             if(color2.a >= a_limit)  
  49.             {  
  50.                 gl_FragColor = border;      // 不透明则将当前像素点设为边框  
  51.                 return;  
  52.             }  
  53.         }  
  54.     }  
  55. ]]  
  56. -- 1.创建glProgram  
  57. local glProgram = cc.GLProgram:createWithByteArrays(vert, frag)  
  58. -- 2.获取glProgramState  
  59. local glProgramState = cc.GLProgramState:getOrCreateWithGLProgram(glProgram)  
  60. -- 3.设置属性值  
  61. local size = self.blur:getTexture():getContentSizeInPixels()  
  62. glProgramState:setUniformVec2("my_size", cc.p(size.width, size.height))  
  63. self.blur:setGLProgram(glProgram)  
  64. self.blur:setGLProgramState(glProgramState)  

当前效果:

Cocos2d-lua 初识shader之四:描边

相反效果:

Cocos2d-lua 初识shader之四:描边



添加渐变效果

原理很简单,对于当前像素点,一层一层进行遍历即可
  1. void main(void)   
  2. {   
  3.     vec2 unit = 1.0 / my_size.xy;       // 单位坐标  
  4.     float step = 30.0;                  // 30度  
  5.     float width = 5.5;                  // 描边宽度  
  6.     float width_step = 0.5;             // 描边宽度_步长  
  7.     float a_limit = 0.4;                // 透明度限制,低于该值即视为透明  
  8.     vec4 border = vec4(1.0,1.0,1.0,1.0);// 边框颜色  
  9.   
  10.     vec4 color1 = texture2D(CC_Texture0, v_texCoord);  
  11.     gl_FragColor = color1;  
  12.     if(color1.a >= a_limit)  
  13.     {  
  14.         return;                         // bu透明像素点不做处理  
  15.     }  
  16.     // 一层一层遍历四周所有像素点  
  17.     for(float w = 0.0; w < width; w += width_step)  
  18.     {  
  19.         for(float i = 0.0; i < 360.0; i += step)  
  20.         {  
  21.             // 当前角度的偏移坐标  
  22.             vec2 offset = vec2(cos(i) * unit.x, sin(i) * unit.y) * w;  
  23.             // 当前像素点偏移坐标下的像素点  
  24.             vec4 color2 = texture2D(CC_Texture0, v_texCoord + offset);   
  25.             if(color2.a >= a_limit)  
  26.             {  
  27.                 gl_FragColor = border * w / width;      // 不透明则将当前像素点设为边框  
  28.                 return;  
  29.             }  
  30.         }  
  31.     }  
  32. }  


Cocos2d-lua 初识shader之四:描边

一层一层的遍历像素点明显效率极低,时间复杂度为(12*n)^m = n^m(n为透明像素点,m为遍历层数),一种解决方法:是将两个循环的颠倒过来,先找到非透明像素,然后在逐步判断两个像素点间的下一个非透明像素的位置。时间复杂度为12*n*m = n*m,提高了一些效率。

  1. void main(void)   
  2. {   
  3.     float a_limit = 0.1;                // 透明度限制,低于该值即视为透明  
  4.     vec4 color1 = texture2D(CC_Texture0, v_texCoord);  
  5.     gl_FragColor = color1;  
  6.     if(color1.a >= a_limit)  
  7.     {  
  8.         return;                         // bu透明像素点不做处理  
  9.     }  
  10.     vec2 unit = 1.0 / my_size.xy;       // 单位坐标  
  11.     float step = 30.0;                  // 30度  
  12.     float width = 5.0;                  // 描边宽度  
  13.     float width_step = 0.5;             // 描边宽度_步长  
  14.     vec4 border = vec4(1.0,1.0,1.0,1.0);// 边框颜色  
  15.     // 遍历四周所有像素点  
  16.     for(float i = 0.0; i < 360.0; i += step)  
  17.     {  
  18.         // 当前角度的偏移坐标  
  19.         vec2 offset = vec2(cos(i) * unit.x, sin(i) * unit.y);  
  20.         // 当前像素点偏移坐标下的像素点  
  21.         vec4 color2 = texture2D(CC_Texture0, v_texCoord + offset * width);   
  22.         if(color2.a >= a_limit)  
  23.         {  
  24.             for(float w = 0.0; w <= width; w += width_step)  
  25.             {     
  26.                 vec4 color3 = texture2D(CC_Texture0, v_texCoord + offset * w);   
  27.                 if (color3.a >= a_limit)  
  28.                 {  
  29.                     gl_FragColor = border * w / width;      // 不透明则将当前像素点设为边框  
  30.                     return;  
  31.                 }      
  32.             }  
  33.         }  
  34.     }  
  35. }  



效果稍有些差异


Cocos2d-lua 初识shader之四:描边


由于这种方式并没有选择最优点来计算描边宽度,因此描边效果并不好,我们应该找到透明像素点周围距离最近的非透明像素点。

  1. void main(void)   
  2. {   
  3.     float a_limit = 0.1;                // 透明度限制,低于该值即视为透明  
  4.     vec4 color1 = texture2D(CC_Texture0, v_texCoord);  
  5.     gl_FragColor = color1;  
  6.     if(color1.a >= a_limit)  
  7.     {  
  8.         return;                         // bu透明像素点不做处理  
  9.     }  
  10.     vec2 unit = 1.0 / my_size.xy;       // 单位坐标  
  11.     float step = 30.0;                  // 30度  
  12.     float width = 5.0;                  // 描边宽度  
  13.     float width_step = 0.5;             // 描边宽度_步长  
  14.     vec4 border = vec4(1.0,1.0,1.0,1.0);// 边框颜色  
  15.     // 遍历四周所有像素点  
  16.     float min_dis = 5.0;                // 最小距离  
  17.     float sum = 0.0;                    // 周围满足条件的非透明像素点个数  
  18.     for(float i = 0.0; i < 360.0; i += step)  
  19.     {  
  20.         // 当前角度的偏移坐标  
  21.         vec2 offset = vec2(cos(i) * unit.x, sin(i) * unit.y);  
  22.         // 当前像素点偏移坐标下的像素点  
  23.         vec4 color2 = texture2D(CC_Texture0, v_texCoord + offset * width);   
  24.         if(color2.a >= a_limit)  
  25.         {  
  26.             for(float w = 0.0; w <= width; w += width_step)  
  27.             {     
  28.                 vec4 color3 = texture2D(CC_Texture0, v_texCoord + offset * w);   
  29.                 if (color3.a >= a_limit && w <= min_dis)  
  30.                 {  
  31.                     min_dis = w;  
  32.                     sum += 1.0;  
  33.                 }      
  34.             }  
  35.         }  
  36.     }  
  37.     if(sum > 0.0)  
  38.     {  
  39.         gl_FragColor = border * min_dis / width;      // 不透明则将当前像素点设为边框  
  40.     }  
  41. }  


效果好了一些

Cocos2d-lua 初识shader之四:描边