Skip to content
CRUSHFORMV1.1.0
GET IT ON FAB

INTEGRATIONMATERIALS AND SHADERS

Materials and shaders

Every deformed body, wheel, pane and tyre is drawn by a material. The plugin provides the functions that move the vertices, paint the wear and draw the dirt, and it provides the data each mesh needs to find its place in the atlas. This page is the reference for those pieces. For the wiring walk-through, with the ten connections of a painted body, see Quick start. For the idea behind it, see the materials part of Core concepts.

All functions are in the Materials folder of the plugin content, /Crushform/Materials. Their inputs and outputs are the pins that a material or a look connects to. The pin names are exact, spaces included.

FunctionInputsOutputsBuilder
MF_CF_DeformNormal, Crumple Tiling, Fold Amplitude, Fold Wavelength, Fine Fold Wavelength, Fine Fold StrengthWorld Position Offset, Normal, Wear Depth, ScratchUCFContentLibrary::BuildDeformFunction
MF_CF_PaintBase Color, Roughness, Metallic, Wear Depth, Primer Color, Metal Color, Scratch Tiling, Dust Color, Mud Color, Dirt Strength, Snow Color, Built-in Look, Scratch, Scratch Line TilingBase Color, Roughness, MetallicBuildPaintFunction
MF_CF_DirtAmountNoneDust, Mud, Snow, Wet, Dirt Fresh, Dirt Ranks, Pattern Position, Pattern NormalBuildDirtAmountFunction
MF_CF_WheelDirtBase Color, Roughness, Metallic, Dust Color, Mud Color, Dirt Strength, Snow Color, Built-in LookBase Color, Roughness, MetallicBuildWheelDirtFunction
MF_CF_WheelDirtAmountNoneDust, Mud, Snow, Wet, Dirt Ranks, Pattern Position, Pattern NormalBuildWheelDirtAmountFunction
MF_CF_GlassCrackBase Color, Opacity, Roughness, Normal, Built-in LookBase Color, Opacity, Roughness, NormalBuildGlassCrackFunction
MF_CF_GlassCrackDataNoneCrack Mask, Crack Frost, Crack Tilt, Crack 0 to Crack 3, Crack Radii, Pane U, Pane VBuildGlassCrackDataFunction
MF_CF_TireDeformAxleAxis, HalfWidth, BulgeAmount, RimRadius, RimFalloff, FlattenScale, MaxVertDispResultBuildTireDeformFunction
MF_CF_TrunkBendNot set by the plugin’s codeWorld-space World Position OffsetMade in the editor, not by code

MF_CF_Deform works in tangent space, so the normal of a wired material must be tangent space too. MF_CF_Paint and MF_CF_WheelDirt take the Built-in Look switch, a static bool that is true by default. The look settings use that switch, as the Looks section below explains.

A tyre material has a scalar parameter RimRadius, the rim’s radius in mesh units. MF_CF_TireDeform takes it as an input, and UCFTireComponent reads the same parameter to know how far it may push a wheel off an obstacle. The first material slot of the tyre mesh that sets RimRadius itself wins, so a rim material that only inherits the master’s default does not hide the tyre’s own value. If no slot sets it, the first slot that has the parameter supplies its default. UCFTireProfile::RimRadiusOverride, in cm, replaces it.

MF_CF_TrunkBend bends the trunk of a tree. Its own description reads “Parabolic trunk bow driven by CPD 0..2; outputs world-space WPO”. Its two masters add it after the wind offset.

The builders rebuild a function from scratch in the editor. They keep the pin ids by name, and they leave the function unsaved. Save the function after a rebuild.

MF_CF_Deform as one function node, with its six inputs and four outputs.
AssetRole
/Crushform/Materials/M_CF_VehiclePaintThe paint master: Clear Coat, with Paint Color over Base Texture where Paint Mask is set
/Crushform/Materials/M_CF_WheelThe wheel master: Default Lit, through MF_CF_WheelDirt and under the switch Tire Deform
/Crushform/Materials/M_CF_GlassShardThe translucent clear glass of a shard
/Crushform/Materials/MPC_CF_DirtThe dirt look, shared by every body and wheel. Its parameters are read by the dirt functions
/Crushform/Atlas/RT_CF_LatticeAtlasThe lattice atlas. Every deformed mesh finds its slot in it
/Crushform/Atlas/RT_CF_DirtAtlas and RT_CF_DirtFreshAtlasThe dirt atlases, of the lattice atlas’s size
/Crushform/Atlas/RT_CF_ScratchAtlasThe scratch atlas: an RG8 render target of the lattice atlas’s size, with each point’s scratch line cover and direction

The masters take their textures from the plugin’s T_CF_Default* textures, and each texture has a plain and a virtual parameter. The parameter names are fixed. An instance sets them by name, so renaming one breaks every instance that sets it.

Each mesh carries its own data in Custom Primitive Data, in blocks that the material functions read. The offsets of the blocks are project settings in Project Settings > Plugins > Crushform > Atlas. In Unreal Engine 5.6 to 5.8 a primitive has 36 floats (FCustomPrimitiveData::NumCustomPrimitiveDataFloats).

BlockOffset settingFloatsDefault offsetContents
Deformed meshUCFSettings::CustomDataOffset180The atlas slot, the mesh-to-root columns, the translation, the inverse cell size, the quantum and the resolution
WheelUCFSettings::WheelCustomDataOffset234Two whole numbers: the dust and mud amounts, and the snow and wet amounts with the layer ranks
Glass paneUCFSettings::GlassCustomDataOffset1618Four crack records of four floats each
TyreUCFSettings::TireCustomDataOffset320Four contact records of eight floats each

The deformed block holds, in order: the slot at index 0, the three columns of the mesh-to-root rotation and scale at 1 to 9, the mesh origin in lattice space at 10 to 12, one over the cell size at 13 to 15, the quantum at 16 and the packed resolution at 17. MF_CF_Deform reads the same indices, so the layout is fixed by the code, not by the asset.

Atlas
AtlasRT_CF_LatticeAtlas
Slot Width256
Slot Height8
Custom Data Offset0
Wheel Custom Data Offset34
Tire Custom Data Offset0
Glass Custom Data Offset18
Scratch AtlasNone
The Atlas group in Project Settings, under Plugins > Crushform.

With the defaults, the blocks sit at 0 to 17 for the deformed mesh, 18 to 33 for a glass pane and 34 to 35 for a wheel. A tyre has its own 0 to 31, which never shares a mesh with the body.

The plugin refuses a block that does not fit or that overlaps another. The glass block must fit the primitive and stay clear of the deformed block. The tyre block must fit and stay clear of the wheel block. A refused glass offset is logged as an error, and the panes get no cracks:

%s: Glass Custom Data Offset %d does not fit beside the deformed block at Custom Data Offset %d; the panes get no cracks

When a block moves, the material functions that bake its indices must be rebuilt. The editor does that when you change one of the four offsets in the project settings. It rebuilds the eight functions that bake them: MF_CF_Deform, MF_CF_Paint, MF_CF_DirtAmount, MF_CF_WheelDirt, MF_CF_WheelDirtAmount, MF_CF_TireDeform, MF_CF_GlassCrack and MF_CF_GlassCrackData. The notice in the editor says:

Crushform: the Custom Data Offsets are in the material functions. Save the Crushform material functions to keep them.

The rebuild is not saved. Save the functions, or the old indices come back when the editor reloads them. While one of the eight functions is open in the Material Editor, the editor rebuilds none of them. The console command Crushform.RebakeOffsets runs the same rebuild by hand, and it exists in the editor only.

Wiring connects a material to the plugin. Three steps do the work:

  1. The vertex offset and the normal go through MF_CF_Deform.
  2. The base colour, roughness and metallic go through MF_CF_Paint. Pass no paint function for glass and interiors that share a mesh with the body. They dent with it and keep their own look.
  3. The material’s Max World Position Offset Displacement is raised to the value you pass in.

A painted material also gets the scalar CF_PaintWear, which is 1 by default. An instance that shares the body’s master, but should not wear, sets it to 0. For a material outside /Crushform/ and /Engine/, wiring also applies the project’s looks to it.

From Python, the editor exposes the same call:

import unreal
material = unreal.load_asset(MATERIAL_PATH) # the material to wire, in your project
deform = unreal.load_asset("/Crushform/Materials/MF_CF_Deform")
paint = unreal.load_asset("/Crushform/Materials/MF_CF_Paint")
bound = 60.0 + unreal.CFContentLibrary.get_default_fold_amplitude()
unreal.CFContentLibrary.wire_material(material, deform, paint, bound)

Replace 60.0 with the Max Displacement of your profile. The fold amplitude comes from the function, not from the profile.

The same call is a Blueprint function of the editor module, so an editor utility Blueprint can call it. The graph below wires a material with the default fold amplitude added to a Max Displacement. Its A pin holds 60 cm, the class default of UCFProfile::MaxDisplacement. Set it to the value of your profile. The material itself goes in the Material pin, which the graph leaves for you to set.

The graph wires a material with the default fold amplitude added to a Max Displacement.

Wiring is refused, and the material is left as it was, when the material takes a world-space normal, or when it uses material attributes and nothing feeds them. The log names the material in each case.

Nanite and Max World Position Offset Displacement

Section titled “Nanite and Max World Position Offset Displacement”

The Max World Position Offset Displacement of a material is a bound. The renderer uses it to know how far the vertices of the mesh may move. The bound must be at least the profile’s Max Displacement plus the material’s fold amplitude. The folds move vertices past the dents. The default fold amplitude is 3 cm (CFContent::DefaultFoldAmplitude), and get_default_fold_amplitude() returns it.

On Nanite the bound matters more. Cluster culling grows its bounds by it, so a bound that is too short lets the renderer cull a dented panel near the edge of the screen. Wiring raises the bound when it is too small. A material that already has an offset and a bound of its own gets the displacement added to its bound. Set it by hand only when you do not wire.

The tyre masters keep Always Evaluate WPO off. UCFTireComponent turns the offset on for each wheel that has a deformer, and a material that always evaluates it makes that switch do nothing. The wheel master’s bound is 16 cm.

A skeletal mesh needs its material marked Used with Skeletal Mesh under Usage, or its shaders are not compiled and it does not bend.

A look replaces the built-in dirt or crack drawing of a function with a material function of your own. Three settings choose the looks, in Project Settings > Plugins > Crushform > Look:

SettingReplacesRequired output
UCFSettings::BodyDirtLookMF_CF_PaintBase Color
UCFSettings::WheelDirtLookMF_CF_WheelDirtBase Color
UCFSettings::GlassCrackLookMF_CF_GlassCrackOpacity
Look
Body Dirt LookNone
Wheel Dirt LookNone
Glass Crack LookNone
The Look group in Project Settings, under Plugins > Crushform.

The look’s inputs and outputs are matched to the function’s pins by name, case not mattering, spaces mattering. A look input that no pin can feed fails the wiring, unless you tick Use Preview Value As Default on that input. A look without its required output is refused. The log names the missing output:

WireLook: %s lacks the output "%s" that the look of %s cannot do without

The look’s Normal input is always fed. It takes the built-in call’s normal output, or what feeds the normal pin, or a flat tangent-space normal. A look’s Normal output is used too. A built-in call with a Normal output takes it as its own normal. Otherwise it goes in front of the Normal input of MF_CF_Deform in a body, or in front of the material’s normal. The paint of a body has no Normal output, so a body look’s normal goes in front of MF_CF_Deform.

LookReplacesData functionInputs it may haveOutputs it may replace
Body Dirt LookMF_CF_PaintMF_CF_DirtAmountBase Color, Roughness, Metallic, Normal; Dust, Mud, Snow, Wet, Dirt Fresh, Dirt Ranks, Pattern Position, Pattern NormalBase Color (required), Roughness, Metallic, Normal
Wheel Dirt LookMF_CF_WheelDirtMF_CF_WheelDirtAmountBase Color, Roughness, Metallic, Normal; Dust, Mud, Snow, Wet, Dirt Ranks, Pattern Position, Pattern NormalBase Color (required), Roughness, Metallic, Normal
Glass Crack LookMF_CF_GlassCrackMF_CF_GlassCrackDataBase Color, Opacity, Roughness, Normal; Crack Mask, Crack Frost, Crack Tilt, Crack 0 to Crack 3, Crack Radii, Pane U, Pane VOpacity (required), Base Color, Roughness, Normal

An input of a look is fed from an output of the built-in call with the same name, or from an output of the data function with that name. An input that neither can feed fails the look, unless it has Use Preview Value As Default ticked:

WireLook: %s has the input "%s", which nothing can feed; name it after a pin of %s or %s, or tick Use Preview Value As Default

A material with several calls of the built-in function gives the look to the first call only. The log warns about the others.

Changing a look setting applies it to every saved material of the project that calls the function. The materials it changes are recompiled and marked dirty, but not saved, so save them to keep the new look. A material open in the Material Editor is skipped with a warning. Close it and run the console command Crushform.RewireLooks, which does the same by hand. The command exists in the editor only.

The dirt layers are baked into the dirt functions. Changing Dirt Layer Order, or a channel’s blend or Z order, rebuilds MF_CF_Paint and MF_CF_DirtAmount in the editor at once. Crushform.RebakeDirtOrder does the same by hand.

The cooker sees a soft reference in a settings object only from the text of an ini file. A default set in a settings constructor does not reach it. So the plugin adds, by hand, every package that a Crushform settings object soft-references. That covers the atlases, the default profile, the surfaces asset, the dirt collection and the look functions. It does this for every settings class of the plugin’s runtime modules, and it reads the class defaults, where the constructors put their defaults. Packages referenced only from editor-only properties are not added.

The effect is that a look or an atlas that a setting points at reaches the game, even when no map uses it.

UCFContentLibrary is the library behind the builders and the wiring above. It is in the CrushformEditor module, so it exists in the editor only. Its functions are static and in the Crushform|Content category: Python and editor utility Blueprints call them. The library has 24 functions that Blueprint can call. Those not covered above:

FunctionWhat it does
WireGlassMaterialRoutes a translucent glass material’s Base Color, Opacity, Roughness and Normal through MF_CF_GlassCrack, keeping what fed them. World Position Offset is left as it is. Glass takes this call instead of WireMaterial, and a glass material gets no paint function. Refused for a material that uses material attributes, a world-space normal, or a function that lacks a pin. Outside /Crushform/ and /Engine/ it then gets the project’s glass look.
ApplyLooksGives a material the three project looks, each to the built-in call of its target through the look wiring. An empty setting takes the look out again. WireMaterial and WireGlassMaterial call it at their end, and Crushform.RewireLooks calls it for each saved material. Materials in /Crushform/ and /Engine/ are left alone.
RepointFunctionCallsPoints every call of one function in a material to another function, keeping the links by pin name. Returns the number of calls changed. Use it to move a material from its own tyre function to MF_CF_TireDeform.
BuildVehiclePaintMaterialRebuilds M_CF_VehiclePaint, the clear-coat master of a body. It does not wire Crushform: call WireMaterial next.
BuildWheelMaterialRebuilds M_CF_Wheel, with MF_CF_TireDeform in World Position Offset and MF_CF_WheelDirt on the colour, roughness and metallic.
BuildGlassShardMaterialRebuilds M_CF_GlassShard: translucent, two-sided, clear glass with brighter edges.
BuildGlassShardsFills a glass definition’s pieces with one tile of shards, each drawn from a list of static meshes. The glass page covers the shards.
BuildGlassShardMeshRebuilds a static mesh as one glass shard: a 1 cm slab of a convex polygon of 3 to 7 corners, about 100 cm across, with a box collision. The same seed gives the same shard.
BuildDirtLookAdds the dirt look parameters that MPC_CF_Dirt lacks, at their defaults. It never changes a parameter that exists, because its value is the project’s tuning. A parameter that no dirt function reads is named in a warning.
SetSkinnedSectionDisabledSwitches one section of a skeletal mesh off or on in one LOD, with the same result as the Disable box of the mesh editor.
GetAtlasPath, GetDirtAtlasPath, GetDirtFreshAtlasPath, GetScratchAtlasPath, GetDirtLookPathThe long package names of the lattice atlas, the dirt atlas, the dirt fresh atlas, the scratch atlas and the dirt look collection, as set in the project settings.
GetDefaultAtlasSizeThe size of an atlas of the project’s slot size, 4 by 32 slots.

The other functions in the library are the builders and wiring functions described in the sections above.

  • Open a wired body material in the Material Editor. The MF_CF_Deform call feeds World Position Offset and Normal, and MF_CF_Paint feeds Base Color, Roughness and Metallic.
  • Run Crushform.RebakeOffsets after you change an offset. The notice tells you to save the Crushform material functions. Save them, then reopen one and check its pins.
  • In Play In Editor, hit the body with Crushform.DebugImpact. The dent shows in the material, and the paint wears where it scraped.

Cause. The look has no Base Color output, or it has an input that no pin can feed. The log says which:

WireLook: %s has the input "%s", which nothing can feed; name it after a pin of %s or %s, or tick Use Preview Value As Default

Fix. Rename the input to match a pin, or tick Use Preview Value As Default on it. Then wire the material again.

The cracks on a glass pane are wrong after an offset change

Section titled “The cracks on a glass pane are wrong after an offset change”

Cause. The rebuild of MF_CF_GlassCrack or MF_CF_GlassCrackData was not saved, so the materials still read the old indices. Or the glass offset now overlaps the deformed block: the rebuild is refused and logged, and that function keeps its old indices.

Fix. Save the Crushform material functions. If the log names an overlap, move GlassCustomDataOffset so that its 16 floats fit clear of the deformed block, as the Custom Primitive Data table above shows.

Cause. The material is not saved, or it is in /Crushform/ or /Engine/, which the plugin leaves alone. The look also does not apply to a call inside a material function of your own.

Fix. Save the material, then run Crushform.RewireLooks. Put the call in the material itself, not in a function of your own.