A-FrameのシーンにThree-VRMでロードしたモデルをsetObject3D関数を使って入れた所、A-Frameで読み込んでいるThree.jsとThree-VRMで読み込んでいるThree.jsのインスタンスが違う為にA-Frame側で弾かれた。
[Error : Entity.setObject3D
was called with an object that was not an instance of THREE.Object3D.]
A-Frameで使用しているシーン(sceneEl)に直接追加してしてみても弾かれます。
[Error : “Trying to add an element that doesn’t have an object3D
“]
そこで今回はA-Frameと違うシーンをThree.jsで作成してからA-Frameのシーンに重ねる方法で表示させてみました。
ライブラリのバージョン
A-Frame : 1.5.0
Tree : r158
Three-VRM : 2.0.7
s-module-shims : 1.8.2
実装イメージ
### 実装の概念的イメージ ###
<!DOCTYPE html>
<html lang="jp">
<head>
<!-- ライブラリの読み込み -->
</head>
<body>
<!-- a-frameの要素配置 -->
<a-scene>
<a-entity vrm=""></a-entity>
</a-scene>
<script type="module">
// VRMモデルのアニメーション関数
function vrmModelAnimate() {}
// A-FrameのエンティティにVRMモデルを設定
AFRAME.registerComponent("three-scene", {
init: function() {
// VRMモデルの読み込み
},
tick: function (time, timeDelta) {
// VRMモデルの表示 update
// A-Frameのレンダラーを使ってカメラ映像の上にVRMモデル表示
}
});
</script>
</body>
</html>
グローバル変数の設定
VRMモデルをアニメーションさせる時計とA-Frameコンポーネント関数から参照出来る変数を用意します。
const clock = new THREE.Clock();
clock.start();
let avatar = undefined;
シーンの作成とVRMモデルの読み込み
コンポーネントのinit関数内で実施させます
init: function() {
// sceneの作成
this.threeScene = new THREE.Scene();
// lightの設定
const light = new THREE.DirectionalLight( 0xffffff, 1 );
light.position.set( 1.0, 3.0, 1.0 ).normalize();
this.threeScene.add( light );
// VRMモデルの読み込みローダーの設定
const loader = new GLTFLoader();
loader.crossOrigin = 'anonymous';
loader.register((parser) => {
return new VRMLoaderPlugin(parser);
});
// VRMモデルのロード
loader.load(
'PATH/your_model.vrm', // VRMファイルのパスを指定
(gltf) => {
const vrm = gltf.userData.vrm;
vrm.scene.traverse((obj) => {
obj.frustumCulled = false;
});
// シーンへvrmモデルを追加
this.threeScene.add(vrm.scene);
// 読み込んだvrmを公開用変数に代入
avatar = vrm;
// vrmモデルの初期位置の調整
vrm.scene.position.y = 0.5;
vrm.scene.position.x = 0;
vrm.scene.position.z = -3;
console.log( vrm );
},
(progress) => console.log('Loading model...', 100.0 * (progress.loaded / progress.total), '%'),
(error) => console.error(error)
);
// 以下モデルが見えない時の検索用ガイド
// Grid Helper
const size = 10;
const divisions = 10;
const gridHelper = new THREE.GridHelper(size, divisions);
this.threeScene.add(gridHelper);
// Axes Helper
const axesHelper = new THREE.AxesHelper(5);
this.threeScene.add(axesHelper);
// VRMモデルのアニメーション関数
animate();
},
シーンのレンダリング
コンポーネントのtick関数内で実施させます
tick: function (time, timeDelta) {
// A-Frameのカメラとレンダラーを取得
var camera = this.el.sceneEl.camera;
var renderer = this.el.sceneEl.renderer;
// A-Frameとinitで作成したシーンの合成
if (camera && renderer) {
renderer.autoClear = false; // A-Frameのレンダリングをクリアしない
renderer.render(this.threeScene, camera);
}
}
アニメーション関数
テスト用に首を揺らすアニメーションを用意しました。
function animate() {
requestAnimationFrame( animate );
const deltaTime = clock.getDelta();
// update avatar components
if ( avatar && avatar.humanoid ) {
// 首振り量
const neckRotation = 0.25 * Math.PI * Math.sin( Math.PI * clock.elapsedTime );
// 首への回転適用
avatar.humanoid.getNormalizedBoneNode( 'neck' ).rotation.y = neckRotation;
// vrmモデルアップデート
avatar.update( deltaTime );
}
}
コード全体
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" name="viewport"/>
<title>A-Frame & VRM view</title>
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar.js"></script>
<script src="https://unpkg.com/es-module-shims@1.8.2/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.158.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.158.0/examples/jsm/",
"@pixiv/three-vrm": "https://unpkg.com/@pixiv/three-vrm@2.0.7/lib/three-vrm.module.js"
}
}
</script>
</head>
<body>
<a-scene arjs="trackingMethod: best; sourceType: webcam; debugUIEnabled: false; patternRatio: 0.80;"
embedded="" renderer="logarithmicDepthBuffer: true; precision: medium;" vr-mode-ui="enabled: false">
<a-entity vrm=""></a-entity>
</a-scene>
<script type="module">
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { VRMLoaderPlugin, VRMUtils } from '@pixiv/three-vrm';
const clock = new THREE.Clock();
clock.start();
let avatar = undefined;
function animate() {
requestAnimationFrame( animate );
const deltaTime = clock.getDelta();
// update avatar components
if ( avatar && avatar.humanoid ) {
// 首振り量
const neckRotation = 0.25 * Math.PI * Math.sin( Math.PI * clock.elapsedTime );
// 首への回転適用
avatar.humanoid.getNormalizedBoneNode( 'neck' ).rotation.y = neckRotation;
// vrmモデルアップデート
avatar.update( deltaTime );
}
}
AFRAME.registerComponent("vrm", {
init: function() {
// sceneの作成
this.threeScene = new THREE.Scene();
// lightの設定
const light = new THREE.DirectionalLight( 0xffffff, 1 );
light.position.set( 1.0, 3.0, 1.0 ).normalize();
this.threeScene.add( light );
// VRMモデルの読み込みローダーの設定
const loader = new GLTFLoader();
loader.crossOrigin = 'anonymous';
loader.register((parser) => {
return new VRMLoaderPlugin(parser);
});
// VRMモデルのロード
loader.load(
'PATH/your_model.vrm', // VRMファイルのパスを指定
(gltf) => {
const vrm = gltf.userData.vrm;
vrm.scene.traverse((obj) => {
obj.frustumCulled = false;
});
// シーンへvrmモデルを追加
this.threeScene.add(vrm.scene);
// 読み込んだvrmを公開用変数に代入
avatar = vrm;
// vrmモデルの初期位置の調整
vrm.scene.position.y = 0.5;
vrm.scene.position.x = 0;
vrm.scene.position.z = -3;
console.log( vrm );
},
(progress) => console.log('Loading model...', 100.0 * (progress.loaded / progress.total), '%'),
(error) => console.error(error)
);
// 以下モデルが見えない時の検索用ガイド
// Grid Helper
const size = 10;
const divisions = 10;
const gridHelper = new THREE.GridHelper(size, divisions);
this.threeScene.add(gridHelper);
// Axes Helper
const axesHelper = new THREE.AxesHelper(5);
this.threeScene.add(axesHelper);
// VRMモデルのアニメーション関数
animate();
},
tick: function (time, timeDelta) {
// A-Frameのカメラとレンダラーを取得
var camera = this.el.sceneEl.camera;
var renderer = this.el.sceneEl.renderer;
// A-Frameとinitで作成したシーンの合成
if (camera && renderer) {
renderer.autoClear = false; // A-Frameのレンダリングをクリアしない
renderer.render(this.threeScene, camera);
}
}
});
</script>
</body>
</html>