CCEffect %{ techniques: - passes: - vert: vs frag: fs blendState: targets: - blend: true blendSrc: src_alpha blendDst: one_minus_src_alpha blendSrcAlpha: one blendDstAlpha: one_minus_src_alpha rasterizerState: cullMode: none depthStencilState: depthTest: false depthWrite: false properties: texture: { value: white } imageSize: { value: [224, 43] } outlineWidth: { value: 2 } outlineColor: { value: [0, 0, 0, 0.5] } }% CCProgram vs %{ precision highp float; #include #include in vec3 a_position; in vec4 a_color; in vec2 a_uv0; out vec4 v_color; out vec2 v_uv0; void main () { vec4 pos = vec4(a_position, 1.0); #if CC_USE_MODEL gl_Position = cc_matViewProj * cc_matWorld * pos; #else gl_Position = cc_matViewProj * pos; #endif v_color = a_color; v_uv0 = a_uv0; } }% CCProgram fs %{ precision highp float; #include in vec4 v_color; in vec2 v_uv0; uniform sampler2D texture; uniform Outline { vec4 outlineColor; vec2 imageSize; float outlineWidth; }; // This sprite uses a standalone, untrimmed texture with dynamic packing disabled. vec4 sampleAt (vec2 point) { vec2 uv = point / imageSize; if (uv.x < 0.0 || uv.y < 0.0 || uv.x > 1.0 || uv.y > 1.0) return vec4(0.0); vec4 color = vec4(1.0); CCTexture(texture, uv, color); return color; } void main () { // The authored quad reserves two pixels per side, keeping the original art at 224 x 43. vec2 point = v_uv0 * (imageSize + vec2(4.0)) - vec2(2.0); vec4 fill = sampleAt(point); float expanded = fill.a; for (int i = 0; i < 16; i++) { float angle = float(i) * 0.3926990817; vec2 offset = vec2(cos(angle), sin(angle)) * outlineWidth; expanded = max(expanded, sampleAt(point + offset).a); } // Composite the original art over a 50%-opaque outline, without accumulating opacity per sample. float border = expanded * outlineColor.a * (1.0 - fill.a); float alpha = fill.a + border; vec3 rgb = (fill.rgb * fill.a + outlineColor.rgb * border) / max(alpha, 0.00001); vec4 color = vec4(rgb * v_color.rgb, alpha * v_color.a); #if USE_BGRA gl_FragColor = color.bgra; #else gl_FragColor = color; #endif } }%