Half Silhouette?

Started by
8 comments, last by BlinksTale 11 years, 6 months ago
Since 2002, 3d Mario games have used this fantastic silhouette feature:

RMCxM.png

If Mario is more than halfway obscured by something else, a silhouette or shadow is drawn to represent the rest of him. This lets you track the player when they run around somewhere the camera does not go, or has not caught up to yet.

I have a rough idea for how to (in OpenGL) mimic a full silhouette (worst case: I can draw everything, check Line of Sight, and if character is not visible, draw a second instance of the character in full black at half opacity as the last thing to be drawn, or something) but I don't have any idea where to start for a silhouette that kicks in at the half way mark, and doesn't overlay the character itself. Google searches are proving that there is either nothing on the subject or that I don't know the right terminology. Any thoughts?
Advertisement
You can do it with a stencil check like this:

1. Draw the whole scene (without your char)
2. Draw your character without writing him to the buffer, set a stencil bit when the z-test failed (=is hidden by something).
3. Draw your character normaly.
4. Draw the bounding box of your char or fullscreen quad, turn stencil test on and darken the area where the stencil test succeed (i.e. blend a dark color with the background).


In OpenGl you should look into stencil opertions: chapter 10, section Stencil Test
Another, simpler method (but may not be that neat) is to simply render the silhouette first with disabled depth test, then render the character normally, with enabled depth test. This will draw the non-obscured pixels on top of the silhouette, thus overwrites where the character is visible.

Or another one (without overdraw): draw the silhouette after the scene and the normal character but with reversed depth test (GL_GREATER if I recall correctly). This means only the obscured portion of the character will be rendered (since the normal character has the same depth, the visible portion of it won't be overwritten). If you apply the same transformation to the silhouette and the normal character (which you'd do by default), then there won't be any z-fighting between the silh and the norm character.

I'd prefer the second method, since all the scene with the character will be on screen (drawn in whatever order that suits you best), and the silhouette is drawn after everything like an effect, so you can do whatever post-processing you want and the renderer will be less messy (all scene drawing will be at one place).

Another, simpler method (but may not be that neat) is to simply render the silhouette first with disabled depth test, then render the character normally, with enabled depth test. This will draw the non-obscured pixels on top of the silhouette, thus overwrites where the character is visible.

Or another one (without overdraw): draw the silhouette after the scene and the normal character but with reversed depth test (GL_GREATER if I recall correctly). This means only the obscured portion of the character will be rendered. If you apply the same transformation to the silhouette and the normal character (which you'd do by default), then there won't be any z-fighting between the silh and the norm character.

Due to overdraw of the character itself you will have trouble with blending in both solutions.
Hmmm, that's true.

You can do it with a stencil check like this:

1. Draw the whole scene (without your char)
2. Draw your character without writing him to the buffer, set a stencil bit when the z-test failed (=is hidden by something).
3. Draw your character normaly.
4. Draw the bounding box of your char or fullscreen quad, turn stencil test on and darken the area where the stencil test succeed (i.e. blend a dark color with the background).


In OpenGl you should look into stencil opertions: chapter 10, section Stencil Test

The problem with that is that the character has overlapping polygons and will z-fail parts of himself.

0) From a clear colour/stencil/z
1) Draw player to stencil buffer, setting bit to non-zero
2) Draw everything the scene (not the player) as per normal, ignoring stencil
3) Draw player again, but set stencil operation to set zero if z-test passes, but leave it alone if it fails
4) Draw fullscreen quad where the stencil buffer is not zero.
"2. Draw your character without writing him to the buffer" that includes without writing to the depth buffer

I can see problems with this though in some corner cases:

[attachment=9010:mario.JPG]

But it should be easy to solve, just swap step 3 and step 4 in Ashaman's solution.

(sorry to chip in)
These are all great sounding solutions, thank you. I think I'll be going the simpler route myself, as my main character is cube shaped and I don't think I'm using any blending right now. Thank you guys though!
I think you don't even need a stencil buffer...

1. just draw your scene normally and leave the character for the end... then
2. disable depth writes and reverse the depth test, draw the character and only output a solid color (this draws the occluded silhoutte)
3. restore the depth states and draw the character normally

maybe I am missing something here because this sounds too simple!

Another, simpler method (but may not be that neat) is to simply render the silhouette first with disabled depth test, then render the character normally, with enabled depth test. This will draw the non-obscured pixels on top of the silhouette, thus overwrites where the character is visible.


SOLUTION: This one worked! Finally got around to trying this out today, and stopping depth test to draw a silhouette copy was the easiest solution, then turning it back on again. Literally, my code:


...
glDisable(GL_DEPTH_TEST); // disable depth test for "shadow cube"
drawSilhouette();
glEnable(GL_DEPTH_TEST); // Then return to normal stuff

// call on cubeShape's function, drawCube, to make a cube visual
draw();
...


And over in cubeShape, my balancing (and admittedly a few magic numbers) to get the silhouette looking right...


// Grabs your rgb1 colors and makes a dark version of yourself
// Perfect for walking behind walls, but player identification
void CubeShape::drawSilhouette() {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable( GL_BLEND );
glPushMatrix();
// These code blocks modified from work on songho.ca
// activate and specify pointer to vertex array
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
// draw first half, range is 6 - 0 + 1 = 7 vertices used
for (int i=0; i<6; i++) {
if (!useNeighbors || !neighbors) {
glColor4f(r1*0.25+0.125,g1*0.25+0.125,b1*0.25+0.125,0.5);
// 0 to 3 means we only have four vertices used for each face
// 6 is the total points we create though from those four.
// indices+6*i is where we are looking, which face in indices
glDrawRangeElements(GL_TRIANGLES, 0, 3, 6, GL_UNSIGNED_BYTE, indices+6*i);
}
}
// deactivate vertex arrays after drawing
glDisableClientState(GL_VERTEX_ARRAY);
glPopMatrix();
glDisable( GL_BLEND );
}


Turning on GL_BLEND ended up being important to have it work with what was already there, rather than just being a starkly different color.

Anyways, it works now, and I'm quite happy with how it looks!

J4tYs.png

Thanks everyone for helping me get this far!

This topic is closed to new replies.

Advertisement