Reflections Using Stencil Buffer [Solved]

Started by
9 comments, last by d h k 18 years, 7 months ago
Hey there, I played around with NeHe's tutorial #26 which introduces the stencil buffer to create some floor reflections. I then decided to implement this effect into my own project... I have an object class set up which stores all data of all objects in the scene ( for example it stores position, scale, texture... ) and I added a new bool attribute called mirror, which is true when the object is a mirror ( when it reflects over normal objects ), if it is false, then the object is just a standard texture-mapped object in the world... The interesting part ( which is also the part that gives me the trouble ) is the object::draw ( )-function... Take a look at it for yourself:

void object::draw ( )
// draws the object to the screen
{
	// clipping plane
	double clipping_plane[] = { 0.0f, -1.0f, 0.0f, 0.0f };

	if ( mirror == true )
	// masking mirror objects
	{
		// activate masking mode for stencil buffer
		glColorMask ( 0, 0, 0, 0 );
		glStencilFunc ( GL_ALWAYS, 1, 1 );
		glStencilOp ( GL_KEEP, GL_KEEP, GL_REPLACE );

		glEnable ( GL_STENCIL_TEST );
		glDisable ( GL_DEPTH_TEST );
		glDisable ( GL_CLIP_PLANE0 );
		glDisable ( GL_BLEND );

		// mask the object
		/* ... */
	}	

	if ( mirror == false )
	// drawing reflections of normal objects
	{
		glColorMask ( 1, 1, 1, 1 );
		glStencilFunc ( GL_EQUAL, 1, 1 );
		glStencilOp ( GL_KEEP, GL_KEEP, GL_KEEP );
		glClipPlane ( GL_CLIP_PLANE0, clipping_plane );

		glEnable ( GL_DEPTH_TEST );
		glEnable ( GL_CLIP_PLANE0 );
		glEnable ( GL_STENCIL_TEST );
		glDisable ( GL_BLEND );

		glPushMatrix ( );

		// mirror around y-axis
		glScalef ( 1.0f, -1.0f, 1.0f );
		
		// draw the object
		/* ... */

		glPopMatrix ( );
	}
		
	if ( mirror == true )
	// drawing blended mirror objects
	{
		// activate blended drawing for floor now
		glColor4f ( 1.0f, 1.0f, 1.0f, 0.8f );
		glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

		glDisable ( GL_CLIP_PLANE0 );
		glDisable ( GL_STENCIL_TEST );
		glEnable ( GL_DEPTH_TEST );
		glEnable ( GL_BLEND );
		
		// draw the object
		/* ... */

	}

	if ( mirror == false )
	// drawing normal objects
	{
		glDisable ( GL_BLEND );
		glDisable ( GL_CLIP_PLANE0 );
		glEnable ( GL_DEPTH_TEST );
		glDisable ( GL_STENCIL_TEST );

		// draw the object
		/* ... */
	}
		
	// flush the pipeline
	glFlush ( );
}




Normal objects are drawn as they should, the mirror objects are correctly masked using the stencil buffer ( thus reflecting the normal objects correctly ), the only problem is that the mirror objects are not drawn blended... Do you see anything wrong with my code? [Edited by - d h k on September 9, 2005 8:08:45 AM]
Advertisement
I think I understood your question, but I'm not entirely sure about this.

After you've masked the mirror objects in the stencil buffer & drawn the reflections, are you setting glColorMask back to (1,1,1,1) before drawing the mirror objects a final time? You are doing so for non blended objects but need to do it before you draw the mirror to the screen for the final time.

The other issue with blending is that if you draw a blended mirror object first & then draw other objects underneath it, they will not be visible where the blended object covers the screen. I don't know how you are drawing the entire scene but typically blended objects need to be drawn last, or at least after everything else that is affected by it.
"I must not fear. Fear is the mindkiller. Fear is the little death that brings total obliteration. I will face my fear. I will permit it to pass over me and through me. And when it has gone past me I will turn to see fear's path. Where the fear has gone there will be nothing. Only I will remain." ~Frank Herbert, DuneMy slice of the web
Thanks for your comments, you definately put me back on the right track.

