2020.06.15

コーディング

【three.js入門・第1回】three.jsの基本的な要素

こんにちわ
フロントエンドの葉っぱ一号です

IT土方です

IT土方さん
three.jsを使うって言っても
実際どんなことをすればいいんでしょう?
なかなかイメージが湧きづらいです

それぞれの要素に対して細かい設定を色々していくから
全体として難しそうに見えるけど、基本的には
・シーン(場、背景)を準備する
・カメラを設定してシーンに配置する
・ライトを設定してシーンに配置する
・オブジェクト(被写体)を設定してシーンに配置する
たったこれだけのことなんだ

へー!
カメラとかライトとか、
現実と同じなんですね!

そうだね
でもthree.jsは現実なんかよりずーっと簡単だから安心してね

あ、はい

という訳で、今回は実際に3Dを表示させるうえでの基本的な要素、「シーン」「カメラ」「ライト」「オブジェクト」について軽く紹介していこうと思います。
それぞれの詳細については後々取り上げていく予定です。

また、この記事の内容はこちらの本を参考にして書いています。詳細が気になった方は是非ご購入を。おすすめです。

O’Reilly Japan | 初めてのThree.js 第2版

それではまず「シーン」から。どうぞ👇

シーン 【THREE.Scene】

sceneオブジェクトは表示したいすべての物体と利用したい全ての光源を保持して変更を監視するコンテナオブジェクトです。
個人的にはカメラやライトや被写体などを配置する場のようなイメージでしょうか(あくまでも個人的なイメージなので間違ってるかもしれません)。
カメラやライトや被写体をこのTHREE.Sceneオブジェクトにどんどん追加していく形になります。THREE.SceneオブジェクトがなければThree.jsは何も描写できません。

下記のようにして宣言します。

var scene = new THREE.Scene();

sceneオブジェクトはさまざまなオブジェクトすべてを保持するコンテナですが、自身のオプションや機能はそれほど多くありません。
シーンに関係する関数としてもっとも重要でもっともよく利用されるものとして下記のようなものがあります。

  • THREE.scene.add ――― オブジェクトをシーンに追加します
  • THREE.scene.remove ――― オブジェクトをシーンから削除します
  • THREE.scene.children ――― シーン内のすべての子要素のリストを取得します
  • THREE.scene.getObjectByName ――― 名前を指定してシーンから特定のオブジェクトを取得します

上でカメラやライトや被写体をこのTHREE.Sceneオブジェクトにどんどん追加していくと書きましたが、それを行うのがadd関数です。

scene.add(カメラやライトや被写体);

addするの忘れてて、あっれーおかしいなー表示されないぞーなんてことにならないようにしたいですね(実体験)

カメラ 【THREE.PerspectiveCamera】
    【THREE.OrthographicCamera】

Three.jsには透視投影カメラと平行投影カメラという、2つの異なるタイプのカメラがあります。

THREE.PerspectiveCameraが透視投影カメラでPerspectiveの名前の通り遠近感のある(カメラから遠くにある物体ほど小さく見える)自然な見た目になります。

透視投影カメラでの描写

対するTHREE.OrthographicCamera、平行投影カメラは遠近感はなくすべての物体が同じサイズで描写されます。物体とカメラの距離が描写されるサイズに影響を与えないのです。
「シムシティ4」などの古いタイプの2Dゲームによくある見た目ですね。

平行投影カメラでの描写

この二つのカメラは作成する際の引数が異なります。
引数を眺めてみると自分が望むカメラを作成するにはどうすればいいのかがなんとなくイメージできてくると思います。

THREE.PerspectiveCamera

