NOTE: These scrips were created with Claude, please validate the structure of the optimized VRM separately before using in production!
Python3 https://www.python.org/downloads/
Pillow
pip install PillowResizes embedded VRM textures and encodes them as KTX2/Basis Universal while preserving VRM-specific extensions and metadata.
Usage:
python3 optimize_vrm.py input.vrm output.vrm [max_size] [quality] [options]Examples:
# Default: 1024px textures, ETC1S quality 128, with mipmaps
python3 optimize_vrm.py my_avatar.vrm my_avatar_optimized.vrm
# Smaller files: 512px textures, ETC1S quality 96
python3 optimize_vrm.py my_avatar.vrm my_avatar_tiny.vrm 512 96
# Higher-quality UASTC output
python3 optimize_vrm.py my_avatar.vrm my_avatar_hq.vrm 2048 --mode uastc
# Disable KTX2 and use the original PNG/JPEG pipeline
python3 optimize_vrm.py my_avatar.vrm my_avatar_legacy.vrm --no-texture-compression
# Legacy pipeline with JPEG quality 90
python3 optimize_vrm.py my_avatar.vrm my_avatar_legacy.vrm 1024 90 --no-texture-compressionParameters:
max_size: Maximum texture dimension in pixels (default: 1024)quality: ETC1S quality 1-255 (default: 128), or JPEG quality 1-100 when--no-texture-compressionis used (default: 85)--mode:etc1sfor smaller files oruastcfor higher quality--uastc-level: UASTC quality level 0-4 (default: 2)--zstd: UASTC Zstandard compression level 0-22 (default: 18)--no-mipmaps: Disable mipmap generation--no-texture-compression/--no-ktx2: Disable KTX2 encoding; retain transparent textures as PNG and convert opaque textures to JPEG--jpeg-quality: Set legacy JPEG quality explicitly (default: 85)--ktx PATH: Use a specificktxor legacytoktxexecutable
Notes:
- Supports RGB and alpha textures
- Marks colour textures as sRGB and data/normal textures as linear
- The default mode adds the standard
KHR_texture_basisuglTF extension - Default KTX2-only output requires client support for
KHR_texture_basisu - The legacy PNG/JPEG mode does not require KTX-Software or
KTX2Loader - Maintains VRM structure and metadata
- Safe to run multiple times
Validates VRM file structure and three-vrm compatibility.
Usage:
python3 validate_vrm.py file.vrmWhat it checks:
- ✓ Valid GLB/glTF 2.0 format
- ✓ VRM extension present
- ✓ Buffer sizes match
- ✓ All buffer views within bounds
- ✓ PNG, JPEG, and KTX2/Basis texture metadata
- ✓ File size warnings
- ✓ Structure integrity
Returns:
- Exit code 0 if valid
- Exit code 1 if validation fails
Supported texture formats:
- ✅ image/png
- ✅ image/jpeg
- ✅ image/ktx2, when
GLTFLoaderis configured withKTX2Loader - ❌ image/webp (not handled by this optimizer)
That's why this script uses PNG/JPEG only - three-vrm doesn't support newer formats like KTX2 or Basis Universal yet.
Install the official Khronos KTX-Software tools as well and ensure ktx (or the
legacy toktx) is on PATH. Alternatively, pass its location with
--ktx /path/to/ktx.
KTX-Software is optional when --no-texture-compression is used.
"Module 'PIL' not found"
pip install Pillow --break-system-packages # On some systemsVRM won't load after optimization
# Run the validator to check what's wrong
python3 validate_vrm.py your_file.vrm
# If it reports errors, try more conservative settings
python3 optimize_vrm.py input.vrm output.vrm 2048 192
# Or use standard PNG/JPEG textures for clients without KTX2Loader
python3 optimize_vrm.py input.vrm output.vrm --no-texture-compressionTextures look too compressed
# Increase ETC1S quality
python3 optimize_vrm.py input.vrm output.vrm 1024 192
# Or use higher-quality UASTC
python3 optimize_vrm.py input.vrm output.vrm 1024 --mode uastcFile is still too large
# Use more aggressive ETC1S compression
python3 optimize_vrm.py input.vrm output.vrm 512 64Web/Mobile (best performance):
python3 optimize_vrm.py input.vrm output.vrm 512 96
# Results in ~1-2MB filesDesktop/VRChat (balanced):
python3 optimize_vrm.py input.vrm output.vrm 1024 128High quality/archival:
python3 optimize_vrm.py input.vrm output.vrm 2048 95
# Results in ~8-12MB filesMost VRMs use 1024x1024 or even 512x512 textures. Large textures are overkill for real-time rendering and causes:
- Slow loading times
- High memory usage
- GPU texture upload stutters
- Poor performance in React Three Fiber
- Janky frame rates
Public domain / MIT - use however you want!