Help us understand the problem. What is going on with this article?

JavaScriptでVRMのモデルビューワーを作る[PlayCanvas]

はじめに

PlayCanvas アドベントカレンダー6日目です!

PlayCanvasとは

PlayCanvasの開発はイギリスのPlayCanvas社(現在はSnapChatのSnap)が行っております。

公式ドキュメントによると

PlayCanvasは、インタラクティブなウェブコンテンツ用のビジュアル開発プラットフォームです。作成するツールとウェブアプリは、どちらもHTML5を使用しています。 プラットフォームはウェブでホストされているため、インストールするものは何もなく、対応されているウェブブラウザを実行する任意のデバイスからアクセスできます。

https://developer.playcanvas.com/ja/user-manual/introduction/

PlayCanvasでVRMのモデルビューワーを作る

1. BlenderでFBXに変換をして表示をする

PlayCanvasではFBXやOBJなどのエディター上でサポートされている形式であればそのまま表示できますのでBlederなどのモデリングツールで変換をする方法があります。現状PlayCanvasでモデリングツールを作る場合にはこの方法を取るとエディター上でVRMのモデルを表示することができます。

VRoid→Blender→PlayCanvasでwebに3Dキャラクターを表示してみる

VRMのモデルを表示する

VRMはglTF2.0という形式がベースとなっており、GLBのモデルを読み込む事ができれば表示できます。

GLTFの読み込めるJavaScriptライブラリ

gltfが読み込めるライブラリは、Three.jsやBabylon.jsなど他にも様々存在します。これからライブラリを選定されている方は、んUnityなどの挙動なども網羅的にまとめられている@cx20さんのこのリポジトリを確認すると良さそうです。
PlayCanvasについてもこちらで比較されています。

lib.PNG

GitHub ★ 120↑ gltf-test

PlayCanvas EditorでVRMを使う

1. PlayCanvasのエディターを使用するためにPlayCanvasに登録

https://playcanvas.jp

2. 自分のプロジェクト一覧にアクセスをする

Projet

  1. https://playcanvas.comにアクセスする
  2. Projectsをクリックする

3. 新規プロジェクトを作成する

  1. NEWボタンから新規プロジェクトを作成する new

4. Model Viewer Starter Kitを選択

- Model Viewer Starter Kit

Model Viewer Starter Kitにはカメラやカメラを操作するスクリプトが最初から入っております。

ModelViewer.PNG

5. シーンの選択画面の説明

作成したモデルビューワーを起動するとこのようなシーンを作成画面が出てきます。

https://blobscdn.gitbook.com/v0/b/gitbook-28427.appspot.com/o/assets%2F-Ln6IBXaqxl-ZPewffQr%2F-LngitHilFlR66-2BNhe%2F-LngitzvlqKuHtkwM4u6%2Fdemostart.png?generation=1567339817366292&alt=media<br>

画面の説明としては以下のようなものになります。

① ここからシーンを選択してプロジェクトの編集ができます
② シーンを追加できます
③ BUILDSでは今までのビルド一覧が見られます
④ PUBLISHではPlayCanvasプロジェクトの公開ができる
⑤ VERSION CONTROLではバージョン管理をできます
⑥ シーンの削除をできます

6. モデルビューワーが表示される

image.png

7. 起動する

作成されたモデルビューワーの起動はLaunchボタンを押すと起動できます。
kidou.PNG

Launchボタンが押されると別タブでPlayCanvasゲームを起動することができます。

起動するとマウスやキーボードを使用してモデルをグリグル見ることの出来るビューワーが作られました。こちらのプロジェクトではデフォルトでマウスとキーボードを使用してモデルをグリグリ操作できるスクリプトが入っています。
kidoudekita.PNG

モデルビューワのモデルをVRMに置き換える

PlayCanvasで現在表示されているモデルをVRMのモデルに置き換える

modelViewer2.PNG

PlayCanvas Editorにplaycanvas-gltfを追加

2. PlayCanvasのglbLoaderを使用して表示する

PlayCanvas Editor でVRM /glbを表示するためにはplaycanvas-gltf という公式の用意しているライブラリを使います。

PlaycanvasGLTF.PNG
playcanvas-gltf GitHub ★ 55 ↑

newAssets.PNG

newAssets.PNG

  • playcanvas-gltf.js
  • playcanvas-anim.js
    の2つのコードをコードエディターから追加をします。コードについては、playcanvas-gltf.js、playcanvas-anim.jsをそれぞれ新規作成をして、コピーアンドペーストします。

3. VRMを表示するためのスクリプトを追加する

同じ要領でvrm-loaderのような名前でScriptを追加します。

vrm-loader.js
/*jshint esversion: 2015, asi: true, laxbreak: true*/
const VrmLoader = pc.createScript('vrmloader');
VrmLoader.attributes.add("vrm", {"type": "asset"})

