news 2026/9/18 5:25:10

t3 时间覆盖机制(Time Overrides):让子图拥有独立于全局时间轴的时钟

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
t3 时间覆盖机制(Time Overrides):让子图拥有独立于全局时间轴的时钟

t3 时间覆盖机制(Time Overrides):让子图拥有独立于全局时间轴的时钟

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

本篇技术文章基于 t3 仓库帮助文档 TimeOverrides.md 展开。t3(TiXL)是一个用于创建实时动态图形(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.0(Playback.cs)。
  • Local vs Playback:"Local time 可以被算子覆盖(如 SetCommandTime 这类算子)和TimeClip"。文档建议绝大多数算子使用 local time;而全局 Playback 时间用于让更新类算子(如PulsateCounter)即使在时间被覆盖的子图中也保持一致、可预测的时序。
  • 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模式时,算子会把输出槽的DirtyFlagTriggerAnimated切成None——冻结时间不再产生动画失效,从而避免下游整棵子图每帧重算。这正是文档所说 "freeze a section" 的工程落地。

注意LocalTimePlaybackTime的分野:如果某个算子运行在被覆盖时间的子图内部,Time算子选LocalTime得到的就是覆盖后的时间,选PlaybackTime得到的仍是全局时间轴——这决定了"子图内部到底跟着谁动"。

3.2SetPlaybackTime算子:改写全局时钟

SetPlaybackTime.cs 是另一个方向的时间操纵器:它直接写入全局时钟。其输入端口(可对照 SetPlaybackTime.t3 中的默认值):

  • SubGraphCommand类型):待"包裹"的子图;
  • TimeInBars(float,默认 0.0):目标时间,单位 bar;
  • TriggerMode(默认OnceEnabledGetsTrue,另一档为Continuously);
  • Enabled(bool):触发开关;
  • ShowLogMessages(bool,默认 false):是否在日志中打印 "Setting playback time to …"。

