109 lines
3.5 KiB
Plaintext
109 lines
3.5 KiB
Plaintext
shader_type canvas_item;
|
||
|
||
uniform bool allow_out_of_bounds = true;
|
||
uniform float outline_thickness: hint_range(0.0, 16.0, 1.0) = 1.0;
|
||
uniform vec4 outline_color: source_color = vec4(1.0);
|
||
uniform int quality: hint_range(1, 3, 1) = 2; // 1=低质量但快速, 2=平衡, 3=高质量但慢
|
||
uniform bool use_fast_mode = true; // 快速模式,牺牲一点质量换取性能
|
||
|
||
bool is_inside_usquare(vec2 x) {
|
||
return x == clamp(x, vec2(0.0), vec2(1.0));
|
||
}
|
||
|
||
vec4 blend(vec4 bottom, vec4 top) {
|
||
float alpha = top.a + bottom.a * (1.0 - top.a);
|
||
if (alpha < 0.0001) return vec4(0.0);
|
||
|
||
vec3 color = mix(bottom.rgb * bottom.a, top.rgb, top.a) / alpha;
|
||
return vec4(color, alpha);
|
||
}
|
||
|
||
void vertex() {
|
||
if (allow_out_of_bounds) VERTEX += (UV * 2.0 - 1.0) * outline_thickness;
|
||
}
|
||
|
||
void fragment() {
|
||
if (outline_thickness <= 0.0 || outline_color.a <= 0.0) {
|
||
COLOR = texture(TEXTURE, UV);
|
||
} else {
|
||
vec2 uv = UV;
|
||
vec4 texture_color = texture(TEXTURE, UV);
|
||
|
||
if (allow_out_of_bounds) {
|
||
vec2 texture_pixel_size = vec2(1.0) / (vec2(1.0) / TEXTURE_PIXEL_SIZE + vec2(outline_thickness * 2.0));
|
||
uv = (uv - texture_pixel_size * outline_thickness) * TEXTURE_PIXEL_SIZE / texture_pixel_size;
|
||
|
||
if (is_inside_usquare(uv)) {
|
||
texture_color = texture(TEXTURE, uv);
|
||
} else {
|
||
texture_color = vec4(0.0);
|
||
}
|
||
}
|
||
|
||
// 如果当前像素已经有alpha,且不是在边缘,可以跳过复杂计算
|
||
if (texture_color.a > 0.9) {
|
||
COLOR = texture_color;
|
||
} else {
|
||
float alpha = 0.0;
|
||
|
||
if (use_fast_mode) {
|
||
// 快速模式:使用较少的采样点
|
||
float step_size = max(1.0, outline_thickness / 4.0);
|
||
int max_samples = 32; // 限制最大采样数
|
||
int sample_count = 0;
|
||
|
||
for (float radius = step_size; radius <= outline_thickness && sample_count < max_samples; radius += step_size) {
|
||
// 使用8个方向的采样点
|
||
vec2 directions[8] = {
|
||
vec2(1.0, 0.0), vec2(-1.0, 0.0), vec2(0.0, 1.0), vec2(0.0, -1.0),
|
||
vec2(0.707, 0.707), vec2(-0.707, 0.707), vec2(0.707, -0.707), vec2(-0.707, -0.707)
|
||
};
|
||
|
||
for (int i = 0; i < 8; i++) {
|
||
vec2 sample_uv = uv + directions[i] * radius * TEXTURE_PIXEL_SIZE;
|
||
if (is_inside_usquare(sample_uv)) {
|
||
float sample_alpha = texture(TEXTURE, sample_uv).a;
|
||
alpha = max(alpha, sample_alpha);
|
||
sample_count++;
|
||
if (alpha > 0.99) break; // 早期退出
|
||
}
|
||
}
|
||
if (alpha > 0.99) break; // 早期退出
|
||
}
|
||
} else {
|
||
// 原始高质量模式,但有优化
|
||
int max_thickness = int(min(outline_thickness, float(8 + quality * 4))); // 限制最大厚度
|
||
|
||
for (int y = 1; y <= max_thickness; y++) {
|
||
for (int x = 0; x <= y; x++) {
|
||
float dist = length(vec2(float(x), float(y) - 0.5));
|
||
if (dist > outline_thickness) break;
|
||
|
||
vec2 offsets[8] = {
|
||
vec2(float(x), float(y)), vec2(float(-x), float(y)),
|
||
vec2(float(x), float(-y)), vec2(float(-x), float(-y)),
|
||
vec2(float(y), float(x)), vec2(float(-y), float(x)),
|
||
vec2(float(y), float(-x)), vec2(float(-y), float(-x))
|
||
};
|
||
|
||
for (int i = 0; i < 8; i++) {
|
||
vec2 sample_uv = uv + offsets[i] * TEXTURE_PIXEL_SIZE;
|
||
if (is_inside_usquare(sample_uv)) {
|
||
float sample_alpha = texture(TEXTURE, sample_uv).a;
|
||
alpha = max(alpha, sample_alpha);
|
||
if (alpha > 0.99) break; // 早期退出
|
||
}
|
||
}
|
||
|
||
if (alpha > 0.99) break; // 早期退出
|
||
}
|
||
|
||
if (alpha > 0.99) break; // 早期退出
|
||
}
|
||
}
|
||
|
||
COLOR = blend(vec4(outline_color.rgb, alpha * outline_color.a), texture_color);
|
||
}
|
||
}
|
||
}
|