VrmLoader.prototype.loadAsset = function(asset,position,rotate) {
    const glb = asset.resource;
    const app = this.app
    const {x,y,z} = position
    loadGlb(glb, app.graphicsDevice,  (err, res) => {
        const asset = new pc.Asset('gltf', 'model', {
            url: ''
        });    
        asset.resource = res.model;
        const animationClips  = res.animations;
        asset.loaded = true;
        app.assets.add(asset);
        this.entity.addComponent('model', {
            asset: asset,
            castShadows: true,
            receiveShadows: true,
            castShadowsLightmap: true
        });

        if(this.entity.script.toon){
            this.entity.script.toon.enabled = true
        }
        this.app.fire(`loaded:model`)
    });

}
VrmLoader.prototype.initialize = function() {
   this.loadAsset(this.vrm, {x: 0, y: 0, z: 0})

};

この3つのスクリプトを追加されていれば大丈夫です。次からスクリプトを適用していきます。
addnew.PNG

4. モデルにスクリプトを適用する

vrmLoaderスクリプトをモデルに適用します。

vrm1.PNG

  1. エディター上から、モデルを選択
  2. ADD COMPONENTからSCRIPTを追加
  3. ADD SCRIPTからvrmLoaderを追加

5. VRMをアップロード

VRMをアップロードするにはドラッグアンドドロップをすることでPlayCanvasエディター上追加できます。
VRM123.PNG

6. 四角いキューブのモデルを削除する

Dash.PNG

  1. 左側のヒエラルキーからModelを選択します
  2. 右側のEntityの中のMODELをゴミ箱のボタンを押して削除します

7. VRMのモデルをSCRIPTのテンプレートに入れる

arrw.PNG

ドラッグアンドドロップをして入れ込んだVRMのモデルをセットします。

8. 起動してモデルを確認する。

VRMのモデルについてはPlayCanvasのエディター上では確認ができませんが、起動をすることで確認することができます。
NotToon.PNG

一応モデルの追加はこれでできました!
アウトラインの追加などもできれば追記をします。

アウトラインを追加したもの


9. スクリプトを追加する

PlayCanvasの開発者のPUBLICリポジトリを参考にToonシェーダーとスクリプトを追加します
Toon shader with skinning

toon.js
toon.js
/*jshint esversion: 6, asi: true, laxbreak: true*/
const Toon = pc.createScript('toon');
Toon.attributes.add('edgeSize', { type: 'number', default: 0.001 });
Toon.attributes.add('edgeColor', { type: 'rgba', default:[0,0,0,1] });
Toon.attributes.add("transformToonEdgeVS", {type: "asset"})
Toon.attributes.add("transformToonEdgeSkinnedVS", {type: "asset"})
Toon.attributes.add("lightDiffuseToonPS", {type: "asset"})
Toon.attributes.add("edgeToonPS", {type: "asset"})


Toon.prototype.initialize = function() {
    this.app.on(`loaded:model`, () => {
    const transformToonEdgeVS = this.transformToonEdgeVS.resource;
    const transformToonEdgeSkinnedVS = this.transformToonEdgeSkinnedVS.resource;
    const lightDiffuseToonPS = this.lightDiffuseToonPS.resource;
    const edgePS = this.edgeToonPS.resource;
    const meshes = this.entity.model.model.meshInstances;
    const edgeMeshes = [];

    for(let mesh of meshes) {
        const mat = mesh.material;
        mat.emissiveMap = mat.diffuseMap
        mat.emissive = mat.diffuse
        mat.update();

        const edgeMat = mat.clone();
        edgeMat.chunks.transformVS = transformToonEdgeVS;
        edgeMat.chunks.transformSkinnedVS = transformToonEdgeSkinnedVS;
        edgeMat.customFragmentShader = edgePS;
        edgeMat.cull = pc.CULLFACE_FRONT;
        edgeMat.update();

        const edgeMesh = new pc.MeshInstance(mesh.node, mesh.mesh, edgeMat);
        edgeMesh.castShadow = true;
        edgeMesh.receiveShadow = true;
        edgeMesh.castShadowsLightmap = true

        edgeMesh.skinInstance = mesh.skinInstance;
        edgeMesh.setParameter("edgeSize", this.edgeSize);
        edgeMesh.setParameter("edgeColor", this.edgeColor.data);
        edgeMesh.updateKey();
        edgeMeshes.push(edgeMesh);
    }
    this.app.scene.removeModel(this.entity.model.model);
    this.entity.model.model.meshInstances = meshes.concat(edgeMeshes);
    this.app.scene.addModel(this.entity.model.model);
    }, null)
};

10. シェーダーをそれぞれ追加する

Shader.PNG
シェーダを4つ追加します。

