xiandie/asset/shader/outline.gdshader
2025-05-30 19:05:06 +08:00

84 lines
2.7 KiB
Plaintext

//https://godotshaders.com/shader/various-canvas-outlines/
shader_type canvas_item;
// The outline color. GradientTexture1D is recommended.
uniform sampler2D color: source_color;
// 影响 alpha 的比重
uniform float alpha_ratio: hint_range(0.0, 1.0) = 1.0;
// The resolution for the gradient. Higher numbers will result in smoother but more expensive passes.
uniform int gradientResolution: hint_range(1, 30) = 10;
// Outline thickness.
uniform float thickness: hint_range(0.0, 100.0) = 1.0;
// Used to compensate for alpha values.
uniform float tolerance: hint_range(0.0, 0.999) = 0.0;
// If on will draw at diagonals. Off is mainly for pixel art but you do you.
uniform bool diagonals = true;
// If diagonals are checked will check for half pixels so it rounds the outline a bit more.
uniform bool rounded = true;
// Compensate UV for outline.
void vertex() {
VERTEX = vec2(VERTEX.x * (1.0 + TEXTURE_PIXEL_SIZE.x * thickness * 2.0), VERTEX.y * (1.0 + TEXTURE_PIXEL_SIZE.y * thickness * 2.0));
}
// Checks a fragment for the edge of an uv.
bool border(vec2 uv) {
vec2 uvBorder = abs(uv - vec2(0.5));
return max(step(0.5, uvBorder.x), step(0.5, uvBorder.y)) > 0.0;
}
// Gets alpha of given fragment if not near the edge.
float get_alpha(sampler2D tex, vec2 uv){
float res = 0.0;
if (!border(uv)) {
res = texture(tex, uv).a;
}
return res;
}
// Checks for neighboring pixels.
bool in_range(vec2 size, sampler2D tex, vec2 uv) {
for (float i = -1.0; i < 2.0; i += 2.0) {
if (get_alpha(tex, uv + vec2(i * size.x, 0.0)) > 0.0) {return true;};
if (get_alpha(tex, uv + vec2(0.0, i * size.y)) > 0.0) {return true;};
if (diagonals) {
for (float j = -1.0; j < 2.0; j += 2.0) {
if (get_alpha(tex, uv + vec2(i * size.x, j * size.y)) > 0.0) {return true;};
if (rounded) {
if (get_alpha(tex, uv + vec2(i * size.x, j * size.y * 0.5)) > 0.0) {return true;};
}
}
}
}
return false;
}
// Get's closes pixel.
float get_distance(vec2 maxDistance, sampler2D tex, vec2 uv) {
for (int i = 1; i < gradientResolution; i++) {
vec2 actualDistance = float(i) / float (gradientResolution) * maxDistance;
if (in_range(actualDistance, tex, uv)) {
return float(i) / float (gradientResolution);
}
}
}
void fragment() {
if (thickness > 0.0) {
// Correct image size to for outline in frame.
vec2 uv = UV;
uv -= vec2(0.5);
vec2 edge = TEXTURE_PIXEL_SIZE * thickness * 2.0;
uv = uv + uv * edge;
uv += vec2(0.5);
// Apply outline.
vec4 newColor = texture(TEXTURE, uv);
if (newColor.a <= tolerance || border(uv)) {
vec4 finalColor = step(1.0 - tolerance, in_range(edge / 2.0, TEXTURE, uv) ? 1.0: 0.0) * texture(color, vec2(get_distance(edge / 2.0 + 0.001, TEXTURE, uv)));
newColor = finalColor;
newColor.a *= alpha_ratio;
}
COLOR = newColor;
}
}