Hexagon Highlight Shader for Godot Canvas Items
This shader highlights hexagonal regions on a canvas item in Godot, in the example below it works on a TileMap. It converts each fragment’s position into hexagonal cube coordinates to determine if it falls within a hexagon. If so, the shader blends the original texture color with red at a fixed 50% intensity, but you can do whatever color and intensity you want. Adjust the uniform parameters (hex size, active center, grid offset, and scaling) to fit your project’s grid layout.
Example C# code for testing:
public override void _Process(double delta)
{
Vector2 localMousePosition = ToLocal(GetGlobalMousePosition());
Vector2I tileCoords = LocalToMap(localMousePosition);
Vector2 centerCoords = MapToLocal(tileCoords);
Vector2 activeCenter = ToGlobal(centerCoords);
if (GetCellSourceId(0,tileCoords) != -1)
{
Vector2[] hexCenters = new Vector2[3];
hexCenters[0] = activeCenter;
hexCenters[1] = activeCenter + new Vector2(120, 0);
hexCenters[2] = activeCenter + new Vector2(60, 105);
_shaderMaterial?.SetShaderParameter("num_active_hexes", 3);
_shaderMaterial?.SetShaderParameter("active_hex_centers", hexCenters);
}
}
Example GDScript code for testing:
func _process(delta):
var local_mouse_position = to_local(get_global_mouse_position())
var tile_coords = local_to_map(local_mouse_position)
var center_coords = map_to_local(tile_coords)
var active_center = to_global(center_coords)
if get_cell_source_id(0, tile_coords) != -1:
var hex_centers = [
active_center,
active_center + Vector2(120, 0),
active_center + Vector2(60, 105)
]
shader_material.set_shader_parameter("num_active_hexes", 3)
shader_material.set_shader_parameter("active_hex_centers", hex_centers)
Shader code
shader_type canvas_item;
const int MAX_HEXES = 64;
uniform int num_active_hexes;
uniform vec2 active_hex_centers[MAX_HEXES];
uniform vec2 hex_size = vec2(120.0, 140.0);
uniform vec2 grid_offset = vec2(0.0, 0.0);
uniform float q_scaling = 1.49;
varying vec2 frag_pos;
void vertex() {
frag_pos = (MODEL_MATRIX * vec4(VERTEX, 0.0, 1.0)).xy;
}
void fragment() {
// Flag to determine if the current fragment is within any hexagon
bool insideHex = false;
for (int i = 0; i < num_active_hexes; i++) {
// Use grid_offset if needed (for overall grid positioning)
vec2 hex_center = active_hex_centers[i] + grid_offset;
vec2 delta = frag_pos - hex_center;
// Convert to hexagonal cube coordinates with scaling adjustment
float q = delta.x / (hex_size.x * 0.75) * q_scaling;
float r = (delta.y - (delta.x / sqrt(3.0))) / (hex_size.y / 2.0);
float s = -q - r;
// Check if the fragment is within the hexagon
float max_distance = max(max(abs(q), abs(r)), abs(s));
if (max_distance <= 1.0) {
insideHex = true;
break;
}
}
vec4 texture_color = texture(TEXTURE, UV);
// Discard fragments with low alpha
if (texture_color.a < 0.1) {
discard;
}
// Apply a fixed red highlight mix if inside any hex
if (insideHex) {
COLOR = mix(texture_color, vec4(1.0, 0.0, 0.0, 1.0), 0.5);
} else {
COLOR = texture_color;
}
}