Layer Blend Modes

The ETextAnimationBlendMode enum defines how an FPerLayerCurves evaluation result merges into FGlyphAnimationState. Blending executes at channel granularity inside FPerLayerCurves::Evaluate; per-layer combination happens in FBakedGlyph::Evaluate.

The Four Blend Modes

Additive

Accumulates the current layer’s channel values onto the existing state:

OutState.Channel += LayerResult.Channel

Use cases:

  • Shake stacking (position shake + rotation shake)
  • Combining multiple animations (base translation animation + extra wave animation)
// Base layer sets the position, overlay layer adds random shake
BaseLayer:  PositionOffset.X = 100200   // left to right
ShakeLayer: PositionOffset.X = -5+5    // Additive → actual position = 95 → 205

Override

Directly replaces the existing state with the current layer’s channel values (commonly used for the base layer):

OutState.Channel = LayerResult.Channel

Use cases:

  • The first layer sets the baseline transform (CreateLayer(..., Override, ...) is the default implementation’s base layer mode)
  • A layer needs absolute control over specific channels

Multiply

Multiplies the current layer’s channel values with the existing state:

OutState.Channel *= LayerResult.Channel

Use cases:

  • Intensity modulation (global opacity envelope)
  • Fade in/out control (channel value 1.0 has no effect, 0.0 fully eliminates)

CrossFade

OutState.Channel = Lerp(OutState.Channel, LayerResult.Channel, 0.5f)

Per-Channel Evaluation Semantics

Internal flow of FPerLayerCurves::Evaluate(float LocalTime, FGlyphAnimationState& InOutState):

  1. Check all 21 channels one by one: empty channels (Keys.Num() == 0) are skipped directly, preserving the existing InOutState values
  2. Non-empty channels temporarily build an FRichCurve and interpolate at LocalTime
  3. Combine the value into InOutState according to this layer’s BlendMode:
for Channel in 21 non-empty channels:
    Value = CurveEval(Channel, LocalTime)
    switch BlendMode:
        Additive:  InOutState[Channel] += Value
        Override:  InOutState[Channel] = Value
        Multiply:  InOutState[Channel] *= Value
        CrossFade: InOutState[Channel] = Lerp(Out, Value, 0.5f)

NOTE

No cross-layer contamination: Layer[0] has ScaleX but no Opacity, Layer[1] has Opacity but no ScaleX → final ScaleX comes from Layer[0], Opacity from Layer[1].

Special Rules for Tint Channels

The identity value of the Color / ShadowColor channels is Transparent (0,0,0,0). Applying the multiply rule (0 × anything = 0) would permanently tint black, so tint channels use additive stacking (Base.Color += Layer.Color, consistent with the bake pipeline). Multi-layer tinting controls intensity through the A channel (tint share).

NOTE

Tint rendering uses Pigment-Based Mixing (PBM) physical pigment mixing (ApplyTint) — a real-time RGB pigment mixing algorithm built on Kubelka–Munk (K–M) two-flux theory with constants reference-calibrated against Mixbox 2.0: yellow + blue makes green, complementary pairs mix into muddy colors (olive/brown/dark purple) instead of gray, and deep colors lightened with white stay saturated. No look-up tables, O(1) constant time.

Multi-Layer Blend Example

Layer 0: Override   — set baseline position, scale, color
Layer 1: Additive   — add small position and rotation shake
Layer 2: Multiply   — global opacity envelope
Layer 3: Additive   — shadow offset animation

FBakedGlyph::Evaluate starts from FGlyphAnimationState::Identity() and combines layers in index order; FactoryOverride is applied once after all layers are blended.

Design Recommendations

TIP

The following recommendations help organize layer blending effectively:

  1. Use Override for the first layer to establish baseline values, avoiding accidental accumulation
  2. Use Additive for middle layers to stack auxiliary animations for independent adjustment
  3. Use Multiply for the last layer for global modulation such as uniform fades
  4. Watch out for multiply/divide traps on tint layers: the tint identity is (0,0,0,0), so Multiply/Divide on tint produces wrong results — use Additive stacking or control via the A channel (share)
images/blend-mode-comparison.png — Blend mode visual comparison: same glyph under four configurations (Override baseline, Additive shake stacking, Multiply opacity envelope, CrossFade interpolation), each annotated with the final state formula