トップ
ソフトウェア
陶芸
陶芸
OpenGL
大戦略1208攻略
リンク
4 視野のセッティング
続いて、視野のセッティングをおこなう。視野を考えるとき、どこから、どこを、どんなレンズで除いているのかなどを設定するが、設定がうまくいかないと何も表示されなくなってしまう。実行したのに何も表示されない場合は一番に疑うべき項目といえる。
混乱する原因は主に以下のとおりである。
・この種のコマンドが複数存在し、それぞれどんな働きをするのかが分からない。
・コマンドの実行順が決まっていることを知らない。(実行順が異なると、おかしくなる)
それをふまえ、ここでは、サンプルコードを書く前に流れとコマンドを解説する。
 
視野における座標とベクトルのイメージ
座標とベクトルは、表記としてはどちらも(x,y,z)で表すので、ごちゃごちゃになっている人が多いと思うので、ここで念のためイメージを植えつけておく。わかる人は飛ばしてもいい。なお、イメージしやすくするため、二次元で解説する。

座標はその空間のなかで、どこにあるのかを表している。家の住所と同じで、自分(カメラ)がどこにいようが、家の住所は変わらないように、座標も変わらない。
左図の赤点にある家(2,1)は、Aさん(2,-3)にとっても、Bさん(-3,-1)にとっても、座標(2,1)にある。

ベクトルは一般的に「向きと大きさ」を持っている。
左図では、Aさんから見た家の位置は「北に4つ離れたところ」であり、ベクトル(青矢印)で書くと(0,4)である。また、Bさんでは「北に2つ、東に5つ離れたところ」であり、ベクトル(緑矢印)だと(5,2)である。

このように、同じ家の位置を表していて、表記が似ていても、意味がまったく異なる。
もし、AさんがBさんの所まで歩いて移動するとしよう。
このとき、ずっと家を見るようにしたい場合、視線の指定を座標ですれば(2,1)のままでよいが、ベクトルで指定すると、今居る位置から家の方向を常に計算しなければならなくなる。
逆に、ずっと北を向いたまま歩きたい場合は、座標で指定の場合は常に計算しなければならないが、ベクトルで指定なら、ずっと(0,4)でよい。

このように、それぞれの得意不得意がある。
なお、ベクトルで方向だけを表す場合、長さが1となるようにすることが普通である(面の法線ベクトルなど)。
 

計算内容について
OpenGLでは4x4行列を構成点座標に乗算することで変換処理をおこなっている。前述のように、物体の動作を表現するには、物体自体の変化とカメラの変化が考えられるが、OpenGLもそれぞれに行列をもっている。
うれしいことに、
行列の形で値を扱わなくても、十分操作できるコマンドが用意されている。前ページで実施したRotateなどのコマンドもその一つで、物体用変換行列を制御するコマンドである。なお、直接行列を設定することもできるが、複雑になるのでここでは触れない。
行列変更もしくは行列制御コマンドを実行する場合、
変更する行列がカメラ用か物体用かを指定する必要がある。プログラム上では、物体とカメラの操作はごちゃまぜで処理することはまずないため、カメラ処理の前後で行列を切替すればよい。この講座では、初期設定部でカメラを設定し、描画部分で物体の変更をしている。
リセット用コマンド GL.glLoadIdentityGL.glMatrixMode
物体用行列制御コマンド GL.glRotateGL.glTranslateGL.glScale
カメラ用行列制御コマンド GL.glOrthoGL.glFrustumGL.gluPerspectiveGL.gluLookAt
計算順について
次に行列で注意しなければいけないのが、乗算する順番で、結果が変わってしまうことである。普通の乗算の場合、
a x b = b x a
なのだが、行列の場合、
A x B <> B x A
なのである。つまり、
どんな順番でコマンドを実行したかで結果が変わる。実はこれが重要で、実際コマンドA、コマンドBの順番で実行された場合 B x A のように左側から乗算する処理が行われる。LookAtとOrthoの順番が逆になると、出来上がる行列が変わってくる。なお、ここで3次元空間を表現するために4次元行列が使われているのは、奥行き等の計算用に第4成分を使っているためである。(ここでは深く触れない)

このことから、カメラを設定するときは
カメラ用行列の初期化 → 描画範囲の設定 → 視点の設定
の順番で行わないと、おかしな描画になってしまう(少なくともVB.netの場合は結果からこうである)。不自然な気もするが、これはルールなので従うこと。蛇足になるが、行列の中身を確認したい場合は、以下の記述を知りたい場所に挿入すればよい。
Dim pm(15) as single
GL.glGetFloatv(Convert.ToUInt32(GLFlags.GL_PROJECTION_MATRIX), pm)
カメラ用行列の初期化
ますは行列の初期化を行う。これを忘れると、変更がどんどん重なってしまい、おかしくなる。また、このとき行列はカメラ用のものを選択する必要がある。
GL.glMatrixMode(Convert.ToUInt32(GLFlags.a))
変更する変換行列を入れ替える。実際、ここであげる以外にも対象行列はあるが、ここでは触れない。
GLFlags.GL_PROJECTION カメラ用行列の変更を指定
GLFlags.GL_MODELVIEW 物体用行列の変更を指定
 
GL.glLoadIdentity()
現在選択中の変換行列をリセット(単位行列を格納)する。これを実行しないと、変化がどんどん蓄積されてしまう。
描画範囲の設定
次に、どの範囲を描画するかを設定する。同時に、描画を正射影でおこなうか、透視法射影でおこなうかも指定する。コマンドによって指定する値がまったく異なるので注意が必要。図中のカメラのレンズ中心がglLookAtのeye座標、一点鎖線がeye座標とcenter座標を結ぶ直線(視線)となる。また、値はxyz座標系ではなく、今自分が向いている視線を0として右、上をそれぞれ+として入力する。
正射影 奥行きが無視されるので、手前の物体も奥の物体も同じ大きさになる。無限遠の望遠レンズともいえる。CADなど、物体の形状確認用に向いている。
透視法射影 視点から遠くに行くほど小さく描画する。3Dゲームなど、奥行きのある画像を作りたいときに向いている。描画角が大きいと、物体が大きくゆがむ。
 

GL.glOrtho(left , right , bottom , top , near , far)
引数はすべてDouble型

正射影で描画範囲を指定する。3つのコマンドで、唯一nearにマイナスを使える。視点を移動しても、大きさは反映されない(大きさが視点からの距離に依存しないため)。視点を移動する場合、nearとfarの値は+にしたほうが混乱しにくいかもしれない。
left 描画範囲の左端。視線を0として+-で入力。
right 描画範囲の右端。視線を0として+-で入力。
bottom 描画範囲の上端。視線を0として+-で入力。
top 描画範囲の下端。視線を0として+-で入力。
near 視点からどれだけ離れた位置から表示するか。+-で入力。
farよりも大きな値やマイナスでもエラーにはならないが、混乱しやすいので注意(farとの大小が逆転すると、描画方向も変わる)。
far 視点からどれだけ離れた位置まで表示するか。+-で入力。

GL.glFrustum( left , right , bottom , top , near , far)
引数はすべてDouble型

透視法射影で描画範囲を指定する。nearとfarの値は+のみ。また、near<farとなること。上4つが同じ値のとき、nearが小さいほど広角レンズ、大きいほど望遠レンズで撮影した画像になります。広角レンズになるほど、物体の湾曲が強くなります。また、左図下方のように、nearの値だけを変えても、距離だけでなく、描画範囲と画角も大きく変わる。描画範囲を変更する場合は注意が必要(farのみの場合は問題ない)。
left 描画範囲の左端。視線を0として+-で入力。
right 描画範囲の右端。視線を0として+-で入力。
bottom 描画範囲の上端。視線を0として+-で入力。
top 描画範囲の下端。視線を0として+-で入力。
near 視点からどれだけ離れた位置から表示するか。必ず0より大きく、かつfarよりも小さな値を入れる。
far 視点からどれだけ離れた位置まで表示するか。必ず0より大きく、かつnearよりも大きな値を入れる。

