HTML5Experts.jp

React VRでここまでできる!VRでジオラマを作ろう

連載: React VR (4)

今回は、こんなVRジオラマをつくるぞ!

前回、3Dプリミティブオブジェクトを使って、レイアウトやライティングの仕方について一通り試しました。前回扱わなかったコンポーネントに、3DモデルのためのコンポーネントModelがあります。Modelコンポーネントを使うと「obj」形式の3Dモデリングデータを読み込み、VR空間に配置することができます。

3Dのモデリングデータを使うと、リアルな物体だったり、複雑な形状を持ったオブジェクトを自由にVR上で扱えるため、VR空間における表現力が飛躍的に高まります。実際VRコンテンツを作る際に使うのはBoxCylinderではなく、Modelコンポーネントになるでしょう。

今回は、3Dのモデリングデータを読み込み、ジオラマを作ってみましょう。子どもの頃、作ったプラモデルに顔を近づけて遊んだ経験はないでしょうか。VRでは、巨大な物体を目の前に表示させるのもお手のものです。

新規プロジェクトの作成

既に本連載の読者であれば何度か行ってきていることですが、今回もサンプルを作るにあたって新規プロジェクトを作成し、プロジェクトディレクトリへ移動します。HelloDioramaというプロジェクト名で進めることにします。

$ react-vr init HelloDiorama
$ cd HelloDiorama

ジオラマに必要なものは何でしょうか? 一つは飾る対象物です。これはロボットなのかもしれないし、戦車かもしれないし、船かもしれません。3Dモデリングデータさえあれば、そしてそれをReact VRで読み込めさえすれば、なんでもジオラマにすることができます。

ちなみに「React VRで読み込めさえすれば」と書いているのは、Webで公開されている3Dモデリングデータの中には何故か表示できないものがあります。表示はできるけど変な形状になったりするものもあります。これはきっと時間と共に解決されていくタイプのものでしょう。気になることがあればReact VRのGitHubリポジトリでIssueを立てるとよいです。

続いてジオラマに必要なもう一つのものは何でしょう? それは雰囲気を与えてくれる背景です。緻密なモデルをさらに引き立ててくれる背景は、ロボットなら宇宙船の内部かもしれないし、船なら海上かもしれません。こちら背景は、今回は3Dプリミティブオブジェクトを利用してそれっぽいものを作ってみます。

用意するもの

ジオラマコンテンツを作るにあたって、用意するものは以下となりました。

まずは、配置する対象物としての3Dモデリングデータから用意しましょう。

1930年代に使われたソ連製のトラックのモデルデータ

今回ジオラマとして、シーンに配置するのは、GAZ-AAという1930年代に使われたソ連製のトラックにしてみました。筆者は、このトラックを好きだったわけではないですが、様々な3Dモデルを探す中でふとこのトラックを見かけ、素朴な中にも車両全面に漂う気品を魅力に感じ、採用した次第です。

このトラックは、ポリゴン数が4,380と、そこそこ複雑な形状をしており、テクスチャなどを含んでいてなかなかにリアリティがあります。

下記より、objフォーマット形式のものをダウンロードしましょう。もし他の3Dデータで試す場合、ライセンスについてチェックしておきましょう。

ちなみに、上記ダウンロードサイトでは、ユーザー登録が必要です。なので、ほかのものを用意できる人は好きな3Dデータを利用してOKです。「gaz-aa_bread OBJ.zip」をダウンロード、解凍したら、下記のディレクトリにそれぞれ配置します。

上記ファイルのうち、実は「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属性を記述することができます。渡す内容は、objmtlというキーを持ったオブジェクトで、それぞれ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」にDirectionalLightPointLightAmbientLightコンポーネントを配置しました。これで、トラックが綺麗に表示されるでしょうか。

            <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>

以上で、トラックが回転するジオラマの完成です。ブラウザをリロードして、じっくり見てみましょう!

完成したVRジオラマ

もし興味があれば、React VR×360度画像!Web上でパノラマVR表示を試すを参考に、GearVRで眺めてみましょう。子供の頃叶わなかった、巨大サイズのジオラマを見ることができますよ。