Sign in to follow this  

Reflections Using Stencil Buffer [Solved]

This topic is 4481 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites
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 buffer
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 );

floorObject->draw(); // draw floor to stencil buffer


// This section draws the reflected cube to the screen area masked by the floor
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 );

glPushMatrix ( );
glScalef ( 1.0f, -1.0f, 1.0f );
cubeObject->draw(); // draw reflected cube
glPopMatrix();


// 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.

Share this post


Link to post
Share on other sites
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[i].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!

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

This topic is 4481 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this