var camera = new TRHEE.PerspectiveCamera(fov, aspect, near, far);
引数 説明
fov FOVはField Of View(視野)の略。これはカメラの位置から見えるシーンの範囲である。
例えば人間はおよそ180度のFOV(視野)を持つが、鳥類には360度すべてのFOVを持つものもある。しかしほとんどのコンピュータースクリーンは視界のすべてを完全に覆うわけではないので、通常はもう少し小さな値が選ばれる。ゲームなどでは多くの場合FOVは60度から90度の間の値が選ばれる。
推奨デフォルト値:50
aspect 描画される出力領域の横幅と縦幅の比。
上記のfovで設定するのは水平方向のfovであり、縦横比によって水平方向のFOVと垂直方向のFOVの差が決定される。
推奨デフォルト値:window.innerWidth/window.innerHeight
near nearプロパティはカメラのどのくらい近くからThree.jsが描画を開始するかを指定する。
通常は非常に小さな値に設定し、カメラの位置からすべてを直接描画する。
推奨デフォルト値:0.1
far farプロパティはカメラからどのくらい遠くまで見えるかを指定する。
値が小さすぎると、シーンの一部がおそらく描画されない。逆に大きすぎると、場合によっては描画のパフォーマンスに影響を与えてしまう。
推奨デフォルト値:2000
zoom zoomプロパティを使用するとシーンにズームインまたはズームアウトすることができる。
1より小さな値を使用するとシーンからズームアウトし、1より大きな値を使用するとズームインする。負の値を指定するとシーンが上下逆に描画されることに注意。なお、このプロパティはコンストラクタ引数としては設定できず、カメラ作成後に値を設定する必要がある。
推奨デフォルト値:1
透視投影カメラでの描写

↑ 透視投影カメラのイメージ

THREE.OrthographicCamera

var camera = new TRHEE.OrthographicCamera(left, right, top, bottom, near, far);
引数 説明
left Three.jsのドキュメント内ではカメラ錐台の左側面(Camera frustum left plane)と表現される。
これは描画される領域の左境界と見ることができる。もし値を-100に設定すれば、左側面よりも外側にあるオブジェクトは何も見えなくなる
right rightプロパティはleftプロパティと同様に動作するが、今回はスクリーンの反対側を指す。
これよりも右側にあるものは何も表示されない
top 描画される上限
bottom 描画される下限
near カメラの位置を基準にこの点より向こうがシーンに描画される
far カメラの位置を基準にこの点までがシーンに描画される
zoom zoomプロパティを使用するとシーンにズームインまたはズームアウトすることができる。
1より小さな値を使用するとシーンからズームアウトし、1より大きな値を使用するとズームインする。負の値を指定するとシーンが上下逆に描画されることに注意。なお、このプロパティはコンストラクタ引数としては設定できず、カメラ作成後に値を設定する必要がある。
推奨デフォルト値:1
透視投影カメラでの描写

↑ 平行投影カメラのイメージ

ライト 【THREE.AmbientLight】
    【THREE.PointLight】
    【THREE.SpotLight】
    【THREE.DirectionalLight】

Three.jsで利用できるライトは多種多様で、それぞれに適した用途や使い方があります。
今回は基本的なライトである「AmbientLight」「PointLight」「SpotLight」「DirectionalLight」を紹介します。
他にも「THREE.HemisphereLight」や「THREE.LensFlare」など特殊なものもありますが、それらはまたライトについて取り上げたときにでも詳しく紹介しようと思います。

ライト 説明
THREE.AmbientLight 基本的なライトのひとつでこのライトの色がシーン内のオブジェクトの色に追加される ×
THREE.PointLight 一点から全方向へと発散する光源 ×
THREE.SpotLight その名の通りスポットライト。円錐状の影響範囲を持つ
THREE.DirectionalLight 無限遠光源とも呼ばれる。このライトから発せられる光線は例えば太陽からの光のようにそれぞれ平行であるように見える

THREE.AmbientLight

THREE.AmbientLightを作成すると、そのライトの色が全体に適用されます。
THREE.AmbientLightに特定の入射角というものはなく、そのためいかなる影も落としません。また、THREE.AmbientLightは形状にかかわらずすべてのオブジェクトのすべての面を同じ色にしてしまうので、シーン内の唯一の光源として利用されることはまずありません。その代わりにTHREE.SpotLightやTHREE.DirectionalLightのような他の光源と組み合わせて、影を和らげたり、シーンに色味を加えたりするために利用します。

