円柱を描画したい場合には,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のコマンドがたくさん出てきましたが,このコマンド,接頭子には
glが入っていることは一番最初に述べました.接尾子に
3fや
fvがつくものをよく見かけたと思います.この接尾子は引数を表す重要な役割を持っています.
例えば
glColor3fは色を設定するコマンドですが,この接尾子は3つの浮動小数点(float)型の引数をとることを意味します.
glColor4fは4つの引数をとり,4つ目にはアルファ値を設定します.
glColor3fvとなる場合は引数がfloat型の配列になります.

引数の型を表す記号を使用頻度の高い順に列挙しておきます.
接尾子 | データ形式 | C言語の型 |
b | 8ビット整数 | char |
ub | 8ビット符号なし整数 | unsigned char |
i | 32ビット整数 | int |
ui | 32ビット符号なし整数 | unsigned int |
f | 32ビット浮動小数点数 | float |
d | 64ビット浮動小数点数 | double |
- 混合処理について:前回作成したティーポットをまわすプログラムを使用し,ティーポットを半透明にしてみましょう.
-
混合処理にあたり,まずターゲットとソースのアルファ値をどのように加減算するかの設定(混合係数)が必要です.この設定にはglBlendFunc()という関数を使用します.
このコマンドでは,第一引数にソースの混合比,第二引数にターゲットの混合比を設定します.例えば
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
と設定すると,ソースの混合係数がGL_SRC_ALPHA(=ソースのアルファ値),ターゲットの混合係数がGL_ONE(=1.0)となります.この設定では,ターゲットのアルファ値の混合比が1ですので,ターゲットはそのままにソースが上書き混合されることを意味します.(上の例)
また,
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
とすると,ソースの混合係数がアルファ値,ターゲットの混合係数がGL_ONE_MINUS_SRC_ALPHA(=1-ソースのアルファ値)となります.この設定では,ターゲットのアルファ値の混合比が1-αですので,ターゲットのアルファ値は1-α倍になります.もともとターゲットが透明であればさらに透明になります.(上の例)
他に引数の設定がたくさんありますが,大抵の混合は上記2つの設定で可能でしょう.詳しくは赤本で見て下さい.
さて,実際に混合処理をさせたい物体には,物体の描画コマンド直前で
glEnable(GL_BLEND);
と混合処理を有効にします.物体を描画する際には,色の設定で
glColor4f(1.0, 1.0, 1.0, 0.2);
というように第4引数でアルファ値を設定するのを忘れずに.最後に
glDisable(GL_BLEND);
で混合処理を無効にします.挟み込まれた部分だけ混合処理がおこなわれます.
- ティーポットの回転プログラムは第3回のおまけを使用します.このティーポットを半透明にするためには,上記のglBlendFunc()を初期化関数init()に加え,glColor4fで物体に色とアルファ値を設定し,glEnable(GL_BLEND),glDisable(GL_BLEND)で挟み込みます.

-
これを見ると,半透明かどうかわかりづらいですね.面の向こう側が透けたり透けなかったりします.この現象はデブスバッファを無効にすることによって解決します.隠面消去を無効にするには,
glDisable(GL_DEPTH_TEST);
隠面消去を有効にするには,
glEnable(GL_DEPTH_TEST);
です.隠面消去を無効にしてからティーポットを描画し,隠面消去を有効にすることを忘れずに.
-
照光処理で半透明をおこなうには,glColor4f()を使うのではなく,物体の属性設定でアルファ値を設定します.前回の演習内容を参考に,照光処理を行った場合の半透明をやってみましょう.

- ディスプレイリストについて
- 講義資料に出てきた関数を参考に,ティーポットの代わりに円柱を表示してみましょう.円柱の関数を現在のプログラムの最初のほうにコピーアンドペーストして,glutTeapot()の代わりにdrawCylinder()を呼んでみます.#include を忘れずに.混合処理関係は取り除いてもかまいません.
- 円柱が点にしか見えず寂しいので拡大しましょう.拡大縮小の関数はglScalef(x,y,z)です.(x,y,z)には倍率が入ります.例えば,
glScalef(30.0, 30.0, 30.0);
drawCylinder(18);
とすれば30倍の大きさになります.
さて拡大縮小をすると単純に変形しますが,照光処理をしていると色が変になってしまいます(見えないこともある).プログラムとしては正しいので,なかなかバグを発見できません*21.
実は拡大縮小をすると,法線ベクトルも拡大されてしまい,照光処理がおかしくなってしまいます.法線ベクトルの長さが1に正規化されていないとこんな現象になります.この場合は次のようなおまじないで回避します.
glScalef(30.0, 30.0, 30.0);
glEnable(GL_NORMALIZE);
drawCylinder(18);
glDisable(GL_NORMALIZE);
このおまじないは,法線の正規化を強制するためのものです.