Though I'll need to figure some kind of sorting out then in order to always draw mirror objects last, I guess?
Yep. It's a pain. Sorting by z depth is one way, (you don't need to sort them based on camera position though) but if it's a simple project or the blended objects only affect a small number of others it's often easier to specify the ordering by hand. For large worlds with a lot to do, sorting will make more sense.
"I must not fear. Fear is the mindkiller. Fear is the little death that brings total obliteration. I will face my fear. I will permit it to pass over me and through me. And when it has gone past me I will turn to see fear's path. Where the fear has gone there will be nothing. Only I will remain." ~Frank Herbert, DuneMy slice of the web
Allright, now it draws the blended floor correctly, but the whole reflection is not working at all ( anymore ). I suppose the reason for that is either (1) the mirror objects are not masked properly or (2) that my normal, not-mirror objects are not drawn properly.

This is my original, unchanged version of void object::draw ( void ):

void object::draw ( )// draws the object to the screen{	// clipping plane	double clipping_plane[] = { 0.0f,-1.0f, 0.0f, 0.0f };	update_position ( );	if ( mirror == true )	// masking mirror objects	{		// set up render modes		glColorMask ( 0, 0, 0, 0 );		glStencilFunc ( GL_ALWAYS, 1, 1 );		glStencilOp ( GL_KEEP, GL_KEEP, GL_REPLACE );		glEnable ( GL_STENCIL_TEST );		glDisable ( GL_DEPTH_TEST );		glDisable ( GL_CLIP_PLANE0 );		glDisable ( GL_BLEND );		// move and rotate to place the cube		glTranslatef ( position.x, position.y, position.z );		glRotatef ( rotation.x, 1.0f, 0.0f, 0.0f );		glRotatef ( rotation.y, 0.0f, 1.0f, 0.0f );		glRotatef ( rotation.z, 0.0f, 0.0f, 1.0f );		// activate the object's texture		surface_texture.activate ( );		if ( shape == SHAPE_BOX )		// only if the object is a box			draw_cube ( size );		if ( shape == SHAPE_FLOOR )		// only if the object is a floor			draw_floor ( size );	}		if ( mirror == false )	// drawing reflections of normal objects	{		// set up render modes		glColorMask ( 1, 1, 1, 1 );		glStencilFunc ( GL_EQUAL, 1, 1 );		glStencilOp ( GL_KEEP, GL_KEEP, GL_KEEP );		glClipPlane ( GL_CLIP_PLANE0, clipping_plane );		glEnable ( GL_DEPTH_TEST );		glEnable ( GL_CLIP_PLANE0 );		glEnable ( GL_STENCIL_TEST );		glDisable ( GL_BLEND );		// push the matrix		glPushMatrix ( );		// mirror around y-axis		glScalef ( 1.0f, -1.0f, 1.0f );		// move and rotate to place the cube		glTranslatef ( position.x, position.y, position.z );		glRotatef ( rotation.x, 1.0f, 0.0f, 0.0f );		glRotatef ( rotation.y, 0.0f, 1.0f, 0.0f );		glRotatef ( rotation.z, 0.0f, 0.0f, 1.0f );		// activate the object's texture		surface_texture.activate ( );		if ( shape == SHAPE_BOX )		// only if the object is a box			draw_cube ( size );		if ( shape == SHAPE_FLOOR )		// only if the object is a floor			draw_floor ( size );		// pop the matrix		glPopMatrix ( );	}			if ( mirror == true )	// drawing blended mirror objects	{		// set up render modes		glColorMask ( 1, 1, 1, 1 );		glColor4f ( 1.0f, 1.0f, 1.0f, 0.8f );		glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );		glDisable ( GL_CLIP_PLANE0 );		glDisable ( GL_STENCIL_TEST );		glEnable ( GL_DEPTH_TEST );		glEnable ( GL_BLEND );		// move and rotate to place the cube		glTranslatef ( position.x, position.y, position.z );		glRotatef ( rotation.x, 1.0f, 0.0f, 0.0f );		glRotatef ( rotation.y, 0.0f, 1.0f, 0.0f );		glRotatef ( rotation.z, 0.0f, 0.0f, 1.0f );		// activate the object's texture		surface_texture.activate ( );		if ( shape == SHAPE_BOX )		// only if the object is a box			draw_cube ( size );		if ( shape == SHAPE_FLOOR )		// only if the object is a floor			draw_floor ( size );	}	if ( mirror == false )	// drawing normal objects	{		// set up render modes		glDisable ( GL_BLEND );		glDisable ( GL_CLIP_PLANE0 );		glEnable ( GL_DEPTH_TEST );		glDisable ( GL_STENCIL_TEST );		// move and rotate to place the cube		glTranslatef ( position.x, position.y, position.z );		glRotatef ( rotation.x, 1.0f, 0.0f, 0.0f );		glRotatef ( rotation.y, 0.0f, 1.0f, 0.0f );		glRotatef ( rotation.z, 0.0f, 0.0f, 1.0f );		// activate the texture		surface_texture.activate ( );		if ( shape == SHAPE_BOX )		// only if the object is a box			draw_cube ( size );		if ( shape == SHAPE_FLOOR )		// only if the object is a floor			draw_floor ( size );	}			// flush the pipeline	glFlush ( );}


