WebサイトにVRMを表示したい！！！

**🎄IPFactory Advent Calendar2023_24日目の記事です🎄**

https://qiita.com/advent-calendar/2023/ipfactory


## はじめに
この記事は、[three.js](https://threejs.org/)と[three-vrm](https://github.com/pixiv/three-vrm)を使用して、**VRMをWebサイトに表示させる方法**を紹介します！！！

## 環境
・VSCode　(何でもヨシ)
・three.js 
・three-vrm
・VSCode Live Server (確認用)

## 条件
今回は **npm** を使用せず、[UNPKG](https://www.unpkg.com/)を多用します。

## とりあえずサンプルを動かす

### 1：HTMLの準備
適当なEditor(今回はVSCode)で作成したHTMLに、下のコードを持ってきます。

https://github.com/pixiv/three-vrm/blob/dev/packages/three-vrm/examples/basic.html


変更するのは、**importmap**の部分

```html:basic.html
<script type="importmap">
{
    "imports": {
    "three": "https://unpkg.com/three@0.159.0/build/three.module.js",
    "three/addons/": "https://unpkg.com/three@0.159.0/examples/jsm/",
    "@pixiv/three-vrm": "https://unpkg.com/@pixiv/three-vrm@2.0.7/lib/three-vrm.module.js"
    }
}
</script>
```

### 2：VRMの準備

公式が用意しているSampleモデルはこちら。

https://github.com/pixiv/three-vrm/blob/dev/packages/three-vrm/examples/models/


DLしてパスを修正すれば終了！

```js:basic.html
loader.load(

    // ココだよ！！！
    './Change/the/PATH/to/your/selected/VRMmodel.vrm',


    (gltf) => {
        const vrm = gltf.userData.vrm;
        VRMUtils.removeUnnecessaryVertices(gltf.scene);
        VRMUtils.removeUnnecessaryJoints(gltf.scene);
        
        vrm.scene.traverse((obj) => {
            obj.frustumCulled = false;
        });
        
        currentVrm = vrm;
        console.log(vrm);
        scene.add(vrm.scene);
    },
    (progress) => console.log('Loading model...', 100.0 * (progress.loaded / progress.total), '%'),
    (error) => console.error(error)
);
```

### 3：localhostで確認
面倒なので今回はVSCode Live Serverを使用してとりあえず見てみる。

https://github.com/ritwickdey/vscode-live-server

![スクショ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680925/50efb25c-b390-0c9c-0826-cbee33096107.png)

右下に追加された **`Go Live`** を押すと？

![スクショ1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680925/13ca6bbd-657e-1541-e1c9-08f5f3c5dc42.png)

表示された！
でも、ちょっと暗いですね

## 改造していく！
### VRMモデルの変更
有坂みと様の [セフィラちゃん](https://mk22.booth.pm/items/2953001) (有料モデル)を使用します

https://mk22.booth.pm/items/2953001

### 背景の色を緑に変える　(追加)

```js:html
    renderer.setClearColor(0x00ff00, 1.0)
```

### 明るくする　(ライトの調整)

```js:html
const light = new THREE.DirectionalLight(0xffffff,3);
light.position.set(1.0, 1.0, -1.0).normalize();
scene.add(light);
```
### 表情の変更　(追加)
animate()内で使用
欲しい表情が無かったら、blendshapeを別で設定する必要がある

```js:html
currentVrm.expressionManager.setValue('happy', 1.0);
currentVrm.expressionManager.update();
```

### 顔が見えるようにしたい　(カメラ位置の調整)
```js:html
// camera
const camera = new THREE.PerspectiveCamera(30.0, window.innerWidth / window.innerHeight, 0.1, 20.0);
camera.position.set(0.0, 1.0, -0.7);

// camera controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.screenSpacePanning = true;
controls.target.set(0.0, 1.1, 0.0);
controls.update();
```
### Tposeの変更
bone名は [こちら](https://github.com/pixiv/three-vrm/blob/d137ae95bc84ab13cd57aa3472a978e0f5d80696/packages/three-vrm-core/src/humanoid/VRMHumanBoneList.ts#L31) を参考にanimate()内で使用

```js:html
currentVrm.humanoid.getNormalizedBoneNode( 'rightUpperArm' ).rotation.z = -0.75;
currentVrm.humanoid.getNormalizedBoneNode( 'leftUpperArm' ).rotation.z = 0.75;
```
### コード全体

```html:html
<!DOCTYPE html>

<html>

<head>
    <meta charset="utf-8" />
    <title>three-vrm example</title>
    <meta name="viewport"
        content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <style>
        body {
            margin: 0;
        }

        canvas {
            display: block;
        }
    </style>
</head>

<body>
    <!-- Import maps polyfill -->
    <!-- Remove this when import maps will be widely supported -->
    <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>

    <script type="importmap">
            {
                "imports": {
                "three": "https://unpkg.com/three@0.159.0/build/three.module.js",
                "three/addons/": "https://unpkg.com/three@0.159.0/examples/jsm/",
                "@pixiv/three-vrm": "https://unpkg.com/@pixiv/three-vrm@2.0.7/lib/three-vrm.module.js"
                }
            }
            </script>

    <script type="module">
        import * as THREE from 'three';
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        import { VRMLoaderPlugin, VRMUtils } from '@pixiv/three-vrm';

        // renderer
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(window.devicePixelRatio);
        
        renderer.setClearColor(0x00ff00, 1.0)

        document.body.appendChild(renderer.domElement);

        // camera
        const camera = new THREE.PerspectiveCamera(30.0, window.innerWidth / window.innerHeight, 0.1, 20.0);
        camera.position.set(0.0, 1.0, -0.7);

        // camera controls
        const controls = new OrbitControls(camera, renderer.domElement);
        controls.screenSpacePanning = true;
        controls.target.set(0.0, 1.12, 0.0);
        controls.update();

        // scene
        const scene = new THREE.Scene();

        // light
        const light = new THREE.DirectionalLight(0xffffff,3);
        light.position.set(1.0, 1.0, -1.0).normalize();
        scene.add(light);

        // gltf and vrm
        let currentVrm = undefined;
        const loader = new GLTFLoader();
        loader.crossOrigin = 'anonymous';

        loader.register((parser) => {

            return new VRMLoaderPlugin(parser);

        });

        loader.load(
            './Change/the/PATH/to/your/selected/VRMmodel.vrm',
            (gltf) => {
                const vrm = gltf.userData.vrm;
                VRMUtils.removeUnnecessaryVertices(gltf.scene);
                VRMUtils.removeUnnecessaryJoints(gltf.scene);
                vrm.scene.traverse((obj) => {
                    obj.frustumCulled = false;
                });
                currentVrm = vrm;
                console.log(vrm);
                scene.add(vrm.scene);


            },
            (progress) => console.log('Loading model...', 100.0 * (progress.loaded / progress.total), '%'),
            (error) => console.error(error)
        );

        // animate
        const clock = new THREE.Clock();
        clock.start();
        
        function animate() {

            requestAnimationFrame(animate);

            const deltaTime = clock.getDelta();

            // update vrm components
            if (currentVrm) {
                

                currentVrm.humanoid.getNormalizedBoneNode( 'rightUpperArm' ).rotation.z = -0.75;
                currentVrm.humanoid.getNormalizedBoneNode( 'leftUpperArm' ).rotation.z = 0.75;

                currentVrm.expressionManager.setValue('happy', 1.0);
                currentVrm.expressionManager.update();
                currentVrm.update( deltaTime );

            }

            // render
            renderer.render(scene, camera);

        }

        animate();
    </script>
</body>

</html>
```

## 最終的にできたもの
![スクショ3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680925/253a6aff-315a-a480-26fb-477b22dcbcad.png)

ｾﾌｨﾃｬﾝかわいいねぇ～



## まとめ
three-vrmの使い方がなんとなく分かった気がします。
市販のVRMモデルを使用する場合は、 **ライセンス** をしっかり確認しましょう！！！