Cocos 2.3 Shader 编写示例

Cocos 2.3 上边对于Shader 的编写使用终于 正式化了. 写几个示例供大家参考:

Shader 最新格式:

// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.  

CCEffect %{
  // teq
}%


CCProgram vs %{
  void main () {
  }
}%


CCProgram fs %{
  void main () {
  }
}%
 

格式说明:

  • CCEffect %{}% 中 编写techniques 相关声明, 与旧版相比, 采用了yaml 编写格式, 而旧版则用json格式. yaml 标准格式目前已有很多地方可见其身影,如 springboot 项目中的配置文件. 优点是语法精简, 且能够包含继承关系等配置.
  • CCProgram 则用来声明渲染管线的代码片断, 其中需要包含代码片断的入口函数void main() 与旧版基本没有太大差别,其中变量传递废弃了标准glsl 语法中的 attribute/ varying 限定符, 而统一改用 in/out/ inout 关键字.

编写示例:

  • CCEffect格式:
CCEffect %{
  techniques:
  - passes:
    - vert: vs
      frag: fs
      blendState:
        targets:
        - blend: true
      rasterizerState:
        cullMode: none
      properties:         // 声明自定义的外部控制属性, 用于在材质系统面板上或动态运行时进行参数控制修改. 对应uniform的声明
        texture: { value: white }
        alphaThreshold: { value: 0.5 }
        time: { value: 0.0 }
        strength: {value: 2.0 }
        width: { value: 0.008 }
        reverse: { value: 0 }
}%
  • CCProgram vs 片断:
    vs 片断,无特殊需求的话,基本不需要改动, 即顶点不做额外控制, 进行正常的映射变换后传递给gl_Position即可, 示例中将不包含vs片断代码.
CCProgram vs %{
  precision highp float;

  #include <cc-global>   // 需要显式的包含内置变量声明文件,如cc_matViewProj 等内置对象.
  #include <cc-local>

  in vec3 a_position;      // 相当于旧版的attribute 声明, 用于从渲染管线中获取当前属性信息.
  in vec4 a_color;         
  out vec4 v_color;        // 相当于旧版的 varying 声明, 用于向下一个渲染管道传递数据.

  #if USE_TEXTURE
  in vec2 a_uv0;
  out vec2 v_uv0;
  #endif

  void main () {
    vec4 pos = vec4(a_position, 1);

    #if CC_USE_MODEL
    pos = cc_matViewProj * cc_matWorld * pos;
    #else
    pos = cc_matViewProj * pos;
    #endif

    #if USE_TEXTURE
    v_uv0 = a_uv0;
    #endif

    v_color = a_color;

    gl_Position = pos;
  }
}%

  • CCProgram fs 片断:
    多数shader 功能实现仅是对 像素颜色进行若干控制/变换, 因此shader中的多数逻辑都在于控制某个像素颜色的显示.
CCProgram fs %{
  precision highp float;
  
  #include <alpha-test>

  in vec4 v_color;

  #if USE_TEXTURE
  in vec2 v_uv0;
  uniform sampler2D texture;
  #endif

  void main () {
    vec4 o = vec4(1, 1, 1, 1);
    
    o *= texture(texture, v_uv0);
    o *= v_color;

    gl_FragColor = o;
  }

}%

以上 fs代码片断,没有任何功能, 仅对贴图(SpriteFrame)各点进行采样, 然后与当前节点(Node)的颜色v_color 进行混合并输出. 即实现了系统默认的 buildin-sprite 材质效果.

下面提供若干效果样例, 以实现常见的shader效果.

  • 毛玻璃效果(模糊效果)
    高斯模糊效果的原理,是将贴图上相邻的若干像素颜色进行加权平均, 即让周围像素的颜色对某一像素产生影响,你中有我,我中有你.
