4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

TypeScript + three.jsでWeb上で表示したVRMモデルにジャグリングさせる

Last updated at Posted at 2020-07-24

VRMという3Dモデルのファイルフォーマットがあるが、
今回Web上で表示した上にジャグリングをさせるという依頼を受けたので、
依頼主様の許可を得て備忘録として記事を投稿する。

#VRMとは

「VRM」はVRアプリケーション向けの人型3Dアバター(3Dモデル)データを扱うためのファイルフォーマットです。glTF2.0をベースとしており、誰でも自由に利用することができます。
また、Unity向けのVRMファイルの読み書きを行うC#による標準実装(UniVRM)がオープンソースで提供されます。
公式サイトより引用

用途はアバターとして利用できて、VRゲームや配信なんかで使うものらしい。
昨今のVtuberみたいなやつかな?
どちらにしてもWebで利用するものではなさそう・・・。

#でもWebで利用する
webで表示ってことはThree.jsを使うんだろうなぁ・・・。
VRMのベースになっているglTF2.0のローダーもあるし。
んで調べたら@pixiv/three-vrmなんてものがあるじゃないですか。
実際使っている人もいる。

https://qiita.com/drumath2237/items/2d43d7e3beb7024285ae
https://qiita.com/Akatsuki1910/items/8c18858d5a8807660008
https://qiita.com/blachocolat/items/2e16eb78328b7997de3c

##すでにある記事を参考にコードを書いてみる

    const container = document.querySelector('セレクタ');
    const width = container.clientWidth;
    const height = container.clientHeight;

    const renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true,
    });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(width, height);
    renderer.setClearColor(0x000000, 0.0);
    container.appendChild(renderer.domElement);

    const camera = new THREE.PerspectiveCamera(45, this.width / this.height, 0.1, 1000);
    camera.position.set(0, 0.75, -3);
    camera.rotation.set(0, Math.PI, 0);

    const scene = new THREE.Scene();

    const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x080820, 1);
    scene.add(hemisphereLight);

    const pointLight = new THREE.PointLight(0xffffff, 1, 100);
    pointLight.position.set(0, 0, -2);
    scene.add(pointLight);


    const loader = new GLTFLoader();
    loader.load('VRMモデルのパス', async (gltf: any) => {
      VRM.from(gltf).then((vrm) => {
        scene.add(vrm.scene);

        // 以下VRMの操作

      });
    });

割と簡単に表示できる。
@pixiv/three-vrmを使えば自分の持っている3Dモデル、いわゆる「うちの子」Web上で表示して遊ぶことができるわけか。
私にはうちの子をいないので今回は養子をとることにしました。
FireShot Capture 003 - Document - localhost.png

#ジャグリングさせる
依頼内容はgunswapというサイトにジャグリングシミュレーターを流用してVRMにジャグリングをさせるというもの。
ソースを確認するとジャグリングの小道具のキャッチするときと投げるときの肘と手の座標を算出しているっぽい。
(こういうのサイトスワップっていうのね。初めて知った。)

@pixiv/three-vrmは各部位のボーンを回転させることができるけどこれを利用して算出した座標に肘と手持っていくのはどう考えてもしんどいので、
three-ikというのを利用する。

IKとは

https://codelabo.com/posts/20200229090229

three-ikの参考記事

https://qiita.com/naotaro0123/items/13f297fa52a92af36d37
https://note.com/npaka/n/n477c15fedaef

結果

ソースの公開はできないのでデモページだけ公開する。
https://torabe.github.io/juggling/
Escキーを押すことでGUIが開きパラメータの調整を可能にしている。
(パラメータによってはボールが手から離れたり破綻するけど・・・。)

工夫・苦労した点

three-ikによるボーンのジョイント

IKでボーンをつなぐとき二の腕、腕、手までにするとボールを手首で受けるようになってしまったので、中指の第一関節までジョイントした。
おかげで、手首が動くようになりボールのキャッチやトスの動きがより自然な形になった。

指の形の調整

何もしないとパーの形のままなので少し指を曲げるように調整するが、

    //右人差し指第三指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightIndexDistal).rotation.z = -Math.PI / 18;
    //右人差し指第二指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightIndexIntermediate).rotation.z = -Math.PI / 3.6;
    //右人差し指第一指骨
    //vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightIndexProximal).rotation.z = -Math.PI / 6;
    //右小指第三指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightLittleDistal).rotation.z = -Math.PI / 18;
    //右小指第二指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightLittleIntermediate).rotation.z = -Math.PI / 3.6;
    //右小指第一指骨
    //vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightLittleProximal).rotation.z = -Math.PI / 6;
    //右中指第三指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightMiddleDistal).rotation.z = -Math.PI / 18;
    //右中指第二指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightMiddleIntermediate).rotation.z = -Math.PI / 3.6;
    //右中指第一指骨
    //vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightMiddleProximal).rotation.z = -Math.PI / 6;
    //右薬指第三指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightRingDistal).rotation.z = -Math.PI / 18;
    //右薬指第二指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightRingIntermediate).rotation.z = -Math.PI / 3.6;
    //右薬指第一指骨
    //vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightRingProximal).rotation.z = -Math.PI / 6;
    //右親指第三指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightThumbDistal).rotation.y = -Math.PI / 3;
    //vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightThumbDistal).rotation.y = -Math.PI / 3;
    //右親指第二指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightThumbDistal).rotation.y = -Math.PI / 3;
    //vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.RightThumbIntermediate).rotation.z = -Math.PI / 3.6;
    //右親指第一指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftThumbProximal).rotation.y = Math.PI / 12;
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftThumbProximal).rotation.z = Math.PI / 12;

    //左人差し指第三指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftIndexDistal).rotation.z = Math.PI / 18;
    //左人差し指第二指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftIndexIntermediate).rotation.z = Math.PI / 3.6;
    //左人差し指第一指骨
    //vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftIndexProximal).rotation.z = Math.PI / 6;
    //左小指第三指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftLittleDistal).rotation.z = Math.PI / 18;
    //左小指第二指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftLittleIntermediate).rotation.z = Math.PI / 3.6;
    //左小指第一指骨
    //vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftLittleProximal).rotation.z = Math.PI / 6;
    //左中指第三指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftMiddleDistal).rotation.z = Math.PI / 18;
    //左中指第二指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftMiddleIntermediate).rotation.z = Math.PI / 3.6;
    //左中指第一指骨
    //vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftMiddleProximal).rotation.z = Math.PI / 6;
    //左薬指第三指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftRingDistal).rotation.z = Math.PI / 18;
    //左薬指第二指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftRingIntermediate).rotation.z = Math.PI / 3.6;
    //左薬指第一指骨
    //vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftRingProximal).rotation.z = Math.PI / 6;
    //左親指第三指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftThumbDistal).rotation.y = Math.PI / 3;
    //vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftThumbDistal).rotation.y = Math.PI / 3;
    //左親指第二指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftThumbDistal).rotation.y = Math.PI / 3;
    //vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftThumbIntermediate).rotation.z = Math.PI / 3.6;
    //左親指第一指骨
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftThumbProximal).rotation.y = Math.PI / 12;
    vrm.humanoid.getBoneNode(VRMSchema.HumanoidBoneName.LeftThumbProximal).rotation.z = Math.PI / 12;

長い。

首の角度

GUIでボールの高さの調整したとき首上に曲げてボールの頂点を見るようにしたいが、
各部位のボーンもつpositionプロパティはおそらく親のボーンからの相対的な座標でscene上の絶対的な座標をもとに角度を算出する方法がわからなかった。
(WebGLの知識不足かな?)
あと、
FireShot Capture 006 - Document - torabe.github.io.png
髪の毛が重力を無視する・・・。

感想

実はTypeScriptは今回が初めてなので「型がanyだらけでTypeScriptの意味ないじゃん」ってコードになっている。要学習。

VRMがもともと人が中に入って動かすことを想定しているためか、
Web上で表示できるといっても、動かすのはひと手間も二手間も必要って印象。
髪の毛が重力に逆らうのは@pixiv/three-vrmでどうにかできるのか?
それともVRMモデル制作時に何か設定があるのか?

でもやってみるととても面白かった。
3Dモデル作ってみようかな?

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?