React VRについて調べてみた
昨年、React VRがプレリリースされた。
VR開発に興味はあるものの、なかなか手を付けられなかったが、とりあえずしらべてみることにした。
React VRとは?
Oculusのブログを読むのが手っ取り早いが、WebVRをつかって、ブラウザ上でVR体験を可能にするには、平面での開発とは勝手が異なり、3Dや映像処理の知識がなければ難しい。
React VRは、それらを隠蔽し、従来のWeb開発に近い形で開発できるライブラリである。同じような思想のライブラリ(フレームワーク)として、A-Frameなどがあるが、React VRのメリットとしては Reactが使える ことにあると思う。例えば、チュートリアルで紹介されているコンポーネントは以下のような感じになる。
class WelcomeToVR extends React.Component {
render() {
return (
<View>
<Pano source={asset('chess-world.jpg')}/>
<Text
style={{
backgroundColor: '#777879',
fontSize: 0.8,
layoutOrigin: [0.5, 0.5],
paddingLeft: 0.2,
paddingRight: 0.2,
textAlign: 'center',
textAlignVertical: 'center',
transform: [{translate: [0, 0, -3]}],
}}>
hello
</Text>
</View>
);
}
};
ビューに関してはおなじみ、このあたりはReact Nativeと同じような感じである。また、<View>
、<Text>
など、React Nativeでみたことのあるコンポーネントが並んでいる。
どんな仕組み?
仕組みとしては、以下の図のようなレイヤーになる。
これもブログを読むのが早いのだが、内部的には、Three.jsをOVRUIでVR用に変換している。また、ReactのコードはWebWorkerを使用して非同期に走るので、パフォーマンス的にも最適化されているとのこと。
自作VRをみるにはどうしたらいいの?
自作VRをみるには、Gear VRを使用し、Oculus StoreでCarmel Developer PreviewというWebVR APIが実装されたブラウザをインストール、その上でVRを動かす。
Carmelはまだプレビュー版なので、アドレスバーがでない
、2D web contentが表示されない
など未実装な部分がある。しかし、WebVRに特化するなら、最新のAPIが使えるし、最適化されているとのこと。また、優先的にこれらを改善していくとアナウンスもしており、FBの本気を感じる。
React VRは、Gear VRをもっていない場合でも、普段使用しているブラウザで開発ができる。ただし、もちろんVRができるわけではなく、自動的にWebGLへダウングレードされたUIを開発することとなる。
デバッグに関しては、Hot Reloading、NuclideのReact Dev Tools Inspectorをサポートしていて、React Native同様に、Webの世界の開発イテレーションをそのまま持ち込める(そもそもReact Nativeのようにネイティブの世界で動かしているわけでもない)。また、WebVRだけあって、Chrome Developer Toolsも使用できる。これらをみると、特にストレスなくデバッグサイクルがまわるはずだと考えた。
どんなことができるの?
今回は、いくつかサンプルを動かしてみようと思う。まずはGetting Startedのサンプルをみていくが、それぞれ以下のような感じである。コードは一部抜粋なので、興味のある人は参考リンクからとんでください。
Meshモデル
class MeshSample extends React.Component {
constructor() {
super();
this.state = {rotation: 0};
this.lastUpdate = Date.now();
this.rotate = this.rotate.bind(this);
}
rotate() {
const now = Date.now();
const delta = now - this.lastUpdate;
this.lastUpdate = now;
this.setState({rotation: this.state.rotation + delta / 20});
this.frameHandle = requestAnimationFrame(this.rotate);
}
componentDidMount() {
this.rotate();
}
componentWillUnmount() {
if (this.frameHandle) {
cancelAnimationFrame(this.frameHandle);
this.frameHandle = null;
}
}
render() {
return (
<View>
<Pano source={asset('chess-world.jpg')}/>
<Mesh
style={{
transform: [
{translate: [0, -15, -70]},
{scale : 0.1 },
{rotateY : this.state.rotation},
{rotateX : -90}
],
}}
source={{mesh:asset('creature.obj'), mtl:asset('creature.mtl'), lit: true}}
/>
<PointLight style={{color:'white', transform:[{translate : [0, 400, 700]}]}} />
<Text style={style}>Creature</Text>
</View>
);
}
};
レイアウト
class LayoutSample extends React.Component {
render() {
return (
<View>
<Pano source={asset('chess-world.jpg')} />
<View style={{
flex: 1,
flexDirection: 'column',
width: 2,
alignItems: 'stretch',
transform: [{translate: [-1, 1, -5]}],
}}>
<HighlightView text='Red' backgroundColor='red'/>
<HighlightView text='Orange' backgroundColor='orange'/>
<HighlightView text='Yellow' backgroundColor='yellow'/>
<HighlightView text='Green' backgroundColor='green'/>
<HighlightView text='Blue' backgroundColor='blue'/>
</View>
</View>
);
}
}
ツアー
/* setStateによってnextLocationIdを出し分け、遷移を実現している */
class TourSample extends React.Component {
render() {
return (
<View>
<View style={{transform:[{rotateY: rotation}]}}>
<Pano source={asset(this.state.data.photos[this.state.nextLocationId].uri)}/>
<NavButton
key={tooltip.linkedPhotoId}
isLoading={isLoading}
onClickSound={asset(soundEffects.navButton.onClick.uri)}
onEnterSound={asset(soundEffects.navButton.onEnter.uri)}
onInput={() => {
this.setState({nextLocationId: tooltip.linkedPhotoId});
}}
rotateY={tooltip.rotationY}
source={asset(this.state.data.nav_icon)}
textLabel={tooltip.text}
translateZ={this.translateZ}
/>
</View>
</View>
);
}
};
Cube
// 作成したコンポーネントに対してカスタム3Dオブジェクトを当て込んでいる
function init(bundle, parent, options) {
const scene = new THREE.Scene();
const cubeModule = new CubeModule();
const vr = new VRInstance(bundle, 'CubeSample', parent, {
cursorVisibility: 'visible',
nativeModules: [ cubeModule ],
scene: scene,
});
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial()
);
cube.position.z = -4;
scene.add(cube);
cubeModule.init(cube);
vr.render = function(timestamp) {
const seconds = timestamp / 1000;
cube.position.x = 0 + (1 * (Math.cos(seconds)));
cube.position.y = 0.2 + (1 * Math.abs(Math.sin(seconds)));
};
vr.start();
return vr;
}
他にも、以下の様なことができる。
パノラマ写真
render() {
return (
<View>
<Pano source={asset('sample_pano.jpg')}/>
</View>
);
}
自分で6方位の面
を画像で定義
render() {
return (
<View>
<Pano source={{ uri: [
'../static_assets/sample_right.jpg',
'../static_assets/sample_left.jpg',
'../static_assets/sample_top.jpg',
'../static_assets/sample_bottom.jpg',
'../static_assets/sample_back.jpg',
'../static_assets/sample_front.jpg'
]}} />
</View>
);
}
render() {
return (
<View>
<Pano source={{ uri: [
'../static_assets/sahara_rt.jpg',
'../static_assets/sahara_lf.jpg',
'../static_assets/sahara_up.jpg',
'../static_assets/sahara_dn.jpg',
'../static_assets/sahara_bk.jpg',
'../static_assets/sahara_ft.jpg'
]}} />
</View>
);
}
ズームイン・アウト、アニメーション
render() {
return (
<View>
<Pano source={ {uri: this.spaceSkymap} }/>
<AmbientLight intensity={ 2.6 } />
<View style={ this.styles.menu }>
<Button text='+' callback={() => this.setState((prevState) => ({ zoom: prevState.zoom + 10 }) ) } />
<Button text='-' callback={() => this.setState((prevState) => ({ zoom: prevState.zoom - 10 }) ) } />
</View>
<Mesh
style={{
transform: [
{translate: [-25, 0, this.state.zoom]}, {scale: 0.05 }, {rotateY: this.state.rotation}, {rotateX: 20}, {rotateZ: -10}
]
}}
source={{mesh:asset('earth.obj'), mtl:asset('earth.mtl'), lit: true}} />
<Mesh
style={{
transform: [
{translate: [10, 10, this.state.zoom - 30]}, {scale: 0.05}, {rotateY: this.state.rotation / 3},
]
}}
source={{mesh:asset('moon.obj'), mtl:asset('moon.mtl'), lit: true}} />
</View>
);
}
その他
- Styleは、React Nativeと同じような感じだが、flexboxをはじめとするCSSライクな構文が使える
- Three.jsでカスタムオブジェクトも作成可能。React Nativeと違い、JSだけ書けばいいと思うと楽
- 現時点ではAPIもけっこう限られているため、Three.jsに詳しくなければ、コンテンツの向き不向きをしっかり考えたほうが良さそう。
- ドキュメントはさくっと読み切れる量なので、事前に使えるAPIを把握しておくと良さそう
まとめ
ブラウザで見るぶんにはただのWebGLなのでそこまで感動はないが、これがそのままVRに展開できると考えるとすごいと思う。また、React VRによって宣言的にビューを作れるのも、たしかに開発効率が上がりそうである。Three.jsの時代と違い、適度に隠蔽されていて、コードが非常に読みやすいのは嬉しい。
WebVRに関しては過去にも度々話題になっていたが、効率的に開発できる手段が成熟していくと、これまでなかった可能性が広がりそうで面白い。
例えば、WebVRそのもののメリットとして、VR端末を持っていなくても、ブラウザからWebGLとしてアクセスできるため、VR専用コンテンツにする必要が無い(より多くのユーザーにアプローチできる)、従来のブラウザと同じようにシンプルに共有できる、などがある。向き不向きはあるが、ゲーム以外の、アクセスやシェアが重要になってくる、動画や画像ベースのコンテンツで有益なのではないだろうか。
以上、React VRについてしらべてみました。VR初心者なので、なにか間違っていたらご指摘ください。
参考
- React VR | A framework for building VR apps using React
- Introducing the React VR Pre-Release | Oculus
- Carmel Developer Preview Launches Today | Oculus
- Getting started with React VR
- ここまで来た!2017年 Web VRでできること