こんにちは、フロントエンドエンジニアの堀江(@nandemo_3_)です。
2023年4月21日にBabylon.jsのバージョン6がリリースされ、界隈では話題になっておりますが、メタバース業界にいるフロントエンドエンジニアとしては、キャッチアップしておきたいと思い、Babylon.jsを触ってみました。
また、Babylon.jsをReactアプリケーション上で使えるreact-babylonjsも合わせて紹介します。
はじめに
今回のアジェンダは以下の3つとなります。
- Babylon.jsとは
- Babylon.js Playgroundでチュッパチャップス型の風車の作り方
- 上記で作成したモデルをreact-babylonjsで描画するデモ
Babylon.jsとは
Babylon.jsは、WebGLを使用して3Dゲームやアプリケーションを開発するためのJavaScriptフレームワークです。
Microsoftによって開発されたOSSで、高速でスムーズなグラフィックス、強力なアニメーション、物理エンジン、音声エンジン、カメラコントロール、そして多くの他の機能を提供しています。また、Babylon.jsは、3Dシーンを容易に操作できるようにする機能が豊富で、WebGLや3Dグラフィックスの知識がなくても、簡単に使うことができます。
WebGLのJavascriptライブラリといえば、Three.jsがあげられますが、2023年4月26日時点のGItHubのスター数を見ても圧倒的に、Three.jsの方が多いですね。
Babylon.jsの強み
Three.jsとBabylon.jsはどちらも、Web上で3DCGを作成するJavaScriptフレームワークですが、それぞれの特徴と、Babylon.jsの強みは何なのでしょうか。
Three.jsは、シンプルなAPIを提供しており、簡単な3Dアプリケーションを素早く開発する場合に、最適な選択肢とされています。
一方、Babylon.jsは物理エンジンとアニメーションエンジンが組み込まれており、3Dゲームといったアプリケーション開発に向いています。また、VRやARをサポートしており、Meta QuestやMicrosoftのHololensとの親和性が高いそうです。
つまり、Babylon.jsは複雑で高度な3Dアプリケーションを開発する場合に、最適な選択肢とされています。
そして、Babylon.jsは公式でPlaygroundが用意されているのも、開発者にとっては非常に嬉しいです。上記でも述べましたが、WebGLや3Dグラフィックスの知識が全くなくても、ちょっとした3DCGなら簡単に作成できるのは良いと思います。
チュッパチャップス型の風車を作る
では、Babylon.jsが提供するPlaygroundを使って、どんなことができるのか試してみます。
Babylon.js Playgroundにアクセスすると、すでに地面と球体が作成されています。
四半球を作る
まず、風車の羽となる四半球を作ります。
BABYLON.MeshBuilder.CreateSphere
APIを使うことで、円形のShapeを作ることができます。
四半球にするために、arc
に0.25
を指定しました。sideOrientation
をBABYLON.Mesh.DOUBLESIDE
にしないと、裏面が透明になるので注意が必要です。
var createScene = function() { var scene = new BABYLON.Scene(engine); //var camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 2, 12, BABYLON.Vector3.Zero(), scene); var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene); // This targets the camera to scene origin camera.setTarget(BABYLON.Vector3.Zero()); // This attaches the camera to the canvas camera.attachControl(canvas, true); var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene); const sphere1 = BABYLON.MeshBuilder.CreateSphere("sphere", {arc: 0.25, sideOrientation: BABYLON.Mesh.DOUBLESIDE}); sphere1.position.y = 2.5; sphere1.rotation.y = -Math.PI / 2; return scene; };
シェイプを複製する
次に、作ったシェイプを複製します。clone
メソッドを使うことで複製ができます。固有のプロパティは上書きすることで変更することができます。
var createScene = function() { var scene = new BABYLON.Scene(engine); //var camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 2, 12, BABYLON.Vector3.Zero(), scene); var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene); // This targets the camera to scene origin camera.setTarget(BABYLON.Vector3.Zero()); // This attaches the camera to the canvas camera.attachControl(canvas, true); var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene); const sphere1 = BABYLON.MeshBuilder.CreateSphere("sphere", {arc: 0.25, sideOrientation: BABYLON.Mesh.DOUBLESIDE}); sphere1.position.y = 2.5; sphere1.rotation.y = -Math.PI / 2; sphere2 = sphere1.clone("sphere2"); sphere2.rotation.y = Math.PI / 2; return scene; };
柱を作る
次に、柱を作ります。今回はBABYLON.MeshBuilder.CreateCylinder
APIを使い、円柱を作りました。
より風車の柱っぽくするために上面と底面のを変えています。
var createScene = function() { var scene = new BABYLON.Scene(engine); //var camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 2, 12, BABYLON.Vector3.Zero(), scene); var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene); // This targets the camera to scene origin camera.setTarget(BABYLON.Vector3.Zero()); // This attaches the camera to the canvas camera.attachControl(canvas, true); var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene); const sphere1 = BABYLON.MeshBuilder.CreateSphere("sphere", {arc: 0.25, sideOrientation: BABYLON.Mesh.DOUBLESIDE}); sphere1.position.y = 2.5; sphere1.rotation.y = -Math.PI / 2; sphere2 = sphere1.clone("sphere2"); sphere2.rotation.y = Math.PI / 2; const cone = BABYLON.MeshBuilder.CreateCylinder("cone", {height: 4, diameterTop: 0.1, diameterBottom: 0.3}, scene); return scene; };
アニメーションをつける
最後に、アニメーションをつけます。アニメーションが分かりやすいように半球に色も着けます。
Babylon.jsあるあるかもしれませんが、複数のシェイプに対してアニメーションをつける場合はTransformNodeを作成し、これを親に設定します。
そして、そのTransformNodeにアニメーションを適用すると正しく動きます。
自分は、メッシュごとに同じアニメーションを適用したため、おかしな動きになりました。
これで、チュッパチャップス型の風車が完成しました。(もっと風車っぽい形にしたかった。。)
Playgroundはこちらです。
var createScene = function() { var scene = new BABYLON.Scene(engine); //var camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 2, 12, BABYLON.Vector3.Zero(), scene); var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene); // This targets the camera to scene origin camera.setTarget(BABYLON.Vector3.Zero()); // This attaches the camera to the canvas camera.attachControl(canvas, true); var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene); const root = new BABYLON.TransformNode(); const material1 = new BABYLON.StandardMaterial("material", scene); material1.diffuseColor = new BABYLON.Color3(1, 0, 0); const material2 = new BABYLON.StandardMaterial("material", scene); material2.diffuseColor = new BABYLON.Color3(1, 1, 0); const sphere1 = BABYLON.MeshBuilder.CreateSphere("sphere", {arc: 0.25, sideOrientation: BABYLON.Mesh.DOUBLESIDE}); sphere1.position.y = 2.5; sphere1.rotation.y = -Math.PI / 2; sphere1.parent = root sphere1.material = material1 sphere2 = sphere1.clone("sphere2"); sphere2.rotation.y = Math.PI / 2; sphere2.material = material2 const cone = BABYLON.MeshBuilder.CreateCylinder("cone", {height: 4, diameterTop: 0.1, diameterBottom: 0.3}, scene); const animSphere = new BABYLON.Animation( "sphereAnimation", "rotation.y", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE ); const sphereKeys = []; // キーフレーム (アニメーションキー) が 0 のとき、 // rotation.y に 0 を設定 sphereKeys.push({ frame: 0, value: 0 }); // キーフレームが 30 (30 FPS なので 1 秒後のこと) のとき、 // rotation.y に 2PI (360度) を設定し一回転を表す sphereKeys.push({ frame: 30, value: 2 * Math.PI }); animSphere.setKeys(sphereKeys); root.animations = []; root.animations.push(animSphere); scene.beginAnimation(root, 0, 30, true); return scene; };
所感
ちょっとアニメーションの所に躓きましたが、思ったより簡単にチュッパチャップス風の風車を作ることができました。
UnityやUnreal、Blenderなどの経験がなくても、Javascript、Typescriptの知識があれば簡単にモデリングができるのは非常に良いですし、思いついたものをパッと具現化できるので、アウトプットが捗りそうだなと思いました。
3Dゲーム向けということもあり、シューティングゲームを作った猛者もいる様なので、Flashゲームみたいなものを作りたいと思いました。
react-babylonjsでチュッパチャップス風の風車を描画する
続きまして、先ほど作ったモデルをWeb上で描画したいと思います。
今回使ったライブラリはreact-babylonjsです。
three.jsを使う場合は、react-three-fiberが良いそうですが、今回はBabylon.jsを採用しているためreact-babylonjsを使いました。
ライブラリインストール
必要なライブラリはこちらです。(Reactアプリのセットアップは省略します)
yarn add @babylonjs/core yarn add @@babylonjs/gui yarn add react-babylonjs
シーンを作る
まず、シーンを作成します。Engineコンポーネントでラップするのがお決まりのようです。
シーンに、カメラや光源、地面、シェイプなどのコンポーネントを内包することで、3D空間が描画されます。
カメラや光源だけでもかなり沼だったので、今回は最も簡単な設定にしました。
import React, { FC } from 'react'; import "@babylonjs/core/Physics/physicsEngineComponent" // side-effect adds scene.enablePhysics function import "@babylonjs/core/Lights/Shadows/shadowGeneratorSceneComponent"; // side-effect for shadow generator import { Vector3 } from '@babylonjs/core/Maths/math.vector'; import { Scene, Engine } from 'react-babylonjs'; import ChupaShupsWindmill from './ChupaShupsWindmill'; const App: FC = () => { return ( <div> <header> <Engine antialias={true} adaptToDeviceRatio={true} canvasId="canvas"> <Scene> <freeCamera name="camera" position={new Vector3(0, 5, -10)} target={Vector3.Zero()} /> <hemisphericLight name="light" intensity={0.7} direction={Vector3.Up()} /> <ChupaShupsWindmill /> </Scene> </Engine> </header> </div> ); } export default App;
チュッパチャップス風の風車を作る
上記のBabylon.js Playgroundで作ったモデルを、react-babylonjsで再作成します。
Babylon.js Playgroundのコーディングをそのまま移行できると思っていましたが、JSXに変換する必要があります。
球体を作る場合はsphere
、円柱はcylinder
といった様にコンポーネントとなっています。プロパティはそのまま指定できるので、これは分かりやすかったです。
また、Reactでアニメーションを実現する上でHooksを使う必要があります。
import React, {FC, useEffect, useRef} from "react"; import { Animation, Vector3 } from '@babylonjs/core' import { Color3 } from '@babylonjs/core/Maths/math.color'; import { useScene } from 'react-babylonjs' type ArcSphereProps = { name: string; rotation: Vector3; diffuseColor?: Color3 } function getSpinAnimation() { const keys = [ { frame: 0, value: 0, }, { frame: 30, value: 2 * Math.PI, }, ] const animation = new Animation('animation', 'rotation.y', 30, 0, 1) animation.setKeys(keys) return [animation] } const ChupaShupsWindmill: FC = () => { const groupRef = useRef(null) const scene = useScene() const position = Vector3.Zero() useEffect(() => { const playAnimation = () => { if (groupRef.current) { const group = groupRef.current const animations = getSpinAnimation() scene!.beginDirectAnimation( group, animations, 0, 30, true ) } } playAnimation() }, [groupRef, scene]) return ( <> <transformNode name="group" ref={groupRef} position={position} > <ArcSphere name="windmill1" rotation={new Vector3(undefined, -Math.PI / 2, undefined)} diffuseColor={Color3.Red()} /> <ArcSphere name="windmill2" rotation={new Vector3(undefined, Math.PI / 2, undefined)} diffuseColor={Color3.Yellow()} /> </transformNode> <cylinder name="cylinder" height={4} diameterTop={0.1} diameterBottom={0.3} > <standardMaterial name="cylinder-material" specularPower={16} diffuseColor={Color3.White()} /> </cylinder> </> ) } const ArcSphere = ({name, rotation, diffuseColor}: ArcSphereProps) => { return ( <sphere name={name} arc={0.25} sideOrientation={2} position={new Vector3(undefined, 2.5, undefined)} rotation={rotation} > <standardMaterial name={`${name}-material`} specularPower={16} diffuseColor={diffuseColor} /> </sphere> ) } export default ChupaShupsWindmill;
実行するとこんな感じになります。
ソースコード
ソースコード全文はこちらのリポジトリをご参照ください。
所感
react-babylonjsのドキュメントが充実しておらず探り探りではあったものの、Playgroundと同じモデルを作ることができました。
Playgroundのような気軽さはないですが、ReactでBabylog.jsを使って3Dモデルを描画できれば、3Dゲームも作れるかもしれませんし、3Dベースのホームページや3Dアバターの表示なども行えるのかなと思いました。
ただ、アニメーションを多用した時のパフォーマンスはどうなるのか検証は必要かと思いました。
最後に
今回は、Babylon.jsのPlaygroundやreact-babylonjsを用いて3Dモデルの作成をしました。
v6の機能のキャッチアップと、元々やりたかったVRMの表示ができなかったので次回以降やりたいと思います。
最後まで読んでくださりありがとうございました。