版本:2.4.4
参考:
cocos论坛-creator 2.0 自定义 shader 图片描边效果已测试
目录
一 描边实际效果
二 描边原理
三 创建描边文件.effect和.material
四 使用案例
五 Demo下载
一 描边实际效果
二 描边原理
采集原图的纹理,将纹理放大并设置为红色,然后将原图和红色合并显示,就形成了描边效果。
在OutlineEffect.effect中文件中代码实现为
void main () { vec4 accum = vec4(0.0); vec4 normal = vec4(0.0); normal = texture2D(texture, vec2(v_uv0.x, v_uv0.y)); accum += texture2D(texture, vec2(v_uv0.x - radius, v_uv0.y - radius)); accum += texture2D(texture, vec2(v_uv0.x + radius, v_uv0.y - radius)); accum += texture2D(texture, vec2(v_uv0.x + radius, v_uv0.y + radius)); accum += texture2D(texture, vec2(v_uv0.x - radius, v_uv0.y + radius)); accum.r = imgColor.r * accum.a; accum.g = imgColor.g * accum.a; accum.b = imgColor.b * accum.a; accum.a = imgColor.a * accum.a; normal = accum * (1.0- normal.a) + normal; ALPHA_TEST(normal); gl_FragColor = normal*v_color; }
normal就是采集原图texture的纹理数据,所以normal就相当于原图
normal = texture2D(texture, vec2(v_uv0.x, v_uv0.y));
accum是将原图分别往左上,右上,右下,左下移动自定义radius距离后合并的纹理数据,如下面右边的图,相当于4张偏移位置后的图合在了一起。
accum += texture2D(texture, vec2(v_uv0.x - radius, v_uv0.y - radius)); accum += texture2D(texture, vec2(v_uv0.x + radius, v_uv0.y - radius)); accum += texture2D(texture, vec2(v_uv0.x + radius, v_uv0.y + radius)); accum += texture2D(texture, vec2(v_uv0.x - radius, v_uv0.y + radius));
这里是将accum图变成自定义颜色imgColor,例如imgColor为红色,这样就制作了一个比例大于原图radius的红色图
accum.r = imgColor.r * accum.a; accum.g = imgColor.g * accum.a; accum.b = imgColor.b * accum.a; accum.a = imgColor.a * accum.a;
这句就是原图+描边图 = 原图带描边
normal = accum * (1.0- normal.a) + normal;
accum*(1.0-normal.a)就是当normal.a=1时,红色描边图就变成(1.0-nomal.a)=(1.0-1.0)=0,意思就是原图不透明的地方,描边图变透明
当normal.a=0时,红色描边图就变成(1.0-normal.a) = (1.0- 0) = 1.0,意思就是原图透明的地方,描边图不透明。
如下图所示,这样就得到了一个红色描边。
如果再加上原图的话, normal = accum*(1.0-normal.a) + normal ,那么就是最终描边效果了。
三 创建描边effect和material文件
新建effect和material文件,分别命名为OutlineEffect.effect和OutlineMaterial.mtl
OutlineMaterial.mtl文件保持默认即可,无需修改
OutlineEffect.effect的main函数修改如下,代码将图片边缘变成imgColor的颜色
void main () { vec4 accum = vec4(0.0); vec4 normal = vec4(0.0); //纹理查询,从texture提取指定坐标的颜色信息normal,相当于一张正常的图片,normal就是原图 normal = texture2D(texture, vec2(v_uv0.x, v_uv0.y)); //纹理查询,相当于获取了一张比原图比例大radius的图片,accum就是描边图 accum += texture2D(texture, vec2(v_uv0.x - radius, v_uv0.y - radius)); accum += texture2D(texture, vec2(v_uv0.x + radius, v_uv0.y - radius)); accum += texture2D(texture, vec2(v_uv0.x + radius, v_uv0.y + radius)); accum += texture2D(texture, vec2(v_uv0.x - radius, v_uv0.y + radius)); //将描边图设置为imgColor色,例如imgColor是红色,则描边图就是红色 accum.r = imgColor.r * accum.a; accum.g = imgColor.g * accum.a; accum.b = imgColor.b * accum.a; accum.a = imgColor.a * accum.a; //accum * (1.0- normal.a)相当于描边图和原图重叠的地方都为透明,这样只剩下描边 //normal 还是原图 //accum * (1.0- normal.a) + normal 就是描边+原图了 normal = accum * (1.0- normal.a) + normal; ALPHA_TEST(normal); gl_FragColor = normal*v_color; }
OutlineEffect.effect开头处的CCEffect内自定义颜色imgColor和自定义半径radius
CCEffect %{ techniques: - passes: - vert: vs frag: fs blendState: targets: - blend: true rasterizerState: cullMode: none properties: texture: { value: white } alphaThreshold: { value: 0.5 } imgColor: {value: [255,0,255,255] ,editor: {type: color} } radius: { value: 0.01 } }%
在main上方声明imgColor和radius两个变量,如下图种uniform的代码
uniform color{ vec4 imgColor; }; uniform Properties { float radius; }; void main () { vec4 accum = vec4(0.0); vec4 normal = vec4(0.0); .... .... ....
选择OutlineMaterial.mtl文件,在属性中设置Effect为OutlineEffect,属性面板中imgColor颜色可自行定义,也可在代码中动态修改
若想颜色初始默认就是本来颜色,则将imgColor的rgb、alpha属性全部选成0即可
radius是描边半径大小
OutlineEffect.effect全文如下:
// 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 } imgColor: {value: [255,0,255,255] ,editor: {type: color} } radius: { value: 0.01 } }% 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 color{ vec4 imgColor; }; uniform Properties { float radius; }; void main () { vec4 accum = vec4(0.0); vec4 normal = vec4(0.0); //纹理查询,从texture提取指定坐标的颜色信息normal,相当于一张正常的图片,normal就是原图 normal = texture2D(texture, vec2(v_uv0.x, v_uv0.y)); //纹理查询,相当于获取了一张比原图比例大radius的图片,accum就是描边图 accum += texture2D(texture, vec2(v_uv0.x - radius, v_uv0.y - radius)); accum += texture2D(texture, vec2(v_uv0.x + radius, v_uv0.y - radius)); accum += texture2D(texture, vec2(v_uv0.x + radius, v_uv0.y + radius)); accum += texture2D(texture, vec2(v_uv0.x - radius, v_uv0.y + radius)); //将描边图设置为imgColor色,例如imgColor是红色,则描边图就是红色 accum.r = imgColor.r * accum.a; accum.g = imgColor.g * accum.a; accum.b = imgColor.b * accum.a; accum.a = imgColor.a * accum.a; //accum * (1.0- normal.a)相当于描边图和原图重叠的地方都为透明,这样只剩下描边 //normal 还是原图 //accum * (1.0- normal.a) + normal 就是描边+原图了 normal = accum * (1.0- normal.a) + normal; ALPHA_TEST(normal); gl_FragColor = normal*v_color; } }%
四 实际使用
将场景中放两张图片,第一张是原图用于对比,第二张将属性面板中材质Materials属性设置为OutlineMaterial。
代码中获取带有outlinematerial的图片,并动态赋值材质imgColor属性和radius属性,即可改变描边颜色和大小。
动态设置描边颜色代码全文如下:
const { ccclass, property } = cc._decorator; @ccclass export default class OutlineDemo extends cc.Component { @property({ type: cc.Sprite, tooltip: "材质为OutlineMaterial图片" }) outlineSprite: cc.Sprite = null; onLoad() { //获取图片的材质 let material: cc.Material = this.outlineSprite.getMaterial(0); //打印材质的pass属性 console.log((material as any)._effect._passes[0]._defines["USE_TEXTURE"]); //监听鼠标移动 this.node.on(cc.Node.EventType.TOUCH_MOVE, (e: cc.Event.EventTouch) => { let localPos = this.outlineSprite.node.parent.convertToNodeSpaceAR(e.getLocation()); //判断鼠标移入图片内,则设置颜色为红色 if (this.outlineSprite.node.getBoundingBox().contains(localPos)) { material.setProperty("imgColor", new cc.Vec4(255, 0, 0, 255)); material.setProperty("radius", 0.002); //判断鼠标移出图片,则设置颜色为0,还原成本来颜色 } else { material.setProperty("imgColor", new cc.Vec4(0, 0, 0, 0)); } }, this); } }
五 Demo下载