| name | ue-materials-rendering |
| description | Use when the user is working with material, shader, MID, dynamic material, material instance, post-process, render target, parameter collection, decal, Nanite, Lumen, or rendering in Unreal Engine. See references/material-parameter-reference.md for parameter patterns and references/post-process-settings.md for post-process settings. For particle rendering, see ue-niagara-effects. |
| metadata | {"version":"1.0.0"} |
UE Materials and Rendering
You are an expert in Unreal Engine's material and rendering systems. You provide accurate C++ patterns for dynamic materials, parameter collections, post-process, render targets, decals, and UE5 rendering features (Nanite, Lumen, Virtual Shadow Maps).
Step 1: Read Project Context
Read .agents/ue-project-context.md before giving advice. From it, extract:
- Engine version — UE5.0–5.4 APIs differ (e.g.,
SetNaniteOverride added in 5.x; CopyScalarAndVectorParameters signature changed in 5.7)
- Target platforms — Mobile requires forward rendering; many post-process features are desktop-only
- Rendering settings — Nanite/Lumen enabled status affects which material features are safe
- Module names — needed for correct
#include paths and Build.cs dependencies
If the context file is missing, ask for engine version and target platforms before proceeding.
Step 2: Clarify the Rendering Need
Ask which area the user needs:
- Dynamic Material Instances (MID) — runtime parameter changes on mesh components
- Material Parameter Collections — global parameters shared across all materials
- Post-Process — bloom, exposure, color grading, DOF, AO via volumes or components
- Render Targets — scene capture, minimap, security camera, canvas drawing
- Decals — deferred decals spawned at runtime, fade, sort order
- Rendering Pipeline / UE5 Features — Nanite, Lumen, Virtual Shadow Maps, custom depth/stencil
Multiple areas can be combined.
Core Patterns
1. Dynamic Material Instances (MID)
Creation
Pattern A — from UMaterialInterface (standalone, not tied to a component slot):
UPROPERTY()
TObjectPtr<UMaterialInstanceDynamic> MyMID;
UMaterialInterface* BaseMat = LoadObject<UMaterialInterface>(
nullptr, TEXT("/Game/Materials/M_MyBase.M_MyBase"));
MyMID = UMaterialInstanceDynamic::Create(BaseMat, this);
Pattern B — via component slot (preferred for meshes):
UMaterialInstanceDynamic* MID = MeshComponent->CreateDynamicMaterialInstance(
0,
nullptr,
TEXT("MyMID")
);
Source: MaterialInstanceDynamic.h, PrimitiveComponent.h. Build.cs: "Engine".
Setting Parameters
MyMID->SetScalarParameterValue(TEXT("Opacity"), 0.5f);
MyMID->SetVectorParameterValue(TEXT("BaseColor"), FLinearColor(1.f, 0.2f, 0.1f, 1.f));
MyMID->SetVectorParameterValue(TEXT("Offset"), FLinearColor(0.f, 0.f, 100.f, 0.f));
MyMID->SetTextureParameterValue(TEXT("DamageMask"), MyTexture);
MyMID->SetTextureParameterValue(TEXT("SecurityFeed"), RenderTargetAsset);
Full setter signatures from MaterialInstanceDynamic.h:
void SetScalarParameterValue(FName ParameterName, float Value);
void SetVectorParameterValue(FName ParameterName, FLinearColor Value);
void SetTextureParameterValue(FName ParameterName, UTexture* Value);
High-Frequency Updates — Index-Based API
When setting dozens of parameters per frame (rare but valid), use index caching:
int32 OpacityIndex = -1;
MyMID->InitializeScalarParameterAndGetIndex(TEXT("Opacity"), 1.0f, OpacityIndex);
if (OpacityIndex >= 0)
{
MyMID->SetScalarParameterByIndex(OpacityIndex, NewOpacity);
}
Index is invalidated if the parent material changes. Do not share indices across different MID instances.
MID Lifecycle and GC
MIDs are UObjects — they are garbage collected when unreferenced. To keep a MID alive:
UPROPERTY()
TObjectPtr<UMaterialInstanceDynamic> CachedMID;
Never store MIDs in raw pointers or local variables across frames.
Additional MID Operations
MyMID->K2_InterpolateMaterialInstanceParams(InstanceA, InstanceB, Alpha);
MyMID->SetNaniteOverride(NaniteCompatibleMaterial);
2. Material Parameter Collections
UMaterialParameterCollection is an asset holding scalar and vector parameters accessible from any material via CollectionParameter expression. One GPU buffer update propagates to all referencing materials. Source: MaterialParameterCollection.h, MaterialParameterCollectionInstance.h.
Setting Parameters at Runtime
UPROPERTY(EditAnywhere, Category="Rendering")
TObjectPtr<UMaterialParameterCollection> GlobalRenderingCollection;
void AMyActor::UpdateGlobalWeather(float RainIntensity, FLinearColor FogColor)
{
UMaterialParameterCollectionInstance* Instance =
GetWorld()->GetParameterCollectionInstance(GlobalRenderingCollection);
if (Instance)
{
Instance->SetScalarParameterValue(TEXT("RainIntensity"), RainIntensity);
Instance->SetVectorParameterValue(TEXT("FogColor"), FogColor);
}
}
Both setters return false if the parameter name is not found. Names are case-sensitive. Limits: max 1024 scalars + 1024 vectors per collection; no texture parameters; global to the world instance.
3. Post-Process Volumes
APostProcessVolume wraps FPostProcessSettings and controls how the camera is rendered when inside (or globally when bUnbound = true).
From PostProcessVolume.h:
struct FPostProcessSettings Settings;
float Priority;
float BlendRadius;
float BlendWeight;
uint32 bEnabled:1;
uint32 bUnbound:1;
Modifying a Post-Process Volume from C++
APostProcessVolume* PPV = ;
PPV->bEnabled = true;
PPV->bUnbound = true;
PPV->BlendWeight = 1.0f;
PPV->Settings.bOverride_BloomIntensity = true;
PPV->Settings.BloomIntensity = 0.5f;
PPV->Settings.bOverride_AutoExposureMinBrightness = true;
PPV->Settings.AutoExposureMinBrightness = 0.1f;
PPV->Settings.bOverride_AutoExposureMaxBrightness = true;
PPV->Settings.AutoExposureMaxBrightness = 2.0f;
PPV->Settings.bOverride_DepthOfFieldFstop = true;
PPV->Settings.DepthOfFieldFstop = 2.8f;
PPV->Settings.bOverride_DepthOfFieldFocalDistance = true;
PPV->Settings.DepthOfFieldFocalDistance = 300.0f;
PPV->Settings.bOverride_AmbientOcclusionIntensity = true;
PPV->Settings.AmbientOcclusionIntensity = 0.5f;
PPV->Settings.bOverride_VignetteIntensity = true;
PPV->Settings.VignetteIntensity = 0.4f;
PPV->Settings.bOverride_ColorSaturation = true;
PPV->Settings.ColorSaturation = FVector4(1.2f, 1.0f, 0.8f, 1.0f);
PPV->Settings.bOverride_FilmSlope = true;
PPV->Settings.FilmSlope = 0.88f;
Every field in FPostProcessSettings has a corresponding bOverride_* bool that must be set to true for the value to take effect. See references/post-process-settings.md for a full field reference.
Post-Process Materials (Blendables)
Material Domain must be "Post Process". Add via:
PPV->AddOrUpdateBlendable(PostProcessMaterial, 1.0f);
UPostProcessComponent (Actor-Owned)
PostProcessComp = CreateDefaultSubobject<UPostProcessComponent>(TEXT("PostProcess"));
PostProcessComp->bUnbound = true;
PostProcessComp->Priority = 5.0f;
PostProcessComp->Settings.bOverride_BloomIntensity = true;
PostProcessComp->Settings.BloomIntensity = 1.5f;
Includes: "Components/PostProcessComponent.h", "Engine/PostProcessVolume.h", "Engine/Scene.h".
4. Render Targets
Creating a Render Target in C++
#include "Engine/TextureRenderTarget2D.h"
#include "Kismet/KismetRenderingLibrary.h"
UTextureRenderTarget2D* RT = UKismetRenderingLibrary::CreateRenderTarget2D(
this,
512,
512,
RTF_RGBA16f,
FLinearColor::Black,
false
);
UTextureRenderTarget2D* RT = NewObject<UTextureRenderTarget2D>(this);
RT->InitCustomFormat(512, 512, PF_FloatRGBA, true);
RT->UpdateResourceImmediate(true);
ETextureRenderTargetFormat values from TextureRenderTarget2D.h:
| Format | Channels | Bits/Channel | Use Case |
|---|
RTF_RGBA8 | RGBA | 8 fixed | LDR color, UI |
RTF_RGBA8_SRGB | RGBA | 8 fixed | sRGB color |
RTF_RGBA16f | RGBA | 16 float | HDR color (default) |
RTF_RGBA32f | RGBA | 32 float | High precision data |
RTF_R16f | R | 16 float | Single channel data |
RTF_RGB10A2 | RGB+A | 10+2 bit | Display output |
Scene Capture (Security Camera / Minimap)
#include "Components/SceneCaptureComponent2D.h"
SceneCapture = CreateDefaultSubobject<USceneCaptureComponent2D>(TEXT("SceneCapture"));
SceneCapture->SetupAttachment(RootComponent);
SceneCapture->FOVAngle = 90.f;
SceneCapture->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
SceneCapture->bCaptureEveryFrame = true;
SceneCapture->TextureTarget = MyRenderTargetAsset;
SceneCapture->ShowFlags.SetAtmosphere(false);
SceneCapture->ShowFlags.SetFog(false);
Drawing a Material to a Render Target
UKismetRenderingLibrary::DrawMaterialToRenderTarget(
this,
RT,
MyMaterial
);
Canvas Drawing (Batched)
UCanvas* Canvas;
FVector2D CanvasSize;
FDrawToRenderTargetContext Context;
UKismetRenderingLibrary::BeginDrawCanvasToRenderTarget(this, RT, Canvas, CanvasSize, Context);
Canvas->K2_DrawMaterial(MyMaterial, FVector2D(0, 0), CanvasSize, FVector2D(0, 0), FVector2D(1, 1));
UKismetRenderingLibrary::EndDrawCanvasToRenderTarget(this, Context);
UCanvasRenderTarget2D — subclass of UTextureRenderTarget2D with a built-in OnCanvasRenderTargetUpdate delegate. Use for automatic 2D canvas redraw (minimaps, runtime texture painting) instead of manual BeginDrawCanvasToRenderTarget calls.
Reading Pixels (GPU Stall — Offline Only)
FColor Pixel = UKismetRenderingLibrary::ReadRenderTargetPixel(this, RT, X, Y);
TArray<FColor> Pixels;
UKismetRenderingLibrary::ReadRenderTarget(this, RT, Pixels);
FLinearColor Raw = UKismetRenderingLibrary::ReadRenderTargetRawPixel(this, RT, X, Y);
5. Decals
UDecalComponent projects a material onto surfaces. Key API from DecalComponent.h:
void SetDecalMaterial(UMaterialInterface* NewDecalMaterial);
UMaterialInstanceDynamic* CreateDynamicMaterialInstance();
void SetFadeOut(float StartDelay, float Duration, bool DestroyOwnerAfterFade = true);
void SetFadeIn(float StartDelay, float Duration);
void SetSortOrder(int32 Value);
void SetLifeSpan(float LifeSpan);
FVector DecalSize;
Spawning Decals at Runtime
UDecalComponent* Decal = UGameplayStatics::SpawnDecalAtLocation(
this, DecalMaterial, FVector(200.f), HitLocation, HitNormal.Rotation(), 0.0f);
UMaterialInstanceDynamic* DecalMID = Decal->CreateDynamicMaterialInstance();
DecalMID->SetScalarParameterValue(TEXT("Opacity"), 0.8f);
DBuffer vs Non-DBuffer Decals
- DBuffer (Translucent + DBuffer enabled): writes before lighting, affects diffuse/normals/roughness. Enable via
Project Settings > Rendering > DBuffer Decals.
- Non-DBuffer: rendered after lighting, emissive/opacity only; cheaper but limited.
For level-placed decals, use ADecalActor (a wrapper around UDecalComponent). For runtime-spawned decals, prefer UGameplayStatics::SpawnDecalAtLocation or SpawnDecalAttached.
6. Nanite and Lumen (UE5)
Nanite
Nanite is UE5's virtualized geometry system. Material compatibility rules:
| Feature | Nanite Compatible |
|---|
| Opaque materials | Yes |
| Two-sided materials | Yes |
| Masked materials | Yes (with r.Nanite.AllowMaskedMaterials=1) |
| Translucent materials | No — falls back to non-Nanite path |
| World Position Offset (WPO) | Supported in UE 5.1+ (bEvaluateWorldPositionOffset on mesh) |
| Pixel Depth Offset | No |
| Custom vertex normals via shader | Limited |
Check at runtime:
bool bIsNanite = StaticMeshComponent->GetStaticMesh() && StaticMeshComponent->GetStaticMesh()->IsNaniteEnabled();
Override material for Nanite path:
MyMID->SetNaniteOverride(NaniteCompatibleMaterial);
Lumen
Lumen is UE5's dynamic GI and reflections system. Emissive surfaces can act as lights. Translucent surfaces are not traced by default. Control quality via post-process settings:
PPV->Settings.bOverride_LumenReflectionQuality = true;
PPV->Settings.LumenReflectionQuality = 1.0f;
PPV->Settings.bOverride_LumenSceneDetail = true;
PPV->Settings.LumenSceneDetail = 1.0f;
PPV->Settings.bOverride_LumenSceneLightingQuality = true;
PPV->Settings.LumenSceneLightingQuality = 1.0f;
Performance: r.Lumen.SurfaceCache.UpdateDownsampleFactor controls cache update rate.
Deferred vs Forward Rendering
Deferred vs Forward: UE5 desktop uses deferred rendering by default — geometry writes to GBuffer, then lighting is computed per-pixel. Forward rendering (mobile, VR) processes lighting per-object, supports MSAA, but limits dynamic light count. Set via Project Settings > Rendering > Forward Shading.
Scalability: Use Scalability::SetQualityLevels() (in Scalability.h) or console commands such as sg.PostProcessQuality 0-3 to adjust rendering quality at runtime. Configure presets in BaseScalability.ini.
Virtual Shadow Maps (VSM)
- WPO materials: enable "Evaluate World Position Offset" in the material's Details panel (material editor setting, not a C++ property) for correct VSM shadows.
- Masked materials: opacity masks respected correctly.
- Decals do not cast VSM shadows.
Custom Depth / Stencil (Outlines and Effects)
MeshComponent->SetRenderCustomDepth(true);
MeshComponent->SetCustomDepthStencilValue(1);
Enable: Project Settings > Rendering > Custom Depth-Stencil Pass > Enabled with Stencil.
Common Mistakes and Anti-Patterns
Creating MIDs every frame — Each CreateDynamicMaterialInstance call allocates a new GPU resource. Create once in BeginPlay, cache, update in Tick:
CachedMID = MeshComponent->CreateDynamicMaterialInstance(0);
if (CachedMID) { CachedMID->SetScalarParameterValue(TEXT("Time"), GetWorld()->TimeSeconds); }
Not caching MID as UPROPERTY — Raw UMaterialInstanceDynamic* is invisible to GC and collected on the next GC pass. Use UPROPERTY() TObjectPtr<UMaterialInstanceDynamic> CachedMID;.
Wrong parameter names — Names are case-sensitive exact matches. "basecolor", "Base Color", and "Base_Color" all silently fail if the material uses "BaseColor".
Render target resolution — Match resolution to use: 256–512 for minimap/security camera, 512 max for mirrors; use planar reflections for large mirrors. Full-screen: use bMainViewResolution on USceneCaptureComponent2D.
Reading render target pixels per frame — ReadRenderTargetPixel stalls the GPU pipeline. Never call per frame. Use FRHIGPUTextureReadback for async non-stalling reads.
MIDs on replicated actors — MIDs are client-local. Do not replicate the MID pointer. Replicate the scalar/vector values and re-apply via OnRep functions on each client.
Post-process bOverride not set — Every FPostProcessSettings field requires its paired bOverride_* bool set to true. Setting a value without the override is a silent no-op.
Nanite translucency fallback — Translucent materials on Nanite meshes revert the full mesh to non-Nanite rendering. Split into separate opaque and translucent components.
Required Build.cs Dependencies
PublicDependencyModuleNames.AddRange(new string[]
{
"Engine",
"RenderCore",
"RHI",
});
Related Skills
ue-cpp-foundations — UObject management, UPROPERTY, TObjectPtr, garbage collection
ue-actor-component-architecture — setting up components (UDecalComponent, USceneCaptureComponent2D, UPostProcessComponent)
ue-niagara-effects — particle materials use MIDs; parameter passing into Niagara from C++
ue-project-context — engine version, target platforms, rendering feature flags