For additional information: void object::update_position ( void ) simply updates position values for the objects, so that you can move around on keypress. void draw_cube ( float size ) as well as void draw_floor ( float size ) start to enter points into the OpenGL pipeline.

I draw a normal, not-mirror cube first and then a mirror floor quad every frame. I move the cube and the camera around but - as I said - the reflections aren't showing up at all...
Sorry for the double post, but this small problem really keeps me away from proceeding further in the developing process - and it's frustrating as hell... ;)

So I am bumping this and adding the newest version of my function as well:

void object::draw ( )// draws the object to the screen{	// clipping plane	double clipping_plane[] = { 0.0f, -1.0f, 0.0f, 0.0f };	// update acceleration values	update_position ( );	// move and rotate to place the object	glTranslatef ( position.x, position.y, position.z );	glRotatef ( rotation.x, 1.0f, 0.0f, 0.0f );	glRotatef ( rotation.y, 0.0f, 1.0f, 0.0f );	glRotatef ( rotation.z, 0.0f, 0.0f, 1.0f );	if ( mirror == true )	// masking mirror objects	{		// set up render modes		glColorMask ( 0, 0, 0, 0 );		glColor4f ( 1.0f, 1.0f, 1.0f, 1.0f );		glStencilFunc ( GL_ALWAYS, 1, 1 );		glStencilOp ( GL_KEEP, GL_KEEP, GL_REPLACE );		glEnable ( GL_STENCIL_TEST );		glDisable ( GL_DEPTH_TEST );		glDisable ( GL_CLIP_PLANE0 );		glDisable ( GL_BLEND );		if ( shape == SHAPE_BOX )		// only if the object is a box			draw_cube ( size );		if ( shape == SHAPE_FLOOR )		// only if the object is a floor			draw_floor ( size );	}		if ( mirror == false )	// drawing reflections of normal objects	{		// set up render modes		glColorMask ( 1, 1, 1, 1 );		glColor4f ( 1.0f, 1.0f, 1.0f, 1.0f );		glStencilFunc ( GL_EQUAL, 1, 1 );		glStencilOp ( GL_KEEP, GL_KEEP, GL_KEEP );		glClipPlane ( GL_CLIP_PLANE0, clipping_plane );		glEnable ( GL_DEPTH_TEST );		glEnable ( GL_CLIP_PLANE0 );		glEnable ( GL_STENCIL_TEST );		glDisable ( GL_BLEND );		// push the matrix		glPushMatrix ( );		// mirror around y-axis		glScalef ( 1.0f, -1.0f, 1.0f );		if ( shape == SHAPE_BOX )		// only if the object is a box			draw_cube ( size );		if ( shape == SHAPE_FLOOR )		// only if the object is a floor			draw_floor ( size );		// pop the matrix		glPopMatrix ( );	}			if ( mirror == true )	// drawing blended mirror objects	{		// set up render modes		glColorMask ( 1, 1, 1, 1 );		glColor4f ( 1.0f, 1.0f, 1.0f, 0.8f );		glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );		glDisable ( GL_CLIP_PLANE0 );		glDisable ( GL_STENCIL_TEST );		glEnable ( GL_DEPTH_TEST );		glEnable ( GL_BLEND );		if ( shape == SHAPE_BOX )		// only if the object is a box			draw_cube ( size );		if ( shape == SHAPE_FLOOR )		// only if the object is a floor			draw_floor ( size );	}	if ( mirror == false )	// drawing normal objects	{		// set up render modes		glColorMask ( 1, 1, 1, 1 );		glColor4f ( 1.0f, 1.0f, 1.0f, 1.0f );		glDisable ( GL_BLEND );		glDisable ( GL_CLIP_PLANE0 );		glEnable ( GL_DEPTH_TEST );		glDisable ( GL_STENCIL_TEST );		if ( shape == SHAPE_BOX )		// only if the object is a box			draw_cube ( size );		if ( shape == SHAPE_FLOOR )		// only if the object is a floor			draw_floor ( size );	}			// flush the pipeline	glFlush ( );}


