ホームに戻る
 OpenGLメモ

1、はじめに

OpenGL は理論的に何をやっているのか?
実際にテストした結果から推測で書いてみます。

必要な数学の知識は、
四則演算、三角関数、ベクトル、行列
ですが、
わからなくてもなんとかなるようにしています。

2、仮想3D空間

OpenGL は主に3次元空間を考慮しますが、人間の視野とは異なります。
人間の視野の形は不定です。
反して、 OpenGL での描画はウィンドウに描けばいいわけですから、
視野が四角形と決まっています。
もうひとつ、人間の場合は目が2つあることを考慮する必要がありますが、
OpenGL の場合は1つで十分です。
OpenGL では1つの視点から四角形の視野が伸びていく世界を考えます。
OpenGL での視界は、視点から離れるにつれて四角形で広がっていきます。
さらに、現実世界の視界は視点から無限長までありますが、OpenGL では制限があります。
具体的には OpenGL では手前と奥を定義する必要があります。
視点の位置から手前の四角形と奥の四角形を決めます。
手前の四角から奥の四角の間にあるものが見えるものです。
あまり近すぎるもの、遠すぎるものは OpenGL は描画しません。
手前の四角形から奥の四角形の間にある物体を、
OpenGL は手前の四角形に映し出します。
(実際に映し出しているのは奥かもしれませんが区別できない。)

OpenGL は視点の位置と視点の方向を動かせません。
決定できるのは、視野の四角形の縦横サイズと、手前の距離と奥の距離です。

追記:
注意する点として右手系と左手系の問題があります。
3D空間は3次元なのでXYZ方向が存在しますが、
上をX方向、右をY方向と決めたときに、
Z方向は奥か手前のどちらに向かうかが定まりません。
よって、どちらかに統一する必要があります。
統一しない場合におこる問題として、
右手系に左手系の物体を配置した場合に、
物体の反転が生じてしまうなどの問題があります。

もうひとつ注意する点として、手前の四角形の縦横の長さの比率があります。
視野空間の四角形が実際に表示するウィンドウと一致しない場合があります。
例えば、縦サイズ 1.0 横サイズ 1.0 の3D空間のスクリーンは、
縦サイズ 600 横サイズ 800 のウィンドウに合いません。
この場合は横サイズのほうが長いので、表示が横に伸びることになります。
このような縦横の引き伸ばしの変換が行われます。
この変換を「ビューポート変換」とか呼びます。
これは3D空間かウィンドウの縦横の比率を調節することで直ります。

3、レイトレーシング

視野については手前の四角形と奥の四角形の話がひとつ前の説明でわかったとします。
次は1つの三角形を描く方法、レイトレーシングについてです。

面を描くのに必要な最小の点は3点です。
3点では三角形が描けますが、2点では線しか描けないからです。
この3点でできた三角形を3D描画の世界ではポリゴンと呼びます。
三角形が2つで四角形が描けますし、四角形が6つで立方体が描けます。
三角形を組み合わせてカクカクの球を描くこともできるし、
三角形の数をもっと使えばより滑らかな球も描くこともできます。
3角形が1つ描ければなんでも描けます。
すべての物質が無数の三角形で構成されていると考えれば、
あらゆる物体は三角形から作り出すことができます。

では、1個の三角形をどうやって描くか?
レイトレーシングという方法を使います。
レイは光線で、トレーシングは走査という意味です。

例えば、視野の手前の四角形が横200×縦160の長さの空間があり、
視点から四角形の四隅を通る視界で奥の四角形まで視野を作っているとします。
この視野空間に三角形がひとつありそれをレイトレーシングで描画してみます。
手前の四角形の左上の座標が(0,0)であるので、
視点からこの(0,0)の座標に光線をだします。
このとき光線が奥の四角形に当たるまでに三角形にぶつかれば色を塗ります。
光線が三角形にぶつからなければ色を塗りません。
最初の行は(1,0)、(2,0)、(3,0)、(4,0)・・・(199、0)と続けます。
次の行は(0,1)、(1,1)、(2,1)、(3,1)・・・(199,1)と続きます。
このままいちばん下の行まで繰り返します。
最後は・・・(197,159)、(198,159)、(199,159)で終わります。
すると色を塗った形は三角形になると思いませんか?
この方法でひとつの三角形を描くことができます。
では、複数の三角形ではどうか?というと、
ビームがすべての三角形に当たるかひとつずつ調べればいいわけですから、
複数の三角形であっても描画できることになりますね。

左から右にビームを撃って当たれば描画する。
次の行も左から右にビームを撃って当たれば描画。
これをすべての行で行えば四角の画面に三角形が描けるのです。
OpenGL は内部でレイトレーシングを行っています。

数学を使った説明(難しいので飛ばしても可)。

三角形に光線が当たるかどうかを判定するには、
「光線ベクトル」と「三角形のある平面」との交点が三角形の内部か外部かわかれば良い。
これを求めるのが意外に難しい。

手順は次の3ステップ、

1、視点から三角形のある平面までの最短距離を求める。
2、光線ベクトルが三角形のある平面に当たるまでの距離を求める。
3、面に当たった点が三角形の内部か外部が調べる。

ステップ1:
視点をPとし三角形のある頂点1点をQとし、三角形の法線をNとする。
三角形の法線は三角形の3点より外積で求めることができる。
法線は長さが1になるように正規化しておく。
法線ベクトルとPQベクトルより、法線とPQのなす角度が内積で計算できる。
PQの長さと角度から三角関数で視点から平面までの最短距離がわかる。

ステップ2:
次に光線ベクトルと三角形の法線ベクトルの角度を内積で求める。
視点から三角形のある平面までの距離はわかっているので、
求めた角度と距離から三角関数で光線ベクトルが平面に当たる距離がわかる。

長さ1の光線ベクトルに求めた距離をかけると、光線の当たる点がわかる。

ステップ3:
光線ベクトルが面に当たる点をOとする。
三角形の3点をそれぞれABCとしたときに、
ABO、BCO、CAOの3つの三角形について考える。
もしOがABCの内部であれば頂点はすべて右回りないし左回りとなる。
OがABCの外部であればこの条件を満たさなくなる。
右回りか左回りかは外積で求めた法線の向きを調べれば良い。
法線と法線で内積をとり、cos値を求め、値が正であれば同じ向きである。

4、光源

以上の説明でレイトレーシングで三角形を描画することはできるとする。
ただ、これではすべての物体が同じ色で塗られてしまう。
光がよく当たるところ、当たらないところで色の明るさが違わない。
だから、立体っぽくない絵しかできない。次は光源を考えます。

光源には線光源、点光源、環境光がある。
線光源とは例えば太陽などの遠くからの光、点光源は電球などから発する光、
環境光はどの方向からもまんべんなく当たる光のことである。
線光源は遠くからの光なので位置を指定せず向きだけを指定する。
線光源は光の線と三角形の面が直角に当たれば一番明るいということになり、
いちばん明るい色を使って三角形が塗られることになる。
線光源と三角形の面の角度が大きくなるにつれ色は暗くなり、
線光源に対して三角の面の向きがまったく逆でいちばん暗くなる。
点光源に関しては位置を指定する。
点光源は光の向きも重要だが、遠さも考慮する。
距離が近ければ明るいし、遠くなるほど暗い。
環境光はすべての三角形にどの方向からでも自然に当たる光のこと。
すなわちどの三角形に一定の明るさを足すことになる。

光源を使用する場合には実際に三角形の法線を計算しておく必要がある。
この計算は3点がわかっていれば自動的に計算できるものなのだが、
OpenGL が自動で計算してくれるわけではないので注意する。

以上の方法で例えば立方体は光の当たらない面に影ができ、
立体であるかのように見えることになる。

光源の落とし穴として、
三角形を描画する際明るさを調べるのは三角形の3つの頂点でしか行われず、
三角形を描画する過程で面の上の1点1点で調べられることはありません。
三角形のある1頂点とある1頂点の明るさが違う場合、
その間の明るさは単純に等間隔で色が変化するグラデーションになるだけです。
もし、点光源などを三角形に当て、思った効果を得ようとするなら、
その三角形をさらに小さな三角形に分解する必要がある。

5、テクスチャと透過

テクスチャを使用すると三角形を描画する色の指定に画像を使うことができます。
三角形の各点がテクスチャ画像のどの位置に対応するか指定すればOKです。
テクスチャをシールだとするとテクスチャシールから
三角形を切り取って自由に縮めたり伸ばしたりして三角形に貼り付けます。
引き伸ばしたりした場合に間の色をどう補うとか、
縮めた場合にどの色を選択するかなど方法は OpenGL である程度選べます。

