【分享】2D图像边缘发光Shader

项目需求要做类似炉石卡牌边缘发光的效果:
image
结果找了半天都没找到个现成的,只能自己写了,先来看看实现的效果:
image
思路来源:https://www.shadertoy.com/view/XdV3Dc
大致原理:对于每个透明的点都用圆来检测其周围的点是否有不透明的,如果有则说明该点为边缘点,涂上边缘的颜色即可。
核心代码:
CCProgram fs %{

  precision lowp float;
  #include <texture>
  #define SAMPLE 16
  #define PI 3.14159265359
  in vec4 v_color;

  #if USE_TEXTURE
  in vec2 v_uv0;
  uniform sampler2D texture;
  uniform myUniform{
    vec4 hightlightColor;
    vec2 textureSize;
    float radius;
  };
  #endif

  vec4 getColorFromTexture(sampler2D texture,vec2 uv){
      vec4 color=vec4(1,1,1,1);
      CCTexture(texture,uv,color);
      return color;
  }

  void main () {
    vec4 o = vec4(1, 1, 1, 1);

    float unitWidth=1.0/textureSize.x;  //单个像素的宽占uv.x的百分比
    float unitHeight=1.0/textureSize.y; //单个像素的高占uv.y的百分比
    float width=radius*unitWidth;   //radius为边缘高光的半径
    float height=radius*unitHeight;

    float angle=0.0;  //角度
    float maxAlpha=0.0; //透明度,经过下面的循环后,如果透明度为0则说明该像素周围没有颜色,即它不是图像的边缘;反之则是图像的边缘
    for(int i=0;i<SAMPLE;i++){
        angle+=1.0/float(SAMPLE)*2.0*PI;  //角度每个循环增加一次,循环结束时角度为2PI,整好一个圆的角度,圆的精度由SAMPLE决定,SAMPLE越高圆越精细,但运算量也会增加
        vec2 testPoint_uv=vec2(width*cos(angle),height*sin(angle));//该点的圆上该角度的相对UV坐标
        testPoint_uv=clamp(v_uv0+testPoint_uv,vec2(0,0),vec2(1,1));//加上该点的UV坐标变为绝对UV坐标
        float tempAlpha=getColorFromTexture(texture,testPoint_uv).a;//用绝对UV坐标从texture读取颜色,看它的透明度
        maxAlpha=max(maxAlpha,tempAlpha);//把透明度结果保存起来
    }

    vec4 finalColor=mix(vec4(0.0),hightlightColor,maxAlpha);//根据检测后的透明度决定该点最终的颜色
    o=getColorFromTexture(texture,v_uv0);//读取该点本来的颜色
    o*=v_color;//乘上顶点颜色,如果想要边缘高光也受node.color的影响,那么也要finalColor*=v_color;
    gl_FragColor = mix(finalColor,o,o.a);//最后按透明度的占比给这俩颜色混合起来
  }
}%

有几个uniform变量要单独设置一下,尤其是textureSize最好用脚本把数据传进去:
image
effect文件:
edgeHighlight.zip (1.6 KB)

14赞

同样是程序猿, 为啥你那么聪明呢

好东西,收藏了

其实原理这样

3赞

没仔细看,直觉告诉我这么做的效率很低

学习了,这种外边框显然比楼主分享的好用,易用

原理明白了,就是不知道咋写…

写出来测试一下

原理就是差不多的,下面这个方法只要把主楼中的SAMPLE改为4,相对UV坐标存一个vec2数组去取而不是计算,最后把maxAlpha从max换成累加就是和下面一模一样了,性能也是差不多的。
其实这个SAMPLE取值用4和8就够了,16是没必要的,至于角度计算肯定是不好的,不清楚硬件会不会优化这个角度计算,但是肯定是存16个vec2,用16/SAMPLE间隔去取理论上性能好一点。

如果原理指的是从纹理采样赋值,那确实差不多。不然的话差异还挺大,lz的方法是探嗅半径r周围的片段,链接图的方法是相当于往四面偏移拓展了有效片段大小

效率不怕,渲染一次就好了。

刚好我的游戏需要一个点击提示的shader,救大命了

介绍下渐变边的思路:
第一次, 绘制到 stencil 或者 mask buffer上
第二次, 开始沿着黑边边界往外+1, 随着这个值越大, alpha 值越小, 对应的颜色变化, 转成uv 顺着一个条带贴图采样。(不要纠结内外,假设大的值为图像区域外,左大右小, 就往左; 上大小小, 就往上; 自己搬着笔杆子数数,然后写死;不要去搜图像处理,形态学,除非你想秃头)
重复上面第二部, 知道效果OK。

(高级的办法是第二部换成3x3, 5x5, 7x7 的卷积模板做)

如果是3D的情况, 就是 第一步之前通过各种办法获得这个mask 或者 stencile

https://www.shadertoy.com/view/WdGSDd
这个描边看起来就不错,还可以调整渐变 :rofl:

2赞

mark1111111111

这种有demo吗 需要只有红色的外边

这个描边效果不错,做了一个 3.5.2 升级

https://gitee.com/yeshao2069/cocos-creator-shader/tree/v3.5.x/demo/2dP1/Creator3.5.2_2D_OuterEdgeOfText

1赞

渲染一次是什么意思

渲染一次保存下来。

保存在哪里