VRM requires one specific bone hierarchy. If your GLB rig uses generic names (e.g., mixamorig:Hips), you must standardize them.
Using CATS Plugin (Easiest):
Manual Method (Without CATS): Use the VRM add-on's humanoid bone assignment panel. Ensure the following bones exist and follow the correct parent-child order: convert glb to vrm full
| Problem | Fix | |--------|-----| | No humanoid bones | Use Mixamo auto-rig, then export as FBX → GLB | | VRM export missing face expressions | Add blend shapes in Blender/Unity | | Texture missing | Re-link images in Blender’s Shader Editor | | Rotation wrong | Apply rotation/scale before export |
Automated option (command-line / batch): VRM requires one specific bone hierarchy
| Feature | Online Converter | Blender + Unity (Full) | | :--- | :--- | :--- | | Time | 30 seconds | 30 minutes | | Requires Software | No | Yes | | Preserves Bones | Basic (if pre-rigged) | Yes (full retargeting) | | Preserves Blendshapes | Rarely | Yes | | Adds Spring Physics | No | Yes | | Adds Eye Tracking/Look-At | No | Yes | | Best For | Low-poly characters, props | VTubing, VRChat, Full Avatars |
This is where most conversions fail. GLB files rarely contain VRM-ready expressions (Joy, Angry, Sad, Fun, etc.). A "full" conversion requires you to map existing shape keys or create new ones. Manual Method (Without CATS): Use the VRM add-on's
Scenario A: Your GLB has Morph Targets (Shape Keys)
Look at the mesh’s Object Data Properties (Green triangle icon). If you see keys like eye_blink_L, mouth_smile, or brow_up, you are lucky.
Scenario B: Your GLB is Static (No Face Shapes) You must sculpt or create shape keys manually. This is time-consuming but essential for a convincing avatar.
For complete VRM export (with textures, bones, and expressions), use this more comprehensive approach:
import json
import numpy as np
from pathlib import Path
class GLBtoVRMConverter:
def init(self):
self.vrm_template = {
"specVersion": "1.0",
"title": "",
"version": "1.0",
"author": "GLB Converter",
"contactInformation": "",
"reference": "",
"texture": [],
"material": [],
"mesh": [],
"node": [],
"scene": 0,
"scenes": ["name": "default", "nodes": []],
"extensions": {
"VRM": {
"specVersion": "1.0",
"meta":
"title": "",
"version": "1.0",
"author": "",
"contactInformation": "",
"reference": "",
"allowedUser": "OnlyAuthor",
"violentUssageName": "Disallow",
"sexualUssageName": "Disallow",
"commercialUssageName": "Disallow"
,
"humanoid":
"humanBones": []
,
"firstPerson": {},
"lookAt": {},
"blendShapeMaster": {}
}
}
}
def convert(self, glb_path, vrm_path, model_info=None):
"""Main conversion function"""
print(f"Converting glb_path to vrm_path")
# Load and parse GLB
with open(glb_path, 'rb') as f:
glb_data = f.read()
# Parse GLB binary structure
# This requires proper GLB parsing - see complete implementation above
# Update VRM metadata
if model_info:
self.vrm_template["extensions"]["VRM"]["meta"].update(model_info)
self.vrm_template["title"] = model_info.get("title", "ConvertedModel")
# Save VRM
with open(vrm_path, 'w', encoding='utf-8') as f:
json.dump(self.vrm_template, f, indent=2, ensure_ascii=False)
print(f"Conversion complete: vrm_path")