VRChat 非本素体发型/衣服整合指南
版本: 1.0
日期: 2026-05-09
适用场景: 将购买的第三方发型、衣服整合到现有 VRChat Avatar,支持溶解 Toggle 系统
1. 核心概念
1.1 什么是非本素体专用
非本素体专用(Third-Party / Non-Native)指的是: - 从 Booth.pm、Pixiv、Fiverr 等购买的模型包 - VRoid Hub 下载的共享发型/衣服 - 自己用 Blender 创建的独立模型包 - AI 生成的发型/衣服模型
这些模型原本不是为目标 Avatar 设计的,需要手动适配。
1.2 整合的挑战
| 挑战 | 说明 | 解决方案 |
|---|---|---|
| 骨骼不匹配 | 原 Avatar 骨骼名称与第三方不一致 | 重定向或重新绑定 |
| 材质冲突 | 多套衣服共用材质导致颜色串扰 | 材质隔离 |
| 权重问题 | 第三方模型权重可能影响原 Avatar | 检查和修复权重 |
| Toggle 整合 | 新衣服需要加入溶解 Toggle 系统 | 独立材质 + Animator 层 |
| PhysBone 配置 | 头发/裙子需要物理效果 | 手动添加 PhysBone |
2. 整合流程总览
第三方模型包 (FBX/VRM/Craft)
↓
在 Unity 中导入
↓
分析模型结构
├─ 骨骼系统
├─ 网格和材质
└─ 动画(如果有)
↓
骨骼重定向 / 重新绑定
↓
Parent to Avatar 骨骼
↓
位置/旋转/缩放调整
↓
材质隔离 + 溶解配置
↓
PhysBone 配置(如需要)
↓
整合到 Toggle 系统
↓
测试验证
3. 骨骼系统处理
3.1 骨骼类型分析
第三方模型的骨骼系统通常有以下类型:
| 类型 | 说明 | 适配难度 |
|---|---|---|
| VRoid 骨骼 | 标准 VRoid 命名 (J_Head, J_Spine 等) | ⭐ 简单 |
| Mixamo 骨骼 | Mixamo 自动绑定骨骼 | ⭐⭐ 中等 |
| Blender Rigify | Blender Rigify 生成的骨骼 | ⭐⭐⭐ 复杂 |
| 自定义骨骼 | 创作者自定义命名 | ⭐⭐⭐ 复杂 |
3.2 VRoid 骨骼兼容模型
如果第三方模型使用标准 VRoid 骨骼命名,可以直接挂载:
// 目标 Avatar 骨骼命名 (VRoid 标准)
J_Bip_C_Head // 头部
J_Bip_C_UpperChest // 上胸部
J_Bip_C_Hips // 髋部
J_Sec_Fcl_HairFront // 头发前部
J_Sec_Fcl_HairBack // 头发后部
// 第三方模型通常使用相同或类似的命名
3.3 非 VRoid 骨骼适配
ec_Fcl_HairFront // 头发前部 J_Sec_Fcl_HairBack // 头发后部
// 第三方模型通常使用相同或类似的命名
### 3.3 非 VRoid 骨骼适配#### 方法 A:直接 Parent + 位置调整
适用于头发、帽子、饰品等不需要骨骼动画的配件:
```csharp
// 找到目标 Avatar 的骨骼
var targetBone = avatarRoot.transform.Find("J_Bip_C_Head");
// 加载第三方配件
var accessory = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/.../Accessory.prefab");
var instance = UnityEngine.Object.Instantiate(accessory);
// 挂载到骨骼
instance.transform.SetParent(targetBone);
// 调整位置(需要反复测试)
instance.transform.localPosition = new Vector3(0, 0.02f, 0);
instance.transform.localRotation = Quaternion.identity;
instance.transform.localScale = Vector3.one;
方法 B:骨骼重定向(复杂)
适用于需要完整骨骼动画的衣服:
// 使用 Unity 的 Humanoid 重定向
var humanoid = thirdPartyModel.GetComponent<Animator>();
if (humanoid != null && humanoid.avatar != null && humanoid.avatar.isHuman) {
// 设置 Avatar Root 位置
thirdPartyModel.transform.SetParent(avatarRoot.transform);
thirdPartyModel.transform.localPosition = Vector3.zero;
// 配置 Avatar 映射
var boneMapping = new Dictionary<string, Transform>();
// ... 手动映射骨骼 ...
}
3.4 骨骼检查脚本
// 检查第三方模型的骨骼结构
var armature = thirdPartyModel.transform.Find("Armature");
if (armature != null) {
foreach (var bone in armature.GetComponentsInChildren<Transform>()) {
Debug.Log("Bone: " + bone.name);
}
}
4. 发型整合
entsInChildren---
## 4. 发型整合### 4.1 发型类型与挂载点
| 发型类型 | 挂载骨骼 | 附加配置 |
|----------|----------|----------|
| 短发/波波头 | `J_Bip_C_Head` | 通常无需 PhysBone |
| 中长发 | `J_Bip_C_Head` 或 `J_Sec_Fcl_HairBack1` | 可能需要轻量 PhysBone |
| 长发/马尾 | 专用 Hair Root 或 `J_Spine` | **需要 PhysBone** |
| 双马尾/辫子 | 专用 Root + `J_Sec_Fcl_HairBack1` | **需要 PhysBone** |
### 4.2 发型整合步骤
#### Step 1: 导入并分析
```csharp
// 导入第三方发型
var hairPath = "Assets/.../Hair_Pack/Hair_Long.fbx";
var hairObject = AssetDatabase.LoadAssetAtPath<GameObject>(hairPath);
// 分析结构
var smr = hairObject.GetComponentInChildren<SkinnedMeshRenderer>();
Debug.Log("Hair bones: " + smr.bones.Length);
Debug.Log("Hair vertices: " + smr.sharedMesh.vertexCount);
Step 2: 挂载到 Avatar
// 选择挂载点
var headBone = avatarRoot.transform.Find("J_Bip_C_Head");
var hairInstance = UnityEngine.Object.Instantiate(hairObject);
// 尝试自动找到合适的根节点
var hairRoot = hairInstance.transform.Find("Hair_Root");
if (hairRoot == null) {
hairRoot = hairInstance.transform.GetChild(0); // 假设第一个子对象是根
}
// 挂载
hairRoot.SetParent(headBone);
hairRoot.localPosition = Vector3.zero;
hairRoot.localRotation = Quaternion.identity;
hairRoot.localScale = Vector3.one;
Step 3: 位置微调
发型位置常见的调整:
| 问题 | 调整 |
|---|---|
| 发型偏左/偏右 | localPosition.x |
| 发型太高/太低 | localPosition.y |
| 发型太前/太后 | localPosition.z |
| 发型整体偏转 | localRotation |
// 微调示例
hairRoot.localPosition = new Vector3(0.01f, 0.005f, -0.02f);
hairRoot.localRotation = Quaternion.Euler(0, 5, 0);
长发需要添加 PhysBone 以实现物理摆动:
// 找到头发根骨骼
var physBoneRoot = hairRoot.transform.Find("HairPhysics"); // 取决于模型命名
var pb = physBoneRoot.gameObject.AddComponent<VRCPhysBone>();
pb.rootBone = physBoneRoot.name;
// 物理参数(根据头发长度调整)
pb.pull = 0.35f; // 拉力
pb.spring = 0.4f; // 弹力
pb.stiffness = 0.1f; // 刚度
pb.gravity = 0.15f; // 重力
pb.immobileType = VRCPhysBoneBase.ImmobileType.InRange;
pb.immobile = 0.3f;
// 碰撞配置
pb.collisionFilter.collisionCheck = true;
5. 衣服整合
5.1 衣服类型与挂载点
| 衣服类型 | 挂载骨骼 | 附加配置 |
|---|---|---|
| 上衣/衬衫 | J_Bip_C_UpperChest 或 J_Spine |
跟随躯干 |
| 裙子 | J_Bip_C_Hips 或专用 SkirtRoot |
可能需要 PhysBone |
| 裤子 | J_Bip_C_Hips 或 J_UpperLeg |
跟随髋/腿 |
| 连衣裙 | J_Spine |
整体跟随 |
| 配件(项链等) | 对应身体部位 | 无需 PhysBone |
5.2 衣服整合步骤
Step 1: 材质隔离(关键)
衣服整合必须进行材质隔离,否则溶解效果会串扰:
// 获取原始材质
var originalMat = thirdPartyCloth.GetComponent<SkinnedMeshRenderer>().sharedMaterial;
// 创建独立副本
var clothMat = UnityEngine.Object.Instantiate(originalMat);
AssetDatabase.CreateAsset(clothMat, "Assets/.../Outfit_Cafe_Cloth.mat");
// 应用到网格
thirdPartyCloth.GetComponent<SkinnedMeshRenderer>().sharedMaterial = clothMat;
// 配置溶解参数
clothMat.SetFloat("_TransparentMode", 1);
clothMat.SetVector("_DissolveParams", new Vector4(1, 0, 0, 0.1f));
clothMat.SetTexture("_DissolveNoiseMask", dissolveNoiseTex);
// 连衣裙挂载到脊柱
var spineBone = avatarRoot.transform.Find("J_Spine");
clothInstance.transform.SetParent(spineBone);
clothInstance.transform.localPosition = Vector3.zero;
clothInstance.transform.localRotation = Quaternion.identity;
// 裙子可能需要单独的根骨骼
var skirtRoot = clothInstance.transform.Find("Skirt_Root");
if (skirtRoot != null) {
var hipsBone = avatarRoot.transform.Find("J_Bip_C_Hips");
skirtRoot.SetParent(hipsBone);
skirtRoot.localPosition = Vector3.zero;
}
Step 3: 权重检查
如果衣服使用了 Avatar 的骨骼,需要检查权重:
// 检查权重影响范围
var mesh = clothSMR.sharedMesh;
var boneCount = mesh.boneWeights.Length;
var vertexCount = mesh.vertexCount;
Debug.Log("Cloth has " + boneCount + " bone weights for " + vertexCount + " vertices");
// 确保没有意外权重
// 如果衣服影响到了不该影响的骨骼,需要在 Blender 中修正
5.3 多套衣服整合
整合多套衣服时,每套需要:
- 独立的材质实例(互相隔离)
- 独立的 Animator 层(参数不冲突)
- 独立的溶解动画(不能互相影响)
整合结构:
AvatarRoot/
├── J_Spine/
│ ├── Original_Cloth (原版衣服)
│ ├── Outfit_School (校服) ← 独立材质
│ └── Outfit_Cafe (咖啡装) ← 独立材质
└── J_Bip_C_Head/
├── Original_Hair (原版发型)
└── Hair_Long (第三方长发) ← 独立材质
6. 整合到溶解 Toggle 系统
_Head/ ├── Original_Hair (原版发型) └── Hair_Long (第三方长发) ← 独立材质
---
## 6. 整合到溶解 Toggle 系统### 6.1 纳入现有 Toggle
整合后的发型/衣服需要添加到溶解 Toggle 系统:
```csharp
// 创建新的 AnimationClip
var clip = new AnimationClip();
clip.name = "Hair_Long_OFF";
clip.legacy = false;
// 动画溶解值 (0 → 1)
var curve = new AnimationCurve();
curve.AddKey(0f, 1f); // 开始时溶解
curve.AddKey(0.3f, 1f);
var binding = EditorCurveBinding.FloatCurve(
"Hair_Long", // GameObject 路径
typeof(SkinnedMeshRenderer),
"material._DissolveParams.z"
);
AnimationUtility.SetEditorCurve(clip, binding, curve);
AssetDatabase.CreateAsset(clip, "Assets/.../Hair_Long_OFF.anim");
AssetDatabase.CreateAsset(clip, "Assets/.../Hair_Long_OFF.anim"); ```### 6.2 添加到 Animator 层
// 在现有 Underwear_Dissolve Layer 或新建 Hair_Control Layer
var hairLayer = new AnimatorControllerLayer();
hairLayer.name = "Hair_Control";
hairLayer.defaultWeight = 1.0f;
hairLayer.stateMachine = new AnimatorStateMachine();
var hairOn = hairLayer.stateMachine.AddState("Hair_Long_ON");
hairOn.motion = AssetDatabase.LoadAssetAtPath<AnimationClip>("Assets/.../Hair_Long_ON.anim");
var hairOff = hairLayer.stateMachine.AddState("Hair_Long_OFF");
hairOff.motion = AssetDatabase.LoadAssetAtPath<AnimationClip>("Assets/.../Hair_Long_OFF.anim");
// 添加过渡
var transition = hairOff.AddTransition(hairOn);
transition.hasExitTime = false;
transition.duration = 0.3f;
transition.AddCondition(AnimatorConditionMode.If, 0, "Hair_Long");
var transition2 = hairOn.AddTransition(hairOff);
transition2.hasExitTime = false;
transition2.duration = 0.3f;
transition2.AddCondition(AnimatorConditionMode.IfNot, 0, "Hair_Long");
// 添加参数
controller.AddParameter("Hair_Long", AnimatorControllerParameterType.Bool);
// 添加层
var layers = controller.layers.ToList();
layers.Add(hairLayer);
controller.layers = layers.ToArray();
// 在 Outfit_Menu 下添加 Toggle
var menu = avatarRoot.transform.Find("Outfit_Menu");
var toggleGO = new GameObject("Hair_Long_Toggle");
toggleGO.transform.SetParent(menu);
var mi = toggleGO.AddComponent<maMenuItemType>();
var so = new UnityEditor.SerializedObject(mi);
so.FindProperty("m_control.type").enumValueIndex = 1; // Toggle
so.FindProperty("m_parameter.name").stringValue = "Hair_Long";
so.FindProperty("label").stringValue = "长发";
so.FindProperty("isDefault").boolValue = true;
so.ApplyModifiedProperties();
7. 验证检查清单
7.1 基础验证
| 检查项 | 方法 | 期望结果 |
|---|---|---|
| 骨骼绑定 | 移动 Avatar 观察第三方部件 | 跟随自然,无脱节 |
| 权重 | 检查是否有不该影响的骨骼 | 无异常权重 |
| 材质 | 检查 sharedMaterial | 独立材质,无共享 |
| 位置 | 对比原版参照 | 位置合理,无明显偏移 |
| 旋转 | 检查正面/侧面 | 无明显旋转错误 |
7.2 Toggle 验证
| 检查项 | 方法 | 期望结果 |
|---|---|---|
| 溶解效果 | 点击 Toggle 按钮 | 噪点溶解,约 0.3s |
| 溶解方向 | 观察 ON/OFF 状态 | ON=显现,OFF=溶解 |
| 无连锁反应 | 单独点击一个 Toggle | 其他部件不受影响 |
| 默认状态 | 重新进入世界 | 部件默认可见 |
7.3 物理验证(头发/裙子)
| 检查项 | 方法 | 期望结果 |
|---|---|---|
| 摆动 | 移动头部/身体 | 头发/裙子自然摆动 |
| 碰撞 | 把手伸向头发/裙子 | 无穿模或轻微 |
| 穿模检查 | 做各种动作 | 无严重穿模 |
8. 常见问题排错
8.1 第三方模型无反应
- 检查是否挂载到正确的骨骼
- 检查 Animator 是否正确配置
- 检查材质是否正确应用
8.2 溶解效果作用在错误的部件
通常是材质共享问题,需要隔离材质。
8.3 头发/裙子僵硬
PhysBone 未正确配置或碰撞体冲突。
8.4 穿模严重
- 添加/调整 PhysBone Collider
- 调整头发/裙子的位置
- 在 Blender 中修正权重
本文档为 VRChat 模体修改知识库的一部分 | 最后更新: 2026-05-09