今回は、透視投影について説明していきます。
透視投影とは、視点より遠いものは実際の大きさより小さくなるように表示して遠近感を表現する方法です。例えば、100m先のものは50m先のものの半分の大きさで描くといった感じです。
前回のサンプルの内容で、スライダーを右に移動させるとだんだん透視投影されて立体感が出てきます。
スライダー値を「0」にすると遠くのものも近くのものも同じ大きさで表示され立体感がなくなります。
下図のz位置にあるオリジナルの木を視点から焦点である投影面(スクリーン)でみた時に小さく表示させるように透視投影させるのですが、実際どのような計算をおこなっているのでしょうか?
ソースリスト上では、Point3Dクラスに透視投影を行うメソッドが定義されています。
これがどのような意味をもつのか理解する必要があります。
この一般解については、てっく煮の下記サイトにて説明してくれています。
AS3.0 で 3D プログラミングを1から勉強する (3) - 透視投影
計算式を見ただけはすぐに理解が出来なかったのですが、何回か読んでいくうちにやっと理解出来ました。
透視投影の一般化
ys = ((zs - zf) / (z - zf)) * y xs = ((zs - zf) / (z - zf)) * x
物体の高さが10m(y)で55m(z)の位置にあるとして、焦点の位置が原点より少し前に進んだ5m(zf)の位置で、物体を実際にスクリーンする位置が15m(zs)とした時に、スクリーン時の物体の大きさが幾つになるのか?
スクリーン時の物体の高さ(ys) = (15 - 5) / 55 - 5) * 10 = (10 / 50) * 10 = 0.5 * 10 = 5
スクリーン時の物体の高さ(ys) は、5m となります。
焦点が原点の場合
ys = y / z xs = x / z
焦点を原点にすると計算が簡単になるので、焦点の位置が原点より少し前に進んだ5m(zf)の位置だったのを全体を5m前にすると、スクリーンする位置が15-5の10m(zs)、物体の位置が55-5の50m(z)となります。
また、スクリーン zs = 1 とするために全体を10で割ります。
ys = (1 / z) * y → ys = y / z → ys = 1 / 5 = 0.5 → 全体を10倍に戻すと 5mが求まる。
スクリーンが原点にある場合
今度はスクリーン(zs)を原点に移動してみる。先程の式 ys = y / z から焦点(zf)を引けばいい。
ys = y / z → ys = (1 / z) * y の式に戻すと分かりやすい。
ys = (-zf / (z -zf)) * y xs = (-zf / (z -zf)) * x
スクリーンと焦点の距離を f (= -zf)と書き換えることで、上の式は次のように書き換えられる。
ys = (f / (z + f)) * y xs = (f / (z + f)) * x
この比例式は、オブジェクトの大きさだけでなく、3次元空間の座標すべてに対して成立ちます。
参照:Vector3Dオブジェクトの座標に遠近法を適用する
焦点距離(f) / (z位置 + 焦点距離(f) )
下記のgetPerspectiveメソッドの引数「viewdistance」が、「スクリーンが原点にある場合」の 焦点距離(f) となります。
//透視投影の比率 Point3D.prototype.getPerspective = function (viewdistance) { return viewdistance / (this.z + viewdistance); }; //3次元空間座標を2次元平面の投影座標へ Point3D.prototype.project = function (perspective) { this.x *= perspective; this.y *= perspective; this.z = 0; };
Cubeクラスのdrawメソッドにて透視投影後の座標を取得する部分
var pt = matrix.transformPoint(this.points[i]); // 点を透視投影する pt.project(pt.getPerspective(f));