其执行逻辑(SetPlaybackTime.cs)值得注意:

  1. 对 NaN / Infinity 的输入时间做防御,直接归零;
  2. 仅当Enabled上升沿触发(MathUtils.WasTriggered)或处于Continuously模式时才写入;
  3. 写入目标是Playback.Current.TimeInBars,并会在没有活动 Playback 时记录警告 "Can't 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",摆放位置单位永远是 bar);
  • SourceRange源内容区间,即"这段剪辑对应素材的哪一段";
  • LayerIndex:时间轴图层索引,重叠检测只在同图层内进行(TimeClip.cs);
  • UsedForRegionMapping:标记该剪辑是否用于"嵌套内容局部时间重映射"。像TimeClipSwitch这类算子可以清掉这个标志,表示拖动剪辑时应把源区间与摆放区间联动(TimeClip.cs);
  • SourceUnitClipTimeUnits枚举,Bars/Seconds):SourceRange所用单位。文档注释解释得很清楚——墙钟类素材(视频、音频)用Seconds,这样改 BPM 时裁剪点和关键帧仍"贴"在内容上;音乐类内容(嵌套合成、MIDI)用Bars(TimeClip.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且两区间重合时时间只是"平移"(remap);Speed != 1时内容被加速或减速(scaled);SourceRange反向时(负时长)则等价于倒放——文档所列 "run a scene backwards, hold it still, or jump non-linearly" 正是通过编辑这两个区间来实现的。

此外还有两个健壮性机制:

  • MakeConform()(TimeClip.cs):把任何非有限值(NaN/Inf)的区间端点修复为合法默认值,新剪辑默认时长 4 个 bar(DefaultClipDuration);
  • SourceToSeconds/SecondsToSource/GetPlaybackSpeed(TimeClip.cs):在Bars单位下按当前 BPM 换算真实秒数,供媒体类算子做解码与展示。

4.2 求值流程:门控 + 重映射 + 恢复

时间覆盖真正生效的地方是 TimeClipSlot.cs。TimeClipSlot<T>是带时间剪辑的输入槽,其UpdateWithTimeRangeCheck(TimeClipSlot.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 / AudioClip),SourceUnit被设为Seconds,源时间轴锚定在素材 0 点而非时间轴摆放位置;否则为Bars(TimeClipSlot.cs)。
  • 旧工程兼容:若文件中读出的TimeClip缺少SourceUnit字段(NeedsSourceUnitConversion为 true),槽会用最近一层拥有独立设置的合成的 BPM(FindCompositionBpm,逐级向上查找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"的注释,可以推断其呈现逻辑为:

  • 重映射(remap)SourceRangeTimeRange不重合,但时长比接近 1——内容只是被"搬"到了时间轴的另一个位置;
  • 缩放(scaled)Speed = SourceRange.Duration / TimeRange.Duration偏离 1——内容在单位时间内的播放速度被改变(负值为倒放)。

从源码结构看,剪辑绘制时以SourceRangeTimeRange的对比结果作为着色依据;对媒体类内容剪辑(IContentTimeClip),由于"重映射是常态",UI 不再把重映射本身当作值得高亮的状态。

六、组合两种手段时的注意事项

  1. 渲染导出路径PlaybackIsRenderingToFile为 true 时会把PlaybackSpeed置 0,并由渲染输出外部提供时间(Playback.cs)。此时SetPlaybackTime这类在帧间抢写全局时间的算子可能与渲染驱动的时间源竞争,导出场景应优先依赖 Time Clip 的静态重映射。
  2. 单位一致性:在 120 BPM 下 1 bar = 2 秒。Time算子的Units=SecsTimeClipSourceUnit=SecondsSourceToSeconds等 API 都是为消除 bar/秒混用而设;BPM 变化后,Bars单位的内容(嵌套合成、MIDI)会随拍走,Seconds单位的内容(视频、音频)保持与墙钟一致。
  3. 倒放/冻结的实现选择:文档列举的 "run a scene backwards, hold it still, or jump non-linearly through an animation" 可分别落到:SourceRange反向或时间信号递减(倒放);Time算子Frozen模式(冻结,且自动停掉动画失效传播);正弦/随机/节拍信号驱动TimeInBars(非线性跳转)。
  4. 范围外行为:需要确认全局设置中TimeClipSuspending是否启用,以决定范围外的子图是挂起(省性能)还是仅标记不活跃(状态可瞬时恢复)。

七、小结

t3 的时间覆盖机制可以归纳为一句话:全局Playback.TimeInBars是默认时钟,而时间覆盖就是在子图求值边界上替换context.LocalTime这一时钟源。它有两个落地形态:一是用信号驱动算子(Time算子读取多种时间源、SetPlaybackTime写入全局时钟);二是时间轴上的 Time Clip(TimeClip双区间仿射映射 +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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 5:25:04

微信小程序指南针实战:传感器、Canvas 与性能调优

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 5:24:39

AI芯片设计从入门到放弃:ESP32-C5板载天线与分层卡点

从一个真事说起。前阵子一个做算法的朋友跟我说&#xff0c;他准备"转行做AI芯片"&#xff0c;理由是"大模型这么火&#xff0c;芯片肯定缺人"。两周后他发来一张照片&#xff1a;一块开发板&#xff0c;天线那一片被他焊得面目全非&#xff0c;旁边配文—…

作者头像 李华
网站建设 2026/9/18 5:22:51

LiteLLM 跑 Ling-3.0-flash-Fin,TaoToken 放到 model_list

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 5:20:44

纯粹合数怎么判断?C++两种解法与质数筛优化思路

把东华OJ的题库翻到第100题&#xff0c;名字叫“纯粹合数”&#xff0c;第一眼看到这个题&#xff0c;我脑子里冒出来的想法是&#xff1a;这不就是判断合数然后筛一遍就行了&#xff1f;等我真正把题面读清楚&#xff0c;才发现“纯粹”两个字的含义比我想象的复杂得多——它要…

作者头像 李华
网站建设 2026/9/18 5:20:42

ANSYS Workbench带孔平板静力学实例:网格收敛与参数化分析

简介&#xff1a;ANSYS Workbench是一款集成多物理场分析的工程仿真平台&#xff0c;这份PDF教程聚焦其Mechanical模块&#xff0c;面向需要开展结构与热分析的工程师、科研人员及高校学生。内容从Workbench环境启动、项目文件管理和单位制设置讲起&#xff0c;系统讲解几何模型…

作者头像 李华
网站建设 2026/9/18 5:20:08

Git Submodule实战:从添加子模块到递归克隆与踩坑指南

前阵子接手一个微服务仓库&#xff0c;里面要复用三个公共库&#xff1a;一份API定义、一套工具函数、一份配置模板。最初大家都是复制粘贴&#xff0c;结果每次一改接口&#xff0c;其他服务马上编译失败&#xff0c;喊破嗓子才对齐。后来我把这三个公共库改成 git submodule …

作者头像 李华