THREE.AmbientLightの作成は非常に簡単で、ほんの数ステップで実現できます。
THREE.AmbientLightは位置情報を持たず、シーン全体に適用されます。そのためコンストラクタに指定するものは色(16進表現)だけです。そのライトをシーンに追加すればTHREE.AmbientLightの準備は完了です。

var ambientLight = new THREE.AmbientLight(ambiColor);
scene.add(ambientLight);

ランダムに立方体が配置された↓のシーンのTHREE.AmbientLightの色は#0c0c0cに設定されています。

ambientLight #0c0c0c

このライトの色を#5f0000へと変更してみると、すべてのオブジェクトが平等に赤みがかります。

ambientLight #be3700

THREE.AmbientLightを使用する際の注意点として、指定する色は慎重に選んだ方が良いです。
明るすぎる色を指定すると全体的に彩度が高すぎる画面になってしまいます。

THREE.PointLight

Three.jsのTHREE.PointLightは一点から全方向に向かって光を発する光源です。

PointLightのイメージ

球と立方体の間にPointLightを設置した様子です。

PointLight

球と立方体がTHREE.PointLightによって照らされていますが、それぞれのオブジェクトから落とされる影がないことが分かると思います。
THREE.PointLightは全方向に光を発するので影の計算がGPUにとって非常に重たい処理になります。そのため影を落とさない仕様なのです。

THREE.AmbientLightは色を与えてシーンに追加するだけでしたがTHREE.PointLightは他にも設定できる項目があります。
今回はイメージを掴むことが目的なので詳しい内容についてはライト編のときに取り上げます。

THREE.SpotLight

THREE.SpotLightは(影を使いたいと思った場合は特に)もっとも頻繁に使用することになるライトのひとつで、効果の範囲が円錐状になります。
懐中電灯やランタンのようなものです。

SpotLightのイメージ

球と立方体の手前から奥に向かって光を当てている様子です。オブジェクト自体が照らされている他にオブジェクトから地面に影が落とされているのが分かると思います。
また、地面の照らされ方が均一ではない点に注目してください。奥に行くにつれうっすら暗くなっています。

SpotLight

THREE.DirectionalLight

THREE.DirectionalLightは非常に遠くから届く光だと考えることができます。発するすべての光線はお互いに平行です。太陽の光がよい例です。太陽は非常に遠くにあるので地球上で受ける光線はお互いに(ほぼ)平行であると考えられます。THREE.DirectionalLightとTHREE.SpotLightとの主な違いはTHREE.DirectionalLightのターゲットがどれほど遠くにあってもこのライトは減衰しないということです。THREE.DirectionalLightに照らされる領域はすべて同じ強さの光を受けます。

DirectionalLightのイメージ

球と立方体を上空から照らした様子です。

DirectionalLight

THREE.SpotLightの様にシーンに円錐状の光は適用されていません。すべての物体が同じ量の光を受けています。光の向きと色、強さだけを使用して色と影が計算されています。

オブジェクト

実際に描画されるオブジェクトは、形状をジオメトリで定義、どのように見えるかをマテリアルで定義して、この二つをメッシュでまとめることでシーンに追加できるようになります。

meshのイメージ

ジオメトリとマテリアルについては様々なものがあるので、また今後個別にとりあげようと思います。
ジオメトリ+マテリアルでメッシュをつくりシーンに追加するということを覚えておきましょう。

例として、球体をシーンに追加するときのコードは以下になります。

var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);               // ジオメトリの作成
var sphereMaterial = new THREE.MeshBasicMaterial({color: 0x7777ff});    // マテリアルの作成
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);            // メッシュでまとめる
scene.add(sphere);                                                      // メッシュをシーンに追加

終わりに

第一回である今回は細かいところまでは踏み込まずに基本的な要素の紹介をしました。
全体の流れがなんとなくは掴めたでしょうか?

次回は今回のことを踏まえてかんたんなシーンの作成をやっていこうと思います。

ご覧いただきありがとうございました。

この記事を書いた人

葉っぱ一号

葉っぱ一号

フロントエンドエンジニア

おいしいお店を探すのが好きです。おいしいお店のために遠出もしちゃいます。遠出したい衝動のためにおいしいお店を探すのかもしれません。そんなものですよね。