BACK TO ARCHIVE
pipeline dcc mechanics houdini nuke unreal real-time unity

从 $F4 到 {FrameID:4}:序列帧命名规则的演变

04.23.2026 ADUCG RESEARCH

背景:同一件事,不同写法

在 CG 制作里,“序列帧命名”看起来只是一个小细节,但它会贯穿渲染输出、合成读写、缓存交换、以及自动化发布等很多环节。
只要你做过跨软件协作,就一定遇到过类似下面这种路径:

example.txt
shot010_lgt_v003.0001.exr
shot010_lgt_v003.0002.exr
...

为了让软件在输出或读取时能自动替换帧号,不同工具演化出了不同的“占位符”语法:Houdini 常见 $F4,Nuke 常见 ####,而 Unity Recorder 之类的工具又会写成 {FrameID:4}

下面按三种典型写法把来龙去脉理一遍。


三类常见占位符

1) $F / $F4:Unix 变量习惯的延续

  • 代表:Houdini(HScript)、部分 Maya/渲染器工具链等
  • 常见写法$F$F4$T

这类写法非常像 Shell 里的环境变量:用 $ 引用变量值。
$F4 为例,可以理解成“帧号(Frame)以 4 位输出,不足补 0”。

houdini-output.txt
shot_v01.$F4.exr  ->  shot_v01.0001.exr

它的优点是简洁,缺点是“变量边界”不总是显式的:当字符串拼接得很复杂时,读起来不如后面两种直观。

2) #### / %04d:更“可视化”的占位

  • 代表:Nuke、Maya、3ds Max、Deadline 等
  • 常见写法####%04d

#### 是一种非常直观的写法:一个 # 代表一位数字,占位位数一眼就能看出来。

%04d 则来自 C 语言 printf 风格的格式化字符串:0 表示补零,4 表示宽度,d 表示整数。

printf-style.txt
out_%04d.jpg  ->  out_0001.jpg

3) {FrameID:4}:更偏“软件工程”的格式化

  • 代表:Unity(Recorder)、Unreal Engine(MRQ/工具链中常见)
  • 常见写法{FrameID:4}{frame_number}

这种写法更接近现代语言的“格式化/插值”机制:用大括号明确标出变量边界,并允许在冒号后附带格式规则(比如位宽、日期格式等)。

unity-recorder.txt
Shot_{FrameID:4}.png  ->  Shot_0001.png

快速对比表

工具语法示例逻辑分类核心驱动
Houdinishot_v01.$F4.exr变量表达式HScript
Nukeshot_v01.####.exr视觉通配符C-style / TCL
UnityShot_{FrameID:4}.png复合格式化C# / .NET
UE5{sequence_name}_{frame}键值对映射Python / C++
FFmpegout_%04d.jpg格式化说明符C Library

为什么 {} 写法越来越常见

$F4#### 本质上都是“约定式”的替换规则:短、快,但也更容易在复杂字符串里产生歧义。
{FrameID:4} 这类写法的优势主要在两点:

  1. 边界清晰:变量从 {} 一眼就能确定范围,减少误判。
  2. 更容易扩展:同一个机制可以支持更多格式化能力,而不需要反复新增解析规则。
Note

在做跨软件自动化时,建议把“占位符解析”当成一个独立的能力做成小模块(哪怕只有几十行),不要在各个脚本里散落一堆字符串替换。


TA / Pipeline 对接时的常见坑

1) 起始帧:1-based vs 0-based

有些工具默认第一帧是 1,有些场景会出现从 0 开始的序列。占位符解决的是“格式”,不是“帧范围”,这两个概念不要混在一起。

2) Padding:到底要 4 位还是 5 位

电影管线里 4 位是常见默认值,但长片/长剧也会遇到 5 位。
不要把位宽写死在脚本里,尽量做到从配置或上游信息读取。

3) 占位符识别:别只做字符串 replace

如果你要在中间层统一处理多种语法,建议用正则先“识别类型”,再做标准化。

frame_placeholder.py
import re
 
PATTERNS = [
    ("houdini", re.compile(r"\\$F(?P<padding>\\d+)?")),
    ("hashes",  re.compile(r"(?P<hashes>#+)")),
    ("printf",  re.compile(r"%(?P<padding>\\d+)d")),
    ("braces",  re.compile(r"\\{\\s*(?P<name>[A-Za-z_][A-Za-z0-9_]*)\\s*:\\s*(?P<padding>\\d+)\\s*\\}")),
]
 
def detect_placeholder(s: str):
    for name, p in PATTERNS:
        m = p.search(s)
        if m:
            return name, m.groupdict()
    return None, {}

结语

序列帧占位符的写法没有绝对的“好坏”,更多是各个时代、各类工具链的选择与历史包袱。
对我们做流程的人来说,关键不是站队哪一种,而是:能识别、能转换、能在团队里形成一致的约定

本文采用 Creative Commons BY-NC-ND 4.0 协议进行授权。

BY-NC-ND: 署名-非商业性使用-禁止演绎

End of Article