一个44行的Python脚本发现Miles Morales在广告牌边缘外太空步了6.5米,而我和视口都没注意到。这引发了我对用脚本驱动Blender工作流的重新思考。

整个项目是一个约60秒的LiveReview解释视频。Miles在日落城市中荡网,每个滑动画面都是街头场景的一部分——墙板、屋顶广告牌、LED走字屏、公交站和地铁灯箱、大型街屏,以及最后失控的列车。所有场景都不是手动编辑的,而是由Python脚本在Blender 5.2中无头生成,用EEVEE渲染在4GB GTX 1650上。场景文件不是打开调整的,而是构建产物,像文件夹一样。每次修复都是代码改动加重建,工作流更像CI而不是艺术课。

渲染时间变长后,我问能否用GPU加速。答案有点尴尬:已经在用了。EEVEE是GPU光栅化器,类似游戏引擎渲染器。渲染时显卡占用约80%,显存2.2GB。没有闲置GPU可切换。另一个渲染器Cycles是路径追踪,在GTX 1650上会更慢。真正的问题不是CPU还是GPU,而是每帧在为什么付费。三个设置主导了开销:所以渲染脚本增加了一个标志来切换这三项。在360p下,每帧从约0.6秒降到约0.33秒,大约2倍。完整58秒电影有1760帧,审查渲染从约18分钟降到约10分钟。放弃的效果真实但无害:没有光线追踪反射,街道看起来更平;没有运动模糊,快速动作更清晰。审查“Miles是否落在窗台上”的人不在乎这些。最终渲染保留全部效果。

其他优化包括:270p选项用于最快的“阻塞是否正确”检查;联系表,在提交任何视频前以25%分辨率和8采样生成静帧条;部分重渲染,只从给定帧开始重新渲染并复用之前帧,用于只有结尾变化时。教训和性能分析慢API一样:在要求更大机器前,先弄清工作是什么。大多数时候你在为没人看的东西付费。

Blender有比预期更多的撤销层:Ctrl+Z和Ctrl+Shift+Z用于会话内撤销重做,编辑→撤销历史可跳回多步。文件→恢复可丢弃未保存更改并从磁盘重载。备份方面,每次保存都会在文件旁保留前一版本,偏好设置→保存与加载→保存版本数可设置数量。文件→恢复→自动保存用于Blender崩溃时。这些都很有用,但在这里不是重点。因为当场景是生成的,它不是真相来源,脚本才是。所以真正的快照按钮是git:如果场景从代码生成,对代码进行版本控制,而不是二进制文件。我在每轮审查后提交,这样任何展示过的版本都能精确重建。旧渲染视频也保留,因为有时想并排播放两个版本而不重建。

第一部分收到一条评论,比一些文档还好。两个提示:BVH文件为每个关节定义自己的欧拉旋转顺序,导入器不总是匹配,所以手腕和脚可能微妙地错误,即使臀部看起来正常。如果姿势几乎正确但手不对,在调整偏移前检查欧拉顺序。连续片段髋部高度不同,比如落地到待机或蹲下到站立,需要混合垂直偏移,否则角色会漂浮或下沉几帧,而水平路径正确对齐。第一个提示接近命中,第二个是我已有但没看到的bug。

BVH文件存储每个关节的旋转为三个角度,但也存储这些旋转的应用顺序,且每个关节不同。一个关节可以说Z然后X然后Y,下一个说Y然后X然后Z。这很重要,因为旋转不可交换。绕Z旋转30度再绕X旋转45度,与绕X旋转45度再绕Z旋转30度,朝向不同。相同数字,不同姿势。在骨架中,每根骨骼继承父级旋转,所以肩部小错误移动肘部,进而移动手腕,再移动手。错误沿链累积。臀部在树顶,看起来完美。手在底部,首先看起来“几乎正确但奇怪”。我检查了导入器调用,半期待最坏情况。它已经传递了“使用每个关节自己的顺序”,所以我们安全。但现在如果CMU片段出现奇怪手腕或脚踝,这是我列表上的第一件事,在任何人使用偏移黑客之前。

