15
\$\begingroup\$

What type of shading algorithm might be used to create shadows like these? enter image description here

the one I'm making is similar but it is all done with a 2D drawing API powered by OpenGL so there is no Z coordinate.

In addition, for the hand itself, I'd really like to get a shaded feel like seen here: enter image description here

I'm just not sure how to achieve a shaded look close to that.

The number of cards is bound to change and the cards get thrown onto the table so I can't use any type of light map of sorts.

What sorts of algorithms should I look into (aside from blur which I know 'll need to do?)

Thanks

Update

I'm making a 2D card game. I want to add dropshadows offset from the cards, a bit like:

enter image description here

The way I'm thinking of doing it is:

  • Keep a texture that is the same size as the backbuffer.
  • Draw dark rectangles as makeshift cards to that texture.

  • Blur that texture.

  • Draw my cards to that texture.
  • Do additional lighting on the cards.
  • Draw this texture to the backbuffer.

My questions are:

  • Is this the right way to do this?

  • Is there a way to do it without render to texture (keeping a bitmap
    as big as the backbuffer)?

  • Is it safe to assume that the maximum texture size will not be
    exceeded by the backbuffer size? (What I mean is, if the backbuffer
    is 2000x3000, then is it safe to say that I can create a texture in
    video memory of that size?

Thanks

\$\endgroup\$
1
  • 1
    \$\begingroup\$ An aesthetic point on your second issue: that isn't really possible if you're actually holding those cards in your hand. Cards pressed next to each other don't appreciably shadow one another. Not unless you're holding a lot of them. You cannot really hold cards the way they're depicted in that image; those cards look like they're separated by several millimeters; they're not flat against one another. I just wouldn't worry about it. \$\endgroup\$ Dec 2, 2011 at 7:33

5 Answers 5

19
+250
\$\begingroup\$

I think everybody's giving way too complicated solutions to this problem..

enter image description here

So first, we have the card (or whatever you want to draw), depicted here by (a). Next, we take a copy of it, fill it in black and run a gaussian blur on it (b). All of this happens in photoshop or whatever your favorite art tool is.

Next, in-game, when we want to draw the card, first draw the blurred black image with multiplicative (or alpha) blend, offset in some direction a bit, and then draw the card on top of that. Voilá.

For further trickery, you can alternate between shadow and card render to get effect like (d), or first draw all shadows and then the cards, like in (e) (I colored the cards in the (e) case to show that they're still separate =)

It's also best not to use pure black (or full alpha) for the shadow to create more subtle, transparent shadows.

\$\endgroup\$
2
  • 2
    \$\begingroup\$ +1 for doing the shadows out of game. You could also take it a step further and modify where the shadow is placed a if there is a light in the scene. \$\endgroup\$
    – ZeunO8
    Dec 12, 2011 at 10:21
  • \$\begingroup\$ +1, nice illustration. Furthermore, in case E you could use render dark shadows onto table and semi-bright on cards with stencil. That will be more complicated, but the effect will be even nicer :) \$\endgroup\$
    – Kromster
    Dec 12, 2011 at 11:19
2
\$\begingroup\$

The first one isn't too hard. If you render an alpha channel for your hand of cards (which could be as simple as black for a pixel with a card in it, and white for transparent), you can perform any kind of blur you want on just the alpha channel, and then offset it and use that to control the lighting of the table. (Presumably, if you don't have a Z-buffer, you'd have to first render out the alpha channel offscreen somewhere, then do the table with the mask, then render the actual cards.)

The second one looks a bit like SSAO (screen-space ambient occlusion). That technique requires a Z coordinate. However, if you don't have the varying angles between cards (which is what I would guess if you have no Z-buffer), you can probably do a pretty good approximation by rendering a drop shadow (like the first one) for each card. The performance could be a little tricky, though—every plane would need a blurred alpha channel for all the planes above it, and if there are a bunch of cards on the table, that could be quite a few rendering passes.

\$\endgroup\$
1
  • \$\begingroup\$ SSAO is overkill =) It's very difficult to tell what shadowing technique is used without seeing the example animated. If the cards always fall in that order and spacing on top of one another it would be easiest to simply pre-render the shadows in, but you'd never know w/out seeing the game in action. \$\endgroup\$ Dec 2, 2011 at 4:04
0
\$\begingroup\$

Could you not do a shadow map? Render the scene to a fbo. Save only the depth values and do the normal depth value check in the shader to see if a shadow should be rendered or not.

\$\endgroup\$
1
  • \$\begingroup\$ I'm not using a Z Buffer, there is no 'depth'. \$\endgroup\$
    – jmasterx
    Dec 8, 2011 at 20:48
0
\$\begingroup\$

The following process doesn't require an off-screen render target. However, in so doing, it gains the requirement that the table be drawn first. Or at least, nothing is drawn on the pixels that the table will cover before the table is drawn. It also requires that the table itself be one, single, non-overlapping piece: an image.

Also, your framebuffer needs an alpha component.

  1. Get a greyscale image of a card. Make it fuzzy around the edges, possibly expanding its size. This is your shadow card image. Note that this is a single-channel image. When you access this texture in your shader, you should make sure that you put the single value in the alpha component. This can be done in your shader or somewhere else.

    A value of 0.0 in the image means that there is no shadow. A value of 1.0 in the image means total shadow. That is, completely pitch black. You probably want a value of 0.5 or so as your darkest color.

  2. Clear the screen such that the alpha is set to zero.

  3. Before rendering anything (or at least anything that will be rendered "under" the table), for each card, render the fuzzy texture, offset from the actual position of the card (but with the same orientation). The color output by the shader should be The blend configuration for this should be (in OpenGL parlance):

    glBlendEquation(GL_MAX);
    

    This blend mode is used to keep overlapping shadows from growing ever darker or lighter. It simply takes the darkest value written to that pixel. It should look good enough.

    You should also use the color write mask to turn off color writes.

  4. Render the table. When doing so The blending should be set up as follows:

    glBlendEquation(GL_ADD);
    glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ZERO);
    

If the restrictions are too restrictive for you, then you have to use a render target. This is done as follows:

  1. Create the shadow card image as before.

  2. First, render the shadows. Bind the shadow framebuffer (which only needs to be a single-channel image, if your hardware can render to one of those). Clear its value to zero.

  3. For each card, render the shadow card image, offset as before. Your shader should write the same value to all four components of the output color. The blend mode again should be glBlendEquation(GL_MAX).

  4. Switch back to the regular framebuffer. Draw everything you want to be shadowed.

  5. Now draw a full-screen quad with shadow image we rendered. The shader should store the texel fetched from the shadow image in the alpha of the output; the RGB is irrelevant. The blend mode should be:

    glBlendEquation(GL_ADD);
    glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
    
\$\endgroup\$
3
  • \$\begingroup\$ In the first method, I think you forgot step 5: disable blending and render the cards. Also, it's worth noting that the first method doesn't allow you to get inter-card shadows as seen in the OP's second screenshot. \$\endgroup\$ Dec 8, 2011 at 22:16
  • \$\begingroup\$ @NathanReed: I figured he knew how to turn things off himself. Also, the "inter-card shadows" doesn't make any sense, as I pointed out in my comment under the question. \$\endgroup\$ Dec 8, 2011 at 22:22
  • 1
    \$\begingroup\$ It may not realistically represent how you'd actually hold cards, but it looks cool! It doesn't really "make sense" for the cards to float above the table as they do in the first shot, either... \$\endgroup\$ Dec 8, 2011 at 23:02
0
\$\begingroup\$

I'd use the stencil buffer. Clear it to 1 (preferably at the same time as you clear depth), draw your table. Then enable stencil test, draw the shadows using a blurry rectangle texture, incrementing if stencil and depth pass, doing nothing if stencil fail, nothing if depth fail, and set your stencil func to GL_EQUAL (ref 1, mask 0xff), disable stencil test. (I haven't fully tested this but it's derived from code that does similar and should work fine; you may need to tweak the params). Then draw everything else.

The calls would be:

glEnable (GL_STENCIL_TEST);
glStencilFunc (GL_EQUAL, 1, 2);
glStencilOp (GL_KEEP, GL_KEEP, GL_INCR);

// draw shadow textures

glDisable (GL_STENCIL_TEST);

The only time this may cause trouble is if you have two blurred edges that overlap slightly; otherwise it needs nothing fancy beyond what basic OpenGL 1.1 offers and will work on all hardware (expect perhaps old 3DFX cards, which I assume you're not really worried about supporting...)

A second alternative, that should work very well in a non-performance-critical game, is glCopyTexSubImage2D. That will work with a texture smaller than the backbuffer too and is also supported well on all hardware (it's part of OpenGL 1.1 and Doom 3 used it, so you can expect support to be very good).

The basic setup would go like this:

  • Clear colour to black.
  • Disable depth writing (although this method doesn't need a depth buffer, so you can ignore that part as you're not using one).
  • Set a viewport the same dimensions as your texture.
  • Draw cards as white filled geometry.
  • glCopyTexSubImage2D to your texture.
  • Switch viewport back to full window size.
  • Draw table as normal.
  • Draw texture as a fullscreen blended quad, using a shader to blur it and invert colour.
  • Draw everything else.

That should also work very well, it's a little more GPU intensive than the stencil buffer method but handles the overlapping case correctly and - like I said - I think a game like your one will have GPU power to burn, even on a low-end card.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .