Unreal Engine C++: how to add custom nodes to the deformer graph

Unreal Engine's "Deformer Graph Editor" is truly amazing, it allows you to define custom deformations for your character through a graphical node system. I can't wait new skinning algorithm implemented with this Deformer Graph such as, cage base deformer, or even collisions directly applied onto the skeletal mesh. In addition to the UI, the underlying C++ API to launch compute shaders is clean and so hard to grasp. A great place to start looking into is:

/UnrealEngine/Engine/Plugins/Animation/DeformerGraph/Source/OptimusCore/Private/DataInterfaces

As it defines the internal nodes you will find in the Palette menu of the Deformer Graph:

Custom Nodes

Now you may want to create your own plugin and build up the existing Deformer Graph by adding new nodes. The following  github repository ] is a template plugin that demonstrate how to do it. For instance, one of the node allows to fetch the matrix of some joint according to its name. In UE 5.2, you can actually do this via the blueprint and set a transform variable in the deformer graph, or get the transform from a control rig via animation attributes. But the goal here, is just to demonstrate how to add nodes.

Setup the plugin

To add a new custom node, start by creating a plugin through the Engine's interface. The file *.uplugin should depend on the DeformerGraph plugin and I set the "LoadingPhase" to be the same as the DeformerGraph plugin i.e. "PreDefault":

"Modules": [
    {
    	"Name": "DeformerGraphBonusTools",
    	"Type": "Runtime",
    	"LoadingPhase": "PreDefault"
    }
],
"Plugins": [
    {
    	"Name": "DeformerGraph",
    	"Enabled": true
    }
]

You will also want to add the correct module dependencies to the Deformer Graph and related features in your PluginName.Build.cs:

PublicDependencyModuleNames.AddRange(
    new string[]
    {
        "Core",
        "OptimusCore",
        "ComputeFramework",
        "RHI",
        "RenderCore",
        "Renderer",
        "Projects"
    }
);

Add code for nodes

Now, you can copy paste some existing nodes in present in the folder /DataInterfaces. You will need to rename a bunch of things such as class and structs names, but also some string describing the name of the nodes, for instance:

TCHAR const* GetClassName() const override { return TEXT("Skeleton"); }
FString UOptimusSkeletonDataInterface::GetDisplayName() const{	return TEXT("Skeleton"); }
BEGIN_SHADER_PARAMETER_STRUCT(FSkeletonDataInterfaceParameters, )
struct FSkeletonDataInterfacePermutationIds { ... }
...

OPTIMUSCORE_API will also become YOURPLUGINNAME_API

Specify shader file path

A node is usually attached to a shader file defining the functions the node provides when connected. For new shader placed in our new plugin folder, we have to tell unreal where to look, this is done at the startup of the plugin's module:

#include "Interfaces/IPluginManager.h"

void FDeformerGraphBonusToolsModule::StartupModule()
{
    // This code will execute after your module is loaded into memory; 
    // the exact timing is specified in the .uplugin file per-module
    
    // We register the folder in which we store the shader files of our new nodes:
    FString PluginShaderDir = IPluginManager::Get().FindPlugin(TEXT("DeformerGraphBonusTools"))->GetBaseDir();
    PluginShaderDir = FPaths::Combine(PluginShaderDir, TEXT("Source"));
    PluginShaderDir = FPaths::Combine(PluginShaderDir, TEXT("DeformerGraphBonusTools"));
    PluginShaderDir = FPaths::Combine(PluginShaderDir, TEXT("Shader"));	
    
    AddShaderSourceDirectoryMapping(TEXT("/Plugin/DeformerGraphBonusTools"), PluginShaderDir);
}

Now our node will know where to look for when doing:

LoadShaderSourceFile(TEXT("/Plugin/DeformerGraphBonusTools/Private/DataInterfaceGetBoneId.ush"), EShaderPlatform::SP_PCD3D_SM5, &TemplateFile, nullptr);

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: