Cubism 4 SDK for Web でモデルを表示する

# はじめに

:::note info
2022/08/28 Cubism 4 SDK R4 に対応しました
:::

Cusibm 4 SDK for Web を触る事になったのですが、まだあまり情報がなさそうなので、自分が手探りで使ってみて分かったことをまとめます。

今は、モデルを表示するだけのサンプルしかありませんが、徐々に増やしていく予定です。

# モデルを表示するのに、最低限必要なファイル

表示するために最低限必要なファイルは以下の２種類
* .moc3
* .png（テクスチャ）
これらのパスはmodel3.jsonに記述されています。

# モデルを一回だけ描画する（[Sample_01](https://github.com/tatsuteb/Cubism4WebGL_Samples/tree/Sample_01_R4 )）

以下は、モデルを一回だけ描画する流れです。
ファイルのロード、WebGLコンテキストの初期化などは省略しています。

## フレームワークの初期化

まず始めに、フレームワークを初期化します。

```ts
import { CubismFramework } from '../../../Framework/live2dcubismframework';

CubismFramework.startUp();
CubismFramework.initialize();
```

## model3.jsonから、CubismModelSettingJsonオブジェクトを作成

modelSetting には、model3.json に記述されている情報が入ります。

```ts
import { ICubismModelSetting } from '../../../Framework/icubismmodelsetting';
import { CubismModelSettingJson } from '../../../Framework/cubismmodelsettingjson';

const modelSetting = new CubismModelSettingJson(model3JsonArrayBuffer, model3JsonArrayBuffer.byteLength) as ICubismModelSetting;

```

CubismModelSettingJson の getModelFileName 関数や getTextureFileName 関数を使って、必要なファイル名を取得できます。

```ts
const moc3FilePath = `${resourcesDir}${modelSetting.getModelFileName()}`;
```

## CubismUserModelの作成

CubismUserModel クラスは派生させて使うようなので、ここでは AppCubismUserModel 派生クラスを作って、モデルオブジェクトを作成しています。

moc3ArrayBuffer、pose3ArrayBuffer には、.moc3、pose3.json を ArrayBuffer として読み込んだデータが入っています。
これらのデータをそれぞれ、loadModel 関数、loadPose 関数に渡してあげます。

pose3.jsonは必須というわけではないのですが、非表示のパーツが全部表示されてしまうので使ってます。

```ts
// CubismUserModelの派生クラス
import AppCubismUserModel from './class/AppCubismUserModel';

const model = new AppCubismUserModel();
// モデルデータをロード
model.loadModel(moc3ArrayBuffer);
// ポーズデータをロード
if (pose3ArrayBuffer !== null)
    model.loadPose(pose3ArrayBuffer, pose3ArrayBuffer.byteLength);
```

## テクスチャをバインド

CubismUserModel のレンダラにテクスチャをバインドします。
バインドする前に、CubismUserModelのcreateRenderer 関数で、モデルオブジェクトにレンダラを作成しておきます。

textures には WebGLTexture オブジェクトが入っています。
gl は HTMLCanvasElement から作成した、WebGLRenderingContext オブジェクトです。

```ts
// レンダラの作成（bindTextureより先にやっておく）
model.createRenderer();
// テクスチャをレンダラに設定
textures.forEach((texture: WebGLTexture, index: number) => {
    model.getRenderer()
        .bindTexture(index, texture);
});

// そのほかレンダラの設定
model.getRenderer().setIsPremultipliedAlpha(false);
model.getRenderer().startUp(gl);
```

## Live2Dモデルのサイズ調整

**この辺は理解できていないです…。こうやったら調節できた、といった感じです。**

キャンバスが長方形だとモデルがゆがむので調整します。

