Install
After purchasing on Fab, open the Epic Games Launcher → Unreal Engine → Library → Fab Library, find Ultimate Celshading PBR and click Install to Engine and choose your engine version. The launcher installs it into the engine as a ready-to-use plugin, so every project on that engine can enable it.
- Open your project.
- Edit → Plugins → Rendering → enable Ultimate Celshading PBR.
- Restart the editor when prompted.
Using a source-built engine? Copy the UltimateCelshadingPBR folder into your project's
Plugins/ folder instead; the plugin compiles with the project. Full C++ and shader source is included.
A copy there stays off until you enable it in Edit → Plugins, the same as the engine install.
Packaging a Blueprint-only project: a project that enables a code plugin gets a small C++ executable of its own when you package it, so the packaging machine needs Visual Studio 2022 with the Game development with C++ workload. The editor works without it.
Demo maps
The plugin ships two maps. Fab installs the plugin into the engine, so in the Content Browser open
Settings and tick both Show Engine Content and Show Plugin Content
(a copy in your project's Plugins/ folder needs only the second), then
UltimateCelshadingPBR Content → Maps:
- Showcase — ten labelled feature stations: PBR with fog, colored lights and volumetrics · color-aware outline · halftone artistic fill · rim light · band gradient dot mask · band gradient strokes mask · a zone volume that blends fog, sun and sky light · full-screen mode · multi-layer stencil (several slots, each with its own style, in one view) · cel-shaded metallic and emissive surfaces.
- Showcase_Presets — the five included presets side by side (Anime, Strokes, Manga, Comix, Black Shade), plus a row of further variations.
Every volume in these maps is an ordinary ACelshadingVolume: select one to see which overrides
and preset produce the look. Both maps use movable lights only, so they open and play without a lighting
build.
Project setup
The plugin gates pixels via the standard UE Custom Depth-Stencil pipeline.
r.CustomDepth
to mode 3 ("Enabled with Stencil") on module startup if your project hasn't set
it already — so a fresh install just works. To make it permanent (or set it manually), add
the following to DefaultEngine.ini under [/Script/Engine.RendererSettings]:
[/Script/Engine.RendererSettings]
r.CustomDepth=3
Equivalent UI path: Project Settings → Rendering → Postprocessing → Custom Depth-Stencil Pass
= Enabled with Stencil.
Per-actor opt-in
For each actor that should be cel-shaded:
- Select the actor in the level (or its
StaticMeshComponent/SkeletalMeshComponent). - Details → Rendering:
- ☑ Render CustomDepth Pass
- CustomDepth Stencil Value = 1 (matches default
r.Celshading.CelStencilRef)
That's it. The actor's existing material — DefaultLit, Subsurface, Cloth, Hair, anything — is preserved. The pass quantizes its lighting and adds outline + rim on top.
Stencil layers
The plugin uses the full 8-bit Custom Stencil byte — pick any value 1..255
for your StencilSlot and set the matching CustomDepth Stencil Value on the
mesh component. There is no fixed slot table and no maximum layer count: place as many independent
volumes as you need, each driving its own slot.
Internally, the registry stores a packed list of active per-slot snapshots — slots without a volume
cost zero GPU time (no dispatch). So if you only have one slot in use, you only pay for one dispatch,
regardless of which slot number you chose (e.g. StencilSlot=42 works the same as
1).
Multiple volumes, same slot? Their styles blend by BlendPriority +
ContributionWeight, just like APostProcessVolume.
Need every visible mesh cel-shaded without per-actor setup? Set bAffectWholeScreen = true
on a volume (or r.Celshading.AffectWholeScreen 1 globally) — the stencil gate is bypassed
entirely. See the Stencil & debug table below.
Volumes & blending
Drop an ACelshadingVolume into the level. By default bUnbound = true — the volume
applies globally. To zone it, untick bUnbound, scale the box, and set SpatialFalloff
for soft falloff outside the box.
Volume properties
StencilSlot[1..255] — which CustomDepth Stencil Value this volume drives.PatternDepthReference— optional Actor pointer. When set, the camera-to-actor distance is used as a single depth pivot for this volume's Halftone + Band Mask cell pitch: pattern density stays uniform across the entire stencil slot regardless of surface curvature (zero strobe). When unset, the patterns are pure screen-fixed (cell pitch in actual pixels). See Pattern Depth Reference below.bUnbound— global mode. Box is hidden.SpatialFalloff(cm) — fade distance outside the box. Smoothstep falloff.ContributionWeight[0..1] — manual multiplier on top of spatial weight.BlendPriority— overlapping volumes sort ascending; higher priority writes on top (matchesAPostProcessVolume::BlendPriority).
Style overrides
Each Style field has a paired bOverride_X bit — only flagged fields participate
in the per-layer blend. Non-flagged fields fall back to the global r.Celshading.* CVars.
A volume with no overrides skips Style entirely (no snapshot pushed).
Lighting overrides
Same per-field bOverride_* pattern for Sun / Sky / Fog. Affects the lights the plugin
auto-discovers in the level: the ADirectionalLight flagged Atmosphere Sun Light
(or the first directional light when none is flagged), the first ASkyLight and the first
AExponentialHeightFog. The scene's own values are captured as the base and restored when
the camera leaves every overriding volume. If you edit those actors manually while volumes are present,
call UCelshadingSubsystem::RefreshBase() to re-capture the base.
Pattern Depth Reference (sprite-anchor)
By default, Halftone and Band Mask (Dots / Strokes) cells are sized in screen pixels —
the same density covers the screen regardless of object distance. For hero meshes you usually want
the dots / strokes to behave like a billboard sprite locked to the actor's pivot:
the pattern grid tracks the actor's screen position and zooms in/out from its centre as the camera
moves toward or away from it. Set the volume's PatternDepthReference to any Actor
(typically the hero itself, or a helper pivot in the centre of an object cluster):
- Unset (default) → pure screen-fixed. Cell pitch =
HalftoneScale/BandDitherScalein actual pixels. Same density everywhere on screen at any distance. Best for stylised overlay looks. - Set to an Actor → sprite-anchored:
- Anchor: the actor's projected screen-space position becomes the cell-grid origin — the pattern stays locked to the actor as the camera orbits or pans, no swimming.
- Zoom: cell pitch is divided by
dist(camera, RefActor) / 500. As you approach the actor, dots zoom outward from its centre exactly like a billboard sprite getting closer to camera; as you retreat, the pattern shrinks back toward it. - A single set of values (anchor + scale) is applied to every pixel of this volume's stencil slot, so pattern density stays uniform across the whole object/cluster — zero strobe regardless of surface curvature.
Reference distance 500 cm: at that range the effective cell pitch matches the
configured Scale exactly. If multiple volumes share the same StencilSlot and each
provides a reference actor, the final anchor is a weight-blended average position across all
contributing volumes (same blend weights as the rest of the per-field overrides).
When the reference actor moves behind the camera, sprite-anchor mode silently falls back to pure screen-fixed for that frame — no clip-space artefacts.
frac() phase into severe
pattern strobing. Anchoring the grid to a single projected actor position means every pixel of the
dispatch shares the exact same anchor + divisor — strobe-free by construction, and the pattern
"feels" attached to the object the way a comic-book screentone is glued to a panel character.
Outside Outline (anime halo)
Two outline systems run independently:
- Inside Laplacian outline (
OutlineThicknessNear/Far) — adaptive kernel that detects creases / depth jumps INSIDE the silhouette. Pixels near silhouette get darkened towardOutlineColor; concave creases get brightened towardSpecularHighlightColor. - Outside Outline / halo (
OutsideOutlineThickness) — solid anime-style halo that wraps AROUND the silhouette over the background. Pixels within N px of a stencil pixel get the colour of the closest cel-shaded silhouette pixel written out — so the halo carries every internal effect (band quantization, brighten blik, rim glow, fog) as a continuous extension of the inside line.
To enable: set OutsideOutlineThickness to 1..32 px on the volume (or globally
via r.Celshading.OutsideOutlineThickness). The plugin dispatches a second
compute pass per stencil layer that has the halo enabled, reading the cel-output as input
and dilating it outward. Pixel-perfect colour match with the inside silhouette.
OutsideOutlineThickness < 1
the second pass is not enqueued at all — only the regular cel pass runs. Use freely on layers
that don't need it.
Typical values: 2..4 px for thin anime line, 5..8 for thicker comic outline. Cost scales O(N²) per halo pixel due to dilation kernel scan; use modest radii in production.
Tint By Light Direction (color of light)
Cel-shaded surfaces can transmit the color of the light that hits them — a yellow
character lit by a warm sun reads warm-yellow on the lit side and cool on the shaded side
(instead of a flat BaseColor × dim shadow). The trick: the cel pass scales the
quantized brightness by the SceneColor's color direction (per-pixel hue),
not by the BaseColor chroma. Result: light hue propagates through quantization, color story
survives the toon math.
Toggle: Volume → Cel Look → Color Response → Tint By Light Direction
(or CVar r.Celshading.TintByLightDirection). Default: on.
When you want it
- Multi-light scenes (warm sun + cool skylight, lanterns + moonlight) — surfaces blend between the lights' hues smoothly per-pixel.
- Sunset / sunrise / mood lighting where the color of the light is part of the art direction, not just the surface color.
- Atmosphere overrides via
ACelshadingVolume— per-zone Sun/Sky color changes show up on every cel-shaded surface within the volume.
When to disable it
- Pure flat-color comic / Saturday-morning-cartoon look, where you want surfaces to read
as their flat BaseColor regardless of which light hits them. Set
Tint By Light Direction = false— the pass falls back toMaxToon × BaseColor.chroma(BaseColor owns all the color). - If you specifically want every band as one solid hue with no per-pixel variation (Black-Shades / Hard-Cel preset territory).
Trade-off you should know
"Color direction" is read per-pixel from SceneColor. Where SceneColor saturates HDR-bright
(specular hotspots), the direction smoothly shifts toward white — in early plugin builds
this read as a soft bloom-like gradient on top of the cel bands. The current pass
automatically fades the tint toward neutral white above HighlightCutoff, so spec
hotspots stay flat-cel even with Tint By Light Direction on. No setup required.
Companion controls (Color Response group)
Banded Color Strength— quantizes the tint intoBandCountcolor steps with a smoothstep transition (usesBandEdgeSoftnessfrom the cel-bands block, so cel-edges and color-edges share the same softness feel). 0 = smooth tint, 1 = full hard-banded color zones (classic comic-book color flat).Base Color Influence— mixes the surface BaseColor's chroma into the light-direction tint. 0 = pure light-direction tint (a white sun on a yellow surface produces a white lit band — full color-of-light effect). 1 = pure BaseColor chroma on lit pixels (surface hue dominates regardless of light colour). Use 0.2..0.4 to keep a hint of surface hue under intense or saturated lighting without abandoning the color-of-light story (avoids the lit band reading as a flat white-out under bright directional lights). CVar:r.Celshading.BaseColorInfluence.Lighting Desaturation— pulls the final tint toward neutral white. 0 = full color story, 1 = pure greyscale lit (kills the trick entirely without flippingTint By Light Directionoff).
Material setup
Nothing special required — Tint By Light Direction reads the standard Lit GBuffer's SceneColor + BaseColor. Works with every UE shading model the plugin already supports (DefaultLit, Subsurface, SubsurfaceProfile, Cloth, Hair, Eye, ClearCoat).
The only material-side gotcha: if your material outputs a very HDR-bright
Emissive (e.g. Emissive = BaseColor × 50) AND the surface is also Lit, the
Emissive contribution dominates SceneColor and the tint reads as the Emissive's color rather
than the lighting's color. Either tag emissive zones via the Ambient Occlusion marker (so the
cel pass routes them through the Emissive path) or keep Emissive intensity moderate.
Presets
UCelshadingPreset is a DataAsset bundling Style + Lighting overrides. Two ways to create:
- Content Browser → Right-click → Miscellaneous → Data Asset → Celshading Preset.
- From a tuned volume: Save As Preset… button on the volume's Details panel.
Apply: assign the preset to the volume's Active Preset field, then click Apply Preset.
This is a one-shot copy — subsequent edits to the preset don't auto-propagate. Re-click to update.
Sharing the stencil byte
CustomStencil is a single 8-bit value shared engine-wide. To coexist with other plugins that use it (engine outline, gameplay highlight, etc.), use a single bit instead of a full value:
r.Celshading.CelStencilMask 128 # check bit 7 (0x80) only
r.Celshading.CelStencilRef 128 # cel-shade when bit 7 is set
Then set CustomDepth Stencil Value = 128 on cel-shaded actors. The lower 7 bits remain free.
Console variables
Style fields are mirrored as r.Celshading.* console variables. CVars provide the global default
when no volume overrides them. All of them are listed below; in the editor console,
DumpCVars r.Celshading. -showhelp prints their live values with the help text.
Core
| Name | Default | Description |
|---|---|---|
r.Celshading.MasterEnable | 1 | Master switch. 0 disables the entire pass. |
r.Celshading.MasterStrength | 1.0 | Final blend amount (0 = scene unchanged). |
r.Celshading.BandCount | 3 | Number of lighting bands (3 = shadow / mid / highlight; 2 = binary shadow/light). |
r.Celshading.HighlightCutoff | 3.0 | Lighting magnitude mapped to the brightest band. |
r.Celshading.OutputBrightness | 1.0 | Final output multiplier. |
r.Celshading.FillLightStrength | 1.0 | Pre-quantize light gain. |
r.Celshading.MinBaseColorBrightness | 0.03 | BaseColor floor when extracting lighting. |
r.Celshading.FogAware | 1 | Reconstruct + reapply fog around quantization. |
r.Celshading.LitDetail | 0 | 0..1 — preserve PBR detail on lit pixels (1 = "PBR highlights + crisp toon shadows" hybrid; 0 = pure cel). |
Shadows
| Name | Default | Description |
|---|---|---|
r.Celshading.ShadowCurve | 2.2 | Pow exponent before quantization. >1 crushes blacks. |
r.Celshading.ShadowFloorBrightness | 0.022 | Dark band brightness as fraction of HighlightCutoff. |
r.Celshading.InterBandContrast | 1.0 | Multiplier between bands. |
r.Celshading.ShadowEntryPoint | 0.30 | Hard cutoff: lighting below this collapses to ShadowFloorBrightness. |
r.Celshading.HighlightEntryPoint | 0.70 | Hard cutoff: lighting above this saturates to HighlightCutoff. |
r.Celshading.BandEdgeSoftness | 0 | Anti-alias band edges (raise to ~0.02 to soften TAA jitter). |
r.Celshading.SuperBlack | 0 | 0..1 — hard-ink "super-black" shadows: crushes toon shadows below SuperBlackThreshold to pure black, independent of ShadowFloorBrightness. 0 = off. |
r.Celshading.SuperBlackThreshold | 0.30 | Shaped-lighting level (0..1) below which SuperBlack applies. Same space as ShadowEntryPoint. |
Outline (adaptive Laplacian)
| Name | Default | Description |
|---|---|---|
r.Celshading.OutlineStrength | 1.0 | Final outline intensity. |
r.Celshading.OutlineDepthScale | 1.0 | Multiplier on depth Laplacian. |
r.Celshading.OutlineNormalScale | 1.0 | Multiplier on normal Laplacian projection. |
r.Celshading.InternalLineThreshold | 1.0 | Depth knee (cm) — filters texture noise. |
r.Celshading.OutlineThicknessFar | 1.0 | Kernel diameter (px) at far depth. Soft cap 32 — type any value; shader saturates beyond 32. |
r.Celshading.OutlineThicknessNear | 3.8 | Kernel diameter (px) at near depth. Same soft cap 32. Cost is O(N²). |
r.Celshading.OutsideOutlineThickness | 0 | Outside silhouette outline (anime halo) thickness in pixels. 0 (default) = disabled (zero GPU cost — second pass not dispatched). >0 = halo of N pixels drawn AROUND the actor's silhouette, sampling the cel-shaded silhouette pixel itself so all internal effects (band quantization, brighten path's SpecularHighlightColor blik, rim glow, fog) carry over. Soft cap 32. Independent from OutlineThicknessNear/Far (Laplacian inside outline for creases). |
r.Celshading.OutlineDepthNear | 500 | Distance (cm) where kernel reaches OutlineThicknessNear. |
r.Celshading.OutlineDepthFar | 3000 | Distance (cm) where kernel reaches OutlineThicknessFar. |
r.Celshading.ColorAwareOutline | 0 | Tint outline by underlying BaseColor — red surfaces produce dark-red lines, blue surfaces dark-blue (anime self-shadow line look). |
r.Celshading.OutlineColorAwareness | 0.7 | Blend amount: 0 = plain OutlineColor, 1 = OutlineColor × BaseColor. Active only when ColorAwareOutline is enabled. |
r.Celshading.OutlineFillMode | 0 | 0..1 — blend between TINT (multiplicative, 0) and FILL (the line replaces the pixel with OutlineColor, 1). |
r.Celshading.InvertOutline | 0 | Flip outline polarity: silhouette edges brighten, the crease highlight darkens to OutlineColor. |
Halftone (manga screentone)
| Name | Default | Description |
|---|---|---|
r.Celshading.HalftoneEnable | 0 | Master toggle for the halftone overlay. Off = entire block GPU-branch-skipped. |
r.Celshading.HalftoneScale | 8.0 | Cell pitch in pixels. Smaller = denser dot pattern. |
r.Celshading.HalftoneAngle | 45.0 | Grid rotation in degrees. |
r.Celshading.HalftoneThreshold | 0.45 | Brightness at which dots peak. Pixels darker than this are fully dotted. |
r.Celshading.HalftoneSoftness | 0.15 | Softness of the brightness-to-dot-size ramp. |
r.Celshading.HalftoneStrength | 0.7 | Overlay opacity (0 = invisible, 1 = full dot color replacement). |
Band Mask (Comix Inspired)
The legacy BandDither* CVar names are kept for backwards compatibility — the user-facing
name is now Band Mask (DOTS / STROKES modes via StretchX + Angle).
| Name | Default | Description |
|---|---|---|
r.Celshading.BandDither | 0 | Master toggle for the Band Mask comic ink layer. Modifies band assignment via per-pixel brightness bias. |
r.Celshading.BandDitherScale | 4.0 | Cell pitch in pixels. Smaller = denser pattern. |
r.Celshading.BandDitherAngle | 45.0 | Grid rotation in degrees. 45° = classic comic ink direction (default for STROKES mode). |
r.Celshading.BandDitherStrength | 1.0 | Mask intensity (0 = clean band edges, 1 = max comic ink along transitions). |
r.Celshading.BandDitherStretchX | 1.0 | Anisotropic stretch on the cell. 1 = DOTS (round halftone). 4..16 = STROKES (elongated comic ink strokes — combine with BandDitherAngle to rotate). |
r.Celshading.BandDitherSoftness | 0.5 | Edge softness of the dot/stroke (0..1). 1 = soft halftone falloff; 0 = hard pixel-precise edge for crisp comic ink. Independent from BandEdgeSoftness, which softens the toon-band quantization edges. |
Color response
| Name | Default | Description |
|---|---|---|
r.Celshading.TintByLightDirection | 1 | Keep the color of the light (sun / sky tint) on toon pixels. 0 = legacy chroma-from-extracted-lighting path. See Tint By Light Direction. |
r.Celshading.BandedColorStrength | 0 | 0..1 — hard banding of the light tint. 0 = smooth, 1 = quantized into BandCount steps per channel. |
r.Celshading.BaseColorInfluence | 0 | 0..1 — mix the surface BaseColor's chroma into the light tint. 0 = pure color of light, 1 = pure surface hue. |
r.Celshading.LightingDesaturation | 0 | 0..1 — desaturate the extracted lighting before quantization. 0 = keep the light chroma, 1 = greyscale. |
Rim light
| Name | Default | Description |
|---|---|---|
r.Celshading.RimLightIntensity | 0.8 | Screen-space rim intensity. On out of the box; 0 = off (GPU branch skipped). Typical range 0.5..30. |
r.Celshading.RimFollowsSunDirection | 1 | 1 = the rim follows the directional light projected into view (RimLightAngle is an offset from the sun). 0 = camera-anchored (RimLightAngle is an absolute view-space angle). |
r.Celshading.RimLightAngle | 325 | Rotation of the rim source in degrees (0..360) — which side of the silhouette glows. |
r.Celshading.RimLightSpread | 0.87 | Width of the rim arc (0..1). |
r.Celshading.RimLightHardness | 4.3 | Power applied to the rim mask. 1 = linear, higher = sharper falloff. |
r.Celshading.RimLightSharpness | 0.07 | 0 = smooth gradient; above 0 = hard step for a flat toon rim band. |
r.Celshading.RimLightFresnelExp | 2.0 | Fresnel exponent. Higher = rim concentrated at grazing angles. |
r.Celshading.RimLightCameraInfluence | 1.0 | 0..1 — 1 = full Fresnel, rim only at silhouettes; 0 = "painted-on" rim that does not shift as you orbit. |
r.Celshading.RimLightOutlineMix | 0 | 0..1 — gate the rim by the outline mask. 1 = the rim fires only where the line-art does. No extra cost. |
Rim band dither
| Name | Default | Description |
|---|---|---|
r.Celshading.RimDitherStrength | 0 | 0..1 — with Band Mask on, break the rim's fade-out zone into the same dot/stroke pattern. 0 = solid rim. |
r.Celshading.RimDitherSolidEdge | 0.2 | 0..0.5 — fraction of the rim nearest the silhouette that stays solid. |
r.Celshading.RimDitherSoftness | 0 | 0..1 — 0 = crisp dots and a sharp solid edge (comic ink), 1 = soft halftone falloff. |
Metallic
| Name | Default | Description |
|---|---|---|
r.Celshading.MetallicLightCompensation | 1 | Metallic pixels get a grazing-angle boost and a lifted dark band so metal never crushes to black. |
r.Celshading.MetallicShadowFloor | 0.08 | Dark-band floor for metal, as a fraction of HighlightCutoff. Raise toward 0.25 for stronger shape readability. |
r.Celshading.MetallicHighlightIntensity | 2.0 | Multiplier on the metallic specular highlight. 0 = no extra highlight. |
r.Celshading.MetallicLitMultiplier | 2.0 | Multiplier on the lit zone of metal. 1 = neutral, 0 = flat metal. |
Emissive
Unlit surfaces are always kept out of the bands (no quantization, outline or rim). Lit materials can mark glowing areas two ways: route
1 − EmissiveMask into the material's Ambient Occlusion output (the AO marker), or lower
EmissiveBrightCutoff so very bright pixels are detected automatically.
| Name | Default | Description |
|---|---|---|
r.Celshading.EmissiveAOCutoff | 1.0 | AO-marker threshold: pixels whose material AO is below this value are treated as emissive (AO = 1 means no marker). |
r.Celshading.EmissiveBrightCutoff | 100 | Brightness (max RGB channel) above which a pixel is treated as emissive. 100 = effectively off; ~1.5..2 catches glowing areas on lit materials. |
r.Celshading.EmissiveRampSoftness | 0.3 | Width of the brightness-detection ramp. Larger = softer toon-to-emissive transition. |
r.Celshading.EmissiveMultiplier | 1.0 | Brightness multiplier on preserved emissive pixels. 2 = glows twice as bright. |
r.Celshading.EmissiveDarkBoost | 0.85 | 0..1 — lift dark bands on emissive pixels so glowing surfaces read as self-lit even in shadow. |
Stencil & debug
| Name | Default | Description |
|---|---|---|
r.Celshading.AffectWholeScreen | 0 | Bypass stencil + depth gates and cel-shade EVERY visible pixel — no per-actor CustomDepth setup required. Per-volume override via bAffectWholeScreen. |
r.Celshading.CelStencilMask | 255 | Bit mask applied to CustomStencil before comparing. |
r.Celshading.CelStencilRef | 1 | Expected (CustomStencil & mask) value. |
r.Celshading.GateOnDepthMatch | 1 | Also require CustomDepth ≈ SceneDepth (occlusion-safe). |
r.Celshading.DepthGateTolerance | 0.005 | Depth-gate tolerance as a fraction of scene depth (0.005 = 0.5%, distance-invariant). The cel pass skips a stencil pixel that sits farther than this behind the front-most opaque surface. |
r.Celshading.DiagnosticView | 0 | 0 = final, 1 = defogged color, 2 = reconstructed fog, 3 = extracted lighting, 4 = outline mask (R = darken, G = brighten), 5 = rim mask. |
Blueprint API
UCelshadingSubsystem
RefreshBase()— re-discover Sun/Sky/Fog and re-snapshot base on next tick. Call after manually editing the scene's lights/fog.
ACelshadingVolume
All three are also buttons in the volume's Details panel (Preset group).
ApplyPreset()— copyActivePreset's Style + Lighting into this volume (overwrites the current overrides; a one-shot copy, not a live link).ResetToDefaults()— clear all Style + Lighting overrides.SaveAsPreset()— editor only: save the volume's current Style + Lighting as a newUCelshadingPresetasset and link it asActivePreset.
Volume properties — bVolumeActive, StencilSlot, bUnbound,
SpatialFalloff, ContributionWeight, BlendPriority,
PatternDepthReference, ActivePreset and the Settings (style) and
Lighting override structs — are BlueprintReadWrite; the subsystem re-evaluates
volumes every tick, so runtime changes apply on the next frame.
Parameter glossary
A handful of parameters benefit from a glossary entry:
- HighlightCutoff — lighting magnitude that maps to the brightest band. Lower this to clamp bright highlights; raise to admit a wider HDR range into quantization.
- HighlightEntryPoint / ShadowEntryPoint — hard cutoffs. Anything above the highlight entry
point saturates to
HighlightCutoff; anything below the shadow entry point collapses toShadowFloorBrightness. The middle bands fill the range between. - InternalLineThreshold — depth knee (cm) below which depth differences don't draw an outline. Filters texture noise on flat surfaces.
- OutlineThicknessNear / Far — kernel diameter (in pixels) at near/far depth. Lerps with camera-to-pixel distance to keep silhouettes visually consistent across the scene. UI slider stops at 32, but the cap is soft — type any value freely; the shader's static loop bounds handle up to 32 px and saturate beyond. Cost scales O(N²) so bump only as needed.
- FogAware — when on, the pass reconstructs height + volumetric fog, removes it before quantization (so colors are clean), then re-applies it at the end. Avoid disabling unless you have a custom post-process fog setup.
- SpecularHighlightColor — color of the inner highlight rim added by the metallic-compensation path. Default white.
FAQ
Does it work with Lumen, volumetric fog, and volumetric lights?
Yes — all of them feed into the scene-color input the pass quantizes. Lumen GI, exponential height fog, volumetric fog, and volumetric lights (god rays / light shafts from Directional and Point lights) all participate in the cel-shaded output without extra setup.
Does it work with Subsurface / Hair / Cloth shading models?
Yes. The pass gates pixels — it does not modify materials. Any UE shading model is supported.
Mobile / Forward / Vulkan?
v1.0 supports Windows and the deferred renderer (SM5 / SM6). Mobile, the forward renderer and other platforms are not supported yet — see Compatibility.
Does it cost much GPU time?
One compute pass over the view per stencil slot that has a volume (1..255 — pay only for slots you
actually populate), a second pass for slots with the outside outline (OutsideOutlineThickness
≥ 1), and two scene-colour copies. Empty slots cost zero — they don't dispatch — and pixels that
don't carry a pass's stencil value exit early, so the cost grows with resolution and the number of
active slots.
Measure it on your own target hardware: stat gpu shows a Celshading row, and
ProfileGPU or Unreal Insights break it down per pass (Celshading.Cel[N],
Celshading.Halo[N]). For reference, the ten-slot Showcase_Presets demo map
measured 0.42 ms for the whole Celshading scope on an RTX 3090 Ti in an
editor viewport of about half a megapixel.
Troubleshooting
Nothing happens when I tick CustomDepth on an actor
- Confirm Project Settings → Rendering → Custom Depth-Stencil Pass =
Enabled with Stencil(the plugin auto-promotes this on startup; the Output Log then showsLogCelshading: UltimateCelshadingPBR: r.CustomDepth was N … promoting to 3 (Enabled with Stencil). If the setting is still lower, another config or device profile forcesr.CustomDepth). - Confirm
r.Celshading.MasterEnableis 1 (default). - Confirm the actor's CustomDepth Stencil Value matches the volume's
StencilSlot(orr.Celshading.CelStencilRef= 1 for the default no-volume single-dispatch fallback).
Volume Style overrides don't apply
- Each field has a checkbox next to it — flip the override toggle on for the field to participate.
- If the volume has
bUnbound = false, make sure the camera is inside the box or withinSpatialFalloff.
Lighting overrides don't apply
- The plugin auto-discovers the first
ADirectionalLight,ASkyLight,AExponentialHeightFogin the level. Make sure these exist. - If you re-keyed the lights manually after PIE, call
UCelshadingSubsystem::RefreshBase().
Compatibility
Ultimate Celshading PBR is a native UE5 C++ render plugin, distributed via Fab.
- ✅ Unreal Engine 5 projects, Blueprint-only or C++ — editor, Play In Editor and packaged Windows builds. The supported engine versions are listed on the Fab page.
- ✅ Deferred renderer (SM5 / SM6): Lumen, shadow maps, virtual shadow maps, fog and volumetrics.
- ✅ Every UE shading model — the pass reads the GBuffer and never edits your materials.
- ❌ Mobile and the forward renderer are not supported in v1.0.
- ❌ UEFN / Fortnite — UEFN does not support C++ plugins. This plugin will not appear in UEFN's plugin list and cannot be installed there.
Support
Questions, bug reports and feature requests: aaanextgenfactory@gmail.com, or the Questions tab on the plugin's Fab page.
To get an answer on the first reply, include:
- Unreal Engine version and plugin version (Edit → Plugins shows both).
- What you expected and what you see — a screenshot helps.
- The Output Log lines that start with
LogCelshading, and the output ofDumpCVars r.Celshading.if you changed any console variables.