UnityEQ Pivot Painter Lab
Guide
Everything you need to use the tool. For version history see the release notes.
What it does
Section titled “What it does”You give it a mesh. It works out which parts are branches and leaves, which branch each part hangs from, where each part should pivot, and how far it swings. It bakes that into data textures and a mesh, and ships hero foliage shaders that animate the whole tree on the GPU — trunk, big branches, small branches, leaves — with every part inheriting its parents’ motion.
No Houdini, no Blender round-trip, no manual UV bookkeeping.
Requirements: Unity 2022.3 LTS or newer (verified through Unity 6.4). Built-In, URP or HDRP. No other packages needed.
Five ideas that explain everything
Section titled “Five ideas that explain everything”Element — a part of the mesh that moves as one piece. Each owns source triangles by index, so ownership is stable even when parts share vertices.
Hierarchy — which element hangs from which. Children inherit their parents’ motion before adding their own. That inheritance is what makes a tree read as a tree instead of a flag.
Pivot, axis, extent — the point an element rotates about, the direction it points, and how far it reaches. Extent is a real distance in metres, so long branches swing further than short ones.
Hierarchy level vs wind bucket — two different things, and confusing them
causes most tuning frustration. Hierarchy level is structural chain depth.
Wind bucket is the wind tier that Level 1–4 in the material actually reads.
They are usually aligned but independent — painting a bucket says “make this
flutter like a leaf” regardless of where it sits structurally.
The bake — writes the answer into a mesh plus two data textures. The shader replays it on the GPU. Nothing animates on the CPU.
Demo scene
Section titled “Demo scene”Assets/EQPivotPainterLab/Demo/EQPivotPainterLab_Demo.unity
Open it, select EQ Pivot Painter Demo in the hierarchy, and press Build Demo in the inspector. It generates box trees with a trunk - branch - twig - leaf hierarchy, solves and bakes them, and drops the stand into the scene with a wind controller attached. Press Play to see it move.
The stand also includes a grass patch of individually rooted strands with an interactor sphere gliding through it, so the character-interaction push is visible immediately, and each tree carries a Wind Variation component so the stand does not sway in lockstep. Both are toggles on the demo object, and the Display option switches the stand between Hierarchy Colors, Element Colors and the plain lit material.
The scene ships empty and builds on demand, so it works in Built-In, URP and HDRP alike - the materials are created for whichever pipeline the project is using. Boxes rather than foliage art on purpose: every element is a separate island you can count, so you can check the hierarchy the solver found instead of taking it on trust. Open the workbench on any generated asset to inspect it.
Everything it generates is written to your generated-assets root, never inside the package.
Your first bake
Section titled “Your first bake”Easiest path: Tools ▸ EQLabs ▸ EQ Pivot Painter Lab ▸ Wizard (Guided Setup)
— pick a source, let it analyse, accept the recommended modes, generate. It bakes
and builds a foliage prefab for you.
Manual path:
Assets ▸ Create ▸ EQ Pivot Painter Lab ▸ Pivot Painter Asset- Select it, click Open in EQ Pivot Painter Lab
- Assign your Source Mesh (never modified)
- Pick a detection mode (below), click Detect Elements
- Click Solve Preview and check the result
- Click Bake
- Drop the generated
_Foliageprefab into your scene
Detection modes
Section titled “Detection modes”| Your mesh | Use |
|---|---|
| Dense one-mesh tree, leaves everywhere | Skeleton First (RGS-PA) |
| Materials already separate bark from leaves | Material Guided |
| Branch/leaf pieces already split apart | Islands |
| One submesh per logical part | Submesh Sections (default) |
| One welded mesh, sharp creases at the joins | SmartCut |
| Small prop or grass tuft, whole-object sway | Single Element |
Material Guided and Skeleton First run the same solver — Material Guided additionally feeds it roles from your material slots.
Tuning: Weld Tolerance (0.001) is how close vertices must be to count as joined. SmartCut Angle (55) is the crease angle that splits. Min Element Triangles (2) folds tiny fragments into neighbours — this is what stops SmartCut producing confetti.
Weeping or hanging growth (willows, vines): turn off Correct Downward Foliage Axes in the preset so they keep genuine downward axes.
Reviewing before you bake
Section titled “Reviewing before you bake”Always run Solve Preview on a new asset. Check, in order:
- Hierarchy view — do colours band like a tree? If it is almost all one colour, the hierarchy did not form and no wind tuning will fix it.
- Pivots — on the geometry, at the joins? A pivot in mid-air causes swimming.
- Axis view — pointing outward along limbs?
- Static view — is moss and fixed dressing frozen?
- Problems view — highlights likely issues directly.
Dense Lines keeps parent and axis lines readable on high element counts.
Bake Doctor
Section titled “Bake Doctor”Tools ▸ EQLabs ▸ EQ Pivot Painter Lab ▸ Bake Doctor
Runs exactly the same checks as the hard pre-bake gate, so its verdict can never disagree with what a bake will do — but writes nothing.
The gate refuses a bake on: triangles owned by more than one element,
coverage below 98%, invalid or self-parenting, hierarchy cycles, chains deeper
than the shader’s 3 hops, or more than 30,000 elements. It names the offending
elements. Select in Scene highlights them; Log indices dumps the list.
A refusal is the tool declining to hand you something subtly broken. Fix what it names rather than relaxing settings until it passes.
Manual repair
Section titled “Manual repair”All operations act on the selection and are undoable.
Topology — Merge Selected (needs 2+), Detach Selected, Parent To Active.
Split — Split By Island, Split By Submesh (act on the active element).
Glue — Static (rides its parent, adds no motion — for moss, bark, fixed
dressing), Dynamic, Auto Glue.
Visibility — Show, Hide, Isolate (preview only; hidden elements still bake).
Family — Structural, Leaf, Attachment, Helper, Clear Family,
Classify Unknown. Attachment also marks static; Helper marks static and hidden.
Level 1–4 — sets hierarchy level and wind bucket.
Wind Bucket L1–L4 / Auto — sets only the wind tier, marked as your intent so
re-detection leaves it alone.
Parents — Lock Parents protects manual structure from re-detection;
Clear Overrides resets locks, family and pivot/axis/extent overrides.
Pivot — Freeze To Active, Pivot Parent Edge (the fix for swimming cards),
Pivot Center, Pivot Base, Extent Bounds, Axis X/Y/Z.
Turn off Auto Detect On Bake once you start hand-authoring.
Branch groups
Section titled “Branch groups”For editing whole limbs at once. Build From Hierarchy scaffolds groups from the
current solve, or New From Selection creates one. Set the group’s colour, level,
static state, parent group or pivot override, then Apply Group To Elements.
Groups are locked by default so their settings survive baking — which is what makes the material’s Level 1–4 target trunk, big branches, small branches and leaves predictably.
Baking and outputs
Section titled “Baking and outputs”PP2 Textures (default) — data textures plus a lookup UV. Required for the hero shaders. PP1 Mesh Data — payloads in mesh UV channels; texture-free, but cannot drive hero materials.
Output lands in Assets/EQPivotPainterLab_Generated/Bakes/<AssetName>/ —
deliberately next to the tool folder, so reinstalling an update never deletes
your bakes:
<name>_BakedMesh.asset— mesh with lookup UVs<name>_PivotParent.asset/<name>_AxisExtent.asset— the data textures<name>_HeroFoliage*.mat— production material, one per submesh<name>_Foliage.prefab— ready to use, wind variation already attached- a JSON report under
Reports/
Re-baking replaces the same deterministic paths. The same input bakes byte-identical output every time.
Baked meshes use vertex colour alpha to carry the element’s material family, so source vertex colours are not preserved.
Wind in a scene
Section titled “Wind in a scene”Three components; none need wiring to each other.
Wind Controller (one per scene) — drives every EQ foliage material at once: master strength/speed/direction, gust settings, shelter and lean, and per-level strength and speed. While enabled it overrides each material’s own wind values. Its defaults are calmer than the material defaults, so adding one visibly settles a scene — tune from there. Gust Front Scale is what stops a forest pulsing in unison: at 0 every tree gusts together; raise it and gusts travel across the world.
Wind Variation — per-renderer phase and strength offset so copies of one tree do not move identically. Already on generated prefabs. Leave Seed at 0 to derive it from world position. (It uses a MaterialPropertyBlock, so that renderer leaves SRP batching; others sharing the material keep batching.)
Interactor — put on a character or vehicle to push foliage aside. Radius, strength, downward press, centre offset. Eight are active at once; beyond that, the nearest to the camera win.
Material wind controls
Section titled “Material wind controls”Start from the Hero Quality / Mid Distance presets, or the wind presets Calm / Hero / Storm / Background.
| Tier | Strength | Speed |
|---|---|---|
| Level 1 Trunk | 0 | 0.55 |
| Level 2 Big Branch | 0.18 | 0.9 |
| Level 3 Small Branch | 0.32 | 1.35 |
| Level 4 Leaf | 0.58 | 2.4 |
Level 1 ships at 0 deliberately — a swaying trunk reads as wrong on most trees.
Parent Chain Depth (default 3) is the biggest performance lever: how many parent hops the shader replays. 1 is much cheaper and fine for mid-distance.
Three wind-character controls — Gust Detail, Shelter Strength and Lean Strength — are tuned on the Wind Controller, not on the material.
Textures and masks
Section titled “Textures and masks”Albedo, normal and packed maps are classified by filename and bound
automatically, with a per-slot review panel to override. Conventional suffixes
(_D, _N, _ORM) get it right first time.
Set Packed Mask Layout to match your texture rather than repacking:
0 Unity MaskMap (R metallic, G AO, A smoothness) · 1 ORM · 2 ORT ·
3 AO/Rough/Height (SpeedTree’s default). An ORM packer builds a packed map from
separate sources.
Impostors and lods
Section titled “Impostors and lods”- Bake with PP2 Textures
- In Generated Outputs, tick Include impostor LOD (and optionally Include source mesh LODs)
- Click Create Foliage Prefab — or just Bake, which uses the same toggles
With source LODs included, the tool reads your FBX’s own LODGroup, skips its LOD0 and any trailing billboard, and slots the remaining meshes in as static LODs reusing your original transition heights.
Import automation
Section titled “Import automation”Tools ▸ EQLabs ▸ EQ Pivot Painter Lab ▸ Import Automation ▸ Create Or Ping Default Profile creates the profile asset and selects it.
Opt-in and off by default. Once enabled on the profile, matching meshes are baked as they are imported. Only the profile at the default path is honoured.
Render pipelines
Section titled “Render pipelines”One bake works in all three. Shading differs where the pipelines do.
- Built-In — Standard surface lighting.
- URP — full main and additional light handling, received shadows, and a DepthNormals pass, so foliage participates in SSAO (in Depth Normals mode) and decal normal blending.
- HDRP — lit by the scene’s main directional light (direction, colour, intensity, cascade shadow) plus spherical-harmonics environment.
Known limitations
Section titled “Known limitations”- HDRP: additional punctual and area lights do not reach the hero shader, and
it writes no motion vectors, so wind motion is not represented to TAA (moving
objects are unaffected). For a richer rig, wrap
EQPivotPainterDeformFoliagein a Shader Graph and shade with HDRP’s Lit master node — the deformation is a plain include designed to be reused that way. - Vertex colours are not preserved on baked meshes (see Baking above).
- Budget: 30,000 elements and a parent chain depth of 3 per asset.
Wrapping the wind include in your own shader
Section titled “Wrapping the wind include in your own shader”Runtime/EQPivotPainterFoliageWind.hlsl deforms object-space position, normal and
tangent only — lighting stays in your shader — so you can wrap it in a Shader Graph
custom function or a hand-written pass and keep your own shading. Before including
it you must:
1. Declare the two data textures and define the sampling macros. The texel-size
uniform itself is declared by EQPivotPainterFoliageWindUniforms.hlsl, not by you.
Built-In:
sampler2D _PivotParentTex; sampler2D _AxisExtentTex;#define EQPP_SAMPLE_PIVOT(uv) tex2Dlod(_PivotParentTex, float4(uv, 0, 0))#define EQPP_SAMPLE_AXIS(uv) tex2Dlod(_AxisExtentTex, float4(uv, 0, 0))#define EQPP_PIVOT_TEXELSIZE _PivotParentTex_TexelSizeURP / HDRP:
TEXTURE2D(_PivotParentTex); SAMPLER(sampler_PivotParentTex);TEXTURE2D(_AxisExtentTex); SAMPLER(sampler_AxisExtentTex);#define EQPP_SAMPLE_PIVOT(uv) SAMPLE_TEXTURE2D_LOD(_PivotParentTex, sampler_PivotParentTex, uv, 0)#define EQPP_SAMPLE_AXIS(uv) SAMPLE_TEXTURE2D_LOD(_AxisExtentTex, sampler_AxisExtentTex, uv, 0)#define EQPP_PIVOT_TEXELSIZE _PivotParentTex_TexelSize2. Provide _Time — a Unity built-in present in all three pipelines.
3. On URP and HDRP, define EQPP_WORLD_TO_OBJECT_MAT as UNITY_MATRIX_I_M.
The default is Built-In’s legacy unity_WorldToObject, and HDRP deliberately turns
that name into a compile error to force UNITY_MATRIX_I_M. This is the one step
that fails loudly if you skip it.
4. On URP and HDRP, include the uniforms header inside your own cbuffer, before including the wind file:
CBUFFER_START(UnityPerMaterial) float4 _MainTex_ST; // ...your own surface properties... #include "EQPivotPainterFoliageWindUniforms.hlsl"CBUFFER_ENDThe wind set is ~35 per-material uniforms. A material property declared outside
UnityPerMaterial makes the whole shader SRP-Batcher incompatible, and foliage is
drawn in bulk, so that costs you per-draw CPU on exactly the content this tool
produces. Built-In has no equivalent concept and needs nothing here — the wind file
includes the uniforms itself at file scope.
Impostor cards. Runtime/EQPivotPainterOctahedral.hlsl exposes the
hemi-octahedron mapping used by the impostor atlas: EQPP_OctaEncode takes a unit
direction with z >= 0 to an atlas UV in [0,1]², and EQPP_OctaDecode inverts it.
They are exact inverses for upper-hemisphere directions. Z is up, and only the upper
hemisphere is encoded, because trees are not seen from below.
Do not hand-edit Runtime/EQPivotPainterPipelineAvailability.hlsl. It is
generated by the tool to record which render-pipeline packages your project has
installed, and it is overwritten on the next domain reload.
Quick fixes
Section titled “Quick fixes”| Symptom | Fix |
|---|---|
| Whole tree moves as one rigid piece | Detection found one element — change detection mode |
| Motion uniform and mushy | Most elements on one level — try Skeleton First |
| Leaves swim or stretch | Pivot off the geometry — Pivot Parent Edge |
| Moss or bark flutters | Select it, click Static |
| Manual edits vanish after re-bake | Turn off Auto Detect On Bake |
| Whole forest moves in unison | Raise Gust Front Scale; add Wind Variation |
| Normal map does nothing | Mesh needs tangents — check the importer |
| Pink / magenta material | Wrong pipeline’s hero shader, or a stripped variant in a build |
| Mask map channels look wrong | Set Packed Mask Layout to match your texture |
| SmartCut not splitting | Each side needs ≥ Min Element Triangles; check the crease angle |
Support: use the feedback form and include the tool version, your Unity version and pipeline, the JSON bake report, and the Bake Doctor verdict.
Opens our feedback form in a new tab with the tool name pre-filled.