Improved outline preservation

Did basically anti aliasing but for the sake of making the outlines more clear.

It worked but I'm gonna continue to noodle on this solution in the future

Also incidentally fixed a minor visual glitch with the projectile directions after bounce
This commit is contained in:
2026-03-09 00:43:54 -07:00
parent 5fe0a716ce
commit 743899be33
4 changed files with 59 additions and 38 deletions

View File

@@ -6,6 +6,7 @@ uniform sampler2D screen_texture : hint_screen_texture, filter_nearest;
uniform float pixel_size = 256.0; // This now represents the "Height" resolution
uniform float color_levels : hint_range(2.0, 256.0) = 2.0;
uniform float PXwidth = 640;
uniform float PXheight = 360;

View File

@@ -4,46 +4,67 @@ shader_type canvas_item;
uniform sampler2D screen_texture : hint_screen_texture, filter_nearest;
uniform sampler2D palette_texture : filter_nearest;
uniform int palette_size = 16;
uniform float pixel_size = 256.0; // This now represents the "Height" resolution
uniform float pixel_size = 256.0;
uniform float color_levels : hint_range(2.0, 256.0) = 2.0;
uniform float black_bias : hint_range(0.0, 1.0) = 1.0; // Lower = stronger preference for black
uniform float outline_sensitivity : hint_range(0.0, 1.0) = 0.25; // Higher = grabs more grey
uniform float PXwidth = 640;
uniform float PXheight = 360;
uniform vec2 target_resolution = vec2(640.0, 360.0);
void fragment() {
float aspect_ratio = SCREEN_PIXEL_SIZE.y / SCREEN_PIXEL_SIZE.x;
vec2 uv = SCREEN_UV;
uv.x = round(uv.x * pixel_size * aspect_ratio) / (pixel_size * aspect_ratio);
uv.y = round(uv.y * pixel_size) / pixel_size;
vec2 uv = floor(SCREEN_UV * target_resolution) / target_resolution;
vec2 v_pixel_size = 1.0 / target_resolution;
vec2 offset = v_pixel_size * 0.5;
// 3. Sample and Posterize
vec4 rgba = texture(screen_texture, uv);
// check center and 4 small offsets to see if black is nearby
vec3 col0 = texture(screen_texture, uv).rgb;
vec3 col1 = texture(screen_texture, uv + vec2(offset.x, 0.0)).rgb;
vec3 col2 = texture(screen_texture, uv - vec2(offset.x, 0.0)).rgb;
vec3 col3 = texture(screen_texture, uv + vec2(0.0, offset.y)).rgb;
vec3 col4 = texture(screen_texture, uv - vec2(0.0, offset.y)).rgb;
// Calculate the darkest brightness among the 5 samples
float b0 = dot(col0, vec3(0.299, 0.587, 0.114));
float b1 = dot(col1, vec3(0.299, 0.587, 0.114));
float b2 = dot(col2, vec3(0.299, 0.587, 0.114));
float b3 = dot(col3, vec3(0.299, 0.587, 0.114));
float b4 = dot(col4, vec3(0.299, 0.587, 0.114));
float min_brightness = min(b0, min(b1, min(b2, min(b3, b4))));
vec3 final_rgb;
// 2. The "Outline Guard"
// If ANY sampled pixel in this area is dark, force it to black immediately
if (min_brightness < outline_sensitivity) {
final_rgb = vec3(0.0, 0.0, 0.0);
} else {
// 3. Normal Palette Snapping
vec3 rgba = col0; // Use center sample for normal colors
float best_dist = 10.0;
vec3 best_color = rgba.rgb;
///*
// test posteriser instead of the for loop:
float levels = 8.0; // Number of color steps per channel
rgba.rgb = floor(rgba.rgb * levels) / levels;
COLOR = vec4(rgba.rgb, 1.0);
//*/
vec3 best_color = rgba;
//*
// Palette snapping logic
for (int i = 0; i < palette_size; i++) {
float x_coord = (float(i) + 0.5) / float(palette_size);
vec3 p_color = texture(palette_texture, vec2(x_coord, 0.5)).rgb;
float dist = distance(rgba.rgb, p_color);
float dist = distance(rgba, p_color);
/*
// Apply bias in the loop as a secondary safety measure
if (length(p_color) < 0.1) {
dist *= black_bias;
}
//*/
if (dist < best_dist) {
best_dist = dist;
best_color = p_color;
}
}
//*/
COLOR = vec4(best_color, 1.0);
final_rgb = best_color;
}
COLOR = vec4(final_rgb, 1.0);
}
void vertex() {

View File

@@ -23,7 +23,7 @@ func _draw():
func _process(delta: float) -> void:
position += direction * speed * delta
#position += playerDirection * speed * delta
look_at(player.global_position)
#look_at(player.global_position)
#look_at(direction)#
@@ -32,18 +32,15 @@ func get_facing_direction():
var direction_vector: Vector2 = Vector2.from_angle(rotation)
return direction_vector
func changeTrajectory(direction_vector: Vector2):
rotation = rad_to_deg(atan2(direction_vector.y, direction_vector.x))
func changeDirection(normal: Vector2):
print("direction:", direction)
# direction = direction.bounce(normal)
direction = normal
#direction = newDirection
#direction = direction.bounce(perp_line)
rotation = rad_to_deg(direction.angle())
rotation = direction.angle()
print("direction:", direction)
func bounce(normal: Vector2):
direction = direction.bounce(normal)
rotation = rad_to_deg(direction.angle())
rotation = direction.angle()

View File

@@ -11,8 +11,9 @@ shader_parameter/palette_texture = ExtResource("4_vet50")
shader_parameter/palette_size = 16
shader_parameter/pixel_size = 256.0
shader_parameter/color_levels = 8.0
shader_parameter/PXwidth = 640.0
shader_parameter/PXheight = 360.0
shader_parameter/black_bias = 0.6
shader_parameter/outline_sensitivity = 0.25
shader_parameter/target_resolution = Vector2(640, 360)
[node name="ShaderTest" type="Node2D" unique_id=1126703629]
@@ -25,6 +26,7 @@ texture = ExtResource("2_ipaa6")
layer = 10
[node name="ColorRect" type="ColorRect" parent="CanvasLayer" unique_id=570744568]
texture_filter = 2
material = SubResource("ShaderMaterial_exucn")
anchors_preset = 15
anchor_right = 1.0