Skip to main content

Face Tracking: Face Studio Mini

A bare-face baseline plus four progressively-glammed looks: Glam Lashes, Lip Statement, Full Glam, and Party Time. Each look is a binary pattern over four face-tracking SceneObjects — Eye Color, Lip Effect, Eyelash Effect, and a hat parented under the Head Tracker — toggled at runtime via SceneObject.setEnabledInHierarchy(...). No textures are swapped; the entire cycle is visibility flipping.

Face Studio Mini demo running in Effect House preview

What you'll build

  • An Eye Color SceneObject with a MeshRenderer + EyeColor component — auto-tracked iris-colored overlay.
  • A Lip Effect SceneObject — a preconfigured FaceMask that targets the lip region. Drop a custom lipstick texture on its Material to change the look.
  • An Eyelash Effect SceneObject — another FaceMask, this one targeting the eyelash region.
  • A Head Tracker hierarchy — a Face Binding parent with a FaceBindingV2JS component anchored to FaceCenter, plus a Head child carrying the head occluder mesh.
  • A PartyHat sphere parented under the Head Tracker's Head. Because of the parent-child relationship, the hat follows the user's head pose every frame for free.
  • A TitleText and StatusText 2D HUD that mirrors the active look.
  • A GameController empty SceneObject hosting FaceStudio — the script that subscribes to global EventType.Touch and toggles the right SceneObjects per look.

Open the demo

↓ face-studio-mini.zip

Unzip and open in Effect House (5.9.0+). The opening scene contains:

  • Face Mask Camera — auto-created by the first face-tracking object; routes the AR overlays to the correct render group.
  • Face Binding / Head — the Head Tracker two-level hierarchy. Face Binding carries the FaceBindingV2JS anchor; Head carries the occluder mesh. Anything you parent under Head (the hat) tracks head pose automatically.
  • Eye Color / Lip Effect / Eyelash Effect — the three face decoration SceneObjects, each a built-in object with its own tracking subsystem.
  • PartyHat — a Sphere with localScale = (0.4, 0.6, 0.4) positioned at localPosition = (0, 14, 0) relative to the Head occluder, riding above the user's head.
  • TitleText + StatusText — the 2D HUD.
  • GameController — empty SceneObject hosting FaceStudio.

Read the script

FaceStudio.ts

@component()
export class FaceStudio extends APJS.BasicScriptComponent {
// Each face decoration is a SceneObject — toggling its enabled-in-hierarchy
// flag is the cleanest way to swap face looks at runtime. We avoid touching
// individual EyeColor / FaceMask components because the SceneObject toggle
// ALSO disables the underlying tracking work, saving GPU.
@serializeProperty eyeColor!: APJS.SceneObject;
@serializeProperty lipEffect!: APJS.SceneObject;
@serializeProperty eyelashEffect!: APJS.SceneObject;
@serializeProperty partyHat!: APJS.SceneObject;

// The status label that names the active look.
@serializeProperty statusText!: APJS.SceneObject;

private statusComp!: APJS.Text;
private touchCallback!: (e: APJS.IEvent) => void;
private cycleIndex: number = -1;
private inited: boolean = false;

// Five looks. Bit-packed across the four decoration flags.
// 0 — Natural: nothing on. Useful as a clean before-and-after baseline.
// 1 — Glam Lashes: eyelashes only — the subtle change.
// 2 — Lip Statement: lipstick only.
// 3 — Full Glam: eye color + lashes + lips. The classic "makeup look".
// 4 — Party Time: full glam + a hat parented under the Head tracker.
private static readonly LOOKS: { name: string; eye: boolean; lip: boolean; lash: boolean; hat: boolean }[] = [
{ name: "Natural", eye: false, lip: false, lash: false, hat: false },
{ name: "Glam Lashes", eye: false, lip: false, lash: true, hat: false },
{ name: "Lip Statement", eye: false, lip: true, lash: false, hat: false },
{ name: "Full Glam", eye: true, lip: true, lash: true, hat: false },
{ name: "Party Time", eye: true, lip: true, lash: true, hat: true },
];

onUpdate(_dt: number): void {
if (this.inited) return;
if (!this.eyeColor || !this.lipEffect || !this.eyelashEffect || !this.partyHat) return;
if (!this.statusText) return;

this.statusComp = this.statusText.getComponent("Text") as APJS.Text;

this.touchCallback = (event: APJS.IEvent) => {
const t = event.args[0] as APJS.TouchData;
if (t.phase !== APJS.TouchPhase.Began) return;
this.advance();
};
APJS.EventManager.getGlobalEmitter()
.on(APJS.EventType.Touch, this.touchCallback, this);

this.advance(); // start at index 0 (Natural)
this.inited = true;
console.log("[FaceStudio] ready — " + FaceStudio.LOOKS.length + " looks wired");
}

onDestroy(): void {
if (this.touchCallback) {
APJS.EventManager.getGlobalEmitter()
.off(APJS.EventType.Touch, this.touchCallback, this);
}
}

private advance(): void {
this.cycleIndex = (this.cycleIndex + 1) % FaceStudio.LOOKS.length;
const look = FaceStudio.LOOKS[this.cycleIndex];

// setEnabledInHierarchy(false) disables the SceneObject AND every component
// it carries — the EyeColor / FaceMask tracking subsystems stop processing
// entirely. Flipping back to true re-engages them without setup cost.
this.eyeColor.setEnabledInHierarchy(look.eye);
this.lipEffect.setEnabledInHierarchy(look.lip);
this.eyelashEffect.setEnabledInHierarchy(look.lash);
this.partyHat.setEnabledInHierarchy(look.hat);

this.statusComp.text = (this.cycleIndex + 1) + " of " + FaceStudio.LOOKS.length + "\n" + look.name;
console.log("[FaceStudio] look " + look.name);
}
}