edgeToonPS
uniform vec4 edgeColor;

void main() {
    gl_FragColor = edgeColor;
}
lightDiffuseToonPS
uniform sampler2D texture_ramp;

float getLightDiffuse() {
    float light = max(dot(dNormalW, -dLightDirNormW), 0.0);
    return texture2D(texture_ramp, vec2(light, 0.0)).g;
}
transformToonEdgeSkinnedVS
uniform float edgeSize;

mat4 getModelMatrix() {
    return getBoneMatrix(vertex_boneIndices.x) * vertex_boneWeights.x +
           getBoneMatrix(vertex_boneIndices.y) * vertex_boneWeights.y +
           getBoneMatrix(vertex_boneIndices.z) * vertex_boneWeights.z +
           getBoneMatrix(vertex_boneIndices.w) * vertex_boneWeights.w;
}

vec4 getPosition() {
    dModelMatrix = getModelMatrix();
    vec4 posW = dModelMatrix * vec4(vertex_position, 1.0);
    posW.xyz += skinPosOffset;
    dNormalMatrix = mat3(dModelMatrix[0].xyz, dModelMatrix[1].xyz, dModelMatrix[2].xyz);
    vec3 normal = normalize(dNormalMatrix * vertex_normal);
    posW.xyz += normal * edgeSize;
    dPositionW = posW.xyz;
    return matrix_viewProjection * posW;
}

vec3 getWorldPosition() {
    return dPositionW;
}
transformToonEdgeVS
uniform float edgeSize;

mat4 getModelMatrix() {
    return matrix_model;
}

vec4 getPosition() {
    dModelMatrix = getModelMatrix();
    vec4 posW = dModelMatrix * vec4(vertex_position, 1.0);
    vec3 normal = normalize(matrix_normal * vertex_normal);
    posW.xyz += normal * edgeSize;
    dPositionW = posW.xyz;
    return matrix_viewProjection * posW;
}

vec3 getWorldPosition() {
    return dPositionW;
}

11. Toon.jsとシェーダーをモデルに適用する

toon.PNG

  1. ModelのスクリプトにToonを追加
  2. Toonスクリプトの中にそれぞれシェーダを適用します。

12. 表示してみる

こうすることで少しきれいなレンダリングがされるようになりました。

きれいなレンダリング.PNG

13. 公開する

一般に公開するにはこちらのPUBLISHボタンからURLを発行します。

puin.PNG

発行されたURLがこちら : https://playcanv.as/p/aCHS01tE/

14. Twitterプレイヤーカードにする

TwitterのPlayerCardとはTwitter上でOGPに指定したURLをiframeのような形で読み込み表示できる機能です。この機能を使うとTwitter上でPlayCanvasで遊ぶことができます。

OGPの設定は少し手間なのでこちらを使います。

Twitterカードジェネレーター

vrm.PNG

PlayCanvasの発行されたURL、タイトルを入力するとプレイヤーカードを作成してくれます。

プレイヤーカードにすると実質Qiitaへの埋め込みもできます。

おまけ

モーフィングを見るスクリプト

  • tween.js
  • morph-script.js を追加することで表情のアニメーションを制御することができます。

image.png

morph-script.js
/*jshint esversion: 6, asi: true, laxbreak: true*/
var MorphScript = pc.createScript("morphScript");
MorphScript.attributes.add("morphStart", { type: "number", default: -0.3 });
MorphScript.attributes.add("morphEnd", { type: "number", default: 0.8 });
MorphScript.attributes.add("morphTime", { type: "number", default: 1.5 });

MorphScript.prototype.initialize = function() {
  this.isLoad = false;
  this.tweenedProperty = {
    value: this.morphStart
  };
  this.app.on(
    `loaded:model`,
    e => {
      console.log(this.entity);
      this.isLoad = true;

      this.tween = this.app
        .tween(this.tweenedProperty)
        .to({ value: this.morphEnd }, this.morphTime, pc.QuadraticIn)
        .delay(0.1)
        .yoyo(true)
        .loop(true);

      this.tween.start();
    },
    null
  );
};

MorphScript.prototype.update = function(dt) {
  if (this.isLoad) {
    for (let meshInstance of this.entity.model.meshInstances) {
      try {

        meshInstance.morphInstance.setWeight(0, this.tweenedProperty.value);
      } catch (e) {

      }
    }
  }
};


PlayCanvas開発で参考になりそうな記事の一覧です。


入門


その他関連
- PlayCanvasタグの付いた記事一覧

PlayCanvasのユーザー会のSlackを作りました!

少しでも興味がありましたら、ユーザー同士で解決・PlayCanvasを推進するためのSlackを作りましたので、もしよろしければご参加ください!

日本PlayCanvasユーザー会 - Slack

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away