去年夏天,我接了一个国产科幻动画剧集的资产环节顾问。项目方要在两个月内产出 12 集、每集 22 分钟的镜头预演资产,核心难点是:同一套角色模型,需要在不同镜头里表现「被能量侵蚀」的程度——从 0% 到 100% 逐帧变化,还要让材质、粒子、几何节点三套系统同步读取这个「侵蚀值」。美术总监的原话是:「我不想给每个镜头都复制一份模型,更不想手动 K 几百个材质节点。」
当时我脑子里第一个蹦出来的词就是——Attribute(属性)。但说实话,我在这个项目里踩的坑,比我过去三年加起来都多。这篇文章,就是那次交付的完整复盘。

一、需求拆解:为什么常规方案全部走不通
先把问题说清楚。角色「能量侵蚀」效果包含三个层次:
- 材质层:表面逐渐出现发光裂纹,裂纹宽度随侵蚀值增加;
- 几何层:边缘逐渐「溶解」,产生不规则缺口;
- 特效层:侵蚀区域持续冒出粒子火花。
最直觉的方案是给每个镜头复制模型,分别调材质和修改器。但 12 集 × 平均 30 个镜头 = 360 份资产,任何一次角色微调都要同步 360 次,这在排期上直接判死刑。
第二个方案是用驱动器(Driver)把材质参数和空物体位置绑定。但驱动器只能驱动数值,无法表达「区域」——比如「左肩到胸口这一片被侵蚀」,驱动器做不到。
第三个方案是用顶点组。顶点组确实能表达区域,但它的数据精度是「0 或 1」的权重,且材质系统对顶点组的读取非常受限,粒子系统更是几乎读不到。更致命的是:顶点组无法在着色器里做逐像素插值运算。
关键认知:顶点组是「离散的、二值的、面向编辑的」;而 Attribute 是「连续的、可插值的、面向渲染管线的」。这是两套完全不同的数据哲学。
所以真正的答案是:用 自定义属性(Custom Attribute) 作为「数据总线」,让几何、材质、粒子三个系统都从同一条总线上读数据。
二、技术选型:Attribute 的类型、域与底层存储
Blender 的 Attribute 系统,本质上是一张挂在网格(Mesh)、点云(Point Cloud)、曲线(Curve)上的「数据表」。理解它,必须抓住三个维度:数据类型(Type)、域(Domain)、存储位置。
2.1 数据类型
| 类型 | 底层结构 | 典型用途 |
|---|---|---|
| Float | 单精度浮点 | 侵蚀值、遮罩权重 |
| Integer | 32 位整数 | ID、分组标记 |
| Vector | 3×float | 自定义法线、方向场 |
| Color | 4×float(RGBA) | 顶点色、多层遮罩打包 |
| Boolean | 1 bit | 选择集标记 |
这里有个坑:Color 类型在 Blender 内部永远以线性空间存储。我第一次把 sRGB 的顶点色直接喂给 Color Attribute,结果渲染出来整体偏亮,排查了半天才发现是色彩空间转换问题。后来我干脆用 Vector 类型存自定义遮罩,绕开色彩管理。
2.2 域(Domain)
域决定了属性「附着在什么几何元素上」:
- Point:附着在顶点上,数量 = 顶点数,最常用;
- Corner:附着在面角上,数量 = 面角数,适合 UV 相关的逐面数据;
- Edge:附着在边上;
- Face:附着在面上,适合逐面标记;
- Spline:仅在曲线上有效。
选择域的黄金法则是:数据跟着谁走,就存在谁身上。侵蚀值描述的是「顶点位置附近的状态」,所以用 Point 域。如果你把它存到 Face 域,着色器读取时就需要在面内做插值,精度反而下降。