GL.gluPerspective( fovy , aspect , near , far)
引数はすべてDouble型

透視法射影で描画範囲を指定するが、glFrustumとは異なり、より視覚的な値で設定する。前2つのコマンドでは左右、上下のバランスを視線方向に対してバラバラに設定できたが、このコマンドでは、視線から均等に割り振られる。実際カメラで撮影した画像を再現する場合はこちらが向いており
、窓から見える風景などは前者が向いている。
左図では、他のコマンドと違い複雑な式が書かれているが、結果的にどのような寸法になるのかの計算式が書かれているだけで、使うときは非常に感覚的な値ですみ、計算も楽である。
fovy 視野の垂直方向画角(角度)。大きいほど広角レンズ、小さいほど望遠レンズになる。180度以上や-も使えるが、混乱するのでお勧めしない。45〜50度あたりが標準レンズ(人の目に近い)である。
aspect 描画範囲の縦横比。Viewportのwidth/Heightをセットすれば、物体の縦横比が正しく描画される。
near 視点からどれだけ離れた位置から表示するか。必ず0より大きく、かつfarよりも小さな値を入れる。
far 視点からどれだけ離れた位置まで表示するか。必ず0より大きく、かつnearよりも大きな値を入れる。
視点の設定
続いて、物体をどこから見るかを決める。このコマンドでは視点の位置(eye)、どこを見るか(center)、どの方向を上に描画するかのベクトル(up)を設定する。上図では、カメラのレンズ中心がeye、レンズの先にcenterがあり、カメラの上方向がupベクトルとなる。飛行機の視線を表現したい場合は、運転席の座標がeye、どの点に向けて飛んでいるかがcenter(方向ベクトルではなく、座標)、飛行機の天井方向を示すベクトルがupとなる。使用時は必ず上記コマンドの後にする。(視線固定ならば、必ずしも使う必要はない)
centerが座標、upがベクトルである理由は、目標物を見ながら、左右に移動するシーンを想像すると分かりやすい。このとき、目標物の位置がcenter、upが上で固定なので、eyeの座標だけを変えればよい。
なお、いろんな書籍や解説では、このgluLookAtは「MODELVIEW」指定直後で設定しているが、ここでは「PROJECTION」指定後、視野範囲を設定したあとで設定している。変化している行列が異なるので、おかしい気もするが、実際には全く同じ結果になる。これは、描画するためには元座標に両行列を掛け合わせて描画位置を決めるのだが、PROJECTIONの最後とMODELVIEWの先頭では、結果的に同じ位置に来るので、結果も同じになっている。私は、このLookAtコマンドはカメラ側の設定なので、PROJECTION側に入れるほうが分かりやすいので、こちらにしている。この辺の賛否は皆さんにお任せする。
GL.gluLookAt(eyeX, eyeY, eyeZ, centercX, centerY, centerZ, upX, upY, upZ)
引数はすべてDouble型
視点の設定をする。詳細は上記を参照のこと。
eye 視点の座標。
center 視線の目標点座標(視点とこの点を結ぶ線が視線となる)。ベクトルではなく座標で入力することに注意。
up 描画範囲の上方向を示すベクトル。eyeとcenterで作るベクトルと垂直になる成分のみが有効になる(左図下方の緑のベクトルは、どちらも同じ値として処理される)。この方向が、上記コマンドのtop方向となる。
どこに描画するか
OpenGLでも、の描画エリアの中のどこに描画するかを設定できる(必ずしも設定する必要はない)。VB.netのDrawImageコマンドを想像してもらうと分かりやすい。指定するのは、描画範囲や視点のコマンドと違い行列操作ではないので、順番に関係なくおこなってもかまわないが、カメラ設定の前後どちらかに記述しておくほうが混乱しなくてよい。
GL.glViewport( x, y , width , height)
引数は全てInteger型
作成画像を、OpenGL.sizeのどこに描画するかを設定する。全体に表示する場合は、widthとheightにOpenGL.sizeのwidthtoHeightを適用すればよい。
x,y 描画範囲の原点位置を指定。単位はピクセル。通常VB.netの画像は左上が(0,0)、下方向が+yだが、ここでは左下が(0,0)、上方向が+yで設定する。
width,height 描画範囲の幅、高さを設定。単位はピクセル。
 
