前回、3Dプリミティブオブジェクトを使って、レイアウトやライティングの仕方について一通り試しました。前回扱わなかったコンポーネントに、3DモデルのためのコンポーネントModel
があります。Model
コンポーネントを使うと「obj」形式の3Dモデリングデータを読み込み、VR空間に配置することができます。
3Dのモデリングデータを使うと、リアルな物体だったり、複雑な形状を持ったオブジェクトを自由にVR上で扱えるため、VR空間における表現力が飛躍的に高まります。実際VRコンテンツを作る際に使うのはBox
やCylinder
ではなく、Model
コンポーネントになるでしょう。
今回は、3Dのモデリングデータを読み込み、ジオラマを作ってみましょう。子どもの頃、作ったプラモデルに顔を近づけて遊んだ経験はないでしょうか。VRでは、巨大な物体を目の前に表示させるのもお手のものです。
新規プロジェクトの作成
既に本連載の読者であれば何度か行ってきていることですが、今回もサンプルを作るにあたって新規プロジェクトを作成し、プロジェクトディレクトリへ移動します。HelloDiorama
というプロジェクト名で進めることにします。
$ react-vr init HelloDiorama $ cd HelloDiorama
ジオラマに必要なものは何でしょうか? 一つは飾る対象物です。これはロボットなのかもしれないし、戦車かもしれないし、船かもしれません。3Dモデリングデータさえあれば、そしてそれをReact VRで読み込めさえすれば、なんでもジオラマにすることができます。
ちなみに「React VRで読み込めさえすれば」と書いているのは、Webで公開されている3Dモデリングデータの中には何故か表示できないものがあります。表示はできるけど変な形状になったりするものもあります。これはきっと時間と共に解決されていくタイプのものでしょう。気になることがあればReact VRのGitHubリポジトリでIssueを立てるとよいです。
続いてジオラマに必要なもう一つのものは何でしょう? それは雰囲気を与えてくれる背景です。緻密なモデルをさらに引き立ててくれる背景は、ロボットなら宇宙船の内部かもしれないし、船なら海上かもしれません。こちら背景は、今回は3Dプリミティブオブジェクトを利用してそれっぽいものを作ってみます。
用意するもの
ジオラマコンテンツを作るにあたって、用意するものは以下となりました。
- 配置する対象物としての3Dモデリングデータ
- 背景の壁と床のテクスチャ
まずは、配置する対象物としての3Dモデリングデータから用意しましょう。
今回ジオラマとして、シーンに配置するのは、GAZ-AAという1930年代に使われたソ連製のトラックにしてみました。筆者は、このトラックを好きだったわけではないですが、様々な3Dモデルを探す中でふとこのトラックを見かけ、素朴な中にも車両全面に漂う気品を魅力に感じ、採用した次第です。
このトラックは、ポリゴン数が4,380と、そこそこ複雑な形状をしており、テクスチャなどを含んでいてなかなかにリアリティがあります。
下記より、objフォーマット形式のものをダウンロードしましょう。もし他の3Dデータで試す場合、ライセンスについてチェックしておきましょう。
ちなみに、上記ダウンロードサイトでは、ユーザー登録が必要です。なので、ほかのものを用意できる人は好きな3Dデータを利用してOKです。「gaz-aa_bread OBJ.zip」をダウンロード、解凍したら、下記のディレクトリにそれぞれ配置します。
- static_assets/gaz_aa/gaz-aa_bread.mtl
- static_assets/gaz_aa/gaz-aa_bread.obj
- static_assets/gaz_aa/gaz-aa_bread_cabin.jpg
- static_assets/gaz_aa/gaz-aa_bread_detail.jpg
- static_assets/gaz_aa/gaz-aa_bread_frame.jpg
- static_assets/gaz_aa/gaz-aa_bread_furgon.jpg
- static_assets/gaz_aa/gaz-aa_bread_wheel.jpg
- static_assets/gaz_aa/gaz-aa_bread_wing.jpg
上記ファイルのうち、実は「gaz-aa_bread.mtl」については変更する必要があります。
テキストエディタなどでファイルを開き、「D:\Program Files\Autodesk\3ds Max 2009\scenes\GAZ-AA free models\gaz-aa_bread_3ds_max*.jpg」となっているところを「*.jpg」に書き換えましょう。これはきっと作ったときそのままのローカルのパスが入ってしまったのですね。公開する際の要注意ポイントと言えそうです。
ジオラマの背景に必要な素材は、角となる2面分の壁用のテクスチャと、床に使うテクスチャです。前回使ったPano
コンポーネントで、6面にテクスチャを貼り、部屋状にしてしまうこともできますが、それだとリアルな空間になりすぎ、ジオラマっぽくなくなるからです。3面しか壁を用意しないことで、後ろには漆黒の空間が広がる感じになり、なんだか超現実的な世界になります。
壁と床のテクスチャは以下からダウンロードしてください。
ちなみに、壁と床のテクスチャは、僕がそのへんで適当に撮ってきたものなので、フリーでお使いいただいて構いません。
ダウンロードしたら、「プロジェクトディレクトリ/static_assets/diorama」以下に「floor.jpg」と「wall.jpg」を配置しましょう。
STEP 1. まずは、ジオラマのベースづくりから
早速トラックを配置したいと、はやる気持ちを抑えてまずはジオラマのベースとなる背景の壁と床を作るところから始めましょう。床は、1000 x 1000の大きさを持った板を想定しました。これは、Pano
コンポーネントで作った空間と同じ大きさとなります。そして、奥の壁は床から500の高さを持つ平面です。これにテクスチャをつけることで、リアリティを持たせます。では、具体的な作業をしましょう。
「index.vr.js」のView
の中を一旦空にし、下記のように修正します。
<View> <View style={{ transform: [ {rotateY: -20}, ] }}> <Plane dimWidth={1000} dimHeight={500} style={{ transform: [ {translate: [-500, 220, 0]}, {rotateY: 90}, ], }} texture={asset('diorama/wall.jpg')} /> <Plane dimWidth={1000} dimHeight={500} style={{ transform: [ {translate: [0, 220, -500]}, ], }} texture={asset('diorama/wall.jpg')} /> <Plane dimWidth={1000} dimHeight={1000} style={{ transform: [ {translate: [0, -22, 0]}, {rotateX: -90}, ], }} texture={asset('diorama/floor.jpg')} /> </View> </View>
それと、importも変更しておきましょう。この先に使うModel
コンポーネントやライティング関係のコンポーネントも加えておきます。
import { AppRegistry, asset, View, Plane, Model, DirectionalLight, PointLight, AmbientLight, } from 'react-vr';
View
コンポーネントが入れ子になっているのは、内側のView
は、ジオラマの背景セットで、セット全体を変形させたい場合はこのように入れ子にしておくと、壁や床を一つの塊として扱えます。ここではジオラマのセット全体をちょっと回転させています。というのも、最初に表示したときに部屋の中央ではなく、部屋の角を見せたいためです。
ジオラマのセット自体を回転させずに、カメラを回転させるという表現方法もありますが、普通ジオラマというのは、インテリアの側面もあり、一番かっこいいポジションで飾るものだと思います。そこで、View
自体をY軸に対して-20度回転させました。
さらに細部を見ていきましょう。3つのPlane
が、内側のView
コンポーネント内に配置されていますが、先頭2つのPlane
コンポーネントは壁として配置してあります。位置を遠くに置いたのと、Y軸方向に90度回転させて垂直にそれぞれのコンポーネントが交わるようにしています。
Plane
コンポーネントに関わらず、3Dプリミティブコンポーネントは、テクスチャを設定することができます。テクスチャはtexture
属性で設定します。
ここではtexture
属性に、「diorama/wall.jpg」を指定しました。これによりのっぺりとした壁が質感のある壁となりました。ちなみに、この壁は近くの公園の壁を撮影したものです。
一番最後のPlane
は、床として配置しています。X軸方向に-90度回転させ、テクスチャを貼りました。これは海に行った時の砂岩の岩肌です。深い意味があるわけではないですが、オフロードっぽい印象になる写真が見つからなかったので、こちらを採用しました。
$ npm start
を実行し、ブラウザでURLを開いてみましょう。ただし、こんどは「http://localhost:8081/vr/?hotreload」で開いてみてください。「?hotreload」を付けてブラウザから開くと、ホットリロードが可能になります。これで、エディタで編集したコンテンツが自動でブラウザ側に反映されるようになります。
3方向のみPlane
を配置しただけの簡素なジオラマセットですが、VRで角のほうを見るとなかなかどうして迫力がありますね。
STEP 2. 3Dモデリングデータを配置しよう!
続いて、お待ちかねの3Dモデルの読み込みです。3Dモデリングデータの読み込みには、Model
コンポーネントを使います。Model
コンポーネントには、source
属性を記述することができます。渡す内容は、obj
とmtl
というキーを持ったオブジェクトで、それぞれobj形式のファイルとmtl形式のファイルへのパスを渡します。
今回であれば、「配置するモデリングデータ」で保存しておいた「static_assets/gaz_aa」以下にあるobjファイルとmtlファイルが対象になります。
Model
コンポーネントは、「index.vr.js」の入れ子の内側のView
以下に配置します。サイズは小さめにし、位置調整とrotateY
プロパティを修正して、若干の回転を加えます。これは、ちょうど左前に向いた形がきれいだからです。コードは下記のようになります。
<View> <View style={{ transform: [ {rotateY: -20}, ] }}> <Model style={{ transform: [ {translate: [-30, -20, -110]}, {scale: 0.03}, {rotateY: 70}, ], }} source={{ obj: asset('gaz_aa/gaz-aa_bread.obj'), mtl: asset('gaz_aa/gaz-aa_bread.mtl') }} lit={true} /> <Plane ... /> <Plane ... /> <Plane ... /> </View> </View>
これで、ブラウザをリロードしてみてください。3Dモデルが表示されるはずです。
ただし、真っ黒なシルエットになってしまっていますが。ということで、続いて光源を用意して、明るくしていきましょう。
STEP 3. ライティングしてトラックを美しく!
ライティングは、前回3Dプリミティブオブジェクトで遊んだ際に試したのを思い出してください。メインの明かり、部分的な明かり、ちょっとした調整です。今回も同じような感じで進めます。
実は、React VRで使われている3DライブラリのThree.js本体には、ライティングに関する様々なヘルパー機能が用意されています。例えば、照明の向き、範囲などを視覚的に表示して微調整をする機能などです。ただ、現時点のReact VRではもっと限定的なサポートとなっているのでそこまで細かくは扱いません。
「index.vr.js」にDirectionalLight
とPointLight
、AmbientLight
コンポーネントを配置しました。これで、トラックが綺麗に表示されるでしょうか。
<View> <DirectionalLight intensity={0.3} style={{ color: 'white', transform: [ {translate: [0, 0, 0]}, {rotateX: 45}] }}/> <PointLight intensity={0.5} style={{ color: 'white', transform: [{translate: [50, 20, -80]}] }}/> <AmbientLight intensity={0.8}/> <View ... /> </View>
ブラウザをリロードしてトラックを再表示してみましょう。明るくなったでしょうか。
STEP 4. トラックをゆっくりと回転させてみる
以上で一旦はジオラマは完成しましたと言ってもいいかもしれません。ただ、今まで物体を空間に置いてみて歯がゆかったのは、その物体の裏側がどうなっているのか、それが見えないということでした。ということで、アニメーションの練習がてらトラックを回転させてみましょう。
回転に関しては、ピッタリのサンプルが公式のリポジトリに用意されています。
このサンプルを参考にトラックのアニメーションを作ってみます。ちょっとサンプルと異なるのは、Reactのコンポーネントとして作るというのと、細かなロジックを修正した点です。Reactコンポーネントとして、回転速度を定義するspeed
属性と、表示位置や大きさを定義するstyle
属性を渡せるように作っておきましょう。「src/component/GazAa.js」として下記のように作成します。ちょっと長くなるので、細部の話は置いておいて、まずは一気に掲載してしまいます。
import React from 'react'; import { asset, View, Model, } from 'react-vr';export default class GazAa extends React.Component {
constructor() { super(); this.state = { rotation: 0 }; }
componentDidMount() { this.rotate(); }
componentWillUnmount() { if (this.frameHandle) { cancelAnimationFrame(this.frameHandle); this.frameHandle = null; } }
rotate(timestamp) { const delta = timestamp - this.lastUpdate || 0; this.setState({rotation: this.state.rotation + delta / this.props.speed}); this.lastUpdate = timestamp; this.frameHandle = requestAnimationFrame(this.rotate.bind(this)); }
render() { return (<View style={this.props.style}> <Model style={{ transform: [ {rotateY: this.state.rotation}, ] }} source={{ obj: asset('gaz_aa/gaz-aa_bread.obj'), mtl: asset('gaz_aa/gaz-aa_bread.mtl') }} lit={true} /> </View> ) } }
ポイントは、GazAa
コンポーネントのローカルステートとそれを変更するためのrotate関数、そしてそこで使われているrequestAnimationFrame
関数とcancelAnimationFrame
関数です。
GazAa
コンポーネントは、シーンに配置した瞬間、つまりコンポーネントがマウントされた時から回転を始めます。逆にコンポーネントをアンマウントすると回転をやめる処理をします。回転の状態はコンポーネントのローカルステートとして定義されているrotation
です。
rotation
は、3DモデルのrotateY
としてY軸方向にどれだけ回転するかという値として使われることになります。その値をコントロールしているのがrotate
関数です。requestAnimationFrame
関数は、ブラウザの再描画のタイミングでrotate
関数を呼び出すことになるので、呼び出すたびに前に呼び出された時間からの回転をrotation
に反映していきます。requestAnimationFrame
関数から返った値は、アニメーションをキャンセルするときにcancelAnimationFrame
関数にて使われます。
requestAnimationFrame
関数について一つ知っておきたいことは、この関数はもともとブラウザに実装されていますが、ブラウザによってはサポートされていないことがあります。そこで、React VRでは、requestAnimationFrame
関数のpolyfillとして用意されています。
これで大体のGazAa
コンポーネントの仕組みが理解できたでしょうか。今はこういうスタイルでアニメーションを実現したりしますが、将来的にはもっと洗練されたアニメーション機構が用意されていくのだろうと推測します。
では、先ほど作ったGazAa
コンポーネントを使っていきましょう。「index.vr.js」で使うので、まずはインポートします。ファイルの先頭あたりにimport文を書きます。
import GazAa from './src/component/GazAa'
続いて、入れ子になったView
コンポーネントの内側に、GazAa
コンポーネントを配置します。プロパティとしては、回転速度を定義するspeed
属性と、表示位置や大きさを定義するstyle
属性を渡しています。これら属性は、GazAa
コンポーネント内のthis.props.XXX
にて受け取ることが可能です。これらを記載したコードが以下になります。
<View> <DirectionalLight ... /> <PointLight ... /> <AmbientLight ... /> <View style={{ transform: [ {rotateY: -20}, ] }}> {/* <Model ... /> */} <GazAa speed={360} style={{ transform: [ {translate: [-42, -20, -110]}, {scale: 0.03}, ], }}/> <Plane ... /> <Plane ... /> <Plane ... /> </View> </View>
以上で、トラックが回転するジオラマの完成です。ブラウザをリロードして、じっくり見てみましょう!
もし興味があれば、React VR×360度画像!Web上でパノラマVR表示を試すを参考に、GearVRで眺めてみましょう。子供の頃叶わなかった、巨大サイズのジオラマを見ることができますよ。