跳转至

name: liltoon-dissolve-material-batch category: gaming description: Batch-configure lilToon dissolve parameters on multiple clothing materials remotely via Unity MCP. Covers the exact property values, scene-based material discovery, and solving stubborn materials via direct file editing. trigger: User asks to configure dissolve on many clothing materials, or materials from external packages (choco/, AyuElla/, Rest/) aren't responding to SetFloat/SetVector


lilToon Dissolve 批量材质配置

ges (choco/, AyuElla/, Rest/) aren't responding to SetFloat/SetVector

lilToon Dissolve 批量材质配置## _DissolveParams 四分量参考(源码级)

lilToon shader 源码 (lil_common_functions.hlsl line 626-666) 定义了 _DissolveParams 的四个分量:

分量 Shader 字段 用途 典型值 动画友好 备注
.x (r) dissolveParams.r 溶解形状模式 0, 1, 2, 3 ❌ 被 round() 0=禁用 1=遮罩 2=UV点 3=世界坐标
.y (g) dissolveParams.g 形状子模式 0 或 1 ❌ 被 round() 0=径向 1=旋转线性
.z (b) dissolveParams.b 边界/进度阈值 -0.5~1.7 主要动画目标 低于 min→全显, 高于 max→全隐
.w (a) dissolveParams.a 边缘宽度/模糊 0.1 (默认) ✅ 可动画 越小边缘越锐利

C# 设置方式 — 由于 .x.y 有 round(),只能设置整数:

mat.SetFloat("_DissolveParams.x", 2f);    // 形状=点
mat.SetFloat("_DissolveParams.y", 0f);    // 子模式=径向
mat.SetFloat("_DissolveParams.z", 1.7f);  // 完全溶解
mat.SetFloat("_DissolveParams.w", 0.1f);  // 边缘宽度
// 或一次性
mat.SetVector("_DissolveParams", new Vector4(2f, 0f, 1.7f, 0.1f));

⚠️ _DissolveParams.w 参照原版材质:原版 Cazalis_Cloth.mat 的 w=0(无边缘模糊)。0.1 也可以用但原版是 0。按项目实际需求选择。

⚠️ _DissolveParams.z 不要设超过 2.0 — lilToon 溶解阈值有效范围约 -0.5~2.0。z=3 或 5 这类过大值可能导致 shader 计算异常(溶解不完整或卡在中间状态)。标准溶解终值应为 1.7,显现值应为 -0.5

形状模式 (x 分量): 5~2.0。z=3 或 5 这类过大值可能导致 shader 计算异常(溶解不完整或卡在中间状态)。标准溶解终值应为 1.7,显现值应为 -0.5

形状模式 (x 分量):| x 值 | 形状 | 计算公式 (y=0 / y=1) | 说明 | |:--:|:----|:--------------------|:----| | 0 | 禁用 | — | 溶解模块完全跳过 | | 1 | 遮罩 | tex.r / 同上 | 基于 _DissolveMask 纹理的 R 通道 | | 2 | 点/UV | distance(uv, dissolvePos.xy) / lilRotateUV(uv, dissolvePos.w).x | 最常用 | | 3 | 世界坐标 | distance(positionOS, dissolvePos.xyz) / dot(positionOS, normalize(dissolvePos.xyz)) | 物体空间位置 | | distance(positionOS, dissolvePos.xyz) / dot(positionOS, normalize(dissolvePos.xyz)) | 物体空间位置 |## 方法 A: 通过场景 SMR 批量配置(推荐)

步骤

  1. 找到 Avatar 根对象
  2. 遍历所有 SkinnedMeshRenderer
  3. 构建 avatar 相对路径
  4. 匹配服装部件路径列表
  5. 修改每个 unique 材质

步骤

  1. 找到 Avatar 根对象
  2. 遍历所有 SkinnedMeshRenderer
  3. 构建 avatar 相对路径
  4. 匹配服装部件路径列表
  5. 修改每个 unique 材质### 代码模板

var noiseTex = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Cazalis/Cazalis_Reflection_Noise.png");
var color = new Color(0.2196f, 0.4784f, 0.9137f, 1.0f); // #387AE9
var avatar = GameObject.Find("Cazalis zigai");
var smrs = avatar.GetComponentsInChildren<SkinnedMeshRenderer>(true);
var done = new HashSet<Material>();

string[] clothPaths = { "Cloth_Dress", "Cloth_Pumps", "Cloud/Boots", ... };

