This technique enables the rendering of roads without the use of masks or meshes, providing infinite resolution and preventing flickering artifacts.
Result from far:
Closer, the shader remove the central line when it find more than 2 segments close to road_width * 1.5
So you have to generate Vector2 segments first:
1 Send all your segment to the shader (Assume the terrain origin is at 0,0,0):
func create_road_spatial_hash():
var grid_res = 1024
var terrain_size = terrain_node.size
# 1. Initialize the CPU-side grid
var grid_buckets = []
grid_buckets.resize(grid_res * grid_res)
for i in range(grid_buckets.size()):
grid_buckets[i] = []
# 2. Sort segments into cells
for path in roads["curved_paths"]:
for i in range(path.size() - 1):
var p1: Vector2 = path[i]
var p2: Vector2 = path[i + 1]
var norm_a = p1 / terrain_size
var norm_b = p2 / terrain_size
# Add a small buffer(or margin) based on road width (normalized to 0.0 - 1.0 range)
var buffer = (road_width * 1.5) / terrain_size # Encrase if you see square glitch
var min_x = clampi(int((min(norm_a.x, norm_b.x) - buffer) * grid_res), 0, grid_res - 1)
var max_x = clampi(int((max(norm_a.x, norm_b.x) + buffer) * grid_res), 0, grid_res - 1)
var min_y = clampi(int((min(norm_a.y, norm_b.y) - buffer) * grid_res), 0, grid_res - 1)
var max_y = clampi(int((max(norm_a.y, norm_b.y) + buffer) * grid_res), 0, grid_res - 1)
for x in range(min_x, max_x + 1):
for y in range(min_y, max_y + 1):
grid_buckets[y * grid_res + x].append([p1, p2])
# 3. Prepare raw float arrays
var data_raw = PackedFloat32Array()
var grid_raw = PackedFloat32Array()
grid_raw.resize(grid_res * grid_res * 2)
var current_offset = 0
for i in range(grid_buckets.size()):
var cell_list = grid_buckets[i]
grid_raw[i * 2] = float(current_offset)
grid_raw[i * 2 + 1] = float(cell_list.size())
for s in cell_list:
data_raw.append(s[0].x)
data_raw.append(s[0].y)
data_raw.append(s[1].x)
data_raw.append(s[1].y)
current_offset += 2
# 4. Convert to 2D Square Texture (to avoid hardware width limits)
var total_vec2_points = data_raw.size() / 2
var data_side = int(ceil(sqrt(total_vec2_points)))
# Pad the array so it fits the square exactly
var required_floats = data_side * data_side * 2
while data_raw.size() < required_floats:
data_raw.append(0.0)
var grid_img = Image.create_from_data(grid_res, grid_res, false, Image.FORMAT_RGF, grid_raw.to_byte_array())
var data_img = Image.create_from_data(data_side, data_side, false, Image.FORMAT_RGF, data_raw.to_byte_array())
var grid_tex = ImageTexture.create_from_image(grid_img)
var data_tex = ImageTexture.create_from_image(data_img)
# 5. Push to Shader
terrain_material.set_shader_parameter("grid_tex", grid_tex)
terrain_material.set_shader_parameter("data_tex", data_tex)
terrain_material.set_shader_parameter("grid_resolution", grid_res)
terrain_material.set_shader_parameter("data_tex_width", data_side)
The shader: (change terrain size to your terrain size)
shader_type spatial;
//render_mode depth_draw_always; // For road offset
uniform float terrain_size = 200000;
// Road segments
uniform sampler2D grid_tex : filter_nearest;
uniform sampler2D data_tex : filter_nearest;
uniform int data_tex_width;
uniform int grid_resolution = 1024;
uniform float road_width = 20.0;
uniform vec3 road_color : source_color = vec3(1.0, 1.0, 1.0);
uniform vec3 road_line_color : source_color = vec3(1.0, 1.0, 1.0);
varying vec3 world_pos;
void vertex() {
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
}
void fragment() {
vec3 color = vec3(1.0, 1.0, 1.0);
vec2 terrain_uv = world_pos.xz / terrain_size;
if (world_pos.y < -2000.0) {
color = vec3(0.05, 0.08, 0.15); // Dark navy blue
}
else if (world_pos.y < -100.0) {
color = vec3(0.08, 0.15, 0.25); // Deep ocean blue
}
else if (world_pos.y < 0.0) {
color = vec3(0.15, 0.28, 0.38); // Turquoise/aquamarine
}
else if (world_pos.y < 20.0) {
color = vec3(0.88, 0.85, 0.75); // Light sand/beige
}
else if (world_pos.y < 300.0) {
color = vec3(0.38, 0.58, 0.28); // Lush green (crop/forest mix)
}
else if (world_pos.y < 2000.0) {
color = vec3(0.171, 0.274, 0.191); // Deep green/brown forest
}
else if (world_pos.y < 4000.0) {
color = vec3(0.48, 0.44, 0.38); // Grayish brown (scree/rock)
}
else {
color = vec3(0.96, 0.96, 0.98); // Clean snow white
}
// Road
int segments_nearby = 0; // For line draw
float line_width = 0.3;
float line_softness = 0.05;
vec2 p = world_pos.xz;
vec2 norm = p / terrain_size;
float inside = step(0.0, norm.x) * step(0.0, norm.y) * step(norm.x, 1.0) * step(norm.y, 1.0);
ivec2 cell = ivec2(clamp(norm, vec2(0.0), vec2(1.0)) * float(grid_resolution));
cell = clamp(cell, ivec2(0), ivec2(grid_resolution - 1));
vec2 grid_uv = (vec2(cell) + 0.5) / float(grid_resolution);
vec2 entry = texture(grid_tex, grid_uv).rg;
int offset = int(entry.r);
int count = int(entry.g);
float min_dist = 1e20;
for (int i = 0; i < count; i++) {
int base = offset + i * 2;
int x1 = base % data_tex_width;
int y1 = base / data_tex_width;
vec2 uv1 = (vec2(float(x1), float(y1)) + 0.5) / float(data_tex_width);
int x2 = (base + 1) % data_tex_width;
int y2 = (base + 1) / data_tex_width;
vec2 uv2 = (vec2(float(x2), float(y2)) + 0.5) / float(data_tex_width);
vec2 a = texture(data_tex, uv1).rg;
vec2 b = texture(data_tex, uv2).rg;
vec2 pa = p - a;
vec2 ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
float d = length(pa - ba * h);
min_dist = min(min_dist, d);
if (d < road_width * 1.5) {segments_nearby++;} // Draw line logic, count segments
}
float mask = smoothstep(road_width, road_width - 1.0, min_dist);
float line_mask = smoothstep(line_width, line_width - line_softness, min_dist);
line_mask *= mask; // Ensure the line only appears on the road (multiplied by road mask)
// Intersection, more than 2 segments, dont draw line
if (segments_nearby > 2) {line_mask = 0.0;}
color = mix(color, road_color, mask);
color = mix(color, road_line_color, line_mask);
// Apply inside mask
ALBEDO = color;
}
I also carve the road into the terrain mesh but i dont use any shader for that. Its another part of the process.


