// Serwus Studio · Cursed Cozy Village series

Alchemist's Corner - Documentation

Stylized toon alchemy props with a transform-driven liquid system. The value is in the shader: one shared material becomes infinite glowing potions, with a surface that stays level when a bottle is tilted and rides along when a bottle is picked up.

Unity 6000.3+URP 17 · Forward / Forward+ Namespace SerwusStudioNo third-party dependencies

01Quick Start

First potion, one click

Tools ▸ Serwus Studio ▸ Cursed Cozy Alchemist ▸ Create Starter Prefabs builds the pack's materials and prefabs and drops a showcase row into the open scene - three potions, a bubbling cauldron with steam, and a standalone vapor emitter. Everything runs in the editor, so you don't even have to press Play. Drag Prefabs/CCAlc_Potion_Round anywhere and you have a glowing potion.

Make it your potion

Select the Liquid child of the bottle. Everything lives on the CCAlc Potion Filler component:

Want to…Do this
change how full it isFill Level slider
recolour itLiquid Color / Deep Color / Emission Color
get a whole new potionright-click the component header → Randomize Potion
pick from the palette in codefiller.SetPotion(2); - witch green, arcane purple, blood red, mana blue, sickly yellow, ember orange
stop the top sitting in the neckraise Top Margin (metres)

Each bottle is its own potion even though they all share one material - the component writes per-instance values, so no material duplicates pile up.

Make it move

Slosh is already on. Grab a bottle in the Scene view and drag it: the surface leans into the movement, overshoots, rocks back level, and the ripples keep churning for a few seconds afterwards. Spinning the bottle stirs it too. The component's Slosh Readout meters show tilt, agitation and speed live - a slow glide barely lifts them, a sharp flick spikes them.

Not dramatic enough? Two knobs, in this order: material → WavesWave Height and Wave Boost When Stirred; then component → SloshTilt per G up and Damping down.

Steam over the neck

Drop Prefabs/CCAlc_Vapor_Steam at the bottle's mouth. On the CCAlc Vapor Emitter pick a Preset - Steam, Smoke or MagicHaze - tweak Density / Speed / Tint, then right-click → Apply Preset.

Using your own bottle model

  1. Model it as two meshes: an outer shell and a slightly smaller inner liquid mesh (or duplicate the shell and scale it inward ~5%). The liquid mesh must be closed - the shader uses its back faces as the liquid's top surface.
  2. Shell → M_CCAlc_Glass. Inner → M_CCAlc_Liquid.
  3. Add CCAlc Potion Filler to the inner object.
  4. Set Top Margin so a full potion sits under the neck.
  5. Leave Batching Static off on the liquid mesh.

The fill range is read from the mesh bounds automatically.

Requirements: Unity 6000.3 or newer, Universal Render Pipeline 17.x (Forward or Forward+). The shaders are hand-written HLSL, SRP Batcher compatible, no GrabPass. No third-party package dependencies.

02Shaders

ShaderUse onHighlights
Liquid (Fill + Bubbles) inner liquid mesh of bottles / flasks (opaque) transform-driven fill (World or Local space), waves and slosh, solid top surface, depth tint, foam line, rising bubbles, pulsing glow, four optional texture slots
Glass (Stylized) bottle / flask shell (transparent) two stylized specular bands, outer + inner fresnel rim, B/W grime mask. No GrabPass, VR/mobile safe
Cauldron (Bubbling Brew) cauldron / pot surface disc (opaque) churning animated brew, popping bubbles, pulsing emission

Liquid and Cauldron share property names (_LiquidColor, _DeepColor, _EmissionColor, _EmissionStrength, _BubbleAmount) so one Potion Filler recolours either.

Glass (Stylized) is the same implementation as the glass in Cursed Cozy Alley - copied in rather than referenced, so this pack still exports standalone. Closed bottle shells want Cull = Back; flat panes can use Off.

Bottle setup

  1. Model the bottle as two meshes: an outer shell and a slightly smaller inner liquid mesh. The liquid mesh should be closed.
  2. Shell → material with Glass (Stylized). Inner → material with Liquid (Fill + Bubbles).
  3. Add CCAlc Potion Filler to the inner liquid object and set fill, colour and glow. The glass draws over the liquid automatically (transparent queue after opaque).

Cauldron setup

  1. A flat disc mesh for the brew surface, UV-unwrapped 0..1. Material → Cauldron (Bubbling Brew).
  2. Optionally add CCAlc Potion Filler to recolour it from the same palette.

03How the fill works

_FillLevel (0..1) is mapped into the mesh's own object-space Y range (_FillMinOS .. _FillMaxOS, written by the Potion Filler from the mesh bounds). The shader rebuilds the cutting plane from the object matrix every frame, so a bottle can be moved, parented to a hand, thrown or animated at zero per-frame script cost - the potion goes with it.

Fill Space - a switch on the material, also reachable from the Potion Filler inspector - decides how that plane is oriented:

Fill SpaceBehaviourUse for
World (default) The surface is a horizontal plane in world space. Tilt the bottle and the liquid stays level; lift it and the liquid rides along. Shelf props, potions being carried, anything where the real-liquid look sells the shot.
Local The surface is welded to the mesh's own XZ plane, so it tilts with the bottle. Corked vials, physics props that tumble, VR grabbing, bottles that end up upside down.

Do not mark liquid meshes as Batching Static. Static batching bakes the transform into the mesh, so the fill plane loses the reference it is built from. The Potion Filler inspector warns about this and offers a one-click fix.

The top surface

The liquid mesh renders double sided (Cull Off). Back faces are only ever visible through the hole the fill plane cuts, so the shader shades them as the flat top of the liquid: the view ray is walked back up to the plane, and the result is shaded with the plane's normal, the surface colour and foam ripples. That is what stops you from seeing straight through the bottle when looking down at it.

_SurfaceDepthTint lets a little of the volume below bleed through the top, so it reads as depth rather than a painted lid.

04Foam and waves

The foam is a band positioned along Y, independent of everything else:

KnobWhat it does
_FoamOffset
Foam Drop Below Surface
Where the band's top edge sits, in metres below the liquid surface. 0 = foam starts right at the surface; raise it for a layer of clear liquid above the froth.
_FoamHeight
Foam Height along Y
How far down the band reaches from that top edge.
_FoamSoftness How much of the band each edge spends fading. 1 = one long gradient from full foam at the top to nothing at the bottom; 0.02 = a crisp painted stripe.
_FoamTexStrength
Foam Edge Raggedness
Erodes the band where the texture is dark, renormalised so the core keeps full opacity while the fringe breaks up. Raggedness never means „less foam". At 1 it becomes a full texture cutout.

In the foam texture, white = foam. That contract holds on the walls and on the top surface, so one texture reads the same on both sides of the rim: white areas are froth, dark areas are clear liquid.

Both also sample at the shared _ProjectionScale, so the pattern keeps the same feature size as it carries over the rim instead of changing density there. On the walls the horizontal coordinate wraps around the bottle and the vertical one is the depth below the surface - measured in the same units, so the pattern keeps its aspect ratio and stays anchored to the surface, riding the waves with it.

To make the top read as strongly as the band, raise Top Foam Amount (_SurfaceRipple). Procedural Ripple Tiling only affects the fallback noise used when there is no foam texture, or when raggedness is below 1.

Waves

Three crossing sines are added to the cut plane itself, so the line where the liquid meets the glass undulates instead of being a ruler-straight edge; the top surface tips its normal along the wave slope and gathers foam on the crests, so the whole top visibly travels. _WaveHeight (metres), _WaveScale and _WaveSpeed shape the resting state; height 0 gives a dead-flat surface back.

05The motion model

Turn on Slosh on the Potion Filler and CCAlcSloshSimulator turns the transform's movement into two signals that read differently on screen.

A transform's position is quantised to frames, so differentiating it twice gives a spike train, not an acceleration - dragging an object produces frames with no movement next to frames with a big jump. Clamp that and every motion saturates: a slow glide and a sharp flick come out identical. The simulator therefore:

Measured response, same 60 fps rig, distance and duration varied:

MotionPeak tiltPeak agitationAgitation 3 s later
slow glide (0.5 m over 1.5 s)0.040.090.03
medium (0.5 m over 0.5 s)0.180.270.10
quick dart (0.3 m over 0.12 s)0.470.640.24
violent yank (0.5 m over 0.08 s)0.710.890.33

Tilt - Tilt per G is the main „how dramatic" knob (how far it leans at 1 g), Frequency how fast it rocks, and Damping is the „it stops too soon" knob - drop it toward 0.02 and the surface rocks for many seconds; push it to 1 and it stops dead like syrup. Max Tilt bounds the target; the spring may overshoot it by up to 50%, which is the wobble, with a hard backstop above that.

Agitation - a 0..1 energy that spikes on jerk, plain speed and spinning the bottle, then decays on its own clock. It multiplies the wave height through _SloshWaveBoost. Agitation Decay is how many seconds the ripples outlive the movement; Agitation Idle holds a permanent simmer.

Slosh needs World fill space (in Local the surface is welded to the mesh, so there is nothing to tilt) and costs one small property-block write per frame. The Potion Filler inspector shows live meters for tilt, agitation and speed - drag a bottle around and watch them separate.

Not seeing enough? Raise _WaveHeight and _SloshWaveBoost on the material first - they set the ceiling the component's agitation scales into.

06Texture slots and mapping

All four slots are optional and keyword-gated - an empty slot costs nothing and the shader keeps its fully procedural look. Drop a texture into the material inspector and the slot switches itself on. Each slot lives in the section it belongs to - the bubble texture under Bubbles, foam under Surface & Foam - together with its own tiling, strength and scroll.

