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.
Last active
August 30, 2021 15:22
-
-
Save winston-yallow/3ae623da94453216805bf50a6af4b55a to your computer and use it in GitHub Desktop.
Godot - Curve Follow Shader
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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