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.
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.
| Approach | Batches | Element count | Reuse | Memory |
|---|---|---|---|---|
| Runtime filter | High (~3/el) | 1× | High | Low |
Atomic PNGs stacked (shadow.png + border.png + …) | OK | 2–4× | High | Medium |
| Per-element bake | Low | 1× | None | High |
| Style-preset bake | Low | 1× | High | Low |
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.
| Treatment | Use a bake | Use the runtime filter |
|---|---|---|
| Cards, panels, frames, fixed-color glow / shadow | Yes | No |
| Static gradient fills | Yes | No |
| Inner-overlay rim, fixed outline color | Yes | No |
| Hover / active / disabled state transitions | No | Yes (state tween writes live uniforms) |
| Shine, flash, dissolve, tile scroll, marching dashes, circling outline | No | Yes |
| Per-instance USS variable overrides | No | Yes |
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.
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.
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.
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.
card-blue, card-green, card-red.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.
Select the preset asset. Its inspector has the live preview, validation banners, and the Bake button.
Set the output geometry.
512 × 512 here is a 512×512 PNG).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.Toggle effects and author parameters. Outer, Outline, Gradient, Inner Overlay. The live preview re-renders on every field change.
Bake. Click Bake to Project — the tool writes the PNG and reimports it with the right sprite settings, including the 9-slice border.
Use it in USS.
.my-card {
background-image: url("/path/to/baked.png");
/* No more `filter: luna-effect(...)` for the static surface. */
}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:
| Section | What you author | What's omitted |
|---|---|---|
| Outer | color, strength, edge spread, offset, width, optional gradient stops | — |
| Outline | color, width, softness, opacity, optional gradient stops, optional pattern texture (scale / opacity / tint), dash count + ratio | Marching-ants speed, circling speed, traveling shine |
| Gradient | linear/radial, angle, opacity, reverse, color bias, stops | — |
| Inner Overlay | color, 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.
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.
Two GPU gotchas are handled inside the baker so the output is drop-in ready:
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.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.
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 | Purpose |
|---|---|
Editor/Shaders/LunaUberFilter_Bake.shader | Blit-compatible variant of the runtime uber filter |
Editor/EffectBaker/UIEffectBakePreset.cs | ScriptableObject + per-effect serializable configs |
Editor/EffectBaker/LunaEffectBaker.cs | Static RenderToRT / RenderToTexture / BakeToAsset API |
Editor/EffectBaker/UIEffectBakePresetEditor.cs | Custom inspector + live preview pane |
Editor/EffectBaker/UIEffectStackExporter.cs | Snapshot a live UIEffectElement into a fresh preset |
UIEffectBakePreset in folder X" so a palette / radius convention change rolls out in one operation.background-image: url(…)).Settings
Theme
Light
Contrast
Material
Dark
Dim
Material Dark
System
Sidebar(Light & Contrast only)
Font Family
DM Sans
Wix
Inclusive Sans
AR One Sans
Direction