Shader for (cubic) bezier curve

Hi there!

Following a suggestion of @Pkeod, I am posting here a minimal Defold project with a shader for drawing cubic bezier curves.

You can use the mouse to move the start and end points and the control points. The controls points define the tangents at the start and end points of the curve. (Note that moving a start/end point does not move the corresponding control point, this may look strange if you are used to a vector drawing program…).

Some notes.

  1. A (planar) Bezier cubic curve is just a curve parametrized by two polynomials of degree 3. The math in the vertex shader is quite simple: a linear system to pass from points data to coefficients of these two polynomials, the evaluations of these polynomials and of their derivative to compute the tangent (and then the normal) to a point of the curve. If someone is interested I may explain it in detail.

  2. The shader works in world coordinates, but one should be able to adapt it to local coordinates.

  3. The model is just a rectangle with two opposite sides subdivided in 64 segments with texture uv-coordinates. The points are then moved on the curve by the vertex shader. (EDIT: here I was wrong, I am not using the uv-coordinates, just the absolute position of the vertices of the model.)

  4. There is a little trick: the texture used is a small 2x4 rectangle with only the central 2x2 pixel blacks and the upper and lower pixels transparent. In this way the GPU interpolates among black and transparent and gives us a perfect smooth curve (no antialias used!).

For any further info please ask here or on Slack.

Ciao, Rocco.

defold_bezier.zip (935.4 KB)

19 Likes

It’s brilliant, and super useful! Good job!

3 Likes

Thanks!

Now that I have taken a second look at this after a long time, I see that the shader maybe somehow simplified. Lines 24–33 compute the coefficients a, b (two vec4) of the polynomials. These are constant along all vertices, hence it maybe moved back to lua code and one can have them as uniforms instead of pnt and tan (points and tangents). In other words, now the same computation is performed for each vertex (i.e. about 256 times for each curves) by the GPU, moving it to lua one has a unique computation each time the curve is changed but in lua. (I hope I have explained the point…)

Ciao, Rocco.

3 Likes

Perhaps make this into a generalized module on GitHub?

I am sorry but I don’t understand…

The materials, model, assets, and helper scripts could be packaged into a generic library that others could include. It’s not necessary to do but it could be done. :slight_smile:

1 Like

I see. Only the bezier curve related part, not also the demo allowing you to move the points, right?

I am not an expert in creating such a module. But I may try.

What do you think about the GPU / lua options I was writing above? I remember being quite scared at that time about the lua performance; today I am way more confident about it! So I would prefer to move that computation to lua.

4 Likes

I encourage you to try the ideas you posted about!

You can put demos in a separate folder rather than the one that you designate as the library folder.

Check other projects for an example of how to set it up https://github.com/indiesoftby/defold-hyper-trails

6 Likes

This great! Thank you for sharing!

3 Likes

I was using this shader and made a library. Here is the github link.

9 Likes

Hello! I set position from the other ‘go’ in update function and see delay.
How fix this?

Hi Alexander,

it is hard to tell where the problem is without any information. Are you sure it is a problem of the shader and not of your “general structure” of your code? Maybe you are using some message passing?

Ciao!

bizier_delay.zip (417.3 KB)

Thank you for sharing the project.

You are animating the end point sprite position via go.animate and you are updating the shader constants (to move the end point of the bezier curve) via a message.

I would say that the message is sent at the end of the frame and you will see the bezier curve end moved in the next frame. I am not sure about this but it is a possible explanation.

I would suggest that you update the end point sprite and the shader constants in a uniform way: both in the update function of the same script. Maybe you could animate (using go.animate) a script property and not directly the end sprite position.

I hope this could help!

Ciao, Rocco.

5 Likes

Any idea how to draw more than one curves?

I simply added a duplicate and a new model on which I draw second line, but noticed it simply increases draw calls, so for many, many lines it would be an overkill :confused:

How do you think could it be optimised? Somehow pass multiple values to material? Could one model draw more than one line if this is a vertex based approach (because my first, gut feeling is that no, but I don’t know how it could be possibly moved to fragment shader :confused: )

Hi Pawel,

I think it should be expected that there is a draw call for each curve. Indeed each curve needs its own values for the attributes in the shader.

Maybe some kind of buffer could provide all the attributes for a list of curves. But, unfortunately, I don’t know how to implement this (should it be possible). I am sorry!

Ciao, Rocco.

2 Likes

It should be possible to do now that it’s possible to upload array data to shaders!

1 Like

Yes, this should be quite easy to implement with arrays.

1 Like