サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

遠近法が投影された座標を求める

ID: FN1308003 Technique: HTML5 and JavaScript Library: EaselJS NEXT

CreateJSには3次元空間の座標を扱う機能はありません。それでも、3次元空間の座標に「遠近法」を加えて2次元平面に投影する計算は、それほど難しくはありません。「透視投影」と呼ばれるこの座標の求め方を説明し、3次元空間の座標を扱うクラスとして定めてみます。


01 消失点と焦点距離

コンピュータグラフィックス(CG)の3次元座標空間の描画は、最終的に2次元平面のスクリーンに対して行われます。したがって、3次元空間で計算した座標に遠近法の効果を加えたうえで、スクリーンに投影するという処理になります。この処理を英語で"perspective projection"と呼び,「遠近法投影」とか「透視投影」と訳されます。

遠近法では、奥行き(z座標)が遠いものほど小さく、また距離も縮めて表現します。そして、かぎりなく遠ざかるにつれ、ある1点に向かって小さくなり、やがて見えなくなります。この点を「消失点」と呼びます(図001)。

図001■遠ざかるオブジェクトは消失点に向けて小さくなる

焦点距離ということばは、カメラで使われます。たとえば、広角レンズは焦点距離が短く、同じ距離でも広い範囲が撮影できます。他方、焦点距離の長い望遠レンズは、写せる範囲は狭くなるものの、遠くのものを近寄せます。それだけでなく、画角は遠近感にも影響を及ぼします。

ふたつのものの奥行きの距離は、レンズが広角になり、焦点距離が短くなるほど、離れて見えるようになります。これを、遠近感の「誇張効果」といいます。逆に、焦点距離の長い望遠レンズでは、肉眼よりも距離が近づいて見えます(図002)。一眼レフカメラでは、撮影する範囲や距離だけでなく、このような遠近感の効果も考えてレンズを選びます(デジタル一眼レフカメラ "α":アルファ「背景と遠近感」参照)。

図002■焦点距離による表現の違い

短い焦点距離

長い焦点距離

02 焦点距離から透視投影した座標を求める

焦点距離は、3次元座標空間における奥行きとなるz軸座標の視点から、投影面(スクリーン)までの距離を表します。焦点距離が長いほど、z位置の離れた(奥の)オブジェクトは、投影像がスクリーンに相対的に大きく表示されます。また、視野角は視点から投影面全体を見た角度です(図003)。したがって、焦点距離は、視野角と連動します。焦点距離を操作すれば、視野角もそれに応じて変わります。

図003■z軸における焦点距離と視野角

図のもとのオブジェクトと投影像をそれぞれ底辺とし、ともに視点が頂点となる相似なふたつの三角形から、比率がつぎのように求められます。

投影像の大きさ/オブジェクトの大きさ = 焦点距離 / (焦点距離 + z位置)
投影像の大きさ = オブジェクトの大きさ×焦点距離 / (焦点距離 + z位置)

したがって、3次元空間のz座標値に応じてxy座標値を2次元平面に透視投影する比率はつぎのとおりです。予め焦点距離を定めたうえで、この投影比率をxy座標に乗じれば、透視投影された座標が定まります。

透視投影比率 = 焦点距離 / (焦点距離 + z位置)

03 3次元空間の座標を透視投影するクラスの定義

透視投影は3次元空間座標に対して行います。CreateJSには、3次元空間座標を扱うクラスがありませんので、新たに定めることにしましょう(コード001)。透視投影の計算は、そのメソッドとして備えます。クラス(Point3D)は、2次元平面座標のPointクラスを継承することにしました(第4行目)。

CreateJSのクラスでは、基本的にコンストラクタは同じ引数でinitialize()メソッドを呼出します(第2行目)。クラスを継承したときは、スーパークラスのinitialize()メソッドを実行するため、参照を別の名前(Point_initialize)に定めたうえで、initialize()メソッドから呼出します(第5および第7行目)[*1]。備えたメソッドcopy()とclone()およびtoString()は、スーパークラスPointにもとづき、3次元に対応させました。

透視投影のメソッド(getProjetedPoint())は、引数の焦点距離(focalLength)から前項の投影比率を求めます(第13行目)。そして、その比率が乗じられたxy座標をプロパティとする新たなPointインスタンスが戻り値です(第14〜16行目)。

コード001■透視投影のメソッドを備えた3次元空間座標のPoint3Dクラス
  1. function Point3D(x, y, z) {
  2.   this.initialize(x, y, z);
  3. }
  4. Point3D.prototype = new createjs.Point();
  5. Point3D.prototype.Point_initialize = Point3D.prototype.initialize;
  6. Point3D.prototype.initialize = function Point3D(x, y, z) {
  7.   this.Point_initialize(x, y);
  8.   this.z = (z == undefined) ? 0 : z;
  9.   return this;
  10. }
  11. Point3D.prototype.getProjetedPoint = function(focalLength) {
  12.   var point2D = new createjs.Point();
  13.   var w = focalLength / (focalLength + this.z);
  14.   point2D.x = this.x * w;
  15.   point2D.y = this.y * w;
  16.   return point2D;
  17. };
  18. Point3D.prototype.copy = function(_point) {
  19.   this.initialize(_point.x, _point.y, _point.z);
  20. };
  21. Point3D.prototype.clone = function() {
  22.   return new Point3D(this.x, this.y, this.z);
  23. }
  24. Point3D.prototype.toString = function() {
  25.   return "[Point3D (x=" + this.x + " y=" + this.y + " z=" + this.z + ")]";
  26. }

透視投影のメソッド(getProjetedPoint())をこのまま試しても、単純な四則演算の結果の座標がPointオブジェクトで得られるだけです。そこで、jsdo.itにインタラクティブなアニメーションのサンプルコードを掲げました。

[HTML]にscript要素で、前掲コード001のクラス(Point3D)が定められています。星形の3次元座標(Point3Dオブジェクト)をy軸で回し、マウスポインタの水平位置に応じてその方向と速さを変えています。

[JavaScript]コードの細かいご説明は省きます。座標を回しているのは、Stage.stagemousemoveイベントのリスナー関数です(rotate())。3次元空間座標の(Point3D)オブジェクトは配列(points)に納めて、Matrix2Dオブジェクト(matrix)で回します。

ただし、y軸を中心にするということは、xz平面における回転になります(第30〜32行目)。なお、EaselJS次期バージョン候補(NEXT)のMatrix2D.transformPoint()メソッドでオブジェクトの座標を回しています。そのうえで、クラス(Point3D)に定めた透視投影のメソッド(getProjetedPoint())で、透視投影したPointオブジェクトを別の配列(points2D)に納めます。

透視投影された座標(points2D)を直線で結ぶ関数(draw())は、別に定めました(第35行目)。これで、星形のワイヤーフレームがy軸で回ります。

  1. var points = [];
  2. var points2D = [];
  1. var angle = Math.PI / 12;
  2. var matrix = new createjs.Matrix2D();
  3. var focalLength = size * 10;
  1. stage.addEventListener("stagemousemove", setAngle);
  1. function rotate(eventObject) {
  2.   var count = points.length;
  3.   points2D.length = 0;
  4.   matrix.identity().rotate(angle);
  5.   for (var i = 0; i < count; i++) {
  6.     var point = points[i];
  7.     var _point = matrix.transformPoint(point.x, point.z);
  8.     point.x = _point.x;
  9.     point.z = _point.y;
  10.     points2D[i] = point.getProjetedPoint(focalLength);
  11.   }
  12.   draw(points2D);
  13. }

[*1] コンストラクタに初期化の処理を書いたときは、スーパークラスのコンストラクタをFunction.call()メソッドで呼出すのが通常です。この例では、CreateJSの書き方にしたがいました。



作成者: 野中文雄
作成日: 2013年8月13日


Copyright © 2001-2013 Fumio Nonaka.  All rights reserved.