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 批量配置(推荐)
步骤
- 找到 Avatar 根对象
- 遍历所有 SkinnedMeshRenderer
- 构建 avatar 相对路径
- 匹配服装部件路径列表
- 修改每个 unique 材质
步骤
- 找到 Avatar 根对象
- 遍历所有 SkinnedMeshRenderer
- 构建 avatar 相对路径
- 匹配服装部件路径列表
- 修改每个 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();
当 SetFloat/SetVector 不生效时(常见于外部包材质 Assets/choco/, Assets/AyuElla/),材质的 property 值无法通过 C# API 修改。
原因
这些材质可能有 Property Override 或未被 AssetDatabase 正确索引。SetFloat 虽然执行成功但不持久化。
ty 值无法通过 C# API 修改。
原因
这些材质可能有 Property Override 或未被 AssetDatabase 正确索引。SetFloat 虽然执行成功但不持久化。### 解决方案:直接编辑 YAML 文件
.mat 文件是 YAML 格式,_DissolveParams 存储为:
注意 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}
_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 源码:
关键文件及其用途:
| 文件 | 用途 |
|---|---|
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.a 或 lilCalcDissolve 函数体。
eue=AlphaTest+10) |
查询示例:找 _DissolveParams.w 的用途 → 搜索 dissolveParams.a 或 lilCalcDissolve 函数体。## 验证代码
// 对所有服装 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(当前项目采用)
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 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 值目标 |