Skip to main content

Updated May 4, 2026

Materials and the material graph

Why this matters​

A UMaterial compiles to a different shader permutation depending on three largely independent choices — domain, blend mode, and shading model — and a fourth decision (instance vs. dynamic instance) determines whether changing a parameter at runtime is free or triggers a shader recompile. Getting any of these wrong doesn't usually crash anything; it just quietly costs performance (an unnecessary translucent pass, a UMaterialInstanceConstant edited at runtime forcing a stall) or produces a material that silently ignores lighting because it was authored for the wrong domain. The material graph itself is a visual shader compiler front end — understanding what it compiles to explains why some node choices are expensive and others are free.

Mental model​

A parent UMaterial owns the graph and therefore the shader permutations that get compiled — domain, blend mode, and shading model are all baked in at that level and changing any of them recompiles shaders. A UMaterialInstanceConstant overrides exposed parameter values without touching the graph, so it's cheap to make many visual variants of one material without paying per-variant shader compiles. A UMaterialInstanceDynamic is the runtime-only version of that same idea — created and mutated in C++ or Blueprint, with parameter changes taking effect immediately and with no recompilation, which is exactly why gameplay code that needs to tint, pulse, or fade a material at runtime should be creating and writing to a UMaterialInstanceDynamic, never editing a UMaterialInstanceConstant from code.

The mechanics​

Material domains​

The Material Domain picks what kind of thing the graph is compiling a shader for, and it changes which inputs are even meaningful:

DomainUsed for
SurfaceOrdinary opaque/masked/translucent surface shading — the default and most common domain
Deferred DecalProjected decals blended into the G-buffer
Light FunctionModulating a light's color/intensity by a projected pattern
VolumeVolumetric materials (fog, clouds)
Post ProcessFull-screen or region post-process passes, see Post process and view extensions
User InterfaceUMG/Slate-facing materials

A material authored for the wrong domain doesn't necessarily fail to compile — it can just produce nothing visible, because inputs like Base Color mean something entirely different (or nothing) outside the Surface domain.

Blend modes​

Blend Mode controls how the shaded pixel combines with what's already in the frame: Opaque, Masked (alpha tested, no blending), Translucent, Additive, Modulate, AlphaComposite (premultiplied alpha), and AlphaHoldout. Translucent materials are meaningfully more expensive than Opaque or Masked — they can't use the deferred G-buffer path the same way and typically don't receive the same lighting features (see the Nanite material-support constraints, which only accept Opaque/Masked).

Shading models​

Shading Model picks the lighting model the pixel is shaded with — Default Lit, Unlit, Subsurface, Subsurface Profile, Clear Coat, and others. A material can expose multiple shading models and select between them per-pixel via a node, at extra cost versus a single fixed shading model.

Substrate is UE5's newer, modular material authoring framework — it replaces the fixed set of blend modes and shading models with composable material "slabs" that can be layered and mixed. It's a project-level toggle rather than the default authoring path in every project.

note

Substrate's node set and exact interaction with the legacy blend-mode/shading-model system continues to evolve; verify whether your project has Substrate enabled before assuming which authoring path a given material graph is using.

Creating and driving a dynamic material instance​

MyWeaponComponent.h
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class MYGAME_API UMyWeaponComponent : public UActorComponent
{
GENERATED_BODY()

public:
void SetHeatLevel(float NewHeatLevel);

protected:
virtual void BeginPlay() override;

UPROPERTY(EditDefaultsOnly, Category = "Weapon|Material")
TObjectPtr<UMaterialInterface> BarrelMaterial;

UPROPERTY(VisibleAnywhere, Category = "Weapon|Material")
TObjectPtr<UMaterialInstanceDynamic> BarrelMID;

UPROPERTY(EditDefaultsOnly, Category = "Weapon|Material")
FName HeatParameterName = TEXT("HeatLevel");
};
MyWeaponComponent.cpp
void UMyWeaponComponent::BeginPlay()
{
Super::BeginPlay();

if (BarrelMaterial)
{
// Creates a runtime-only instance parented to BarrelMaterial (or a UMaterialInstanceConstant).
BarrelMID = UMaterialInstanceDynamic::Create(BarrelMaterial, this);
}
}

void UMyWeaponComponent::SetHeatLevel(float NewHeatLevel)
{
if (BarrelMID)
{
// No shader recompile — this just writes a parameter value the graph already exposed.
BarrelMID->SetScalarParameterValue(HeatParameterName, NewHeatLevel);
}
}

The mesh component using this material must actually be set to BarrelMID, not the original BarrelMaterial — UMaterialInstanceDynamic::Create doesn't retroactively rebind anything that was already assigned the parent material.

Gotchas​

Never edit a UMaterialInstanceConstant at runtime

Modifying a UMaterialInstanceConstant's parameters through code forces the material system to treat it like an editor edit — expensive, and not what it's designed for at runtime. Create a UMaterialInstanceDynamic for anything gameplay code needs to change per-frame or per-instance.

Translucent materials are not a drop-in replacement for Opaque

Beyond the blending cost itself, translucent materials lose some deferred-rendering features and, per Nanite, aren't supported on Nanite meshes at all — an unsupported blend mode there falls back silently to a default material.

A material built for the wrong domain fails quietly

There's no compile error for putting Surface-domain logic into a Post Process material — inputs just mean something different or evaluate to nothing. Confirm domain first when a material "does nothing" after assignment.

See also​