Changelog
Version 1.7.0
Added ... Multilingual Text Lip Sync
TextLipSyncLanguageenum. New language selector for text-to-viseme conversion:Auto,English,Russian,Chinese,Japanese,Korean. Can be set in the Inspector or changed at runtime viaCrystalTextLipSync.Language.Autodetection.CrystalTextToViseme.DetectLanguage()scans the input text for Unicode script ranges (Cyrillic → Russian, Kana → Japanese, Hangul → Korean, CJK Ideographs → Chinese, Latin → English). Kana presence alongside CJK is recognised as Japanese.Russian (Cyrillic) mapping. All 33 Russian letters mapped to visemes by phonetic category: vowels (а/е/ё/и/о/у/ы/э/ю/я), bilabials (п/б/м → PP), labiodentals (ф/в → FF), alveolars (д/т/л → DD), nasals (н → NN), velars (к/г/х → KK), fricatives (с/з/ц → SS), postalveolars (ш/щ/ч/ж → CH), trill (р → RR), semi-vowel (й → I), and hard/soft signs (ъ/ь → SIL).
Japanese mapping. 86-entry lookup table maps every hiragana (U+3041...U+3096) to its vowel sound (a/i/u/e/o/n). Katakana uses the same table offset by 0x60. CJK kanji distributed deterministically across five vowel visemes for visual variety.
Korean mapping. Hangul syllable blocks (U+AC00...U+D7A3) decomposed into Jamo; the medial vowel index (21 entries) determines the viseme. Compatibility Jamo vowels (U+314F...U+3163) also supported.
Chinese mapping. CJK Ideographs (U+4E00...U+9FFF, U+3400...U+4DBF) distributed across vowel visemes. Bopomofo (Zhuyin) phonetic symbols (U+3100...U+312F) mapped to consonant and vowel visemes.
CJK punctuation as silence.
IsSilenceChar()recognises CJK Symbols & Punctuation (U+3000...U+303F) and fullwidth punctuation (U+FF00 block) as silence across all languages.English label clarified.
TextLipSyncLanguage.Englishexplicitly documented as working for all Latin-alphabet languages (German, French, Spanish, Italian, Portuguese, etc.).Backward-compatible API. The original
Generate(text, charsPerSecond)overload is preserved and delegates to the newGenerate(text, language, charsPerSecond)withAuto.
Added ... Setup Wizard: Eye Movement Toggle & Text Language Dropdown
Enable Eye Movement sub-toggle. When "Add Eye Blink & Movement" is checked, an indented "Enable Eye Movement" toggle appears (default: on). Sets
CrystalEyeBlink.EnableEyeMovementon the created component, allowing users to add blinking without gaze movement.Text Language dropdown. When "Add Text Lip Sync" is checked, an indented "Text Language" dropdown appears (default: Auto). Tooltip explains each language option and clarifies that English covers all Latin-script languages. The selected language is applied to the component and logged in the setup summary.
Added ... GC2 Crystal Eye Look At Bridge
RigCrystalEyeLookAtIK rig. New GC2 IK rig that bridges Game Creator 2's Look At IK system with CrystalEyeBlink's look target. When the character'sRigLookTohas an active target, CrystalEyeBlink's eyes automatically track the same point. When the look target is cleared (via "Stop Looking At"), CrystalEyeBlink returns to idle eye movement. Add it to the character's IK rig list after "Look at Targets".
Fixed ... DAZ Genesis 9 Blink Blendshape Auto-Detection
"Crescent" blink variants incorrectly preferred over standard blinks. On DAZ Genesis 9 models,
facs_ctrl_EyeBlinkCrescentLeft/Right(squint-style partial blinks) were auto-mapped instead of the correctfacs_bs_EyeBlinkLeft/Right(full eye closures). Both names contain "eye" + "blink" and scored identically at 500 pts; the crescent variants won simply by appearing at lower blendshape indices.−300 score penalty for "crescent" token.
ClassifyBlinkBlendshape()now detects thecrescenttoken and reduces the match score by 300 (floored at 1), so standardEyeBlink*shapes (500 pts) always outrankEyeBlinkCrescent*shapes (200 pts).
Fixed ... DAZ Genesis 9 Brow Sync Side Detection
HD2 suffix blocked left/right detection. Genesis 9 FACS HD brow blendshapes (e.g.
facs_bs_BrowOuterUpRight_HD2) end withhd2after canonicalization, soDetectBrowSide()could not matchEndsWith("right")orEndsWith("left"). All brow shapes were classified as "Both" instead of per-eye Left/Right.Suffix stripping in
DetectBrowSide(). The method now strips trailinghd2andhdsuffixes before checking for side indicators, correctly mappingBrowOuterUpRight_HD2→ Right andBrowSqueezeLeft_HD2→ Left.Genesis 9 brow controller preference. Auto-detection now runs a Genesis 9-specific brow preference pass on
targetMesh: it prioritizes per-eyebrow up/downcontroller blendshapes when present (bipolar morphs using signed weights), falls back to per-eye raise/lower candidates, and avoids usingBrow Raise (Both)/Brow Lower (Both)whenever valid Left/Right mappings exist.
Added ... Multi-Mesh Look Blendshape Support (DAZ Genesis 9)
Genesis 9 switched to bone-only eye movement. For DAZ Genesis 9, auto-detection now enables
Force Bone Rotation, clearsLook Mesh, and clears all eye movement look blendshape indices (Left/Right/Both) so Eye Movement Blendshape Mapping staysNone.Applied consistently across entry points. The same Genesis 9 bone-only behavior now runs when the component is added, when Setup Wizard configures Eye Blink, and when
Detect All/Detect Eye Movementare used.Per-eye bone fallback in hybrid mode. Eye movement now falls back to bone rotation per eye and per axis when look blendshapes are unmapped (
None). If one eye/axis has a blendshape and the other does not, blendshape drives the mapped side while bones drive the unmapped side.Fallback Phase 2 scan. For non-Genesis 9 models, the previous two-phase approach is preserved: Phase 1 scans
targetMeshfor both look and brow shapes; Phase 2 scans all other meshes if no look blendshapes were found.Editor inspector support. The Look Blendshapes section shows the
Look Meshfield with its own blendshape name dropdowns. Brow Sync dropdowns always reference the body mesh. Detection dialogs report whether look blendshapes were found on a separate mesh.
Added ... Genesis 9 Unity Import Guide (GitBook Markdown)
New troubleshooting/setup guide. Added a dedicated Genesis 9 guide documenting known DAZ3D→Unity import/export issues and recommended Crystal LipSync setup.
Eye movement recommendation. For Genesis 9, the guide recommends bone-driven eye movement (
Force Bone Rotationon) withLook Mesh = Noneand all Eye Movement look blendshape slots left atNone.Blendshape profile recommendation. For Genesis 9 lip blendshape profiles, the guide recommends reducing vowel viseme strengths (
A,E,I,O,U) to about0.3instead of1.0.Jaw recommendation. Guide recommends using a conservative jaw rotation limit around
4°for jaw-bone-driven mouth motion on Genesis 9 imports.
Fixed ... DAZ Genesis 8.1 FACS Viseme Auto-Mapping
Missing FACS viseme codes. Added
"sh"to the CH viseme codes and"iy"to the I viseme codes. DAZ Genesis 8.1 FACS blendshapesfacs_ctrl_vSHandfacs_ctrl_vIYwere not recognised, causing CH and I to fall back to incorrect blendshapes.Dedicated FACS viseme controls outscored by incidental matches. Added a +300 scoring bonus when blendshape tokens contain the DAZ FACS pattern
ctrl v <code>. Without this, generic blendshapes likeSmileOpenFullFace(matching codeopenfor AA at 500 pts) andfacs_bs_TongueOut(matching codetongue_outfor TH at 500 pts) would tie or beat the correctfacs_ctrl_vAA/facs_ctrl_vTHblendshapes. The bonus ensures dedicated viseme controls always win.PBMStomachDepthfalse-positive on CH. The substringchinside "stomachdepth" was matching at Tier 6 (50 pts), incorrectly mapping CH to a body morph. Now thatfacs_ctrl_vSHis recognised and boosted, it correctly claims the CH slot.Affected visemes and corrections:
PP:
facs_ctrl_MouthPress→facs_ctrl_vM(FACS bilabial)TH:
facs_bs_TongueOut→facs_ctrl_vTH(FACS dental)DD:
facs_jnt_TongueUp→facs_ctrl_vT(FACS alveolar)CH:
PBMStomachDepth→facs_ctrl_vSH(FACS postalveolar)AA:
SmileOpenFullFace→facs_ctrl_vAA(FACS open vowel)I:
SFD_Gen8F_TC_TipWide→facs_ctrl_vIY(FACS close front vowel)
Changed ... DAZ Genesis 9 Jaw Bone Setup
Genesis 9 jaw bone max angle set to 8° in Combined mode. Genesis 9 blendshapes have lighter jaw-opening deltas than older Genesis models (2/3/8/8.1), so the jaw bone needs a larger rotation range for natural mouth movement. Previously Genesis 9 fell into the generic DAZ branch at 4°.
CharacterModelType.DAZGenesis9enum value. The Setup Wizard now distinguishes Genesis 9 from older Genesis models.DetectModelType()checks for"genesis9"in mesh/GameObject names before the generic"genesis"pattern.All DAZ-specific wizard behaviors extended to Genesis 9. SIL mapping auto-clear, separate L/R blink (clearing "Both"), eyelash blink synchronisation, and jaw bone axis/direction configuration now apply to both
DAZGenesisandDAZGenesis9.
Fixed ... DAZ Genesis 9 Look Blendshape Auto-Detection
Genesis 9 FACS look blendshapes not detected. The
CrystalEyeBlinkauto-detection could not find look blendshapes on DAZ Genesis 9 models. After mesh-prefix stripping and canonicalization, names likefacs_bs_EyeLookUpRightbecomefacsbseyelookupright... thefacsbs/facsctrl/baseanimefacscbsFACS prefixes remained in the string, preventing any existing matcher (ARKit, DAZ eCTRL, VRM, generic) from recognising them.TryMapGenesis9Look()method. New matcher that strips Genesis 9 FACS prefixes (facsbs,facsctrl,facscbs,baseanimefacscbs) to extract the core ARKit-like pattern, then matches:Per-eye directional:
EyeLookUp,EyeLookDown,EyeLookIn,EyeLookOut+ Left/Right (8 blendshapes). Uses ARKit In/Out conventions (In = toward nose, Out = away).Bipolar up/down:
EyeLookUp-Down,EyeLookUp-DownLeft,EyeLookUp-DownRight... mapped conditionally (only fills slots still at -1) to avoid overwriting superior per-axis direct mappings.Bipolar side-to-side:
EyeLookSide-Side,EyeLookSide-SideLeft,EyeLookSide-SideRight... same conditional logic.
BaseAnime_facs_cbs_variants also detected. Thebaseanimefacscbsprefix is stripped first (longest match), covering Genesis 9 BaseAnime facial blendshapes.facs_ctrl_preferred overfacs_bs_. Sincefacs_ctrl_blendshapes appear afterfacs_bs_in the mesh and direct matches overwrite unconditionally, the controller-level blendshape (higher quality) automatically wins for per-eye slots."squeeze"added toBrowLowerTokens. Genesis 9facs_bs_BrowSqueezeLeft/Rightblendshapes (brow compression) are now detected as brow-lower candidates."facsctrl","facscbs","baseanimefacscbs"added toBrowStripPrefixes. Ensures brow core-name analysis strips Genesis 9 FACS prefixes cleanly before checking raise/lower/side tokens.
Added ... Genesis 9 FACS Mouth Supplement Mappings
Supplementary FACS mouth blendshapes for Genesis 9. When a Genesis 9 model has optional FACS mouth blendshapes (
facs_ctrl_MouthClose,facs_ctrl_MouthPucker,facs_ctrl_MouthStretch,facs_ctrl_MouthFunnel,facs_bs_TongueOut,facs_ctrl_TongueUp, etc.), they are now automatically added as additional entries on the viseme mappings after the primaryfacs_ctrl_v*auto-map. These supplement ... not replace ... the existing viseme shapes, enhancing mouth articulation quality.HasGenesis9FacsMouthShapes()detection. Returns true if ≥3 of the 6 signature FACS mouth names (mouthclose,mouthfunnel,mouthpucker,mouthstretch,tongueout,tongueup) are found on the mesh.Genesis9FacsSupplementMappings[15][]preset table. Defines per-viseme supplementary entries with conservative weights:PP: MouthClose 80% + MouthCompress 40%
FF: MouthFunnel 50%
TH: TongueOut 40%
DD: TongueUp 30% + MouthStretch 15%
CH: MouthFunnel 50% + MouthPurse 30%
SS: MouthStretch 40%
NN: MouthClose 40%
RR: MouthPucker 25%
AA: MouthFunnel 15%
E: MouthStretch 50%
I: MouthStretch 30%
O: MouthPucker 60%
U: MouthPucker 70%
ApplyGenesis9FacsSupplement(). Builds a core-name → index lookup by stripping both mesh prefix and FACS prefix, then adds matching entries viaAddMapping(). Returns the number of entries added. Prefersfacs_ctrl_overfacs_bs_variants (higher quality controller-level blendshapes).Setup Wizard integration. After the standard auto-map for non-ARKit models, the wizard checks
HasGenesis9FacsMouthShapes()and applies the supplement. Log shows the count of added entries.Editor Auto-Map integration. The "Auto-Map" button in the BlendshapeTarget inspector also applies the supplement when Genesis 9 FACS mouth shapes are detected. The results dialog shows the supplement count.
Version 1.6.0
Added ... Yarn Spinner Integration
CrystalLipSync.YarnSpinnerassembly. New optional assembly (CrystalLipSync.YarnSpinner.asmdef) referencingCrystalLipSync.RuntimeandYarnSpinner.Unity.CrystalYarnLipSynccomponent. Per-character bridge between Yarn Spinner and CrystalLipSync. Static registry maps Yarn character names to components. Auto-provisioning creates instances on-demand if a character GameObject is found by name.CrystalYarnTextPresentercomponent.DialoguePresenterBasethat feeds dialogue text toCrystalTextLipSyncfor text-driven lip sync during Yarn dialogue. Supports per-character targeting viaCrystalYarnLipSyncregistry.CrystalYarnVoiceOverPresentercomponent.DialoguePresenterBasereplacing Yarn Spinner'sVoiceOverPresenter. Routes voice-over audio clips to the character'sAudioSourcefor audio-driven lip sync. Handles missing clips and missing AudioSource gracefully without prematurely advancing dialogue.Demo Yarn scripts.
CrystalLipSyncDemo.yarn(text-driven demo) andCrystalVoiceOverDemo.yarn(audio-driven demo) with setup instructions.Audio folder with README. Instructions for naming voice-over clips to match Yarn Spinner's localisation system.
.yarnprojectlocalisation config. Addeden.assets = "../Audio"toCrystalVoiceOverDemoProject.yarnprojectso Yarn Spinner locates audio clips correctly.
Fixed ... Yarn Spinner Integration
Dialogue flashing / premature line advance.
CrystalYarnVoiceOverPresenterno longer callsRequestNextLine()when no audio clip is present or when the character has no AudioSource. Previously this cancelled all active presenters immediately, causing dialogue to flash and disappear.VisemeTypecast error (CS1503). FixedSetMapping(LipSyncMood.Neutral, v, mapping[v])→SetMapping(LipSyncMood.Neutral, (VisemeType)v, mapping[v])inCrystalYarnLipSync.
Added ... Natural Eye Movement System
Eye movement merged into
CrystalEyeBlink. The existing blink component now also drives natural idle eye movement, providing lifelike gaze behaviour in all situations (dialogue, idle, gameplay) without requiring a separate component.Saccade system. Large eye movements every 2...5 seconds with Gaussian center-biased random targets. Configurable amplitude (default 15°) and speed (default 400°/s).
Micro-saccade system. Small involuntary eye movements (~0.5°) every ~600ms, adding subtle liveliness between saccades.
Slow drift. Continuous low-amplitude drift (default 1°) simulating natural fixation instability.
Vestibulo-ocular reflex (VOR). Counter-rotates eyes when the head moves, keeping gaze stable during head motion. Tracks head bone rotation delta per frame and applies inverse offset to eye gaze. Decay rate returns eyes to center when head stops moving.
Blendshape-driven gaze. Applies gaze offsets to up/down/left/right look blendshapes (per-eye or combined). Preferred method when look blendshapes are detected.
Bone rotation fallback. When no look blendshapes are found but eye bones exist, rotates eye bones directly (up to configurable
maxBoneAngle, default 20°). Captures baseline rotations on init to avoid overriding existing animations.Auto-detect look blendshapes.
AutoDetectLookBlendshapes(root)scans all meshes for look direction blendshapes. Supports ARKit (eyeLookUpLeft,eyeLookInLeft, etc.), DAZ (eCTRLEyesUpDownL,eCTRLEyesSideSideR), VRM (Fcl_EYE_LookUp, etc.), and generic patterns (eyeLookUp,eyesLookLeft).Auto-detect bones.
AutoDetectBones()finds head and eye bones viaAnimator.GetBoneTransform(HumanBodyBones)with name-based fallback for non-humanoid rigs (CC_Base_Head, lEye, rEye, mixamorig:Head, etc.).12 look blendshape indices. Per-eye (LookUpLeft, LookUpRight, LookDownLeft, LookDownRight, LookLeftLeft, LookLeftRight, LookRightLeft, LookRightRight) and combined (LookUpBoth, LookDownBoth, LookLeftBoth, LookRightBoth) for maximum model compatibility.
RedetectEyeMovement()public API. Re-runs bone and look blendshape detection at runtime.
Changed ... Eye Movement
CrystalEyeBlinkEditorupdated. Inspector now uses foldout sections: "Eye Blink" and "Eye Movement". Eye movement section shows all movement parameters, bone fields, per-eye and combined look blendshape dropdowns, and "Detect Eye Movement" / "Detect All" buttons.Setup Wizard updated for eye movement. The wizard now calls
AutoDetectBones()andAutoDetectLookBlendshapes()after creating theCrystalEyeBlinkcomponent. Log messages report detected look blendshape count and bone fallback status. Toggle label changed to "Add Eye Blink & Movement" with updated tooltip.Welcome text updated. Wizard description now mentions "natural blinking + eye movement".
Added ... Synkinetic Brow Movement
Brow sync system. Eyebrows now subtly follow vertical gaze direction ... looking up raises the brows, looking down lowers them. This mimics the natural synkinetic reflex between eye and brow muscles.
browSyncIntensitysetting. Controls how strongly brows follow gaze (0...1, default 0.25). Brow raise is applied at full intensity, brow lowering at 50% for a natural asymmetric response.6 brow blendshape indices.
browRaiseLeft/Right/BothandbrowLowerLeft/Right/Both, supporting per-eye and combined brow blendshapes.Auto-detection of brow blendshapes. Scans for brow raise/lower blendshapes across ARKit (
browOuterUpLeft/Right,browInnerUp,browDownLeft/Right), DAZ Genesis (eCTRLBrowRaiseInner/Outer,eCTRLBrowDown), VRM (Fcl_BRW_Raise/Down), iClone CC (Brow_Raise_Inner_L/R,Brow_Drop_L/R), and generic patterns.Brow sync section in CrystalEyeBlinkEditor. Inspector shows brow sync intensity slider and per-eye + combined brow raise/lower blendshape dropdowns under the Eye Movement foldout.
Wizard and detection dialogs updated. Setup wizard log and Detect All / Detect Eye Movement dialogs now report brow blendshape detection results.
Notes ... Eye Movement
Eye movement is enabled by default when look blendshapes or eye bones are detected. Disable via the
enableEyeMovementtoggle in the inspector.Saccade targets use a Gaussian distribution biased toward the center of the eye's range, producing natural-looking gaze patterns rather than uniform random movements.
VOR requires a head bone to be assigned. Without it, VOR is silently skipped.
The system works across all model types: ARKit (per-eye look blendshapes), iClone CC (per-eye), DAZ Genesis (combined up/down + side-to-side), VRM (combined), and generic humanoid rigs (bone rotation fallback).
Added ... Look Target Tracking
Optional look-at target. Assign a
TransformtolookTargetand the eyes will track it in world space. Leave it empty for pure natural idle gaze.Runtime-switchable target.
LookTargetproperty can be set or cleared at any time. Gaze smoothly transitions when the target changes or is removed.Harmonized with natural movement. At full
lookTargetWeight(1.0), 15% of the natural eye movement (micro-saccades, drift) is preserved as subtle perturbation, keeping the gaze alive rather than mechanically locked on.lookTargetWeight(0...1). Blends between natural idle gaze and target tracking. At 0 the target is ignored; at 1 the eyes lock on with micro-movement overlay.lookTargetSmoothing(1...30, default 8). Controls transition speed when the target moves. Higher values produce snappier tracking.lookTargetMaxAngle(5...60°, default 35°). Maximum angular deflection the eyes will follow. Targets outside this cone are clamped to the edge.WorldToGaze()helper. Converts a world-space position to normalised −1...1 gaze coordinates relative to the head bone (or eye midpoint / character root as fallback). UsesAtan2decomposition into yaw and pitch, normalised bylookTargetMaxAngle.Smooth decay on target removal. When the look target is cleared,
smoothedTargetGazedecays toward zero using the same smoothing rate, producing a natural return to idle gaze.
Changed ... Look Target
CrystalEyeBlinkEditorupdated. Inspector now shows a "Look Target" section inside the Eye Movement foldout with Transform field, weight slider, smoothing, and max angle. Info box updated to describe the feature.UpdateEyeMovement()pipeline extended. Natural gaze is now computed asnaturalGaze, then blended with the smoothed look target direction before being applied to blendshapes/bones.
Fixed ... Look Target
WorldToGaze()using wrong reference frame. Was usingheadBone.forward/right/upfor direction decomposition, but head bone local axes vary wildly between rigs (CC3 Z-forward, ARKit Y-forward, etc.), causing eyes to track the wrong direction (e.g. looking down-left instead of down-right). Fixed by caching agazeReferenceTransformfrom the Animator root transform inInitEyeMovement()and using its stable forward/right/up for yaw/pitch decomposition while keeping head bone position as the gaze origin.Enhanced debug logging. Debug output now shows
GazeReftransform name, all 12 blendshape indices, and look-target specifics (target position, head position, direction vectors, raw gaze).
Fixed ... DAZ Genesis 8 Brow Mapping
JCM corrective morphs hijacking brow slots. DAZ Genesis 8 exports include JCM (Joint Controlled Morph) correctives like
eJCMOwen8_eCTRLBrowUp-DownRthat contain brow keywords. These were being matched as brow blendshapes and stealing slots from the actual brow morphs. Fixed by filtering names containingejcmorjcminAutoDetectLookBlendshapes().Bipolar brow morphs not handled. DAZ
eCTRLBrowUp-Downis a single bipolar morph (positive = raise, negative = lower) but was only mapped to either raise or lower.TryMapBrow()now detects when the core name contains both "up" and "down", mapping the same index to both raise and lower slots.ApplyBrowSync()detects shared raise/lower indices and usesSetBipolarWeight()with a net signed value.
Added ... Multi-Blendshape Brow Support
BrowBlendEntryclass. New[Serializable]class (matching the visemeBlendEntrypattern) withblendshapeIndex(int, default −1) andweight(float, 0...200%, default 100%).6 extras arrays.
browRaiseLeftExtra,browRaiseRightExtra,browRaiseBothExtra,browLowerLeftExtra,browLowerRightExtra,browLowerBothExtra... each aBrowBlendEntry[]supporting additional brow blendshapes per slot.AssignBrowSlot()static method. Auto-detection routes overflow blendshapes to extras when the primary slot is already taken (with duplicate prevention). For example, DAZ G8's outer brow (eCTRLBrowUp-Down) maps to the primary slot and inner brow (eCTRLBrowInnerUp-Down) maps to extras automatically.ApplyBrowExtras()method. Drives all extra entries per brow slot with per-entry weight scaling duringApplyBrowSync().Editor "+" button for brow extras. Each of the 6 brow slots now shows a "+" button to manually add additional blendshape entries. Extra rows display a blendshape dropdown, weight field (0...200%), and "−" remove button ... matching the viseme multi-blendshape UI pattern.
CountBrowMappings()helper. Detect dialogs now include extras in their brow blendshape count.
Added ... Hybrid Blendshape/Bone Eye Movement
Automatic per-axis hybrid mode. The gaze system now independently checks horizontal and vertical blendshape coverage. If look blendshapes only cover one axis (e.g. DAZ
eCTRLEyesUpDownfor vertical only), the missing axis is automatically driven by bone rotation. No configuration required.HasVerticalLookBS()/HasHorizontalLookBS()helpers. Check whether any vertical (up/down) or horizontal (left/right) look blendshapes are mapped, respectively.HasAnyLookBlendshape()now delegates to these.ApplyGazeToBones()axis parameters. AcceptsapplyYawandapplyPitchbooleans so bone rotation can be applied selectively for unmapped axes only, without affecting the axis driven by blendshapes.Debug mode shows hybrid state. Debug logging now shows
Mode=Hybrid(H=Bone,V=BS)(or similar) when partial blendshape coverage is detected, alongsideBlendshapes,Bones, andForcedBones.
Notes
The hybrid mode is fully automatic ... no toggle or setting needed. If all look blendshapes are mapped, behaviour is identical to before (pure blendshape mode). If none are mapped, it falls back to pure bone mode. Partial coverage triggers hybrid.
Force Bone Rotationstill overrides everything to bones-only regardless of blendshape mappings.
Fixed ... Brow "Both" Slot Clearing
Combined brow morphs discarded when per-eye brow morphs exist. The over-driving prevention logic was clearing
browRaiseBothIndexandbrowLowerBothIndexentirely when per-eye brow slots were also populated. On DAZ Genesis 8, this causedeCTRLBrowInnerUp-Down(a combined both-eyes morph) to be silently dropped. Fixed by demoting the "Both" primary to an extra on each per-eye slot instead of discarding it. Affects Genesis 3, 8, and 9 (which all have both per-eye and combined brow morphs). Genesis 2 is unaffected (only combined morphs exist).
Changed ... Jaw Bone Defaults
iClone CC jaw bone max angle reduced from 10° to 8°. Provides more natural jaw movement in Combined mode. DAZ Genesis remains at 4°, ARKit/FACS at 6°.
Changed ... Setup Wizard & GC2 Integration
Setup Wizard no longer blocks Eye Blink on Game Creator 2 characters. The wizard previously detected
GameCreator.Runtime.Characters.Characterand disabled the "Add Eye Blink & Movement" toggle, assuming GC2's built-in IK eye blink would conflict. This restriction has been removed ...CrystalEyeBlinknow offers significantly more features (eye movement, brow sync, look targets, hybrid blendshape/bone mode) and should be used instead.RigCrystalEyeBlinkmarked[Obsolete]. Users should use the Setup Wizard to add theCrystalEyeBlinkcomponent directly, which provides eye movement, brow sync, look target tracking, and hybrid blendshape/bone support that the GC2 IK rig does not.RigCrystalLipSyncmarked[Obsolete]. Users should use the Setup Wizard (Tools → Crystal LipSync → Setup Wizard) for better cross-model compatibility, auto-detection, and feature coverage.
Version 1.5.0
Added
Multi-blendshape per viseme architecture. Each
VisemeBlendshapeMappingnow supports a primary blendshape plus an array ofBlendEntryadditional entries, each with an independent weight.LateUpdateiterates all entries per viseme. Fully backward-compatible ... existing single-blendshape mappings continue to work unchanged.BlendEntryclass. New serializable class holdingblendshapeIndex+weightfor additional blend contributions beyond the primary slot.AddMapping()/ClearMapping()API onBlendshapeTarget.AddMappingfills the primary slot first, then appends toadditionalEntries.ClearMappingresets both primary and additional entries for a given viseme.ARKit / FACS model detection.
IsARKitModel(smr)checks for ≥6 ARKit signature blendshape names (jawOpen, mouthFunnel, mouthPucker, mouthSmileLeft/Right, mouthClose, mouthStretchLeft/Right, etc.).ARKit preset combination mappings.
ARKitPresetMappings[15][]provides tuned multi-blendshape combinations for all 15 Oculus visemes using anatomical ARKit blendshapes:SIL: (none)
PP: mouthClose 100% + mouthPressLeft/Right 60%
FF: mouthFunnel 80% + mouthLowerDownLeft/Right 40%
TH: jawOpen 25% + mouthFunnel 30%
DD: jawOpen 20% + mouthStretchLeft/Right 20%
KK: jawOpen 30%
CH: mouthFunnel 70% + mouthShrugUpper/Lower 40%
SS: mouthStretchLeft/Right 50% + jawOpen 10%
NN: mouthClose 50% + jawOpen 10%
RR: mouthRollLower 60% + mouthRollUpper 40% + mouthPucker 30%
AA: jawOpen 80% + mouthFunnel 20%
E: mouthStretchLeft/Right 60% + jawOpen 25%
I: mouthSmileLeft/Right 70% + jawOpen 15%
O: mouthPucker 80% + jawOpen 35%
U: mouthPucker 90% + jawOpen 20%
ApplyARKitPreset(). Builds a canonical name→index lookup from the mesh, clears existing mappings, and applies all preset entries viaAddMapping().ARKit integration in Setup Wizard. Step 3 now detects ARKit models and branches to
ApplyARKitPreset()instead of the standard single-blendshape auto-map.ARKit integration in Editor Auto-Map. The "Auto-Map by Name" button in the
BlendshapeTargetinspector detects ARKit models and applies the preset. Standard auto-map also clears any leftoveradditionalEntries.Editor UI for multi-blendshape mappings. Each viseme row in the
BlendshapeTargetinspector now shows a "+" button to add additional blendshape entries listed as indented rows with "−" remove buttons. Copy, Paste, and Clear operations all handle additional entries.
Fixed
ARKit preset typo. Corrected "mouthstretchtleft" → "mouthstretchleft" (extra 't') in
ARKitSignatureNamesand three preset entries (DD, SS, E). Without this fix, mouthStretchLeft would silently fail to match.
Notes
ARKit/FACS models use anatomical muscle blendshapes rather than pre-composed viseme shapes. A single blendshape per viseme produces barely visible lip movement. The multi-blendshape preset combines 2...3 ARKit shapes per viseme to approximate each mouth pose.
Preset weights are starting values tuned for typical ARKit-rigged models. Users can fine-tune individual weights via the inspector's per-entry weight sliders.
The multi-blendshape architecture is generic and not limited to ARKit ... users can manually add additional blendshape entries for any model type via the "+" button in the inspector.
Added (continued)
ARKit jaw bone max angle (6°). The Setup Wizard now sets the jaw bone max angle to 6° for ARKit/FACS models in Combined mode, sitting between iClone CC (15°) and DAZ Genesis (4°). ARKit preset blendshapes include moderate jaw contribution, so a mid-range bone angle is appropriate.
Eyelash blink synchronisation for DAZ Genesis. The wizard now auto-detects auxiliary meshes (eyelashes) that share blink blendshape names with the body mesh via
FindAuxiliaryBlinkMeshes(). If aCrystalBlendshapeSynchronizeralready exists from viseme sync (step 3b), the eyelash mesh is merged into it; otherwise a new synchronizer is created. This ensures DAZ eyelashes blink in sync with the body mesh.FindAuxiliaryBlinkMeshes()in AutoMapper. Finds auxiliary meshes sharing blink blendshape names (canonical match) with the master body mesh, given the detected blink L/R/Both indices.
Changed
Eye blink "Blink Both (combined)" cleared for DAZ and iClone. The wizard now sets
BlinkBothIndex = -1(None) after auto-detection for DAZ Genesis and iClone CC models. These models have separate L/R blink blendshapes, so using a combined mapping would double-drive the blink.
Version 1.4.0
Added
Blendshape Synchronizer ... new runtime component (
CrystalBlendshapeSynchronizer) that copies blendshape weights from a master mesh to auxiliary (slave) meshes every frame. CC3/CC4/CC5 and DAZ Genesis models have auxiliary meshes (eyelashes, beards, stubble, clothing) that duplicate the body's blendshapes and need to stay in sync during lip sync.Auto-detection of auxiliary meshes ... new
CrystalLipSyncAutoMapper.FindAuxiliaryVisemeMeshes()method scans the character hierarchy for SkinnedMeshRenderers that share blendshape names with the master mesh. Uses two-tier detection: Criterion A matches viseme-scoring blendshapes (CC models), Criterion B detects high general overlap (≥ 5 shared blendshapes) for DAZ Genesis eyelashes and similar face-adjacent meshes that duplicate body morphs without matching viseme naming. Clothing, hair, and accessory meshes are excluded from Criterion B viaNonAuxiliaryMeshPatterns.Synchronizer editor inspector (
CrystalBlendshapeSynchronizerEditor) with Auto-Detect Auxiliaries and Rebuild Mappings buttons, per-mesh sync pair counts, and add/remove controls.Combined lip sync mode ... the Setup Wizard now supports a third target mode (
Combined) that adds both a BlendshapeTarget and a JawBoneTarget to the same character. CC/iClone and DAZ Genesis models use blendshapes for lip shaping and jaw bone rotation for mouth opening ... both are needed for realistic results.Jaw bone auto-detection in wizard ...
OnTargetChangednow probes the hierarchy for jaw bone transforms and auto-selects Combined mode when both viseme blendshapes and a jaw bone are found.Model-specific jaw bone configuration ... the Setup Wizard detects whether the character is iClone/CC or DAZ Genesis and automatically configures the JawBoneTarget:
iClone/CC: Z axis, inverted direction, 15° max angle.
DAZ Genesis: X axis, positive direction, 4° max angle (blendshapes handle most jaw opening).
DAZ Genesis SIL mapping auto-clear ... the wizard clears the SIL (Silence) blendshape mapping on DAZ Genesis models to prevent the rest-pose blendshape from pushing the jaw into the upper face.
SetRotationAxis(),SetInvertDirection(),SetMaxAngle()... new public API onCrystalLipSyncJawBoneTargetfor programmatic configuration.DAZ Genesis preferred mesh patterns ...
FindBestVisemeMeshandCrystalEyeBlink.AutoDetectBlendshapesnow recognize Genesis2...9 mesh naming (Genesis8Male.Shape,Genesis8Female.Shape, etc.) as preferred body meshes, preventing auxiliary meshes likeasjeans_16097.Shapefrom being selected as the primary target.IsPreferredMesh()public API onCrystalLipSyncAutoMapper... reusable helper for any component that needs to identify the main body mesh vs. auxiliaries.
Fixed
3D spatialized audio producing silence ...
GetSpectrumData/GetOutputDatareturn post-spatialization data, which is near-zero when the AudioListener is far from the source. The controller now auto-detectsspatialBlend > 0and switches to direct AudioClip PCM sampling (AudioClip.GetData), bypassing the entire Unity audio pipeline including distance rolloff. Debug log showspath=clip-direct-3d.Separator-agnostic blendshape matching ... new
Canonicalize()method strips all separators (underscores, spaces, hyphens, dots) and lowercases names before comparison. iClone CC models export blendshapes inconsistently across versions:Dental_Lip(CC4),Dental Lip(CC3),DentalLip(some exports),dental-lip... all now match correctly at Tier 1 (1000 points).Non-viseme blendshape false positives ... added early-exit blacklist for non-viseme tokens (
brow,eye,cheek,nose,squint,blink, etc.). Blendshapes likeBrow_Raise_Inner_Lno longer falsely match the NN viseme via the substring "nn" inside "inner".Directional indicator false positives ... new
IsDirectionalIndicator()guard prevents single-letter tokensl/r/d/ufrom matching viseme codes when they're actually directional suffixes.Jaw_Rotate_Dno longer matches DD (the "D" means Down),Lips_Drop_R_Sideno longer matches RR (the "R" means Right).Multi-word code matching ... Tier 2 now checks multi-word codes (e.g.,
dental_lip) as phrases against joined tokens, so CC3 names with spaces (Dental Lip→ tokens["dental", "lip"]) correctly match at score 500.Tier 3 prefix pattern normalization ... multi-word codes in prefix patterns (
"v dental lip") are now normalized so they match regardless of original separator style.Tier 6 canonical substring matching ... weak substring matches now use canonicalized forms so partial code matches work across all separator variants.
Mesh selection picking wrong GameObject ...
FindBestVisemeMeshpreviously pickedCC_Base_EyeOcclusionorStubbleoverCC_Base_Bodybecause they shared blendshape names and scored similarly. Fixed with:Non-viseme mesh exclusion list (eyeocclusion, tearline, eyelash, stubble, tearduct, hair, teeth, tongue, clothing, etc.)
+100,000 score bonus for preferred mesh names (body, face, head, Genesis patterns)
Eye blink picking wrong mesh ...
CrystalEyeBlink.AutoDetectBlendshapesnow applies the same preferred-mesh bonus, preventing auxiliary meshes from being selected for eye blink detection.Combined mode jaw clipping on DAZ Genesis ... in Combined mode, DAZ viseme blendshapes already include jaw-opening vertex deltas (applied in bind pose before skinning). A 15° bone rotation on top of that caused the lower jaw to arc into the upper mouth. Fixed by reducing max angle to 4° for DAZ Genesis (blendshapes do the heavy lifting) while keeping 15° for iClone/CC (blendshapes don't include jaw opening).
Changed
Setup Wizard now automatically adds the
CrystalBlendshapeSynchronizercomponent when auxiliary meshes with matching viseme blendshapes are detected during setup.Setup Wizard target mode dropdown now has three options: Blendshape, Jaw Bone, Combined ... with context-aware auto-detection helpbox.
Setup Wizard detects iClone/CC vs DAZ Genesis models via
DetectModelType()and configures jaw axis, direction, max angle, and SIL mapping accordingly.ScoreMeshrefactored intoScoreMeshDetailed(returns distinct viseme count alongside total score) for better mesh ranking.
Version 1.3.2
Added
Reallusion Character Creator (CC3/CC4/CC5) auto-mapping. CrystalLipSyncAutoMapper now recognizes Character Creator "Pairs" viseme blendshape naming and maps it to the Oculus 15-viseme setup (including CC3, CC4/CC5 V_ prefixed names, and CC Direct short codes like AE, EE, Er, Oh, Ah, Th).
DAZ Studio Genesis auto-mapping (Genesis 2 / 3 / 8). The auto-mapper now detects DAZ's Preston-Blair 10-viseme morph sets and maps them to the closest Oculus visemes (e.g., Rest→SIL, M→PP, F→FF, L→DD, K→KK, AA→AA, EH→E, OW→O, UW→U, W→U).
Improved DAZ fallback detection. Added support so Genesis 2 "vsm" prefixed morph names are more reliably recognized in fallback matching.
Developer docs update. Added a MatchRules doc comment listing all supported naming conventions (Oculus/VRC, Character Creator, CC Direct, DAZ, descriptive/ARKit).
Fixed
Eye blink auto-detection for DAZ internal morph names. Updated the tokenizer used by CrystalEyeBlink and the GC2 IK eye blink variant to correctly split acronym-to-word boundaries, fixing detection for DAZ exports such as PHMEyesClosed, CTRLEyesClosed, and eCTRLEyesClosed(L/R).
Notes
Character Creator: Tongue_Narrow and Tongue_Lower don't have a strong Oculus equivalent and are left unmapped (manual assignment recommended if you use them).
DAZ Genesis 8.1 / 9: These generations rely on FACS morphs (not dedicated visemes). Eye blink auto-detection works out of the box, but lip sync viseme matching is only best-effort unless you provide dedicated viseme shapes or assign blendshapes manually.
DAZ viseme coverage: DAZ's Preston-Blair set has fewer shapes than Oculus 15, so some Oculus slots (e.g., TH/CH/SS/NN/RR) may remain unmapped depending on the model.
Version 1.3.1
Added
WebGL build-target awareness in the Setup Wizard. The wizard now detects when the active build target is WebGL and adjusts its setup options accordingly. Baked Lip Sync option for WebGL. When targeting WebGL, the wizard shows a Baked Lip Sync toggle to add CrystalBakedLipSync to your character. WebGL baking notice. The wizard now displays a warning explaining that real-time audio analysis isn't available on WebGL and that lip sync must be baked offline via Tools → Crystal LipSync → Bake Lip Sync.
Changed Microphone Lip Sync disabled on WebGL. The wizard now automatically disables the microphone option when the build target is WebGL, with an info message explaining Unity's platform limitation and noting that WebGL microphone support is planned for a future update. Wizard guidance updated. The character help box now lists CrystalBakedLipSync among the components the wizard can provision. Safety guard for WebGL. The setup process now prevents adding microphone-related components when building for WebGL to avoid unsupported configurations.
Version 1.3.0
Added
WebGL real-time audio lip sync.
New WebGL path captures raw PCM via OnAudioFilterRead, runs a pure-managed FFT, and feeds the same viseme classification pipeline used on desktop. Includes CrystalLipSyncAudioCapture, CrystalFFT, controller auto-routing on WebGL, plus a forceAudioCapture toggle for testing and an IsUsingAudioCapture property.
Pre-Baked (offline) Lip Sync workflow.
Bake AudioClips into CrystalLipSyncClipData assets and play them back at runtime with zero-cost analysis using CrystalBakedLipSync. Includes the Bake Window (Tools → Crystal Lip Sync → Bake Lip Sync) with batch baking, preview timeline, profile import, and re-bake-in-place.
Game Creator 2:
Baked Lip Sync integration.
CrystalBakedClipLookup (AudioClip → baked data map) with editor utilities.
GC2 instructions: Play Baked Lip Sync and Stop Baked Lip Sync (with optional audio playback + wait-to-complete).
Direct AudioClip sampling when AudioSource is silenced.
CrystalLipSyncController now detects near-zero volume and switches to a clip-sampling path (AudioClip.GetData() + managed FFT), bypassing Unity's audio pipeline. (Requires AudioClip set to Decompress On Load or Compressed In Memory, not Streaming.)
Jaw Bone Target (no blendshapes required).
Added CrystalLipSyncJawBoneTarget (Viseme/Volume/Combined drive modes) plus a custom editor with auto-detect, live debug view, and a test-pose slider. Setup Wizard now supports selecting Blendshape vs JawBone target mode and auto-suggests based on the character.
Auto-mapper: Daz Studio Genesis 2/3/8 support.
Expanded viseme mapping for Daz's eCTRLv blendshape conventions with consonant codes + mouth-context gating.
Changed
Integrations now auto-use baked data when available.
CrystalDialogueLipSync (GC2) can automatically start baked playback when a matching baked clip exists.
CrystalDialogueSystemLipSync (Pixel Crushers Dialogue System) can monitor the speaker AudioSource, resolve the playing clip via the lookup, and start baked playback...works with Resources/Addressables/AssetBundles.
Bake Window: optional auto-populate lookup.
Assign a CrystalBakedClipLookup and baked assets are registered automatically while baking.
Analyzer pipeline refactor (backwards compatible).
Internal processing was split so the Unity spectrum path and the managed FFT path share the same logic. Added an overload for offline baking that accepts an explicit deltaTime (since Time.unscaledDeltaTime is meaningless there). Controller reinitialization now properly refreshes the capture component when the capture path is active.
Fixed
GC2 real-time lip sync could be silent when AudioSource volume = 0 (Unity 6).
Some setups force volume to 0 to avoid double-audio; Unity then provides zero spectrum/filter data. Fixed via direct clip sampling + managed FFT fallback.
Baked Lip Sync not applying on WebGL in some cases (priority/execution order).
On WebGL, real-time buffers can be empty while baked sampling is valid; baked playback now correctly takes priority so viseme weights get applied.
Auto-mapper false E-viseme matches on Daz blendshape names.
Prevented the leading e prefix from incorrectly scoring as an E-vowel match across many blendshapes.
Version 1.2.0
Added
PixelCrushers Dialogue System integration (CrystalDialogueSystemLipSync).
New drop-in component: add it to your Dialogue Manager and text lip sync works automatically for every conversation line using the Dialogue System's OnConversationLine / OnConversationLineEnd callbacks.
Automatically resolves the speaker from Subtitle.speakerInfo.transform
Finds CrystalTextLipSync on the speaker (root or children)
Configurable characters-per-second (CPS)
Optional: skip text lip sync when the Sequence field contains voice/audio commands (AudioWait, Audio, Voice)
Fixed
Text lip sync viseme weights being overwritten by the controller.
CrystalLipSyncController was running FFT analysis every frame even when no audio was active, overwriting VisemeWeights[] with zeros and wiping text-driven visemes before they could be applied. The controller now skips FFT analysis when inactive (IsActive == false), preserving externally-written weights (e.g., from CrystalTextLipSync).
Text lip sync ending instantly for Dialogue Entries without delays/audio waits.
In some sequences, OnConversationLineEnd could fire immediately after OnConversationLine when the Sequence field contained no Delay() / AudioWait(), canceling PlayText before visemes had time to animate. Removed the immediate stop on line end so text lip sync now plays to natural completion, and is cleaned up on the next line start or conversation end.
Version 1.1.0
Added
Added Text to Lipsync Option B Demo Scene for GC2 (Install through the GC2 Install menu)
Changed
Setup Wizard: automatically disables CrystalEyeBlink on Game Creator 2 Characters.
The wizard now detects GC2's Character component (via a loose, string-based type check with no hard dependency) and disables the Eye Blink toggle with an info message. GC2 Characters already use their own IK-based eye blink system, so CrystalEyeBlink would be redundant. The wizard still compiles and works even if GC2 isn't installed.
Dialogue tracking is now fully event-driven (no Update polling).
Removed the Update() polling loop and switched to GC2 dialogue lifecycle events for cleaner and more reliable behavior.
Debug logs are now controlled by a toggle.
Logs only appear when Show Debug Logs (showDebugLogs) is enabled; normal operation stays silent.
Removed EyeBlinking MonoBehavior Component from GC2 Characters in the GC2 Demo Scenes - They are redundant since in GC2 you are using the IK Eyeblink of Crystal LipSync
Fixed
Text lip sync not triggering during dialogue.
Fixed an issue where dialogue text was not available in the EventStartNext timing. The integration now resolves text directly via node.GetText(Args.EMPTY).
First dialogue node being missed.
Fixed a race where subscribing in Update() happened too late, after the first node event already fired. The integration now subscribes in OnEnable() using Dialogue.EventAnyStart / Dialogue.EventAnyFinish, ensuring the very first node is captured.
Version 1.0.0
Initial release
Last updated