Hatena::ブログ(Diary)

Imaginable Reality

2016-01-04

Processingで3Dグラフィックスを扱う上での注意点

 Processingのグラフィックスの描画系は内部でOpenGLを利用していますが、ProcessingのAPIは特有の癖があるのでOpenGLと同じ感覚でやっていると時々戸惑うことがあります。3DグラフィックスまわりのProcessing特有の仕様について、気をつけておくべき点をここにメモしておきます(Processing 2.2.1において動作確認)。


(2017/7/19)内容を加筆しました(主に行列のところ)

座標系

  • Processingは左手系。OpenGLは右手系。
  • 視点に対する各軸の向きは、x軸が右方向、y軸が下方向、z軸が手前方向となっている。

f:id:kougaku-navi:20160104103420p:image:w400

3Dグラフィックスで扱われる行列とその取得方法

  • Processingの内部で以下の行列が管理されている。
    • プロジェクション行列(projection)
    • モデルビュー行列(modelview)
    • モデルビュー行列の逆行列(modelviewInv)
    • ビュー行列(camera)
    • ビュー行列の逆行列(cameraInv)
    • プロジェクション行列とモデルビュー行列を乗算した行列(projmodelview)
  • ワールド座標系とローカル座標系のあいだのモデリング変換を表わすモデル行列は定義されていない。モデル行列が欲しい場合は、ビュー行列の逆行列とモデルビュー行列を乗算して求めればよい。

f:id:kougaku-navi:20170719152146p:image

  • プロジェクション行列やモデルビュー行列は、PAppletのメンバ変数である「g」から取得できる。「g」はPGraphicsクラスのオブジェクトだが、その派生クラスであるPGraphicsOpenGL(またはPGraphics3D)でキャストしてやると、modelviewやprojectionにアクセスできる。
  • 各行列への参照を取得できるので、行列の値を書き変えてジオメトリを直接コントロールすることもできる。
  • 行列の値を直接書き変える場合は、ペアになっている逆行列(modelviewの場合はmodelviewInv)のほうも更新したほうがよいようだ。
  • 行列のコピーが欲しい場合は、.get()で取得すればよい。

行列への「参照」が欲しい場合(内部のパラメータを直接書き変えたい場合)
// プロジェクション行列
PMatrix3D projection = ((PGraphicsOpenGL)g).projection;

// モデルビュー行列
PMatrix3D modelview = ((PGraphicsOpenGL)g).modelview;

// モデルビュー行列の逆行列
PMatrix3D modelviewInv = ((PGraphicsOpenGL)g).modelviewInv;

// ビュー行列
PMatrix3D camera = ((PGraphicsOpenGL)g).camera;

// ビュー行列の逆行列
PMatrix3D cameraInv = ((PGraphicsOpenGL)g).cameraInv;

// プロジェクション行列とモデルビュー行列を乗算した行列
PMatrix3D projmodelview = ((PGraphicsOpenGL)g).projmodelview;


行列の「コピー」が欲しい場合
// プロジェクション行列
PMatrix3D projection = ((PGraphicsOpenGL)g).projection.get();

// モデルビュー行列
PMatrix3D modelview = ((PGraphicsOpenGL)g).modelview.get();

// モデルビュー行列の逆行列
PMatrix3D modelviewInv = ((PGraphicsOpenGL)g).modelviewInv.get();

// ビュー行列
PMatrix3D camera = ((PGraphicsOpenGL)g).camera.get();

// ビュー行列の逆行列
PMatrix3D cameraInv = ((PGraphicsOpenGL)g).cameraInv.get();

// プロジェクション行列とモデルビュー行列を乗算した行列
PMatrix3D projmodelview = ((PGraphicsOpenGL)g).projmodelview.get();

デフォルトの視点位置

  • デフォルトではワールド座標系≠カメラ座標系であることに注意。
  • デフォルトの視点位置は以下の通り。
    • 目の位置:( width/2, height/2, (height/2)/tan(PI/6) )
    • 注視点の位置:( width/2, height/2, 0 )
  • これは、画面の左上隅が(0, 0, 0)、右下隅が(width, height, 0)にそれぞれ対応するような視点の配置になっている。ただし、これは透視投影の視野角がデフォルトの60度であることを考慮した計算のため、perspective()などで視野角が変更されるとこの関係はくずれる。

f:id:kougaku-navi:20160104104715p:image:w600

毎フレームの行列の初期化

  • 毎フレーム、draw()開始時に、モデルビュー行列が自動的に初期化される。
  • より具体的に言うと、モデルビュー行列(modelview)がビュー行列(camera)で初期化される(同じ値になる)。
  • デフォルトでは、camera()にデフォルトパラメータを与えた時の行列(すなわち上図のカメラ配置にするための行列)で毎フレーム初期化される。
    • 具体的には、以下の形になっている。ここでwidthとheightは画面サイズ。
 1    0    0       -width/2
 0    1    0       -height/2
 0    0    1    -(height/2)/tan(PI/6)
 0    0    0          1
  • resetMatrix()を実行すると、それ以降、毎フレーム「単位行列」で初期化されるようになる。
  • camera()を実行すると、それ以降、毎フレームそのcamera()で指定したパラメータによって決まるビュー行列で初期化されるようになる。
  • ただし、resetMatrix()やcamera()を実行している箇所をpushMatrix()・popMatrix()で囲んでいた場合はこの限りではない。
  • この挙動については、内部でビューイング変換(ビュー行列)が常に保持されていて、draw()開始時にモデリング変換(モデル行列)のみを毎回単位行列にリセットしている、と考えればよい。
  • draw()の冒頭で毎回resetMatrix()やcamera()を実行している場合は特に気にする必要はないが、draw()の外や特定のタイミングでそれらを実行する場合は留意したい。

camera()の挙動

  • camera()はワールド座標系に対する視点の位置・姿勢(=ビューイング変換)を設定する関数である。
  • camera()を実行すると、内部で保有している5つの行列が更新される(modelview、modelviewInv、camera、cameraInv、projmodelview)
  • camera()を実行すると、パラメータで指定された視点になるようにビュー行列(camera)が設定され、モデル行列が単位行列に初期化される。その結果としてモデルビュー行列(modelview)はビュー行列に等しくなる。
  • camera()を実行すると、それ以降のフレームにおいて、draw()開始時のモデルビュー行列の初期化に影響をもたらす(camera()で設定されたビュー行列で初期化されるようになる)。
  • 上方向ベクトル(upX, upY, upZ)は(downX, downY, downZ)じゃないの?という疑念がある。Y軸が下に向いているのに(0, 1, 0)で上方向を表わすのは気持ち悪い。フォーラムでも同様の指摘がある。

resetMatrix()の挙動

  • resetMatrix()を実行すると、modelview、modelviewInv、camera、cameraInvが単位行列に初期化され、projmodelviewがprojectionと同じ値になる。
  • resetMatrix()を実行すると、それ以降のフレームにおいて、draw()開始時のモデルビュー行列は単位行列になる(ビュー行列も単位行列になってしまっているから)。

さらなる探究

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/kougaku-navi/20160104/p1