ステンシルバッファ

2010年1月7日

ファイル

26-stencil.tar.gz

使い方

マウスでドラッグすると、モデルがぐりぐり回転します。キーボードのキー "s" を押すと、ステンシルバッファのオン・オフが切り替わります。

見所

トーラスの床への映り込みを、床をはさんだ反対側にトーラスを実際に描画することで実現しています。ステンシルバッファなしでは、その舞台裏が見えてしまいます。ステンシルバッファありなら、"偽" トーラスは床のあるところにしか描画されないので、舞台裏は無事に隠されます。

内容

ステンシルバッファを使って、トーラスの床への映り込みを表現します。ステンシルというのは型紙のことで、下図のように、型を切り抜いて、その上から塗りつぶすと、切り抜いた部分にだけ色が塗られるというようなものです。ステンシルバッファはこのように描画領域を制限する場合に使います。

トーラスの床への映り込みは、床を挟んだ反対側に、あたかも鏡に映ったかのように反転させたトーラスを置き、床越しに透かして (ブレンドを使って) 描画することで表現します。鏡像トーラスを描くときには、光源の位置も反転しないと鏡像にならないことに注意します。

	glPushMatrix();
	glScalef(1., -1., 1.);
	glLightfv(GL_LIGHT0, GL_POSITION, light0Position);
	glPushMatrix();
	glTranslatef(0., 0.7, 0.);
	glCullFace(GL_FRONT);
	glutSolidTorus(0.1, 0.5, 20, 20);
	glCullFace(GL_BACK);
	glPopMatrix();
	glPopMatrix()

上では、スケーリングによって光源とトーラスを反転させて描画しています。glCullFace() は、カリングに関する処理です。カリングというのは、視点から見て「裏返し」の面を描画しないようにする処理で、以下のように有効化します。

	glEnable(GL_CULL_FACE);

面の「おもて」と「裏」の区別は、視点から見て頂点が時計回りになっているか反時計回りになっているかで判断します。デフォルトでは反時計回りが「おもて」とみなされますが、設定を変えることもできます。上の鏡像トーラスの場合、スケーリングで上下反転させたときに面がすっかり裏返ってしまうので、描画がおかしくなります。そこで、普通カリングは裏面を描きませんが、鏡像トーラスを描くときにだけ一時的に glCullFace(GL_FRONT) でおもて面を描かず裏面を描く設定にしています。

	glFrontFace(GL_CW);
	glColor4f(0.3, 0.3, 0.5, 1.);
	drawFloor();
	glFrontFace(GL_CCW);

	glDisable(GL_DEPTH_TEST);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glColor4f(0.3, 0.3, 0.3, 0.8);
	drawFloor();
	glDisable(GL_BLEND);

上では床を描画しています。まず床の裏を描画しています。カリングにより裏面は描画しないので、ここでは glFrontFace(GL_CW) で頂点の時計回りをおもてとみなす設定に変更し、床裏を描画してから glFrontFace(GL_CCW) で頂点の反時計回りをおもてとみなす設定に戻しています。その後、デプステストを無効にし、ブレンドを有効にして床のおもて面を描画しています。デプステストを無効にしたら、床裏の方向から見たときに、床の裏面をおもて面が上書きしてしまうように見えますが、カリングを行っているので、裏向きのおもて面は描画されず、問題ありません。

映り込みの処理は以上でよいのですが、このままだと視点の位置によっては映り込み表現用の偽トーラスが見えてしまいます。偽トーラスを隠すために、ステンシルバッファを使います。ステンシルバッファを使うために、ディスプレイモードを以下のように設定します。

	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);

また、クリア時にステンシルバッファをクリアするように指定します。

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

以下がステンシルの設定です。

		glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
		glEnable(GL_STENCIL_TEST);
		glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
		glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
		drawFloor();
		glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
		glStencilFunc(GL_EQUAL, 1, 0xfffffff);
		glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

床を描画させていますが、これによって床の部分が型紙の穴の部分になります。この後描かれる鏡像トーラスは、穴の部分、ステンシルを設定した床の部分だけになります。鏡像トーラスを描画したあと、ステンシルを無効にします。

	if(stencil) glDisable(GL_STENCIL_TEST);

OpenGL プログラミング 目次へ