混合処理とディスプレイリスト

Last update: <2004/05/14 22:39:12 +0900>

混合処理

色の混ぜ合わせであるブレンディング(blending)について説明します.混合処理とは,半透明な物体の表現です.半透明処理によって,ガラスや液体などの物質を表現できるのは当然ですが,物体を透過的に表示することは大変便利で,実際には見えていないがバーチャル空間では見ることができる,という効果を容易に実現できます.例えば医療データをバーチャル空間で表示する場合には,データを半透明表示することによって中身を一望できる手法がとられています.バーチャルリアリティならではの表現として,混合処理は大変有用です.


このプログラムの実行ファイルデータファイル.興味のある人は実行してみて下さい.


上の画像は脳のMRIデータを混合処理を利用して表示したものです.手製プログラムのため,モノクロで見づらいかもしれませんが,脳内部の密度分布を半透明のデータで表現しています.この半透明による表示手法はボリュームグラフィクス(Volume Graphics)と呼ばれており,中身の詰まったデータ(volume)の表示に威力を発揮しています.ちなみにボリュームデータに対して力覚(触覚)を発生させる手法をボリュームハプティクス(Volume Haptics)と言います.



混合処理は通常,不透明度を表すアルファ値を用います.アルファ値が高いものほど不透明で,OpenGLでは0で完全透明,1で不透明となっています.物体それぞれにアルファ値を定義することによって,混合を実行できます.例えばガラスの向こうがわに物体があるシーンを考えた場合,ガラスの色と物体の色は混ざり合って見えます.ガラスが透明度20%のスモークガラスの場合ガラスのアルファ値は0.8で,見ている色は物体が80%,ガラスが20%の割合に混ざり合います.このようなアルファ値による混合処理をアルファブレンディング(alpha blending)とよぶこともあります.

混合処理では色の加減算をするわけですが,実際の処理はフレームバッファに保存されているピクセルカラーと物体のカラーの足し算になります.このとき,フレームバッファ側のカラー値をターゲット(target),物体側のカラー値をソース(source)といいます.

次の例はピンクの不透明物体に透明度が50%の緑の物体を重ねあわせた場合の混合処理例です.



もしピンクの不透明物体が手前にある場合は,隠面処理によって緑の面が隠されてしまいます.不透明な物体がある場合は,前後関係によって後ろの物体が消えるのは当然です.ここまでは簡単です.



複雑なのは,混合処理では描画の順序によってソースの色が優先される場合があることです.例えば,次の例はピンクの透明物体に緑の透明物体を重ねる場合,ターゲットとソースとで色を入れ替えると結果が変わり,ソースのほうが優先されていることがわかります.


ターゲットがピンク色,ソースが緑色の場合,混合結果は後から描いた緑が強い.



ターゲットが緑色,ソースがピンク色.混合結果は後から描いたピンクが強い.


この例ではまずターゲットが描画される際,アルファ値が0.5の半透明になります.ソースと混合する際に重なり部分が生じるわけですが,重なり部分ではソースのアルファ値が0.5であることから,ソースに0.5,ターゲットに1.0-0.5=0.5をそれぞれを乗算されます.結果としてターゲットのアルファ値が0.25,ソースのアルファ値が0.5になります.したがって,重なり部分ではソース(上書き描画)の色味が優先される結果となっています.

後から描画される色味が優先されるケースばかりではありません.色味をすべて等しく扱いたい場合は,重ねがきをする際にターゲットに0.5を乗算しなければよいわけです.下の例では混合の際,重なり部分に0.5をかけていません.結果としてターゲットのアルファ値が0.5,ソースのアルファ値が0.5と等しい混合となっています.


混合係数を設定すると,ターゲットの色を100%,ソースの色を50%使用することもできる.ターゲットは元々アルファ値が50%なので,上の例では等しく混合されている.


これまでの例を見てもわかる通り,ターゲットとソースにはそれぞれ色とアルファ値が定義されていて,重なり部分でアルファ値をどう使うかで結果が異なってきます.OpenGLの混合処理では,ターゲットとソースのアルファ値をどう利用するかを混合係数として設定することができます.


ディスプレイリスト*19


ディスプレイリストとは,描画実行時に効率的に処理できるように,描画に関するコマンド群をひとまとめに記憶しリスト化しておくことです.

例えば次のソースプログラムは,円柱を描画するものです.円柱の上面と底面は円になるので,円を表示するために三角関数を呼び出します.また円柱の側面もポリゴンを表示するため,同様に三角関数を使用しています.