foreach (var smr in smrs) {
    var path = smr.gameObject.name;
    var p = smr.transform.parent;
    while (p != null && p != avatar.transform) {
        path = p.name + "/" + path;
        p = p.parent;
    }
    bool isCloth = false;
    foreach (var cp in clothPaths) { if (path == cp) { isCloth = true; break; } }
    if (!isCloth) continue;

    foreach (var mat in smr.sharedMaterials) {
        if (mat == null || done.Contains(mat)) continue;
        done.Add(mat);

        mat.SetFloat("_DissolveParams.x", 2f);
        mat.SetFloat("_DissolveParams.y", 0f);
        mat.SetFloat("_DissolveParams.z", 1.7f); // default: fully dissolved
        mat.SetFloat("_DissolveParams.w", 0.1f); // 边缘宽度,不要设 0
        mat.SetVector("_DissolvePos", new Vector4(0f, 1f, 0f, 0f));
        mat.SetTexture("_DissolveNoiseMask", noiseTex);
        mat.SetFloat("_DissolveNoiseStrength", 0.3f);
        mat.SetColor("_DissolveColor", color);
        mat.EnableKeyword("GEOM_TYPE_BRANCH_DETAIL");
        EditorUtility.SetDirty(mat);
    }
}
AssetDatabase.SaveAssets();
M_TYPE_BRANCH_DETAIL"); EditorUtility.SetDirty(mat); } } AssetDatabase.SaveAssets(); ```## 方法 B: 直接编辑 .mat 文件(处理顽固材质)

当 SetFloat/SetVector 不生效时(常见于外部包材质 Assets/choco/, Assets/AyuElla/),材质的 property 值无法通过 C# API 修改。

原因

这些材质可能有 Property Override 或未被 AssetDatabase 正确索引。SetFloat 虽然执行成功但不持久化。 ty 值无法通过 C# API 修改。

原因

这些材质可能有 Property Override 或未被 AssetDatabase 正确索引。SetFloat 虽然执行成功但不持久化。### 解决方案:直接编辑 YAML 文件

.mat 文件是 YAML 格式,_DissolveParams 存储为:

- _DissolveParams: {r: 2, g: 0, b: 1.7, a: 0.1}

注意 a (即 .w/边缘宽度) 参照原版材质设定。原版 Cazalis_Cloth.mat w=0,项目标准也是 0。

使用正则替换后强制 AssetDatabase.Refresh()

⚠️ 文件编辑必须同时处理以下所有项,缺一不可: 1. _DissolveParams — {r: 2, g: 0, b: 1.7, a: 0}(参照原版,w=0) 2. m_Shader — 必须改为 Cutout/Transparent 变体的 GUID(Opaque 下溶解不生效) 3. _TransparentMode — 必须为 0(TM=1 会导致 Cutout 渲染异常) 4. m_CustomRenderQueue — 必须为 3000(-1 会让 shader 自动算出 2450,渲染顺序错误) 5. m_ValidKeywords — 原版 Cazalis_Cloth 不需要 GEOM_TYPE_BRANCH_DETAIL,设为 [] 即可 6. _DissolveNoiseMask — 噪点图纹理引用,格式:

- _DissolveNoiseMask:
    m_Texture: {fileID: 2800000, guid: <纹理GUID>, type: 3}
    m_Scale: {x: 1, y: 1}
    m_Offset: {x: 0, y: 0}
6. _DissolveNoiseStrength — 噪点强度,标准值 0.3 7. _DissolvePos — 溶解方向,标准值 {r: 0, g: 1, b: 0, a: 0} 8. _DissolveColor — 溶解边缘颜色,标准值 {r: 0.22, g: 0.48, b: 0.91, a: 1}

⚠️ 常见遗漏:只修了 DP 和 KW,忘了加噪点图和噪点强度。没有噪点图时溶解边缘是完全直切的,非常生硬。Cazalis 项目统一使用 Cazalis_Reflection_Noise.png(GUID: e21822f135616e54a8b13f8cd408a826)。

⚠️ TM/Queue 致命错误_TransparentMode=1 + m_CustomRenderQueue=-1 会导致 shader 自动计算出 Queue=2450,渲染顺序完全错乱,材质变透明/不可见。必须 TM=0 + Queue=3000(参照原版 Cazalis_Cloth.mat)。