CCProgram fs %{
  // 省略若干声明
  void main () {

    vec4 o = vec4(1, 1, 1, 1);
    
    // 随机采样次数.
    float repeats = 5.0;

    for(float i=0.0; i<repeats; i++) {

      // 以下两步, 主要用来产生一个随机偏移量, 即以当前v_uv0坐标为基础, 叠加一个偏移量, 从而获得偏移后的周边某点的采样颜色.
       vec2 q = vec2(
         cos(degrees(i*360.0/repeats)),
         sin(degrees(i*360.0/repeats))
       );
       q*= (rand(vec2(i,v_uv0.x + v_uv0.y ))+ num );
       vec2 uv2 = v_uv0 + q*num;
      
       // 将周边某点颜色叠加到一起进行颜色混合.
       o += texture(texture, uv2);

    }

    // 中和, 刚才的循环累加了repeats 次, 颜色分量应当除以repeats, 否则颜色分量可能超过1,即高曝光效果.
    o /= repeats;

    // 降低亮度. 并用节点本身颜色进行混合.
    float light = 0.5;
    o = o* v_color* light;

    o.a = 1.0;

    gl_FragColor = o;
  }
}%

效果:

  • 圆形头像裁剪效果(还能带羽化)
    圆形头像裁剪的原理就是以 贴图中心点为基准,画一个半径0.5r 的圆形, 距离中心点超过0.5r 的坐标点的颜色统统 discard掉.
CCProgram fs %{

  precision highp float;
  
  #include <alpha-test>

  in vec4 v_color;

  #if USE_TEXTURE
  in vec2 v_uv0;
  uniform sampler2D texture;
  #endif

  uniform Params{       // 与旧版的又一声明变化, 旧版 直接声明 uniform vec2 center;  而新版则要求,所有的uniform 声明放到一个UBO {}中,  "Parms" 名称随意, 其中的声明字段需要进行字节对齐.详细看官方文档.
    vec2 center;
    float radius;
    float feather;
  };

  void main () {

   
    // 计算当前坐标点与 中心点(0.5, 0.5)的距离, 距离超过半径的点,直接 discard; 即return.
    float dis = distance(v_uv0, center);

    if( dis > radius ){
        discard;
    }

   // 正常计算坐标点的颜色值. 
    vec4 o = vec4(1, 1, 1, 1);
    o *= texture(texture, v_uv0);
    o *= v_color;

    // 判断圆形周边的一圈像素, 根据羽化参数大小, 对周边一圈颜色的透明度进行平滑降低, 即>0.5r 为透明a=0, < 0.4r为不透明a=1,  否则透明度a= 线性0~1过渡.
    // dis < 0.4 则为1, >0.5则为0, 否则就为0~1之间插值
    if( feather > 0.0 ){
      o.a = smoothstep(radius, radius - feather, dis);
    }
    
    gl_FragColor = o;
  }
}%

效果:

  • 溶解效果(若干像素变没了, 像腐烂的破布)

CCProgram fs %{
  precision lowp float;

  in vec4 v_color;

  #if USE_TEXTURE
  in vec2 v_uv0;
  uniform sampler2D texture;
  #endif

  uniform Timer{
    float time;
  };

  void main () {
    
    vec4 o = v_color * texture2D(texture, v_uv0);

    // 将当前坐标点的三个颜色分量进行混合, 并把其中小于某值的颜色 discard;掉.
    // 实际应用场景可能需要另外单独提供一张用来判断是否discard像素的贴图,此处简化,直接对当前图像的色彩进行判断.
    float h = (o.g + o.r +o.b)/3.0;
    
    // time 参数为uniform, 即通过运行时代码传递进来的参数.动态修改time 即颜色动态融化的效果. 
    if(h < time) {
      discard;
    }

    gl_FragColor = o;
  }
}%

静态效果:

附时间time 更新组件, 直接挂在相应的sprite节点上即可.

const { ccclass, property } = cc._decorator;
@ccclass
export default class ShaderTime extends cc.Component {

    @property
    max: number = 0.0;

    @property
    step: number = 0.001

    @property
    loop: number = 0;

    private mat: cc.Material = null;

    private current: number = 0;

    private finished: boolean = false;

    private loopTimes: number = 0;

    onEnable() {
        const sp = this.getComponent(cc.Sprite);
        if (!sp) {
            return;
        }

        const mat = sp.getMaterial(0);
        mat.setProperty("time", this.max);

        this.mat = mat;

        this.finished = false;

        this.loopTimes = this.loop <= 0 ? 2 ^ 63 : this.loop;
    }