調整しない場合、1:1の枠に収まっているモデルが、1:(キャンバス高さ/キャンバス幅）に変形されるみたいなので、高さだけを（キャンバス幅/キャンバス高さ）倍して、モデルの比を戻すようなスケールを projectionMatrix に設定してみました。
公式のマニュアルもこんな感じですが、理解が正しいかどうかは微妙です…。

<font color="Red">2022/08/28 **Cubism 4 SDK R4 ではスケールの調整は不要になりました。** この辺の計算は何か変わったのかも？</font>
~~また、比を直すだけだと、モデルが小さかったので、適当に大きくしています。~~

最後に、モデルのレンダラに計算済みのマトリクスを設定しています。

```ts
const modelMatrix = model.getModelMatrix();
const projectionMatrix = new CubismMatrix44();
const scale = 1;

projectionMatrix.scale(1, canvas.width / canvas.height);

// モデルが良い感じの大きさになるように拡大・縮小
projectionMatrix.scaleRelative(scale, scale);

projectionMatrix.multiplyByMatrix(modelMatrix);
model.getRenderer().setMvpMatrix(projectionMatrix);
```

## モデルの描画

ついにモデルの描画です。
まず、CubismUserModel の update 関数を呼んで、モデルの頂点情報を更新します。
パラメータの更新やMVPマトリクスの設定等をする場合は、この関数よりも前に行います。

最後に、モデルのレンダラの drawModel 関数を呼んであげると、モデルが表示されます。

```ts
// NOTE: フレームワークの更新に伴う変更
// フレームバッファを用意
const frameBuffer: WebGLFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);

// 省略

//  頂点の更新
model.update();

// NOTE: フレームワークの更新に伴う変更
// フレームバッファとビューポートを、フレームワーク設定
const viewport: number[] = [
    0, 
    0, 
    canvas.width,
    canvas.height
];
model.getRenderer().setRenderState(frameBuffer, viewport);

// モデルの描画
model.getRenderer().drawModel();
```

![Samle_01.png](https://qiita-image-store.s3.amazonaws.com/0/141789/622173f0-db41-d7a4-da1d-27fcfb83a140.png)

# モデルのレスポンシブ対応（[Sample_02](https://github.com/tatsuteb/Cubism4WebGL_Samples/tree/Sample_02_R4 )）

canvasをウィンドウいっぱいに広げて、サイズを変えるとそれに合わせてモデルもリサイズするようにします。
主な変更点や注意点は以下。

## canvasの比に合わせてモデルの比も調整

まず、canvasのサイズが変わっても、width と height は更新されないので、width と height を clientWidth、clientHeight を元に更新します。
この時、スマートフォン用に、devicePixelRatio倍しておきます。これを忘れるとスマホでぼやけます。

```ts
canvas.width = canvas.clientWidth * devicePixelRatio;
canvas.height = canvas.clientHeight * devicePixelRatio;
```

## requestAnimationFrame で更新

window.onresize でcanvasとモデルサイズが調整されたら、すぐに反映するため、
モデルの更新を requestAnimationFrame のループで行うようにします。

```ts
const loop = (time: number) => {

  // 頂点の更新
  model.update();

  viewport[2] = canvas.width;
  viewport[3] = canvas.height;
  model.getRenderer().setRenderState(frameBuffer, viewport);
    
  // モデルの描画
  model.getRenderer().drawModel();

  requestAnimationFrame(loop);

};
requestAnimationFrame(loop);
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141789/a98f99ea-3255-55aa-7022-93ac12fc70d1.gif" alt="responsive.gif" width=468 />

# モーション、物理演算、自動目ぱち対応（[Sample_03](https://github.com/tatsuteb/Cubism4WebGL_Samples/tree/Sample_03_R4 )）

# サンプルコード

SDKのバージョンアップとは無関係ですが、前回と構成を変えました。
主な変更点は以下です。

**Cubism 4 SDK for Web をサブモジュールとして取り込む**
このようにしておくと、SDKが更新された時は、チェックアウト先を変えるだけで対応できます。
この Sample_01 では、`/l2dsdk`フォルダにサブモジュールとして取り込んであります。

**SDKからのインポートを一つのファイルにまとめて、必要なものをエクスポートする**
<font color="Red">2022/08/28 **Cubism 4 SDK R4 では as で名前を書き替える必要が無くなったのでインポートを一か所にまとめる方法を廃止しました。** 一応記述は折りたたみで残しておきます。</font>
<details><summary>アーカイブ</summary><div>

**SDKからのインポートを一つのファイルにまとめて、必要なものをエクスポートする**
SDK からのインポートをすべて`/src/index.ts`にまとめました。
他のファイルは`/src/index.ts`から必要なクラスなどをインポートする形になっています。
このようにすることで、仮にSDKの場所を変えることになっても、`/src/index.ts`だけ変更すれば対応できます。

また、Cubism 4 SDK for Web は他のフレームワークと合わせるため名前空間で切っているので、

```
import { Live2DCubismFramework as icubismmodelsetting } from '../l2dsdk/Framework/icubismmodelsetting';
import ICubismModelSetting = icubismmodelsetting.ICubismModelSetting;
```

という形で、少し長くなりがちですが、`/src/index.ts`にまとめておくと、他のファイルのインポート部分はシンプルになるかと思います。~~

</div></details>

**ファイルのロードはPromise.allでまとめて行う**
説明ではファイルを一つずつロードして、オブジェクトを作成していますが、
サンプルではまとめてダウンロードしてから各オブジェクトを作成しています。

GitHubにサンプルコードを上げておきました。
各サンプルごとのプロジェクトはタグで分けてあります。
[Cubism4WebGL_Samples](https://github.com/tatsuteb/Cubism4WebGL_Samples/tree/develop)

# 参考

[Cubism SDK Manual](http://docs.live2d.com/cubism-sdk-manual/top/)

# 最後に

使い方を探りつつ、サンプルコードを増やしていく予定です。
また、自分の理解と共に、この記事も修正していこうと思います。