透過に関しても透過度をテクスチャに含ませることで実現しています。
透過は背景に現在の色を計算した比率で混ぜることで実現します。
現在の色と背景の色をどのように混ぜるかはある程度 OpenGL で選べます。

テクスチャに関しては光源と違い、三角形の面上の1点1点について描画していきます。

6、フォグ

フォグの原理は非常に簡単で、
視野の奥にいくほど単純に色を足すというものです。
奥にいくほどフォグの色に隠れて見えにくくなります。

7、デプスバッファ

デプスバッファは視点から物体までの遠さを管理します。

例えば手前からの距離が5の三角形Aと距離が8の三角形Bがあり、
普通は視点から見ると手前の三角形Aに奥の三角形Bが一部隠れます。
しかし、レイトレーシングで三角形A→三角形Bの順番で描画するとどうなるでしょう?
手前の三角形Aの手前に奥の三角形Bが描かれてしまいます。
変ですね。
偶然に三角形B→三角形Aの順で描ければ正確に描画できるのですが、
三角形が増えれば偶然がうまくいってくれる確率も減ります。

これを解決するのがデプスバッファです。
仕組みは例えば横200×縦160の画面に対して、
1点1点につき描画した三角形の最短の距離を保存しておきます。
例えば先に三角形Aを描いた場合にはデプスバッファに、
三角形Aの形で距離の数値5が書き込まれることになります。
次に既にデプスバッファに数値が書き込まれた状態で三角形Bを描こうとすると、
三角形Aと三角形Bが重なる部分だけバッファの値が5になっているため、
距離が8の三角形Bは描画されないのです。

OpenGL ではデプスバッファの設定をするだけで、
数値の評価を自動でしてくれます。

ただし、自動でやられると困る場合もあります。
デプスバッファが問題になる場合は半透明な物体を描くときです。
手前に半透明な三角形を描いたあとに奥の三角形を描く場合、
奥の三角形は手前の半透明の三角形に透けて見えないといけません。
ただしデプスバッファが有効であると奥の三角形は描かれないのです。

よって半透明の物体を描くときは、
デプスバッファを無効にしたり描画順を変えたり工夫する必要があります。

8、カリング

さて、ここまで書いてこなかったことですが、
実は三角形を描く場合には表と裏で2回の描画が必要です。
例えば、表が赤で裏が青の回転する三角形を描く場合に、
表を描くということと、裏を描くことを2つ考えないといけないからです。
このときカリングとは描く面を片側に限定することです。

例えば透明で無い立方体は立方体の内部がどの方向からも見えません。
よって、立方体を構成する三角形がすべて外側を向いていれば、
内側を描く必要は無いわけです。
この描画を行わないことである程度の処理の軽減になります。

さて、表と裏の見分け方ですが、
三角形の頂点が右回りか左回りかで見分けます。
立方体にカリングを適用する場合には、
立方体の外側の面をすべて表か裏で作る必要があります。

OpenGL では描画しない面を指定するだけで、
自動的にカリングを行ってくれます。
プログラマが注意するのは三角形の頂点が右回りか左回りかということです。

9、点、線、曲線、曲面

OpenGL は点、線、曲線、曲面に対応しています。
点、線、曲線、曲面を描くための関数が揃っています。
曲面は曲線をつなげたものなので曲線ができればできます。
まずは曲線の数式についての理解が必要です。

10、アキュームレーションバッファ

アキュームレーションとは累積を意味します。
累積とはつまり数値を足していけるバッファということです。

モーションブラー(数秒前の動きが残って見える残像)や、
アンチエイリアス(意図的なぶれによって境界をぼかす)
などに利用できるようです。
中身の構造としてはデプスバッファと同じようなものと思っていいでしょう。

11、ステンシルバッファ

ステンシルバッファはデプスバッファと違って自分で書き込む数値を選べます。
中身の構造はこれもデプスバッファと同じようなものでしょう。
バッファの数値よりも小さければ描画とか、大きければ描画などの処理が行えます。
自由に使えるバッファなのでどのように使っても自由です。

12、クリッピング

クリッピングは意図的に描画しない領域を定めることで、
その部分を描画しないだけ処理を軽くしようとするものです。

