Skip to content

Instantly share code, notes, and snippets.

@winston-yallow
Last active August 30, 2021 15:22
Show Gist options
  • Save winston-yallow/3ae623da94453216805bf50a6af4b55a to your computer and use it in GitHub Desktop.
Save winston-yallow/3ae623da94453216805bf50a6af4b55a to your computer and use it in GitHub Desktop.
Godot - Curve Follow Shader

Attention

I created this for smooth curves with a small bake interval. The shader will interpolate the forward direction, so this is not suited for curves that are not smooth.

shader_type spatial;
uniform float speed = 0.15;
uniform sampler2D curve: hint_black;
const int CURVE_POSITION = 0;
const int CURVE_FORWARD = 1;
const int CURVE_UP = 2;
vec3 get_curve_data(float t, int data) {
float size = float(textureSize(curve, 0).x);
float offset = clamp(t, 0.0, 1.0) * (size - 1.0);
float off_a;
float ratio = modf(offset, off_a);
float off_b = min(off_a + 1.0, size - 1.0);
vec3 a = texelFetch(curve, ivec2(int(off_a), data), 0).xyz;
vec3 b = texelFetch(curve, ivec2(int(off_b), data), 0).xyz;
return mix(a, b, ratio);
}
mat4 get_curve_transform(float t) {
vec3 pos = get_curve_data(t, CURVE_POSITION);
vec3 forward = get_curve_data(t, CURVE_FORWARD);
vec3 up = get_curve_data(t, CURVE_UP);
vec3 side = normalize(cross(forward, up));
return mat4(
vec4(side, 0.0),
vec4(up, 0.0),
vec4(-forward, 0.0),
vec4(pos, 1.0)
);
}
void vertex() {
float t = mod(TIME * speed, 1.0);
mat4 transform = get_curve_transform(t);
VERTEX = (transform * vec4(VERTEX, 1.0)).xyz;
}
func curve_to_texture(curve: Curve3D) -> ImageTexture:
# Bake curve into a texture. The texture will have three rows of
# pixels, representing (top to bottom): position, forward, up
# The side vector is not baked as it can be easily calculated
# from `forward` and `up` by using the cross product. That way
# we save one texture read in the shader.
if curve.get_point_count() == 0:
return ImageTexture.new()
# Using StreamPeerBuffer as it is a convenient way to construct
# byte arrays. We do not really use any networking features.
var buffer := StreamPeerBuffer.new()
var baked_points := curve.get_baked_points()
for point in baked_points:
buffer.put_float(point.x)
buffer.put_float(point.y)
buffer.put_float(point.z)
var baked_forwards := get_curve_forward_vectors(curve)
for forward in baked_forwards:
buffer.put_float(forward.x)
buffer.put_float(forward.y)
buffer.put_float(forward.z)
var baked_ups := curve.get_baked_up_vectors()
for up in baked_ups:
buffer.put_float(up.x)
buffer.put_float(up.y)
buffer.put_float(up.z)
var image := Image.new()
image.create_from_data(baked_points.size(), 3, false, Image.FORMAT_RGBF, buffer.data_array)
var texture := ImageTexture.new()
texture.create_from_image(image)
# Disable filter and mipmaps
texture.flags &= ~ImageTexture.FLAG_FILTER
texture.flags &= ~ImageTexture.FLAG_MIPMAPS
return texture
func get_curve_forward_vectors(curve: Curve3D) -> Array:
# Calculates the forward vectors by taking the direction from
# the point before to the point after. Tries to preserver original
# out/in vectors for first/last point.
# Not necessarily the best way, but it works close enough.
var vectors := []
if curve.get_point_count() == 0:
return vectors
var baked_points := curve.get_baked_points()
var first := curve.get_point_out(0)
var last := -curve.get_point_in(curve.get_point_count() - 1)
for idx in baked_points.size():
if idx == 0 and first.length() > 0:
vectors.append(first.normalized())
elif idx == baked_points.size() - 1 and last.length() > 0:
vectors.append(last.normalized())
else:
var before := baked_points[max(0, idx - 1)]
var after := baked_points[min(idx + 1, baked_points.size() - 1)]
vectors.append(before.direction_to(after))
return vectors
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment