Skip to content

UnityEQ Pivot Painter Lab

Guide

Everything you need to use the tool. For version history see the release notes.


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.


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.


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 demo's grass: 74 patches from one baked mesh, every blade its own element, bent by one Wind Controller and parted by an Interactor.

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.


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:

  1. Assets ▸ Create ▸ EQ Pivot Painter Lab ▸ Pivot Painter Asset
  2. Select it, click Open in EQ Pivot Painter Lab
  3. Assign your Source Mesh (never modified)
  4. Pick a detection mode (below), click Detect Elements
  5. Click Solve Preview and check the result
  6. Click Bake
  7. Drop the generated _Foliage prefab into your scene
A static tree through the workbench: Detect and Solve Preview (221 elements, each with a pivot and a parent), the pivots view, then the bake: a mesh, two 16×14 pivot textures, hero materials and a prefab.

Your meshUse
Dense one-mesh tree, leaves everywhereSkeleton First (RGS-PA)
Materials already separate bark from leavesMaterial Guided
Branch/leaf pieces already split apartIslands
One submesh per logical partSubmesh Sections (default)
One welded mesh, sharp creases at the joinsSmartCut
Small prop or grass tuft, whole-object swaySingle 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.


Always run Solve Preview on a new asset. Check, in order:

  1. 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.
  2. Pivots — on the geometry, at the joins? A pivot in mid-air causes swimming.
  3. Axis view — pointing outward along limbs?
  4. Static view — is moss and fixed dressing frozen?
  5. Problems view — highlights likely issues directly.

Dense Lines keeps parent and axis lines readable on high element counts.


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.


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.


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.


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.


Three components; none need wiring to each other.

One Wind Controller on a stand of baked trees: strength, gust fronts and direction, then an Interactor parting the canopy.

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.

Start from the Hero Quality / Mid Distance presets, or the wind presets Calm / Hero / Storm / Background.

TierStrengthSpeed
Level 1 Trunk00.55
Level 2 Big Branch0.180.9
Level 3 Small Branch0.321.35
Level 4 Leaf0.582.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.


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.


  1. Bake with PP2 Textures
  2. In Generated Outputs, tick Include impostor LOD (and optionally Include source mesh LODs)
  3. 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.


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.


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.
  • 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 EQPivotPainterDeformFoliage in 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_TexelSize

URP / 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_TexelSize

2. 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_END

The 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.


SymptomFix
Whole tree moves as one rigid pieceDetection found one element — change detection mode
Motion uniform and mushyMost elements on one level — try Skeleton First
Leaves swim or stretchPivot off the geometry — Pivot Parent Edge
Moss or bark fluttersSelect it, click Static
Manual edits vanish after re-bakeTurn off Auto Detect On Bake
Whole forest moves in unisonRaise Gust Front Scale; add Wind Variation
Normal map does nothingMesh needs tangents — check the importer
Pink / magenta materialWrong pipeline’s hero shader, or a stripped variant in a build
Mask map channels look wrongSet Packed Mask Layout to match your texture
SmartCut not splittingEach 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.

Report an issue with this tool

Opens our feedback form in a new tab with the tool name pre-filled.