⚠️ Alpha 蒙版模式是材质不显示的常见根因:lilToon 的 Alpha 蒙版(_AlphaMaskMode)如果设为"替换"而不是"无",在没有蒙版纹理的情况下会导致整个材质变透明/不可见。这是最常见的材质不显示问题,排查时优先检查! Toon 的 Alpha 蒙版(_AlphaMaskMode)如果设为"替换"而不是"无",在没有蒙版纹理的情况下会导致整个材质变透明/不可见。这是最常见的材质不显示问题,排查时优先检查!```csharp var appPath = Application.dataPath; string[] targets = { "choco/Seraphic Bloom Dress/Material/Tex1_2_1.mat", ... };

// 噪点图 GUID(Cazalis 项目标准) var noiseGUID = "e21822f135616e54a8b13f8cd408a826"; Tex1_2_1.mat", ... };

// 噪点图 GUID(Cazalis 项目标准) var noiseGUID = "e21822f135616e54a8b13f8cd408a826";foreach (var rel in targets) { var fullPath = Path.GetFullPath(appPath + "/../Assets/" + rel); var content = File.ReadAllText(fullPath);

// 1. Replace _DissolveParams
content = Regex.Replace(content,
    @"_DissolveParams: \{r: [^,]+,\s*g: [^,]+,\s*b: [^,]+,\s*a: [^}]+\}",
    "_DissolveParams: {r: 2, g: 0, b: 1.7, a: 0.1}");

// 2. Add _DissolveNoiseMask if missing
if (!content.Contains("_DissolveNoiseMask")) {
    // Insert after _DissolveColor block
    var noiseBlock = "  - _DissolveNoiseMask:\n      m_Texture: {fileID: 2800000, guid: " + noiseGUID + ", type: 3}\n      m_Scale: {x: 1, y: 1}\n      m_Offset: {x: 0, y: 0}\n";
    var colorIdx = content.IndexOf("_DissolveColor");
    if (colorIdx >= 0) {
        var afterOffset = content.IndexOf("\n", content.IndexOf("m_Offset", colorIdx));
        content = content.Insert(afterOffset + 1, noiseBlock);
    }
}

// 3. Add/fix _DissolveNoiseStrength
if (!content.Contains("_DissolveNoiseStrength")) {
    // Add after _DissolveParams.w line
    var dpwIdx = content.IndexOf("_DissolveParams.w");
    if (dpwIdx >= 0) {
        var lineEnd = content.IndexOf("\n", dpwIdx);
        content = content.Insert(lineEnd + 1, "  - _DissolveNoiseStrength: 0.3\n");
    }
} else {
    content = Regex.Replace(content, @"(_DissolveNoiseStrength:\s*)\d+\.?\d*", "${1}0.3");
}

// 4. Add GEOM_TYPE_BRANCH_DETAIL to m_Valid

oiseStrength:\s)\d+.?\d", "${1}0.3"); }

// 4. Add GEOM_TYPE_BRANCH_DETAIL to m_ValidoiseStrength:\s*)\d+\.?\d*", "${1}0.3");
}

// 4. Add GEOM_TYPE_BRANCH_DETAIL to m_ValidKeywords + m_ShaderKeywords
// ... (同前)

File.WriteAllText(fullPath, content);

} AssetDatabase.Refresh(); ywords // ... (同前) File.WriteAllText(fullPath, content); } AssetDatabase.Refresh();### 注意事项 - AssetDatabase.Refresh() 会触发 domain reload - 修改后 Material 对象可能被销毁重建,需重新 LoadAssetAtPath - Refresh 后需要等 editor_state.isCompiling=false

⚠️ Opaque 模式下溶解完全无效(源码级证实)

这是最常见的溶解不生效原因。lil_pass_forward_normal.hlsl 第 202 行: ```hlsl

if defined(LIL_FEATURE_DISSOLVE) && LIL_RENDER != 0

```

  • LIL_RENDER = 0 (Opaque) → 溶解和 AlphaMask 都被跳过
  • LIL_RENDER = 1 (Cutout) → 溶解和 AlphaMask 生效
  • LIL_RENDER = 2 (Transparent) → 溶解和 AlphaMask 生效

如果材质是 Opaque 模式(_TransparentMode=0),溶解相关代码根本不在 Shader Variant 中编译。检查方法: 在 Inspector 中确认材质不是 Opaque 模式,或者通过代码读取 mat.GetFloat("_TransparentMode")

排查技巧:克隆 lilToon 源码分析隐藏行为

当对 shader property 行为有疑问时,可以直接从 GitHub 克隆 lilToon 仓库并搜索 shader 源码:

git clone --depth 1 https://github.com/lilxyzw/lilToon.git

关键文件及其用途:

文件 用途
Shader/Includes/lil_common_functions.hlsl lilCalcDissolve(), lilCalcDissolveWithNoise() — 溶解计算逻辑
Shader/Includes/lil_common_frag.hlsl AlphaMask, Dissolve, Dither 的 OVERRIDE 宏定义
Shader/Includes/lil_pass_forward_normal.hlsl Shader 执行管线顺序,Alpha finalization
Shader/Includes/lil_common_input_opt.hlsl Shader property 声明
Shader/lts_onetrans.shader Transparent 模式 shader 标签(Queue=AlphaTest+10)

查询示例:找 _DissolveParams.w 的用途 → 搜索 dissolveParams.alilCalcDissolve 函数体。 eue=AlphaTest+10) |

查询示例:找 _DissolveParams.w 的用途 → 搜索 dissolveParams.alilCalcDissolve 函数体。## 验证代码

// 对所有服装 SMR 验证材质配置
foreach (var smr in smrs) {
    var path = GetAvatarPath(smr, avatar);
    if (!clothPaths.Contains(path)) continue;
    foreach (var mat in smr.sharedMaterials) {
        var dp = mat.GetVector("_DissolveParams");
        // 期望: dp=(2, 0, 1.7, 0)
        var noise = mat.GetTexture("_DissolveNoiseMask");
        // 期望: noise != null
    }
}

已知的顽固材质(Cazalis 项目)

以下材质的 _DissolveParams 无法通过 C# API SetFloat/SetVector 修改,必须用方法 B:

材质路径 所属套装
Assets/choco/Seraphic Bloom Dress/Material/Tex*.mat 露肩短裙(Cazalis4 Variant)
Assets/choco/Seraphic Bloom Dress/Material/M.mat 露肩短裙
Assets/choco/Seraphic Bloom Dress/Material/C.mat 露肩短裙
Assets/AyuElla/SilentSkinPeek/Material/*.mat 开衫毛衣

Cazalis 基础材质(Assets/Cazalis/Material/*.mat)和 Cloud 系材质(Assets/Rest/Awakoi_Code/Mat/*)可用 SetFloat 正常修改。

Animator Layer 2状态结构(推荐,已验证)

有两种可行的实现方案:

方案 A:1帧 clip + writeDefault=False(当前项目采用)

OFF ↔ ON
每个 layer 绑定独立 Bool 参数
writeDefaultValues = false
transition duration = 1s

ON clip(1帧): - t=0: _DissolveParams.z = -0.5(显现)+ m_IsActive = true

OFF clip(2帧): - t=0: _DissolveParams.z = 1.7(只溶解,不动m_IsActive) - t=1: m_IsActive = false(只关衣服)

原理:writeDefault=False 下,clip 只写入它动画的属性。transition 期间 crossfade 两个 clip 的值。但注意 crossfade 是线性插值,不是平滑的溶解曲线。 riteDefault=False 下,clip 只写入它动画的属性。transition 期间 crossfade 两个 clip 的值。但注意 crossfade 是线性插值,不是平滑的溶解曲线。### 方案 B:clip自带渐变曲线 + writeDefault=True(落羽松教程采用)

ON ↔ OFF
每个 layer 绑定独立 Bool 参数
writeDefaultValues = true
transition duration = 0.25s(实际渐变靠 clip 自身)

ON clip(~45帧 / 0.75s): - t=0: _DissolveParams.z = 1.7(溶解状态) - t=45: _DissolveParams.z = -0.5(渐变到显现) + m_IsActive = true

OFF clip(~45帧 / 0.75s): - t=0: _DissolveParams.z = -0.5(显现状态) - t=45: _DissolveParams.z = 1.7(渐变到溶解) + m_IsActive = false

原理:clip 本身的 Animation Curve 提供了平滑的溶解渐变。writeDefault=True 保证进入状态时所有未动画的属性保持原值。transition duration 只做很短的 crossfade 过渡。

主要区别

特性 方案 A(1帧+transition) 方案 B(渐变clip)
clip 帧数 1~2帧 ~45帧
渐变方式 靠 Animator transition crossfade 靠 Animation Curve 渐变
writeDefault False True
曲线控制 线性插值 可自定义缓入缓出
transition duration 1s 0.25s
灵活性

⚠️ 常见 Dissolve 动画问题排查

现象 原因 解决
溶解"不够完全",部分残留 _DissolveParams.z 值过大(>2.0)或各部件值不统一 统一设为 1.7,不超过 2.0
溶解动画卡在中间态 writeDefault=True + 1帧clip + transition duration,crossfade 过程中值悬停 改用方案B(渐变clip),或确保 writeDefault=False
过渡中溶解方向反转 ON/OFF 的 _DissolvePos 不同(如 y=1 vs y=-1) 统一 ON/OFF 的 _DissolvePos
溶解时瞬切(无渐变) Opaque模式下溶解跳过 切到 Cutout 或 Transparent
某些部件溶解不一致 各部件 clip 中 z 值不同,crossfade 速率不同 统一 z 值目标