    update(dt) {

        if (this.finished) return

        this.current += this.step;

        if (this.current >= this.max) {
            this.current = 0.0;
            if (--this.loopTimes <= 0) {
                this.finished = true;
                return
            }
        }

        if (this.mat) {
            this.mat.setProperty("time", this.current);
        }
    }
}

– 圆角矩形(切圆角必备, 但不完美)
圆角矩形就是将大于圆角半径的若干像素discard,保留其他像素.

CCProgram fs %{
  precision highp float;
  
  #include <alpha-test>

  in vec4 v_color;

  #if USE_TEXTURE
  in vec2 v_uv0;
  uniform sampler2D texture;
  #endif

  uniform Params{
    float radius;
  };

  void main () {

    vec4 o = vec4(1, 1, 1, 1);
    
    o *= texture(texture, v_uv0) * v_color;

    // 计算出以4个圆角圆心的坐标点区域.
    vec2 uv = v_uv0 - vec2(0.5,0.5);
      uv.x = abs(uv.x);
      uv.y = abs(uv.y);
    float r = 0.5- radius;
    
   
    if (uv.x > r && uv.y > r) {
      uv.x -= r;
      uv.y -= r;

      // 计算平移后的坐标点距离圆心(0 ,0 )的距离, 与圆形头像原理一致,
      float dis = distance(uv, vec2(0,0));

      if( dis >= radius ) {
        discard;
      }
    }

    gl_FragColor = o;
  }
}%

效果:

  • 扫光效果 ( 柯南眼镜上总会闪现一道光,秘密被揭开的感觉)
    扫光效果的原理就是 将一块区域内的像素颜色增强,使其变的更亮,甚至曝光变白的效果, 再配合时间进行区域平移即可.
CCProgram fs %{
  // 省略若干
  void main () {

    float start = time;

    vec4 color = v_color * texture(texture,v_uv0);
        
    // 斜线控制区域. 其实就是 y = -x+width  y = -x -width 夹着的区域. 还可以乘上斜率,以使扫光的倾斜程度变化. 加上time 时间控制, 可以使区域根据时间推移,平滑的从左往右移动.

    if(v_uv0.x <= (-v_uv0.y + width + start) && v_uv0.x >= (-v_uv0.y - width + start)) {
          color *= strength;  // 给颜色增强若干倍. 
    }
//   else if( reverse != 0 ) {  
//   // 可以通过参数控制, 是否仅显示扫光区域, 看起来就会变成一道光照亮了一片区域,而其他地方则是虚无. 手电筒的效果.
//      discard;
//    }

    gl_FragColor = color;
  }
}%

效果:

  • 其他色彩效果 ( 涉及PS 对颜色的各种处理算法.)
// 冰冻效果
        gl_FragColor.r = abs(col.r-col.g-col.b)*3.0/2.0;
        gl_FragColor.g = abs(col.g-col.b-col.r)*3.0/2.0;
        gl_FragColor.b = abs(col.b-col.r-col.g)*3.0/2.0;
        gl_FragColor.a = 1.0;
// 熔铸效果类似打铁的场景
         gl_FragColor.r = col.r*0.5/(col.g+col.b);
        gl_FragColor.g = col.g*0.5/(col.r+col.b);
        gl_FragColor.b = col.b*0.5/(col.r+col.g);
        gl_FragColor.a = 1.0;
// 颜色加变暗效果
        gl_FragColor.r = col.r*col.r;
        gl_FragColor.g = col.g*col.g;
        gl_FragColor.b = col.b*col.b;
        gl_FragColor.a = 1.0;
// 反相效果
        gl_FragColor.r = col.g*col.b;
        gl_FragColor.g = col.r*col.b;
        gl_FragColor.b = col.r*col.g;
        gl_FragColor.a = 1.0;