- さて,drawCylinder()関数の引数は円周の分割数です.分割数が高いほどきれいな円を描くことができます.大小を試してみましょう.
分割数を10000にしてみましょう.画面がもたつくと思います.分割数が10000だと,概算でおよそ40000ポリゴンになりますね.これほどポリゴン数が多くなると画面の更新速度が遅くなってきます.
ところで画面の更新頻度はフレームレート(frame rate)と言い,一秒間に何枚描画できるか(fps: Frame Per SecondまたはHz)という単位で表すのが一般的です.人間の目には残像特性があり,10[fps]以上でも動画として認識できますが,それ以下になると「ぱらぱらマンガ」にしかみえません.
-
プログラムでFPSを計測する方法について説明します.以下にプログラムのポイントを掲載しますので,自分で実装して下さい.
注: すでに出てきたところは端折ってあります
#include
#include
.....
unsigned long count=0; /* 表示したフレーム数を数える */
unsigned int start_time, end_time; /* 開始時間と終了時間を保管する */
void keyboard(unsigned char key, int x, int y)
{
switch(key){
case 'q':
case 'Q': /* 終了キーが押されたら */
end_time=GetTickCount(); /* 終了時刻を得る */
printf(" %d [frames] 表示するのに", count);
printf(" %.2f [sec] 要した.\n", (end_time-start_time)/1000.0);
/* 開始時刻,終了時刻よりfpsを求める */
printf(" Frame Rate %2.2f[fps]\n",
(float)count*1000.0/(end_time-start_time));
exit(1);
}
}
void drawCylinder(int div)
{
.....
}
void display(void)
{
count++; /* 描画フレーム数をインクリメント */
.....
}
void init(void)
{
.....
}
void reshape(int w, int h) /*画面サイズ変更時に呼ばれる*/
{
.....
}
int main(int argc, char** argv)
{
.....
start_time=GetTickCount(); /* 開始時間を得る */
glutKeyboardFunc(keyboard);
glutMainLoop(); /*描画開始*/
return 0;
}
GetTickCount()は0.001秒単位で現在時間を得る関数です.これを利用して,プログラム開始時と終了時の時間差を測定し,フレームレートを算出しています.
うまく実装できれば,Q or qキーを押したとき次のような画面になります.

-
円の分割数をいろいろ変えてみて,フレームレートを見てみましょう.分割数(ポリゴン数)を増やすとフレームレートが低下することが確認できます.
-
ディスプレイリストを使ってみましょう.
講義資料の通り,
int listCylinder; /* Global 変数 */
というようなリストの識別子をグローバル変数として作成します.初期化関数では,
listCylinder=glGenLists(1);
glNewList(listCylinder, GL_COMPILE);
drawCylinder(100);
glEndList();
とすれば,listCylinderにdrayCylinder(18)という描画コマンドを定義できます.今回の場合は,
listCylinder=glGenLists(1);
glNewList(listCylinder, GL_COMPILE);
glScalef(30.0, 30.0, 30.0);
glEnable(GL_NORMALIZE);
drawCylinder(100);
glDisable(GL_NORMALIZE);
glEndList();
とするとよいでしょう.
実際にリストを呼び出したい場合には
glCallList(listCylinder);
とします.
- 追加:ディスプレイリストによってパフォーマンスの改善はみられないこともあります.計算機環境によって効果が異なるようです.
- ここで勉強したOpenGLの関数は,
glBlendFunc()
glScalef()
glColor4f()
glMaterialfv()
です.赤本やGLUTのマニュアルを利用して,復習しておきましょう.
戻る