烘焙管线概览

Texturge 的烘焙管线将 UTextAnimationBlueprint 与指定文本编译为预计算的 FBakedAnimation 数据。烘焙在需要时(控件初始化、文本变化、蓝图编译后)完成,运行时仅做高效求值。

NOTE

Texturge 不存在字体 → SDF 纹理烘焙管线。动画编译管线处理的是逐字形曲线数据,不涉及 GPU 纹理生成。

管线入口

FAnimationCompiler::BakeAllStages() 是烘焙管线的唯一入口:

struct TEXTURGE_API FAnimationCompiler
{
    static constexpr int32 MinFramesBetweenRebakes = 3;

    using FAnimInstanceCustomizer = TFunction<void(UTextAnimInstance*)>;

    static FBakedAnimation BakeAllStages(
        UTextAnimationBlueprint* Blueprint,
        const FString& Text,
        const FAnimInstanceCustomizer& InstanceCustomizer = nullptr);
};

第三个参数 InstanceCustomizer 用于注入烘焙临时实例(如应用参数覆盖),蓝图 CDO 永不修改

管线阶段

第一阶段:构建调度信息

  1. BuildScheduleInfos(Text) 遍历文本全部 Unicode 码点,生成 TArray<FGlyphScheduleInfo>
  2. 预填每个字形的索引、字符串与分类(Whitespace / Punctuation / CJK / Decorator)
  3. 分类由 Texturge::Unicode 工具判定,代理对(Surrogate Pair Emoji)映射为单字形

第二阶段:蓝图事件烘焙

  1. 创建临时 UTextAnimInstanceBlueprint->GeneratedClass
  2. 若传入 InstanceCustomizer,先应用到临时实例(参数覆盖注入点)
  3. ProcessEvent 调用 BP 覆盖的 BuildDefaultAnimation(GlyphCount, Text)
  4. 蓝图覆盖返回无效结果时,回退到 C++ 默认 _Implementation
BuildScheduleInfos(Text)
  → CreateLayer(GlyphAnimations[0], Override, Schedule)   ← 基础层
  → CreateFactory(BaseLayer, Schedule)
  → for i in 1..N: Factory->AddLayer(CreateLayer(GlyphAnimations[i], Additive, Schedule))
  → Factory->Bake() → FBakedStage

第三阶段:工厂烘焙(UGlyphAnimationFactory::Bake)

Bake() 返回 FBakedStage,执行三步管线:

STEP 1:   逐层独立烘焙
          for each Layer: BakeStageWithCurves(Layer.Curves, Layer.Schedule)
          → 每个 Glyph 获得单层烘焙结果

STEP 1.5: 消费 Layer 级有序微调步骤
          for each Layer: ApplyGlyphOverrideSteps(Layer.GlyphCurveOverrides)
          → 按序对 TArray<FRichCurveKey> 执行 Multiply/Divide/Add/Subtract
          → 乘除: Value/Tangent/Weight 等比缩放(加权贝塞尔切线)
          → 加减: 仅 Value
          → 空轨道: 基于已有键时间范围创建恒等双键

STEP 2:   逐 Glyph 逐层拷贝
          for each Glyph:
            for each Layer:
              SrcCurveSet.OffsetBy(LayerStartTime)
              FBakedGlyph.AddLayer() → FPerLayerCurves
          → 不合并曲线,每层保留独立曲线数据

STEP 3:   工厂级全局后处理
          3a: ApplyFactorySchedule(Skip/StartDelay) — 作用于合并后 Glyph StartTime
          3b: Factory.GlyphCurveOverrides 烘焙进 FBakedGlyph.FactoryOverride
              → 求值时对混合后的最终状态整体应用一次
          3c: 动画剪切(SetAnimationClipAt)— 前端/末端裁剪(播放轴口径)
          → 跳过 bIsStatic Glyph

NOTE

动画剪切(0.3.0):层与工厂级 SetAnimationClipAt(FrontTrim, BackTrim) 按播放轴口径裁剪,有限循环展开物化(只改最后一次循环),全裁字形静态化。

第四阶段:组装最终输出

  1. 组装 FBakedAnimationDefaultStageBuildDefaultAnimation 返回,IntroStage / OutroStage 为 nullptr
  2. 计算双指纹:TextFingerprint = CityHash64(SourceText)BlueprintFingerprint(见下)
  3. 返回 FBakedAnimation(move 语义,支持 Double-Buffer 发布)

指纹缓存

指纹计算方式失效条件
TextFingerprintCityHash64(SourceText)文本内容变化
BlueprintFingerprint哈希:路径名 + 所有 GlyphAnimation 资产签名(MovieScene + 所有 Track/Section 的 GetSignature())+ 所有 BlueprintVisible CDO 属性值 + StageTransitionConfig + CompileCount + GeneratedClass 指针地址蓝图编译 / 参数变化

关键机制:

  • Track/Section 签名遍历:Sequencer 编辑仅更新 Section 签名,不更新父级 MovieScene 签名——指纹必须遍历所有子对象
  • CompileCount:每次编译递增的 uint32,确保 UE 编译实例重用时地址不变的情况下指纹仍然变化
  • 重烘焙冷却MinFramesBetweenRebakes = 3 帧,防止参数扫频时洪水重烘焙
  • 缓存失效职责在控件层 / 编辑器预览层(FTexturgePreviewBakeCache),编译期不负责缓存刷新策略

TIP

通过 SetBlueprintVariable 修改参数后,指纹变化触发重烘焙,但受 3 帧冷却约束——高频参数扫频时最多延迟 3 帧生效。

images/bake-pipeline-flow.png — 烘焙管线流程图:输入(蓝图 GlyphAnimations + 源文本)→ 第一阶段 BuildScheduleInfos(码点分类)→ 第二阶段 BuildDefaultAnimation 事件(Layer/Factory 编排)→ 第三阶段 Factory::Bake 三步管线(逐层烘焙 → 层微调 → 逐字形拷贝 → 工厂后处理)→ 第四阶段 FBakedAnimation 输出与双指纹计算 — Bake pipeline flow diagram: inputs (blueprint GlyphAnimations + source text) → stage 1 BuildScheduleInfos (code point classification) → stage 2 BuildDefaultAnimation event (Layer/Factory orchestration) → stage 3 Factory::Bake three-step pipeline (per-layer bake → layer tweaks → per-glyph copy → factory post-process) → stage 4 FBakedAnimation output with dual fingerprint computation