// 颜色饱和度增强

        float vibrance = 4.0;
        float average = (col.r + col.g + col.b) / 3.0;
        float mx = max(col.r, max(col.g, col.b));
        float amt = (mx - average) * (-vibrance * 3.0);
        col.rgb = mix(col.rgb, vec3(mx), amt);
        gl_FragColor = col;


各自效果:


PS中各常见算法引用:
https://blog.csdn.net/kezunhai/article/details/41832317

65赞

点赞!

火钳刘明

感谢分享

mark111

mark

借楼问一下,这两个属性在shader中怎么获取? 或者说我怎么知道模型的高度

膜拜大佬~

大佬!

大佬!

通过在shader中声明 uniform 参数, 并在外挂组件中获取到材质对象, 并调用 mate.setProperty()进行设置. 这样shader中就能拿到你想要的属性值.

增加一个渐变消失的特效, 常用于两张幻灯片的切换.

渐变消失效果


CCProgram fs %{
   // 省略若干...
  uniform Time{
    float time;
    float vertical;
    float width;
  };

  void main () {

    vec4 o = vec4(1, 1, 1, 1);

    o *= v_color* texture(texture, v_uv0);

    // 外部控制从上到下还是从左向右消失.
    if(vertical > 0.0) {
      if( v_uv0.y < time ) {
        // 对消失区域进行透明度插值渐变.
        o.a = smoothstep(time - width,time, v_uv0.y);
      }
    } else {
      if( v_uv0.x < time ) {
        o.a = smoothstep(time - width,time, v_uv0.x);
      }
    }

    gl_FragColor = o;
  }
}%

效果:

新增水波动态特效

  • 水波特效产生图片折射扭曲的动效.
    原理就是对当前的uv 坐标进行 线性+三解函数等混合变换, 产生一个新的采样坐标,以此来产生像素不在原位的效果.同时又区别于模糊效果那种随机采样的混乱感.
CCProgram fs %{
  precision highp float;
  
  #include <alpha-test>
  #include <cc-global>

  in vec4 v_color;

  #if USE_TEXTURE
  in vec2 v_uv0;
  uniform sampler2D texture;
  #endif

  uniform Param{
    vec2 size;
    float time;
  };

  #define F(x,y) vec2( cos(x-y) * cos(y) , sin(x+y) * sin(y))

  vec2 wave(in vec2 p) {

      // 返回一个 x,y 与时间相关的平移点坐标. 
      // 将线性x/y 函数, 结合F(),三角函数,生成一个随机变化的坐标点.
      float d = time * 0.2, x=8.*(p.x + d), y=8.*(p.y + d);
      return F(x , y);
  }

  void main () {

    vec4 o = vec4(1, 1, 1, 1);

    o *= v_color;

    ALPHA_TEST(o);

    // size 由外部传递进来的sprite节点的实际尺寸.
    vec2 q = v_uv0 + 2.0 / size.x * ( wave(v_uv0) - wave( v_uv0 + size ) );
    // 用新的点进行采样颜色.
    gl_FragColor = o * texture(texture , q);
  }
}%

实际效果(静态瞬间):

新增变色龙效果

动态变化图片的rgb通道强弱.

CCProgram fs %{
  precision highp float;
  
  #include <alpha-test>

  in vec4 v_color;

  #if USE_TEXTURE
  in vec2 v_uv0;
  uniform sampler2D texture;
  #endif

  uniform Time{
    float time;
  };

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

    o = v_color* texture(texture, v_uv0);

   // 使用三角函数,分别生成rgb通道的 强弱数值,范围都是[0-1], 然后同时叠加给正常的像素颜色值.
    float r = abs(sin(time));
    float g = abs(cos(time*1.7));
    float b = abs(sin(time*2.3));
    o.r *= r;
    o.g *= g;
    o.b *= b;

    gl_FragColor = o;
  }
}%

静态瞬间:

2赞

好的,我以为有像a_position一样的字段能直接获取到呢

mark

1赞

mark

大佬,我的那个变色龙效果····和你的怎么感觉有点不一样?

人家的变色龙是基于rgb,你的变色龙是基于HSL。当然不一样。

我只看了眼说是shader,我也没细看·····
rgb的话,直接用color 也可以么?
我错了·····