Everything is drawn correctly except there are NO reflections at all. Either it is wrong masking or the reflections of the other objects won't be drawn properly. Please help me out!
Hey,

I take it you have a main draw function which is doing things like

cubeObject->draw();
floorObject->draw();

etc?

So if you draw the cube and then the floor, here's what's actually happening:

// Draw the cube (mirror == false)		// set up render modes		glColorMask ( 1, 1, 1, 1 );		glColor4f ( 1.0f, 1.0f, 1.0f, 1.0f );		glStencilFunc ( GL_EQUAL, 1, 1 );		glStencilOp ( GL_KEEP, GL_KEEP, GL_KEEP );		glClipPlane ( GL_CLIP_PLANE0, clipping_plane );		glEnable ( GL_DEPTH_TEST );		glEnable ( GL_CLIP_PLANE0 );		glEnable ( GL_STENCIL_TEST );		glDisable ( GL_BLEND );		// push the matrix		glPushMatrix ( );		// mirror around y-axis		glScalef ( 1.0f, -1.0f, 1.0f );		if ( shape == SHAPE_BOX )		// only if the object is a box			draw_cube ( size );		// pop the matrix		glPopMatrix ( );		// drawing normal objects		// set up render modes		glColorMask ( 1, 1, 1, 1 );		glColor4f ( 1.0f, 1.0f, 1.0f, 1.0f );		glDisable ( GL_BLEND );		glDisable ( GL_CLIP_PLANE0 );		glEnable ( GL_DEPTH_TEST );		glDisable ( GL_STENCIL_TEST );		if ( shape == SHAPE_BOX )		// only if the object is a box			draw_cube ( size );	}glFlush();// Draw the floor (mirror == true)	// masking mirror objects	{		// set up render modes		glColorMask ( 0, 0, 0, 0 );		glColor4f ( 1.0f, 1.0f, 1.0f, 1.0f );		glStencilFunc ( GL_ALWAYS, 1, 1 );		glStencilOp ( GL_KEEP, GL_KEEP, GL_REPLACE );		glEnable ( GL_STENCIL_TEST );		glDisable ( GL_DEPTH_TEST );		glDisable ( GL_CLIP_PLANE0 );		glDisable ( GL_BLEND );		if ( shape == SHAPE_FLOOR )		// only if the object is a floor			draw_floor ( size );	// drawing blended mirror objects			// set up render modes		glColorMask ( 1, 1, 1, 1 );		glColor4f ( 1.0f, 1.0f, 1.0f, 0.8f );		glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );		glDisable ( GL_CLIP_PLANE0 );		glDisable ( GL_STENCIL_TEST );		glEnable ( GL_DEPTH_TEST );		glEnable ( GL_BLEND );		if ( shape == SHAPE_FLOOR )		// only if the object is a floor			draw_floor ( size );	}glFlush();


So you're drawing the cube with stencil buffer enabled but no values are changed as GL_KEEP, GL_KEEP, GL_KEEP is being used (cube does not create a mask in the stencil buffer), and then disabling the stencil, then drawing the cube to the screen.
Next you draw the floor mask to the stencil buffer (GL_KEEP, GL_KEEP, GL_REPLACE), and then disable it and draw the floor to the screen.

I don't think this would draw any reflections as the mask is being drawn after you actually need to use it for the reflected cube.

Have you tried moving the stencil testing code out of the object draw routine and into the "parent" calling function? (i.e. the one calling cubeObject->draw(); etc)

the new code would look like this:

// This section masks the floor in the stencil bufferglColorMask ( 0, 0, 0, 0 );		glColor4f ( 1.0f, 1.0f, 1.0f, 1.0f );		glStencilFunc ( GL_ALWAYS, 1, 1 );		glStencilOp ( GL_KEEP, GL_KEEP, GL_REPLACE );		glEnable ( GL_STENCIL_TEST );		glDisable ( GL_DEPTH_TEST );		glDisable ( GL_CLIP_PLANE0 );		glDisable ( GL_BLEND );floorObject->draw(); // draw floor to stencil buffer// This section draws the reflected cube to the screen area masked by the floorglColorMask ( 1, 1, 1, 1 );		glStencilFunc ( GL_EQUAL, 1, 1 );		glStencilOp ( GL_KEEP, GL_KEEP, GL_KEEP );		glClipPlane ( GL_CLIP_PLANE0, clipping_plane );		glEnable ( GL_DEPTH_TEST );		glEnable ( GL_CLIP_PLANE0 );glPushMatrix ( );glScalef ( 1.0f, -1.0f, 1.0f );cubeObject->draw(); // draw reflected cubeglPopMatrix();// This section draws the blended floor to the screen		glDisable ( GL_CLIP_PLANE0 );		glDisable ( GL_STENCIL_TEST );		glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );		glEnable ( GL_BLEND );floorObject->draw(); // Draw a visible floor to the screen// Draw the cube (not reflected)cubeObject->draw();


Also try disabling the clipping plane and see if that's clipping the reflections by accident.

Hope that helps.

bear with me on this, there could be errors as i find it difficult to figure out other people's code straight away. No fault on your part, just my useless brain.
"I must not fear. Fear is the mindkiller. Fear is the little death that brings total obliteration. I will face my fear. I will permit it to pass over me and through me. And when it has gone past me I will turn to see fear's path. Where the fear has gone there will be nothing. Only I will remain." ~Frank Herbert, DuneMy slice of the web
Thank you for the reply!

I have a draw_scene ( ) function, that looks like this:

void draw_scene ( void )// draws the scene{	// clear screen buffer, depth buffer and stencil buffer	glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );	// reset the matrix	glLoadIdentity ( );	// draw the gui	draw_gui ( );	// draw all objects	for ( int i = 0; i < 2; i++ )	{		// reset the matrix		glLoadIdentity ( );		// position the camera		cam.update ( );		// draw object		obj.draw ( );	}}


I could do all the stencil testing right there as you suggested, but my aim was to include those reflections in a simple, wrapped-up way, so you really only need to set obj[1].mirror = true for example and not add several lines before and after calling the obj[1].draw ( ) function. I'd love to have my draw_scene ( ) function to handle that stuff.

Thanks for helping out any way!
Yeah ok, the thing is because they're not actually real reflections, just fake ones (even with the stencil buffer being used), you still need to draw:

the floor (only to the stencil buffer) followed by the cube (reflection), then the floor (to the screen), then the cube (the real one).

So the loop could work in theory, but you can't get round the fact that each one has to be drawn twice per frame.

Instead, add a couple more render routines to the object class:

object1->drawToStencil(); //floor
object2->drawReflected(); //cube
object1->drawToScreen();
object2->drawToScreen();

and move the relevant parts of your initial draw() routine into these ones

[Edited by - DrewGreen on September 7, 2005 7:51:54 PM]
"I must not fear. Fear is the mindkiller. Fear is the little death that brings total obliteration. I will face my fear. I will permit it to pass over me and through me. And when it has gone past me I will turn to see fear's path. Where the fear has gone there will be nothing. Only I will remain." ~Frank Herbert, DuneMy slice of the web
For more complicated scenes, keep a list of which objects will need to be reflected
i.e. floor reflects cube, floor reflects anothercube

floor->drawToStencil();
floor->drawRelectedObjects();
floor->drawToScreen();
cube->drawToScreen();
anothercube->drawToScreen();


and floor->drawReflectedObjects()
would call
cube->drawReflection();
anothercube->drawReflection();

.. somehow. My OO programming is a bit shaky so I don't know if this is possible with the object class accessing another object?
However with a bit of thought, this could help if you wanted to draw a mirror that reflects another mirror,(...) without having to directly set up the entire scene yourself.
"I must not fear. Fear is the mindkiller. Fear is the little death that brings total obliteration. I will face my fear. I will permit it to pass over me and through me. And when it has gone past me I will turn to see fear's path. Where the fear has gone there will be nothing. Only I will remain." ~Frank Herbert, DuneMy slice of the web

This topic is closed to new replies.

Advertisement