[UE Deformer Graph] partial application of the deformer graph

Documentation of Unreal nodes Skinned Mesh Vertex Attributes and Skin Weights as Vertex Mask

Skinned Mesh Vertex Attributes

Let's use the Skinned Mesh Vertex Attributes node of the deformer graph, this node allows you to read the value of a weight map painted over the surface of your model:

(In red each vertex is assigned 1.0f and the rest 0.0f)

Let's open up a skeletal mesh with the skeletal mesh editor:

First we need to create a per vertex attribute buffer, in New Attribute section type in your attribute name and hit Add Weight Map Layer

It should show up now in the Vertex Attributes Inspector:

We can move on to the Paint Maps tab and start painting the weight map:

Now go ahead and paint the area you'd like to deform:

Don't forget to push Apply otherwise changes will be discarded when leaving the menu.

For the deformer graph asset to access this new buffer it seems necessary to check import vertex attributes:

Go to a deformer asset, for instance /All/EngineData/Plugins/DeformerGraph/Deformers/DG_LinearBlendSkin

Let's modify to try out our new weight map, first add Skinned Mesh Vertex Attributes and connect it to your kernel:

Click on the node Skinned Mesh Vertex Attributes and set the name of the attribute in the detailed pannel

Now in your compute shader kernel code, for instance LBS, you can customize it to only deform when the weight map equals 1.0:

if (Index >= ReadNumThreads().x) 
    return;

float3 Position = ReadPosition(Index);
float4 LocalTangentX = ReadTangentX(Index);
float4 LocalTangentZ = ReadTangentZ(Index);
float3x4 WeightedBoneMatrix = ReadWeightedBoneMatrix(Index);

// Compute Skin
float3 SkinnedPosition = mul(WeightedBoneMatrix, float4(Position, 1));
float4 SkinnedTangentX = float4(normalize(mul((float3x3)WeightedBoneMatrix, LocalTangentX.xyz)), LocalTangentX.w);
float4 SkinnedTangentZ = float4(normalize(mul((float3x3)WeightedBoneMatrix, LocalTangentZ.xyz)), LocalTangentZ.w);



float3 Save = SkinnedPosition;
float w = ReadValue(Index); // Weight Map
// lerp between LBS position and input rest pose position:
SkinnedPosition = (1.0 - w) * SkinnedPosition.xyz + w * Position.xyz; 
SkinnedTangentX = float4( (1.0 - w) * SkinnedTangentX.xyz + w * LocalTangentX.xyz, LocalTangentX.w);
SkinnedTangentZ = float4( (1.0 - w) * SkinnedTangentZ.xyz + w * LocalTangentZ.xyz, LocalTangentZ.w);

    
// Set Data
WriteOutPosition(Index, SkinnedPosition);
WriteOutTangentX(Index, SkinnedTangentX);
WriteOutTangentZ(Index, SkinnedTangentZ);

And voila! Your model's vertices should only display animation for areas that were not painted

Here we did not took care of smoothing the transition between the head and the body, so the weight maps abruptly changes from 1 to 0.

LOD

I haven't looked much into LODs but you should check if Skinned Mesh Vertex Attributes works properly for all LOD levels (it wasn't for me at the time of writing). Especially if the LOD level was not auto-generated by UE but manually imported.

Skin Weights as Vertex Mask

Instead of manually painting a weight map, you can generate one using the underlying skin weights of your model. For instance, considering a subset of bones and their skin weights, and make the union of all these skin weight merged into a single weight map. This what Skin Weight as Vertex Mask allows to do

Here are the available settings:

Output: float Mask
a weight map (associates a float to each vertex).

Input:

- Skin Weight Profile if empty use the default skin weights of the skeletal mesh model. Otherwise use the extra skin weight profile defined in the skin profile section of the skeletal mesh editor.

- Bone Names list of bones to extract the weight map from.

- Expand Towards Leaf as the name indicates from bones in Bone Names we include leaf joints up to a point. Below are examples with various values of Expand Towards Leaf and Bone Names set to the root joint of the model:


Expand Leaf: 0

Expand Leaf: 3

Expand Leaf: 10

Expand Leaf: 999

As you can see when Debug Draw Included Bones is checked it displays the joints included in the generation of the weight map. Here the weight map disables skinning when set to 1. Root joint has no influence over the mesh so the deformation stays untouched. Level 3 expands the area to 1.0 for part of the hip and thighs since the root joint is connected to hip joint and thigh joints. Level 999 includes all joints the model is undeformed and displayed in rest pose.

- Expand Towards Root is as expected the same but probagates the influence towards the root joint. Below it is set to 3 with Bone Names set to "spine_05":

Mask computation

Below is the shader function that computes the mask. Skin weights are simply added together for any bone that is selected. In terms of cost the mask will generate a new "BoneIsSelected" buffer with the same structure and size of a skin weight buffer. An additional cost to loop trough and blend skin weights together is also incurred. So there might be some room to optimize this and have the vertex mask entirely computed on CPU, that would reduce memory and GPU computation cost.

float ReadMask_{DataInterfaceName}(uint VertexIndex)
{
#if !ENABLE_DEFORMER_BONES
	return 0;
#else
	float MaskValue = 0;
	uint NumBones = ReadNumBones_{DataInterfaceName}(VertexIndex);
	uint BoneIndex = 0;
	for (BoneIndex = 0; BoneIndex < NumBones; BoneIndex++)
	{
		float BoneWeight = ReadBoneWeight_{DataInterfaceName}(VertexIndex, BoneIndex);
		uint BoneIsSelected = ReadBoneIsSelected_{DataInterfaceName}(VertexIndex, BoneIndex);
		MaskValue += BoneIsSelected ? BoneWeight : 0;		
	}
	return MaskValue;
#endif
}

 
Paypal donation Donate

No comments

(optional field, I won't disclose or spam but it's necessary to notify you if I respond to your comment)
All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.
Anti-spam question: