RSS2.0

LWJGL で立方体を半透明に表示してみる

今回のテーマはモデルの半透明表示です。前回作成した立方体を半透明に表示してみたいと思います。半透明表示には、OpenGL のアルファブレンド(色のアルファ合成)と呼ばれる機能を使っていきます。
透過表示はゲームのエフェクトでもよく使われる演出効果ですね。メッセージウインドウを半透明にして背景画像をうっすら見せたりとか!
8db1cecd08d12045b008b0c05dc83dc963c2.png
こんな感じに仕上げて行きたいと思います。
左から透明→不透明と透過率を上げていったモデルをいくつか表示し、フェードアウトしていくようにしてみました。

ついでに、左の立方体ほどスケールを縮小するようにし、角度も徐々に傾くように補正しています。この辺については前回扱った内容になります。今回のように、同じモデルを少しずつ変化させつつ使いまわしたいという事は多いので、モデルの拡大や移動だけを後からできるようにしておくと便利ですね。

ソースコード

今回新しく記載するクラスは、前回までのプロジェクトに test.monolith.v2 というパッケージを作って、その中に置いてください。
また、立方体の回転の時に作った以下のクラスも参照しているので、test.monolith.v1 パッケージの中に残しておいてくださいねー。
・test.monolith.v1.Direction
・test.monolith.v1.Point

TransparentMonolith.java
package test.monolith.v2;

import static org.lwjgl.opengl.GL11.GL_BACK;
import static org.lwjgl.opengl.GL11.GL_BLEND;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
import static org.lwjgl.opengl.GL11.GL_MODELVIEW;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_PROJECTION;
import static org.lwjgl.opengl.GL11.GL_QUADS;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.glBegin;
import static org.lwjgl.opengl.GL11.glBlendFunc;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glCullFace;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glEnd;
import static org.lwjgl.opengl.GL11.glLoadIdentity;
import static org.lwjgl.opengl.GL11.glMatrixMode;
import static org.lwjgl.opengl.GL11.glOrtho;
import static org.lwjgl.opengl.GL11.glPopMatrix;
import static org.lwjgl.opengl.GL11.glPushMatrix;
import static org.lwjgl.opengl.GL11.glRotatef;
import static org.lwjgl.opengl.GL11.glScalef;
import static org.lwjgl.opengl.GL11.glTranslatef;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;

import test.monolith.v1.Direction;

public class TransparentMonolith {
	private int			width = 300;
	private int			height = 200;
	private int		 depth = 200;

	private float			xAngle = 0;
	private float			yAngle = 0;
	private Direction		input = null;

	public void start() {
		try {
			//	ウインドウを生成する
			Display.setDisplayMode(new DisplayMode(width, height));
			Display.setTitle("Transparent Monolith");
			Display.create();
		} catch(LWJGLException e) {
			e.printStackTrace();
			return;
		}
		
		try {
			//	初期設定

			//	ポリゴンの片面(表 or 裏)表示を有効にする
			glEnable(GL_CULL_FACE);
			//	ポリゴンの表示面を表(裏を表示しない)のみに設定する
			glCullFace(GL_BACK);

			//	アルファブレンドを有効化する
			glEnable(GL_BLEND);
			//	アルファブレンドの係数を設定する
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

			//	カメラ用行列の設定変更を指示する
			glMatrixMode(GL_PROJECTION);
			//	今まで設定してきた行列を単位行列に置き換え、初期化する
			glLoadIdentity();
			//	視体積(目に見える範囲)を定義する
			glOrtho(0, width, 0, height, 0, depth);

			//	物体用行列の設定変更を指示する
			glMatrixMode(GL_MODELVIEW);
			
			while (!Display.isCloseRequested()) {
				//	オフスクリーンを初期化する
				glClear(GL_COLOR_BUFFER_BIT);

				//	キー入力を処理する
				update();
				
				//	オフスクリーンに描画する
				render();

				//	オフスクリーンをスクリーンに反映する
				Display.update();
			}
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			Display.destroy();
		}
	}

	private void update() {
		input = Direction.getPressing();
		
		if (input != null) {
			xAngle = input.rotateXAngle(xAngle);
			yAngle = input.rotateYAngle(yAngle);
		}
	}

	private void render() {
		//	現在設定されている行列を単位行列に置き換え、初期化する
		glLoadIdentity();

		//	画面中央の座標を (0, 0, 0) とするよう座標系を移動する
		glTranslatef(width / 2f, height / 2f, depth / -2f);

		int	count = 7;
		for (int i = 0; i < count; i++) {
			//	現在設定されている行列のコピーを行列スタックにプッシュする。
			glPushMatrix();

			//	モデルの位置を少しずつ X 軸方向にずらす
			float	diffX = (i - (count / 2f)) * 30f;
			glTranslatef(diffX, 0, 0);

			//	モデルのスケール、角度、透過率を少しずつずらす
			float	scale = 20f - (count - 1 - i) * 2f;
			float	angle = (count - 1 - i) * 10f;
			float	alpha = 1f / (count - 1) * i;
			renderMonolith(scale, angle, alpha);

			//	行列スタックから行列をポップし、現在設定されている行列に置き換える
			glPopMatrix();
		}
	}
	
	private void renderMonolith(float scale, float angle, float alpha) {
		//	回転する角度を引数 angle 値で補正する 
		float	xAngle = this.xAngle + angle;
		float	yAngle = this.yAngle + angle;

		if (360f < xAngle) xAngle %= 360f;
		if (360f < yAngle) yAngle %= 360f;
		
		glRotatef(xAngle, 0, 1, 0);	//	y 軸を中心に xAngle 度回転させる
		glRotatef(yAngle, 1, 0, 0);	//	x 軸を中心に yAngle 度回転させる

		//	座標のスケールを指定する
		//	ここで指定した x, y, z 毎の係数が、次に指定する座標にそれぞれ掛けられる
		glScalef(scale, scale, scale);

		glBegin(GL_QUADS);

		for (Face face: Face.values()) {
			face.draw(alpha);
		}
		
		glEnd();
	}

	public static void main(String[] args) {
		new TransparentMonolith().start();
	}

}

Color.java
package test.monolith.v2;

import static org.lwjgl.opengl.GL11.glColor3f;
import static org.lwjgl.opengl.GL11.glColor4f;

public enum Color {
	RED(1f, 0.6f, 0.6f),
	DARK_RED(1f, 0.5f, 0.5f),
	BLUE(0.6f, 0.6f, 1f),
	DARK_BLUE(0.5f, 0.5f, 1f),
	GREEN(0.6f, 1f, 0.6f),
	DARK_GREEN(0.5f, 1f, 0.5f),
	YELLOW(1f, 1f, 0.6f),
	DARK_YELLOW(1f, 1f, 0.5f);

	private final float	r;
	private final float	g;
	private final float	b;

	private Color(float r, float g, float b) {
		this.r = r;
		this.g = g;
		this.b = b;
	}
	
	public void set() {
		glColor3f(r, g, b);
	}

	public void set(float alpha) {
		//	RGBA 値で色情報と透過率を同時に指定する
		glColor4f(r, g, b, alpha);
	}
}

Face.java
package test.monolith.v2;

import static test.monolith.v1.Point.P_BACK_BOTTOM_LEFT;
import static test.monolith.v1.Point.P_BACK_BOTTOM_RIGHT;
import static test.monolith.v1.Point.P_BACK_TOP_LEFT;
import static test.monolith.v1.Point.P_BACK_TOP_RIGHT;
import static test.monolith.v1.Point.P_FRONT_BOTTOM_LEFT;
import static test.monolith.v1.Point.P_FRONT_BOTTOM_RIGHT;
import static test.monolith.v1.Point.P_FRONT_TOP_LEFT;
import static test.monolith.v1.Point.P_FRONT_TOP_RIGHT;
import static test.monolith.v2.Color.BLUE;
import static test.monolith.v2.Color.DARK_BLUE;
import static test.monolith.v2.Color.DARK_RED;
import static test.monolith.v2.Color.GREEN;
import static test.monolith.v2.Color.RED;
import test.monolith.v1.Point;

public enum Face {
	F_FRONT(new Point[] {
			P_FRONT_TOP_RIGHT,
			P_FRONT_TOP_LEFT,
			P_FRONT_BOTTOM_LEFT,
			P_FRONT_BOTTOM_RIGHT
	}, BLUE, RED),
	F_BACK(new Point[] {
			P_BACK_TOP_LEFT,
			P_BACK_TOP_RIGHT,
			P_BACK_BOTTOM_RIGHT,
			P_BACK_BOTTOM_LEFT,
	}, BLUE, RED),
	F_LEFT(new Point[] {
			P_FRONT_TOP_LEFT,
			P_BACK_TOP_LEFT,
			P_BACK_BOTTOM_LEFT,
			P_FRONT_BOTTOM_LEFT
	}, BLUE, GREEN),
	F_RIGHT(new Point[] {
			P_BACK_TOP_RIGHT,
			P_FRONT_TOP_RIGHT,
			P_FRONT_BOTTOM_RIGHT,
			P_BACK_BOTTOM_RIGHT
	}, BLUE, GREEN),
	F_TOP(new Point[] {
			P_BACK_TOP_RIGHT,
			P_BACK_TOP_LEFT,
			P_FRONT_TOP_LEFT,
			P_FRONT_TOP_RIGHT			
	}, DARK_BLUE, DARK_BLUE),
	F_BOTTOM(new Point[] {
			P_FRONT_BOTTOM_RIGHT,
			P_FRONT_BOTTOM_LEFT,
			P_BACK_BOTTOM_LEFT,
			P_BACK_BOTTOM_RIGHT
	}, DARK_RED, DARK_RED);

	private final Point[]  points;
	private final Color	color1;
	private final Color	color2;
	
	private Face(Point[] points, Color color1, Color color2) {
		this.points = points;
		this.color1 = color1;
		this.color2 = color2;
	}

	public void draw() {
		color1.set();
		points[0].point();
		points[1].point();

		color2.set();
		points[2].point();
		points[3].point();
	}

	public void draw(float alpha) {
		color1.set(alpha);
		points[0].point();
		points[1].point();

		color2.set(alpha);
		points[2].point();
		points[3].point();
	}

}

半透明に表示する

 ある色に別の色を半透明で重ね合わせると、実際に目に見えるのは、2つの色の中間色になります。
b9d43bad00b81049840bf830446581f1277d.png
 コーヒーとミルクを混ぜるとカフェオレの色になるのと同じ感じです。Photoshop などの画像処理ソフトを使ったことがある方は、別の色のレイヤーを不透明度 50% で重ねた感じをイメージしてください。

 中間色を作る際に、2つの色をどのくらいの割合で混ぜ合わせるかという比率を透過率といいます。先程の例で言うと、カップ半分のコーヒーとカップ半分のミルクを混ぜる場合の透過率は 50 % ですね。この透過率を小さくしたり大きくしたりすることで、どの程度モデルをすけすけにするかを調整することができます。

RGBA 値で透過率を指定する

 透過率は RGBA 値で表すことができます。今まで LWJGL の glColor3f() で扱ってきた RGB 値は、Red、Green、Blue を表す色の3原色ですが、これに透過率(Alpha 値)をあわせたものが RGBA 値になります。これは OpenGL に限った話ではなく、jpg や png などの一般的な画像でも同じです。

 LWJGL では glColor4f() メソッドで RGBA 値を指定することができます(いつも通り引数の型などによって亜種メソッドあり)。glColor4f() に指定するアルファ値は、他の色情報と同じく float 型で 0f ~ 1f で指定します。
public void set(float alpha) {
	//	RGBA 値で色情報と透過率(アルファ値)を同時に指定する
	glColor4f(r, g, b, alpha);
}
 ちなみに、glColor3f() では透過率は 1f(不透明)として描画されます。

アルファブレンドの有効化

OpenGL ではアルファブレンド機能を有効にしないとアルファ値は無視されます。LWJGL の glEnable() メソッドに、定数 GL_BLEND を渡してやってください。
//	アルファブレンドを有効化する
glEnable(GL_BLEND);
サンプルプログラムの、OpenGL の初期設定のこれですね。

アルファブレンドの設定

冒頭で少し触れましたが、OpenGL のアルファブレンドでは色を混ぜ合わせる合成方法がいろいろあり、今回の半透明表示はそのうちの1つになります。なのでアルファブレンドを有効化する時には、アルファブレンドをどのように行うかという数式(の係数)を設定する必要があります。

ひとまず、RGB 値で中間色の求め方について考えてみましょう。
赤(255, 0, 0)を透過率 50% で緑(0, 255, 0)に重ねると、中間色である暗めの黄色(127, 127, 0)になります。
もう少し詳しく、それぞれの色の R 値だけについて見てみると、赤(255)の 50% + 緑(0)の 50% で暗めの黄色(127)になります。これを RGB の3色についてそれぞれ計算すれば中間色が求められます。

さて、数式にしてみるとこんな感じです。
中間色 = (重ねる色 * 重ねる色の透過率) + (元の色 * (1 - 重ねる色の透過率))
※ ここで透過率は 0.0 ~ 1.0

この数式を OpenGL でプログラミングすると以下のようになります。
//	アルファブレンドの合成方法の係数を設定する
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
アルファブレンドの設定は、LWJGL の glBlendFunc() メソッドで行います。
OpenGL ではまさに上の数式が使われていれ、glBlendFunc() の引数は以下の位置に当てはまります。
中間色 = (重ねる色 * glBlendFunc() の第1引数) + (元の色 * glBlendFunc() の第2引数)
第1引数に渡している GL_SRC_ALPHA は、『重ねる色の透過率』を表す定数です。
第2引数に渡している GL_ONE_MINUS_SRC_ALPHA は、『1 - 重ねる色の透過率』を表す定数です。
glBlendFunc() メソッドでは重ねる色と元の色に掛ける係数をそれぞれ引数として渡してやります。
定数名の ONE_MINUS_…なんていうのはそのまま数式の 1 - …のことですね。
SRC は source color、つまり重ねる色のことです。反対に GL_DST_ALPHA なんていう定数もあって、その DST は destination color、重ね先の色のことですね。

これは LWJGL 独自の設定方法ではなく、OpenGL の API をそのままなぞった物になります。なので、OpenGL を使っている限りは java だろうと C 言語だろうと同じように設定することになります。

おっわり~

今回はテーマをアルファブレンドに絞ってコンパクトにまとめてみたつもりなのですが、いかがだったでしょうか。
(や、今までも別にいろいろ詰め込もうとしていたわけではないです…。必要なことを説明していったら盛り盛りになってただけですよ!)

glBlendFunc() は引数の指定が難しいと思いますが、逆に言うとアルファブレンドの合成方法を細かく指定できるということなので、ぜひ原理を理解してほしいと思います。
ぶっちゃけゲームで使うようなアルファブレンドのパターンは数種類くらいしかないのですが、なんでそんな設定をするのかというのを理解しておくと、デバッグ作業でとても役立ちます。もちろん使いこなせばすごいエフェクト効果も出せますしね!
  LWJGL  コメント (0)  2012/03/11 18:28:58


公開範囲:
プロフィール HN: ももかん
ゲーム作ったり雑談書いたり・・・していた時期が私にもありました。
カレンダー
<<2019, 5>>
2829301234
567891011
12131415161718
19202122232425
2627282930311