asset-optimization
Optimize game assets through compression, format conversion, and streaming. Covers textures, meshes, audio, and automated batch processing workflows.
Asset Optimization
Workflow Overview
Asset optimization follows this 4-step pattern:
- Audit - Identify largest assets, measure load times
- Compress - Apply platform-specific compression
- Convert - Transform to efficient runtime formats
- Validate - Verify quality and measure savings
Texture Optimization Workflow
Step 1: Audit Current Usage
# Find all textures and sort by size
find ./Assets/Textures -type f \( -name "*.png" -o -name "*.tga" \) -exec ls -lh {} \; | sort -k5 -hr | head -20
# Output total size
du -sh ./Assets/Textures
Step 2: Apply Compression
BAD - Uncompressed RGBA32:
Character_Diffuse.png: 2048x2048, RGBA32
Size: 16 MB
Memory: 16 MB at runtime
GOOD - Platform-optimized BC7:
Character_Diffuse.dds: 2048x2048, BC7
Size: 2 MB
Memory: 2 MB at runtime (8x savings)
Step 3: Batch Convert with texconv
# Windows - Convert all PNG to BC7 DDS with mipmaps
for file in ./source/*.png; do
texconv -f BC7 -m 0 -o ./optimized/ "$file"
done
# Mobile - Convert to ASTC 6x6
for file in ./source/*.png; do
astcenc -cl "$file" ./optimized/$(basename "$file" .png).astc 6x6 -medium
done
Step 4: Validate Quality
# Compare file sizes
du -sh ./source ./optimized
# Visual diff (requires ImageMagick)
compare -metric PSNR source.png optimized.png diff.png
WebP/AVIF Conversion for Web
WebP Conversion
# Single file - 80% quality, lossless alpha
cwebp -q 80 input.png -o output.webp
# Batch convert entire directory
find ./images -name "*.png" -exec bash -c 'cwebp -q 80 "$0" -o "${0%.png}.webp"' {} \;
# With fallback generation
for img in ./images/*.png; do
cwebp -q 80 "$img" -o "${img%.png}.webp"
# Keep original as fallback
done
Before/After Example:
hero-banner.png: 1.2 MB (PNG, lossless)
hero-banner.webp: 180 KB (WebP, 85% savings)
AVIF Conversion (Better Compression)
# Install avif encoder
npm install -g @squoosh/cli
# Convert with quality 60 (good balance)
squoosh-cli --avif '{"cqLevel":60}' input.png
# Batch process
find ./images -name "*.png" | xargs squoosh-cli --avif '{"cqLevel":60}'
Compression Comparison:
product-photo.jpg: 450 KB (JPEG)
product-photo.webp: 210 KB (WebP, 53% savings)
product-photo.avif: 120 KB (AVIF, 73% savings)
HTML Picture Element with Fallbacks
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.png" alt="Hero banner" width="1200" height="600">
</picture>
Mesh Optimization Workflow
Step 1: Analyze Polygon Count
# Blender script - print mesh stats
import bpy
for obj in bpy.data.objects:
if obj.type == 'MESH':
print(f"{obj.name}: {len(obj.data.polygons)} tris")
Step 2: Generate LODs
BAD - Single LOD for all distances:
Character.fbx: 80,000 triangles at all distances
Draw calls: 100 characters = 8M triangles/frame
GOOD - Distance-based LODs:
Character_LOD0.fbx: 80,000 tris (0-10m)
Character_LOD1.fbx: 30,000 tris (10-30m)
Character_LOD2.fbx: 10,000 tris (30-60m)
Character_LOD3.fbx: 2,000 tris (60m+)
Draw calls at 30m: 100 characters = 1M triangles/frame (8x savings)
Step 3: Apply Mesh Compression
// Unity - Enable mesh compression in import settings
ModelImporter importer = (ModelImporter)assetImporter;
importer.meshCompression = ModelImporterMeshCompression.High;
importer.optimizeMeshPolygons = true;
importer.optimizeMeshVertices = true;
Audio Optimization Workflow
Step 1: Identify Compression Strategy
Music (long, looped): Vorbis 128-192 kbps, STREAM
SFX (short, one-shot): ADPCM, DECOMPRESS_ON_LOAD
Voice (dialogue): Vorbis 96-128 kbps, STREAM
Ambient (background): Vorbis 96 kbps, STREAM
Step 2: Convert Audio Files
# Convert WAV to Vorbis OGG
ffmpeg -i music.wav -c:a libvorbis -b:a 128k music.ogg
# Batch convert all music files
for file in ./audio/music/*.wav; do
ffmpeg -i "$file" -c:a libvorbis -b:a 128k "${file%.wav}.ogg"
done
# Convert to ADPCM for short SFX
ffmpeg -i sfx.wav -c:a adpcm_ima_wav sfx_compressed.wav
Before/After Example:
background_music.wav: 45 MB (uncompressed PCM)
background_music.ogg: 4 MB (Vorbis 128kbps, 91% savings)
Step 3: Set Unity Import Settings
// Unity Editor script - batch configure audio
AudioImporter audioImporter = (AudioImporter)assetImporter;
if (assetPath.Contains("Music"))
{
audioImporter.loadInBackground = true;
audioImporter.preloadAudioData = false;
audioImporter.compressionFormat = AudioCompressionFormat.Vorbis;
audioImporter.quality = 0.7f; // 128 kbps
}
else if (assetPath.Contains("SFX"))
{
audioImporter.loadInBackground = false;
audioImporter.preloadAudioData = true;
audioImporter.compressionFormat = AudioCompressionFormat.ADPCM;
}
Automated Batch Processing
#!/usr/bin/env python3
# Production-ready asset processor
import subprocess
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
PLATFORM_SETTINGS = {
'pc': {'format': 'bc7', 'max_size': 4096, 'quality': 'high'},
'mobile': {'format': 'astc', 'max_size': 1024, 'quality': 'medium'},
'console': {'format': 'bc7', 'max_size': 2048, 'quality': 'high'},
}
def compress_texture(input_path: Path, output_path: Path, platform: str):
"""Compress single texture for target platform."""
config = PLATFORM_SETTINGS[platform]
output_path.parent.mkdir(parents=True, exist_ok=True)
cmd = [
'texconv',
'-f', config['format'],
'-w', str(config['max_size']),
'-h', str(config['max_size']),
'-m', '0', # Generate all mipmaps
'-sepalpha', # Separate alpha channel
'-o', str(output_path.parent),
str(input_path)
]
subprocess.run(cmd, check=True, capture_output=True)
# Report savings
original_size = input_path.stat().st_size
compressed_size = output_path.stat().st_size
savings = (1 - compressed_size / original_size) * 100
print(f"{input_path.name}: {savings:.1f}% savings")
def batch_process(input_dir: Path, output_dir: Path, platform: str, workers: int = 8):
"""Process all textures in parallel."""
textures = list(input_dir.glob('**/*.png')) + list(input_dir.glob('**/*.tga'))
with ThreadPoolExecutor(max_workers=workers) as executor:
futures = []
for texture in textures:
rel_path = texture.relative_to(input_dir)
output_path = output_dir / rel_path.with_suffix('.dds')
futures.append(executor.submit(compress_texture, texture, output_path, platform))
# Wait for all
for future in futures:
future.result()
if __name__ == '__main__':
batch_process(
input_dir=Path('./Assets/Textures/Source'),
output_dir=Path('./Assets/Textures/Optimized'),
platform='pc'
)
Platform-Specific Guidelines
Mobile Budget
Textures: Max 1024x1024, prefer 512x512
ASTC 8x8 compression (0.5 bytes/pixel)
Total budget: 100-500 MB
Meshes: Characters: 10K tris max
Props: 500 tris max
Total scene: 500K tris
Audio: Vorbis compression required
Stream files > 200 KB
Total budget: 20-50 MB
Console/PC Budget
Textures: Max 4096x4096, common 2048x2048
BC7 compression (1 byte/pixel)
Total budget: 2-4 GB
Meshes: Hero characters: 80-100K tris
NPCs: 25-30K tris
Total scene: 8-10M tris
Audio: Light compression acceptable
Stream music and voice
Total budget: 200-500 MB
Quick Reference
Texture Formats by Platform:
PC/Console: BC7 (best) or BC1/DXT1 (smaller)
iOS: ASTC 6x6 (balanced) or PVRTC (older)
Android: ETC2 or ASTC 8x8
Web: WebP (good) or AVIF (best)
Compression Tools:
texconv # DirectX texture tool (BC7, BC1)
astcenc # ARM ASTC encoder
cwebp # Google WebP encoder
squoosh-cli # Multi-format web optimizer
ffmpeg # Audio/video conversion
File Size Targets:
Character texture: 1-2 MB (compressed)
Environment texture: 2-4 MB (compressed)
Music track: 3-5 MB (Vorbis)
SFX: 10-100 KB (ADPCM)
Use this skill: When build sizes are too large, assets load slowly, or memory usage is high.