BabylonjsをNextjs14で使う。
threejsよりbabylonjs派なんですが、なんせ情報が少ない。
ということで、babylonjs人口を増やすべく、
なるべく多くbabylonjsの情報を増やしていきたいっっっ⭐️
Babylonjsとは?
chatgpt曰く
Babylon.jsは、ウェブブラウザ上で3Dグラフィックスを表示するための強力なJavaScriptフレームワークです。WebGLをベースにしており、リアルタイムの3Dアプリケーション、ゲーム、インタラクティブなビジュアライゼーションを開発するための豊富な機能を提供しています。Babylon.jsを使用することで、デベロッパーは簡単に複雑な3Dシーンを構築し、アニメーション、物理エンジンの統合、ユーザーインタラクションなど、多岐にわたる機能を活用することができます。
Babylon.jsの主な特徴には以下のようなものがあります:
- パフォーマンス: 高いパフォーマンスを実現するために最適化されています。
- 使いやすさ: 直感的なAPIと豊富なドキュメント、チュートリアルが提供されており、3D開発の入門からプロフェッショナルまで幅広くサポートします。
- 機能の豊富さ: マテリアル、ライティング、カメラ、アニメーション、物理エンジン統合など、多彩な3Dグラフィックス機能を提供します。
- コミュニティ: アクティブなコミュニティによるサポートと、多くのユーザーによる貢献があります。
Babylon.jsは、ウェブベースの3Dアプリケーション開発において、手軽に始められる一方で、高度な3D機能やパフォーマンスを必要とするプロジェクトにも適しています。
とのことです。
これです。
Babylon.js: Powerful, Beautiful, Simple, Open - Web-Based 3D At Its Best
なるべく行数を減らしたい。
なんでbabylonを使っているかというと。
webglを使い出した頃調べていた限りでは、babylonjsの方がthreejsよりも
少ない行数で立方体を置けたから。
Babylon.jsの例
// Babylon.jsを使ってシーンをセットアップする
import { Engine, Scene, ArcRotateCamera, HemisphericLight, MeshBuilder } from 'babylonjs';
// キャンバス要素を取得
const canvas = document.getElementById('renderCanvas') as HTMLCanvasElement;
// Babylon.js エンジンを初期化
const engine = new Engine(canvas, true);
// シーンを作成
const scene = new Scene(engine);
// カメラを作成
const camera = new ArcRotateCamera('camera', Math.PI / 2, Math.PI / 4, 10, new BABYLON.Vector3(0, 0, 0), scene);
camera.attachControl(canvas, true);
// 照明を追加
const light = new HemisphericLight('light', new BABYLON.Vector3(1, 1, 0), scene);
// 立方体を作成
const box = MeshBuilder.CreateBox('box', {size: 2}, scene);
// レンダーループを設定
engine.runRenderLoop(() => {
// 立方体を回転させる
box.rotation.x += 0.01;
box.rotation.y += 0.01;
// シーンを描画
scene.render();
});
// ウィンドウのリサイズに対応
window.addEventListener('resize', () => {
engine.resize();
});
Three.jsの例
// Three.jsを使ってシーンをセットアップする
import * as THREE from 'three';
// シーンを作成
const scene = new THREE.Scene();
// カメラを作成
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// レンダラーを作成
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 立方体のジオメトリとマテリアルを作成
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 立方体メッシュを作成し、シーンに追加
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// アニメーション関数を作成
function animate() {
requestAnimationFrame(animate);
// 立方体を回転させる
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
// シーンをレンダリング
renderer.render(scene, camera);
}
// アニメーションを開始
animate();
// ウィンドウのリサイズに対応
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
今見るとそんなに変わらない気もするんですが、
なぜか当時はbabylonがいいなと思ったので、
僕の中でデファクトスタンダードになってます。
.svgのロードなどで部分できにthree使ったりもしてるんですが、、、、
BabylonjsをNextjsで使う。
BabylonjsをNextjsで使う。ということで、
特に特別なことはしておらず、公式のドキュメント通りにやってます。
ちなみにApp routerです。
ただ、最近はtypescriptを使うようにしているので、それだけ変えた。
babylonCanvasコンポーネントを作る
"use client";
import React, { useEffect, useRef } from "react";
import * as BABYLON from "@babylonjs/core";
import styles from "./BabylonCanvas.module.scss";
import * as TestSakamotoApp from "@/Test/Test";
/**
* BabylonCanvasPropsの型定義。
*
* @interface
*/
interface BabylonCanvasProps {
antialias?: boolean; // アンチエイリアス
engineOptions?: BABYLON.EngineOptions; // エンジンのオプション
adaptToDeviceRatio?: boolean; // デバイスの比率に合わせる
sceneOptions?: BABYLON.SceneOptions; // シーンのオプション
onRender?: (scene: BABYLON.Scene) => void; // レンダリング時の処理
onSceneReady?: (scene: BABYLON.Scene) => void; // シーンが準備できた時の処理
[key: string]: any; // その他のPropsを受け入れる
}
/**
* Babylon.jsを使用して3DシーンをレンダリングするReactコンポーネント。
*
* @param {BabylonCanvasProps} props - コンポーネントに渡されるProps。
* @returns {JSX.Element} Canvas要素。
*/
const BabylonCanvas = ({
antialias,
engineOptions,
adaptToDeviceRatio,
sceneOptions,
onRender,
onSceneReady,
...rest
}: BabylonCanvasProps): JSX.Element => {
const reactCanvas = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const { current: canvas } = reactCanvas;
if (!canvas) return;
const engine = new BABYLON.Engine(
canvas,
antialias,
engineOptions,
adaptToDeviceRatio
);
engine.setHardwareScalingLevel(1 / window.devicePixelRatio);
const scene = new BABYLON.Scene(engine, sceneOptions);
if (scene.isReady()) {
onSceneReady?.(scene);
} else {
scene.onReadyObservable.addOnce((scene) => onSceneReady?.(scene));
}
engine.runRenderLoop(() => {
if (typeof onRender === "function") onRender(scene);
scene.render();
});
const resize = () => {
scene.getEngine().resize();
TestSakamotoApp.Functions.resizeBabylonCamera(
scene.activeCamera as BABYLON.Camera
);
};
window.addEventListener("resize", resize);
return () => {
scene.getEngine().dispose();
window.removeEventListener("resize", resize);
};
}, [
antialias,
engineOptions,
adaptToDeviceRatio,
sceneOptions,
onRender,
onSceneReady,
]);
return (
<canvas ref={reactCanvas} {...rest} className={styles.babylonCanvasEl} />
);
};
export default BabylonCanvas;
page.tsxを作る
page.tsx
"use client";
import React, { use } from "react";
import * as BABYLON from "@babylonjs/core";
import * as TestSakamotoApp from "@/Test/Test";
const Page = () => {
const onSceneReady = async (scene: BABYLON.Scene) => {
scene.clearColor = BABYLON.Color4.FromHexString("#000000ff");
const camera = new BABYLON.ArcRotateCamera(
"camera",
Math.PI / 2,
Math.PI / 2,
2,
new BABYLON.Vector3(0, 0, 0),
scene
);
camera.setPosition(new BABYLON.Vector3(0, 0, -10));
camera.attachControl(scene.getEngine().getRenderingCanvas(), true);
camera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA;
TestSakamotoApp.Functions.resizeBabylonCamera(camera);
const canvas = scene.getEngine().getRenderingCanvas();
const engine = scene.getEngine();
// This creates a light, aiming 0,1,0 - to the sky (non-mesh)
const light = new BABYLON.HemisphericLight(
"light",
new BABYLON.Vector3(0, 1, 0),
scene
);
// Default intensity is 1. Let's dim the light a small amount
light.intensity = 0.7;
const createTestBox = (
scene: BABYLON.Scene,
props = {
num: 1,
size: 0.1,
distance: 0.1,
rotateSpeed: 0.2,
disableLight: false,
color: new BABYLON.Color3(0.5, 0.5, 0.5),
} as {
num: number;
size: number;
distance: number;
rotateSpeed?: number;
disableLight?: boolean;
color?: BABYLON.Color3;
}
) => {
for (let index = 0; index < props.num; index++) {
const testBox = BABYLON.MeshBuilder.CreateBox(
`testbox_${index}_in_${location.pathname}`,
{ size: props.size },
scene
);
testBox.position.x = index * props.distance;
const mat = new BABYLON.StandardMaterial(
`test
boxMat_${index}_in_${location.pathname}`,
scene
);
mat.emissiveColor = props.color || new BABYLON.Color3(0.5, 0.5, 0.5);
testBox.material = mat;
mat.disableLighting = props.disableLight || false;
scene.registerBeforeRender(() => {
testBox.rotation.x += props.rotateSpeed || 0.2;
testBox.rotation.y += props.rotateSpeed || 0.2;
});
}
};
createTestBox(scene, {
num: 1,
size: 1.5,
distance: 1,
rotateSpeed: 0.05,
disableLight: true,
color: BABYLON.Color3.FromHexString("#0000ff"),
});
};
const onRender = (scene: BABYLON.Scene) => {
// console.log("🟢 onRender");
};
return (
<>
<TestSakamotoApp.Components.BabylonCanvas
antialias
onSceneReady={onSceneReady}
// onRender={onRender}
id="my-canvas"
/>
</>
);
};
export default Page;
p5jsでいうと?
p5jsでいうと、僕の感覚では、
onSceneReady()は、ほぼsetup()です。
onRender()は、ほぼdraw()です。
例では、onSceneReady()でscene.registerBeforeRender(() => {}書いてますが、、、、、
以上、いったんこんなもんか!!!!
Discussion