Translations: English
Get started (Three.js)
Three.jsとVerseEngineの簡易ライブラリverse-threeを使った、VerseEngineの組み込み方法を簡単に紹介します。
あるいは、完成版のソースコードをここ(github.com)からダウンロードすることもできます。
Step 0: 空間を作る
まず、Three.jsで簡単な空間を作成します。
詳細については、Three.jsのマニュアルを参照してください。
ソースコードを表示
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1,viewport-fit=cover"/>
<style> body { margin: 0; overflow: hidden; }</style>
<script async src="https://cdn.jsdelivr.net/npm/[email protected]/dist/es-module-shims.min.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
"three/examples/jsm/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/",
"./index": "./index.js"
}
}
</script>
<script type="module">
import * as App from "./index";
</script>
</head>
<body></body>
</html>
import * as THREE from "three";
import { Sky } from "three/examples/jsm/objects/Sky.js";
function setupScene() {
const renderer = new THREE.WebGLRenderer();
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
0.1,
100
);
camera.position.set(0.0, 1.6, 0);
scene.add(camera);
scene.add(new THREE.AmbientLight(0xffffff, 1.0));
{
const sky = new Sky();
sky.scale.setScalar(450000);
scene.add(sky);
const sun = new THREE.Vector3();
sun.setFromSphericalCoords(
1,
THREE.MathUtils.degToRad(60),
THREE.MathUtils.degToRad(180)
);
sky.material.uniforms["sunPosition"].value.copy(sun);
}
let ground;
{
ground = new THREE.Mesh(
new THREE.PlaneGeometry(50, 50, 1, 1),
new THREE.MeshLambertMaterial({
color: 0x5e5e5e
})
);
ground.rotation.x = Math.PI / -2;
scene.add(ground);
}
scene.add(new THREE.GridHelper(50, 50));
const animate = () => {
renderer.render(scene, camera);
};
renderer.setAnimationLoop(animate);
}
function main() {
setupScene();
}
main();
Step 1: setupSceneを拡張する
setupScene
を変更します。
- 毎フレーム実行する処理を外部から指定可能にする
- Sceneなどの変数を返す
...
/**
* @param {Array<(number)=>void>} ticks - 毎フレーム実行する処理の配列
*/
function setupScene(ticks) {
...
ticks ||= [];
const clock = new THREE.Clock();
const animate = () => {
const dt = clock.getDelta();
ticks.forEach((f) => f(dt));
renderer.render(scene, camera);
};
renderer.setAnimationLoop(animate);
return { scene, renderer, camera, ground};
}
function main() {
const ticks = [];
const { scene, renderer, camera, ground } = setupScene(ticks);
}
...
Step 2: プレイヤーと地形を設定する
...
function main() {
const ticks = [];
const { scene, renderer, camera, ground } = setupScene(ticks);
// プレイヤーの作成
const cameraContainer = new THREE.Object3D(); // 頭
cameraContainer.add(camera);
const player = new THREE.Object3D(); // プレイヤー本体
player.add(cameraContainer);
scene.add(player);
camera.position.set(0.0, 1.6, 0); // カメラ位置を頭のデフォルトの高さに設定
// VRコントローラでテレポートする対象
const teleportTargetObjects = [ground];
// 地面と障害物
const collisionBoxes = [new THREE.Box3().setFromObject(ground)];
}
...
Step 3: VerseEngineをロードする
verse-threeのES Modules版と、verse-three内部で使用するthree-vrmをロードします。
...
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
"three/examples/jsm/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/",
"@pixiv/three-vrm": "https://cdn.jsdelivr.net/npm/@pixiv/[email protected]/lib/three-vrm.module.min.js",
"verse-three": "https://cdn.jsdelivr.net/npm/@verseengine/[email protected]/dist/esm/index.min.js",
"./index": "./index.js"
}
}
</script>
...
Step 4: VerseEngineと接続する
アニメーションとアバターファイルは独自に用意ことも可能ですが、 verse-threeのexampleをダウンロードして手早く試すこともできます。
setup-verse.js
import * as VerseThree from "verse-three";
const VERSE_WASM_URL =
"https://cdn.jsdelivr.net/npm/@verseengine/[email protected]/dist/verse_core_bg.wasm";
const ENTRANCE_SERVER_URL = "https://entrance.verseengine.cloud";
// アニメーションファイル
const ANIMATION_MAP = {
idle: "./asset/animation/idle.fbx",
walk: "./asset/animation/walk.fbx",
};
// アバターファイル
const range = (n) => [...Array(n).keys()];
export const PRESET_AVATARS = [
...range(3).map((i) => `f${i}`),
...range(3).map((i) => `m${i}`),
].map((n) => ({
thumbnailURL: `./asset/avatar/${n}.png`,
avatarURL: `./asset/avatar/${n}.vrm`,
}));
const DEFAULT_AVATAR_URL =
PRESET_AVATARS[Math.floor(Math.random() * PRESET_AVATARS.length)].avatarURL;
// WebRTC RTCIceServers
const ICE_SERVERS = [
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:stun1.l.google.com:19302" },
];
export const setupVerse = async (
scene, renderer, camera, cameraContainer, player,
collisionBoxes, teleportTargetObjects
) => {
const adapter = new VerseThree.DefaultEnvAdapter(renderer, scene,
camera, cameraContainer, player,
() => collisionBoxes,
() => [], // 壁などの障害物(今回は未指定)
() => teleportTargetObjects,
isLowSpecMode: true, // 髪などの揺れを省略してパフォーマンスを改善する
);
const mayBeLowSpecDevice = VerseThree.isLowSpecDevice();
const res = await VerseThree.start(adapter, scene,
VERSE_WASM_URL, ENTRANCE_SERVER_URL, DEFAULT_AVATAR_URL,
ANIMATION_MAP, ICE_SERVERS,
{
maxNumberOfPeople: mayBeLowSpecDevice ? 8 : 16,
maxNumberOfParallelFileTransfers: mayBeLowSpecDevice ? 1 : 4,
presetAvatars: PRESET_AVATARS,
}
);
return res.tick; // 定期処理の関数を返す
};
index.html
...
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
"three/examples/jsm/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/",
"@pixiv/three-vrm": "https://cdn.jsdelivr.net/npm/@pixiv/[email protected]/lib/three-vrm.module.min.js",
"verse-three": "https://cdn.jsdelivr.net/npm/@verseengine/[email protected]/dist/esm/index.min.js",
"./setup-verse": "./setup-verse.js",
"./index": "./index.js"
}
}
</script>
...
index.js
...
import { setupVerse } from "./setup-verse";
...
function main() {
const ticks = [];
const { scene, renderer, camera, ground } = setupScene(ticks);
// プレイヤーの作成
const cameraContainer = new THREE.Object3D(); // 頭
cameraContainer.add(camera);
const player = new THREE.Object3D(); // プレイヤー本体
player.add(cameraContainer);
scene.add(player);
camera.position.set(0.0, 1.6, 0); // カメラ位置を頭のデフォルトの高さに設定
// VRコントローラでテレポートする対象
const teleportTargetObjects = [ground];
// 地面と障害物
const collisionBoxes = [new THREE.Box3().setFromObject(ground)];
setupVerse(
scene, renderer, camera, cameraContainer, player,
collisionBoxes, teleportTargetObjects
).then((tick) => {
ticks.push(tick);
});
}
...
完成版のリンク等
より詳細な使い方は、verse-threeリファレンスを参照してください。
Tips
WARNING
VerseEngineは、WebページのURL毎にユーザーのネットワークを構築します。
開発のためにlocalhostやローカルIP(192.168.xxx.xxx等)を使うときに、URLによっては見知らぬ人と接続してしまう場合があります。
hostsファイルでIPに専用の名前を割り当てるか、htmlファイルに十分に長いランダムのファイル名をつけることでそれを回避することができます。
# https://very-long-name12345678/
127.0.0.1 very-long-name12345678
TIP
全ての機能を使うためには、HTTPSで通信する必要があります。
簡単にWebサーバーをHTTPS対応できるサービス
- Cloudflare - ウェブサイトのセキュリティとパフォーマンス改善
- Let's Encrypt - フリーな SSL/TLS 証明書
- ZeroSSL - Free SSL Certificates and SSL Tools
ローカルに開発用証明書を作成するツール
Macでローカル開発用にHTTPS Serverを起動する例
# Homebrewを使ってmkcertをインストールする
brew install mkcert
mkcert -install
# 証明書を作成する (IPアドレスは環境に合わせて変更)
mkdir cert
cd cert
mkcert localhost 127.0.0.1 192.168.10.2
cd ..
# サーバーを起動する
npx http-server -c-1 --ssl --key ./cert/localhost+2-key.pem --cert ./cert/localhost+2.pem -p 8080