home / skills / project-n-e-k-o / n.e.k.o / vrm-mtoon-outline

vrm-mtoon-outline skill

/.agent/skills/vrm-mtoon-outline

This skill helps adjust VRM MToon outline thickness by switching to screen coordinates, keeping outlines consistent across zoom and scale.

npx playbooks add skill project-n-e-k-o/n.e.k.o --skill vrm-mtoon-outline

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
4.7 KB
---
name: vrm-mtoon-outline
description: Adjusting VRM MToon material outline thickness in three-vrm. Covers outline width modes, property access, and how to fix outlines that appear too thick when models are scaled.
---

# VRM MToon Outline Thickness Adjustment

This skill covers how to adjust outline thickness for VRM models using MToon materials in `@pixiv/three-vrm`.

## Common Symptoms

1. **Outline too thick** when model is scaled up or camera is close
2. **Outline thickness inconsistent** at different zoom levels
3. **Need to adjust outline programmatically** at runtime

---

## Key Concepts

### Outline Width Modes

MToon materials have two main outline width modes:

| Mode | Description | Behavior |
|------|-------------|----------|
| `'worldCoordinates'` | Outline width is a physical world-space size | Outline appears **thicker when model is scaled up** or camera is closer |
| `'screenCoordinates'` | Outline width is relative to screen pixels | Outline **stays consistent size** regardless of zoom/scale |

### Critical Properties

```javascript
material.outlineWidthMode    // 'none' | 'worldCoordinates' | 'screenCoordinates'
material.outlineWidthFactor  // Number - the actual width value
material.isMToonMaterial     // Boolean - true for MToon materials
material.needsUpdate         // Set to true after modifying properties
```

---

## Solution: Switch to Screen Coordinates

To make outlines stay consistent regardless of zoom/scale:

```javascript
function adjustOutlineThickness(vrm, screenFactor = 0.005) {
    vrm.scene.traverse((object) => {
        if (object.isMesh || object.isSkinnedMesh) {
            const materials = Array.isArray(object.material) ? object.material : [object.material];
            
            materials.forEach(material => {
                if (!material || !material.isMToonMaterial) return;
                
                // Check if this material has outlines enabled
                const hasOutline = material.outlineWidthFactor > 0 && 
                                   material.outlineWidthMode !== 'none';
                
                if (hasOutline) {
                    // Switch to screen coordinates mode
                    material.outlineWidthMode = 'screenCoordinates';
                    material.outlineWidthFactor = screenFactor;
                    material.needsUpdate = true;
                }
            });
        }
    });
}
```

### Factor Value Guidelines

For `screenCoordinates` mode:

| Factor Value | Approximate Effect |
|-------------|-------------------|
| `0.002 - 0.003` | Very thin outline (1 pixel) |
| `0.005` | Thin outline (1-2 pixels) |
| `0.01` | Medium outline (2-3 pixels) |
| `0.02+` | Thick outline |

---

## Detection Pattern

To diagnose outline issues, log all materials:

```javascript
function debugOutlineMaterials(vrm) {
    let count = 0;
    vrm.scene.traverse((object) => {
        if (!object.isMesh && !object.isSkinnedMesh) return;
        
        const materials = Array.isArray(object.material) ? object.material : [object.material];
        materials.forEach(material => {
            if (material?.isMToonMaterial || 'outlineWidthFactor' in material) {
                count++;
                console.log({
                    name: material.name || '未命名',
                    type: material.type,
                    isMToonMaterial: material.isMToonMaterial,
                    outlineWidthMode: material.outlineWidthMode,
                    outlineWidthFactor: material.outlineWidthFactor
                });
            }
        });
    });
    console.log(`Found ${count} MToon/Outline materials`);
}
```

---

## Important Notes

> [!IMPORTANT]
> **Call timing matters!** The function must be called AFTER `currentModel` is set. In `vrm-core.js`, the `loadModel()` function sets `manager.currentModel` near line 923. Any function that accesses `currentModel` must be called after this point.

> [!NOTE]
> Materials named `"XXX (Outline)"` are outline pass materials automatically created by three-vrm. They share properties with the main material but render the outline effect.

---

## API Reference (three-vrm MToon)

The enum values for `outlineWidthMode`:

```javascript
// From three-vrm.module.min.js
{
    None: "none",
    WorldCoordinates: "worldCoordinates", 
    ScreenCoordinates: "screenCoordinates"
}
```

| Property | Type | Description |
|----------|------|-------------|
| `outlineWidthMode` | string | Width calculation mode |
| `outlineWidthFactor` | number | Width value (meaning depends on mode) |
| `outlineColorFactor` | Color | Outline color |
| `outlineLightingMixFactor` | number | How much lighting affects outline |
| `outlineWidthMultiplyTexture` | Texture | Texture to modulate outline width |

Overview

This skill explains how to adjust VRM MToon material outline thickness when using @pixiv/three-vrm. It focuses on outline width modes, the key MToon properties to change, and a reliable fix for outlines that look too thick when models are scaled or the camera is close. The guidance is practical and ready to apply at runtime.

How this skill works

The approach inspects every mesh in the VRM scene for MToon materials and switches outlineWidthMode to screenCoordinates when outlines are enabled. It then sets a screen-based outlineWidthFactor and marks the material for update so the renderer applies the change immediately. Detection helpers let you log and diagnose materials before modifying them.

When to use it

  • Model outlines look too thick after scaling the model or zooming the camera
  • Outline thickness varies with camera distance and must be consistent on-screen
  • You need to change outline thickness programmatically at runtime
  • You want pixel-consistent outlines across devices and resolutions
  • Debugging which materials in the scene contribute to outline artifacts

Best practices

  • Run the adjustment after the model is fully loaded and currentModel is set
  • Prefer screenCoordinates for consistent on-screen thickness; use worldCoordinates for physically-scaled effects
  • Pick outlineWidthFactor by testing on target resolutions (0.002–0.005 for thin, 0.01 for medium)
  • Always set material.needsUpdate = true after changing MToon properties
  • Log materials first to avoid accidentally changing non-MToon shaders or outline-pass materials

Example use cases

  • Automatically normalize outlines when loading user-uploaded VRM avatars with arbitrary scale
  • Adjust outline thickness in a character customization UI where users zoom in on the model
  • A runtime toggle to switch between stylized (screen-based) and physical (world-based) outlining
  • Batch-fix outlines for imported model packs that use inconsistent MToon settings

FAQ

Why do outlines get thicker when I scale the model?

If outlineWidthMode is set to worldCoordinates the width is a world-space size, so scaling the mesh increases the visible outline thickness.

What factor value should I use for a 1–2 pixel outline?

Use screenCoordinates mode and start around 0.005; reduce toward 0.002–0.003 for thinner (≈1px) outlines depending on resolution.