Single SDF hex outline overlay
A shader that draws a single hex outline as an overlay, designed to create borders for a multimesh of hexagonal prisms. This was a learning project so may contain errors but I’ve thrown some reference links and comments to help everyone follow along.
References:
https://www.shadertoy.com/view/MsVBWt
https://andrewhungblog.wordpress.com/2018/07/28/shader-art-tutorial-hexagonal-grids/
https://inspirnathan.com/posts/173-interactive-hexagon-grid-tutorial-part-4/
Shader code
/**
A shader that draws a single hex outline as an overlay, designed to create borders for a multimesh of hexagonal prisms. This was a learning project so could have errors!
References:
https://www.shadertoy.com/view/MsVBWt
https://andrewhungblog.wordpress.com/2018/07/28/shader-art-tutorial-hexagonal-grids/
https://inspirnathan.com/posts/173-interactive-hexagon-grid-tutorial-part-4/
By Paul Chamberlain, contact@arkem.org - MIT License
*/
shader_type spatial;
/** Size of the hex relative to the entire surface */
uniform float hex_size : hint_range(0.0, 0.7, 0.05) = 0.5;
/** Scale positions to account for non-regular hexagons */
uniform vec2 hex_surface_aspect_ratio = vec2(1.0, 1.0);
/** Shift the position of the hexagon */
uniform vec2 hex_offset = vec2(0.0, 0.0);
/** Color that's applied on top of the vertex COLOR (or custom color) */
uniform vec3 tint_color : source_color = vec3(0.0, 0.0, 0.0);
/** Tint intensity value (mixes between COLOR and tint_color) */
uniform float tint_intensity : hint_range(0.0, 1.0) = 1.0;
/** Threshold used for "distance to hex edge" to start drawing the hex */
uniform float threshold : hint_range(0.0, 0.1, 0.01) = 0.01;
/** Modifier to threshold used to finish drawing the hex */
uniform float width : hint_range(0.0, 0.1, 0.01) = 0.03;
/** Only draw hexes facing "up", this value controls the maximum slope allowed */
uniform float min_normal_y : hint_range(0.0, 1.0, 0.1) = 0.75;
/** Pointy-top hexagon versus flat-top hexagon */
uniform bool point_top = true;
varying vec3 local_position; // used instead of UV to be independent of texture usage
varying vec3 local_normal; // used to limit hexagons to "top" faces
/** SDF that defines the shape of a hexagon */
float hex_distance(vec2 p) {
vec2 abs_p = abs(p);
vec2 s = point_top ? vec2(1.0, 1.7320508) : vec2(1.7320508, 1.0); // sqrt(3)
float limit = point_top ? abs_p.x : abs_p.y;
return max(dot(abs_p, s*.5), limit) - hex_size;
}
void vertex() {
/** Save vertex data in a "varying" variable so the local/model space value will be available and be interpolated for each fragment */
local_position = VERTEX;
local_normal = NORMAL;
}
void fragment() {
vec2 hex_coords = vec2(local_position.x * hex_surface_aspect_ratio.x, local_position.z * hex_surface_aspect_ratio.y);
hex_coords += hex_offset;
float hex_edge_distance = hex_distance(hex_coords);
float is_eligible = step(min_normal_y, local_normal.y); // slope/facing test
ALBEDO = COLOR.rgb * mix(COLOR.rgb, tint_color, tint_intensity); // lerp between base color and tint (vertex COLOR or custom color)
ALPHA = min(is_eligible, step(threshold, hex_edge_distance) - step(threshold + width, hex_edge_distance)); // draw a line
}


