TiXL 变体系统(Variations)完全指南:Preset、Snapshot 与实时混合实战
2026/9/18 4:59:40 网站建设 项目流程

TiXL 变体系统(Variations)完全指南:Preset、Snapshot 与实时混合实战

【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3

TiXL 的变体(Variation)系统是现场演出与快速调参的核心工具:它以缩略图的形式保存、浏览并实时混合算子的参数状态,让你能在演出中像操作采样器一样切换画面与场景。本文将围绕 TiXL 官方文档中关于 Presets、Snapshots 的使用说明,结合仓库源码(Editor/Gui/Interaction/Variations)与编辑器的 Variations 窗口实现,深入讲解预览、应用、混合、快照存储路径与程序化触发机制,帮助你完整掌握这套"参数快照 + 实时混音"的工作流。

变体体系的两种形态:Presets 与 Snapshots

根据 .help/embedded/Variations.md 的定义,变体分为两种形态:

  • Presets(预设):针对单个算子符号(如[Blob][Layer2d])的参数组合。它保存的是该符号实例上非默认值的输入参数,属于"这个算子长什么样"的抽象描述,可复用到该符号的任意实例上。
  • Snapshots(快照):针对组合(Composition)内一组算子实例的参数状态,例如在演出中切换整个场景时,把舞台上所有受控算子的参数一次性存下来。

两者的存储归属也不同,VariationWindow.md 中明确说明:

A snapshot is stored on the operator's own symbol, while a preset stores instance values into the composition that uses it — same blending, different owner.

即快照挂在所属组合的符号上,预设则把实例值写进使用它的组合中——二者共享同一套混合机制,只是"所有者"不同。从源码看,这个差异体现在 Variation.cs 的IsPreset布尔字段上(IsSnapshot => !IsPreset),而应用时的指令构造也分叉为TryCreateApplyPresetCommand(面向选中实例的输入)与TryCreateApplyVariationCommand(面向组合内多个子实例),两者都在 SymbolVariationPool.cs 中实现。

预览与应用变体

点击应用

在 Variations 窗口(或 Snapshot 控制视图中),点击缩略图即可应用对应的参数组合。应用动作以宏命令(MacroCommand)形式进入撤销栈,见SymbolVariationPool.Apply();对于算子程序化触发的应用,则走ApplyWithoutUndo()——因为自动化输出不应污染用户的撤销历史(该逻辑同时用于 [ActivateSnapshot] 算子)。

Preview on hover:悬停预览

开启Preview on hover后,鼠标悬停在缩略图上会临时显示该变体的效果,移开后自动还原。源码层面由BeginHover()StopHover()成对实现:

  • BeginHover()构造应用指令并立即Do(),同时通过RememberModificationFlagForPreview()记住组合当前的"干净"状态;
  • StopHover()执行指令的Undo()完全还原数值,并RestoreModificationFlagAfterPreview()恢复修改标志——这样仅仅预览不会把只读符号误标为已修改、从而触发"另存为副本"对话框。

ALT + 悬停:变体之间的混合

文档给出的核心交互是:按住 ALT 键并悬停,可将当前状态与目标变体进行混合;你还可以用选择框(selection fence)一次选中 1~3 个缩略图,控制哪些变体参与混合。这与源码中的实时混合权重(live blend weights)机制对应:

  • 每个变体在会话内持有一个混合权重_blendWeights(见SymbolVariationPool),权重向量默认归一化为 1;
  • 拖动混合推子时先BeginBlendWeightDrag()记录起始权重,随后SetBlendWeight()按"拖拽起始时其他推子的比例"重新分配剩余预算,因此可以从 100% 拖回而不会丢失混合源;
  • BeginWeightedBlend()支持 2 个及以上变体的加权混合:对 Snapshot 调用CreateWeightedBlendSnapshotCommand,对 Preset 调用CreateWeightedBlendPresetCommand,混合函数取自ValueUtils.WeightedBlendMethods——Preset 与 Snapshot 不能混入同一次加权混合,否则会输出错误日志并拒绝执行。

Live render previews:实时渲染预览

开启Live render previews后,缩略图会针对当前固定的输出(pinned output)持续渲染,让你在下游流中直接看到每个预设将如何影响最终画面。这些实时缩略图是临时的——关闭该选项即恢复默认缩略图。

