【Three.js】画面からはみ出す問題について考える

JavaScript,Three.js

よくできました。

はじめに

Three.jsでブラウザ上に3D空間を映し出します。大きな画面で見るとこんな感じ。

でもスマホみたいな縦長の画面にすると横がはみ出てしまいます。

ほら。僕らの宇宙船地球号が見えなくなってんじゃん。この問題、なんかうやむやにしてたけど、あとあと困る時が来るだろうなぁと思って一生懸命考えました。

CSSをいじる

よく分からないけどCSSいじれば何とかなるんじゃない?そう思って<canvas>の親要素の<div>に"width: 100%"とかやってもダメ。高さも指定するのか?"height: 100%"ダメ。もうダメダメ。なんにも変わりません。

それもそのはず。コレ全然関係ない。大きな間違い。マッスルリベンジャーの壁画見て、技かけてる方とかけられてる方を間違えちゃうぐらい恥ずかしいこと。

そもそもはみ出してなんかいない

そうなんです。実はコレはみ出してないんです。

そういう事だったんですね。

でもなんで映像が縦長になってしまうのでしょうか?そのせいで全体が見えなくなってしまうじゃないですか。一体だれがこんな嫌がらせをしているんでしょう?

僕です

そう。僕なんですねぇ。そうなるように、知らず知らずのうちに僕が自分でカメラの設定をしていたんです。

var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 10, 100);

コンストラクタに渡す引数は「視野角」「アスペクト比」「近平面までの距離」「遠平面までの距離」です。

がんばって描きました。ちゃんと下書きまでして。

とにかくですね、紙の下の方にも書いてあるように、台形の中に入ったものが画面に映るワケです。はみ出すということは、この台形に入っていないんです。

結局どうすればいいの?

台形を大きくしてあげればいいんです。

でも、大きくすればいいからといって、安易に「アスペクト比を大きくして横幅を広げればいいんだな」とか考えてはいけません。いけないワケではないけど、画面いっぱいに表示させたいならダメです。

おそらく宇宙船地球号は画面内に入りますが、映像すべてが横方向にギューっとつぶされて細長くなってしまうでしょう。試してないけど。いまの僕にはわかる。あったかいコーヒー飲んで頭が冴えているから。ちょっと横道にそれますが説明させてください。

横道

前提として「3D空間を画面いっぱいに表示したい」とします。そのためのレンダラーの設定がコレです。

renderer.setSize(window.innerWidth, window.innerHeight);

“window.innerWidth"と"window.innerHeight"で「画面いっぱい」を指定しています。そしてカメラのアスペクト比を今までの2倍にするとします。

var camera = new THREE.PerspectiveCamera(45, (window.innerWidth * 2 ) / window.innerHeight, 10, 100);

“window.innerWidth"を2倍にしてあげればアスペクト比も2倍になります。

この結果、画面の横幅2倍の大きさの物をムリヤリ画面の横幅に収めようとすることになります。幅200pxの画像を、幅100pxの領域に押し込むのと同じです。そうすると画像が縦に伸びてしまいます。

本題に戻ります

というワケで、アスペクト比は"window.innerWidth / window.innerHeight"、つまり画面の縦横比のままで台形を大きくする必要があります。そのためには視野角を広げてあげればいいんです。

視野角を広げると高さの値が大きくなって、つられて幅の値も大きくなります。

今は視野角を45で固定していますが、画面の横幅が狭い時は視野角を広げるようにしてあげればうまくいくでしょう。

// アスペクト比
var aspRatio = window.innerWidth / window.innerHeight;

// 視野角
var fov;
if (aspRatio > 1) {
  fov = 45;
} else if (aspRatio > 0.8) {
  fov = 55;
} else if (aspRatio > 0.6) {
  fov = 70;
} else if (aspRatio > 0.5) {
  fov = 80;
} else {
  fov = 90;
}

// カメラ
var camera = new THREE.PerspectiveCamera(fov, aspRatio, 10, 100);

こんな感じ。で、これを実行すると…。

デモサイトはこちら

ほらできた。クール。極端に細長いスマホ(アスペクト比が0.4以下とか)でもない限り画面内におさまっているんじゃないでしょうか。

あとがき

解決策として、「視野角を広げる」以外にも「カメラを遠ざける」でもイケると思います。試してないけど。

心のどこかでずっと気にしながらも、「なんかムズそうだな…。」と思って見て見ぬフリをしていた問題。解決できてスッキリしました。今日はよく寝られそうです。ありがとうございました。

おしゃれ度

★★☆☆☆

Posted by ナカタ