跳转至

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()) { Debug.Log("Bone: " + bone.name); } }

---

## 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);
sition = new Vector3(0.01f, 0.005f, -0.02f); hairRoot.localRotation = Quaternion.Euler(0, 5, 0); ```#### Step 4: 配置 PhysBone(如需要)

长发需要添加 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_UpperChestJ_Spine 跟随躯干
裙子 J_Bip_C_Hips 或专用 SkirtRoot 可能需要 PhysBone
裤子 J_Bip_C_HipsJ_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);
rams", new Vector4(1, 0, 0, 0.1f)); clothMat.SetTexture("_DissolveNoiseMask", dissolveNoiseTex); ```#### Step 2: 挂载到骨骼

// 连衣裙挂载到脊柱
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 多套衣服整合

整合多套衣服时,每套需要:

  1. 独立的材质实例(互相隔离)
  2. 独立的 Animator 层(参数不冲突)
  3. 独立的溶解动画(不能互相影响)
整合结构:
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");
rCurve(clip, binding, curve);

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();
ayers = controller.layers.ToList(); layers.Add(hairLayer); controller.layers = layers.ToArray(); ```### 6.3 添加 MA MenuItem

// 在 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 第三方模型无反应

  1. 检查是否挂载到正确的骨骼
  2. 检查 Animator 是否正确配置
  3. 检查材质是否正确应用

8.2 溶解效果作用在错误的部件

通常是材质共享问题,需要隔离材质。

8.3 头发/裙子僵硬

PhysBone 未正确配置或碰撞体冲突。

8.4 穿模严重

  1. 添加/调整 PhysBone Collider
  2. 调整头发/裙子的位置
  3. 在 Blender 中修正权重

本文档为 VRChat 模体修改知识库的一部分 | 最后更新: 2026-05-09