How to create Shape Keys in Geometry Nodes, including support for curve handles?

I am trying to replicate the functionality of Shape Keys but in Geometry Nodes. At the core of the setup I am using the technique quellenform introduced in this answer, but I am adding the ability to chain multiple "Shape Key" Group nodes together, support for vertex groups, the ability to specify the "Relative Key", and the ability to expand the Range Min/Max to -10 to 10. Where I am stuck though is with curve objects. Bezier handle positions are not being interpreted correctly.

(.blend file links are included further below.)

Below is a screenshot of two test objects and their deformations - on the left a mesh cube that deforms correctly and a Bezier circle that does not deform correctly. The cube has a vertex group specifying deformation on only its left half, that's why it looks the way it does. To the right of each of those objects are three other objects with matching indices, that the deformation positions are taken from - the shape key poses, basically. The problem is that although Curve.Basis is supposed to be deforming to look like Curve.Key1 (because the modifier's slider is at 1.0), instead it's bent in the shape of a sine wave. I believe that the curve points are in the right place, but their handles are not.

The expected behavior would be the behavior of a typical curve object that has Shape Keys, where those keys deform the handles on the curve's points.

I'll explain how this works and what is working correctly, before I go into what isn't.

Below is an overview of the node tree setup. There are two types of Group Nodes - one that specifies the "Shape Key Basis" (it gets the vert positions using a Transfer Attribute node), and another that represents a "Shape Key" and can be iterated repeatedly as many times as desired, one iteration per key.

The screenshot below shows the inside of the "Shape Key Basis" group. It gets Attribute data and passes it down the chain. The Relative Key of the "basis" get passed unchanged from the "Relative to Basis" output socket. It gets routed into the "Rel. to Basis" input socket of the next Group node. The node tree is set up so every key will be relative to the basis by default. But alternatively, Attribute data for the Relative Key can be plugged into "Rel. to Other (specify)" and it will override "Rel. to Basis". This is designed to be the equivalent to selecting a key from the Shape Key part of Blender's UI where there is a field labeled "Relative To".

Above: The contents of the frame labeled "For Handles of Curve Objects" is what is handling curve data in this group, so that's the place to focus on most.

Now let's get into the real meat of this thing - the "Shape Key" Group node:

Yes, it looks like a lot is going on here. But the only area giving trouble is inside the bottom frame labeled "For curve objects...". Also the purple vector wires getting routed through to the Group Output sockets that are associated with left and right Bezier handle vectors are something to pay attention to.

I learned about the Curve Handle Positions node from this answer by Crantisz and used the same Transfer Attribute node logic that worked for vertex positions to try to assign the handle positions iteratively. But somewhere something isn't right.

gn-shape-keys-curve-handles-incorrect.blend (Blender 3.2.1)

A simple working setup

Something encouraging is that I've managed to get curve handles working in a simplified file with no iterating Group nodes or additional features:

gn-shape-keys-curve-handles-working.blend (Blender 3.2.1)

So this leads me to believe it is possible, with some tweaking, to get this working in iterated form. And it will be quite a handy node tree to have if we can figure out how to handle the handles. Hopefully somebody can spot where I've gone wrong. Cheers.

• @quellenform Thanks! Looks promising. I'll see if I can implement that in my setup. Jul 11, 2022 at 15:51
• Please excuse my naive question--I am using Blender 3.5 now. This example .blend is very helpful! -is there a way to "randomize" the mix-shape order of the "shape keys" in Geometry Nodes so that it won't always cycle from 1 to 2 to 3? sorry I cannot figure this out, and thanks for the above file.
– Ge53
Jan 13, 2023 at 23:01

Shape Keys in Geometry Nodes

Here is my solution. I completely rebuilt this with a different, simpler approach. The Relative Key is now always the basis geometry. The object to put the Geometry Nodes modifier on is the object with the basis geometry.

Here is an overview of the node tree:

In each "Shape Key" Group Node the key pose object's geometry is evaluated, and its attributes get compared against those of the basis geometry, scaled as specified by the Value sliders, and added to the shape key mix.

Inside the "Shape Key" Group Node:

The mix of shape key vectors is then committed inside the final "Shape Key Mix" node.

Inside the "Shape Key Mix" Group Node:

↑ The most important detail for getting curve handles to be interpreted correctly was routing Attributes for the Curve Handle Positions of the basis geometry into the Position socket of each Set Handle Positions node. (The mix of vector attributes for the mix of the other keys is routed into the Offset socket.) While the Set Position node's Position socket can be left disconnected, this is not the case for Set Handle Positions.

The objects with the poses representing each "shape", a Vertex Group, and the Shape Key Value sliders are accessible from the modifier:

↑ A Vertex Group value of 1 means all geometry is selected. So if not specifying a Vertex Group here, this should be left on 1. Vertex Groups are only supported for mesh objects, but I wired the Selection socket of the Set Handle Positions nodes anyway, in the event a different selection method (besides Vertex Groups) is used.

Some limitations to note:

• As Set Position now only occurs once, only one Vertex Group input is supported for selecting the vertices to target for the entire shape key mix.

• 2D curve objects yield buggy results. The fill geometry tends to jut out of the curve bounds. I suspect this can't be helped. Make sure to set the shape type to "3D" when using curves.

• As mentioned above, the Relative Key is now always the basis geometry. Since I always mix relative to Basis, (doesn't just about everyone?), I chose to simplify the node tree at the expense of the option to specify non-basis geometry.

• The "Range -10 to 10" setting was dropped for simplicity's sake. Instead, if a range other than 0~1 is desired, it should be specified in the Geometry Node Editor's Group tab by selecting a Value socket from the list and setting Min/Max.

• There can be a slight mathematical difference in the offset compared to actual Shape Keys. I'm not sure why this is. I have asked a separate question about it.

geometry_nodes_shape_keys.blend (Blender v3.2.1)

Notes regarding the example file

Inside the example .blend file there are three objects using a Geometry Nodes modifier for Shape Keys. Their object names end in .Basis and they are:

• A curve object
• a flat mesh object with separate parts for separate keys
• A subdivided cube mesh object keyed to completely different shapes

Each of these objects has a hidden Collection you can see in the Outliner that contains the objects storing the key poses that the node tree references. Each object also has an accompanying actual Shape Keyed version for comparison.

Note that it is not necessary to delete unused Shape Key data from objects being referenced by Geometry Nodes. If there are Shape Keys on the object, the geometry used will be the mixed result of those keys. To isolate the the desired key, set its Value to 1.0 and consider unchecking (or at least zero-out) the other keys. Of course deleting the Shape Key data from the object, leaving it in the pose you want is also fine. It's probably best to keep a copy of the keyed original object if you started with one, especially if it is a curve object, since Join as Shapes is currently not working for curve objects.

I would have a solution for you here (bezier curves only), where you can cascade the node groups even easier.

I'm honestly not quite sure if this is what you wanted to achieve, but here I simply determine the differences to the original curve and store them in a Named Attribute.

I add the previously calculated values and finally I transfer these vectors to the handles.

The node group that calculates the vectors looks like this:

And when all shapes have been processed, I simply transfer these values as offset to the individual positions with this group:

(Blender 3.2)

• Thank you for this contribution! :-) It's interesting to see an alternative to my solution that uses Named Attributes. Sorry, it seems I had either forgotten to accept my answer or was waiting to see if anyone could resolve the accuracy issue. (Please let me know if you believe your solution does.) In any case, I think it's useful to be able to see both approaches. Upvoting. Aug 30, 2022 at 2:14
• @Mentalist OK, I see you take this more seriously than me ;-) ...that was not meant angry, I was just wondering. Aug 31, 2022 at 7:38
• Whoops! Reading this I went to my profile and accepted the answers to my questions. 😓 Nov 1, 2022 at 15:32