正射影で設定
正射影で設定してみる。透視法射影に比べ混乱しにくいが、勘違いするとやはり見えなくなるので注意が必要。
初期設定部
Protected Overrides Sub InitGLContext()
  GL.glClearColor(1.0, 1.0, 0, 1.0) ’背景色
  
GL.glMatrixMode(Convert.ToUInt32(GLFlags.GL_PROJECTION)) ’カメラ用行列を指定
  
GL.glLoadIdentity() '行列を初期化
  GL.glOrtho( -1, 1, -1, 1, 0.1, 2) ’視界を正射影で設定
  GL.gluLookAt( 0, 0, 1, 0, 0, 0, 0, 1, 0) ’eye 0,0,1 center 0,0,0 up 0,1,0

  
GL.glMatrixMode(Convert.ToUInt32(GLFlags.GL_MODELVIEW)) ’物体用行列を指定 
End Sub
描画部分 
こちらのコードと同じ
実行すると、最初にテストしたときと同じ表示になるはずである。試しにeyeのz=-0.2にしてみても、描画に変化はない。
続いて、以下に変えてみる。
  GL.glOrtho( -1, 1, -1, 1, -0.9, 1) ’視界を正射影で設定
  GL.gluLookAt( 0, 0, 0, 0, 0, -1, 0, 1, 0) ’eye 0,0,0 center 0,0,-1 up 0,1,0

同じ結果になるはずである。後者の場合、例えば(0,0,0)の周りに立方体があった場合、centerの座標を変えるだけで、ほぼ全方向からの描画を実現できる(centerが(0,y,0)の場合を除く)。しかも、centerの値には(0,y,0)以外数字であれば、見えなくならないことが便利である。前者の場合、centerが固定となり、eyeを移動するが、eyeの座標に併せてOrthoのnearとfarの値も変化させないと、見えなくなる。ただし、この考え方は、次の透視法射影とまったく同じなので、両方の描画を使い分ける場合は、こちらの方法で描画したほうがよいといえる(OrthoとFrustumの文字を置き換えるだけでよい)。

Orthoで描画(z = 1、0.2)

Frustumで描画(z = 1)

Frustumで描画(z = 0.2)
 
透視法射影で視点を設定 その1 glFrustum
続いて、透視法射影で設定。
初期設定部
  GL.glLoadIdentity() '行列を初期化
  
GL.glFrustum( -1, 1, -1, 1, 0.1, 2) ’視界を透視法射影で設定
  GL.gluLookAt( 0, 0, 1, 0, 0, 0, 0, 1, 0) ’eye 0,0,1 center 0,0,0 up 0,1,0
描画部分 
変更無し
実行すると、同じ表示になる。試しにeyeのz=-0.2にしてみると、Orthoと違い、ポリゴンの大きさが変わる。これが正射影と透視法射影の違いである。また、Ortho、Frustumの両方で、描画部分を以下に書き換えると、Frustumの方だけ小さなポリゴンが見える。
描画部分(数値のみ変更)
  GL.glColor3d(0.0, 1.0, 0)
  GL.glVertex3d(-0.5, -0.5, -0.5)
  GL.glVertex3d(-0.5, 0.5, -0.5)
  GL.glVertex3d(-0.5, 0.5, 0.5)
  GL.glVertex3d(-0.5, -0.5, 0.5)

Frustum、Perspectiveでの画像

画角45度での画像
 
透視法射影で視点を設定 その2 gluPerspective
続いてgluPerspectiveコマンドで前例をそのまま再現してみる。
初期設定部
GL.glLoadIdentity() '行列を初期化

Dim w, h As Double
Dim fovy, aspect, near, far As Double
Dim rad = Math.Atan(1) / 45