OpenGL ではレイトレーシングにて指定した四角領域以外を描画しないシザーテストと、
仮想3次元空間で座標軸に平行な面に対して片側を描画しない平面クリップがある。

13、拡大、回転、移動

OpenGL の仮想空間上で三角形を拡大、回転、移動したい場合があるとします。
この場合は OpenGL の関数を使えば簡単にできるわけですが、
内部で行列計算を行っているとわかると理解が深まります。
例えば、現在の座標に角度を指定してX軸回転の行列を掛け合わせると、
原点を中心に座標をX軸を中心に回転してくれるのです。
必ず原点が中心になるというのは重要です。
もうひとつ、掛けあわす場合の順番が重要です。
回転してから移動するのと、移動してから回転するのでは結果が違います。
回転してから移動すると既に回転したものが目的の位置へ移動しますが、
移動して回転すると、移動したあとの回転によってまた位置がぐるっと大きく変わってしまいます。

数学的には以下のような計算です。
要素はX,Y,Zの3つなのに4×4行列を用いるのがポイント。

X軸回転
                          | 1     0    0  0 |
|x' y' z' 1'| = |x y z 1|*| 0  cosX sinX  0 |
                          | 0 -sinX cosX  0 |
                          | 0     0    0  1 |
Y軸回転
                          | cosY 0 -sinY  0 |
|x' y' z' 1'| = |x y z 1|*|    0 1     0  0 |
                          | sinY 0  cosY  0 |
                          |    0 0     0  1 |
Z軸回転
                          |  cosZ sinZ 0 0 |
|x' y' z' 1'| = |x y z 1|*| -sinZ cosZ 0 0 |
                          |     0    0 1 0 |
                          |     0    0 0 1 |
拡大縮小
                          | sX  0  0 0 |
|x' y' z' 1'| = |x y z 1|*|  0 sY  0 0 |
                          |  0  0 sZ 0 |
                          |  0  0  0 1 |
平行移動
                          |  1  0  0 0 |
|x' y' z' 1'| = |x y z 1|*|  0  1  0 0 |
                          |  0  0  1 0 |
                          | dx dy dz 1 |

行列の計算は A×B と B×A は同じではありません。
(AXB)XC と A×(B×C) は同じです。
回転して移動する行列をひとつにまとめたりすることは可能です。
あと、逆行列などを利用してトリッキーな処理を組むこともできます。
このあたりは行列のルールをよく勉強する必要があります。

14、カメラ

世界が動くのであればカメラは動く必要はありません。
カメラを左に動かしたいのであれば世界を右に動かせばいいわけです。
OpenGL にはカメラの概念はありません。

15、ハードウェアによる高速化

14までの説明はソフトウェアに関する説明です。
ここまでの説明を元に自力でオリジナル3D描画ライブラリを作成することは可能です。

ただ、ハードウェアの壁が存在します。

例えば、
一定の変換行列群をハード上のメモリにストックできる。
行列演算を専用に行うチップをハード上に搭載する。
テクスチャ画像をハード上のメモリにストックできる。
デプスバッファ、ステンシルバッファをハード上に用意する。
などです。

OpenGL がこのうちどれくらいサポートしているのかはわかりませんが、
とにかく OpenGL 対応のグラフィックボードであればハードがサポートするので処理が高速です。
オリジナル3D描画ライブラリの限界はこのあたりにあります。

16、さいごに

よくある疑問

ビットマップは読み込めないか?

読み込むことができる画像のデータ形式はありません。
自力で画像データをメモリに読み出します。

DXFなどの3Dデータは読み込めないのか?

読み込めないので自力で読み込むライブラリを作るなりします。

別の物体に隠れて光が当たらない部分に影をつけることはできないのか?

こういった場合の影つけも自力でやればつけることができますが、
OpenGL の通常の処理では影付けは自動で行われません。

画面をクリックした場合にどの三角形を触ったか判別できるか?

これも OpenGL サポート外なので、自力の処理を書きます。
普通はクリックした位置から光線を出して三角形との当たり判定を出します。
三角形の座標をウィンドウ上の2次元座標に変換しておく手もあります。
もしくは、三角形にステンシルバッファで値を与えておき、判断するのもありです。
また、三角形の色を取得し判断してしまうような簡単な方法もあります。

何ができて何ができないかという情報は貴重です。
インターネットで「OpenGL FAQ」を検索すると何かしらの情報が得られるものです。

inserted by FC2 system