自定义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