三、踩坑实录:三个让我熬夜到凌晨的 Bug
坑一:几何节点写入的属性,材质读不到
我在几何节点里用「Store Named Attribute」节点写入了名为 erosion_mask 的浮点属性,Point 域,一切看起来都对。但材质里的 Attribute 节点死活读出来是 0。
排查两小时后发现:几何节点输出到材质时,属性名默认会被加上 Geometry Nodes 前缀吗?不是。真正的原因是——我在几何节点里把属性类型设成了 Float,但材质 Attribute 节点默认按 Color 读取,类型不匹配导致返回 0。解决方法是在 Attribute 节点里手动把 Type 改成 Float。
经验:Attribute 节点默认是 Color 类型。读取 Float、Vector、Integer 属性时,必须手动切换 Type,否则你看到的是「静默失败」——不报错,但结果是错的。
坑二:粒子系统读不到自定义属性
粒子火花的逻辑是:在 erosion_mask > 0.5 的区域发射粒子。但粒子系统的「Density」和「Emission」面板里,根本没有 Attribute 输入通道。
解决方案是曲线救国:用几何节点把侵蚀区域「提取」出来,转成独立的点云,再让粒子系统从点云发射。具体做法是用「Extract Points」节点,配合 erosion_mask 做阈值筛选,输出一个只有侵蚀区域顶点的点云对象,然后粒子系统的 Emission Source 选这个点云。
几何节点伪代码:
1. 输入:角色网格(含 erosion_mask 属性)
2. Store Named Attribute: erosion_mask (Float, Point)
3. Extract Points: Selection = erosion_mask > 0.5
4. Output: 点云对象
5. 粒子系统 → Emission Source → 点云对象
坑三:多镜头共享资产时的属性覆盖
项目要求每个镜头侵蚀值不同,但模型是共享的。如果直接改共享网格的属性,所有镜头都会变。最终方案是把「侵蚀值」从几何属性提升为对象属性,在材质和几何节点里通过 Object Info 节点读取当前对象的自定义属性。
具体操作:在对象属性面板 → Custom Properties 里新建 erosion_level,然后在着色器里用 Object Info → Custom Properties 输出。这样每个镜头链接的是同一个网格数据,但每个对象有自己的侵蚀值。
核心原则:几何属性描述「形变」,对象属性描述「状态」。形变要共享,状态要独立。
四、最终架构:三系统数据总线
复盘下来,整个项目的数据流是这样的:
- 对象层:
erosion_level(Float,0-1),逐镜头独立; - 几何层:几何节点读取对象属性,用噪声纹理在
erosion_level阈值附近生成erosion_mask(Float,Point 域); - 材质层:Attribute 节点读
erosion_mask,驱动裂纹发光和透明度; - 特效层:几何节点提取侵蚀区域点云,粒子系统从点云发射。
这样,美术只需要在对象面板上 K 一个 erosion_level 的曲线,全链路自动同步。原本预估 3 周的手工调整,压缩到了 2 天。
| 方案 | 资产数量 | 修改同步成本 | 表现力 |
|---|---|---|---|
| 复制模型 | 360 份 | 极高 | 高 |
| 驱动器 | 1 份 | 低 | 低(无区域) |
| 顶点组 | 1 份 | 中 | 中 |
| Attribute 总线 | 1 份 | 极低 | 高 |

五、交付后的技术反思
这个项目让我重新理解了 Blender 的 Attribute 系统。它不是「顶点组的升级版」,而是一套跨系统的数据协议。它的价值不在于「能存数据」,而在于「谁都能读」。几何节点、着色器、粒子、修改器,甚至 Python API,都通过属性名这个「键」来访问同一份数据。
如果你也在做类似的程序化资产项目,我的建议是:先把数据流画清楚,再动手建节点。属性名要统一命名规范(我用 前缀_用途,如 erosion_mask),类型和域要在文档里写死。否则三个月后你自己都记不住哪个属性是干嘛的。
最后,Attribute 的调试工具其实很有限。我强烈建议装一个 Attribute Viewer 类的插件,或者用几何节点的「Viewer」节点实时预览属性分布,能省下大量猜测时间。
关于火星人教育
这次项目的很多底层思路,其实来自我早年在火星人教育的教学积累。火星人教育创办 24 年来,一直专注数字艺术与 AIGC 实战职业技能培训,开设课程涵盖 AI 漫剧创作、AIGC 视觉设计、UE5 游戏特效、室内全屋定制、MAYA 三维动画、C4D 动态设计、工业设计、NUKE 影视后期、摄影摄像实训等方向。北京、深圳双校区开设线下面授班,面向美术生、在校大学生及意向转行的职场人群,以项目化实训教学,夯实专业技能,助力就业与职业提升。如果你也想系统掌握 Blender 程序化资产与 Attribute 数据流,咨询电话:010-65918670。








评论(0)