关于 scenario-blender-hair
面向使用 Blender 5.2 制作毛发和皮毛的艺术家与 Agent,涵盖曲线梳理、渲染毛发及游戏用发片。通过引导曲线、遮罩程序化层和质量检查组织工作流程。
主要功能
- 01规划并放置毛发引导曲线
- 02构建遮罩程序化梳理层
- 03检查 UV、根部、间距和密度
- 04审核全密度渲染与姿势
- 05将梳理曲线转换为游戏发片
适用场景
- 01制作带有毛簇和杂毛的动物皮毛
- 02围绕分线塑造人类发型
- 03检查变形时的毛发效果
- 04准备游戏导出的毛发片
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ scenario-labs/ skills --skill scenario-blender-hair安装后在 Claude Code 中输入/scenario-blender-hair确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/critique.mdexpert-notes.mdprocedures.mdsources.mdscripts/bx_hair.py详细介绍
毛发梳理(毛发曲线,Blender 5.2)
专业的梳理只需少量间距均匀的引导曲线来确定方向和轮廓。叠加使用经过遮罩的程序化图层,增加密度、成束效果、毛躁感、长度变化和散毛,并在完整密度和摆好姿势的光照渲染中进行评估。在 5.2.1 中,将引导曲线编写为数据(无法通过脚本绘制曲线笔刷笔触),并通过 Essentials 毛发节点组资产做出所有外观决策。如果这里提到的同级技能不在可用技能中,请要求用户安装它
(npx skills add scenario-labs/skills --skill <name>);无人值守时,根据工具架构继续操作,并标明缺失的技能。
必备背景: scenario-blender-expert(执行渠道、审查循环、5.2 API 变更)。
原则(专家要点)
- 方向正确之前,每条引导曲线只设 2 个点(Schmidbauer)。更多点会导致梳理缠乱和穿插。短毛用 3 个点,长毛才用 8 个点。Bystedt 会在塑形前将长的人类头发重新采样为 15 个点。
- 引导曲线均匀分布,并始终为 Interpolate 添加遮罩(Schmidbauer)。子毛会生成在整个表面上,而远离任何引导曲线的子毛会“自行其是”。间隙会形成秃斑,并在变形时变得明显。
- 密度按平方米计数,因此数值必须很大(Thommes)。长毛使用 1,000,000/m²,半径约为 0.2 mm。工作时将 Viewport Amount 设为 4%。
- 按曲线随机化,而不是按点随机化(Thommes)。让 80% 的毛束没有毛躁感。散毛占曲线的 10%,并使用强噪波。
- 关闭 Replace Length,并使用 Random Offset 进行修剪(Schmidbauer、Thommes)。这是成本最低的真实感提升方法。为其添加遮罩,否则短毛区域会失去引导曲线,变得光秃。
- 在人类发型中,沿分缝将头发种植在独立的发帽上(Bystedt):这样就不必在意身体网格拓扑,也能避免插值跨越分缝。分缝处仍需要更高密度并向上提起。
- 所有资产的塑形方式都相同:1 只影响末端,0 影响整根毛发,负值影响根部。使用负值 Frizz Shape,保留卷曲和成束的末端(Thommes、Bystedt)。
- 在完整密度和摆好姿势的情况下评估渲染(Thommes、Schmidbauer)。网格的剧烈变形会让毛发“看起来糟十倍”。
首先确认
| 输入 | 影响 | 未说明时的默认值 |
|---|---|---|
| 目标 | Cycles 毛束与 EEVEE 或游戏卡片之间的选择 | Cycles 毛束(Principled Hair BSDF) |
| 对象 | 人类发型(发帽、分缝)与动物毛发(身体网格、标志点) | 遵循参考资料 |
| 长度类别 | 每条引导曲线的点数、成束和卷曲图层 | 短毛:3 个点,长毛:8 个点,长的人类头发:15 个点 |
| 变形 | 动画场景意味着 Hair Dynamics (Animation) 保持在最后,并需检查摆姿效果 | 动画 |
| 预算 | 渲染曲线、卡片三角形 | 说明数量:完整密度下的求值曲线数 |
| 参考资料 | 同一亚种、同一季节,最好是同一个体(Schmidbauer) | 主动索取;先绘制标志点并确定毛发走向 |
梳理前先完成拓扑和无重叠 UV(毛发会绑定到 UV 空间),并应用缩放: 密度按网格的局部面积计算(已验证)。
工作流
| 任务 | 无头模式 | 实时会话 |
|---|---|---|
| 曲线对象、表面绑定、作为数据的引导线、吸附、细分或重采样、资产修改器、输入、遮罩、计数、渲染、卡片 | 是(scripts/bx_hair.py) | 是,在对象模式下使用相同代码 |
进入雕刻曲线模式、激活 11 种曲线笔刷、设置笔刷参数、sculpt_curves.select_random | 不适用 | 是(bx_gui.activate_brush("SCULPT_CURVES", "Comb")) |
| 梳理、添加、密度、增长/收缩、拉动尖端笔触 | 否 | 否。sculpt_curves.brush_stroke 没有 exec,因此 bx_gui.stroke("SCULPT_CURVES", ...) 会返回 PASS_THROUGH,且不做任何更改。设置好笔刷后,请用户进行笔触操作,或使用以下替代方案。 |
- 准备表面。
H.uv_report(surface):UV 重叠会导致曲线忽略绑定;UDIM 没问题;未合并的镜像接缝需要使用 UV Merge by Distance(Schmidbauer)。人体模型:沿分缝将发帽拆分为多个岛。动物模型:将口腔内部单独分离。蒙版:使用顶点组(速度快)或纹理(适用于低多边形网格的细节)。关卡:overlap_faces == 0,已应用缩放,每个区域使用一个蒙版。 - 规划流向和对象。 将绘制稿转成区域(顶点组),每个区域指定一个流向。Schmidbauer:每个特征部位(脸部、脸颊、耳朵、脖子、身体、尾巴、脚)使用一个曲线对象,以提升性能。Thommes:使用一个带蒙版的对象。Bystedt:将前后部分拆开,避免梳理一侧时扰动另一侧。关卡: 写下区域、流向和对象的清单。
- 引导曲线。
hair = H.new_groom(surface),P, N = H.sample_roots(surface, spacing, mask=...),H.add_guides(hair, P, N, length, points=2, flow=..., flow_blend=...)(吸附并写入surface_uv_coordinate)。之后可使用H.subdivide_guides或H.resample_guides(hair, 15)。画笔的替代方式:使用方向场实现梳理,用droop实现重力效果,通过删除并重新添加引导曲线实现密度和添加效果;Bystedt 的“先长长地生长,梳理,再缩短”可通过长引导曲线加 Trim 实现。关卡: 在H.groom_report中,roots_off_surface和spacing_gaps_2x均为 0,且length_outliers为 0。仅显示引导曲线的渲染应能清楚呈现每个方向。 - 密度。 先添加 Set Hair Curve Profile,再添加带 Density Mask 的 Interpolate Hair Curves。Bystedt 在分缝处绘制 50% 灰色和 100%,然后重新映射。5.2 版的 Interpolate 也支持 Mask Texture 图像。关卡:
H.count(hair)(完整密度)在预算范围内,root_area_fraction远低于 0.5,且渲染中没有秃斑。 - 程序化图层,每层都带蒙版,并通过切换
show_viewport和渲染进行 A/B 测试。- 长毛(Thommes,预设
H.FUR_STACK_LONG,通过H.build_stack应用):Noise、Curl、strays、使用负形状的 Frizz、Clump、Clump 2、最后的 Frizz(关闭 Cumulative)、Trim、Rotate。 - 动物(Schmidbauer):Trim、Clump(脸颊、Tip Spread、Clump Offset)、低强度 Frizz,每项都使用自己的顶点组。
- 人体(Bystedt):在引导曲线上使用 Noise(关闭 Cumulative,开启 Offset per Curve),仅在尖端使用 Roll,然后对梳理后的引导曲线按群体应用 Interpolate、Profile、Clump;宽、细图层需要使用不同种子。
- 每条曲线的随机性:
H.random_curve_mask(hair, "frizz_mask", 0.8),然后输入"attr:frizz_mask";从根部到尖端的渐变:H.along_curve_attr。关卡: 每个图层都必须在 A/B 渲染中证明其价值。
- 长毛(Thommes,预设
- 分缝和发际线(人体)。 保持开启 Part by Mesh Islands(默认):测试中,关闭岛屿时,分缝附近有 21% 的子曲线越过分缝;开启时为 0%。对于隆起效果,
H.distance_weights(cap, "part_lift", "part", radius, "bump")输入 Displace Hair Curves(开启 Surface Normal,距离设小,Shape 接近 0,Factor 为attr:part_lift)。关卡: 顶视图中分缝处没有头皮空隙,头顶也不扁平。 - 着色和渲染。 使用
H.hair_material(name, "CYCLES", melanin=(root, tip)),或使用"EEVEE"并指定颜色,然后使用H.setup_render(scene, engine, close_up)。关卡:H.review([surface, hair], out, engine)通过references/critique.md中的标准。 - 形变。 设置 2 或 3 个极限姿势的关键帧,并使用同一摄像机渲染每一帧。关卡: 所有姿势下都没有秃斑或穿插。
- 游戏:卡片。 运行
H.add_cards(hair, width, material=H.card_material(...)),然后转换副本object.convert(target='MESH')并导出 glTF。在卡片下方放置带纹理的发帽。关卡: 三角形数量在预算范围内,卡片 UV 位于半个图块内,发根位于头皮上。
数值
| 项目 | 数值 | 对应来源 |
|---|---|---|
| 引导点 | 放置时为 2 个,短毛为 3 个,长发为 8 个 | Schmidbauer [00:20:24],[00:21:34] |
| 长人发 | 重采样为 15 个点 | Bystedt [00:08:38] |
| 添加笔刷长度 | 毛发约为 0.01 m(默认值为 0.3 m) | Schmidbauer [00:17:59] |
| 密度,人类图层 | 每束群组每 m² 5,000 根 | Bystedt [00:17:28] |
| 密度,长毛 | 1,000,000/m²,半径 0.2 mm,视口显示 4% | Thommes [00:09:02],[00:16:44] |
| 密度贴图 | 分缝处 1.0,底部 0.5,秃斑处 0;渐变黑色标记略高于 0.5 | Bystedt [00:22:04],[00:23:46] |
| 宽发束 | 形状约为 0.3,减小 Factor,梳理引导线 | Bystedt [00:19:47] |
| 卷曲 | 半径 2 cm,Curl Start 1,Random Offset 0.5,Guide Distance 1 cm,Guide Mask 0.2 | Thommes [00:12:04],[00:14:18] |
| 子发束 | Guide Distance 1 mm,Mask 0.1;第 2 层:4 mm、0.25、Clump Offset 5 mm | Thommes [00:16:12],[00:31:01] |
| 毛躁 | 2 mm,负向 Shape;最终值:-0.2,关闭 Cumulative,应用于 20% 的曲线 | Thommes [00:15:04],[00:32:08],[00:26:03] |
| 杂乱发丝 | 占曲线的 10%,Noise distance 5 mm | Thommes [00:30:28] |
| 修剪、旋转 | 关闭 Replace Length,Random Offset 5 mm;Rotate 随机旋转 5° | Thommes [00:22:20] |
| 发片细分 | 每 20 cm 发片长度细分 1 次 | Bystedt g_ZIOafq4QU [00:05:08] |
| 发片和管状剖面 | 弧形:平面 1 段,推荐 3 段;管状:平面螺旋,分辨率 6,1 圈,高度 0 | Matsumoto [00:19:54],[00:22:40] |
| 发片 UV 宽度 | 发片使用管状物 UV 瓦片宽度的一半 | Matsumoto [00:31:27] |
| 覆盖率合理性检查 | 密度 × π × 半径²:Thommes 的毛发为 0.13 | [added] check |
质量门槛
代码中:
import sys; sys.path.append("<skills>/scenario-blender-hair/scripts"); import bx_hair as H
print(H.uv_report(surface)) # overlap_faces == 0
rep = H.groom_report(hair); print(H.verdict(rep)) # [] or a list of fixes
print(H.count(hair)) # full-density curves and points vs budget
print(H.card_stats(hair)) # cards: tris, UV range
groom_report 会检查附着 UV、根部是否位于表面上、间距空隙、长度凹陷(与相邻发丝相差超过 50%)、Hair Dynamics 是否位于最后、Trim Replace Length、未遮罩的 Frizz 或 Noise、缺失的 Density Mask、半径与密度、Strand 显示,以及 EEVEE 下的 Cycles 毛发着色器。
视觉检查:H.review(objs, out, engine, views=("front", "threequarter", "top")) 使用 Thommes 的布光方案(暖色顶部补光、侧面主光、冷色轮廓光、深蓝灰色世界背景),渲染一张全密度受光效果图。根据 references/critique.md 评分:秃斑、分缝覆盖、发束、杂乱发丝、长度变化、发梢、从根部到发梢的颜色、高光。
常见错误
| 错误 | 表现 | 修复方案 |
|---|---|---|
| 阻挡时画笔默认大小为 8 点 | 缠结、穿插、梳理时相互干扰 | 设为 2 点,之后再调大(Schmidbauer) |
| 未使用密度遮罩进行插值 | 毛发长进嘴巴或眼睛,子发束远离引导发束 | 使用顶点组遮罩;分离嘴部内部区域 |
| 引导发束间距不均 | 出现秃斑,摆姿势后更严重 | 检查 sample_roots 间距;检查 spacing_gaps_2x |
| 默认半径下密度过高 | 毛发变成实心团块或“一团糟” | 先调整轮廓;密度为 1M/m² 时约为 0.2 mm(Thommes) |
| 使用默认设置修剪 | 每根发束都变成 1 m 长 | 关闭 Replace Length(Thommes) |
| 在卷发或发束上使用正向毛躁 | 卷发末端被破坏,发束末端变得毛糙 | 使用负向 Shape;对最后一个 Frizz 关闭 Cumulative Offset |
| 使用相同种子复制图层 | 宽发束和细发束以相同方式堆叠 | 更改种子和频率(Bystedt) |
| 发梢笔直 | “糟糕的 CG 头发” | 仅在发梢使用 Roll Hair Curves(Bystedt) |
| 梳理后编辑了表面 | 发根悬浮或陷入表面 | 使用 snap_to_surface(hair, "DEFORM");拓扑更改后则使用 NEAREST |
| 仅以 4% 视口密度进行评估 | 未发现发束均匀、长度一致的问题 | 使用完整密度渲染 |
| 在 EEVEE 中使用 Principled Hair BSDF | 颜色暗淡扁平(测得中心亮度为 0.09,而非 0.26) | 使用带有显式颜色的 Principled BSDF |
| 通过脚本执行梳理笔触 | 没有任何效果,返回 PASS_THROUGH | 写入位置,或将笔触交给用户 |
Blender 5.2 说明
- Empty Hair 和 Quick Fur 会添加一个固定的 Hair Dynamics 修改器(模式为
Animation或Physics (Experimental))。它取代了旧的 Surface Deform,并使根部随变形表面移动(已验证形态键和骨架)。请将其保留为最后一个修改器。 - Interpolate 没有 Surface 或 UV Map 输入;它会读取曲线对象的表面。新增输入包括 Part by Mesh Islands、Follow Surface Normal、Mask Texture 和 Resting Surface(开启)。开启 Resting Surface 后,Thommes 的“Mask modifier on the surface”补丁预览不会生效。请改用补丁顶点组作为 Density Mask,或在预览时关闭 Resting Surface。
- 使用
md.properties.inputs.<id>.value或.type = 'ATTRIBUTE'设置修改器输入。在无头模式下,求值结果会保持过期状态,直到调用obj.update_tag()。H.set_inputs会为你解析 UI 名称并添加标签。后台求值会应用 Viewport Amount,因此请使用H.count(full=True)。 - 笔刷是资产:Snake Hook 对应 Pull Tips,Selection Paint 对应 Select;Add 默认添加 8 个点,间距为 0.3 m;设置位于
brush.curves_sculpt_settings。 - 出厂设置中的
render.hair_type为STRAND:请使用STRIP,并将hair_subdiv >= 1。Cycles 的形状包括:RIBBONS、THICK、THICK_LINEAR。“Curves Info”是ShaderNodeHairInfo;Principled Hair 提供 Chiang 和 Huang。Hair Curves Noise 会写入resolution12,自 5.0 起曲线渲染时会采用该值。 - 发片:Curve to Mesh 有一个 Scale 输入(4.5),Arc Resolution 用于统计点数。EEVEE 的 Alpha 使用
surface_render_method = 'DITHERED'。读取obj.evaluated_geometry().mesh时,请确保 GeometrySet 仍然有效。 - Quick Fur 的密度会随对象大小缩放(直径 10 cm 的球体使用 MEDIUM 时:80,220/m²,每条引导曲线 12 个点)。
参考资料
references/expert-notes.md:用于辅助判断,按专家分类并附有时间戳(人类发型、动物皮毛、程序化皮毛、发片、网格毛发)。references/procedures.md:编写代码前请先阅读。该文件包含完整且经过测试的 P1 至 P8 流程,以及bx_hair调用对应的原始 bpy 写法。references/critique.md:审查渲染结果或报告时请加载,作为评分标准。references/sources.md:用于引用或重看来源,并附有最佳时间戳。scripts/bx_hair.py:工具包(已在 5.2.1 上测试)。测试位于tests/code/blender-hair/。