【分享】自定义shader遮罩,支持位移、缩放

自定义shader实现可用遮罩,只需要一个Sprite节点就可以实现遮罩效果,支持缩放和位置。
实现逻辑:自定义可视区域宽高,计算uv后传入shader判断当前uv是否在可视区域内。
预览效果:


js代码:

cc.Class({
extends: cc.Sprite,
editor: {
    executeInEditMode: true,
},properties: {
    maskWidth: {
        default: 0,
        notify() {
            this.updateMat();
        },
    },
    maskHeight: {
        default: 0,
        notify() {
            this.updateMat();
        },
    },
    changeTest: {
        default: false,
        notify() {
            this.updateMat();
        },
    },
},

ctor() {
},
// LIFE-CYCLE CALLBACKS:
onLoad() {
    this.updateMat();
},
update(dt) {
},
onDestroy() {
},

//EXTENDS
updateMat() {
    let mat = this.materials[0];

    if (this.spriteFrame && this.maskWidth && this.maskHeight && mat && mat.effectAsset && mat.effectAsset.name == "rect") {
        let frame = this.spriteFrame;
        let rect = frame.getRect();
        let width = rect.width;
        let height = rect.height;
        let changeWidth = this.node.scaleX * width;
        let changeHeight = this.node.scaleY * height;
        //缩放转化比
        let rateX = this.maskWidth / (changeWidth);
        let rateY = this.maskHeight / (changeHeight);
        //位置偏移转化比
        let shiftDegreeY = (this.node.y / changeHeight);
        let shiftDegreeX = -(this.node.x / changeWidth);

        // xMin
        let l = frame.uv[0];
        // xMax
        let r = frame.uv[6];
        // yMax
        let b = frame.uv[3];
        // yMin
        let t = frame.uv[5];

        //uv变化值
        let wShift = (r - l) / 2;
        let hShift = (b - t) / 2;

        //位置偏移
        let posShiftY = 2 * hShift * shiftDegreeY;
        let posShiftX = 2 * wShift * shiftDegreeX;

        //锚点
        let aX = l + wShift + posShiftX;
        let aY = t + hShift + posShiftY;

        //mask uv
        let lView = Math.max(0, l, (aX - wShift * rateX));
        let rView = Math.min(1, r, (aX + wShift * rateX));
        let tView = Math.max(0, t, (aY - hShift * rateY));
        let bView = Math.min(1, b, (aY + hShift * rateY));

        // 纹理在遮罩中的边界坐标
        let u_uvBottomLeft = new cc.v2(lView, tView);
        let u_uvTopRight = new cc.v2(rView, bView);
        // 纹理是否旋转
        //let u_uvRotated = frame.isRotated() ? 1.0 : 0.0;


        // 编辑器mask边框,需要使用的话需要将下面的代码取消注释,并同步恢复rect.effect中的代码
        // if (CC_EDITOR) {
        //     mat.setProperty("u_editor", 1.0);
        // }
        mat.setProperty("u_uvBottomLeft", u_uvBottomLeft);
        mat.setProperty("u_uvTopRight", u_uvTopRight);
    }
},

});

effect代码:

CCEffect %{
  techniques:
  - passes:
    - vert: vs
      frag: fs
      blendState:
        targets:
        - blend: true
      rasterizerState:
        cullMode: none
      properties:
        texture: { value: white }
        alphaThreshold: { value: 0.5 }
        //u_editor: { value: 0.0 }
        u_uvBottomLeft: { value: [0.0, 0.0] }
        u_uvTopRight: { value: [1.0, 1.0] }
}%


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 ARGS{
      vec2 u_uvBottomLeft;
      vec2 u_uvTopRight;
      //float u_editor;
  };

    // return 1 if v inside the box, return 0 otherwise
  float insideBox(vec2 v, vec2 bottomLeft, vec2 topRight) {
      vec2 s = step(bottomLeft, v) - step(topRight, v);
      return s.x * s.y;
  }

  float insideBox2(vec2 v, vec2 bottomLeft, vec2 topRight) {
      float lineWidth = 0.003;

      vec2 s = step(bottomLeft+lineWidth, v) - step(topRight-lineWidth, v);
      return s.x * s.y;
  }
  void main () {
    vec4 o = vec4(1, 1, 1, 1);

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

    float t = insideBox(v_uv0, u_uvBottomLeft, u_uvTopRight);
    //if(u_editor > 0.0){
        //vec4 oLine = vec4(0, 1, 0, 1);
        //float t2 = insideBox2(v_uv0, u_uvBottomLeft, u_uvTopRight);
        //float pct = t * (1.0 - t2);
        //o = mix(o, oLine, pct);
    //}
    o.a = t * o.a;
    gl_FragColor = o;
  }
}%

缺点:1.未处理透明度、旋转 2.不支持动态合图 3.不支持图集
优点:可以配合分层渲染

欢迎各位大佬指正和提出优化方案

shader实现参考:https://stackoverflow.com/questions/12751080/glsl-point-inside-box-test/37426532

1赞