DreamShaderLang
Tooling

Decompiler

Exporting an existing UMaterial or UMaterialFunction back to .dsm / .dsf — what round-trips, what falls back to UE.Expression, and what is simply lost.

The decompiler walks an existing UMaterial or UMaterialFunction node graph and writes an equivalent DreamShaderLang source file. It is how you move a material that already exists into the language without rebuilding it by hand.

AspectValue
AcceptsUMaterial · UMaterialFunction · UMaterialFunctionMaterialLayer · UMaterialFunctionMaterialLayerBlend
Producesone .dsm or .dsf file, UTF-8 without BOM
Writes to<SourceDirectory>/Decompiled/… unless an explicit output path is given
Sincesince 1.3.5 the Content Browser actions

This is a migration starting point, not a round-trip guarantee. The exporter reproduces the graph's structure and the parts of the node state it can express, then leaves a // Warning: comment for everything it could not. Read Known gaps before you delete the original asset — several classes of node state are dropped with no per-property warning at all.

Invoking it

RouteWhereProduces
Content Browserright-click a UMaterialDreamShaderExport DSM.dsm
Content Browserright-click a UMaterialFunction, UMaterialFunctionMaterialLayer or UMaterialFunctionMaterialLayerBlendDreamShaderExport DSF.dsf
Material Editorthe DreamShader toolbar combo ▸ Export DSM / Export DSFas above
Commandlet-run=DreamShader decompile -Asset=<object path> [-Out=<file>]as above

Both editor routes require exactly one selected asset, write the file, and then open it in your preferred editor — see Editor Tools. The headless route is on Commandlet.

A progress dialog appears after a 0.25 s delay, titled Decompiling Material '{Asset}'... or Decompiling Material Function '{Asset}'..., with one frame per visited node. It is suppressed in commandlet runs.

Where the file goes

Asset classDirectoryExtension
UMaterial<SourceDirectory>/Decompiled/Materials/.dsm
UMaterialFunction<SourceDirectory>/Decompiled/Functions/.dsf
UMaterialFunctionMaterialLayer<SourceDirectory>/Decompiled/Layers/.dsf
UMaterialFunctionMaterialLayerBlend<SourceDirectory>/Decompiled/LayerBlends/.dsf

<SourceDirectory> is the Source Directory project setting, DShader by default. Layers and layer blends get their own directories, and the class test is ordered blend-first, so a layer blend never lands in Layers.

Inside the category directory the asset's package path becomes the relative file path, one directory per package segment:

/Game/Materials/Metal/M_Steel
  →  <Project>/DShader/Decompiled/Materials/Game/Materials/Metal/M_Steel.dsm
RuleDetail
Leading and trailing /stripped from the package name
Illegal characterscontrol characters and < > : " / \ | ? * become _
Empty segmentbecomes Folder<N> for a folder, Asset<N> for the last segment, <N> being the 1-based index
No packagethe asset's own name is used as the only segment

Passing -Out= to the commandlet overrides the whole computation and writes exactly where told (after path normalization). The editor routes never take an override.

The name inside the file

The Name= written inside the file is not the file path. It is Decompiled/<Category>/<package segments>, with \ / . : replaced by _ in each segment:

/Game/Materials/Metal/M_Steel
  →  Shader(Name="Decompiled/Materials/Game/Materials/Metal/M_Steel")

Recompiling the exported file therefore creates a new asset under /Game/Decompiled/Materials/… and leaves the original untouched. That is deliberate: you can compare the two before committing. Edit Name= and Root= when you are ready to take over the original path — see Asset Paths.

File layout

