+++ date = "2024-03-17" tags = ["vrm", "threejs"] title = "three-vrmでvrmaを読み込む" slug = "vrm" +++ [three-vrm](https://github.com/pixiv/three-vrm)でvrmaを読み込むことができるようになりました。そこで今回は色々なtipsを紹介します。 `three-vrm`は、私がよく3d-modelの読み込みに使っている`three.js`を`.vrm`に対応させたものです。three.jsは`.gltf(v2.0)`を読み込めますので、その拡張である`.vrm`を`.gltf`や`.glb`に変換して読み込めばいいのですが、色々と問題があります。そのため`three-vrm`を使ったほうが見栄えが良くなります。 ## three-vrm -> vrma 使用するのは、`npm`, `webpack`, `ts`あたりです。 nodeは`v18.14.1`です。場合によっては`nvm`を使用してください。 ```sh . ├── dist │ ├── index.html │ ├── vrm/ai.vrm │ └── vrma/VRMA_01.vrma ├── package.json ├── src │ └── index.ts ├── tsconfig.json └── webpack.config.js ``` `./dist/vrm/`, `./dist/vrma/`にファイルを置いてください。 - vrm : [download](https://hub.vroid.com/characters/675572020956181239/models/7175071267176594918) - vrma : [download](https://vroid.booth.pm/items/5512385) 後述しますが、`src/index.ts`の以下の部分で読み込みます。 ```js:src/index.ts load("/vrm/ai.vrm"); load("/vrma/VRMA_01.vrma"); ``` ```json:package.json { "name": "model", "version": "1.0.0", "private": true, "scripts": { "build": "webpack", "dev": "webpack-dev-server --open" }, "devDependencies": { "ts-loader": "^9.5.1", "typescript": "^5.4.2", "webpack": "^5.90.3", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.3" }, "dependencies": { "@pixiv/three-vrm": "^2.1.1", "@pixiv/three-vrm-animation": "^2.1.1", "three": "^0.162.0" } } ``` ```json:tsconfig.json { "compilerOptions": { "target": "es2016", "module": "commonjs", "skipLibCheck": true } } ``` ```js:webpack.config.js const path = require('path'); module.exports = { mode: 'development', entry: './src/index.ts', module: { rules: [ { test: /\.ts$/, loader: 'ts-loader' } ] }, resolve: { extensions: ['.ts', '.js'] }, output: { filename: 'main.js', path: path.join(__dirname, "dist") }, devServer: { static: { directory: path.join(__dirname, "dist"), } } } ``` ```sh $ npm i ``` ```js:src/index.ts import * as THREE from "three" import { Vector3 } from "three"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader" import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import { VRMLoaderPlugin } from "@pixiv/three-vrm"; import { createVRMAnimationClip, VRMAnimationLoaderPlugin } from "@pixiv/three-vrm-animation"; window.addEventListener("DOMContentLoaded", () => { const canvas = document.getElementById("canvas"); if (canvas == null) return; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 30, canvas.clientWidth/canvas.clientHeight, 0.1, 20); camera.position.set(0.0, 0, -4.0) camera.rotation.set(0.0, Math.PI, 0.0) camera.lookAt(new THREE.Vector3(0, 0, 0)); const renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(canvas.clientWidth, canvas.clientHeight); renderer.setClearColor(0x7fbfff, 1.0); canvas.appendChild(renderer.domElement); const light = new THREE.DirectionalLight(0xffffff, Math.PI); light.position.set(1.0, 1.0, 1.0); scene.add(light); let currentVrm: any = undefined; let currentVrmAnimation: any = undefined; let currentMixer:any = undefined; function load(url: string) { loader.load( url, (gltf) => { tryInitVRM(gltf); tryInitVRMA(gltf); }, (progress) => console.log( "Loading model...", 100.0 * (progress.loaded / progress.total), "%" ), (error) => console.error(error) ); } function tryInitVRM(gltf: any) { const vrm = gltf.userData.vrm; if ( vrm == null ) { return; } currentVrm = vrm; scene.add(vrm.scene); initAnimationClip(); } function tryInitVRMA(gltf: any) { const vrmAnimations = gltf.userData.vrmAnimations; if (vrmAnimations == null) { return; } currentVrmAnimation = vrmAnimations[0] ?? null; initAnimationClip(); } function initAnimationClip() { if (currentVrm && currentVrmAnimation) { currentMixer = new THREE.AnimationMixer(currentVrm.scene); const clip = createVRMAnimationClip(currentVrmAnimation, currentVrm); currentMixer.clipAction(clip).play(); } } const loader = new GLTFLoader(); loader.register((parser) => { return new VRMLoaderPlugin(parser); }); loader.register((parser) => { return new VRMAnimationLoaderPlugin(parser); }); // ここで読み込む load("/vrm/ai.vrm"); load("/vrma/VRMA_01.vrma"); const clock = new THREE.Clock(); clock.start(); scene.background = new THREE.Color( 0x404040 ); const directionalLight = new THREE.DirectionalLight(0xffffff); directionalLight.position.set(1, 1, 1); scene.add(directionalLight); const ambientLight = new THREE.AmbientLight(0x333333); scene.add(ambientLight); const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.2; controls.enableRotate = true; controls.target.set( 0.0, 1.0, 0.0 ); const update = () => { controls.update(); requestAnimationFrame(update); const deltaTime = clock.getDelta(); if (currentMixer) { currentMixer.update(deltaTime); } if (currentVrm) { currentVrm.update(deltaTime); } renderer.render(scene, camera); } update(); }) ``` ```html:dist/index.html
``` ```sh $ npm run build $ npm run dev ```