Luna's UI Effect Baker is an editor tool that pre-renders a static effect stack into a PNG + 9-slice sprite, so authors can swap a runtime UI Effect (which costs roughly 3 SetPass per element) for a single background-image that batches like any other sprite. Same authoring DX, same look, one draw call per element.

Why bake?

The runtime UI Effects filter is a great fit for elements whose look genuinely needs to change at runtime — hover/active states, dissolves, shines, palette swaps. For everything else — cards, panels, frames, framed portraits, fixed gradients — the filter pays full per-frame cost for pixels that never move.

ApproachBatchesElement countReuseMemory
Runtime filterHigh (~3/el)HighLow
Atomic PNGs stacked (shadow.png + border.png + …)OK2–4×HighMedium
Per-element bakeLowNoneHigh
Style-preset bakeLowHighLow

A baked style-preset PNG is shared across every instance of that style. Multiple cards using the same card-elevated look batch together — often better than the runtime path because UITK can fold them into a single draw call.

When to bake vs. keep the filter

TreatmentUse a bakeUse the runtime filter
Cards, panels, frames, fixed-color glow / shadowYesNo
Static gradient fillsYesNo
Inner-overlay rim, fixed outline colorYesNo
Hover / active / disabled state transitionsNoYes (state tween writes live uniforms)
Shine, flash, dissolve, tile scroll, marching dashes, circling outlineNoYes
Per-instance USS variable overridesNoYes

Rule of thumb: if the visual is the same every frame and never reacts to USS state, it's a bake candidate. Otherwise leave it on the runtime filter.

Design philosophy

Bake style composites, not atomic effects

Don't bake shadow-md.png, glow-lg.png, border-1.png separately and stack three sprites per element. Bake named composites that combine the full effect stack into one PNG per look:

  • card-elevated → one baked PNG (shadow + border + rounded corners)
  • button-primary → one baked PNG (gradient + inner shadow + border)
  • panel-glass → one baked PNG (gradient + edge highlight + border)

One element, one draw call, full effect stack. Composite PNGs reuse across every instance of that style.

9-slice is the foundation

Bake each composite at a reference size with padding for the largest glow/outline radius, then mark 9-slice borders so the center stretches without distorting edges. One bake handles every element size as long as the effects are edge or radially uniform — drop shadows, borders, rounded corners, edge glows, inner shadows all qualify.

What doesn't 9-slice cleanly

  • Full-element gradients, noise, distortions — bake a few canonical aspect ratios and pick the nearest, or accept a small atlas per style.
  • State variants (hover/focus/pressed/disabled) — bake as separate textures, swap on state. Do not drop back to runtime shaders just for states.
  • Theming / dynamic color — preferred: bake neutral, tint at runtime via vertex color (still batches when material is shared). Alternative: bake per theme if you only have two or three.

Animated effects are excluded by design

Shine, Flash, Dissolve, Tile, OutlineCirclingSpeed, OutlineShineSpeed are intentionally not baked — a single-frame snapshot would mislead the author. The dashCount parameter on outline is preserved (static dashes bake correctly) but the per-frame speed offset stays at zero.

If the element needs animation, leave it on the runtime filter.

Watch out for

  • Atlas bloat from color variants. Tint at runtime instead of baking card-blue, card-green, card-red.
  • Text effects. UI Toolkit text doesn't compose cleanly with baked backgrounds — keep text-specific shaders or use SDF for text shadows.
  • Blur padding. Bake with enough transparent padding around the content rect for the largest glow/outline radius, or edges clip when 9-sliced.
  • Reference size. Too small → upscaling artifacts. Too large → wasted memory. Pick based on the largest realistic usage of each style.

Quick start

  1. Create a preset. Right-click in the Project window → Create → CupkekGames → Luna UI → UI Effect Bake Preset. Or click Export in the Luna Effects window (Tools → CupkekGames → Luna Effects) to snapshot a live element straight into a preset.

  2. Select the preset asset. Its inspector has the live preview, validation banners, and the Bake button.

  3. Set the output geometry.

    • Texture Size — final exported dimensions in pixels. What you enter is what you get (512 × 512 here is a 512×512 PNG).
    • Border Radii — corner radii in pixels, in the runtime convention (TL, TR, BR, BL).
    • Padding — margin around the element in pixels (L, T, R, B). Must be at least as large as the widest outer.width / outline.width, otherwise the glow/shadow gets clipped at the edge of the texture. The inner drawable element is textureSize − padding on each side.
  4. Toggle effects and author parameters. Outer, Outline, Gradient, Inner Overlay. The live preview re-renders on every field change.

  5. Bake. Click Bake to Project — the tool writes the PNG and reimports it with the right sprite settings, including the 9-slice border.

  6. Use it in USS.

    css
    .my-card { background-image: url("/path/to/baked.png"); /* No more `filter: luna-effect(...)` for the static surface. */ }

