:让子图拥有独立于全局时间轴的时钟)
t3 时间覆盖机制Time Overrides让子图拥有独立于全局时间轴的时钟【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3本篇技术文章基于 t3 仓库帮助文档 TimeOverrides.md 展开。t3TiXL是一个用于创建实时动态图形realtime motion graphics的开源节点式软件其核心调度围绕时间进行时间覆盖Time Override是其中一种关键机制——它允许你给某个算子或子图喂入自己的一路时间而不是全局播放时间从而独立地擦洗scrub、循环loop、冻结freeze或重映射remap项目中的某一段。读完后你将理解 t3 中全局时间 vs 局部时间的双时间体系、TimeClip的仿射时间映射算法以及如何用时间信号与时间剪辑Time Clip实现倒放、静止保持和非线性时间跳转并能定位到仓库中对应的核心源码实现。一、什么是时间覆盖文档给出的核心定义t3 帮助文档对该主题的完整表述是A way to feed an operator or sub-graph its own time instead of the global playback time — so you can scrub, loop, freeze, or remap a section of your project independently. Wrap a part of the graph so its time comes from a signal you supply — a sine, a remapped random, a beat — and everything inside animates against that instead of the timeline.翻译过来即时间覆盖是一种让算子或子图使用自定义时间、而非全局播放时间playback time的方式目的是让你可以独立地擦洗、循环、冻结或重映射项目中的某一段。把图中的一部分包起来让它的时间来自你提供的一路信号——正弦波、重映射后的随机数、节拍——那么其中的所有内容都会相对于这路信号而不是时间轴进行动画。文档还给出了典型用途让场景倒着播、让它静止不动、或非线性地跳着穿过一段动画。文档同时指出时间轴上的时间剪辑Time Clips本质上做的是同一件事每个剪辑门控gates图中哪一部分会被求值并重映射喂入其子图的时间橙色orange和红色red的视觉提示分别表示时间正在被重映射或被缩放。下面结合仓库源码把这条机制拆成两层全局/局部时间的底层定义以及两种具体的时间覆盖手段。二、双时间体系Playback全局时间与context.LocalTime局部时间要理解时间覆盖先要理解 t3 的时间术语体系。Playback.cs 文件头部的注释给出了权威说明Time vs TimeInSecs除非带*InSecs后缀默认时间单位是小节bar。在 120 BPM 下一个 bar 单位等于 2 秒。换算关系由BarsFromSeconds/SecondsFromBars实现公式为secs * Bpm / 240.0Playback.cs。Local vs PlaybackLocal time 可以被算子覆盖如 SetCommandTime 这类算子和TimeClip。文档建议绝大多数算子使用 local time而全局 Playback 时间用于让更新类算子如Pulsate、Counter即使在时间被覆盖的子图中也保持一致、可预测的时序。Time vs FxTimeFxTime效果时间在开启 Idle Motion空闲继续播放时仍会前进大多数特效类算子应使用它Time则用于 UI 交互与一切由关键帧驱动的内容。在 EvaluationContext.cs 中可以看到默认行为求值上下文初始化时LocalTime Playback.TimeInBars即局部时间默认等于全局播放时间——时间覆盖要做的正是在求值子图之前把这个值替换掉。Playback.Update()每帧推进TimeInBars播放时按timeSinceLastFrame * PlaybackSpeed * Bpm / 240.0累加并支持LoopRange循环回绕Playback.cs。时间覆盖机制就构建在这套基础设施之上。三、手段一用信号驱动子图时间文档中wrap a part of the graph so its time comes from a signal you supply 对应的是把子图包在一个算子里、由外部信号决定其时间的用法。仓库中有两个代表性算子。3.1Time算子读取多种时间源Time.cs 实现了一个时间读取算子其Mode输入支持五种时间模式TimeModes枚举模式取值来源语义LocalIdleMotionFxTimecontext.LocalFxTime局部效果时间Idle Motion 下仍前进LocalTimecontext.LocalTime局部时间——已被时间覆盖时就是覆盖后的时间PlaybackTimecontext.Playback.TimeInBars全局播放时间不受局部覆盖影响Runtime应用运行以来的秒数换算成 bar启动后单调递增Frozen恒为 0冻结且会切换输出槽的 dirty flag 触发方式为None以避免无谓下游更新它还提供Units输入Bars/Secs和SpeedFactor输入做线性缩放。关键细节是 Time.cs 中当切换到Frozen模式时算子会把输出槽的DirtyFlagTrigger从Animated切成None——冻结时间不再产生动画失效从而避免下游整棵子图每帧重算。这正是文档所说 freeze a section 的工程落地。注意LocalTime与PlaybackTime的分野如果某个算子运行在被覆盖时间的子图内部Time算子选LocalTime得到的就是覆盖后的时间选PlaybackTime得到的仍是全局时间轴——这决定了子图内部到底跟着谁动。3.2SetPlaybackTime算子改写全局时钟SetPlaybackTime.cs 是另一个方向的时间操纵器它直接写入全局时钟。其输入端口可对照 SetPlaybackTime.t3 中的默认值SubGraphCommand类型待包裹的子图TimeInBarsfloat默认 0.0目标时间单位 barTriggerMode默认OnceEnabledGetsTrue另一档为ContinuouslyEnabledbool触发开关ShowLogMessagesbool默认 false是否在日志中打印 Setting playback time to …。其执行逻辑SetPlaybackTime.cs值得注意对 NaN / Infinity 的输入时间做防御直接归零仅当Enabled上升沿触发MathUtils.WasTriggered或处于Continuously模式时才写入写入目标是Playback.Current.TimeInBars并会在没有活动 Playback 时记录警告 Cant set playback time without active Playback。结合文档的用法提示可以这样组合倒放场景给TimeInBars输入一个随真实时间递减的信号例如起点 - Runtime子图就会反向穿过动画非线性跳转把正弦波、重映射随机数或节拍相位仓库中 BarPhaseTracker.cs、BeatSynchronizer.cs 等音频分析组件输出的相位/拍号信号接到时间输入子图即按该节奏重放保持静止用Time算子的Frozen模式或让SetPlaybackTime持续写入同一个值。需要区分的是SetPlaybackTime改的是全局时钟影响所有读取PlaybackTime的内容而时间剪辑改的是局部时钟只影响被包住的子图。这正是文档把两种方式并列但语义不同的原因。四、手段二时间轴上的 Time Clip时间剪辑文档指出时间轴上的时间剪辑同样做这件事每个剪辑门控图中哪一部分会被求值并重映射喂入其子图的时间。下面从数据结构到求值流程逐层印证。4.1TimeClip数据结构两个区间的仿射映射TimeClip.cs 定义了时间剪辑的核心数据TimeRange时间轴上的摆放区间文档注释明确Timeline placement is always bars摆放位置单位永远是 barSourceRange源内容区间即这段剪辑对应素材的哪一段LayerIndex时间轴图层索引重叠检测只在同图层内进行TimeClip.csUsedForRegionMapping标记该剪辑是否用于嵌套内容局部时间重映射。像TimeClipSwitch这类算子可以清掉这个标志表示拖动剪辑时应把源区间与摆放区间联动TimeClip.csSourceUnitClipTimeUnits枚举Bars/SecondsSourceRange所用单位。文档注释解释得很清楚——墙钟类素材视频、音频用Seconds这样改 BPM 时裁剪点和关键帧仍贴在内容上音乐类内容嵌套合成、MIDI用BarsTimeClip.cs。旧文件读取后由NeedsSourceUnitConversion标记一次性的单位换算。时间重映射的数学核心是两个互逆的仿射函数// Core/Animation/TimeClip.cs#L85-L93 public double MapTimelineToSource(double timelineBars) { var pos timelineBars - TimeRange.Start; var duration TimeRange.End - TimeRange.Start; if (Math.Abs(duration) 0.0001f) pos * (SourceRange.End - SourceRange.Start) / duration; return pos SourceRange.Start; }即source (timeline - TimeRange.Start) * SourceRange.Duration / TimeRange.Duration SourceRange.Start。文档强调这是affine and unbounded — callers clamp or gate as needed仿射且无界由调用方负责门控或钳制。MapSourceToTimeline是其逆映射用于把源时间上的数据如关键帧摆到它生效的全局播放位置上。由此派生出文档中红色提示 缩放的判据// Core/Animation/TimeClip.cs#L75 public float Speed MathF.Abs(TimeRange.Duration) 0.001f ? 1 : SourceRange.Duration / TimeRange.Duration;Speed 1且两区间重合时时间只是平移remapSpeed ! 1时内容被加速或减速scaledSourceRange反向时负时长则等价于倒放——文档所列 run a scene backwards, hold it still, or jump non-linearly 正是通过编辑这两个区间来实现的。此外还有两个健壮性机制MakeConform()TimeClip.cs把任何非有限值NaN/Inf的区间端点修复为合法默认值新剪辑默认时长 4 个 barDefaultClipDurationSourceToSeconds/SecondsToSource/GetPlaybackSpeedTimeClip.cs在Bars单位下按当前 BPM 换算真实秒数供媒体类算子做解码与展示。4.2 求值流程门控 重映射 恢复时间覆盖真正生效的地方是 TimeClipSlot.cs。TimeClipSlotT是带时间剪辑的输入槽其UpdateWithTimeRangeCheckTimeClipSlot.cs完整对应文档描述的门控 重映射两步// 1. 门控时间轴时间不在 TimeRange 内 → 子图不求值 if (!EvaluateOutsideRange ((context.LocalTime TimeClip.TimeRange.Start) || (context.LocalTime TimeClip.TimeRange.End))) { LastUpdateStatus CoreSettings.Config.TimeClipSuspending ? UpdateStates.Suspended : UpdateStates.Active; return; } // 2. 重映射把全局/局部时间换算到源时间再求值子图 var prevTime context.LocalTime; var prevFxTime context.LocalFxTime; context.LocalTime TimeClip.MapTimelineToSource(prevTime); context.LocalFxTime TimeClip.MapTimelineToSource(prevFxTime); _baseUpdateAction(context); // 3. 恢复出栈时还原保证嵌套/兄弟子图互不污染 context.LocalTime prevTime; context.LocalFxTime prevFxTime;几个值得注意的实现细节Suspended 状态范围外默认只标记Inactive当全局设置CoreSettings.Config.TimeClipSuspending打开时范围外的子图进入Suspended挂起状态以节省算力——这是门控的性能收益所在。EvaluateOutsideRange旁路媒体类剪辑视频、音频需要首尾帧钳制/解码预卷因此这类槽关掉范围外门控改为总是求值、只做时间重映射由算子自己负责钳制TimeClipSlot.cs。IContentTimeClip标记SetOutputData中若父算子实现IContentTimeClip墙钟媒体剪辑如 VideoClip / AudioClipSourceUnit被设为Seconds源时间轴锚定在素材 0 点而非时间轴摆放位置否则为BarsTimeClipSlot.cs。旧工程兼容若文件中读出的TimeClip缺少SourceUnit字段NeedsSourceUnitConversion为 true槽会用最近一层拥有独立设置的合成的 BPMFindCompositionBpm逐级向上查找Symbol.CompositionSettings取不到则回退Playback.Current?.Bpm ?? 120把源区间与子图关键帧时间一次性换算成秒TimeClipSlot.cs并通过Animator.ScaleKeyTimesOfChild同步缩放关键帧——保证升级前的文件打开后时间语义不变。嵌套安全重映射前后对context.LocalTime做保存/恢复因此一个算子的时间覆盖只作用于自己的子树父图与兄弟子图不受影响。4.3 被覆盖的时间流向了哪里局部时间一旦改变下游所有按局部时间采样的机制都会跟着走。Animator.cs 的注释与实现表明关键帧曲线在context.LocalTime处采样例如GetSampledValue(ctx.LocalTime)且键值必须插在局部时间空间中。因此一个 Time Clip 覆盖的不仅是一个算子的感觉而是子图内所有关键帧动画、所有读取LocalTime/LocalFxTime的算子的时间基准——这与文档everything inside animates against that instead of the timeline的表述精确对应。五、时间轴上的橙色与红色提示文档提到时间轴上橙色orange与红色red的提示分别表示时间正在被重映射或被缩放。结合 TimeClip.cs 的Speed属性与 TimeClipItem.cs 中time-remap is the norm for them的注释可以推断其呈现逻辑为重映射remapSourceRange与TimeRange不重合但时长比接近 1——内容只是被搬到了时间轴的另一个位置缩放scaledSpeed SourceRange.Duration / TimeRange.Duration偏离 1——内容在单位时间内的播放速度被改变负值为倒放。从源码结构看剪辑绘制时以SourceRange和TimeRange的对比结果作为着色依据对媒体类内容剪辑IContentTimeClip由于重映射是常态UI 不再把重映射本身当作值得高亮的状态。六、组合两种手段时的注意事项渲染导出路径Playback在IsRenderingToFile为 true 时会把PlaybackSpeed置 0并由渲染输出外部提供时间Playback.cs。此时SetPlaybackTime这类在帧间抢写全局时间的算子可能与渲染驱动的时间源竞争导出场景应优先依赖 Time Clip 的静态重映射。单位一致性在 120 BPM 下 1 bar 2 秒。Time算子的UnitsSecs、TimeClip的SourceUnitSeconds、SourceToSeconds等 API 都是为消除 bar/秒混用而设BPM 变化后Bars单位的内容嵌套合成、MIDI会随拍走Seconds单位的内容视频、音频保持与墙钟一致。倒放/冻结的实现选择文档列举的 run a scene backwards, hold it still, or jump non-linearly through an animation 可分别落到SourceRange反向或时间信号递减倒放Time算子Frozen模式冻结且自动停掉动画失效传播正弦/随机/节拍信号驱动TimeInBars非线性跳转。范围外行为需要确认全局设置中TimeClipSuspending是否启用以决定范围外的子图是挂起省性能还是仅标记不活跃状态可瞬时恢复。七、小结t3 的时间覆盖机制可以归纳为一句话全局Playback.TimeInBars是默认时钟而时间覆盖就是在子图求值边界上替换context.LocalTime这一时钟源。它有两个落地形态一是用信号驱动算子Time算子读取多种时间源、SetPlaybackTime写入全局时钟二是时间轴上的 Time ClipTimeClip双区间仿射映射 TimeClipSlot的门控/重映射/恢复三段式求值。核心实现集中在 TimeClip.cs、TimeClipSlot.cs、Playback.cs、EvaluationContext.cs 与 SetPlaybackTime.cs 等文件帮助文档见 TimeOverrides.md。掌握这套机制后你可以独立地为任意子图搭建自己的时钟实现倒放、冻结、循环与节拍驱动的动画重放。【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考