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,结构为两级字典:外层键是子实例的SymbolChildId(Guid.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计算)。
哪些参数会被捕获?
快照捕获并非"全量保存",而是有严格的白名单过滤(见TryCreateVariationForCompositionInstances与CollectControlledInputIds):
- 只有支持混合的类型才会被纳入(
ValueUtils.BlendMethods.ContainsKey(type)); - 子实例必须在组合 UI 中开启 Snapshot 控制(
childUi.EnabledForSnapshots/IsInputIncludedForVariation); - 默认值不存储——未存储的受控参数在应用时会被重置回默认值(
ResetInputToDefault),这是变体"干净切换"的关键; 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)后才结算端点完成事件。
注意事项与限制
- Lib 命名空间的符号禁止应用符号级变体:
VariationHandling.Update()中明确拒绝Namespace.StartsWith("Lib.")的组合快照池。原因在代码注释中说明——多数变体操作会改写父符号本身,在单个符号内调参没问题,但若该符号被大量实例化(库算子正是如此),副作用可能不可控且危险。 - 反馈渲染冲突:实时缩略图渲染与反馈类效果(
[AdvancedFeedback]、[SimpleLiquid])互斥,遇到时关闭 Live previews 与 hover preview。 - 预设与快照不可混合加权:加权混合要求参与者全部为 Preset 或全部为 Snapshot,混用会报错。
- 默认值即"重置":变体只存储非默认值,应用时未存储的受控参数会重置到默认——理解这一点才能设计出可预期切换的变体。
- 文件随项目走: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),仅供参考