SlotChannelWhat it does
Liquid BodyRGB Multiplied into the liquid colour - swirls, murk, sediment, gradients. Scrollable.
Foam / RipplesR White = foam. Two jobs at once: makes the band's edge ragged (_FoamTexStrength erodes it where the texture is dark, so it only changes the shape of the edge; the band's position and size stay with _FoamOffset/_FoamHeight) and becomes the froth on the top surface. Scrollable.
BubblesR Replaces the procedural bubble noise. Rise speed, scale and cutoff still apply.
Emission MaskR Where the potion glows - veins, blobs, a rune - instead of the whole volume.

Mapping. By default the shader projects textures in object space (a diagonal XZ → U, Y → V projection): seam free, needs no UV unwrap, works on any bottle, and moves with the object instead of swimming through world space. _ProjectionScale sets the base density, then each slot has its own tiling and offset. Tick Use Mesh UVs if you have authored UVs you would rather use. The top surface always uses a flat top-down projection, since a surface is planar by definition.

07Components

Menu: Component ▸ Serwus Studio ▸ Cursed Cozy Alchemist ▸ …

CCAlc Potion Filler

Per-instance potion via MaterialPropertyBlock - no extra materials.

With Slosh off there is no per-frame cost at all: the component only writes when something changes, because the shader derives the fill plane from the transform itself. With Slosh on it writes one vector per frame.

CCAlc Vapor Emitter

Configures the attached ParticleSystem from a preset - Steam, Smoke or MagicHaze - so you get rising vapor without hand-tuning modules. Assign a soft puff particle material, pick a preset, tweak Density / Speed / Tint, then right-click → Apply Preset. Place it over a bottle neck or cauldron mouth.

Scripting API

// zmiana mikstury z palety (0..5)
var filler = potion.GetComponentInChildren<CCAlcPotionFiller>();
filler.SetPotion(2);

// smooth bottle emptying
filler.FillLevel = Mathf.Lerp(filler.FillLevel, 0f, Time.deltaTime);

08Property reference

PropertyMeaning
_FillSpaceLocalFill Space switch - 0 = World, 1 = Local (keyword _FILLSPACE_LOCAL)
_FillLevel0..1 fill inside the range below
_FillMinOS / _FillMaxOSobject-space Y range the level maps into (written by the Potion Filler)
_DepthRangeover how many metres the liquid darkens toward _DeepColor
_LiquidColor / _DeepColor / _SurfaceColorbody, depth and near-surface colours
_SurfaceBandsoft band of _SurfaceColor just under the fill level
_FoamColor / _FoamHeight / _FoamOffset / _FoamSoftnessthe foam band placed along Y - colour, how tall, how far below the surface its top edge sits, how soft both edges are
_SurfaceDepthTinthow much of the volume shows through the top surface
_SurfaceRipple / _SurfaceRippleSpeedhow much froth floats on the top surface, and how fast it drifts
_SurfaceTilingtiling of the procedural ripple used when there is no foam texture
_WaveHeight / _WaveScale / _WaveSpeedripples on the surface and on the cut edge
_SloshWaveBoosthow much the filler's agitation swells those ripples
_SloshTilthidden - written per instance by the Potion Filler (xy = tilt, z = agitation)
_UseMeshUV / _ProjectionScaletexture mapping (keyword _UV_MESH)
_LiquidTex / _FoamTex / _BubbleTex / _EmissionMasktexture slots, each with its own strength, scroll and tiling
_BubbleColor / _BubbleAmount / _BubbleScale / _BubbleSpeed / _BubbleCutoffrising bubbles, added unlit like a highlight
_EmissionColor / _EmissionStrength / _PulseAmount / _PulseSpeedHDR glow and its pulse
_ShadowTint / _ShadeThreshold / _ShadeSoftnessseries toon shading

09Troubleshooting

SymptomCause
Liquid vanishes or sits at the wrong height when the bottle moves Batching Static is on. Static batching bakes the transform away and the fill plane loses its reference. The inspector warns and offers a one-click fix.
You can see straight through the bottle from above The liquid mesh isn't closed - there are no back faces to stand in for the top surface.
Foam is invisible Foam Color and Surface Color are too similar. They sit in the same place; pull them apart.
Foam is a flat slab, not a soft band Foam Edge Softness is low. 1 = one long gradient, 0.02 = a crisp stripe.
Foam looks inverted between the top and the walls It shouldn't - white = foam on both. Check the texture isn't inverted, and that it is imported as Non-Color (sRGB off) so the greys read as data.
Top froth is much fainter than the band Raise Top Foam Amount until they match at the rim.
Nothing sloshes Fill Space is Local (the surface is welded to the mesh there, so there's nothing to tilt), or Slosh is unchecked.
Surface tilts when you only want it level That's World fill space doing its job. Switch to Local for corked vials and physics props that tumble.
Everything is magenta The project isn't running URP, or the URP asset isn't assigned in Graphics settings.

Cross-pack backbone

Same series toon look as Alley and Vegetation - half-Lambert stepped main light, tinted shadow, soft additional lights - so lanterns read cosy on glass and brews. The shared includes CCAlc_Lighting.hlsl and CCAlc_Noise.hlsl must ship with the package.

Support: dustin.smela@gmail.com - report issues with your Unity version, render pipeline and a screenshot of the material inspector.