The Face-Tracking namespace calls of interest:

  • Eye Color built-in object — auto-creates a MeshRenderer + EyeColor component. Self-contained: includes its own iris tracking, no Face Segmentation parent needed. Drop a custom texture or color on the EyeColor component to change the look.
  • Lip Effect / Eyelash Effect built-in objects — each preconfigures a FaceMask component targeting the named region. Both auto-track facial landmarks; replace the texture on the MeshRenderer material to change the makeup look.
  • Face Mask is the underlying component (also surfaced as a generic built-in object) — it supports a region enum (Whole, Eyes, Eyelashes, Lips, Teeth) for arbitrary face overlays.
  • Head Tracker built-in object — creates a two-level hierarchy: Face Binding (parent, with FaceBindingV2JS anchored to FaceCenter) → Head (child, with the head occluder mesh). The Head bounds are roughly 18 × 29 × 21 world units. To make AR accessories follow the head, parent them under Head — they automatically inherit head pose.
  • SceneObject.setEnabledInHierarchy(true|false) — the runtime toggle for whole face-decoration objects. It's the cheapest way to swap looks because it disables the underlying tracking work as well, not just the visible mesh.
  • FaceBinding / FaceBindingV2 — the binding component that anchors a SceneObject to a face landmark (FaceBindingAnchorType). Used internally by Head Tracker; expose it directly to attach 3D objects to specific landmarks like LeftEye, Nose, or Lips (see FaceBindingAnchorType).

Look table

IndexNameEye ColorLip EffectEyelashParty Hat
0Naturaloffoffoffoff
1Glam Lashesoffoffonoff
2Lip Statementoffonoffoff
3Full Glamonononoff
4Party Timeonononon

Each row is the same physical scene — only four setEnabledInHierarchy calls differ.

Customize

On GameControllerFaceStudio:

  • eyeColor / lipEffect / eyelashEffect — swap the wired SceneObjects. Any face-bound SceneObject works (a 3D Head Binding Mesh, a custom Face Mask with a specific region, etc.).
  • partyHat — drop in any 3D object parented under the Head Tracker's Head child. The script doesn't care what it is — it just toggles enabled state.
  • LOOKS — a static readonly array at the top of the script. Add rows like a "Subtle Glow" with only the eye color on, or a "Just the Hat" with all face decorations off.

In the editor:

  • Eye Color Material — change the iris color or plug in a decorative iris texture (see the EyeColor reference for property details).
  • Lip Effect / Eyelash Effect Material — replace the texture for a different lipstick / mascara look. The region field on FaceMask controls which part of the face the texture maps to.
  • PartyHat TransformlocalPosition.y = 14 rides above the head occluder. Drop to 9 to perch a crown on the brow, push to 20 for a tall novelty hat.
  • PartyHat Material — swap to any Standard PBR look. The demo uses a vivid orange emissive so the hat reads against any background.

Suggestions for further play:

  • Add a DyeHair SceneObject and toggle it as part of the cycle for a hair-color preset. Combine with DyeHairMode to choose between full-color replacement and tint blending.
  • Use FaceMakeup for a region-specific cosmetic layer (blush, contour) without using the generic FaceMask. The FaceMakeup component understands the cosmetic blending modes natively.
  • Bind a 3D accessory to a specific landmark via FaceBinding + FaceBindingAnchorType instead of parenting under Head Tracker — useful for sunglasses anchored to the bridge of the nose, or earrings on individual ear landmarks.
  • Read Face106Interface to drive your own logic from raw face landmarks (the 106-point face mesh used internally by FaceMask and the makeup components).

What you learned

This tutorial used:

  • Built-in face-tracking objectsEye Color, Lip Effect, Eyelash Effect, Head Tracker. Each is a one-shot add_builtin_object that brings the right components + hierarchy.
  • SceneObject.setEnabledInHierarchy(true|false) — the runtime toggle. It disables the SceneObject and its tracking work, not just the visible mesh, so cycling looks doesn't waste GPU on hidden trackers.
  • The Head Tracker hierarchyFace Binding parent + Head child + your accessory parented under Head. The accessory follows head pose for free.
  • @serializeProperty SceneObject × 5 — wired in the inspector, mapped to runtime references in the lazy-init onUpdate.
  • The look-table state pattern — a static readonly LOOKS[] array indexed by cycleIndex, advanced on global EventType.Touch. The same shape applies to lighting cycles, filter stacks, and material showrooms in the other tutorials.

Read the full EyeColor reference, the FaceMakeup reference, the FaceBinding reference, the FaceBindingAnchorType reference, the DyeHair reference, the Eyelashes3D reference, the Face106Interface reference, and the Face-Tracking namespace overview.

For the touch-event subscription pattern used here, see the Events & Input tutorial. For swapping the look cycle for a per-tap gesture variant, see GestureType.

Copyright © 2026 TikTok. All rights reserved.
About TikTokHelp CenterCareersContactLegalTerms of ServicePrivacy PolicyCookies