/* #include <math.h> が必要 */ #define M_PI 3.14159265 /* 底面の直径が2,高さが2 の円柱を描画する */ /* divで底面の円の分割数を指定する */ void drawCylinder(int div) { int i; glBegin(GL_POLYGON); glNormal3f(0.0, 1.0, 0.0); /* 円柱の上面の描画 */ for(i=0;i<div;i++) glVertex3f((float)cos((double)i*2.0*M_PI/div), 1.0, (float)sin((double)i*2.0*M_PI/div)); glEnd(); glBegin(GL_POLYGON); glNormal3f(0.0,-1.0, 0.0); /* 円柱の底面の描画 */ for(i=0;i<div;i++) glVertex3f((float)cos(i*2.0*M_PI/div), -1.0, (float)sin(i*2.0*M_PI/div)); glEnd(); /* 円柱の側面の描画 */ glBegin(GL_QUAD_STRIP); for(i=0;i<=div;i++){ float co,si; co=cos(i*2.0*M_PI/div); si=sin(i*2.0*M_PI/div); glNormal3f(co, 0.0, si); glVertex3f(co, 1.0, si); glVertex3f(co,-1.0, si); } glEnd(); }

円柱を描画したい場合には,display関数の中で上記のdrawCylinder()を呼び出します.さらに円柱を複数個描画する場合は何度もこの関数を呼び出すことになりそうです.ところがこの関数をよく見ると,毎回三角関数を呼び出して円の形を算出し頂点位置を指定しています.何度も計算するのも悪くないのですが,円柱を何百個も描画したいときに同じ計算をするのはナンセンスで,計算を省く工夫が必要です*20

ディスプレイリストは繰り返し実行されるOpenGLコマンドの保存,再生時に威力を発揮します.上記の円柱の例では,最初にディスプレイリストに円柱描画のコマンドを保存しておくだけで,描画時にには計算することなく処理することが可能になります.リストを保管するためのメモリを余分に使用しますが,描画処理,演算処理速度は大幅に改善されます.これは映像のリアルタイム性を必要とするアプリケーションには欠かせない手法です.

具体的な実装方法ですが,最初に初期化関数(例えばinit関数)にディスプレイリストを作成するためのコマンドを入れます.

int listCylinder; /* Global 変数 */ void init(void) { void drawCylinder(int); … listCylinder=glGenLists(1); glNewList(listCylinder, GL_COMPILE); drawCylinder(18); glEndList(); … } グローバルで宣言されているint型の整数は,ディスプレイリストの識別子です. ディスプレイリストを呼びたい場合には, glCallList(listCylinder); を実行します.複数のディスプレイリストを定義する場合は,識別子の名前を変えてglGenLists(1)をおこなうか,glGenLists()の引数にディスプレイリストの個数を入れるかのどちらかです.例えばlist=glGenLists(3)とした場合,ディスプレイリストの識別子はそれぞれlist(=list+0),list+1,list+2となります.

ディスプレイリストは同じ描画ループ内で複数回呼ぶこともできます.むしろ複数回利用する描画ルーチンは積極的にディスプレイリスト化すべきでしょう. またディスプレイリストは階層的に定義することも可能です.あらかじめ定義しておいたディスプレイリストを新しいディスプレイリスト定義中に呼ぶこともできます.

ディスプレイリストの欠点は冒頭で述べたように,メモリを消費することです.格納するコマンド,頂点の数が増加するにしたがってメモリを圧迫するようになります.主記憶容量を超すと,大抵のOSでは外部記憶装置などにスワップを開始するため,パフォーマンスの著しい低下を招きます. また,一度格納されたディスプレイリストはその性質上,後から変更することはできません.以下のような状況では,新しい数値(rot=0.0)は無効となります.

ディスプレイリスト格納時 float rot=45.0; glNewList(listCylinder, GL_COMPILE); glRotatef(rot, 1.0, 0.0, 0.0); glutSolidCube(10.0); glEndList(); ディスプレイリスト実行時   rot=0.0; glCallList(listCylinder);


OpenGLの関数名に関するヒント

ここまでOpenGLのコマンドがたくさん出てきましたが,このコマンド,接頭子にはglが入っていることは一番最初に述べました.接尾子に3ffvがつくものをよく見かけたと思います.この接尾子は引数を表す重要な役割を持っています.

例えばglColor3fは色を設定するコマンドですが,この接尾子は3つの浮動小数点(float)型の引数をとることを意味します.glColor4fは4つの引数をとり,4つ目にはアルファ値を設定します.glColor3fvとなる場合は引数がfloat型の配列になります.




引数の型を表す記号を使用頻度の高い順に列挙しておきます.

接尾子データ形式C言語の型
b8ビット整数char
ub8ビット符号なし整数unsigned char
i32ビット整数int
ui32ビット符号なし整数unsigned int
f32ビット浮動小数点数float
d64ビット浮動小数点数double



演習問題



自習問題

  1. ここで勉強したOpenGLの関数は,
    です.赤本やGLUTのマニュアルを利用して,復習しておきましょう.

戻る