@moeru/three-mmd
v0.2.0-beta.2
Published
Use MMD on Three.js
Readme
@moeru/three-mmd
MMD loading, animation, materials, and runtime lifecycle for Three.js.
Install
pnpm add three @moeru/three-mmd
pnpm add -D @types/threeLoad a model and play a VMD animation
import { buildAnimation, MMDLoader, VMDLoader } from '@moeru/three-mmd'
import { AnimationMixer, Timer } from 'three'
const mmd = await new MMDLoader().loadAsync('/models/miku_v2.pmd')
const vmd = await new VMDLoader().loadAsync('/motions/wavefile_v2.vmd')
const mixer = new AnimationMixer(mmd.mesh)
const timer = new Timer()
mixer.clipAction(buildAnimation(vmd, mmd.mesh)).play()
// Call this once per render frame.
const update = () =>
mmd.updateWithMixer(timer.getDelta(), mixer)updateWithMixer() restores the previous animation pose, advances the mixer,
then applies MMD IK, grants, and the optional physics service. Use update()
directly when the mixer is managed elsewhere; do not call both methods for the
same frame.
For a static VPD pose, load it with VPDLoader and apply it with applyVPD:
import { applyVPD, VPDLoader } from '@moeru/three-mmd'
const vpd = await new VPDLoader().loadAsync('/poses/pose.vpd')
applyVPD(mmd, vpd)Coordinate multiple MMD assets
MMDAnimationManager owns one standard Three.js mixer per MMD model and can
also coordinate one camera animation and one audio source:
import { MMDAnimationManager } from '@moeru/three-mmd'
const manager = new MMDAnimationManager()
manager.add(firstMMD, { animation: firstAnimation })
manager.add(secondMMD, { animation: secondAnimation })
manager.add(camera, { animation: cameraAnimation })
manager.add(audio, { delayTime: 160 / 30 })
// Keep this as the only update call for the registered assets.
const update = () => manager.update(timer.getDelta())Animations passed to add are created and played by the manager. The mixer and
actions remain internal, so callers only need to register objects and call
update().
Physics plugins
Physics is provided by separate packages. Register one plugin before loading the model:
import { MMDLoader } from '@moeru/three-mmd'
import { MMDAmmoPlugin } from '@moeru/three-mmd-physics-ammo'
const mmd = await new MMDLoader()
.register(MMDAmmoPlugin)
.loadAsync('/models/miku_v2.pmd')The default material backend is the WebGL MMDToonMaterial.
Opt-in Physical material
MMDPhysicalMaterial keeps Three's native physical lights and environment
lighting. Register it before loading a model and provide a PMREM-backed
scene.environment for stable image-based lighting:
import { MMDLoader, MMDMaterialPlugin } from '@moeru/three-mmd'
import { MMDPhysicalMaterial } from '@moeru/three-mmd/materials/physical'
const loader = new MMDLoader()
loader.register(parser => new MMDMaterialPlugin(parser, {
materialType: MMDPhysicalMaterial,
}))
const mmd = await loader.loadAsync('/models/miku.pmx')The baseline maps diffuse color/map and double-sided rendering directly,
keeps metalness at 0, and approximates PMX shininess as GGX roughness.
PMX ambient, toon, sphere, outline, and texture-morph colors remain explicitly
unsupported by this pure Physical backend. PMX specular color is opt-in through
constructor options; when selecting options through the loader, provide a small
subclass:
import type { MMDMaterialDescriptor } from '@moeru/three-mmd/materials'
import { MMDPhysicalMaterial } from '@moeru/three-mmd/materials/physical'
class ProjectPhysicalMaterial extends MMDPhysicalMaterial {
constructor(descriptor: MMDMaterialDescriptor) {
super(descriptor, {
alphaMode: 'evaluate',
specularMode: 'physical-color',
})
}
}