Working with MorphTargets and Morphable Heads - ME3Tweaks/LegendaryExplorer GitHub Wiki
The Mass Effect trilogy uses a system called Morph Targets on top of base head meshes to create a variety of different face shapes. This is used both by the character creator and for most NPCs. Until recently, it was very difficult to preview, let alone edit Morph Targets, but recent experiments in LEX have changed this. We now have the code to export, preview, and re-import morph targets, and even create new ones for existing or new head meshes. I will detail how to work with morph targets in this article.
Requirements
- LEX Nightly from July 2025 or newer
- Basic knowledge of LEX (I will assume you know how to open a pcc file and navigate to a particular export and view the properties and binary)
- Experiments enabled in LEX
- Blender 4.2-4.4 (there are known incompatibilities with older versions; future versions may break compatibility; untested with other 3d modeling software)
- The DarklightGames psk/psa plugin for Blender, which can be installed easily in the extensions menu in Blender
- basic 3d modeling knowledge
Background
Morph Targets
This section is not necessary, just helps you understand better what you are working with. So, what exactly is a morph target or a MorphTargetSet? Let's look at an example to see. I will show examples from LE1 in the file BIOG_HMF_HED_PROMorph_R, which has a MorphTargetSet at the top of the file in export 2383. There are two properties on this export: a list of targets (under the export in the tree) and a base skeletal mesh. The targets are the morph targets that can be applied to that base head. They don't make any sense without a base head, as they are just offsets on top of that base head. They also will not work with any other base head, as they rely on the exact vertex order and count. The MorphTargetSet is used by the character creator and was used during the game development/build to generate most NPC's heads. There are MorphTargetSets for humans (male and female), Asari, Turians, Krogan, Salarians, and Batarians. Note that the base heads and animations got reworked between ME2 and ME3, so while things are identical between ME1 and ME2, they are generally different in ME3 and are not compatible with the base head from the other one. They are identical between the original release and Legendary Edition for the corresponding game, though.
So, let's dig into a specific morph target. I will choose eyes_wide, export number 2288. This morph target, when applied, makes the eyes further apart on a character. If you look at the binary interpreter tab of this export, you will see MorphLODModels and BoneOffsets. MorphLODModels has two entries under it for LOD 0 and LOD 1. We are only going to worry about LOD 0. If you expand that node, you will see NumBasemeshVerts, which is the number of vertices in the base mesh, 2232. You will also see a list of 481 vertices, each with a position delta, tangentZ (aka normal) delta, and source index. Note that the source index is not necessarily the same as the index of the item. The TangentZ Delta does not appear to be actually used in game and is likely an artifact of the build process.
So the first item means that vertex 0 in LOD0 of the base mesh will get moved by the position delta when this morph target is applied. Looking at the position delta, all the components are small, but the y component is by far the largest. This makes sense. If we are making the eyes further apart, you will mostly need to move vertices around the eyes in the y direction, which is side to side. The other 480 vertices will look similar.
Along with the vertex offsets, you can see bone offsets. There are 6 items, each with a bone name and a vector offset. All the bones have to do with eyes and eyelids. This makes sense for this morph target. The offsets are less intuitive to visualize for bone offsets because they are relative to the bone's rotation, so increasing y might move a bone down or backwards, rather than side to side as you would expect. Note that the bone offsets are not specific to any LOD, as all LODs share the same skeleton.
All togetherm the bone offsets set a new base pose for the head on top of which all animations are applied, in this case moving the eye and eyelid bones further out from the center of the face, and the vertices near the eyes are moved as well so as to not clip with the moved eyes.
Below are screenshots from Blender to help visualize how this morph target works. We will go through how to do this visualization shortly.
base head with no morph targets applied:
head with eyes_Wide vertex offsets applied:
head with only eyes_Wide bone offsets applied:
head with eyes_Wide vertex and bone offsets applied:
Note that in the case of only the bone offsets, the eyeball clips with the surrounding eye socket. In the case of only vertex offsets, there is an odd gap between the eyeball and the outside edge of the eyelids. With both applied, everything aligns correctly. Animations will also not work correctly without both applied.
There is one final piece needed to understand morph targets. They can be applied by a value between 0 and 1. If a morph target is applied with a value of 0, it does nothing. If it is applied with a value of 1, it moves the corresponding vertices and bones by the full offset. If it is applied with a value of .5, it will move the vertices and bones by half the offset. For any value in between, you multiply the offset by the value and add that to the positions on the base mesh. You can apply as many morph targets as you want at the same time, and they all apply on top of each other. This is how hundreds of unique NPC heads were created during the game development, and how the sliders in the character creator change the shape of Shepard's head. This is combined with swapping out textures and various material parameters to create most of the faces in the games.
Now that we know how to work with Morph Targets and the meshes they work on, we can use them to easily make new NPC heads, add new sliders to the character creator (or mods such as AMM LE3 that can work with morph targets), or even make brand new head meshes with new ways to change the shape.
Morphable Heads
This section is also not necessary, but it will help you understand how meshes are stored in Mass Effect and other formats and how that affects what you can do with them, as well as why it was so hard to edit morph targets for so long. Morph targets rely on the exact vertex count and order. If the number of vertices changes, or the order changes, the morph targets will no longer apply how you want them to. The issue is that the ways we had to export and import meshes both messed up the vertex count and order. This has been rectified by recently added LEX experiments that can export and import psk files without messing up vertex count and order. Luckily, the Blender psk pipeline maintains vertex count and order as long as you follow a few rules.
To understand why the previous workflow messed up the count and order, let's look at the example of a simple UV mapped cube. If you create this in Blender, it will have 8 vertices and 6 square faces, as you would expect. But if you exported this as psk and then imported it into Mass Effect using UDK, it would import with 12 faces and 14 vertices. If you export using UModel and put it back into blender, it will have 12 faces and 8 vertices again. Obviously if you are relying on exact vertex count and order, it is unacceptable for the count to change between 8 and 14 seemingly randomly, and it was exactly this behavior that prevented us from working with morph targets. Heads, when exported, would have a different number and order of vertices than they did in game, and again when imported back in.
This discrepancy has to do with what data is stored on vertices and how, and how programs attempt to translate between these different systems.
In Blender, a vertex has a position in space and nothing else. There are also what are called edge loops, which have a material index, a UV coordinate, and a vertex index. There can be zero or more edge loops per vertex. Each face has an edge loop per corner, which will be shared with adjacent faces if possible. It is not possible to share wedges if the material or UV coordinate do not match, so there will be 2+ wedges per vertex anywhere there is a material or UV seam.
The psk format stored things similarly, but it calls edge loops weighted edges, or wedges. Also, it enforces that all faces are triangles specifically, whereas Blender allows for more sides. Due to the default and logical UV mapping of a cube introducing UV seams, a cube will have 8 vertices and 14 edge loops/wedges within Blender or a psk file. Each square face in Blender will be converted to two triangles, leading to the face count difference.
The big problem arises when you look at how vertices are stored within ME games. There is no concept of edge loops/wedges. Just vertices that have exactly one position and UV Coordinate each. The material is stored on the triangle, not the vertices. So any time there is a UV seam, all of those vertices are duplicated on either side with the same coordinate but different UV coordinates. This leads to a vertex count as high as (or higher than) the wedge count in the other systems.
The problem is that UModel, when we export a mesh, combines these duplicated vertices across UV/material seams, messing up the count and order. UDK, when we reimport it, has to redo the duplication of vertices across UV/material seams, and it does so a bit differently than UModel. This is fine for meshes where the vertex count/order doesn't matter, but the morph target sets have those duplicated vertices and exact order built into them, and will not work if that count or order changes.
The solution to this is to export from LEX without making any attempt to de-duplicate vertices, then carefully avoid editing the mesh in any ways that change the vertex count or order, then import maintaining that count and order. This is exactly what the relatively new experiments do, which allows us to actually work with head meshes that have morph targets without breaking said morph targets.
How to import and visualize morph targets in Blender
You will first need to find a MorphTargetSet object in Package Editor. You can do this using Asset Database. You can also find all MorphTargetSets from the game compiled here. This includes ones pulled from OT1, where they shipped a ton of assets that were not used in the final build, along with some kindly provided to us for LE3. LE2 uses the exact same morph targets as LE1, and Legendary Edition uses the same ones as the original releases (with some very minor fixes for eye clipping, I think). Note that ME1/2 morphs and ME3 morphs are not compatible.
Once you have found and selected a MorphTargetSet in Package Editor, open the experiments menu, hover over "Squid's new and exciting ways to break stuff" and then select "Export selected item as pskx/psa". It will prompt you for where to save the output files. It will create three files: a pskx containing all of the base head information and the vertex offsets of each morph target, a psa file containing the bone offsets of each morph target as a single frame animation, and a config file telling it how to import those animations correctly. All will share the same name as whatever you tell it to save as, just with different extensions.
open Blender and either drag/drop the pskx file onto the 3d viewport or select File > Import > unreal PSK. I recommend leaving the import options as the default, as shown below. The "Shape Keys" and "Import Skeleton" boxes must be checked.
Next, ensure that you are in Object mode and the just imported armature is selected. It should already be like this after importing the new skeletal mesh. Drag and drop the psa onto the 3d viewport or select File > Import > Unreal PSA. The default settings will not work here for this purpose. make sure that "stash" and "use config file" are checked when you import. Make sure that "prefix action name" is not checked. The import settings should look like this:
Now, Blender has a feature called shape keys, which can apply vertex offsets with a value, just like morph targets, and this is how they get imported. However, Blender cannot by default apply bone offsets along with vertex offsets. I have made a script to hack this functionality into Blender. Once you have the pskx and psa imported as detailed above, go to the scripting tab, create a new script, and paste the following code into it:
import bpy
import re
def findShapeKeys(armatureObject):
for obj in bpy.data.objects:
if (obj.type == 'MESH' and armatureObject in [m.object for m in obj.modifiers if m.type == 'ARMATURE']):
return obj.data.shape_keys
# loop through all armatures, do this for each one
for armature in bpy.data.objects:
if (armature.type != 'ARMATURE'):
continue
shapeKeys = findShapeKeys(armature)
for track in armature.animation_data.nla_tracks:
# get the name, which may include some .### suffix. we need that
name = track.name
# we also need the name without the suffix
match = re.match('^(.+?)(?:\.(\d+))?$', name)
cleanName = match.group(1)
strip = track.strips[name]
# set the blend type to combine so they can layer on top of each other
strip.blend_type = 'COMBINE'
# unmute this; we want the influence to be determined by the shape key influence
track.mute = False
# set animated influence to true so we can add a driver to the influence
strip.use_animated_influence = True
# next, add a driver to the armature targeting the track influence
driverCurve = armature.driver_add(f'animation_data.nla_tracks["{name}"].strips["{name}"].influence')
# set the driver type
driverCurve.driver.type = 'AVERAGE'
# add a new variable
# TODO don't add duplicates if this is already present
v = driverCurve.driver.variables.new()
# this is the object it will get he value from
v.targets[0].id_type = 'KEY'
v.targets[0].id = shapeKeys
#this is the path on that object
v.targets[0].data_path = f'key_blocks["{cleanName}"].value'
# this is the name; probably not neccessary
v.name = 'value'
# delete the keyframe it automatically adds and which breaks the driver
strip.keyframe_delete('influence', frame=0)
# lock this track at the end
track.lock = True
Press the "run script" button.
It's not essential to understand what the script does, so skip this paragraph if you want. This script should create a driver between each shape key value and the corresponding animation track's animated influence track. It also changes each animation track to be additive, rather than replace other tracks. It will clean up the animated influence keyframe that is automatically created so that the driver actually works. It unmutes each track so that they actually apply animations. It locks the track because there is no reason to be editing it after this.
What this means is that when you change the shape key value to 0.5, it will also apply the bone offsets with the same name with an influence of 0.5, which should match the in game behavior. Note that you need to have animations playing (spacebar is the default keybind) or change frames to see the bone offsets change after changing a shape key value.
I also recommend that you apply the following settings on the armature modifier of the mesh: "On Cage" and "Edit Mode" shown underlined in red. These are not enabled by default, but will be very helpful for editing while seeing the bone offsets applied.
Now, you finally have everything imported and linked up and ready to visualize. Select the mesh (not the armature), go to the "data" tab (looks like a triangle with squares on each vertex, by default on the left side of the lower right screen area), and find shape keys. select a shape key, make sure animations are playing so the bone offsets apply, and change the value and you should see the shape of the face update. Congratulations, you can now visualize what morph targets look like and you are set up to start editing them!
Editing Morph Targets
You can edit both the vertex offsets and the bone offsets of a morph target in Blender with accurate visualizations, as well as adding new ones. I'll walk you through how to do these things and get it back into LEX. First, though a note about what you cannot do:
You cannot edit anything about the vertices other than position. No adding or removing vertices. No modifiers that create new vertices, such as subdividing. No splitting faces away from their neighbors, as this implicitly creates new vertices as it duplicates on either side. No merging vertices by distance, as this combines vertices with the same (or similar) positions, therefore removing some. You can only move vertices.
As for bones, you can only translate them. No rotations can be added to a morph target.
If you are editing the head rather than the morph targets, there are a couple more things you can do without breaking morph targets. You can technically add or remove triangles (using the existing vertices only) although there is very little reason to do so. You can change the skeleton freely, adding bones or changing their base positions including rotations. You can change the materials or add new ones as long as you don't introduce new material seams. So adding separate materials per eye is fine, or putting the inner mouth portion of the scalp material on a separate material are fine, but splitting the face or scalp material down the middle would not be allowed.
All that aside, let's edit an existing morph target.
TBD