片段链接方式:每个片段大部分“原地”,所以为了让Miles实际移动,每个片段的运动加在上一个结束的位置上。这是一个累积和。这就是根运动,游戏引擎也用同样想法。每个人都记得混合路径的水平部分。第二个提示是垂直部分也需要混合。游戏引擎通常叫Y,Blender是Z向上。在平地上我从没看到问题。在抬高的表面上我正好有这个bug,只有测量才发现。探针很简单:在每个混合的每一帧,取最低脚趾和下方表面的间隙。目标约+5厘米,因为脚趾骨骼略高于鞋底。22厘米的弹跳是蜘蛛侠无故短暂悬浮。第一个修复按提示做了,但什么都没改变。我在混合窗口内平滑缓动根高度,而不是让它阶跃。数字几乎没动。测量显示了原因。臀部不是问题。交叉淡入淡出的姿势才是。蹲下和站立姿势中间,混合的腿半伸展,所以脚自己上升,无论臀部移动多轻柔。髋部高度的平滑曲线无法跟随腿的动作。有效的是停止预测,开始测量。对于抬高镜头的每一帧,找到最低脚趾,移动根使脚趾固定在表面上方一定距离。然后轻微平滑以免抖动。这就是实际函数,稍微修剪过。那里有承重。没有上限,一个真正在结尾跳起的片段被“纠正”了8米。函数看到脚在空中,决定地板一定很远,把整个角色拖过去。±35厘米的钳制意味着真正的跳跃保持跳跃。是的,“从后往前删除”的注释是伤疤。

一旦探针每帧打印一个数字,数字开始指向我在视口永远不会看到的东西。第一个bug:Miles在18米高,恰好一帧。早期地板锁定过程每隔一帧设置根高度关键帧,中间线性插值。在单个镜头内完全合理。在硬切之间就很糟糕。从屋顶到街道的切点,两个关键帧之间的帧落在两个镜头中间。线性插值平均了“屋顶”和“街道”,把Miles放在街道上方约18米,一帧。30fps下是33毫秒的故障。播放视频永远不会发现。探针立即标记。修复:在这些范围内每帧设置关键帧,没有中间帧可插值。

第二个bug:分数帧上的硬切。片段以浮点帧调度,因为混合长度和速度是计算的。所以“硬切”可能从帧1037.4开始。Blender渲染整帧,意味着帧1037显示新位置上的旧姿势。一个镜头的一半粘在另一个上。修复:硬切用四舍五入到整帧,镜头的最后一帧是减一。这是动画版的差一错误,藏在浮点数里。

第三个bug:迭代时删除关键帧。代码收集要替换的关键帧引用,然后循环调用删除。但删除一个关键帧会移动之后所有索引。所以列表中的下一个引用现在指向它的邻居,循环静默删除了错误的键。两个镜头的第一帧最终没有高度键。和Python列表中迭代时删除项一样的bug,只是穿了3D外衣。修复是上面的片段:收集索引,然后从后往前删除。整个部分的教训不是关于任何一个bug。而是第一个修复理论上正确但实践无效,只有每帧的数字才能告诉我。不要目测根运动,测量它。

一旦脚部探针存在,只在怀疑问题时运行似乎很傻。所以它变成了永久预渲染检查,约40行,回答一个问题,采样电影每三帧:他脚下有东西吗?它使用Blender的,忽略角色或道具的一部分(网不是地板),跳过应该空中的时刻,将标记帧分组为运行,输出像测试报告。每次渲染前运行,就像部署前运行测试。它抓住了视觉检查完全错过的。太空步:开头那个。Miles在广告牌框架外太空步了6.5米,而且方向与背景中走出屏幕的顾客相反。修复分两部分:屏幕下方钢制走道,因为真实广告牌有维护通道。太空步现在与顾客同向,是更好的笑话。25厘米板上40厘米站姿:他站在顶面25厘米深的墙板上。站姿40厘米。一只脚悬在前方,站在空气中。修复是板上1.25米服务边缘。一个转弯把他甩离边缘2米:这是最喜欢的,因为原因纯几何。原地片段的根枢轴离臀部很远。这里约4米。所以当混合旋转根让他转150度时,这放大了任何位置误差。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