分享一个转场shader

项目里需要实现一个类似相册切页的效果,如下:

实现方式是shader

分析动画逻辑

基础显示逻辑

是一系列余弦曲线的阵列和旋转。
将在余弦范围内的片元显示出来。
看正交情况:
image

所以阵列算出各个图形余弦值,然后和本身的uv值比较,如果在cosX和-cosX内,则显示。

if(abs(centerUV.x)<amount*ratio){//用于限定只在一个波峰,横向
 float _isInY=step(_disToLineY,cos(centerUV.x*_toRadius)*_cosA);//判断是否在余弦函数波峰内
 _result+=_isInY;
 if(_result>=1.0)break;
}
if(abs(centerUV.y)<amount*ratio){//用于限定只在一个波峰,竖向
 float _isInX=step(_disToLineX,cos(centerUV.y*_toRadius)*_cosA);//判断是否在余弦函数波峰内
 _result+=_isInX;
 if(_result>=1.0)break;
 }

横向的情况一样处理
image

变换逻辑

因为要有旋转,并且需要是正方向的扩散,所以需要对坐标系进行旋转和缩放变换。保证横竖轴比例一致。加上从左上角到中间的移动变换。那么三个变换都有了。
变换代码如下:

vec2 centerUV=vec2(v_uv0.x-0.5,0.5-v_uv0.y);//坐标系变换,位移变换,将左上角坐标系移到中心点
centerUV.x*=size.x/size.y;//坐标系变换,缩放变换,将x轴和y轴缩放到一样
centerUV=vec2((centerUV.x*cos(_rad)-centerUV.y*sin(_rad)),centerUV.x*sin(_rad)+centerUV.y*cos(_rad));//坐标系变换,旋转变换,旋转到指定的角度

最后在组件里用tween调用就可以了。

因为是动画,所以用到了tween和proxy的联合。

代码如下:

    start() {
        const _node = this.node.getChildByName('pic');
        const _mat = _node.getComponent(cc.Sprite).getMaterial(0);
        _mat.setProperty('size', [_node.width, _node.height]);
        _mat.setProperty('cellSize', 75.0);
        _mat.setProperty('rotation', 45.0);
        const _proxy = new Proxy({ amount: 0.0 }, {
            set(target, key, value) {
                target[key] = value;
                if (key == 'amount') {
                    _mat.setProperty('amount', value);
                }
                return true;
            }
        })
        cc.tween(_proxy).delay(0.5).to(1, { amount: 15.0 }).start();
    },

在tween逐帧改动自定义对象amount的时候,代理会去更新材质的uniform属性。

可以支持网格大小,旋转值的自定义

shader代码如下:

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

CCEffect %{
  techniques:
  - passes:
    - vert: vs
      frag: fs
      blendState:
        targets:
        - blend: true
      rasterizerState:
        cullMode: none
      properties:
        texture: { value: white }
        alphaThreshold: { value: 0.5 }
        size: { value: [750.0,1624.0]}
        amount: { value: 0.0}
        cellSize: { value: 75.0}
        rotation: { value: 45.0}
}%
/* 
uniform 属性介绍
size:图片尺寸
amount:当前中间条纹的序号
cellSize:最大格子尺寸
rotation:旋转角度
*/

CCProgram vs %{
  precision highp float;

  #include <cc-global>
  #include <cc-local>

  in vec3 a_position;
  in vec4 a_color;
  out vec4 v_color;

  #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 %{
  precision highp float;

  #include <alpha-test>
  #include <texture>

  in vec4 v_color;

  #if USE_TEXTURE
  in vec2 v_uv0;
  uniform sampler2D texture;
  #endif
  
  uniform blocku{
    vec2 size;
    float amount;
    float cellSize;
    float rotation;
  };
  
  void main () {
    vec4 o = vec4(1, 1, 1, 1);
    #if USE_TEXTURE
      CCTexture(texture, v_uv0, o);
    #endif
    float ratio=cellSize/size.y;//获得格子实际尺寸归一化值
    float _rad=radians(rotation);//旋转角度转弧度值
    vec2 centerUV=vec2(v_uv0.x-0.5,0.5-v_uv0.y);//坐标系变换,位移变换,将左上角坐标系移到中心点
    centerUV.x*=size.x/size.y;//坐标系变换,缩放变换,将x轴和y轴缩放到一样
    centerUV=vec2((centerUV.x*cos(_rad)-centerUV.y*sin(_rad)),centerUV.x*sin(_rad)+centerUV.y*cos(_rad));//坐标系变换,旋转变换,旋转到指定的角度
    float _amount=amount*2.0+1.0;//扩展条纹
    float _h=min((1.6429*amount-0.6429)*3.6,120.0);//纹理高度,amount越大高度越大,有最大限制
    float _toRadius=3.14/(2.0*amount*ratio);//用于折算uv到一个波峰
    float _result=0.0;//用于决定最后是否显示该片元
    for(float i=0.0;i<100.0;i+=1.0){
      if(i>=_amount)break;
       float _dis=i-amount;//各条纹距离中心的距离个数
       float _distance=floor(_dis)*ratio;//各条纹距离中心的距离归一化值
       float _disToLineY=abs(centerUV.y-_distance);//当前y值距离当前横向条纹距离
       float _disToLineX=abs(centerUV.x-_distance);//当前x值距离当前竖向条纹距离
       float _cosA=cos(_distance*_toRadius)*_h/size.y;//振幅
       if(abs(centerUV.x)<amount*ratio){//用于限定只在一个波峰,横向
        float _isInY=step(_disToLineY,cos(centerUV.x*_toRadius)*_cosA);//判断是否在余弦函数波峰内
        _result+=_isInY;
        if(_result>=1.0)break;
       }
       if(abs(centerUV.y)<amount*ratio){//用于限定只在一个波峰,竖向
        float _isInX=step(_disToLineX,cos(centerUV.y*_toRadius)*_cosA);//判断是否在余弦函数波峰内
        _result+=_isInX;
        if(_result>=1.0)break;
       }
    }



    o *= v_color;

    o.a*=_result;


    ALPHA_TEST(o);

    #if USE_BGRA
      gl_FragColor = o.bgra;
    #else
      gl_FragColor = o.rgba;
    #endif
  }
}%

不足之处恳请指正哈!!

image

14赞

感谢大佬分享,冒昧问一句,是Creator的哪个版本?

你好,是用的2.4.6实现的。

好的,感谢大佬,希望以后我也能写出这么好看的shader :2:

支持一下!!!

6p,战略插眼~

6p plus,感谢大佬分享

666,感谢大佬分享

mark!

盲僧摸眼来到此处

这个for100性能咋样

如果是一次性使用应该还行,如果频繁使用还需要优化,100也不是肯定要执行完的。除非真的设置这么多。嘿嘿,做这个的时候,没太考虑性能。

还有个疑问,这个转场是有两张图片,但是代码里只有一个texture,另外一张图是如何读取进来的呢