What gets baked

The baker runs the bake variant of the same Luna uber filter the runtime uses (Hidden/Luna/UberFilter_Bake). The shader includes the same effect math files (LunaCommon, LunaSDF, LunaOuter, LunaOutline, LunaGradient, LunaInnerOverlay), so the bake matches the runtime pixel-for-pixel modulo expected sub-pixel differences from the lack of atlas resampling.

The preset surfaces a static subset of each runtime effect:

SectionWhat you authorWhat's omitted
Outercolor, strength, edge spread, offset, width, optional gradient stops
Outlinecolor, width, softness, opacity, optional gradient stops, optional pattern texture (scale / opacity / tint), dash count + ratioMarching-ants speed, circling speed, traveling shine
Gradientlinear/radial, angle, opacity, reverse, color bias, stops
Inner Overlaycolor, per-edge widths, opacity, softness

Live-export from a UIEffectElement (via the Luna Effects window) writes the resolved uniform values from the on-screen element directly into the preset — capturing whatever the user sees, including --luna-fx-* overrides from tier classes, hover state, and theme tokens.

9-slice border math

The baker keeps every decorative band inside the corner region so the center stretches without distorting glow / outline / radii:

left = padding.L + max(TL_radius, BL_radius) + max(outer.width, outline.width) top = padding.T + max(TL_radius, TR_radius) + max(outer.width, outline.width) right = padding.R + max(TR_radius, BR_radius) + max(outer.width, outline.width) bottom = padding.B + max(BL_radius, BR_radius) + max(outer.width, outline.width)

If textureSize minus these borders is ≤ 0 on either axis, the inspector shows a warning. Either grow textureSize (more stretchable middle), or shrink padding / radii / outer / outline widths until a positive center remains.

The output PNG dimensions are exactly textureSize. The sprite border is set on import.

Color space & alpha

Two GPU gotchas are handled inside the baker so the output is drop-in ready:

  • Linear projects. SetVectorArray does not auto-linearize colors, so ApplyStops calls Color.linear on every gradient stop before packing. Flat-color uniforms (_OutlineColor, _OuterColor, _InnerOverlayColor) go through Material.SetColor, which Unity linearizes correctly. The output RenderTexture uses RenderTextureReadWrite.sRGB in Linear projects, so the GPU runs linear → sRGB on store and the PNG ends up gamma-encoded — ready to import back as an sRGB sprite.
  • Premultiplied → straight alpha. The filter pipeline operates in premultiplied alpha throughout. The bake renders premultiplied, then converts back to straight alpha before EncodeToPNG. UITK sprites expect straight alpha — without this conversion, RGB values get "burned" (multiplied twice when UITK re-premultiplies during composite).

When you assign a straight-alpha PNG as the preset's sourceContent, leave sourceIsStraightAlpha = true. The shader premultiplies on sample so it composites with the rest of the pipeline. Captures from another bake (already premul) should set this to false.

Export from a live element

In the Luna Effects editor window, every tracked UIEffectElement has an Export button. Click it to snapshot the currently-rendering state — including all USS-resolved --luna-fx-* values — into a fresh UIEffectBakePreset asset.

The exporter reads each uniform back from a MaterialPropertyBlock after the same ApplyToMPB + ApplyOverrides pipeline the runtime uses, so the preset captures exactly what's on screen. Animated state is intentionally dropped (the preset is a static snapshot).

Use this when iterating in-scene with USS until the look is right, then export → bake → drop the PNG on background-image.

File map

FilePurpose
Editor/Shaders/LunaUberFilter_Bake.shaderBlit-compatible variant of the runtime uber filter
Editor/EffectBaker/UIEffectBakePreset.csScriptableObject + per-effect serializable configs
Editor/EffectBaker/LunaEffectBaker.csStatic RenderToRT / RenderToTexture / BakeToAsset API
Editor/EffectBaker/UIEffectBakePresetEditor.csCustom inspector + live preview pane
Editor/EffectBaker/UIEffectStackExporter.csSnapshot a live UIEffectElement into a fresh preset

Roadmap

  • Sprite-sheet baking — N frames at intervals into a single texture for animated effects (outline shine, dissolve, tile). Would need re-including the animated shader includes in the bake variant and a frame-stepping driver in the baker.
  • State variants in one preset — bake hover / active / disabled poses to suffixed assets in a single click.
  • Batch re-bake — "re-bake every UIEffectBakePreset in folder X" so a palette / radius convention change rolls out in one operation.
  • Class-driven auto-bind — bind a baked sprite to a USS class so authoring stays in USS while the renderer swaps to the sprite path. Today the bind is manual (background-image: url(…)).

Settings

Theme

Light

Contrast

Material

Dark

Dim

Material Dark

System

Sidebar(Light & Contrast only)

Light
Dark

Font Family

DM Sans

Wix

Inclusive Sans

AR One Sans

Direction

LTR
RTL