从源码看,缩略图渲染由 VariationThumbnailRenderer.cs 负责,且在VariationHandling.Update()中被逐帧调用,因此即使 Variations 窗口未打开(例如仅使用快照控制视图),缩略图也能照常刷新。

重要限制:文档特别强调,缩略图渲染会干扰依赖反馈渲染(feedback rendering)的效果,典型如[AdvancedFeedback][SimpleLiquid]这类把输出回喂到输入的算子。遇到这种情况应关闭 Live previews 与 hover preview,否则画面会出现意外的反馈痕迹或闪烁。

Variations 窗口与快照激活

Variations 窗口是"捕获、浏览与混合命名快照"的可视化入口:

  • 捕获快照:打开窗口即可把当前已启用(snapshot-enabled)算子的参数状态抓取为快照,之后随时召回或混合;
  • 键盘浏览:用方向键在已保存的状态间步进;
  • 索引驱动:快照支持从索引输入驱动,因此在演出中可以让一个循环递增的数字自动遍历各快照。

"索引"在源码中对应Variation.ActivationIndex字段。它同时承担两个职责:MIDI/控制器槽位快照排序顺序,因此每个快照的索引必须唯一。SymbolVariationPool构造时执行的DeduplicateSnapshotActivationIndices()会在加载时把冲突或负索引自动重分配到最小的空闲槽位(内存中修正,下次保存时落盘)。新建快照时:

  • 若指定了显式索引(如 MIDI pad),直接使用该索引并覆盖同索引的旧快照;
  • 否则在活动快照之后取下一个空闲索引(首个快照从 0 开始)。

对应交互动作封装在 SnapshotActions.cs 中:SaveSnapshotAtIndex(保存到指定槽位)、ActivateOrCreateSnapshotAtIndex(有则激活、无则创建)、RemoveSnapshotAtIndex(删除指定槽位),以及ActivateSnapshotByModuloIndex——该函数按阅读顺序对快照数取模定位,任意整数都能折叠到某个存在的快照,这正是 [ActivateSnapshot] 算子的底层实现,循环计数器驱动场景切换即源于此。

快照的数据模型与文件存储

存储位置:项目的 .meta 文件夹

4.2 版本起,变体文件存放在项目专属的 meta 文件夹中,随项目一起携带(VariationWindow.md 明确提示"keep that folder with your project")。源码路径解析位于SymbolVariationPool.ResolveVariationFilePaths()

  • 可写包的符号:<packageFolder>/.meta/Variations/<symbolId>.var,其中.meta常量定义于 FileLocations.cs 的MetaSubFolder
  • 只读包(如独立构建中的 Lib):包内.var只提供官方内置默认(Defaults),用户创建的内容写入 AppData 下的用户覆盖目录variations/<symbolId>.var
  • 路径在每次加载/保存时重新解析,因为符号可能在不同包之间移动。

文件为 JSON 格式,顶层结构为{ "Id": <symbolId>, "Variations": [...] },序列化实现在Variation.ToJson()/Variation.TryLoadVariationFromJson()

变体内容:ParameterSetsForChildIds

变体数据的核心是ParameterSetsForChildIds,结构为两级字典:外层键是子实例的SymbolChildIdGuid.Empty特指组合自身,用于 Preset 场景),内层键是输入定义 ID,值为InputValue。一个简化的 JSON 结构示意如下:

{ "Id": "98f0...", "Variations": [ { "Id": "a1b2...", "Title": "Scene A", "IsPreset": false, "ActivationIndex": 0, "PosOnCanvas": { "X": 120.0, "Y": 80.0 }, "ParameterSetsForChildIds": { "7c3e...": { "inputId-1": { "Type": "Float", "Value": 0.75 }, "inputId-2": { "Type": "Color", "Value": [1, 0.2, 0.1, 1] } } } } ] }

加载时系统会跳过ExcludedFromPresets标记的输入,并把无法解析的输入 ID 以警告记入日志(见Variation.TryLoadVariationFromJson)。此外每个变体还记录画布位置PosOnCanvas与缩略图尺寸,便于在窗口中以自由排布的画布形式呈现(新建缩略图位置由VariationBaseCanvas.FindFreePositionForNewThumbnail计算)。

哪些参数会被捕获?

