# Question about Morph Targets implementation

**URL:** https://forum.castle-engine.io/t/question-about-morph-targets-implementation/2075
**Category:** Animation
**Created:** [January 4, 2026, 12:08pm UTC](https://forum.castle-engine.io/t/question-about-morph-targets-implementation/2075 "2026-01-04T12:08:38Z")
**Posts on this page:** 5
**Page:** 1

<div class="post-metadata">

### Author: ![DiggiDoggi](https://forum.castle-engine.io/letter_avatar_proxy/v4/letter/d/6a8cbe/32.png) [@DiggiDoggi](https://forum.castle-engine.io/u/DiggiDoggi)
#### Post date: [January 4, 2026, 12:08pm UTC](https://forum.castle-engine.io/t/question-about-morph-targets-implementation/2075/1 "2026-01-04T12:08:38Z")

</div>

Happy New Year everyone 🙂

I really do enjoy the skinned animations on GPU, and how I see it, it also makes implementation of Morph Targets (Blender’s shape keys) easier.

Are there any undergoing works in the morphs direction, or plans to implement it?

I understand that glTF loader already reads the position, normal, and tangent deltas. CGE has most of the requirements implemented already. Vertex attributes and animating morph weights are basically a copy/paste of skinning. So if no one is tackling the issue already, I though about coding it myself.

I admit I asked AI for a brief summary - CGE code is quite vast - to assess the amount of work. And, even though AI is very optimistic about how easy it is, I’m not so much 😉 Well, it’s not very complicated, but it needs changing few units, which is not always straight forward… CGE receives tons of updates and I may break compatibility.

I also think about necessary changes to shadows and bounding boxes. Skinned animations provide the necessary tools, but will need some additional work.

1. For shadows purpose, I guess, I should apply the morphs first (for example a human smile) and then skinned animation (like an open jaw). That would make the shadows work exactly the same way they would without morphs, if I’m not wrong.

2. To calculate the bounding box I’ll need to start with base mesh, and apply the morph weights on CPU. Then the CGE can apply bone transforms as it does already. So again, morphs first, then skinned bones.

Although the bounding box formula wouldn’t slow down the existing bounding box calculation much, having dozens of morphs per model could affect the FPS when many models are present and animated. But most morphs could be calculated only once when the shape changes (e.g. human model is loaded and their body weight or jaw size are set, and aren’t changing after that). Which means calculating the bounding box could be on-demand (by explicit call) rather that with every animation frame - as long as the overall shape/size don’t change much. It’s a compromise that would limit CPU usage. Or maybe would it be better to allow users some flag to control this behaviour? If I’m to submit a PR then it may be beneficial to use the flag, but it’ll require more changes, and it would have to be serialised adding more properties in the editor.

* * *

I know I could replace morphs (shape keys) with bones, but with dozens of morphs on every model it’ll be quite a burden. And as we already have in CGE most of what’s needed I think it wouldn’t hurt to add morphs.

I’m open to suggestions and welcome any advice.

---

<div class="post-metadata">

### Author: ![michalis](https://forum.castle-engine.io/user_avatar/forum.castle-engine.io/michalis/32/3_2.png) [@michalis](https://forum.castle-engine.io/u/michalis)
#### Post date: [January 4, 2026, 2:04pm UTC](https://forum.castle-engine.io/t/question-about-morph-targets-implementation/2075/2 "2026-01-04T14:04:10Z")

</div>

We indeed plan to support _morph targets_.

Useful reading to understand below musing: [how animation is done using “interpolator” nodes in X3D](https://castle-engine.io/x3d_implementation_interpolation.php).

2 ideas how to handle morph targets (at glTF → X3D conversion time):

1. Internally, X3D graph already supports a node `TCoordinateInterpolatorNode` which can be used to “drive” the animation of morphing. This addresses most concerns you mention:

2. Another approach, **and after some thinking I would actually recommend to start like it from the beginning** , would be to introduce a node node like (new) `TMorphTargetNode` to express it, and (already existing) `TScalartInterpolatorNode` that is animating a simple float value that determined the influence of this morph target. In this case you don’t use `TCoordinateInterpolatorNode`.

I hesitated, but from my experience with our `Skin`, I would say it makes sense to do this “proper implementation”, in AD 2, from start. Doing `TCoordinateInterpolatorNode` as a first step is… tempting, but ultimately it will result in some useless code that we’ll need to throw away later (precalculating animation at loading). However, if someone wants to contribute, I would be fine with either AD 1 or AD 2 approach, and in case of AD 1 - just plan on upgrading it to AD 2, but it will absolutely useful for most practical purposes already.

I hope this is all helpful – to explain how I imagine implementing it. As always, help is appreciated, and if you want to tackle this task and submit a PR – you’re absolutely welcome and thank you in advance! 🙂

---

<div class="post-metadata">

### Author: ![michalis](https://forum.castle-engine.io/user_avatar/forum.castle-engine.io/michalis/32/3_2.png) [@michalis](https://forum.castle-engine.io/u/michalis)
#### Post date: [January 4, 2026, 2:19pm UTC](https://forum.castle-engine.io/t/question-about-morph-targets-implementation/2075/3 "2026-01-04T14:19:40Z")

</div>

Note: I’ve done some edits to the above explanation after first posting. If you read it ~15 minutes ago, please reread, it’s better now 🙂

---

<div class="post-metadata">

### Author: ![DiggiDoggi](https://forum.castle-engine.io/letter_avatar_proxy/v4/letter/d/6a8cbe/32.png) [@DiggiDoggi](https://forum.castle-engine.io/u/DiggiDoggi)
#### Post date: [January 4, 2026, 4:43pm UTC](https://forum.castle-engine.io/t/question-about-morph-targets-implementation/2075/4 "2026-01-04T16:43:34Z")

</div>

Thank you for detailed answer. I think the option AD 2 makes a lot of sense. Especially that morphs can be mixed together at runtime.

It also aligns well with my draft, however I was keen to use “normal” class instead of x3d node. But having it as TX3dChildNode descendant opens important additional options:

1. Fully compatible with CGE philosophy,

2. Serialisation. If stored in an external x3d file it can be read by any x3d editing tool. Also it allows extracting the morph’s data from one model and reuse it for many - as long as indexes and vertex counts are unchanged (which is always true by-design for my parametric humans, at least the closest LOD level where morphs matters most).

I’ll have a look at it. I need it anyway, but can’t promise a short time frame.

---

<div class="post-metadata">

### Author: ![DiggiDoggi](https://forum.castle-engine.io/letter_avatar_proxy/v4/letter/d/6a8cbe/32.png) [@DiggiDoggi](https://forum.castle-engine.io/u/DiggiDoggi)
#### Post date: [January 18, 2026, 7:41pm UTC](https://forum.castle-engine.io/t/question-about-morph-targets-implementation/2075/5 "2026-01-18T19:41:28Z")

</div>

As I’m working on the morph targets, I also do the x3d extension, so they can be stored and accessed like everything else in CGE. No problems in coding here so far, however, I was thinking about the x3d nodes in the way that is:

- Fully aligned with CGE’s ways,
- Is fully aligned with x3d philosophy where things are named in a descriptive ways and independent of specific hardware or software.

I’m asking about it now, before I start implementing it, as it’s wiser than producing something “big” and then ask. Also, it’ll be nice to validate the auto-generation script before I proceed.

So, my proposal is:

1. The x3d node for morphs is named BlendShape, it contains the list of morphs and weights. The name BlendShape and BlendShapeTarget are descriptive and very commonly used, more than MorphTarget. The weights are per morph, so for X morphs equal X weights are needed. When x3d data is loaded (or glTF data), the list of weights will be truncated / extended with zeroes in case of a mismatch.

2. The node used for targets is named BlendShapeTarget.

- “name” - optional field useful for debugging and editor. It can be “Smile”, “Frown”, etc.

- “deltaCoords”, “deltaNormals”, “deltaTangents” - as the name says, it contains the difference as a Vector3F / Vector4F. The count of the deltas must strictly equal the count of vertices in the base model. That helps the GPU calculate stuff efficiently, and it’s expected by glTF. X3D doesn’t enforce it. I don’t intend to keep the “count” as a field in the x3d structure as it may happen that we load the morph list even before we load the model, which in turn means that we have the count anyway (Array.Length) and only need to validate the morph before we try to apply it to the model. The internal record TMorphTargetData holds the BaseVertexCount for speed though.

- “bakeAtLoad” - which indicates if the morph is applied at load time, and then discarded.  
It is useful when a morph (e.g. overall body shape such as muscular, tall,  
stylized) affects the geometry but is not animated.

I have decided to use internally (inside `TAbstractGeometryNode`) a structure named `TMorphTargetData`, because MorphTarget would be more intuitive from programming point (also because @michalis is using it 😉 ). That draws a clear distinction between the internal CGE-related structures from x3d.

The `TMorphTargetData` contains also HasNormals, HasTangents boolean fields determined by existence of the data in the source file. To the best of my knowledge the 2 are supported by glTF, however not always supported by others. I may be outdated but for Blender, Unity, Unreal, Maya and FBX tangents are always recomputed, while normals are optional. So I think having these Boolean fields is easier than _always_ validating the corresponding arrays.

The main reason behind asking for approval is that the rest of my work on it depends on these steps to some extent, and your opinion matters. Most importantly, CGE has some influence on x3d standards, so maybe our implementation of morphs could get some attention and I’d love to hear it was accepted 🙂 The closer we get to possible real extension, the less work in the future too.

* * *

Here’s the “script to auto-generate the x3d nodes with `x3d-to-pascal`. It’s attached to CastleEngineExtensions.txt. I still have limited knowledge in this part of CGE, so I may be wrong with my definitions. Eg. do we need chEverything on “name” change.

```auto
BlendShapeTarget : X3DChildNode {
  SFString [in,out] name ""
    change: chEverything
    doc: """
    Optional name of this morph target. For example 'Smile'.
    """

  MFVec3d [in,out] deltaCoords []
    change: chEverything
    doc: """
    Required. 
    @italic(Each delta array (coordinates, normals, and tangents) must match the base mesh vertex count).
    """

  MFVec3d [in,out] deltaNormals []
    change: chEverything
    doc: """
    Optional. 
    """

  MFVec4d [in,out] deltaTangents []
    change: chEverything
    doc: """
    Optional. 
    """

  SFBool [in,out] bakeAtLoad FALSE
    change: chEverything
    doc: """
    It is useful when a morph (e.g. overall body shape such as muscular, tall, stylized) affects the geometry but is not animated.
    Every morph can be stored separately from the mesh and reused across models, but @italic(static) morphs can be discarded once the model is loaded,
    because after that they do nothing.
    This helps reduce memory usage and avoids keeping morph data that is never needed at runtime.
    It aligns well with 3d engines and modelling tools, where you can @italic(bake) morphs, actions, and deformations.
    """
}

```

```auto
BlendShape : X3DChildNode {
  SFString [in,out] name ""
    change: chEverything
    doc: """
    Optional name of this morph group. For example 'Facial_expressions'.
    """

  MFNode [in,out] targets []
    change: chEverything
    range: BlendShapeTarget
    doc: """
    List of Blend Shape targets, also known as Morph Targets (glTF) and Shape Keys (Blender)
    """

  MFFloat [in,out] weights [] 
    doc: """
    Morph target weights. Must have the same count as @italic(targets)targets.
    """
}

```
