Re:JavaScriptから始める3D生活

この記事は[クラスター Advent Calendar 2022](https://qiita.com/advent-calendar/2022/cluster)の5日目の記事です。

クラスター株式会社でソフトウェアエンジニアをやっていますkoutaです。主にフロントエンドを担当しています。プリパラ＆キラッとプリ☆チャン＆ワッチャプリマジ！　Winter Live 2022を見ながら書いてたら、見入ってしまい全く筆が進まなくて困ってました。

昨日は@neguse_kさんの[clusterのroom serverの作り方](https://qiita.com/neguse_k/items/00d834f6ed96ba4ec11d)でした。アバターでのやりとりをすることを考えるとリアルタイム性は重要であり、そのリアルタイム性がどのような技術でなりたっているのかを知ることができるうれしい記事でしたね！

さて、この記事ではJavaScriptで3Dを扱えるライブラリについて簡単に紹介しようと思っています。

最近、[クラフトアイテムにスクリプトが付けられるようになりました](https://note.com/cluster_official/n/n5ca8ed9426f6)。このスクリプトにはJavaScriptが使われています。Clusterでアイテム制作やワールド作成をされている方にとっては、3Dを扱ったことはあるけどWeb関連は触ったことがなく、これを機にJavaScriptを始める人もいたのではないかなと思っています。
JavaScriptを使うようにできるとWebブラウザで様々なことが実現できるようになります。その中には、3Dを扱う技術もあります。

せっかくJavaScriptを覚えたのならいろんなところで活用してみてほしい！という想いがあり、Clusterを触る人にとって馴染みのある3Dの活用は、次の一歩としてありかも？と思ってました。
そこで、新たにJavaScriptに触れてみた人向けに、JavaScriptで3Dを扱おうとするとどんな方法があるの？というのをざっくりと紹介しようと思います。

# 記事の想定読者
* JavaScriptを始めたばかりでどういう3Dライブラリがあるのかがまだわからない人
 
# この記事で説明すること
* 3Dライブラリの紹介
* Webで3Dを扱うための技術の紹介

# この記事で説明しないこと
* 3Dライブラリの使い方
* Webで3Dを扱うための技術の詳細説明

# ライブラリを使わない場合
## WebGL
Canvasを通して、GPUアクセラレータや画像処理を実現するためのJavaScriptのAPIです。
WebGLで3Dを扱う場合、JavaScriptによる制御構文とシェーダーのコードで構成します。

参考として[MDN](https://developer.mozilla.org/ja/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL)からコードを引用します。一部抜粋したコードなので、そのままでは動かないことはご了承ください。


``` js
// HTML
<body>
  <canvas id="glCanvas" width="640" height="480"></canvas>
</body>


// シェーダーのコード
const fsSource = `
    void main() {
      gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
  `;


// JavaScriptによる制御構文
// シェーダーを初期化する処理。上で用意したシェーダーをJSのAPIで初期化している。
function initShaderProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
 /// 省略。ここでシェーダーを初期化するための処理を記述していく。省略されてないコードはMDNのページを参照。
}

function main() {
  const canvas = document.querySelector("#glCanvas");
  // GL コンテキストを初期化する
  const gl = canvas.getContext("webgl");

  // クリアカラーを黒に設定し、完全に不透明にします
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
　// 以降は省略。コンテキストに対してAPIを実行していくことでCanvasに描画できる。シェーダーのロードも同様。
}

window.onload = main;

```

初期化や各値を使用するためには、WebGLのAPIを利用して自分で用意する必要があります。そして、見た目を意図通りにするにはシェーダーに関する知識も必要になります。そのため、記述量も多くなり、なかなか初学者には厳しいなという印象です（自分もWebGLでアプリを作ったことはないのでまだその大変さに本格的には触れられてはいません。ですが、学習用のサイトを写経している段階でも記述量が多かったので大変なんだろうなと想像してます）。

## WebGPU
WebGLより後発のウェブ標準のAPI群です。
現段階ではWork is in progressになっている個所が多く、ブラウザで有効にするように設定することで試すことができます（[実装状況](https://github.com/gpuweb/gpuweb/wiki/Implementation-Status)）。
これから使われていく技術だと思うので、こういうのもあるのだな程度の認識で問題ないかなと思います（自分もまだ触れたことがないので詳しく説明することもできない状態です）。
ライブラリの対応状況としては、Babylo.jsではWebGPUのフル対応したほか、[Three.jsのExample](https://threejs.org/examples/?q=webgpu#webgpu_compute)にもWegGPUが存在していました。

### WebGPU参考リンク
* [WebGPU - コアの全てを canvas 抜きで](https://inzkyk.xyz/misc/webgpu/)

# ライブラリを使う場合
## Three.js
Webで3Dを扱うときによく紹介されるライブラリです。生のWebGLを使う必要なく3Dを扱えるようになります。
Webで3Dモデルを扱うライブラリでは、Three.jsが導入されていることが前提のも多く存在します（後述する@pixiv/three-vrmやAR.jsなど）。
ライブラリを使わなかった際には、前述したようにWebGLを用いてJavaScriptによる操作とGLSLを書く必要があります。

コードを[次のサイトから一部引用](https://ics.media/entry/14771/)します。
WebGLを書く場合と比べて、シーンにカメラや光源・表示したいオブジェクト配置してく形になっており直感的に書けるようになっています。

``` js
// HTML
<body>
  <canvas id="glCanvas" width="640" height="480"></canvas>
</body>

// JS
function main() {

  // レンダラーを作成
  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector('#glCanvas')
  });
  renderer.setSize(width, height);
  renderer.setPixelRatio(window.devicePixelRatio);
  
  // シーンを作成
  const scene = new THREE.Scene();

  // カメラを作成
  const camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
  // カメラの初期座標を設定（X座標:0, Y座標:0, Z座標:0）
  camera.position.set(0, 0, 1000);

  // 箱を作成
  const geometry = new THREE.BoxGeometry(500, 500, 500);
  const material = new THREE.MeshStandardMaterial({color: 0x0000FF});
  const box = new THREE.Mesh(geometry, material);
  scene.add(box);

  // 平行光源
  const light = new THREE.DirectionalLight(0xFFFFFF);
  light.intensity = 2; // 光の強さを倍に
  light.position.set(1, 1, 1); // ライトの方向
  // シーンに追加
  scene.add(light);
  renderer.render(scene, camera);
}
```

ネットにも参考となる記事が多いので、迷ったらThree.jsから始めるのが良いかなと個人的には思っています。

注意点として、Three.jsを使用する際に、どのバージョンを使用するのかは確認しておくとよいかと思います。
依存しているパッケージによっては、バージョンが違うと動かないということがあります。
特にThree.jsは、メジャーバージョンがなく、マイナーバージョンが0.146,0.147という形で上がっていきます。なので、マイナーバージョンの変更なので問題ないと思ったら、破壊的変更があり動かないということがありえます。どのバージョンに依存しているかは、よく確認しておくと良いと思います（使いたいライブラリのREADMEやpackage.jsonに書いてあるバージョンを確認するとわかるかなと思います）。

## Babylon.js
[今年の3月](https://github.com/BabylonJS/Babylon.js/releases/tag/5.0.0)に5系がリリースされたライブラリです。
特徴としては、TypeScriptで記述されていることとWebGPUに対応していることです。
[87.2%がTypeScript](https://github.com/BabylonJS/Babylon.js)で記述されていることからモダンな作りになっているのではないかな？ということが伺えます。
[公式のページ](https://www.babylonjs.com/)を見ると、WebGPU対応がWelcomeの次に書かれているので力を入れて対応をしているのではないかなと思います。

[公式からコードを引用](https://doc.babylonjs.com/features/introductionToFeatures/chap1/first_scene)してみます。

``` ts
const createScene =  () => {
    const scene = new BABYLON.Scene(engine);

    const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 3, new BABYLON.Vector3(0, 0, 0));
    camera.attachControl(canvas, true);

    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0));

    const box = BABYLON.MeshBuilder.CreateBox("box", {});

    return scene;
}
```

Three.jsと同じく、シーンにカメラなどのオブジェクトを配置していくことで記述できているので直感的だなと思います。
使いたいライブラリとの兼ね合いを考えて、どちらのライブラリを使うのかを選択すれば良いのかなと思いました。

## React-Three-Fiber
ReactでThree.jsを扱うためのライブラリです。Three.jsでは、前述したように命令的にオブジェクトを配置していく形となっており、Reactの宣言的なスタイルと組み合わせると可読性が落ちてしまうことが考えられます。
そこで、Reactで利用しやすくするためのラッパーライブラリとして開発されました。

[公式からコードを引用](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction)して記載します。ここから、Function Componentとして記述できてることがわかります。


``` tsx
import { createRoot } from 'react-dom/client'
import React, { useRef, useState } from 'react'
import { Canvas, useFrame } from '@react-three/fiber'

function Box(props) {
  // This reference will give us direct access to the mesh
  const mesh = useRef()
  // Set up state for the hovered and active state
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)
  // Subscribe this component to the render-loop, rotate the mesh every frame
  useFrame((state, delta) => (mesh.current.rotation.x += 0.01))
  // Return view, these are regular three.js elements expressed in JSX
  return (
    <mesh
      {...props}
      ref={mesh}
      scale={active ? 1.5 : 1}
      onClick={(event) => setActive(!active)}
      onPointerOver={(event) => setHover(true)}
      onPointerOut={(event) => setHover(false)}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
    </mesh>
  )
}

createRoot(document.getElementById('root')).render(
  <Canvas>
    <ambientLight />
    <pointLight position={[10, 10, 10]} />
    <Box position={[-1.2, 0, 0]} />
    <Box position={[1.2, 0, 0]} />
  </Canvas>,
)
```

Reactを学んだことがあり、ReactでThree.jsを使いたいという場合には良い選択肢に見えます。
Reactを学んだことがない状態でWebで3Dを触ってみたいという場合は、Reactの学習コストもかかると思います。なので、Three.jsから始めるのがよいかなと思います。

# その他の3Dモデルを活用するライブラリの紹介
Three.jsのみを使用して3D を利用するアプリを作ろうとすると労力がかかり大変かなと思います。ただ、先人がThree.jsを活用するライブラリを開発してくれているので、それらを使うことでARやVRMの描画ができたりします。なので、一部ではあるのですが簡単に紹介をできたらと思っています。

## A-Frame
WebでVRを実現するためのライブラリです。AR.jsというライブラリも活用することでARの表現もできます。[Quest2のHandTracking](https://aframe.io/blog/aframe-v1.1.0/)にも対応しており、Quest2を持っている方は是非試してみてください。

## @pixiv/three-vrm
VRMを読み込んでWebに表示するためのライブラリです。このライブラリを使うことでclusterで使っているVRM形式のアバターをWebで表示ということにも使えます。
[API](https://pixiv.github.io/three-vrm/packages/three-vrm/docs/)も揃っており、アバターに対してある程度自由な制御ができるのではないかと思います。

## @lookingglass/webxr
LookingGlassに表示するコンテンツの制御もJavaScriptから行えます。こちらはLookingGlassを持っていないと試すことできないのですが、デバイスで表示するコンテンツもJavaScriptで動かすことができるという紹介で挙げさせていただきました（あと、自分がLookingGlassを持っていながらまだ何にも活用できておらず、紹介することで触る機運を高めているという目的もあります・・・）。

# おわりに
Webで3Dを扱うライブラリについて簡単に紹介しました。
WebGLやWebGPUを生で扱おうとすると専門的な知識も必要となり大変だと思うのですが、Three.jsといったライブラリを活用すると意外と簡単に扱えると感じてもらえたら幸いです。

まだまだ他にもたくさんあると思うので、これもあるぞ！と推しライブラリがありましたらぜひとも教えてください。

明日は@clngnさんの「clusterでベストショットを撮る技術2.0」です。自アバターの写真をキレイに撮れるととても楽しくなれるので楽しみですね！