CCEffect %{ techniques: - passes: - vert: sprite-vs:vert frag: miaobian-fs:frag embeddedMacros: { USE_LOCAL: true } depthStencilState: depthTest: false depthWrite: false blendState: targets: - blend: true blendSrc: src_alpha blendDst: one_minus_src_alpha blendDstAlpha: one_minus_src_alpha rasterizerState: cullMode: none properties: alphaThreshold: { value: 0.5 } outlineThreshold: { value: 0.5 } outlineColor: { value: [1, 0.8, 0.1, 1] } outlineWidthPx: { value: 2.0 } outlineMode: { value: 0.0 } # 0=纯色描边 1=藤蔓描边 vineColor: { value: [1, 1, 1, 1] } vineTiling: { value: [6, 1] } vineOffset: { value: [0, 0] } vineTexture: { value: white, editor: { type: texture } } # 注意:texelSize 现在由代码自动计算,无需手动传入,但保留以防万一 texelSize: { value: [0.001, 0.001] } }% CCProgram shared-ubos %{ uniform Constants { vec4 outlineColor; vec4 vineColor; vec2 vineTiling; vec2 vineOffset; vec2 texelSize; // 依然保留,但在代码中我们会尝试自动修正它 float outlineThreshold; float outlineWidthPx; float outlineMode; float _pad0; }; }% CCProgram sprite-vs %{ precision highp float; #include #include #if USE_LOCAL #include #endif #if SAMPLE_FROM_RT #include #endif in vec3 a_position; in vec2 a_texCoord; in vec4 a_color; out vec4 color; out vec2 uv0; vec4 vert () { vec4 pos = vec4(a_position, 1); #if USE_LOCAL pos = cc_matWorld * pos; #endif #if USE_PIXEL_ALIGNMENT pos = cc_matView * pos; pos.xyz = floor(pos.xyz); pos = cc_matProj * pos; #else pos = cc_matViewProj * pos; #endif uv0 = a_texCoord; #if SAMPLE_FROM_RT CC_HANDLE_RT_SAMPLE_FLIP(uv0); #endif color = a_color; return pos; } }% CCProgram miaobian-fs %{ precision highp float; #include #include #include in vec4 color; in vec2 uv0; #pragma builtin(local) layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture; uniform sampler2D vineTexture; // 判断像素是否属于主体 float isSolid(vec2 uv) { return step(outlineThreshold, texture(cc_spriteTexture, uv).a); } vec4 frag () { vec4 baseTex = texture(cc_spriteTexture, uv0); vec4 base = baseTex * color; // 当前像素是否为不透明主体 float inside = isSolid(uv0); // 稳定的“逐圈膨胀”采样:固定 8 次循环,避免 WebGL1 的动态循环限制 vec2 pixelSize = texelSize; float maxSteps = clamp(ceil(outlineWidthPx), 1.0, 8.0); float expanded = inside; for (int i = 1; i <= 8; i++) { float enabled = step(float(i), maxSteps + 0.5); float fi = float(i); vec2 d = pixelSize * fi; expanded = max(expanded, enabled * isSolid(uv0 + vec2( d.x, 0.0))); expanded = max(expanded, enabled * isSolid(uv0 + vec2(-d.x, 0.0))); expanded = max(expanded, enabled * isSolid(uv0 + vec2(0.0, d.y))); expanded = max(expanded, enabled * isSolid(uv0 + vec2(0.0, -d.y))); expanded = max(expanded, enabled * isSolid(uv0 + vec2( d.x, d.y))); expanded = max(expanded, enabled * isSolid(uv0 + vec2(-d.x, d.y))); expanded = max(expanded, enabled * isSolid(uv0 + vec2( d.x, -d.y))); expanded = max(expanded, enabled * isSolid(uv0 + vec2(-d.x, -d.y))); } // 计算描边遮罩:膨胀区域 - 原始区域 float outlineMask = expanded * (1.0 - inside); // 计算最终颜色 vec4 outlineCol = outlineColor; // 藤蔓模式 if (outlineMode >= 0.5) { vec2 vineUV = uv0 * vineTiling + vineOffset; outlineCol = texture(vineTexture, vineUV) * vineColor; } vec4 outCol = base; // 混合颜色 outCol.rgb = mix(outCol.rgb, outlineCol.rgb, outlineMask * outlineCol.a); // 混合透明度 outCol.a = max(outCol.a, outlineMask * outlineCol.a); ALPHA_TEST(outCol); return outCol; } }%