快照捕获并非"全量保存",而是有严格的白名单过滤(见TryCreateVariationForCompositionInstancesCollectControlledInputIds):

  1. 只有支持混合的类型才会被纳入(ValueUtils.BlendMethods.ContainsKey(type));
  2. 子实例必须在组合 UI 中开启 Snapshot 控制childUi.EnabledForSnapshots/IsInputIncludedForVariation);
  3. 默认值不存储——未存储的受控参数在应用时会被重置回默认值(ResetInputToDefault),这是变体"干净切换"的关键;
  4. InputUi.ExcludedFromPresets标记的输入会被跳过。

编辑器还提供逐参数级别的控制:ToggleParameterSnapshotControl()把"启用/停用某个参数的快照控制"做成一条可撤销的宏命令,启用时捕获该参数当前值写入所有既有快照,停用时从所有快照中移除其存储值;ApplyParameterToVariations()则支持把当前值批量写入单个或全部快照(对应快照控制视图中的 "Apply to snapshot" / "Apply to all snapshots")。

从 MIDI 推子到程序化混合:混合的三种触发方式

1. 界面混合(ALT + 悬停 / 权重推子)

如前所述,界面混合基于会话内的权重向量,可预览、可撤销地烘焙(ApplyCurrentBlend)。

2. MIDI 交叉推子(BlendActions)

BlendActions.cs 实现了面向MIDI crossfader(0–127)的双端混合模型:

  • _snapshotLeft(推子位置 0)与_snapshotRight(推子位置 127)分别是混合的两端,_activeIsLeft记录当前活动端;
  • 混合目标永远是活动端的对侧:推子移到中间时按归一化位置混合,到达端点(≥0.99 或 ≤0.01)时该侧成为新的活动快照;
  • 推子中途停止(StopBlendingTowards)时,把当前混合结果ApplyCurrentBlend()固化。

3. 算子程序化驱动([BlendSnapshots] / [ActivateSnapshot])

VariationHandling.Update()每帧消费SnapShotBlendingData中由算子提交的请求:

  • [ActivateSnapshot]:一次性激活请求,ProcessSnapshotActivationRequests()按当前快照活动组合匹配后调用ActivateSnapshotByModuloIndex,执行后即清空;
  • [BlendSnapshots]:持续性的混合请求,ProcessSnapshotBlendRequests()中每个算子每帧重新武装其请求,若某算子停止求值(被删除、断开或暂停),混合会在下一帧自动释放——这一设计保证程序化混合不会悬空。混合权重由算子提供并归一化,驱动期间pool.IsBlendDrivenByOperator置位,界面推子转为只读,避免双方打架;权重向量与活动快照也会同步到选择器中,让你看到程序化混合的实时构成。

值得注意的细节:MIDI 交叉推子的 127 级分辨率会造成混合"台阶感",BlendActions.SmoothVariationBlending因此用弹簧阻尼(MathUtils.SpringDamp,刚度 20)对权重做平滑插值,只有阻尼收敛(速度 < 0.0005)后才结算端点完成事件。

注意事项与限制

  1. Lib 命名空间的符号禁止应用符号级变体VariationHandling.Update()中明确拒绝Namespace.StartsWith("Lib.")的组合快照池。原因在代码注释中说明——多数变体操作会改写父符号本身,在单个符号内调参没问题,但若该符号被大量实例化(库算子正是如此),副作用可能不可控且危险。
  2. 反馈渲染冲突:实时缩略图渲染与反馈类效果([AdvancedFeedback][SimpleLiquid])互斥,遇到时关闭 Live previews 与 hover preview。
  3. 预设与快照不可混合加权:加权混合要求参与者全部为 Preset 或全部为 Snapshot,混用会报错。
  4. 默认值即"重置":变体只存储非默认值,应用时未存储的受控参数会重置到默认——理解这一点才能设计出可预期切换的变体。
  5. 文件随项目走:4.2 起变体存储在项目的.meta/Variations/下,共享项目时务必携带该文件夹;只读包的用户变体则落在用户数据目录的variations/覆盖层。

小结

TiXL 的变体系统把"保存参数状态"与"实时混合"统一到了同一套权重与指令机制之上:Presets 描述单个符号的形态,Snapshots 记录组合内的场景状态;二者都通过.meta/Variations下的.varJSON 持久化,都支持悬停预览、ALT 混合、MIDI 推子交叉淡化与算子程序化驱动。掌握本文介绍的交互方式、存储路径与源码级混合语义,你就可以在 TiXL 中搭建出可实时切换、平滑过渡的多场景演出工作流。

【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询