From a1f52de801954c5dea1baae6b8792965b2207144 Mon Sep 17 00:00:00 2001 From: rui40000 Date: Tue, 18 Aug 2026 15:44:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Unmult=20=E5=A2=9E=E5=8A=A0=E3=80=8C?= =?UTF-8?q?=E4=B8=BB=E4=BD=93=E4=BF=9D=E6=8A=A4=E3=80=8D=E5=BC=80=E5=85=B3?= =?UTF-8?q?=EF=BC=8C=E6=8E=A7=E5=88=B6=E6=98=AF=E5=90=A6=E9=87=87=E7=BA=B3?= =?UTF-8?q?=20subject=5Fmask?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit subject_mask 的合并逻辑此前已存在,本次补上开关: - 启用(默认):alpha 取 max(unmult 结果, subject_mask) - 关闭:即便上游已连线也完全不采纳,等同纯 Unmult 想对比「有无 AI 介入」的差别时拨开关即可,不必拔线。 兼容旧英文名 use_subject_mask。 顺带把黑点/白点 tooltip 里的「(主体保护)」字样去掉 —— 那是描述 参数作用的措辞,和新开关撞名会让人以为是同一功能。 实测(黑底 + 暗色实体,alpha 真值恒为 1,下半近纯黑): 不接 mask 暗部 alpha 0.080 ← 黑衣黑发被扣穿 接 mask + 保护启用 暗部 alpha 1.000 接 mask + 保护关闭 暗部 alpha 0.080,与不接 mask 逐元素一致 即开关确实起作用,且关闭时行为与纯 Unmult 完全等价。 这同时解掉了上次记录的适用边界:黑底 unmult 本质是拿亮度当不透明度, 对实体不成立;现在接一路语义抠图的 alpha 进 subject_mask 即可破解 —— 语义模型负责「哪里是主体」,Unmult 负责「边缘有多透」。README 已按此改写。 Co-Authored-By: Claude Opus 4.8 --- README.md | 26 ++++++++++++++++---------- unmult_node.py | 23 ++++++++++++++++++++--- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index df95adf..a15dffa 100644 --- a/README.md +++ b/README.md @@ -1203,6 +1203,8 @@ Unity/Godot 归一化 pivot(左下为原点):(0.5411, 0.0699) - `bg_color` (STRING): 要去除的背景色,`#RRGGBB`。常用 `#000000` / `#FFFFFF` / `#00FF00` / `#FF00FF` - `黑点` (FLOAT, 滑块): 低于此值的 alpha 归零,用于清除背景残留噪点。调太高会丢边缘细节 - `白点` (FLOAT, 滑块): 高于此值的 alpha 归一,用于让主体更实。调太低会让边缘硬化 +- `主体保护` (BOOLEAN): 是否采纳 `subject_mask`。**关闭时即便已连线也完全不采纳**,等同纯 Unmult——想对比「有无 AI 介入」时拨这个开关即可,不必拔线 +- `subject_mask` (MASK, 可选): 接抠图节点输出的 alpha,节点执行 `max(unmult_α, subject_mask)` 合并 后两项用中文参数名 + `display: slider`,界面上就是两条滑块,与 LayerStyle 的 BiRefNet Ultra 观感一致。函数内部用 `**kwargs` 接收(中文名不能直接做函数形参),并兼容旧的 `alpha_low`/`alpha_high` 调用。 @@ -1218,20 +1220,24 @@ Unity/Godot 归一化 pivot(左下为原点):(0.5411, 0.0699) 数学上是精确解,三种底色都能完美还原。 -**⚠ 适用边界(很重要,选错底色会翻车)**: +**⚠ 适用边界与破解办法**: -| 素材 | 结果 | -|:-----|:-----| -| 光效、火焰、烟雾、粒子、UI 特效 | ✅ 最佳选择,半透明层次完整保留 | -| 绿幕/品红等**与素材反差大**的底色 | ✅ 实体素材也能扣干净 | -| **黑底 + 暗色实体** | ❌ **暗部会被当成背景扣掉** | +| 素材 | 纯 Unmult | 接 `subject_mask` 后 | +|:-----|:---------|:--------------------| +| 光效、火焰、烟雾、粒子、UI 特效 | ✅ 最佳选择,半透明层次完整保留 | 一般不需要 | +| 绿幕/品红等**与素材反差大**的底色 | ✅ 实体素材也能扣干净 | 一般不需要 | +| **黑底 + 暗色实体** | ❌ **暗部会被当成背景扣掉** | ✅ **已解决** | -实测同一个实体素材(alpha 真值恒为 1): +实测同一个实体素材(alpha 真值恒为 1,下半部分接近纯黑): -- 黑底:亮部还原 alpha 0.900,**暗部只有 0.080** —— 黑头发、深色衣服、鞋子会被扣穿 -- 绿幕:亮部 0.900,**暗部 0.940** ✓ 正常 +| 条件 | 暗部还原 alpha | +|:-----|-------------:| +| 黑底,不接 mask | **0.080** ← 黑头发、深色衣服、鞋子被扣穿 | +| 黑底,接 mask 且「主体保护」启用 | **1.000** ✅ | +| 黑底,接 mask 但「主体保护」关闭 | 0.080(与不接完全一致,开关确实生效)| +| 绿幕,不接 mask | 0.940 ✓ 本来就正常 | -原因是黑底 unmult 本质在用"亮度当不透明度",这对发光物成立,对实体不成立。**所以黑底的角色素材请用 [Lucida](#23-lucida-抠图--lucida-matting) 或 [FeyNobg](#22-feynobg-抠图--feynobg-matting) 这类语义抠图**,unmult 留给光效。 +根因是黑底 unmult 本质在用"亮度当不透明度",这对发光物成立、对实体不成立。**破法是接一路语义抠图(Lucida / FeyNobg / BiRefNet)的 alpha 进 `subject_mask`**:主体区域强制不透明,主体之外仍走 Unmult 的精确半透明。两者各取所长——语义模型负责"哪里是主体",Unmult 负责"边缘有多透"。 **四个抠图节点怎么选**: diff --git a/unmult_node.py b/unmult_node.py index 351d9c3..bed4f7f 100644 --- a/unmult_node.py +++ b/unmult_node.py @@ -11,6 +11,7 @@ AI 主体保护(可选):接入 BiRefNet / FeyNobg 等抠图节点输出的 subject_mask, 用 max(unmult_α, subject_mask) 合并,防止主体中与背景色相近的区域被误判为透明。 +该合并由「主体保护」开关控制,关闭时即便连了 mask 也不采纳。 典型场景:黑底光效中人物穿黑衣 → 纯 Unmult 会让黑衣半透明, 接入主体 mask 后黑衣区域强制保留为不透明。 @@ -96,15 +97,27 @@ class RuiUnmult: "黑点": ("FLOAT", { "default": 0.0, "min": 0.0, "max": 0.5, "step": 0.01, "display": "slider", - "tooltip": "黑点(主体保护):低于此值的 alpha 强制归零。\n" + "tooltip": "低于此值的 alpha 强制归零。\n" "调高可清除背景残留噪点,但过高会丢失边缘细节。" }), "白点": ("FLOAT", { "default": 1.0, "min": 0.3, "max": 1.0, "step": 0.01, "display": "slider", - "tooltip": "白点(主体保护):高于此值的 alpha 强制归一。\n" + "tooltip": "高于此值的 alpha 强制归一。\n" "调低可让主体更实、减少半透明损失,但过低会让边缘硬化。" }), + "主体保护": ("BOOLEAN", { + "default": True, + "label_on": "启用", + "label_off": "关闭", + "tooltip": "是否采纳下方接入的 subject_mask。\n\n" + "启用:alpha 取 max(unmult 结果, subject_mask),\n" + " 主体区域强制不透明,光效边缘仍保留半透明。\n" + " 黑底素材里的黑衣、黑发全靠它保住。\n\n" + "关闭:即便已经连了 subject_mask 也完全不采纳,\n" + " 等同于纯 Unmult。想对比「有无 AI 介入」的差别时,\n" + " 拨这个开关即可,不必拔线。" + }), }, "optional": { "subject_mask": ("MASK", { @@ -114,7 +127,7 @@ class RuiUnmult: "主体内部强制不透明,光效边缘保留半透明。\n\n" "典型场景:黑底光效中人物穿黑衣 →\n" "纯 Unmult 会让黑衣半透明,接入主体 mask 后黑衣保留。\n\n" - "不连接时等同于纯 Unmult,无 AI 介入。" + "不连接、或上方「主体保护」关闭时,等同于纯 Unmult,无 AI 介入。" }), }, } @@ -129,7 +142,11 @@ class RuiUnmult: bg_color = kwargs.get("bg_color", "#000000") alpha_low = kwargs.get("黑点", kwargs.get("alpha_low", 0.0)) alpha_high = kwargs.get("白点", kwargs.get("alpha_high", 1.0)) + # 主体保护开关:关闭时彻底忽略 subject_mask,哪怕上游已经连线 + use_subject = kwargs.get("主体保护", kwargs.get("use_subject_mask", True)) subject_mask_tensor = kwargs.get("subject_mask", None) + if not use_subject: + subject_mask_tensor = None bg_rgb = _hex_to_rgb01(bg_color)