Ownway.Info
 
HOMESDL + OpenGLでグラフィックス → アルファブレンディングを利用した透過処理
アルファブレンディングを利用した透過処理

以下にこのページで説明するコードを示す。

フルセットには、以下に示す5つのプロジェクトが入っている。

Alphablend1半透明処理に問題がある例。
Alphablend2Alphablend1 の問題を解消した例。

1: 半透明な面を描画するアルファブレンディング

この節で説明するコード

■ Alphablend1/main.cpp

OpenGLを利用して半透明な面を描画するためにはアルファブレンディングという処理を利用しなければならない。 アルファブレンディングとは「既に描かれている絵に対して」、これから描こうとしている絵を混ぜ合わせる処理のことである。 何対何で掛け合わせるかを表す数値のことをアルファ値という。 関連するコードとして、Alphablend1/main.cppの一部を以下に示す。

void drawPlane(float red, float green, float blue, float alpha, float z) {
  // マテリアルを設定する
  Material4(red, green, blue, alpha, 1.0f).set();

  if(1.0f!=alpha) {
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  }

  glBegin(GL_TRIANGLE_STRIP);
  glNormal3f( 0.0f, 0.0f,-1.0f);
  glVertex3f(-1.0f,-1.0f, z);

  glNormal3f( 0.0f, 0.0f,-1.0f);
  glVertex3f( 1.0f,-1.0f, z);

  glNormal3f( 0.0f, 0.0f,-1.0f);
  glVertex3f(-1.0f, 1.0f, z);

  glNormal3f( 0.0f, 0.0f,-1.0f);
  glVertex3f( 1.0f, 1.0f, z);
  glEnd();

  if(1.0f!=alpha) {
    glDisable(GL_BLEND);
  }
}
		

Material4 の第4引数がアルファ値である。 アルファ値は、1.0 が不透明*1で 0.5 が半透明*2、 0.0 が透明*3である。 事例では 1.0 や 0.5 等の切りの良い数値を上げたが、もちろん中間値を設定することもできる。

アルファブレンディングを行う際は、glEnabled(GL_BLEND) によりアルファブレンドを有効にしなければならない。 glBlendFunc は混ぜ合わせ方法の指定である。 アルファブレンディングを有効にすると色の混ぜ合わせを行うという処理が余計に発生するため処理が重くなる *4。 無駄な処理を回避するためアルファブレンディングが不要な不透明の絵を描くときは、アルファブレンディングを無効にしている。

今回のサンプルでは、上記で紹介した drawPlane 関数を使って順番に並んだ3枚の面を出力している。

関連するコードとして、Alphablend1/main.cppの一部を以下に示す。

  drawPlane(1.0f, 0.0f, 0.0f, 0.5f,-1.0f);
  drawPlane(0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
  drawPlane(0.0f, 0.0f, 1.0f, 0.5f, 1.0f);
		

中にある緑の不透明な面を、半透明の赤と青の面が挟む形になっているが、このプログラムには実は問題がある。 下記に出力例を示す。

青の面は半透明になるが、赤の面が半透明にならない。これはなぜか?それは出力順序の問題である。

*1: これから書こうとしている絵で元の絵を完全に上塗りする。
*2: これから書こうとしている絵と元の絵の色を半分ずつ混ぜ合わせる。
*3: これから書こうとしている絵が透明で、元の絵がそのまま残る。
*4: 1 ピクセル毎に混ぜ合わせの処理が発生すると考えれば良いと思う。
2: 出力順序を意識して透過処理を行う

この節で説明するコード

■ Alphablend2/main.cpp
■ Alphablend2/Rectangle3.cpp
■ Alphablend2/Rectangle3.hpp

アルファブレンディングを使って透過処理を行うためには、面の出力順序を意識する必要がある。 アルファブレンディングは、あくまで「既に描かれている絵に対して」色を混ぜ合わせる処理であり、 不透明な面が後に描かれると半透明で描きたいと思っていた面が上塗りされてしまう。 透過処理を正常に行うためには、画面から見て奥の面から順番に面を出力しなければならない。

先ほどの事例では、青(半透明)⇒緑(不透明)⇒赤(半透明)という面の描画順序が固定されていたため、 回転により奥行きに対する順序関係が変わった際に透過処理に問題が生じた。 これを解決するために、表示対象となる面を一度リストに貯めておいてから、奥行き順に並べなおして一括出力するという方法を取る。 それを実現しているのが Alphablend2 サンプルである。

ポイントを下記にまとめる。

射影を自分で計算する必要がある件について、補足する。 本サンプルでは、射影計算をしてくれる2つのクラスを作成し導入している。 関連するコードとして、Alphablend2/Rectangle3.cppの一部を以下に示す。

void Rectangle3::project() {
  Body2WorldProjector projector1;
  projectedVertices_[0]=projector1.project(-1.0f,-1.0f, z_);
  projectedVertices_[1]=projector1.project( 1.0f,-1.0f, z_);
  projectedVertices_[2]=projector1.project(-1.0f, 1.0f, z_);
  projectedVertices_[3]=projector1.project( 1.0f, 1.0f, z_);

  NormalProjector projector2;
  projectedNormal_=projector2.project( 0.0f, 0.0f,-1.0f);

  projectedZ_=(projectedVertices_[0].z+projectedVertices_[1].z+
               projectedVertices_[2].z+projectedVertices_[3].z)/4.0f;
}
		

Body2WorldProjector および NormalProjector クラスがそのクラスである。 Body2WorldProjector は、現在の射影行列を使って物体を中心としたローカル座標のベクトルをワールド座標のベクトルに変換するクラスである。 NormalProjector は、現在の射影行列を使って法線ベクトルを変換するクラスである。

また、その他のソートや一括描画なども補助クラスとして Graphics3 というクラスを作成し、導入している。 関連するコードとして、Alphablend2/Rectangle3.cppの一部を以下に示す。

  Graphics3 graphics;

  graphics.draw(createPlane(1.0f, 0.2f, 0.2f, 0.5f,-1.0f));
  graphics.draw(createPlane(0.2f, 0.2f, 1.0f, 0.5f, 1.0f));

  graphics.drawAll();
  		

Graphics3 は、draw メソッドで該当の面を判断し、不透明だったら即時出力し、半透明であったらリストにその面の情報を保存する。 最後に呼び出した drawAll メソッドで半透明の面をソートし、一括出力している。

問題を解消したプログラムによる出力例を下記に示す。

青の面も赤の面も半透明の出力に成功している。したいのがたったこれだけでも大変だ。

*5: 概ねこの考え方でOKだが、これだけでは正確な表現ができない場合がある。 大きさの全く異なる面が存在する場合や面が交差する場合などは正確な表示はできない。 正確さを犠牲にする分、処理が単純でスピードが出る。 本方式で問題が発生する場合は、事前に面を分割してモデリングする等の処置を検討する。 プログラムで交差面を自動で検出し、切断する方法もあるがプログラムも複雑で処理時間も増えるためあまり良い選択肢ではない。 一度、VB で作ったことがある(笑)なぜに VB!?みたいな(笑)
戻る: SDL + OpenGLでグラフィックス