w = 2 : h = 2 ’縦横幅はそれぞれ2
aspect = w / h ’縦横比
near = 0.1 : far = 2 ’近点、遠点
fovy = (Math.Atan(h / 2 / near) * 2) / rad ’画角(ラジアン->度変換含む)

’Frustumでの場合とPerspectiveでの場合(どちらかだけを有効にする)
 'GL.glFrustum(-w / 2, w / 2, -h / 2, h / 2, near, far) '視界を正射影で設定

GL.gluPerspective(fovy, aspect, near, far)
GL.gluLookAt(0, 0, 0.2, 0, 0, 0, 0, 1, 0) 'eye 0,0,0.2 center 0,0,0 up 0,1,0
描画部分 
変更無し
実行すると、FrustumとPerspectiveは同じ表示になる。このことから、この二つのコマンドは同じ透視法射影であることがわかる。
次に、人の目で見たときと同じような画角で描画する方法を考える。
初期設定部
GL.glLoadIdentity() '行列を初期化

Dim w, h, l, r As Double
Dim x, y, z As Double
Dim fovy, aspect, near, far As Double
Dim rad = Math.Atan(1) / 45
Dim okuyuki As Double


fovy = 45 '画角を設定
w = 2 : h = 2 '近点での縦横の表示範囲を設定
aspect = w / h
l = h / 2 / Math.Tan(fovy / 2 * rad) '近点から視点の距離
r = 1 '目標点の周辺の表示範囲半径
near = l
far = near + r * 2
x = 0 : y = 0 : z = l + r '視点の座標

GL.gluPerspective(fovy, aspect, near, far)
GL.gluLookAt(x, y, z, 0, 0, 0, 0, 1, 0) 'eye 0,0,l+r center 0,0,0 up 0,1,0
 
どのコマンドで描画するのがよいのか?
ここまで視野の設定について述べてきたが、実際にどのコマンドを使って描画したらよいのかを考えないといけない。それぞれの長所・短所を踏まえて選ぶようにしよう。
正射影か透視法射影か?
奥行きのない正射影、奥行きのある透視法射影、どちらを用いるかは何を再現するかでほぼ決まる。例えば、3Dゲームでは透視法射影でないと、遠い敵なのか近い敵なのかが分からなくなってしまうので、必然的に透視法射影になる。逆に、CADで作った物体を立体的に見る場合、歪みのない正射影が向いている。写真の中に作った物体をはめ込んで見たい場合は、撮影したレンズと同じ画角の透視法射影で作るとよい。
正射影とする場合、自動的にOrthoコマンドになる。
では、透視法射影の場合、どちらを使えばよいか。
 
透視法射影コマンドの選び方
実際の世界での見え方を考えてみる。人の目は、基本的に画角が決まっており、見える範囲もある意味では決まっている。また視線の左右や上下で見える範囲もほぼ等しくなる。カメラではどうか。カメラはレンズを交換すると画角がまず変わり、それに伴って写る範囲が変わってくる。このときも、レンズの方向に対して写る範囲は上下左右対称となる。つまりPerspectiveは目やカメラを再現していると考えてよい。つまり、これらをそのまま再現したい場合はPerspectiveが向いているといえる。例えば、レンズ交換を再現する場合、fovyの値を変えるだけでよい。Frustumで画角のみを変更するためには、全ての値を計算しなおす必要がある。nearだけ変更したい場合でも同じである。
では、Frustumはどのような場合に使うのか。これまでの例のように、まず物体ありきの場合だろうか?物体ありきでもPerspectiveで十分表示可能である。Frustumでしかできないこと、それは視線の方向に対して左右、上下の比率を変えられる点のみである。そのような操作が必要なのはどんなときか。例えば視点は客車の中、見えるのは窓からの外の風景ではどうだろう。視点が窓の中央にあればPrespectiveで再現できるであろうが、少しでもずれると、下図のように左右の値が変わってくる。このような場合は面倒だがglFrustumを使う。また、サンプルのように、OrthoとFrustumは基本的に設定する数値が同じなので、二つの描画を切替ながら使う場合はFrustumの方が都合よい場合もある。
 
前へ 次へ
  
Copyright Lichu since 2008 all rights reserved