NotationMeaningExample
<x>Placeholder — substitute a real value; the angle brackets are not typed.Name = <string>
[ x ]Optional — the whole group may be left out.[, Root = <string>]
{ a | b }Choice — take exactly one of the alternatives separated by |.{ Node( … ) | Comment( … ) }
Repetition — the preceding item may appear any number of times.<property-declaration> …
// Decompiled from <full object path>
[// Warning: <text>]…

[<VirtualFunction declaration>]…

{ Shader | ShaderFunction | ShaderLayer | ShaderLayerBlend }(Name="<generated name>")
{
    [Properties = { … }]
    [Inputs     = { … }]   // function kinds only
    [Settings   = { … }]   // Shader only
    [Outputs    = { … }]
    [Graph      = { … }]
    [Layout     = { … }]
}
Block kind emittedSource asset
ShaderUMaterial
ShaderFunctionUMaterialFunction
ShaderLayerUMaterialFunctionMaterialLayer
ShaderLayerBlendUMaterialFunctionMaterialLayerBlend

The Settings block of a decompiled Shader always begins with Domain, ShadingModel and BlendMode, emitted unconditionally. Every other setting is emitted only when it differs from the UMaterial class default; the round-trip set is on Material Settings. When a Base.FrontMaterial binding was decompiled the shading model is forced to Substrate, because a Substrate material's own shading-model enum does not describe its surface.

Outputs declares one variable per connected material property and binds it, in this fixed order:

EmissiveColor, BaseColor, Metallic, Specular, Roughness, Anisotropy, Opacity, OpacityMask,
Normal, Tangent, WorldPositionOffset, SubsurfaceColor, CustomData0, CustomData1,
AmbientOcclusion, Refraction, PixelDepthOffset, MaterialAttributes,
FrontMaterial        (UE 5.4 and newer only)

Unconnected properties are skipped entirely.

What round-trips faithfully

The node walker has a curated case for each class below. Everything else falls through to the generic fallback.

Emitted as DreamShaderLang syntax

UMaterialExpression classEmitted as
Constanta float literal
Constant2Vectorfloat2(x, y)
Constant3Vectorfloat3(r, g, b)
Constant4Vectorfloat4(r, g, b, a)
Adda + b
Subtracta - b
Multiplya * b
Dividea / b
OneMinus1.0 - x
LinearInterpolatelerp(a, b, alpha)
Clampclamp(x, min, max) — only when the clamp mode is the default two-sided clamp
Powerpow(base, exponent)
DotProductdot(a, b)
Normalizenormalize(v)
Minmin(a, b)
Maxmax(a, b)
Absabs(x)
Saturatesaturate(x)
Floorfloor(x)
Ceilceil(x)
Fracfrac(x)
SquareRootsqrt(x)
Sinesin(x) — only when Period is 1
Cosinecos(x) — only when Period is 1
ComponentMaska swizzle on the input, built from the R/G/B/A flags in that order
AppendVectorfloatN(a, b), or a single merged swizzle when both operands swizzle the same base value
TimeUE.Time() — only when it neither ignores pause nor overrides the period
Reroutenothing — plain reroutes are traced through, with a cycle guard
NamedRerouteDeclaration, NamedRerouteUsagea named Graph temporary, reused by every usage
FunctionInputthe name declared in the Inputs section
MaterialFunctionCalla generated VirtualFunction declaration placed above the block, plus a call to it

Unconnected operand pins fall back to the node's own constant property when it has one — Min, Max, LinearInterpolate, Power and Clamp all read their Const* values — otherwise to a literal 0.0.

The math-builtin spellings above are the same 19 names documented on Math Builtins. Note the gaps: there is no decompiler branch for UMaterialExpressionFmod, so an existing Fmod node comes back as a generic UE.Expression(Class="Fmod", …) call rather than fmod(…). The source is equivalent; it just is not the builtin spelling.

Emitted as a property declaration

These become entries in the Properties section and are referenced by name in the Graph. Names are uniquified, and a ParameterName= metadata entry is added whenever the DreamShaderLang identifier had to differ from the asset's parameter name.

UMaterialExpression classDeclaration
ScalarParameterScalarParameter <Name> = <default>;
VectorParameterVectorParameter <Name> = float4(r, g, b, a);
TextureObjectParameterTextureObjectParameter <Name>[ = <asset path>];
TextureSampleParameter2DTextureSampleParameter2D <Name>[ = <asset path>];only when no input pin is connected

A TextureSampleParameter2D with any connected input pin is emitted as a curated UE.Expression instead, carrying its parameter arguments and its sampler arguments together since 1.3.7. Its RGBA output is emitted once into a named temporary; every other pin becomes a swizzle of it.

Emitted as a curated UE.Expression

These keep a hand-written argument list rather than a reflection dump, so only the properties that actually differ from the node default appear.

UMaterialExpression classArguments emitted
CurveAtlasRowParameterParameterName, Group, SortPriority and Desc when non-default, then DefaultValue, Curve, Atlas, UseCustomPrimitiveData with PrimitiveDataIndex, and CurveTime when the time pin is connected
StaticComponentMaskParameterInput, DefaultR, DefaultG, DefaultB, DefaultA, and ParameterName when set. OutputType follows the number of enabled channels
StaticSwitchParameterTrue, False, and ParameterName, DefaultValue, DynamicBranch when non-default
TextureCoordinateCoordinateIndex, UTiling, VTiling — each only when non-default
TimebIgnorePause, bOverride_Period, Period — used when the node is not at its defaults
Sine / CosineInput, Period — used when Period is not 1
ClampInput, Min, Max, ClampMode — used when the clamp mode is min-only or max-only
PannerCoordinate or ConstCoordinate, Time, and either Speed or the non-zero SpeedX / SpeedY, plus bFractionalPart
RotatorCoordinate or ConstCoordinate, Time, and CenterX, CenterY, Speed when non-default
WorldPositionWorldPositionShaderOffset when it is not the default
CameraVectorWS(none)
ObjectPositionWS(none)
ScreenPosition(none)
VertexColor(none) — always typed float4, with the pin's mask emitted as a swizzle
TextureSamplethe texture, sampler and mip arguments; the RGBA output is emitted once and every other pin becomes a swizzle of it
CustomCode, Description, Output for a secondary output, the full AdditionalOutputs list, and one argument per connected input. OutputType is the node's own declared return type, never the selected output's type

The UE.Expression fallback

Any class without a case above is exported as a generic UE.Expression call, and the decompiler records a warning for it.

The argument list is built in two passes:

  1. Every connected input pin, named after the pin, in pin order.
  2. Every reflected literal property whose value differs from the class default since 1.3.7.

A property is exported in pass 2 only when all of these hold:

RequirementDetail
Not deprecated, transient or duplicate-transient
Not a material-expression inputthose are pass 1
Marked editableCPF_Edit
Not a control nameClass, OutputType, ResultType, Output, OutputName, OutputIndex
Not an editor-only nameMaterialExpressionEditorX, MaterialExpressionEditorY, Desc, bCommentBubbleVisible, bShowOutputNameOnPin, bHidePreviewWindow, bCollapsed, bShaderInputData, SortPriority
A supported property typebool, numeric, enum, byte, name, string, text, or object reference
Different from the class defaultidentical values are omitted
Not already an argumentthe first writer of a name wins

Names are compared after normalization, so bTwoSided and Two Sided collide.

Struct, array, map, set and delegate properties are dropped silently. They are not one of the supported property types, so a node whose state lives in a struct exports with that state at its class default, and no warning names the specific property. Re-set those by hand after the first compile, or keep the node as UE.Expression and add the missing arguments yourself.

OutputType is always emitted, resolved from the real output index. When a named output selector (Output= / OutputName=) is present, OutputIndex is suppressed, because the generator rejects a call carrying both. Calls with more than three arguments, or longer than 120 characters, are emitted across multiple lines.

Layout export

Controlled by the Export Decompiled Layout project setting, default on. When on, the file gets a Layout section:

Emitted lineFrom
Comment(Name="<text>", X=<x>, Y=<y>, W=<w>, H=<h>, Color=float4(r, g, b, a));every editor comment box
Node(Var="<name>", X=<x>, Y=<y>);every named expression, sorted by X, then Y, then name

Comment boxes whose text begins with DreamShader: are skipped — those are generated markers, not authored comments.

Independently of the setting, each expression is also assigned to the smallest comment box that encloses it, and those assignments become #Region / #EndRegion directives around the corresponding Graph statements. Turning layout export off removes the Layout block but not the regions. See Layout and #Region.

Diagnostics

Runtime substitutions are written {Placeholder}.

Warnings written into the file

Each is emitted once, as a // Warning: … comment under the // Decompiled from … header line. None of them fails the export.

MessageCause
Exported '{Class}' as UE.Expression; review reflected literal properties if the node has editor-only state.a node with no curated case
MaterialFunctionCall '{Path}' is not a plain MaterialFunction; it was exported through UE.Expression.the call targets a layer or layer blend rather than a plain material function
A MaterialFunctionCall had no function asset and was exported as a zero literal.the call node has no function assigned
Failed to emit VirtualFunction for '{Path}': {Error}the called function's declaration could not be built
Named reroute usage '{Node}' has no valid declaration; emitted a default literal.a dangling named-reroute usage reached as a node
Named reroute usage '{Node}' has no valid declaration; emitted its default value.the same, reached through an input pin
Detected a recursive graph dependency while decompiling node '{Node}'; emitted a default literal to avoid stack overflow.a cycle in the expression graph
Detected a recursive reroute dependency while decompiling node '{Node}'; emitted a default literal to avoid stack overflow.a cycle through plain reroutes
Detected a recursive named reroute dependency for '{Node}'; emitted a default literal to avoid stack overflow.a cycle through named reroutes
Append node '{Node}' resolved to {A} + {B} components, which cannot fit a float4; masked its inputs down to {A2} + {B2}. Review the emitted swizzle.an append whose operands exceed four components

Export failures

MessageCauseFix
No Material asset was provided.A null material reached the decompiler.
No MaterialFunction asset was provided.A null function reached the decompiler.
No asset was provided.A null asset reached the service.
MaterialFunction '{Name}' does not expose any outputs.The function declares no outputs.Add an output to the asset, or export the material that calls it instead.
DreamShader decompile supports Material and MaterialFunction assets only: {Path}Any other asset class — including UMaterialInstanceConstant.Export the parent UMaterial and re-create the instance.
Decompile did not produce source text.The decompile reported failure with no message.
DreamShader failed to resolve an output file path.The computed output path was empty.
DreamShader failed to create output directory '{Directory}'.The directory could not be created.
DreamShader failed to write decompiled source '{File}'.The file could not be written.

Editor toasts

ToastCause
DreamShader could not find the selected Material. / …Material Function.the asset was unloaded between right-click and click
DreamShader failed to export DSM: {Error} / DreamShader failed to export DSF: {Error}the decompile failed
(the raw write error)the file could not be saved
Exported DSM but could not open it: {File}written, but the editor could not be launched
Exported DSM: {File} / Exported DSF: {File}success

Logs: Exported Material '{Asset}' to DSM '{File}'. at Display, and Failed to export Material '{Asset}' to DSM: {Error} at Warning.

Known gaps

Verified behaviour of 1.5.0. Every row is something the exported file will not reproduce. Check the ones that apply to your asset before treating the source file as the truth.

GapEffectWork-around
Material-function settings are never emittedDescription, ExposeToLibrary, LibraryCategories and UserExposedCaption are lost when exporting a UMaterialFunctionadd a Settings block by hand
Only the blessed UMaterial property set is emittedproperties outside it — OpacityMaskClipValue, NumCustomizedUVs, translucency lighting mode, displacement scaling, Nanite override — keep the class defaultadd the keys to Settings; they resolve by reflection — see Material Settings
Struct-, array-, map- and set-valued node properties are not reflecteda fallback UE.Expression node loses that state, with no per-property warningset the property on the material after generation, or extend the emitted call
Node comment text (Desc) and node SortPriority are droppedcomment bubbles and pin ordering are not reproducedre-apply by hand
Node positions depend on a settingwith Export Decompiled Layout off, the regenerated graph is auto-laid-out insteadleave the setting on, or write Layout by hand
Comment boxes prefixed DreamShader: are droppedgenerated markers are not re-emitted, by designnone needed
A MaterialFunctionCall on a layer or layer blend falls back to UE.Expressionthe call is not expressed as a VirtualFunctionexport the layer separately and call it
A MaterialFunctionCall with no assigned function becomes 0.0the branch is silently constant-foldedre-assign the function in the original asset and re-export
Cycles emit a default literalthe cyclic branch evaluates to a constantbreak the cycle in the original graph
An append wider than four components is masked downcomponents are droppedcheck the emitted swizzle
Material instances are not supportedUMaterialInstanceConstant is rejected outrightexport the parent UMaterial, then re-create the instance
Texture-sample GatherMode round-trips only on UE 5.6 and neweron older engines the property is omittednone
bHasPixelAnimation is in the emitted flag set only on UE 5.4 and neweron older engines the flag is omittednone
Base.FrontMaterial and the Substrate shading-model spelling exist only on since UE 5.4a Substrate material cannot be exported meaningfully below 5.4none
The generated Name= points into Decompiled/…recompiling creates a second asset rather than replacing the originaledit Name= / Root= once the source is trusted
Large graphs skip automatic layout at generation timea big regenerated graph can come back visually unordered when no Layout block is presentkeep layout export on

Example

Export /Game/Materials/M_Steel headlessly, then inspect the result:

& "$Engine\Binaries\Win64\UnrealEditor-Cmd.exe" "I:\Project\Project.uproject" `
    -run=DreamShader decompile -Asset="/Game/Materials/M_Steel" `
    -unattended -nopause -nosplash -stdout -log
DreamShader decompiled '/Game/Materials/M_Steel.M_Steel' to
'I:/Project/DShader/Decompiled/Materials/Game/Materials/M_Steel.dsm'.

The written file:

// Decompiled from /Game/Materials/M_Steel.M_Steel
Shader(Name="Decompiled/Materials/Game/Materials/M_Steel")
{
    Properties = {
        ScalarParameter Roughness_0 = 0.35 [ParameterName="Roughness"];
        VectorParameter Tint = float4(0.8, 0.8, 0.82, 1.0);
    }
    Settings = {
        Domain = "Surface";
        ShadingModel = "DefaultLit";
        BlendMode = "Opaque";
    }

    Outputs = {
        float3 BaseColor;
        float Metallic;
        float Roughness;

        Base.BaseColor = BaseColor;
        Base.Metallic  = Metallic;
        Base.Roughness = Roughness;
    }

    Graph = {
        BaseColor = Tint.rgb;
        Metallic  = 1.0;
        Roughness = saturate(Roughness_0);
    }

    Layout = {
        Node(Var="Tint", X=-640, Y=-208);
        Node(Var="Roughness_0", X=-640, Y=48);
    }
}

Note ParameterName="Roughness" on the first property. The asset's parameter is called Roughness, but that identifier was already taken in this file, so the declaration was uniquified to Roughness_0 and the real parameter name preserved as metadata — otherwise the regenerated material would expose a parameter under the wrong name. See Metadata and Groups.

Where next

On this page