Convert Glb To Vrm Fixed
# glb_to_vrm_converter.py
import numpy as np
import trimesh
from typing import Optional, Dict, Any, List
import json
from pathlib import Path
class GLBtoVRMConverter:
"""Convert GLB files to VRM format with fixed bone structure and humanoid mapping"""
def __init__(self):
# Standard VRM humanoid bone mapping
self.human_bones =
'hips': 'hips',
'spine': 'spine',
'chest': 'chest',
'upper_chest': 'upper_chest',
'neck': 'neck',
'head': 'head',
'left_shoulder': 'left_shoulder',
'right_shoulder': 'right_shoulder',
'left_upper_arm': 'left_upper_arm',
'right_upper_arm': 'right_upper_arm',
'left_lower_arm': 'left_lower_arm',
'right_lower_arm': 'right_lower_arm',
'left_hand': 'left_hand',
'right_hand': 'right_hand',
'left_upper_leg': 'left_upper_leg',
'right_upper_leg': 'right_upper_leg',
'left_lower_leg': 'left_lower_leg',
'right_lower_leg': 'right_lower_leg',
'left_foot': 'left_foot',
'right_foot': 'right_foot',
'left_toes': 'left_toes',
'right_toes': 'right_toes'
def convert(self, glb_path: str, output_path: str,
fix_bones: bool = True,
add_vrm_metadata: bool = True,
fix_textures: bool = True) -> bool:
"""
Convert GLB to VRM with fixes
Args:
glb_path: Path to input GLB file
output_path: Path for output VRM file
fix_bones: Automatically fix bone structure
add_vrm_metadata: Add required VRM metadata
fix_textures: Fix texture issues
Returns:
bool: Success status
"""
try:
# Load GLB
scene = trimesh.load(glb_path, force='scene')
if not scene.geometry:
raise ValueError("No geometry found in GLB file")
# Extract mesh data
meshes = self._extract_meshes(scene)
# Extract skeleton if present
skeleton = self._extract_skeleton(scene) if fix_bones else None
# Fix bone structure if needed
if fix_bones and skeleton:
skeleton = self._fix_bone_structure(skeleton)
# Create VRM structure
vrm_data = self._create_vrm_structure(
meshes,
skeleton,
add_vrm_metadata,
fix_textures
)
# Export as VRM
self._export_vrm(vrm_data, output_path)
print(f"✅ Successfully converted glb_path to output_path")
return True
except Exception as e:
print(f"❌ Conversion failed: str(e)")
return False
def _extract_meshes(self, scene) -> List[Dict]:
"""Extract mesh data from scene"""
meshes = []
for name, geometry in scene.geometry.items():
if hasattr(geometry, 'vertices') and hasattr(geometry, 'faces'):
mesh_data =
'name': name,
'vertices': geometry.vertices.copy(),
'faces': geometry.faces.copy(),
'vertex_colors': getattr(geometry, 'visual', None)
meshes.append(mesh_data)
return meshes
def _extract_skeleton(self, scene) -> Optional[Dict]:
"""Extract skeleton data from scene"""
# Try to find armature/joint data
skeleton = {
'bones': [],
'bone_hierarchy': {}
}
# Check for animation data
if hasattr(scene, 'animations') and scene.animations:
for anim in scene.animations:
if hasattr(anim, 'joints'):
skeleton['bones'] = anim.joints
return skeleton if skeleton['bones'] else None
def _fix_bone_structure(self, skeleton: Dict) -> Dict:
"""Fix bone structure to match VRM humanoid requirements"""
fixed_bones = []
bone_mapping = {}
# Create bone mapping
for i, bone in enumerate(skeleton['bones']):
bone_name = bone.get('name', f'bone_i').lower()
# Map to VRM bone names
for vrm_bone, mapping in self.human_bones.items():
if mapping in bone_name or vrm_bone in bone_name:
bone_mapping[bone.get('name')] = vrm_bone
break
else:
bone_mapping[bone.get('name')] = bone.get('name')
skeleton['bone_mapping'] = bone_mapping
skeleton['humanoid_bones'] = self.human_bones
return skeleton
def _create_vrm_structure(self, meshes: List[Dict],
skeleton: Optional[Dict],
add_metadata: bool,
fix_textures: bool) -> Dict:
"""Create VRM JSON structure"""
vrm =
'glTF':
'asset':
'version': '2.0',
'generator': 'GLBtoVRMConverter',
'copyright': 'Converted from GLB'
,
'scenes': ['nodes': [0]],
'nodes': [],
'meshes': [],
'materials': [],
'textures': [],
'images': [],
'skins': [],
'animations': []
,
'VRM':
'meta':
'title': 'Converted Model',
'version': '1.0',
'author': 'GLB to VRM Converter',
'contactInformation': '',
'reference': '',
'allowedUserName': 'OnlyAuthor',
'violentUsage': 'Disallow',
'sexualUsage': 'Disallow',
'commercialUsage': 'Disallow',
'otherPermissionUrl': '',
'licenseName': 'Other',
'otherLicenseUrl': ''
,
'humanoid':
'humanBones': [],
'armStretch': 0.05,
'legStretch': 0.05,
'upperArmTwist': 0.5,
'lowerArmTwist': 0.5,
'upperLegTwist': 0.5,
'lowerLegTwist': 0.5,
'feetSpacing': 0,
'hasTranslationDoF': False
,
'firstPerson':
'firstPersonBone': 'head',
'firstPersonBoneOffset': 'x': 0, 'y': 0.06, 'z': 0,
'meshAnnotations': []
,
'lookAt':
'offsetFromHeadBone': 'x': 0, 'y': 0.06, 'z': 0,
'gazeDirection': 'BoneAxisPositiveY',
'rangeMapHorizontal': 30,
'rangeMapVertical': 20
,
'blendShape':
'blendShapeGroups': []
,
'secondaryAnimation':
'boneGroups': [],
'colliderGroups': [],
'springBones': []
,
'material':
'version': 1,
'pluginEnabled': True,
'shader': 'VRM_USE_GLTFSHADER'
# Add mesh nodes
for i, mesh in enumerate(meshes):
node =
'name': mesh['name'],
'mesh': i,
'translation': [0, 0, 0],
'rotation': [0, 0, 0, 1],
'scale': [1, 1, 1]
vrm['glTF']['nodes'].append(node)
# Add mesh data
mesh_def =
'name': mesh['name'],
'primitives': [
'attributes':
'POSITION': 0,
'NORMAL': 1,
'TEXCOORD_0': 2
,
'indices': 3,
'material': 0
]
vrm['glTF']['meshes'].append(mesh_def)
# Add skeleton nodes if available
if skeleton and skeleton.get('bones'):
for bone in skeleton['bones']:
bone_node =
'name': bone.get('name', 'bone'),
'translation': bone.get('position', [0, 0, 0]),
'rotation': bone.get('rotation', [0, 0, 0, 1]),
'scale': [1, 1, 1]
vrm['glTF']['nodes'].append(bone_node)
# Add skin data
skin =
'inverseBindMatrices': 4,
'skeleton': len(vrm['glTF']['nodes']) - len(skeleton['bones']),
'joints': list(range(len(vrm['glTF']['nodes']) - len(skeleton['bones']),
len(vrm['glTF']['nodes'])))
vrm['glTF']['skins'].append(skin)
# Add VRM humanoid bones
if skeleton and skeleton.get('bone_mapping'):
for gltf_bone, vrm_bone in skeleton['bone_mapping'].items():
if vrm_bone in self.human_bones.values():
vrm['VRM']['humanoid']['humanBones'].append(
'bone': vrm_bone,
'node': self._find_node_index(vrm['glTF']['nodes'], gltf_bone),
'useDefaultValues': True,
'min': 'x': -180, 'y': -90, 'z': -45,
'max': 'x': 180, 'y': 90, 'z': 45
)
return vrm
def _find_node_index(self, nodes: List[Dict], node_name: str) -> int:
"""Find node index by name"""
for i, node in enumerate(nodes):
if node.get('name') == node_name:
return i
return -1
def _export_vrm(self, vrm_data: Dict, output_path: str):
"""Export to VRM file format"""
# Convert to GLB first, then add VRM extension
import struct
import json
output_path = Path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
# Save as JSON for now (actual VRM requires binary GLB + VRM extension)
with open(output_path.with_suffix('.json'), 'w') as f:
json.dump(vrm_data, f, indent=2)
# Note: Full VRM export requires binary GLB container
# This is a simplified version that creates a VRM-compatible structure
print(f"⚠️ VRM structure saved to output_path.with_suffix('.json')")
print(" Full VRM export requires binary GLB container with VRM extension")
GLBs usually use Standard Lit shaders (PBR). VRM uses MToon (a specific anime-style shader). Without a shader conversion, your avatar will turn bright pink (missing shader) or completely black.
To get a "fixed" conversion, you cannot rely on a simple drag-and-drop web tool. You need a pipeline that repairs the rig, renames the bones, and bakes the textures. convert glb to vrm fixed
Title: How to Convert GLB to VRM (Fixed Method)
Converting a standard 3D model (GLB) to a VRM avatar for VRChat or other social platforms can often result in broken bones or "T-poses." Here is the reliable method to ensure your model converts correctly.
Step 1: Preparation
Ensure your GLB model is "humanoid" ready. If you created it in Blender, verify that the armature fits the standard humanoid bone structure (Hips, Spine, Neck, Head, Shoulders, Arms, Legs). # glb_to_vrm_converter
Step 2: The Conversion Tool
The most stable way to convert GLB to VRM is using the VRoid Studio (for automatic conversion) or Blender with the VRM Add-on.
Step 3: Exporting
Select your mesh and armature. Export as VRM. In the export settings, ensure "Force T-Pose" is checked during the initial setup, then unchecked for the final export to preserve your pose.
Step 4: Testing
Upload your new VRM file to the VRM Viewer or VRChat to ensure the eyes, fingers, and limbs move correctly. GLBs usually use Standard Lit shaders (PBR)
Click Export VRM.
Test it: Drag your new .vrm file into VRM Posing Viewer. Does the model follow the mouse? Are the eyes blinking? Yes. That is the "fixed" result.
The "Fixed" VRM file was tested in the VRM Viewer (web) and VRChat.
| Feature | Pre-Fix (GLB) Status | Post-Fix (VRM) Status |
| :--- | :--- | :--- |
| Pose | Relaxed / Arbitrary | Standardized T-Pose |
| Scaling | Variable (Unstable) | Confirmed 1:1 Scale |
| Materials | PBR (Standard) | MToon (Optimized for Toon shading) |
| Expression | N/A | Blendshapes mapped for Blink/Viseme |
| Colliders | N/A | Collider components added to hips/chest for physics |